Advanced#
Here we demonstrate some more advanced uses of mpltoolbox
.
Callbacks#
Every tool in the mpltoolbox
comes with entry points for adding callbacks to different events. The different events are:
on_create
: called when drawing the shape (rectangle, line or polygon) is completeon_change
: called when the shape has changed in any way (position, size…)on_remove
: called when the shape is deleted (middle-click, or Ctrl + left-click)on_vertex_press
: called when a vertex is clicked (left-click)on_vertex_move
: called when a vertex is movedon_vertex_release
: called when the mouse button is released after clicking a vertexon_drag_press
: called when the entire shape (rectangle, line, etc..) is right clicked to initiate dragon_drag_move
: called for every movement during shape drag (right-click and hold)on_drag_release
: called when the shape is released after drag
Below is a couple of examples on how these callbacks are used.
[1]:
%matplotlib widget
import numpy as np
import mpltoolbox as tbx
import matplotlib.pyplot as plt
plt.ioff()
[1]:
<contextlib.ExitStack at 0x7f8938c46080>
Example 1: Add markers to slice 3d cube#
We first make some three-dimensional data:
[2]:
N = 200
M = 300
L = 100
xx = np.arange(N, dtype=np.float64)
yy = np.arange(M, dtype=np.float64)
zz = np.arange(L, dtype=np.float64)
x, y, z = np.meshgrid(xx, yy, zz, indexing="ij")
b = N / 20.0
c = M / 2.0
d = L / 2.0
r = np.sqrt(((x - c) / b) ** 2 + ((y - c) / b) ** 2 + ((z - d) / b) ** 2)
a = 10.0 * np.sin(r) + np.random.random([N, M, L])
Create a figure to display the first z
slice of the data as a two-dimensional image, as well as an empty subplot below:
[3]:
fig, ax = plt.subplots(2, 1, figsize=(7, 7))
fig.canvas.header_visible = False
ax[0].imshow(a[..., 0], interpolation="none", origin="lower")
ax[0].set(xlabel='x', ylabel='y')
ax[1].set(xlabel='z')
fig.tight_layout()
Then we add a Points
tool where:
When a dot is added on the image, a line is created in the lower panel, showing a one-dimensional
z
slice at the location of the markerWhen a dot is moved, the
z
line is updated accordinglyWhen a dot is removed, remove the corresponding
z
profile
[4]:
def make_line(new_point):
(line,) = ax[1].plot(a[int(new_point.y), int(new_point.x), :])
new_point.associated = line
def update_line(new_point):
new_point.associated.set_ydata(a[int(new_point.y), int(new_point.x), :])
def remove_line(point):
point.associated.remove()
points = tbx.Points(ax=ax[0], mec="white")
points.on_create(make_line)
points.on_change(update_line)
points.on_remove(remove_line)
[6]:
fig.canvas
[6]:
Example 2: Histogram inside a rectangular region#
In the second example, we use the Ractangles
tool to draw rectangles on the same 2d image. This defines a region of interest, inside which the data will be histogrammed and displayed on the lower panel.
[7]:
fig2, ax2 = plt.subplots(2, 1, figsize=(7, 7))
fig2.canvas.header_visible = False
fig2.tight_layout()
ax2[0].imshow(a[..., 0], interpolation="none", origin="lower")
def make_hist(new_rectangle):
xy = new_rectangle.xy
ix0 = int(xy[0])
iy0 = int(xy[1])
ix1 = int(xy[0] + new_rectangle.width)
iy1 = int(xy[1] + new_rectangle.height)
n, bins, patches = ax2[1].hist(
a[min(iy0, iy1) : max(iy0, iy1), min(ix0, ix1) : max(ix0, ix1), :].ravel(),
histtype="step",
lw=1.5,
color=new_rectangle.edgecolor,
)
new_rectangle.associated = patches
def update_hist(new_rectangle):
for patch in new_rectangle.associated:
patch.remove()
make_hist(new_rectangle)
def remove_hist(rectangle):
for patch in rectangle.associated:
patch.remove()
rects = tbx.Rectangles(ax=ax2[0], facecolor=(0, 0, 0, 0.3))
rects.on_create(make_hist)
rects.on_change(update_hist)
rects.on_remove(remove_hist)
[9]:
fig2.canvas
[9]:
Example 3: Multiple callbacks#
It is also possible to add multiple callbacks by calling the on_*
methods multiple times:
[10]:
fig, ax = plt.subplots(2, 1, figsize=(7, 7))
fig.canvas.header_visible = False
ax[0].set(xlabel='x', ylabel='y')
ax[1].set(xlabel='z')
fig.tight_layout()
ax[0].imshow(a[..., 0], interpolation="none", origin="lower")
def make_line(p):
(line,) = ax[1].plot(a[int(p.y), int(p.y), :])
p.associated = line
def update_line(p):
p.associated.set_ydata(a[int(p.y), int(p.y), :])
def make_text(p):
t = ax[1].text(50.0, np.random.random() * 10, f"x={int(p.x)}; y={int(p.y)}")
p.text = t
def update_text(p):
p.text.set_text(f"x={int(p.x)}; y={int(p.y)}")
points = tbx.Points(ax=ax[0], mec="white")
points.on_create(make_line)
points.on_create(make_text)
points.on_change(update_line)
points.on_change(update_text)
[12]:
fig.canvas
[12]:
Programmatic control#
It can sometimes be useful to programmatically add or remove artists on the figure, instead of using the mouse. This can either be if a very accurate position is required, or for software testing purposes.
Simulating clicks#
Every tool has a click()
method that can be used to simulate a mouse click event. It accepts two numbers for the x
and y
coordinates of the event, as well as an optional button
argument that can be used to change which mouse button is used. The different values for button
are: 1
for left-click (default), 2
for middle-click, 3
for right-click.
In the following example, we will add lines to a figure using the click
method.
[13]:
fig, ax = plt.subplots(dpi=96)
ax.set_xlim(0, 100)
ax.set_ylim(0, 100)
lines = tbx.Lines(ax=ax, n=2)
[14]:
lines.click(x=20, y=40) # first line vertex
lines.click(x=80, y=70) # second line vertex
lines.click(x=30, y=10)
lines.click(x=40, y=90)
lines.click(x=50, y=50)
lines.click(x=85, y=15)
[15]:
fig.canvas
[15]:
Removing artists#
To remove an artist from a figure, the remove
method accepts
an integer index, in which case the artist with the corresponding position in the list of children will be removed
an artist (using
tool.children
will give a list of all artists the tool is responsible for)a string, which should be the
id
(uuid
) of the artist to be removed
[17]:
lines.remove(0) # remove the first line that was added
lines.remove(lines.children[-1]) # remove the last line that was added
fig.canvas
[17]: