实际问题:有一堆数据点,知道它们的横纵坐标,横纵坐标的范围不在一个数量级。这些点最终展示到可视化界面时(每个点上还写了文字说明),一些离得太近的点相互叠加没办法看。需要在不改变这些点的相对位置的情况下让离得太近的点散开(最终的结果用于概览,不表示精确的输出)。
横纵坐标范围不是同一个数量级,所以需要分别扩散。
初始思路:曲线投影然后拉长。
思路如上图,三个点经过三次变换,离右侧点近的点和它的距离拉远了,离右侧点远的点距离未发生改变。先找到聚集的一堆点的中心,然后按这样变换,离中心近的点距离会拉开,离中心远的点距离基本不会变。
import math
from matplotlib import pyplot as plt
def find_mid(numlist):
"""求数组的中位数"""
idx = len(numlist)//2
mid_num = sorted(numlist)[idx]
return mid_num
def find_max_distance(aimpoint, points):
"""找离指定点最远的点和指定点之间的长度"""
max_distance = max(abs(aimpoint-p) for p in points)
return max_distance
def circle_to_shortline(aimpoint, pa, radius, cycles):
"""把弧上的点投影到短线上
:param aimpoint: 中位点, 位置固定;
:param pa: 要以中位点为参照重排的点;
:param radius: 圆半径;
"""
arc_len = abs(aimpoint - pa) # 两点间的距离, 即变圆后的弧长
arc_angle = arc_len / radius # 变圆后该点和中位点之间的弧度
shortline_len = math.sin(arc_angle) * radius # 到直线投影的长度
# 伸长的小段距离
# r/maxd = sl/ll => ll = maxd*sl/r = sl*pi/2
move_len = (shortline_len * math.pi / 2 - arc_len) / cycles
if pa < aimpoint:
b_x = pa - move_len
else:
b_x = pa + move_len
return b_x
def scatter_x(xdata, cycles):
"""扩散点
:param xdata(list): 横坐标列表;
:param cycles(int): 扩散次数;
"""
aimpoint = find_mid(xdata)
max_distance = find_max_distance(aimpoint, xdata)
circle_radius = 2 * max_distance / math.pi
new_xdata = []
for pa in xdata:
x = circle_to_shortline(aimpoint, pa, circle_radius, cycles)
new_xdata.append(x)
return new_xdata
def main(xdata, ydata):
# 方便判断每个点的位置变化所以加了颜色(使用的数据为95个点)
colors = ['#{0:0>2}{0:0>2}FF'.format(i) for i in range(95)]
cycles = 0
while cycles < 20:
aimpoint_x = find_mid(xdata)
aimpoint_y = find_mid(ydata)
plt.axis([-2, 2, -20, 20])
plt.scatter(xdata, ydata, c=colors)
plt.scatter([aimpoint_x], [aimpoint_y], marker='D')
plt.savefig(f'ok{cycles}.png')
plt.clf()
xdata = scatter_x(xdata, cycles+1)
ydata = scatter_x(ydata, cycles+1)
cycles += 1
使用这次的点进行测试: