Как добавить сектор клина на полярный график Matplotlib - PullRequest
0 голосов
/ 28 января 2019

Я хочу добавить клин, очерчивающий группу полярных данных, используя Matplotlib из Python.Я попытался использовать патч Wedge безуспешно по неизвестным причинам.Я хочу либо понять эти неизвестные, либо найти альтернативу подходу патч-артиста.

Основная проблема заключается в том, что патч Wedge не отображается так, как я ожидаю.Учитывая мой код, я ожидаю, что он будет ориентирован под углом и охватит диапазон ~ 0,05 радиуса.Это помещает его в сектор сектора здесь: 1

Но этот клин имеет другие размеры и расположение, чем я ожидал.Он также смещается при просмотре уменьшенного графика: 2

Клин имеет приблизительно правильный угловой диапазон (около ~ 25-27 градусов), но начинается с неправильного радиуса (должно быть ~ 0,4), и это неправильная ширина (должно быть ~ 0,05).Почему это так, и как я могу нарисовать клин с этими желаемыми размерами?

Я уже просмотрел и адаптировал код по аналогичным вопросам (см., Например, Python: добавьте сектор кольца или клин кполярный график ).

Вот адаптация моего основного кода, включая примеры данных.


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)


1 Ответ

0 голосов
/ 28 января 2019

Это оказалось намного сложнее, чем я поначалу ожидал.Основная проблема здесь заключается в том, что координаты и углы, заданные для Wedge, находятся в координатах осей, в то время как на самом деле нужен клин в координатах данных.Особенно углы немного трудно понять правильно.

Решение, которое я нашел, состоит в том, чтобы преобразовать угловые точки клина в координаты осей, а затем использовать эти точки для вычисления центра, радиусов и углов клина с использованием линейной алгебры.Вероятно, есть способ сделать это прямо с координатами данных, но по крайней мере это работает.Я нашел помощь в руководстве по преобразованию matplotlib и в некоторых других ответах SO:

  • этот ответ о том, как вычислить пересечение двух линий
  • этот ответ о том, как вычислить угол между двумя линиями
  • этот ответ о том, как решить проблему с преобразованиями в осях с равным аспектом
  • этот ответ о том, как зафиксировать r-пределы экземпляра полярных осей

Чтобы облегчить решение, я изменил координаты клина вмой пример и добавил некоторые пронумерованные аннотации для геометрических точек, которые я использовал в расчетах.Вот код:

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

И изображение, которое он производит:

result of above code

Объяснение :

В коде я определяю 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

Здесь rmin и rmax - минимальный и максимальный радиусы осей соответственно.Другая проблема - путаница с тем, как рисуется частичный клин.Согласно документации:

Если указана ширина, то частичный клин выводится из внутреннего радиуса r-ширина во внешний радиус 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)

Пожалуйста, дайте мне знать, если я что-то неправильно понял.

...