1

最初,我希望我可以为 in 中的标记创建一种类matplotlib,它是一个带有文本的正方形,显示 x 坐标和标签,所以我可以用类似(伪代码)的东西来实例化它:

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

...但据我所知,你不能做那样的事情。

但是,标记的自定义似乎在旧版本中不可用matplotlib;所以我想减少我的问题:如何在 old 中获取自定义(路径)标记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()

如果我们在此处进行任意缩放矩形,则标记保持相同大小:

标记-ex01

通过路径自定义标记

这记录在艺术家 (Line2D.set_marker) — Matplotlib 1.2.1 文档中;但是,它不适用于 old 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 作为 old 的标记matplotlib,因为渲染的路径将在屏幕坐标中定义,所以它们不会在任意缩放时改变它们的大小?

4

1 回答 1

3

非常感谢@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); 处指定一个矩形;半尺寸 2(所以,正方形);然后我可以通过以下方式将其路径计算为顶点

[(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)在一个坐标系(数据)中指定,并且size(或width、、heighthalfsize)在另一个坐标系中指定,在这种情况下,屏幕坐标系:因为我希望形状在缩放时保持它们的大小,我实际上想要保持它们的屏幕像素大小相同!

这就是为什么在我的情况下没有任何转换会有所帮助的原因 - 任何转换都可以在路径的所有顶点上起作用,就像在单个坐标系中解释的那样!然而,我想要的是得到类似的东西:

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":在上面的代码中可能是错误的;但是worksforme(现在)。

因为可以说标记大小是分开管理的(从它的位置)——这意味着您不必在自定义标记路径规范中进行任何特殊计算;您需要做的就是确保它的顶点在 (0.0,0.0) 到 (1.0, 1.0) 的范围内 - 标记绘图引擎将完成剩下的工作。

好吧,我希望我理解这一点 - 但如果还有其他类似的方法,我肯定想知道这些:)

希望这对某人有帮助,
干杯!

于 2013-05-20T18:21:36.363 回答