| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| Add API for composing semantic axes layouts from text or nested lists | ||
| --------------------------------------------------------------------- | ||
|
|
||
| The `.Figure` class has a provisional method to generate complex grids | ||
| of named `.axes.Axes` based on nested list input or ASCII art: | ||
|
|
||
| .. plot:: | ||
| :include-source: True | ||
|
|
||
| axd = plt.figure(constrained_layout=True).subplot_mosaic( | ||
| [["Top", "Top", "Edge"], | ||
| ["Left", ".", "Edge"]] | ||
| ) | ||
| for k, ax in axd.items(): | ||
| ax.text(0.5, 0.5, k, | ||
| ha='center', va='center', fontsize=36, | ||
| color='darkgrey') | ||
|
|
||
| or as a string (with single-character Axes labels): | ||
|
|
||
| .. plot:: | ||
| :include-source: True | ||
|
|
||
| axd = plt.figure(constrained_layout=True).subplot_mosaic( | ||
| """ | ||
|
Comment thread
tacaswell marked this conversation as resolved.
|
||
| TTE | ||
| L.E | ||
| """) | ||
| for k, ax in axd.items(): | ||
| ax.text(0.5, 0.5, k, | ||
| ha='center', va='center', fontsize=36, | ||
| color='darkgrey') | ||
|
|
||
|
|
||
|
|
||
| See :ref:`sphx_glr_tutorials_provisional_mosaic.py` for more | ||
| details and examples. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| Expand Up | @@ -166,6 +166,23 @@ def _make_twin_axes(self, *args, **kwargs): | |
| self._twinned_axes.join(self, twin) | ||
| return twin | ||
|
|
||
| def __repr__(self): | ||
| fields = [] | ||
| if self.get_label(): | ||
| fields += [f"label={self.get_label()!r}"] | ||
| titles = [] | ||
| for k in ["left", "center", "right"]: | ||
| title = self.get_title(loc=k) | ||
| if title: | ||
| titles.append(f"{k!r}:{title!r}") | ||
| if titles: | ||
| fields += ["title={" + ",".join(titles) + "}"] | ||
| if self.get_xlabel(): | ||
| fields += [f"xlabel={self.get_xlabel()!r}"] | ||
| if self.get_ylabel(): | ||
| fields += [f"ylabel={self.get_ylabel()!r}"] | ||
|
Comment thread
Copy link
Copy Markdown
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityProbably should update Axes3D, too? Or just do a if hasattr(self, 'get_zlabel()') or something?
Sorry, something went wrong.
All reactions
Copy link
Copy Markdown
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityI think it would be better to have a custom repr on the Axes 3D than to inject knowledge of it here. The subtle but pervasive interconnection is one of the things that makes maintaining Matplotlib hard.
Sorry, something went wrong.
All reactions
|
||
| return f"<{self.__class__.__name__}:" + ", ".join(fields) + ">" | ||
|
|
||
|
|
||
| # this here to support cartopy which was using a private part of the | ||
| # API to register their Axes subclasses. | ||
| Expand Down | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| Expand Up | @@ -8,6 +8,7 @@ | |||||
| Control the default spacing between subplots. | ||||||
| """ | ||||||
|
|
||||||
| import inspect | ||||||
| import logging | ||||||
| from numbers import Integral | ||||||
|
|
||||||
| Expand Down Expand Up | @@ -1522,6 +1523,204 @@ def subplots(self, nrows=1, ncols=1, sharex=False, sharey=False, | |||||
| .subplots(sharex=sharex, sharey=sharey, squeeze=squeeze, | ||||||
| subplot_kw=subplot_kw)) | ||||||
|
|
||||||
| @staticmethod | ||||||
| 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, | ||||||
| empty_sentinel='.'): | ||||||
|
Comment thread
Copy link
Copy Markdown
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityI'm 👍 on empty_sentinel kwarg
Sorry, something went wrong.
All reactions
|
||||||
| """ | ||||||
| Build a layout of Axes based on ASCII art or nested lists. | ||||||
|
Comment thread
Copy link
Copy Markdown
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityASCII art should be replaced with string
Sorry, something went wrong.
All reactions
Copy link
Copy Markdown
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityIt has type str, but it has extra meaning (it should look like the layout you want) which I think is captured by ASCII art. I don't like that it suggests only ASCII will work, but I don't think anyone would understand "unicode-art"?
Sorry, something went wrong.
All reactions
Copy link
Copy Markdown
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityI think art implies well art and that's kind of idiomatic/metaphorical when it's literally a string of characters.
Sorry, something went wrong.
All reactions
|
||||||
|
|
||||||
| This is a helper function to build complex GridSpec layouts visually. | ||||||
|
|
||||||
| .. note :: | ||||||
|
|
||||||
| This API is provisional and may be revised in the future based on | ||||||
| early user feedback. | ||||||
|
|
||||||
|
|
||||||
| Parameters | ||||||
| ---------- | ||||||
| layout : list of list of {hashable or nested} or str | ||||||
|
Comment thread
Copy link
Copy Markdown
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Qualityhashable: Is there any benefit from using non-strings as keys? Various places in this PR imply these are strings, e.g. the Return section below:
I would formally only specify str for now even though more may work out of the box. One can always expand later.
Suggested change
Sorry, something went wrong.
All reactions
Copy link
Copy Markdown
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityArrays of Axes work and integers work (at @ImportanceOfBeingErnest 's request)
Sorry, something went wrong.
All reactions
Copy link
Copy Markdown
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityOn thing we should decide is if tuple should be treated like strings/scalars (they are hashable) or like lists (they are iterables)
Sorry, something went wrong.
All reactions
|
||||||
|
|
||||||
| A visual layout of how you want your Axes to be arranged | ||||||
| labeled as strings. For example :: | ||||||
|
|
||||||
| x = [['A panel', 'A panel', 'edge'], | ||||||
| ['C panel', '.', 'edge']] | ||||||
|
|
||||||
| Produces 4 axes: | ||||||
|
|
||||||
| - 'A panel' which is 1 row high and spans the first two columns | ||||||
| - 'edge' which is 2 rows high and is on the right edge | ||||||
| - 'C panel' which in 1 row and 1 column wide in the bottom left | ||||||
| - a blank space 1 row and 1 column wide in the bottom center | ||||||
|
|
||||||
| Any of the entries in the layout can be a list of lists | ||||||
| of the same form to create nested layouts. | ||||||
|
|
||||||
| If input is a str, then it must be of the form :: | ||||||
|
|
||||||
| ''' | ||||||
| AAE | ||||||
| C.E | ||||||
| ''' | ||||||
|
Comment thread
efiring marked this conversation as resolved.
|
||||||
|
|
||||||
| where each character is a column and each line is a row. | ||||||
| This only allows only single character Axes labels and does | ||||||
| not allow nesting but is very terse. | ||||||
|
|
||||||
| subplot_kw : dict, optional | ||||||
| Dictionary with keywords passed to the `.Figure.add_subplot` call | ||||||
| used to create each subplot. | ||||||
|
|
||||||
| gridspec_kw : dict, optional | ||||||
| Dictionary with keywords passed to the `.GridSpec` constructor used | ||||||
| to create the grid the subplots are placed on. | ||||||
|
|
||||||
| empty_sentinel : object, optional | ||||||
|
Comment thread
efiring marked this conversation as resolved.
|
||||||
| Entry in the layout to mean "leave this space empty". Defaults | ||||||
| to ``'.'``. Note, if *layout* is a string, it is processed via | ||||||
| `inspect.cleandoc` to remove leading white space, which may | ||||||
| interfere with using white-space as the empty sentinel. | ||||||
|
|
||||||
| Returns | ||||||
| ------- | ||||||
| dict[label, Axes] | ||||||
| A dictionary mapping the labels to the Axes objects. | ||||||
|
Comment thread
Comment on lines
+1591
to
+1592
Copy link
Copy Markdown
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Qualitycan this be [name, Axes] - worried label will imply that that's the label we're going to apply to the axes. Unless is that the plan - these keys end up being the defaults for a latex like subfigure label?
Sorry, something went wrong.
All reactions
|
||||||
|
|
||||||
| """ | ||||||
| subplot_kw = subplot_kw or {} | ||||||
| gridspec_kw = gridspec_kw or {} | ||||||
| # special-case string input | ||||||
|
Comment thread
Comment on lines
+1595
to
+1597
Copy link
Copy Markdown
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityThe internet says this is an antipattern...
Sorry, something went wrong.
All reactions
Copy link
Copy Markdown
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityThis is far from our only type-normalization on parsing input....
Sorry, something went wrong.
All reactions
Copy link
Copy Markdown
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityI choked on that, too. It harks back to my Perl days, thankfully long gone.
Sorry, something went wrong.
All reactions
Copy link
Copy Markdown
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityThe choking episode was not fatal...
Sorry, something went wrong.
All reactions
|
||||||
| if isinstance(layout, str): | ||||||
| layout = self._normalize_grid_string(layout) | ||||||
|
|
||||||
| def _make_array(inp): | ||||||
| """ | ||||||
| Convert input into 2D array | ||||||
|
|
||||||
| We need to have this internal function rather than | ||||||
| ``np.asarray(..., dtype=object)`` so that a list of lists | ||||||
| of lists does not get converted to an array of dimension > | ||||||
| 2 | ||||||
|
|
||||||
| Returns | ||||||
| ------- | ||||||
| 2D object array | ||||||
|
|
||||||
| """ | ||||||
| r0, *rest = inp | ||||||
| for j, r in enumerate(rest, start=1): | ||||||
| if len(r0) != len(r): | ||||||
| raise ValueError( | ||||||
| "All of the rows must be the same length, however " | ||||||
| f"the first row ({r0!r}) has length {len(r0)} " | ||||||
| f"and row {j} ({r!r}) has length {len(r)}." | ||||||
| ) | ||||||
| out = np.zeros((len(inp), len(r0)), dtype=object) | ||||||
| for j, r in enumerate(inp): | ||||||
| for k, v in enumerate(r): | ||||||
| out[j, k] = v | ||||||
| return out | ||||||
|
|
||||||
| def _identify_keys_and_nested(layout): | ||||||
| """ | ||||||
| Given a 2D object array, identify unique IDs and nested layouts | ||||||
|
|
||||||
| Parameters | ||||||
| ---------- | ||||||
| layout : 2D numpy object array | ||||||
|
|
||||||
| Returns | ||||||
| ------- | ||||||
| unique_ids : Set[object] | ||||||
| The unique non-sub layout entries in this layout | ||||||
| nested : Dict[Tuple[int, int]], 2D object array | ||||||
| """ | ||||||
| unique_ids = set() | ||||||
| nested = {} | ||||||
| for j, row in enumerate(layout): | ||||||
| for k, v in enumerate(row): | ||||||
| if v == empty_sentinel: | ||||||
| continue | ||||||
| elif not cbook.is_scalar_or_string(v): | ||||||
| nested[(j, k)] = _make_array(v) | ||||||
| else: | ||||||
| unique_ids.add(v) | ||||||
|
|
||||||
| return unique_ids, nested | ||||||
|
|
||||||
| def _do_layout(gs, layout, unique_ids, nested): | ||||||
| """ | ||||||
| Recursively do the layout. | ||||||
|
|
||||||
| Parameters | ||||||
| ---------- | ||||||
| gs : GridSpec | ||||||
|
|
||||||
| layout : 2D object array | ||||||
| The input converted to a 2D numpy array for this level. | ||||||
|
|
||||||
|
Comment thread
tacaswell marked this conversation as resolved.
|
||||||
| unique_ids : Set[object] | ||||||
| The identified scalar labels at this level of nesting. | ||||||
|
|
||||||
| nested : Dict[Tuple[int, int]], 2D object array | ||||||
| The identified nested layouts if any. | ||||||
|
|
||||||
| Returns | ||||||
| ------- | ||||||
| Dict[label, Axes] | ||||||
| A flat dict of all of the Axes created. | ||||||
| """ | ||||||
| rows, cols = layout.shape | ||||||
| output = dict() | ||||||
|
|
||||||
| # create the Axes at this level of nesting | ||||||
| for name in unique_ids: | ||||||
| indx = np.argwhere(layout == name) | ||||||
| start_row, start_col = np.min(indx, axis=0) | ||||||
| end_row, end_col = np.max(indx, axis=0) + 1 | ||||||
| slc = (slice(start_row, end_row), slice(start_col, end_col)) | ||||||
|
|
||||||
| if (layout[slc] != name).any(): | ||||||
| raise ValueError( | ||||||
| f"While trying to layout\n{layout!r}\n" | ||||||
| f"we found that the label {name!r} specifies a " | ||||||
| "non-rectangular or non-contiguous area.") | ||||||
|
|
||||||
| ax = self.add_subplot( | ||||||
| gs[slc], **{'label': str(name), **subplot_kw} | ||||||
| ) | ||||||
| output[name] = ax | ||||||
|
|
||||||
| # do any sub-layouts | ||||||
| for (j, k), nested_layout in nested.items(): | ||||||
| rows, cols = nested_layout.shape | ||||||
| nested_output = _do_layout( | ||||||
| gs[j, k].subgridspec(rows, cols, **gridspec_kw), | ||||||
| nested_layout, | ||||||
| *_identify_keys_and_nested(nested_layout) | ||||||
| ) | ||||||
| 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}") | ||||||
| output.update(nested_output) | ||||||
| return output | ||||||
|
|
||||||
| layout = _make_array(layout) | ||||||
| rows, cols = layout.shape | ||||||
| gs = self.add_gridspec(rows, cols, **gridspec_kw) | ||||||
| ret = _do_layout(gs, layout, *_identify_keys_and_nested(layout)) | ||||||
|
Comment thread
tacaswell marked this conversation as resolved.
|
||||||
| for k, ax in ret.items(): | ||||||
| if isinstance(k, str): | ||||||
| ax.set_label(k) | ||||||
| return ret | ||||||
|
|
||||||
| def delaxes(self, ax): | ||||||
| """ | ||||||
| Remove the `~.axes.Axes` *ax* from the figure; update the current axes. | ||||||
| Expand Down | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| Expand Up | @@ -1274,6 +1274,87 @@ 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, | ||
| empty_sentinel='.', **fig_kw): | ||
|
Comment thread
Copy link
Copy Markdown
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Qualitynot thrilled that all the docs need to be repeated here, is it so help? works
Sorry, something went wrong.
All reactions
Copy link
Copy Markdown
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityIt is also a slightly different API because we also create the figure. Having the duplication is annoying, but I don't think there is a mechanical way to do the re-writing that is simpler in the long run that just having the duplicate text.
Sorry, something went wrong.
All reactions
|
||
| """ | ||
| Build a layout of Axes based on ASCII art or nested lists. | ||
|
|
||
| This is a helper function to build complex GridSpec layouts visually. | ||
|
|
||
| .. note :: | ||
|
|
||
| This API is provisional and may be revised in the future based on | ||
| early user feedback. | ||
|
|
||
|
|
||
| Parameters | ||
| ---------- | ||
| layout : 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 :: | ||
|
|
||
| x = [['A panel', 'A panel', 'edge'], | ||
| ['C panel', '.', 'edge']] | ||
|
|
||
| Produces 4 axes: | ||
|
|
||
| - 'A panel' which is 1 row high and spans the first two columns | ||
| - 'edge' which is 2 rows high and is on the right edge | ||
| - 'C panel' which in 1 row and 1 column wide in the bottom left | ||
| - a blank space 1 row and 1 column wide in the bottom center | ||
|
|
||
| Any of the entries in the layout can be a list of lists | ||
| of the same form to create nested layouts. | ||
|
|
||
| If input is a str, then it must be of the form :: | ||
|
|
||
| ''' | ||
| AAE | ||
| C.E | ||
| ''' | ||
|
|
||
| where each character is a column and each line is a row. | ||
| This only allows only single character Axes labels and does | ||
| not allow nesting but is very terse. | ||
|
|
||
| subplot_kw : dict, optional | ||
| Dictionary with keywords passed to the `.Figure.add_subplot` call | ||
| used to create each subplot. | ||
|
|
||
| gridspec_kw : dict, optional | ||
| Dictionary with keywords passed to the `.GridSpec` constructor used | ||
| to create the grid the subplots are placed on. | ||
|
|
||
| empty_sentinel : object, optional | ||
| Entry in the layout to mean "leave this space empty". Defaults | ||
| to ``'.'``. Note, if *layout* is a string, it is processed via | ||
| `inspect.cleandoc` to remove leading white space, which may | ||
| interfere with using white-space as the empty sentinel. | ||
|
|
||
| **fig_kw | ||
| All additional keyword arguments are passed to the | ||
| `.pyplot.figure` call. | ||
|
|
||
| Returns | ||
| ------- | ||
| fig : `~.figure.Figure` | ||
| The new figure | ||
|
|
||
| dict[label, Axes] | ||
| A dictionary mapping the labels to the Axes objects. | ||
|
|
||
| """ | ||
| fig = figure(**fig_kw) | ||
| ax_dict = fig.subplot_mosaic( | ||
| layout, | ||
| subplot_kw=subplot_kw, | ||
| gridspec_kw=gridspec_kw, | ||
| empty_sentinel=empty_sentinel | ||
| ) | ||
| return fig, ax_dict | ||
|
|
||
|
|
||
| def subplot2grid(shape, loc, rowspan=1, colspan=1, fig=None, **kwargs): | ||
| """ | ||
| Create a subplot at a specific location inside a regular grid. | ||
| Expand Down | ||
| Back | FazBrowse Home | New Git URL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityPlease clearly mark as Experimental
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Qualityand also make explicit that this is Semantic/named axes layouting (basically we want even non-R folks to grok this immediately)
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.