Matplotlib: Finding Out Xlim and Ylim After Zoom

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.

The Axes instance supports callbacks through a callbacks attribute which is a CallbackRegistry instance. The events you can connect to are xlim_changed and ylim_changed and the callback will be called with func(ax) where ax is the Axes instance.

In your scenario, you'd want to register callback functions on the 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 as ax2. And it will show the ticklabels.

Once you zoom in on 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()

Sample Image

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



Leave a reply



Submit