読者です 読者をやめる 読者になる 読者になる

d3のd3.interpolateObjectで返される補間オブジェクトが共有化されてる

issue:1030

d3.interpolateObjectは2つのオブジェクトを補間してくれるので欠損データの扱いとかが楽にできるんですが、バグ?というか、ハマるところ。

var ts = [
  {time: new Date(2012, 0, 1), sum: 132435},
  {time: new Date(2012, 0, 2), sum: 133450},
  {time: new Date(2012, 0, 3), sum: 164345},
  {time: new Date(2012, 0, 4), sum: 133100},
  {time: new Date(2012, 0, 8), sum: 46345},
  {time: new Date(2012, 0, 9), sum: 136345},
  {time: new Date(2012, 0, 10), sum: 106345}
];

のようなデータがあるけど、Holt-Wintersとかで使うには都合が悪い これを一定間隔にしないと行けない。補間の方法は、線形補完とか、スプラインとかいろいろあるけど、それは割愛

で、時間を埋めるのに

var idx = 0;
var prev = ts[idx], cur, cnt;
while(idx++ < ts.length - 1){
  cur = ts[idx];
  if(cur.time - prev.time > 24*3600*1000){
    cnt = (cur.time - prev.time) / 24 * 3600 * 1000;
    var ip = d3.interpolateObject(prev, cur);
    d3.range(1, cnt).forEach(function(d){
      ts.splice(idx++, 0, ip(d/cnt));
    });
  }
}

とすると、timeがnew Date(2012, 0, 7)のものでうめつくされて困る。

これは、最初にあげたissueでもあるように、.interpolateObject()がパフォーマンスを考え、共有オブジェクトの参照を返してくるから。pull reqも考えたんだけど、手元で対応するほうがいい気もして、悩ましい。手元でやるなら、循環参照さえないことがわかっていれば*1、さくっと

      ts.splice(idx++, 0, JSON.parse(JSON.stringify(ip(d/cnt))));

としてあげたほうがよい

*1:普通のデータ配列には参照は無いはず