我正在使用D3时标。我的输入数据是以秒为单位,它是持续时间数据而不是日期 - 所以10秒,30秒等等。
我想创建一个让我可以执行以下操作的轴:
这是我的代码:
var values = [100,200,300....]; // values in seconds
var formatCount = d3.format(",.0f"),
formatTime = d3.time.format("%Mm %Ss"),
formatMinutes = function(d) {
var t = new Date(2012, 0, 1, 0, 0, d);
t.setSeconds(t.getSeconds() + d);
return formatTime(t);
};
var x = d3.scale.linear()
.domain([0, d3.max(values)])
.range([0, width]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(formatMinutes);
这给了我不规则间隔的格式良好的刻度:“16m 40s”,“33m 20s”等。如何在“10m 00s”,“20m 00s”等处生成刻度?
显而易见的答案是将values数组转换为分钟,使用线性比例并编写格式化程序来处理它,但我更愿意使用时间刻度
这是一个证明问题的JSFiddle:http://jsfiddle.net/83Xmf/
答案 0 :(得分:27)
通常在制作时间刻度时,您会使用d3.time.scale()
,而不是线性刻度。
您的案例有点奇怪,因为您正在使用抽象的持续时间时间,而不是特定的点及时获取数据。不幸的是,似乎d3的内置时间功能并不适合这种情况。我可以考虑几种方法来解决变通方法:
.tickValues()
而不是使用Date
对象格式化刻度。您可以简单地将数据值(以秒为单位)分解为小时,分钟和秒。像这样:
formatMinutes = function(d) {
var hours = Math.floor(d / 3600),
minutes = Math.floor((d - (hours * 3600)) / 60),
seconds = d - (minutes * 60);
var output = seconds + 's';
if (minutes) {
output = minutes + 'm ' + output;
}
if (hours) {
output = hours + 'h ' + output;
}
return output;
};
基本上,这需要总秒数,每3600秒创建一个小时,每剩余60秒创建一分钟,最后返回剩余秒数。然后它输出一个字符串表示,例如:17s
或12m 42s
或4h 8m 22s
。
然后当您创建轴时,可以使用.tickValues()
方法将范围从零指定为数据的最大值,步长为600,因为在10分钟内有600秒。这看起来像这样:
var x = d3.scale.linear()
.domain([0, d3.max(values)])
.range([0, width]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(formatMinutes)
.tickValues(d3.range(0, d3.max(values), 600));
这里输出的JSFiddle。
.ticks()
时间刻度可让您直接指定每10分钟刻度一次。您只需将d3持续时间和乘数传递给轴的.ticks()
方法即可。像这样:
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(d3.time.minute, 10)
要执行此操作,您必须先设置时间刻度。对于您的比例域,您可以使用一系列毫秒值,因为d3会将这些值转换为Date
个对象。在这种情况下,由于您的数据以秒为单位,我们可以简单地乘以1000来获得毫秒。在这种情况下,我们将最大值向上舍入到最接近的毫秒,因为它必须是一个整数才能生成有效日期:
var x = d3.time.scale()
.domain([0, Math.ceil(d3.max(values) * 1000)])
.range([0, width]);
最后,您可以使用.tickFormat()
:
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(d3.time.minute, 10)
.tickFormat(d3.time.format('%Mm %Ss'));
然而,在这一点上,我需要指出一些问题,因为正如我所提到的,内置时间函数不适合处理抽象持续时间。我还要更改.tickFormat
以显示小时数:
.tickFormat(d3.time.format('%Hh %Mm %Ss'));
查看JSFiddle结果会是什么......
根据您在世界的哪个位置,您可以获得不同的小时值。我在美国东海岸,所以我的工作时间表示 19 。它来自哪里?它不应该为零吗?
嗯,不幸的是,当我们将比例域从0变为最大数据值的毫秒数时,它创建了常规Date
对象,使用毫秒输入的这些值。这意味着它们代表自1970年1月1日午夜UTC时间以来的毫秒数。在美国东部时区,这意味着它是1969年12月31日19:00:00。那就是19来自,或任何其他价值。
如果你知道你的所有数据都不到1小时,那么也许你可以忽略它。如果您需要使用小时位置,可以通过强制d3使用UTC时间来使用d3.time.format.utc()
格式化轴来解决此问题:
.tickFormat(d3.time.format.utc('%Hh %Mm %Ss'))
此处JSFiddle已更新为使用UTC。
现在你可以看到小时是0,正如预期的那样。
当然,如果您的任何数据超过24小时,这种方法根本不起作用,您必须手动操作轴,如选项1 强>
希望这有助于至少让你开始,但这是一个棘手的问题,而且似乎并不是一个内置于库中的优雅解决方案来处理这个问题。也许它会在d3的git repo上提出一个很好的功能请求。我很想知道@mbostock是否有任何关于如何处理d3中抽象持续时间的建议,而不必绑定Date
对象,这需要引用绝对时间点。