如何处理d3.layout.stack()中缺少数据点的图层

时间:2013-02-05 17:36:00

标签: d3.js

我正在使用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'。

有没有办法让d3.stack为丢失的数据点假设零值?如果没有,是否有一个优雅的解决方案来填补缺失的值?

3 个答案:

答案 0 :(得分:17)

这不是d3特定的,而是用于填充键控数据阵列中的间隙的一般解决方案。我用以下函数修改了你的jsfiddle here

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分配默认值。

答案 1 :(得分:5)

Stack实际上就是它所说的堆叠图,因此您作为用户负责以正确的格式提供数据。如果你考虑它,这是有道理的,因为堆栈基本上是数据格式不可知的。它提供了很大的灵活性,唯一的限制是每层可以访问相同数量的点。如何确定缺少哪些点?鉴于第一层有五个点而第二层有十个点,第一层是否缺少五个点?或者都是图层缺失点,因为第三层包含更多点。然后,如果缺少点,哪些?在开始时,最后,在中间的某个地方?同样,堆栈实现没有明智的方法来解决这个问题(除非它会强制使用非常严格的数据结构)。

所以,但你有什么办法吗?我想你可以。我不能给你一个完整的实现,但可以给你一些正确方向的指示。我们从这里开始:

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

这里只返回值,在您的示例中,这将是nest运算符的结果。因此,此时您可以“修复”这些值。

您需要做的第一件事是确定观察的最大数量。

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

现在是棘手的部分。一旦知道元素的最大数量,就需要进行调整 传递给值的函数。在这里,您必须对数据做出假设。 从你的问题我明白,对于一些群体,价值观缺失。所以有两个 可能性。您可以假设具有最大元素数量的组包含范围内的所有项目,或者您假定某个范围并检查所有组,如果它们 包含您范围内每个“tick”的值。因此,如果您的范围是一个日期范围(如您的 并且你希望每天(或者在任何时间间隔内)进行测量,你必须走在小组中的项目并自己填补空白。我将尝试给出一个(未经测试的)数值范围的例子:

// 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)
...

如上所述,这部分是未经测试的,并没有直接解决你的问题(因为我使用的是数值范围),但我认为它应该让你知道如何解决你的问题(以及问题的实际来源)是)。

答案 2 :(得分:1)

正如其他人所解释的那样,堆积图表猜测每个数据点的缺失值是不合理的,因为有很多方法可以插值,而且没有明显的选择。

但是,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; } 的部分。在下面进行插值:

{{1}}