按x比例缩放matplotlib.pyplot.Axes.scatter标记大小

17

我希望根据x/y轴上点的数量来调整matplotlib.pyplot.Axes.scatter图中markersize的大小。

import matplotlib.pyplot as plt
import numpy as np

vmin = 1
vmax = 11

x = np.random.randint(vmin, vmax, 5)
y = np.random.randint(vmin, vmax, 5)

fig, ax = plt.subplots()
for v in np.arange(vmin, vmax):
    ax.axvline(v - 0.5)
    ax.axvline(v + 0.5)
    ax.axhline(v - 0.5)
    ax.axhline(v + 0.5)

ax.set_xlim(vmin - 0.5, vmax + 0.5)
ax.set_ylim(vmin - 0.5, vmax + 0.5)
ax.scatter(x, y)

ax.set_aspect(1)
plt.show()

ax 总是使用相等的长宽比,两个轴具有相同的 lim 值。

当前运行上述代码会生成以下绘图... enter image description here

...更改 vmax = 41 的值会生成以下绘图 enter image description here

这两个绘图中的 markersize 均保留默认值,即 markersize=6

我的问题是,如何计算 markersize 的值,使得 marker 边缘能够与每个单元格的边缘相接触?(每个单元格最多只有一个数据点。)


好问题,但是提供的绘图应该是 vmax = 11。 :) - tdube
1个回答

34

使用圆形

一个简单的选项是用半径为0.5的Circles替换散点图,使用PatchCollection实现。

circles = [plt.Circle((xi,yi), radius=0.5, linewidth=0) for xi,yi in zip(x,y)]
c = matplotlib.collections.PatchCollection(circles)
ax.add_collection(c)

输入图像描述

使用数据单位的标记大小绘制散点图

如果需要绘制散点图,另一种选择是将标记大小更新为数据单位。

这里的简单解决方案是先绘制一次图形,然后获取坐标轴大小,并从中计算出标记大小(以点为单位)。

import matplotlib.pyplot as plt
import numpy as np

vmin = 1
vmax = 11

x = np.random.randint(vmin, vmax, 5)
y = np.random.randint(vmin, vmax, 5)

fig, ax = plt.subplots(dpi=141)
for v in np.arange(vmin, vmax):
    ax.axvline(v - 0.5)
    ax.axvline(v + 0.5)
    ax.axhline(v - 0.5)
    ax.axhline(v + 0.5)

ax.set_xlim(vmin - 0.5, vmax + 0.5)
ax.set_ylim(vmin - 0.5, vmax + 0.5)

ax.set_aspect(1)
fig.canvas.draw()
s = ((ax.get_window_extent().width  / (vmax-vmin+1.) * 72./fig.dpi) ** 2)

ax.scatter(x, y, s = s, linewidth=0)

plt.show()

关于如何使用散点的标记大小,可以参考这个答案。上述解决方案的缺点是将标记大小固定为绘图的大小和状态。如果坐标轴限制发生变化或绘图被缩放,散点图的大小会再次出现错误。

因此,下面的解决方案更加通用。 这个解决方案有一些复杂,类似于使用数据单位绘制线条

import matplotlib.pyplot as plt
import numpy as np

vmin = 1
vmax = 32

x = np.random.randint(vmin, vmax, 5)
y = np.random.randint(vmin, vmax, 5)

fig, ax = plt.subplots()
for v in np.arange(vmin, vmax):
    ax.axvline(v - 0.5)
    ax.axvline(v + 0.5)
    ax.axhline(v - 0.5)
    ax.axhline(v + 0.5)

ax.set_xlim(vmin - 0.5, vmax + 0.5)
ax.set_ylim(vmin - 0.5, vmax + 0.5)

class scatter():
    def __init__(self,x,y,ax,size=1,**kwargs):
        self.n = len(x)
        self.ax = ax
        self.ax.figure.canvas.draw()
        self.size_data=size
        self.size = size
        self.sc = ax.scatter(x,y,s=self.size,**kwargs)
        self._resize()
        self.cid = ax.figure.canvas.mpl_connect('draw_event', self._resize)

    def _resize(self,event=None):
        ppd=72./self.ax.figure.dpi
        trans = self.ax.transData.transform
        s =  ((trans((1,self.size_data))-trans((0,0)))*ppd)[1]
        if s != self.size:
            self.sc.set_sizes(s**2*np.ones(self.n))
            self.size = s
            self._redraw_later()
    
    def _redraw_later(self):
        self.timer = self.ax.figure.canvas.new_timer(interval=10)
        self.timer.single_shot = True
        self.timer.add_callback(lambda : self.ax.figure.canvas.draw_idle())
        self.timer.start()


sc = scatter(x,y,ax, linewidth=0)

ax.set_aspect(1)
plt.show()

由于这个问题,我更新了代码以使用计时器重新绘制画布。


3
非常好的回答!我非常喜欢将其转换为数据单位的想法。 - tdube
@tdube 没有必要删除您的答案。您可以通过评论中提供的信息进行更新。但既然您已经将其删除,我在我的回答中包含了该部分,因为它可能确实对其他人有用。 - ImportanceOfBeingErnest
很棒的答案!正是我想要做的。我更喜欢简单的方法,因为它对我所需的足够了,但还是感谢您解释数据单位方法。 - fsimkovic
请查看我的问题https://stackoverflow.com/questions/57543182/specifying-matplotlib-scatter-size-in-plot-units 我尝试实现了您的类,但似乎没有在真实数据单位中绘制。您能否帮忙看看?谢谢! - user32882

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接