【问题标题】:Can I define a max number of ticks in a d3.js axis?我可以在 d3.js 轴中定义最大刻度数吗?
【发布时间】:2019-03-27 11:29:12
【问题描述】:

我有一个在 X 轴上使用 d3.scaleTime() 的 dc.js seriesChart。数据动态变化,从一周到几年不等,所以我没有为 scaleTime 定义域或范围。相反,我使用的是 dc.js elasticX(true),我猜它会根据需要计算它们。我还使用了fake group to remove the empty bins,以便在用户更改数据过滤器时自动刷新 X 轴。

一切都按预期工作,但如果我只过滤一周,我会得到比我想要的更多的滴答声。请注意,我每天只有一个数据点,但最多 4 个滴答声:

我可以使用axis.ticks 每天只生成一个刻度:

chart.xAxis()
     .tickFormat(d3.timeFormat('%Y-%m-%d'))
     .ticks(d3.timeDay);

但是它会产生每天一个滴答声。如果我选择一整年,那其中有 365 个是重叠的。

我不能使用.ticks(n) 始终生成n 滴答声,因为这会带回我最初遇到的问题:当我设置的数字大于所选天数时重复滴答声。我也不能使用.tickValues([ ]),因为这些值是动态的。

有没有办法告诉 d3 在给定的时间间隔内生成刻度,但限制为最大刻度数?所以在我的例子中,它实际上是“如果它适合,或者每隔天”。

我发现最接近的是this answer,它们会根据范围动态更改间隔。但是,由于我让 dc.js 和 Crossfilter 处理数据和过滤,因此计算起来并不容易。我想我必须在图表中使用attach a renderlet event,并使用xAxisMin() and xAxisMax()?我认为它可以工作,但它看起来不太好。有没有更简单的方法?

【问题讨论】:

    标签: d3.js dc.js axis-labels


    【解决方案1】:

    我有第一个“可行”的解决方案......它比我想象的要丑:

    const chart = dc.seriesChart('#myChart')
        // ...
        .x(d3.scaleTime())
        .elasticY(true)
        .elasticX(true)
        .on('renderlet', updateTicks);
        .xAxis()
            .tickFormat(d3.timeFormat('%Y-%m-%d'))
            .ticks(10); // We set 10 ticks by default
    chart.render();
    
    // Needed to prevent infinite loops...
    let redrawing = false;
    
    function updateTicks(chart) {
        if (redrawing) {
            redrawing = false;
        } else {
            const days = (chart.xAxisMax() - chart.xAxisMin()) / 86400000;
            if (days < 10) {
                chart.xAxis().ticks(d3.timeDay);
            } else {
                chart.xAxis().ticks(10);
            }
            redrawing = true;
            chart.redraw();
        }
    }
    

    xAxisMin()xAxisMax() 值在图表已经被渲染或重绘之前不会被计算,所以我必须使用renderlet 事件而不是preRedraw。而且由于图表已经绘制,我对轴所做的任何更改都将在下一次重绘之前生效......所以我必须自己强制它(并防止无限循环,因为我的重绘将触发一个新的渲染组件事件)。

    这不仅是丑陋的代码,轴的转换实际上在图表上是可见的。正如 Gordon 所指出的,我可以通过使用 pretransition 事件来避免它。或者通过直接从 Crossfilter 组计算 preRender/preRedraw 事件的最小值和最大值,但它开始感觉像是一个巨大的矫枉过正。


    这是一个可行但仍然丑陋的解决方案,我自己计算最小值和最大值:

    const chart = dc.seriesChart('#myChart')
        // ...
        .x(d3.scaleTime())
        .elasticY(true)
        .elasticX(true)
        .on('preRender', updateTicks),
        .on('preRedraw', updateTicks);
        .xAxis().tickFormat(d3.timeFormat('%Y-%m-%d'));
    chart.render();
    
    function updateTicks(chart) {
        const range = chart.group().all().reduce((accum, d) => minMax(d.key.date, accum), {});
        const days = 1 + ((new Date(range.max) - new Date(range.min)) / 86400000);
        if (days <= 7) {
            chart.xAxis().ticks(d3.timeDay);
        } else if (days <= 30) {
            chart.xAxis().ticks(d3.timeMonday);
        } else {
            chart.xAxis().ticks(d3.timeMonth);
        }
    }
    
    function minMax(val, obj) {
        if ((obj.min === undefined) || (val < obj.min)) {
            obj.min = val;
        }
        if ((obj.max === undefined) || (val > obj.max)) {
            obj.max = val;
        }
        return obj;
    }
    

    代码本身并不难看,只是很烦人 - 每次我重绘图表时都要遍历整个数据数组,只是为了找到最小值和最大值,无论如何这将由 dc/crossfilter/d3 再次计算。

    【讨论】:

    • 你能用pretransition代替renderlet吗?那应该是在绘制之后但无需等待转换完成。我真的很讨厌额外的重绘……preRedraw 可以解决所有这些问题,也许你可以尝试使用chart.x().domain()[0]chart.x().domain()[1] 而不是xAxisMinxAxisMax(我很惊讶不要' t 工作,因为重绘是在渲染之后)。我想我需要一个运行示例来进一步帮助...
    • 谢谢@Gordon。 pretransition 可以解决可见的重绘,但它仍然存在于代码中,困扰着我...... :)。我让它与preRedraw preRender一起工作,我自己寻找最小值和最大值(chart.group().all().reduce( ... )),我也讨厌。我将尝试使用.x().domain(),或者如果它不起作用,请尝试包含一个 MCVE fiddle。
    • 不。在“pre”事件中,x().domain() 仍然具有当前值,而不是用于重绘的值。与xAxisMin() 相同。所以我猜要么是pretransition 并强制再次重绘,要么自己获取最小值和最大值。
    • 我不知道是什么让数据变得陈旧。我猜你要换组? xAxisMin 应该在组被替换后立即反映当前组数据。我敢打赌这与系列图表有关。明天我会尝试重现。
    • 好的,我添加了一个系列进度示例,并且能够重现此问题。问题是系列图表在重绘时会创建数据的静态嵌套副本,并将其传递给子图表。复合图表xAxisMin 代表子级,因此您会看到陈旧的数据。我认为您的新解决方案与当前架构一样好。 :-/ 这与其他图表无关。
    猜你喜欢
    • 1970-01-01
    • 2013-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多