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
@@ -29,9 +29,9 @@ const arcRad = 0.25
29
29
  // Computed positions
30
30
  const rodRot = 90 - rodAng
31
31
  const eqnY = pivotY + rodLen
32
- const [ bobX, bobY ] = polar(rodRot, rodLen, [pivotX, pivotY])
33
- const [ midX, midY ] = polar(rodRot, 0.50 * rodLen, [pivotX, pivotY])
34
- const [ tenX, tenY ] = polar(rodRot, 0.75 * rodLen, [pivotX, pivotY])
32
+ const [ bobX, bobY ] = polard(rodRot, rodLen, [pivotX, pivotY])
33
+ const [ midX, midY ] = polard(rodRot, 0.50 * rodLen, [pivotX, pivotY])
34
+ const [ tenX, tenY ] = polard(rodRot, 0.75 * rodLen, [pivotX, pivotY])
35
35
 
36
36
  return <Box margin={0.06}>
37
37
  <VStack spacing={0.05}>
@@ -11,8 +11,8 @@ const aspect = 2
11
11
  const ratio = pi / aspect
12
12
  return <Box margin={0.3}>
13
13
  <Group coord={[0, 1, 2*pi, -1]} aspect={aspect}>
14
- <HMesh locs={5} lim={[0, 2*pi]} opacity={0.3} />
15
- <VMesh locs={5} lim={[-1, 1]} opacity={0.3} />
14
+ <HMesh locs={5} opacity={0.15} />
15
+ <VMesh locs={5} opacity={0.15} />
16
16
  <HAxis ticks={5} lim={[0, 2*pi]} pos={[pi, -1]} size={[2*pi, 0.08]} />
17
17
  <VAxis ticks={5} lim={[-1, 1]} pos={[0, 0]} size={[0.08*ratio, 2]} />
18
18
  <SymLine fy={sin} xlim={[0, 2*pi]} />
@@ -16,11 +16,10 @@ const shapes = [
16
16
  ]
17
17
 
18
18
  const RegularPolygon = ({ n, ...args }) =>
19
- <SymShape {...args}
20
- aspect spin={90*(n-2)/n}
19
+ <SymShape {...args} aspect
21
20
  xlim={[-1, 1]} ylim={[-1, 1]}
22
- tvals={linspace(0, 2*pi, n+1)}
23
- fx={cos} fy={sin}
21
+ tvals={linspace(0, 2*pi, n, false)}
22
+ f={t => polar(t+pi/2*(n-2)/n)}
24
23
  />
25
24
 
26
25
  return <Slide title="Simple Regular Polygons" wrap={25}>
@@ -19,8 +19,8 @@ const theta0 = linspace(0, 2 * pi, n, false).map(t => t - pi / 2)
19
19
  const theta1 = theta0.map(t => t + pi / n)
20
20
 
21
21
  // get inner/outer point positions
22
- const points0 = theta0.map(t => polar(t * r2d))
23
- const points1 = theta1.map(t => polar(t * r2d, R))
22
+ const points0 = theta0.map(t => polar(t))
23
+ const points1 = theta1.map(t => polar(t, R))
24
24
  const points = zip(points0, points1).flat()
25
25
 
26
26
  // return full spline
@@ -33,8 +33,8 @@ Prompt: two ellipses, one wider and one taller
33
33
  Generated code:
34
34
  ```jsx
35
35
  <Group>
36
- <Ellipse pos={[0.3, 0.2]} size={[0.4, 0.2]} />
37
- <Ellipse pos={[0.6, 0.6]} size={[0.4, 0.5]} />
36
+ <Ellipse pos={[0.3, 0.2]} rad={[0.2, 0.1]} />
37
+ <Ellipse pos={[0.6, 0.6]} rad={[0.2, 0.25]} />
38
38
  </Group>
39
39
  ```
40
40
 
@@ -57,8 +57,8 @@ Prompt: elliptical and circular arcs using start and end angles
57
57
  Generated code:
58
58
  ```jsx
59
59
  <Group>
60
- <Arc pos={[0.3, 0.5]} size={[0.4, 0.3]} start={-45} end={210} stroke={blue} stroke-width={2} />
61
- <Arc pos={[0.7, 0.5]} size={0.3} start={90} end={-150} stroke={red} stroke-width={2} />
60
+ <Arc pos={[0.3, 0.5]} rad={[0.2, 0.15]} start={-45} end={210} stroke={blue} stroke-width={2} />
61
+ <Arc pos={[0.7, 0.5]} rad={0.15} start={90} end={-150} stroke={red} stroke-width={2} />
62
62
  </Group>
63
63
  ```
64
64
 
@@ -81,7 +81,7 @@ Prompt: draw a piecewise line spiraling outwards (with dots at vertices)
81
81
 
82
82
  Generated code:
83
83
  ```jsx
84
- const spiral = linspace(0, 5, 25).map(t => polar(360 * t, t/5))
84
+ const spiral = linspace(0, 5, 25).map(t => polar(2*pi * t, t/5))
85
85
  return <Box margin>
86
86
  <Graph coord={[-1, -1, 1, 1]}>
87
87
  <Line points={spiral} />
@@ -107,7 +107,7 @@ Prompt: draw a stop sign
107
107
 
108
108
  Generated code:
109
109
  ```jsx
110
- const hexagon = linspace(0, 360, 8, false).map(t => polar(t))
110
+ const hexagon = linspace(0, 2*pi, 8, false).map(t => polar(t))
111
111
  return <Box fill="#bbb" rounded padding margin>
112
112
  <Graph xlim={[-1, 1]} ylim={[-1, 1]} aspect={1}>
113
113
  <Shape points={hexagon} fill="#CC0202" stroke={white} stroke_width={20} spin={180/8} />
@@ -6,7 +6,7 @@
6
6
 
7
7
  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`.
8
8
 
9
- **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.
9
+ **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.
10
10
 
11
11
  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]`.
12
12
 
@@ -124,7 +124,10 @@ Prompt: A plot of three different increasing curves of varying steepness and mul
124
124
 
125
125
  Generated code:
126
126
  ```jsx
127
- <Plot xlim={[-1, 1]} ylim={[-1, 1]} grid margin={0.3} aspect xlabel="time (seconds)" ylabel="space (meters)" title="Spacetime Vibes">
127
+ <Plot coord={[-1, -1, 1, 1]} margin={0.2} grid
128
+ xlabel="time (seconds)" ylabel="space (meters)"
129
+ title="Spacetime Vibes"
130
+ >
128
131
  <Points point-size={0.04} points={[
129
132
  [0, 0.5], [0.5, 0], [-0.5, 0], [0, -0.5]
130
133
  ]} />
@@ -103,6 +103,6 @@ Generated code:
103
103
  <Node id="test" pos={[0.75, 0.25]} wrap={6}>This is a test of wrapping capabilities</Node>
104
104
  <Node id="ball" pos={[0.75, 0.75]}><Ellipse aspect={1.5} fill={blue}/></Node>
105
105
  <Edge start="hello" end="test" />
106
- <Edge start="hello" end="ball" start-side="s" curve={3} />
106
+ <Edge start="hello" end="ball" start-side="s" />
107
107
  </Network>
108
108
  ```
@@ -4,11 +4,12 @@
4
4
 
5
5
  *Inherits*: **Group** > **Element**
6
6
 
7
- 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`.
7
+ 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`.
8
8
 
9
9
  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.
10
10
 
11
11
  Parameters:
12
+ - `f` — a function mapping t-values to `[x, y]` points
12
13
  - `fx`/`fy` — a function mapping from x-values, y-values, or t-values
13
14
  - `point-size` = `0.05` — a size or a function mapping from `(x, y, t, i)` values to a size
14
15
  - `point-shape` = `Dot` — a shape or function mapping from `(x, y, t, i)` values to a shape
@@ -35,9 +36,10 @@ return <Plot xlim={[0, 2*pi]} ylim={[-1.5, 1.5]} grid fill={lightgray} margin={[
35
36
 
36
37
  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**.
37
38
 
38
- 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`.
39
+ 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`.
39
40
 
40
41
  Parameters:
42
+ - `f` — a function mapping t-values to `[x, y]` points
41
43
  - `fx`/`fy` — a function mapping from x-values, y-values, or t-values
42
44
  - `xlim`/`ylim`/`tlim` — a pair of numbers specifying variable limits
43
45
  - `xvals`/`yvals`/`tvals` — a list of x-values, y-values, or t-values to use
@@ -59,9 +61,10 @@ Generated code:
59
61
 
60
62
  *Inherits*: **Shape** > **Pointstring** > **Element**
61
63
 
62
- Flexible interface to generate shapes symbolically or in combination with fixed inputs. Operates similarly to **Shape**, but generates a shape from the points generated by `fx`/`fy`.
64
+ Flexible interface to generate shapes symbolically or in combination with fixed inputs. Operates similarly to **Shape**, but generates a shape from points generated by `f` or `fx`/`fy`.
63
65
 
64
66
  Parameters:
67
+ - `f` — a function mapping t-values to `[x, y]` points
65
68
  - `fx`/`fy` — a function mapping from x-values, y-values, or t-values
66
69
  - `xlim`/`ylim`/`tlim` — a pair of numbers specifying variable limits
67
70
  - `xvals`/`yvals`/`tvals` — a list of x-values, y-values, or t-values to use
@@ -73,12 +76,11 @@ Prompt: Draw a rounded star shape with a blue fill. Wrap it in a rounded frame.
73
76
 
74
77
  Generated code:
75
78
  ```jsx
76
- const rad = t => 1 - 0.3 * cos(2.5 * t)**2
79
+ const rad = t => 1 - 0.2 * cos(5 * (t - pi/2))
77
80
  return <Frame rounded padding margin>
78
81
  <SymShape aspect fill={blue}
79
82
  tlim={[0, 2*pi]} N={200}
80
- fx={t => rad(t) * sin(t)}
81
- fy={t => rad(t) * cos(t)}
83
+ f={t => polar(t, rad(t))}
82
84
  />
83
85
  </Frame>
84
86
  ```
@@ -91,6 +93,7 @@ Flexible interface to generate smooth two-dimensional spline curves symbolically
91
93
 
92
94
 
93
95
  Parameters:
96
+ - `f` — a function mapping t-values to `[x, y]` points
94
97
  - `fx`/`fy` — a function mapping from x-values, y-values, or t-values
95
98
  - `xlim`/`ylim`/`tlim` — a pair of numbers specifying variable limits
96
99
  - `xvals`/`yvals`/`tvals` — a list of x-values, y-values, or t-values to use
@@ -118,9 +121,10 @@ return <Plot xlim={[0, 2*pi]} ylim={[-1, 1]} grid margin={0.15} aspect={phi}>
118
121
 
119
122
  *Inherits*: **Fill** > **Shape** > **Pointstring** > **Element**
120
123
 
121
- 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`.
124
+ 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`.
122
125
 
123
126
  Parameters:
127
+ - `f1`/`f2` — functions mapping t-values to `[x, y]` points for either bound
124
128
  - `fx1`/`fy1` — a function generating one of the bounds for the fill (or a constant)
125
129
  - `fx2`/`fy2` — a function generating the other bound for the fill (or a constant)
126
130
  - `xlim`/`ylim`/`tlim` — a pair of numbers specifying variable limits
@@ -135,7 +139,7 @@ Generated code:
135
139
  ```jsx
136
140
  const decay = x => exp(-0.1*x) * sin(x)
137
141
  return <Graph xlim={[0, 6*pi]} ylim={[-1, 1]} aspect={phi}>
138
- <SymFill fy1={decay} fy2={0} fill={blue} fill-opacity={0.5} N={250} />
142
+ <SymFill f1={t => [t, decay(t)]} f2={t => [t, 0]} tlim={[0, 6*pi]} fill={blue} fill-opacity={0.5} N={250} />
139
143
  <SymLine fy={decay} N={250} />
140
144
  </Graph>
141
145
  ```
@@ -29,25 +29,28 @@ Here we collect a variety of global mathematical functions and constants. You ca
29
29
  - `round(x)` — the rounding function
30
30
  - `clamp(x, lim=[0, 1])` — clamp `x` to the range `lim`
31
31
  - `rescale(x, lim=[0, 1])` — linearly rescale `x` to the range `lim`
32
- - `polar(angle, radius=1, center=[0, 0])` — convert polar coordinates (`angle` in degrees, `radius` scalar or size vector) to a 2D point around `center`
32
+ - `polar(theta, radius=1, center=[0, 0])` — convert polar coordinates (`theta` in radians, `radius` scalar or size vector) to a 2D point around `center`
33
+ - `polard(angle, radius=1, center=[0, 0])` — same as `polar` but takes `angle` in degrees
33
34
 
34
35
  Angles use gum's usual screen-space convention: `0` points right and `90` points down.
35
36
 
36
37
  **Example**
37
38
 
38
- Prompt: embed a five point star in a circle. place red dots at its vertices.
39
+ Prompt: draw a spirograph in a box for a set of example parameters
39
40
 
40
41
  Generated code:
41
42
  ```jsx
42
- const verts = linspace(0, 360, 10, false).map((t, i) => {
43
- const radius = i % 2 == 0 ? 1 : 0.5
44
- return polar(90 + t, radius)
45
- })
46
- return <Graph aspect coord={[-1, -1, 1, 1]}>
47
- <Circle pos={[0, 0]} size={2} stroke={darkgray} />
48
- <Shape points={verts} stroke={blue} stroke-width={2} />
49
- {verts.map(pos => <Dot pos={pos} size={0.05} fill={red} />)}
50
- </Graph>
43
+ const [R, r, d, k] = [10, 7, 4, 7]
44
+ const fx = t => (R - r) * cos(t) + d * cos(((R - r) / r) * t)
45
+ const fy = t => (R - r) * sin(t) - d * sin(((R - r) / r) * t)
46
+ return <TitleFrame title="Spirograph" padding={0.2} margin rounded>
47
+ <Graph aspect coord={[-R, -R, R, R]}>
48
+ <Circle pos={[0, 0]} rad={R} stroke={darkgray} stroke-dasharray={10} />
49
+ <Circle pos={[0, 0]} rad={R - r} stroke-dasharray={5} />
50
+ <SymSpline fx={fx} fy={fy} tlim={[0, 2*pi*k]} stroke={blue} stroke-width={2} />
51
+ </Graph>
52
+ <Span pos={[0.5, 1.1]} ysize={0.05} font-family={mono}>R = 10 | r = 7 | d = 4</Span>
53
+ </TitleFrame>
51
54
  ```
52
55
 
53
56
  ## Arrays
@@ -146,9 +149,10 @@ Prompt: load "data.csv" and plot each row as a blue dot
146
149
 
147
150
  Generated code:
148
151
  ```jsx
149
- return <Graph xlim={[0, 10]} ylim={[0, 10]}>
152
+ return <Graph aspect coord={[0, 0, 10, 10]}>
153
+ <Mesh2D xlocs={10} ylocs={10} opacity={0.25} />
150
154
  {loadTable('data.csv').map(({ x, y }) =>
151
- <Dot pos={[x, y]} rad={0.1} fill={blue} />
155
+ <Dot pos={[x, y]} size={0.5} fill={blue} />
152
156
  )}
153
157
  </Graph>
154
158
  ```
@@ -171,7 +175,7 @@ Generated code:
171
175
  ```jsx
172
176
  <Box rounded clip>
173
177
  <Group aspect={2}>
174
- <LoadImage id="image.png" xsize={1} />
178
+ <LoadImage id="image.png" xrect={[0, 1]} />
175
179
  </Group>
176
180
  </Box>
177
181
  ```
package/src/elems/core.ts CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  import { THEME } from '../lib/theme'
4
4
  import { DEFAULTS as D, svgns, sans, light, blue, red, d2r } from '../lib/const'
5
- import { is_scalar, abs, cos, sin, tan, cot, mul2, div2, filter_object, expand_rect, rect_box, cbox_rect, rect_cbox, merge_points, merge_rects, ensure_vector, broadcast_point, rounder, heavisign, abs_min, abs_max, rect_radial, rotate_aspect, remap_rect, rescaler, resizer, rect_size, vector_angle, polar, upright_rect } from '../lib/utils'
5
+ import { is_scalar, abs, cos, sin, tan, cot, mul2, div2, filter_object, expand_rect, rect_box, cbox_rect, rect_cbox, merge_points, merge_rects, join_limits, ensure_pair, rounder, heavisign, abs_min, abs_max, rect_radial, rotate_aspect, remap_rect, rescaler, resizer, rect_size, vector_angle, polard, upright_rect } from '../lib/utils'
6
6
  import { random } from '../lib/rng'
7
7
 
8
- import type { Point, Rect, Size, AlignValue, Align, Side, Attrs, MNumber, MPoint, Spec } from '../lib/types'
8
+ import type { Point, Rect, Size, AlignValue, Align, Side, Attrs, MNumber, MPoint, Spec, Limit } from '../lib/types'
9
9
 
10
10
  //
11
11
  // rect embedding
@@ -118,7 +118,7 @@ function adjust_rotate(rotate: number, prect: Rect, coord: Rect): number {
118
118
  const csize = rect_size(coord)
119
119
  const psize = rect_size(prect)
120
120
  const proj = div2(psize, csize)
121
- const vec = polar(rotate, proj)
121
+ const vec = polard(rotate, proj)
122
122
  return vector_angle(vec)
123
123
  }
124
124
 
@@ -217,7 +217,7 @@ class Context {
217
217
  const transform = rotate1 ? rotate_repr(rotate1, [ x0, y0 ], this.prec) : undefined
218
218
 
219
219
  // broadcast align into [ halign, valign ] components
220
- const [ hafrac, vafrac ] = ensure_vector(align, 2).map(align_frac)
220
+ const [ hafrac, vafrac ] = ensure_pair(align).map(align_frac)
221
221
  const [ x, y ] = [
222
222
  x0 + (0.5 - hafrac) * (w - w0),
223
223
  y0 + (0.5 - vafrac) * (h - h0),
@@ -259,7 +259,7 @@ function props_repr(d: Attrs, prec: number): string {
259
259
 
260
260
  // reserved keys
261
261
  const SPEC_KEYS = [ 'rect', 'coord', 'aspect', 'aspect0', 'expand', 'align', 'upright', 'offset', 'rotate', 'rotate_adjust', 'rotate_invar' ]
262
- const HELP_KEYS = [ 'pos', 'size', 'xsize', 'ysize', 'flex', 'spin', 'orient' ]
262
+ const HELP_KEYS = [ 'pos', 'size', 'xsize', 'ysize', 'rad', 'xrad', 'yrad', 'xrect', 'yrect', 'flex', 'spin', 'orient' ]
263
263
  const EXTR_KEYS = [ 'stack_size' ]
264
264
  const RESERVED_KEYS = [ ...SPEC_KEYS, ...HELP_KEYS, ...EXTR_KEYS ]
265
265
 
@@ -300,6 +300,11 @@ interface ElementArgs extends SpecArgs {
300
300
  size?: number | Size
301
301
  xsize?: number
302
302
  ysize?: number
303
+ rad?: number | Size
304
+ xrad?: number
305
+ yrad?: number
306
+ xrect?: number | Limit
307
+ yrect?: number | Limit
303
308
  flex?: boolean
304
309
  spin?: number
305
310
  orient?: number
@@ -316,7 +321,7 @@ class Element {
316
321
  attr: Attrs
317
322
 
318
323
  constructor(args: ElementArgs = {}) {
319
- const { tag, unary, children, pos, size, xsize, ysize, flex, spin, orient, ...attr0 } = args
324
+ const { tag, unary, children, pos, size: size0, xsize: xsize0, ysize: ysize0, rad, xrad, yrad, xrect, yrect, flex, spin, orient, ...attr0 } = args
320
325
  const [ spec, attr ] = spec_split(attr0, false)
321
326
  this.args = args
322
327
 
@@ -332,12 +337,27 @@ class Element {
332
337
  this.spec = spec
333
338
  this.attr = attr
334
339
 
335
- // handle pos/size conveniences
336
- if (pos != null || size != null || xsize != null || ysize != null) {
337
- const has_xy = xsize != null || ysize != null
338
- const size1 = has_xy ? [ xsize ?? 0, ysize ?? 0 ] as Point : undefined
339
- this.spec.rect ??= cbox_rect([ ...(pos ?? D.pos), ...(broadcast_point(size ?? size1 ?? D.size)) ])
340
- if (has_xy) this.spec.expand = true
340
+ // handle pos/rad/xrad/yrad conveniences
341
+ const [ x, y ] = pos ?? D.pos
342
+ const size = rad != null ? mul2(rad, 2) : size0
343
+ const xsize = xrad != null ? 2 * xrad : xsize0
344
+ const ysize = yrad != null ? 2 * yrad : ysize0
345
+
346
+ // handle rect conveniences
347
+ if (this.spec.rect != null) {
348
+ // already have a rect
349
+ } else if (size != null) {
350
+ const [ w, h ] = ensure_pair(size)
351
+ this.spec.rect = cbox_rect([ x, y, w, h ])
352
+ } else if (xrect != null || yrect != null) {
353
+ const xrect1 = ensure_pair(xrect ?? x)
354
+ const yrect1 = ensure_pair(yrect ?? y)
355
+ this.spec.rect = join_limits({ h: xrect1, v: yrect1 })
356
+ if (xrect == null || yrect == null) this.spec.expand = true
357
+ } else if (xsize != null || ysize != null) {
358
+ const [ w, h ] = ensure_pair([ xsize ?? 0, ysize ?? 0 ])
359
+ this.spec.rect = cbox_rect([ x, y, w, h ])
360
+ if (xsize == null || ysize == null) this.spec.expand = true
341
361
  }
342
362
 
343
363
  // various convenience conversions
@@ -642,7 +662,7 @@ class Svg extends Group {
642
662
  constructor(args: SvgArgs = {}) {
643
663
  const { children: children0, size : size0 = D.svg_size, padding = 1, bare = false, dims = true, filters, aspect: aspect0 = 'auto', view: view0, style, xmlns = svgns, font_family = sans, font_weight = light, prec = D.prec, ...attr } = THEME(args, 'Svg')
644
664
  const children = ensure_children(children0)
645
- const size_base = broadcast_point(size0)
665
+ const size_base = ensure_pair(size0)
646
666
 
647
667
  // precompute aspect info
648
668
  const aspect = aspect0 == 'auto' ? children_aspect(children) : aspect0
@@ -1,9 +1,9 @@
1
1
  // geometry elements
2
2
 
3
3
  import { THEME } from '../lib/theme'
4
- import { DEFAULTS as D, d2r, none, gray } from '../lib/const'
5
- import { is_boolean, is_scalar, is_array, ensure_vector, ensure_point, check_array, upright_limits, rounder, abs, rect_radial, make_mpoint, squeeze_mpoint, merge_points, broadcast_point, sub2m, add2, sub2, mul2, div2, norm, angle_direc, unit_direc, vector_angle, polar, prefix_split} from '../lib/utils'
6
- import { cubic_spline_data } from '../lib/interp'
4
+ import { DEFAULTS as D, none, gray } from '../lib/const'
5
+ import { is_boolean, is_scalar, is_array, ensure_vector, ensure_point, check_array, upright_limits, rounder, abs, rect_radial, make_mpoint, merge_points, ensure_pair, add2, sub2, mul2, div2, norm, angle_direc, unit_direc, vector_angle, polard, prefix_split} from '../lib/utils'
6
+ import { cubic_spline_data, cubic_spline_tangent } from '../lib/interp'
7
7
  import { Context, Element, Group, Rectangle } from './core'
8
8
 
9
9
  import type { Point, Rect, Limit, Grad, Attrs, MPoint, Orient, Rounded, Direc } from '../lib/types'
@@ -186,12 +186,11 @@ interface RayArgs extends LineArgs {
186
186
  class Ray extends Line {
187
187
  constructor(args: RayArgs = {}) {
188
188
  const { angle = 0, loc = D.pos, size = 0.5, ...attr } = THEME(args, 'Ray')
189
- const theta = angle * d2r
190
189
  const [ x, y ] = loc
191
190
  const [ rx, ry ] = ensure_vector(size, 2)
192
191
  const points: Point[] = [
193
192
  [ x, y ],
194
- polar(theta, [ rx, ry ], [ x, y ])
193
+ polard(angle, [ rx, ry ], [ x, y ])
195
194
  ]
196
195
  super({ points, ...attr })
197
196
  this.args = args
@@ -553,9 +552,8 @@ class CubicSplineCmd extends Command {
553
552
 
554
553
  args(ctx: Context): string {
555
554
  // use dir if provided, otherwise use tan
556
- const dist = squeeze_mpoint(sub2m(this.pos2, this.pos1)).map(abs) as Point
557
- const tan1 = this.dir1 != null ? mul2(this.dir1, dist) : this.tan1
558
- const tan2 = this.dir2 != null ? mul2(this.dir2, dist) : this.tan2
555
+ const tan1 = cubic_spline_tangent(this.pos1, this.pos2, this.dir1, this.tan1)
556
+ const tan2 = cubic_spline_tangent(this.pos1, this.pos2, this.dir2, this.tan2)
559
557
  if (tan1 == null || tan2 == null) throw new Error('Spline tangent must be defined')
560
558
 
561
559
  // compute scaled tangents
@@ -624,7 +622,7 @@ function parse_rounded(rounded: Rounded): Point[] {
624
622
  const [ rx, ry ] = rounded
625
623
  rounded = [[rx, ry], [rx, ry], [rx, ry], [rx, ry]]
626
624
  }
627
- return rounded.map(broadcast_point)
625
+ return rounded.map(ensure_pair)
628
626
  }
629
627
 
630
628
  interface RoundedRectArgs extends ElementArgs {
@@ -717,9 +715,9 @@ class Arc extends Path {
717
715
  constructor(args: ArcArgs = {}) {
718
716
  const { start, end, upright = true, ...attr } = THEME(args, 'Arc')
719
717
  if (start == null || end == null) throw new Error('Must provide `start` and `end` angles')
720
- const [ theta0, theta1 ] = upright_limits([ start, end ])
721
- const large = (theta1 - theta0) > 180
722
- const [ point0, point1 ] = [ theta0, theta1 ].map(t => polar(t, 0.5, [0.5, 0.5]))
718
+ const [ angle0, angle1 ] = upright_limits([ start, end ])
719
+ const large = (angle1 - angle0) > 180
720
+ const [ point0, point1 ] = [ angle0, angle1 ].map(t => polard(t, 0.5, [0.5, 0.5]))
723
721
  const children = [ new MoveCmd(point0), new ArcCmd(point1, 0.5, true, large) ]
724
722
  super({ children, upright, ...attr })
725
723
  this.args = args
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { THEME } from '../lib/theme'
4
4
  import { DEFAULTS as D, none } from '../lib/const'
5
- import { is_scalar, ensure_vector, broadcast_point, log, exp, max, sum, zip, div2, cumsum, reshape, repeat, meshgrid, padvec, normalize, mean, identity, invert, aspect_invariant, check_singleton, check_array, rect_center, rect_radius, join_limits, radial_rect, norm_side, intersperse, prefix_split, merge_points } from '../lib/utils'
5
+ import { is_scalar, ensure_vector, ensure_pair, log, exp, max, sum, zip, div2, cumsum, reshape, repeat, meshgrid, padvec, normalize, mean, identity, invert, aspect_invariant, check_singleton, check_array, rect_center, rect_radius, join_limits, radial_rect, norm_side, intersperse, prefix_split, merge_points } from '../lib/utils'
6
6
  import { wrapWidths } from '../lib/wrap'
7
7
 
8
8
  import { Context, Group, Element, Rectangle, Spacer, spec_split, align_frac, ensure_children } from './core'
@@ -328,7 +328,7 @@ function computeGridLayout(children: Element[][], rows: number, cols: number, {
328
328
  const aspect_ideal = exp(log_mu - mean(widths.map(log)) + mean(heights.map(log)))
329
329
 
330
330
  // adjust widths and heights to account for spacing
331
- const [spacex, spacey] = broadcast_point(spacing)
331
+ const [spacex, spacey] = ensure_pair(spacing)
332
332
  const [scalex, scaley] = [1 - spacex * (cols-1), 1 - spacey * (rows-1)]
333
333
  widths = widths.map(w => scalex * w)
334
334
  heights = heights.map(h => scaley * h)
@@ -382,11 +382,9 @@ class Grid extends Group {
382
382
  const aspect = aspect0 ?? aspect_ideal
383
383
 
384
384
  // make grid
385
- const rects = meshgrid(rranges, cranges).map(([ ylim, xlim ]) =>
386
- join_limits({ h: xlim, v: ylim })
387
- )
388
- const items = zip(children, rects).map(([ child, rect ]) =>
389
- child.clone({ rect })
385
+ const mesh = meshgrid(rranges, cranges)
386
+ const items = zip(children, mesh).map(([ child, [ ylim, xlim ] ]) =>
387
+ child.clone({ xrect: xlim, yrect: ylim })
390
388
  )
391
389
 
392
390
  // pass to Group
@@ -1,14 +1,14 @@
1
1
  // network elements
2
2
 
3
3
  import { THEME } from '../lib/theme'
4
- import { abs, sub2, mul2, check_singleton, is_string, rect_center, side_direc, prefix_split } from '../lib/utils'
4
+ import { abs, sub2, mul2, check_singleton, is_string, rect_center, side_direc, prefix_split, join_limits } from '../lib/utils'
5
5
 
6
6
  import { Context, Element, Group, ensure_children } from './core'
7
- import type { ElementArgs, GroupArgs } from './core'
8
7
  import { Frame } from './layout'
9
8
  import { Arrow } from './geometry'
10
9
  import { Text } from './text'
11
10
 
11
+ import type { ElementArgs, GroupArgs } from './core'
12
12
  import type { AlignValue, Point, Side } from '../lib/types'
13
13
 
14
14
  //
@@ -135,8 +135,9 @@ class Edge extends Element {
135
135
 
136
136
  class Network extends Group {
137
137
  constructor(args: GroupArgs = {}) {
138
- const { children: children0, coord, ...attr0 } = THEME(args, 'Network')
138
+ const { children: children0, xlim, ylim, coord: coord0, ...attr0 } = THEME(args, 'Network')
139
139
  const [ node_attr, edge_attr, attr ] = prefix_split([ 'node', 'edge' ], attr0)
140
+ const coord = coord0 ?? join_limits({ h: xlim, v: ylim })
140
141
  const children = ensure_children(children0)
141
142
 
142
143
  // process nodes and make label map
package/src/elems/plot.ts CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { THEME } from '../lib/theme'
4
4
  import { DEFAULTS as D, none, blue, white } from '../lib/const'
5
- import { sign, abs, linspace, invert_orient, join_limits, ensure_vector, is_scalar, is_string, is_object, ensure_singleton, check_singleton, rounder, enumerate, aspect_invariant, rect_aspect, merge_rects, expand_limits, flip_rect, resolve_limits, smoothstep, prefix_split, prefix_join } from '../lib/utils'
5
+ import { sign, abs, linspace, invert_orient, join_limits, split_limits, ensure_vector, is_scalar, is_string, is_object, ensure_singleton, check_singleton, rounder, enumerate, aspect_invariant, rect_aspect, merge_rects, expand_limits, flip_rect, resolve_limits, smoothstep, prefix_split, prefix_join } from '../lib/utils'
6
6
  import { Span } from './text'
7
7
 
8
8
  import { Element, Group, Spacer, spec_split, is_element, ensure_children } from './core'
@@ -416,10 +416,10 @@ interface LegendArgs extends ElementArgs {
416
416
 
417
417
  class Mesh extends Scale {
418
418
  constructor(args: MeshArgs = {}) {
419
- const { children: children0, locs: locs0, direc = 'h', lim = D.lim, span = D.lim, ...attr } = THEME(args, 'Mesh')
420
- const locs = auto_array(locs0, lim)
421
- const coord = join_limits({ [direc]: lim, [invert_orient(direc)]: span })
422
- super({ locs, direc, coord, span, ...attr })
419
+ const { children: children0, locs: locs0 = 10, direc = 'h', xlim, ylim, coord, ...attr } = THEME(args, 'Mesh')
420
+ const { [direc]: lim, [invert_orient(direc)]: span } = resolve_limits(xlim, ylim, coord as Rect)
421
+ const locs = auto_array(locs0, lim ?? D.lim)
422
+ super({ locs, direc, span, xlim, ylim, coord, ...attr })
423
423
  this.args = args
424
424
  }
425
425
  }
@@ -442,23 +442,9 @@ class VMesh extends Mesh {
442
442
 
443
443
  class Mesh2D extends Group {
444
444
  constructor(args: Mesh2DArgs = {}) {
445
- let { children: children0, locs, xlocs, ylocs, direc = 'h', xlim = D.lim, ylim = D.lim, xspan, yspan, ...attr } = THEME(args, 'Mesh2D')
446
-
447
- // set default values
448
- xlocs ??= locs
449
- ylocs ??= locs
450
- xspan ??= xlim
451
- yspan ??= ylim
452
-
453
- // convert locs to arrays
454
- xlocs = auto_array(xlocs, xlim)
455
- ylocs = auto_array(ylocs, ylim)
456
-
457
- // create meshes
458
- const hmesh = new HMesh({ locs: xlocs, span: yspan, lim: xlim, ...attr })
459
- const vmesh = new VMesh({ locs: ylocs, span: xspan, lim: ylim, ...attr })
460
-
461
- // pass to Group
445
+ let { children: children0, locs, xlocs, ylocs, direc = 'h', xlim, ylim, coord, ...attr } = THEME(args, 'Mesh2D')
446
+ const hmesh = new HMesh({ locs: xlocs ?? locs, xlim, ylim, coord })
447
+ const vmesh = new VMesh({ locs: ylocs ?? locs, xlim, ylim, coord })
462
448
  super({ children: [ hmesh, vmesh ], ...attr })
463
449
  this.args = args
464
450
  }
@@ -527,8 +513,8 @@ function outer_limits(children: Element[], { xlim, ylim, padding = 0 }: { xlim?:
527
513
  if (children.length == 0) return
528
514
 
529
515
  // pull in child coordinate system
530
- const coord0 = merge_rects(children.map((c: Element) => c.graphCoord()))
531
- const { xlim: xlim0, ylim: ylim0 } = resolve_limits(xlim, ylim, coord0)
516
+ const coord = merge_rects(children.map((c: Element) => c.graphCoord()))
517
+ const { h: xlim0, v: ylim0 } = resolve_limits(xlim, ylim, coord)
532
518
 
533
519
  // expand with padding
534
520
  const [ xpad, ypad ] = ensure_vector(padding, 2)
@@ -607,13 +593,13 @@ interface PlotArgs extends BoxArgs {
607
593
  class Plot extends Box {
608
594
  constructor(args: PlotArgs = {}) {
609
595
  let {
610
- children: children0, xlim, ylim, axis = true, xaxis, yaxis, xticks = 5, yticks = 5, xanchor, yanchor, grid, xgrid, ygrid, xlabel, ylabel, title, tick_size = 0.015, label_size = 0.05, label_offset = 0.125, title_size = 0.075, title_offset = 0.05, xlabel_size, ylabel_size, xlabel_offset, ylabel_offset, xtick_label_offset = 0.75, ytick_label_offset = 0.25, xtick_size, ytick_size, padding, margin, aspect: aspect0 = 'auto', clip, debug = false, ...attr0
596
+ children: children0, xlim, ylim, axis = true, xaxis, yaxis, xticks = 5, yticks = 5, xanchor, yanchor, grid, xgrid, ygrid, xlabel, ylabel, title, tick_size = 0.015, label_size = 0.05, label_offset = 0.125, title_size = 0.075, title_offset = 0.05, xlabel_size, ylabel_size, xlabel_offset, ylabel_offset, xtick_label_offset = 0.75, ytick_label_offset = 0.25, xtick_size, ytick_size, padding, margin, coord: coord0 = 'auto', aspect: aspect0 = 'auto', clip, debug = false, ...attr0
611
597
  } = THEME(args, 'Plot')
612
598
  const children = ensure_children(children0)
613
599
 
614
600
  // determine coordinate system and aspect
615
- const coord = outer_limits(children, { xlim, ylim, padding }) as Rect
616
- const [ xmin, ymin, xmax, ymax ] = coord
601
+ const coord = coord0 == 'auto' ? outer_limits(children, { xlim, ylim, padding }) : coord0
602
+ const [ xmin, ymin, xmax, ymax ] = coord ?? D.coord
617
603
  xlim = [ xmin, xmax ]
618
604
  ylim = [ ymin, ymax ]
619
605
 
@@ -666,9 +652,8 @@ class Plot extends Box {
666
652
  if (xaxis === true) xaxis = new HAxis({ ticks: xticks, lim: xlim })
667
653
  if (xaxis != null && xaxis !== false) {
668
654
  const xtick_size1 = xtick_size * (ymax - ymin)
669
- const xaxis_ylim: Limit = [ xanchor - xtick_size1, xanchor + xtick_size1 ]
670
- const xaxis_rect = join_limits({ h: xlim, v: xaxis_ylim })
671
- xaxis = xaxis.clone({ rect: xaxis_rect, ...xaxis_attr }) as HAxis
655
+ const xaxis_ylim = [ xanchor - xtick_size1, xanchor + xtick_size1 ]
656
+ xaxis = xaxis.clone({ xrect: xlim, yrect: xaxis_ylim, ...xaxis_attr }) as HAxis
672
657
  fg_elems.push(xaxis)
673
658
  }
674
659
 
@@ -676,16 +661,15 @@ class Plot extends Box {
676
661
  if (yaxis === true) yaxis = new VAxis({ ticks: yticks, lim: ylim })
677
662
  if (yaxis != null && yaxis !== false) {
678
663
  const ytick_size1 = ytick_size * (xmax - xmin)
679
- const yaxis_xlim: Limit = [ yanchor - ytick_size1, yanchor + ytick_size1 ]
680
- const yaxis_rect = join_limits({ h: yaxis_xlim, v: ylim })
681
- yaxis = yaxis.clone({ rect: yaxis_rect, ...yaxis_attr }) as VAxis
664
+ const yaxis_xlim = [ yanchor - ytick_size1, yanchor + ytick_size1 ]
665
+ yaxis = yaxis.clone({ xrect: yaxis_xlim, yrect: ylim, ...yaxis_attr }) as VAxis
682
666
  fg_elems.push(yaxis)
683
667
  }
684
668
 
685
669
  // automatic xgrid generation
686
670
  if (xgrid != null && xgrid !== false) {
687
671
  const locs = (xgrid === true && xaxis != null && xaxis !== false) ? xaxis.locs : xgrid
688
- const xgrid_elem = new HMesh({ locs: locs as number[], lim: xlim, rect: coord, ...xgrid_attr })
672
+ const xgrid_elem = new HMesh({ locs: locs as number[], ...xgrid_attr })
689
673
  bg_elems.unshift(xgrid_elem)
690
674
  } else {
691
675
  xgrid = undefined
@@ -694,7 +678,7 @@ class Plot extends Box {
694
678
  // automatic ygrid generation
695
679
  if (ygrid != null && ygrid !== false) {
696
680
  const locs = (ygrid === true && yaxis != null && yaxis !== false) ? yaxis.locs : ygrid
697
- const ygrid_elem = new VMesh({ locs: locs as number[], lim: ylim, rect: coord, ...ygrid_attr })
681
+ const ygrid_elem = new VMesh({ locs: locs as number[], ...ygrid_attr })
698
682
  bg_elems.unshift(ygrid_elem)
699
683
  } else {
700
684
  ygrid = undefined