Allow 'ultraplot'-style figure sizing options
A frequent source of annoyance when using matplotlib or ggplot is the need to specify the dimensions of the entire figure. After that, the figure is subdivided into axes, labels, etc. This makes it difficult to control the appearance of the figure and often results in guess-and-check tweaking of the figure size, which is doable for static figures but practically impossible to get right if figures are dynamically generated under a fairly wide range of conditions.
The library ultraplot builds on top of matplotlib and notably it enables users to specify figure sizing in a more natural way. There are two main options (to my understanding):
- Specify the width of the figure and the aspect ratios of the subplots. The figure height is then grown to accommodate the figure elements.
- Specify the size of the subplots. The figure height and width is then grown to accommodate the figure elements.
The ultraplot documentation describes this in a little more detail.
There are many cases where this is useful. For example, if I use facet_grid with n rows, then I typically want to describe the shape of a row and have the figure be as tall as needed to accommodate all n rows. In matplotlib or ggplot, you have to change height manually to do this and often I end up trying to guesstimate some formula like height = 100 + 200*n. Another example is a heatmap where you want to set the size of individual cells which is easy to do by setting the subplot widths and heights but is very difficult to do by setting the figure width and height because the space needed for row/column labels and colorbars isn't known in advance. Setting the aspect ratio of the heatmap typically results in whitespace surrounding the plot, such as this:
import numpy as np
import lets_plot as lp
import polars as lp
i,j = np.meshgrid(np.arange(10), np.arange(100))
data = pl.DataFrame({
'x': i.flatten(),
'y': j.flatten(),
'z': np.random.random(size=i.flatten().shape[0]),
})
p1 = (lp.ggplot(data, lp.aes(x='x', y='y',fill='z'))
+ lp.geom_tile()
+ lp.coord_fixed(ratio=1)
)
lp.ggsave(p1, "out.png", w=5, h = 10, unit='in', dpi=70)# what should I pick for h so that I fill the desired width?
As far as I know, LetsPlot does not have support for this. Adopting the figure size specification options of ultraplot would significantly address this difficulty.
Hi @tgbrooks ! The ultraplot-style sizing approach would be useful, but it's very difficult to implement because it's literally the opposite of how we currently handle plot sizing (we go figure size → panel size, not the other way around).
But, we'll see what can be done.
Thanks for considering it! One way to implement this (or at least some of it) would be to do it in two passes. LetsPlot already gets spacing, aspect ratios, etc. nice in the figure but just rescales it and puts whitespace on the sides. If the overall aspect ratio of the rendered area is known, then we could just do a second pass with the correct height for our desired width. So if ggsave() or a similar function would output this ratio, a user could implement this by just calling ggsave() twice, something like:
rendered_aspect_ratio = ggsave(fig, "temp.png", w=10, h=10, units="in")
ggsave(fig, "final.png", w=10, h=10*rendered_aspect_ratio, units="in")