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

fsi*_*vic 8 python scatter matplotlib

我想缩放markersizematplotlib.pyplot.Axes.scatter基于在x/y轴的点的数量曲线图.

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()
Run Code Online (Sandbox Code Playgroud)

ax始终使用相等的纵横比,两个轴具有相同的lim值.

目前,运行上面的内容会产生以下情节...... 在此输入图像描述

......并且改变了它的价值 vmax = 41 在此输入图像描述

markersize两地块留给默认,即markersize=6.

我的问题是,我怎样才能计算出markersize值,以便markers触及每个单元格的边缘?(每个单元格最多有一个数据点.)

Imp*_*est 10

使用圈子

一个简单的选择是用半径0.5 PatchCollection组成散射Circles.

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)
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

使用数据单位中带有大小标记的散点图

如果需要散点图,则替代方案是将标记大小更新为数据单元.

这里的简单解决方案是首先绘制一次图形,然后取轴尺寸并从中计算标记尺寸.

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()
Run Code Online (Sandbox Code Playgroud)

有关如何使用散射的标记化的一些背景,请参阅此答案.上述解决方案的缺点是将标记大小固定为绘图的大小和状态.如果轴限制将改变或绘图被缩放,散点图将再次具有错误的尺寸.

因此,以下解决方案将更加通用.这有点牵扯,其作用类似于绘制宽度为数据单位的线.

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()
Run Code Online (Sandbox Code Playgroud)

(由于此问题,我更新了代码以使用计时器重绘画布)

  • 优秀的答案!我真的很喜欢转换为数据单位的想法。 (3认同)