How to Use the Plotly Graphing Library in Hugo

· Plotly

I’ve recently been using the Plotly Graphing Library for Python to experiment with simple data visualization. As I continue to learn more about Hugo, I thought it would be fun to see if I could bring the two together and find a way to display figures generated with Plotly on my Hugo site.

I found that Plotly can export charts and graphs as JSON. Knowing this, I can use Python to manipulate data locally, generate visualizations with Plotly, and then export the figures as JSON to display on my Hugo site. Still with me? I’m just as surprised as you are! Let’s dig in.



⏳ Conditionally load the script

First, we’ll need to load the Plotly script on pages where we want to display a chart or graph. We don’t need this scipt to load on pages where it’s not required, so we can include it conditionally and only call it when needed. We can use a corresponding parameter in our content’s front matter to handle this.

Let’s create (or modify) the head.html file in /layouts/partials/ to load the Plotly script. Plotly provides a minified version of the latest script on their CDN, so we can use that and wrap it in a Go template conditional:

📂 /layouts/partials/head.html:

// .. existing <head> content
<!-- Plotly -->
{{ if .Params.plotly }}
    <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
{{ end }}
// existing <head> content ..

.Params.plotly will look for the plotly parameter in a content file’s front matter. If it’s present and set to true, the script will be loaded. Now when editing content on our site we can load the script only on pages that require it. As an example, the front matter of this post currently contains:

+++
title = 'How to Use the Plotly Graphing Library in Hugo'
date = '2024-08-01T12:39:26-07:00'
draft = true
plotly = true
categories = ["Data", "Hugo"]
tags = ["Plotly", "Python"]
+++

With plotly = true, the script will be loaded and we’re ready to use it on our page. The question now is how best to do so.


💻 Create a Plotly shortcode

Hugo Shortcodes allow us to create small, reusable snippets that we can embed within our site. Since we most likely want to create multiple graphs, shortcodes provide a great solution for our intended use case. We can abstract out the rendering logic and then simply pass the desired JSON into each shortcode to be displayed. I found a helpful reference that I shamelessly stole from referenced to create the new shortcode.

📂 layouts/shortcodes/plotly.html:

{{ $json := .Get "json" }}
{{ $height := .Get "height" | default "400px" }}
{{ $width := .Get "width" | default "100%" }}
{{ $id := .Get "id" | default (printf "plotly-%s" (now.UnixNano)) }}
<div id="{{ $id }}" class="plotly" style="height: {{ $height }}; width: {{ $width }};"></div>
<script>
    Plotly.d3.json("{{ $json }}", function(err, fig) {
        if (err) {
            document.getElementById('{{ $id }}').innerHTML = "Error loading graph: " + err.message;
            console.error(err);
            return;
        }
        Plotly.plot('{{ $id }}', fig.data, fig.layout, {
            responsive: true,
            displayModeBar: false,
            displaylogo: false
        });
    });
</script>

This shortcode will take the JSON file path and a few optional parameters to fetch and render the Plotly graph. The graph will be wrapped in a new div. If an id is not specified within the shortcode then one will be generated with now.UnixNano which generates a timestamp down to the millisecond to ensure each graph’s parent div has a unique id to avoid potential conflicts.

I also added some basic error handling and included a few Plotly configuration options which can be removed or modified as well.


📊 Get some data

With our script loaded and the shortcode ready to display a graph, we just need to find some data to feed it. For this example, I created a Jupyter Notebook and used plotly.io.write_json to return the JSON needed to create a simple graph.

import plotly.io as pio
import plotly.graph_objs as go

# Let's pretend I have some fruit...
fruits = ['Apples', 'Oranges', 'Bananas']
quantities = [10, 14, 8]

# Now we can create the figure:
fig = go.Figure(data=[go.Bar(x=fruits, y=quantities)])

# And give it a title:
fig.update_layout(title='Hypothetical Fruit')

# Finally, we can export it to a JSON file:
pio.write_json(fig, 'plotly_graph.json')

This will create a JSON file that can be passed to the shortcode to display the graph on our Hugo site. I copied this file to a subdirectory within this post’s parent directory here: content/posts/plotly-graphing-in-hugo/data/plotly-graph.json. Now we just need to put it all together.


📈 Insert the shortcode into a post or page:

We’ve got our data and we’re ready to take our new shortcode for a test drive. Our shortcode includes a few optional parameters but the only thing we need to throw at it is some JSON. Let’s keep it simple and stick with the defaults for now and insert our shortcode like so:

// Remove *'s used for escaping the shortcode
{{*< plotly json="data/plotly-graph.json" >*}}

🥁 Drum roll…

Plotly.d3.json(“data\/plotly-graph.json”, function(err, fig) { if (err) { document.getElementById(‘plotly-1744407305987989000’).innerHTML = “Error loading graph: ” + err.message; console.error(err); return; } Plotly.plot(‘plotly-1744407305987989000’, fig.data, fig.layout, { responsive: true, displayModeBar: false, displaylogo: false }); });

🎉 We have hypotethical fruit! I’m not sure what I’ll do with 14 oranges, but that’s a problem for another time. Back to the shortcode, we can further customize the basic layout of each graph that is inserted by passing the optional parameters. Let’s specify a height, width, and custom ID for the parent div:

// Remove *'s used for escaping
{{*< plotly
    json="data/plotly-graph.json"
    height="300px"
    width="80%"
    id="fruit-graph" >*}}

Plotly.d3.json(“data\/plotly-graph.json”, function(err, fig) { if (err) { document.getElementById(‘fruit-graph’).innerHTML = “Error loading graph: ” + err.message; console.error(err); return; } Plotly.plot(‘fruit-graph’, fig.data, fig.layout, { responsive: true, displayModeBar: false, displaylogo: false }); });

To share a more practical example than hypothetical fruits, I borrowed some numbers from the Economic Policy Institute. I used them to recreate my own take on their graph using Plotly’s Python library. I exported that chart as JSON following similar steps as above and now I can insert that graph with the following:

📂 content/posts/plotly-graphing-in-hugo/index.md:

// Remove *'s used for escaping
{{*< plotly json="data/unions.json" height="380px" >*}}

And we’ll get something like this:

Plotly.d3.json(“data\/unions.json”, function(err, fig) { if (err) { document.getElementById(‘plotly-1744407305988016000’).innerHTML = “Error loading graph: ” + err.message; console.error(err); return; } Plotly.plot(‘plotly-1744407305988016000’, fig.data, fig.layout, { responsive: true, displayModeBar: false, displaylogo: false }); });

Source: As union membership declines, income inequality increases

There we have it. With the heavy lifting done, we can now export Plotly graphs as JSON and display them on our Hugo site with a simple shortcode. I’ve included some references below for further reading and would be thrilled to hear any feedback or suggestions you have on this approach!


📚 References and Further Reading