我可以在 d3.js 轴上定义最大刻度数吗?
Can I define a max number of ticks in a d3.js axis?
我有一个在 X 轴上使用 d3.scaleTime()
的 dc.js seriesChart。数据动态变化,从一周到几年不等,所以我没有为 scaleTime 定义域或范围。相反,我使用 dc.js elasticX(true)
, which I guess calculates them as needed. I'm also using a fake group to remove the empty bins,以便 X 轴在用户更改数据过滤器时自动刷新。
一切都按预期工作,但如果我只过滤一周,我得到的报价比我想要的要多。请注意,我只有一个数据点,但每天最多 4 个刻度:
我可以使用 axis.ticks
每天只生成一个价格变动:
chart.xAxis()
.tickFormat(d3.timeFormat('%Y-%m-%d'))
.ticks(d3.timeDay);
但随后它会创建 每天一个报价单。如果我 select 一整年,那就是其中 365 个重叠。
我不能使用 .ticks(n)
总是生成 n
刻度,因为那会带回我首先遇到的问题:当我设置的数字大于selected 天数。而且我也不能使用 .tickValues([ ])
,因为这些值是动态的。
有什么方法可以告诉 d3 在给定的时间间隔内生成滴答声,但限制为最大滴答声数? 所以在我的例子中,它实际上是 "daily if it fits, or every whatever days otherwise".
我发现的最接近的是 this answer, where they change the interval dynamically depending on the range. However, since I'm letting dc.js and Crossfilter deal with the data and filtering, it wouldn't be easy to calculate that. I guess I would have to attach a renderlet event to the chart, and use xAxisMin()
and xAxisMax()
?我认为它可以工作,但它看起来不太好。有更简单的方法吗?
我有第一个 "working" 解决方案...它比我想象的要丑陋:
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 事件).
不仅是丑陋的代码,轴过渡实际上在图表上可见。正如 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 都会再次计算这些值。
我有一个在 X 轴上使用 d3.scaleTime()
的 dc.js seriesChart。数据动态变化,从一周到几年不等,所以我没有为 scaleTime 定义域或范围。相反,我使用 dc.js elasticX(true)
, which I guess calculates them as needed. I'm also using a fake group to remove the empty bins,以便 X 轴在用户更改数据过滤器时自动刷新。
一切都按预期工作,但如果我只过滤一周,我得到的报价比我想要的要多。请注意,我只有一个数据点,但每天最多 4 个刻度:
我可以使用 axis.ticks
每天只生成一个价格变动:
chart.xAxis()
.tickFormat(d3.timeFormat('%Y-%m-%d'))
.ticks(d3.timeDay);
但随后它会创建 每天一个报价单。如果我 select 一整年,那就是其中 365 个重叠。
我不能使用 .ticks(n)
总是生成 n
刻度,因为那会带回我首先遇到的问题:当我设置的数字大于selected 天数。而且我也不能使用 .tickValues([ ])
,因为这些值是动态的。
有什么方法可以告诉 d3 在给定的时间间隔内生成滴答声,但限制为最大滴答声数? 所以在我的例子中,它实际上是 "daily if it fits, or every whatever days otherwise".
我发现的最接近的是 this answer, where they change the interval dynamically depending on the range. However, since I'm letting dc.js and Crossfilter deal with the data and filtering, it wouldn't be easy to calculate that. I guess I would have to attach a renderlet event to the chart, and use xAxisMin()
and xAxisMax()
?我认为它可以工作,但它看起来不太好。有更简单的方法吗?
我有第一个 "working" 解决方案...它比我想象的要丑陋:
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 事件).
不仅是丑陋的代码,轴过渡实际上在图表上可见。正如 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 都会再次计算这些值。