Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit eb53398

Browse files
committed
Merge pull request #4795 from QuLogic/custom-axes-view
ENH: Add API to manage view state in custom Axes.
2 parents a81b8fc + 7e582b1 commit eb53398

File tree

3 files changed

+193
-196
lines changed

3 files changed

+193
-196
lines changed

lib/matplotlib/axes/_base.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3139,6 +3139,167 @@ def set_navigate_mode(self, b):
31393139
"""
31403140
self._navigate_mode = b
31413141

3142+
def _get_view(self):
3143+
"""
3144+
Save information required to reproduce the current view.
3145+
3146+
Called before a view is changed, such as during a pan or zoom
3147+
initiated by the user. You may return any information you deem
3148+
necessary to describe the view.
3149+
3150+
.. note::
3151+
3152+
Intended to be overridden by new projection types, but if not, the
3153+
default implementation saves the view limits. You *must* implement
3154+
:meth:`_set_view` if you implement this method.
3155+
"""
3156+
xmin, xmax = self.get_xlim()
3157+
ymin, ymax = self.get_ylim()
3158+
return (xmin, xmax, ymin, ymax)
3159+
3160+
def _set_view(self, view):
3161+
"""
3162+
Apply a previously saved view.
3163+
3164+
Called when restoring a view, such as with the navigation buttons.
3165+
3166+
.. note::
3167+
3168+
Intended to be overridden by new projection types, but if not, the
3169+
default implementation restores the view limits. You *must*
3170+
implement :meth:`_get_view` if you implement this method.
3171+
"""
3172+
xmin, xmax, ymin, ymax = view
3173+
self.set_xlim((xmin, xmax))
3174+
self.set_ylim((ymin, ymax))
3175+
3176+
def _set_view_from_bbox(self, bbox, original_view, direction='in',
3177+
mode=None, twinx=False, twiny=False):
3178+
"""
3179+
Update view from a selection bbox.
3180+
3181+
.. note::
3182+
3183+
Intended to be overridden by new projection types, but if not, the
3184+
default implementation sets the view limits to the bbox directly.
3185+
3186+
Parameters
3187+
----------
3188+
3189+
bbox : tuple
3190+
The selected bounding box limits, in *display* coordinates.
3191+
3192+
original_view : any
3193+
A view saved from before initiating the selection, the result of
3194+
calling :meth:`_get_view`.
3195+
3196+
direction : str
3197+
The direction to apply the bounding box.
3198+
* `'in'` - The bounding box describes the view directly, i.e.,
3199+
it zooms in.
3200+
* `'out'` - The bounding box describes the size to make the
3201+
existing view, i.e., it zooms out.
3202+
3203+
mode : str or None
3204+
The selection mode, whether to apply the bounding box in only the
3205+
`'x'` direction, `'y'` direction or both (`None`).
3206+
3207+
twinx : bool
3208+
Whether this axis is twinned in the *x*-direction.
3209+
3210+
twiny : bool
3211+
Whether this axis is twinned in the *y*-direction.
3212+
"""
3213+
3214+
lastx, lasty, x, y = bbox
3215+
3216+
x0, y0, x1, y1 = original_view
3217+
3218+
# zoom to rect
3219+
inverse = self.transData.inverted()
3220+
lastx, lasty = inverse.transform_point((lastx, lasty))
3221+
x, y = inverse.transform_point((x, y))
3222+
Xmin, Xmax = self.get_xlim()
3223+
Ymin, Ymax = self.get_ylim()
3224+
3225+
if twinx:
3226+
x0, x1 = Xmin, Xmax
3227+
else:
3228+
if Xmin < Xmax:
3229+
if x < lastx:
3230+
x0, x1 = x, lastx
3231+
else:
3232+
x0, x1 = lastx, x
3233+
if x0 < Xmin:
3234+
x0 = Xmin
3235+
if x1 > Xmax:
3236+
x1 = Xmax
3237+
else:
3238+
if x > lastx:
3239+
x0, x1 = x, lastx
3240+
else:
3241+
x0, x1 = lastx, x
3242+
if x0 > Xmin:
3243+
x0 = Xmin
3244+
if x1 < Xmax:
3245+
x1 = Xmax
3246+
3247+
if twiny:
3248+
y0, y1 = Ymin, Ymax
3249+
else:
3250+
if Ymin < Ymax:
3251+
if y < lasty:
3252+
y0, y1 = y, lasty
3253+
else:
3254+
y0, y1 = lasty, y
3255+
if y0 < Ymin:
3256+
y0 = Ymin
3257+
if y1 > Ymax:
3258+
y1 = Ymax
3259+
else:
3260+
if y > lasty:
3261+
y0, y1 = y, lasty
3262+
else:
3263+
y0, y1 = lasty, y
3264+
if y0 > Ymin:
3265+
y0 = Ymin
3266+
if y1 < Ymax:
3267+
y1 = Ymax
3268+
3269+
if direction == 'in':
3270+
if mode == 'x':
3271+
self.set_xlim((x0, x1))
3272+
elif mode == 'y':
3273+
self.set_ylim((y0, y1))
3274+
else:
3275+
self.set_xlim((x0, x1))
3276+
self.set_ylim((y0, y1))
3277+
elif direction == 'out':
3278+
if self.get_xscale() == 'log':
3279+
alpha = np.log(Xmax / Xmin) / np.log(x1 / x0)
3280+
rx1 = pow(Xmin / x0, alpha) * Xmin
3281+
rx2 = pow(Xmax / x0, alpha) * Xmin
3282+
else:
3283+
alpha = (Xmax - Xmin) / (x1 - x0)
3284+
rx1 = alpha * (Xmin - x0) + Xmin
3285+
rx2 = alpha * (Xmax - x0) + Xmin
3286+
if self.get_yscale() == 'log':
3287+
alpha = np.log(Ymax / Ymin) / np.log(y1 / y0)
3288+
ry1 = pow(Ymin / y0, alpha) * Ymin
3289+
ry2 = pow(Ymax / y0, alpha) * Ymin
3290+
else:
3291+
alpha = (Ymax - Ymin) / (y1 - y0)
3292+
ry1 = alpha * (Ymin - y0) + Ymin
3293+
ry2 = alpha * (Ymax - y0) + Ymin
3294+
3295+
if mode == 'x':
3296+
self.set_xlim((rx1, rx2))
3297+
elif mode == 'y':
3298+
self.set_ylim((ry1, ry2))
3299+
else:
3300+
self.set_xlim((rx1, rx2))
3301+
self.set_ylim((ry1, ry2))
3302+
31423303
def start_pan(self, x, y, button):
31433304
"""
31443305
Called when a pan operation has started.

lib/matplotlib/backend_bases.py

Lines changed: 16 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -2940,8 +2940,7 @@ def press_zoom(self, event):
29402940
for i, a in enumerate(self.canvas.figure.get_axes()):
29412941
if (x is not None and y is not None and a.in_axes(event) and
29422942
a.get_navigate() and a.can_zoom()):
2943-
self._xypress.append((x, y, a, i, a.viewLim.frozen(),
2944-
a.transData.frozen()))
2943+
self._xypress.append((x, y, a, i, a._get_view()))
29452944

29462945
id1 = self.canvas.mpl_connect('motion_notify_event', self.drag_zoom)
29472946
id2 = self.canvas.mpl_connect('key_press_event',
@@ -2964,17 +2963,15 @@ def _switch_off_zoom_mode(self, event):
29642963

29652964
def push_current(self):
29662965
"""push the current view limits and position onto the stack"""
2967-
lims = []
2966+
views = []
29682967
pos = []
29692968
for a in self.canvas.figure.get_axes():
2970-
xmin, xmax = a.get_xlim()
2971-
ymin, ymax = a.get_ylim()
2972-
lims.append((xmin, xmax, ymin, ymax))
2969+
views.append(a._get_view())
29732970
# Store both the original and modified positions
29742971
pos.append((
29752972
a.get_position(True).frozen(),
29762973
a.get_position().frozen()))
2977-
self._views.push(lims)
2974+
self._views.push(views)
29782975
self._positions.push(pos)
29792976
self.set_history_buttons()
29802977

@@ -3014,7 +3011,7 @@ def drag_zoom(self, event):
30143011

30153012
if self._xypress:
30163013
x, y = event.x, event.y
3017-
lastx, lasty, a, ind, lim, trans = self._xypress[0]
3014+
lastx, lasty, a, ind, view = self._xypress[0]
30183015

30193016
# adjust x, last, y, last
30203017
x1, y1, x2, y2 = a.bbox.extents
@@ -3043,23 +3040,14 @@ def release_zoom(self, event):
30433040

30443041
for cur_xypress in self._xypress:
30453042
x, y = event.x, event.y
3046-
lastx, lasty, a, ind, lim, trans = cur_xypress
3043+
lastx, lasty, a, ind, view = cur_xypress
30473044
# ignore singular clicks - 5 pixels is a threshold
30483045
if abs(x - lastx) < 5 or abs(y - lasty) < 5:
30493046
self._xypress = None
30503047
self.release(event)
30513048
self.draw()
30523049
return
30533050

3054-
x0, y0, x1, y1 = lim.extents
3055-
3056-
# zoom to rect
3057-
inverse = a.transData.inverted()
3058-
lastx, lasty = inverse.transform_point((lastx, lasty))
3059-
x, y = inverse.transform_point((x, y))
3060-
Xmin, Xmax = a.get_xlim()
3061-
Ymin, Ymax = a.get_ylim()
3062-
30633051
# detect twinx,y axes and avoid double zooming
30643052
twinx, twiny = False, False
30653053
if last_a:
@@ -3070,83 +3058,15 @@ def release_zoom(self, event):
30703058
twiny = True
30713059
last_a.append(a)
30723060

3073-
if twinx:
3074-
x0, x1 = Xmin, Xmax
3075-
else:
3076-
if Xmin < Xmax:
3077-
if x < lastx:
3078-
x0, x1 = x, lastx
3079-
else:
3080-
x0, x1 = lastx, x
3081-
if x0 < Xmin:
3082-
x0 = Xmin
3083-
if x1 > Xmax:
3084-
x1 = Xmax
3085-
else:
3086-
if x > lastx:
3087-
x0, x1 = x, lastx
3088-
else:
3089-
x0, x1 = lastx, x
3090-
if x0 > Xmin:
3091-
x0 = Xmin
3092-
if x1 < Xmax:
3093-
x1 = Xmax
3094-
3095-
if twiny:
3096-
y0, y1 = Ymin, Ymax
3097-
else:
3098-
if Ymin < Ymax:
3099-
if y < lasty:
3100-
y0, y1 = y, lasty
3101-
else:
3102-
y0, y1 = lasty, y
3103-
if y0 < Ymin:
3104-
y0 = Ymin
3105-
if y1 > Ymax:
3106-
y1 = Ymax
3107-
else:
3108-
if y > lasty:
3109-
y0, y1 = y, lasty
3110-
else:
3111-
y0, y1 = lasty, y
3112-
if y0 > Ymin:
3113-
y0 = Ymin
3114-
if y1 < Ymax:
3115-
y1 = Ymax
3116-
31173061
if self._button_pressed == 1:
3118-
if self._zoom_mode == "x":
3119-
a.set_xlim((x0, x1))
3120-
elif self._zoom_mode == "y":
3121-
a.set_ylim((y0, y1))
3122-
else:
3123-
a.set_xlim((x0, x1))
3124-
a.set_ylim((y0, y1))
3062+
direction = 'in'
31253063
elif self._button_pressed == 3:
3126-
if a.get_xscale() == 'log':
3127-
alpha = np.log(Xmax / Xmin) / np.log(x1 / x0)
3128-
rx1 = pow(Xmin / x0, alpha) * Xmin
3129-
rx2 = pow(Xmax / x0, alpha) * Xmin
3130-
else:
3131-
alpha = (Xmax - Xmin) / (x1 - x0)
3132-
rx1 = alpha * (Xmin - x0) + Xmin
3133-
rx2 = alpha * (Xmax - x0) + Xmin
3134-
if a.get_yscale() == 'log':
3135-
alpha = np.log(Ymax / Ymin) / np.log(y1 / y0)
3136-
ry1 = pow(Ymin / y0, alpha) * Ymin
3137-
ry2 = pow(Ymax / y0, alpha) * Ymin
3138-
else:
3139-
alpha = (Ymax - Ymin) / (y1 - y0)
3140-
ry1 = alpha * (Ymin - y0) + Ymin
3141-
ry2 = alpha * (Ymax - y0) + Ymin
3142-
3143-
if self._zoom_mode == "x":
3144-
a.set_xlim((rx1, rx2))
3145-
elif self._zoom_mode == "y":
3146-
a.set_ylim((ry1, ry2))
3147-
else:
3148-
a.set_xlim((rx1, rx2))
3149-
a.set_ylim((ry1, ry2))
3064+
direction = 'out'
3065+
else:
3066+
continue
3067+
3068+
a._set_view_from_bbox((lastx, lasty, x, y), view, direction,
3069+
self._zoom_mode, twinx, twiny)
31503070

31513071
self.draw()
31523072
self._xypress = None
@@ -3179,16 +3099,14 @@ def _update_view(self):
31793099
position stack for each axes
31803100
"""
31813101

3182-
lims = self._views()
3183-
if lims is None:
3102+
views = self._views()
3103+
if views is None:
31843104
return
31853105
pos = self._positions()
31863106
if pos is None:
31873107
return
31883108
for i, a in enumerate(self.canvas.figure.get_axes()):
3189-
xmin, xmax, ymin, ymax = lims[i]
3190-
a.set_xlim((xmin, xmax))
3191-
a.set_ylim((ymin, ymax))
3109+
a._set_view(views[i])
31923110
# Restore both the original and modified positions
31933111
a.set_position(pos[i][0], 'original')
31943112
a.set_position(pos[i][1], 'active')

0 commit comments

Comments
 (0)