Repository navigation
layout.grid enhancements for plotly.py to replace make_subplots #3507
Description
Background
plotly.py has a system for building figures with subplots that predates the plotly.js layout.grid system for subplots. I would like to replace this custom subplot system with layout.grid to reduce complexity inside plotly.py and add support for all trace types (plotly.py's system only supports Cartesian subplot types). This transition will also make the integration of px into plotly.py much smoother.
Needs
As far as I can tell, there are two features that are implemented in plotly.py's make_subplots function that are not supported by layout.grid.
The first two are both demonstrated in this example https://plot.ly/python/subplots/#custom-sized-subplot-with-subplot-titles.
- Spanned subplots:
make_subplotsallows individual axes to span multiple subplot grid entries - Subplot titles:
make_subplotshas a system for positioning text annotations as titles for individual subplots - column/row widths:
make_subplotssupportscolumn_widthandrow_widtharrays to support non-uniform widths of the rows and columns in the grid. - insets:
make_subplotssupports the specification of insets. TBH I'm not really sure how this works and I don't know if there are any examples of this outside of the docstring. And this might not be something that makes sense to support inlayout.grid. Here's the docstring
insets (kwarg, list of dictionaries):
Inset specifications.
- Each item in 'insets' is a dictionary.
The available keys are:
* cell (tuple, default=(1,1)): (row, col) index of the
subplot cell to overlay inset axes onto.
* is_3d (boolean, default=False): flag for 3d scenes
* l (float, default=0.0): padding left of inset
in fraction of cell width
* w (float or 'to_end', default='to_end') inset width
in fraction of cell width ('to_end': to cell right edge)
* b (float, default=0.0): padding bottom of inset
in fraction of cell height
* h (float or 'to_end', default='to_end') inset height
in fraction of cell height ('to_end': to cell top edge)
API ideas
To kick things off, here's a idea for a potential plotly.js API
- We could add
layout.grid.rowspanandlayout.grid.colspanproperties that may optionally be set to 2D arrays of integers. So the example above would have a acolspanproperty of
[[1, 1],
[2, 0]]
which would imply a grid.subplots property of
[['xy' , 'x2y2'],
['x3y3', null]]
- We could add a
grid.titlesproperty that is a 2D array of title strings. So the example above would have agrid.titlesproperty of
[['First Subplot', 'Second Subplot'],
['Third Subplot' , null]]
- We could add
grid.widthsandgrid.heightsproperties that would be 1D arrays of numbers. We could internally normalize each array and use the elements to determine the width/height of each column/row.
Activity
nicolaskruchten commentedon Feb 2, 2019
Yup, I agree this would be great! Subplot titles would be really handy, especially if they could be used for facet labelling somehow i.e. placed on the side as well as the top and if a subplot could have two titles, for the corner one ;)
Barring that, they would still be very handy for wrapped-facet labelling
alexcjohnson commentedon Feb 3, 2019
Thanks for kicking this discussion off @jonmmease! Definitely a great set of features to have, and I think you've covered all the important functionality.
Spans
Your proposal works slightly differently from html tables, which seem to me the closest common analog. The difference is tables don't include entries (
<td>elements) for the already-occupied cells at all, whereas you show them withnullor0. I think I prefer your version despite the divergence, but I think it's worth mentioning and exploring the implications. Let's take a somewhat more complex case:In your version this would look like:
whereas using the table analogy it would be:
If we're only talking about cartesian subplots, this difference doesn't matter much; these three arrays are all self-consistent either way. The table version is nice because we're not forced to add placeholder entries, we're just enumerating cells in the order we encounter them. On the other hand your version keeps the arrays rectangular, which seems easier to construct - you can make an empty array of the right dimensions, then place cells into it by putting the cell spec in the upper left corner and fill the rest of the cell with
0/null.But seems to me yours has a clear advantage in referencing spanned grid cells in
subplot.domain, to insert non-cartesian subplots in the grid. With the table version there's ambiguity: do you refer to the column and row number before the cells were merged, or to the indices in the grid spec arrays? With your version the two are identical, and moreover we can givesubplot.domainits owncolspanandrowspan, which by default inherits from the specified cell ingrid, but if you prefer you can omitgrid.colspanetc and just specify(col|row)spaninsubplot.domain.Titles
If
grid.titlesis a 2D array as you describe, it should at least be an array of objects likelayout.title. But it could also be a 1D array, of objects likelayout.titleplus attributes(col|row)[span]to target a subplot, andside: ('left'|'right'|'bottom'|'top')that sets the default position and rotation of the text. This would naturally allow multiple titles on one subplot, and avoid a sparse array in cases like just labeling the edges of a large grid.There may still be edge cases this doesn't cover. The most flexible solution of all would be annotations with
(x|y)ref: 'grid'and(col|row)[span]attrs. Shapes and images could also easily support such positioning.Column/row widths
grid.widthsandgrid.heightssound great, normalization included!(x|y)gapbecomes a little bit confusing, but we should be able to sort that out.Insets
I agree, no need to build this into the grid, just make another pair of axes (or non-cartesian subplot) and specify
domainmanually. That covers more edge cases, like partial overlap, the only downside being the need to calculate the edges of the cell you want to reference - but it's pretty unusual to use an inset AND a grid anyway, and by the time you get to that level of refinement (which is likely about the last step before publication) you're probably not going to mind having to tweak this.etpinard commentedon Feb 14, 2019
rowspan / colspan
That's the winning argument for me. I'm voting for
Subplot tItles
First referencing #2746
I think I'd vote for keeping subplot title out of
layout.grid.(x|y)ref: 'grid'sounds like a general enough solution. It might make it slightly tricky for plotly.py to retain parity with the currentmake_subplots: it would need to "add" annotations behind the scenes. @jonmmease please let us know if that's a big deal.Column/row widths
👍
Insets
I looked through plotly/plotly.py#170 to try to find why
insetswere added tomake_subplots... to no avail. Yeah 🔪 sounds ok!jonmmease commentedon Feb 17, 2019
I like this solution a lot, and it should be easy enough to support from plotly.py. Thanks @etpinard and @alexcjohnson for improving on my suggestion!
gvwilson commentedon Jun 11, 2024
Hi - this issue has been sitting for a while, so as part of our effort to tidy up our public repositories I'm going to close it. If it's still a concern, we'd be grateful if you could open a new issue (with a short reproducible example if appropriate) so that we can add it to our stack. Cheers - @gvwilson