2. 分布的表示

我们在之前的文章《使用 LaTeX 绘制图表》中讨论了表示分布的一般方法。同时,在《图的自动布局算法》一文中也提到,生成的图表应有助于读者理解内容。我们指出,并非所有的表示方式都一样,有些方式明显优于其他。

本篇文章将聚焦于如何为图表选择合适的线性刻度。特别是,我们将探讨如何确定图表 Y 轴的上下限,以及刻度线的位置。

我们会先通过一个例子说明问题,再提供解决方案。

3. 一个不合适的表示方式

我们先来看一个分布图,Y 轴设置了 5 个刻度:

Rendered by QuickLaTeX.com

从图中可以直观地看出 Y 轴刻度设置存在问题。我们期望散点图能覆盖大部分区域,而不是只集中在顶部。

此外,我们还能观察到:

  • 刻度线在没有数据的区域反而更密集
  • 在数据密集区(如 $10 \leq y \leq 16$)几乎没有刻度线
  • 某个刻度保留了两位小数,而其他都保留整数,风格不统一

因此,可以判断:Y 轴刻度的选择方式存在明显问题。

4. 更好的表示方式

现在我们来看一个改进后的图表,同样是 5 个刻度,但视觉效果明显更好:

Rendered by QuickLaTeX.com

这个图表一眼就能看出哪些数据点更高,以及大致高多少。✅

  • 刻度线分布均匀
  • 刻度值都是整数
  • 刻度范围略微超出了数据的最小值和最大值

5. 选择线性刻度的标准

根据上面两个图表的对比,我们可以总结出选择 Y 轴刻度时应遵循以下原则:

  • ✅ Y 轴范围应略低于最小值、略高于最大值
  • ✅ 刻度线应均匀分布在上下限之间
  • ✅ 优先选择“看起来顺眼”的数值(如整数、5 的倍数等)

第一个图表完全没遵循这些标准,而第二个图表则很好地满足了这些要求,所以视觉效果更好。

6. 刻度选择的实现步骤

我们可以将上述标准转化为一个清晰的实现流程:

步骤 1:获取数据范围

设原始数据的最小值为 lower,最大值为 upper,则:

$$ \text{range} = \text{upper} - \text{lower} $$

步骤 2:计算理想刻度间距

设我们希望显示 N 个刻度,则每个刻度之间的理想间隔为:

$$ \text{tick range} = \frac{\text{range}}{\text{N}} $$

比如这个值是 6.7,我们可以向上取整到一个“顺眼”的值,比如 10、5、2 或 1 等(优先级递减)。

步骤 3:调整上下限

根据调整后的刻度间隔 rounded tick range,重新计算 Y 轴的上下限:

  • 新的最小值:

$$ \text{new lower} = \text{rounded tick range} \times \left\lceil \frac{\text{old lower}}{\text{rounded tick range}} \right\rceil $$

  • 新的最大值:

$$ \text{new upper} = \text{rounded tick range} \times \left\lceil 1 + \frac{\text{old upper}}{\text{rounded tick range}} \right\rceil $$

⚠️ 注意:加 1 是为了避免上下限相等的边界情况。

步骤 4:生成刻度位置

new lower 开始,每次加上 rounded tick range,直到达到或超过 new upper

示例代码(Java 实现)

public class NiceScale {
    public static List<Double> getNiceTicks(double lower, double upper, int numTicks) {
        double range = upper - lower;
        double rawTickRange = range / numTicks;
        double roundedTickRange = niceRound(rawTickRange);

        double newLower = roundedTickRange * Math.ceil(lower / roundedTickRange);
        double newUpper = roundedTickRange * Math.ceil(1 + upper / roundedTickRange);

        List<Double> ticks = new ArrayList<>();
        for (double tick = newLower; tick <= newUpper; tick += roundedTickRange) {
            ticks.add(tick);
        }

        return ticks;
    }

    private static double niceRound(double value) {
        double exponent = Math.pow(10, Math.floor(Math.log10(value)));
        double fraction = value / exponent;

        if (fraction <= 1.0) return 1 * exponent;
        if (fraction <= 2.0) return 2 * exponent;
        if (fraction <= 5.0) return 5 * exponent;
        return 10 * exponent;
    }

    public static void main(String[] args) {
        List<Double> ticks = getNiceTicks(10.2, 15.8, 5);
        System.out.println("Nice ticks: " + ticks);
    }
}

输出结果类似:

Nice ticks: [10.0, 12.0, 14.0, 16.0, 18.0]

7. 小结

  • ✅ 刻度应略超出数据范围
  • ✅ 刻度值应为“顺眼”的数字(如 1、2、5、10 的倍数)
  • ✅ 刻度分布要均匀,避免集中在无数据区域
  • ✅ 实现逻辑清晰,适合封装成工具类(如上)

这样设置 Y 轴刻度,能让图表更直观、专业,也更利于读者理解数据分布。


原始标题:Choosing an Attractive Linear Scale for a Graph's Y Axis