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

时间:2014-07-02 21:26:42

标签: 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);

这给了我不规则间隔的格式良好的刻度:“16m 40s”,“33m 20s”等。如何在“10m 00s”,“20m 00s”等处生成刻度?

显而易见的答案是将values数组转换为分钟,使用线性比例并编写格式化程序来处理它,但我更愿意使用时间刻度

这是一个证明问题的JSFiddle:http://jsfiddle.net/83Xmf/

1 个答案:

答案 0 :(得分:27)

通常在制作时间刻度时,您会使用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;
};

基本上,这需要总秒数,每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));

这里输出的JSFiddle


选项2:使用.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对象,这需要引用绝对时间点。