D3 - how to deal with JSON data structures?

40,859

Solution 1

When you join data to a selection via selection.data, the number of elements in your data array should match the number of elements in the selection. Your data array has two elements (for Jim and Ray), but the selection you are binding it to only has one SVG element. Are you trying to create multiple SVG elements, or put the score rects for both Jim and Ray in the same SVG element?

If you want to bind both data elements to the singular SVG element, you can wrap the data in another array:

var chart = d3.select("#charts").append("svg")
    .data([data])
    .attr("class", "chart")
    …

Alternatively, use selection.datum, which binds data directly without computing a join:

var chart = d3.select("#charts").append("svg")
    .datum(data)
    .attr("class", "chart")
    …

If you want to create multiple SVG elements for each person, then you'll need a data-join:

var chart = d3.select("#charts").selectAll("svg")
    .data(data)
  .enter().append("svg")
    .attr("class", "chart")
    …

A second problem is that you shouldn't use d3.values with an array; that function is for extracting the values of an object. Assuming you wanted one SVG element per person (so, two in this example), then the data for the rect is simply that person's associated scores:

var rect = chart.selectAll("rect")
    .data(function(d) { return d.scores; })
  .enter().append("rect")
    …

If you haven't already, I recommend reading these tutorials:

Solution 2

This may clarify the nested aspect, in addition to mbostock's fine answer.

Your data has 2 degrees of nesting. You have an array of 2 objects, each has an array of ints. If you want your final image to reflect these differences, you need to do a join for each.

Here's one solution: Each user is represented by a group g element, with each score represented by a rect. You can do this a couple of ways: Either use datum on the svg, then an identity function on each g, or you can directly join the data on the g. Using data on the g is more typical, but here are both ways:

Using datum on the svg:

var chart = d3.select('body').append('svg')
  .datum(data)             // <---- datum
  .attr('width',800)
  .attr('height',350)
  .selectAll('g')
  .data(function(d){ return d; })  // <----- identity function
  .enter().append('g')
    .attr('class', function(d) { return d.user; })
    .attr('transform', function(d, i) { return 'translate(0, ' + i * 140 + ')'; })
    .selectAll('rect')
    .data(function(d) { return d.scores; })
    .enter().append('rect')
      .attr('y', function(d, i) { return i * 20; })
      .attr('width', function(d) { return d; })
      .attr('height', 20);

Using data on the group (g) element:

var chart = d3.select('body').append('svg')
  .attr('width',800)
  .attr('height',350)
  .selectAll('g')
  .data(data)          // <--- attach directly to the g
  .enter().append('g')
    .attr('class', function(d) { return d.user; })
    .attr('transform', function(d, i) { return 'translate(0, ' + i * 140 + ')'; })
    .selectAll('rect')
    .data(function(d) { return d.scores; })
    .enter().append('rect')
      .attr('y', function(d, i) { return i * 20; })
      .attr('width', function(d) { return d; })
      .attr('height', 20);

Again, you don't have to create these g elements, but by doing so I can now represent the user scores differently (they have different y from the transform) and I can also give them different styles, like this:

.jim {
  fill: red;
}
.ray {
  fill: blue;
}
Share:
40,859
Ray
Author by

Ray

Updated on September 04, 2020

Comments

  • Ray
    Ray over 3 years

    I'm new to D3, and spent already a few hours to find out anything about dealing with structured data, but without positive result. I want to create a bar chart using data structure below. Bars are drawn (horizontally), but only for user "jim".

    var data = [{"user":"jim","scores":[40,20,30,24,18,40]},
                {"user":"ray","scores":[24,20,30,41,12,34]}];
    
    var chart = d3.select("div#charts").append("svg")                                   
                  .data(data)
                  .attr("class","chart")
                  .attr("width",800)
                  .attr("height",350);
    
    chart.selectAll("rect")    
        .data(function(d){return d3.values(d.scores);})    
        .enter().append("rect")
        .attr("y", function(d,i){return i * 20;})
        .attr("width",function(d){return d;})
        .attr("height", 20);
    

    Could anyone point what I did wrong?

  • Ray
    Ray about 12 years
    Hi Mike, I am honored with getting help from you :) What I want in this example, is to obtain a single SVG image. Seems my mistake was not using .data([data]), just .data(data) instead. Unfortunately I can't check it at once, but I will do it this evening. I had skimmed "nested selections" before. That seemed to not cover this subject, but since you recommed it, I will deep dive into these tutorials. I will update this post. Thanks!
  • mbostock
    mbostock about 12 years
    Glad I could help. If this answered your question, please add the checkmark to resolve it. Thanks!
  • Ray
    Ray about 12 years
    Hello again, that solution doesn't work. This time there is no image at all this time :\
  • mbostock
    mbostock about 12 years
    If you need further assistance, update your question with additional details. Ideally, post a link to whatever you have working that demonstrates the problem. You can use JS Bin, JsFiddle or bl.ocks.org to host examples.
  • Ray
    Ray about 12 years
    As I don't want to waste your time to much, I am going to read the tutorials first. If this won't help, I will post again. Thanks.
  • Thomas Browne
    Thomas Browne over 9 years
    Actually for learning the enter-update-exit pattern I prefer Mike's "3 little circles", a more recent tutorial which makes it all extremely clear. bost.ocks.org/mike/circles
  • Shouvik
    Shouvik almost 9 years
    @Ray Consider this as a solution to your question?