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!

+3


source to share


2 answers


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.

+3


source


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!

+1


source







All Articles