You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

散点图中饼图标记的扇区尺寸不准确问题排查

饼图标记散点图扇区尺寸偏差问题解决

问题背景

实现了以饼图作为标记的散点图,功能可正常运行,但部分饼图扇区的显示尺寸存在细微差异——尽管单个饼图内所有扇区的比例设置完全一致。尝试将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 15:44:57