Org blog interactive graphing options

Posted on Jan 12, 2025

I’m searching for a javascript graphing library to spruce up this org / Hugo / Emacs blog. In addition to drawing great graphs, the library should produce visualizations which are:

  • Interactive
  • Resizable
  • Relatively lightweight

tl;dr: Charts.js integrates easily with org and checks off all my requirements.

What I was using before: Python seaborn

In prior posts I’ve used Python’s seaborn to generate graphs as images which I embed in the site. Seaborn is excellent for static graphs, but it doesn’t take advantage of the web platform – you can’t resize or interact with the images.

Here’s an example of a seaborn graph I created earlier of acres burned since 1983:

import seaborn as sns
import pandas as pd
import matplotlib.pyplot as plt

# Data preparation
data = pd.DataFrame({
    "Year": [
        2023, 2022, 2021, 2020, 2019, 2018, 2017, 2016, 2015, 2014, 2013, 2012, 2011, 2010,
        2009, 2008, 2007, 2006, 2005, 2004, 2003, 2002, 2001, 2000, 1999, 1998, 1997, 1996,
        1995, 1994, 1993, 1992, 1991, 1990, 1989, 1988, 1987, 1986, 1985, 1984, 1983
    ],
    "Acres Burned": [
        2693910, 7577183, 7125643, 10122336, 4664364, 8767492, 10026086, 5509995, 10125149,
        3595613, 4319546, 9326238, 8711367, 3422724, 5921786, 5292468, 9328045, 9873745,
        8689389, 8097880, 3960842, 7184712, 3570911, 7393493, 5626093, 1329704, 2856959,
        6065998, 1840546, 4073579, 1797574, 2069929, 2953578, 4621621, 1827310, 5009290,
        2447296, 2719162, 2896147, 1148409, 1323666
    ]
})

# Create a joint plot with regression line in blue
g = sns.jointplot(data=data, x="Year", y="Acres Burned", kind="reg", height=8, color="blue")

# Remove the marginal histogram for the x-dimension
g.ax_marg_x.remove()

# Add a title to the plot, adjusting its position
g.fig.suptitle("Acres Burned by Year: US (Linear Reg)", y=0.98)

# Show plot
plot_path = "../static/imgs/fig-fire-weather-acres-by-year.svg"
plt.savefig(plot_path)
plt.close()
return plot_path

The one that works: Charts.js

For simple graphs, Charts.js checks the boxes, producing beautiful graphs that integrate well with this blog. You can mouseover the datapoints below to see the actual value, and the graph will shrink down with the webpage.

I’ll use Charts.js for quick line / bar plots embedded in this blog, but it is missing some features relative to Seaborn, e.g. a better range of plots and support for statistical niceties such as regression lines.

To avoid having to tweak the Content-Security-Policy, and side-step third party CDNs and unexpected library version changes, I vendorized the Charts.js library into static/javascript so Hugo would include it in the build..

<script src="/javascript/chart.umd.min.js"></script>

<canvas id="temperatureChart" width="800" height="400"></canvas>

<script>
const ctx = document.getElementById('temperatureChart').getContext('2d');
new Chart(ctx, {
    type: 'line',
    data: {
        labels: ['2014', '2015', '2016', '2017', '2018', '2019', '2020', '2021', '2022', '2023'],
        datasets: [{
            label: 'Temperature Anomaly',
            data: [1.09, 1.23, 1.37, 1.27, 1.20, 1.33, 1.36, 1.20, 1.25, 1.54],
            borderColor: 'rgb(75, 192, 192)',
            tension: 0.1,
            errorBars: {
                '2014': { plus: 0.03, minus: 0.03 },
                '2015': { plus: 0.03, minus: 0.03 },
                '2016': { plus: 0.03, minus: 0.03 },
                '2017': { plus: 0.03, minus: 0.03 },
                '2018': { plus: 0.03, minus: 0.03 },
                '2019': { plus: 0.03, minus: 0.03 },
                '2020': { plus: 0.03, minus: 0.03 },
                '2021': { plus: 0.03, minus: 0.03 },
                '2022': { plus: 0.03, minus: 0.03 },
                '2023': { plus: 0.04, minus: 0.04 }
            }
        }]
    },
    options: {
        elements: { point: { radius: 5 } },
        responsive: true,
        scales: {
            y: {
                title: {
                    display: true,
                    text: 'Temperature Anomaly (°C)'
                }
            },
            x: {
                title: {
                    display: true,
                    text: 'Year'
                }
            }
        },
        plugins: {
            title: {
                display: true,
                text: 'Global Temperature Anomaly (2014-2023)'
            }
        }
    }
});
</script>

The one that doesn’t work: Plotly

Plotly is similarly a javascript web-native option, however I was unable to get it working from with org mode.

I’m leaving this half-finished attempt to spur myself / others to figure it out – I believe the issue is that plotly isn’t correctly populating the temperatureChart, though am unclear why.

<script src="https://cdn.plot.ly/plotly-2.30.1.min.js"></script>
<div id="plotlyChart"></div>
<script>
const data = [{
    x: [2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023],
    y: [1.09, 1.23, 1.37, 1.27, 1.20, 1.33, 1.36, 1.20, 1.25, 1.54],
    error_y: {
        type: 'data',
        array: [0.03, 0.03, 0.03, 0.03, 0.03, 0.03, 0.03, 0.03, 0.03, 0.04],
        visible: true
    },
    type: 'scatter',
    mode: 'lines+markers',
    line: {width: 2},
    marker: {size: 8}
}];

const layout = {
    title: 'Global Temperature Anomaly (2014-2023)',
    xaxis: {
        title: 'Year',
        showgrid: true
    },
    yaxis: {
        title: 'Temperature Anomaly (°C)',
        showgrid: true
    },
    hovermode: 'closest',
    width: 300,
    height: 200
};

Plotly.newPlot('plotlyChart', data, layout);
</script>

AI Disclaimer

Claude was used to help generate the javascript tables and proofread this post. Otherwise, this post is certified human slop.