Skip to content

Commit 6861b23

Browse files
committed
a first draft for dashboards/altair
1 parent edc8382 commit 6861b23

7 files changed

Lines changed: 481 additions & 0 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
README*
2+
fastapi_random_yields.py
3+
data/
4+
media/
5.28 KB
Binary file not shown.
Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
---
2+
jupytext:
3+
custom_cell_magics: kql
4+
formats: ipynb,md:myst
5+
text_representation:
6+
extension: .md
7+
format_name: myst
8+
kernelspec:
9+
name: python3
10+
display_name: Python 3 (ipykernel)
11+
language: python
12+
language_info:
13+
name: python
14+
nbconvert_exporter: python
15+
pygments_lexer: ipython3
16+
---
17+
18+
# altair
19+
20+
```{admonition} download the zip
21+
:class: warning
22+
23+
like many similar systems, altair-made dashboards might not run well in Jupyter notebooks,
24+
so you can {download}`download the companion zip<./ARTEFACTS-altair.zip>`
25+
to run our samples locally
26+
```
27+
28+
this notebook shows a very basic dashboard made in altair / vega-lite from dynamic data (fetched at a web service)
29+
what we will demonstrate is
30+
31+
- how to write a simple web server that generates random data - as an anticipation of the S2 courses
32+
- how to create an altair chart from that data, as an example of using altair's "graphic grammar" approach
33+
- how to transform the result into a standalone web app
34+
35+
36+
one of the pros of using altair is the ability to produce an interactive visualisation as a self-contained HTML file
37+
38+
+++
39+
40+
## dependencies
41+
42+
you may need to
43+
44+
```bash
45+
pip install "fastapi[standard]" altair
46+
```
47+
48+
+++
49+
50+
## imports
51+
52+
```{code-cell} ipython3
53+
import pandas as pd
54+
55+
# here is how to import altair
56+
import altair as alt
57+
```
58+
59+
## the fastapi web service
60+
61+
we provide you with the source code for a small web service that is able to produce random data for the leases, in a format compatible with the one in `leases.csv`
62+
this is the purpose of `fastapi_random_yields.py`
63+
64+
+++
65+
66+
### how to start
67+
68+
assuming you have installed fastapi as above, you can start the web service by typing in your terminal
69+
70+
```bash
71+
fastapi dev fastapi_random_yields.py
72+
```
73+
74+
this will behave a bit like `jupyter lab`, in that it will
75+
76+
- start a web server
77+
- display the URL to use to join it
78+
- and it will *block*, meaning the terminal is busy, and no longer responding to your commands
79+
80+
so first off, leave this terminal running, and create another one if needed
81+
82+
+++
83+
84+
### how to get the docs
85+
86+
inside the terminal where you triggered the server, you'll see a URL
87+
something like <http://localhost:8000/docs/>
88+
just open a web browser and cut-n-paste that URL
89+
90+
```{admonition} link maybe clickable ?
91+
:class: dropdown tip
92+
93+
in some terminal setups, you may be able to e.g. Command-click or Alt-click on the link in the terminal to open it in your browser (this is rather likely on linux and MacOS, not sure wbout Windows)
94+
```
95+
96+
as you might have guessed now, this means
97+
98+
- use the http protocol
99+
- to reach a service running on the computer named `localhost` (your own laptop, that is)
100+
- on port 8000
101+
- and on the `/docs/` route
102+
103+
```{admonition} what are ports ?
104+
ports is a very simple mechanism to allow your computer to run many different services (in real life, you would typically have ssh on port 22, web on port 443, dns on port 53, and so on..)
105+
```
106+
107+
the `/docs/` route is a predefined route, auto-generated by FastAPI, that gives us.. the doc of how to use this web service
108+
109+
+++
110+
111+
### how to use
112+
113+
and so, from this doc, you can see that you can send requests with this pattern
114+
115+
```
116+
/api/yields/1931/1933
117+
```
118+
119+
so, try it out from your browser and enter this URL: <http://localhost:8000/api/yields/1931/1933>
120+
121+
```{admonition} also from the terminal
122+
:class:dropdown
123+
124+
you can achieve the same result from the terminal if you prefer
125+
first you need to
126+
`pip install httpie`
127+
and then you just do
128+
`http :8000/api/yields/1931/1933`
129+
130+
then [take a look at `jq`](https://jqlang.org/manual/) that can filter this JSON stream for you
131+
```
132+
133+
+++
134+
135+
## the API outcome
136+
137+
either way - from the browser or from the terminal - you should see a JSON stream with the random data generated
138+
it's actually a list of dicts, and its structure mimicks this example taken from altair's documentation
139+
140+
```{code-cell} ipython3
141+
import altair as alt
142+
from vega_datasets import data
143+
144+
source = data.barley()
145+
source.head(10)
146+
```
147+
148+
so our API call above would issue a variation around this predefined data, over 3 years 1931 .. 1933
149+
and there's one generated entry for each combination of site x variety x year
150+
151+
```{admonition} exercice
152+
how many different sites and varieties are there in the source ?
153+
```
154+
155+
+++
156+
157+
## altair visualisations
158+
159+
altair offers a "grammar-oriented" visualisation paradigm where the visualisation is defined in a **declarative** way
160+
161+
+++
162+
163+
### a stacked bar from altair's doc
164+
165+
here's an example taken from the altair documentation
166+
167+
```{code-cell} ipython3
168+
# stolen from https://altair-viz.github.io/gallery/stacked_bar_chart.html
169+
170+
(
171+
alt.Chart(source)
172+
.mark_bar()
173+
.encode(
174+
x='variety',
175+
y='sum(yield)',
176+
color='site'
177+
)
178+
)
179+
```
180+
181+
### **exo**: inspect the data
182+
183+
take some time to get a glimpse at what the data looks like...
184+
185+
```{code-cell} ipython3
186+
import itables
187+
itables.init_notebook_mode()
188+
189+
source
190+
```
191+
192+
```{code-cell} ipython3
193+
# your code here
194+
# feel free to create extra cells if needed
195+
```
196+
197+
### **exo**: write your own pivot
198+
199+
you should be able to see a resemblance with some sort of *pivot table* here
200+
would you be able to compute a pivot table that resonates with this visualisation ?
201+
202+
````{admonition} solution
203+
:class: dropdown
204+
205+
```{code-cell} python
206+
source.pivot_table(
207+
values='yield',
208+
aggfunc="sum",
209+
index="site",
210+
columns="variety",
211+
)
212+
```
213+
````
214+
215+
```{code-cell} ipython3
216+
# your code
217+
```
218+
219+
### a few useful additions
220+
221+
here's a few additions to that sample chart, that will make our life easier:
222+
223+
- we set a *width* and *height*
224+
- as well as a *title*
225+
- and make it interactive: try to scroll up or down in the figure with 2 fingers
226+
227+
````{admonition} not interactive ?
228+
:class: dropdown
229+
230+
Oh but no, the interactive thing is not working for us here; it is because the X axis does not have numeric values !
231+
but let's keep this trick in mind for later, it will come in handy at some point
232+
````
233+
234+
```{code-cell} ipython3
235+
# once and for good
236+
alt.renderers.enable("mimetype")
237+
238+
# same beginning
239+
(
240+
alt.Chart(source)
241+
.mark_bar()
242+
.encode(
243+
x='variety',
244+
y='sum(yield)',
245+
color='site'
246+
).properties(
247+
height=300,
248+
width=800,
249+
title=f"Barley yields",
250+
)
251+
.interactive()
252+
)
253+
```
254+
255+
## dynamic data
256+
257+
ofcourse, we can easily switch from the (static) data source to our web service
258+
259+
```{code-cell} ipython3
260+
# just replace the source with this
261+
URL = "http://localhost:8000/api/yields/1931/1933"
262+
263+
chart = (
264+
alt.Chart(URL)
265+
.mark_bar()
266+
.encode(
267+
# here we need to help altair (actually vega-lite)
268+
# and be explicit on the types of the various fields
269+
x='variety:N',
270+
y='sum(yield):Q',
271+
color='site:N'
272+
).properties(
273+
height=300,
274+
width=800,
275+
title=f"Barley yields",
276+
)
277+
.interactive()
278+
)
279+
```
280+
281+
## save as an HTML standalone app
282+
283+
```{code-cell} ipython3
284+
import altair as alt
285+
286+
# Convert to HTML, adding a custom button and reload script
287+
html = f"""
288+
<!DOCTYPE html>
289+
<html>
290+
<head>
291+
<meta charset="utf-8">
292+
<title>Altair Chart with Refresh</title>
293+
<script src="https://cdn.jsdelivr.net/npm/vega@5"></script>
294+
<script src="https://cdn.jsdelivr.net/npm/vega-lite@5"></script>
295+
<script src="https://cdn.jsdelivr.net/npm/vega-embed@6"></script>
296+
</head>
297+
<body style="font-family:sans-serif">
298+
<h2>Yield Chart (with Refresh)</h2>
299+
<button id="refresh">🔄 Refresh</button>
300+
<div id="visual"></div>
301+
302+
<script type="text/javascript">
303+
const spec = {chart.to_json(indent=None)};
304+
const container = document.getElementById('visual');
305+
306+
function render() {{
307+
vegaEmbed(container, spec, {{ actions: false }});
308+
}}
309+
310+
// Initial render
311+
render();
312+
313+
// Reload button handler
314+
document.getElementById('refresh').addEventListener('click', () => {{
315+
// Force Vega-Lite to reload the URL by tweaking it with a cache-busting parameter
316+
const url = new URL(spec.data.url);
317+
url.searchParams.set('_t', Date.now());
318+
spec.data.url = url.toString();
319+
render();
320+
}});
321+
</script>
322+
</body>
323+
</html>
324+
"""
325+
326+
# Save to a standalone file
327+
with open("yield-chart.html", "w", encoding="utf-8") as f:
328+
f.write(html)
329+
330+
print("✅ Saved to yield-chart.html")
331+
```
332+
333+
## try it out
334+
335+
the previous cell should have created a local file named `yield-chart.html`
336+
the simplest way to try it is to open it in your browser (e.g. double-click it from your file explorer)
337+
338+
of course this can also be inserted into another app, etc...
339+
340+
+++
341+
342+
## conclusion
343+
344+
with altair:
345+
- the way the diagram is built, using this so-called *graphic grammar*, reminds a bit of the way seaborn lets us use several dimensions to outline various aspects of the data
346+
- you have a rather easy way to write nice web apps with mostly Python, and a limited knowledge of the web technos like HTML and the like
347+
- you can easily use dynamic data sources
348+
- it can come in handy sometimes to come up with great demos in a reasonable time
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
site
2+
University Farm
3+
Waseca
4+
Morris
5+
Crookston
6+
Grand Rapids
7+
Duluth
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
variety
2+
Manchuria
3+
Glabron
4+
Svansota
5+
Velvet
6+
Trebi
7+
No. 457
8+
No. 462
9+
Peatland
10+
No. 475
11+
Wisconsin No. 38

0 commit comments

Comments
 (0)