D3 - как работать с структурами данных JSON?

Я новичок в D3 и потратил уже несколько часов, чтобы узнать что-нибудь о работе со структурированными данными, но без положительного результата. Я хочу создать гистограмму, используя структуру данных ниже. Бары рисуются (горизонтально), но только для пользователя "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);

Может ли кто-нибудь указать, что я сделал неправильно?

Ответ 1

Когда вы присоедините данные к выбору через selection.data, количество элементов в ваших данных массив должен соответствовать количеству элементов в элементе выбора. Ваш массив данных имеет два элемента (для Jim и Ray), но выбор, который вы связываете с ним, имеет только один элемент SVG. Вы пытаетесь создать несколько элементов SVG или поместили контрольные точки для обоих Jim и Ray в один и тот же элемент SVG?

Если вы хотите связать оба элемента данных с единственным элементом SVG, вы можете обернуть данные в другой массив:

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

В качестве альтернативы используйте selection.datum, который напрямую связывает данные без вычисления соединения:

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

Если вы хотите создать несколько элементов SVG для каждого человека, вам понадобится объединение данных:

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

Вторая проблема заключается в том, что вы не должны использовать d3.values ​​ с массивом; эта функция предназначена для извлечения значений объекта. Предполагая, что вам нужен один элемент SVG на человека (так, два в этом примере), тогда данные для rect - это просто связанные с человеком оценки:

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

Если вы еще этого не сделали, я рекомендую прочитать эти руководства:

Ответ 2

Это может прояснить вложенный аспект, помимо тонкого ответа mbostock.

Ваши данные имеют 2 степени вложенности. У вас есть массив из двух объектов, каждый из которых имеет массив ints. Если вы хотите, чтобы ваше окончательное изображение отражало эти различия, вам нужно сделать соединение для каждого.

Здесь одно решение: каждый пользователь представлен элементом группы g, каждый балл которого представлен rect. Вы можете сделать это несколькими способами: либо используйте datum на svg, либо функцию идентификации на каждом g, либо вы можете напрямую присоединиться к данным на g. Использование data в g более типично, но здесь оба способа:

Использование datum на 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);

Использование данных в группе (g):

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);

Опять же, вам не нужно создавать эти g-элементы, но тем самым я могу теперь представлять оценки пользователей по-разному (они имеют разные y из преобразования), и я также могу дать им разные стили, например:

.jim {
  fill: red;
}
.ray {
  fill: blue;
}