Подтвердить что ты не робот

D3 доступ к вложенным данным в сгруппированной гистограмме

Я строю сгруппированную гистограмму, вставляя CSV файл. Диаграмма также будет отображаться в виде линейной диаграммы, поэтому я хочу, чтобы структура вложенности соответствовала объекту линии. Мой оригинальный .csv выглядит следующим образом:

Month,Actual,Forecast,Budget
Jul-14,200000,-,74073.86651
Aug-14,198426.57,-,155530.2499
Sep-14,290681.62,-,220881.4631
Oct-14,362974.9,-,314506.6437
Nov-14,397662.09,-,382407.67
Dec-14,512434.27,-,442192.1932
Jan-15,511470.25,511470.25,495847.6137
Feb-15,-,536472.5467,520849.9105
Mar-15,-,612579.9047,596957.2684
Apr-15,-,680936.5086,465313.8723
May-15,-,755526.7173,739904.081
Jun-15,-,811512.772,895890.1357

и моя вложенность такова:

  d3.csv("data/net.csv", function(error, data) {
    if (error) throw error;

            var headers = d3.keys(data[0]).filter(function(head) {
            return head != "Month";
          });

                  data.forEach(function(d) {
                    d.month = parseDate(d.Month);
          });
            var categories = headers.map(function(name) { 

              return {
                name: name, // "name": the csv headers except month
                values: data.map(function(d) { 
                  return {
                    date: d.month, 
                    rate: +(d[name]),
                    };
                }),
              };

            });

Код для построения моей диаграммы:

  var bars = svg.selectAll(".barGroup")
        .data(data) // Select nested data and append to new svg group elements
        .enter()
        .append("g")
        .attr("class", "barGroup")   
        .attr("transform", function (d) { return "translate(" + xScale(d.month) + ",0)"; });

  bars.selectAll("rect")
        .data(categories)
        .enter()
        .append("rect")
        .attr("width", barWidth)
        .attr("x", function (d, i) { if (i < 2) {return 0;} else {return xScale.rangeBand() / 2;}})
        .attr("y", function (d) { return yScale(d.rate); })
        .attr("height", function (d) { return h - yScale(d.rate); })
        .attr("class", function (d) { return lineClass(d.name); });

Элементы g являются точными, а отдельные столбцы сопоставляются с ними, причем значение x и класс применяются правильно.

Моя проблема связана с доступом к данным для "rate" для значений высоты и y. В приведенной выше форме дается NaN. Я также попытался использовать данные категории, чтобы добавить g-элементы, а затем добавить исправления с помощью:

  .data(function(d) { return d.values })

Это позволяет мне получить доступ к данным скорости, но отображает все 36 баров в каждую из диапазонов диапазонов.

Он также отлично работает в более плотной структуре данных, но я не могу использовать его, когда он вложен на два уровня, несмотря на то, что он просматривает множество примеров и вопросов SO.

Как мне получить доступ к данным скорости?

В ответ на запрос Кирилла, здесь полный код:

    var margin = {top: 20, right: 18, bottom: 80, left: 50},
        w = parseInt(d3.select("#bill").style("width")) - margin.left - margin.right,
        h = parseInt(d3.select("#bill").style("height")) - margin.top - margin.bottom;

    var customTimeFormat = d3.time.format.multi([
      [".%L", function(d) { return d.getMilliseconds(); }],
      [":%S", function(d) { return d.getSeconds(); }],
      ["%I:%M", function(d) { return d.getMinutes(); }],
      ["%I %p", function(d) { return d.getHours(); }],
      ["%a %d", function(d) { return d.getDay() && d.getDate() != 1; }],
      ["%b %d", function(d) { return d.getDate() != 1; }],
      ["%b", function(d) { return d.getMonth(); }],
      ["%Y", function() { return true; }]
    ]);


    var parseDate = d3.time.format("%b-%y").parse;

    var displayDate = d3.time.format("%b %Y");

    var xScale = d3.scale.ordinal()
        .rangeRoundBands([0, w], .1);

    var xScale1 = d3.scale.linear()
          .domain([0, 2]);

    var yScale = d3.scale.linear()
         .range([h, 0])
         .nice();

    var xAxis = d3.svg.axis()
        .scale(xScale)
        .tickFormat(customTimeFormat)
        .orient("bottom");

    var yAxis = d3.svg.axis()
        .scale(yScale)
        .orient("left")
        .innerTickSize(-w)
        .outerTickSize(0);

    var svg = d3.select("#svgCont")
        .attr("width", w + margin.left + margin.right)
        .attr("height", h + margin.top + margin.bottom)
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    var thous = d3.format(",.0f")

    var lineClass = d3.scale.ordinal().range(["actual", "forecast", "budget"]);  

    var tip = d3.tip()
      .attr('class', 'd3-tip')
      .offset([-10, 0])
      .html(function(d) {
        return "<p id='date'>" + displayDate(d.date) + "</p><p id='value'>$" + thous(d.rate);
      })

    d3.csv("data/net.csv", function(error, data) {
      if (error) throw error;

              var headers = d3.keys(data[0]).filter(function(head) {
              return head != "Month";
            });

                    data.forEach(function(d) {
                      d.month = parseDate(d.Month);
            });
              var categories = headers.map(function(name) { 

                return {
                  name: name, 
                  values: data.map(function(d) {
                    return {
                      date: d.month, 
                      rate: +(d[name]),
                      };
                  }),
                };

              });

    var min = d3.min(categories, function(d) {
                        return d3.min(d.values, function(d) {
                            return d.rate;
                        });
                    });



    var max = d3.max(categories, function(d) {
                        return d3.max(d.values, function(d) {
                            return d.rate;
                        });
                    });

    var minY = min < 0 ? min * 1.2 : min * 0.8;

                  xScale.domain(data.map(function(d) { return d.month; }));
                  yScale.domain([minY, (max * 1.1)]);

    var barWidth = headers.length > 2 ? xScale.rangeBand() / 2 : xScale.rangeBand() ;

    svg.call(tip);

    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + h + ")")
        .call(xAxis);

    svg.append("g")
          .attr("class", "y axis")
          .call(yAxis);

    var bars = svg.selectAll(".barGroup")
          .data(data) 
          .enter()
          .append("g")
          .attr("class", "barGroup")   
          .attr("transform", function (d) { return "translate(" + xScale(d.month) + ",0)"; });

    bars.selectAll("rect")
          .data(categories)
          .enter()
          .append("rect")
          .attr("width", barWidth)
          .attr("x", function (d, i) { if (i < 2) {return 0;} else {return xScale.rangeBand() / 2;}})
          .attr("y", function (d) { return yScale(d.rate); })
          .attr("height", function (d) { return h - yScale(d.rate); })
          .attr("class", function (d) { return lineClass(d.name) + " bar"; });


    var legend = svg.selectAll(".legend")
          .data(headers) 
          .enter()
          .append("g")
          .attr("class", "legend");

    legend.append("line")
          .attr("class", function(d) { return lineClass(d); })
          .attr("x1", 0)
          .attr("x2", 40)
          .attr("y1", function(d, i) { return (h + 30) + (i *14); })
          .attr("y2", function(d, i) { return (h + 30) + (i *14); });

    legend.append("text")
        .attr("x", 50)
        .attr("y", function(d, i) { return (h + 32) + (i *14); })
        .text(function(d) { return d; });

    svg.selectAll(".bar")
       .on('mouseover', tip.show)
       .on('mouseout', tip.hide);

    });

Обновление 18 февраля '16.

Кажется, я не объяснил, что я пытался сделать достаточно хорошо. Линейные и штриховые варианты диаграммы будут видны отдельно, то есть пользователи могут видеть один из них в соответствии с вводом элемента выбора. Также обратите внимание, что я не контролирую, как данные поступают изначально.

У меня версия того, как она должна работать здесь.

Этот вопрос был поднят, когда я все еще работал над ним, но я никогда не решал проблему - я использовал обходное решение для создания двух отдельных гнезд данных.

4b9b3361

Ответ 1

Ссылка на jsfiddle: https://jsfiddle.net/sladav/rLh4qwyf/1/

Я думаю, что корень проблемы в том, что вы хотите использовать две переменные, которые явно не существуют в вашем исходном наборе данных: (1) Категория и (2) Оценка.

Ваши данные форматируются в широком формате, поскольку каждая категория получает свою собственную переменную, а значение ставки существует на перекрестке месяца и одной из данных категорий. Я думаю, что способ, которым вы гнездились, в конечном счете, или, по крайней мере, должен решить это, но мне непонятно, где или где что-то теряется в переводе. Концептуально, я думаю, что имеет смысл начать с организации, которая соответствует тому, что вы пытаетесь выполнить. Я переформатировал исходные данные и снова приблизился к нему - на концептуальном уровне вложение кажется простым и простым...

НОВЫЕ КОЛОННЫ:

  • Месяц: переменная времени; отображается на ось X
  • Категория: категориальные ценности [фактический, прогноз, бюджет]; используется для группировки/цвета
  • Тариф: числовое значение; отображается на ось Y

Реорганизованный CSV (сброшенные значения NULL):

Month,Category,Rate
Jul-14,Actual,200000
Aug-14,Actual,198426.57
Sep-14,Actual,290681.62
Oct-14,Actual,362974.9
Nov-14,Actual,397662.09
Dec-14,Actual,512434.27
Jan-15,Actual,511470.25
Jan-15,Forecast,511470.25
Feb-15,Forecast,536472.5467
Mar-15,Forecast,612579.9047
Apr-15,Forecast,680936.5086
May-15,Forecast,755526.7173
Jun-15,Forecast,811512.772
Jul-14,Budget,74073.86651
Aug-14,Budget,155530.2499
Sep-14,Budget,220881.4631
Oct-14,Budget,314506.6437
Nov-14,Budget,382407.67
Dec-14,Budget,442192.1932
Jan-15,Budget,495847.6137
Feb-15,Budget,520849.9105
Mar-15,Budget,596957.2684
Apr-15,Budget,465313.8723
May-15,Budget,739904.081
Jun-15,Budget,895890.1357

С вашими вновь отформатированными данными вы начнете с использования d3.nest, чтобы GROUP ваши данные явно с переменной CATEGORY. Теперь ваши данные существуют в двух уровнях. Первый уровень состоит из трех групп (по одной для каждой категории). Второй уровень содержит данные RATE для каждой строки/набора баров. Вы также должны сами разбить свои данные - первый слой используется для рисования линий, второго слоя для баров.

Вложение ваших данных:

var nestedData = d3.nest()
      .key(function(d) { return d.Category;})
      .entries(data)

Создайте группы svg для ваших сгруппированных данных 1-го уровня:

d3.select(".plot-space").selectAll(".g-category")
    .data(nestedData)
    .enter().append("g")
    .attr("class", "g-category")

Используйте эти данные, чтобы добавить свои линии/пути:

d3.selectAll(".g-category").append("path")
    .attr("class", "line")
    .attr("d", function(d){ return lineFunction(d.values);})
    .style("stroke", function(d) {return color(d.key);})

Наконец, "перейдите в" второй уровень ", чтобы добавить бары/прямоугольник:

d3.selectAll(".g-category").selectAll(".bars")
     .data(function(d) {return d.values;})
     .enter().append("rect")
        .attr("class", "bar")
        .attr("x", function(d) {return x(d.Month);})
        .attr("y", function(d) {return y(d.Rate);})
        .attr("width", 20)
        .attr("height", function(d) {return height - y(d.Rate)})
        .attr("fill", function(d) {return color(d.Category)})

Это простой подход (по крайней мере, для меня), поскольку вы берете его по одной категории за раз, используя сгруппированные данные для рисования строки, затем отдельные данные указывают на рисование баров.

LAZY EDIT:

Чтобы получить боксы категории бок о бок

Создайте категорию сопоставления порядкового масштаба до [1, nCategories]. Используйте это для динамически смещенных баров с чем-то вроде

translate( newScale(category)*barWidth )

Чтобы отобразить строки или строки (не оба)

Создайте функцию, которая выбирает полосы/линии и переходы/переключает их видимость/непрозрачность. Запустите, когда ваш раскрывающийся вход изменится и с выпадающим входом в качестве входа в функцию.

Ответ 2

Проблема, я верю, заключается в том, что вы привязываете массив категорий к выбору столбцов, например:

bars.selectAll("rect").data(categories)

Насколько я могу видеть (без использования демонстрационной версии), это массив с четырьмя значениями (по одному для каждой категории).

Вам нужно сделать один шаг "глубже" в вашей вложенной структуре данных.

Чтобы нарисовать набор баров для каждой категории, вам нужно будет перебирать по категориям и привязывать массив значений, который содержит фактические значения для выбора.

Что-то вроде:

  categories.each(function (category) {
    var klass = category.name;
    bars.selectAll("rect ." + klass)
        .data(category.values)
        .enter()
        .append("rect")
        .attr("class", klass)
        .attr("width", barWidth)
        .attr("x", function (d, i) { /* omitted */})
        .attr("y", function (d) { return yScale(d.rate); })
        .attr("height", function (d) { return h - yScale(d.rate); });
  });

---- Редактировать

Вместо приведенного выше кода подумайте о том, как рисовать столбцы так же, как и линии. Вот так:

var bars = svg.selectAll(".barGroup")
    .data(categories)
    .enter()
    .append("g")
    .attr("class", function (d) { return lineClass(d.name) + "Bar barGroup"; })
    .attr("transform", function (d, i) {
        var x = i > 1 ? xScale.rangeBand() / 2 : 0;
        return "translate(" + x + ",0)";
    })
    .selectAll('rect')
    .data(function (d) { return d.values; })
    .enter()
    .append("rect")
    .attr("class", "bar")
    .attr("width", barWidth)
    .attr("x", function (d, i) { return xScale(d.date); })
    .attr("y", function (d, i) { return yScale(d.rate); })
    .attr("height", function (d) { return h - yScale(d.rate); });