我在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()
?我认为它可行,但看起来不太好。有更简单的方法吗?
我有一个第一个“工作”的解决方案......它比我想象的还要丑陋:
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
事件来避免它,正如Gordon指出的那样。或者通过直接从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再次计算。