Pluto Integration

JSXGraph.jl integrates with Pluto.jl's reactive @bind mechanism, letting you read the live state of a board's interactive elements (draggable points, sliders) directly into Julia variables — and have any downstream cell re-execute automatically when the user drags something in the browser.

Quickstart

Opt in by passing bindable=true to board(...):

using JSXGraph

@bind state board("demo"; xlim=(-5, 5), ylim=(-5, 5), bindable=true) do b
    push!(b, point(-2.0,  1.0; name="A"))
    push!(b, point( 0.0, -1.0; name="B"))
    push!(b, point( 2.0,  2.0; name="C"))
end

In another cell:

xs, ys = points_xy(state)   # parallel vectors in declaration order
state["A"].x                 # access by element name

Drag any point in the rendered board → both cells re-evaluate.

Bound-state shape

When bindable=true, the bound value is a Dict{String,Any}:

Element kindKeyValue
point (2D, not fixed)name attr, else "point_$i"(x = …, y = …) named tuple
point3d (not fixed)name, else "point3d_$i"(x = …, y = …, z = …)
slidername, else "slider_$i"Float64 value

Other element kinds (curves, lines, axes, …) do not contribute to the bound state.

Helpers

points_xy(state)  -> (xs::Vector{Float64}, ys::Vector{Float64})
points_xyz(state) -> (xs::Vector{Float64}, ys::Vector{Float64}, zs::Vector{Float64})

Both helpers project the bound state into parallel coordinate vectors in element-declaration order. points_xy skips 3D points and sliders; points_xyz skips 2D points and sliders.

Duplicate names

A bindable board with two interactive elements sharing the same name raises ArgumentError at construction time — the bound-state schema needs unambiguous keys.

board("dup"; bindable=true) do b
    push!(b, point(0.0, 0.0; name="X"))
    push!(b, point(1.0, 1.0; name="X"))  # → ArgumentError
end

Non-bindable boards keep current behaviour (duplicates allowed).

How it works

When bindable=true, the renderer wraps the board's <div> in an outer <span> whose .value property holds the current state. A throttled JavaScript handler (≈30 Hz) updates .value and dispatches a standard input event on every interactive change, with an unconditional final fire on drag-end. Pluto's @bind macro listens for that event — no custom protocol, no extra dependencies on the Julia side.

Outside Pluto (raw HTML page, Documenter, Jupyter), the wrapper is inert: the board renders normally and the input event fires harmlessly with nothing listening.

If AbstractPlutoDingetjes is loaded (which Pluto does automatically), seed coordinates flow through Pluto's binary published_to_js channel instead of being inlined as JSON literals — useful for boards with very large seed arrays. The package extension JSXGraphAbstractPlutoDingetjesExt makes this transparent; the JSON fallback is used everywhere else.

Runnable example

The following Pluto notebook demonstrates a complete spline-editor workflow: drag points in a JSXGraph board, and a Makie plot of the fitted spline recomputes reactively.

Download the notebook — open it in Pluto locally to interact with it.

<!– PLUTONOTEBOOKSTART –>

begin
    using Pkg
    # Pluto auto-adds `JSXGraph` from the General registry, which (until
    # 0.6 is published) currently resolves to a different UUID than the
    # local checkout. Remove that auto-added entry (if present), then
    # develop the local path. Adjust `path=...` if you place this
    # notebook elsewhere.
    try
        Pkg.rm("JSXGraph")
    catch
    end
    Pkg.develop(PackageSpec(; path=joinpath(@__DIR__, "..", "..")))
    using JSXGraph
end

JSXGraph.jl + Pluto @bind

This notebook demonstrates JSXGraph.jl's Pluto integration. Drag the points in the board below — the cells below it re-execute reactively with the new coordinates.

The key ingredient is bindable=true on board(...), plus the standard @bind macro to surface the board's interactive state as a Julia variable.

@bind state board("demo"; xlim=(-5, 5), ylim=(-5, 5), bindable=true) do b
    push!(b, point(-2.0, 1.0; name="A"))
    push!(b, point(0.0, -1.0; name="B"))
    push!(b, point(2.0, 2.0; name="C"))
end

Raw bound state

state
missing

Parallel coordinate vectors

points_xy(state) projects the bound state into two Vector{Float64}s, in element-declaration order.

xs, ys = points_xy(state)
(Float64[], Float64[])

Live derivation

Any downstream cell that reads state, xs, or ys re-evaluates each time you drag a point.

let
    n = length(xs)
    sumx = isempty(xs) ? 0.0 : sum(xs)
    centroid_x = isempty(xs) ? 0.0 : sumx / n
    centroid_y = isempty(ys) ? 0.0 : sum(ys) / n
    "centroid of the $(n) draggable points is ($(round(centroid_x; digits=3)), $(round(centroid_y; digits=3)))"
end
"centroid of the 0 draggable points is (0.0, 0.0)"

<!– PLUTONOTEBOOKEND –>

API reference

JSXGraph.points_xyFunction
points_xy(state) -> (xs::Vector{Float64}, ys::Vector{Float64})

Project the bound state of a bindable board into parallel x and y coordinate vectors, in the declaration order of the 2D points on the board. 3D points and sliders are skipped.

State entries are recognised as 2D points when they look like a 2-tuple (x, y) or a 2-key dict / NamedTuple (x = …, y = …).

Companion to points_xyz.

source
JSXGraph.points_xyzFunction
points_xyz(state) -> (xs::Vector{Float64}, ys::Vector{Float64}, zs::Vector{Float64})

Project the bound state into parallel x, y, z coordinate vectors, in declaration order of the 3D points on the board. 2D points and sliders are skipped.

source