2. 分布的表示
我们在之前的文章《使用 LaTeX 绘制图表》中讨论了表示分布的一般方法。同时,在《图的自动布局算法》一文中也提到,生成的图表应有助于读者理解内容。我们指出,并非所有的表示方式都一样,有些方式明显优于其他。
本篇文章将聚焦于如何为图表选择合适的线性刻度。特别是,我们将探讨如何确定图表 Y 轴的上下限,以及刻度线的位置。
我们会先通过一个例子说明问题,再提供解决方案。
3. 一个不合适的表示方式
我们先来看一个分布图,Y 轴设置了 5 个刻度:
从图中可以直观地看出 Y 轴刻度设置存在问题。我们期望散点图能覆盖大部分区域,而不是只集中在顶部。
此外,我们还能观察到:
- 刻度线在没有数据的区域反而更密集
- 在数据密集区(如 $10 \leq y \leq 16$)几乎没有刻度线
- 某个刻度保留了两位小数,而其他都保留整数,风格不统一
因此,可以判断:Y 轴刻度的选择方式存在明显问题。
4. 更好的表示方式
现在我们来看一个改进后的图表,同样是 5 个刻度,但视觉效果明显更好:
这个图表一眼就能看出哪些数据点更高,以及大致高多少。✅
- 刻度线分布均匀
- 刻度值都是整数
- 刻度范围略微超出了数据的最小值和最大值
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 轴刻度,能让图表更直观、专业,也更利于读者理解数据分布。