Just as I was thinking I should dive deeper into JavaScript tools for science, Anaconda (a widely used Python distribution) does something amazing; they’re making client-side Python easy and accessible with PyScript! Yay. This reminds me of the potential for edge computing and the sharing of scientific algorithms when Google released PNaCl. One of main difficulties with research is enabling others to reproduce results; Python (and tools like Numpy) have made this easier; but they still leaves out those that are less code savvy. I love the idea of bringing domain knowledge and works from the benchtop directly to the interested user, especially in blog form.

I wanted to see how easy it would be to setup some simple (and powerful) data plotting examples in this Hugo based blog. This details the steps I had to troubleshoot to get multiple examples working on the same page.

Examples

The following examples were implemented from a post here that describes each example in more detail. These examples incorporate some essential scientific tools like tabular data objects with Pandas and plotting with both Matplotlib and Plotly.

Matplotlib and Pandas Example

This example is a direct implementation from the tutorial Create an Interactive Web App with Pyscript.

Weather Data

Some graphs about the weather in London in 2020.

- matplotlib - pandas - seaborn # Import libraries import pandas as pd import matplotlib.pyplot as plt import seaborn as sns # Get the data from pyodide.http import open_url url = 'https://raw.githubusercontent.com/alanjones2/uk-historical-weather/main/data/Heathrow.csv' url_content = open_url(url) df = pd.read_csv(url_content) # filter the data for the year 2020 df = df[df['Year']==2020] # Function to plot the chart def plot_matplotlib(chart): fig, ax = plt.subplots() sns.lineplot(y=chart, x="Month", data=df, ax=ax) assert fig is not None pyscript.write("chart-matplotlib",fig) # Set up a proxy to be called when a 'change' # event occurs in the select control from js import document from pyodide import create_proxy # Read the value of the select control # and call 'plot' def selectChange_matplotlib(event): choice = document.getElementById("select-matplotlib").value plot_matplotlib(choice) # set the proxy def setup_matplotlib(): # Create a JsProxy for the callback function change_proxy = create_proxy(selectChange_matplotlib) e = document.getElementById("select-matplotlib") e.addEventListener("change", change_proxy) setup_matplotlib() # Intitially call plot with 'Tmax' plot_matplotlib('Tmax')

Plotly and Pandas Example

Building on the previous example from How to Use Plotly with PyScript.

Weather Data

Some graphs about the weather in London in 2020.

- matplotlib - pandas - seaborn - plotly # Import libraries import pandas as pd import matplotlib.pyplot as plt import seaborn as sns import plotly import plotly.express as px import json import js # Get the data from pyodide.http import open_url url = 'https://raw.githubusercontent.com/alanjones2/uk-historical-weather/main/data/Heathrow.csv' url_content = open_url(url) df = pd.read_csv(url_content) # filter the data for the year 2020 df = df[df['Year']==2020] # Function to plot the chart def plot_plotly(chart): fig = px.line(df, x="Month", y=chart) graphJSON = json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder) js.plot_plotly(graphJSON,"chart-plotly") # Set up a proxy to be called when a 'change' # event occurs in the select control from js import document from pyodide import create_proxy # Read the value of the select control # and call 'plot' def selectChange_plotly(event): choice = document.getElementById("select-plotly").value plot_plotly(choice) # set the proxy def setup_plotly(): # Create a JsProxy for the callback function change_proxy = create_proxy(selectChange_plotly) e = document.getElementById("select-plotly") e.addEventListener("change", change_proxy) setup_plotly() # Intitially call plot with 'Tmax' plot_plotly('Tmax')

The Trouble

The issues I ran into were not so bad, but, I figured this might help the next dev.

First Rendering HTML

I was hoping that the HTML could be directly injected into the markdown text; this resulted in a few errors due to the markdown processing of the post page. The python code would be directly rendered to the page instead of executing e.g.

<html>

<link rel="stylesheet"
	href="https://pyscript.net/alpha/pyscript.css" />
<script defer
	src="https://pyscript.net/alpha/pyscript.js">
</script>

<py-env>
  - pandas
</py-env>

<py-script>

print("hello world")

</py-script>

</html>

results in

print(“hello world”)

however, this can be easily remedied by making a Hugo shortcode. For my build the html content was put into a file with this path layouts/shortcodes/pyscript_print_hello.html.

The short code:

{{< pyscript_print_hello >}}

results in

- pandas print("hello world")

Wrong Double-Quote Character

I also had an issue with copying code from pages with example code. Somehow the double-quote was not recognized by the Python interpreter and this resulted in an exception thrown in Pyodide. I’m not sure why I can’t reproduce it now.

Python Interpreter Session

It wasn’t obvious to me that successive py-script runs will execute code in the same interpreter scope/session. This means that care should be taken when naming variables and definitions within each script. For example, the plot command (defined in the examples above) was renamed in each script i.e.:

I suppose this should’ve been expected since the page is the scope.

Conclusion

Overall this was really fun to work with. As others have noted, the loading time is much slower than folks would come to expect. PyScript now shows a loading spinner so that your users won’t get confused about the delay in page loading, as shown in the screenshot below.

loading

Each stage of loading is updated in the text below the spinner:

  • Loading runtime…
  • Runtime created…
  • Initializing components…

Embedding the Python code into Hugo ‘shortcodes’ was definitely the right workflow. I’d like to inject as little Python code as possible into HTML; this makes linting and formatting much easier. The current workflow seems to be reliant on pure python modules from PyPi for projects with any substantial complexity. I think a workflow that incorporates pip installs directly from GitHub, would make this ideal.