D3 Multi-series transition overlapping data - javascript
I have a graph that correctly plots several lines based on a sensor's serial number. The problem is that I need it to transition to a new dataset and the data will overlap. Here's what I have so far......
var thresholdTemp = 72;
var minutes = 5;
var transInterval;
//Main function to create a graph plot
function plot(date1, date2, interval) {
var data;
//If we define a date search parameter then we don't want to have it load interactive
if (date1 == undefined && date2==undefined) {
data = loadMinutesJSON(minutes);
} else {
data = searchJSON(date1, date2, interval);
}
var margin = {top: 20, right: 80, bottom: 60, left: 50},
width = 960 - margin.left - margin.right ,
height = 500 - margin.top - margin.bottom;
//Re-order the data to be more usable by the rest of the script
var parseDate = d3.time.format("%Y-%m-%d %H:%M:%S").parse;
//Set the X domain to exist within the date and times given
var x = d3.time.scale().domain([getMinDate(data), getMaxDate(data)]).range([0, (width)]);
//Y Axis scale set to start at 45 degrees and go up to 30 degrees over highest temp
var y = d3.scale.linear()
.domain([
45,
getMaxTemp(data) + 10
])
.range([height, 0]);
//Set up the line colors based on serial number, this generates a color based on an ordinal value
var color = d3.scale.category20().domain(d3.keys(data).filter(function(key) { return key;}));
//.domain(d3.keys(data[0]).filter(function(key) { return key === 'serial';}));
//Define where the X axis is
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(d3.time.format("%b %d %H:%M:%S"));
//Define where the Y axis is
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
//When called creates a line with the given datapoints
var line = d3.svg.line()
.interpolate("basis")
.x(function(d) { return x(d.date);})
.y(function(d) { return y(d.reading); });
//An extra line to define a maximum temperature threshold
var threshold = d3.svg.line()
.x(function(d) { return x(d.date);})
.y(function(d) { return y(thresholdTemp); });
//Append the SVG to the HTML element
var svg = d3.select("#plot").append("svg")
.attr("width", (width + margin.left + margin.right))
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//Define the clipping boundaries
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", (width + margin.left + margin.right))
.attr("height", height + margin.top + margin.bottom);
//Add the X axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform" , function (d) {return "rotate(-35)"});
//Add the Y axis and a label denoting it as temperature in F
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Temperature (F)");
//Create the lines based on serial number
var serial = svg.selectAll(".serial")
.data(data);
var serials = serial.enter().append("g")
.attr("class", "serial");
//Add the extra line for a threshold and align it with the current time
var threshElement = svg.selectAll(".thresh")
.data(data)
.enter().append("g")
.attr("class", ".thresh");
//Add the path to draw lines and clipping so they stay within bounds
var path = serial.append("path")
.attr("clip-path", "url(#clip)")
.attr("class", "line")
.attr("d", function (d) {return line(d.values);})
.style("stroke", function(d,i) {return color(i);});
//Custom path to add a line showing the temperature threshold
var threshpath = threshElement.append("path")
.attr("clip-path", "url(#clip)")
.attr("class", "line")
.attr("d", function(d) { return threshold(d.values);})
.style("stroke", "red");
//Add a label to the end of the threshold line denoting it as the threshold
threshElement.append("text")
.attr("transform", "translate(" + x(getMaxDate(data)) + "," + y(thresholdTemp) + ")")
.attr("x", 3)
.attr("dy", ".35em")
.text("Threshold");
serial.exit().remove();
//Add the legend
//plotLegend(data);
//Load in the new data and add it to the SVG
function transition() {
data = loadMinutesJSON(minutes);
x.domain([getMinDate(data), getMaxDate(data)]);
y.domain([45, getMaxTemp(data) + 10]);
d3.select(".x.axis")
.transition()
.duration(500).call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform" , function (d) {return "rotate(-35)"});
d3.select(".y.axis").transition().duration(500).call(yAxis);
serial.data(data).enter().append("g").attr("class", "serial");
d3.selectAll("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values);})
.style("stroke", function(d,i) { return color(i);});
}
if(date1 == undefined && date2 == undefined) {
//Set the transition loop to run every 30 seconds
transInterval = setInterval(transition,30000);
}
}
//Set the new time in minutes and re-draw the graph
function setMinutes(newminutes) {
if (transInterval) {
clearInterval(transInterval);
}
d3.selectAll("svg").remove();
console.log("Setting new minutes " + newminutes);
minutes=newminutes;
plot();
}
//Search the database for data between 2 dates and create a new plot of it
function searchDB(date1, date2, intervalInMinutes) {
if (transInterval) {
clearInterval(transInterval);
}
d3.selectAll("svg").remove();
plot(date1, date2, intervalInMinutes);
console.log("Processing Search ");
}
//Quick function to determine the maximum date in a dataset
function getMaxDate(data) {
var arr = [];
for (x in data) {
arr.push(d3.max(data[x].values, function(d) { return d.date;}));
}
return d3.max(arr);
//return d3.max(data[0].values, function(d) { return d.date;});
}
//Calculate the minimum data
function getMinDate(data) {
var arr = [];
for (x in data) {
arr.push(d3.min(data[x].values, function(d) { return d.date;}));
}
return d3.min(arr);
//return d3.min(data[0].values, function(d) { return d.date;});
}
//Calculate the upper maximum temperature
function getMaxTemp(data) {
var arr = [];
for (x in data) {
arr.push(d3.max(data[x].values, function(d) { return d.reading;}));
}
return d3.max(arr);
//return d3.max(data, function(d) { return d3.max(data.values, function(d) {return d.reading})});
}
Here's examples of the data:
First
[{"serial":"2D0008017075F210","values":[{"date":"2013-08-23T20:43:46.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:44:16.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:44:46.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:45:16.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:45:47.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:46:17.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:46:47.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:47:17.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:47:47.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:48:17.000Z","reading":76.1,"elevation":null,"room":null,"system":null}]},{"serial":"1D00080170496D10","values":[{"date":"2013-08-23T20:43:46.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:44:16.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:44:46.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:45:16.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:45:47.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:46:17.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:46:47.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:47:17.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:47:47.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:48:17.000Z","reading":73.4,"elevation":null,"room":null,"system":null}]},{"serial":"380008017037ED10","values":[{"date":"2013-08-23T20:43:46.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:44:16.000Z","reading":75.2,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:44:46.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:45:16.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:45:47.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:46:17.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:46:47.000Z","reading":75.2,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:47:17.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:47:47.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:48:17.000Z","reading":74.3,"elevation":null,"room":null,"system":null}]}]
And then transitioning to:
[{"serial":"2D0008017075F210","values":[{"date":"2013-08-23T20:44:46.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:45:16.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:45:47.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:46:17.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:46:47.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:47:17.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:47:47.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:48:17.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:48:47.000Z","reading":76.1,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:49:17.000Z","reading":76.1,"elevation":null,"room":null,"system":null}]},{"serial":"1D00080170496D10","values":[{"date":"2013-08-23T20:44:46.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:45:16.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:45:47.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:46:17.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:46:47.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:47:17.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:47:47.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:48:17.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:48:47.000Z","reading":73.4,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:49:17.000Z","reading":73.4,"elevation":null,"room":null,"system":null}]},{"serial":"380008017037ED10","values":[{"date":"2013-08-23T20:44:46.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:45:16.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:45:47.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:46:17.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:46:47.000Z","reading":75.2,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:47:17.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:47:47.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:48:17.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:48:47.000Z","reading":74.3,"elevation":null,"room":null,"system":null},{"date":"2013-08-23T20:49:17.000Z","reading":74.3,"elevation":null,"room":null,"system":null}]}]
UPDATE:
I've modified the code quite a bit, but the jsfiddle is here http://jsfiddle.net/8cguF/1/
I'm not sure why the fiddle doesn't work, I tested it on my webserver and it works fine. But what I'm trying to do is construct a graph with the data1 variable in there and then transition it to the data2 variable.
Related
how to make the x axis scrollable in a d3 line chart graph
Below is the code I used. This code is working fine to me, but problem is when the out put comes for the below code, some part of the graph doesn't show in x axis. Some parts are hidden because of the x axis length is not enough to show. So I decided to make this graph scrollable and also I tried a code to make this scrollable.But It didnt work. please help me to sort out this. I used this reference :- http://computationallyendowed.com/blog/2013/01/21/bounded-panning-in-d3.html graph : HTML CODE: <div class="row"> <div class="col-sm-12"> <div class="lineChart1" style=" overflow: scroll"> <svg width="960" height="500" style=" overflow: scroll"></svg> </div> </div> </div> JAVASCRIPT CODE: function createLineChart() { var number=1; var data = [ { label: "Execution 1 - buddhika#gmail.com", x: ["1","2","2","3","3","4","4","5","5","6","6","7","7","8","8","9","9","10","10","11","11","12","12"], y: ["3","3","3","3","3","3","2","2","3","3","3","3","3","3","3","3","3","3","2","2","3","3","3","3"] }] ; var xy_chart = d3_xy_chart() .width(960) .height(500) .xlabel("TCS") .ylabel("STATUS"); var svg = d3.select(".lineChart" + number).append("svg") .datum(data) .call(xy_chart); function d3_xy_chart() { var width = 640, height = 480, xlabel = "X Axis Label", ylabel = "Y Axis Label"; function chart(selection, svg) { selection.each(function (datasets) { // // Create the plot. // var margin = {top: 20, right: 80, bottom: 30, left: 50}, innerwidth = width - margin.left - margin.right, innerheight = height - margin.top - margin.bottom; var x_scale = d3.scale.linear() .range([0, innerwidth]) .domain([d3.min(datasets, function (d) { return d3.min(d.x); }), d3.max(datasets, function (d) { return d3.max(d.x); })]); var y_scale = d3.scale.linear() .range([innerheight, 0]) .domain([d3.min(datasets, function (d) { return d3.min(d.y); }), d3.max(datasets, function (d) { return d3.max(d.y); })]); var color_scale = d3.scale.category10() .domain(d3.range(datasets.length)); var x_axis = d3.svg.axis() .scale(x_scale) .orient("bottom") .tickFormat(function (d, i) { if (d % 1 == 0) { return parseInt(d) } else { return " " } }); var y_axis = d3.svg.axis() .scale(y_scale) .orient("left") .tickFormat(function (d, i) { if (d == "1") { return "NOT EXECUTED" } else if (d == "2") { return "FAILED" } else if (d == "3") { return "PASSED" } else { return " " } }); var x_grid = d3.svg.axis() .scale(x_scale) .orient("bottom") .tickSize(-innerheight) .tickFormat(""); var y_grid = d3.svg.axis() .scale(y_scale) .orient("left") .tickSize(-innerwidth) .tickFormat(""); var draw_line = d3.svg.line() .interpolate("linear") .x(function (d) { return x_scale(d[0]); }) .y(function (d) { return y_scale(d[1]); }); var svg = d3.select(this) .attr("width", width) .attr("height", height) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); svg.append("g") .attr("class", "x grid") .attr("transform", "translate(0," + innerheight + ")") .call(x_grid); svg.append("g") .attr("class", "y grid") .call(y_grid); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + innerheight + ")") .call(x_axis) .append("text") .attr("dy", "-.71em") .attr("x", innerwidth) .style("text-anchor", "end") .text(xlabel); svg.append("g") .attr("class", "y axis") .call(y_axis) .append("text") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", "0.71em") .style("text-anchor", "end") .text(ylabel); var data_lines = svg.selectAll(".d3_xy_chart_line") .data(datasets.map(function (d) { return d3.zip(d.x, d.y); })) .enter().append("g") .attr("class", "d3_xy_chart_line"); data_lines.append("path") .attr("class", "line") .attr("d", function (d) { return draw_line(d); }) .attr("stroke", function (_, i) { return color_scale(i); }); data_lines.append("text") .datum(function (d, i) { return {name: datasets[i].label, final: d[d.length - 1]}; }) .attr("transform", function (d) { return ( "translate(" + x_scale(d.final[0]) + "," + y_scale(d.final[1]) + ")" ); }) .attr("x", 3) .attr("dy", ".35em") .attr("fill", function (_, i) { return color_scale(i); }) .text(function (d) { return d.name; }); // scrolling code START var xscale = d3.scale.linear().domain([0, 12]).range([0, 12]), yscale = d3.scale.linear().domain([0, 100]).range([innerheight, 0]); var line = d3.svg.line() .x(function(d) { return xscale(d[0]); }) .y(function(d) { return yscale(d[1]); }) .interpolate('basis'); svg.append('g') .datum(datasets) .append('path') .attr('class', 'data') .attr('d', line); var zoom = d3.behavior.zoom() .scaleExtent([1, 1]) .x(xscale) .on('zoom', function() { svg.select('.data').attr('d', line) }); svg.call(zoom); // Scrolling code END }); }
i found the issue in here. issue is in json data what you provided. that all json data stored as strings not as integers. all integer numbers with in the double quotes. that mean it become as a string. when the max number taking for the x axis from the json data, it take the 9 as max number. it happen because 9 is the max number when numbers are strings. because of that graph not going to continue after 9. but expected value is 12. no need the scrolling code here. simply can make the graph scrollable by adding css entry in div ("overflow: scroll")
Keep legend constant and only update chart in D3
my coding is to plot x axis with location, y axis with (value1,value2 or value3) and legend with types(high, medium,low). what I'm trying to do is to add menu with value1,2,3 and add legend with different types so if I change from either menu or click on legend, plot got updated with only selected data. however, my code below is only able to create legend set as default type or clicked but not able to include all types. is there any way to include all types in legends constantly no matter what type is clicked and only update chart accordingly? thank you, <script> var margin = {top: 20, right: 20, bottom: 30, left: 40}, width = 960- margin.left - margin.right, height = 900 - margin.top - margin.bottom, radius = 3.5, padding = 1, xVar = "location", cVar= " type"; default = "high"; // add the tooltip area to the webpage var tooltip = d3.select("body").append("div") .attr("class", "tooltip") .style("opacity", 0); // force data to update when menu is changed var menu = d3.select("#menu select") .on("change", change); // load data d3.csv("sample.csv", function(error, data) { formatted = data; draw(); }); // set terms of transition that will take place // when new indicator from menu or legend is chosen function change() { //remove old plot and data var svg = d3.select("svg"); svg.transition().duration(100).remove(); //redraw new plot with new data d3.transition() .duration(750) .each(draw) } function draw() { // add the graph canvas to the body of the webpage var svg = d3.select("body").append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")") // setup x var xValue = function(d) { return d[xVar];}, // data -> value xScale = d3.scale.ordinal() .rangeRoundBands([0,width],1), //value -> display xMap = function(d) { return (xScale(xValue(d)) + Math.random()*10);}, // data -> display xAxis = d3.svg.axis().scale(xScale).orient("bottom"); // setup y var yVar = menu.property("value"), yValue = function(d) { return d[yVar];}, // data -> value yScale = d3.scale.linear().range([height, 0]), // value -> display yMap = function(d) { return yScale(yValue(d));}, // data -> display yAxis = d3.svg.axis().scale(yScale).orient("left"); // setup fill color var cValue = function(d) { return d[cVar];}, color = d3.scale.category10(); // filter the unwanted data and plot with only chosen dataset. data = formatted.filter(function(d, i) { if (d[cVar] == default) { return d; } }); data = formatted; // change string (from CSV) into number format data.forEach(function(d) { d[xVar] = d[xVar]; d[yVar] = +d[yVar]; }); xScale.domain(data.sort(function(a, b) { return d3.ascending(a[xVar], b[xVar])}) .map(xValue) ); // don't want dots overlapping axis, so add in buffer to data domain yScale.domain([d3.min(data, yValue)-1, d3.max(data, yValue)+1]); // x-axis svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis) .append("text") .attr("class", "label") .attr("x", width) .attr("y", -6) .style("text-anchor", "end") .text(xVar); // y-axis svg.append("g") .attr("class", "y axis") .call(yAxis) .append("text") .attr("class", "label") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", ".71em") .style("text-anchor", "end") .text(yVar); // draw dots var dot = svg.selectAll(".dot") .data(data) .enter() .append("circle") .attr("class", "dot") .attr("r", radius) .attr("cx", xMap) .attr("cy", yMap) .style("fill", function(d) { return color(cValue(d));}) .on("mouseover", function(d) { tooltip.transition() .duration(200) .style("opacity", .9); tooltip.html(d[SN] + "<br/> (" + xValue(d) + ", " + yValue(d) + ")") .style("left", (d3.event.pageX + 5) + "px") .style("top", (d3.event.pageY - 28) + "px"); }) .on("mouseout", function(d) { tooltip.transition() .duration(500) .style("opacity", 0); }); // draw legend var legend = svg.selectAll(".legend") .data(color.domain().slice()) .enter().append("g") .attr("class", "legend") .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }); // draw legend colored rectangles legend.append("rect") .attr("x", width - 18) .attr("width", 18) .attr("height", 18) .style("fill", color) .on("click", function (d){ default = d; return change(); }); // draw legend text legend.append("text") .attr("x", width - 24) .attr("y", 9) .attr("dy", ".35em") .style("text-anchor", "end") .text(function(d) { return d;}) .on("click", function (d) { default = d; return change(); }); }; </script> </body> sample.csv location type value1 value2 value3 A high 1 -2 -5 B medium 2 3 4 C low 4 1 2 C medium 6 3 4 A high 4 5 6 D low -1 3 2
I found a way to include all types in the legend. first, extract unique types from column "type" and save them in the "legend_keys" as array. second, instead of pre-define "default", set the first type in the "legend_keys" as a default. but next default will be set by the event on click out of legend. d3.csv("sample.csv", function(error, data) { formatted = data; var nest = d3.nest() .key(function(d) { return d[cVar]; }) .entries(formatted); console.log(nest); legend_keys = nest.map(function(o){return o.key}); default = legend_keys[0]; //console.log(legend_keys[0]); draw(); }); Finally, when define the legend, read "legend_keys" as data as below. By doing this, I can always keep all types in the legend. var legend = svg.selectAll(".legend") .data(legend_keys) .enter().append("g") .attr("class", "legend") .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }) .on("click", function (d){ default = d; console.log(default); return change(); });
ISO 8601 date/time along X-axis with D3
I'm trying to build a line graph with the date and time of my data as the x-axis. I currently have the start date/time and end date/time hard coded. My hard coded values are the dates and times from the JSON data I'm using for my graph. I would rather have a dynamic x-axis. This way, if my data changes, I don't have to go in and change the start and end dates. Any suggestions would be very much appreciated. Here is my x-axis domain: x.domain([timeFormat.parse('2015-04-01T00:00:00'), timeFormat.parse('2015-04-02T23:50:00')]) I've already tried using this, but can't get it to work: x.domain(d3.extent(data, function (d) { return d.metricDate; })); Here is the body of my code without data: var vis = d3.select("#visualisation"), WIDTH = 2500, HEIGHT = 800, MARGINS = { top: 70, right: 800, bottom: 70, left: 100 } //Defining time format var timeFormat = d3.time.format('%Y-%m-%dT%H:%M:%S'); //Defining range for x. Defining range and domain for y var x = d3.time.scale().range([MARGINS.left, WIDTH - MARGINS.right]) var y = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]) var y1 = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]) //Defining domain for x x.domain([timeFormat.parse('2015-04-01T00:00:00'), timeFormat.parse('2015-04-02T23:50:00')]) //x.domain(d3.extent(data, function (d) { return d.metricDate; })); y.domain([0, d3.max(data, function (d) { return +d.ServerLogon; })]); y1.domain([0, d3.max(data, function (d) { return +d.Processor; })]); //Define x axis var xAxis = d3.svg.axis() .scale(x) .ticks(9) .orient("bottom") .tickFormat(d3.time.format("%m-%d% %H:%M:%S%p")); //<== insert the tickFormat function //Define left y axis var yAxis = d3.svg.axis() .scale(y) .orient("left"); //Define right y axis var yAxisRight = d3.svg.axis() .scale(y1) .orient("right"); //Appending the axes to the svg vis.append("svg:g") .attr("class", "axis") .attr("transform", "translate(0," + (HEIGHT - MARGINS.bottom) + ")") .call(xAxis); vis.append("svg:g") .attr("class", "axis") .attr("transform", "translate(" + (MARGINS.left) + ",0)") .call(yAxis); vis.append("svg:g") .attr("class", "axis") .attr("transform", "translate(" + (WIDTH - MARGINS.right) + ",0)") .call(yAxisRight); vis.append("text") .attr("transform", "translate(" + (WIDTH / 2) + " ," + (HEIGHT + MARGINS.bottom) + ")") .style("text-anchor", "middle") .text("Date/Time"); //Define ServerLogon line var lineGen = d3.svg.line() .x(function (d) { return x(timeFormat.parse(d.metricDate)); }) .y(function (d) { return y(d.ServerLogon); }) .interpolate("basis"); //Define Processor line var lineGen2 = d3.svg.line() .x(function (d) { return x(timeFormat.parse(d.metricDate)); }) .y(function (d) { return y1(d.Processor); }) .interpolate("basis"); //Appending the line to the svg vis.append('svg:path') .attr('d', lineGen(data)) .attr('stroke', 'green') .attr('stroke-width', 2) .attr('fill', 'none'); //Appending second line to the svg vis.append('svg:path') .attr('d', lineGen2(data)) .attr('stroke', 'pink') .attr('stroke-width', 2) .attr('fill', 'none'); I have a lot of JSON data so, here are the first and last groups of data: First: { "metricDate": "2015-04-01T00:00:00", "ServerLogon": "1535.000000", "ServerExport": "704.000000", "Processor": "15.268909", "AdminLogon": "1731.000000" } Last: { "metricDate": "2015-04-02T23:50:00", "ServerLogon": "2386.000000", "ServerExport": "706.500000", "Processor": "10.172466", "AdminLogon": "1919.000000" }
A common workflow is the extent function. Combine it with the already available timeFormat function to find the min and max values: x.domain(d3.extent(data, function(d) { return timeFormat.parse(d.metricDate); }));
Controlling bar position with tickValues in d3.js
I'm trying to build a multi series bar chart using d3 but running into problems due to the sparse nature of the dataset. I want to force the x-axis to have a tick for every day, even if there is no data. The test data I have can have data points that are weeks apart so I'm expecting wide areas with no bars - which is fine. I thought I could force the xAxis to use a set of predefined ticks using the tickValues array, but these leads to very strange display of overlaying the text for each day on top of days that do have some data. I've included a screenshot of what I mean. I get the feeling I'm supposed to do something when calculating the width of the bars but can't figure out what that might be. Code: var data = []; var tickValues = []; var max = _.max(chartData.tabular, function(assessment) { return assessment.dateUTC; }); var min = _.min(chartData.tabular, function(assessment) { return assessment.dateUTC; }); var iter = moment.twix(min.dateUTC, max.dateUTC).iterate("days"); while(iter.hasNext()){ var momentObj = iter.next(); var assessment = _.find(chartData.tabular, {'date': momentObj.format('DD/MM/YYYY')}); tickValues.push(momentObj.valueOf()); if(assessment != null){ if(assessment.type == 'calculated'){ data.push({date: momentObj.valueOf(), calculated: assessment.score, manual: null}); } if(assessment.type == 'manual'){ data.push({date: momentObj.valueOf(), calculated: null, manual: assessment.score}); } } } log(data); var margin = {top: 20, right: 55, bottom: 30, left: 40}, width = $('#cahai-chart').width() - margin.left - margin.right, height = 500 - margin.top - margin.bottom; var x = d3.scale.ordinal() .rangeRoundBands([0, width], .1); var y = d3.scale.linear() .rangeRound([height, 0]); var xAxis = d3.svg.axis() .scale(x) .orient("bottom") .tickValues(tickValues) .tickFormat(function(d){return d3.time.format('%d/%m/%y')(new Date(d))}); var yAxis = d3.svg.axis() .scale(y) .orient("left"); var color = d3.scale.ordinal() .range(["#001c9c","#101b4d","#475003","#9c8305","#d3c47c"]); var svg = d3.select("#cahai-chart svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); var labelVar = 'date'; var varNames = d3.keys(data[0]).filter(function (key) { return key !== labelVar;}); color.domain(varNames); data.forEach(function (d) { var y0 = 0; d.mapping = varNames.map(function (name) { return { name: name, label: d[labelVar], y0: y0, y1: y0 += +d[name] }; }); d.total = d.mapping[d.mapping.length - 1].y1; }); x.domain(data.map(function (d) { return d.date; })); y.domain([0, d3.max(data, function (d) { return d.total; })]); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis); svg.append("g") .attr("class", "y axis") .call(yAxis) .append("text") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", ".71em") .style("text-anchor", "end") .text("Score"); var selection = svg.selectAll(".series") .data(data) .enter().append("g") .attr("class", "series") .attr("transform", function (d) { return "translate(" + x(d.date) + ",0)"; }); selection.selectAll("rect") .data(function (d) { return d.mapping; }) .enter().append("rect") .attr("width", x.rangeBand()) .attr("y", function (d) { return y(d.y1); }) .attr("height", function (d) { return y(d.y0) - y(d.y1); }) .style("fill", function (d) { return color(d.name); }) .style("stroke", "grey") .on("mouseover", function (d) { showPopover.call(this, d); }) .on("mouseout", function (d) { removePopovers(); }) var legend = svg.selectAll(".legend") .data(varNames.slice().reverse()) .enter().append("g") .attr("class", "legend") .attr("transform", function (d, i) { return "translate(55," + i * 20 + ")"; }); legend.append("rect") .attr("x", width - 10) .attr("width", 10) .attr("height", 10) .style("fill", color) .style("stroke", "grey"); legend.append("text") .attr("x", width - 12) .attr("y", 6) .attr("dy", ".35em") .style("text-anchor", "end") .text(function (d) { return d; }); function removePopovers () { $('.popover').each(function() { $(this).remove(); }); } function showPopover (d) { $(this).popover({ title: d.name, placement: 'auto top', container: 'body', trigger: 'manual', html : true, content: function() { return "Date: " + d3.time.format('%d/%m/%y')(new Date(d.label)) + "<br/>Score: " + d3.format(",")(d.value ? d.value: d.y1 - d.y0); } }); $(this).popover('show') }
An ordinal scale will always show as many ticks are there are values in the domain. You just need to pass the full array of dates as the domain. Replace this line x.domain(data.map(function (d) { return d.date; })); with this x.domain(tickValues); It looks like you have everything else set up correctly, so this will space the bars out along the axis and make them slimmer.
d3js - Sortable Group Bar Chart
Full disclosure: I'm not new to programming, but I'm pretty new to d3 and javascript. I am trying to combine the Grouped Bar Chart Example and the Sortable Bar Chart Example. I have a total of 51 groups of 3 variables. Here is a truncated form of my dataset you can use to run the code if you want: State,Response,Predicted,Difference 1,0.0526,0.0983,0.0456 2,0.1161,0.1093,0.0068 5,0.0967,0.1035,0.0067 4,0.0998,0.0942,0.0055 6,0.0888,0.0957,0.0069 I want to be able to order the data by the Response variable by checking a box. Right now I can get the x-axis labels to move accordingly, but I can't get the bars to move with them. To get to this point I renamed the variables in the change() function according to my data. I tried saving the transition.selectAll(".state") function as state2 and then using state2.selectAll(".rect") to modify the x-coordinates of the rectangles, but I realized that wasn't going to get me anywhere. Here is my code right now (mostly copied from the examples linked above). The relevant function is at the end. var margin = {top: 20, right: 20, bottom: 30, left: 40}, width = 1000 - margin.left - margin.right, height = 500 - margin.top - margin.bottom, code = ""; var x0 = d3.scale.ordinal() .rangeRoundBands([0, width], .1); var x1 = d3.scale.ordinal(); var y = d3.scale.linear() .range([height, 0]); var color = d3.scale.category10(); var xAxis = d3.svg.axis() .scale(x0) .orient("bottom"); var yAxis = d3.svg.axis() .scale(y) .orient("left") .tickFormat(d3.format(".0%")); var svg = d3.select("body").append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); d3.csv("data.csv", function(error, data) { var ageNames = d3.keys(data[0]).filter(function(key) { return key !== "State"; }); data.forEach(function(d) { d.ages = ageNames.map(function(name) { return {name: name, value: +d[name]}; }); }); x0.domain(data.map(function(d) { return d.State; })); x1.domain(ageNames).rangeRoundBands([0, x0.rangeBand()]); y.domain([0, d3.max(data, function(d) { return d3.max(d.ages, function(d) { return d.value; }); })]); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis); svg.append("g") .attr("class", "y axis") .call(yAxis) .append("text") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", ".71em") .style("text-anchor", "end") .text("Prevalence"); var state = svg.selectAll(".state") .data(data) .enter().append("g") .attr("class", "g") .attr("transform", function(d) { return "translate(" + x0(d.State) + ",0)"; }); state.selectAll("rect") .data(function(d) { return d.ages; }) .enter().append("rect") .attr("width", x1.rangeBand()) .attr("x", function(d) { return x1(d.name); }) .attr("y", function(d) { return y(d.value); }) .attr("height", function(d) { return height - y(d.value); }) .style("fill", function(d) { return color(d.name); }); d3.select("input").on("change", change); var sortTimeout = setTimeout(function() { d3.select("input").property("checked", true).each(change); }, 2000); var legend = svg.selectAll(".legend") .data(ageNames.slice().reverse()) .enter().append("g") .attr("class", "legend") .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; }); legend.append("rect") .attr("x", width - 18) .attr("width", 18) .attr("height", 18) .style("fill", color); legend.append("text") .attr("x", width - 24) .attr("y", 9) .attr("dy", ".35em") .style("text-anchor", "end") .text(function(d) { return d; }); function change() { clearTimeout(sortTimeout); // Copy-on-write since tweens are evaluated after a delay. var x2 = x0.domain(data.sort(this.checked ? function(a, b) { return b.Response - a.Response; } : function(a, b) { return d3.ascending(a.State, b.State); }) .map(function(d) { return d.State; })) .copy(); var transition = svg.transition().duration(750), delay = function(d, i) { return i * 50; }; var state2 = transition.selectAll(".state") .delay(delay) .attr("x", function(d) { return x2(d.State); }); transition.select(".x.axis") .call(xAxis) .selectAll("g") .delay(delay); } }) Any help would be greatly appreciated. I've found nothing so far searching SO and Google.
I assume that you want to keep the grouping when sorting. Your groups are contained in g elements, so all you need to do is adjust the coordinates of the groups. That is, the code to move the groups would look something like svg.selectAll("g.g") .transition().duration(750) .delay(delay) .attr("transform", function(d) { return "translate(" + x2(d.State) + ",0)"; });
Am tried with the stacked bar chart. To sort the stacked chart, please find the Stacked Bar Chart function change() { // Copy-on-write since tweens are evaluated after a delay. var x0 = x.domain(data.sort(this.checked ? function(a, b) { return b.noncomplete - a.noncomplete; } : function(a, b) { return d3.ascending(a.moduleName, b.moduleName); }) .map(function(d) { return d.moduleName; })) .copy(); var transition = svg.transition().duration(750), delay = function(d, i) { return i * 60; }; transition.selectAll(".moduleName") .delay(delay) .attr("transform",function(d, i) { return "translate(" + (x0(d.moduleName)) + ",0)"; } ); transition.select(".x.axis") .call(xAxis) .selectAll("g") .delay(delay); }