我正在使用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);
Run Code Online (Sandbox Code Playgroud)
这给了我不规则间隔的格式良好的刻度:"16m 40s","33m 20s"等.如何在"10m 00s","20m 00s"等处生成刻度?
显而易见的答案是将值数组转换为分钟,使用线性比例并编写格式化程序来处理它,但我更愿意尽可能使用时间刻度
这是一个JSFiddle来演示这个问题:http://jsfiddle.net/83Xmf/
jsh*_*ley 28
通常在制作时间刻度时,您将使用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;
};
Run Code Online (Sandbox Code Playgroud)
基本上,这需要总秒数,每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));
Run Code Online (Sandbox Code Playgroud)
这是输出的JSFiddle.
.ticks()时间刻度让您可以每10分钟直接指定您想要的刻度.您只需将d3持续时间和乘数传递给.ticks()轴的方法即可.像这样:
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(d3.time.minute, 10)
Run Code Online (Sandbox Code Playgroud)
为此,您必须先设置时间刻度.对于比例域,您可以使用一系列毫秒值,因为d3会将这些值转换为Date对象.在这种情况下,由于您的数据以秒为单位,我们可以简单地乘以1000来获得毫秒.在这种情况下,我们将最大值四舍五入到最接近的毫秒,因为它必须是一个整数才能生成有效日期:
var x = d3.time.scale()
.domain([0, Math.ceil(d3.max(values) * 1000)])
.range([0, width]);
Run Code Online (Sandbox Code Playgroud)
最后,您可以使用以下命令将格式直接传递给轴.tickFormat():
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(d3.time.minute, 10)
.tickFormat(d3.time.format('%Mm %Ss'));
Run Code Online (Sandbox Code Playgroud)
但是,在这一点上,我需要指出一些问题,因为正如我所提到的,内置时间函数不适合处理抽象持续时间.我要改变它.tickFormat来显示时间:
.tickFormat(d3.time.format('%Hh %Mm %Ss'));
Run Code Online (Sandbox Code Playgroud)
看看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'))
Run Code Online (Sandbox Code Playgroud)
这是使用UTC更新的JSFiddle.
现在你可以看到小时是0如预期的那样.
当然,如果您的任何数据超过24小时,则此方法根本不起作用,您将不得不像在选项1中那样手动操作轴.
希望这有助于至少让你开始,但这是一个棘手的问题,并且似乎没有一个优雅的解决方案内置到库中来处理这个问题.也许它会在d3的git repo上提出一个很好的功能请求.我很想知道@mbostock是否有任何关于如何处理d3中抽象持续时间的建议,而不必绑定到Date需要引用绝对时间点的对象.