0%

根据给定的最大值和最小值生成较为整齐的刻度范围和刻度间隔

设计思路

算法的主要思路如下:

  • 首先,根据给定的最大值和最小值,计算出一个初始的理想刻度间隔值 temp_gap,该值表示希望将刻度范围分成多少段。
  • 然后,通过一个预定义的魔术数组 magic_array,找到一个合适的倍数 multiple,使得 temp_gap 除以 multiple 后处于魔术数字区间内。魔术数组中的数字是经验值,用于提供较为整齐的刻度间隔选择。
  • 接下来,通过当前的倍数 multiple 和魔术数组,找到大于 temp_gap / multiple 的第一个魔术数字,记为 estep。这个 estep 将作为刻度间隔。
  • 根据 estep,计算出期望的最大刻度值 maxi 和最小刻度值 mini,它们分别是 estep 的整数倍。如果开启了 symmetrical 参数,还会考虑正负刻度的对称性。
  • 如果 is_deviation 参数为 False,则进入一个循环,根据一些条件判断逐步调整最大值和最小值,以保证刻度的数量接近预期的分割数量。
  • 最后,计算刻度间隔 interval,即 (maxi - mini) / split_number,并返回包含最小值、最大值和刻度间隔的列表作为结果。

这个算法的目标是生成一个较为整齐的刻度范围和刻度间隔,使得数据能够在图表上展示出合适的刻度划分。通过调整倍数和使用魔术数字,可以获得相对均匀的刻度间隔选择。根据实际需求,可以通过调整初始的理想刻度间隔段数、魔术数组和其他参数来优化刻度的生成。

具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
def auto_lim(max_value, min_value, is_deviation=False):
"""
根据最大值和最小值,自动生成一个较为整齐的 xlim 或 ylim,lim 包括 x 或 y 轴的最大值,x 或 y 轴的最小值,
以及 x 或 y 轴的刻度 locator
:param max_value: x 或 y 数据的最大值
:param min_value: x 或 y 数据的最小值
:param is_deviation 是否允许误差,默认为 False
:return: 返回一个 auto lim list [lim_min, lim_max, locator]
"""
# 初始化一个理想的刻度间隔段数,即希望刻度区间有多少段
split_number = 4
# 初始化一个魔术数组
magic_array = [2, 5, 10, 15, 20, 25, 30, 40, 50, 60, 70, 80, 90, 100] # 计算出初始间隔 temp_gap 和缩放比例 multiple
temp_gap = (max_value - min_value) / split_number
# temp_gap 除以 magic_array 后刚刚处于魔数区间内,先求 multiple 的幂 10 指数,
# 例如当 temp_gap 为 120,想要把 temp_gap 映射到魔数数组(即处理为 10 到 100 之间的数),则倍数为 10,即 10 的 1 次方。
multiple = 10 ** (math.floor(math.log10(temp_gap) - 1))
# 查找大于temp_gap / multiple的第一个魔术数字
estep = next((val * multiple for val in magic_array if val > temp_gap / multiple), None)

# 求出期望的最大刻度和最小刻度,为 estep 的整数倍
maxi, mini = count_degree(estep, max_value, min_value)

if not is_deviation:
while True:
temp_split_number = round((maxi - mini) / estep)
# 根据条件判断更新最大值和最小值
if (maxi == 0 or mini - min_value <= maxi - max_value) and temp_split_number < split_number:
mini -= estep # 更新最小值(向左移动)
else:
maxi += estep # 更新最大值(向右移动)
# 达到预期的分割数量,退出循环
if temp_split_number == split_number:
break
if temp_split_number > split_number:
# 查找当前魔术数字的索引
magic_idx = next((i for i, val in enumerate(magic_array) if val * multiple == estep), None)
# 如果索引存在且不是最后一个,更新 estep 和最大值最小值
if magic_idx is not None and magic_idx < len(magic_array) - 1:
estep = magic_array[magic_idx + 1] * multiple # 更新 estep(增加)
maxi, mini = count_degree(estep, max_value, min_value) # 更新最大值和最小值
else:
break
else:
# 查找当前魔术数字的索引
magic_idx = next((i for i, val in enumerate(magic_array) if val * multiple == estep), None)
# 如果索引存在且不是第一个,更新 estep 和最大值最小值
if magic_idx is not None and magic_idx > 0:
estep = magic_array[magic_idx - 1] * multiple # 更新 estep(减少)
maxi, mini = count_degree(estep, max_value, min_value) # 更新最大值和最小值
else:
break

# 得到间距
interval = (maxi - mini) / split_number

lim = [mini, maxi, interval]

return lim


def count_degree(estep, max_value, min_value, symmetrical=False):
"""
求出期望的最大刻度和最小刻度,为 estep 的整数倍
:param estep: 最佳期望的间隔
:param max_value: 数据的最大值
:param min_value: 数据的最小值
:param symmetrical: 是否开启正负刻度
:return:
"""
# 最终效果是当 max/estep 属于 (-1,Infinity) 区间时,向上取 1 格,否则取 2 格。
# 当 min/estep 属于 (-Infinity,1) 区间时,向下取 1 格,否则取 2 格。
maxi = int(max_value / estep + 1) * estep
mini = int(min_value / estep - 1) * estep
# 如果 max 和 min 刚好在刻度线的话,则按照上面的逻辑会向上或向下多取一格
if max_value == 0:
maxi = 0
if min_value == 0:
mini = 0
if symmetrical and maxi * mini <= 0:
tm = max(abs(maxi), abs(mini))
maxi = tm
mini = -tm

return maxi, min
  • 本文作者: Hsiun YuBin
  • 本文链接: https://ikuns.icu/018/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!