Recipe System

The recipe system allows external packages to define how their custom types are rendered on a JSXGraph board without depending on JSXGraph.jl directly. Instead, they depend on the lightweight JSXGraphRecipesBase.jl interface package.

Overview

The recipe workflow follows three steps:

  1. A package defines a custom type (e.g., Triangle).
  2. A recipe is registered with @jsxrecipe, returning a list of ElementSpec objects.
  3. When the user calls plot! (or +) with an instance of that type, JSXGraph.jl converts the specs into real board elements.

Quick Start

Defining a Recipe

using JSXGraphRecipesBase

struct Triangle
    A::Tuple{Float64, Float64}
    B::Tuple{Float64, Float64}
    C::Tuple{Float64, Float64}
end

@jsxrecipe function f(t::Triangle; color="blue")
    p1 = ElementSpec(:point, t.A[1], t.A[2]; name="A")
    p2 = ElementSpec(:point, t.B[1], t.B[2]; name="B")
    p3 = ElementSpec(:point, t.C[1], t.C[2]; name="C")
    [p1, p2, p3, ElementSpec(:polygon, p1, p2, p3; strokeColor=color)]
end

The @jsxrecipe macro turns the function into a method of apply_recipe, dispatching on the annotated argument type.

Using a Recipe

using JSXGraph

b = Board(""; xlim=(-2, 5), ylim=(-2, 5))
tri = Triangle((0.0, 0.0), (3.0, 0.0), (1.5, 2.5))

# Mutating — add elements in-place
plot!(b, tri; color="red")

# Non-mutating — returns a new board
b2 = plot(b, tri)

# Operator syntax
b3 = b + tri

Do-block Syntax

Recipes compose naturally with the do-block board constructor:

b = board(xlim=(-2, 5), ylim=(-2, 5)) do b
    plot!(b, Triangle((0.0, 0.0), (3.0, 0.0), (1.5, 2.5)); color="red")
    plot!(b, Triangle((1.0, 1.0), (4.0, 1.0), (2.5, 3.5)); color="blue")
end

Element Dependencies

An ElementSpec can reference another ElementSpec as a parent. When realized, JSXGraph.jl substitutes the references with the corresponding JSXElement objects. Specs are processed in order, so parent specs must appear before dependent specs in the returned vector.

@jsxrecipe function f(c::Circle2D; color="black")
    center = ElementSpec(:point, c.cx, c.cy; visible=false)
    [center, ElementSpec(:circle, center, c.r; strokeColor=color)]
end

Attribute Aliases

Recipe keyword arguments are forwarded through the standard alias resolution system, so Plots.jl-friendly names work:

plot!(b, my_obj; color="red", lw=3, alpha=0.5)

Checking for Recipes

has_recipe(Triangle)      # true
has_recipe(my_triangle)   # true (instance check)
has_recipe(Int)           # false

API Reference

JSXGraphRecipesBase

JSXGraphRecipesBase.ElementSpecType
ElementSpec(element_type::Symbol, parents...; kwargs...)

Specification for a single JSXGraph element to be created by a recipe.

Fields

  • element_type::Symbol: JSXGraph element type (e.g., :point, :line, :polygon)
  • parents::Vector{Any}: positional arguments for board.create() — may include numbers, strings, tuples, vectors, or references to other ElementSpec objects
  • attributes::Dict{Symbol, Any}: keyword attributes (e.g., color, name)

An ElementSpec can reference another ElementSpec as a parent, enabling element dependencies within a single recipe (e.g., a polygon referencing its vertex points).

Examples

# A simple point
ElementSpec(:point, 1, 2; name="P")

# A line referencing two point specs
p1 = ElementSpec(:point, 0, 0)
p2 = ElementSpec(:point, 1, 1)
l  = ElementSpec(:line, p1, p2; strokeColor="red")
source
JSXGraphRecipesBase.@jsxrecipeMacro
@jsxrecipe function f(obj::MyType; kwargs...)
    # return Vector{ElementSpec}
end

Define a recipe for rendering MyType on a JSXGraph board.

The macro rewrites the function as a method of apply_recipe, dispatching on the annotated argument type. The function body must return a Vector{ElementSpec}.

Arguments

  • The first positional argument must have a type annotation (e.g., obj::MyType)
  • Additional keyword arguments are supported and forwarded by apply_recipe

Example

struct Circle2D
    cx::Float64
    cy::Float64
    r::Float64
end

@jsxrecipe function f(c::Circle2D; color="black")
    center = ElementSpec(:point, c.cx, c.cy; visible=false)
    [center, ElementSpec(:circle, center, c.r; strokeColor=color)]
end

After definition, apply_recipe(Circle2D(0, 0, 1)) returns the element specs, and has_recipe(Circle2D) returns true.

source
JSXGraphRecipesBase.apply_recipeFunction
apply_recipe(obj; kwargs...) -> Vector{ElementSpec}

Apply the registered recipe for the given object, returning a vector of ElementSpec describing the JSXGraph elements to create.

This function is extended by @jsxrecipe definitions. Calling it for a type without a recipe will throw a MethodError.

source
JSXGraphRecipesBase.has_recipeFunction
has_recipe(::Type{T}) -> Bool
has_recipe(obj) -> Bool

Check whether a @jsxrecipe has been defined for the given type.

Examples

has_recipe(Triangle)   # true  (after @jsxrecipe definition)
has_recipe(Int)        # false
has_recipe(my_obj)     # checks typeof(my_obj)
source

JSXGraph.jl Integration

JSXGraph.plot!Function
plot!(board, obj; kwargs...)

Add elements to board by applying the @jsxrecipe registered for typeof(obj).

Arguments

  • board::Board: target board (mutated in place)
  • obj: any object whose type has a registered recipe
  • kwargs...: forwarded to apply_recipe

Returns

The board, with the recipe's elements appended.

Throws

ArgumentError if no recipe is defined for typeof(obj).

Example

b = Board("")
plot!(b, Triangle((0,0), (3,0), (1.5,2.5)); color="red")
source
JSXGraph.realize_specsFunction
realize_specs(specs)

Convert a vector of ElementSpec objects (as returned by apply_recipe) into concrete JSXElement objects.

Specs are processed in order; each spec may reference earlier specs as parents.

Returns

Vector{JSXElement} — the realised elements, ready to be added to a board.

source