簡體   English   中英

屏幕坐標大小為(較舊)matplotlib的自定義標記

[英]Custom markers with screen coordinate size in (older) matplotlib

最初,我希望我可以在matplotlib為標記創建一個類,它將是一個帶有文本的正方形,例如x坐標和一個標簽,所以我可以用(偽代碼)實例化它:

plt.plot(..., marker=myMarkerClass(label="X:"), ... )

......但據我所知,你不能做那樣的事情。

但是,似乎在較舊的matplotlib無法使用標記的自定義; 所以我想把我的問題簡化為:如何在較舊的matplotlib獲得自定義(路徑)標記,因此它們的大小是在屏幕坐標中定義的(因此標記在縮放時不會縮放)? 為了澄清,這里有一些例子:

默認(未定制)標記

下面是一個默認matplotlib標記的示例,它與較舊的matplotlib 請注意,我已經嘗試過而不是使用pyplot.plot() ,我正在嘗試直接使用matplotlib.figure.Figure (因為這是通常用於各種后端的表單),需要使用“數字管理器”(請參閱還有matplotlib-devel - 后端對象結構 ):

import matplotlib
import matplotlib.pyplot as plt
import matplotlib.figure
import numpy as np

t = np.arange(0.0,1.5,0.25)
s = np.sin(2*np.pi*t)

mfigure = matplotlib.figure.Figure(figsize=(5,4), dpi=100)
ax = mfigure.add_subplot(111)
ax.plot(t,s, marker='o', color='b', markerfacecolor='orange', markersize=10.0)

fig = plt.figure() # create something (fig num 1) for fig_manager
figman = matplotlib._pylab_helpers.Gcf.get_fig_manager(1)
figman.canvas.figure = mfigure    # needed
mfigure.set_canvas(figman.canvas) # needed
plt.show()

如果我們在這里執行任意縮放rect,則標記保持相同的大小:

標記-EX01

通過路徑自定義標記

這在藝術家(Line2D.set_marker) - Matplotlib 1.2.1文檔中記錄 ; 但是,它不適用於較舊的matplotlib ; 這是一個例子:

import matplotlib
import matplotlib.pyplot as plt
import matplotlib.figure
import matplotlib.path
import numpy as np
print("matplotlib version {0}".format(matplotlib.__version__))

def getCustomSymbol1(inx, iny, sc, yasp):
  verts = [
      (-0.5, -0.5), # left, bottom
      (-0.5, 0.5), # left, top
      (0.5, 0.5), # right, top
      (0.5, -0.5), # right, bottom
      (-0.5, -0.5), # ignored
      ]
  codes = [matplotlib.path.Path.MOVETO,
           matplotlib.path.Path.LINETO,
           matplotlib.path.Path.LINETO,
           matplotlib.path.Path.LINETO,
           matplotlib.path.Path.CLOSEPOLY,
           ]
  pathCS1 = matplotlib.path.Path(verts, codes)
  return pathCS1, verts

t = np.arange(0.0,1.5,0.25)
s = np.sin(2*np.pi*t)

mfigure = matplotlib.figure.Figure(figsize=(5,4), dpi=100)
ax = mfigure.add_subplot(111)
pthCS1, vrtCS1 = getCustomSymbol1(0,0, 1,1)
# here either marker=pthCS1 or marker=np.array(vrtCS1)
# have the same effect:
ax.plot(t,s, marker=pthCS1, markerfacecolor='orange', markersize=10.0)
#ax.plot(t,s, marker=np.array(vrtCS1), markerfacecolor='orange', markersize=10.0)

fig = plt.figure() # create something (fig num 1) for fig_manager
figman = matplotlib._pylab_helpers.Gcf.get_fig_manager(1)
figman.canvas.figure = mfigure    # needed
mfigure.set_canvas(figman.canvas) # needed
plt.show()

在較新的matplotlib ,這對我來說運行正常,但在較舊版本中失敗:

$ python3.2 test.py 
matplotlib version 1.2.0

$ python2.7 test.py      # marker=pthCS1
matplotlib version 0.99.3
Traceback (most recent call last):
  File "test.py", line 36, in <module>
    ax.plot(t,s, marker=pthCS1, markerfacecolor='orange', markersize=10.0)
  ...
  File "/usr/lib/pymodules/python2.7/matplotlib/lines.py", line 804, in set_marker
    self._markerFunc = self._markers[marker]
KeyError: Path([[-0.5 -0.5]
 [-0.5  0.5]
 [ 0.5  0.5]
 [ 0.5 -0.5]
 [-0.5 -0.5]], [ 1  2  2  2 79])

$ python2.7 test.py      # marker=np.array(vrtCS1)
matplotlib version 0.99.3
Traceback (most recent call last):
  File "test.py", line 38, in <module>
    ax.plot(t,s, marker=np.array(vrtCS1), markerfacecolor='orange', markersize=10.0)
  ...
  File "/usr/lib/pymodules/python2.7/matplotlib/lines.py", line 798, in set_marker
    if marker not in self._markers:
TypeError: unhashable type: 'numpy.ndarray'

但是,當它在Python 3.2中工作時,標記再次保持它們的大小跨越圖形的縮放,正如我所期望的那樣:

標記-EX02

...雖然注意到這個問題: 從頂點列表創建的自定義標記縮放錯誤·問題#1980·matplotlib / matplotlib·GitHub ,就這種類型的自定義標記而言。

通過PatchCollection中的路徑

我從一些互聯網帖子中獲取了部分代碼,但現在找不到鏈接。 在任何情況下,我們都可以避免繪制標記,並且可以使用PatchCollection來繪制應該是標記的內容。 這是代碼,它運行在較舊的matplotlib

import matplotlib
import matplotlib.pyplot as plt
import matplotlib.figure
import matplotlib.path, matplotlib.patches, matplotlib.collections
import numpy as np

def getCustomSymbol1(inx, iny, sc, yasp):
  verts = [
      (inx-0.5*sc, iny-0.5*sc*yasp), # (0., 0.), # left, bottom
      (inx-0.5*sc, iny+0.5*sc*yasp), # (0., 1.), # left, top
      (inx+0.5*sc, iny+0.5*sc*yasp), # (1., 1.), # right, top
      (inx+0.5*sc, iny-0.5*sc*yasp), # (1., 0.), # right, bottom
      (inx-0.5*sc, iny-0.5*sc*yasp), # (0., 0.), # ignored
      ]
  codes = [matplotlib.path.Path.MOVETO,
           matplotlib.path.Path.LINETO,
           matplotlib.path.Path.LINETO,
           matplotlib.path.Path.LINETO,
           matplotlib.path.Path.CLOSEPOLY,
           ]
  pathCS1 = matplotlib.path.Path(verts, codes)
  return pathCS1
def getXyIter(inarr):
  # this supports older numpy, where nditer is not available
  if np.__version__ >= "1.6.0":
    return np.nditer(inarr.tolist())
  else:
    dimensions = inarr.shape
    xlen = dimensions[1]
    xinds = np.arange(0, xlen, 1)
    return np.transpose(np.take(inarr, xinds, axis=1))

t = np.arange(0.0,1.5,0.25)
s = np.sin(2*np.pi*t)

mfigure = matplotlib.figure.Figure(figsize=(5,4), dpi=100)
ax = mfigure.add_subplot(111)
ax.plot(t,s)

customMarkers=[]
for x, y in getXyIter(np.array([t,s])): #np.nditer([t,s]):
  #printse("%f:%f\n" % (x,y))
  pathCS1 = getCustomSymbol1(x,y,0.05,1.5*500.0/400.0)
  patchCS1 = matplotlib.patches.PathPatch(pathCS1, facecolor='orange', lw=1) # no
  customMarkers.append(patchCS1)
pcolm = matplotlib.collections.PatchCollection(customMarkers)
pcolm.set_alpha(0.9)
ax.add_collection(pcolm)

fig = plt.figure() # create something (fig num 1) for fig_manager
figman = matplotlib._pylab_helpers.Gcf.get_fig_manager(1)
figman.canvas.figure = mfigure    # needed
mfigure.set_canvas(figman.canvas) # needed
plt.show()

現在,我在這里嘗試將初始寬高比考慮在內,實際上,首先渲染時,“標記”在大小方面看起來是正確的 - 但是...:

標記-EX03

...當我們嘗試進行任意縮放時,很明顯路徑已在數據坐標中指定,因此它們的大小會根據縮放矩形而變化。 (另一個缺點是沒有服從facecolor='orange' ;但可以用pcolm.set_facecolor('orange')修復

那么,有沒有一種方法可以使用PatchCollection作為舊matplotlib標記,因為渲染路徑將在屏幕坐標中定義,因此它們在任意縮放時不會改變它們的大小?

非常感謝@tcaswell關於混合變換評論 - 試圖弄清楚為什么對我不起作用,我終於找到了解決方案。 首先,代碼 - 基本上使用matplotlib的默認標記引擎(取決於使用較舊的matplotlib(0.99)還是更新的):

import matplotlib
import matplotlib.pyplot as plt
import matplotlib.figure
import numpy as np

# create vertices and Path of custom symbol
def getCustomSymbol1():
  verts = [
      (0.0, 0.0), # left, bottom
      (0.0, 0.7), # left, top
      (1.0, 1.0), # right, top
      (0.8, 0.0), # right, bottom
      (0.0, 0.0), # ignored
      ]
  codes = [matplotlib.path.Path.MOVETO,
           matplotlib.path.Path.LINETO,
           matplotlib.path.Path.LINETO,
           matplotlib.path.Path.LINETO,
           matplotlib.path.Path.CLOSEPOLY,
           ]
  pathCS1 = matplotlib.path.Path(verts, codes)
  return pathCS1, verts

if matplotlib.__version__ < "1.0.0":
  # define a marker drawing function, that uses
  # the above custom symbol Path
  def _draw_mypath(self, renderer, gc, path, path_trans):
    gc.set_snap(renderer.points_to_pixels(self._markersize) >= 2.0)
    side = renderer.points_to_pixels(self._markersize)
    transform = matplotlib.transforms.Affine2D().translate(-0.5, -0.5).scale(side)
    rgbFace = self._get_rgb_face()
    mypath, myverts = getCustomSymbol1()
    renderer.draw_markers(gc, mypath, transform,
                          path, path_trans, rgbFace)
  # add this function to the class prototype of Line2D
  matplotlib.lines.Line2D._draw_mypath = _draw_mypath
  # add marker shortcut/name/command/format spec '@' to Line2D class,
  # and relate it to our custom marker drawing function
  matplotlib.lines.Line2D._markers['@'] = '_draw_mypath'
  matplotlib.lines.Line2D.markers = matplotlib.lines.Line2D._markers
else:
  import matplotlib.markers
  def _set_mypath(self):
    self._transform = matplotlib.transforms.Affine2D().translate(-0.5, -0.5)
    self._snap_threshold = 2.0
    mypath, myverts = getCustomSymbol1()
    self._path = mypath
    self._joinstyle = 'miter'
  matplotlib.markers.MarkerStyle._set_mypath = _set_mypath
  matplotlib.markers.MarkerStyle.markers['@'] = 'mypath'
  matplotlib.lines.Line2D.markers = matplotlib.markers.MarkerStyle.markers

# proceed as usual - use the new marker '@'
t = np.arange(0.0,1.5,0.25)
s = np.sin(2*np.pi*t)

mfigure = matplotlib.figure.Figure(figsize=(5,4), dpi=100)
ax = mfigure.add_subplot(111)
ax.plot(t,s, marker='@', color='b', markerfacecolor='orange', markersize=20.0)

fig = plt.figure() # create something (fig num 1) for fig_manager
figman = matplotlib._pylab_helpers.Gcf.get_fig_manager(1)
figman.canvas.figure = mfigure    # needed
mfigure.set_canvas(figman.canvas) # needed
plt.show()

它顯示了一個稍微“創造性”的標記(如果我可以這樣說,我自己:) ) - 這就是它在縮放下的行為方式:

定制標記修復

......就像我想要的那樣 - 標記保持它們在數據坐標中的位置; 但是在縮放時保持它們的大小。


討論

對我來說最令人困惑的事情之一是:基本上,您可以通過x,y坐標指定一個矩形作為“位置”和大小(高度/寬度)。 所以如果我在x處指定一個矩形,y =(2,3); halfsize 2(so,square); 那么作為頂點通過我可以計算出它的路徑:

[(x-hs,y-hs), (x-hs,y+hs), (x+hs,y+hs), (x+hs,y-hs)]

這基本上就是getCustomSymbol1一直試圖返回的內容。 另外,例如matplotlib.patches.Rectangle通過位置和大小實例化,如Rectangle((x,y), width, height)

現在,問題是 - 我真正想要的是,標記,作為形狀, 保持在它們的位置 - 在數據坐標中,因此移動和縮放圖形保持其作為數據的位置; 但是,他們應該保持縮放尺寸。

這意味着我希望在一個坐標系(數據(x,y)指定(x,y) ,並在另一個坐標系中指定size (或widthheighthalfsize ),在本例中為屏幕坐標系:因為我想要形狀為了保持縮放尺寸,我實際上想要保持屏幕像素大小相同!

這就是為什么在我的情況下沒有這樣的轉換會有所幫助 - 任何轉換都可以在路徑的所有頂點上工作,如在單個坐標系中解釋的那樣! 然而,我想要的是,得到類似的東西:

hsd = screen2dataTransform(10px)
[(x-hsd,y-hsd), (x-hsd,y+hsd), (x+hsd,y+hsd), (x+hsd,y-hsd)]

...每次縮放級別或圖形平移或窗口大小改變時,都必須重復重新計算標記路徑的頂點。

因此,頂點/路徑(和補丁)和單獨的變換不能用於此目的。 但是,幸運的是,我們可以使用matplotlib自己的引擎; 我們只需知道對ax.plot(...marker=...)任何調用實際上都將標記的繪制委托給matplotlib.lines.Line2D ; Line2D維護一個內部字典,它將標記單字符格式說明符/命令(如'o''*'等)與特定的繪圖函數聯系起來; 最后,繪圖函數在代碼中進行大小轉換(在上面的解決方案代碼中,繪圖大部分來自's' (方形)標記實現):

side = renderer.points_to_pixels(self._markersize)
transform = matplotlib.transforms.Affine2D().translate(-0.5, -0.5).scale(side)

請注意,這是舊版matplotlib的情況(在我的情況下為0.99.3); 較新的matplotlib (1.2.0在我的情況),有一個單獨的類MarkerStyle其保持標記格式說明之間的關系,以及功能-和功能不_draw_了,它就是_set_ -但除此之外,這是同樣的原則。

注意:我實際上不確定何時引入MarkerStyle ,我只能找到matplotlib.markers - Matplotlib 1.3.x文檔並且它沒有說; 所以if matplotlib.__version__ < "1.0.0":在上面的代碼中可能是錯誤的; 但是workforme(fornow)。

因為標記大小可以說是單獨管理(從其位置) - 這意味着您不必在自定義標記路徑規范中進行任何特殊計算; 你需要做的就是確保它的頂點適合范圍(0.0,0.0)到(1.0,1.0) - 標記繪圖引擎將完成剩下的工作。

好吧,我希望我理解這一點 - 但如果有其他方法可以這樣工作,我肯定想知道那些:)

希望這有助於某人,
干杯!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM