[英]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,则标记保持相同的大小:
这在艺术家(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中工作时,标记再次保持它们的大小跨越图形的缩放,正如我所期望的那样:
...虽然注意到这个问题: 从顶点列表创建的自定义标记缩放错误·问题#1980·matplotlib / matplotlib·GitHub ,就这种类型的自定义标记而言。
我从一些互联网帖子中获取了部分代码,但现在找不到链接。 在任何情况下,我们都可以避免绘制标记,并且可以使用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()
现在,我在这里尝试将初始宽高比考虑在内,实际上,首先渲染时,“标记”在大小方面看起来是正确的 - 但是...:
...当我们尝试进行任意缩放时,很明显路径已在数据坐标中指定,因此它们的大小会根据缩放矩形而变化。 (另一个缺点是没有服从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
(或width
, height
或halfsize
),在本例中为屏幕坐标系:因为我想要形状为了保持缩放尺寸,我实际上想要保持屏幕像素大小相同!
这就是为什么在我的情况下没有这样的转换会有所帮助 - 任何转换都可以在路径的所有顶点上工作,如在单个坐标系中解释的那样! 然而,我想要的是,得到类似的东西:
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.