New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
d3.ticks returns too much ticks #61
Comments
This is the expected behavior. The given count is a hint, not an upper bound on the number of returned ticks. If you want an upper bound, I suggest looking at the implementation and forking it. |
Let me disagree, this is the unexpected behavior. I've tried to calculate how stable this function is. const countMin = 2;
const countMax = 100;
const step = 1;
const start = 0;
const max = 100;
const threshold = 0.25;
let maxError = -Infinity;
let worst = null;
let total = 0;
let failures = 0;
for (let end = start + step; end <= max; end += step) {
for (let count = countMin; count <= countMax; count++) {
let ticks = d3.ticks(start, end, count);
let error = (ticks.length - count) / count;
if (error > 0 && error > maxError) {
maxError = error;
worst = {
start,
end,
count,
result: ticks.length
};
}
total++;
if (error > threshold) {
failures++;
}
}
}
const percent = (x) => `${(x * 100).toFixed()}%`;
console.log(`> Worst result: d3.ticks(${worst.start}, ${worst.end}, ${worst.count})`);
console.log(`> Array(${worst.result}) (error ${percent(maxError)})`);
console.log(`> Error more than ${percent(threshold)} in ${percent(failures / total)} cases`); |
Happy to consider a change that improves the log-error rate but I believe the current implementation is optimal given the constraints. By “expected” I mean that the behavior is documented in the README (and has remained consistent since D3’s first release). If we wanted this functionality it would probably need to be a different method than d3.ticks (and by extension scale.ticks and axis.ticks), so it would be very difficult to change while retaining backwards compatibility. |
To address the two worst cases you mentioned: d3.ticks(0, 6, 2) tries to generate 3 (count + 1) ticks. The two closest possible results are [0, 2, 4, 6] (4 ticks) and [0, 5] (2 ticks). The log errors of these possibilities are |log(4) - log(3)| = 0.287682 and |log(2) - log(3)| = 0.405465 respectively. Log error is used to evaluate the length of the generated ticks array relative to the desired length. The first result is 4/3 = e^0.287682 = 1.333× too big; the second result is 3/2 = e^0.405465 = 1.5× too small. Therefore [0, 2, 4, 6] is considered the optimal result. Likewise d3.ticks(0, 22, 7) tries to generate 8 ticks. The closest two possible results are [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22] (12 ticks, log error of 0.405465) and [0, 5, 10, 15, 20] (5 ticks, log error of 0.470003). So the 12-tick result is considered optimal. |
I understand that this solution is based on log error, but I would prefer that error calculation would be based on aesthetics as far as finally we want to beautifully draw axis ticks. If I pass 2 ticks to I've already familiar with D3 v3 implementation, I'll read your notes considering ticks-issues and maybe come with a solution. Currently I workaround this by decreasing ticks count in a loop until resulting ticks count becomes lower. Here is a visualization of errors, there seems to be some dependency. |
d3.ticks isn’t used only for axis ticks; it’s used for a variety of other purposes where human-readable numbers are desired, such as for histogram.thresholds and contours.thresholds. Also, this API has no understanding of how “wide” a tick is, or how long an axis is. If you want to prevent overlapping axis ticks, you’re probably better off doing it at the display layer, say by post-processing the SVG generated by d3-axis. |
We are also facing the same kind of issues in V4 while drawing axes. In V3, as error calculation was different, ticks generated was better to read than in current version. For generating ticks, we are thinking to use V3 tick calculation method. It would be better if you provide some other solution. @mbostock |
I would prefer that ticks count would never be exceeded. This will prevent axis ticks overlap, when ticks count is calculated based on displayed tick size.
The text was updated successfully, but these errors were encountered: