One can use JSServe and WGLMakie in Pluto, IJulia, Webpages - and Documenter! It's possible to create interactive apps and dashboards, serve them on live webpages, or export them to static HTML.
This tutorial will run through the different modes and what kind of limitations to expect.
First, one should use the new Page mode for anything that displays multiple outputs, like Pluto/IJulia/Documenter. This creates a single entry point, to connect to the Julia process and load dependencies. For Documenter, the page needs to be set to
exportable=true, offline=true. Exportable has the effect of inlining all data & js dependencies, so that everything can be loaded in a single HTML object.
offline=true will make the Page not even try to connect to a running Julia process, which makes sense for the kind of static export we do in Documenter.
using JSServe Page(exportable=true, offline=true)
After the page got displayed by the frontend, we can start with creating plots and JSServe Apps:
using WGLMakie # Set the default resolution to something that fits the Documenter theme set_theme!(resolution=(800, 400)) scatter(1:4, color=1:4)
As you can see, the output is completely static, because we don't have a running Julia server, as it would be the case with e.g. Pluto. To make the plot interactive, we will need to write more parts of WGLMakie in JS, which is an ongoing effort. As you can see, the interactivity already keeps working for 3D:
N = 60 function xy_data(x, y) r = sqrt(x^2 + y^2) r == 0.0 ? 1f0 : (sin(r)/r) end l = range(-10, stop = 10, length = N) z = Float32[xy_data(x, y) for x in l, y in l] surface( -1..1, -1..1, z, colormap = :Spectral )
There are a couple of ways to keep interacting with Plots in a static export.
JSServe allows to record a statemap for all widgets, that satisfy the following interface:
# must be true to be found inside the DOM is_widget(x) = true # Updating the widget isn't dependant on any other state (only thing supported right now) is_independant(x) = true # The values a widget can iterate function value_range end # updating the widget with a certain value (usually an observable) function update_value!(x, value) end
Currently, only sliders overload the interface:
using Observables App() do session::Session n = 10 index_slider = Slider(1:n) volume = rand(n, n, n) slice = map(index_slider) do idx return volume[:, :, idx] end fig = Figure() ax, cplot = contour(fig[1, 1], volume) rectplot = linesegments!(ax, Rect(-1, -1, 12, 12), linewidth=2, color=:red) on(index_slider) do idx translate!(rectplot, 0,0,idx) end heatmap(fig[1, 2], slice) slider = DOM.div("z-index: ", index_slider, index_slider.value) return JSServe.record_states(session, DOM.div(slider, fig)) end