D3.js - Stacked Barchart from JSON? - javascript
I have been trying to recreate this fiddle: https://jsfiddle.net/1c5eqrp3/3/
In the context of a horizontal stacked bar chart that I am building using my data.
var bar1Data = [{
position: 1,
label: '70k',
value: 1708026
},
{
position: 2,
label: '71K - 149K',
value: 1915059
},
];
//sort bars based on value
bar1Data = bar1Data.sort(function(a, b) {
return d3.ascending(a.position, b.position);
})
var colors = ['green', 'blue'];
var bars = d3.select('#bars')
.append('svg')
.attr('width', 800)
.attr('height', 200);
bars.selectAll('rect')
.data(bar1Data)
.enter()
.append('rect')
.attr('width', function(d) {
return d;
})
.attr('x', function(d, i) {
return sum(bar1Data.value, 0, i);
})
.attr('fill', function(d, i) {
return colors[i];
})
.attr('y', 0)
.attr('height', 100);
function sum(array, start, end) {
var total = 0;
for (var i = start; i < end; i++) total += array[i];
return total;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="bar"></div>
All was going well, until I tried to get it to read the bar1Data I created.
Now, I am getting the following errors:
Error: <rect> attribute width: Expected length, "[object Object]".
(anonymous) # d3.js:1211
selection_each # d3.js:1176
selection_attr # d3.js:1233
(anonymous) # bars3.js:23
Promise.then (async)
(anonymous) # bars3.js:1
bars3.js:33 Uncaught (in promise) TypeError: Cannot read property '0' of undefined
at sum (bars3.js:33)
at SVGRectElement.<anonymous> (bars3.js:26)
at SVGRectElement.<anonymous> (d3.js:1209)
at Selection.selection_each [as each] (d3.js:1176)
at Selection.selection_attr [as attr] (d3.js:1233)
at bars3.js:25
I have been greatly struggling to create a stacked horizontal bar chart using d3.v5. I have been trying various examples using d3.v4 but I have not been able to get them to work. This was the simplest, most light-weight example I have found. But it does not yet include scales (which I shall hopefully put in a little later).
Could you please help me to solve this issue with an explanation?
In .attr('width', function(d){ return d;}), you're setting the width of the bar to the entire object d. You need to understand that .data(bar1Data) creates one entry per value of bar1Data, and that that value is accessible as d in all those functions. That means that, as soon as you've passed .data(bar1Data), you shouldn't directly touch that object anymore.
Now, from your data I seem to understand that you want to use the values of all preceding entries to calculate the cumulative sum up to that point. I will implement that in the answer, but would also like to point you towards stack = d3.stack(), which does it for you.
Note also that your values are too large. You need to transform them using a domain, otherwise you get one pixel per dollar (or other unit of money), which means the bars are drawn 100x the width of your monitor to the right.
var bar1Data = [{
position: 1,
label: '70k',
value: 50
},
{
position: 2,
label: '71K - 149K',
value: 40
},
];
//sort bars based on value
bar1Data = bar1Data.sort(function(a, b) {
return d3.ascending(a.position, b.position);
})
var colors = ['green', 'blue'];
var bars = d3.select('#bars')
.append('svg')
.attr('width', 800)
.attr('height', 200);
bars.selectAll('rect')
.data(bar1Data)
.enter()
.append('rect')
.attr('width', function(d) {
return d.value;
})
.attr('x', function(d, i) {
// Note that I use `.map` here to take only the `.value` attribute
// from the data and create an array of numbers.
return sum(bar1Data.map((e) => e.value), 0, i);
})
.attr('fill', function(d, i) {
return colors[i];
})
.attr('y', 0)
.attr('height', 100);
function sum(array, start, end) {
var total = 0;
for (var i = start; i < end; i++) total += array[i];
return total;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id='bars'>
</div>
Related
D3 only render to certain depth
For d3, given an array of nested objects. Is it possible to only render a certain amount of depth? I am basing off of some sunburst examples online like : https://github.com/Nikhilkoneru/react-d3-zoomable-sunburst/blob/master/src/index.js Using the .selectAll method from d3, can I only limit the sunburst to render 'X' depths, instead of rendering the entire array of nested objects? I'm trying to render a really large array, and it causes a really laggy experience. This is the selectAll that I'm using from the github example. svg.selectAll('path') .data(partition(root) .descendants()) .enter() .append('path') .style('fill', (d) => { let hue; const current = d; if (current.depth === 0) { return '#33cccc'; } if (current.depth <= 1) { hue = hueDXScale(d.x0); current.fill = d3.hsl(hue, 0.5, 0.6); return current.fill; } if(current.depth <= 2){ console.log("depth > 2: ", d) current.fill = current.parent.fill.brighter(0.5); const hsl = d3.hsl(current.fill); hue = hueDXScale(current.x0); const colorshift = hsl.h + (hue / 4); return d3.hsl(colorshift, hsl.s, hsl.l); } }) .attr('stroke', '#fff') .attr('stroke-width', '1') .on('click', d => click(d, node, svg, x, y, radius, arc)) .on('mouseover', function (d) { if (props.tooltip) { d3.select(this).style('cursor', 'pointer'); tooltip.html(() => { const name = utils.formatNameTooltip(d); return name; }); return tooltip.transition().duration(50).style('opacity', 1); } return null; }) .on('mousemove', () => { if (props.tooltip) { tooltip .style('top', `${d3.event.pageY - 50}px`) .style('left', `${props.tooltipPosition === 'right' ? d3.event.pageX - 100 : d3.event.pageX - 50}px`); } return null; }) .on('mouseout', function () { if (props.tooltip) { d3.select(this).style('cursor', 'default'); tooltip.transition().duration(50).style('opacity', 0); } return null; }); As you can see, from the current.depth, i'm able to filter out what depth is more than 2. Is it possible to add display: none to anything more than depth of 2, or is there a better way to not render anything more than depth of 2 from current
D3: Combine two colour scales into one
I currently have two variables that I can map to two different colours on two colours scales s1 and s2. s1 gives me the shade of red corresponding to my a value of my variable X (4 different possible colours). s1 gives me the shade of blue corresponding to my a value of my variable Y (4 different possible colours too). Now what I would like to get is something that allows me to combine these two to get a unique colour for a combination of the variables. So for a pair (X,Y) I get a colour on the scale. So I get a scale of 16 possible colours. Here is a legend that illustrate the kind of thing I am looking for: I have been looking at online examples but cannot figure out how to achieve this.
You could combine two threshold scales fairly easily into a new scale function. The core of the function could look like: d3.scaleBivariate = function() { function scaleBivariate(value) { var r = reds(value[0]); var b = blues(value[1]); return "rgb("+r+","+((r+b)/2)+","+b+")"; } var blues = d3.scaleThreshold() .range([255,205,155,105,55]) .domain([0,1,2,3,4,5]); var reds = d3.scaleThreshold() .range([255,205,155,105,55]) .domain([0,1,2,3,4,5]); return scaleBivariate; } This sets the red and blue channels with the help of two d3 threshold scales. The green is simply set as the average between the two, though you could set that to whatever is desirable, say 0 or the minimum of the two other channels. My red/blue ranges are arbitrary and easily changed as well. The above could be used as: d3.scaleBivariate = function() { function scaleBivariate(value) { var r = reds(value[0]); var b = blues(value[1]); return "rgb("+r+","+((r+b)/2)+","+b+")"; } var blues = d3.scaleThreshold() .range([255,205,155,105,55]) .domain([0,1,2,3,4,5]); var reds = d3.scaleThreshold() .range([255,205,155,105,55]) .domain([0,1,2,3,4,5]); return scaleBivariate; } // Dummy data: var data = d3.range(16).map(function(d) { return {x: d%4, y: Math.floor(d/4) } }) var svg = d3.select("svg"); var size = 30; var color = d3.scaleBivariate(); svg.selectAll("rect") .data(data) .enter() .append("rect") .attr("x", function(d) { return d.x * size }) .attr("y", function(d) { return d.y * size }) .attr("width",size) .attr("height",size) .attr("fill",function(d) { return color([d.x,d.y]); }); <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.0.0/d3.min.js"></script> <svg></svg> Of course you might want to add some flexibility by adding methods to modify what datum property sets what colors are associated with what properties, what the thresholds should be, etc. To provide a basic example, the example below has added accessors for setting what property should be mapped to blue and red channels: d3.scaleBivariate = function() { function scaleBivariate(value) { var r = reds(red(value)); var b = blues(blue(value)); return "rgb("+r+","+((r+b)/2)+","+b+")"; } var blues = d3.scaleThreshold() .range([255,205,155,105,55]) .domain([0,1,2,3,4,5]); var reds = d3.scaleThreshold() .range([255,205,155,105,55]) .domain([0,1,2,3,4,5]); var red = function(d) { return d[0]; } var blue = function(d) { return d[1];} // Accessors: scaleBivariate.red = function(_) { return arguments.length ? (red = _, scaleBivariate): red; } scaleBivariate.blue = function(_) { return arguments.length ? (blue = _, scaleBivariate): blue; } return scaleBivariate; } var data = d3.range(16).map(function(d) { return {x: d%4, y: Math.floor(d/4) } }) var svg = d3.select("svg"); var size = 30; // set up the color scale: var color = d3.scaleBivariate() .red(function(d) { return d.x; }) .blue(function(d) { return d.y; }); svg.selectAll("rect") .data(data) .enter() .append("rect") .attr("x", function(d) { return d.x * size }) .attr("y", function(d) { return d.y * size }) .attr("width",size) .attr("height",size) .attr("fill",color); <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.0.0/d3.min.js"></script> <svg></svg>
d3js v5 + Topojson v3 Show/Hide element on click legend
I would like this reverse action http://bl.ocks.org/d3noob/5d621a60e2d1d02086bf. When I click on an item of my legend, I want focus all elements of the map within these class. However, how isolate just one class compared to others? For example in this image, just show elements where the value is included between 23500 and 29000 and hide other elements. I suggest to 1/ filter data and 2/ fill with the same color these elements but surely it's easier. Here is my code : https://plnkr.co/edit/ga82Syjc8zxTdAxTVNXu?p=preview <!DOCTYPE html> <head> <meta charset="utf-8"> <script src="https://unpkg.com/d3#5.0.0/dist/d3.min.js"></script> <script src="https://unpkg.com/topojson#3.0.0/dist/topojson.min.js"></script> </head> <svg class="carte_evolution"> <style> </style> <body> <script> let w = 600; let h = 600; let view = [0,0,600,600]; //Dimension let svgCarteEvolution = d3.select(".carte_evolution") .attr("width", "100%") .attr("height", h) .attr("preserveAspectRatio","xMidYMid meet") .attr("viewBox", `${view[0]},${view[1]},${view[2]},${view[3]}`); //Map projection choro = d3.map(); projection = d3.geoConicConformal() .center([1.282743, 46.328377]) .scale(2600) .translate([w / 2, h / 2]); path = d3.geoPath() .projection(projection); //Load csv and topojson promises1 = d3.json("ze_WGS84_UTF8.topojson"); promises2 = d3.csv("data.csv"); Promise.all([promises1, promises2]).then(function(fr){ //Join csv + topojson let featureCollection = topojson.feature(fr[0],fr[0].objects.ze_WGS84_UTF8) for (var i=0; i< fr[1].length;i++){ var csvId = fr[1][i].codgeo; var csvValue0 = parseFloat(fr[1][i].value0); var csvYear0 = fr[1][i].year0; for (var j=0; j<featureCollection.features.length;j++){ var jsonId = featureCollection.features[j].properties.codgeo; if (csvId === jsonId) { featureCollection.features[j].properties.value0 = csvValue0; featureCollection.features[j].properties.year0 = csvYear0; break; } } } let color = d3.scaleQuantile() .range(["#d1e0c9","#b3cea8","#8fb983","#539f53","#008d36"]); color.domain([ 12000,17500,19500,21500,23500,29000 ]); //Map svgCarteEvolution.append("g") .selectAll("path") .data(featureCollection.features) .enter() .append("path") .attr("class", "dep") .attr("d", path) .style("fill", function(d){ var value = d.properties["value0"]; return value? color(value):"#ccc"; }); //legend let legend = svgCarteEvolution.selectAll("g.legend_entree") .data(color.range().reverse()) .enter() .append("g") .attr("class","legend_entree"); legend .append('rect') .attr("x", 30) .attr("y", function(d, i) { return 65 + i * 20; }) .attr("width", 20) .attr("height", 10) .style("stroke", "black") .style("stroke-width", 0.1) .style("fill", function(d){return d;}); //the data objects are the fill colors legend .append('text') .attr("x", 55) //leave 5 pixel space after the <rect> .attr("y", function(d, i) { return 65 + i * 20; }) .attr("dy", "0.8em") //place text one line *below* the x,y point .style("font-family","latomedium") .style("font-size","0.9em") .text(function(d,i) { var extent = color.invertExtent(d); //extent will be a two-element array, format it however you want: var format = d3.format(""); return `${format(+extent[0])} - ${format(+extent[1])}` }); }); </script> </body> </html> data.csv "codgeo","year0","value0","year1","value1" "0050",2012,19162,2014,19698.8 "0051",2012,18501.125,2014,19125.5 "0052",2012,18684.6666666667,2014,19454 "0053",2012,19826,2014,20573.9 "0054",2012,18881.1111111111,2014,19513 "0055",2012,17942.5,2014,18657.3 "0056",2012,19299.0476190476,2014,19703.9 "0057",2012,18873.8095238095,2014,19539.3 "0059",2012,18199,2014,18719 "0060",2012,18921,2014,19563.2 "0061",2012,21259.5238095238,2014,21748.3 "0101",2012,,2014, "0102",2012,,2014, "0103",2012,,2014, "0106",2012,,2014, "0201",2012,,2014,17250 "0202",2012,,2014,17246.7 "0203",2012,,2014,14972 "0204",2012,,2014,14612.6 "0205",2012,,2014,15617.3 "0206",2012,,2014,13369 "0301",2012,,2014, "0302",2012,,2014, "0303",2012,,2014, "0401",2012,,2014,12522.3 "0402",2012,,2014,15483.8 "0403",2012,,2014,14344.6 "0404",2012,,2014,13134 "0601",2012,,2014, "1101",2012,23144.7619047619,2014,23464 "1102",2012,22223.5,2014,22729.1 "1103",2012,20637.2222222222,2014,21088 "1104",2012,20402,2014,20901 "1105",2012,21782.7777777778,2014,22274.4 "1106",2012,18304.375,2014,18918 "1107",2012,20038.6666666667,2014,20406.5 "1108",2012,19493,2014,19936.5 "1109",2012,28156.8901098901,2014,28660 "1110",2012,20294.8,2014,20639.5 "1111",2012,22490.6666666667,2014,22812.4 "1112",2012,25996.6666666667,2014,26503.5 "1113",2012,25301.7391304348,2014,25639.6 "1114",2012,21184,2014,21611.9 "1115",2012,20586,2014,20954 "1116",2012,25449.5,2014,25742.4 "1117",2012,22279.3333333333,2014,22665 "1118",2012,19040,2014,19307.2 "1119",2012,22059.0909090909,2014,22519 "2101",2012,17416,2014,18133.9 "2102",2012,18721,2014,19275.4 "2103",2012,19554.7826086957,2014,20204 "2104",2012,20352.8571428571,2014,20892.8 "2105",2012,19630.5555555556,2014,20157.6 "2106",2012,18689,2014,19445.2 "2107",2012,17676.6666666667,2014,18309.1 "2201",2012,19209.5238095238,2014,19791 "2202",2012,17090,2014,17820 "2203",2012,16258.6666666667,2014,17014 "2204",2012,17987.619047619,2014,18675.2 "2205",2012,17320.8333333333,2014,18056 "2206",2012,18163.2,2014,18851 "2207",2012,19732.1428571429,2014,20284.7 "2208",2012,19973.3333333333,2014,20690 "2209",2012,17625,2014,18402 "2210",2012,18778.0952380952,2014,19447.6 "2211",2012,17420,2014,18158.8 "2301",2012,18978.8461538462,2014,19609.3 "2302",2012,19920,2014,20419 "2303",2012,18947,2014,19704.7 "2304",2012,20011.7391304348,2014,20639 "2305",2012,18633.4166666667,2014,19348.6 "2306",2012,19222.6666666667,2014,19937.6 "2307",2012,19716,2014,20349.3 "2401",2012,19496,2014,20116.7 "2402",2012,17587.3913043478,2014,18264.3 "2403",2012,18039.3333333333,2014,18668.9 "2404",2012,20862,2014,21386.8 "2405",2012,19088,2014,19617.5 "2406",2012,19682,2014,20246 "2407",2012,17765,2014,18244 "2408",2012,18813.3333333333,2014,19590.5 "2409",2012,18460.8695652174,2014,19134 "2410",2012,19341.3333333333,2014,20286.2 "2411",2012,18503.5,2014,19170 "2412",2012,20074.7619047619,2014,20669 "2413",2012,19886,2014,20426.5 "2414",2012,18549,2014,19111.7 "2415",2012,19298.0952380952,2014,19929 "2416",2012,19159.5833333333,2014,19763.3 "2417",2012,19013.3333333333,2014,19521.9 "2418",2012,20852,2014,21314 "2419",2012,19696.8,2014,20135.4 "2501",2012,18832.9166666667,2014,19616.7 "2502",2012,19839.6428571429,2014,20447.3 "2503",2012,19357,2014,19949.3 "2504",2012,17890,2014,18668.5 "2505",2012,17836.1904761905,2014,18619.5 "2506",2012,18373.0434782609,2014,19247.3 "2507",2012,18855,2014,19788.7 "2508",2012,18192.0833333333,2014,19076 "2509",2012,19105,2014,19896.6 "2510",2012,18837.2222222222,2014,19621.4 "2511",2012,18071.5,2014,18835 "2512",2012,18059.5,2014,18796.7 "2513",2012,17989.5652173913,2014,18533.5 "2601",2012,20356,2014,21106.7 "2602",2012,18218.4,2014,18926.7 "2603",2012,20719.6666666667,2014,21264.4 "2604",2012,18828.3333333333,2014,19445.7 "2605",2012,17496,2014,18232 "2606",2012,18618.3333333333,2014,19419.2 "2607",2012,18150,2014,18717 "2608",2012,19290,2014,20024 "2609",2012,18376,2014,19020 "2610",2012,18123.5,2014,18997.2 "2611",2012,18232,2014,19093.1 "2612",2012,19023.3962264151,2014,19581.7 "2613",2012,18402.2222222222,2014,19055 "2614",2012,19352.6666666667,2014,19970 "3110",2012,17632,2014,18171.3 "3111",2012,19872.3214285714,2014,20498.6 "3112",2012,17909.5,2014,18786 "3113",2012,19370.8695652174,2014,20029 "3114",2012,17411.6666666667,2014,18153.1 "3115",2012,16527.5,2014,17161.7 "3116",2012,17494,2014,18124.5 "3117",2012,16522.5,2014,17252.5 "3121",2012,18958.5714285714,2014,19749.5 "3122",2012,15848,2014,16471.3 "3123",2012,17325,2014,18070 "3124",2012,17319,2014,18080.7 "3125",2012,16943,2014,17776.5 "3126",2012,17054,2014,17783.8 "3127",2012,17860.6666666667,2014,18527.8 "4101",2012,19057,2014,20396.5 "4102",2012,18167.3913043478,2014,18923.8 "4103",2012,20100,2014,20734.3 "4104",2012,19253.4615384615,2014,19914.7 "4105",2012,18178,2014,18899.6 "4106",2012,18415.5555555556,2014,19180 "4107",2012,19765.3333333333,2014,20611.4 "4108",2012,17936.3043478261,2014,18670.5 "4109",2012,19220,2014,19884 "4110",2012,19699,2014,20456 "4111",2012,19975.5555555556,2014,21490 "4112",2012,18561.9047619048,2014,19239.6 "4113",2012,18533,2014,19207 "4114",2012,17932,2014,18625 "4115",2012,18513.6,2014,19304 "4201",2012,21662,2014,22259.4 "4202",2012,22121.4285714286,2014,22678 "4203",2012,20694.9743589744,2014,21249.2 "4204",2012,20996.6666666667,2014,21554 "4205",2012,20637.2,2014,20939.5 "4206",2012,22284.4,2014,22998 "4207",2012,21402,2014,21892 "4208",2012,20530.4761904762,2014,20987.7 "4209",2012,27116,2014,27578 "4301",2012,19866.7857142857,2014,20474.7 "4302",2012,19037.6923076923,2014,19688.7 "4303",2012,26342.380952381,2014,26900.4 "4304",2012,23223.3333333333,2014,24219 "4305",2012,19166.4516129032,2014,19840 "4306",2012,19092.2222222222,2014,19879.5 "4307",2012,20652,2014,21500.8 "4308",2012,18128,2014,18743 "4309",2012,18185,2014,18844.7 "5201",2012,18881.4814814815,2014,19667.6 "5202",2012,17665.6,2014,18286 "5203",2012,20616,2014,21241 "5204",2012,19844.8,2014,20508 "5205",2012,19443,2014,20094.4 "5206",2012,18905.7142857143,2014,19605 "5207",2012,17964.6666666667,2014,18666 "5208",2012,18107.3333333333,2014,18734.4 "5209",2012,18951.7857142857,2014,19703.8 "5210",2012,18635.7142857143,2014,19332 "5211",2012,18973.2258064516,2014,19556.5 "5212",2012,18178.6666666667,2014,18824 "5213",2012,19304.6666666667,2014,19966.7 "5214",2012,18363.3333333333,2014,19098.9 "5215",2012,19146.6666666667,2014,19786.7 "5216",2012,18103.3333333333,2014,18876.7 "5217",2012,18984,2014,19686 "5218",2012,19186.1904761905,2014,19813.8 "5219",2012,20042.380952381,2014,20712.9 "5301",2012,19939.2,2014,20655.3 "5302",2012,18221.4285714286,2014,18960 "5303",2012,20016,2014,20691.3 "5304",2012,18641.5,2014,19282.2 "5305",2012,19581.3333333333,2014,20232.9 "5306",2012,19916.0869565217,2014,20649.4 "5307",2012,17941.3333333333,2014,18671 "5308",2012,19373.3333333333,2014,19991.9 "5309",2012,19634.347826087,2014,20377.6 "5310",2012,18645.3846153846,2014,19283.8 "5311",2012,18720,2014,19397.2 "5312",2012,20578,2014,21143.8 "5313",2012,19380.6666666667,2014,20159.3 "5314",2012,19439,2014,20248.1 "5315",2012,19573,2014,20304.3 "5316",2012,18751.25,2014,19417.2 "5317",2012,18940,2014,19701.3 "5318",2012,20075.4166666667,2014,20740 "5401",2012,17907.5,2014,18522.9 "5402",2012,18704.6666666667,2014,19349 "5403",2012,18580.3846153846,2014,19280 "5404",2012,17407,2014,18222 "5405",2012,19327.5,2014,20055.3 "5406",2012,18512.4,2014,19128 "5407",2012,19217.6,2014,19903.5 "5408",2012,18673,2014,19379.3 "5409",2012,20270,2014,20855.6 "5410",2012,19489.1666666667,2014,20238.1 "5411",2012,17890,2014,18620.5 "5412",2012,18078.0952380952,2014,18834 "5413",2012,19446.5,2014,20043 "7201",2012,18008.4637681159,2014,18697.3 "7202",2012,18425,2014,19159 "7203",2012,18106.6666666667,2014,18658.7 "7204",2012,20667,2014,21249.5 "7205",2012,18035.3333333333,2014,18711.3 "7206",2012,18450.9523809524,2014,19101.3 "7207",2012,20320.8695652174,2014,20948 "7208",2012,19536,2014,20100 "7209",2012,18869.3333333333,2014,19480.7 "7210",2012,17737.5,2014,18362 "7211",2012,17410,2014,17987.6 "7212",2012,20164.6666666667,2014,20683.9 "7213",2012,19245,2014,19834 "7214",2012,20302,2014,20867.6 "7301",2012,18256,2014,18876.3 "7302",2012,17957,2014,18514 "7303",2012,18034.6666666667,2014,18724.8 "7304",2012,19017.2222222222,2014,19799.3 "7305",2012,17916.1904761905,2014,18698 "7306",2012,18578,2014,19336.7 "7307",2012,18857.619047619,2014,19230 "7308",2012,18793.3333333333,2014,19457.6 "7309",2012,19058.6956521739,2014,19787.6 "7310",2012,18895.3333333333,2014,19429.5 "7311",2012,18878.1780538302,2014,19430.7 "7312",2012,18183.8461538462,2014,18879.2 "7313",2012,18197.3333333333,2014,18632 "7401",2012,19040,2014,19716 "7402",2012,18143.3333333333,2014,18897.2 "7403",2012,17301.3043478261,2014,17984 "7404",2012,19118,2014,19698 "8201",2012,20643.8095238095,2014,21227.1 "8202",2012,20174.6666666667,2014,20682.4 "8203",2012,18474.4444444444,2014,19119.6 "8204",2012,18964.6153846154,2014,19694 "8205",2012,17833.8461538462,2014,18486 "8206",2012,18796,2014,19433 "8207",2012,18750.4347826087,2014,19414.5 "8208",2012,19158.8,2014,19833.1 "8209",2012,20058.9655172414,2014,20741.1 "8210",2012,21119.1153846154,2014,21684.5 "8211",2012,19926,2014,20499.3 "8212",2012,18585.7142857143,2014,19272 "8213",2012,21155,2014,21768 "8214",2012,20965,2014,21470 "8215",2012,20356.9230769231,2014,20849.2 "8216",2012,21009.6,2014,21576.5 "8217",2012,20380.7692307692,2014,20996 "8218",2012,23005.2173913043,2014,23712.7 "8219",2012,27201.9230769231,2014,27749.3 "8220",2012,21017,2014,21626.5 "8221",2012,21245.0909090909,2014,21706.7 "8222",2012,23036.0869565217,2014,23862.9 "8301",2012,18453.9393939394,2014,19023.3 "8302",2012,18534.3333333333,2014,19190 "8303",2012,18492,2014,19091.3 "8304",2012,18855.7142857143,2014,19606.7 "8305",2012,17307.5595238095,2014,18159 "8306",2012,17459.1666666667,2014,18457 "8307",2012,17890.4761904762,2014,18484.8 "8308",2012,18963,2014,19466.1 "8309",2012,18347.1428571429,2014,19084 "8310",2012,20412,2014,21071.7 "8311",2012,19008.5,2014,19795.7 "8312",2012,18111.6666666667,2014,18856.5 "9101",2012,17408.5714285714,2014,17945.3 "9102",2012,16617.3333333333,2014,17173 "9103",2012,17123.3333333333,2014,17661 "9104",2012,16840,2014,17349.6 "9105",2012,18586,2014,19386.7 "9106",2012,18106.6666666667,2014,18625 "9107",2012,17128.4615384615,2014,17763 "9108",2012,16940.8695652174,2014,17503.5 "9109",2012,17352,2014,17867.5 "9110",2012,16858,2014,17391.3 "9111",2012,19566.6666666667,2014,20024.4 "9112",2012,17690.4,2014,18320.7 "9113",2012,18336.5217391304,2014,19110 "9114",2012,18100.4545454545,2014,18640.7 "9115",2012,17447.2222222222,2014,17903.9 "9116",2012,17108,2014,17654.6 "9301",2012,18502,2014,19034.2 "9302",2012,18934.8,2014,19401.5 "9303",2012,18952.3076923077,2014,19365.3 "9304",2012,18794.7619047619,2014,19335 "9305",2012,20466.1111111111,2014,20830.8 "9306",2012,20292.8,2014,20668.7 "9307",2012,19959,2014,20333.3 "9308",2012,21603.3333333333,2014,22211.1 "9309",2012,18084,2014,18679.5 "9310",2012,19009,2014,19507 "9311",2012,19514.6666666667,2014,20090 "9312",2012,19813.6,2014,20490.6 "9313",2012,18840,2014,19254.8 "9314",2012,19946.0357142857,2014,20238 "9315",2012,19492,2014,20070.5 "9316",2012,17739.1304347826,2014,18245.7 "9317",2012,17904,2014,18510.6 "9401",2012,19633.8461538462,2014,20418.3 "9402",2012,17570,2014,17890 "9403",2012,17200.8333333333,2014,17640.5 "9404",2012,17777.2666666667,2014,18376.4 "9405",2012,17254.5,2014,17473.5 "9406",2012,17553.3333333333,2014,18426 "9407",2012,16885,2014,17577.7
Your instincts will work - filter the path data according to the color: features.filter(function(feature) { return color(feature.properties["value0"]) == d; }) I saved your feature paths in the variable features in the plunkrs below As the datum of each legend entry is a color, we can just filter the paths based on which datum would produce the same color when scaled. We could alternatively give class names or apply other indicators to filter. Color the filtered paths on the event, revert all paths on some sort anti-event. Since you haven't said what will reset the map, I'll assume if you click on the same legend entry twice in a row the map should reset (this lets you toggle between each legend entry). To do so we'll need to keep track of which legend entry is currently in focus: var highlighted = ""; Then we just listen for a click event on the legend entry and: .on("click",function(d,i) { highlighted = ""; //reset // revert map back to default state } else { highlighted = d; // filter features to highlight certain ones. }) Altogether that might look like: .on("click",function(d,i) { // clicking an active entry: reset: if(highlighted == d) { // reset opacity on each path: features.style("opacity",1); // reset legend entries' fills: legend.selectAll("rect") .style("fill",function(d) { return d; }); // reset highlight variable since nothing is highlighted highlighted = ""; } // clicking a different entry: highlight that entry: else { //update highlighted variable highlighted = d; // set opacity low for all features, filter for chosen features and style accordingly: features.style("opacity",0.2) .filter(function(f) { return color(f.properties["value0"]) == d; }) .style("opacity",1); // hollow legend entries legend.selectAll("rect") .style("fill","white") // fill selected option: d3.select(this).select("rect") .style("fill",function(d) { return d;}); } }) Which looks something like: (I'm formatting the legend entries to show what is highlighted - I changed your stroke to accomodate this) Here's an updated plunkr. If you want to be able to toggle multiple legend items at once, then the logic gets a bit more detailed and the highlighted variable becomes an array As an alternative with mouseover/off events rather than clicks (so no need to track what is actively shown), here's a slightly different approach using the same principles: plunkr
D3 graph not updating with uniform data
I'm working on a bar chart. Strangely enough, the bar chart works fine for unique data, but when updated with data that is exactly the same, the graph does not update. It is not a scale/spacing problem, as the rect's representing the data aren't being generated at all. Here is a JSFiddle demonstrating my problem. More information The bar chart is given data in the following form: data.push({ temperature: 10, humidity: 20, light: 30 }); The bar chart creates a separate bar for each attribute. The attributes are represented by this type object, to help with spacing and naming the bars: // type objects typeObject = { temperature: { string: "temperature", //type name class: "temp", //css class for styling initDis: 0, //defines where to place first bar dis: 3, //defines space between the next temp bar base: tempBase, //the <g> element base color: "#A6E22E" //color }, humidity: { ... }, ... }; And, every time data is added, these type objects are iterated through and passed into the following function, to make a d3 'update pattern': var createBarsForCategory = function (type) { var bars; //`data` has been updated to an array containing newly added data bars = type.base.selectAll("rect") .data(data, function(d) { return d[type.string]; }); //general update transition bars.transition().duration(500).attr("x", function(d, i) { return i * (m.bar.w + m.bar.space) * type.dis + m.bar.w * type.initDis; }); // enter bars.enter() .insert("rect") .attr("class", type.class) .attr("width", m.bar.w) .attr("height", function (d) { return 0; }) .attr("x", function (d, i) { return i * (m.bar.w + m.bar.space) * type.dis + m.bar.w * type.initDis; }) .attr("y", function (d) { return scales.yscale(0); }) .transition().delay(100).duration(1000) .attr("height", function (d) { return scales.yscale(0) - scales.yscale(d[type.string]); }) .attr("y", function (d) { return scales.yscale(d[type.string]); }) ; // remove bars.exit().remove(); }; Thank you in advance for your time.
I have updated your fiddle http://jsfiddle.net/pyLh7tcn/33/ with the following code on lines 173-178: bars = type.base.selectAll("rect") .data(data); Now both random and uniform data sets are plotting properly. Hope this helps.
d3: Adding and removing force.nodes based on slider values
I had some difficulty a while back adding and removing nodes based on user input which was solved by updating the entire set of objects pushed into force.nodes() each time the user selected which they wanted to view from a list of checkboxes. Updating from a slider however I think requires a more finesse touch - I don't want to update the entire set every time the slider is moved. I want to push and pop nodes in and out of force.nodes(). With my current code the nodes are coming out just fine - they're just not going back in. jsfiddle here - https://jsfiddle.net/hiwilson1/ancmtxux/3/ This is the part causing problems; function brushed() { var exists; //var newd = new Date(2013, 05, 01) data.forEach(function(d, i) { //if data point in range (between extent 0 and 1) if (d.date >= brush.extent()[0] && d.date <= brush.extent()[1]) { exists = force.nodes().some(function(node, i) { //check if data point already exists in force.nodes() return (node.mIndex == d.mIndex) }) console.log(exists) if (!exists) { force.nodes().push(d) } } else { force.nodes().splice(i, 1) } }) d3.select("#nodeCount").text(force.nodes().length) } For each data point, I'm checking whether or not the point lies between extent()[0] and [1]. If it does, then check force.nodes() to see if it's currently a member there. If it isn't, then push it into force.nodes(). If the data point doesn't lie between the extents then splice it from force.nodes(). This last bit works just fine. UPDATE: Fixed. I actually also worked out how to filter links attached to nodes as well. jsfiddle here - https://jsfiddle.net/hiwilson1/7oumeat5/2/. Never try and do this with indexes/hard coded indexes, compare the nodes/links as objects. Incidentally I'm seeing links over the top of nodes. If there's some way to fix this I'd be happy to hear it. FURTHER UPDATE: To ensure links behind nodes use .insert("line", ":first-child") in place of .append("line")
There are a few problems with your current code. The fundamental problem is that you're giving data to force.nodes(), which means that the two data structures are actually the same. That is, when you're removing elements from force.nodes(), you're modifying the underlying data as well. Hence you can't add them back -- they're gone. This is easily fixed by passing a copy of data to force.nodes(): var force = d3.layout.force() .nodes(JSON.parse(JSON.stringify(data))) Then you're removing the wrong nodes from force.nodes() -- the index you're using is for data, not force.nodes(). You can compute the index of the data element in force.nodes() and use it like this: data.forEach(function(d, i) { var idx = -1; force.nodes().forEach(function(node, j) { if(node.mIndex == d.mIndex) { idx = j; } }); //if data point in range (between extent 0 and 1) if (d.date >= brush.extent()[0] && d.date <= brush.extent()[1]) { if (idx == -1) { force.nodes().push(d) } } else if(idx > -1) { force.nodes().splice(idx, 1) } Finally, you need to call force.start() at the end of brushed for the changes to become visible after the layout has settled down. Complete example here.
Building on the answer from the mighty #Lars Kotthoff, who fixed your technical problem, I will focus on the architecture. Here is an architecture that is simpler and more in keeping with d3 idiom. The main principles are: Use Array.prototype.filter() to manage the data Use standard, data driven patterns. Drive the visualisation by managing the data and then use the standard, general update pattern to drive the changes into the vis. Seperate data events and animation events. Respond to the data changes only when need... do it in the brushed event, not on every tick. The brushed event is a data event and the tick routine is an animation event. It's not optimal to be managing data on animation events on the off chance that it is required. Make the entry and exit of the nodes a bit smoother. The benefit of point 1 is that the filtered array is actually an array of reference to the original data elements, so when the extra state is added on the copied array, it is actually added on the original data array. Thus, the previous state is available when it is filtered back in, hence the smooth exit and entry behaviour. Meanwhile, no elements are deleted in the original data when the brush filters down: only the references to them are deleted in the cloned array. I have to admit I had not expected that but it is a nice discovery, even if by accident! Of course this only works because the array elements are objects. working example here... var width = 700, height = 600, padding = 20; var start = new Date(2013, 0, 1), end = new Date(2013, 11, 31) var data = [] for (i = 0; i < 100; i++) { var point = {} var year = 2013; var month = Math.floor(Math.random() * 12) var day = Math.floor(Math.random() * 28) point.date = new Date(year, month, day) point.mIndex = i data.push(point) } var force = d3.layout.force() .size([width - padding, height - 100]) .on("tick", tick) .start() var svg = d3.select("body").append("svg") .attr({ "width": width, "height": height }) //build stuff var x = d3.time.scale() .domain([start, end]) .range([padding, width - 6 * padding]) .clamp(true) var xAxis = d3.svg.axis() .scale(x) .tickSize(0) .tickPadding(20) //.tickFormat(d3.time.format("%x")) var brush = d3.svg.brush() .x(x) .extent([start, end]) .on("brush", brushed1) //append stuff var slidercontainer = svg.append("g") .attr("transform", "translate(100, 500)") var axis = slidercontainer.append("g") .call(xAxis) var slider = slidercontainer.append("g") .call(brush) .classed("slider", true) //manipulate stuff d3.selectAll(".resize").append("circle") .attr("cx", 0) .attr("cy", 0) .attr("r", 10) .attr("fill", "Red") .classed("handle", true) d3.select(".domain") .select(function () { return this.parentNode.appendChild(this.cloneNode(true)) }) .classed("halo", true) function brushed1(e) { var nodes = includedNodes(data, brush); nodes.enter().append("circle") .attr("r", 10) .attr("fill", "red") .call(force.drag) .attr("class", "node") .attr("cx", function (d) { return d.x }) .attr("cy", function (d) { return d.y }) nodes .exit() .remove() force .nodes(includedData(data, brush)) .start() } function includedData(data, brush) { return data.filter(function (d, i, a) { return d.date >= brush.extent()[0] && d.date <= brush.extent()[1] }) } function includedNodes(data, brush) { return svg.selectAll(".node") .data(includedData(data, brush), function (d, i) { return d.mIndex }) } function tick() { includedNodes(data, brush) .attr("cx", function (d) { return d.x }) .attr("cy", function (d) { return d.y }) } brushed1() .domain { fill: none; stroke: #000; stroke-opacity: .3; stroke-width: 10px; stroke-linecap: round; } .halo { fill: none; stroke: #ddd; stroke-width: 8px; stroke-linecap: round; } .tick { font-size: 10px; } .selecting circle { fill-opacity: .2; } .selecting circle.selected { stroke: #f00; } .handle { fill: #fff; stroke: #000; stroke-opacity: .5; stroke-width: 1.25px; cursor: crosshair; } <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script> <p id="nodeCount"></p>