ggaction 0.0.16 → 0.0.18

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 (299) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +3 -2
  3. package/knowledge/action-card.schema.json +1 -1
  4. package/knowledge/action-cards.json +1 -1
  5. package/knowledge/intent-taxonomy.json +1 -1
  6. package/knowledge/mcp-resources.json +1 -1
  7. package/knowledge/task-resolver.js +1 -1
  8. package/package.json +10 -4
  9. package/src/BasicChartProgram.js +1 -1
  10. package/src/accessibility.js +3 -2
  11. package/src/actions/boxPlots/create.js +14 -4
  12. package/src/actions/boxPlots/edit.js +39 -33
  13. package/src/actions/boxPlots/materialize.js +7 -3
  14. package/src/actions/boxPlots/options.js +37 -11
  15. package/src/actions/canvas/actions.js +70 -95
  16. package/src/actions/canvas/fitting.js +16 -1
  17. package/src/actions/canvas/index.js +3 -0
  18. package/src/actions/canvas/plotBorder.js +28 -0
  19. package/src/actions/charts/area.js +2 -2
  20. package/src/actions/charts/bar.js +34 -7
  21. package/src/actions/charts/ecdf.js +4 -18
  22. package/src/actions/charts/endpoints.js +4 -18
  23. package/src/actions/charts/heatmap.js +4 -4
  24. package/src/actions/charts/index.js +2 -0
  25. package/src/actions/charts/interval-regression.js +2 -2
  26. package/src/actions/charts/line.js +1 -1
  27. package/src/actions/charts/pie.js +3 -47
  28. package/src/actions/charts/radial.js +34 -12
  29. package/src/actions/charts/scatter.js +16 -2
  30. package/src/actions/charts/shared.js +2 -2
  31. package/src/actions/charts/text.js +35 -0
  32. package/src/actions/composition/actions.js +6 -2
  33. package/src/actions/data/bin.js +1 -1
  34. package/src/actions/data/box.js +2 -1
  35. package/src/actions/data/create.js +18 -5
  36. package/src/actions/data/density.js +1 -1
  37. package/src/actions/data/derived.js +15 -1
  38. package/src/actions/data/edit.js +20 -7
  39. package/src/actions/data/filter.js +15 -4
  40. package/src/actions/data/index.js +3 -0
  41. package/src/actions/data/interval.js +2 -0
  42. package/src/actions/data/regression.js +6 -2
  43. package/src/actions/data/revise.js +37 -5
  44. package/src/actions/data/shared.js +33 -4
  45. package/src/actions/data/sort.js +21 -0
  46. package/src/actions/data/stack.js +3 -1
  47. package/src/actions/data/summary.js +1 -1
  48. package/src/actions/encodings/angle.js +4 -1
  49. package/src/actions/encodings/appearance.js +43 -31
  50. package/src/actions/encodings/barWidth.js +36 -11
  51. package/src/actions/encodings/channels.js +0 -1
  52. package/src/actions/encodings/color/continuous.js +5 -2
  53. package/src/actions/encodings/color/index.js +5 -2
  54. package/src/actions/encodings/color/policy.js +10 -3
  55. package/src/actions/encodings/density/resolve.js +1 -6
  56. package/src/actions/encodings/offset.js +4 -1
  57. package/src/actions/encodings/position/policies/arc.js +12 -10
  58. package/src/actions/encodings/position/policies/area.js +3 -3
  59. package/src/actions/encodings/position/policies/bar.js +15 -6
  60. package/src/actions/encodings/position/policies/line.js +17 -6
  61. package/src/actions/encodings/position/resolve.js +9 -3
  62. package/src/actions/encodings/ranged.js +23 -1
  63. package/src/actions/encodings/remove.js +3 -2
  64. package/src/actions/encodings/ruleAppearance.js +4 -1
  65. package/src/actions/encodings/seriesLayout.js +1 -0
  66. package/src/actions/encodings/stroke.js +4 -1
  67. package/src/actions/encodings/strokeDash.js +6 -1
  68. package/src/actions/encodings/text.js +12 -4
  69. package/src/actions/errorBands/create.js +2 -2
  70. package/src/actions/errorBands/edit.js +2 -1
  71. package/src/actions/facets/actions.js +158 -240
  72. package/src/actions/gradientPlots/resolve.js +2 -17
  73. package/src/actions/guides/applicability.js +1 -1
  74. package/src/actions/guides/axes/axis.js +16 -4
  75. package/src/actions/guides/axes/edit.js +4 -4
  76. package/src/actions/guides/axes/labels.js +120 -46
  77. package/src/actions/guides/axes/lines.js +6 -5
  78. package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
  79. package/src/actions/guides/axes/parallel/policy.js +8 -7
  80. package/src/actions/guides/axes/remove.js +3 -2
  81. package/src/actions/guides/axes/tickGroups.js +5 -2
  82. package/src/actions/guides/axes/ticks.js +6 -3
  83. package/src/actions/guides/axes/titles.js +71 -26
  84. package/src/actions/guides/basic.js +2 -0
  85. package/src/actions/guides/facadeLegend.js +10 -2
  86. package/src/actions/guides/grids/grid.js +7 -1
  87. package/src/actions/guides/grids/resolve.js +59 -12
  88. package/src/actions/guides/legends/blocks.js +3 -1
  89. package/src/actions/guides/legends/categorical/actions.js +70 -17
  90. package/src/actions/guides/legends/categorical/components.js +12 -8
  91. package/src/actions/guides/legends/categorical/index.js +6 -0
  92. package/src/actions/guides/legends/categorical/layout.js +37 -8
  93. package/src/actions/guides/legends/categorical/options.js +31 -20
  94. package/src/actions/guides/legends/categorical/resolve.js +2 -2
  95. package/src/actions/guides/legends/categorical/symbols.js +18 -12
  96. package/src/actions/guides/legends/categorical/validation.js +1 -6
  97. package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
  98. package/src/actions/guides/legends/continuous/common.js +1 -447
  99. package/src/actions/guides/legends/continuous/gradient.js +47 -34
  100. package/src/actions/guides/legends/continuous/interval.js +4 -4
  101. package/src/actions/guides/legends/continuous/opacity.js +19 -12
  102. package/src/actions/guides/legends/edit.js +1 -604
  103. package/src/actions/guides/legends/focused.js +2 -2
  104. package/src/actions/guides/legends/group.js +1 -0
  105. package/src/actions/guides/legends/lane.js +1 -516
  106. package/src/actions/guides/legends/remove.js +3 -0
  107. package/src/actions/guides/legends/sampling.js +1 -1
  108. package/src/actions/guides/legends/size.js +51 -43
  109. package/src/actions/guides/legends/strokeWidth.js +11 -29
  110. package/src/actions/guides/legends/target.js +3 -3
  111. package/src/actions/guides/legends/transition.js +21 -7
  112. package/src/actions/guides/polar/axes/labels.js +4 -2
  113. package/src/actions/guides/polar/axes/titles.js +7 -5
  114. package/src/actions/guides/polar/resolve.js +6 -14
  115. package/src/actions/index.js +16 -16
  116. package/src/actions/marks/arc/actions.js +3 -10
  117. package/src/actions/marks/area/actions.js +8 -5
  118. package/src/actions/marks/area/materialize.js +8 -9
  119. package/src/actions/marks/bar/create.js +17 -4
  120. package/src/actions/marks/bar/edit.js +18 -7
  121. package/src/actions/marks/bar/materialize.js +9 -6
  122. package/src/actions/marks/line/actions.js +11 -4
  123. package/src/actions/marks/line/materialize.js +9 -5
  124. package/src/actions/marks/point/create.js +8 -1
  125. package/src/actions/marks/point/edit.js +13 -6
  126. package/src/actions/marks/point/materialize.js +4 -2
  127. package/src/actions/marks/rect/actions.js +26 -11
  128. package/src/actions/marks/remove.js +2 -0
  129. package/src/actions/marks/rule/actions.js +31 -12
  130. package/src/actions/marks/text/actions.js +42 -11
  131. package/src/actions/marks/text/layout.js +3 -0
  132. package/src/actions/marks/tick/actions.js +23 -8
  133. package/src/actions/primitives/semanticAction.js +37 -3
  134. package/src/actions/primitives/semanticValidation/dataset.js +13 -1
  135. package/src/actions/primitives/semanticValidation/layer.js +26 -2
  136. package/src/actions/primitives/semanticValidation/scale.js +6 -0
  137. package/src/actions/regression/create.js +26 -5
  138. package/src/actions/regression/edit.js +50 -15
  139. package/src/actions/regression/follow.js +22 -0
  140. package/src/actions/regression/resolve.js +2 -6
  141. package/src/actions/scales/channels.js +20 -15
  142. package/src/actions/scales/consumers/common.js +2 -1
  143. package/src/actions/scales/consumers/families.js +16 -5
  144. package/src/actions/scales/create.js +10 -4
  145. package/src/actions/scales/definitions.js +1 -425
  146. package/src/actions/scales/edit.js +3 -3
  147. package/src/actions/scales/editPolicy.js +9 -3
  148. package/src/actions/scales/materialize.js +1 -1
  149. package/src/actions/scales/parallel.js +1 -1
  150. package/src/actions/scales/patch.js +1 -1
  151. package/src/actions/scales/preview.js +49 -2
  152. package/src/actions/scales/quantitativeColor.js +3 -3
  153. package/src/actions/selection/actions.js +1 -13
  154. package/src/actions/theme/reconcile.js +83 -27
  155. package/src/actions/theme/state.js +12 -0
  156. package/src/actions/titles/actions.js +5 -18
  157. package/src/actions/titles/resolve.js +41 -19
  158. package/src/actions/violinPlots/edit.js +3 -20
  159. package/src/core/action.js +3 -2
  160. package/src/core/compositionState.js +4 -1
  161. package/src/core/font.js +9 -2
  162. package/src/core/optionRequirements.js +1 -0
  163. package/src/core/programState.js +5 -1
  164. package/src/core/resourceReferences.js +3 -0
  165. package/src/core/textMetricProfile.js +3 -2
  166. package/src/core/textMetrics.js +41 -4
  167. package/src/core/validation.js +26 -0
  168. package/src/core/vocabulary.js +4 -0
  169. package/src/grammar/aggregate.js +4 -18
  170. package/src/grammar/arcs.js +1 -386
  171. package/src/grammar/areaPaint.js +10 -0
  172. package/src/grammar/areaSeries.js +18 -10
  173. package/src/grammar/bars/geometry.js +30 -3
  174. package/src/grammar/bars/policy.js +12 -1
  175. package/src/grammar/bin.js +41 -22
  176. package/src/grammar/bin2d.js +1 -6
  177. package/src/grammar/boxPlot.js +56 -9
  178. package/src/grammar/categoryOrder.js +1 -6
  179. package/src/grammar/complete.js +15 -19
  180. package/src/grammar/computed.js +92 -25
  181. package/src/grammar/curveCommands.js +29 -16
  182. package/src/grammar/datasetSchema.js +382 -0
  183. package/src/grammar/density.js +53 -5
  184. package/src/grammar/displayLabels.js +28 -0
  185. package/src/grammar/ecdf.js +2 -9
  186. package/src/grammar/facets/dependencies.js +6 -2
  187. package/src/grammar/facets/guides.js +5 -0
  188. package/src/grammar/facets/index.js +2 -5
  189. package/src/grammar/filter.js +133 -39
  190. package/src/grammar/fold.js +2 -12
  191. package/src/grammar/gradientProfile.js +1 -6
  192. package/src/grammar/impute.js +21 -48
  193. package/src/grammar/interval.js +34 -10
  194. package/src/grammar/itemMissing.js +59 -0
  195. package/src/grammar/legendGradient.js +18 -0
  196. package/src/grammar/legendGroups.js +1 -0
  197. package/src/grammar/legendTitleStyle.js +22 -0
  198. package/src/grammar/lineSeries.js +32 -17
  199. package/src/grammar/markLabels.js +5 -2
  200. package/src/grammar/normalize.js +4 -54
  201. package/src/grammar/numeric.js +1 -290
  202. package/src/grammar/parallelCoordinates.js +1 -6
  203. package/src/grammar/parseTime.js +41 -0
  204. package/src/grammar/pathCommands.js +11 -4
  205. package/src/grammar/polar.js +1 -184
  206. package/src/grammar/positionCompatibility.js +3 -3
  207. package/src/grammar/regression/derive.js +63 -16
  208. package/src/grammar/regression/index.js +1 -0
  209. package/src/grammar/regression/models.js +116 -59
  210. package/src/grammar/regression/parameters.js +69 -6
  211. package/src/grammar/roundedRect.js +42 -54
  212. package/src/grammar/scales/appearance.js +1 -206
  213. package/src/grammar/scales/color.js +1 -382
  214. package/src/grammar/scales/colorConsumers.js +5 -2
  215. package/src/grammar/scales/definition.js +1 -190
  216. package/src/grammar/scales/discretized.js +1 -195
  217. package/src/grammar/scales/size.js +1 -456
  218. package/src/grammar/scales/temporal.js +2 -13
  219. package/src/grammar/scales/transformed.js +1 -383
  220. package/src/grammar/scales/types.js +1 -1
  221. package/src/grammar/scales/validation.js +1 -66
  222. package/src/grammar/schemas/concreteGraphic.js +11 -0
  223. package/src/grammar/schemas/graphic.js +3 -0
  224. package/src/grammar/schemas/semanticPath.js +5 -3
  225. package/src/grammar/sort.js +88 -0
  226. package/src/grammar/stack.js +2 -10
  227. package/src/grammar/statisticalReference.js +1 -6
  228. package/src/grammar/summary.js +120 -45
  229. package/src/grammar/text.js +19 -0
  230. package/src/grammar/timeUnit.js +16 -7
  231. package/src/grammar/transformKeys.js +47 -0
  232. package/src/grammar/transformTopology.js +1 -0
  233. package/src/grammar/transforms.js +25 -12
  234. package/src/grammar/valueFormat.js +17 -5
  235. package/src/grammar/weightedStatistics.js +1 -6
  236. package/src/grammar/window.js +4 -58
  237. package/src/inspection.js +495 -0
  238. package/src/inspectionDescriptors.js +3 -0
  239. package/src/layout/canvas.js +1 -110
  240. package/src/layout/facets.js +1 -244
  241. package/src/layout/labels.js +1 -0
  242. package/src/layout/legend.js +13 -0
  243. package/src/layout/legendGroup.js +1 -0
  244. package/src/layout/legendItems.js +59 -15
  245. package/src/layout/legendLane.js +1 -441
  246. package/src/layout/legendText.js +1 -0
  247. package/src/layout/title.js +35 -13
  248. package/src/materialization/bars/aggregate.js +11 -5
  249. package/src/materialization/bars/histogram.js +4 -1
  250. package/src/materialization/bars/ranged.js +19 -6
  251. package/src/materialization/bars/resolve.js +26 -3
  252. package/src/materialization/coordinateBounds.js +8 -0
  253. package/src/materialization/dependencies.js +4 -44
  254. package/src/materialization/facetGuides/layout.js +1 -0
  255. package/src/materialization/facetGuides/placement.js +19 -8
  256. package/src/materialization/facetGuides/preparation.js +15 -7
  257. package/src/materialization/facetHeaders.js +1 -347
  258. package/src/materialization/facets.js +1 -185
  259. package/src/materialization/graphicHierarchy.js +1 -1
  260. package/src/materialization/guides/layout.js +1 -57
  261. package/src/materialization/guides/resources.js +1 -123
  262. package/src/materialization/layout.js +53 -0
  263. package/src/materialization/legendGroups.js +1 -0
  264. package/src/materialization/legendText.js +1 -0
  265. package/src/materialization/legends.js +1 -1
  266. package/src/materialization/marks/capabilities.js +5 -6
  267. package/src/materialization/rect.js +1 -1
  268. package/src/materialization/revisionIdentity.js +19 -0
  269. package/src/materialization/scaleGuideDependencies.js +1 -1
  270. package/src/materialization/scales/map.js +3 -0
  271. package/src/materialization/scales/policies/arc.js +8 -7
  272. package/src/materialization/scales/policies/bar.js +19 -7
  273. package/src/materialization/scales/policies/series.js +2 -1
  274. package/src/materialization/scales/resolve.js +72 -7
  275. package/src/materialization/selection/items/arc.js +1 -1
  276. package/src/materialization/selection/items/bar.js +7 -6
  277. package/src/materialization/selection/items/path.js +7 -6
  278. package/src/materialization/selection/items/rect.js +2 -2
  279. package/src/materialization/text.js +17 -6
  280. package/src/materialization/textLines.js +15 -0
  281. package/src/persistence/codec.js +37 -6
  282. package/src/persistence/validation.js +47 -0
  283. package/src/persistence.js +94 -8
  284. package/src/renderers/canvas/circle.js +6 -3
  285. package/src/renderers/canvas/fill.js +7 -1
  286. package/src/renderers/canvas/index.js +2 -1
  287. package/src/renderers/canvas/line.js +1 -0
  288. package/src/renderers/canvas/path.js +1 -1
  289. package/src/renderers/canvas/rect.js +11 -8
  290. package/src/renderers/canvas/stroke.js +2 -1
  291. package/src/renderers/canvas/text.js +5 -3
  292. package/src/renderers/svg.js +4 -1
  293. package/src/selectors/layers.js +19 -0
  294. package/src/theme/defaults.js +8 -2
  295. package/src/version.js +1 -1
  296. package/types/basic.d.ts +1 -0
  297. package/types/index.d.ts +7 -0
  298. package/types/inspection.d.ts +17 -0
  299. package/types/program.d.ts +494 -112
@@ -1,3 +1,6 @@
1
+ import {parseTimeValue,validateParseTimeFormat} from "./parseTime.js";
2
+ import { rejectUnknownProperties as rejectUnknownKeys } from "../core/validation.js";
3
+ import { requireStringValue as requireField } from "../core/validation.js";
1
4
  import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
2
5
 
3
6
  const TRANSFORM_KEYS = Object.freeze(["type", "as", "expression"]);
@@ -7,23 +10,12 @@ const NUMERIC_UNARY = new Set(["negate", "absolute", "log", "sqrt"]);
7
10
  const BOOLEAN_UNARY = new Set(["not", "isNull"]);
8
11
  const LOGICAL = new Set(["and", "or"]);
9
12
  const VARIADIC = new Set(["coalesce", "concat"]);
13
+ const RESERVED_PATH_KEYS = new Set(["__proto__", "prototype", "constructor"]);
10
14
  const MAX_DEPTH = 16;
11
15
  const MAX_NODES = 128;
12
16
  const MAX_WORK = 10_000_000;
13
17
 
14
- function rejectUnknownKeys(value, supported, label) {
15
- const unknown = Object.keys(value).find(key => !supported.includes(key));
16
- if (unknown !== undefined) {
17
- throw new Error(`Unknown ${label} property "${unknown}".`);
18
- }
19
- }
20
18
 
21
- function requireField(value, label) {
22
- if (typeof value !== "string" || value.length === 0) {
23
- throw new TypeError(`${label} must be a non-empty string.`);
24
- }
25
- return value;
26
- }
27
19
 
28
20
  function validateConstant(value) {
29
21
  if (value === null || typeof value === "string" || typeof value === "boolean") return;
@@ -50,7 +42,7 @@ function validateOperands(expression, minimum) {
50
42
  }
51
43
  }
52
44
 
53
- function inspectExpression(expression, depth, state) {
45
+ function inspectExpression(expression, depth, state, optionalPathRoot = false) {
54
46
  if (!isPlainObject(expression)) {
55
47
  throw new TypeError("Computed expression nodes must be plain objects.");
56
48
  }
@@ -72,7 +64,9 @@ function inspectExpression(expression, depth, state) {
72
64
  }
73
65
  if (kinds[0] === "field") {
74
66
  rejectUnknownKeys(expression, ["field"], "computed field expression");
75
- state.fields.add(requireField(expression.field, "Computed expression field"));
67
+ const field = requireField(expression.field, "Computed expression field");
68
+ state.fields.add(field);
69
+ if (!optionalPathRoot) state.requiredFields.add(field);
76
70
  return;
77
71
  }
78
72
  if (kinds[0] === "constant") {
@@ -88,7 +82,39 @@ function inspectExpression(expression, depth, state) {
88
82
  inspectExpression(expression.right, depth + 1, state);
89
83
  return;
90
84
  }
91
- if (NUMERIC_UNARY.has(expression.op) || BOOLEAN_UNARY.has(expression.op)) {
85
+ if (expression.op === "getPath") {
86
+ rejectUnknownKeys(expression, ["op", "operand", "path"], "computed path expression");
87
+ if (!isPlainObject(expression.operand) ||
88
+ Object.keys(expression.operand).length !== 1 ||
89
+ !Object.hasOwn(expression.operand, "field")) {
90
+ throw new TypeError("Computed getPath operand must be a literal field reference.");
91
+ }
92
+ if (!Array.isArray(expression.path) || expression.path.length < 1 ||
93
+ expression.path.length > 8 || !Array.from(expression.path).every(key =>
94
+ typeof key === "string" && key.length > 0 && !RESERVED_PATH_KEYS.has(key))) {
95
+ throw new TypeError("Computed getPath path must contain 1 to 8 nonempty, nonreserved string keys.");
96
+ }
97
+ inspectExpression(expression.operand, depth + 1, state, true);
98
+ return;
99
+ }
100
+ if (expression.op === "replaceLiteral") {
101
+ rejectUnknownKeys(expression, ["op", "operand", "search", "replacement"], "computed literal replacement");
102
+ requireNode(expression, "operand", expression.op);
103
+ if (typeof expression.search !== "string" || expression.search.length === 0 ||
104
+ typeof expression.replacement !== "string") {
105
+ throw new TypeError("Computed replaceLiteral requires a nonempty string search and string replacement.");
106
+ }
107
+ inspectExpression(expression.operand, depth + 1, state);
108
+ return;
109
+ }
110
+ if (expression.op === "parseTime") {
111
+ rejectUnknownKeys(expression, ["op", "operand", "format"], "computed time expression");
112
+ requireNode(expression, "operand", expression.op);
113
+ validateParseTimeFormat(expression.format);
114
+ inspectExpression(expression.operand, depth + 1, state);
115
+ return;
116
+ }
117
+ if (NUMERIC_UNARY.has(expression.op) || BOOLEAN_UNARY.has(expression.op) || expression.op === "toNumber") {
92
118
  rejectUnknownKeys(expression, ["op", "operand"], "computed unary expression");
93
119
  requireNode(expression, "operand", expression.op);
94
120
  inspectExpression(expression.operand, depth + 1, state);
@@ -131,7 +157,7 @@ function inspectComputedTransform(transform) {
131
157
  throw new Error(`Unsupported computed transform "${transform.type}".`);
132
158
  }
133
159
  requireField(transform.as, "Computed output field");
134
- const state = { nodes: 0, fields: new Set(), ancestors: new WeakSet() };
160
+ const state = { nodes: 0, fields: new Set(), requiredFields: new Set(), ancestors: new WeakSet() };
135
161
  inspectExpression(transform.expression, 1, state);
136
162
  return state;
137
163
  }
@@ -233,6 +259,27 @@ function compare(left, right, operation, rowIndex) {
233
259
  : order >= 0;
234
260
  }
235
261
 
262
+ function ownDataProperty(record, key, rowIndex) {
263
+ const descriptor = Object.getOwnPropertyDescriptor(record, key);
264
+ if (descriptor === undefined) return undefined;
265
+ if (!Object.hasOwn(descriptor, "value")) {
266
+ throw new TypeError(`Computed getPath cannot read an accessor at row ${rowIndex}.`);
267
+ }
268
+ return descriptor.value;
269
+ }
270
+
271
+ function nestedScalar(expression, row, rowIndex) {
272
+ let value = ownDataProperty(row, expression.operand.field, rowIndex);
273
+ for (const key of expression.path) {
274
+ if (value === undefined || value === null) return null;
275
+ if (!isPlainObject(value)) {
276
+ throw new TypeError(`Computed getPath requires plain records at row ${rowIndex}.`);
277
+ }
278
+ value = ownDataProperty(value, key, rowIndex);
279
+ }
280
+ return scalar(value, "Computed getPath result", rowIndex);
281
+ }
282
+
236
283
  function evaluate(expression, row, rowIndex) {
237
284
  if (Object.hasOwn(expression, "field")) {
238
285
  return scalar(
@@ -243,6 +290,16 @@ function evaluate(expression, row, rowIndex) {
243
290
  }
244
291
  if (Object.hasOwn(expression, "constant")) return expression.constant;
245
292
  const operation = expression.op;
293
+ if (operation === "getPath") return nestedScalar(expression, row, rowIndex);
294
+ if (operation === "replaceLiteral") {
295
+ const value = evaluate(expression.operand, row, rowIndex);
296
+ if (typeof value !== "string") {
297
+ throw new TypeError(`Computed replaceLiteral requires a string at row ${rowIndex}.`);
298
+ }
299
+ const index = value.indexOf(expression.search);
300
+ return index < 0 ? value
301
+ : value.slice(0, index) + expression.replacement + value.slice(index + expression.search.length);
302
+ }
246
303
  if (operation === "if") {
247
304
  return boolean(evaluate(expression.condition, row, rowIndex), operation, rowIndex)
248
305
  ? evaluate(expression.then, row, rowIndex)
@@ -282,6 +339,17 @@ function evaluate(expression, row, rowIndex) {
282
339
  ? value === null
283
340
  : !boolean(value, operation, rowIndex);
284
341
  }
342
+ if (operation === "parseTime") {
343
+ return parseTimeValue(evaluate(expression.operand, row, rowIndex), expression.format);
344
+ }
345
+ if (operation === "toNumber") {
346
+ const value = evaluate(expression.operand, row, rowIndex);
347
+ if (typeof value === "number") return number(value, operation, rowIndex);
348
+ if (typeof value !== "string" || !/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(value.trim())) {
349
+ throw new TypeError(`Computed toNumber requires a finite number or decimal string at row ${rowIndex}.`);
350
+ }
351
+ return number(Number(value.trim()), operation, rowIndex);
352
+ }
285
353
  if (NUMERIC_UNARY.has(operation)) {
286
354
  const value = number(evaluate(expression.operand, row, rowIndex), operation, rowIndex);
287
355
  if (operation === "log" && value <= 0) {
@@ -318,7 +386,7 @@ function evaluate(expression, row, rowIndex) {
318
386
  }
319
387
 
320
388
  export function deriveComputedRows(rows, transform) {
321
- const { nodes, fields } = inspectComputedTransform(transform);
389
+ const { nodes, fields, requiredFields } = inspectComputedTransform(transform);
322
390
  if (rows.length * nodes > MAX_WORK) {
323
391
  throw new RangeError(`Computed work cannot exceed ${MAX_WORK} row-nodes.`);
324
392
  }
@@ -326,15 +394,14 @@ export function deriveComputedRows(rows, transform) {
326
394
  throw new Error(`Computed output field "${transform.as}" already exists.`);
327
395
  }
328
396
  if (rows.length > 0) {
329
- rows.forEach((row, index) => {
330
- for (const field of fields) {
331
- if (!Object.hasOwn(row, field)) {
332
- throw new Error(
333
- `Computed source does not contain field "${field}" at row ${index}.`
334
- );
397
+ for (const field of fields) {
398
+ const knownOptional = !requiredFields.has(field) && rows.some(row => Object.hasOwn(row, field));
399
+ rows.forEach((row, index) => {
400
+ if (!knownOptional && !Object.hasOwn(row, field)) {
401
+ throw new Error(`Computed source does not contain field "${field}" at row ${index}.`);
335
402
  }
336
- }
337
- });
403
+ });
404
+ }
338
405
  }
339
406
  let outputType;
340
407
  return rows.map((row, index) => {
@@ -1,6 +1,5 @@
1
1
  import { freezeOwned, isPlainObject } from "../core/immutable.js";
2
- import { validateGeneratedItemLimit } from "../core/validation.js";
3
- import { buildLinearPathCommands } from "./pathCommands.js";
2
+ import { buildLinearPathCommands, validatePathCommandCount } from "./pathCommands.js";
4
3
 
5
4
  export const CURVE_INTERPOLATIONS = Object.freeze([
6
5
  "linear",
@@ -42,6 +41,15 @@ function validatePoints(points) {
42
41
  return points;
43
42
  }
44
43
 
44
+ export function validateCardinalTension(tension, curve) {
45
+ if (tension === undefined) return 0;
46
+ if (curve !== "cardinal") throw new Error("Line tension requires cardinal interpolation.");
47
+ if (!Number.isFinite(tension) || tension < 0 || tension > 1) {
48
+ throw new RangeError("Cardinal tension must be between 0 and 1.");
49
+ }
50
+ return tension;
51
+ }
52
+
45
53
  export function validateCurveInterpolation(curve) {
46
54
  if (!CURVE_INTERPOLATIONS.includes(curve)) {
47
55
  throw new Error(`Unsupported curve interpolation "${curve}".`);
@@ -134,7 +142,7 @@ function buildBasisCommands(points) {
134
142
  return ownCommands(commands);
135
143
  }
136
144
 
137
- function buildCardinalCommands(points) {
145
+ function buildCardinalCommands(points, tension = 0) {
138
146
  const commands = [{ op: "M", x: points[0].x, y: points[0].y }];
139
147
  for (let index = 0; index < points.length - 1; index += 1) {
140
148
  const before = points[index - 1] ?? points[index];
@@ -143,10 +151,10 @@ function buildCardinalCommands(points) {
143
151
  const after = points[index + 2] ?? next;
144
152
  commands.push({
145
153
  op: "C",
146
- x1: current.x + (next.x - before.x) / 6,
147
- y1: current.y + (next.y - before.y) / 6,
148
- x2: next.x - (after.x - current.x) / 6,
149
- y2: next.y - (after.y - current.y) / 6,
154
+ x1: current.x + (next.x - before.x) / 6 * (1 - tension),
155
+ y1: current.y + (next.y - before.y) / 6 * (1 - tension),
156
+ x2: next.x - (after.x - current.x) / 6 * (1 - tension),
157
+ y2: next.y - (after.y - current.y) / 6 * (1 - tension),
150
158
  x: next.x,
151
159
  y: next.y
152
160
  });
@@ -265,7 +273,7 @@ function buildNaturalCommands(points) {
265
273
  return ownCommands(commands);
266
274
  }
267
275
 
268
- function buildValidatedCurvePathCommands(points, curve) {
276
+ function buildValidatedCurvePathCommands(points, curve, tension) {
269
277
  if (curve === "linear" ||
270
278
  (SMOOTH_CURVES.has(curve) &&
271
279
  curve !== "monotone" &&
@@ -277,7 +285,7 @@ function buildValidatedCurvePathCommands(points, curve) {
277
285
  }
278
286
  if (curve === "basis") return buildBasisCommands(points);
279
287
  if (curve === "cardinal") {
280
- return buildCardinalCommands(points);
288
+ return buildCardinalCommands(points, tension);
281
289
  }
282
290
  if (curve === "monotone") {
283
291
  return buildMonotoneCommands(points);
@@ -301,17 +309,19 @@ function curveCommandCount(pointCount, curve) {
301
309
  : pointCount;
302
310
  }
303
311
 
304
- export function buildCurvePathCommands(points, curve = "linear") {
312
+ export function buildCurvePathCommands(points, curve = "linear", tension) {
313
+ validateCardinalTension(tension, curve);
305
314
  const validatedPoints = validatePoints(points);
306
315
  const validatedCurve = validateCurveInterpolation(curve);
307
- validateGeneratedItemLimit(
316
+ validatePathCommandCount(
308
317
  curveCommandCount(validatedPoints.length, validatedCurve),
309
318
  "Curve path command count"
310
319
  );
311
320
  const direct = buildValidatedCurvePathCommands(
312
321
  validatedPoints,
313
- validatedCurve
322
+ validatedCurve, tension
314
323
  );
324
+ validatePathCommandCount(direct.length, "Curve path command count");
315
325
  if (finiteCommands(direct)) return direct;
316
326
 
317
327
  let xScale = 0;
@@ -327,7 +337,7 @@ export function buildCurvePathCommands(points, curve = "linear") {
327
337
  x: point.x / xScale,
328
338
  y: point.y / yScale
329
339
  })),
330
- validatedCurve
340
+ validatedCurve, tension
331
341
  );
332
342
  const restored = normalized.map(command => {
333
343
  const copy = {};
@@ -341,6 +351,7 @@ export function buildCurvePathCommands(points, curve = "linear") {
341
351
  if (!finiteCommands(restored)) {
342
352
  throw new RangeError("Curve interpolation exceeds the finite numeric range.");
343
353
  }
354
+ validatePathCommandCount(restored.length, "Curve path command count");
344
355
  return ownCommands(restored);
345
356
  }
346
357
 
@@ -403,7 +414,7 @@ export function buildAreaCurvePathCommands(
403
414
  const validatedCurve = validateCurveInterpolation(curve);
404
415
  const validatedLower = validatePoints(lowerPoints);
405
416
  const validatedUpper = validatePoints(upperPoints);
406
- validateGeneratedItemLimit(
417
+ validatePathCommandCount(
407
418
  curveCommandCount(validatedLower.length, validatedCurve) +
408
419
  curveCommandCount(validatedUpper.length, validatedCurve) + 1,
409
420
  "Area path command count"
@@ -416,10 +427,12 @@ export function buildAreaCurvePathCommands(
416
427
  const upper = reverseOpenPathCommands(
417
428
  buildOrientedCurvePathCommands(validatedUpper, validatedCurve, independentAxis)
418
429
  );
419
- return ownCommands([
430
+ const commands = [
420
431
  ...lower,
421
432
  { op: "L", x: upper[0].x, y: upper[0].y },
422
433
  ...upper.slice(1),
423
434
  { op: "Z" }
424
- ]);
435
+ ];
436
+ validatePathCommandCount(commands.length, "Area path command count");
437
+ return ownCommands(commands);
425
438
  }