Skip to content

Commit 2d3e860

Browse files
committed
Add guide section on Matplotlib charts.
1 parent fc8b514 commit 2d3e860

File tree

2 files changed

+180
-0
lines changed

2 files changed

+180
-0
lines changed

hyperdiv_docs/menu.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from .pages.guide.using_the_app_template import using_the_app_template
1616
from .pages.guide.static_assets import static_assets
1717
from .pages.guide.deploying import deploying
18+
from .pages.guide.matplotlib_charts import matplotlib_charts
1819

1920
from .pages.reference.components import components
2021
from .pages.reference.env_variables import env_variables
@@ -41,6 +42,7 @@
4142
"Asynchronous Tasks": {"href": tasks.path},
4243
"Pages & Navigation": {"href": pages_and_navigation.path},
4344
"Using The App Template": {"href": using_the_app_template.path},
45+
"Matplotlib Charts": {"href": matplotlib_charts.path},
4446
"Static Assets": {"href": static_assets.path},
4547
"Deploying Hyperdiv": {"href": deploying.path},
4648
},
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
from ...router import router
2+
from ...page import page
3+
from ...code_examples import docs_markdown
4+
5+
6+
@router.route("/guide/matplotlib-charts")
7+
def matplotlib_charts():
8+
with page() as p:
9+
p.title("# Matplotlib Charts")
10+
11+
docs_markdown(
12+
"""
13+
14+
Hyperdiv does not have a specific component for
15+
[Matplotlib](https://matplotlib.org) charts but a
16+
Matplotlib chart can be added to a Hyperdiv app by
17+
rendering the chart to image bytes and passing the bytes
18+
to the @component(image) component.
19+
20+
"""
21+
)
22+
23+
p.heading("## Basic Use")
24+
25+
docs_markdown(
26+
"""
27+
28+
```py-nodemo
29+
import io
30+
import matplotlib
31+
import matplotlib.pyplot as plt
32+
import hyperdiv as hd
33+
34+
matplotlib.use("Agg")
35+
36+
def get_chart_image(fig):
37+
'''
38+
Renders the chart to png image bytes.
39+
'''
40+
buf = io.BytesIO()
41+
fig.savefig(buf, format="png")
42+
buf.seek(0)
43+
image_bytes = buf.getvalue()
44+
buf.close()
45+
plt.close(fig)
46+
return image_bytes
47+
48+
def main():
49+
# Create a chart:
50+
fig, ax = plt.subplots()
51+
ax.plot([1, 2, 3, 4], [10, 11, 12, 13])
52+
53+
# Render the image bytes in the UI:
54+
hd.image(get_chart_image(fig), width=20)
55+
```
56+
57+
Note that `matplotlib.use("Agg")` is important. It tells
58+
Matplotlib to run headless, without depending on a
59+
GUI. Without this setting, attempting to add a Matplotlib
60+
chart to Hyperdiv will fail.
61+
62+
More on this [here](https://matplotlib.org/stable/users/explain/figure/backends.html#selecting-a-backend).
63+
64+
"""
65+
)
66+
67+
p.heading("## Asynchronous Chart Creation with `task`")
68+
69+
docs_markdown(
70+
"""
71+
72+
In the example above, The chart is re-created on every run
73+
of the app function. We can use a @component(task) to
74+
create the chart only once and cache its result:
75+
76+
```py-nodemo
77+
def get_chart():
78+
fig, ax = plt.subplots()
79+
ax.plot([1, 2, 3, 4], [10, 11, 12, 13])
80+
81+
return get_chart_image(fig)
82+
83+
def main():
84+
task = hd.task()
85+
task.run(get_chart)
86+
if task.result:
87+
hd.image(task.result, width=20)
88+
```
89+
90+
In this example, the function `get_chart` is called only
91+
once and the image bytes are cached in `task.result`.
92+
93+
"""
94+
)
95+
96+
p.heading("## Dynamically Updating Charts")
97+
98+
docs_markdown(
99+
"""
100+
101+
We can also re-create a chart on demand, with new data.
102+
103+
```py-nodemo
104+
def get_chart(data):
105+
fig, ax = plt.subplots()
106+
ax.plot(*data)
107+
108+
return get_chart_image(fig)
109+
110+
def main():
111+
state = hd.state(
112+
chart_data=([1, 2, 3, 4], [10, 11, 12, 13])
113+
)
114+
115+
task = hd.task()
116+
task.run(get_chart, state.chart_data)
117+
if task.result:
118+
hd.image(task.result, width=20)
119+
120+
if hd.button("Update Chart").clicked:
121+
state.chart_data = ([1, 2, 3, 4], [5, 20, 8, 10])
122+
task.clear()
123+
```
124+
125+
In this example, we store the chart's line data in
126+
@component(state). When the `Update Chart` button is
127+
clicked, we update the chart data and clear the task. The
128+
task will then re-run with the new chart data, and an
129+
updated chart will be rendered.
130+
131+
"""
132+
)
133+
134+
p.heading("## Responding to Theme Mode")
135+
136+
docs_markdown(
137+
"""
138+
139+
By default, Matplotlib chart images are rendered on white
140+
background. There's currently no easy way to match the
141+
chart's color scheme to Hyperdiv's theme exactly, but
142+
Matplotlib provides a basic way to render a chart in dark
143+
or light mode. We can then sync the chart's theme mode to
144+
Hyperdiv's theme mode.
145+
146+
```py-nodemo
147+
def get_chart(data, is_dark):
148+
if is_dark:
149+
with plt.style.context("dark_background"):
150+
fig, ax = plt.subplots()
151+
ax.plot(*data)
152+
else:
153+
fig, ax = plt.subplots()
154+
ax.plot(*data)
155+
156+
return get_chart_image(fig)
157+
158+
def main():
159+
theme = hd.theme()
160+
task = hd.task()
161+
task.run(
162+
get_chart,
163+
([1, 2, 3, 4], [5, 20, 8, 10]),
164+
# Pass the current theme mode to the task:
165+
theme.is_dark
166+
)
167+
168+
if task.result:
169+
hd.image(task.result, width=20)
170+
171+
# When the Hyperdiv theme changes, re-render the chart
172+
# in the new theme mode:
173+
if theme.changed:
174+
task.clear()
175+
```
176+
177+
"""
178+
)

0 commit comments

Comments
 (0)