Как обрабатывать слои с отсутствующими точками данных в 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 просто принимал нулевые значения для отсутствующих точек данных? Если нет, есть ли элегантное решение для заполнения недостающих значений?
Ответы
Ответ 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;
}