Matplotlib: Finding out xlim and ylim after zoom
matplotlib has an event handling API you can use to hook in to actions like the ones you're referring to. The Event Handling page gives an overview of the events API, and there's a (very) brief mention of the x- and y- limits events on the Axes page.
In your scenario, you'd want to register callback functions on theThe
Axes
instance supports callbacks through a callbacks attribute which is aCallbackRegistry
instance. The events you can connect to arexlim_changed
andylim_changed
and the callback will be called withfunc(ax)
whereax
is theAxes
instance.
Axes
object's xlim_changed
and ylim_changed
events. These functions will get called whenever the user zooms or shifts the viewport.Here's a minimum working example:
Python 2
import matplotlib.pyplot as plt
#
# Some toy data
x_seq = [x / 100.0 for x in xrange(1, 100)]
y_seq = [x**2 for x in x_seq]
#
# Scatter plot
fig, ax = plt.subplots(1, 1)
ax.scatter(x_seq, y_seq)
#
# Declare and register callbacks
def on_xlims_change(event_ax):
print "updated xlims: ", event_ax.get_xlim()
def on_ylims_change(event_ax):
print "updated ylims: ", event_ax.get_ylim()
ax.callbacks.connect('xlim_changed', on_xlims_change)
ax.callbacks.connect('ylim_changed', on_ylims_change)
#
# Show
plt.show()
Python 3
import matplotlib.pyplot as plt
#
# Some toy data
x_seq = [x / 100.0 for x in range(1, 100)]
y_seq = [x**2 for x in x_seq]
#
# Scatter plot
fig, ax = plt.subplots(1, 1)
ax.scatter(x_seq, y_seq)
#
# Declare and register callbacks
def on_xlims_change(event_ax):
print("updated xlims: ", event_ax.get_xlim())
def on_ylims_change(event_ax):
print("updated ylims: ", event_ax.get_ylim())
ax.callbacks.connect('xlim_changed', on_xlims_change)
ax.callbacks.connect('ylim_changed', on_ylims_change)
#
# Show
plt.show()
Recalculate x y values after zoom based on current ylim and xlim in Matplotlib
This may not be as easy as it sounds. When changing the limits, you would change the limits, such that the callback runs infinitly, making your window crash.
I would hence opt for another solution, using a second axes. So let's say you have two axes:
ax2
is the axes to plot to. But is has no frame and no ticklabels. This is the axes you can change the limits with.ax
is empty. It initially has the same limits asax2
. And it will show the ticklabels.
ax2
the callback function can change the limits of ax
to your liking. This is then what is shown on the screen. import matplotlib.pyplot as plt
# Some toy data
x_seq = [x / 100.0 for x in xrange(1, 100)]
y_seq = [x**2 for x in x_seq]
# ax is empty
fig, ax = plt.subplots()
ax.set_navigate(False)
# ax2 will hold the plot, but has invisible labels
ax2 = fig.add_subplot(111,zorder=2)
ax2.scatter(x_seq, y_seq)
ax2.axis("off")
ax.set_xlim(ax2.get_xlim())
ax.set_ylim(ax2.get_ylim())
#
# Declare and register callbacks
def on_lims_change(axes):
# change limits of ax, when ax2 limits are changed.
a=ax2.get_xlim()
ax.set_xlim(0, a[1]-a[0])
a=ax2.get_ylim()
ax.set_ylim(0, a[1]-a[0])
ax2.callbacks.connect('xlim_changed', on_lims_change)
ax2.callbacks.connect('ylim_changed', on_lims_change)
# Show
plt.show()
Limit the points in subplots only to zoomed in region
You would need to filter the data, depending on the limits of the main axes. One can connect callbacks on zoom events, see Matplotlib: Finding out xlim and ylim after zoom and connect them to a function that performs the filtering on the data.
import numpy as np
import matplotlib.pyplot as plt
numPoints = 100
x = np.random.rand(numPoints)*20
y = np.random.rand(numPoints)*20
zeros = np.zeros_like(x)
# Set up the axes with gridspec
fig = plt.figure(figsize=(6, 6), constrained_layout=True)
grid = fig.add_gridspec(ncols=2, nrows=2, width_ratios=[0.3, 5], height_ratios=[5, 0.3])
ax_main = fig.add_subplot(grid[:-1, 1:])
ax_y = fig.add_subplot(grid[:-1, 0], xticklabels=[], sharey=ax_main)
ax_x = fig.add_subplot(grid[-1, 1:], yticklabels=[], sharex=ax_main)
ax_main.plot(x, y, 'ok', markersize=3, alpha=0.2)
xline, = ax_x.plot(x, zeros, marker='o', ls="none", color='gray')
yline, = ax_y.plot(zeros, y, marker='o', ls="none", color='gray')
ax_main.grid(True, lw = 1, ls = '--', c = '.75')
ax_y.grid(True, axis="x", lw = 1, ls = '--', c = '.75')
ax_x.grid(True, axis="y", lw = 1, ls = '--', c = '.75')
def xchange(evt):
ymin, ymax = ax_main.get_ylim()
filt = (y <= ymax) & (y >= ymin)
xline.set_data(x[filt], zeros[filt])
def ychange(evt):
xmin, xmax = ax_main.get_xlim()
filt = (x <= xmax) & (x >= xmin)
yline.set_data(zeros[filt], y[filt])
ax_main.callbacks.connect('xlim_changed', ychange)
ax_main.callbacks.connect('ylim_changed', xchange)
plt.show()
update several matplotlib axes when a zoom is done on another one with sahex property?
The callback is only triggered for the axes of which the limits actually change externally. You may just use a single function to update both plots simultaneously.
def onlimschange(ax):
on_xlims_change1(ax)
on_xlims_change2(ax)
def on_xlims_change1(axes):
lim = axes.get_xlim()
f, Pxx_den = signal.periodogram(x[np.bitwise_and(t >lim[0] , t <lim[1])], Fs)
ax2.clear()
ax2.plot(f[:np.where(f>50)[0][0]],Pxx_den[:np.where(f>50)[0][0]] )
def on_xlims_change2(axes):
lim = axes.get_xlim()
f, Pxx_den = signal.periodogram(x[np.bitwise_and(t >lim[0] , t <lim[1])], Fs)
ax4.clear()
ax4.plot(f[:np.where(f>50)[0][0]],Pxx_den[:np.where(f>50)[0][0]] )
ax1.callbacks.connect('xlim_changed', onlimschange)
ax3.callbacks.connect('xlim_changed', onlimschange)
Amore general solution where arbitrary axes can be shared, could be to introduce a mapping from a source_axes
to a target_axes
and to loop over all shared axes to update the respective target axes according to the mapping.import matplotlib.pyplot as plt
import numpy as np
from scipy import signal
Fs=1024.
t=np.arange(0,10,1/Fs)
F=np.arange(0,10,1/Fs)
x = np.sin(2 * 3.1416 * F *t )
plt.figure()
ax1 = plt.subplot(221)
plt.plot(t,x)
ax2 = plt.subplot(222)
f, Pxx_den = signal.periodogram(x, Fs)
line1, = plt.plot(f[:np.where(f>50)[0][0]],Pxx_den[:np.where(f>50)[0][0]] )
ax3 = plt.subplot(223, sharex=ax1)
plt.plot(t,x)
ax4 = plt.subplot(224)
f, Pxx_den = signal.periodogram(x, Fs)
line1, = plt.plot(f[:np.where(f>50)[0][0]],Pxx_den[:np.where(f>50)[0][0]] )
def func1(source_axes):
lim = source_axes.get_xlim()
f, Pxx_den = signal.periodogram(x[np.bitwise_and(t >lim[0] , t <lim[1])], Fs)
X,Y = f[:np.where(f>50)[0][0]],Pxx_den[:np.where(f>50)[0][0]]
return X,Y
mapping = {ax1 : [ax2, func1], ax3 : [ax4, func1]}
def onlimschange(source_axes):
for source_ax in source_axes.get_shared_x_axes().get_siblings(source_axes):
target_ax = mapping[source_ax][0]
X,Y = mapping[source_ax][1](source_axes)
target_ax.clear()
target_ax.plot(X,Y)
ax1.callbacks.connect('xlim_changed', onlimschange)
ax3.callbacks.connect('xlim_changed', onlimschange)
plt.show()
Keep zoom and ability to zoom out to current data extent in matplotlib.pyplot
The "Home" button takes you back to the zoom that has been registered as default. You should set it programmatically.
I do not know a direct interface for setting this default, but you can register the current status of the Canvas as the default, using ToolBar's push_current
method.
In your case, you should make this in the place where you change the data.
Instead of simply changing the data
line.set_data(x2, y2)
you should do some bookkeeping:fig = plt.gcf() # I assume the OP already has this data
ax = fig.gca() # I assume the OP already has this data
# save the current zoom for restoring later
old_x_lim = ax.get_xlim()
old_y_lim = ax.get_ylim()
# let matplotlib calculate the optimal zoom for the new data
ax.relim()
ax.autoscale()
# Now the tricky part... I did not find much documentation on this
toolbar = fig.canvas.manager.toolbar
toolbar.update() # Clear the axes stack
toolbar.push_current() # save the current status as home
ax.set_xlim(old_x_lim) # and restore zoom
ax.set_ylim(old_y_lim)
I hope this works in you setup. Otherwise you need to poke into matplotlib's internals.
Related Topics
How to Flatten a Pandas Dataframe with Some Columns as JSON
How to Build a Systemtray App for Windows
Pairwise Crossproduct in Python
How to Save and Restore Multiple Variables in Python
What Is the Default _Hash_ in Python
Subclassing Python Dictionary to Override _Setitem_
What Are All the Dtypes That Pandas Recognizes
Binary Numpy Array to List of Integers
How to Get Tweets Older Than a Week (Using Tweepy or Other Python Libraries)
How to Assign the Same Value to Multiple Keys in a Dict Object at Once
Why Does the Floating-Point Value of 4*0.1 Look Nice in Python 3 But 3*0.1 Doesn'T
Python Decompression Relative Performance
Python Running as Windows Service: Oserror: [Winerror 6] the Handle Is Invalid
How Does the Max() Function Work on List of Strings in Python
Check If an Item Is in a Nested List
Pyspark Dataframes - Way to Enumerate Without Converting to Pandas