散点图中饼图标记的扇区尺寸不准确问题排查
饼图标记散点图扇区尺寸偏差问题解决
问题背景
实现了以饼图作为标记的散点图,功能可正常运行,但部分饼图扇区的显示尺寸存在细微差异——尽管单个饼图内所有扇区的比例设置完全一致。尝试将dpi提升至1000,未能解决此问题。生成的结果图中可见,部分小扇区的显示比例存在明显偏差,比如第一个饼图的红色扇区视觉上比设定的2%更大。
问题原因
核心问题是自定义marker时用linspace()生成的顶点数不足。默认情况下linspace()仅生成50个点,对于占比极小的扇区(比如第一个饼图的红色扇区仅2%),这么少的顶点无法精确拟合圆弧,导致渲染出来的扇区形状和比例出现偏差。提升dpi只是提高像素密度,无法解决顶点数不足导致的形状近似误差。
解决方案
有两种可行的修复方式:
方式1:增加linspace的采样点数
直接修改marker生成函数,给linspace指定足够多的采样点(比如设为1000),让小扇区的圆弧拟合更精确:
import numpy as np import matplotlib.pyplot as plt # 修改后的marker生成函数,增加linspace的点数 def get_piechart4_markers(fraction_lst, num_points=1000): cat1_lst = [] cat2_lst = [] cat3_lst = [] cat4_lst = [] for frc in fraction_lst: # 每个linspace都指定num_points参数 x_lst = np.cos(2 * np.pi * np.linspace(0, frc[0], num_points)) y_lst = np.sin(2 * np.pi * np.linspace(0, frc[0], num_points)) cat1_lst.append(np.row_stack([[0, 0], np.column_stack([x_lst, y_lst])])) x_lst = np.cos(2 * np.pi * np.linspace(frc[0], frc[1], num_points)) y_lst = np.sin(2 * np.pi * np.linspace(frc[0], frc[1], num_points)) cat2_lst.append(np.row_stack([[0, 0], np.column_stack([x_lst, y_lst])])) x_lst = np.cos(2 * np.pi * np.linspace(frc[1], frc[2], num_points)) y_lst = np.sin(2 * np.pi * np.linspace(frc[1], frc[2], num_points)) cat3_lst.append(np.row_stack([[0, 0], np.column_stack([x_lst, y_lst])])) x_lst = np.cos(2 * np.pi * np.linspace(frc[2], 1, num_points)) y_lst = np.sin(2 * np.pi * np.linspace(frc[2], 1, num_points)) cat4_lst.append(np.row_stack([[0, 0], np.column_stack([x_lst, y_lst])])) return cat1_lst, cat2_lst, cat3_lst, cat4_lst # 后续代码不变 x_arr = np.array([1,2,3,4,5]) y_arr = np.array([1,2,3,4,5]) pie_size_lst = [2000, 4000, 6000, 8000, 10000] pie_slice_lst = [[0.02, 0.04, 0.08], [0.04, 0.08, 0.16], [0.06, 0.12, 0.24], [0.08, 0.16, 0.32], [0.10, 0.20, 0.40]] pie_cat1_lst, pie_cat2_lst, pie_cat3_lst, pie_cat4_lst = get_piechart4_markers(pie_slice_lst) fig, ax = plt.subplots(figsize=(10, 10)) for xp, yp, mr, sm in zip(x_arr, y_arr, pie_cat1_lst, pie_size_lst): ax.scatter(xp, yp, marker=mr, s=sm, color='red', alpha=0.9, zorder=10, edgecolors='none') for xp, yp, mr, sm in zip(x_arr, y_arr, pie_cat2_lst, pie_size_lst): ax.scatter(xp, yp, marker=mr, s=sm, color='green', alpha=0.9, zorder=10, edgecolors='none') for xp, yp, mr, sm in zip(x_arr, y_arr, pie_cat3_lst, pie_size_lst): ax.scatter(xp, yp, marker=mr, s=sm, color='blue', alpha=0.9, zorder=10, edgecolors='none') for xp, yp, mr, sm in zip(x_arr, y_arr, pie_cat4_lst, pie_size_lst): ax.scatter(xp, yp, marker=mr, s=sm, color='grey', alpha=0.9, zorder=10, edgecolors='none') ax.set_xlim(0,6) ax.set_ylim(0,6) fig.tight_layout() image_file_str = r'test_bubble.png' fig.savefig(image_file_str, facecolor='w', dpi=300)
方式2:改用Wedge对象绘制饼图(更推荐)
自定义marker的方式本质是用多边形模拟扇形,不如直接用matplotlib内置的Wedge(扇形补丁)来绘制,精度更高,也更易维护:
import numpy as np import matplotlib.pyplot as plt from matplotlib.patches import Wedge fig, ax = plt.subplots(figsize=(10, 10)) x_arr = np.array([1,2,3,4,5]) y_arr = np.array([1,2,3,4,5]) pie_size_lst = [2000, 4000, 6000, 8000, 10000] pie_slice_lst = [[0.02, 0.04, 0.08], [0.04, 0.08, 0.16], [0.06, 0.12, 0.24], [0.08, 0.16, 0.32], [0.10, 0.20, 0.40]] colors = ['red', 'green', 'blue', 'grey'] # 遍历每个散点位置 for xp, yp, size, slices in zip(x_arr, y_arr, pie_size_lst, pie_slice_lst): # 计算饼图半径:scatter的s是面积,半径和面积的关系是s=πr²,所以r=sqrt(s/π) radius = np.sqrt(size / np.pi) / 72 # 除以72是转换为matplotlib的坐标单位 start_angle = 0 # 遍历每个扇区 for i, end_frac in enumerate(slices + [1.0]): end_angle = start_angle + end_frac * 360 # 创建扇形补丁 wedge = Wedge((xp, yp), radius, start_angle, end_angle, color=colors[i], alpha=0.9) ax.add_patch(wedge) start_angle = end_angle ax.set_xlim(0,6) ax.set_ylim(0,6) ax.set_aspect('equal') # 保证饼图是正圆形 fig.tight_layout() image_file_str = r'test_bubble.png' fig.savefig(image_file_str, facecolor='w', dpi=300)
这种方式直接用原生扇形对象,完全避免了多边形近似的误差,扇区比例会严格按照设定值渲染,同时还能保证饼图是正圆形。
内容的提问来源于stack exchange,提问作者heikneus
相关产品推荐
相关产品推荐

