http://mathling.com/art/meanders  library module

http://mathling.com/art/meanders


Meanders: smooth meanders
Regular meander:
θ(s) = Σ[i=1:N](A[i] sin( (2π/λ[i])s + φ[i] )
θ = tangential angle, s=arc length
For set of A,λ,φ; let's take φ[i]=0
(x,y) = (∫[0:s] cos(θ(s)), ∫[0:s] sin(θ(s))

Looping meander()
dθ(s) = sΣ[i=1:N](A[i] sin( (2π/λ[i])s + φ[i] )
For set of A,λ,φ; let's take φ[i]=0
(x,y) = (∫[0:s] cos(dθ(s)), ∫[0:s] sin(dθ(s))
dθ(s) = dΣ[i=1:N](A[i] sin( (2π/λ[i])s ))
= Σ[i=1:N] d((A[i] sin( (2π/λ[i])s )))
= Σ[i=1:N] A[i] d((sin( (2π/λ[i])s )))
= Σ[i=1:N] A[i] (2π/λ[i]) cos( (2π/λ[i])s )

Example: Plot a random smooth meander in upper left of canvas

let $box := box:box(0, 0, box:width($canvas) div 2, box:height($canvas) div 2): let $path := meanders:meander($box, $randomizers, $parameters)
return draw:draw($path, map {"stroke":"blue","fill":"none","width":5})

Parameters:

Randomizers:
meanders.A.n: terms in the meander function; higher=more complex
meanders.A.initial: A1
meanders.A.ratio: A2/A1
meanders.A.fade: decay rate for A3...An
meanders.λ.initial: λ1
meanders.λ.ratio: λ2/λ1
meanders.λ.fade: decay rate for λ3...λn
meanders.extent: extent of curve to draw
meanders.rotation: rotation to apply to curve

Rendering parameters:

Copyright© Mary Holstege 2020-2023
CC-BY (https://creativecommons.org/licenses/by/4.0/)

mmm 2023
Status: Bleeding edge

Imports

http://mathling.com/art/components
import module namespace components="http://mathling.com/art/components"
       at "../art/components.xqy"
http://mathling.com/type/distribution
import module namespace dist="http://mathling.com/type/distribution"
       at "../types/distributions.xqy"
http://mathling.com/type/wrapper
import module namespace wrapper="http://mathling.com/type/wrapper"
       at "../types/wrapper.xqy"
http://mathling.com/geometric/rectangle
import module namespace box="http://mathling.com/geometric/rectangle"
       at "../geo/rectangle.xqy"
http://mathling.com/geometric/spline
import module namespace spline="http://mathling.com/geometric/spline"
       at "../geo/spline.xqy"
http://mathling.com/geometric
import module namespace geom="http://mathling.com/geometric"
       at "../geo/euclidean.xqy"
http://mathling.com/geometric/point
import module namespace point="http://mathling.com/geometric/point"
       at "../geo/point.xqy"
http://mathling.com/core/random
import module namespace rand="http://mathling.com/core/random"
       at "../core/random.xqy"
http://mathling.com/art/core
import module namespace core="http://mathling.com/art/core"
       at "../art/core.xqy"
http://mathling.com/geometric/path
import module namespace path="http://mathling.com/geometric/path"
       at "../geo/path.xqy"
http://mathling.com/core/utilities
import module namespace util="http://mathling.com/core/utilities"
       at "../core/utilities.xqy"
http://mathling.com/shape/paths
import module namespace paths="http://mathling.com/shape/paths"
       at "../shapes/paths.xqy"
http://mathling.com/shape
import module namespace shapes="http://mathling.com/shape"
       at "../shapes/shapes.xqy"
http://mathling.com/core/errors
import module namespace errors="http://mathling.com/core/errors"
       at "../core/errors.xqy"

Functions

Function: components
declare function components() as map(xs:string, map(xs:string,item()*))


Standard callback: Subcomponent map.
Example. render=true() and mode="default" are the defaults if unspecified
map {
"example":
map {
"namespace": "http://mathling.com/art/example",
"render": true(),
"mode": "default"
}
}

Returns
  • map(xs:string,map(xs:string,item()*))
declare function this:components() as map(xs:string, map(xs:string,item()*))
{
  components:expand(
    map {
    },
    this:lookup#2
  )
}

Function: rendering-parameters
declare function rendering-parameters($canvas as map(xs:string,item()*), $algorithm-parameters as map(xs:string,item()*)) as map(xs:string,item()*)


Standard callback: Set of rendering parameters.

Params
  • canvas as map(xs:string,item()*): drawing canvas
  • algorithm-parameters as map(xs:string,item()*): set of algorithm parameters
Returns
  • map(xs:string,item()*)
declare function this:rendering-parameters(
  $canvas as map(xs:string,item()*),
  $algorithm-parameters as map(xs:string,item()*)
) as map(xs:string,item()*)
{
  map {
  }
}

Function: algorithm-mode-parameters
declare function algorithm-mode-parameters($mode as xs:string, $resolution as xs:string, $canvas as map(xs:string,item()*)) as map(xs:string,item()*)


Standard callback: Set of algorithm parameters specific to a particular mode.

Params
  • mode as xs:string: selected mode
  • resolution as xs:string: defined resolution
  • canvas as map(xs:string,item()*): drawing canvas
Returns
  • map(xs:string,item()*)
declare function this:algorithm-mode-parameters(
  $mode as xs:string,
  $resolution as xs:string,
  $canvas as map(xs:string,item()*)
) as map(xs:string,item()*)
{
  switch ($mode)
  case "default" return map {}
  default return errors:error("ML-BADPARMS", ("mode", $mode))
}

Function: algorithm-parameters
declare function algorithm-parameters($resolution as xs:string, $canvas as map(xs:string, item()*)) as map(xs:string,item()*)


Standard callback: Set of default algorithm parameters.

Params
  • resolution as xs:string: defined resolution
  • canvas as map(xs:string,item()*): drawing canvas
Returns
  • map(xs:string,item()*)
declare function this:algorithm-parameters(
  $resolution as xs:string,
  $canvas as map(xs:string, item()*)
) as map(xs:string,item()*)
{
  map {
  }
}

Function: randomizers
declare function randomizers($canvas as map(xs:string,item()*), $parameters as map(xs:string,item()*)) as map(xs:string,item()*)


Standard callback: Set of randomizers.

Params
  • canvas as map(xs:string,item()*): drawing canvas
  • parameters as map(xs:string,item()*): algorithm parameter bundle
Returns
  • map(xs:string,item()*)
declare function this:randomizers(
  $canvas as map(xs:string,item()*),
  $parameters as map(xs:string,item()*)
) as map(xs:string,item()*)
{
  map {
    "meanders.A.n": dist:binomial(4, 30),
    "meanders.A.initial": dist:uniform(1.5, 2.2)=>dist:cast("decimal"),
    "meanders.A.ratio": dist:uniform(0.15, 0.25)=>dist:cast("decimal"),
    "meanders.A.fade": dist:normal(1.0, 0.2)=>dist:min(0.3)=>dist:max(3.0)=>dist:cast("decimal"),
    "meanders.λ.initial": dist:constant(1.0)=>dist:cast("decimal"),
    "meanders.λ.ratio": dist:uniform(0.15, 0.55)=>dist:cast("decimal"),
    "meanders.λ.fade": dist:normal(1.0, 0.5)=>dist:min(0.1)=>dist:max(3.0)=>dist:cast("decimal"),
    "meanders.extent": dist:normal(2.0, 1.0)=>dist:min(0.25)=>dist:cast("decimal"),
    "meanders.rotation": $rand:STD-DEGREES=>dist:cast("decimal")
  }
}

Function: colophon
declare function colophon($parameters as map(xs:string,item()*)) as xs:string?


Standard callback: component colophon (string attached to signature).

Params
  • parameters as map(xs:string,item()*): algorithm parameter bundle
Returns
  • xs:string?
declare function this:colophon($parameters as map(xs:string,item()*)) as xs:string?
{
  "Smooth meanders: θ(s) = Σ[i=1:N](A[i] sin(2π/λ[i])s);
Looping meanders: dθ(s) = sΣ[i=1:N](A[i] sin(2π/λ[i])s);
θ=tangential angle; plotted via Whewell equation
"
}

Function: metadata
declare function metadata($canvas as map(xs:string,item()*), $randomizers as map(xs:string,item()*), $parameters as map(xs:string,item()*))


Standard callback: component metadata (in addition to dump of randomizers
and parameters)

Params
  • canvas as map(xs:string,item()*): drawing canvas
  • randomizers as map(xs:string,item()*): active randomizers for component
  • parameters as map(xs:string,item()*): active parameters for component
declare function this:metadata(
  $canvas as map(xs:string,item()*),
  $randomizers as map(xs:string,item()*),
  $parameters as map(xs:string,item()*)
)
{
  ()
}

Function: meander
declare function meander($canvas as map(xs:string, item()*), $randomizers as map(xs:string, item()*), $parameters as map(xs:string, item()*))


meander()
Construct a random smooth meander that fits the canvas.

$canvas: space for meander; it will be centered and scaled to this space
$randomizers: randomizers to use
$parameters: parameters to use

Params
  • canvas as map(xs:string,item()*)
  • randomizers as map(xs:string,item()*)
  • parameters as map(xs:string,item()*)
declare function this:meander(
  $canvas as map(xs:string, item()*),
  $randomizers as map(xs:string, item()*),
  $parameters as map(xs:string, item()*)
)
{
  let $dynamics :=
    core:random-parameters(
      core:random-parameter-map((
        "meanders.A.n",
        "meanders.A.initial", "meanders.A.ratio", "meanders.A.fade",
        "meanders.λ.initial", "meanders.λ.ratio", "meanders.λ.fade",
        "meanders.extent", "meanders.rotation"
      )),
      $randomizers
    )
  let $n := core:parameter("meanders.A.n", $dynamics)
  let $A-initial := core:parameter("meanders.A.initial", $dynamics)
  let $A-ratio := core:parameter("meanders.A.ratio", $dynamics)
  let $A-fade := core:parameter("meanders.A.fade", $dynamics)
  let $λ-initial := core:parameter("meanders.λ.initial", $dynamics)
  let $λ-ratio := core:parameter("meanders.λ.ratio", $dynamics)
  let $λ-fade := core:parameter("meanders.λ.fade", $dynamics)
  let $As := ($A-initial, reverse(tail(util:powspace($n, $A-fade)))!util:decimal(. * $A-ratio, 2))
  let $λs := ($λ-initial, reverse(tail(util:powspace($n, $λ-fade)))!util:decimal(. * $λ-ratio, 2))
  let $extent := core:parameter("meanders.extent", $dynamics)
  let $rotation := core:parameter("meanders.rotation", $dynamics)
  let $center := box:center($canvas)
  let $radius := min((box:width($canvas), box:height($canvas))) div 2
  let $n-points := util:round(box:diagonal($canvas) * 2)
  let $stroke := (
    paths:meander(
      $center, $radius,
      $As, $λs,
      $n-points, $extent, false()
    )=>geom:decimal(1)=>spline:spline()=>
      geom:mutate(function($p) {$p=>point:as-dimension(2)})=>
      geom:rotate($rotation, $center)=>
      shapes:scale-and-center($canvas)=>
      path:with-edge-ts()
  )
  return (
    wrapper:wrapper(
      $stroke,
      $dynamics
    )
  )
}

Function: looping-meander
declare function looping-meander($canvas as map(xs:string, item()*), $randomizers as map(xs:string, item()*), $parameters as map(xs:string, item()*))


looping-meander()
Construct a random smooth looping meander that fits the canvas.

$canvas: space for meander; it will be centered and scaled to this space
$randomizers: randomizers to use
$parameters: parameters to use

Params
  • canvas as map(xs:string,item()*)
  • randomizers as map(xs:string,item()*)
  • parameters as map(xs:string,item()*)
declare function this:looping-meander(
  $canvas as map(xs:string, item()*),
  $randomizers as map(xs:string, item()*),
  $parameters as map(xs:string, item()*)
)
{
  let $dynamics :=
    core:random-parameters(
      core:random-parameter-map((
        "meanders.A.n",
        "meanders.A.initial", "meanders.A.ratio", "meanders.A.fade",
        "meanders.λ.initial", "meanders.λ.ratio", "meanders.λ.fade",
        "meanders.extent", "meanders.rotation"
      )),
      $randomizers
    )
  let $n := core:parameter("meanders.A.n", $dynamics)
  let $A-initial := core:parameter("meanders.A.initial", $dynamics)
  let $A-ratio := core:parameter("meanders.A.ratio", $dynamics)
  let $A-fade := core:parameter("meanders.A.fade", $dynamics)
  let $λ-ratio := core:parameter("meanders.λ.ratio", $dynamics)
  let $λ-initial := core:parameter("meanders.λ.initial", $dynamics)
  let $λ-fade := core:parameter("meanders.λ.fade", $dynamics)
  let $As := ($A-initial, reverse(tail(util:powspace($n, $A-fade)))!util:decimal(. * $A-ratio, 2))
  let $λs := ($λ-initial, reverse(tail(util:powspace($n, $λ-fade)))!util:decimal(. * $λ-ratio, 2))
  let $extent := core:parameter("meanders.extent", $dynamics)
  let $rotation := core:parameter("meanders.rotation", $dynamics)
  let $center := box:center($canvas)
  let $radius := min((box:width($canvas), box:height($canvas))) div 2
  let $n-points := util:round(box:diagonal($canvas) * 2)
  let $stroke := (
    paths:looping-meander(
      $center, $radius,
      $As, $λs,
      $n-points, $extent, false()
    )=>geom:decimal(1)=>spline:spline()=>
      geom:mutate(function($p) {$p=>point:as-dimension(2)})=>
      geom:rotate($rotation, $center)=>
      shapes:scale-and-center($canvas)=>
      path:with-edge-ts()
  )
  return (
    wrapper:wrapper(
      $stroke,
      $dynamics
    )
  )
}

Original Source Code

xquery version "3.1";
(:~
 : Meanders: smooth meanders
 : Regular meander:
 : θ(s) = Σ[i=1:N](A[i] sin( (2π/λ[i])s + φ[i] )
 : θ = tangential angle, s=arc length
 : For set of A,λ,φ; let's take φ[i]=0
 : (x,y) = (∫[0:s] cos(θ(s)), ∫[0:s] sin(θ(s))
 :
 : Looping meander()
 : dθ(s) = sΣ[i=1:N](A[i] sin( (2π/λ[i])s  + φ[i] )
 : For set of A,λ,φ; let's take φ[i]=0
 : (x,y) = (∫[0:s] cos(dθ(s)), ∫[0:s] sin(dθ(s))
 : dθ(s) = dΣ[i=1:N](A[i] sin( (2π/λ[i])s ))
 :       = Σ[i=1:N] d((A[i] sin( (2π/λ[i])s )))
 :       = Σ[i=1:N] A[i] d((sin( (2π/λ[i])s )))
 :       = Σ[i=1:N] A[i] (2π/λ[i]) cos( (2π/λ[i])s )
 :
 : Example: Plot a random smooth meander in upper left of canvas
 :
 : let $box := box:box(0, 0, box:width($canvas) div 2, box:height($canvas) div 2): let $path := meanders:meander($box, $randomizers, $parameters)
 : return draw:draw($path, map {"stroke":"blue","fill":"none","width":5})
 :
 : Parameters:
 :
 : Randomizers:
 : meanders.A.n: terms in the meander function; higher=more complex
 : meanders.A.initial: A1
 : meanders.A.ratio: A2/A1
 : meanders.A.fade: decay rate for A3...An
 : meanders.λ.initial: λ1
 : meanders.λ.ratio: λ2/λ1
 : meanders.λ.fade: decay rate for λ3...λn
 : meanders.extent: extent of curve to draw
 : meanders.rotation: rotation to apply to curve
 :
 : Rendering parameters:
 :
 : Copyright© Mary Holstege 2020-2023
 : CC-BY (https://creativecommons.org/licenses/by/4.0/)
 : @since mmm 2023
 : @custom:Status Bleeding edge
 :)
module namespace this="http://mathling.com/art/meanders";

import module namespace core="http://mathling.com/art/core"
       at "../art/core.xqy";
import module namespace components="http://mathling.com/art/components"
       at "../art/components.xqy";
import module namespace errors="http://mathling.com/core/errors"
       at "../core/errors.xqy";
import module namespace rand="http://mathling.com/core/random"
       at "../core/random.xqy";
import module namespace dist="http://mathling.com/type/distribution"
       at "../types/distributions.xqy";
import module namespace wrapper="http://mathling.com/type/wrapper"
       at "../types/wrapper.xqy";
import module namespace util="http://mathling.com/core/utilities"
       at "../core/utilities.xqy";
import module namespace geom="http://mathling.com/geometric"
       at "../geo/euclidean.xqy";
import module namespace point="http://mathling.com/geometric/point"
       at "../geo/point.xqy";
import module namespace box="http://mathling.com/geometric/rectangle"
       at "../geo/rectangle.xqy";
import module namespace spline="http://mathling.com/geometric/spline"
       at "../geo/spline.xqy";
import module namespace path="http://mathling.com/geometric/path"
       at "../geo/path.xqy";
import module namespace paths="http://mathling.com/shape/paths"
       at "../shapes/paths.xqy";
import module namespace shapes="http://mathling.com/shape"
       at "../shapes/shapes.xqy";

declare namespace svg="http://www.w3.org/2000/svg";
declare namespace map="http://www.w3.org/2005/xpath-functions/map";
declare namespace math="http://www.w3.org/2005/xpath-functions/math";
declare namespace art="http://mathling.com/art";

(:~
 : In XQuery this is pointless. It is here for single-sourcing, because in
 : XSLT we have to make sure the lookup happens in the right context. So:
 : a bit of a hack, to be sure.
 :)
declare %private function this:lookup($qname as xs:QName, $n as xs:integer) as function(*)?
{
  function-lookup($qname, $n)
};

(:~
 : Standard callback: Subcomponent map.
 : Example. render=true() and mode="default" are the defaults if unspecified
 : map {
 :   "example":
 :     map {
 :       "namespace": "http://mathling.com/art/example",
 :       "render": true(),
 :       "mode": "default"
 :     }
 : }
 :)
declare function this:components() as map(xs:string, map(xs:string,item()*))
{
  components:expand(
    map {
    },
    this:lookup#2
  )
};

(:~
 : Standard callback: Set of rendering parameters.
 : @param $canvas: drawing canvas
 : @param $algorithm-parameters: set of algorithm parameters
 :)
declare function this:rendering-parameters(
  $canvas as map(xs:string,item()*),
  $algorithm-parameters as map(xs:string,item()*)
) as map(xs:string,item()*)
{
  map {
  }
};

(:~
 : Standard callback: Set of algorithm parameters specific to a particular mode.
 : @param $mode: selected mode
 : @param $resolution: defined resolution
 : @param $canvas: drawing canvas
 :)
declare function this:algorithm-mode-parameters(
  $mode as xs:string,
  $resolution as xs:string,
  $canvas as map(xs:string,item()*)
) as map(xs:string,item()*)
{
  switch ($mode)
  case "default" return map {}
  default return errors:error("ML-BADPARMS", ("mode", $mode))
};

(:~
 : Standard callback: Set of default algorithm parameters.
 : @param $resolution: defined resolution
 : @param $canvas: drawing canvas
 :)
declare function this:algorithm-parameters(
  $resolution as xs:string,
  $canvas as map(xs:string, item()*)
) as map(xs:string,item()*)
{
  map {
  }
};

(:~
 : Standard callback: Set of randomizers.
 : @param $canvas: drawing canvas
 : @param $parameters: algorithm parameter bundle
 :)
declare function this:randomizers(
  $canvas as map(xs:string,item()*),
  $parameters as map(xs:string,item()*)
) as map(xs:string,item()*)
{
  map {
    "meanders.A.n": dist:binomial(4, 30),
    "meanders.A.initial": dist:uniform(1.5, 2.2)=>dist:cast("decimal"),
    "meanders.A.ratio": dist:uniform(0.15, 0.25)=>dist:cast("decimal"),
    "meanders.A.fade": dist:normal(1.0, 0.2)=>dist:min(0.3)=>dist:max(3.0)=>dist:cast("decimal"),
    "meanders.λ.initial": dist:constant(1.0)=>dist:cast("decimal"),
    "meanders.λ.ratio": dist:uniform(0.15, 0.55)=>dist:cast("decimal"),
    "meanders.λ.fade": dist:normal(1.0, 0.5)=>dist:min(0.1)=>dist:max(3.0)=>dist:cast("decimal"),
    "meanders.extent": dist:normal(2.0, 1.0)=>dist:min(0.25)=>dist:cast("decimal"),
    "meanders.rotation": $rand:STD-DEGREES=>dist:cast("decimal")
  }
};

(:~
 : Standard callback: component colophon (string attached to signature).
 : @param $parameters: algorithm parameter bundle
 :)
declare function this:colophon($parameters as map(xs:string,item()*)) as xs:string?
{
  "Smooth meanders: θ(s) = Σ[i=1:N](A[i] sin(2π/λ[i])s);
Looping meanders: dθ(s) = sΣ[i=1:N](A[i] sin(2π/λ[i])s);
θ=tangential angle; plotted via Whewell equation
"
};

(:~
 : Standard callback: component metadata (in addition to dump of randomizers
 : and parameters)
 :
 : @param $canvas: drawing canvas
 : @param $randomizers: active randomizers for component
 : @param $parameters: active parameters for component
 :)
declare function this:metadata(
  $canvas as map(xs:string,item()*),
  $randomizers as map(xs:string,item()*),
  $parameters as map(xs:string,item()*)
)
{
  ()
};

(:======================================================================:)

(:~
 : meander()
 : Construct a random smooth meander that fits the canvas.
 :
 : $canvas: space for meander; it will be centered and scaled to this space
 : $randomizers: randomizers to use
 : $parameters: parameters to use
 : @return Scaled spline wrapped with dynamic parameters
 :)
declare function this:meander(
  $canvas as map(xs:string, item()*),
  $randomizers as map(xs:string, item()*),
  $parameters as map(xs:string, item()*)
)
{
  let $dynamics :=
    core:random-parameters(
      core:random-parameter-map((
        "meanders.A.n",
        "meanders.A.initial", "meanders.A.ratio", "meanders.A.fade",
        "meanders.λ.initial", "meanders.λ.ratio", "meanders.λ.fade",
        "meanders.extent", "meanders.rotation"
      )),
      $randomizers
    )
  let $n := core:parameter("meanders.A.n", $dynamics)
  let $A-initial := core:parameter("meanders.A.initial", $dynamics)
  let $A-ratio := core:parameter("meanders.A.ratio", $dynamics)
  let $A-fade := core:parameter("meanders.A.fade", $dynamics)
  let $λ-initial := core:parameter("meanders.λ.initial", $dynamics)
  let $λ-ratio := core:parameter("meanders.λ.ratio", $dynamics)
  let $λ-fade := core:parameter("meanders.λ.fade", $dynamics)
  let $As := ($A-initial, reverse(tail(util:powspace($n, $A-fade)))!util:decimal(. * $A-ratio, 2))
  let $λs := ($λ-initial, reverse(tail(util:powspace($n, $λ-fade)))!util:decimal(. * $λ-ratio, 2))
  let $extent := core:parameter("meanders.extent", $dynamics)
  let $rotation := core:parameter("meanders.rotation", $dynamics)
  let $center := box:center($canvas)
  let $radius := min((box:width($canvas), box:height($canvas))) div 2
  let $n-points := util:round(box:diagonal($canvas) * 2)
  let $stroke := (
    paths:meander(
      $center, $radius,
      $As, $λs,
      $n-points, $extent, false()
    )=>geom:decimal(1)=>spline:spline()=>
      geom:mutate(function($p) {$p=>point:as-dimension(2)})=>
      geom:rotate($rotation, $center)=>
      shapes:scale-and-center($canvas)=>
      path:with-edge-ts()
  )
  return (
    wrapper:wrapper(
      $stroke,
      $dynamics
    )
  )
};

(:~
 : looping-meander()
 : Construct a random smooth looping meander that fits the canvas.
 :
 : $canvas: space for meander; it will be centered and scaled to this space
 : $randomizers: randomizers to use
 : $parameters: parameters to use
 : @return Scaled spline wrapped with dynamic parameters
 :)
declare function this:looping-meander(
  $canvas as map(xs:string, item()*),
  $randomizers as map(xs:string, item()*),
  $parameters as map(xs:string, item()*)
)
{
  let $dynamics :=
    core:random-parameters(
      core:random-parameter-map((
        "meanders.A.n",
        "meanders.A.initial", "meanders.A.ratio", "meanders.A.fade",
        "meanders.λ.initial", "meanders.λ.ratio", "meanders.λ.fade",
        "meanders.extent", "meanders.rotation"
      )),
      $randomizers
    )
  let $n := core:parameter("meanders.A.n", $dynamics)
  let $A-initial := core:parameter("meanders.A.initial", $dynamics)
  let $A-ratio := core:parameter("meanders.A.ratio", $dynamics)
  let $A-fade := core:parameter("meanders.A.fade", $dynamics)
  let $λ-ratio := core:parameter("meanders.λ.ratio", $dynamics)
  let $λ-initial := core:parameter("meanders.λ.initial", $dynamics)
  let $λ-fade := core:parameter("meanders.λ.fade", $dynamics)
  let $As := ($A-initial, reverse(tail(util:powspace($n, $A-fade)))!util:decimal(. * $A-ratio, 2))
  let $λs := ($λ-initial, reverse(tail(util:powspace($n, $λ-fade)))!util:decimal(. * $λ-ratio, 2))
  let $extent := core:parameter("meanders.extent", $dynamics)
  let $rotation := core:parameter("meanders.rotation", $dynamics)
  let $center := box:center($canvas)
  let $radius := min((box:width($canvas), box:height($canvas))) div 2
  let $n-points := util:round(box:diagonal($canvas) * 2)
  let $stroke := (
    paths:looping-meander(
      $center, $radius,
      $As, $λs,
      $n-points, $extent, false()
    )=>geom:decimal(1)=>spline:spline()=>
      geom:mutate(function($p) {$p=>point:as-dimension(2)})=>
      geom:rotate($rotation, $center)=>
      shapes:scale-and-center($canvas)=>
      path:with-edge-ts()
  )
  return (
    wrapper:wrapper(
      $stroke,
      $dynamics
    )
  )
};