Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 47a15d9

Browse filesBrowse files
authored
Merge pull request #17067 from anntzer/zoomlogit
Simplify and generalize _set_view_from_bbox.
2 parents 1259895 + 2f6d98c commit 47a15d9
Copy full SHA for 47a15d9

File tree

Expand file treeCollapse file tree

2 files changed

+96
-93
lines changed
Filter options
Expand file treeCollapse file tree

2 files changed

+96
-93
lines changed

‎lib/matplotlib/axes/_base.py

Copy file name to clipboardExpand all lines: lib/matplotlib/axes/_base.py
+50-92Lines changed: 50 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -3849,18 +3849,15 @@ def _set_view_from_bbox(self, bbox, direction='in',
38493849
twiny : bool
38503850
Whether this axis is twinned in the *y*-direction.
38513851
"""
3852-
Xmin, Xmax = self.get_xlim()
3853-
Ymin, Ymax = self.get_ylim()
3854-
38553852
if len(bbox) == 3:
3856-
# Zooming code
3857-
xp, yp, scl = bbox
3853+
Xmin, Xmax = self.get_xlim()
3854+
Ymin, Ymax = self.get_ylim()
3855+
3856+
xp, yp, scl = bbox # Zooming code
38583857

3859-
# Should not happen
3860-
if scl == 0:
3858+
if scl == 0: # Should not happen
38613859
scl = 1.
38623860

3863-
# direction = 'in'
38643861
if scl > 1:
38653862
direction = 'in'
38663863
else:
@@ -3889,90 +3886,51 @@ def _set_view_from_bbox(self, bbox, direction='in',
38893886
"of length 3 or 4. Ignoring the view change.")
38903887
return
38913888

3892-
# Just grab bounding box
3893-
lastx, lasty, x, y = bbox
3894-
3895-
# zoom to rect
3896-
inverse = self.transData.inverted()
3897-
(lastx, lasty), (x, y) = inverse.transform([(lastx, lasty), (x, y)])
3898-
3899-
if twinx:
3900-
x0, x1 = Xmin, Xmax
3901-
else:
3902-
if Xmin < Xmax:
3903-
if x < lastx:
3904-
x0, x1 = x, lastx
3905-
else:
3906-
x0, x1 = lastx, x
3907-
if x0 < Xmin:
3908-
x0 = Xmin
3909-
if x1 > Xmax:
3910-
x1 = Xmax
3911-
else:
3912-
if x > lastx:
3913-
x0, x1 = x, lastx
3914-
else:
3915-
x0, x1 = lastx, x
3916-
if x0 > Xmin:
3917-
x0 = Xmin
3918-
if x1 < Xmax:
3919-
x1 = Xmax
3920-
3921-
if twiny:
3922-
y0, y1 = Ymin, Ymax
3923-
else:
3924-
if Ymin < Ymax:
3925-
if y < lasty:
3926-
y0, y1 = y, lasty
3927-
else:
3928-
y0, y1 = lasty, y
3929-
if y0 < Ymin:
3930-
y0 = Ymin
3931-
if y1 > Ymax:
3932-
y1 = Ymax
3933-
else:
3934-
if y > lasty:
3935-
y0, y1 = y, lasty
3936-
else:
3937-
y0, y1 = lasty, y
3938-
if y0 > Ymin:
3939-
y0 = Ymin
3940-
if y1 < Ymax:
3941-
y1 = Ymax
3942-
3943-
if direction == 'in':
3944-
if mode == 'x':
3945-
self.set_xlim((x0, x1))
3946-
elif mode == 'y':
3947-
self.set_ylim((y0, y1))
3948-
else:
3949-
self.set_xlim((x0, x1))
3950-
self.set_ylim((y0, y1))
3951-
elif direction == 'out':
3952-
if self.get_xscale() == 'log':
3953-
alpha = np.log(Xmax / Xmin) / np.log(x1 / x0)
3954-
rx1 = pow(Xmin / x0, alpha) * Xmin
3955-
rx2 = pow(Xmax / x0, alpha) * Xmin
3956-
else:
3957-
alpha = (Xmax - Xmin) / (x1 - x0)
3958-
rx1 = alpha * (Xmin - x0) + Xmin
3959-
rx2 = alpha * (Xmax - x0) + Xmin
3960-
if self.get_yscale() == 'log':
3961-
alpha = np.log(Ymax / Ymin) / np.log(y1 / y0)
3962-
ry1 = pow(Ymin / y0, alpha) * Ymin
3963-
ry2 = pow(Ymax / y0, alpha) * Ymin
3964-
else:
3965-
alpha = (Ymax - Ymin) / (y1 - y0)
3966-
ry1 = alpha * (Ymin - y0) + Ymin
3967-
ry2 = alpha * (Ymax - y0) + Ymin
3968-
3969-
if mode == 'x':
3970-
self.set_xlim((rx1, rx2))
3971-
elif mode == 'y':
3972-
self.set_ylim((ry1, ry2))
3973-
else:
3974-
self.set_xlim((rx1, rx2))
3975-
self.set_ylim((ry1, ry2))
3889+
# Original limits.
3890+
xmin0, xmax0 = self.get_xbound()
3891+
ymin0, ymax0 = self.get_ybound()
3892+
# The zoom box in screen coords.
3893+
startx, starty, stopx, stopy = bbox
3894+
# Convert to data coords.
3895+
(startx, starty), (stopx, stopy) = self.transData.inverted().transform(
3896+
[(startx, starty), (stopx, stopy)])
3897+
# Clip to axes limits.
3898+
xmin, xmax = np.clip(sorted([startx, stopx]), xmin0, xmax0)
3899+
ymin, ymax = np.clip(sorted([starty, stopy]), ymin0, ymax0)
3900+
# Don't double-zoom twinned axes or if zooming only the other axis.
3901+
if twinx or mode == "y":
3902+
xmin, xmax = xmin0, xmax0
3903+
if twiny or mode == "x":
3904+
ymin, ymax = ymin0, ymax0
3905+
3906+
if direction == "in":
3907+
new_xbound = xmin, xmax
3908+
new_ybound = ymin, ymax
3909+
3910+
elif direction == "out":
3911+
x_trf = self.xaxis.get_transform()
3912+
sxmin0, sxmax0, sxmin, sxmax = x_trf.transform(
3913+
[xmin0, xmax0, xmin, xmax]) # To screen space.
3914+
factor = (sxmax0 - sxmin0) / (sxmax - sxmin) # Unzoom factor.
3915+
# Move original bounds away by
3916+
# (factor) x (distance between unzoom box and axes bbox).
3917+
sxmin1 = sxmin0 - factor * (sxmin - sxmin0)
3918+
sxmax1 = sxmax0 + factor * (sxmax0 - sxmax)
3919+
# And back to data space.
3920+
new_xbound = x_trf.inverted().transform([sxmin1, sxmax1])
3921+
3922+
y_trf = self.yaxis.get_transform()
3923+
symin0, symax0, symin, symax = y_trf.transform(
3924+
[ymin0, ymax0, ymin, ymax])
3925+
factor = (symax0 - symin0) / (symax - symin)
3926+
symin1 = symin0 - factor * (symin - symin0)
3927+
symax1 = symax0 + factor * (symax0 - symax)
3928+
new_ybound = y_trf.inverted().transform([symin1, symax1])
3929+
3930+
if not twinx and mode != "y":
3931+
self.set_xbound(new_xbound)
3932+
if not twiny and mode != "x":
3933+
self.set_ybound(new_ybound)
39763934

39773935
def start_pan(self, x, y, button):
39783936
"""

‎lib/matplotlib/tests/test_backend_bases.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_backend_bases.py
+46-1Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import re
22

33
from matplotlib.backend_bases import (
4-
FigureCanvasBase, LocationEvent, RendererBase)
4+
FigureCanvasBase, LocationEvent, MouseButton, MouseEvent,
5+
NavigationToolbar2, RendererBase)
56
import matplotlib.pyplot as plt
67
import matplotlib.transforms as transforms
78
import matplotlib.path as path
@@ -99,3 +100,47 @@ def test_location_event_position(x, y):
99100
ax.format_coord(x, y))
100101
ax.fmt_xdata = ax.fmt_ydata = lambda x: "foo"
101102
assert re.match("x=foo +y=foo", ax.format_coord(x, y))
103+
104+
105+
def test_interactive_zoom():
106+
fig, ax = plt.subplots()
107+
ax.set(xscale="logit")
108+
109+
class NT2(NavigationToolbar2):
110+
def _init_toolbar(self): pass
111+
112+
tb = NT2(fig.canvas)
113+
tb.zoom()
114+
115+
xlim0 = ax.get_xlim()
116+
ylim0 = ax.get_ylim()
117+
118+
# Zoom from x=1e-6, y=0.1 to x=1-1e-5, 0.8 (data coordinates, "d").
119+
d0 = (1e-6, 0.1)
120+
d1 = (1-1e-5, 0.8)
121+
# Convert to screen coordinates ("s"). Events are defined only with pixel
122+
# precision, so round the pixel values, and below, check against the
123+
# corresponding xdata/ydata, which are close but not equal to d0/d1.
124+
s0 = ax.transData.transform(d0).astype(int)
125+
s1 = ax.transData.transform(d1).astype(int)
126+
127+
# Zoom in.
128+
start_event = MouseEvent(
129+
"button_press_event", fig.canvas, *s0, MouseButton.LEFT)
130+
fig.canvas.callbacks.process(start_event.name, start_event)
131+
stop_event = MouseEvent(
132+
"button_release_event", fig.canvas, *s1, MouseButton.LEFT)
133+
fig.canvas.callbacks.process(stop_event.name, stop_event)
134+
assert ax.get_xlim() == (start_event.xdata, stop_event.xdata)
135+
assert ax.get_ylim() == (start_event.ydata, stop_event.ydata)
136+
137+
# Zoom out.
138+
start_event = MouseEvent(
139+
"button_press_event", fig.canvas, *s1, MouseButton.RIGHT)
140+
fig.canvas.callbacks.process(start_event.name, start_event)
141+
stop_event = MouseEvent(
142+
"button_release_event", fig.canvas, *s0, MouseButton.RIGHT)
143+
fig.canvas.callbacks.process(stop_event.name, stop_event)
144+
# Absolute tolerance much less than original xmin (1e-7).
145+
assert ax.get_xlim() == pytest.approx(xlim0, rel=0, abs=1e-10)
146+
assert ax.get_ylim() == pytest.approx(ylim0, rel=0, abs=1e-10)

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.