”;
Viewlims or View limits, refer to the range of data that is displayed within a plot along the x and y axes. Viewlims are useful for interactive data visualization because they allow users to dynamically adjust the display of data.
Matplotlib provides various methods and tools for setting and updating viewlims interactively, enabling users to explore and analyze data effectively. In general, view limits are automatically determined based on the data being plotted in matplotlib plots.
This tutorial will see step by setp implementation of creating interactive zoom functionality in a Matplotlib plot with viewlims to create dynamic visualizations coresponding to the user interactions.
Creating an Interactive Zoom Plot
Create a class that regenerates a fractal set (the Mandelbrot set is a famous fractal in mathematics) as we zoom in, allowing us to observe increasing detail. Additionally, we”ll display a box in the left panel to show the area to which we are zoomed.
This class defines the compute_image method to calculate the Mandelbrot set based on the provided bounds. And the axes_update method updates the plot based on the current view limits. It ensures that the Mandelbrot set is recalculated and redrawn whenever the view limits change.
class InteractiveFractal: def __init__(self, h=500, w=500, niter=50, radius=2., power=2): self.height = h self.width = w self.niter = niter self.radius = radius self.power = power def compute_image(self, xstart, xend, ystart, yend): self.x = np.linspace(xstart, xend, self.width) self.y = np.linspace(ystart, yend, self.height).reshape(-1, 1) c = self.x + 1.0j * self.y threshold_time = np.zeros((self.height, self.width)) z = np.zeros(threshold_time.shape, dtype=complex) mask = np.ones(threshold_time.shape, dtype=bool) for i in range(self.niter): z[mask] = z[mask]**self.power + c[mask] mask = (np.abs(z) < self.radius) threshold_time += mask return threshold_time def axes_update(self, ax): ax.set_autoscale_on(False) self.width, self.height = np.round(ax.patch.get_window_extent().size).astype(int) vl = ax.viewLim extent = vl.x0, vl.x1, vl.y0, vl.y1 im = ax.images[-1] im.set_data(self.compute_image(*extent)) im.set_extent(extent) ax.figure.canvas.draw_idle()
Updating View Limits
We”ll create a class UpdateRectangle to create a rectangle that represents the area to which we are zoomed in the Mandelbrot set. This class extends the Rectangle class and is used to update the rectangle representing the zoom area as the view limits change. As we zoom in on the left panel, the rectangle will update its shape to match the bounds of the axes.
class UpdateRectangle(Rectangle): def __call__(self, ax): self.set_bounds(*ax.viewLim.bounds) ax.figure.canvas.draw_idle()
Connecting Callbacks
Callbacks are connected to the xlim_changed and ylim_changed events of the second subplot (ax2). These callbacks trigger the update of the rectangle and the Mandelbrot set whenever the view limits change.
# Connect for changing the view limits ax2.callbacks.connect(''xlim_changed'', rect) ax2.callbacks.connect(''ylim_changed'', rect) ax2.callbacks.connect(''xlim_changed'', md.ax_update) ax2.callbacks.connect(''ylim_changed'', md.ax_update)
Here is the complete code
The Mandelbrot set is initially plotted in two subplots (ax1 and ax2). The rectangle representing the zoom area is added to ax1, and callbacks are connected to ax2 to handle view limit changes.
Example
import matplotlib.pyplot as plt import numpy as np from matplotlib.patches import Rectangle class UpdateRectangle(Rectangle): def __call__(self, ax): self.set_bounds(*ax.viewLim.bounds) ax.figure.canvas.draw_idle() class InteractiveFractal: def __init__(self, h=500, w=500, niter=50, radius=2., power=2): self.height = h self.width = w self.niter = niter self.radius = radius self.power = power def compute_image(self, xstart, xend, ystart, yend): self.x = np.linspace(xstart, xend, self.width) self.y = np.linspace(ystart, yend, self.height).reshape(-1, 1) c = self.x + 1.0j * self.y threshold_time = np.zeros((self.height, self.width)) z = np.zeros(threshold_time.shape, dtype=complex) mask = np.ones(threshold_time.shape, dtype=bool) for i in range(self.niter): z[mask] = z[mask]**self.power + c[mask] mask = (np.abs(z) < self.radius) threshold_time += mask return threshold_time def axes_update(self, ax): ax.set_autoscale_on(False) self.width, self.height = np.round(ax.patch.get_window_extent().size).astype(int) vl = ax.viewLim extent = vl.x0, vl.x1, vl.y0, vl.y1 im = ax.images[-1] im.set_data(self.compute_image(*extent)) im.set_extent(extent) ax.figure.canvas.draw_idle() md = InteractiveFractal() Z = md.compute_image(-2., 0.5, -1.25, 1.25) fig1, (ax1, ax2) = plt.subplots(1, 2, figsize=(7, 4)) ax1.imshow(Z, origin=''lower'', extent=(md.x.min(), md.x.max(), md.y.min(), md.y.max())) ax2.imshow(Z, origin=''lower'', extent=(md.x.min(), md.x.max(), md.y.min(), md.y.max())) rect = UpdateRectangle( [0, 0], 0, 0, facecolor=''none'', edgecolor=''black'', linewidth=1.0) rect.set_bounds(*ax2.viewLim.bounds) ax1.add_patch(rect) # Connect for changing the view limits ax2.callbacks.connect(''xlim_changed'', rect) ax2.callbacks.connect(''ylim_changed'', rect) ax2.callbacks.connect(''xlim_changed'', md.axes_update) ax2.callbacks.connect(''ylim_changed'', md.axes_update) ax2.set_title("Zoom here") plt.show()
Output
On executing the above program you will get the following output −
Observe the below video to see how the viewlims works here −
”;