Skip to content

Commit

Permalink
Add support for seaborn figure-level functions (#21)
Browse files Browse the repository at this point in the history
Add support for seaborn figure-level functions
  • Loading branch information
karelvaculik authored Jun 22, 2024
1 parent 244a4f3 commit c65dd8a
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 19 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
# Changelog

## 2.2.0 (2024-06-22)

- Added support for Seaborn's `PairGrid`, `FacetGrid`, `JointGrid`, and `ClusterGrid`.

## 2.1.2 (2024-06-01)

- Version of installed `bokeh` package is now used to create matching JavaScript links.
- Version of installed `bokeh` package is now used to create matching JavaScript links.
- Fixed invalid link in Configuration page of documentation.

## 2.1.1 (2024-06-01)
Expand Down
43 changes: 43 additions & 0 deletions docs/examples/plotting_seaborn_figure_level.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import seaborn as sns

import pyreball as pb

tips = sns.load_dataset("tips")
anscombe = sns.load_dataset("anscombe")
penguins = sns.load_dataset("penguins")

fig_facet_grid_lmplot = sns.lmplot(data=penguins, x="bill_length_mm", y="bill_depth_mm")
pb.print_figure(
fig_facet_grid_lmplot,
caption="Seaborn FacetGrid from lmplot.",
matplotlib_format="png",
embedded=False,
)

fig_facet_grid = sns.FacetGrid(tips, col="time", row="sex")
fig_facet_grid.map(sns.scatterplot, "total_bill", "tip")
pb.print_figure(
fig_facet_grid,
caption="Seaborn FacetGrid.",
matplotlib_format="png",
embedded=False,
)

fig_pair_grid = sns.PairGrid(penguins)
fig_pair_grid.map(sns.scatterplot)
pb.print_figure(
fig_pair_grid,
caption="Seaborn PairGrid.",
matplotlib_format="png",
embedded=False,
)

fig_joint_grid = sns.JointGrid(data=penguins, x="bill_length_mm", y="bill_depth_mm")
fig_joint_grid.plot_joint(sns.scatterplot, s=100, alpha=0.5)
fig_joint_grid.plot_marginals(sns.histplot, kde=True)
pb.print_figure(
fig_joint_grid,
caption="Seaborn JointGrid.",
matplotlib_format="png",
embedded=False,
)
53 changes: 41 additions & 12 deletions docs/plotting.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ Pyreball currently supports plotting figures created
by [Matplotlib](https://matplotlib.org/), [Seaborn](https://seaborn.pydata.org/), [Vega-Altair](https://altair-viz.github.io/index.html),
[Plotly](https://plotly.com/), and [Bokeh](https://bokeh.org/).

There is a single function [`print_figure()`](../api/pyreball_html/#pyreball.html.print_figure) for all these libraries.
There is a single
function [`print_figure()`](../api/pyreball_html/#pyreball.html.print_figure) for all
these libraries.
Similarly to [`print_table()`](../api/pyreball_html/#pyreball.html.print_table), it uses
parameters `caption`, `align`, `caption_position` and `numbered` with the same meaning.
In contrast to table captions, the default position of figure captions is `bottom`.
Expand All @@ -14,46 +16,73 @@ In contrast to table captions, the default position of figure captions is `botto
When plotting with Matplotlib, it is necessary to create a figure object and pass it
to [`print_figure()`](../api/pyreball_html/#pyreball.html.print_figure).

In case of Matplotlib, a user can select the format of the figure via `matplotlib_format` parameter: either `"png"`
In case of Matplotlib, a user can select the format of the figure
via `matplotlib_format` parameter: either `"png"`
or `"svg"`.
In case of `"svg"`, it is also possible to choose whether the figure should be embedded into the HTML file
or saved into a separate file and referenced in the HTML file by setting `embedded` accordingly.
In case of `"svg"`, it is also possible to choose whether the figure should be embedded
into the HTML file
or saved into a separate file and referenced in the HTML file by setting `embedded`
accordingly.
Figures in `"png"` format cannot be embedded into the HTML file.

When the figure is stored into a file, the file is saved in a directory with name equal to the filename stem of the HTML
file. For example, for HTML file `report.html`, the image file will be stored in directory `report`.
When the figure is stored into a file, the file is saved in a directory with name equal
to the filename stem of the HTML
file. For example, for HTML file `report.html`, the image file will be stored in
directory `report`.

The following code shows an example of a bar chart created with Matplotlib and stored in a `"png"` format.
The following code shows an example of a bar chart created with Matplotlib and stored in
a `"png"` format.

{{ inline_source("docs/examples/plotting_matplotlib_png.py") }}

<iframe style="border:2px solid;" src="../examples/plotting_matplotlib_png.html" height="540" width="100%" title="Iframe Example"></iframe>

The next example shows the same chart, but embedded directly into the HTML document in `"svg"` format.
The next example shows the same chart, but embedded directly into the HTML document
in `"svg"` format.

{{ inline_source("docs/examples/plotting_matplotlib_svg.py") }}

<iframe style="border:2px solid;" src="../examples/plotting_matplotlib_svg.html" height="540" width="100%" title="Iframe Example"></iframe>

## Seaborn

Seaborn is based on Matplotlib and therefore the code is very similar. It is also necessary to create a figure,
which is then passed to Pyreball. It is also possible to use parameters `matplotlib_format` and `embedded`.
Seaborn is based on Matplotlib and therefore the code is very similar.
Seaborn functions are either _axes-level_ or _figure-level_,
see [documentation](https://seaborn.pydata.org/tutorial/function_overview.html#figure-level-vs-axes-level-functions).
For axes-level functions, one can create a `Figure` and `Axes` objects as with
Matplotlib:

{{ inline_source("docs/examples/plotting_seaborn.py") }}

<iframe style="border:2px solid;" src="../examples/plotting_seaborn.html" height="540" width="100%" title="Iframe Example"></iframe>

On the other hand, _figure-level_ functions
(e.g., [lmplot](https://seaborn.pydata.org/generated/seaborn.lmplot.html)) don't
use `ax` parameter and return an object of type `PairGrid`, `FacetGrid`, `JointGrid`,
or `ClusterGrid`.
In such a case, the object can be also passed
to [`print_figure()`](../api/pyreball_html/#pyreball.html.print_figure).
Here are a few examples borrowed from Seaborn's documentation:

{{ inline_source("docs/examples/plotting_seaborn_figure_level.py") }}

<iframe style="border:2px solid;" src="../examples/plotting_seaborn_figure_level.html" height="800" width="100%" title="Iframe Example"></iframe>

As can be seen from examples above, it is also possible to use
parameters `matplotlib_format` and `embedded` with Seaborn plots.

## Vega-Altair

For Vega-Altair charts, Pyreball does not offer any special parameters like for Matplotlib and Seaborn charts.
For Vega-Altair charts, Pyreball does not offer any special parameters like for
Matplotlib and Seaborn charts.
The charts are always embedded into the HTML and kept interactive.

{{ inline_source("docs/examples/plotting_vega_altair.py") }}

<iframe style="border:2px solid;" src="../examples/plotting_vega_altair.html" height="540" width="100%" title="Iframe Example"></iframe>

The previous example used `altair.Chart` object, but other types of charts are also supported,
The previous example used `altair.Chart` object, but other types of charts are also
supported,
e.g. `altair.ConcatChart`, `altair.HConcatChart`.

## Plotly
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pyreball"
version = "2.1.2"
version = "2.2.0"
description = "Python reporting tool."
authors = ["Karel Vaculik <[email protected]>"]
license = "Apache-2.0"
Expand Down
31 changes: 26 additions & 5 deletions src/pyreball/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
# noinspection PyPackageRequirements
import plotly # type: ignore[unused-ignore]

# noinspection PyPackageRequirements
import seaborn # type: ignore[unused-ignore]

AltairFigType = Union[
"altair.vegalite.v5.api.Chart",
"altair.vegalite.v5.api.ConcatChart",
Expand All @@ -51,6 +54,10 @@
]
FigType = Union[
"matplotlib.figure.Figure",
"seaborn.axisgrid.PairGrid",
"seaborn.axisgrid.FacetGrid",
"seaborn.axisgrid.JointGrid",
"seaborn.matrix.ClusterGrid",
"plotly.graph_objs._figure.Figure",
"altair.vegalite.v5.api.Chart",
"altair.vegalite.v5.api.ConcatChart",
Expand Down Expand Up @@ -1038,6 +1045,16 @@ def _prepare_bokeh_image_element(fig: "bokeh.plotting._figure.figure") -> str:
return f"<div>{div}{script}</div>"


def _is_seaborn_figure_level_type(fig: FigType) -> bool:
return (
type(fig).__name__ in ["PairGrid", "FacetGrid", "JointGrid"]
and type(fig).__module__ == "seaborn.axisgrid"
) or (
type(fig).__name__ in ["ClusterGrid"]
and type(fig).__module__ == "seaborn.matrix"
)


def _prepare_image_element(
fig: FigType,
fig_index: int,
Expand All @@ -1048,9 +1065,7 @@ def _prepare_image_element(
# (if we checked type of fig, we would have to add the libraries to requirements)
if (
type(fig).__name__ == "Figure" and type(fig).__module__ == "matplotlib.figure"
) or (
type(fig).__name__ == "PairGrid" and type(fig).__module__ == "seaborn.axisgrid"
):
) or _is_seaborn_figure_level_type(fig):
img_element = _prepare_matplotlib_image_element(
fig=fig,
fig_index=fig_index,
Expand Down Expand Up @@ -1079,7 +1094,9 @@ def _prepare_image_element(
"bokeh.plotting.figure",
"bokeh.plotting._figure",
]:
img_element = _prepare_bokeh_image_element(fig=fig)
img_element = _prepare_bokeh_image_element(
fig=fig # type: ignore[arg-type,unused-ignore]
)
img_type = "bokeh"
else:
raise ValueError(f"Unknown figure type {type(fig)}.")
Expand All @@ -1105,7 +1122,11 @@ def _print_figure(
]:
from bokeh.plotting import show

show(fig)
show(fig) # type: ignore[arg-type,unused-ignore]
elif _is_seaborn_figure_level_type(fig):
from matplotlib import pyplot as plt

plt.show()
else:
fig.show()
else:
Expand Down

0 comments on commit c65dd8a

Please sign in to comment.