Skip to content

Commit

Permalink
render matplotlib charts as png, drop mpld3
Browse files Browse the repository at this point in the history
  • Loading branch information
oliverlambson committed Aug 25, 2024
1 parent 42103a8 commit d6965fb
Show file tree
Hide file tree
Showing 8 changed files with 34 additions and 60 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Build easy, minimal, PDF-able data reports with markdown and python.
- [x] allow decoration of functions that return figures directly
- [x] make report_name path parameter optional
- [x] altair figures as html
- [ ] matplotlib figures as png (remove mpld3)
- [x] matplotlib figures as png (drop mpld3)
- [ ] pdf exports with selenium in headless mode
- [ ] cli? (`boredcharts init`, `boredcharts export [report]`, `boredcharts dev`, `boredcharts serve`)
- [ ] deploy to [bored-charts-example.oliverlambson.com](https://bored-charts-example.oliverlambson.com)
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.6.1"
__version__ = "0.7.0"

from boredcharts.router import BCRouter
from boredcharts.webapp import boredcharts
Expand Down
53 changes: 18 additions & 35 deletions bored-charts/boredcharts/jinja.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import base64
import logging
import string
import uuid
from io import BytesIO
from textwrap import dedent, indent
from typing import Any

import altair as alt
import markdown
import matplotlib.figure as mplfig
import mpld3
from fastapi import Request
from jinja2 import Undefined, pass_context
from jinja2.runtime import Context
Expand Down Expand Up @@ -66,39 +66,22 @@ def altair_to_html(chart: alt.Chart) -> Markup:


def mpl_to_html(fig: mplfig.Figure) -> Markup:
"""Renders a matplotlib Figure as HTML."""
# TODO: return base64 encoded PNG instead of using mpld3
figid = f"mpl-{uuid.uuid4()}" # html id can't start with digit
script = dedent(
string.Template(
"""
<script>
async function resizeMpld3(event, figid) {
var targetDiv = event.detail.elt.querySelector(`#${figid}`);
if (targetDiv) {
var svgElements = targetDiv.querySelectorAll('.mpld3-figure');
svgElements.forEach(function(svgElement) {
var width = svgElement.getAttribute('width');
var height = svgElement.getAttribute('height');
svgElement.setAttribute('viewBox', `0 0 ${width} ${height}`);
svgElement.setAttribute('width', '100%');
svgElement.removeAttribute('height');
});
}
}
document.addEventListener("htmx:afterSettle", (event) => { resizeMpld3(event, "${figid}") });
</script>
"""
).safe_substitute(figid=figid)
).strip()
return Markup(
mpld3.fig_to_html(
fig,
no_extras=True,
figid=figid,
)
+ script
)
"""Renders a Matplotlib Chart as HTML."""
with BytesIO() as buffer:
fig.savefig(buffer, format="png", dpi=250)
buffer.seek(0)
png = buffer.read()
png64 = base64.b64encode(png).decode("utf-8")

title = fig.get_suptitle()
if not title:
titles = []
for ax in fig.get_axes():
if t := ax.get_title():
titles.append(t)
title = "; ".join(titles)

return Markup(f"""<img src="data:image/png;base64,{png64}" alt="{title}">""")


@pass_context
Expand Down
1 change: 0 additions & 1 deletion bored-charts/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ dependencies = [
"markdown>=3.6",
"markupsafe>=2.1.5",
"matplotlib>=3.9.2",
"mpld3>=0.5.10",
"altair>=5.4.0",
]
readme = "README.md"
Expand Down
12 changes: 12 additions & 0 deletions examples/full/pages/elasticity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## Price elasticity

Here we did some more fiddly analysis and wanted to use matplotlib for whatever reason,
no problem—it's exactly the same thing (except that these aren't interactive, just images):

<pre>
{%- raw %}
{{ figure("elasticity_vs_profit") }}
{% endraw -%}
</pre>

{{ figure("elasticity_vs_profit") }}
5 changes: 0 additions & 5 deletions examples/full/pages/populations.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,3 @@ We can also dip into html when we need to
{{ figure("population", country="United Kingdom") }}
{{ figure("population", country="France") }}
</div>

Or a matplotlib char
(this is buggy though, mpld3 is not actively supported which means we shouldn't really do this):

{{ figure("elasticity_vs_profit") }}
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.*", "mpld3.*"]
module = ["plotly.*"]
ignore_missing_imports = true

[tool.ruff.lint]
Expand Down
17 changes: 1 addition & 16 deletions uv.lock

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

0 comments on commit d6965fb

Please sign in to comment.