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:
- A package defines a custom type (e.g.,
Triangle). - A recipe is registered with
@jsxrecipe, returning a list ofElementSpecobjects. - 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)]
endThe @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 + triDo-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")
endElement 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)]
endAttribute 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) # falseAPI Reference
JSXGraphRecipesBase
JSXGraphRecipesBase.ElementSpec — Type
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 forboard.create()— may include numbers, strings, tuples, vectors, or references to otherElementSpecobjectsattributes::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")JSXGraphRecipesBase.@jsxrecipe — Macro
@jsxrecipe function f(obj::MyType; kwargs...)
# return Vector{ElementSpec}
endDefine 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)]
endAfter definition, apply_recipe(Circle2D(0, 0, 1)) returns the element specs, and has_recipe(Circle2D) returns true.
JSXGraphRecipesBase.apply_recipe — Function
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.
JSXGraphRecipesBase.has_recipe — Function
has_recipe(::Type{T}) -> Bool
has_recipe(obj) -> BoolCheck 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)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 recipekwargs...: forwarded toapply_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")JSXGraph.realize_specs — Function
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.