Skip to content

Commit

Permalink
support seaborn figures
Browse files Browse the repository at this point in the history
  • Loading branch information
oliverlambson committed Aug 26, 2024
1 parent 395c472 commit 815b479
Show file tree
Hide file tree
Showing 13 changed files with 69 additions and 10 deletions.
1 change: 1 addition & 0 deletions bored-charts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ my-reports
- [matplotlib](https://matplotlib.org/)
- [plotly](https://plotly.com/python/)
- [vega-altair](https://altair-viz.github.io/)
- [seaborn](https://seaborn.pydata.org/)

## Extensibility

Expand Down
2 changes: 1 addition & 1 deletion bored-charts/boredcharts/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "0.12.1"
__version__ = "0.13.0"

from boredcharts.router import FigureRouter
from boredcharts.webapp import boredcharts
Expand Down
18 changes: 16 additions & 2 deletions bored-charts/boredcharts/jinja.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
import uuid
from io import BytesIO
from textwrap import dedent, indent
from typing import Any, cast
from typing import Any, TypeAlias, cast

import altair as alt
import markdown
import matplotlib.figure as mplfig
import seaborn as sns
from fastapi import Request
from jinja2 import Undefined, pass_context
from jinja2.runtime import Context
Expand All @@ -22,7 +23,12 @@ def md_to_html(md: str) -> Markup:
return Markup(markdown.markdown(md))


def to_html(fig: Figure | mplfig.Figure | alt.typing.ChartType) -> Markup:
SeabornGrid: TypeAlias = (
sns.FacetGrid | sns.PairGrid | sns.JointGrid
) # I think that's all of them


def to_html(fig: Figure | mplfig.Figure | alt.typing.ChartType | SeabornGrid) -> Markup:
"""Renders a Figure to an HTML string."""
match fig:
case Figure():
Expand All @@ -34,6 +40,9 @@ def to_html(fig: Figure | mplfig.Figure | alt.typing.ChartType) -> Markup:
return altair_to_html(fig)
case mplfig.Figure():
return mpl_to_html(fig)
case _ if isinstance(fig, SeabornGrid):
fig = cast(SeabornGrid, fig)
return sns_to_html(fig)
case _:
raise ValueError(
f"Input must be a Plotly/Matplotlib Figure, got {type(fig)}"
Expand Down Expand Up @@ -87,6 +96,11 @@ def mpl_to_html(fig: mplfig.Figure) -> Markup:
return Markup(f"""<img src="data:image/png;base64,{png64}" alt="{title}">""")


def sns_to_html(fig: SeabornGrid) -> Markup:
"""Renders a Seaborn Chart as HTML."""
return mpl_to_html(fig.figure)


@pass_context
def figure(
context: Context,
Expand Down
2 changes: 2 additions & 0 deletions bored-charts/boredcharts/webapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pathlib import Path
from typing import NamedTuple

import matplotlib
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
Expand Down Expand Up @@ -104,6 +105,7 @@ async def my_chart() -> go.Figure:
async def healthz() -> dict[str, str]:
return {"status": "ok"}

matplotlib.use("agg") # force non-interactive
return app


Expand Down
3 changes: 2 additions & 1 deletion bored-charts/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ dependencies = [
"jinja2>=3.1.4",
"markdown>=3.6",
"markupsafe>=2.1.5",
"plotly>=5.23.0",
"matplotlib>=3.9.2",
"plotly>=5.23.0",
"altair>=5.4.0",
"seaborn>=0.13.2",
]
readme = "README.md"
license = "MIT"
Expand Down
5 changes: 3 additions & 2 deletions examples/full/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ Project structure:
│ ├── cars.py
│ ├── elasticity.py
│ ├── medals.py
│ ├── penguins.py
│ ├── population.py
│ └── ... <-- add more figures here
├── pages
│ ├── more - note you can create nested paths:
│ │ └── test.md <- this will be at /more/test
│ ├── the-arctic - note you can create nested paths:
│ │ └── pengiuns.md <- this will be at /the-arctic/penguins
│ ├── populations.md <- this will be at /populations
│ ├── price-elasticity.md
│ ├── vega-lite-is-cool.md
Expand Down
10 changes: 10 additions & 0 deletions examples/full/analysis/penguins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import seaborn as sns
from boredcharts.router import FigureRouter

figures = FigureRouter()


@figures.chart("penguins")
def penguins() -> sns.PairGrid:
df = sns.load_dataset("penguins")
return sns.pairplot(df, hue="species")
3 changes: 2 additions & 1 deletion examples/full/app.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os
from pathlib import Path

from analysis import cars, elasticity, medals, population
from analysis import cars, elasticity, medals, penguins, population
from boredcharts import boredcharts

pages = Path(__file__).parent / "pages"
Expand All @@ -12,6 +12,7 @@
medals.figures,
cars.figures,
population.figures,
penguins.figures,
],
)

Expand Down
1 change: 0 additions & 1 deletion examples/full/pages/more/test.md

This file was deleted.

5 changes: 5 additions & 0 deletions examples/full/pages/the-arctic/penguins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## Brr...

This is a seaborn plot:

{{ figure("penguins") }}
3 changes: 3 additions & 0 deletions examples/full/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ requires-python = ">=3.12"
dependencies = [
"bored-charts",
"uvicorn>=0.30.5",
"matplotlib>=3.9.2",
"plotly>=5.23.0",
"altair[all]>=5.4.0",
"vega-datasets>=0.9.0",
"seaborn>=0.13.2",
]

[project.scripts]
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ strict = true
exclude = ["^examples/_.*$"]

[[tool.mypy.overrides]]
module = ["plotly.*", "vega_datasets.*"]
module = ["plotly.*", "seaborn.*", "vega_datasets.*"]
ignore_missing_imports = true

[tool.ruff.lint]
Expand Down
24 changes: 23 additions & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 815b479

Please sign in to comment.