gum-jsx 1.6.2 → 1.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +2 -3
  2. package/docs/code/Arc.jsx +2 -2
  3. package/docs/code/Ellipse.jsx +2 -2
  4. package/docs/code/Images.jsx +1 -1
  5. package/docs/code/Line.jsx +1 -1
  6. package/docs/code/Math.jsx +12 -10
  7. package/docs/code/Network.jsx +1 -1
  8. package/docs/code/Points.jsx +4 -1
  9. package/docs/code/Shape.jsx +1 -1
  10. package/docs/code/SymFill.jsx +1 -1
  11. package/docs/code/SymShape.jsx +3 -4
  12. package/docs/code/Tables.jsx +3 -2
  13. package/docs/text/Box.md +1 -1
  14. package/docs/text/Element.md +11 -14
  15. package/docs/text/Gum.md +5 -5
  16. package/docs/text/Math.md +2 -1
  17. package/docs/text/SymFill.md +2 -1
  18. package/docs/text/SymLine.md +2 -1
  19. package/docs/text/SymPoints.md +2 -1
  20. package/docs/text/SymShape.md +2 -1
  21. package/docs/text/SymSpline.md +1 -0
  22. package/gala/code/atomic_orbitals.jsx +1 -2
  23. package/gala/code/complex_plot.jsx +1 -1
  24. package/gala/code/pendulum_physics.jsx +3 -3
  25. package/gala/code/plot_manual.jsx +2 -2
  26. package/gala/code/polygon_slide.jsx +3 -4
  27. package/gala/code/spline_star.jsx +2 -2
  28. package/package.json +1 -1
  29. package/scripts/gum.ts +14 -11
  30. package/scripts/test.ts +0 -69
  31. package/skills/gum-jsx/SKILL.md +12 -15
  32. package/skills/gum-jsx/references/gala/atomic_orbitals.md +1 -2
  33. package/skills/gum-jsx/references/gala/complex_plot.md +1 -1
  34. package/skills/gum-jsx/references/gala/pendulum_physics.md +3 -3
  35. package/skills/gum-jsx/references/gala/plot_manual.md +2 -2
  36. package/skills/gum-jsx/references/gala/polygon_slide.md +3 -4
  37. package/skills/gum-jsx/references/gala/spline_star.md +2 -2
  38. package/skills/gum-jsx/references/geometry.md +6 -6
  39. package/skills/gum-jsx/references/layout.md +5 -2
  40. package/skills/gum-jsx/references/networks.md +1 -1
  41. package/skills/gum-jsx/references/symbolic.md +12 -8
  42. package/skills/gum-jsx/references/utilities.md +18 -14
  43. package/src/elems/core.ts +33 -13
  44. package/src/elems/geometry.ts +10 -12
  45. package/src/elems/layout.ts +5 -7
  46. package/src/elems/network.ts +4 -3
  47. package/src/elems/plot.ts +19 -35
  48. package/src/elems/symbolic.ts +30 -24
  49. package/src/eval.ts +13 -3
  50. package/src/gum.ts +3 -3
  51. package/src/lib/interp.ts +10 -4
  52. package/src/lib/types.ts +1 -2
  53. package/src/lib/utils.ts +32 -24
  54. package/src/render.ts +7 -27
package/README.md CHANGED
@@ -91,11 +91,10 @@ CLI options:
91
91
  | Option | Description | Default |
92
92
  |--------|-------------|---------|
93
93
  | `file` | Gum JSX file to render | stdin |
94
- | `-s, --size <size>` | Image size in pixels | 1000 |
94
+ | `-s, --size <size>` | SVG/viewBox size in pixels | 1000 |
95
95
  | `-t, --theme <theme>` | Theme: `light` or `dark` | light |
96
96
  | `-b, --background <color>` | Background color | white |
97
97
  | `-f, --format <format>` | Format: `json`, `svg`, `png`, `kitty` | auto |
98
98
  | `-o, --output <output>` | Output file | stdout |
99
- | `-w, --width <width>` | Max width of the PNG | auto |
100
- | `-h, --height <height>` | Max height of the PNG | auto |
99
+ | `-r, --raster-size <size>` | Max rasterized PNG size | auto |
101
100
  | `-d, --dev` | Live update display | off |
package/docs/code/Arc.jsx CHANGED
@@ -1,5 +1,5 @@
1
1
  // elliptical and circular arcs using start and end angles
2
2
  <Group>
3
- <Arc pos={[0.3, 0.5]} size={[0.4, 0.3]} start={-45} end={210} stroke={blue} stroke-width={2} />
4
- <Arc pos={[0.7, 0.5]} size={0.3} start={90} end={-150} stroke={red} stroke-width={2} />
3
+ <Arc pos={[0.3, 0.5]} rad={[0.2, 0.15]} start={-45} end={210} stroke={blue} stroke-width={2} />
4
+ <Arc pos={[0.7, 0.5]} rad={0.15} start={90} end={-150} stroke={red} stroke-width={2} />
5
5
  </Group>
@@ -1,5 +1,5 @@
1
1
  // two ellipses, one wider and one taller
2
2
  <Group>
3
- <Ellipse pos={[0.3, 0.2]} size={[0.4, 0.2]} />
4
- <Ellipse pos={[0.6, 0.6]} size={[0.4, 0.5]} />
3
+ <Ellipse pos={[0.3, 0.2]} rad={[0.2, 0.1]} />
4
+ <Ellipse pos={[0.6, 0.6]} rad={[0.2, 0.25]} />
5
5
  </Group>
@@ -1,6 +1,6 @@
1
1
  // load "image.png" and display a 2x1 clip from the center
2
2
  <Box rounded clip>
3
3
  <Group aspect={2}>
4
- <LoadImage id="image.png" xsize={1} />
4
+ <LoadImage id="image.png" xrect={[0, 1]} />
5
5
  </Group>
6
6
  </Box>
@@ -1,5 +1,5 @@
1
1
  // draw a piecewise line spiraling outwards (with dots at vertices)
2
- const spiral = linspace(0, 5, 25).map(t => polar(360 * t, t/5))
2
+ const spiral = linspace(0, 5, 25).map(t => polar(2*pi * t, t/5))
3
3
  return <Box margin>
4
4
  <Graph coord={[-1, -1, 1, 1]}>
5
5
  <Line points={spiral} />
@@ -1,10 +1,12 @@
1
- // embed a five point star in a circle. place red dots at its vertices.
2
- const verts = linspace(0, 360, 10, false).map((t, i) => {
3
- const radius = i % 2 == 0 ? 1 : 0.5
4
- return polar(90 + t, radius)
5
- })
6
- return <Graph aspect coord={[-1, -1, 1, 1]}>
7
- <Circle pos={[0, 0]} size={2} stroke={darkgray} />
8
- <Shape points={verts} stroke={blue} stroke-width={2} />
9
- {verts.map(pos => <Dot pos={pos} size={0.05} fill={red} />)}
10
- </Graph>
1
+ // draw a spirograph in a box for a set of example parameters
2
+ const [R, r, d, k] = [10, 7, 4, 7]
3
+ const fx = t => (R - r) * cos(t) + d * cos(((R - r) / r) * t)
4
+ const fy = t => (R - r) * sin(t) - d * sin(((R - r) / r) * t)
5
+ return <TitleFrame title="Spirograph" padding={0.2} margin rounded>
6
+ <Graph aspect coord={[-R, -R, R, R]}>
7
+ <Circle pos={[0, 0]} rad={R} stroke={darkgray} stroke-dasharray={10} />
8
+ <Circle pos={[0, 0]} rad={R - r} stroke-dasharray={5} />
9
+ <SymSpline fx={fx} fy={fy} tlim={[0, 2*pi*k]} stroke={blue} stroke-width={2} />
10
+ </Graph>
11
+ <Span pos={[0.5, 1.1]} ysize={0.05} font-family={mono}>R = 10 | r = 7 | d = 4</Span>
12
+ </TitleFrame>
@@ -4,5 +4,5 @@
4
4
  <Node id="test" pos={[0.75, 0.25]} wrap={6}>This is a test of wrapping capabilities</Node>
5
5
  <Node id="ball" pos={[0.75, 0.75]}><Ellipse aspect={1.5} fill={blue}/></Node>
6
6
  <Edge start="hello" end="test" />
7
- <Edge start="hello" end="ball" start-side="s" curve={3} />
7
+ <Edge start="hello" end="ball" start-side="s" />
8
8
  </Network>
@@ -1,5 +1,8 @@
1
1
  // A plot of three different increasing curves of varying steepness and multiple points spaced at regular intervals. The x-axis label is "time (seconds)", the y-axis label is "space (meters)", and the title is "Spacetime Vibes". There are axis ticks in both directions with assiated faint grid lines.
2
- <Plot xlim={[-1, 1]} ylim={[-1, 1]} grid margin={0.3} aspect xlabel="time (seconds)" ylabel="space (meters)" title="Spacetime Vibes">
2
+ <Plot coord={[-1, -1, 1, 1]} margin={0.2} grid
3
+ xlabel="time (seconds)" ylabel="space (meters)"
4
+ title="Spacetime Vibes"
5
+ >
3
6
  <Points point-size={0.04} points={[
4
7
  [0, 0.5], [0.5, 0], [-0.5, 0], [0, -0.5]
5
8
  ]} />
@@ -1,5 +1,5 @@
1
1
  // draw a stop sign
2
- const hexagon = linspace(0, 360, 8, false).map(t => polar(t))
2
+ const hexagon = linspace(0, 2*pi, 8, false).map(t => polar(t))
3
3
  return <Box fill="#bbb" rounded padding margin>
4
4
  <Graph xlim={[-1, 1]} ylim={[-1, 1]} aspect={1}>
5
5
  <Shape points={hexagon} fill="#CC0202" stroke={white} stroke_width={20} spin={180/8} />
@@ -1,6 +1,6 @@
1
1
  // a decaying sine wave filled in with blue
2
2
  const decay = x => exp(-0.1*x) * sin(x)
3
3
  return <Graph xlim={[0, 6*pi]} ylim={[-1, 1]} aspect={phi}>
4
- <SymFill fy1={decay} fy2={0} fill={blue} fill-opacity={0.5} N={250} />
4
+ <SymFill f1={t => [t, decay(t)]} f2={t => [t, 0]} tlim={[0, 6*pi]} fill={blue} fill-opacity={0.5} N={250} />
5
5
  <SymLine fy={decay} N={250} />
6
6
  </Graph>
@@ -1,9 +1,8 @@
1
1
  // Draw a rounded star shape with a blue fill. Wrap it in a rounded frame.
2
- const rad = t => 1 - 0.3 * cos(2.5 * t)**2
2
+ const rad = t => 1 - 0.2 * cos(5 * (t - pi/2))
3
3
  return <Frame rounded padding margin>
4
4
  <SymShape aspect fill={blue}
5
5
  tlim={[0, 2*pi]} N={200}
6
- fx={t => rad(t) * sin(t)}
7
- fy={t => rad(t) * cos(t)}
6
+ f={t => polar(t, rad(t))}
8
7
  />
9
- </Frame>
8
+ </Frame>
@@ -1,6 +1,7 @@
1
1
  // load "data.csv" and plot each row as a blue dot
2
- return <Graph xlim={[0, 10]} ylim={[0, 10]}>
2
+ return <Graph aspect coord={[0, 0, 10, 10]}>
3
+ <Mesh2D xlocs={10} ylocs={10} opacity={0.25} />
3
4
  {loadTable('data.csv').map(({ x, y }) =>
4
- <Dot pos={[x, y]} rad={0.1} fill={blue} />
5
+ <Dot pos={[x, y]} size={0.5} fill={blue} />
5
6
  )}
6
7
  </Graph>
package/docs/text/Box.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  This is a simple container class allowing you to add padding, margins, and a border to a single **Element**. It's pretty versatile and is often used to set up the outermost positioning of a figure. Mirroring the standard CSS definitions, padding is space inside the border and margin is space outside the border. This has no border by default, but there is a specialized subclass of this called **Frame** that defaults to `border = 1`.
6
6
 
7
- **Box** can be pretty handly in various situations. It is differentiated from [Group](/docs/Group) in that it will adopt the `aspect` of the child element. This is useful if you want to do something like shift an element up or down by a certain amount while maintaining its aspect ratio. Simply wrap it in a **Box** and set child's `pos` to the desired offset.
7
+ **Box** can be pretty handly in various situations. It is differentiated from [Group](/docs/Group) in that it will adopt the `aspect` of the first child element. This is useful if you want to do something like shift an element up or down by a certain amount while maintaining its aspect ratio. Simply wrap it in a **Box** and set child's `pos` to the desired offset.
8
8
 
9
9
  There are multiple ways to specify padding and margins. If given as a scalar, it is constant across all sides. If two values are given, they correspond to the horizontal and vertical sides. If four values are given, they correspond to `[left, top, right, bottom]`.
10
10
 
@@ -1,26 +1,23 @@
1
1
  # Element
2
2
 
3
- The base class for all `gum.jsx` objects. You will usually not be working with this object directly unless you are implementing your own custom elements. An **Element** has a few methods that can be overriden, each of which takes a [Context](/docs/Context) object as an argument. The vast majority of implementations will override only `props` and `inner` (for non-unary elements).
3
+ The base class for all Gum objects. You will usually not be working with this object directly unless you are implementing your own custom elements. However, many of the arguments are common to all elements and are documented here.
4
4
 
5
- The position and size of an element are specified in the internal coordinates (`coord`) of its parent, which defaults to the unit square. Rectangles are always specified in `[left, top, right, bottom]` format. You can also specify the placement by specifying `pos` and `size` or various combinations of `xsize`/`ysize`. When not specified, `rect` defaults to the unit square.
5
+ The position and size of an element are specified in the internal coordinates (`coord`) of its parent, which defaults to the unit square. Rectangles are always specified in `[left, top, right, bottom]` format. Where the element is placed is ultimately determined by its `rect`. However, it is more common to specify the `rect` using one of the convenience parameters below.
6
+
7
+ There are several convenience parameters that can be used to specify the `rect` in a more intuitive way. These include `size`/`xsize`/`ysize` for controlling the extent of the rectangle around `pos` and the analogous `rad`/`xrad`/`yrad` for radial specification. You can also specify the limits in either or both dimensions with `xrect`/`yrect`, where a single number is interpreted as a zero-length limit at that position.
8
+
9
+ The `expand` parameter can be used to control whether the element should be expanded to fully contain its `rect`. This is useful if you have an aspected element with a desired size in one dimension. A very common case is a [Text](/docs/Text) element where you specify `pos` and `ysize` and leave the width to be determined by the aspect ratio.
6
10
 
7
11
  Parameters:
8
- - `tag` = `g` — the SVG tag associated with this element
9
- - `unary` = `false` — whether there is inner text for this element
10
12
  - `aspect` = `null` — the width to height ratio for this element
13
+ - `rect` — a fully specified rectangle to place the child in (takes precedence over other parameters)
11
14
  - `pos` — the desired position of the center of the child's rectangle
12
- - `size` ­— the desired size of the child's rectangle (can be single number or pair)
13
- - `xsize`/`ysize` ­— specify the size for a specific dimension (and expand the other)
14
- - `rect` — a fully specified rectangle to place the child in (this will override `pos`/`size`)
15
- - `aspect` — the aspect ratio of the child's rectangle
15
+ - `size`/`xsize`/`ysize` ­— the desired size of the child's rectangle (`size` can be single number or pair)
16
+ - `rad`/`xrad`/`yrad` the desired radius of the child's rectangle (`rad` can be single number or pair)
17
+ - `xrect`/`yrect` — the limits of the child's rectangle in the x and y dimensions (both can be single number or pair)
16
18
  - `expand` — when `true`, instead of embedding the child within `rect`, it will make the child just large enough to fully contain `rect`
17
19
  - `align` — how to align the child when it doesn't fit exactly within `rect`, options are `left`, `right`, `center`, or a fractional position (can set vertical and horizontal separately with a pair)
18
20
  - `rotate` — how much to rotate the child by (degrees counterclockwise)
19
21
  - `spin` — like rotate but will maintain the same size
20
22
  - `flex` ­— override to set `aspect = null`
21
- - `...` = `{}` — additional attributes that are included in `props`
22
-
23
- Methods:
24
- - `props(ctx)` — returns a dictionary of attributes for the SVG element. The default implementation returns the non-null `attr` passed to the constructor
25
- - `inner(ctx)` — returns the inner text of the SVG element (for non-unary). Defaults to returing empty string
26
- - `svg(ctx)` — returns the rendered SVG of the element as a `String`. Default implementation constructs SVG from `tag`, `unary`, `props`, and `inner`
23
+ - `...` = `{}` — additional attributes are applied directly to the resulting SVG
package/docs/text/Gum.md CHANGED
@@ -1,15 +1,15 @@
1
1
  # Gum
2
2
 
3
- Welcome to the `gum.jsx` docs! Click on an item in the list on the left to get more info about a particular class (usually an [Element](/docs/Element), function, or constant).
3
+ Welcome to the Gum documentation! Click on an item in the list on the left to get more info about a particular class (usually an [Element](/docs/Element), function, or constant).
4
4
 
5
- Each entry has a description of the operation and arguments of the item and an associated example code snippet. You can edit the code snippet, but note that these will get clobbered if you navigate to another entry! Go to the [main editor](/) for non-ephemeral work.
5
+ Each entry has a description of the operation and arguments of the item and an associated example code snippet. You can edit the code snippet, but note that these will get clobbered if you navigate to another entry! Go to the [main editor](/gum/studio) for non-ephemeral work.
6
6
 
7
- The syntax is an XML component style one familiar to React developers. The output is pure SVG. You can nest objects in interesting ways and specify their parameters. Positions and sizes are specified proportionally (i.e. between `0` and `1`), but some quantities like `border` or `stroke-width` are specified in absolute units.
7
+ The syntax is a JSX component style familiar to React developers. The output is pure SVG. You can nest objects in interesting ways and specify their parameters. Positions and sizes are specified proportionally (i.e. between `0` and `1`), but some quantities like `border` or `stroke-width` are still specified in pixel units.
8
8
 
9
9
  ## Common Patterns
10
10
 
11
11
  *Parameter specification*: You can specify boolean parameters like `border` just by writing their name. Some parameters, such as `margin` or `padding` default to `0` when not specified, but also take a specific value when specified as boolean `true` (in both cases `0.1`). You can also pass SVG properties such as `stroke-width` directly.
12
12
 
13
- *Subunit arguments*: for compound elements that inherit [Group](/docs/Group), some keyword arguments are passed down to the constituent parts. For instance, in [Plot](/docs/Plot), one can specify arguments intended for the `XAxis` unit by prefixing them with `xaxis-`. For example, setting the `stroke-width` for this subunit can be achieved with `xaxis-stroke-width`.
13
+ *Subunit arguments*: for compound elements that inherit [Group](/docs/Group), some keyword arguments are passed down to the constituent parts. For instance, in [Plot](/docs/Plot), one can specify arguments intended for the [XAxis](/docs/Axis) unit by prefixing them with `xaxis-`. For example, setting the `stroke-width` for this subunit can be achieved with `xaxis-stroke-width`.
14
14
 
15
- *Constructive layout*: we try to avoid hard-coding absolute values as much as possible. Instead, we use proportions relative to the parent element's size. For instance, `margin = 0.1` means "10% of the parent's width/height". Similarly, instead of manually positioning elements in a row, we use [HStack](/docs/HStack) or [VStack](/docs/VStack) to automatically arrange them.
15
+ *Constructive layout*: we try to avoid hard-coding absolute values as much as possible. Instead, we use proportions relative to the parent element's size. For instance, `margin = 0.1` means "10% of the parent's width/height". Similarly, instead of manually positioning elements in a row, we use [HStack](/docs/Stack) or [VStack](/docs/Stack) to automatically arrange them.
package/docs/text/Math.md CHANGED
@@ -27,6 +27,7 @@ Here we collect a variety of global mathematical functions and constants. You ca
27
27
  - `round(x)` — the rounding function
28
28
  - `clamp(x, lim=[0, 1])` — clamp `x` to the range `lim`
29
29
  - `rescale(x, lim=[0, 1])` — linearly rescale `x` to the range `lim`
30
- - `polar(angle, radius=1, center=[0, 0])` — convert polar coordinates (`angle` in degrees, `radius` scalar or size vector) to a 2D point around `center`
30
+ - `polar(theta, radius=1, center=[0, 0])` — convert polar coordinates (`theta` in radians, `radius` scalar or size vector) to a 2D point around `center`
31
+ - `polard(angle, radius=1, center=[0, 0])` — same as `polar` but takes `angle` in degrees
31
32
 
32
33
  Angles use gum's usual screen-space convention: `0` points right and `90` points down.
@@ -2,9 +2,10 @@
2
2
 
3
3
  *Inherits*: [Fill](/docs/Fill) > [Shape](/docs/Shape) > **Pointstring** > [Element](/docs/Element)
4
4
 
5
- Flexible interface to generate filled in paths symbolically or in combination with fixed inputs. This generates a polygon by running through the points generated by `fx1`/`fy1` and then backwards through the points generated by `fx2`/`fy2`. To generate a simple filled curve, pass your function to `fy1` and let `fy2` be `0`.
5
+ Flexible interface to generate filled in paths symbolically or in combination with fixed inputs. This generates a polygon by running through the points generated by `f1` or `fx1`/`fy1` and then backwards through the points generated by `f2` or `fx2`/`fy2`. To generate a simple filled curve, pass your function to `fy1` and let `fy2` be `0`.
6
6
 
7
7
  Parameters:
8
+ - `f1`/`f2` — functions mapping t-values to `[x, y]` points for either bound
8
9
  - `fx1`/`fy1` — a function generating one of the bounds for the fill (or a constant)
9
10
  - `fx2`/`fy2` — a function generating the other bound for the fill (or a constant)
10
11
  - `xlim`/`ylim`/`tlim` — a pair of numbers specifying variable limits
@@ -4,9 +4,10 @@
4
4
 
5
5
  Flexible interface to generate two-dimensional paths symbolically or in combination with fixed inputs. There are variety of acceptable input combinations, but the most common usage is to specify the range to use for x-values with `xlim` and a function to plot with `fy`. To plot a polygon instead of a line, use [SymShape](/docs/SymShape).
6
6
 
7
- Alternatively, you can specify the transpose with `ylim`/`fx`, or even do a fully parametric path using `tlim`/`fx`/`fy`. In any of these cases, one can either specify limits with `xlim`/`ylim`/`tlim` or specific values with `xvals`/`yvals`/`tvals`.
7
+ Alternatively, you can specify the transpose with `ylim`/`fx`, or do a fully parametric path using `tlim` with either `f` or `fx`/`fy`. In any of these cases, one can either specify limits with `xlim`/`ylim`/`tlim` or specific values with `xvals`/`yvals`/`tvals`.
8
8
 
9
9
  Parameters:
10
+ - `f` — a function mapping t-values to `[x, y]` points
10
11
  - `fx`/`fy` — a function mapping from x-values, y-values, or t-values
11
12
  - `xlim`/`ylim`/`tlim` — a pair of numbers specifying variable limits
12
13
  - `xvals`/`yvals`/`tvals` — a list of x-values, y-values, or t-values to use
@@ -2,11 +2,12 @@
2
2
 
3
3
  *Inherits*: [Group](/docs/Group) > [Element](/docs/Element)
4
4
 
5
- Flexible interface to generate sets of points symbolically or in combination with fixed inputs. The most common usage is to specify the range for x-values with `xlim` and a function to plot with `fy`. But you can specify the transpose with `ylim`/`fx`, or do a fully parametric path using `tlim`/`fx`/`fy`.
5
+ Flexible interface to generate sets of points symbolically or in combination with fixed inputs. The most common usage is to specify the range for x-values with `xlim` and a function to plot with `fy`. But you can specify the transpose with `ylim`/`fx`, or do a fully parametric path using `tlim` with either `f` or `fx`/`fy`.
6
6
 
7
7
  You can also specify point size functionally with `point-size` and the shape with `point-shape`. Both of these functions take `(x, y, t, i)` values as inputs and return the desired value for each point. As with other groups, the **SymPoints** element itself can still be laid out with the normal `size`/`xsize`/`ysize` element parameters.
8
8
 
9
9
  Parameters:
10
+ - `f` — a function mapping t-values to `[x, y]` points
10
11
  - `fx`/`fy` — a function mapping from x-values, y-values, or t-values
11
12
  - `point-size` = `0.05` — a size or a function mapping from `(x, y, t, i)` values to a size
12
13
  - `point-shape` = `Dot` — a shape or function mapping from `(x, y, t, i)` values to a shape
@@ -2,9 +2,10 @@
2
2
 
3
3
  *Inherits*: [Shape](/docs/Shape) > **Pointstring** > [Element](/docs/Element)
4
4
 
5
- Flexible interface to generate shapes symbolically or in combination with fixed inputs. Operates similarly to [Shape](/docs/Shape), but generates a shape from the points generated by `fx`/`fy`.
5
+ Flexible interface to generate shapes symbolically or in combination with fixed inputs. Operates similarly to [Shape](/docs/Shape), but generates a shape from points generated by `f` or `fx`/`fy`.
6
6
 
7
7
  Parameters:
8
+ - `f` — a function mapping t-values to `[x, y]` points
8
9
  - `fx`/`fy` — a function mapping from x-values, y-values, or t-values
9
10
  - `xlim`/`ylim`/`tlim` — a pair of numbers specifying variable limits
10
11
  - `xvals`/`yvals`/`tvals` — a list of x-values, y-values, or t-values to use
@@ -6,6 +6,7 @@ Flexible interface to generate smooth two-dimensional spline curves symbolically
6
6
 
7
7
 
8
8
  Parameters:
9
+ - `f` — a function mapping t-values to `[x, y]` points
9
10
  - `fx`/`fy` — a function mapping from x-values, y-values, or t-values
10
11
  - `xlim`/`ylim`/`tlim` — a pair of numbers specifying variable limits
11
12
  - `xvals`/`yvals`/`tvals` — a list of x-values, y-values, or t-values to use
@@ -27,8 +27,7 @@ const dz2_r = (t) => 0.28 * abs(3 * cos(t) ** 2 - 1)
27
27
  // plot radial orbital lobes via SymSpline
28
28
  const OrbitalLobe = ({ rfn, t0, t1, sign }) =>
29
29
  <SymSpline N={50} tlim={[t0, t1]}
30
- fx={t => rfn(t) * cos(t)}
31
- fy={t => rfn(t) * sin(t)}
30
+ f={t => polar(t, rfn(t))}
32
31
  stroke={sign ? pos_col : neg_col}
33
32
  fill={sign ? pos_fill : neg_fill}
34
33
  stroke-width={2} opacity={0.6}
@@ -3,7 +3,7 @@ const Curve = ({ fx, stroke }) => <SymLine fx={fx} ylim={ylim} stroke={stroke} s
3
3
  const xlabel = <Latex>x = a + bi</Latex>
4
4
  const ylabel = <Latex>c</Latex>
5
5
  return <Plot aspect={2} margin={0.3} xlim={xlim} ylabel={ylabel} xlabel={xlabel} ylabel-offset={0.075}>
6
- <Mesh2D xlocs={41} ylocs={21} xlim={xlim} ylim={ylim} opacity={0.3} />
6
+ <Mesh2D xlocs={41} ylocs={21} xlim={xlim} ylim={ylim} opacity={0.1} />
7
7
  <HLine loc={0} lim={xlim} opacity={0.3} />
8
8
  <VLine loc={0} lim={ylim} opacity={0.3} />
9
9
  <Curve fx={y => -y + sqrt(maximum(0, y*y-1))} stroke={blue} />
@@ -18,9 +18,9 @@ const arcRad = 0.25
18
18
  // Computed positions
19
19
  const rodRot = 90 - rodAng
20
20
  const eqnY = pivotY + rodLen
21
- const [ bobX, bobY ] = polar(rodRot, rodLen, [pivotX, pivotY])
22
- const [ midX, midY ] = polar(rodRot, 0.50 * rodLen, [pivotX, pivotY])
23
- const [ tenX, tenY ] = polar(rodRot, 0.75 * rodLen, [pivotX, pivotY])
21
+ const [ bobX, bobY ] = polard(rodRot, rodLen, [pivotX, pivotY])
22
+ const [ midX, midY ] = polard(rodRot, 0.50 * rodLen, [pivotX, pivotY])
23
+ const [ tenX, tenY ] = polard(rodRot, 0.75 * rodLen, [pivotX, pivotY])
24
24
 
25
25
  return <Box margin={0.06}>
26
26
  <VStack spacing={0.05}>
@@ -2,8 +2,8 @@ const aspect = 2
2
2
  const ratio = pi / aspect
3
3
  return <Box margin={0.3}>
4
4
  <Group coord={[0, 1, 2*pi, -1]} aspect={aspect}>
5
- <HMesh locs={5} lim={[0, 2*pi]} opacity={0.3} />
6
- <VMesh locs={5} lim={[-1, 1]} opacity={0.3} />
5
+ <HMesh locs={5} opacity={0.15} />
6
+ <VMesh locs={5} opacity={0.15} />
7
7
  <HAxis ticks={5} lim={[0, 2*pi]} pos={[pi, -1]} size={[2*pi, 0.08]} />
8
8
  <VAxis ticks={5} lim={[-1, 1]} pos={[0, 0]} size={[0.08*ratio, 2]} />
9
9
  <SymLine fy={sin} xlim={[0, 2*pi]} />
@@ -5,11 +5,10 @@ const shapes = [
5
5
  ]
6
6
 
7
7
  const RegularPolygon = ({ n, ...args }) =>
8
- <SymShape {...args}
9
- aspect spin={90*(n-2)/n}
8
+ <SymShape {...args} aspect
10
9
  xlim={[-1, 1]} ylim={[-1, 1]}
11
- tvals={linspace(0, 2*pi, n+1)}
12
- fx={cos} fy={sin}
10
+ tvals={linspace(0, 2*pi, n, false)}
11
+ f={t => polar(t+pi/2*(n-2)/n)}
13
12
  />
14
13
 
15
14
  return <Slide title="Simple Regular Polygons" wrap={25}>
@@ -8,8 +8,8 @@ const theta0 = linspace(0, 2 * pi, n, false).map(t => t - pi / 2)
8
8
  const theta1 = theta0.map(t => t + pi / n)
9
9
 
10
10
  // get inner/outer point positions
11
- const points0 = theta0.map(t => polar(t * r2d))
12
- const points1 = theta1.map(t => polar(t * r2d, R))
11
+ const points0 = theta0.map(t => polar(t))
12
+ const points1 = theta1.map(t => polar(t, R))
13
13
  const points = zip(points0, points1).flat()
14
14
 
15
15
  // return full spline
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gum-jsx",
3
- "version": "1.6.2",
3
+ "version": "1.6.3",
4
4
  "description": "Language for vector graphics generation.",
5
5
  "type": "module",
6
6
  "author": "Douglas Hanley",
package/scripts/gum.ts CHANGED
@@ -4,7 +4,7 @@ import { Command } from 'commander'
4
4
  import { readFileSync, writeFileSync } from 'fs'
5
5
  import { dirname, resolve } from 'path'
6
6
 
7
- import { evaluateGum } from '../src/eval'
7
+ import { evaluateGum, fitSize } from '../src/eval'
8
8
  import { rasterizeSvg, formatImage, readStdin } from '../src/render'
9
9
  import { Element, Group } from '../src/elems/core'
10
10
  import type { CliArgs, LoadFile } from '../src/lib/types'
@@ -16,7 +16,7 @@ import { devCommand } from './dev'
16
16
 
17
17
  function transformArgs(cmd: Command) {
18
18
  const [ file0 ] = cmd.args
19
- let { format, output, theme, background, size, width, height, dev } = cmd.opts()
19
+ let { format, output, theme, background, size, rasterSize, dev } = cmd.opts()
20
20
 
21
21
  // add white background for light theme
22
22
  if (theme == 'light' && background == null) background = 'white'
@@ -41,7 +41,7 @@ function transformArgs(cmd: Command) {
41
41
  : readFileSync(file, encoding as BufferEncoding)
42
42
  }
43
43
 
44
- return { file, format, output, theme, background, size, width, height, dev, loadFile }
44
+ return { file, format, output, theme, background, size, rasterSize, dev, loadFile }
45
45
  }
46
46
 
47
47
  //
@@ -64,7 +64,7 @@ function convertToTree(elem: Element): any {
64
64
  //
65
65
 
66
66
  async function runCommand(args: CliArgs) {
67
- const { file, format, output, theme, background, size: size0 = 1000, width, height, dev, loadFile } = args
67
+ const { file, format, output, theme, background, size: size0 = 1000, rasterSize, dev, loadFile } = args
68
68
 
69
69
  // divert to dev command if update is on
70
70
  if (dev) {
@@ -77,8 +77,6 @@ async function runCommand(args: CliArgs) {
77
77
 
78
78
  // evaluate gum with size
79
79
  const elem = evaluateGum(code, { size: size0, theme, loadFile })
80
- const svg = format != 'json' ? elem.svg() : ''
81
- const size = elem.size
82
80
 
83
81
  // rasterize output
84
82
  let out: string | Buffer
@@ -86,9 +84,15 @@ async function runCommand(args: CliArgs) {
86
84
  const tree = convertToTree(elem)
87
85
  out = JSON.stringify(tree, null, 2)
88
86
  } else if (format == 'svg') {
89
- out = svg
87
+ out = elem.svg()
90
88
  } else if (format == 'png' || format == 'kitty') {
91
- const dat = await rasterizeSvg(svg, { size, width, height, background })
89
+ let svg = elem.svg()
90
+ if (rasterSize != null) {
91
+ const [ rasterWidth, rasterHeight ] = fitSize(elem.size, rasterSize)
92
+ const elem1 = elem.clone({ width: rasterWidth, height: rasterHeight })
93
+ svg = elem1.svg()
94
+ }
95
+ const dat = await rasterizeSvg(svg, { background })
92
96
  out = (format == 'kitty') ? (formatImage(dat) + '\n') : dat
93
97
  } else {
94
98
  throw new Error(`Unsupported output format: ${format}`)
@@ -112,9 +116,8 @@ program.name('gum')
112
116
  .option('-f, --format <format>', 'format to output')
113
117
  .option('-t, --theme <theme>', 'theme to use', 'light')
114
118
  .option('-b, --background <background>', 'background color')
115
- .option('-s, --size <size>', 'size of the SVG', (value: string) => parseInt(value))
116
- .option('-w, --width <width>', 'width of the PNG', (value: string) => parseInt(value))
117
- .option('-h, --height <height>', 'height of the PNG', (value: string) => parseInt(value))
119
+ .option('-s, --size <size>', 'SVG/viewBox size', (value: string) => parseInt(value))
120
+ .option('-r, --raster-size <size>', 'max rasterized PNG size', (value: string) => parseInt(value))
118
121
  .option('-o, --output <output>', 'output file')
119
122
  .action(async function(this: Command) {
120
123
  const args = transformArgs(this)
package/scripts/test.ts CHANGED
@@ -13,51 +13,6 @@ function loadFile(path: string, encoding: string = 'utf8') {
13
13
  : readFileSync(file, encoding as BufferEncoding)
14
14
  }
15
15
 
16
- function matchNumbers(text: string, pattern: RegExp, message: string): number[] {
17
- const match = text.match(pattern)
18
- if (match == null) throw new Error(message)
19
- return match.slice(1).map(Number)
20
- }
21
-
22
- function rotatePoint([ x, y ]: number[], angle: number, [ cx, cy ]: number[]): number[] {
23
- const theta = angle * Math.PI / 180
24
- const [ dx, dy ] = [ x - cx, y - cy ]
25
- const [ COS, SIN ] = [ Math.cos(theta), Math.sin(theta) ]
26
- return [ cx + dx * COS - dy * SIN, cy + dx * SIN + dy * COS ]
27
- }
28
-
29
- function midpoint(p0: number[], p1: number[]): number[] {
30
- return [ (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2 ]
31
- }
32
-
33
- function dot(p0: number[], p1: number[]): number {
34
- return p0[0] * p1[0] + p0[1] * p1[1]
35
- }
36
-
37
- function assertGraphArrowHeadForward(svg: string) {
38
- const line = matchNumbers(svg, /<line x1="([-\d.]+)" y1="([-\d.]+)" x2="([-\d.]+)" y2="([-\d.]+)"/, 'Missing arrow shaft')
39
- const path = matchNumbers(svg, /<path d="M ([-\d.]+),([-\d.]+) L ([-\d.]+),([-\d.]+) M [-\d.]+,[-\d.]+ L ([-\d.]+),([-\d.]+)"/, 'Missing arrow head')
40
- const rotate = matchNumbers(svg, /transform="rotate\(([-\d.]+), ([-\d.]+), ([-\d.]+)\)"/, 'Missing arrow head transform')
41
-
42
- const [ x1, y1, x2, y2 ] = line
43
- const [ tipx, tipy, base0x, base0y, base1x, base1y ] = path
44
- const [ angle, cx, cy ] = rotate
45
- const center = [ cx, cy ]
46
- const tip = rotatePoint([ tipx, tipy ], angle, center)
47
- const base = midpoint(
48
- rotatePoint([ base0x, base0y ], angle, center),
49
- rotatePoint([ base1x, base1y ], angle, center),
50
- )
51
- const shaft = [ x2 - x1, y2 - y1 ]
52
- const head = [ tip[0] - base[0], tip[1] - base[1] ]
53
- if (dot(shaft, head) <= 0) throw new Error('Arrow head points opposite the shaft')
54
- }
55
-
56
- function assertRoundedLineCornerCircular(svg: string) {
57
- const [ rx, ry ] = matchNumbers(svg, /<path d="[^"]* A ([-\d.]+),([-\d.]+) /, 'Missing rounded corner arc')
58
- if (Math.abs(rx - ry) > 1e-6) throw new Error(`RoundedLine corner arc is skewed: ${rx},${ry}`)
59
- }
60
-
61
16
  const dirs = ['docs/code', 'gala/code']
62
17
  let passed = 0
63
18
  let failed = 0
@@ -80,30 +35,6 @@ for (const dir of dirs) {
80
35
  }
81
36
  }
82
37
 
83
- try {
84
- const code = '<Frame aspect={1.2}><Graph coord={[0, 0, 1, 1]}><Arrow points={[[0.25, 0.25], [0.75, 0.75]]} /></Graph></Frame>'
85
- const elem = evaluateGum(code, { size: 500, theme: 'dark', loadFile })
86
- assertGraphArrowHeadForward(elem.svg())
87
- console.log('PASS regression Graph Arrow wide aspect')
88
- passed++
89
- } catch (e: any) {
90
- const { message = 'Unknown error' } = e
91
- console.error(`FAIL regression Graph Arrow wide aspect: ${message}`)
92
- failed++
93
- }
94
-
95
- try {
96
- const code = '<Frame aspect={4}><RoundedLine points={[[0.1,0.2],[0.5,0.2],[0.5,0.8]]} radius={0.1} /></Frame>'
97
- const elem = evaluateGum(code, { size: 500, theme: 'dark', loadFile })
98
- assertRoundedLineCornerCircular(elem.svg())
99
- console.log('PASS regression RoundedLine circular corners')
100
- passed++
101
- } catch (e: any) {
102
- const { message = 'Unknown error' } = e
103
- console.error(`FAIL regression RoundedLine circular corners: ${message}`)
104
- failed++
105
- }
106
-
107
38
  console.log()
108
39
  console.error(`${passed} passed`)
109
40
  console.error(`${failed} failed`)
@@ -160,30 +160,27 @@ Below is the full documentation for the core `gum.jsx` components: `Element`, `G
160
160
 
161
161
  ## Element
162
162
 
163
- The base class for all `gum.jsx` objects. You will usually not be working with this object directly unless you are implementing your own custom elements. An **Element** has a few methods that can be overriden, each of which takes a **Context** object as an argument. The vast majority of implementations will override only `props` and `inner` (for non-unary elements).
163
+ The base class for all Gum objects. You will usually not be working with this object directly unless you are implementing your own custom elements. However, many of the arguments are common to all elements and are documented here.
164
164
 
165
- The position and size of an element are specified in the internal coordinates (`coord`) of its parent, which defaults to the unit square. Rectangles are always specified in `[left, top, right, bottom]` format. You can also specify the placement by specifying `pos` and `size` or various combinations of `xsize`/`ysize`. When not specified, `rect` defaults to the unit square.
165
+ The position and size of an element are specified in the internal coordinates (`coord`) of its parent, which defaults to the unit square. Rectangles are always specified in `[left, top, right, bottom]` format. Where the element is placed is ultimately determined by its `rect`. However, it is more common to specify the `rect` using one of the convenience parameters below.
166
+
167
+ There are several convenience parameters that can be used to specify the `rect` in a more intuitive way. These include `size`/`xsize`/`ysize` for controlling the extent of the rectangle around `pos` and the analogous `rad`/`xrad`/`yrad` for radial specification. You can also specify the limits in either or both dimensions with `xrect`/`yrect`, where a single number is interpreted as a zero-length limit at that position.
168
+
169
+ The `expand` parameter can be used to control whether the element should be expanded to fully contain its `rect`. This is useful if you have an aspected element with a desired size in one dimension. A very common case is a **Text** element where you specify `pos` and `ysize` and leave the width to be determined by the aspect ratio.
166
170
 
167
171
  Parameters:
168
- - `tag` = `g` — the SVG tag associated with this element
169
- - `unary` = `false` — whether there is inner text for this element
170
172
  - `aspect` = `null` — the width to height ratio for this element
173
+ - `rect` — a fully specified rectangle to place the child in (takes precedence over other parameters)
171
174
  - `pos` — the desired position of the center of the child's rectangle
172
- - `size` ­— the desired size of the child's rectangle (can be single number or pair)
173
- - `xsize`/`ysize` ­— specify the size for a specific dimension (and expand the other)
174
- - `rect` — a fully specified rectangle to place the child in (this will override `pos`/`size`)
175
- - `aspect` — the aspect ratio of the child's rectangle
175
+ - `size`/`xsize`/`ysize` ­— the desired size of the child's rectangle (`size` can be single number or pair)
176
+ - `rad`/`xrad`/`yrad` the desired radius of the child's rectangle (`rad` can be single number or pair)
177
+ - `xrect`/`yrect` — the limits of the child's rectangle in the x and y dimensions (both can be single number or pair)
176
178
  - `expand` — when `true`, instead of embedding the child within `rect`, it will make the child just large enough to fully contain `rect`
177
179
  - `align` — how to align the child when it doesn't fit exactly within `rect`, options are `left`, `right`, `center`, or a fractional position (can set vertical and horizontal separately with a pair)
178
180
  - `rotate` — how much to rotate the child by (degrees counterclockwise)
179
181
  - `spin` — like rotate but will maintain the same size
180
182
  - `flex` ­— override to set `aspect = null`
181
- - `...` = `{}` — additional attributes that are included in `props`
182
-
183
- Methods:
184
- - `props(ctx)` — returns a dictionary of attributes for the SVG element. The default implementation returns the non-null `attr` passed to the constructor
185
- - `inner(ctx)` — returns the inner text of the SVG element (for non-unary). Defaults to returing empty string
186
- - `svg(ctx)` — returns the rendered SVG of the element as a `String`. Default implementation constructs SVG from `tag`, `unary`, `props`, and `inner`
183
+ - `...` = `{}` — additional attributes are applied directly to the resulting SVG
187
184
 
188
185
  **Example**
189
186
 
@@ -234,7 +231,7 @@ Generated code:
234
231
 
235
232
  This is a simple container class allowing you to add padding, margins, and a border to a single **Element**. It's pretty versatile and is often used to set up the outermost positioning of a figure. Mirroring the standard CSS definitions, padding is space inside the border and margin is space outside the border. This has no border by default, but there is a specialized subclass of this called **Frame** that defaults to `border = 1`.
236
233
 
237
- **Box** can be pretty handly in various situations. It is differentiated from **Group** in that it will adopt the `aspect` of the child element. This is useful if you want to do something like shift an element up or down by a certain amount while maintaining its aspect ratio. Simply wrap it in a **Box** and set child's `pos` to the desired offset.
234
+ **Box** can be pretty handly in various situations. It is differentiated from **Group** in that it will adopt the `aspect` of the first child element. This is useful if you want to do something like shift an element up or down by a certain amount while maintaining its aspect ratio. Simply wrap it in a **Box** and set child's `pos` to the desired offset.
238
235
 
239
236
  There are multiple ways to specify padding and margins. If given as a scalar, it is constant across all sides. If two values are given, they correspond to the horizontal and vertical sides. If four values are given, they correspond to `[left, top, right, bottom]`.
240
237
 
@@ -38,8 +38,7 @@ const dz2_r = (t) => 0.28 * abs(3 * cos(t) ** 2 - 1)
38
38
  // plot radial orbital lobes via SymSpline
39
39
  const OrbitalLobe = ({ rfn, t0, t1, sign }) =>
40
40
  <SymSpline N={50} tlim={[t0, t1]}
41
- fx={t => rfn(t) * cos(t)}
42
- fy={t => rfn(t) * sin(t)}
41
+ f={t => polar(t, rfn(t))}
43
42
  stroke={sign ? pos_col : neg_col}
44
43
  fill={sign ? pos_fill : neg_fill}
45
44
  stroke-width={2} opacity={0.6}
@@ -14,7 +14,7 @@ const Curve = ({ fx, stroke }) => <SymLine fx={fx} ylim={ylim} stroke={stroke} s
14
14
  const xlabel = <Latex>x = a + bi</Latex>
15
15
  const ylabel = <Latex>c</Latex>
16
16
  return <Plot aspect={2} margin={0.3} xlim={xlim} ylabel={ylabel} xlabel={xlabel} ylabel-offset={0.075}>
17
- <Mesh2D xlocs={41} ylocs={21} xlim={xlim} ylim={ylim} opacity={0.3} />
17
+ <Mesh2D xlocs={41} ylocs={21} xlim={xlim} ylim={ylim} opacity={0.1} />
18
18
  <HLine loc={0} lim={xlim} opacity={0.3} />
19
19
  <VLine loc={0} lim={ylim} opacity={0.3} />
20
20
  <Curve fx={y => -y + sqrt(maximum(0, y*y-1))} stroke={blue} />