D3: Coloring multiple lines from nested data
I am currently collecting some line plots with circles in datapoints from arrays of JSON objects formatted like this:
var data = [{
"name": "metric1",
"datapoints": [
[10.0, 1333519140],
[48.0, 1333519200]
]
}, {
"name": "metric2",
"datapoints": [
[48.0, 1333519200],
[12.0, 1333519260]
]
}]
I want to have a color for each metric, so I am trying to color them based on the index of the object in the dataset. The code I have for just placing the circles looks like this:
// We bind an svg group to each metric.
var metric_groups = this.vis.selectAll("g.metric_group")
.data(data).enter()
.append("g")
.attr("class", "metric_group");
// Then bind a circle for each datapoint.
var circles = metric_groups.selectAll("circle")
.data(function(d) { return d.datapoints; });
circles.enter().append("circle")
.attr("r", 3.5);
Now if I change this last bit to something like:
circles.enter().append("circle")
.attr("r", 3.5);
.style("fill", function(d,i) { return i%2 ? "red" : "blue"; }
I get alternating red and blue circles as I would expect.
Taking some advice from Nested Fetch: "Nesting and Indexing" I tried:
circles.enter().append("circle")
.attr("r", 3.5);
.style("fill", function(d,i,j) { return j%2 ? "red" : "blue"; }
Which doesn't work (j is undefined), presumably because we're in the named properties of datapoints, not an array element. How can I make the coloration I want without changing the data structure? Thank!
source to share
The easiest way to do this is to make the circles inherit the fill style from the parent G:
var color = d3.scale.category20();
var metricGroup = vis.selectAll(".metric-group")
.data(data)
.enter().append("g")
.attr("class", "metric-group")
.style("fill", function(d) { return color(d.name); });
var circle = metricGroup.selectAll("circle")
.data(function(d) { return d.datapoints; })
.enter().append("circle")
.attr("r", 3.5);
If you have defined categorical colors as CSS classes, you can also use a dynamic class name and inherit like this:
var metricGroup = vis.selectAll(".metric-group")
.data(data)
.enter().append("g")
.attr("class", function(d) { return "metric-group " + color(d.name); });
With the appropriate CSS:
.metric1 circle { fill: red; }
.metric2 circle { fill: blue; }
Another approach is to use each to access parent data:
metricGroup.each(function(p, j) {
d3.select(this).selectAll("circle")
.data(p.datapoints)
.enter().append("circle")
.attr("r", 3.5)
.style("fill", color(p.name));
});
I also think that using a group index j
will work; I'm not sure why this is undefined for you, but there is a parasitic semicolon (in .attr("r", 3.5);
) in your example code , so it is possible that something is happening. Anyway, it's more idiomatic to infer categorical colors from data rather than group index, so I would use one of the above methods.
source to share
In one of my renderings, I had the same problem. My solution was as follows (adapted to your example):
var count = 0;
var color = d3.scale.category20();
var metric_groups = this.vis.selectAll("g.metric_group")
.data(data).enter()
.append("g")
.attr("class", "metric_group");
var circles = metric_groups.selectAll("circle")
.data(function(d) {return d.datapoints;});
circles.enter().append("circle")
.attr("r", 3.5)
.style("fill", function(d,i){
d.number = count;
count++;
return color(d.number);
});
The key is to give each base object a unique attribute that can be used to give the corresponding element a unique color for different groups.
Hope this helps or helps you find a similar solution!
source to share