So you want inline interactive plots in your blog posts? But you don’t want to learn javascript and there wasn’t an obvious way to embed matplotlib?

If your comfortable with (or willing to learn) Julia, there is a simple and elegant solution! Makie.jl is a julia plotting package with a lot of functionality, including a backend system consisting of WGLMakie and JSServe which provides a way to embed makie plots—including 3d visualizations—right into html and inserted into markdown jekyll blog posts.

DISCLAIMER: plots may not appear on some browsers, specifically safari, as WebGL is required and might not be supported. Also, loading the page might take a couple seconds.

a first example

A simple script to display a static 3d visualization—a contour plot of a random volume—looks like this:

using WGLMakie, JSServe
WGLMakie.activate!()

n = 10
volume = rand(n, n, n)
fig, ax, plotobj = contour(volume, figure=(resolution=(700, 700),))

output_file = "static/plot_html/interactive_plotting/contour.html"

# this is the essential component
open(output_file, "w") do io
    println(io, "<center>")
    show(io, MIME"text/html"(), Page(exportable=true, offline=true))
    show(io, MIME"text/html"(), fig) # we insert the figure here
    println(io, "</center>")
end

To include the plot in a jekyll blog post just use liquid—i.e.

{{< include-html "static/plot_html/interactive_plotting/contour.html" >}}

which outputs:

Using a mouse or touchpad, the figure can be rotated and zoomed. Pretty cool eh?

  • click and drag to rotate
  • mouse wheel or 2 fingers on track pad to zoom

This script works by converting the makie fig into an html blob that is written into output_file

more interactivity

Now lets try something with a little more interactivity; sliders are a good place to start.

Using makie we can write:

using WGLMakie, JSServe
WGLMakie.activate!()

# this is optional and just changes the color theme
set_theme!(theme_dark())

# radial sinc function with scale parameter "a"
radial_sinc(x, y, a) = sinc(a * hypot(x, y)) 

# domain of surface
xs = LinRange(-5, 5, 150)
ys = LinRange(-5, 5, 150)

# creating the javascript app
app = App() do session::Session

    # create the slider
    scale_slider = Slider(1:3)

    # map slider values to surface states
    states = map(scale_slider) do a
        return [radial_sinc(x, y, a) for x in xs, y in ys]
    end

    # create the figure
    fig, = surface(xs, ys, states)

    # show the slider value above the slider 
    # using "\\(a = \\)" to embed inline latex in markdown 
    scale_value = DOM.div("\\(a = \\)", scale_slider.value)
    
    # record a state map for the app and display fig above value above slider
    return JSServe.record_states(
        session,
        DOM.div(fig, scale_value, scale_slider)
    )
end

output_file = "static/plot_html/interactive_plotting/sinc_surface.html"

open(output_file, "w") do io
    println(io, "<center>")
    show(io, MIME"text/html"(), Page(exportable=true, offline=true))
    show(io, MIME"text/html"(), app)
    println(io, "</center>")
end

which outputs: