【问题标题】:D3.js time scale: nicely-spaced ticks at minute intervals when data is in seconds?D3.js 时间尺度:当数据以秒为单位时,以分钟为间隔的间隔良好的滴答声?
【发布时间】:2014-07-02 21:26:41
【问题描述】:

我正在使用 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”等处生成刻度?

显而易见的答案是将值数组转换为分钟,使用线性刻度并编写格式化程序来处理它,但如果可能的话我更喜欢使用时间刻度

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

【问题讨论】:

    标签: d3.js


    【解决方案1】:

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

    【讨论】:

    • 很好的答案,谢谢!在编写我的初始代码时,我参考了@mbostock 的这个答案,该答案建议以您建议的方式处理持续时间数据:stackoverflow.com/a/11287084/194000 正如您所说,这不是最优雅的解决方案,但它似乎是 D3 中唯一的解决方案.
    • 仅供参考,formatMinutes 函数将错误地计算秒数,因为它只考虑分钟片段消耗的秒数。它还应该考虑小时片段消耗的秒数。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-10-16
    • 2013-08-29
    • 2014-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多