Skip to content

Commit 1b79379

Browse files
authored
Seaborn plots should fill their output_plot (#785)
1 parent 30ef70e commit 1b79379

File tree

4 files changed

+57
-21
lines changed

4 files changed

+57
-21
lines changed

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,10 @@ typings/matplotlib/__init__.pyi: ## grab type stubs from GitHub
5757
mv typings/python-type-stubs/stubs/matplotlib typings/
5858
rm -rf typings/python-type-stubs
5959

60-
pyright: typings/uvicorn typings/matplotlib/__init__.pyi ## type check with pyright
60+
typings/seaborn:
61+
pyright --createstub seaborn
62+
63+
pyright: typings/uvicorn typings/matplotlib/__init__.pyi typings/seaborn ## type check with pyright
6164
pyright
6265

6366
lint: ## check style with flake8

shiny/render/_try_render_plot.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -104,17 +104,16 @@ def _get_img_size_px(
104104
# size to that exactly. We assume there's some reason they wanted that exact size.
105105
user_specified_size_px = self.user_specified_size_px[i]
106106
if user_specified_size_px is not None:
107-
return user_specified_size_px, f"{user_specified_size_px}px"
108-
109-
# If they specified a figure size in their plotting code, we'll respect that.
110-
if abs(fig_initial_size_inches - fig_result_size_inches) > 1e-6:
111-
native_size = fig_result_size_inches * dpi
112-
return native_size, f"{native_size}px"
113-
114-
# If the user didn't specify an explicit size on @render.plot and didn't modify
115-
# the figure size in their plotting code, then assume that they're filling the
116-
# container, in which case we set the img size to 100% in order to have nicer
117-
# resize behavior.
107+
if user_specified_size_px == 0:
108+
# If the explicit size is 0, we'll respect the user's figure size.
109+
native_size = fig_result_size_inches * dpi
110+
return native_size, f"{native_size}px"
111+
else:
112+
return user_specified_size_px, f"{user_specified_size_px}px"
113+
114+
# If the user didn't specify an explicit size on @render.plot then assume that
115+
# they're filling the container, in which case we set the img size to 100% in
116+
# order to have nicer resize behavior.
118117
#
119118
# Retrieve the container size, taking a reactive dependency
120119
container_size_px = self._container_size_px_fn[i]()

tests/e2e/plot-sizing/app.py

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@
66

77
import matplotlib.pyplot as plt
88
import numpy as np
9+
import seaborn as sns
910
from PIL import Image
10-
from plotnine import aes, element_rect, facet_wrap, geom_point, stat_smooth, theme
11+
from plotnine import aes, element_rect, geom_point, theme, theme_minimal
1112
from plotnine.data import mtcars
1213
from plotnine.ggplot import ggplot
1314

1415
from shiny import App, Inputs, Outputs, Session, module, render, req, ui
1516

17+
tips = sns.load_dataset("tips")
18+
dpi = 150
19+
1620

1721
@module.ui
1822
def plot_ui():
@@ -51,7 +55,7 @@ def plot_dom_size():
5155
def plot_decorator_size():
5256
return plot_fn(None)
5357

54-
@render.plot
58+
@render.plot(width=0, height=0)
5559
def plot_native_size():
5660
return plot_fn((300, 200))
5761

@@ -65,12 +69,19 @@ def plot_native_size():
6569
plot_ui("mpl"),
6670
value="mpl",
6771
),
72+
ui.nav(
73+
"seaborn",
74+
ui.p(
75+
"The following four plots should all be the same size. The last one should have larger text."
76+
),
77+
plot_ui("sns"),
78+
value="sns",
79+
),
6880
ui.nav(
6981
"plotnine",
7082
ui.p(
7183
"The following four plots should all be the same size. The last one should have larger text."
7284
),
73-
ui.p("It may take a moment for the plots to render."),
7485
plot_ui("plotnine"),
7586
),
7687
ui.nav(
@@ -99,19 +110,31 @@ def plot_with_mpl(fig_size: tuple[float, float] | None) -> object:
99110

100111
return fig
101112

113+
def plot_with_sns(fig_size: tuple[float, float] | None) -> object:
114+
kwargs = dict()
115+
if fig_size:
116+
kwargs["height"] = fig_size[1] / dpi
117+
kwargs["aspect"] = fig_size[0] / fig_size[1]
118+
119+
# FacetGrid has an opinion about its figure size
120+
g = sns.FacetGrid(tips, **kwargs) # pyright: ignore[reportUnknownArgumentType]
121+
g.figure.set_facecolor("lavender")
122+
g.map(sns.scatterplot, "total_bill", "tip")
123+
plt.gca().set_facecolor("lavender")
124+
if fig_size:
125+
plt.gcf().set_dpi(dpi)
126+
102127
def plot_with_plotnine(fig_size: tuple[float, float] | None) -> object:
103128
p = (
104-
ggplot(mtcars, aes("wt", "mpg", color="factor(gear)"))
129+
ggplot(mtcars, aes("wt", "mpg"))
105130
+ geom_point()
106-
+ stat_smooth(method="lm")
107-
+ facet_wrap("~gear")
131+
+ theme_minimal()
108132
+ theme(
109133
plot_background=element_rect(fill="lavender"),
110134
legend_background=element_rect(fill="lavender"),
111135
)
112136
)
113137
if fig_size is not None:
114-
dpi = 150
115138
p = p + theme(
116139
figure_size=(fig_size[0] / dpi, fig_size[1] / dpi),
117140
plot_background=element_rect(fill="lavender"),
@@ -124,6 +147,7 @@ def plot_with_pil(fig_size: tuple[float, float] | None) -> object:
124147
return Image.open(Path(__file__).parent / "bike.jpg")
125148

126149
plot_server("mpl", plot_with_mpl)
150+
plot_server("sns", plot_with_sns)
127151
plot_server("plotnine", plot_with_plotnine)
128152
plot_server("pil", plot_with_pil)
129153

tests/e2e/plot-sizing/test_plot_sizing.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ def test_output_image_kitchen(page: Page, local_app: ShinyAppProc) -> None:
1515
"mpl-plot_decorator_size",
1616
"mpl-plot_native_size",
1717
],
18+
"sns": [
19+
"sns-plot_default",
20+
"sns-plot_dom_size",
21+
"sns-plot_decorator_size",
22+
"sns-plot_native_size",
23+
],
1824
"plotnine": [
1925
"plotnine-plot_default",
2026
"plotnine-plot_dom_size",
@@ -40,8 +46,12 @@ def test_output_image_kitchen(page: Page, local_app: ShinyAppProc) -> None:
4046
rect = page.evaluate(
4147
f"() => document.querySelector('#{plotid} img').getBoundingClientRect()"
4248
)
43-
assert abs(rect["width"] - 300) < 1e-4
44-
assert abs(rect["height"] - 200) < 1e-4
49+
tolerance = 1e-4
50+
if plotid.startswith("sns"):
51+
# Not sure why but sns native-sized plot is a bit off
52+
tolerance += 2
53+
assert abs(rect["width"] - 300) <= tolerance
54+
assert abs(rect["height"] - 200) <= tolerance
4555

4656

4757
def test_decorator_passthrough_size():

0 commit comments

Comments
 (0)