我可以在d3.js轴上定义最大刻度数吗?

时间:2019-03-27 11:29:13

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

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

一切正常,但是如果我只过滤一个星期,我得到的滴答声就会超过我的期望。请注意,我每天只有一个数据点,但最多有4个滴答声:

Too many ticks

我可以使用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()?我认为它可以工作,但看起来不会太好。有更简单的方法吗?

1 个答案:

答案 0 :(得分: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。而且由于已经绘制了图表,因此我对轴所做的任何更改在下一次重绘之前都不会生效...所以我必须自己强制执行(并防止无限循环,因为我的重绘将触发新的renderlet事件)。

不仅丑陋的代码,而且轴过渡实际上在图表上也可见。正如戈登指出的那样,我可以通过使用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再次计算。