15. Interactive plots in Julia#

As a simple example, one can rotate and zoom in/out with 3D plots.

using WGLMakie, Bonito
Page(exportable=true, offline=true)
WGLMakie.activate!()

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
)
App() do session
    s = Slider(1:5, startvalue=2)
    value = map(s.value) do x
        return x ^ 2
    end
    # Record states is an experimental feature to record all states generated in the Julia session and allow the slider to stay interactive in the statically hosted docs!
    return Bonito.record_states(session, DOM.div(s, value))
end
1
app = App() do session
    slider = Slider(1:10)
    slider[] = 2  # startvalue doesn't work, do it manually
    slider_val = DOM.p(slider[])

    onjs(session, slider.value, js"""function on_update(new_val) {
        const p_element = $(slider_val)
        p_element.innerText = new_val
    }
    
    """)

    Bonito.on_document_load(session, js"""
        const p_element = $(slider_val)
        console.log("slider: ", p_element.innerText)
    """)

    return DOM.div(slider, slider_val)
end

2

These sliders can be tied to various aspects of the plot (found under geometry or material). In the below graph, the slider value is used as the slope. The Javascript code calculates a new end-point for the line segment and updates it. There is also a text box but it is not doing anything yet.

app = App() do session
    text = TextField("test")
    slider = Slider(1:10, startvalue = 2)
    slider_val = DOM.p(slider[])
    fig, ax, plot = ablines(0,2)

    onjs(session, text.value, js"(v) => console.log(v)")
    onjs(session, slider.value, js"""function on_update(new_val) {
        $(plot).then(plots=>{
            const abline = plots[0]
            const x = abline.geometry.attributes.linepoint_end.data.array[4]
            abline.geometry.attributes.linepoint_end.data.array[5] = x*new_val
            abline.geometry.attributes.linepoint_end.needsUpdate = true
        })
    }
    """)
    onjs(session, slider.value, js"""function on_update(new_val) {
        const p_element = $(slider_val)
        p_element.innerText = new_val
    }
    
    """)

    Bonito.on_document_load(session, js"""
        $(plot).then(plots=>{
            const abline = plots[0]
            console.log(abline)
        })

        const p_element = $(slider_val)
        console.log("slider: ", p_element.innerText)
    """)

    return DOM.div(text, slider, slider_val, fig)
end

1

Here the slider moves the first point.

app = App() do session::Session
    s1 = Slider(1:100)
    slider_val = DOM.p(s1[])
    fig, ax, splot = scatter(1:4)

    Bonito.on_document_load(session, js"""
        $(splot).then(plots=>{
            const scatter_plot = plots[0]
            console.log(scatter_plot)
        })
    """)

    onjs(session, s1.value, js"""function on_update(new_value) {
        $(splot).then(plots=>{
            const scatter_plot = plots[0]
            scatter_plot.geometry.attributes.pos.array[0] = (new_value/100) * 4
            scatter_plot.geometry.attributes.pos.array[1] = (new_value/100) * 4
            scatter_plot.geometry.attributes.pos.needsUpdate = true
        })
    }
    """)
    return DOM.div(s1, fig)
end

15.1. Further reading#