在(旧)matplotlib中具有屏幕坐标大小的自定义标记
最初,我希望我能为在(旧)matplotlib中具有屏幕坐标大小的自定义标记,matplotlib,Matplotlib,最初,我希望我能为matplotlib中的标记创建一个类,它是一个正方形,文本显示x坐标和标签,因此我可以用类似(伪代码)的东西来实例化它: 。。。但在我看来,你不能做那样的事 但是,在较旧的matplotlib中,似乎无法自定义标记;因此,我想把我的问题简化为:如何在较旧的matplotlib中获得自定义(路径)标记,以便它们的大小在屏幕坐标中定义(以便标记不会在缩放时缩放)?为了澄清,以下是一些示例: 默认(未格式化)标记 下面是一个使用默认matplotlib标记的示例,该标记适用于较旧的
matplotlib
中的标记创建一个类,它是一个正方形,文本显示x坐标和标签,因此我可以用类似(伪代码)的东西来实例化它:
。。。但在我看来,你不能做那样的事
但是,在较旧的matplotlib
中,似乎无法自定义标记;因此,我想把我的问题简化为:如何在较旧的matplotlib
中获得自定义(路径)标记,以便它们的大小在屏幕坐标中定义(以便标记不会在缩放时缩放)?为了澄清,以下是一些示例:
默认(未格式化)标记
下面是一个使用默认matplotlib
标记的示例,该标记适用于较旧的matplotlib
。请注意,我尝试使用pyplot.plot()
,而不是直接使用matplotlib.figure.figure
(这是通常用于各种后端的表单),需要使用“图形管理器”(另请参阅):
如果在此处执行任意缩放矩形,则标记的大小保持不变:
通过路径自定义标记
这记录在;但是,它不适用于较旧的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'
现在,我尝试将图形的初始纵横比考虑在内,事实上,在第一次渲染时,“标记”在大小方面看起来是正确的,但是…:
。。。当我们尝试进行任意缩放时,很明显路径是在数据坐标中指定的,因此它们的大小根据缩放矩形而变化。(另一个问题是没有遵守facecolor='orange'
;但这可以通过pcolm.set\u facecolor='orange')
解决)
那么,有没有一种方法可以使用PatchCollection作为旧版matplotlib
的标记,即渲染路径将在屏幕坐标中定义,这样它们在任意缩放时不会改变大小?非常感谢@tcaswell的帮助,他们试图找出为什么这对我不起作用,我终于找到了解决办法。首先,代码-基本上使用matplotlib的默认标记引擎(取决于使用的是较旧的matplotlib(0.99)还是较新的matplotlib):
本质上,这就是getCustomSymbol1
一直试图返回的内容。此外,例如,matplotlib.patches.Rectangle
通过位置和大小进行实例化,如Rectangle((x,y),width,height)
现在,问题是——我真正想要的是,标记,作为形状,保持在它们的位置上——在数据坐标中,所以移动和缩放图形会保持它们作为数据的位置;但是,它们应该在缩放时保持其大小
这意味着我希望在一个坐标系(数据)中指定(x,y)
,在另一个坐标系中指定大小(或宽度
,高度
,或半尺寸
),在这种情况下,屏幕坐标系:因为我希望形状在缩放时保持其大小,我实际上想保持屏幕像素大小不变
这就是为什么在我的例子中没有这样的变换会有帮助——任何变换都会对路径的所有顶点起作用,就像在单个坐标系中解释的那样!然而,我想得到的是:
hsd = screen2dataTransform(10px)
[(x-hsd,y-hsd), (x-hsd,y+hsd), (x+hsd,y+hsd), (x+hsd,y-hsd)]
。。。每次缩放级别、图形平移或窗口大小更改时,都必须重复对标记路径顶点的重新计算
因此,顶点/路径(和面片)和变换本身不能用于此目的。不过,谢天谢地,我们可以使用matplotlib
s自己的引擎;我们只需要知道,对ax.plot(…marker=…)
的任何调用实际上都会将标记的绘制委托给matplotlib.lines.Line2D
;和Line2D
维护一个内部dict,该dict将标记单字符格式说明符/命令(如'o'
,'*'
等)与特定绘图功能相关联;最后,绘图功能在代码中进行大小转换,如(在上面的解决方案代码中,绘图大部分取自's'
(方形)标记实现):
请注意,对于较旧的matplotlib
(在我的示例中为0.99.3)就是这种情况;对于较新的matplotlib
(在我的例子中是1.2.0),有一个单独的类MarkerStyle
,它维护标记格式说明符和函数之间的关系-函数不再是\u draw\u
,它只是\u set\u
-但除此之外,它是相同的原理
注:我实际上不确定是什么时候推出的MarkerStyle
,我只能找到,它没有说;因此,上述代码中的如果matplotlib.\uuuuuuuuuuuuuuuu版本1.0.0:
可能是错误的;但我(暂时)还在工作
因为可以说,标记大小是单独管理的(与其位置无关),这意味着您不必在自定义标记路径规范中进行任何特殊计算;您所需要做的就是确保其顶点在(0.0,0.0)到(1.0,1.0)范围内匹配-标记绘制引擎将完成其余操作
好吧,我希望我正确地理解了这一点——但如果有其他类似的方法,我肯定想知道这些:)
希望这对某人有所帮助,
干杯 因此,您要问的是如何将mpl的新ish版本的功能支持到非常旧的版本?我猜你想让a做你想做的。另外,关于颜色:非常感谢你的评论,@tcaswell-他们终于把我带到了。我不一定要在语法方面“backport”——我只是在寻找一种既适用于较旧的matplo,也适用于较新的matplo的方法
$ 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'
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()
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-hs,y-hs), (x-hs,y+hs), (x+hs,y+hs), (x+hs,y-hs)]
hsd = screen2dataTransform(10px)
[(x-hsd,y-hsd), (x-hsd,y+hsd), (x+hsd,y+hsd), (x+hsd,y-hsd)]
side = renderer.points_to_pixels(self._markersize)
transform = matplotlib.transforms.Affine2D().translate(-0.5, -0.5).scale(side)