this time I am trying to create a stacked bar with toggleable series- based on Mike Bostock's example (thanks once more Mike!) I have already succeeded into making it responsive and zoomable, and the toggleable series through a legend is the last thing remaining.
I created the legend items, and applied the correct color by using 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 item, I came to the conclusion that I somehow have to be able to toggle it from the keys AND the dataset - or is there another way to do it? I have managed to remove a specific key from the keys, but not from the dataset, I have no idea how to map it properly.
The second issue is that I can't figure of a way to toggle a key, but just remove it. This is the original 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 were provided from a nested dataset, I could attach a key named 'enabled' to each object and could easily filter the dataset, but can't figure out how to attach a key to help in the filtering proccess.
edit3 Removed useless information from the question:
Here is a working fiddle: https://jsfiddle.net/fgseaxoy/2/
There are a few things that needed fixing:
First, JavaScript assignes objects by reference. It means that after
var fKeys = keys;
both fKeys and keys point to the same array. This is not what you want. You want something copying such as:
var fKeys = keys.slice();
Then your legendItem "click" handler was wrong because it doesn't really toggle 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 povide key fuction when you call data to get stackedBars. This is important because otherwise data would be bound by index and always the last piece of data would be removed.
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 once again as child nodes cache 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's data
})
.attr("x", function (d) {
return xz(d.data.country);
})
...
Without such call, hiding first piece of data ("Vodafone") would not move other stacked pieces down.
Also there are a few too many global vairables (i.e. too few vars) and a few unnecessary variables.
Update (auto-scale y)
If you also want your Y-scale to be updated, you move var stackedData higher in the code of the redraw so you can use it to calculate your y as following
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 whole code in the fork of your original fiddle.
SergGr's code works well, but some parts can be cleaner.
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();
});
g.selectAll(".d3-group").remove();//remove all groups and draw them all again
stackedBars = g
.selectAll(".d3-group")
.data(d3.stack().keys(fKeys)(dataset));
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With