这是我之前使用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值将添加为零;
我会先说出来。虽然它在这个实例中完成了我想要的工作,但它距离优雅或可扩展还有很长的路要走。如果有人比我聪明的人能够看到如何减少攻击性,我将不胜感激。
答案 0 :(得分:10)
我的整体方法没有太大改进,但如果你使用一些更多的内置方法并添加下划线/ 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};
});
如果我们告诉它应该使用每月滴答,那么我们可以再次将ticks数组退出,而不是构建一个单独的桶数组。
然后从那时起我们只使用.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/
答案 1 :(得分:4)
这是使用d3.get()
而不是_.find()
而不使用lodash /下划线填充零的另一个选项。但不确定这会如何影响性能。
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};
});
答案 2 :(得分: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并在此发布。