【问题标题】:How to add a wedge sector onto a polar Matplotlib plot如何在极坐标 Matplotlib 图上添加楔形扇区
【发布时间】:2019-06-21 01:27:54
【问题描述】:

我希望使用 Python 的 Matplotlib 添加一个楔形,勾勒出一组极坐标数据。由于未知原因,我尝试使用 Wedge 补丁艺术家未成功。我希望了解这些未知数,或者找到补丁艺术家方法的替代方法。

主要问题是 Wedge 补丁未按预期显示。给定我的代码,我希望它以一个角度定向,并跨越约 0.05 的半径范围。这将其置于此处的扇区图中: 1

但是这个楔子的尺寸和位置与我预期的不同。在查看缩小的图时,它也会发生变化: 2

楔形的角度范围大致正确(大约 ~25-27 度),但它从错误的半径开始(应该是 ~0.4),并且是错误的宽度(应该是 ~0.05)。为什么会这样?如何绘制具有这些所需尺寸的楔形?

我已经查看并改编了类似问题的代码(例如,参见Python: Add a Ring Sector or a Wedge to a Polar Plot)。

这是我的主要代码的改编,包括示例数据。


import numpy as np
import matplotlib.pyplot as plt

from matplotlib.patches import Wedge


##Enter data

thetaRad = np.array([0.455, 0.456, 0.455, 0.456, 0.46 , 0.459, 0.461, 0.461, 0.453,
       0.459, 0.46 , 0.46 , 0.46 , 0.451, 0.46 , 0.457, 0.45 , 0.451,
       0.45 , 0.45 , 0.451, 0.452, 0.461, 0.459, 0.451, 0.455, 0.454,
       0.457, 0.459, 0.451, 0.46 , 0.453, 0.46 , 0.452, 0.452, 0.45 ,
       0.453, 0.452, 0.452, 0.456, 0.45 , 0.458, 0.461, 0.457, 0.45 ,
       0.453, 0.459, 0.459, 0.455, 0.456, 0.457, 0.457, 0.454, 0.453,
       0.455, 0.456, 0.459, 0.455, 0.453, 0.455, 0.454, 0.459, 0.457,
       0.454, 0.46 , 0.458, 0.459, 0.457, 0.451, 0.45 , 0.455, 0.461,
       0.455, 0.458, 0.456, 0.449, 0.459, 0.453, 0.458, 0.457, 0.456,
       0.45 , 0.459, 0.458, 0.453, 0.452, 0.459, 0.454, 0.455, 0.452,
       0.453, 0.451, 0.453, 0.461, 0.452, 0.458, 0.449, 0.461, 0.459,
       0.452, 0.458, 0.455, 0.452, 0.451, 0.457, 0.457, 0.457, 0.457,
       0.456, 0.456, 0.451, 0.451, 0.452, 0.459, 0.45 , 0.453, 0.45 ,
       0.449, 0.453, 0.455, 0.457])

Zs = np.array([0.052, 0.052, 0.057, 0.058, 0.058, 0.058, 0.058, 0.058, 0.059,
       0.059, 0.059, 0.059, 0.06 , 0.06 , 0.06 , 0.06 , 0.064, 0.134,
       0.134, 0.134, 0.134, 0.135, 0.135, 0.135, 0.135, 0.135, 0.135,
       0.135, 0.135, 0.135, 0.135, 0.135, 0.135, 0.136, 0.136, 0.136,
       0.136, 0.136, 0.136, 0.137, 0.309, 0.311, 0.32 , 0.328, 0.352,
       0.379, 0.381, 0.381, 0.382, 0.382, 0.383, 0.383, 0.386, 0.387,
       0.39 , 0.392, 0.392, 0.392, 0.392, 0.393, 0.393, 0.394, 0.394,
       0.394, 0.394, 0.394, 0.394, 0.395, 0.395, 0.396, 0.422, 0.426,
       0.48 , 0.482, 0.483, 0.483, 0.484, 0.487, 0.487, 0.489, 0.489,
       0.49 , 0.49 , 0.491, 0.491, 0.491, 0.491, 0.492, 0.492, 0.496,
       0.497, 0.498, 0.5  , 0.505, 0.764, 0.767, 0.771, 0.771, 0.777,
       0.833, 0.844, 0.855, 0.858, 0.863, 0.866, 0.868, 0.869, 0.87 ,
       0.871, 0.872, 0.875, 0.994, 0.995, 0.996, 1.002, 1.004, 1.01 ,
       1.01 , 1.011, 1.475, 1.667])


maxZ = 0.55
minZ = 0.28


##Prepare plot

fig = plt.figure()
color = 'k'
m = 'o'
size = 1

ax = fig.add_subplot(111, projection='polar')
plt.scatter(thetaRad,Zs, c=color, marker=m, s = size)

ax.set_rmax(maxZ)
ax.set_rmin(minZ)

#set theta limits to be scaled from the dataset
minTheta = 0.95*min(thetaRad)
maxTheta = 1.05*max(thetaRad)

#uncomment these for the partial sector plot:
#ax.set_thetamin(np.rad2deg(minTheta))
#ax.set_thetamax(np.rad2deg(maxTheta))
#ax.set_rorigin(-minZ)

ticks = np.linspace(minTheta, maxTheta, 4)
ax.set_xticks(ticks)


##Add a wedge

#define the wedge's width and range
window = np.array([0.35,0.40])
dTheta = np.deg2rad(0.5)
wedgeRange = [minTheta+dTheta, maxTheta-dTheta]
wedgeRange = np.rad2deg(wedgeRange)

r = window[1]
width = window[1]-window[0]
width = width

#apparently, plt's polar plot is silently centered at (0.5,0.5) instead of the
#origin, so set this:
center = (0.5,0.5)

wedge = Wedge(center, r, wedgeRange[0],wedgeRange[1],width=width, transform=ax.transAxes, linestyle='--', fill=False, color='red')
ax.add_artist(wedge)


【问题讨论】:

    标签: python matplotlib plot data-visualization polar-coordinates


    【解决方案1】:

    事实证明,这比我最初预期的要复杂得多。这里的主要问题是给Wedge 的坐标和角度是轴坐标,而真正想要的是数据坐标中的楔形。尤其是角度有点难以正确。

    我找到的解决方案是将楔形的角点转换为 Axes 坐标,然后使用这些点使用线性代数计算楔形的中心、半径和角度。可能有一种方法可以直接使用数据坐标执行此操作,但至少这是可行的。我在 matplotlib transformation tutorial 和其他一些 SO 答案中找到了帮助:

    为了使解决方案更易于解释,我更改了示例中的楔形坐标,并为我在计算中使用的几何点添加了一些编号注释。代码如下:

    import numpy as np
    import matplotlib.pyplot as plt
    from matplotlib.patches import Wedge
    
    def perp( a ) :
        ##from https://stackoverflow.com/a/3252222/2454357
        b = np.empty_like(a)
        b[0] = -a[1]
        b[1] = a[0]
        return b
    
    
    def seq_intersect(a1,a2, b1,b2) :
        ##from https://stackoverflow.com/a/3252222/2454357
        da = a2-a1
        db = b2-b1
        dp = a1-b1
        dap = perp(da)
        denom = np.dot( dap, db)
        num = np.dot( dap, dp )
        return (num / denom.astype(float))*db + b1
    
    def angle(a1, a2, b1, b2):
        ##from https://stackoverflow.com/a/16544330/2454357
        x1, y1 = a2-a1
        x2, y2 = b2-b1
        dot = x1*x2 + y1*y2      # dot product between [x1, y1] and [x2, y2]
        det = x1*y2 - y1*x2      # determinant
        return np.arctan2(det, dot)  # atan2(y, x) or atan2(sin, cos)
    
    
    def draw_wedge(
        ax, r_min = 0.3, r_max = 0.5, t_min = np.pi/4, t_max = 3*np.pi/4
        ):
    
        ##some data
        R = np.random.rand(100)*(r_max-r_min)+r_min
        T = np.random.rand(100)*(t_max-t_min)+t_min
        ax.scatter(T,R)
    
        ##compute the corner points of the wedge:
        axtmin = 0
    
        rs = np.array([r_min,  r_max,  r_min, r_max, r_min, r_max])
        ts = np.array([axtmin, axtmin, t_min, t_min, t_max, t_max])
    
        ##display them in a scatter plot
        ax.scatter(ts, rs, color='r', marker='x', lw=5)
    
        ##from https://matplotlib.org/users/transforms_tutorial.html
        trans = ax.transData + ax.transAxes.inverted()
    
        ##convert to figure cordinates, for a starter
        xax, yax = trans.transform([(t,r) for t,r in zip(ts, rs)]).T
    
        for i,(x,y) in enumerate(zip(xax, yax)):
            ax.annotate(
                str(i), (x,y), xytext = (x+0.1, y), xycoords = 'axes fraction',
                arrowprops = dict(
                    width=2,
    
                ),
            )
    
    
        ##compute the angles of the wedge:
        tstart = np.rad2deg(angle(*np.array((xax[[0,1,2,3]],yax[[0,1,2,3]])).T))
        tend = np.rad2deg(angle(*np.array((xax[[0,1,4,5]],yax[[0,1,4,5]])).T))
    
        ##the center is where the two wedge sides cross (maybe outside the axes)
        center=seq_intersect(*np.array((xax[[2,3,4,5]],yax[[2,3,4,5]])).T)
    
        ##compute the inner and outer radii of the wedge:
        rinner = np.sqrt((xax[1]-center[0])**2+(yax[1]-center[1])**2)
        router = np.sqrt((xax[2]-center[0])**2+(yax[2]-center[1])**2)
    
        wedge = Wedge(center,
                      router, tstart, tend,
                      width=router-rinner,
                      #0.6,tstart,tend,0.3,
                      transform=ax.transAxes, linestyle='--', lw=3,
                      fill=False, color='red')
        ax.add_artist(wedge)
    
    
    fig = plt.figure(figsize=(8,4))
    
    ax1 = fig.add_subplot(121, projection='polar')
    ax2 = fig.add_subplot(122, projection='polar')
    
    ##reducing the displayed theta and r ranges in second axes:
    ax2.set_thetamin(10)
    ax2.set_thetamax(40)
    
    ## ax.set_rmax() does not work as one would expect -- use ax.set_ylim() instead
    ## from https://stackoverflow.com/a/9231553/2454357
    ax2.set_ylim([0.2,0.8])
    ax2.set_rorigin(-0.2)
    
    #from https://stackoverflow.com/a/41823326/2454357
    fig.canvas.draw()
    
    draw_wedge(ax1)
    draw_wedge(ax2, t_min=np.deg2rad(15), t_max=np.deg2rad(30))
    
    plt.show()
    

    以及它产生的图像:

    解释

    在代码中,我定义了 6 个几何点:楔形的 4 个角和theta=0 线上的两个点,它们对应于楔形的内半径和外半径。然后我使用转换ax.transData+ax.transAxes.inverted() 将这些点从数据转换为轴坐标。现在,在轴坐标中,我使用这些点来计算楔形的中心(楔形左右两侧的交点;点 2、3、4 和 5)以及theta=0 线与楔形的侧面(分别为点 0,1,2,3 和 0,1,4,5)。这两个半径可以计算为楔形中心与点 2 和 3 之间的欧几里得距离。有了这些数字,最终可以构造出楔形。

    请注意,此解决方案对所有图形和轴操作都不可靠。特别是在添加楔形之后更改轴限制或纵横比会使它错位。调整图形大小是可以的并且经过测试。希望这会有所帮助。

    旧答案

    这有点好笑,但显然半径参数与轴的数据无关。您可以通过添加一个半径为0.5 的楔形来检查这一点,它与center=(0.5,0.5) 一起将产生一个跨越整个数据范围的楔形。您可以定义一个函数将楔形半径从数据坐标转换为这些坐标:

    def transform_radius(r, rmin, rmax):
        return (r-rmin)/(rmax-rmin)*0.5
    

    这里rminrmax 分别是轴的最小和最大半径。另一个问题是如何绘制部分楔形的混淆。根据文档:

    如果给定宽度,则从内半径r-width到外半径r绘制一个部分楔形。

    因此,在您的情况下,您传递给Wedge 的半径应该是外半径,而不是内半径。综上所述,这应该可以正确显示楔形:

    r_inner = transform_radius(r, minZ, maxZ)
    r_outer = transform_radius(r+width, minZ, maxZ)
    wedge = Wedge(    
        center,
        r_outer,
        wedgeRange[0],wedgeRange[1],
        width=r_outer-r_inner,
        transform=ax.transAxes, linestyle='--',
        fill=False, color='red'
    )
    ax.add_artist(wedge)
    

    如果我误解了什么,请告诉我。

    【讨论】:

    • 感谢您对绘图坐标的见解,这有助于解决问题。我很好奇——这个奇怪的坐标系统是由 Matplotlib 记录的吗?似乎它很好并且在绘图点的引擎盖下,但添加其他任何东西都会有问题。半径细节很有意义。我知道外半径被传递给楔形,但问题是由于宽度没有被转换造成的。这一切都解决了我的问题,谢谢!
    • 实际上,这只是解决了显示整个极坐标图时的问题;在扇形图link 上绘制楔形时仍然存在问题。很明显,楔形是在两个图中的相同“全局”位置绘制的。我必须做哪些缩放才能在扇区中正确显示楔形?
    • @ascendants 你如何制作扇形图?
    • @ascendants 我终于有时间研究这个了。请参阅我的答案的编辑。
    • 啊,我认为这是一个比我最初想象的更深层次的问题。我修改了你的代码,它终于像我想象的那样工作了!感谢您为编写这个出色的答案所做的所有努力。
    猜你喜欢
    • 1970-01-01
    • 2017-10-26
    • 1970-01-01
    • 2022-07-22
    • 1970-01-01
    • 1970-01-01
    • 2017-05-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多