如何在楔形Matplotlib图上添加楔形扇区 [英] How to add a wedge sector onto a polar Matplotlib plot

查看:67
本文介绍了如何在楔形Matplotlib图上添加楔形扇区的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我希望使用Python的Matplotlib添加一个楔形来概述一组极性数据.由于未知原因,我尝试使用Wedge修补程序艺术家失败.我希望了解这些未知因素,或者寻找补丁艺术家方法的替代方法.

主要问题是Wedge补丁未按我期望的那样显示.给定我的代码,我希望它以一定角度定向,并且半径范围为〜0.05.这将其放置在此处的扇区图中:

说明:

在代码中,我定义了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之间的欧几里得距离.利用这些数字,最终可以构造楔形.

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

旧答案:

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

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

此处 rmin rmax 分别是轴的最小半径和最大半径.另一个问题是如何绘制局部楔形的困惑.根据文档:

如果指定了宽度,则会从内半径 r-width 到外半径 r 绘制部分楔形.

因此,在您的情况下,传递给 Wedge 的半径应该是外部半径,而不是内部半径.放在一起,这应该正确显示楔形:

  r_inner = transform_radius(r,minZ,maxZ)r_outer = transform_radius(r + width,minZ,maxZ)楔子=楔形(中央,路由器,楔子范围[0],楔子范围[1],宽度= r_outer-r_inner,transform = ax.transAxes,linestyle ='-',fill = False,颜色='red')ax.add_artist(楔) 

如果我误解了一些东西,请告诉我.

I wish to add a wedge outlining a group of polar data using Python's Matplotlib. I have tried using the Wedge patch artist unsuccessfully, due to unknown reasons. I wish to either understand these unknowns, or find an alternative to the patch artist approach.

The primary issue is that the Wedge patch is not displaying as I am expecting it to. Given my code, I am expecting it to be oriented at an angle, and span a range of ~0.05 in radius. This places it within the sector plot here: 1

But this wedge has different dimensions and location than what I'm expecting. It also is shifted when viewing a zoomed-out plot: 2

The wedge has approximately the correct angular range (about ~25-27 degrees), but it starts at the wrong radius (should be ~0.4), and is the wrong width (should be ~0.05). Why is this, and how can I draw a wedge with these desired dimensions?

I have already viewed and adapted code from similar questions (see, e.g., Python: Add a Ring Sector or a Wedge to a Polar Plot ).

Here is an adaptation of my main code, with sample data included.


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)


解决方案

This turned out to be much more complicated that I at first anticipated. The main problem here is that the coordinates and angles given to Wedge are in axes coordinates, while what is really wanted is a Wedge in data coordinates. Especially the angles are a little bit hard to get right.

The solution I found is to convert the corner points of the wedge into Axes coordinates and then use these points to compute the center, radii, and angles of the wedge using linear algebra. There probably is a way to do this straight with data coordinates, but at least this works. I found help in the matplotlib transformation tutorial and in some other SO answers:

  • this answer for how to compute the intersect of two lines
  • this answer for how to compute the angle between two lines
  • this answer for how to solve a problem with transformations in equal-aspect Axes
  • this answer for how to fix the r-limits of a polar axes instance

To make the solution a little bit easier to explain, I changed the wedge coordinates in my example and added some numbered annotations for the geometrical points that I used in the calculations. Here is the code:

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()

And the image that it produces:

Explanation:

In the code I define 6 geometrical points: the 4 corners of the wedge and two points on the theta=0 line that correspond to the inner and outer radii of the wedge. I then transform these points from data to axes coordinates using the transform ax.transData+ax.transAxes.inverted(). Now, in axes coordinates I use these points to compute the center of the wedge (the intersect of the left and right sides of the wedge; points 2,3,4, and 5) and the angles between the theta=0 line and the sides of the wedge (points 0,1,2,3 and 0,1,4,5, respectively). The two radii can be computed as the Euclidean distance between the wedge center and, say, points 2 and 3. With these numbers the wedge can finally be constructed.

Note that this solution is not robust agains all figure and axes manipulations. In particular changing axes limits or aspect ratios after adding the wedge will misplace it. Resizing of the Figure is ok and tested. Hope this helps.

Old Answer:

This is a bit funny, but apparently the radius argument is not relative to the data of the Axes. You can check this by adding a wedge of radius 0.5, which, together with center=(0.5,0.5), will produce a wedge that spans the entire data range. You can define a function to transform the wedge radii from data coordinates to these coordinates:

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

Here rmin and rmax are the minimum and maximum radii of the Axes, respectively. Another issue is the confusion with how the partial wedge is drawn. According to the documentation:

If width is given, then a partial wedge is drawn from inner radius r-width to outer radius r.

So in your case the radius you pass to Wedge should be the outer, not the inner, radius. Putting it all together, this should display the wedge correctly:

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)

Please let me know if I misunderstood something.

这篇关于如何在楔形Matplotlib图上添加楔形扇区的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆