19 Visuals
19.1 The Basic Visual Protocol
procedure
(visual-path? value) → boolean?
value : any/c
procedure
(visual-target-path target) → visual-path?
target : (or/c visual? symbol? visual-path?)
generic interface
A custom Visual implementation must always return a symbol. Immutable update methods must preserve the symbol.
procedure
(visual-position visual) → vec2?
visual : visual?
procedure
(visual-with-position visual position) → visual?
visual : visual? position : vec2?
A minimal position-only Visual can be defined as follows:
(struct marker (id position) #:transparent #:methods gen:visual [(define (visual-id value) (marker-id value)) (define (visual-position value) (marker-position value)) (define (visual-with-position value position) (struct-copy marker value [position position]))])
Such a Visual can use move-to, but it cannot use rotation or scale animations.
19.2 Whole-Visual Affine Maps
procedure
(affine-map content map) → affine-map-visual?
content : visual? map : affine2?
The wrapper is itself an affine-visual?. It retains a canonical local copy of the subtree plus its full local-to-parent map, which lets ordinary group composition preserve the map and lets descendants remain addressable. This is the semantic bridge used by nested apply-affine requests.
procedure
(affine-map-visual? value) → boolean?
value : any/c
procedure
(affine-map-visual-content visual) → visual?
visual : affine-map-visual?
procedure
(affine-map-visual-map visual) → affine2?
visual : affine-map-visual?
19.3 The Affine-Visual Protocol
generic interface
procedure
(affine-visual? value) → boolean?
value : any/c
procedure
(visual-transform visual) → affine-transform?
visual : affine-visual?
procedure
(visual-with-transform visual transform) → affine-visual?
visual : affine-visual? transform : affine-transform?
procedure
(visual-rotation visual) → finite-real?
visual : affine-visual?
procedure
(visual-scale visual) → vec2?
visual : affine-visual?
procedure
(visual-with-rotation visual rotation) → affine-visual?
visual : affine-visual? rotation : finite-real?
procedure
(visual-with-scale visual scale) → affine-visual?
visual : affine-visual? scale : scale-factor?
A built-in group or formula assembly accepts only a uniform scale, so its x and y components must be equal. Other built-in affine Visuals, including arrows and axes, accept non-uniform scale.
19.4 The Opacity-Visual Protocol
generic interface
Opacity is semantic model data. Renderers should draw the Visual normally. The Pict adapter applies global opacity after it selects and runs a renderer.
procedure
(opacity-visual? value) → boolean?
value : any/c
procedure
(visual-opacity visual) → opacity?
visual : opacity-visual?
procedure
(visual-with-opacity visual opacity) → opacity-visual?
visual : opacity-visual? opacity : opacity?
The built-in circle, rectangle, path, arrow, axes, plain-text, formula, formula-assembly, and group Visuals implement this protocol.
A position-only custom Visual can implement opacity as follows:
(struct marker (id position opacity) #:transparent #:methods gen:visual [(define (visual-id value) (marker-id value)) (define (visual-position value) (marker-position value)) (define (visual-with-position value position) (struct-copy marker value [position position]))] #:methods gen:opacity-visual [(define (visual-opacity value) (marker-opacity value)) (define (visual-with-opacity value opacity) (struct-copy marker value [opacity opacity]))])
19.5 The Stroke-Width-Visual Protocol
procedure
(stroke-width? value) → boolean?
value : any/c
generic interface
The protocol is renderer-independent model data. Built-in renderers read the stored widths of the Visual types they support; third-party renderers decide how to interpret the width exposed by their own Visual implementations.
procedure
(stroke-width-visual? value) → boolean?
value : any/c
procedure
(visual-stroke-width visual) → (and/c finite-real? (>=/c 0))
visual : stroke-width-visual?
procedure
(visual-with-stroke-width visual stroke-width) → stroke-width-visual? visual : stroke-width-visual? stroke-width : (and/c finite-real? (>=/c 0))
The built-in circle, rectangle, path, arrow, axes, number-line, and point-marker Visuals implement this protocol. Coordinate plots and filled areas that are themselves path Visuals participate without a separate plot-animation mechanism. A scatter-plot result is instead a top-level group; its nested point-marker children are not independent scene-state animation targets, so the scatter group does not implement this protocol. A callout’s callout-visual-connector-width is likewise separate frame-space connector style and is not controlled by stroke-width-to.
A position-only custom Visual can opt in independently of affine transforms and opacity:
(struct width-marker (id position stroke-width) #:transparent #:methods gen:visual [(define (visual-id value) (width-marker-id value)) (define (visual-position value) (width-marker-position value)) (define (visual-with-position value position) (struct-copy width-marker value [position position]))] #:methods gen:stroke-width-visual [(define (visual-stroke-width value) (width-marker-stroke-width value)) (define (visual-with-stroke-width value stroke-width) (struct-copy width-marker value [stroke-width stroke-width]))])
19.6 Fill-Color and Stroke-Color Visual Protocols
generic interface
procedure
(fill-color-visual? value) → boolean?
value : any/c
procedure
(visual-fill-color visual) → any/c
visual : fill-color-visual?
procedure
(visual-with-fill-color visual color) → fill-color-visual?
visual : fill-color-visual? color : paint?
generic interface
procedure
(stroke-color-visual? value) → boolean?
value : any/c
procedure
(visual-stroke-color visual) → any/c
visual : stroke-color-visual?
procedure
(visual-with-stroke-color visual color) → stroke-color-visual?
visual : stroke-color-visual? color : color-spec?
Circles, rectangles, paths, and point markers implement both protocols. Arrows, axes, and number lines implement the stroke-color protocol. A scatter-plot result is a group whose nested marker children are not independent scene-state targets, so the group itself implements neither color protocol. Callout callout-visual-connector-stroke is separate frame-space connector style and is not controlled by stroke-color-to. The protocols are independent of affine transforms, opacity, and stroke width, so third-party Visuals may opt into either one separately.
19.7 Circle Visuals
procedure
(circle #:id id [ #:center center #:rotation rotation #:scale scale #:opacity opacity #:radius radius #:fill fill #:stroke stroke #:stroke-width stroke-width]) → circle-visual? id : symbol? center : vec2? = origin rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 radius : (and/c finite-real? positive?) = 1 fill : any/c = "dodgerblue" stroke : any/c = "black" stroke-width : (and/c finite-real? (>=/c 0)) = 2
The built-in Pict renderer treats fill and stroke as color values and stroke-width as a Pict border width. These style values are stored without adapter-specific validation.
Circle Visuals implement gen:visual, gen:affine-visual, gen:opacity-visual, and gen:stroke-width-visual. The opacity value multiplies the complete rendered circle after renderer dispatch.
procedure
(circle-visual? value) → boolean?
value : any/c
procedure
(circle-visual-radius circle) → (and/c finite-real? positive?)
circle : circle-visual?
procedure
(circle-visual-fill circle) → any/c
circle : circle-visual?
procedure
(circle-visual-stroke circle) → any/c
circle : circle-visual?
procedure
(circle-visual-stroke-width circle)
→ (and/c finite-real? (>=/c 0)) circle : circle-visual?
19.8 Rectangle Visuals
procedure
(rectangle #:id id [ #:center center #:rotation rotation #:scale scale #:opacity opacity #:width width #:height height #:fill fill #:stroke stroke #:stroke-width stroke-width]) → rectangle-visual? id : symbol? center : vec2? = origin rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 width : (and/c finite-real? positive?) = 2 height : (and/c finite-real? positive?) = 1 fill : any/c = "goldenrod" stroke : any/c = "black" stroke-width : (and/c finite-real? (>=/c 0)) = 2
The id argument is required. Style values are stored for an adapter to interpret. Rectangle Visuals implement the basic, affine, opacity, and stroke-width Visual protocols. The opacity value multiplies the complete rendered rectangle.
procedure
(rectangle-visual? value) → boolean?
value : any/c
procedure
(rectangle-visual-width rectangle)
→ (and/c finite-real? positive?) rectangle : rectangle-visual?
procedure
(rectangle-visual-height rectangle)
→ (and/c finite-real? positive?) rectangle : rectangle-visual?
procedure
(rectangle-visual-fill rectangle) → any/c
rectangle : rectangle-visual?
procedure
(rectangle-visual-stroke rectangle) → any/c
rectangle : rectangle-visual?
procedure
(rectangle-visual-stroke-width rectangle)
→ (and/c finite-real? (>=/c 0)) rectangle : rectangle-visual?
19.9 Plain, Multiline, and Rich Text Visuals
A text Visual stores immutable Unicode content, inline style runs, and explicit font/layout anchors. plain-text preserves the original one-line API; paragraph adds explicit lines and renderer-measured wrapping; and rich-text adds styled spans. Every form implements gen:visual, gen:affine-visual, and gen:opacity-visual. Its raw structure constructor and internal transform and opacity fields are not public.
The reference position is an anchor selected on the untransformed text box. Horizontal alignment chooses its left edge, center, or right edge. Vertical alignment chooses its top edge, center, font baseline, or bottom edge. For a paragraph the baseline is the first rendered line’s baseline. Scale and rotation are applied around that anchor.
procedure
(text-font-family? value) → boolean?
value : any/c
'default 'decorative 'roman 'script 'swiss 'modern 'symbol 'system
A family is a portable request, not a promise of one particular installed font face. The drawing backend chooses a suitable platform font.
procedure
(text-font-style? value) → boolean?
value : any/c
procedure
(text-font-weight? value) → boolean?
value : any/c
procedure
(text-horizontal-alignment? value) → boolean?
value : any/c
procedure
(text-vertical-alignment? value) → boolean?
value : any/c
procedure
(text-span content [ #:font-size font-size #:font-face font-face #:font-family font-family #:font-style font-style #:font-weight font-weight #:color color]) → text-span? content : string? font-size : (or/c false/c (and/c finite-real? positive?)) = #f font-face : (or/c false/c string?) = #f font-family : (or/c false/c text-font-family?) = #f font-style : (or/c false/c text-font-style?) = #f font-weight : (or/c false/c text-font-weight?) = #f color : any/c = #f
procedure
(text-span? value) → boolean?
value : any/c
procedure
(text-span-content span) → string?
span : text-span?
procedure
(text-span-font-size span)
→ (or/c false/c (and/c finite-real? positive?)) span : text-span?
procedure
(text-span-font-face span) → (or/c false/c string?)
span : text-span?
procedure
(text-span-font-family span) → (or/c false/c text-font-family?)
span : text-span?
procedure
(text-span-font-style span) → (or/c false/c text-font-style?)
span : text-span?
procedure
(text-span-font-weight span) → (or/c false/c text-font-weight?)
span : text-span?
procedure
(text-span-color span) → any/c
span : text-span?
procedure
(plain-text content #:id id [ #:center center #:rotation rotation #:scale scale #:opacity opacity #:font-size font-size #:font-face font-face #:font-family font-family #:font-style font-style #:font-weight font-weight #:color color #:horizontal-alignment horizontal-alignment #:vertical-alignment vertical-alignment]) → text-visual? content : string? id : symbol? center : vec2? = origin rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 font-size : (and/c finite-real? positive?) = 1/2 font-face : (or/c string? #f) = #f font-family : text-font-family? = 'default font-style : text-font-style? = 'normal font-weight : text-font-weight? = 'normal color : any/c = "black" horizontal-alignment : text-horizontal-alignment? = 'center vertical-alignment : text-vertical-alignment? = 'center
content may be empty and may contain arbitrary Unicode characters, but it may not contain a carriage return or newline. The constructor copies the string into immutable storage. A mutable font-face string is copied in the same way. A false font-face asks the backend to select a face from font-family. When both are supplied, the face is preferred and the family remains the fallback classification.
font-size is measured in local world units before the Visual’s scale is applied. The default is one half world unit. Non-uniform scale may stretch the rendered text independently in x and y. Rotation is counter-clockwise in radians.
color is deliberately opaque model data. The built-in Pict renderer passes it to Pict color handling. A different renderer may interpret it differently. opacity is semantic global opacity and is applied to the complete rendered line after renderer dispatch.
The alignment arguments determine which point of the original text box is at center. Alignment is resolved before scale and rotation. This makes a left-baseline label, for example, grow to the right and rotate around the start of its baseline.
procedure
(paragraph content #:id id [ #:center center #:rotation rotation #:scale scale #:opacity opacity #:font-size font-size #:font-face font-face #:font-family font-family #:font-style font-style #:font-weight font-weight #:color color #:horizontal-alignment horizontal-alignment #:vertical-alignment vertical-alignment #:width width #:line-spacing line-spacing #:line-alignment line-alignment]) → text-visual? content : string? id : symbol? center : vec2? = origin rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 font-size : (and/c finite-real? positive?) = 1/2 font-face : (or/c string? #f) = #f font-family : text-font-family? = 'default font-style : text-font-style? = 'normal font-weight : text-font-weight? = 'normal color : any/c = "black" horizontal-alignment : text-horizontal-alignment? = 'center vertical-alignment : text-vertical-alignment? = 'center width : (or/c false/c (and/c finite-real? positive?)) = #f line-spacing : (and/c finite-real? positive?) = 1 line-alignment : text-horizontal-alignment? = 'left
line-spacing multiplies the largest natural line height in the paragraph. line-alignment aligns every resolved line inside the widest resolved line, while horizontal-alignment selects the anchor of that complete paragraph. The first line supplies a 'baseline anchor.
procedure
(rich-text #:id id [ #:center center #:rotation rotation #:scale scale #:opacity opacity #:font-size font-size #:font-face font-face #:font-family font-family #:font-style font-style #:font-weight font-weight #:color color #:horizontal-alignment horizontal-alignment #:vertical-alignment vertical-alignment #:width width #:line-spacing line-spacing #:line-alignment line-alignment] piece ...) → text-visual? id : symbol? center : vec2? = origin rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 font-size : (and/c finite-real? positive?) = 1/2 font-face : (or/c string? #f) = #f font-family : text-font-family? = 'default font-style : text-font-style? = 'normal font-weight : text-font-weight? = 'normal color : any/c = "black" horizontal-alignment : text-horizontal-alignment? = 'center vertical-alignment : text-vertical-alignment? = 'center width : (or/c false/c (and/c finite-real? positive?)) = #f line-spacing : (and/c finite-real? positive?) = 1 line-alignment : text-horizontal-alignment? = 'left piece : (or/c string? text-span?)
procedure
(text-visual? value) → boolean?
value : any/c
procedure
(text-visual-content visual) → string?
visual : text-visual?
procedure
(text-visual-spans visual) → (listof text-span?)
visual : text-visual?
procedure
(text-visual-font-size visual) → (and/c finite-real? positive?)
visual : text-visual?
procedure
(text-visual-font-face visual) → (or/c string? #f)
visual : text-visual?
procedure
(text-visual-font-family visual) → text-font-family?
visual : text-visual?
procedure
(text-visual-font-style visual) → text-font-style?
visual : text-visual?
procedure
(text-visual-font-weight visual) → text-font-weight?
visual : text-visual?
procedure
(text-visual-color visual) → any/c
visual : text-visual?
procedure
(text-visual-horizontal-alignment visual)
→ text-horizontal-alignment? visual : text-visual?
procedure
(text-visual-vertical-alignment visual)
→ text-vertical-alignment? visual : text-visual?
procedure
(text-visual-width visual)
→ (or/c false/c (and/c finite-real? positive?)) visual : text-visual?
procedure
(text-visual-line-spacing visual)
→ (and/c finite-real? positive?) visual : text-visual?
procedure
(text-visual-line-alignment visual)
→ text-horizontal-alignment? visual : text-visual?
procedure
(text-visual-with-content visual content) → text-visual?
visual : text-visual? content : string?
procedure
(text-visual-with-spans visual spans) → text-visual?
visual : text-visual? spans : (listof text-span?)
19.10 Numeric Displays
SCENE-EF provides number-shaped text and numerical transitions without introducing a mutable value tracker. Static constructors return ordinary text-visual? values. parameter-display and rolling-number-display format the current scalar scene parameter independently at each sampled frame. Both are fixed-structure relation-visual? values with an explicit scalar dependency.
procedure
(numeric-display-anchor? value) → boolean?
value : any/c
procedure
(format-integer value [ #:grouping? grouping? #:show-sign? show-sign? #:unit unit]) → string? value : exact-integer? grouping? : boolean? = #f show-sign? : boolean? = #f unit : string? = ""
procedure
(format-decimal value [ #:decimal-places decimal-places #:grouping? grouping? #:show-sign? show-sign? #:unit unit]) → string? value : finite-real? decimal-places : exact-nonnegative-integer? = 2 grouping? : boolean? = #f show-sign? : boolean? = #f unit : string? = ""
procedure
(unit symbol [#:power power]) → numeric-unit?
symbol : string? power : exact-integer? = 1
procedure
(numeric-unit? value) → boolean?
value : any/c
procedure
(unit-product first rest ...) → numeric-unit?
first : numeric-unit? rest : numeric-unit?
procedure
(format-unit value) → string?
value : (or/c string? numeric-unit?)
procedure
(format-scientific value [ #:significant-figures figures #:show-sign? show-sign? #:unit unit]) → string? value : finite-real? figures : exact-positive-integer? = 3 show-sign? : boolean? = #f unit : (or/c string? numeric-unit?) = ""
procedure
(format-significant value [ #:significant-figures figures #:notation notation #:grouping? grouping? #:show-sign? show-sign? #:unit unit]) → string? value : finite-real? figures : exact-positive-integer? = 3 notation : (or/c 'auto 'fixed 'scientific) = 'auto grouping? : boolean? = #f show-sign? : boolean? = #f unit : (or/c string? numeric-unit?) = ""
procedure
(format-rational value [ #:max-denominator maximum #:mixed? mixed? #:show-sign? show-sign? #:unit unit]) → string? value : finite-real? maximum : exact-positive-integer? = 1000 mixed? : boolean? = #f show-sign? : boolean? = #f unit : (or/c string? numeric-unit?) = ""
procedure
(format-complex value [ #:decimal-places decimal-places #:grouping? grouping? #:show-sign? show-sign? #:imaginary-unit imaginary-unit #:unit unit]) → string? value : (or/c finite-real? finite-complex?) decimal-places : exact-nonnegative-integer? = 2 grouping? : boolean? = #f show-sign? : boolean? = #f imaginary-unit : string? = "i" unit : (or/c string? numeric-unit?) = ""
procedure
(integer value #:id id [ #:center center #:font-size font-size #:font-family font-family #:font-style font-style #:font-weight font-weight #:color color #:horizontal-alignment horizontal-alignment #:vertical-alignment vertical-alignment #:grouping? grouping? #:show-sign? show-sign? #:unit unit]) → text-visual? value : exact-integer? id : symbol? center : vec2? = origin font-size : (and/c finite-real? positive?) = 1/2 font-family : text-font-family? = 'default font-style : text-font-style? = 'normal font-weight : text-font-weight? = 'normal color : any/c = "black" horizontal-alignment : text-horizontal-alignment? = 'center vertical-alignment : text-vertical-alignment? = 'center grouping? : boolean? = #f show-sign? : boolean? = #f unit : string? = ""
procedure
(decimal-number value #:id id [ #:center center #:font-size font-size #:font-family font-family #:font-style font-style #:font-weight font-weight #:color color #:horizontal-alignment horizontal-alignment #:vertical-alignment vertical-alignment #:decimal-places decimal-places #:grouping? grouping? #:show-sign? show-sign? #:unit unit]) → text-visual? value : finite-real? id : symbol? center : vec2? = origin font-size : (and/c finite-real? positive?) = 1/2 font-family : text-font-family? = 'default font-style : text-font-style? = 'normal font-weight : text-font-weight? = 'normal color : any/c = "black" horizontal-alignment : text-horizontal-alignment? = 'center vertical-alignment : text-vertical-alignment? = 'center decimal-places : exact-nonnegative-integer? = 2 grouping? : boolean? = #f show-sign? : boolean? = #f unit : string? = ""
procedure
(scientific-number value #:id id [ #:center center #:significant-figures figures #:unit unit #:font-size font-size #:font-family font-family #:color color]) → text-visual? value : finite-real? id : symbol? center : vec2? = origin figures : exact-positive-integer? = 3 unit : (or/c string? numeric-unit?) = "" font-size : (and/c finite-real? positive?) = 1/2 font-family : text-font-family? = 'default color : any/c = "black"
procedure
(significant-number value #:id id [ #:center center #:significant-figures figures #:notation notation #:unit unit #:font-size font-size #:font-family font-family #:color color]) → text-visual? value : finite-real? id : symbol? center : vec2? = origin figures : exact-positive-integer? = 3 notation : (or/c 'auto 'fixed 'scientific) = 'auto unit : (or/c string? numeric-unit?) = "" font-size : (and/c finite-real? positive?) = 1/2 font-family : text-font-family? = 'default color : any/c = "black"
procedure
(rational-number value #:id id [ #:center center #:max-denominator maximum #:mixed? mixed? #:unit unit #:font-size font-size #:font-family font-family #:color color]) → text-visual? value : finite-real? id : symbol? center : vec2? = origin maximum : exact-positive-integer? = 1000 mixed? : boolean? = #f unit : (or/c string? numeric-unit?) = "" font-size : (and/c finite-real? positive?) = 1/2 font-family : text-font-family? = 'default color : any/c = "black"
procedure
(complex-number value #:id id [ #:center center #:decimal-places decimal-places #:imaginary-unit imaginary-unit #:unit unit #:font-size font-size #:font-family font-family #:color color]) → text-visual? value : (or/c finite-real? finite-complex?) id : symbol? center : vec2? = origin decimal-places : exact-nonnegative-integer? = 2 imaginary-unit : string? = "i" unit : (or/c string? numeric-unit?) = "" font-size : (and/c finite-real? positive?) = 1/2 font-family : text-font-family? = 'default color : any/c = "black"
procedure
(numeric-label value #:id id [ #:center center #:kind kind #:decimal-places decimal-places #:significant-figures figures #:notation notation #:max-denominator maximum #:mixed? mixed? #:grouping? grouping? #:show-sign? show-sign? #:imaginary-unit imaginary-unit #:unit unit #:font-size font-size #:font-family font-family #:font-style font-style #:font-weight font-weight #:color color #:horizontal-alignment horizontal-alignment #:vertical-alignment vertical-alignment]) → text-visual? value : (or/c finite-real? finite-complex?) id : symbol? center : vec2? = origin
kind :
(or/c 'auto 'integer 'decimal 'scientific 'significant 'rational 'complex) = 'auto decimal-places : exact-nonnegative-integer? = 2 figures : exact-positive-integer? = 3 notation : (or/c 'auto 'fixed 'scientific) = 'auto maximum : exact-positive-integer? = 1000 mixed? : boolean? = #f grouping? : boolean? = #f show-sign? : boolean? = #f imaginary-unit : string? = "i" unit : (or/c string? numeric-unit?) = "" font-size : (and/c finite-real? positive?) = 1/2 font-family : text-font-family? = 'default font-style : text-font-style? = 'normal font-weight : text-font-weight? = 'normal color : any/c = "black" horizontal-alignment : text-horizontal-alignment? = 'center vertical-alignment : text-vertical-alignment? = 'center
procedure
(parameter-display source #:id id [ #:center center #:kind kind #:decimal-places decimal-places #:significant-figures figures #:notation notation #:max-denominator maximum #:mixed? mixed? #:grouping? grouping? #:show-sign? show-sign? #:imaginary-unit imaginary-unit #:unit unit #:anchor anchor #:font-size font-size #:font-family font-family #:font-style font-style #:font-weight font-weight #:color color #:vertical-alignment vertical-alignment]) → relation-visual? source : (or/c symbol? scene-parameter?) id : symbol? center : vec2? = origin
kind :
(or/c 'integer 'decimal 'scientific 'significant 'rational 'complex) = 'decimal decimal-places : exact-nonnegative-integer? = 2 figures : exact-positive-integer? = 3 notation : (or/c 'auto 'fixed 'scientific) = 'auto maximum : exact-positive-integer? = 1000 mixed? : boolean? = #f grouping? : boolean? = #f show-sign? : boolean? = #f imaginary-unit : string? = "i" unit : (or/c string? numeric-unit?) = "" anchor : numeric-display-anchor? = 'right font-size : (and/c finite-real? positive?) = 1/2 font-family : text-font-family? = 'default font-style : text-font-style? = 'normal font-weight : text-font-weight? = 'normal color : any/c = "black" vertical-alignment : text-vertical-alignment? = 'center
The #:anchor choice fixes one stable reference as the text width changes. 'left, 'center, and 'right are the normal text anchors. 'sign forces a visible sign and anchors its left edge. 'decimal creates a small resolved group containing local 'whole and 'fraction children on opposite sides of the fixed decimal point. They are separate text runs, so this first release does not attempt kerning across that join.
procedure
(rolling-number-display source #:id id [ #:center center #:integer-digits integer-digits #:decimal-places decimal-places #:show-sign? show-sign? #:unit unit #:anchor anchor #:font-size font-size #:font-family font-family #:font-style font-style #:font-weight font-weight #:color color #:vertical-alignment vertical-alignment]) → derived-visual? source : (or/c symbol? scene-parameter?) id : symbol? center : vec2? = origin integer-digits : exact-positive-integer? = 3 decimal-places : exact-nonnegative-integer? = 0 show-sign? : boolean? = #f unit : (or/c string? numeric-unit?) = "" anchor : numeric-display-anchor? = 'right font-size : (and/c finite-real? positive?) = 1/2 font-family : text-font-family? = 'modern font-style : text-font-style? = 'normal font-weight : text-font-weight? = 'normal color : any/c = "black" vertical-alignment : text-vertical-alignment? = 'center
19.11 Matrices and Tables
matrix and table return ordinary immutable group-visual? values. Their rows and cells are regular nested groups, not a separate rendering or animation object. Existing path-addressed operations therefore work directly: (indicate (matrix-entry-path 'A 1 2)), move-to, follow-anchor, and transform-from-copy need no matrix/table variants.
Both constructors take a nonempty rectangular list of nonempty rows. Each entry must be an affine Visual. The constructor re-bases every entry at its cell centre, preserving identity, rotation, scale, opacity, style, and children but intentionally replacing its supplied reference position. Width and height can be one shared measure, one explicit per-axis list, or an 'auto construction-time measurement. The result is still an ordinary immutable group with no renderer dependency after construction.
procedure
(matrix rows #:id id [ #:center center #:rotation rotation #:scale scale #:opacity opacity #:entry-width entry-width #:entry-height entry-height #:entry-padding entry-padding #:column-gap column-gap #:row-gap row-gap #:brackets? brackets? #:bracket-width bracket-width #:bracket-gap bracket-gap #:stroke stroke #:stroke-width stroke-width]) → group-visual? rows : (listof (listof (and/c visual? affine-visual?))) id : symbol? center : vec2? = origin rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1
entry-width :
(or/c 'auto (and/c finite-real? positive?) (listof (and/c finite-real? positive?))) = 1
entry-height :
(or/c 'auto (and/c finite-real? positive?) (listof (and/c finite-real? positive?))) = 1 entry-padding : (and/c finite-real? (>=/c 0)) = 1/5 column-gap : (and/c finite-real? (>=/c 0)) = 1/4 row-gap : (and/c finite-real? (>=/c 0)) = 1/4 brackets? : boolean? = #t bracket-width : (and/c finite-real? positive?) = 1/5 bracket-gap : (and/c finite-real? (>=/c 0)) = 1/10 stroke : any/c = "black" stroke-width : (and/c finite-real? (>=/c 0)) = 2
When brackets? is true, the matrix has ordinary open path children named 'left-bracket and 'right-bracket. They use square brackets, stroke, and stroke-width.
For either entry dimension, a positive scalar supplies one shared cell extent; a list supplies one extent per column or row; and 'auto measures each entry with the active default Pict renderer and selects the largest visible-box extent in that column or row. entry-padding is added on both sides of each auto-sized extent. This is a snapshot: later text/formula changes do not reflow a constructed matrix.
procedure
(matrix-row-id row) → symbol?
row : exact-positive-integer?
procedure
(matrix-column-id column) → symbol?
column : exact-positive-integer?
procedure
(matrix-row-path matrix-id row) → visual-path?
matrix-id : symbol? row : exact-positive-integer?
procedure
(matrix-entry-path matrix-id row column) → visual-path?
matrix-id : symbol? row : exact-positive-integer? column : exact-positive-integer?
procedure
(matrix-bracket-path matrix-id side) → visual-path?
matrix-id : symbol? side : (or/c 'left 'right)
procedure
(table rows #:id id [ #:center center #:rotation rotation #:scale scale #:opacity opacity #:cell-width cell-width #:cell-height cell-height #:cell-padding cell-padding #:column-gap column-gap #:row-gap row-gap #:stroke stroke #:stroke-width stroke-width]) → group-visual? rows : (listof (listof (and/c visual? affine-visual?))) id : symbol? center : vec2? = origin rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1
cell-width :
(or/c 'auto (and/c finite-real? positive?) (listof (and/c finite-real? positive?))) = 1
cell-height :
(or/c 'auto (and/c finite-real? positive?) (listof (and/c finite-real? positive?))) = 3/4 cell-padding : (and/c finite-real? (>=/c 0)) = 1/5 column-gap : (and/c finite-real? (>=/c 0)) = 0 row-gap : (and/c finite-real? (>=/c 0)) = 0 stroke : any/c = "black" stroke-width : (and/c finite-real? (>=/c 0)) = 2
The cell-size arguments follow the same scalar/list/'auto policy as matrix. Auto measurement adds cell-padding on all sides and does not remeasure after construction.
procedure
(table-row-id row) → symbol?
row : exact-positive-integer?
procedure
(table-column-id column) → symbol?
column : exact-positive-integer?
procedure
(table-row-path table-id row) → visual-path?
table-id : symbol? row : exact-positive-integer?
procedure
(table-cell-path table-id row column) → visual-path?
table-id : symbol? row : exact-positive-integer? column : exact-positive-integer?
19.12 Deterministic Traced Paths
procedure
(traced-path phase position #:id id [ #:start-time start-time #:sample-count sample-count #:trail-length trail-length #:dissipate? dissipate? #:minimum-opacity minimum-opacity #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → derived-visual? phase : (or/c symbol? scene-parameter?) position : (-> derived-context? finite-real? vec2?) id : symbol? start-time : finite-real? = 0 sample-count : (and/c exact-integer? (>=/c 2)) = 121
trail-length : (or/c false/c (and/c finite-real? (>=/c 0))) = #f dissipate? : boolean? = #f minimum-opacity : opacity? = 0 opacity : opacity? = 1 stroke : any/c = "crimson" stroke-width : (and/c finite-real? (>=/c 0)) = 3
With trail-length, the interval instead begins at the larger of start-time and current phase minus that length. With dissipate?, the resolved trace is an ordinary group of consecutive path segments whose opacity rises from minimum-opacity to opacity; otherwise it is one ordinary path Visual. No automatic tracking of arbitrary Visual motion or adaptive/discontinuity sampling is attempted in this stage.
19.13 LaTeX Formula Visuals
A formula Visual stores an immutable LaTeX mathematical snippet and explicit typesetting data. It implements gen:visual, gen:affine-visual, and gen:opacity-visual. Its raw structure constructor and internal transform and opacity fields are not public.
Formula model values are backend-independent. They do not contain Picts, PDF pages, Poppler values, process handles, or cached TeX results. The built-in adapter calls latex-pict only when a nonempty formula is rendered.
procedure
(formula-mode? value) → boolean?
value : any/c
'inline 'display 'display-environment
The 'inline mode uses ordinary inline mathematics. The 'display mode uses display-style mathematics in a tight inline box. The 'display-environment mode uses a real LaTeX display environment, which can include wider horizontal margins.
procedure
(latex-option? value) → boolean?
value : any/c
procedure
(latex-formula source #:id id [ #:center center #:rotation rotation #:scale scale #:opacity opacity #:mode mode #:font-size font-size #:preamble preamble #:document-class-options document-class-options #:preview-options preview-options #:horizontal-alignment horizontal-alignment #:vertical-alignment vertical-alignment]) → formula-visual? source : string? id : symbol? center : vec2? = origin rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 mode : formula-mode? = 'display font-size : (and/c finite-real? positive?) = 1 preamble : string? = "" document-class-options : (listof latex-option?) = '() preview-options : (listof latex-option?) = '() horizontal-alignment : text-horizontal-alignment? = 'center vertical-alignment : text-vertical-alignment? = 'center
source is a LaTeX mathematical snippet without surrounding dollar signs, \\( ... \\), or \\[ ... \\] delimiters. The selected mode supplies those delimiters. Formula source may contain carriage returns and newlines. It may also be empty. The constructor copies it into an immutable string.
The mode-to-typesetter mapping is:
Mode | latex-pict operation |
'inline | tex-math |
'display | tex-display-math |
'display-environment | tex-real-display-math |
The font-size value is measured in local world units before semantic scale is applied. The adapter asks latex-pict to typeset at its natural scale and then maps the selected document base of 10pt, 11pt, or 12pt to the requested world-unit size. When no standard size option is present, 10pt is assumed. Supplying more than one distinct standard size option is an error. Other document-class options and preamble commands may still change the formula’s visible metrics.
The complete visible height and width depend on the formula content. The constructor does not automatically separate two independent formula Visuals, so their rendered boxes can overlap when their anchors are placed too close together. Use visual-layout-box, visual-place-above, visual-place-below, or arrange-visuals-vertically when spacing must follow the actual rendered boxes.
preamble is inserted into the generated LaTeX document. The document-class-options and preview-options lists are passed in stored order. Their strings and preamble are copied into immutable storage. Option order is significant because a LaTeX document class or package may interpret options in order. The adapter passes these values and an extra typesetter scale of one explicitly, so process-wide latex-pict parameters do not silently change a formula Visual.
The alignment arguments select the left, center, or right horizontal point and the top, center, baseline, or bottom vertical point of the untransformed typeset Pict. That point is placed at center. Scale and rotation are then applied around the anchor.
Named parts in a formula-assembly can be styled with formula-style, formula-color, or formula-color-map. This is an assembly-level semantic operation: a bare latex-formula has no part namespace. The Pict and tagged-SVG adapters apply the selected colour at their own rendering boundaries rather than relying on a generic outer recolouring operation.
Rendering a nonempty formula requires the latex-pict package, a working pdflatex, Poppler, the requested document class, and every package named by the preamble. Model construction, scene sampling, and empty formula rendering do not run TeX. Exact output depends on those external tools and their installed versions. Formula source and preamble are trusted input; this library does not sandbox the TeX process.
19.13.1 Making latex-pict Available
Use the same Racket installation for this library and for latex-pict. For example, to install the catalog package with Racket 9.3.0.2 on macOS:
"/Applications/Racket v9.3.0.2/bin/raco" pkg install \ |
--auto \ |
latex-pict |
For a local checkout, link the checkout with that same raco executable:
"/Applications/Racket v9.3.0.2/bin/raco" pkg install \ |
--auto \ |
--link \ |
"/Users/soegaard/Dropbox/GitHub/latex-pict" |
A one-command alternative is to add the checkout root to PLTCOLLECTS:
PLTCOLLECTS="/Users/soegaard/Dropbox/GitHub/latex-pict:" \ |
"/Applications/Racket v9.3.0.2/bin/racket" -c \ |
examples/formula-visuals.rkt \ |
frames/formula-visuals \ |
formula-visuals.mp4 |
The trailing colon is significant. It keeps Racket’s ordinary collection paths after the added checkout. A package linked with one Racket installation is not automatically visible to another installation, so use matching racket and raco executables.
procedure
(formula-visual? value) → boolean?
value : any/c
procedure
(formula-visual-source visual) → string?
visual : formula-visual?
procedure
(formula-visual-mode visual) → formula-mode?
visual : formula-visual?
procedure
(formula-visual-font-size visual)
→ (and/c finite-real? positive?) visual : formula-visual?
procedure
(formula-visual-preamble visual) → string?
visual : formula-visual?
procedure
→ (listof latex-option?) visual : formula-visual?
procedure
(formula-visual-preview-options visual)
→ (listof latex-option?) visual : formula-visual?
procedure
(formula-visual-horizontal-alignment visual)
→ text-horizontal-alignment? visual : formula-visual?
procedure
(formula-visual-vertical-alignment visual)
→ text-vertical-alignment? visual : formula-visual?
procedure
(formula-visual-with-source visual source) → formula-visual?
visual : formula-visual? source : string?
19.14 Tagged Formula Layouts
struct
(struct formula-fragment (name source) #:transparent) name : symbol? source : string?
Fragments are deliberately explicit. Animate does not parse arbitrary TeX into tokens, so a fragment must be a valid piece of the complete math expression and must produce visible ink.
procedure
(tagged-formula #:id id [ #:center center #:rotation rotation #:scale scale #:opacity opacity #:mode mode #:font-size font-size #:preamble preamble #:document-class-options document-class-options #:color-map color-map] fragment ...) → formula-assembly-visual? id : symbol? center : vec2? = origin rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 mode : formula-mode? = 'display font-size : (and/c finite-real? positive?) = 1 preamble : string? = "" document-class-options : (listof latex-option?) = '() color-map : (hash/c symbol? color-spec?) = (hash) fragment : formula-fragment?
Construction runs the external latex and dvisvgm executables once. It wraps every fragment in a dvisvgm SVG group, measures the group, and returns an ordinary formula assembly whose parts render as the resulting SVG fragments. Those SVG fragments are renderer-cached, so sampling or rendering animation frames does not run TeX again. Both executables must be available on PATH when this constructor is called.
All keyword options have the same validation and semantic meaning as for latex-formula, except that Preview-package options and per-fragment anchors are not applicable to a formula whose layout is computed as one unit. The returned assembly can be moved, rotated, scaled, faded, addressed through nested part paths, and used with the normal formula-correspondence operations.
color-map maps declared fragment names to semantic colours. It is applied after the complete TeX layout and SVG crops have been created, so it does not alter kerning, scripts, or measurements. Every key must name a declared fragment.
procedure
(math-tex #:id id [ #:center center #:rotation rotation #:scale scale #:opacity opacity #:mode mode #:font-size font-size #:preamble preamble #:document-class-options document-class-options #:color-map color-map #:source-map source-map #:parts parts] source ...) → formula-assembly-visual? id : symbol? center : vec2? = origin rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 mode : formula-mode? = 'display font-size : (and/c finite-real? positive?) = 1 preamble : string? = "" document-class-options : (listof latex-option?) = '() color-map : (hash/c symbol? color-spec?) = (hash) source-map : (or/c 'none 'declared 'tokens) = 'tokens parts : (listof source-part?) = '() source : string?
math-tex records a canonical source string and a conservative token-to-rendered-part source map by default. Use formula-find or formula-source-select to query rendered source material by a literal string, regexp, source span, or occurrence. 'none is the explicit opt-out when no source queries are required. 'declared requires #:parts, a list of named source-part declarations; it maps only those author-declared ranges. The token scanner establishes safe TeX boundaries, not algebraic meaning or a complete TeX parse: user macros, category-code changes, and source that has no visible output may not be selectable.
procedure
(glyph-tex #:id id [ #:center center #:rotation rotation #:scale scale #:opacity opacity #:mode mode #:font-size font-size #:preamble preamble #:document-class-options document-class-options #:color-map color-map] source ...) → formula-assembly-visual? id : symbol? center : vec2? = origin rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 mode : formula-mode? = 'display font-size : (and/c finite-real? positive?) = 1 preamble : string? = "" document-class-options : (listof latex-option?) = '() color-map : (hash/c symbol? color-spec?) = (hash) source : string?
The generated glyph parts retain the complete author TeX source, but their matching identity is the referenced dvisvgm path outline together with their typesetting options. Consequently, exact unchanged glyphs can match between two separately compiled expressions despite dvisvgm assigning different local font-definition ids on each compilation. Use tagged-formula or math-tex when several glyphs need one semantic identity: glyph leaves are not TeX tokens, a superscript or accent can contain several leaves, and repeated outlines match greedily in source order.
color-map maps generated names such as 'glyph-0 to semantic colours. Generated names are positional, so explicit tagged fragments are usually preferable for durable pedagogical styling.
19.15 Source-Addressable Formulas
Source selectors address character ranges in the canonical TeX source retained by math-tex. They complement named formula fragments; they do not recognize algebraic roles or prove mathematical equivalence. Source indices are Racket string-character indices in half-open ranges, so (source-span 2 5) selects characters 2 through 4.
struct
(struct source-span (start end))
start : exact-nonnegative-integer? end : exact-nonnegative-integer?
struct
(struct source-occurrence (selector index))
selector : source-selector? index : exact-nonnegative-integer?
struct
(struct source-part (name selector))
name : symbol? selector : source-selector?
procedure
(source-selector? value) → boolean?
value : any/c
procedure
(formula-source-match? value) → boolean?
value : any/c
procedure
(visual-selection? value) → boolean?
value : any/c
procedure
(formula-source formula) → string?
formula : formula-assembly-visual?
procedure
(formula-find formula selector) → (listof formula-source-match?)
formula : formula-assembly-visual? selector : source-selector?
procedure
(formula-source-select formula selector) → visual-selection?
formula : formula-assembly-visual? selector : source-selector?
procedure
(formula-source-select-one formula selector) → visual-selection? formula : formula-assembly-visual? selector : source-selector?
procedure
(plan-matching-strings source destination [ #:matches matches #:copies copies]) → string-match-plan? source : formula-assembly-visual? destination : formula-assembly-visual? matches : (listof string-match?) = '() copies : (listof string-copy?) = '()
procedure
(string-match source destination [ #:route route #:mode mode #:appearance-complete-at-x appearance-complete-at-x #:appearance-duration appearance-duration]) → string-match? source : source-selector? destination : source-selector? route : (or/c #f formula-route?) = #f mode : (or/c 'auto 'rigid 'glyphwise 'cross-fade) = 'auto appearance-complete-at-x : (or/c #f source-selector?) = #f
appearance-duration : (or/c #f (and/c finite-real? positive? (<=/c 1))) = #f
When both appearance keywords are supplied, the part keeps its ordinary route, but its changed appearance completes when that route first reaches the current x-coordinate of appearance-complete-at-x. The duration is a fraction of the enclosing transition: the old and new appearances cross-fade only in the interval immediately preceding that deadline. This is useful for a moving + that should become - exactly as it passes an equality sign.
procedure
(string-copy source destination [ #:route route #:mode mode]) → string-copy? source : source-selector? destination : source-selector? route : (or/c #f formula-route?) = #f mode : (or/c 'auto 'rigid 'glyphwise 'cross-fade) = 'auto
procedure
(string-match? value) → boolean?
value : any/c
procedure
(string-copy? value) → boolean?
value : any/c
procedure
(string-match-plan? value) → boolean?
value : any/c
procedure
(string-match-plan->datum plan) → immutable-hash?
plan : string-match-plan?
procedure
(transform-matching-strings source destination [ #:matches matches #:key-map key-map #:protect-source protect-source #:protect-destination protect-destination #:copies copies #:on-ambiguity on-ambiguity #:path-arc path-arc #:mismatch-mode mismatch-mode]) → transform-formula-parts-request? source : formula-assembly-visual? destination : formula-assembly-visual? matches : (listof string-match?) = '() key-map : (listof string-match?) = '() protect-source : (listof source-selector?) = '() protect-destination : (listof source-selector?) = '() copies : (listof string-copy?) = '() on-ambiguity : (or/c 'left-to-right 'error) = 'left-to-right path-arc : finite-real? = 0 mismatch-mode : (or/c 'fade 'fade-transform) = 'fade
procedure
(formula-part-path source-name destination-name route) → formula-part-path? source-name : symbol? destination-name : symbol? route : formula-route?
procedure
(formula-part-copy source-name destination-name route) → formula-part-copy? source-name : symbol? destination-name : symbol? route : formula-route?
procedure
(formula-part-path? value) → boolean?
value : any/c
procedure
(formula-part-copy? value) → boolean?
value : any/c
procedure
(formula-route? value) → boolean?
value : any/c
procedure
(formula-arc #:angle angle) → formula-route?
angle : finite-real?
procedure
(formula-relative-path geometry) → formula-route?
geometry : path-geometry?
procedure
(tagged-formula-fragment-visual? value) → boolean?
value : any/c
procedure
(tagged-formula-fragment-visual-svg-source visual) → string?
visual : tagged-formula-fragment-visual?
procedure
(transform-matching-parts source destination [ #:matches matches]) → transform-formula-parts-request? source : formula-assembly-visual? destination : formula-assembly-visual? matches : (listof formula-part-match?) = '()
Exact matches render as one rigid SVG group that moves with its local transform. Changed explicit matches are moving cross-fades, and unmatched fragments use the normal fade-out/fade-in behavior. This operation does not infer algebraic equivalence, parse TeX tokens, choose paths/arcs for the movement, or morph glyph outlines.
procedure
(transform-matching-glyphs source destination [ #:matches matches #:path-arc path-arc #:part-paths part-paths #:copies copies #:mismatch-mode mismatch-mode #:changed-mode changed-mode]) → transform-formula-parts-request? source : formula-assembly-visual? destination : formula-assembly-visual? matches : (listof formula-part-match?) = '() path-arc : finite-real? = 0 part-paths : (listof formula-part-path?) = '() copies : (listof formula-part-copy?) = '() mismatch-mode : (or/c 'fade 'fade-transform) = 'fade changed-mode : (or/c 'fade 'morph) = 'fade
This matches and moves whole rendered glyph leaves. 'fade is the default: changed matches use the ordinary moving cross-fade. With #:changed-mode 'morph, a changed matched pair instead interpolates its outline only when both cropped dvisvgm SVG fragments expand to one identically painted path whose positive-length contours are all closed and compatible in count. Animate globally pairs those destination contours with the source, phase-aligns them without reversing their traversal, normalizes the resulting paths to compatible cubic segments, and uses that path geometry only for interior frames; the ordinary tagged SVG fragments remain exact endpoints. Glyphs with multiple independently painted paths, open contours, incompatible contour topology, changed paint, or unsupported geometry safely fall back to the moving cross-fade.
This operation does not derive a mathematical operation, identify TeX characters or terms, perform semantic grouping, or infer which changed glyphs should be paired.
procedure
(rewrite-formula source destination #:anchor anchor [ #:matches matches #:stationary stationary #:path-arc path-arc #:part-paths part-paths #:copies copies #:mismatch-mode mismatch-mode]) → transform-formula-parts-request? source : formula-assembly-visual? destination : formula-assembly-visual? anchor : (or/c symbol? formula-part-match?) matches : (listof formula-part-match?) = '() stationary : (listof (or/c symbol? formula-part-match?)) = '() path-arc : finite-real? = 0 part-paths : (listof formula-part-path?) = '() copies : (listof formula-part-copy?) = '() mismatch-mode : (or/c 'fade 'fade-transform) = 'fade
When scene-play compiles the request, Animate translates the complete destination layout so the destination anchor coincides with the corresponding part in the current source formula. Consequently, a sequence of rewrites keeps the anchor fixed even when the formula values passed as earlier templates were constructed at their own default positions. The translation preserves the target formula’s TeX spacing and baselines.
Each stationary entry names an additional matched pair: a symbol means the same source and destination part name, while a formula-part-match permits different names. The pair is made explicit, and at clip compilation the destination fragment receives the current source fragment’s exact affine transform. Thus several selected terms can remain fixed even if the rest of the destination layout moves or reflows. This is an explicit presentation choice; it does not infer which terms should remain still or maintain a general layout constraint between them.
The remaining keywords have the same meaning as in transform-matching-parts: explicit matches take priority, routes and copies select intentional term motion, and 'fade-transform cross-fades remaining unmatched parts while moving them. Like the lower-level operation, this is whole-fragment correspondence rather than TeX parsing or glyph-outline morphing.
procedure
(formula-step destination [ #:anchor anchor #:stationary stationary #:matches matches #:path-arc path-arc #:part-paths part-paths #:copies copies #:mismatch-mode mismatch-mode #:duration duration #:pause pause #:explanation explanation]) → formula-derivation-step? destination : formula-assembly-visual? anchor : (or/c false/c symbol? formula-part-match?) = #f stationary : (listof (or/c symbol? formula-part-match?)) = '() matches : (listof formula-part-match?) = '() path-arc : finite-real? = 0 part-paths : (listof formula-part-path?) = '() copies : (listof formula-part-copy?) = '() mismatch-mode : (or/c 'fade 'fade-transform) = 'fade duration : (and/c finite-real? positive?) = 1 pause : (and/c finite-real? (>=/c 0)) = 1/2 explanation : (or/c false/c string?) = #f
anchor defaults to #f, which means that the derivation’s shared anchor is used. A step can override it with a same-name symbol or an explicit formula-part-match. stationary has the same meaning as in rewrite-formula and makes additional matched parts fixed for this one step. This data does not claim that the rewrite is algebraically valid; it records the author’s chosen presentation.
procedure
(formula-derivation-step? value) → boolean?
value : any/c
procedure
(formula-derivation scene initial #:anchor anchor #:steps steps [ #:explanation-position explanation-position #:explanation-id explanation-id #:explanation-font-size explanation-font-size #:explanation-color explanation-color]) → scene? scene : scene? initial : formula-assembly-visual? anchor : (or/c symbol? formula-part-match?) steps : (listof formula-derivation-step?) explanation-position : (or/c false/c vec2?) = #f explanation-id : symbol? = 'derivation-note explanation-font-size : (and/c finite-real? positive?) = 1/4 explanation-color : any/c = "darkslategray"
When any step has an explanation, supply explanation-position. The builder creates plain text with explanation-id, which must be absent from the initial scene. Later explanations replace only that generated Visual. The final explanation remains visible unless a later step omits it.
This is immutable convenience syntax over existing scene and formula APIs. It does not parse TeX, infer operations, prove a derivation, choose matches/routes, or automatically lay out the explanation.
19.16 Named Formula Parts and Correspondence
A formula assembly is a composite Visual made from independently typeset LaTeX formula parts. Each part has a symbol name. That name is local to one assembly and is also the identity of the part’s formula Visual.
Part order is significant back-to-front drawing order. Part positions are local to the assembly anchor. The library does not ask TeX to lay out several parts as one document. The caller chooses each local position explicitly. Parts can overlap when their local anchors are placed too close together.
struct
(struct formula-part (name formula) #:transparent) name : symbol? formula : formula-visual?
The name field is local to one formula assembly. The formula field contains the complete semantic formula Visual used to render the fragment. The structure guard requires (eq? name (visual-id formula)). This rule gives each local name one stable formula identity.
The formula transform and opacity are local to its containing assembly. The structure is immutable and transparent.
procedure
(latex-formula-part source #:name name [ #:center center #:rotation rotation #:scale scale #:opacity opacity #:mode mode #:font-size font-size #:preamble preamble #:document-class-options document-class-options #:preview-options preview-options #:horizontal-alignment horizontal-alignment #:vertical-alignment vertical-alignment]) → formula-part? source : string? name : symbol? center : vec2? = origin rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 mode : formula-mode? = 'display font-size : (and/c finite-real? positive?) = 1 preamble : string? = "" document-class-options : (listof latex-option?) = '() preview-options : (listof latex-option?) = '() horizontal-alignment : text-horizontal-alignment? = 'center vertical-alignment : text-vertical-alignment? = 'center
The center value is local to the formula assembly that will contain the part. Formula source, preamble, and string options are copied into immutable model storage.
Example:
(latex-formula-part "n(n+1)" #:name 'numerator #:center (vec2 0 1/2) #:mode 'inline #:font-size 1/3)
procedure
(formula-assembly parts #:id id [ #:center center #:rotation rotation #:scale scale #:opacity opacity]) → formula-assembly-visual? parts : (listof formula-part?) id : symbol? center : vec2? = origin rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1
Part names must be unique within the assembly. Because every part name is also its formula Visual identity, id must differ from every part name. Part names are local: two different assemblies may use the same names.
The assembly center is its reference position in the containing coordinate system. The assembly rotation is measured counter-clockwise in radians. Its own scale must be uniform after normalization. This is the same restriction used by group; a non-uniform parent scale followed by a rotated part can require shear, which the current transform model cannot represent. Individual formula parts may still use non-uniform local scales.
The assembly implements gen:visual, gen:affine-visual, and gen:opacity-visual. Existing movement, rotation, uniform scale, opacity, fade-in, and fade-out operations therefore work on the complete assembly.
Every nonempty part is typeset separately. The caller is responsible for local part spacing. The Pict adapter passes the same explicit renderer list to every part. A custom renderer placed before the defaults may instead support and replace the complete assembly. An empty assembly renders as stable transparent one-pixel local geometry without running TeX.
procedure
(formula-assembly-visual? value) → boolean?
value : any/c
procedure
(formula-assembly-visual-parts assembly)
→ (listof formula-part?) assembly : formula-assembly-visual?
procedure
(formula-assembly-visual-with-parts assembly parts) → formula-assembly-visual? assembly : formula-assembly-visual? parts : (listof formula-part?)
procedure
(formula-assembly-visual-part-names assembly)
→ (listof symbol?) assembly : formula-assembly-visual?
procedure
(formula-assembly-visual-has-part? assembly name) → boolean? assembly : formula-assembly-visual? name : symbol?
procedure
(formula-assembly-visual-ref assembly name) → formula-part?
assembly : formula-assembly-visual? name : symbol?
procedure
(formula-select formula name) → visual-path?
formula : formula-assembly-visual? name : symbol?
procedure
(formula-style formula selection [ #:color color #:opacity opacity]) → formula-assembly-visual? formula : formula-assembly-visual? selection : (or/c symbol? (and/c pair? (listof symbol?))) color : (or/c false/c color-spec?) = #f opacity : (or/c false/c opacity?) = #f
The new assembly preserves its identity, part order, formula source, TeX/SVG artifact, and ordinary transforms. Its selected formula leaves implement the existing fill-colour and opacity protocols. Equal styles therefore retain normal rigid matching motion; a paint change between formula-rewrite endpoints uses the established cross-fade fallback.
procedure
(formula-color formula selection color)
→ formula-assembly-visual? formula : formula-assembly-visual? selection : (or/c symbol? (and/c pair? (listof symbol?))) color : color-spec?
procedure
(formula-color-map formula color-map) → formula-assembly-visual?
formula : formula-assembly-visual? color-map : (hash/c symbol? color-spec?)
struct
(struct formula-part-match (source-name destination-name) #:transparent) source-name : symbol? destination-name : symbol?
source-name names a part in a source assembly. destination-name names a part in a destination assembly. The structure itself checks only that both fields are symbols. A formula-correspondence checks that the names exist and are used one-to-one.
struct
(struct formula-correspondence (source destination matches) #:transparent) source : formula-assembly-visual? destination : formula-assembly-visual? matches : (listof formula-part-match?)
The source and destination fields store the exact immutable assembly values used when the correspondence is created. The matches field is stored in significant caller order.
Construction checks all of the following:
Every source name exists in source.
Every destination name exists in destination.
A source name appears at most once.
A destination name appears at most once.
The match list may be empty. Equal names are not matched automatically. Parts omitted from matches remain explicitly unmatched. The list order is also the order of matched transition layers created by transform-formula-parts.
procedure
(formula-correspondence-auto source destination) → formula-correspondence? source : formula-assembly-visual? destination : formula-assembly-visual?
procedure
(formula-correspondence-unmatched-source-names correspondence)
→ (listof symbol?) correspondence : formula-correspondence?
procedure
(formula-correspondence-unmatched-destination-names correspondence)
→ (listof symbol?) correspondence : formula-correspondence?
A formula correspondence stores endpoint templates. It does not store sampled transition layers. Those layers are compiled when transform-formula-parts is passed to scene-play, so the operation can use the current formulas, local transforms, and local opacities from the scene.
19.17 Path Visuals
A path Visual combines local path-geometry with identity, affine placement, fill, stroke, and cosmetic stroke width. Its geometry may contain line segments, cubic Bézier segments, or both. It implements gen:visual, gen:affine-visual, and gen:opacity-visual.
The Visual’s reference position is the translation component of its affine transform. Its path points remain local model data. Scale and rotation are applied around the local origin before the Visual is translated to its reference position.
procedure
(make-path-visual path #:id id [ #:center center #:rotation rotation #:scale scale #:opacity opacity #:fill fill #:stroke stroke #:stroke-width stroke-width]) → path-visual? path : path-geometry? id : symbol? center : vec2? = origin rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 fill : any/c = #f stroke : any/c = "black" stroke-width : (and/c finite-real? (>=/c 0)) = 2
The built-in Pict renderer interprets a false fill as transparent and a false stroke as no outline. Other style values are passed to the Racket drawing backend as color values. Stroke width is cosmetic and measured in output pixels; semantic scale does not multiply it.
Empty path geometry is accepted and produces a transparent one-pixel Pict in the built-in renderer.
procedure
(path-visual? value) → boolean?
value : any/c
procedure
(path-visual-path visual) → path-geometry?
visual : path-visual?
procedure
(path-visual-fill visual) → any/c
visual : path-visual?
procedure
(path-visual-stroke visual) → any/c
visual : path-visual?
procedure
(path-visual-stroke-width visual)
→ (and/c finite-real? (>=/c 0)) visual : path-visual?
procedure
(path-visual-with-path visual path) → path-visual?
visual : path-visual? path : path-geometry?
procedure
(line start end #:id id [ #:rotation rotation #:scale scale #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → path-visual? start : vec2? end : vec2? id : symbol? rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 stroke : any/c = "black" stroke-width : (and/c finite-real? (>=/c 0)) = 2
The constructor uses the midpoint of the two points as the Visual’s reference position and subtracts that midpoint from both stored path points. The local line is therefore centered at the origin. Rotation and scale are applied around that midpoint. The fill style is always #f. The optional opacity value is preserved as semantic global opacity.
For example:
(line (vec2 -2 0) (vec2 2 0) #:id 'axis #:stroke "navy" #:stroke-width 3)
procedure
(polygon vertices #:id id [ #:rotation rotation #:scale scale #:opacity opacity #:fill fill #:stroke stroke #:stroke-width stroke-width]) → path-visual? vertices : (listof vec2?) id : symbol? rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 fill : any/c = "cornflowerblue" stroke : any/c = "black" stroke-width : (and/c finite-real? (>=/c 0)) = 2
The constructor computes the center of the vertices’ axis-aligned bounding box and uses it as the Visual’s reference position. It subtracts that center from every stored path point, so scale and rotation occur around the bounding-box center. The constructor does not calculate a polygon centroid.
The closing edge from the last vertex to the first is implicit. Do not repeat the first vertex merely to close the polygon; repeating it adds a zero-length segment before the implicit closing edge. The optional opacity value is stored as semantic global opacity.
19.18 Bitmap Images
procedure
(image source #:id id [ #:center center #:rotation rotation #:scale scale #:opacity opacity] #:width width #:height height) → image-visual? source : path-string? id : symbol? center : vec2? = origin rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 width : (and/c finite-real? positive?) height : (and/c finite-real? positive?)
The built-in renderer loads the source lazily, scales it to the requested world size at the current camera scale, then applies the normal Visual scale and rotation. A missing or unreadable source therefore raises a renderer-time error. Its renderer-local bitmap cache is bounded and does not affect scene semantics. Image Visuals implement affine and opacity protocols, so standard movement, scaling, rotation, fading, grouping, layout, camera placement, and frame rendering work without a special timeline request.
procedure
(image-visual? value) → boolean?
value : any/c
procedure
(image-visual-source visual) → immutable-string?
visual : image-visual?
procedure
(image-visual-width visual) → (and/c finite-real? positive?)
visual : image-visual?
procedure
(image-visual-height visual) → (and/c finite-real? positive?)
visual : image-visual?
19.19 Full-Fidelity SVG Images
procedure
(svg-image source #:id id [ #:center center #:rotation rotation #:scale scale #:opacity opacity] #:width width #:height height) → svg-image-visual? source : path-string? id : symbol? center : vec2? = origin rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 width : (and/c finite-real? positive?) height : (and/c finite-real? positive?)
width and height specify unscaled local world dimensions, independently of the SVG document’s viewport. The Visual otherwise behaves like image: standard movement, scaling, rotation, opacity animation, groups, layout, camera placement, and frame rendering work normally. The renderer has a bounded local source-Pict cache. Use svg->visual rather than this constructor when individual SVG elements must be directly addressed or animated.
procedure
(svg-image-visual? value) → boolean?
value : any/c
procedure
(svg-image-visual-source visual) → immutable-string?
visual : svg-image-visual?
procedure
(svg-image-visual-width visual)
→ (and/c finite-real? positive?) visual : svg-image-visual?
procedure
(svg-image-visual-height visual)
→ (and/c finite-real? positive?) visual : svg-image-visual?
19.20 Semantic SVG Import
procedure
(svg->visual source #:id id [ #:center center #:rotation rotation #:scale scale #:opacity opacity]) → group-visual? source : path-string? id : symbol? center : vec2? = origin rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1
The root takes id. An SVG element’s nonempty id becomes its stable child identity; missing IDs receive deterministic generated symbols. Nested g elements become nested built-in groups, so imported IDs participate in scene-ref, scene-visual-at, derived-context lookup, and all nested style and transform requests. The constructor rejects duplicate IDs using the existing built-in group-tree invariant.
SVG’s screen-down y coordinate is converted to the semantic world-up y coordinate. Unitless translate(x[, y]) transforms are supported. Other SVG transforms must be flattened before import. Inherited fill, stroke, stroke-width, and opacity attributes (including simple inline style declarations) are preserved. CSS stylesheets, clipping, text, use elements, arcs, and paint servers are outside this deliberately semantic subset.
19.21 Arrow and Cartesian Axes Visuals
Arrow and axes values are semantic affine Visuals. They implement gen:visual, gen:affine-visual, and gen:opacity-visual. Their raw structure constructors and internal local geometry fields are not public.
19.21.1 Arrows
procedure
(arrow start end #:id id [ #:rotation rotation #:scale scale #:opacity opacity #:stroke stroke #:stroke-width stroke-width #:tip-length tip-length #:tip-width tip-width #:start-tip? start-tip? #:end-tip? end-tip?]) → arrow-visual? start : vec2? end : vec2? id : symbol? rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 stroke : any/c = "black" stroke-width : (and/c finite-real? (>=/c 0)) = 2 tip-length : (and/c finite-real? positive?) = 3/10 tip-width : (and/c finite-real? positive?) = 1/4 start-tip? : boolean? = #f end-tip? : boolean? = #t
The points must be distinct and their distance must be finite. The constructor uses their midpoint as the Visual’s reference position and stores the two endpoints relative to that midpoint. The optional rotation and scale therefore act around the midpoint.
start-tip? and end-tip? independently select closed triangular tips. The default is one tip at the end. Both flags may be false, or both may be true. tip-length measures the distance from an apex to the center of its base. tip-width measures the full base width. Both are local world-unit geometry and are affected by semantic scale. A tip is allowed to be longer than the shaft.
stroke is adapter-specific style data. The built-in Pict renderer uses it for the shaft, tip fill, and tip outline. stroke-width is a cosmetic output width and is not multiplied by semantic scale. opacity is applied to the complete rendered arrow after renderer dispatch.
procedure
(arrow-visual? value) → boolean?
value : any/c
procedure
(arrow-visual-length arrow) → (and/c finite-real? positive?)
arrow : arrow-visual?
procedure
(arrow-visual-stroke arrow) → any/c
arrow : arrow-visual?
procedure
(arrow-visual-stroke-width arrow)
→ (and/c finite-real? (>=/c 0)) arrow : arrow-visual?
procedure
(arrow-visual-tip-length arrow)
→ (and/c finite-real? positive?) arrow : arrow-visual?
procedure
(arrow-visual-tip-width arrow) → (and/c finite-real? positive?)
arrow : arrow-visual?
procedure
(arrow-visual-start-tip? arrow) → boolean?
arrow : arrow-visual?
procedure
(arrow-visual-end-tip? arrow) → boolean?
arrow : arrow-visual?
procedure
(arrow-visual-start arrow) → vec2?
arrow : arrow-visual?
procedure
(arrow-visual-end arrow) → vec2?
arrow : arrow-visual?
procedure
(arrow-visual-point-at arrow progress) → vec2?
arrow : arrow-visual? progress : (real-in 0 1)
19.21.2 Dynamic Endpoint Geometry
SCENE-CN provides deterministic geometry relationships without mutable updaters. Each endpoint accepted by the procedures below may be a literal vec2, a point-valued scene-parameter? handle, a top-level Visual/symbol/nested visual-path?, or a value made with anchor-of. A plain Visual reference selects its semantic reference position. Parameter values must be vec2 at every sampled time.
procedure
target : (or/c visual? symbol? visual-path?)
anchor :
(or/c 'bottom-left 'bottom 'bottom-right 'left 'center 'right 'top-left 'top 'top-right) = 'center offset : vec2? = origin
procedure
(line-between start end #:id id [ #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → relation-visual? start : any/c end : any/c id : symbol? opacity : opacity? = 1 stroke : any/c = "black" stroke-width : stroke-width? = 2
procedure
(segment-between start end #:id id [ #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → relation-visual? start : any/c end : any/c id : symbol? opacity : opacity? = 1 stroke : any/c = "black" stroke-width : stroke-width? = 2
procedure
(arrow-between start end #:id id [ #:opacity opacity #:stroke stroke #:stroke-width stroke-width #:tip-length tip-length #:tip-width tip-width #:start-tip? start-tip? #:end-tip? end-tip?]) → relation-visual? start : any/c end : any/c id : symbol? opacity : opacity? = 1 stroke : any/c = "black" stroke-width : stroke-width? = 2 tip-length : (and/c finite-real? positive?) = 3/10 tip-width : (and/c finite-real? positive?) = 1/4 start-tip? : boolean? = #f end-tip? : boolean? = #t
procedure
(ray-from start through #:id id [ #:length length #:opacity opacity #:stroke stroke #:stroke-width stroke-width #:tip-length tip-length #:tip-width tip-width #:start-tip? start-tip? #:end-tip? end-tip?]) → relation-visual? start : any/c through : any/c id : symbol? length : (and/c finite-real? positive?) = 2 opacity : opacity? = 1 stroke : any/c = "black" stroke-width : stroke-width? = 2 tip-length : (and/c finite-real? positive?) = 3/10 tip-width : (and/c finite-real? positive?) = 1/4 start-tip? : boolean? = #f end-tip? : boolean? = #t
Every endpoint constructor returns a relation-visual?. Literal points, parameters, and centre references create a 'semantic relation; a non-centre anchor-of creates a 'layout relation, measured against the current renderer-visible box after normal scene sampling. The relations retain their identity, support their ordinary outer movement and opacity animation, and can be inspected before rendering. Endpoint geometry must still resolve to distinct points at the sampled time.
19.21.3 Mathematical Annotations
SCENE-CO supplies small semantic, path-backed marks for explanatory diagrams. SCENE-ED additionally gives selected marks the same live endpoint protocol as line-between: a literal vec2, point-valued scene-parameter, Visual ID/path (its semantic centre), or anchor-of description. Literal points return the same immediate path-visual? or group-visual? values as before. Parameter and centre-reference inputs create semantic relations; an edge/corner anchor creates a layout relation. Both are deterministic from the sampled state. The layout phase remains top-level in this release, and it uses complete renderer bounds rather than exact visible outlines.
procedure
(arc #:id id [ #:center center #:radius radius #:start-angle start-angle #:angle angle #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → path-visual? id : symbol? center : vec2? = origin radius : (and/c finite-real? positive?) = 1 start-angle : finite-real? = 0 angle : (and/c finite-real? (not/c zero?)) = (/ pi 2) opacity : opacity? = 1 stroke : any/c = "black" stroke-width : stroke-width? = 2
procedure
(dashed-path geometry #:id id [ #:dash-length dash-length #:gap-length gap-length #:center center #:rotation rotation #:scale scale #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → path-visual? geometry : path-geometry? id : symbol? dash-length : (and/c finite-real? positive?) = 1/5 gap-length : stroke-width? = 1/8 center : vec2? = origin rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 stroke : any/c = "black" stroke-width : stroke-width? = 2
procedure
(dashed-line start end #:id id [ #:dash-length dash-length #:gap-length gap-length #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → path-visual? start : vec2? end : vec2? id : symbol? dash-length : (and/c finite-real? positive?) = 1/5 gap-length : stroke-width? = 1/8 opacity : opacity? = 1 stroke : any/c = "black" stroke-width : stroke-width? = 2
procedure
(angle first vertex second #:id id [ #:radius radius #:reflex? reflex? #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → path-visual? first : vec2? vertex : vec2? second : vec2? id : symbol? radius : (and/c finite-real? positive?) = 1/3 reflex? : boolean? = #f opacity : opacity? = 1 stroke : any/c = "black" stroke-width : stroke-width? = 2
procedure
(right-angle first vertex second #:id id [ #:size size #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → path-visual? first : vec2? vertex : vec2? second : vec2? id : symbol? size : (and/c finite-real? positive?) = 1/3 opacity : opacity? = 1 stroke : any/c = "black" stroke-width : stroke-width? = 2
procedure
(angle-between first vertex second #:id id [ #:radius radius #:reflex? reflex? #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → visual? first : any/c vertex : any/c second : any/c id : symbol? radius : (and/c finite-real? positive?) = 1/3 reflex? : boolean? = #f opacity : opacity? = 1 stroke : any/c = "black" stroke-width : stroke-width? = 2
procedure
(right-angle-between first vertex second #:id id [ #:size size #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → visual? first : any/c vertex : any/c second : any/c id : symbol? size : (and/c finite-real? positive?) = 1/3 opacity : opacity? = 1 stroke : any/c = "black" stroke-width : stroke-width? = 2
procedure
(brace-between start end #:id id [ #:offset offset #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → visual? start : any/c end : any/c id : symbol? offset : (and/c finite-real? (not/c zero?)) = 1/3 opacity : opacity? = 1 stroke : any/c = "black" stroke-width : stroke-width? = 2
procedure
(brace start end #:id id [ #:offset offset #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → visual? start : any/c end : any/c id : symbol? offset : (and/c finite-real? (not/c zero?)) = 1/3 opacity : opacity? = 1 stroke : any/c = "black" stroke-width : stroke-width? = 2
procedure
(brace-label start end label #:id id [ #:offset offset #:gap gap #:font-size font-size #:color color #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → visual? start : any/c end : any/c label : string? id : symbol? offset : (and/c finite-real? (not/c zero?)) = 1/3 gap : stroke-width? = 1/6 font-size : (and/c finite-real? positive?) = 1/4 color : color-spec? = "black" opacity : opacity? = 1 stroke : any/c = "black" stroke-width : stroke-width? = 2
procedure
(curved-arrow-between start end #:id id [ #:angle angle #:opacity opacity #:stroke stroke #:stroke-width stroke-width #:tip-length tip-length #:tip-width tip-width]) → visual? start : any/c end : any/c id : symbol? angle : finite-real? = (/ pi 2) opacity : opacity? = 1 stroke : any/c = "black" stroke-width : stroke-width? = 2 tip-length : (and/c finite-real? positive?) = 3/10 tip-width : (and/c finite-real? positive?) = 1/4
procedure
(surrounding-rectangle target #:id id [ #:padding padding #:opacity opacity #:fill fill #:stroke stroke #:stroke-width stroke-width]) → relation-visual? target : (or/c visual? symbol? visual-path?) id : symbol? padding : stroke-width? = 1/8 opacity : opacity? = 1 fill : any/c = #f stroke : any/c = "yellow" stroke-width : stroke-width? = 3
19.21.4 Mathematical Shape Catalogue
SCENE-DJ adds a compact family of path-backed shapes. Except for the two convenience groups, each constructor returns an ordinary path-visual? with the usual affine placement, opacity, fill, and stroke protocols. They do not introduce renderer-specific leaf classes; the existing path renderer draws their line and cubic geometry, including odd-even holes in annulus.
procedure
(ellipse #:id id [ #:center center #:width width #:height height #:rotation rotation #:scale scale #:opacity opacity #:fill fill #:stroke stroke #:stroke-width stroke-width]) → path-visual? id : symbol? center : vec2? = origin width : (and/c finite-real? positive?) = 2 height : (and/c finite-real? positive?) = 1 rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 fill : any/c = "cornflowerblue" stroke : any/c = "black" stroke-width : stroke-width? = 2
procedure
(annulus #:id id [ #:center center #:inner-radius inner-radius #:outer-radius outer-radius #:rotation rotation #:scale scale #:opacity opacity #:fill fill #:stroke stroke #:stroke-width stroke-width]) → path-visual? id : symbol? center : vec2? = origin inner-radius : (and/c finite-real? positive?) = 1/2 outer-radius : (and/c finite-real? positive?) = 1 rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 fill : any/c = "cornflowerblue" stroke : any/c = "black" stroke-width : stroke-width? = 2
procedure
(sector #:id id [ #:center center #:radius radius #:start-angle start-angle #:angle angle #:rotation rotation #:scale scale #:opacity opacity #:fill fill #:stroke stroke #:stroke-width stroke-width]) → path-visual? id : symbol? center : vec2? = origin radius : (and/c finite-real? positive?) = 1 start-angle : finite-real? = 0 angle : finite-real? = (/ pi 2) rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 fill : any/c = "cornflowerblue" stroke : any/c = "black" stroke-width : stroke-width? = 2
procedure
(regular-polygon #:id id [ #:center center #:sides sides #:radius radius #:start-angle start-angle #:rotation rotation #:scale scale #:opacity opacity #:fill fill #:stroke stroke #:stroke-width stroke-width]) → path-visual? id : symbol? center : vec2? = origin sides : exact-integer? = 5 radius : (and/c finite-real? positive?) = 1 start-angle : finite-real? = (/ pi 2) rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 fill : any/c = "cornflowerblue" stroke : any/c = "black" stroke-width : stroke-width? = 2
procedure
(star #:id id [ #:center center #:points points #:outer-radius outer-radius #:inner-radius inner-radius #:start-angle start-angle #:rotation rotation #:scale scale #:opacity opacity #:fill fill #:stroke stroke #:stroke-width stroke-width]) → path-visual? id : symbol? center : vec2? = origin points : exact-integer? = 5 outer-radius : (and/c finite-real? positive?) = 1 inner-radius : (and/c finite-real? positive?) = 1/2 start-angle : finite-real? = (/ pi 2) rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 fill : any/c = "gold" stroke : any/c = "black" stroke-width : stroke-width? = 2
procedure
(rounded-rectangle #:id id [ #:center center #:width width #:height height #:corner-radius corner-radius #:rotation rotation #:scale scale #:opacity opacity #:fill fill #:stroke stroke #:stroke-width stroke-width]) → path-visual? id : symbol? center : vec2? = origin width : (and/c finite-real? positive?) = 2 height : (and/c finite-real? positive?) = 1 corner-radius : stroke-width? = 1/5 rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 fill : any/c = "cornflowerblue" stroke : any/c = "black" stroke-width : stroke-width? = 2
procedure
(arc-between-points start end #:id id [ #:angle angle #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → path-visual? start : vec2? end : vec2? id : symbol? angle : finite-real? = (/ pi 2) opacity : opacity? = 1 stroke : any/c = "black" stroke-width : stroke-width? = 2
procedure
(curved-arrow start end #:id id [ #:angle angle #:opacity opacity #:stroke stroke #:stroke-width stroke-width #:tip-length tip-length #:tip-width tip-width]) → group-visual? start : vec2? end : vec2? id : symbol? angle : finite-real? = (/ pi 2) opacity : opacity? = 1 stroke : any/c = "black" stroke-width : stroke-width? = 2 tip-length : (and/c finite-real? positive?) = 3/10 tip-width : (and/c finite-real? positive?) = 1/4
procedure
(double-arrow start end #:id id [ #:rotation rotation #:scale scale #:opacity opacity #:stroke stroke #:stroke-width stroke-width #:tip-length tip-length #:tip-width tip-width]) → arrow-visual? start : vec2? end : vec2? id : symbol? rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 stroke : any/c = "black" stroke-width : stroke-width? = 2 tip-length : (and/c finite-real? positive?) = 3/10 tip-width : (and/c finite-real? positive?) = 1/4
procedure
(labeled-point label #:id id [ #:center center #:radius radius #:label-offset label-offset #:font-size font-size #:font-family font-family #:fill fill #:stroke stroke #:stroke-width stroke-width #:color color #:opacity opacity]) → group-visual? label : string? id : symbol? center : vec2? = origin radius : (and/c finite-real? positive?) = 1/10 label-offset : vec2? = (vec2 1/4 1/4) font-size : (and/c finite-real? positive?) = 1/4 font-family : any/c = 'roman fill : any/c = "crimson" stroke : any/c = "firebrick" stroke-width : stroke-width? = 2 color : any/c = stroke opacity : opacity? = 1
19.21.5 Axis Ranges
struct
(struct axis-range (minimum maximum tick-step) #:transparent) minimum : finite-real? maximum : finite-real? tick-step : (and/c finite-real? positive?)
minimum is the smallest represented coordinate.
maximum is the largest represented coordinate.
tick-step is the positive distance between regular tick coordinates.
minimum must be less than maximum. The computed difference (- maximum minimum) must also remain a positive finite real; this rejects an inexact endpoint pair whose subtraction overflows. The structure is immutable and transparent. Linear axes require their ranges to contain zero; logarithmic axes require strictly-positive ranges. Its public bindings include axis-range, axis-range?, the three field accessors, and struct:axis-range.
procedure
(axis-range-contains? range value) → boolean?
range : axis-range? value : any/c
procedure
(axis-range-tick-values range) → (listof finite-real?)
range : axis-range?
For example:
(axis-range-tick-values (axis-range -3 5 2))
returns '(-2 2 4). The procedure does not choose ticks from camera pixels or available label space.
Exact endpoint quotients are handled exactly. For inexact quotients, the procedure uses a fixed relative tolerance of 1e-12 when choosing the first and last integer indexes. This prevents ordinary decimal input such as -0.3, 0.3, and 0.1 from losing endpoint ticks because of binary floating-point rounding. It raises an exception when dividing a range endpoint by the step produces an infinite or NaN index.
procedure
(axis-scale? value) → boolean?
value : any/c
19.21.6 Cartesian Axes
procedure
(axes #:id id [ #:center center #:rotation rotation #:scale scale #:opacity opacity #:x-range x-range #:y-range y-range #:x-scale x-scale #:y-scale y-scale #:x-log-base x-log-base #:y-log-base y-log-base #:x-length x-length #:y-length y-length #:stroke stroke #:stroke-width stroke-width #:tick-size tick-size #:tip-length tip-length #:tip-width tip-width #:x-tip? x-tip? #:y-tip? y-tip?]) → axes-visual? id : symbol? center : vec2? = origin rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 x-range : axis-range? = (axis-range -6 6 1) y-range : axis-range? = (axis-range -3 3 1) x-scale : axis-scale? = 'linear y-scale : axis-scale? = 'linear x-log-base : (and/c finite-real? (>/c 1)) = 10 y-log-base : (and/c finite-real? (>/c 1)) = 10 x-length : (and/c finite-real? positive?) = 12 y-length : (and/c finite-real? positive?) = 6 stroke : any/c = "black" stroke-width : (and/c finite-real? (>=/c 0)) = 2 tick-size : (and/c finite-real? (>=/c 0)) = 3/20 tip-length : (and/c finite-real? positive?) = 3/10 tip-width : (and/c finite-real? positive?) = 1/4 x-tip? : boolean? = #t y-tip? : boolean? = #t
The full interval from (axis-range-minimum x-range) to (axis-range-maximum x-range) is mapped to x-length local world units. The y interval is mapped independently to y-length. The x and y unit lengths can therefore differ. Each resulting length-per-display-unit must remain a positive finite real.
With 'log for x-scale or y-scale, that axis accepts only a strictly-positive axis-range. Numeric coordinates are converted through (log value) in the configured base before they are placed. A log axis uses numeric one as its shaft reference when it is visible (otherwise the minimum range value); coordinate zero is invalid. x-log-base and y-log-base must be finite and greater than one. The tick-step of a log range is a step in base-logarithm exponent space, so the usual value of one produces ticks at successive powers of the base.
The x shaft is drawn at numeric y coordinate zero on a linear y axis, and at numeric one (or the visible minimum) on a log y axis; the y shaft follows the same rule for its x coordinate. Regular ticks come from axis-range-tick-values on linear axes and powers of the configured base on log axes. tick-size is the full local length of each tick. A value of zero hides all ticks while preserving the ranges and coordinate conversion.
x-tip? and y-tip? select triangular tips at the maximum-x and maximum-y endpoints, respectively. tip-length and tip-width are local world-unit geometry. stroke-width is cosmetic. The built-in renderer uses stroke for shafts, ticks, tip fill, and tip outlines.
The constructor does not create numeric labels, axis-name labels, grid lines, or sampled plots. They can be added as separate Visuals. Renderer-aware layout can place labels around the complete axes render box.
procedure
(axes-visual? value) → boolean?
value : any/c
procedure
(axes-visual-x-range axes) → axis-range?
axes : axes-visual?
procedure
(axes-visual-y-range axes) → axis-range?
axes : axes-visual?
procedure
(axes-visual-x-scale axes) → axis-scale?
axes : axes-visual?
procedure
(axes-visual-y-scale axes) → axis-scale?
axes : axes-visual?
procedure
(axes-visual-x-log-base axes) → (and/c finite-real? (>/c 1))
axes : axes-visual?
procedure
(axes-visual-y-log-base axes) → (and/c finite-real? (>/c 1))
axes : axes-visual?
procedure
(axes-visual-x-length axes) → (and/c finite-real? positive?)
axes : axes-visual?
procedure
(axes-visual-y-length axes) → (and/c finite-real? positive?)
axes : axes-visual?
procedure
(axes-visual-stroke axes) → any/c
axes : axes-visual?
procedure
(axes-visual-stroke-width axes) → (and/c finite-real? (>=/c 0))
axes : axes-visual?
procedure
(axes-visual-tick-size axes) → (and/c finite-real? (>=/c 0))
axes : axes-visual?
procedure
(axes-visual-tip-length axes) → (and/c finite-real? positive?)
axes : axes-visual?
procedure
(axes-visual-tip-width axes) → (and/c finite-real? positive?)
axes : axes-visual?
procedure
(axes-visual-x-tip? axes) → boolean?
axes : axes-visual?
procedure
(axes-visual-y-tip? axes) → boolean?
axes : axes-visual?
procedure
(axes-x-unit-length axes) → (and/c finite-real? positive?)
axes : axes-visual?
procedure
(axes-y-unit-length axes) → (and/c finite-real? positive?)
axes : axes-visual?
procedure
(axes-coordinates->point axes x y) → vec2?
axes : axes-visual? x : finite-real? y : finite-real?
The numeric coordinates are not required to lie inside the displayed ranges. This permits extrapolation and placement just outside the visible axes. A value on a logarithmic axis must nevertheless be a positive finite real.
procedure
(axes-point->coordinates axes point) → vec2?
axes : axes-visual? point : vec2?
For finite inputs this is the inverse of axes-coordinates->point up to ordinary numeric precision. A nonzero rotation normally introduces inexact trigonometric results.
19.22 Linear-Algebra Diagrams
SCENE-CZ adds small, conventional linear-algebra diagrams without adding a mutable diagram class. Each constructor below returns an ordinary immutable Visual or group-visual?. Its children retain their normal nested paths, so existing scene operations work directly. In particular, apply-matrix can map one complete top-level diagram coherently.
procedure
(number-plane #:id id [ #:x-range x-range #:y-range y-range #:x-length x-length #:y-length y-length #:center center #:rotation rotation #:scale scale #:grid? grid? #:labels? labels? #:grid-stroke grid-stroke #:grid-stroke-width grid-stroke-width #:axes-stroke axes-stroke #:axes-stroke-width axes-stroke-width #:label-font-size label-font-size #:label-color label-color]) → group-visual? id : symbol? x-range : axis-range? = (axis-range -4 4 1) y-range : axis-range? = (axis-range -3 3 1) x-length : (and/c finite-real? positive?) = 8 y-length : (and/c finite-real? positive?) = 6 center : vec2? = origin rotation : finite-real? = 0 scale : scale-factor? = 1 grid? : boolean? = #t labels? : boolean? = #f grid-stroke : any/c = "lightsteelblue" grid-stroke-width : (and/c finite-real? (>=/c 0)) = 1 axes-stroke : any/c = "navy" axes-stroke-width : (and/c finite-real? (>=/c 0)) = 2 label-font-size : (and/c finite-real? positive?) = 1/4 label-color : any/c = "navy"
The stable direct-child paths are produced by number-plane-grid-path, number-plane-axes-path, and number-plane-labels-path. When #:grid? or #:labels? is false, its corresponding path intentionally does not resolve.
procedure
(number-plane-grid-path plane-id) → visual-path?
plane-id : symbol?
procedure
(number-plane-axes-path plane-id) → visual-path?
plane-id : symbol?
procedure
(number-plane-labels-path plane-id) → visual-path?
plane-id : symbol?
procedure
(vector-arrow endpoint [ #:start start] #:id id [ #:stroke stroke #:stroke-width stroke-width #:tip-length tip-length #:tip-width tip-width]) → arrow-visual? endpoint : vec2? start : vec2? = origin id : symbol? stroke : any/c = "darkorchid" stroke-width : (and/c finite-real? (>=/c 0)) = 3 tip-length : (and/c finite-real? positive?) = 3/10 tip-width : (and/c finite-real? positive?) = 1/4
procedure
(vector-coordinates arrow) → vec2?
arrow : arrow-visual?
procedure
(vector-label arrow #:id id [ #:text text #:offset offset #:font-size font-size #:color color]) → text-visual? arrow : arrow-visual? id : symbol? text : (or/c false/c string?) = #f offset : vec2? = (vec2 1/5 1/5) font-size : (and/c finite-real? positive?) = 1/4 color : any/c = "darkorchid"
procedure
(basis-vectors #:id id [ #:origin origin #:e1 e1 #:e2 e2 #:e1-color e1-color #:e2-color e2-color #:stroke-width stroke-width]) → group-visual? id : symbol? origin : vec2? = origin e1 : vec2? = (vec2 1 0) e2 : vec2? = (vec2 0 1) e1-color : any/c = "crimson" e2-color : any/c = "forestgreen" stroke-width : (and/c finite-real? (>=/c 0)) = 3
procedure
(linear-transformation-diagram #:id id [ #:x-range x-range #:y-range y-range #:vector-end vector-end #:unit-square? unit-square? #:grid? grid?]) → group-visual? id : symbol? x-range : axis-range? = (axis-range -4 4 1) y-range : axis-range? = (axis-range -3 3 1) vector-end : vec2? = (vec2 3 2) unit-square? : boolean? = #t grid? : boolean? = #t
For example, this keeps all geometric parts together while a title remains fixed outside the mapped group:
(define diagram (linear-transformation-diagram #:id 'diagram #:vector-end (vec2 3 2))) (scene-play (scene-add (make-scene) diagram) (apply-matrix 'diagram (linear2 1 1 0 1)) #:duration 3)
19.23 Complex and Polar Coordinates
SCENE-DA uses ordinary Racket complex numbers and converts only at the drawing boundary. SCENE-DB follows the same approach for polar coordinate values and paths. Both planes are normal immutable group trees, not special scene types.
procedure
(complex->point value) → vec2?
value : complex?
procedure
(point->complex point) → complex?
point : vec2?
procedure
(complex-domain-color value [ #:saturation saturation #:brightness brightness #:radial? radial?]) → rgba-color? value : complex? saturation : (real-in 0 1) = 3/4 brightness : (real-in 0 1) = 4/5 radial? : boolean? = #t
procedure
(complex-domain-coloring function #:id id [ #:x-min x-min #:x-max x-max #:y-min y-min #:y-max y-max #:columns columns #:rows rows #:saturation saturation #:brightness brightness #:radial? radial? #:opacity opacity]) → group-visual? function : (procedure-arity-includes/c 1) id : symbol? x-min : finite-real? = -3 x-max : finite-real? = 3 y-min : finite-real? = -2 y-max : finite-real? = 2 columns : exact-positive-integer? = 24 rows : exact-positive-integer? = 16 saturation : (real-in 0 1) = 3/4 brightness : (real-in 0 1) = 4/5 radial? : boolean? = #t opacity : (real-in 0 1) = 1
procedure
(complex-plane #:id id [ #:x-range x-range #:y-range y-range #:x-length x-length #:y-length y-length #:center center #:rotation rotation #:scale scale #:grid? grid? #:labels? labels? #:grid-stroke grid-stroke #:grid-stroke-width grid-stroke-width #:axes-stroke axes-stroke #:axes-stroke-width axes-stroke-width #:label-font-size label-font-size #:label-color label-color]) → group-visual? id : symbol? x-range : axis-range? = (axis-range -4 4 1) y-range : axis-range? = (axis-range -3 3 1) x-length : (and/c finite-real? positive?) = 8 y-length : (and/c finite-real? positive?) = 6 center : vec2? = origin rotation : finite-real? = 0 scale : scale-factor? = 1 grid? : boolean? = #t labels? : boolean? = #t grid-stroke : any/c = "lightsteelblue" grid-stroke-width : (and/c finite-real? (>=/c 0)) = 1 axes-stroke : any/c = "navy" axes-stroke-width : (and/c finite-real? (>=/c 0)) = 2 label-font-size : (and/c finite-real? positive?) = 1/4 label-color : any/c = "navy"
procedure
(apply-complex-function target function [ #:samples samples #:adaptive? adaptive? #:tolerance tolerance #:max-depth max-depth #:discontinuities discontinuities]) → apply-pointwise-request? target : (or/c visual? symbol? visual-path?) function : (procedure-arity-includes/c 1) samples : exact-positive-integer? = 24 adaptive? : boolean? = #t tolerance : (and/c finite-real? positive?) = 1/32 max-depth : exact-nonnegative-integer? = 8 discontinuities : (or/c 'split 'error) = 'error
procedure
(apply-complex-homotopy target homotopy [ #:samples samples #:adaptive? adaptive? #:tolerance tolerance #:max-depth max-depth #:discontinuities discontinuities]) → apply-homotopy-request? target : (or/c visual? symbol? visual-path?) homotopy : (procedure-arity-includes/c 2) samples : exact-positive-integer? = 24 adaptive? : boolean? = #t tolerance : (and/c finite-real? positive?) = 1/32 max-depth : exact-nonnegative-integer? = 8 discontinuities : (or/c 'split 'error) = 'error
procedure
(polar-coordinate? value) → boolean?
value : any/c
procedure
(polar-coordinate-radius value) → (and/c finite-real? (>=/c 0))
value : polar-coordinate?
procedure
(polar-coordinate-angle value) → finite-real?
value : polar-coordinate?
procedure
(polar->point radius angle) → vec2?
radius : finite-real? angle : finite-real?
procedure
(point->polar point) → polar-coordinate?
point : vec2?
procedure
(polar-plane #:id id [ #:radii radii #:angles angles #:center center #:rotation rotation #:scale scale #:labels? labels? #:stroke stroke #:stroke-width stroke-width #:grid-stroke grid-stroke #:grid-stroke-width grid-stroke-width #:label-font-size label-font-size #:label-color label-color]) → group-visual? id : symbol? radii : (listof (and/c finite-real? positive?)) = '(1 2 3) angles : (listof finite-real?) = (list 0 (/ pi 4) (/ pi 2)) center : vec2? = origin rotation : finite-real? = 0 scale : (and/c finite-real? positive?) = 1 labels? : boolean? = #t stroke : any/c = "steelblue" stroke-width : (and/c finite-real? (>=/c 0)) = 2 grid-stroke : any/c = "lightsteelblue" grid-stroke-width : (and/c finite-real? (>=/c 0)) = 1 label-font-size : (and/c finite-real? positive?) = 1/4 label-color : any/c = "navy"
procedure
(polar-graph radius-function #:id id [ #:start start #:end end #:samples samples #:center center #:rotation rotation #:scale scale #:opacity opacity #:stroke stroke #:stroke-width stroke-width #:fill fill]) → path-visual? radius-function : (procedure-arity-includes/c 1) id : symbol? start : finite-real? = 0 end : finite-real? = (* 2 pi) samples : (and/c exact-integer? (>=/c 2)) = 240 center : vec2? = origin rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1 stroke : any/c = "crimson" stroke-width : (and/c finite-real? (>=/c 0)) = 3 fill : any/c = #f
19.24 Coordinate and Calculus Helpers
SCENE-CP provides static, axes-aware construction helpers for common teaching diagrams. They evaluate numeric procedures during construction and return ordinary immutable Visuals or points. For an animated construction, place one of these calls inside derived-visual and rebuild it from the sampled parameter value.
procedure
(graph-point axes function x) → vec2?
axes : axes-visual? function : (procedure-arity-includes/c 1) x : finite-real?
procedure
(graph-label axes function x label #:id id [ #:offset offset #:font-size font-size #:color color]) → text-visual? axes : axes-visual? function : (procedure-arity-includes/c 1) x : finite-real? label : string? id : symbol? offset : vec2? = (vec2 1/5 1/5) font-size : finite-real? = 1/4 color : any/c = "black"
procedure
(vertical-line-to-graph axes function x #:id id [ #:baseline baseline #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → path-visual? axes : axes-visual? function : (procedure-arity-includes/c 1) x : finite-real? id : symbol? baseline : finite-real? = 0 opacity : opacity? = 1 stroke : any/c = "gray" stroke-width : (and/c finite-real? (>=/c 0)) = 2
procedure
(horizontal-line-to-graph axes function x #:id id [ #:baseline baseline #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → path-visual? axes : axes-visual? function : (procedure-arity-includes/c 1) x : finite-real? id : symbol? baseline : finite-real? = 0 opacity : opacity? = 1 stroke : any/c = "gray" stroke-width : (and/c finite-real? (>=/c 0)) = 2
procedure
(tangent-line axes function x #:id id [ #:dx dx #:length length #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → path-visual? axes : axes-visual? function : (procedure-arity-includes/c 1) x : finite-real? id : symbol? dx : (and/c finite-real? (>/c 0)) = 1/100 length : (and/c finite-real? (>/c 0)) = 2 opacity : opacity? = 1 stroke : any/c = "crimson" stroke-width : (and/c finite-real? (>=/c 0)) = 3
procedure
(secant-line axes function x dx #:id id [ #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → path-visual? axes : axes-visual? function : (procedure-arity-includes/c 1) x : finite-real? dx : (and/c finite-real? (not/c zero?)) id : symbol? opacity : opacity? = 1 stroke : any/c = "darkorange" stroke-width : (and/c finite-real? (>=/c 0)) = 3
procedure
(secant-slope-group axes function x dx #:id id [ #:opacity opacity #:secant-stroke secant-stroke #:guide-stroke guide-stroke #:stroke-width stroke-width #:marker-radius marker-radius]) → group-visual? axes : axes-visual? function : (procedure-arity-includes/c 1) x : finite-real? dx : (and/c finite-real? (not/c zero?)) id : symbol? opacity : opacity? = 1 secant-stroke : any/c = "darkorange" guide-stroke : any/c = "gray" stroke-width : (and/c finite-real? (>=/c 0)) = 3 marker-radius : (and/c finite-real? (>/c 0)) = 1/10
procedure
(area-under-graph axes function #:id id [ #:x-min x-min #:x-max x-max #:baseline baseline #:sample-count sample-count #:opacity opacity #:fill fill #:stroke stroke #:stroke-width stroke-width]) → path-visual? axes : axes-visual? function : (procedure-arity-includes/c 1) id : symbol? x-min : (or/c finite-real? false/c) = #f x-max : (or/c finite-real? false/c) = #f baseline : finite-real? = 0 sample-count : (and/c exact-integer? (>=/c 2)) = 101 opacity : opacity? = 2/5 fill : any/c = "cornflowerblue" stroke : any/c = #f stroke-width : (and/c finite-real? (>=/c 0)) = 0
procedure
(area-between-curves axes first-function second-function #:id id [ #:x-min x-min #:x-max x-max #:sample-count sample-count #:opacity opacity #:fill fill #:stroke stroke #:stroke-width stroke-width]) → path-visual? axes : axes-visual? first-function : (procedure-arity-includes/c 1) second-function : (procedure-arity-includes/c 1) id : symbol? x-min : (or/c finite-real? false/c) = #f x-max : (or/c finite-real? false/c) = #f sample-count : (and/c exact-integer? (>=/c 2)) = 101 opacity : opacity? = 2/5 fill : any/c = "mediumpurple" stroke : any/c = #f stroke-width : (and/c finite-real? (>=/c 0)) = 0
procedure
(riemann-rectangles axes function #:id id [ #:x-min x-min #:x-max x-max #:count count #:baseline baseline #:opacity opacity #:fill fill #:stroke stroke #:stroke-width stroke-width]) → path-visual? axes : axes-visual? function : (procedure-arity-includes/c 1) id : symbol? x-min : (or/c finite-real? false/c) = #f x-max : (or/c finite-real? false/c) = #f count : exact-positive-integer? = 8 baseline : finite-real? = 0 opacity : opacity? = 2/5 fill : any/c = "seagreen" stroke : any/c = "darkgreen" stroke-width : (and/c finite-real? (>=/c 0)) = 1
19.25 Coordinate Curves and Plots
The procedures in this section convert ordered numeric coordinates to semantic path geometry. They use the local coordinate system of an axes Visual. They do not store a sampling procedure or a caller-owned point list in the result.
19.25.1 Interpolation Modes
procedure
(curve-interpolation? value) → boolean?
value : any/c
'linear connects each accepted pair with a line segment.
'smooth creates cubic Bézier segments that pass through all accepted samples in order.
Every public coordinate-plot procedure uses 'linear by default. An unsupported symbol or another value returns #f.
Smooth interpolation is applied separately to every accepted run. Suppose one run contains points P0 through Pn. For a segment from Pi to Pi+1, the usual interior control points are:
C1 = Pi + (Pi+1 - Pi-1) / 6
C2 = Pi+1 + (Pi - Pi+2) / 6
At an end of a run, the endpoint is repeated for the missing neighboring point. A run containing exactly two points uses a line-equivalent cubic whose controls are one third and two thirds of the way along the segment. The result therefore follows the same traversal order and reaches every accepted sample.
When clipping is enabled, sample pairs are clipped as line segments before smooth interpolation is calculated. Generated control points are then clamped to the closed axes rectangle. A cubic Bézier curve lies inside the convex hull of its endpoints and controls, so the resulting visible curve stays inside the rectangle. Clamping may reduce smoothness where a run touches a boundary.
19.25.2 Sampled Curves and Fields
procedure
(sample-implicit-path axes field [ #:level level #:x-count x-count #:y-count y-count]) → path-geometry? axes : axes-visual? field : (procedure-arity-includes/c 2) level : finite-real? = 0 x-count : (and/c exact-integer? (>=/c 2)) = 65 y-count : (and/c exact-integer? (>=/c 2)) = 65
procedure
(implicit-curve axes field #:id id [ #:level level #:x-count x-count #:y-count y-count #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → path-visual? axes : axes-visual? field : (procedure-arity-includes/c 2) id : symbol? level : finite-real? = 0 x-count : (and/c exact-integer? (>=/c 2)) = 65 y-count : (and/c exact-integer? (>=/c 2)) = 65 opacity : opacity? = 1 stroke : any/c = "darkorange" stroke-width : (and/c finite-real? (>=/c 0)) = 2
procedure
(vector-field axes field #:id id [ #:x-count x-count #:y-count y-count #:scale scale #:opacity opacity #:stroke stroke #:stroke-width stroke-width #:tip-length tip-length #:tip-width tip-width]) → group-visual? axes : axes-visual? field : (procedure-arity-includes/c 2) id : symbol? x-count : (and/c exact-integer? (>=/c 1)) = 9 y-count : (and/c exact-integer? (>=/c 1)) = 7 scale : finite-real? = 1/4 opacity : opacity? = 1 stroke : any/c = "seagreen" stroke-width : (and/c finite-real? (>=/c 0)) = 2 tip-length : (and/c finite-real? (>/c 0)) = 3/20 tip-width : (and/c finite-real? (>/c 0)) = 1/8
19.26 Deterministic ODE Flow and Streamlines
SCENE-DC turns a two-dimensional vector field into reproducible integral-curve geometry. A two-argument field is autonomous; a three-argument field receives time first. Fixed-step fourth-order Runge–Kutta (RK4) remains the default, with the caller selecting the step size and number of streamline steps. Neither the direct numerical solver nor a prepared trajectory depends on a prior rendered frame. prepare-ode-trajectory stores immutable canonical RK4 checkpoints, so an animated particle does not recompute the complete seed-to-time prefix for every frame. The renderer batches selected frame times by checkpoint interval, sharing each interval’s full RK4 suffix steps.
SCENE-DN adds an optional deterministic adaptive Dormand–Prince 5(4) backend. It stores accepted endpoint derivatives for cubic Hermite dense lookup, accepts time-dependent fields, can stop at one scalar sign-crossing event, and records immutable diagnostics. Once an adaptive trajectory is prepared, dense lookup and frame rendering never invoke the author field.
SCENE-3D-K factors the numerical operations behind both trajectory families into an immutable ode-state-space. The built-in real-ode-state-space, vec2-ode-state-space, and vec3-ode-state-space values make the shared RK4 and RK45 algorithms explicit; (numeric-vector-ode-state-space n) creates a fixed-length immutable numeric-vector state space. These values are useful when building a numerical extension, while the ordinary two-dimensional and spatial trajectory constructors remain the author-facing API.
struct
(struct ode-state-space ( dimension add subtract scale norm interpolate finite?) #:transparent) dimension : exact-positive-integer? add : procedure? subtract : procedure? scale : procedure? norm : procedure? interpolate : procedure? finite? : procedure?
procedure
(numeric-vector-ode-state-space dimension) → ode-state-space?
dimension : exact-positive-integer?
procedure
(ode-flow-position field seed time [ #:step-size step-size]) → vec2?
field :
(or/c (procedure-arity-includes/c 2) (procedure-arity-includes/c 3)) seed : vec2? time : finite-real? step-size : (and/c finite-real? positive?) = 1/20
The final partial step is included, so the requested time is reached exactly in ordinary arithmetic rather than rounded to a step-grid endpoint. This is a fixed-step solver, not an adaptive tolerance-controlled integrator.
procedure
(adaptive-rk45 [ #:relative-tolerance relative-tolerance #:absolute-tolerance absolute-tolerance #:initial-step initial-step #:minimum-step minimum-step #:maximum-step maximum-step #:maximum-steps maximum-steps]) → adaptive-rk45? relative-tolerance : (and/c finite-real? positive?) = 1e-6 absolute-tolerance : (and/c finite-real? positive?) = 1e-8 initial-step : (and/c finite-real? positive?) = 1/10 minimum-step : (and/c finite-real? positive?) = 1e-8 maximum-step : (and/c finite-real? positive?) = 1 maximum-steps : exact-positive-integer? = 100000
procedure
(adaptive-rk45? value) → boolean?
value : any/c
procedure
(ode-event function [ #:direction direction #:name name]) → ode-event?
function :
(or/c (procedure-arity-includes/c 2) (procedure-arity-includes/c 3)) direction : (or/c 'any 'increasing 'decreasing) = 'any name : symbol? = 'event
procedure
(ode-event? value) → boolean?
value : any/c
procedure
(ode-trajectory? value) → boolean?
value : any/c
procedure
(prepare-ode-trajectory field seed #:time-range time-range [ #:step-size step-size #:checkpoint-every checkpoint-every #:solver solver #:event event]) → ode-trajectory?
field :
(or/c (procedure-arity-includes/c 2) (procedure-arity-includes/c 3)) seed : vec2? time-range : (cons/c finite-real? finite-real?) step-size : (and/c finite-real? positive?) = 1/20 checkpoint-every : exact-positive-integer? = 16 solver : (or/c false/c adaptive-rk45?) = #f event : (or/c false/c ode-event?) = #f
For a lookup, the trajectory begins at the preceding checkpoint between zero and the requested time, takes fewer than checkpoint-every full steps, and then takes the usual final remainder step. It therefore preserves the fixed-RK4 numerical meaning of ode-flow-position without repeating a long prefix for each frame.
The field must be pure and stable for the lifetime of the prepared value. The library cannot determine whether an arbitrary Racket procedure’s captured state has changed.
When solver is #f, this is the established fixed-RK4 checkpoint trajectory. event is then rejected. With an adaptive-rk45? value, accepted Dormand–Prince endpoint positions and derivatives are stored instead. event, when supplied, truncates the actual supported range at its dense scalar root.
procedure
(ode-trajectory-time-range trajectory)
→ (cons/c finite-real? finite-real?) trajectory : ode-trajectory?
procedure
(ode-trajectory-step-size trajectory)
→ (or/c (and/c finite-real? positive?) false/c) trajectory : ode-trajectory?
procedure
(ode-trajectory-checkpoint-every trajectory)
→ (or/c exact-positive-integer? false/c) trajectory : ode-trajectory?
procedure
(ode-trajectory-solver trajectory)
→ (or/c 'fixed-rk4 adaptive-rk45?) trajectory : ode-trajectory?
procedure
(ode-trajectory-diagnostics trajectory)
→ (or/c false/c ode-trajectory-diagnostics?) trajectory : ode-trajectory?
procedure
(ode-trajectory-diagnostics? value) → boolean?
value : any/c
procedure
(ode-trajectory-position trajectory time) → vec2?
trajectory : ode-trajectory? time : finite-real?
procedure
(streamline-points field seed [ #:direction direction #:step-size step-size #:steps steps]) → (listof vec2?)
field :
(or/c (procedure-arity-includes/c 2) (procedure-arity-includes/c 3)) seed : vec2? direction : (or/c 'forward 'backward 'both) = 'both step-size : (and/c finite-real? positive?) = 1/20 steps : exact-positive-integer? = 120
procedure
(streamline axes field seed #:id id [ #:direction direction #:step-size step-size #:steps steps #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → path-visual? axes : axes-visual?
field :
(or/c (procedure-arity-includes/c 2) (procedure-arity-includes/c 3)) seed : vec2? id : symbol? direction : (or/c 'forward 'backward 'both) = 'both step-size : (and/c finite-real? positive?) = 1/20 steps : exact-positive-integer? = 120 opacity : opacity? = 1 stroke : any/c = "royalblue" stroke-width : (and/c finite-real? (>=/c 0)) = 2
procedure
(streamlines axes field seeds #:id id [ #:direction direction #:step-size step-size #:steps steps #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → group-visual? axes : axes-visual?
field :
(or/c (procedure-arity-includes/c 2) (procedure-arity-includes/c 3)) seeds : (listof vec2?) id : symbol? direction : (or/c 'forward 'backward 'both) = 'both step-size : (and/c finite-real? positive?) = 1/20 steps : exact-positive-integer? = 120 opacity : opacity? = 1 stroke : any/c = "royalblue" stroke-width : (and/c finite-real? (>=/c 0)) = 2
procedure
(flow-particle axes trajectory phase #:id id [ #:shape shape #:size size #:fill fill #:stroke stroke #:stroke-width stroke-width #:opacity opacity]) → derived-visual? axes : axes-visual? trajectory : ode-trajectory? phase : (or/c symbol? scene-parameter?) id : symbol? shape : point-marker-shape? = 'circle size : (and/c finite-real? positive?) = 1/5 fill : any/c = "crimson" stroke : any/c = "black" stroke-width : (and/c finite-real? (>=/c 0)) = 1 opacity : opacity? = 1
Before render-frames! creates frame workers, it samples all requested phase values and freezes the corresponding particle coordinates in an immutable table. The preparation pass walks each used checkpoint interval once, sharing full RK4 suffix steps among its selected times. Workers only read those coordinates; they never call the author field. Direct arbitrary-time scene sampling remains deterministic through ode-trajectory-position.
For an adaptive trajectory, the preparation pass instead reads its stored dense output directly; no numerical integration or field call occurs after the trajectory has been prepared.
procedure
(sample-function-path axes function [ #:x-min x-min #:x-max x-max #:sample-count sample-count #:clip? clip? #:max-jump max-jump #:detect-discontinuities? detect-discontinuities? #:interpolation interpolation]) → path-geometry? axes : axes-visual? function : (procedure-arity-includes/c 1) x-min : (or/c finite-real? false/c) = #f x-max : (or/c finite-real? false/c) = #f sample-count : (and/c exact-integer? (>=/c 2)) = 201 clip? : boolean? = #t
max-jump :
(or/c false/c (and/c finite-real? (>=/c 0))) = #f detect-discontinuities? : boolean? = #f interpolation : curve-interpolation? = 'linear
For a logarithmic x axis, spacing is uniform in the selected base-logarithm display coordinate instead. Thus a base-ten range from one through one thousand samples successive decades evenly. Explicit x-min and x-max bounds follow the same rule and must be strictly positive on a log axis.
All arguments are checked before function is called. When sampling completes without an error, the function is called exactly once for each sample x value. Sampling stops at the first invalid result or exception. Each call must return exactly one value. That value has these meanings:
A finite real is one numeric y sample.
#f is an explicit gap and breaks the current run.
Positive infinity, negative infinity, and NaN also create a gap.
Any other result raises an exception that reports the x value and the returned value.
Returning zero values or more than one value raises an exception that reports the x value and result count. An exception raised by function is not converted to a gap. It is reported together with the sample x value and the original exception message. This keeps programming errors separate from explicit discontinuities.
When max-jump is a number, two adjacent finite samples are connected only when the absolute difference between their numeric y values is no greater than that number. The threshold is applied before axes scaling and before clipping. The default #f performs no jump rejection. Use an explicit #f result when the location of a discontinuity is known.
When clip? is true, every accepted sample pair is clipped to the closed rectangle described by the axes x and y ranges. Clipping is performed on segments, so an intersection with a boundary becomes an exact path endpoint when exact arithmetic permits it. When an inexact coordinate difference would overflow, clipping temporarily uses the exact represented input values. When clip? is false, finite out-of-range samples remain in the path. Clipping does not decide whether a segment crossing the rectangle is a true discontinuity.
The interpolation argument controls the path segment kind as described by curve-interpolation?. Linear interpolation stores line segments. Smooth interpolation stores cubic Bézier segments through each accepted run. Breaks from non-finite values, explicit #f results, maximum-jump rejection, or clipping keep the runs separate.
When detect-discontinuities? is true, two adjacent samples that lie beyond opposite sides of the visible numeric y interval are treated as the hidden sides of a vertical asymptote and are not connected. This opt-in rule prevents clipping from drawing a false segment through the plot window while preserving the historical default behavior for steep continuous graphs.
The result contains zero or more open subpaths in sampling order. An isolated finite sample with no accepted adjacent pair does not create a point-only subpath. Every stored point uses the untransformed local coordinate system of axes. Numeric x is multiplied by axes-x-unit-length, and numeric y is multiplied by axes-y-unit-length. The axes translation, rotation, and scale are not applied to the returned geometry.
The sampling grid is deterministic. Exact bounds produce exact rational intermediate x values when ordinary exact arithmetic permits it. The result contains only immutable path geometry. Rendering it later does not call function again.
procedure
(function-graph axes function #:id id [ #:x-min x-min #:x-max x-max #:sample-count sample-count #:clip? clip? #:max-jump max-jump #:detect-discontinuities? detect-discontinuities? #:interpolation interpolation #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → path-visual? axes : axes-visual? function : (procedure-arity-includes/c 1) id : symbol? x-min : (or/c finite-real? false/c) = #f x-max : (or/c finite-real? false/c) = #f sample-count : (and/c exact-integer? (>=/c 2)) = 201 clip? : boolean? = #t
max-jump :
(or/c false/c (and/c finite-real? (>=/c 0))) = #f detect-discontinuities? : boolean? = #f interpolation : curve-interpolation? = 'linear opacity : opacity? = 1 stroke : any/c = "royalblue" stroke-width : (and/c finite-real? (>=/c 0)) = 3
The graph copies the current translation, rotation, and scale of axes. Its local geometry already uses the axes x and y unit lengths, so the graph and axes coincide at construction time even when the axes are translated, rotated, or non-uniformly scaled. This is a snapshot. Updating either immutable Visual later does not update the other. Put both in a group or apply matching animation requests when they should continue to move together.
The graph has no fill. The identity, opacity, and stroke width are checked before the numeric function is called. stroke and stroke-width are used by the ordinary path renderer. The result works with create, uncreate, path replacement, movement, rotation, non-uniform scaling, fading, groups, layout, and custom path renderers. There is no graph-specific renderer or timeline request.
procedure
(sample-adaptive-function-path axes function [ #:x-min x-min #:x-max x-max #:initial-sample-count initial-sample-count #:max-deviation max-deviation #:max-depth max-depth #:clip? clip? #:max-jump max-jump #:detect-discontinuities? detect-discontinuities? #:excluded-intervals excluded-intervals #:interpolation interpolation]) → path-geometry? axes : axes-visual? function : (procedure-arity-includes/c 1) x-min : (or/c finite-real? false/c) = #f x-max : (or/c finite-real? false/c) = #f initial-sample-count : (and/c exact-integer? (>=/c 2)) = 17 max-deviation : (and/c finite-real? (>=/c 0)) = 1/100 max-depth : (and/c exact-integer? (>=/c 0)) = 12 clip? : boolean? = #t max-jump : (or/c false/c (and/c finite-real? (>=/c 0))) = #f detect-discontinuities? : boolean? = #t excluded-intervals : list? = '() interpolation : curve-interpolation? = 'linear
The x-coordinate rule is the same as sample-function-path: linear axes use arithmetic interpolation and log axes use uniform logarithmic display interpolation. Callback values follow the same finite-real/#f/nonfinite rules, except that an exact numeric division-by-zero exception is treated as a gap. Other callback exceptions are reported with their x value.
With detect-discontinuities? true, an interval whose adjacent samples lie beyond opposite visible y boundaries is refined and ultimately broken, rather than clipped through the axes. max-jump adds an independent numeric y-distance break rule. excluded-intervals is a list of either (cons minimum maximum) or (list minimum maximum) values; each finite increasing interval splits the domain and no segment crosses its interior. Overlapping exclusions are merged deterministically.
The result uses the ordinary clipping and linear/smooth path interpolation machinery. It contains immutable axes-local geometry and retains neither the function nor adaptive evaluation cache. No finite initial grid can detect an oscillation that aliases every one of its samples; raise initial-sample-count for that case.
procedure
(adaptive-function-graph axes function #:id id [ #:x-min x-min #:x-max x-max #:initial-sample-count initial-sample-count #:max-deviation max-deviation #:max-depth max-depth #:clip? clip? #:max-jump max-jump #:detect-discontinuities? detect-discontinuities? #:excluded-intervals excluded-intervals #:interpolation interpolation #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → path-visual? axes : axes-visual? function : (procedure-arity-includes/c 1) id : symbol? x-min : (or/c finite-real? false/c) = #f x-max : (or/c finite-real? false/c) = #f initial-sample-count : (and/c exact-integer? (>=/c 2)) = 17 max-deviation : (and/c finite-real? (>=/c 0)) = 1/100 max-depth : (and/c exact-integer? (>=/c 0)) = 12 clip? : boolean? = #t max-jump : (or/c false/c (and/c finite-real? (>=/c 0))) = #f detect-discontinuities? : boolean? = #t excluded-intervals : list? = '() interpolation : curve-interpolation? = 'linear opacity : opacity? = 1 stroke : any/c = "royalblue" stroke-width : (and/c finite-real? (>=/c 0)) = 3
procedure
(derived-function-graph axes field #:id id [ #:x-min x-min #:x-max x-max #:sample-count sample-count #:clip? clip? #:max-jump max-jump #:detect-discontinuities? detect-discontinuities? #:interpolation interpolation #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → derived-visual? axes : axes-visual? field : (procedure-arity-includes/c 2) id : symbol? x-min : (or/c finite-real? false/c) = #f x-max : (or/c finite-real? false/c) = #f sample-count : (and/c exact-integer? (>=/c 2)) = 201 clip? : boolean? = #t
max-jump :
(or/c false/c (and/c finite-real? (>=/c 0))) = #f detect-discontinuities? : boolean? = #f interpolation : curve-interpolation? = 'linear opacity : opacity? = 1 stroke : any/c = "royalblue" stroke-width : (and/c finite-real? (>=/c 0)) = 3
For each resolved scene state, the field is sampled anew and produces one concrete path Visual with the requested identity, style, and axes transform. This permits an immutable parameter or another resolved Visual to drive a plot without a mutable updater. As with any derived-visual?, animate its source values or dependencies rather than applying a direct Visual animation to the derived graph.
19.26.1 Parametric Curves
struct
(struct parameter-range (start end) #:transparent) start : finite-real? end : finite-real?
start is the first parameter passed to a sampling procedure.
end is the last parameter passed to a sampling procedure.
The values must be distinct finite reals. The computed difference (- end start) must also remain a nonzero finite real. The order is significant. When start is greater than end, sampling proceeds in decreasing order.
The structure is immutable and transparent. Its public bindings include parameter-range, parameter-range?, both field accessors, and struct:parameter-range.
procedure
(sample-parametric-path axes function [ #:parameter-range domain #:sample-count sample-count #:clip? clip? #:max-distance max-distance #:interpolation interpolation]) → path-geometry? axes : axes-visual? function : (procedure-arity-includes/c 1) domain : parameter-range? = (parameter-range 0 1) sample-count : (and/c exact-integer? (>=/c 2)) = 201 clip? : boolean? = #t
max-distance :
(or/c false/c (and/c finite-real? (>=/c 0))) = #f interpolation : curve-interpolation? = 'linear
All arguments are checked before function is called. Each sampling call must return exactly one value:
A vec2 is one finite numeric coordinate.
#f is an explicit gap.
Another value, zero values, or multiple values raise an exception that reports the parameter value. An exception from function is reported with the same parameter and the original exception message. Sampling stops at the first error.
When max-distance is a number, two adjacent coordinates are connected only when their Euclidean distance in numeric-coordinate units is no greater than that number. The distance is measured before independent axes scaling. The default #f applies no distance rejection.
Clipping and interpolation follow the common rules described above. The result contains axes-local open subpaths and does not retain function or domain. Empty runs and isolated finite coordinates produce no drawn segment.
procedure
(parametric-curve axes function #:id id [ #:parameter-range domain #:sample-count sample-count #:clip? clip? #:max-distance max-distance #:interpolation interpolation #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → path-visual? axes : axes-visual? function : (procedure-arity-includes/c 1) id : symbol? domain : parameter-range? = (parameter-range 0 1) sample-count : (and/c exact-integer? (>=/c 2)) = 201 clip? : boolean? = #t
max-distance :
(or/c false/c (and/c finite-real? (>=/c 0))) = #f interpolation : curve-interpolation? = 'linear opacity : opacity? = 1 stroke : any/c = "royalblue" stroke-width : (and/c finite-real? (>=/c 0)) = 3
The returned path has no fill and copies the current axes translation, rotation, and scale. This is a construction-time snapshot, not a live link. The result can use every operation available to an ordinary path Visual, including create, uncreate, morphing, affine animation, opacity, groups, and renderer-aware layout.
19.26.2 Ordered Data Plots
procedure
(data-series-path axes points [ #:clip? clip? #:max-distance max-distance #:interpolation interpolation]) → path-geometry? axes : axes-visual? points : (listof (or/c vec2? false/c)) clip? : boolean? = #t
max-distance :
(or/c false/c (and/c finite-real? (>=/c 0))) = #f interpolation : curve-interpolation? = 'linear
List order is traversal order. The procedure does not sort by x, infer time order, remove repeated coordinates, or retain the input list. An empty list, a one-point list, or a finite coordinate isolated by gaps produces no drawn segment.
The max-distance, clip?, and interpolation arguments have the same meanings as for sample-parametric-path. Distance is Euclidean in numeric-coordinate units. The result contains only immutable path geometry.
procedure
(data-plot axes points #:id id [ #:clip? clip? #:max-distance max-distance #:interpolation interpolation #:opacity opacity #:stroke stroke #:stroke-width stroke-width]) → path-visual? axes : axes-visual? points : (listof (or/c vec2? false/c)) id : symbol? clip? : boolean? = #t
max-distance :
(or/c false/c (and/c finite-real? (>=/c 0))) = #f interpolation : curve-interpolation? = 'linear opacity : opacity? = 1 stroke : any/c = "seagreen" stroke-width : (and/c finite-real? (>=/c 0)) = 3
The returned path has no fill and copies the axes translation, rotation, and scale at construction time. It works with ordinary path rendering, creation, removal, morphing, movement, rotation, non-uniform scaling, fading, groups, and relative layout.
19.27 Group Visuals
A group is a semantic composite. Its children are stored as ordinary Visual values, not as Picts. The child list is significant back-to-front order. Child positions are local to the group anchor.
All children must implement both gen:visual and gen:affine-visual. Circles, rectangles, paths, function graphs, parametric curves, data plots, arrows, axes, plain text, formulas, and groups all satisfy this requirement. A child may itself be a group. Direct siblings must have distinct identities, and a group identity may not occur anywhere below that group. The same child identity may be reused in separate nested branches; its complete Visual path identifies it unambiguously. A custom affine Visual is treated as one leaf because there is no public protocol for inspecting children hidden inside it.
procedure
(group children #:id id [ #:center center #:rotation rotation #:scale scale #:opacity opacity]) → group-visual? children : (listof (and/c visual? affine-visual?)) id : symbol? center : vec2? = origin rotation : finite-real? = 0 scale : scale-factor? = 1 opacity : opacity? = 1
The center value places the group anchor in its containing coordinate system. At the top level this is a world-space point. In a parent group it is a local point. Each child’s existing reference position is interpreted in the group’s local coordinates.
The group scale may be a positive finite scalar or a positive vec2, but its normalized x and y components must be equal. This uniform-scale restriction lets parent transforms compose exactly with rotated children without introducing shear. The group may be rotated by any finite angle.
The group opacity is applied to the complete composed result. Child opacity is applied first, so opacity is inherited multiplicatively through nested groups.
The constructor rejects a non-affine child, a nonsymbol child identity, a repeated identity anywhere in the built-in group tree, or a descendant whose identity equals id. For a custom affine child, its reported position must agree with the translation in its reported affine transform.
Nested children are addressed by nonempty paths such as '(parent child) for scene-state lookup and compatible animation requests. Their identity remains local to the containing group, so a bare child symbol is not a top-level scene identity.
procedure
(group-visual? value) → boolean?
value : any/c
procedure
(group-visual-children group)
→ (listof (and/c visual? affine-visual?)) group : group-visual?
procedure
(group-visual-with-children group children) → group-visual?
group : group-visual? children : (listof (and/c visual? affine-visual?))
19.28 First-Class Relation Visuals
A relation is an immutable Visual whose concrete geometry is recomputed from explicit dependencies in each sampled scene state. It replaces the former split between pure endpoint geometry and renderer-aware endpoint wrappers. No relation uses a mutable updater or needs the preceding frame.
procedure
(relation-visual template [ #:depends-on dependencies #:phase phase #:structure structure #:space space #:cache-key cache-key] resolver) → relation-visual? template : visual? dependencies : (listof relation-dependency?) = '() phase : (or/c 'semantic 'layout) = 'semantic structure : (or/c 'root-only 'fixed) = 'root-only space : (or/c 'world 'local) = 'world cache-key : any/c = #f resolver : (-> relation-context? visual? visual?)
The ordinary movement, rotation, scale, opacity, fill, stroke, and stroke-width controls form an outer envelope. They are applied after the resolver has computed the current geometry, so a relation may be animated concurrently with its own changing dependencies. A requested style must be supported by both the template and the concrete result. A 'fixed relation preserves the template’s complete child-ID tree and may expose nested paths; a 'root-only relation deliberately exposes only its root ID.
A 'semantic relation is resolved from model data. A 'layout relation may use relation-context-anchor-ref, relation-context-layout-box, or selection boxes after the active renderer has measured its targets. Layout relations are currently top-level only, and their measurements are complete Pict boxes rather than tight visible outlines.
procedure
(relation-context-layout-box context visual) → layout-box? context : relation-context? visual : visual?
procedure
(relation-visual? value) → boolean?
value : any/c
procedure
(relation-visual-dependencies relation)
→ (listof relation-dependency?) relation : relation-visual?
procedure
(relation-visual-cacheability relation)
→ (or/c 'serializable 'explicit-key 'disabled) relation : relation-visual?
procedure
(relation-dependency? value) → boolean?
value : any/c
procedure
(relation-context? value) → boolean?
value : any/c
procedure
(relation-context-anchor-ref context target anchor) → vec2? context : relation-context? target : (or/c visual? symbol? visual-path?) anchor : symbol?
procedure
(value-dependency target) → relation-dependency?
target : (or/c symbol? scene-parameter?)
procedure
(visual-dependency target) → relation-dependency?
target : (or/c visual? symbol? visual-path?)
procedure
(anchor-dependency target anchor) → relation-dependency?
target : (or/c visual? symbol? visual-path?) anchor : symbol?
procedure
(selection-dependency selection) → relation-dependency?
selection : visual-selection?
procedure
(scene-validate-relations state) → immutable-hash?
state : scene-state?
procedure
(scene-relation-report state [target]) → any/c
state : scene-state? target : (or/c #f visual? symbol? visual-path?) = #f
procedure
(scene-relation-sample-report state [target]) → any/c
state : scene-state? target : (or/c #f visual? symbol? visual-path?) = #f
For a 'layout relation those fields are #f. Determining its actual reads requires the active renderer’s measured layout boxes, which this headless report intentionally does not invent.
Built-in line-between, arrow-between, ray-from, parameter-display, and follow-anchor use serializable relation specifications. The live angle, brace, and curved-arrow constructors use generic relations because their builder procedure is author-specific; therefore they deliberately do not claim automatic persistent-cache reuse.
procedure
(follow-anchor content target [ #:offset offset #:target-anchor target-anchor #:self-anchor self-anchor]) → relation-visual? content : visual? target : (or/c visual? symbol? visual-path?) offset : vec2? = origin
target-anchor :
(or/c 'bottom-left 'bottom 'bottom-right 'left 'center 'right 'top-left 'top 'top-right) = 'center
self-anchor :
(or/c 'bottom-left 'bottom 'bottom-right 'left 'center 'right 'top-left 'top 'top-right) = 'center
The content must be a concrete, non-frame-space Visual, and content and target must have distinct identities. Attachments may be animated through the normal relation envelope. One attachment may target another when the resulting relation graph is acyclic; they neither avoid other labels nor inherit target rotation. Layout attachments are top-level and renderer-dependent; a semantic centre attachment can be queried directly from a sampled scene state.
19.28.1 Acyclic Live Layout
SCENE-DE gives the renderer-aware attachment model concise relationship names. Their relation graph is resolved from a concrete target outward at each render. A direct or indirect cycle raises an exception; no prior frame is consulted.
procedure
(follow-above content target [#:gap gap]) → relation-visual?
content : visual? target : (or/c visual? symbol? visual-path?) gap : (and/c finite-real? (>=/c 0)) = 0
procedure
(follow-below content target [#:gap gap]) → relation-visual?
content : visual? target : (or/c visual? symbol? visual-path?) gap : (and/c finite-real? (>=/c 0)) = 0
procedure
(follow-left-of content target [#:gap gap]) → relation-visual?
content : visual? target : (or/c visual? symbol? visual-path?) gap : (and/c finite-real? (>=/c 0)) = 0
procedure
(follow-right-of content target [#:gap gap]) → relation-visual?
content : visual? target : (or/c visual? symbol? visual-path?) gap : (and/c finite-real? (>=/c 0)) = 0