Open jbednar opened 9 years ago
Well, good to see my notebooks are good for something - making mockups! ;-p
When we discussed this earlier you suggested that maybe it is possible to use a GridSpace
to group C and D together. I thought I might be worth mentioning as a reminder as I haven't gotten around to testing the suggestion yet.
I don't think you'll get this working very well with a GridSpace as it is. However I've always considered whether there should be another display mode for GridSpace where each row and column share axes like in Seaborn's grids.
Edit: This wouldn't even be very hard to implement instead of drawing our custom axes we could just rely on the axes drawn by each subplot and selectively disable axes on all plots except the first row and last column. One wrinkle here is that RasterGridPlot cannot support this easily so we'd have to make sure that if this option is enabled then a Grid of Rasters is passed to GridPlot rather than RasterGridPlot.
I do like the seaborn style for a GridSpace, and it would be great to support that in any case..
In general, though, I keep hoping for some general way to wrap up a set of Elements as a composite that will then be laid out just like any other.
We've discussed this a little bit and agree that nesting Layouts in the way you suggest might be nice. Jean-Luc suggested possibly adding a &
operator, which would nest a Layout in another Layout. The specification for the plot above would then be:
(A + B & (C+D).cols(1) + E).cols(2)
We certainly won't be implementing this for this release but I think this would be a very nice way to easily compose complex plots, especially together with the new aspect based Layouts. We could target this feature for a 1.2 release.
I've also now implemented the Seaborn style grids:
%%opts GridSpace [shared_yaxis=True shared_xaxis=True fig_size=150]
layout.Parameters.Sines.last
By default GridSpaces will behave as they do currently, but you can enable the inner axes with the shared_x/yaxis
plot option and disable the outer GridSpace axes with x/yaxis=None
. This will only work if +axiswise is enabled. By grouping stuff using the normalization system you can make fairly complex Layouts with shared axes this way.
I love the new shared axes, and think they ought to be on by default, whenever they make sense -- I don't see who wouldn't want it to work this way. There seems to be an extra line on the right and top of the plot, though.
It's great that you think that it's feasible to add nesting of Layouts in the subsequent release. I'm confused by your example, though. Isn't what you specified incompatible with &
having a lower precedence than +
, as it does normally? I.e., won't (A + B & (C+D).cols(1) + E).cols(2)
be interpreted as ((A + B) & ((C+D).cols(1) + E)).cols(2)
, and thus somehow try to put C, D, and E into one cell of the larger Layout? I could just be confused, but I don't see how it would work or how to use it in general.
Also, would this proposal add fully hierarchical support, or only one level? I.e., could one do a group of a group of a group? If that's possible in Matplotlib, supporting it would allow any arbitrary axis-aligned pattern of subplots, not just a group of a group. If you flip through any issue of Science or Nature or J. Neuroscience you can see a variety of such configurations, with HoloViews currently handling maybe 40% of the cases (a fixed or variable-sized grid with up to one item per cell). If we add support for a single additional grouping level within a grid cell, we could cover maybe 40% more. Maybe 10% would be covered by a group of groups of groups, and then the rest are just some arbitrary configuration (which if supported in HoloViews would mean allowing/requiring people to specify the locations of each item to be laid out in some canvas area, I guess, which would be independent of anything we are discussing here).
Anyway, the natural way to specify nested grouping would be to use parentheses, but I don't think that's compatible with how you currently build up Layouts as flat structures. What about a method analogous to .cols()
, e.g. .group()
? I.e., (A + B + (C+D).cols(1).group() + E).cols(2)
, for the above example? To me that seems much clearer and interpretable, and should work fine hierarchically.
The attribute name group
is problematic for obvious reasons but I think I like the basic idea - I only prefer new operators if it really helps readability! For one, a new operator would mean more code than doing it via a method. What I think I would prefer is something like this:
(A + B + (C+D).nest(1) + E).cols(2)
The idea is that nest
(or nested
?) is the nested version of cols
. Alternatively, there could be an additional boolean argument called 'nest' that can be supplied to cols
?
(A + B + (C+D).cols(1, nest=True) + E).cols(2)
Obviously you can supply nest
as a positional argument which is shorter but gives less indication as to what it does (and could therefore be confusing). As a final suggestion, maybe the alternative method could be called ncols
where the 'n' prefix indicates nesting?:
(A + B + (C+D).ncols(1) + E).cols(2)
You could probably use ncols
or cols
interchangeably unless you require nesting in which case the difference would become apparent. Thoughts?
Edit 1: I should clarify that I think this is the right approach because I think you should always explicitly specify cols for an inner (i.e nested) layout. You don't want to rely on arbitrary defaults when building a complex, nested figure.
Edit 2: If the ambiguity between cols
and ncols
is a worry, we could just check if the layout returned by ncols
is itself in a Layout
or not - then we can either warn (or even generate an exception). That will make sure people use cols
instead of ncols
as appropriate and means that people will only come across ncols
when they first encounter nested Layouts
.
Oops, you're right -- can't use group
in HoloViews for anything new!
I did think about proposing that cols() simply act like your proposed ncols(), since I can't think of a case where one would want .cols() not to nest in this way (is there one)? I personally think that once .cols() has been specified, the plots included should be taking up that many columns, which implies nesting if they are subsequently put into another Layout (and no change if they are not).
But then I thought about the converse -- maybe someone would want to do nesting without forcing a specific number of columns? Maybe if someone has a variable number of tall thin plots in a Layout, and wants them all to fill a single large grid cell, regardless of how many of the thin plots there are? Maybe that would still be ok if it works fine to specify a large number of columns in that case, larger than one expects to use.
I should also say I really like the new GridSpace
format and although Philipp implemented all the code, I was the one to suggest putting the plot parameters on the top and right side like that. It turned out really nicely and I would also like it to be default!
Good job both of you, then! I do like having the parameters on the top and right in that case; very compact and readable.
Just one clarification, though: when I said I don't see who wouldn't want it to work this way, I was imagining figures where the axes are repeated for every single plot; this format has all the same information, with less space and busyness, so it's clearly better. But the existing GridSpace defaults are also useful, now that I directly compare them with the above -- they present a simpler view, with no inner axes at all, which is less cluttered and clearly sometimes useful. On balance, I think the shared axes version above is the best default -- provide all the information, in a usable format. But it should be easy to select the current behavior as well, with examples of how to do so in the tutorials, because the current behavior makes for clean, beautiful, and less busy plots, at the cost of not showing what the inner axes are.
My understanding is that this will be a plot option which means we will be able to switch between the more informative and the old display - which is better when you are looking at a large number of curves to compare their shape.
I agree a new method is preferable to a new operator, no comment on the naming yet.
I love the new shared axes, and think they ought to be on by default, whenever they make sense -- I don't see who wouldn't want it to work this way. There seems to be an extra line on the right and top of the plot, though.
The lines at the right and top indicate that it is on one axis but I could just get rid of them.
But it should be easy to select the current behavior as well, with examples of how to do so in the tutorials.
Right they are plot options, you can selectively toggle this behavior for each axis e.g.
%%opts GridSpace [shared_yaxis=False shared_xaxis=True fig_size=150]
layout.Parameters.Sines.last
or disable the outer axis completely:
%%opts GridSpace [shared_yaxis=True shared_xaxis=True yaxis=None fig_size=150]
layout.Parameters.Sines.last
Also, would this proposal add fully hierarchical support, or only one level? I.e., could one do a group of a group of a group? If that's possible in Matplotlib, supporting it would allow any arbitrary axis-aligned pattern of subplots, not just a group of a group. If you flip through any issue of Science or Nature or J. Neuroscience you can see a variety of such configurations, with HoloViews currently handling maybe 40% of the cases (a fixed or variable-sized grid with up to one item per cell). If we add support for a single additional grouping level within a grid cell, we could cover maybe 40% more. Maybe 10% would be covered by a group of groups of groups, and then the rest are just some arbitrary configuration (which if supported in HoloViews would mean allowing/requiring people to specify the locations of each item to be laid out in some canvas area, I guess, which would be independent of anything we are discussing here).
Once nesting is supported it'll be to an arbitrary level. You can already see this if you try to nest a grid within a grid, it works but the result is a mess due to overlapping labels. A grid is obviously a very tight configuration and won't ever work well with nesting (unless you make the canvas huge). Nested Layouts should be more forgiving although you'll probably have to do some fine adjustment once you go two levels deep.
One problem with enabling the new GridSpace options by default is that at the default size you'll get overlapping labels, particularly if you put the GridSpace inside a Layout. It could be disabled automatically when it's inside a Layout, might be confusing though.
Maybe the last numeric tick label on the right could be omitted for the x axes of all but the last plot, and the last tick label on the bottom eliminated for the y axes always (since it almost always crowds the 0.0 point on the x axis, even with font sizes that are otherwise ok)?
The other options you list sound good. I'd vote for removing the top and right lines; I see what you mean about what they convey semantically, but because they overlap with the edges of the plot borders, they seem like mistakes. Maybe they would work if slightly offset above and to the right of the subplots, but that might just make the plot too busy; not sure.
Regarding nesting, it's great that it will work to arbitrary levels. One issue -- when nesting Layouts, I think that people will want one of three possible behaviors for the subfigure labels:
a
and b
within the overall set of items labeled C
. This is often done in published papers (though perhaps less often than option 1), and makes semantic sense, but implementing it would probably require the C to be placed in the upper left of the grid cell (regardless of where the individual items are), separately from the a
and b
that would be placed alongside the individual items. This approach allows referring to the individual items (a
or b
) as well as to the whole group (C
), but it requires more space on the page and seems more difficult to implement.Maybe the last numeric tick label on the right could be omitted for the x axes of all but the last plot, and the last tick label on the bottom eliminated for the y axes always (since it almost always crowds the 0.0 point on the x axis, even with font sizes that are otherwise ok)?
Doing that would be fairly involved as the ticks would then have to be precomputed by the GridPlot or I'd have to create a custom ticker for different axes. I'm also no longer convinced it should be enabled by default, as it can far too easily get too crowded, in which case you'll have to explicitly resize it. Here's two 5x5 GridSpaces in a Layout at default sizing:
It did automatically adjust the number of ticks but the x-ticks would still be massively overlapping unless I rotated them.
The other options you list sound good. I'd vote for removing the top and right lines; I see what you mean about what they convey semantically, but because they overlap with the edges of the plot borders, they seem like mistakes. Maybe they would work if slightly offset above and to the right of the subplots, but that might just make the plot too busy; not sure.
I just got rid of them.
Regarding nesting, it's great that it will work to arbitrary levels. One issue -- when nesting Layouts, I think that people will want one of three possible behaviors for the subfigure labels:
Sub-sub-sub...labels would also emerge naturally out of the nesting although their positioning will probably leave a lot to be desired by default.
Philipp's example above there suggests to me that I would like the axes on (by default) for a GridSpace
displayed on its own but revert to the old style if it is in a Layout (or if +axiswise
).
I believe this is possible (Layout
passes the appropriate plot option if the element is a GridSpace
) but generally I dislike this sort of coding for special cases. Then again, I think that if you agree this behaviour is better then maybe we could make it work like this?
Edit: Philipp already suggested this in fact. I think it is reasonable to change defaults for clarity when composing things together as long as it is possible to switch back as appropriate.
It could be disabled automatically when it's inside a Layout, might be confusing though.
I agree with making the default have the most useful behavior in cases we have figured out, as long as there is a way to override it.
Also see the discussion in https://github.com/ioam/holoviews/pull/444.
Now is probably the time to revisit this issue, because we can follow what Bokeh did to improve layouts at first, and then see whether we can just copy the same system over to matplotlib after that. I still favor being able to use + and * as always, but with some grouping mechanism that lets us combine into chunks that get laid out as single items. Adjusting the relative sizes, etc. can be then done by stretching or squashing individual plots (which are otherwise all equal), and possibly even allow adjusting the positions similarly (to allow insets, etc.). And then maybe we can copy the resulting arrangement into a template, as in PR #444, that can be applied wholesale to another layout?
After some discussion with @philippjfr and @jlstevens today, I propose the following implementation for Layout grouping:
We create a new base class LayoutGroup (presumably not just Group, to distinguish it from labels and groups). Features of a LayoutGroup:
.transpose()
method that would switch between different visible organizations (row or column, or transposed grid array), without (necessarily?) changing the indexing order. (Or maybe transpose=True
is a plot option?).reverse()
, to make them display bottom to top?]..ncols()
(or something similar that is agnostic to orientation) that will determine how the items in the list are broken into sub-groups for display. A Layout will also support .ncols()
, which will return a LayoutGroup.+
operator will return a flat Layout if both operands are either Layouts or Elements. If one of them is a LayoutGroup, the result will be a hierarchical structure, e.g. a Layout of an Element and a LayoutGroup.The existing NdLayout and GridSpace classes would change to inherit from LayoutGroup, and would therefore provide grouping and .transpose
, and would now be allowed to be used in a Layout. GridSpace would have 2D indexing, while NdLayout would have 1D, as they do already.
Open issues:
Sounds like a good proposal overall. Just two comments for now and one question:
Every LayoutGroup would ... be treated as a single unit responsible for its own layout internally
At the level of the user API that is true, but I suspect the actual layout code used in plotting will need to compute across all these things. Unless we figure out a way to build plotting classes for the various LayoutGroups
that compose properly? Even if we do that, we would need a fair bit of communication between these classes behind the scenes.
Will any container be allowed to contain a Layout? Or will Layout be special (top level only), with just LayoutGroup allowed to nest?
I think the rules are:
This would be flexible while making sure all the layout related classes are at the top of the hierarchy.
Now for the question - although this would be far more flexible than we have now, where would layout templates fit into this? The idea of a layout template is to supply an overlay of Bounds objects (or similar) to allow precise positioning of cells in a layout.
Could you please give an update on this issue?
It would really be nice to some more advanced layout options, e.g. like the above mentioned LayoutGroup.
Many thanks for this great project!
Kind regards,
Axel
After reading this over, it appears to be the inspiration behind Panel, leading me to conclude that incorporating Panel into HoloViews will result in resolving this open issue.
Is this correct?
In the long run, yes; rebuilding HoloViews layouts on Panel would solve this problem, but it's tricky because HoloViews supports different backends, and Panel is only a good drop-in replacement for the Bokeh backend. The Matplotlib backend has its own layout system with its own important features, such as supporting SVG fully and allowing subfigure labels like in the above, and so we're not ready to replace such layouts entirely. So while moving to Panel-based layouts is a long-term goal, and we've been making good progress towards it already, it's still some ways off.
Meanwhile, going in the other direction already works, and immediately allows layouts like the one at the start of this issue. You can already use HoloViews to create any groups of plots that you want to be organized together (linked and aligned), then use Panel to arrange the whole figure however you like. For interactive use and HTML, that should cover everything you need, though it's very slightly more verbose because it requires using pn.Row() and pn.Column() objects instead of the HoloViews +
operator. But such a layout cannot currently be exported to SVG for publication, because the Bokeh backend on which Panel is based only supports SVG exports of individual plots, not an entire layout. So there's still work to do...
HI all,
I had the same problem of arranging a layout/dashboard of holoview plots, and after finding this issue (#91), I used panel.Row()
and panel.Column()
to construct my layout as seen in the example below.
This example also shows how to solve https://github.com/holoviz/holoviews/issues/3910.
import pandas as pd
import holoviews as hv
import holoviews.plotting.bokeh
import panel as pn
pn.extension()
df = pd.DataFrame({
'x': ['a', 'a', 'b', 'b'],
'y': ['c', 'd', 'c', 'd'],
'z': [1, 2, 3, 4]})
hv_heatmap1 = hv.HeatMap(df, kdims=['x', 'y'], vdims=['z']).opts(invert_yaxis=True)
hv_heatmap2 = hv.HeatMap(df, kdims=['x', 'y'], vdims=['z']).opts(invert_yaxis=True)
hv_heatmap3 = hv.HeatMap(df, kdims=['x', 'y'], vdims=['z']).opts(invert_yaxis=True)
hv_heatmap4 = hv.HeatMap(df, kdims=['x', 'y'], vdims=['z']).opts(invert_yaxis=True)
pn_plot = pn.Column(
hv_heatmap1.opts(width=300, height=100),
pn.Row(
hv_heatmap2.opts(width=100, height=300),
pn.Column(
hv_heatmap3.opts(width=200, height=200),
hv_heatmap4.opts(width=200, height=200))))
pn_plot
Are there any updates on this? I'm trying to make layouts for static publication-ready figures, but unfortunately I don't see how that's possible with holoviews at the moment. As far as I can tell, the bokeh backend does not allow easy export of layouts as vector-graphics and with the matplotlib backend all elements in the layout are restricted to the same width & height, if I'm not mistaken? This is a real bummer, since otherwise I'm a big fan of this project.
@W-L This is indeed a long wished for feature but unfortunately nothing has changed just yet...
That said, the next release of HoloViews will work with an upcoming version of panel/bokeh with greatly a reworked and improved layout engine. That might be our chance to revisit this again!
Thanks for your answer, that sounds somewhat encouraging! Incidentally, I came across the fantastic patchworklib today. It allows for flexible compositing of matplotlib-derived plots with very simple syntax; and labeling (A, B, C, etc.) of subplots too. I'm wondering if this could be used or serve as inspiration for the matplotlib backend of holoviews? Just a thought..
As of e927280e, Layout plots have much better support for plots of different aspect_ratios. However, there are still a variety of different organizations that people will need that are not supported. For instance, this arrangement works fine:
But I'd like to add one more plot, which doesn't currently work well. The example in C is model output, and I'd like to put the corresponding experimental data (which looks just like it), just above or below what's in C, without changing the overall size or layout of the figure. Unfortunately, it's not possible to do what this mockup shows:
I can imagine creating some dummy dimension "Model" vs. "Experiment", then putting C and D's Elements into it, and creating a GridSpace to show that dimension as the new C, but (a) I don't know if that would work, and (b) it seems like a big pain.
What I don't want to do is to end up with an overall 3x2 grid, as in this mockup (which would probably work in the latest release):
Apart from making the figure larger and more unwieldy, this approach doesn't let me line up C and D on the same timebase, nor would it work for three strips rather than 2. Stacking vertically emphasizes comparisons at each time, which is what's appropriate in this particular case. Swapping plots D and E would line up C and D, but I think they'd be very far apart from each other and there would be lots of wasted space. So I don't see how to get the appropriate organization from a Layout in this case (or in many similar cases where different plots have different sizes or aspect ratios). The new Layout code is great, since otherwise C and D would have tons of empty space in the last example, but it seems to me that we need some way of grouping several plots into a single box that works in a Layout like the other Elements and Containers do.