D3.js时间刻度:当数据以秒为单位时,以分钟为间隔的间距很大的刻度线?

Ric*_*ard 9 d3.js

我正在使用D3时标.我的输入数据是以秒为单位,它是持续时间数据而不是日期 - 所以10秒,30秒等.

我想创建一个让我执行以下操作的轴:

  • 显示以分钟和秒为单位格式的刻度:如"0m 30s","1m 00s"等.这种格式本身相当简单,但是当我还需要...
  • 以分钟为单位格式化时,以一定的间隔显示刻度.如果我只是使用D3的默认刻度格式,那么我会在几分钟内有意义的间隔,但不是秒.

这是我的代码:

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的内置时间功能并不适合这种情况.我可以考虑几种方法来解决变通方法:


选项1:使用手动线性刻度 .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秒创建一分钟,最后返回剩余的秒数.然后它输出一个字符串表示,例如:17s12m 42s4h 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.


选项2:使用具有固定持续时间的时间刻度 .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需要引用绝对时间点的对象.

  • 仅供参考,formatMinutes函数将错误地计算秒数,因为它只考虑了分钟片段所消耗的秒数.它还应该考虑小时片段消耗的秒数. (2认同)