D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
phoebebright
Full window
Github gist
d3 rescale axis - values to percentages and visa versa
<html> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> <title>Burndown with Context</title> <script src="https://d3js.org/d3.v2.js"></script> <script src="colorbrewer.js"></script> <link href='https://fonts.googleapis.com/css?family=Tienne' rel='stylesheet' type='text/css'> <style type="text/css"> svg { font: 10px sans-serif; /*background: rgba(213, 213, 246, 0.15);*/ } .axis path, .axis line { fill: none; stroke: black; shape-rendering: crispEdges; } .lines { fill: none; stroke-width: 1.5px; shape-rendering: crispEdges; } .axis { font-family: sans-serif; font-size: 11px; } path.area { fill: #FFC; } #chart { float: left; } #content {float:left; } .clear { clear: both; } </style> </head> <body> <h1>Phoebe's Burndown Chart</h1> <button id="rescale_pct" onclick="as_pct();">As Percent</button><button id="rescale_hours" onclick="as_hrs();">As Hours</button> <div id="chart"></div> <script type="text/javascript"> var csvfile = "data.csv"; var width = 640, height = width, margin = 40, // space from edge of page to svg padding = 100; // space around chart to edge of svg // incomding date format var in_format = d3.time.format("%Y-%m-%d"); // display date format var out_format = d3.time.format("%a%d%b"); // Define Axes as far as possible // define the y scale (vertical) var yScale = d3.scale.linear() .range([height - padding, padding]); // map these top and bottom of the chart var xScale = d3.scale.linear() .range([padding, width - padding]); // map these sides of the chart, in this case 100 and 600 // define the y axis var yAxis = d3.svg.axis() .orient("left") .scale(yScale); // define the x axis var xAxis = d3.svg.axis() .orient("bottom") .scale(xScale); var line = d3.svg.line() .x(function(d,i) { return xScale(i); }) .y(function(d) { return yScale(d.value); }) .interpolate("basis"); var x_domain = [], y_domain = []; var color = d3.scale.ordinal() .range(["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"]); // create the svg var div = d3.select("#chart"); div.select("svg").remove(); var vis = div.append("svg") .attr("width", width) .attr("height", height) .attr("transform", "translate(" + margin + "," + margin + ")"); d3.csv(csvfile, function(csv){ // groups data by sprint/release and get date range var data_set = organise_csv(csv); var data = data_set[1], date_extent = data_set[0]; // data is two levels deep so have to nest max to get at the values var y_domain = [0, d3.max(data, function(d) { return d3.max(d.values, function(d) { return d.value; }); }) ]; // assume the number of entries for each sprint is the number of days in the sprint var x_domain = [0, d3.max(data, function(d) { return d.values.length; })]; // opacity scale for lines, the older they are the more faint var opScale = d3.time.scale() .domain(date_extent) .range([0.1,1]); // from very faint to full opacity yScale.domain(y_domain); xScale.domain(x_domain); // draw y axis with labels and move in from the size by the amount of padding vis.append("g") .data([y_domain, [100]]) .attr("class", "axis yaxis") .attr("transform", "translate("+padding+",0)") .call(yAxis); // draw x axis with labels and move to the bottom of the chart area vis.append("g") .data([x_domain, [100]]) .attr("class", "axis xaxis") .attr("transform", "translate(0," + (height - padding) + ")") .call(xAxis); // now add titles to the axes vis.append("text") .attr("class", "yaxis_label") .attr("text-anchor", "middle") // this makes it easy to centre the text as the transform is applied to the anchor .attr("transform", "translate("+ (padding/2) +","+(height/2)+")rotate(-90)") // text is drawn off the screen top left, move down and out and rotate .text("Total number of hours outstanding"); vis.append("text") .attr("class", "xaxis_label") .attr("text-anchor", "middle") // this makes it easy to centre the text as the transform is applied to the anchor .attr("transform", "translate("+ (width/2) +","+(height-(padding/3))+")") // centre below axis .text("Days into Sprint"); // DRAW LINES // each element is a separate sprint and has a separate line // they can have a different number of total hours and take place of a variable number of days // so each needs it's own scale var domains = new Array(); data.forEach(function(row) { // get date range to determine opacity and scale domains[row.key] = d3.extent(row.values, function(d) { return d.date; }); // get amount outstanding at end of sprint to determin score var last = row.values[row.values.length - 1]; var score = ( last.outstanding * 1.0) / (last.time * 1.0); // percent complete /* vis.append("svg:path") .attr("d", line(row.values)) .attr("class", "lines "+row.key) ; */ vis.selectAll("path."+row.key) .data([row.values]) .enter() .append("svg:path") .attr("d", line) .attr("class", "lines "+row.key) .attr("stroke", function(d) { return color(row.key); }) .attr("opacity", function(d) { return opScale(d[0]['date']); // use first date of the sprint to determine opacity }); }); // data.forEach }); // load csv function as_pct() { // change scles to be percentage yScale.domain([0,100]) // redraw as percentage outstanding vis.select(".yaxis") .transition().duration(1500).ease("sin-in-out") // https://github.com/mbostock/d3/wiki/Transitions#wiki-d3_ease .call(yAxis); // redraw lables vis.select(".yaxis_label") .text("% of hours outstanding"); xScale.domain([0,100]) // redraw as percentage outstanding vis.select(".xaxis") .transition().duration(1500).ease("sin-in-out") // https://github.com/mbostock/d3/wiki/Transitions#wiki-d3_ease .call(xAxis); // redraw lables vis.select(".xaxis_label") .text("Days into Sprint"); // now redraw the line to use pct line.y(function(d) { return yScale(d.y_pct); }); line.x(function(d, i) { return xScale(d.x_pct); }); vis.selectAll(".lines") .transition() .duration(500) .ease("linear") .attr("d", line); var burn = [{"x_pct": 0, "y_pct":0}, {"x_pct": 0, "y_pct":100}, {"x_pct": 100, "y_pct":0}, {"x_pct": 0, "y_pct":0}]; vis.selectAll(".burndown") .data([burn]) .enter() .append("svg:path") .attr("class", "area") .attr("d", d3.svg.area() .y(function(d) { return yScale(d.y_pct); }) .x(function(d) { return xScale(d.x_pct); }) .y0(function(d) { return height; })) .attr("opacity", 0.05); } function as_hrs() { // change scles to show actula hours yScale.domain(y_domain); vis.select(".yaxis") .transition().duration(1500).ease("sin-in-out") // https://github.com/mbostock/d3/wiki/Transitions#wiki-d3_ease .call(yAxis); // redraw lables vis.select(".yaxis_label") .text("Total number of hours outstanding"); xScale.domain(x_domain); vis.select(".xaxis") .transition().duration(1500).ease("sin-in-out") // https://github.com/mbostock/d3/wiki/Transitions#wiki-d3_ease .call(xAxis); // redraw lables vis.select(".xaxis_label") .text("% of time to go"); // now redraw the line to use hours/days line.x(function(d,i) { return xScale(i); }) line.y(function(d) { return yScale(d.value); }) vis.selectAll(".lines") .transition() .duration(500) .ease("linear") .attr("d", line); vis.selectAll(".burndown") .remove(); } function organise_csv(csv) { var clean_data = new Array(), date_list = new Array(); // coerse strings to numbers csv.forEach(function(line) { // ignore lines if no time element specified if (line.time > "") { line.time = parseFloat(line.time); if ('variance' in line && line.variance != "") { line.variance = parseFloat(line.variance); } else { line.variance = 0.0; } if ('outstanding' in line && line.outstanding != "") { line.outstanding = parseFloat(line.outstanding); } else { line.outstanding = 0.0; } line.value = line.outstanding + line.variance; line.y_pct = Math.floor((line.value / line.time) * 100); // if line.date is a string, parse to date if (typeof(line.date) == "string") { line.date = in_format.parse(line.date); } date_list.push(line.date) clean_data.push(line); } }); // group data by sprint var data = d3.nest() .key(function(d) { return d.sprint; }) .entries(clean_data); // data starts at the end of the first day when outstanding (should be) less than total time. // create an additional day 0 item for before the sprint starts, a day 0 data.forEach(function(sprint) { // hold num days for each sprint sprint['num_days'] = sprint.values.length; // now calculate x_pct as have num_days to do it with for(var i=0; i<sprint.values.length; i++){ sprint['values'][i]['x_pct'] = Math.floor((i +1)/ sprint['num_days'] * 100); } // make additional entry for day 0 day0 = sprint.values[0]; day0['outstanding'] = day0['time']; day0['variance'] =0; day0['y_pct'] = 100; day0['x_pct'] = 0; sprint.values.unshift(day0); }); // return an array with date range and clean data return [d3.extent(date_list), data]; } </script> </body> </html>
Modified
http://d3js.org/d3.v2.js
to a secure url
https://d3js.org/d3.v2.js