Newbie to Matplotlib
There is a dated discussion on how to zoom a scatter plot here which is very handy.
I tried a little hack to change the code to display 2 separate scatter plots. I modified the code to have two scatter plots and instantiate the class ZoomPan to each of the plots. The Pan function works fine -- I can choose to pan either plot without affecting the other. However, when I move my mouse to one scatter plot and use the mouse wheel to zoom the plot. Both plots zoom in and out together.
from matplotlib.pyplot import figure, show
import matplotlib.pyplot as plt
import numpy
class ZoomPan:
def __init__(self,ax):
self.press = None
self.cur_xlim = None
self.cur_ylim = None
self.x0 = None
self.y0 = None
self.x1 = None
self.y1 = None
self.xpress = None
self.ypress = None
self.ax = ax
def zoom_factory(self, base_scale = 2.):
def zoom(event):
ax = self.ax
cur_xlim = ax.get_xlim()
cur_ylim = ax.get_ylim()
xdata = event.xdata # get event x location
ydata = event.ydata # get event y location
if event.button == 'down':
# deal with zoom in
scale_factor = 1 / base_scale
elif event.button == 'up':
# deal with zoom out
scale_factor = base_scale
else:
# deal with something that should never happen
scale_factor = 1
print(event.button)
new_width = (cur_xlim[1] - cur_xlim[0]) * scale_factor
new_height = (cur_ylim[1] - cur_ylim[0]) * scale_factor
relx = (cur_xlim[1] - xdata)/(cur_xlim[1] - cur_xlim[0])
rely = (cur_ylim[1] - ydata)/(cur_ylim[1] - cur_ylim[0])
ax.set_xlim([xdata - new_width * (1-relx), xdata new_width * (relx)])
ax.set_ylim([ydata - new_height * (1-rely), ydata new_height * (rely)])
ax.figure.canvas.draw()
ax = self.ax
self.ax.canvas.mpl_connect('scroll_event',zoom)
fig.canvas.mpl_connect('scroll_event', zoom)
return zoom
def pan_factory(self):
def onPress(event):
ax = self.ax
if event.inaxes != ax: return
self.cur_xlim = ax.get_xlim()
self.cur_ylim = ax.get_ylim()
self.press = self.x0, self.y0, event.xdata, event.ydata
self.x0, self.y0, self.xpress, self.ypress = self.press
def onRelease(event):
ax = self.ax
self.press = None
ax.figure.canvas.draw()
def onMotion(event):
ax = self.ax
if self.press is None: return
if event.inaxes != ax: return
dx = event.xdata - self.xpress
dy = event.ydata - self.ypress
self.cur_xlim -= dx
self.cur_ylim -= dy
ax.set_xlim(self.cur_xlim)
ax.set_ylim(self.cur_ylim)
ax.figure.canvas.draw()
ax = self.ax
fig = ax.get_figure() # get the figure of interest
# attach the call back
fig.canvas.mpl_connect('button_press_event',onPress)
fig.canvas.mpl_connect('button_release_event',onRelease)
fig.canvas.mpl_connect('motion_notify_event',onMotion)
#return the function
return onMotion
def setscatter(scatter_label,ax):
ax.set_title(scatter_label)
x,y,s,c = numpy.random.rand(4,200)
s *= 200
ax.scatter(x,y,s,c)
scale = 1.1
zp = ZoomPan(ax)
zp.zoom_factory(base_scale=scale)
def main():
fig, axs = plt.subplots(nrows=1,ncols=2)
setscatter('ax',axs[0])
setscatter('bx',axs[1])
fig.tight_layout()
plt.show()
if __name__ == '__main__':
main()
I tried to dig a bit inside the code and I wonder if I can attach the call back functions to individual subplot instead to the figure itself
self.ax.canvas.mpl_connect('scroll_event',zoom)
fig.canvas.mpl_connect('scroll_event', zoom)
It seems there is no such method .canvas for a subplot and I couldn't wrap my head around to find a way to pass the current inaxes to the class when the zoom function is triggered as in the example shown here.
I realized that the original discussion has been dated for a while. Maybe there are new features in Matplotlib can achieve the same.
Thanks in advance for any help.
CodePudding user response:
I tried to dig a bit inside the code and I wonder if I can attach the call back functions to individual subplot instead to the figure itself.
You cannot. However, you can employ the same logic used at the start of onPress
such that the function body of zoom
is only executed if the cursor is within the axis limits:
def zoom(event):
ax = self.ax
if event.inaxes != ax: return
...
# rest of function