D3.js stacked shaft with toggleable series

This time I'm trying to create a stacked panel with a toggleable series based on the example of Mike Bostok (thanks again to Mike!) I already managed to make it responsive and scalable, and the alternating series according to legend - the last thing remains.

I created legend elements and applied the correct color using the keys:

var legendItem = d3.select(".legend") .selectAll("li") .data(keys) .enter() .append("li") .on('click', function(d) { keys.forEach(function(c) { if (c != d) tKeys.push(c) }); fKeys = tKeys; tKeys = []; redraw(); }); legendItem .append("span") .attr("class", "color-square") .style("color", function(d) { return colorScale5(d); }); legendItem .append("span") .text(function(d) { return (d) }); 

Based on the structure, in order to create the toggleable element, I came to the conclusion that somehow I should be able to switch it from the keys and the data set - or is there another way to do this? I managed to remove a certain key from the keys, but not from the data set, I have no idea how to correctly match it.

The second problem is that I cannot figure out how to switch the key, but just delete it. This is the source dataset:

 var data = [{ "country": "Greece", "Vodafone": 57, "Wind": 12, "Cosmote": 20 }, { "country": "Italy", "Vodafone": 40, "Wind": 24, "Cosmote": 35 }, { "country": "France", "Vodafone": 22, "Wind": 9, "Cosmote": 9 }] 

In the values ​​provided from the nested dataset, I could attach a key with the name "enabled" to each object and I could easily filter the dataset, but I can’t figure out how to connect the key to help in the filtering process.

edit3 Removed unnecessary information from the question:

Here is a working fiddle: https://jsfiddle.net/fgseaxoy/2/

+5
source share
2 answers

SergGr code works well, but some parts may be cleaner.

Onclick

 var fKeys = keys.slice(); //a helper object to record the state of keys var fKeyReference = fKeys.map(function () { return true; //used to indicate if the corresponding key is active }); function getActiveKeys(reference) { return reference.map(function (state, index) { if (state) { return keys[index]; //just keep keys whoes state is true } return false; //return false to be filered }).filter(function (name) { return name }); } ... .on('click', function (d) { if (fKeys.length === 1 && fKeys[0] === d) { return; } var index = keys.indexOf(d); fKeyReference[index] = !fKeyReference[index]; // toggle state of fKeyReference fKeys = getActiveKeys(fKeyReference); redraw(); }); 

.stack ()

 g.selectAll(".d3-group").remove();//remove all groups and draw them all again stackedBars = g .selectAll(".d3-group") .data(d3.stack().keys(fKeys)(dataset)); 

update axis ( y.domain )

 y.domain([ 0, 1.2 * d3.max(dataset, function (d) { return fKeys.reduce(function (pre, key) {//calculate the sum of values of fKeys return pre + d[key]; }, 0); }) ]); 

And finally, jsfiddle

+2
source

There are several things to fix:

JavaScript first assigns objects by reference. This means that after

 var fKeys = keys; 

both fKeys and keys point to the same array. This is not what you want. You want to copy something, for example:

 var fKeys = keys.slice(); 

Then your legendItem "click" handler was wrong because it does not switch the selected item. What you want is something like

  .on('click', function (keyToToggle) { // Go through both keys and fKeys to find out proper // position to insert keyToToggle if it is to be inserted var i, j; for (i = 0, j = 0; i < keys.length; i++) { // If we hit the end of fKeys, keyToToggle // should be last if (j >= fKeys.length) { fKeys.push(keyToToggle); break; } // if we found keyToToggle in fKeys - remove it if (fKeys[j] == keyToToggle) { // remove it fKeys.splice(j, 1); break; } // we found keyToToggle in the original collection // AND it was not found at fKeys[j]. It means // it should be inserted to fKeys at position "j" if (keys[i] == keyToToggle) { // add it fKeys.splice(j, 0, keyToToggle); break; } if (keys[i] == fKeys[j]) j++; } redraw(); }); 

Next, you want to use key fuction when you call data to get stackedBars . This is important because otherwise the data will be bound by the index and the last piece of data will always be deleted.

  var stackedData = d3.stack().keys(fKeys)(dataset); var stackedBars = g .selectAll(".d3-group") .data(stackedData , function (__data__, i, group) { return __data__.key; }); 

And finally, when you update '.d3-rect' , you want to call data again, since the child nodes cache the data from the last draw, and you want to override it with new data.

  stackedBars.selectAll('.d3-rect') .data(function (d) { return d; // force override with updated parent data }) .attr("x", function (d) { return xz(d.data.country); }) ... 

Without such a call, hiding the first piece of data ("Vodafone") will not cause other stacked pieces to move.

There are also a few too large global variables (i.e. too few var s) and a few unnecessary variables.

Refresh (autoscale y)

If you also want to update the Y-scale, you move var stackedData higher into the redraw code so that you can use it to calculate your y as follows

  var stackedData = d3.stack().keys(fKeys)(dataset); var autoScaleY = true; // scale Y according to selected data or always use original range var stackedDataForMax; if (autoScaleY && stackedData.length > 0) { // only selected data stackedDataForMax = stackedData; } else { // full range stackedDataForMax = d3.stack().keys(keys)(dataset); } var maxDataY = 1.2 * d3.max(stackedDataForMax.map(function (d) { return d3.max(d, function (innerD) { return innerD[1]; }); })); y.domain([0, maxDataY]).rangeRound([height, 0]); 

You can find all the code in the plug of your original violin .

+5
source

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


All Articles