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

Как обрабатывать слои с отсутствующими точками данных в d3.layout.stack()

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

[  
   {key:'Group1',value,date},  
   {key:'Group1',value,date},  
   {key:'Group1',value,date},  
   {key:'Group2',value,date},  
   {key:'Group2',value,date}  
]

и после того, как я запустил его через nest() и stack(), я получаю этот формат, как и ожидалось:

[  
   {key: 'Group1',  
    values: [ {key,value,date}, {key,value,date}, {key,value,date} ] },  
   {key: 'Group2',  
    values: [ {key,value,date}, {key,value,date} ]  }  
]

Я немного изменил образец уложенной области, чтобы продемонстрировать проблему в этом jsFiddle: http://jsfiddle.net/brentkeller/rTC3c/2/

Если вы удалите любую из точек данных в массиве sourceData, вы увидите сообщение об ошибке "Невозможно прочитать свойство" 1 "из undefined" в консоли.

Есть ли способ, чтобы d3.stack просто принимал нулевые значения для отсутствующих точек данных? Если нет, есть ли элегантное решение для заполнения недостающих значений?

4b9b3361

Ответ 1

Это не d3, а скорее общее решение для заполнения пробелов в массиве данных с ключами. Я изменил ваш jsfiddle здесь со следующей функцией:

function assignDefaultValues( dataset )
{
    var defaultValue = 0;
    var keys = [ 'Group1' , 'Group2', 'Group3' ];
    var hadData = [ true, true, true];
    var newData = [];
    var previousdate = new Date();
    var sortByDate = function(a,b){ return a.date > b.date ? 1 : -1; };

    dataset.sort(sortByDate);
    dataset.forEach(function(row){
        if(row.date.valueOf() !== previousdate.valueOf()){
            for(var i = 0 ; i < keys.length ; ++i){
                if(hadData[i] === false){
                    newData.push( { key: keys[i], 
                                   value: defaultValue, 
                                   date: previousdate });
                }
                hadData[i] = false;
            }
            previousdate = row.date;
        }
        hadData[keys.indexOf(row.key)] = true; 
    });
    for( i = 0 ; i < keys.length ; ++i){
        if(hadData[i] === false){
            newData.push( { key: keys[i], value: defaultValue, 
                            date: previousdate });
        }
    }
    return dataset.concat(newData).sort(sortByDate);
}

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

Ответ 2

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

Итак, но вы ничего не можете сделать? Думаю, что сможешь. Я не могу дать вам полную реализацию, но могу дать вам несколько указателей в правильном направлении. Мы начинаем здесь:

var stack = d3.layout.stack()
  .offset("zero")
  .values(function(d) { return d.values; })

Здесь вы просто возвращаете значения, которые в вашем примере будут результатом оператора гнезда. Поэтому на данный момент у вас есть возможность "исправить" значения.

Первое, что вам нужно сделать, это определить максимальное количество наблюдений.

var nested = nest.entries(data);
var max = nested.reduce(function(prev, cur) {
  return Math.max(prev, cur.values.length);
}, 0);

Теперь сложная часть. Как только вы узнаете максимальное количество элементов, вам нужно будет настроить функция, которая передается значениям. Здесь вам нужно будет сделать предположения о данных. Из вашего вопроса я понимаю, что для некоторых групп значения отсутствуют. Таким образом, есть два возможности. Либо вы предполагаете, что группа с максимальным количеством элементов содержит все элементы в диапазоне или вы принимаете определенный диапазон и проверяете все группы, если они содержат значения для каждого "галочки" в вашем диапазоне. Поэтому, если ваш диапазон - это диапазон дат (как в вашем пример), и вы ожидаете, что каждый день (или какой-нибудь интервал, если на то пошло) измерение, вам придется ходить по элементам в группе и заполнять пробелы самостоятельно. Я попробую дать (непроверенный) пример для числового диапазона:

// define some calculated values that can be reused in correctedValues
var range = [0, 1];
var step = 0.1;

function correctedValues(d) {
  var values = d.values;
  var result = [];
  var expected = 0;
  for (var i = 0; i < values.length; ++i) {
     var value = values[i];
     // Add null-entries
     while (value.x > expected) {
       result.push({x: expected, otherproperties_you_need... });
       expected += step;
     }
     result.push(value); // Now add the real data point.
     expected = value.x;
  }

  // Fill up the end of of the array if needed
  while(expected < range[1]) {
    result.push({x: expected, otherproperties_you_need... });
    expected += step;
  }
  return result;
}

// Now use our costom function for the stack
var stack = d3.layout.stack()
 .offset("zero")
 .values(correctedValues)
...

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

Ответ 3

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

Однако d3.svg.line(), по-видимому, предлагает разумный способ выбрать собственный метод интерполяции и заполнить отсутствующие значения. Хотя он предназначен для создания путей SVG, вы можете, вероятно, адаптировать его для определения линий в целом. Здесь предлагаются методы интерполяции:

https://github.com/mbostock/d3/wiki/SVG-Shapes#wiki-line_interpolate

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

Однако на данный момент вы можете просто создать вилку d3 и выполнить промежуточный результат line(data) до того, как она будет записана в строку SVG path, в части источник, который выполняет интерполяцию ниже:

  function line(data) {
    var segments = [],
        points = [],
        i = -1,
        n = data.length,
        d,
        fx = d3_functor(x),
        fy = d3_functor(y);

    function segment() {
      segments.push("M", interpolate(projection(points), tension));
    }

    while (++i < n) {
      if (defined.call(this, d = data[i], i)) {
        points.push([+fx.call(this, d, i), +fy.call(this, d, i)]);
      } else if (points.length) {
        segment();
        points = [];
      }
    }

    if (points.length) segment();

    return segments.length ? segments.join("") : null;
  }