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

Python 缩放matplotlib.pyplot.Axes.scatter标记按x比例缩放,python,matplotlib,scatter,Python,Matplotlib,Scatter,我想根据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(v

我想根据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

目前,运行上述程序将生成以下绘图。。。

…并更改
vmax=41的值

两个图中的
markersize
保留为默认值,即
markersize=6

我的问题是,如何计算
markersize
值,使
标记
接触每个单元格的边缘?(每个单元格最多有一个数据点。)

使用圆圈 一个简单的选择是用半径为0.5的
圆圈组成的
补丁集合
替换散布

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)

使用带有数据单位大小标记的散布 如果需要散点图,另一种方法是将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(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()
有关如何使用散射体标记化的一些背景信息,请参见例如。上述解决方案的缺点是将标记大小固定到绘图的大小和状态。如果轴限制发生变化或绘图被缩放,散点图的大小将再次错误

因此,以下解决方案更通用。 这是一个小的参与,并将工作类似于


(我更新了代码,使用计时器重新绘制画布,因为)

回答得很好!我真的很喜欢转换成数据单位的想法。@tdube没有必要删除你的答案。你可以用评论中的信息来更新它。但既然你删除了它,我就在上面的回答中包含了这一部分,因为它可能对其他人也很有用。回答得很好!这正是我想做的。我更喜欢简单的方法,因为它足以满足我的需要,但也感谢您对数据单位方法的解释。请看一下我的问题,我已经尝试实现了您的类,但它似乎不是以真正的数据单位绘制的。你介意看一下吗?谢谢好问题,但对于提供的绘图,应为
vmax=11
。)
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()