Scale matplotlib.pyplot.Axes.scatter markersize by x-scale

I would like to scale the markersize graphics of matplotlib.pyplot.Axes.scatter based on the number of points on the x / y axis.

 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 always uses the same aspect ratio, and both axes have the same lim values.

Currently, when starting above, the following chart is generated ... enter image description here

... and by changing the value vmax = 41 enter image description here

markersize on both charts remains by default, i.e. markersize=6 .

My question is: how could I calculate the markersize value markersize that the marker touches the edges of each cell? (Each cell has a maximum of one data point.)

+5
source share
1 answer

Using circles

An easy option is to replace the scatter with a PatchCollection consisting of Circles radius 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) 

enter image description here

Using scatter with size markers in data units

An alternative, if a scatter plot is desired, would be updating the markers in data units.

A simple solution here would be to first draw the shape once, then take the size of the axes and calculate the markers at points from it.

 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() 

For some background on how a dispersion marker is used, see for example this answer . The disadvantage of the above solution is that the marker size is fixed to the size and state of the graph. If the axis limits change or the graph is enlarged, the scatter graph will again be in the wrong size.

Therefore, the following solution would be more general. This is a bit related and will work the same way as Building a line with a width in data units .

 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() 

(I updated the code to use a timer to redraw the canvas, due to this problem )

+4
source

Source: https://habr.com/ru/post/1274601/


All Articles