1. 概述

在本文中,我们将研究如何在图表中自动布局网格线(gridline)的间隔。该算法特别适用于柱状图(bar chart)中的网格线设置,能够帮助我们选择“视觉上友好”的间隔数值,使得图表清晰易读。

2. 网格线间隔的放置策略

在之前的文章《为 y 轴选择线性刻度》中,我们讨论了如何在线图(line chart)中确定轴上的刻度位置。而本文则关注一个相关的算法,它更适合用于柱状图中网格线的间隔设定。

以一个表示分布的图表为例,比如某股票连续 10 天的每日价格图:

Rendered by QuickLaTeX.com

我们希望确定 y 轴上刻度的合理数量与位置。目标是找到一个合适的间隔值,使得观察值之间的相对位置能够清晰呈现。

3. 需要避免的常见错误

分布的范围是我们首先要考虑的约束条件。在这个范围内,我们需要大致均匀地布置网格线。但同时,我们也要避免两个常见错误:

太少的网格线,会导致图表信息不充分,难以判断数值之间的相对关系:

Rendered by QuickLaTeX.com

太多的网格线,则可能导致刻度重叠,视觉混乱:

Rendered by QuickLaTeX.com

4. 平衡才是关键

我们希望网格线既覆盖图表区域,又留有足够空间,让刻度与数值清晰可读。如果处理得当,最终效果如下图所示:

Rendered by QuickLaTeX.com

这样的图表既具有信息量,又不会显得拥挤。

5. 什么样的数值是“友好”的?

接下来,我们需要确定哪些数值适合作为网格线的刻度点。

视觉认知研究表明,1、2 和 5 的倍数最容易被人类理解。例如,左边的数值比右边的更“友好”:

友好 不友好
1 1.7
2 3
5 4.9
10 9

这一原则也适用于不同数量级:

友好 不友好
0.1 0.17
20 30
0.05 0.49
1000 900

这意味着我们可以建立一个与尺度无关的“友好数值”列表。只要我们在 [1,10] 区间内定义好一个列表,就可以通过乘以 10 的幂次来扩展到任意数量级。

例如:如果我们定义了一个 [1, 2, 2.5, 3, 5, 7, 7.5, 10] 的列表,就可以根据需要缩放为 10x、100x、0.1x 等。

6. 算法实现

我们可以设计一个算法,根据输入的数值范围和最大刻度数,自动选择合适的网格线间隔。

输入参数:

  • dataRange: 数据范围(如最大值 - 最小值)
  • maxTicks: 用户期望的最大刻度数

输出结果:

  • 一组“友好”的刻度值(tick marks)

算法步骤:

  1. 计算理想间隔:idealInterval = dataRange / maxTicks
  2. 找到最接近 idealInterval 的数量级(即 10 的幂)
  3. 在预定义的“友好数值”表中查找最接近的间隔值
  4. 根据这个间隔值生成刻度点列表

示例代码(Java):

public class NiceGridlines {
    private static final double[] NICE_VALUES = {1, 2, 2.5, 3, 5, 7, 7.5, 10};

    public static double calculateNiceInterval(double range, int maxTicks) {
        double rawInterval = range / maxTicks;
        double magnitude = Math.pow(10, Math.floor(Math.log10(rawInterval)));
        double relativeValue = rawInterval / magnitude;

        // Find the closest nice value
        for (int i = 0; i < NICE_VALUES.length; i++) {
            if (NICE_VALUES[i] >= relativeValue) {
                return NICE_VALUES[i] * magnitude;
            }
        }
        return NICE_VALUES[NICE_VALUES.length - 1] * magnitude;
    }

    public static List<Double> generateTicks(double min, double max, double interval) {
        List<Double> ticks = new ArrayList<>();
        double current = Math.ceil(min / interval) * interval;
        while (current <= max) {
            ticks.add(current);
            current += interval;
        }
        return ticks;
    }

    public static void main(String[] args) {
        double min = 0;
        double max = 100;
        int maxTicks = 5;

        double interval = calculateNiceInterval(max - min, maxTicks);
        List<Double> ticks = generateTicks(min, max, interval);

        System.out.println("Generated ticks: " + ticks);
    }
}

示例输出:

Generated ticks: [20.0, 40.0, 60.0, 80.0, 100.0]

算法流程图示意:

gridlines

7. 小结

本文介绍了一个用于自动选择“友好”网格线间隔的算法。该算法基于对人类视觉认知的理解,通过预定义的“友好数值”表,结合输入数据范围与最大刻度数,自动计算出最佳间隔,从而提升图表的可读性与视觉体验。


原始标题:Algorithm for “Nice” Grid Line Intervals on a Graph