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 var
s) 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