Skip to content

Commit bc305f3

Browse files
authored
Merge pull request #164 from lukelbd/axis-sharing-fixes
Fix axis sharing bugs
2 parents 07dc8b6 + 6c3ee3c commit bc305f3

File tree

5 files changed

+251
-260
lines changed

5 files changed

+251
-260
lines changed

proplot/axes/base.py

Lines changed: 88 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,12 @@ def __init__(self, *args, number=None, main=False, **kwargs):
105105
self._left_panels = []
106106
self._right_panels = []
107107
self._tightbbox = None # bounding boxes are saved
108+
self._panel_hidden = False # True when "filled" with cbar/legend
108109
self._panel_parent = None
109-
self._panel_side = None
110110
self._panel_share = False
111-
self._panel_hidden = False # True when "filled" with cbar/legend
112-
self._sharex_override = False
113-
self._sharey_override = False
111+
self._panel_sharex_group = False
112+
self._panel_sharey_group = False
113+
self._panel_side = None
114114
self._inset_parent = None
115115
self._inset_zoom = False
116116
self._inset_zoom_data = None
@@ -174,9 +174,63 @@ def __init__(self, *args, number=None, main=False, **kwargs):
174174

175175
# Automatic axis sharing and formatting
176176
# TODO: Instead of format() call specific setters
177-
self._share_setup()
177+
self._auto_share_setup()
178178
self.format(rc_mode=1) # mode == 1 applies the rcShortParams
179179

180+
def _auto_share_setup(self):
181+
"""
182+
Automatically configure axis sharing based on the horizontal and
183+
vertical extent of subplots in the figure gridspec.
184+
"""
185+
# Panel axes sharing, between main subplot and its panels
186+
# NOTE: _panel_share means "include this panel in the axis sharing group"
187+
# while _panel_sharex_group indicates the group itself and may include main axes
188+
def shared(paxs):
189+
return [pax for pax in paxs if not pax._panel_hidden and pax._panel_share]
190+
191+
# Internal axis sharing, share stacks of panels and main axes with each other
192+
# NOTE: *This* block is why, even though share[xy] are figure-wide
193+
# settings, we still need the axes-specific _share[xy]_override attr
194+
if not self._panel_side: # this is a main axes
195+
# Top and bottom
196+
bottom = self
197+
paxs = shared(self._bottom_panels)
198+
if paxs:
199+
bottom = paxs[-1]
200+
bottom._panel_sharex_group = False
201+
for iax in (self, *paxs[:-1]):
202+
iax._panel_sharex_group = True
203+
iax._sharex_setup(bottom) # parent is bottom-most
204+
paxs = shared(self._top_panels)
205+
for iax in paxs:
206+
iax._panel_sharex_group = True
207+
iax._sharex_setup(bottom)
208+
# Left and right
209+
# NOTE: Order of panel lists is always inside-to-outside
210+
left = self
211+
paxs = shared(self._left_panels)
212+
if paxs:
213+
left = paxs[-1]
214+
left._panel_sharey_group = False
215+
for iax in (self, *paxs[:-1]):
216+
iax._panel_sharey_group = True
217+
iax._sharey_setup(left) # parent is left-most
218+
paxs = shared(self._right_panels)
219+
for iax in paxs:
220+
iax._panel_sharey_group = True
221+
iax._sharey_setup(left)
222+
223+
# External axes sharing, sometimes overrides panel axes sharing
224+
# NOTE: This can get very repetitive, but probably minimal impact?
225+
# Share x axes
226+
parent, *children = self._get_extent_axes('x')
227+
for child in children:
228+
child._sharex_setup(parent)
229+
# Share y axes
230+
parent, *children = self._get_extent_axes('y')
231+
for child in children:
232+
child._sharey_setup(parent)
233+
180234
def _draw_auto_legends_colorbars(self):
181235
"""
182236
Generate automatic legends and colorbars. Wrapper funcs
@@ -259,10 +313,21 @@ def _hide_panel(self):
259313
self.patch.set_alpha(0)
260314
self._panel_hidden = True
261315

316+
def _is_panel(self):
317+
"""
318+
Return whether the current axes is a panel.
319+
"""
320+
return bool(self._panel_parent)
321+
322+
def _is_panel_parent_or_child(self, other):
323+
"""
324+
Return whether the axes are related.
325+
"""
326+
return self._panel_parent is other or other._panel_parent is self
327+
262328
def _loc_translate(self, loc, mode=None, allow_manual=True):
263329
"""
264-
Return the location string `loc` translated into a standardized
265-
form.
330+
Return the location string `loc` translated into a standardized form.
266331
"""
267332
if mode == 'legend':
268333
valid = tuple(LOC_TRANSLATE.values())
@@ -357,7 +422,7 @@ def _range_tightbbox(self, x):
357422
else:
358423
return bbox.ymin, bbox.ymax
359424

360-
def _reassign_suplabel(self, side):
425+
def _reassign_subplot_label(self, side):
361426
"""
362427
Re-assign the column and row labels to the relevant panel if
363428
present. This is called by `~proplot.figure.Figure._align_suplabel`.
@@ -422,82 +487,21 @@ def _sharex_setup(self, sharex):
422487
Configure x-axis sharing for panels. Main axis sharing is done in
423488
`~CartesianAxes._sharex_setup`.
424489
"""
425-
self._share_short_axis(sharex, 'left')
490+
self._share_short_axis(sharex, 'left') # x axis of left panels
426491
self._share_short_axis(sharex, 'right')
427-
self._share_long_axis(sharex, 'bottom')
492+
self._share_long_axis(sharex, 'bottom') # x axis of bottom panels
428493
self._share_long_axis(sharex, 'top')
429494

430495
def _sharey_setup(self, sharey):
431496
"""
432497
Configure y-axis sharing for panels. Main axis sharing is done in
433498
`~CartesianAxes._sharey_setup`.
434499
"""
435-
self._share_short_axis(sharey, 'bottom')
500+
self._share_short_axis(sharey, 'bottom') # y axis of bottom panels
436501
self._share_short_axis(sharey, 'top')
437-
self._share_long_axis(sharey, 'left')
502+
self._share_long_axis(sharey, 'left') # y axis of left panels
438503
self._share_long_axis(sharey, 'right')
439504

440-
def _share_setup(self):
441-
"""
442-
Automatically configure axis sharing based on the horizontal and
443-
vertical extent of subplots in the figure gridspec.
444-
"""
445-
# Panel axes sharing, between main subplot and its panels
446-
# NOTE: While _panel_share just means "include this panel" in the
447-
# axis sharing between the main subplot and panel children,
448-
# _sharex_override and _sharey_override say "override the sharing level
449-
# for these axes, because they belong to a panel group", and may
450-
# include the main subplot itself.
451-
def shared(paxs):
452-
return [
453-
pax for pax in paxs
454-
if not pax._panel_hidden and pax._panel_share
455-
]
456-
457-
# Internal axis sharing; share stacks of panels and the main
458-
# axes with each other.
459-
# NOTE: *This* block is why, even though share[xy] are figure-wide
460-
# settings, we still need the axes-specific _share[xy]_override attr
461-
if not self._panel_side: # this is a main axes
462-
# Top and bottom
463-
bottom = self
464-
paxs = shared(self._bottom_panels)
465-
if paxs:
466-
bottom = paxs[-1]
467-
bottom._sharex_override = False
468-
for iax in (self, *paxs[:-1]):
469-
iax._sharex_override = True
470-
iax._sharex_setup(bottom) # parent is bottom-most
471-
paxs = shared(self._top_panels)
472-
for iax in paxs:
473-
iax._sharex_override = True
474-
iax._sharex_setup(bottom)
475-
# Left and right
476-
# NOTE: Order of panel lists is always inside-to-outside
477-
left = self
478-
paxs = shared(self._left_panels)
479-
if paxs:
480-
left = paxs[-1]
481-
left._sharey_override = False
482-
for iax in (self, *paxs[:-1]):
483-
iax._sharey_override = True
484-
iax._sharey_setup(left) # parent is left-most
485-
paxs = shared(self._right_panels)
486-
for iax in paxs:
487-
iax._sharey_override = True
488-
iax._sharey_setup(left)
489-
490-
# Main axes, sometimes overrides panel axes sharing
491-
# TODO: This can get very repetitive, but probably minimal impact?
492-
# Share x axes
493-
parent, *children = self._get_extent_axes('x')
494-
for child in children:
495-
child._sharex_setup(parent)
496-
# Share y axes
497-
parent, *children = self._get_extent_axes('y')
498-
for child in children:
499-
child._sharey_setup(parent)
500-
501505
def _share_short_axis(self, share, side):
502506
"""
503507
Share the "short" axes of panels belonging to this subplot
@@ -528,38 +532,6 @@ def _share_long_axis(self, share, side):
528532
for pax in paxs:
529533
getattr(pax, '_share' + axis + '_setup')(share)
530534

531-
def _update_axis_labels(self, x='x', **kwargs):
532-
"""
533-
Apply axis labels to the relevant shared axis. If spanning
534-
labels are toggled this keeps the labels synced for all subplots in
535-
the same row or column. Label positions will be adjusted at draw-time
536-
with figure._align_axislabels.
537-
"""
538-
# TODO: Major issues with this algorithm, especially when twin axes
539-
# are present.
540-
# TODO: Share axis locators and formatters just like matplotlib shares
541-
# axis limits and proplot shares axis labels.
542-
if x not in 'xy':
543-
return
544-
545-
# Update label on this axes
546-
axis = getattr(self, x + 'axis')
547-
axis.label.update(kwargs)
548-
kwargs.pop('color', None)
549-
if getattr(self.figure, '_share' + x) == 0:
550-
return
551-
552-
# Get "shared axes" siblings
553-
ax, *_ = self._get_extent_axes(x, panels=True)
554-
axs = [ax]
555-
if getattr(ax.figure, '_span' + x):
556-
side = axis.get_label_position()
557-
if side in ('left', 'bottom'):
558-
axs = ax._get_side_axes(side, panels=True)
559-
for ax in axs:
560-
axis = getattr(ax, x + 'axis')
561-
axis.label.update(kwargs) # apply to main axes
562-
563535
def _update_title_position(self, renderer):
564536
"""
565537
Update the position of proplot inset titles and builtin matplotlib
@@ -775,7 +747,7 @@ def format(
775747
'fontfamily': 'font.family'
776748
}, context=True)
777749
if suptitle or kw:
778-
fig._update_figtitle(suptitle, **kw)
750+
fig._update_super_title(suptitle, **kw)
779751

780752
# Labels
781753
rlabels = _not_none(rightlabels=rightlabels, rlabels=rlabels)
@@ -797,7 +769,7 @@ def format(
797769
'fontfamily': 'font.family'
798770
}, context=True)
799771
if labels or kw:
800-
fig._update_labels(self, side, labels, **kw)
772+
fig._update_subplot_labels(self, side, labels, **kw)
801773

802774
# Helper function
803775
def sanitize_kw(kw, loc):
@@ -1320,7 +1292,7 @@ def heatmap(self, *args, aspect=None, **kwargs):
13201292
"""
13211293
obj = self.pcolormesh(*args, **kwargs)
13221294
aspect = _not_none(aspect, rc['image.aspect'])
1323-
xlocator, ylocator = None, None
1295+
xlocator = ylocator = None
13241296
if hasattr(obj, '_coordinates'):
13251297
coords = obj._coordinates
13261298
coords = (coords[1:, ...] + coords[:-1, ...]) / 2
@@ -1475,7 +1447,7 @@ def indicate_inset_zoom(
14751447
line.set_visible(visible)
14761448
line_prev.set_visible(False)
14771449
self._inset_zoom_data = (rectpatch, connects)
1478-
return (rectpatch, connects)
1450+
return rectpatch, connects
14791451

14801452
def panel_axes(self, side, **kwargs):
14811453
"""
@@ -1542,9 +1514,8 @@ def parametric(
15421514
number of additional color levels between the line joints
15431515
and the halfway points between line joints.
15441516
scalex, scaley : bool, optional
1545-
These parameters determine if the view limits are adapted to
1546-
the data limits. The values are passed on to
1547-
`~matplotlib.axes.Axes.autoscale_view`.
1517+
Whether the view limits are adapted to the data limits. The values are
1518+
passed on to `~matplotlib.axes.Axes.autoscale_view`.
15481519
15491520
Other parameters
15501521
----------------
@@ -1587,17 +1558,20 @@ def parametric(
15871558
coords, cmap=cmap, norm=norm,
15881559
linestyles='-', capstyle='butt', joinstyle='miter',
15891560
)
1590-
hs.set_array(np.array(values))
1561+
values = np.asarray(values)
1562+
hs.set_array(values)
15911563
hs.update({
15921564
key: value for key, value in kwargs.items()
15931565
if key not in ('color',)
15941566
})
15951567

15961568
# Add collection with some custom attributes
1569+
# NOTE: Modern API uses self._request_autoscale_view but this is
1570+
# backwards compatible to earliest matplotlib versions.
15971571
self.add_collection(hs)
15981572
self.autoscale_view(scalex=scalex, scaley=scaley)
15991573
hs.values = values
1600-
hs.levels = levels # needed for other functions some
1574+
hs.levels = levels # needed for other functions
16011575
return hs
16021576

16031577
def violins(self, *args, **kwargs):

0 commit comments

Comments
 (0)