【问题标题】:How to add in zero values into a time series in d3.js / JavaScript如何在 d3.js / JavaScript 中将零值添加到时间序列中
【发布时间】:2014-06-07 07:46:02
【问题描述】:

这是我之前使用 php 或(我认为是)不必要的复杂 MySQL 查询解决的问题,但我突然想到 JavaScript / d3.js 中必须有更优雅的解决方案。

假设我有一个日期和值的数据集,我想在 d3.js 中将其转换为条形图。

date,value
2013-01,53
2013-02,165
2013-03,269
2013-04,344
2013-05,376
2013-06,410
2013-07,421
2013-09,376
2013-10,359
2013-11,392
2013-12,433
2014-01,455
2014-02,478

您会注意到数据中没有第 8 个月(8 月)的条目。假设 8 月是零值,最终结果是生成的条形图看起来正常,但当然没有第 8 个月应该是间隙(零)。

我有一个jsfiddle of the script and data here 供参考。

我考虑过尝试添加一个填充为零的完整数据集,然后对其进行迭代以包含数据中的值,但这似乎也过于复杂。我假设有一个优雅的解决方案,我太无知了。

感谢您的帮助。

编辑#1:回应 explunit 的回答:

理想情况下,解决方案应该是对数据系列的操作,而不是仅适用于条形图。这意味着this jsfiddle 处的折线图的等价物将在其中间突然下降。

编辑#2:玩了一会儿之后:

在尝试了Google groups page here 上的建议后,我设法获得了一段代码来完成我正在寻找的事情。它获取时间戳数据,根据时间范围创建一个域,并创建一个具有不同月份的单独数组(在本例中)。然后我粗略地遍历这两个数组集,并将适合初始(未完全填充时间值)数组的值添加到所有时间值(并且数据值最初设置为零)的数组中。

最终结果是一个折线图,最初看起来像这样,因为它在 2013 年 7 月到 9 月之间迭代;

随后被渲染成这样,因为 August 的值将被添加为零;

有一个jsfiddle of the code here;

我会第一个说。虽然它完成了我在这种情况下想要的工作,但距离优雅或可扩展还有很长的路要走。如果比我更聪明的人能够看到如何减少冒犯性,我将不胜感激。

【问题讨论】:

  • 嗨 d3noob!我喜欢你非常有用的书。那么,你是说你想要一个差距......就像this 一样?我不清楚这个......对不起。
  • 一定是您没有幸知道特定数据点的数据丢失...因此您提到的参考零填充数据...嗯,会让你有空时插话:)
  • 谢谢蕨类植物。您对差距的看法是正确的,并且没有奢侈地知道数据没有与时间戳相关的零值是正确的。我将在下面探讨 explunit 的答案,看看是否符合要求。乍一看还不错:-)。
  • 事实上,现在搜索该主题时使用的假设是我正在尝试做的是使定量尺度像序数尺度一样。似乎谷歌群组上有一个帖子也提出了一些想法。 groups.google.com/forum/#!topic/d3-js/7GsDmnB4kdE

标签: javascript d3.js


【解决方案1】:

我对你的整体方法没有太大的改进,但是如果你使用更多的内置方法并添加下划线/lodash,你可以使数据转换更短:

x.domain(d3.extent(data, function(d) { return d.date; })).ticks(d3.time.month);
y.domain([0, d3.max(data, function(d) { return d.value; })]);

var newData = x.ticks().map(function(monthBucket) {
    return _.find(data, {date: monthBucket}) || {date: monthBucket, value: 0};
});

如果我们告诉它应该使用每月刻度,那么我们可以再次取出刻度数组,而不是构造一个单独的 buckets 数组。

然后从那时起,我们只使用.map 而不是for 循环和lodash(或下划线)_.find 方法来匹配我们的原始数据。 在这里更新小提琴:http://jsfiddle.net/a5jUz/3/


下面的原始答案...如果您想使用 D3 刻度在条形图上展开值:

1 - 您必须使用时间刻度而不是序数刻度:

var x = d3.time.scale().range([0, width]);

2 - 您需要根据日期范围的最小值/最大值设置该比例的域:

x.domain(d3.extent(data, function(d) { return d.date; })).nice();

3 - [丑陋的部分] 现在你没有使用序数比例,你没有用于条形定位的 rangeBand 函数:

  // TODO: calculate based on overall width & number of data points  
  .attr("x", function(d) { return x(d.date); })
  .attr("width", 16)

这里更新了小提琴: http://jsfiddle.net/LWyjf/

【讨论】:

  • 感谢这是一个聪明的解决方案,我认为它可以用于条形图,但需要稍微修改一下代码。很好的努力。
  • 但是......我的目标解决方案(我应该更好地表达它,抱歉)实际上会将零值引入数据。我的要求可能有点过分,我几乎可能会被指责移动了球门柱,但在理想情况下,该解决方案将适用于上面的条形图和每个小提琴的等效折线图 (jsfiddle.net/4kdm4)。我将在问题中添加更多描述。不过感谢您的回答!
  • @d3noob 根据您的说明添加了代码示例和小提琴
  • 哇!这看起来好得令人难以置信!我需要一天左右的时间来尝试理解代码,但我在那里看到的看起来非常聪明!干得好!
  • 好的,这是官方的。那岩石。我以前从来没有遇到过 Lo-Dash 或 Underscore,所以这对于一开始来说真的很聪明,并且使用 ticks 函数来平均间隔数组中的值是非常熟练的。好的。非常感谢。我将为D3 Tips and Tricks 这本书写一个部分,并包括对该技术的更长分解(在您允许的情况下,我将其称为“explunit 方法”。如果您有兴趣查看该部分,请给我写信:本书前面的电子邮件地址(介绍部分)
【解决方案2】:

为了改进@explunit 的答案,我更喜欢在将数据映射到域范围之前填充零,以便您获得不受域规模变化影响的完整数据集:

var date_range = d3.time.days(minX, maxX, 1);
var newData = date_range.map(function(dayBucket) {
    return _.find(data, function(d) {
        return d.date = dayBucket;
    } || {date: dayBucket, value: 0};
});

然后

x.domain(d3.extent(newData, function(d) { return d.date; })).ticks(d3.time.day);
y.domain([0, d3.max(newData, function(d) { return d.value; })]);

等等

我会尽快更新 JSFiddle 并在此处发布。

【讨论】:

  • 感谢@greenafrican,这是一个不错的解决方案,我可以看到我将来会使用它。我喜欢它不需要 lodash 库的事实(并不是说 lodash 不是很棒)。这里的两个答案都很好。
  • 它确实使用了 lodash 库 _.find,就像 @explunit 的回答一样 :)
  • 请原谅我的疏忽。确实如此。
【解决方案3】:

这是另一种不使用 lodash/下划线填充零的选项,使用 d3.get() 而不是 _.find()。不过不确定这会如何影响性能。

var date_range = d3.time.hours(startDate, endDate, 1);

var m = d3.map(data, function(d) { return d.date });
var newData = date_range.map(function(bucket) {
    return m.get(bucket) || {date: bucket, value: 0};
});

【讨论】:

    猜你喜欢
    • 2012-07-02
    • 2013-07-31
    • 1970-01-01
    • 2012-12-03
    • 1970-01-01
    • 2019-11-16
    • 1970-01-01
    • 2017-12-15
    • 1970-01-01
    相关资源
    最近更新 更多