oliver13
oliver13

Reputation: 1036

Accessing nested data for graph creation

I have nested data, as follows:

data  
    > key
    > values
       > date
       > reldif
       > absdif

Everything works fine in the line diagram:

var line = svg.selectAll(".lines")
  .data(data, function(d) { return d.key; })
  .enter()
  .append("g")
  .attr("class", "lines")
  .append("path")
  .attr("class", "line")
  .attr("d", function(d) { return line(d.values); })
  .style("stroke", function(d) { return color(d.key); })
  .style("stroke-width", 4)

However, trying to use the same technique for circles, I am failing miserably. As far as I understand, I need x, y and r (which can be absolute, like 5 or so). X should be date, Y should be reldif. How do I access these values? If I just use d.values it hands over an array that I cannot process in this way I guess.

(In general - if you have any suggestions on tutorials for nested data in D3, I'm happy to hear those suggestions. The ones I find treat every topic except for using them for graphs)

Upvotes: 2

Views: 164

Answers (2)

musically_ut
musically_ut

Reputation: 34288

To deal with nested data, you can create nested selections. What is confusing here is that lines did not need any nested selections.

This way of drawing lines is a little special in D3 since it is drawn using the path SVG element. The path element has one attribute d which accepts a DSL. This string is a tad bit cumbersome and, hence, D3 provides helper objects d3.svg.line.

On this helper object, one can set x and y accessors which will decide the x and y points which make the line. This object also allows you to set many other properties of the line (like which kind of interpolation you want). Finally, this helper object can be called as a function data and it will produce the DSL needed for the d attribute for a path.

This is how it is being used in the example you have pasted here: .attr("d", function(d) { return line(d.values); }). Somewhere before, this line object was created via a call to d3.svg.line and it probably has x accessor set to .x(function (d) { return d.date; }) and y accessor set to .y(function (d) { return d.reldif; }.

Note: you are overriding the line variable when you assign the result of the call the line again. JSHint will warn you if you do this.

Now the circle element is considerably simpler. Here, you do not need any helper functions because, as you point out, you can set the x, y and r attributes directly without an intermediate DSL. Hence, you it would look something like this:

var circles = svg.selectAll(".circles")
    .data(data, function(d) { return d.key; })
    .enter()
    .append("g")
    .attr("class", "circles")
  .selectAll(".circle")
    .data(function (d) { return d.values; })
    .enter()
    .append("circle")
    .attr("class", "circle")
    .attr("cx", function(d) { return d.date; })
    .attr("cy", function(d) { return d.reldif; })
    .attr("r", 5)
    .style("stroke", function(d) { return color(d.key); })
    .style("stroke-width", 4)

If d.values.date is actually a date object instead of a number, then you can use a time scale to convert it to nice numbers.

Upvotes: 1

Lars Kotthoff
Lars Kotthoff

Reputation: 109242

There's a fundamental difference between lines and circles -- lines represent collections of points, while circles represent single points. In practice, this means that the data element you use to create a line is an array, while it is only a single data point for a circle.

In your example, you're passing an array (d.values) to the drawing primitive. This doesn't work for circles because it expects a single datum. What you need for that case are nested selections that introduce a grouping element for the circles belonging to the same "line". The code would look something like this.

svg.selectAll("g")
   .data(data, function(d) { return d.key; })
   .enter()
   .append("g")
   .selectAll("circle")
   .data(function(d) { return d.values; })
   .enter()
   .append("circle")
   .attr("cx", function(d) { return x(d.date); })
   // etc

The first part of the code is identical, but after appending the g elements, you're making a subselection for circles that represent the elements of d.values. After that, the code is straightforward again with circles appended and configured in the usual manner.

On a general note, the way nested data is used for a graph is highly dependent on the particular graph -- a collapsible tree is different from a chord diagram. The tutorial on nested selections is worth reading, but in this particular case it's not really a problem of nested data, but of the different things different graphical elements represent.

Upvotes: 2

Related Questions