From de8dc8a123be0ccf9410433de996bbdf6c244d17 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Thu, 6 May 2021 10:50:07 -0700 Subject: [PATCH] Backport PR #20150: Rename mosaic layout Merge pull request #20150 from tacaswell/rename_mosaic_layout Rename mosaic layout --- .../next_api_changes/behavior/20150-TAC.rst | 10 +++ lib/matplotlib/figure.py | 72 +++++++++--------- lib/matplotlib/pyplot.py | 6 +- tutorials/provisional/mosaic.py | 76 ++++++++++++------- 4 files changed, 96 insertions(+), 68 deletions(-) create mode 100644 doc/api/next_api_changes/behavior/20150-TAC.rst diff --git a/doc/api/next_api_changes/behavior/20150-TAC.rst b/doc/api/next_api_changes/behavior/20150-TAC.rst new file mode 100644 index 000000000000..e5109d9afa43 --- /dev/null +++ b/doc/api/next_api_changes/behavior/20150-TAC.rst @@ -0,0 +1,10 @@ +Rename fist arg to subplot_mosaic +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Both `.FigureBase.subplot_mosaic`, and `.pyplot.subplot_mosaic` have had the +first position argument renamed from *layout* to *mosaic*. This is because we +are considering to consolidate *constrained_layout* and *tight_layout* keyword +arguments in the Figure creation functions of `.pyplot` into a single *layout* +keyword argument which would collide. + +As this API is provisional, we are changing this with no deprecation period. diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 2af6e3db1e1c..ba670f55c63c 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1674,7 +1674,7 @@ def _normalize_grid_string(layout): layout = inspect.cleandoc(layout) return [list(ln) for ln in layout.strip('\n').split('\n')] - def subplot_mosaic(self, layout, *, subplot_kw=None, gridspec_kw=None, + def subplot_mosaic(self, mosaic, *, subplot_kw=None, gridspec_kw=None, empty_sentinel='.'): """ Build a layout of Axes based on ASCII art or nested lists. @@ -1689,7 +1689,7 @@ def subplot_mosaic(self, layout, *, subplot_kw=None, gridspec_kw=None, Parameters ---------- - layout : list of list of {hashable or nested} or str + mosaic : list of list of {hashable or nested} or str A visual layout of how you want your Axes to be arranged labeled as strings. For example :: @@ -1748,8 +1748,8 @@ def subplot_mosaic(self, layout, *, subplot_kw=None, gridspec_kw=None, subplot_kw = subplot_kw or {} gridspec_kw = gridspec_kw or {} # special-case string input - if isinstance(layout, str): - layout = self._normalize_grid_string(layout) + if isinstance(mosaic, str): + mosaic = self._normalize_grid_string(mosaic) def _make_array(inp): """ @@ -1767,10 +1767,10 @@ def _make_array(inp): """ r0, *rest = inp if isinstance(r0, str): - raise ValueError('List layout specification must be 2D') + raise ValueError('List mosaic specification must be 2D') for j, r in enumerate(rest, start=1): if isinstance(r, str): - raise ValueError('List layout specification must be 2D') + raise ValueError('List mosaic specification must be 2D') if len(r0) != len(r): raise ValueError( "All of the rows must be the same length, however " @@ -1783,24 +1783,24 @@ def _make_array(inp): out[j, k] = v return out - def _identify_keys_and_nested(layout): + def _identify_keys_and_nested(mosaic): """ - Given a 2D object array, identify unique IDs and nested layouts + Given a 2D object array, identify unique IDs and nested mosaics Parameters ---------- - layout : 2D numpy object array + mosaic : 2D numpy object array Returns ------- unique_ids : tuple - The unique non-sub layout entries in this layout + The unique non-sub mosaic entries in this mosaic nested : dict[tuple[int, int]], 2D object array """ # make sure we preserve the user supplied order unique_ids = cbook._OrderedSet() nested = {} - for j, row in enumerate(layout): + for j, row in enumerate(mosaic): for k, v in enumerate(row): if v == empty_sentinel: continue @@ -1811,58 +1811,58 @@ def _identify_keys_and_nested(layout): return tuple(unique_ids), nested - def _do_layout(gs, layout, unique_ids, nested): + def _do_layout(gs, mosaic, unique_ids, nested): """ - Recursively do the layout. + Recursively do the mosaic. Parameters ---------- gs : GridSpec - layout : 2D object array + mosaic : 2D object array The input converted to a 2D numpy array for this level. unique_ids : tuple The identified scalar labels at this level of nesting. nested : dict[tuple[int, int]], 2D object array - The identified nested layouts, if any. + The identified nested mosaics, if any. Returns ------- dict[label, Axes] A flat dict of all of the Axes created. """ - rows, cols = layout.shape + rows, cols = mosaic.shape output = dict() # we need to merge together the Axes at this level and the axes - # in the (recursively) nested sub-layouts so that we can add + # in the (recursively) nested sub-mosaics so that we can add # them to the figure in the "natural" order if you were to # ravel in c-order all of the Axes that will be created # # This will stash the upper left index of each object (axes or - # nested layout) at this level + # nested mosaic) at this level this_level = dict() # go through the unique keys, for name in unique_ids: # sort out where each axes starts/ends - indx = np.argwhere(layout == name) + indx = np.argwhere(mosaic == name) start_row, start_col = np.min(indx, axis=0) end_row, end_col = np.max(indx, axis=0) + 1 # and construct the slice object slc = (slice(start_row, end_row), slice(start_col, end_col)) # some light error checking - if (layout[slc] != name).any(): + if (mosaic[slc] != name).any(): raise ValueError( - f"While trying to layout\n{layout!r}\n" + f"While trying to layout\n{mosaic!r}\n" f"we found that the label {name!r} specifies a " "non-rectangular or non-contiguous area.") # and stash this slice for later this_level[(start_row, start_col)] = (name, slc, 'axes') - # do the same thing for the nested layouts (simpler because these + # do the same thing for the nested mosaics (simpler because these # can not be spans yet!) - for (j, k), nested_layout in nested.items(): - this_level[(j, k)] = (None, nested_layout, 'nested') + for (j, k), nested_mosaic in nested.items(): + this_level[(j, k)] = (None, nested_mosaic, 'nested') # now go through the things in this level and add them # in order left-to-right top-to-bottom @@ -1870,43 +1870,43 @@ def _do_layout(gs, layout, unique_ids, nested): name, arg, method = this_level[key] # we are doing some hokey function dispatch here based # on the 'method' string stashed above to sort out if this - # element is an axes or a nested layout. + # element is an axes or a nested mosaic. if method == 'axes': slc = arg # add a single axes if name in output: raise ValueError(f"There are duplicate keys {name} " - f"in the layout\n{layout!r}") + f"in the layout\n{mosaic!r}") ax = self.add_subplot( gs[slc], **{'label': str(name), **subplot_kw} ) output[name] = ax elif method == 'nested': - nested_layout = arg + nested_mosaic = arg j, k = key - # recursively add the nested layout - rows, cols = nested_layout.shape + # recursively add the nested mosaic + rows, cols = nested_mosaic.shape nested_output = _do_layout( gs[j, k].subgridspec(rows, cols, **gridspec_kw), - nested_layout, - *_identify_keys_and_nested(nested_layout) + nested_mosaic, + *_identify_keys_and_nested(nested_mosaic) ) overlap = set(output) & set(nested_output) if overlap: raise ValueError( f"There are duplicate keys {overlap} " - f"between the outer layout\n{layout!r}\n" - f"and the nested layout\n{nested_layout}" + f"between the outer layout\n{mosaic!r}\n" + f"and the nested layout\n{nested_mosaic}" ) output.update(nested_output) else: raise RuntimeError("This should never happen") return output - layout = _make_array(layout) - rows, cols = layout.shape + mosaic = _make_array(mosaic) + rows, cols = mosaic.shape gs = self.add_gridspec(rows, cols, **gridspec_kw) - ret = _do_layout(gs, layout, *_identify_keys_and_nested(layout)) + ret = _do_layout(gs, mosaic, *_identify_keys_and_nested(mosaic)) for k, ax in ret.items(): if isinstance(k, str): ax.set_label(k) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 755535096186..030ae51e7f27 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -1443,7 +1443,7 @@ def subplots(nrows=1, ncols=1, sharex=False, sharey=False, squeeze=True, return fig, axs -def subplot_mosaic(layout, *, subplot_kw=None, gridspec_kw=None, +def subplot_mosaic(mosaic, *, subplot_kw=None, gridspec_kw=None, empty_sentinel='.', **fig_kw): """ Build a layout of Axes based on ASCII art or nested lists. @@ -1458,7 +1458,7 @@ def subplot_mosaic(layout, *, subplot_kw=None, gridspec_kw=None, Parameters ---------- - layout : list of list of {hashable or nested} or str + mosaic : list of list of {hashable or nested} or str A visual layout of how you want your Axes to be arranged labeled as strings. For example :: @@ -1518,7 +1518,7 @@ def subplot_mosaic(layout, *, subplot_kw=None, gridspec_kw=None, """ fig = figure(**fig_kw) ax_dict = fig.subplot_mosaic( - layout, + mosaic, subplot_kw=subplot_kw, gridspec_kw=gridspec_kw, empty_sentinel=empty_sentinel diff --git a/tutorials/provisional/mosaic.py b/tutorials/provisional/mosaic.py index 64039107a927..120e80d97d5d 100644 --- a/tutorials/provisional/mosaic.py +++ b/tutorials/provisional/mosaic.py @@ -66,27 +66,30 @@ def identify_axes(ax_dict, fontsize=48): fig = plt.figure(constrained_layout=True) ax_array = fig.subplots(2, 2, squeeze=False) -ax_array[0, 0].bar(['a', 'b', 'c'], [5, 7, 9]) +ax_array[0, 0].bar(["a", "b", "c"], [5, 7, 9]) ax_array[0, 1].plot([1, 2, 3]) -ax_array[1, 0].hist(hist_data, bins='auto') +ax_array[1, 0].hist(hist_data, bins="auto") ax_array[1, 1].imshow([[1, 2], [2, 1]]) identify_axes( - {(j, k): a for j, r in enumerate(ax_array) for k, a in enumerate(r)} + {(j, k): a for j, r in enumerate(ax_array) for k, a in enumerate(r)}, ) ############################################################################### -# Using `.Figure.subplot_mosaic` we can produce the same layout but give the +# Using `.Figure.subplot_mosaic` we can produce the same mosaic but give the # axes semantic names fig = plt.figure(constrained_layout=True) ax_dict = fig.subplot_mosaic( - [['bar', 'plot'], - ['hist', 'image']]) -ax_dict['bar'].bar(['a', 'b', 'c'], [5, 7, 9]) -ax_dict['plot'].plot([1, 2, 3]) -ax_dict['hist'].hist(hist_data) -ax_dict['image'].imshow([[1, 2], [2, 1]]) + [ + ["bar", "plot"], + ["hist", "image"], + ], +) +ax_dict["bar"].bar(["a", "b", "c"], [5, 7, 9]) +ax_dict["plot"].plot([1, 2, 3]) +ax_dict["hist"].hist(hist_data) +ax_dict["image"].imshow([[1, 2], [2, 1]]) identify_axes(ax_dict) ############################################################################### @@ -106,18 +109,18 @@ def identify_axes(ax_dict, fontsize=48): # "draw" the Axes we want as "ASCII art". The following -layout = """ +mosaic = """ AB CD """ ############################################################################### # will give us 4 Axes laid out in a 2x2 grid and generates the same -# figure layout as above (but now labeled with ``{"A", "B", "C", +# figure mosaic as above (but now labeled with ``{"A", "B", "C", # "D"}`` rather than ``{"bar", "plot", "hist", "image"}``). fig = plt.figure(constrained_layout=True) -ax_dict = fig.subplot_mosaic(layout) +ax_dict = fig.subplot_mosaic(mosaic) identify_axes(ax_dict) @@ -183,7 +186,7 @@ def identify_axes(ax_dict, fontsize=48): # empty sentinel with the string shorthand because it may be stripped # while processing the input. # -# Controlling layout and subplot creation +# Controlling mosaic and subplot creation # ======================================= # # This feature is built on top of `.gridspec` and you can pass the @@ -211,14 +214,14 @@ def identify_axes(ax_dict, fontsize=48): ############################################################################### # Or use the {*left*, *right*, *bottom*, *top*} keyword arguments to -# position the overall layout to put multiple versions of the same -# layout in a figure +# position the overall mosaic to put multiple versions of the same +# mosaic in a figure -layout = """AA +mosaic = """AA BC""" fig = plt.figure() axd = fig.subplot_mosaic( - layout, + mosaic, gridspec_kw={ "bottom": 0.25, "top": 0.95, @@ -231,7 +234,7 @@ def identify_axes(ax_dict, fontsize=48): identify_axes(axd) axd = fig.subplot_mosaic( - layout, + mosaic, gridspec_kw={ "bottom": 0.05, "top": 0.75, @@ -243,6 +246,19 @@ def identify_axes(ax_dict, fontsize=48): ) identify_axes(axd) +############################################################################### +# Alternatively, you can use the sub-Figure functionality: + +mosaic = """AA + BC""" +fig = plt.figure(constrained_layout=True) +left, right = fig.subfigures(nrows=1, ncols=2) +axd = left.subplot_mosaic(mosaic) +identify_axes(axd) + +axd = right.subplot_mosaic(mosaic) +identify_axes(axd) + ############################################################################### # We can also pass through arguments used to create the subplots @@ -264,17 +280,18 @@ def identify_axes(ax_dict, fontsize=48): # list), for example using spans, blanks, and *gridspec_kw*: axd = plt.figure(constrained_layout=True).subplot_mosaic( - [["main", "zoom"], - ["main", "BLANK"] - ], + [ + ["main", "zoom"], + ["main", "BLANK"], + ], empty_sentinel="BLANK", - gridspec_kw={"width_ratios": [2, 1]} + gridspec_kw={"width_ratios": [2, 1]}, ) identify_axes(axd) ############################################################################### -# In addition, using the list input we can specify nested layouts. Any element +# In addition, using the list input we can specify nested mosaics. Any element # of the inner list can be another set of nested lists: inner = [ @@ -282,22 +299,23 @@ def identify_axes(ax_dict, fontsize=48): ["inner B"], ] -outer_nested_layout = [ +outer_nested_mosaic = [ ["main", inner], ["bottom", "bottom"], ] axd = plt.figure(constrained_layout=True).subplot_mosaic( - outer_nested_layout, empty_sentinel=None + outer_nested_mosaic, empty_sentinel=None ) identify_axes(axd, fontsize=36) ############################################################################### # We can also pass in a 2D NumPy array to do things like -layout = np.zeros((4, 4), dtype=int) +mosaic = np.zeros((4, 4), dtype=int) for j in range(4): - layout[j, j] = j + 1 + mosaic[j, j] = j + 1 axd = plt.figure(constrained_layout=True).subplot_mosaic( - layout, empty_sentinel=0 + mosaic, + empty_sentinel=0, ) identify_axes(axd)