问题
I am having a really hard time understanding d3.layout.stack() when groups are not listed manually. In the below example, similar to what I've found in other questions, groups are listed in [] as "Apple", etc., but as far as I understand this has to be inputted manually. I am seeking a way to not have to manually input "Apple", "Blueberry", etc.
var dataset = d3.layout.stack()(["Apple", "Blueberry", "Lettuce", "Orange"].map(function(fruit) {
return data.map(function(d) {
return {x: d.orchard, y: +d[fruit]};
});
}));
I've tried inserting a line in my data object as below, called 'names':
[{names='Apple','Blueberry','Lettuce','Orange'}, {Apple=1.0, Orange=2.0, Lettuce=1.0, orchard=小明, Blueberry=1.0}, {Apple=1.0, Orange=1.0, Lettuce=1.0, orchard=小陈, Blueberry=1.0}, {Apple=1.0, Orange=1.0, Lettuce=1.0, orchard=小虎, Blueberry=1.0}, {Orange=1.0, Lettuce=1.0, orchard=小桃, Blueberry=1.0, Apple=1.0}]
Is there a way to code something similar to below?
var dataset = d3.layout.stack()([d3.keys(names)].map(function(fruit) {
Should I be focused more on inserting a unique list of names into my data object, or do so by parsing my data in my d3 code itself to accumulate a list of unique group names?
I am wondering, if the d3.keys logic makes sense, if it can be applied to the below context too, instead of enumerating each case:
legend.append("text")
.attr("x", width + 5)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "start")
.text(function(d, i) {
for(var j =0; j<4; j++){
switch (i) {
case j: return d3.keys[j]
// switch (i) {
//
// case 0: return "orange"
// case 1: return "apple"
// case 2: return "blueberry"
// case 3: return "lettuce"
}
}
});
回答1:
I ended up just converting the entire graph to d3 v5. Below is some notes based off a lot of sources I looked at mixed with my own work:
Better practice for stacked bar is to use
.data(d3.stack().keys(keys)(data))
where
var keys = d3.keys(data[0]).filter(function(d){
return d != "orchard";
});
or in other words:
var keys = d3.keys(data[0]).filter(d => d != "orchard")
This is useful for data that is pre-parsed in javascript. Say you have just columns in a csv:
var keys = csv.columns.slice(0);
is useful, but same philosophy for stacking applies.
Slight issue: if you have new categories arising later on in the data, i.e. a new fruit pineapple is part of data[1] but not data[0], key will not identify pineapple. It only responds to the data object of the first entry.
To not rely on data[1], data[0], etc., and "accumulate" keys for data[0], data[1], etc. while maintaining the same filter:
var key = [];
for(var i =0; i < d3.keys(data).length; i++){
var joinin = d3.keys(data[i]).filter(d => d != "orchard")
var key = key.concat(joinin)
// console.log(key)
}
There's most likely a better way of writing that code, but the explanation is:
If you wrote something like this you'd get keys for only one set of data:
var key = d3.keys(data[2]).filter(function(d){
return d != "orchard";
});
If you wrote this you get the keys for each iteration of data:
var key = [];
for(var i =0; i < d3.keys(data).length; i++){
var key = d3.keys(data[i]).filter(d => d != "orchard")
console.log(key)
key.push(key);
}
So the trick is to use a for loop to get each iteration of data but concat that into one singular list, which has no repeats.
[EDIT] What if you wanted the value given to each key? Again, this is for a data structure like this, which is a little unconventional:
data = [
{0:
{"Apple": 1}
{"orchard": xx}
}
{1:
{"Apple": 2}
{"orchard": xx}
}
]
You can use the below, where key will return ["Apple"], and key_values will return [1, 2]. Basically the filter d > 0 prevents any strings, so like names of orchards "xx". Does the same as filtering out orchards.
var key = [];
var key_values = [];
for(var i =0; i < d3.keys(data).length; i++){
var key_value = d3.entries(data[i]).map(d => d.value).filter(d => d > 0)
var key_values = key_values.concat(key_value)
var joinin = d3.keys(data[i]).filter(d => d != "orchard")
var key = key.concat(joinin)
}
(EDIT2) About the legend..
I just replaced my code with, and I know d3v5 can simplify the below(?),
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter()
.append("g")
.attr("class","legend")
.attr("transform",function(d,i) {
return "translate(1300," + i * 15 + ")";
});
In my case input variables are discrete, like "Apple" etc., not integers, so we use scaleOrdinal and just use "keys" for the domain. You don't really need to write that though, I think it defaults to the input list of discrete variables? Not sure why it works.
var color = d3.scaleOrdinal()
.domain(keys)
// .range (whatever you want)
来源:https://stackoverflow.com/questions/61088270/avoid-enumerating-group-names-in-d3-layout-stackgroup-1-group-2