Гистограмма с накоплением в D3js - столбцы не в нужных местах

Я пытаюсь построить столбчатую диаграмму с накоплением в D3js. У меня проблемы с правильной установкой атрибутов y и y0 и рисованием полос на их правильных позициях. Возможно, у меня есть ошибка в расчетах, но я не могу ее найти. Это ссылка на пример кода FIDDLE. Сценарий следующий:

  1. Сначала я группирую данные по «периодам», и эти периоды отображаются на xAxis.
  2. Затем у меня есть группировка по "типу" - МЕСЯЦ и ВХОД, которые должны быть сложены столбиками разного цвета.
  3. Сумма «сумма» для каждого типа за каждый период отображается на yAxis.

Я использую функцию гнезда с двумя клавишами для структурирования данных. Проблема возникает, когда я рисую столбцы на фактической гистограмме с накоплением. Я не уверен, заключается ли проблема в способе доступа к данным (ключ и значения) или в том, как я устанавливаю атрибуты «y» и «height».

selection.selectAll("rect")
    .data(function (d) { return d.values; })
    .enter().append("rect")
    .attr("width", x.rangeBand())
    .attr("y", function (d) { return y(d.values); })
    .attr("height", function (d) { return y(d.y0) + y(d.values); })
    //.attr("height", function (d) { return y(d.y0) - y(d.values); })
    .style("fill", function (d) { return color(d.key); })

Очевидные ошибки заключаются в том, что одна из полосок скрыта за другой. И вторая полоса находится под xAxis.

Я новичок в d3js и не могу найти решение. Кто-нибудь может мне помочь?


person Ils    schedule 15.10.2014    source источник


Ответы (1)


Я вижу несколько вещей:

  1. Похоже, вы слишком усложняете nest. Вам нужно вложить только один уровень.
  2. Максимальное значение, которое вы вычисляете, всегда будет максимумом для одного элемента стека, когда вы действительно хотите, чтобы максимум равнялся общей сумме стека.
  3. Элементы группы, которую вы создаете (g), кажутся сгруппированными «неправильно». Обычно вы хотите сгруппировать один и тот же «бит» каждого стека. То есть вы хотите, чтобы первые rect каждого стека находились в той же группе, что и другие первые rect. Затем второй в каждой стопке будет сгруппирован с другими вторыми rect и так далее. Вероятно, это связано с ошибкой вложенности в первом пункте.
  4. Вам действительно нужно вычислить valueOffset, которое у вас есть в скрипке, но закомментировано. Это значение используется для установки относительной позиции при построении стека.

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

    var margin = {top: 20, right: 20, bottom: 30, left: 40},
    width = 400 - margin.left - margin.right,
    height = 400 - margin.top - margin.bottom;
            
var color = d3.scale.category10();

var data = [
    {
      "period":201409,
      "type":"MONTH",
      "amount":85.0
    },
    {
      "period":201409,
      "type":"ENTRY",
      "amount":111.0
    },
    {
      "period":201410,
      "type":"MONTH",
      "amount":85.0
    },
    {
      "period":201410,
      "type":"ENTRY",
      "amount":55.0
    }   
];
    
var x = d3.scale.ordinal().rangeRoundBands([0, width], .1, 0);
var y = d3.scale.linear().range([height, 0]);


var xAxis = d3.svg.axis()
                .scale(x)
                .orient("bottom");

var yAxis = d3.svg.axis()
                .scale(y)
                .orient("left").ticks(10);

var svg = d3.select("#chart")
                .append("svg")
                .attr("width", width + margin.left + margin.right)
                .attr("height", height + margin.top + margin.bottom)
                .append("g")
                .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
                
            data.forEach(function(d) {
                d["period"] = d["period"];
                d["amount"] = +d["amount"];
                d["type"] = d["type"];
            });

var nest = d3.nest()
                .key(function(d) { return d["type"];});

var dataByType = nest.entries(data);
//var max = d3.max(dataByGroup, function(d) { return d3.sum(d.values, function(e) { return e.values; }); })

            //console.log("dataByGroup", dataByGroup);  
var stack = d3.layout.stack()
                .values(function(d) { return d.values; })
                .x(function(d) { return d.period; })
                .y(function(d) { return d.amount; })
                .out(function(d, y0) { 
                  d.valueOffset = y0; 
                });
            
//data: key: group element, values: values in each group
stack(dataByType);
var yMax = d3.max(dataByType, function(type) { return d3.max(type.values, function(d) { return d.amount + d.valueOffset; }); });

color.domain(dataByType[0].values.map(function(d) { return d.type; }));
x.domain(dataByType[0].values.map(function(d) { return d.period; }));
y.domain([0, yMax]);
            
svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

svg.append("g")
    .attr("class", "y axis")
    .call(yAxis)
    .append("text")
    .attr("transform", "rotate(-90)")
    .attr("y", 3)
    .attr("dy", ".71em")
    .style("text-anchor", "end");

var selection = svg.selectAll(".group")
    .data(dataByType)
  .enter().append("g")
    .attr("class", "group");
    //.attr("transform", function(d) { return "translate(0," + y0(y0.domain()[0]) +  ")"; });

selection.selectAll("rect")
    .data(function (d) { return d.values; })
  .enter().append("rect")
    .attr("width", x.rangeBand())
    .attr("x", function(d) { return x(d.period); })
    .attr("y", function (d) { return y(d.amount + d.valueOffset); })
    .attr("height", function (d) { return y(d.valueOffset) - y(d.valueOffset + d.amount); })
    .style("fill", function (d) { return color(d.type); })
    .style("stroke", "grey");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="chart"></div>

Некоторые примечания к приведенному выше фрагменту (которые соответствуют моим комментариям):

  1. Гораздо более простое гнездо:

    var nest = d3.nest()
                 .key(function(d) { return d["type"];});
    

    Это намного проще, чем предыдущее, и нет необходимости выполнять функцию объединения. Свертывания обычно требуются, когда вы хотите агрегировать свои данные, в этом случае вам не нужно, что должно свидетельствовать о том, что ваше вложение было слишком сложным.

  2. Расчет максимального значения по оси y:

    var yMax = d3.max(dataByType, function(type) { return d3.max(type.values, function(d) { return d.amount + d.valueOffset; }); });
    

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

  3. Если вы посмотрите на получившийся SVG, вы поймете, что я имею в виду, говоря о группировке rect в каждом стеке. Я вообще считаю, что так группировать проще. Думаю, "правильного" пути не существует, но обычно он работает лучше всего для меня.

  4. Расчет valueOffset в stack:

    d3.layout.stack()
             .values(function(d) { return d.values; })
             .x(function(d) { return d.period; })
             .y(function(d) { return d.amount; })
             .out(function(d, y0) { 
               d.valueOffset = y0; 
             });
    

    Вычисленное valueOffset используется для «перемещения» каждого rect в стеке в положение относительно других rect. Вы увидите, что он использовался несколько раз, вычисляя максимальное значение y, атрибут y каждого rect и height каждого rect.

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

person Ben Lyall    schedule 15.10.2014
comment
Спасибо за развернутый ответ! Это действительно полезно. Я просто хочу спросить о максимальном значении y. Что касается вычислений, значение yMax становится больше, чем максимальное значение суммы в массиве данных JSON, потому что оно равно d.amount + d.valueOffset. Можно ли оставить его как максимальное значение в данных, а нижнее rect быть перед более высокими. Спасибо! - person Ils; 15.10.2014
comment
О, теперь я понимаю. Если у меня одинаковое значение для каждого типа группы, один из них также будет скрыт. Думаю, я неправильно понял логику диаграммы. - person Ils; 15.10.2014
comment
yMax используется только для установки максимального значения, отображаемого на оси y. Если вы собираетесь складывать значения, вам необходимо учесть максимум, к которому приведет стек. В случае ваших данных это 196 (85 + 111). Делая это таким образом, вы получите столбец, соответствующий значению 85, а затем столбец со значением 111, расположенный сверху. Если вы помещаете один прямоугольник перед другим, то на самом деле вы не создаете гистограмму с накоплением. - person Ben Lyall; 15.10.2014