D3 monthly data set - update data - add a new group

I am working on visualizing personal finance for training d3 in what seems like a useful project. I was able to schedule how I want (daily + or minus) for each month. Now I want to be able to change from one month to another. This works if the old month (before the update) has more days (aka data) than in the new month (after the update). If the old one has fewer data points than the new ones, additional data points are added over the chart. I add each data point to my histogram as a group (the bar itself, data label + date label). I moved the whole group down for every new day. I need to find out if I have more or fewer data points after the update, and if I have less, I need to translate the new ones down. If you know what I mean? Here is the code of how I add columns first:

bar = chart.selectAll("g") .data(data) .enter().append("g") .attr("transform", function(d, i) { return "translate(0," + i * barHeight + ")"; }); //bar //grey background bars bar.append("rect") .attr("class", "backgroundBar") .attr("x", 10) .attr("width", (width-30)) .attr("height", barHeight-1) .attr("fill", "#dddddd") .attr("fill-opacity", "0.3"); //dateLabel bar.append("text") .attr("class", "dateLabel") .attr("x", width/2-20) .attr("y", barHeight-5) .attr("fill", "black") .text(function(d){ return d.key}) bar.append("rect") .attr("class", "bar") .attr("x", function(d) { if(scale(d.values.total)<0){return width/2+widthDateLabel;}else{return width/2-scale(d.values.total)-widthDateLabel;}}) .attr("width", function(d) { return Math.abs(scale(d.values.total)); }) .attr("height", barHeight - 1) .attr("fill", function(d) { if(scale(d.values.total)<0){ return "DeepPink"}else{return "MediumSeaGreen"}}); //BarLabel bar.append("text") .attr("class", "barLabel") .attr("x",function(d) { if(scale(d.values.total)<0){return window.width/2-scale(d.values.total)+5+widthDateLabel;}else{return window.width/2-scale(d.values.total)-5-widthDateLabel;}}) .attr("y", barHeight/2) .attr("dy", ".35em") .attr("text-anchor", function(d) { if(scale(d.values.total)<0){ return "start"}else{return "end"}}) .attr("fill", function(d) { if(scale(d.values.total)<0){ return "DeepPink"}else{return "MediumSeaGreen"}}) .text(function(d) { return Math.round(d.values.total*100)/100; }); 

I obviously couldn't work with groups, but translate every y coordinate, but this seems like a dirty solution, no? Here is a screenshot of the problem: enter image description here

EDIT: And here is my current update function. which works, but gives the result in the screenshot

 //update the bar itself var bar=chartgroups.selectAll(".bar") .data(data); bar.enter().append("rect") .attr("class", "bar") .attr("x", function(d) { if(scale(d.values.total)<0){return width/2+widthDateLabel;}else{return width/2-scale(d.values.total)-widthDateLabel;}}) .attr("width", function(d) { return Math.abs(scale(d.values.total)); }) .attr("height", barHeight - 1) .attr("fill", function(d) { if(scale(d.values.total)<0){ return "DeepPink"}else{return "MediumSeaGreen"}}); bar.exit().remove(); bar .transition().duration(750) .attr("height", barHeight - 1) .attr("x", function(d) { if(scale(d.values.total)<0){return width/2+widthDateLabel;}else{return width/2-scale(d.values.total)-widthDateLabel;}}) .attr("width", function(d) { return Math.abs(scale(d.values.total)); }) .attr("fill", function(d) { if(scale(d.values.total)<0){ return "DeepPink"}else{return "MediumSeaGreen"}}); //update the barLabel var barLabel=chart.selectAll(".barLabel").data(data); barLabel.enter().append("text") .attr("x",function(d) { if(scale(d.values.total)<0){return window.width/2-scale(d.values.total)+5+widthDateLabel;}else{return window.width/2-scale(d.values.total)-5-widthDateLabel;}}) .attr("y", barHeight/2) .attr("dy", ".35em") .attr("text-anchor", function(d) { if(scale(d.values.total)<0){ return "start"}else{return "end"}}) .attr("fill", function(d) { if(scale(d.values.total)<0){ return "DeepPink"}else{return "MediumSeaGreen"}}) .text(function(d) { return Math.round(d.values.total*100)/100; }); barLabel.exit().remove(); barLabel .transition().duration(750) .attr("x",function(d) { if(scale(d.values.total)<0){return window.width/2-scale(d.values.total)+5+widthDateLabel;}else{return window.width/2-scale(d.values.total)-5-widthDateLabel;}}) .attr("y", barHeight/2) .attr("text-anchor", function(d) { if(scale(d.values.total)<0){ return "start"}else{return "end"}}) .attr("fill", function(d) { if(scale(d.values.total)<0){ return "DeepPink"}else{return "MediumSeaGreen"}}) .text(function(d) { return Math.round(d.values.total*100)/100; }); //update dates var dateLabel=chart.selectAll(".dateLabel").data(data); dateLabel.enter().append("text") .attr("class", "dateLabel") .attr("x", width/2-20) .attr("y", barHeight-5) .attr("fill", "black") .text(function(d){ return d.key}) dateLabel.exit().remove(); dateLabel .transition().duration(750) .text(function(d){ return d.key}) .attr("y", barHeight-5) //update background bars var backgroundBar=chart.selectAll(".backgroundBar").data(data); backgroundBar.enter().append("rect") .attr("class", "backgroundBar") .attr("x", 10) .attr("width", (width-30)) .attr("height", barHeight-1) .attr("fill", "#dddddd") .attr("fill-opacity", "0.3"); backgroundBar.exit().remove(); backgroundBar .transition().duration(750) .attr("height", barHeight-1) 
+5
source share
2 answers

Here is a snippet of working code that jumps between two months when you click a button. This is very close to your code. These are just minor differences in how the update happens.

 var january = [ { "day": "1/1/2015", "value": 105}, { "day": "1/2/2015", "value": -119}, { "day": "1/3/2015", "value": 148}, { "day": "1/4/2015", "value": -161}, { "day": "1/5/2015", "value": 142}, { "day": "1/6/2015", "value": -105}, { "day": "1/7/2015", "value": 131}, { "day": "1/8/2015", "value": 42}, { "day": "1/9/2015", "value": -74}, { "day": "1/10/2015", "value": 175}, { "day": "1/11/2015", "value": 154}, { "day": "1/12/2015", "value": 164}, { "day": "1/13/2015", "value": 31}, { "day": "1/14/2015", "value": 81}, { "day": "1/15/2015", "value": 5}, { "day": "1/16/2015", "value": -194}, { "day": "1/17/2015", "value": -90}, { "day": "1/18/2015", "value": 8}, { "day": "1/19/2015", "value": 161}, { "day": "1/20/2015", "value": -99}, { "day": "1/21/2015", "value": -42}, { "day": "1/22/2015", "value": -145}, { "day": "1/23/2015", "value": 168}, { "day": "1/24/2015", "value": -44}, { "day": "1/25/2015", "value": -2}, { "day": "1/26/2015", "value": 177}, { "day": "1/27/2015", "value": -21}, { "day": "1/28/2015", "value": -29}, { "day": "1/29/2015", "value": 192}, { "day": "1/30/2015", "value": 199}, { "day": "1/31/2015", "value": 79} ]; var february = [ { "day": "2/1/2015", "value": "36"}, { "day": "2/2/2015", "value": "151"}, { "day": "2/3/2015", "value": "-157"}, { "day": "2/4/2015", "value": "39"}, { "day": "2/5/2015", "value": "-69"}, { "day": "2/6/2015", "value": "97"}, { "day": "2/7/2015", "value": "-55"}, { "day": "2/8/2015", "value": "156"}, { "day": "2/9/2015", "value": "151"}, { "day": "2/10/2015", "value": "-72"}, { "day": "2/11/2015", "value": "-17"}, { "day": "2/12/2015", "value": "154"}, { "day": "2/13/2015", "value": "77"}, { "day": "2/14/2015", "value": "80"}, { "day": "2/15/2015", "value": "-112"}, { "day": "2/16/2015", "value": "-155"}, { "day": "2/17/2015", "value": "21"}, { "day": "2/18/2015", "value": "-63"}, { "day": "2/19/2015", "value": "-136"}, { "day": "2/20/2015", "value": "127"}, { "day": "2/21/2015", "value": "-43"}, { "day": "2/22/2015", "value": "-66"}, { "day": "2/23/2015", "value": "105"}, { "day": "2/24/2015", "value": "2"}, { "day": "2/25/2015", "value": "-92"}, { "day": "2/26/2015", "value": "-160"}, { "day": "2/27/2015", "value": "13"}, { "day": "2/28/2015", "value": "163"} ]; function updateData(data) { var maxValue = d3.max(data, function(d) { return Math.abs(d.value); }); scaleX.domain([0, maxValue]); //update background bars var backgroundBar = chartgroups.selectAll(".backgroundBar").data(data); backgroundBar.enter().append("rect"); backgroundBar.attr("class", "backgroundBar") .attr("x", 0 - margin) .attr("y", function (d, i) { return (i * barHeight); }) .attr("width", chartWidth*2 + margin) .attr("height", barHeight - 1) .attr("fill", "#dddddd") .attr("fill-opacity", "0.3"); backgroundBar.exit().remove(); var bars = chartgroups.selectAll(".bar") .data(data); bars.enter().append("rect") .attr("fill", function (d) { if (d.value < 0) { return "DeepPink" } else { return "MediumSeaGreen" } }); bars.attr("class", "bar") .transition() .duration(1000) .attr("x", function (d) { if (d.value < 0) { return negativeStart; } else { return scaleWidth - scaleX(d.value); } }).attr("y", function (d, i) { return i * barHeight; }) .attr("width", function (d) { return scaleX(Math.abs(d.value)); }) .attr("height", barHeight - 1) .attr("fill", function (d) { if (d.value < 0) { return "DeepPink" } else { return "MediumSeaGreen" } }); bars.exit().remove(); //update the barLabel var barLabel = chartgroups.selectAll(".barLabel").data(data); barLabel.enter().append("text"); barLabel.attr("class", "barLabel") .transition() .duration(1000) .attr("x", function (d) { if (d.value < 0) { return negativeStart + scaleX(Math.abs(d.value)); } else { return scaleWidth - scaleX(d.value); } }) .attr("y", function (d, i) { return (i * barHeight) + (barHeight/2); }) .attr("dy", ".35em") .attr("text-anchor", function (d) { if (d.value < 0) { return "start" } else { return "end" } }) .attr("fill", function (d) { if (d.value < 0) { return "DeepPink" } else { return "MediumSeaGreen" } }) .text(function (d) { return Math.round(d.value * 100) / 100; }); barLabel.exit().remove(); // //update dates var dateLabel = chartgroups.selectAll(".dateLabel").data(data); dateLabel.enter().append("text"); dateLabel.attr("class", "dateLabel") .attr("fill", "black") .transition() .duration(1000) .attr("x", scaleWidth) .attr("y", function (d, i) { return (i * barHeight) + (barHeight/2) + 1; }) .text(function (d) { return d.day; }); dateLabel.exit().remove(); } var container = d3.select(".chart"); var margin = 60; var containerWidth = container.node().getBoundingClientRect().width; var chartWidth = containerWidth - (2*margin); var barHeight = 20; var dateLabelWidth = 80; var chartHeight = 31 * barHeight; var scaleWidth = (chartWidth - dateLabelWidth) / 2; var negativeStart = chartWidth - scaleWidth; var scaleX = d3.scale.linear() .range([0, scaleWidth]); var chartgroups = container.append("svg") .attr("width", containerWidth) .attr("height", chartHeight) .append("g") .attr("transform", "translate(" + margin + "," + 0 + ")"); updateData(january); d3.select(".january").on("click", function() { updateData(january); }); d3.select(".february").on("click", function() { updateData(february); }); 
 .chart { width: 100%; } 
 <!DOCTYPE html> <html> <meta charset="utf-8"> <head> </head> <body> <button type="button" class="january">January</button> <button type="button" class="february">February</button> <div class="chart"> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script> </body> </html> 

Usually negative numbers will be on the left. You can probably also use the y scale, and there might be a cleaner way to create the x scale.

+3
source

You will need to do a few things:

  • Update your dataset.
  • Refresh your D3 selection with new things.
  • Remove all deleted items from selection D3
  • Animating any updated stuff with a transition

Quick example:

 data.unshift(newData); // #1; add new data at the beginning // (You could also remove stuff; update data in the middle; do whatever you need.) var selection = chart.selectAll("g") .data(data, function(d) { return d.id; })); selection.enter() // #2; Add the new stuff just like you did before .append("g") .attr("transform", function(d, i) { return "translate(0," + i * barHeight + ")"; }); // . . . all the other setup goes here, too selection.exit() // #3; Hide any removed elements (should slide them down and turn them invisible) .transition().duration(400) .attr("transform", function(d, i) { return "translate(0," + (i + 1) * barHeight + ")"; }); .style("opacity", 0) .remove(); selection // #4; Move everything to the right location .transition().duration(400) .attr("transform", function(d, i) { return "translate(0," + i * barHeight + ")"; }); 

There are some really good examples of how to do such updates in this series: General update pattern .

One explicit comment discusses key features. You will need to enable a key function similar to the one I added above - read the articles about the general update and it should explain what you need to know.

+2
source

Source: https://habr.com/ru/post/1246777/


All Articles