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,10 +1,16 @@
1
1
  import { ChartProgram } from "./ChartProgram.js";
2
2
  import { BasicChartProgram } from "./BasicChartProgram.js";
3
- import { cloneAndFreeze } from "./core/immutable.js";
3
+ import { cloneAndFreeze, freezeOwned } from "./core/immutable.js";
4
4
  import { hasRegisteredExtension, isBuiltinProgramClass } from "./core/extensionRegistry.js";
5
5
  import { packageVersion } from "./version.js";
6
- import { decodeValue, encodeValue, invalidSnapshot } from "./persistence/codec.js";
6
+ import { decodeValue, encodeValue, encodeSharedValue, invalidSnapshot } from "./persistence/codec.js";
7
7
  import { exactKeys, requireObject, STATE_KEYS, validateGraphic, validateProgramState, validateTrace } from "./persistence/validation.js";
8
+ import {
9
+ deriveTransformSchema,
10
+ inferDatasetSchema,
11
+ validateDatasetSchema
12
+ } from "./grammar/datasetSchema.js";
13
+ import { restoreResolvedScaleBindings } from "./actions/scales/preview.js";
8
14
 
9
15
  function canonicalState(program, extensions, path = "payload") {
10
16
  if (!(program instanceof ChartProgram || program instanceof BasicChartProgram) || !isBuiltinProgramClass(program.constructor)) {
@@ -19,7 +25,7 @@ function canonicalState(program, extensions, path = "payload") {
19
25
  }
20
26
 
21
27
  function envelope(kind, payload, extensions = []) {
22
- return JSON.stringify({ schemaVersion: 1, kind, packageVersion, extensions, payload: encodeValue(payload) });
28
+ return JSON.stringify({ schemaVersion: kind === "editable" ? 3 : 1, kind, packageVersion, extensions, payload: kind === "editable" ? encodeSharedValue(payload) : encodeValue(payload) });
23
29
  }
24
30
 
25
31
  function readEnvelope(text, kind) {
@@ -27,24 +33,87 @@ function readEnvelope(text, kind) {
27
33
  let value;
28
34
  try { value = JSON.parse(text); } catch { throw invalidSnapshot("Snapshot must be valid JSON.", "snapshot"); }
29
35
  exactKeys(value, ["schemaVersion", "kind", "packageVersion", "extensions", "payload"], "snapshot");
30
- if (value.schemaVersion !== 1 || value.kind !== kind) throw invalidSnapshot(`Unsupported snapshot schema or kind; expected schema 1 ${kind}.`, "snapshot");
36
+ const versions = kind === "editable" ? [1, 2, 3] : [1];
37
+ if (!versions.includes(value.schemaVersion) || value.kind !== kind) {
38
+ throw invalidSnapshot(
39
+ `Unsupported snapshot schema or kind; expected schema ${versions.join(" or ")} ${kind}.`,
40
+ "snapshot"
41
+ );
42
+ }
31
43
  if (typeof value.packageVersion !== "string" || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(value.packageVersion)) throw invalidSnapshot("Invalid snapshot packageVersion.", "snapshot.packageVersion");
32
44
  if (!Array.isArray(value.extensions) || new Set(value.extensions).size !== value.extensions.length ||
33
45
  value.extensions.some(name => typeof name !== "string" || !hasRegisteredExtension(name)) || (kind === "graphic" && value.extensions.length)) {
34
46
  throw invalidSnapshot("Snapshot requires unknown, duplicate, or inapplicable extensions.", "snapshot.extensions");
35
47
  }
48
+ if (value.schemaVersion < 3 && value.payload?.[0] === "shared") {
49
+ throw invalidSnapshot("Shared array payloads require editable snapshot version 3.", "snapshot.payload");
50
+ }
36
51
  return { ...value, payload: decodeValue(value.payload) };
37
52
  }
38
53
 
39
- function restoreState(state, extensions, path = "payload") {
54
+ function restoreState(state, extensions, path = "payload", { requireSchemas = false } = {}) {
40
55
  exactKeys(state, STATE_KEYS, path);
41
56
  if (!Array.isArray(state.actionStack) || state.actionStack.length) throw invalidSnapshot("Snapshot actionStack must be empty.", `${path}.actionStack`);
42
57
  requireObject(state.children, `${path}.children`);
43
58
  validateTrace(state.trace, ChartProgram, extensions, `${path}.trace`);
44
- const children = Object.fromEntries(Object.entries(state.children).map(([id, child]) => [id, restoreState(child, extensions, `${path}.children.${id}`)]));
59
+ const children = Object.fromEntries(Object.entries(state.children).map(([id, child]) => [id, restoreState(
60
+ child,
61
+ extensions,
62
+ `${path}.children.${id}`,
63
+ { requireSchemas }
64
+ )]));
45
65
  const program = new ChartProgram({ ...state, children });
46
66
  validateProgramState(program, path);
47
- return program;
67
+ if (requireSchemas) {
68
+ for (const dataset of program.semanticSpec.datasets) {
69
+ if (dataset.schema === undefined) {
70
+ throw invalidSnapshot(`Dataset "${dataset.id}" requires schema in editable snapshot version 2 or later.`, `${path}.semanticSpec.datasets`);
71
+ }
72
+ validateDatasetSchema(dataset.schema);
73
+ }
74
+ }
75
+ return restoreResolvedScaleBindings(program);
76
+ }
77
+
78
+ function migrateStateSchemas(state) {
79
+ const datasets = state.semanticSpec.datasets;
80
+ const byId = new Map(datasets.map(dataset => [dataset.id, dataset]));
81
+ const schemas = new Map();
82
+ const active = new Set();
83
+ function schemaFor(id) {
84
+ if (schemas.has(id)) return schemas.get(id);
85
+ if (active.has(id)) throw invalidSnapshot(`Cyclic data source "${id}".`, "payload.semanticSpec.datasets");
86
+ active.add(id);
87
+ const dataset = byId.get(id);
88
+ let schema;
89
+ if (dataset.schema !== undefined) {
90
+ schema = validateDatasetSchema(dataset.schema);
91
+ } else if (dataset.source === undefined) {
92
+ schema = inferDatasetSchema(dataset.values ?? []);
93
+ } else {
94
+ const sourceSchema = schemaFor(dataset.source);
95
+ schema = deriveTransformSchema(
96
+ sourceSchema,
97
+ dataset.transform?.[0] ?? {},
98
+ dataset.values,
99
+ { sourceId: dataset.source, ownerId: dataset.id }
100
+ );
101
+ }
102
+ active.delete(id);
103
+ schemas.set(id, schema);
104
+ return schema;
105
+ }
106
+ const semanticSpec = {
107
+ ...state.semanticSpec,
108
+ datasets: datasets.map(dataset => ({ ...dataset, schema: schemaFor(dataset.id) }))
109
+ };
110
+ return {
111
+ ...state,
112
+ semanticSpec,
113
+ children: Object.fromEntries(Object.entries(state.children).map(
114
+ ([id, child]) => [id, migrateStateSchemas(child)]
115
+ ))
116
+ };
48
117
  }
49
118
 
50
119
  export function serializeProgram(program) {
@@ -52,10 +121,27 @@ export function serializeProgram(program) {
52
121
  return envelope("editable", canonicalState(program, extensions), [...extensions].sort());
53
122
  }
54
123
 
124
+ // Decoding creates fresh plain containers. Own them once across the whole graph
125
+ // so child constructors retain shared immutable data instead of copying each pool.
126
+ function ownDecodedState(value, seen = new WeakSet()) {
127
+ if (value === null || typeof value !== "object" || seen.has(value)) return;
128
+ seen.add(value);
129
+ for (const child of Object.values(value)) ownDecodedState(child, seen);
130
+ freezeOwned(value);
131
+ }
132
+
55
133
  export function deserializeProgram(text) {
56
134
  const stored = readEnvelope(text, "editable");
57
135
  const extensions = new Set();
58
- const program = restoreState(stored.payload, extensions);
136
+ let payload = stored.payload;
137
+ if (stored.schemaVersion === 1) {
138
+ // Validate the historical state before adding any inferred metadata. Migration
139
+ // must never repair an invalid reference graph or malformed payload.
140
+ restoreState(payload, new Set());
141
+ payload = migrateStateSchemas(payload);
142
+ }
143
+ ownDecodedState(payload);
144
+ const program = restoreState(payload, extensions, "payload", { requireSchemas: true });
59
145
  if (extensions.size !== stored.extensions.length || stored.extensions.some(name => !extensions.has(name))) {
60
146
  throw invalidSnapshot("Snapshot extensions must exactly match its action traces.", "snapshot.extensions");
61
147
  }
@@ -1,3 +1,4 @@
1
+ import { resolveCanvasSolidPaint } from "./fill.js";
1
2
  import { requireFiniteProperty } from "./validation.js";
2
3
  import { validateConcreteGraphicProperties } from
3
4
  "../../grammar/schemas/concreteGraphic.js";
@@ -25,7 +26,7 @@ function drawCircle(context, child, collectionId) {
25
26
  throw new Error(`Graphic "${graphicId}" requires opacity from 0 to 1.`);
26
27
  }
27
28
 
28
- context.fillStyle = properties.fill;
29
+ context.fillStyle = resolveCanvasSolidPaint(properties.fill);
29
30
  context.globalAlpha = opacity;
30
31
  context.beginPath();
31
32
  context.arc(x, y, radius, 0, Math.PI * 2);
@@ -44,8 +45,10 @@ function drawCircle(context, child, collectionId) {
44
45
  `Graphic "${graphicId}" requires a non-negative strokeWidth.`
45
46
  );
46
47
  }
47
- applyCanvasStroke(context, properties);
48
- context.stroke();
48
+ if (strokeWidth > 0) {
49
+ applyCanvasStroke(context, properties);
50
+ context.stroke();
51
+ }
49
52
  }
50
53
  }
51
54
 
@@ -4,10 +4,16 @@ import {
4
4
  } from "../../grammar/paint.js";
5
5
  import { resolveCanvasGradientCoordinates } from "./native.js";
6
6
 
7
+ // SVG's no-paint keyword is not a valid Canvas color assignment. Assign a real
8
+ // transparent color so the backend cannot retain a previous opaque paint.
9
+ export function resolveCanvasSolidPaint(paint) {
10
+ return paint === "none" ? "rgba(0,0,0,0)" : paint;
11
+ }
12
+
7
13
  export function applyCanvasFill(context, fill, bounds, graphicId) {
8
14
  validateFillPaint(fill, `${graphicId}.fill`);
9
15
  if (!isLinearGradientPaint(fill)) {
10
- context.fillStyle = fill;
16
+ context.fillStyle = resolveCanvasSolidPaint(fill);
11
17
  return fill;
12
18
  }
13
19
  if (typeof context.createLinearGradient !== "function") {
@@ -1,3 +1,4 @@
1
+ import { resolveCanvasSolidPaint } from "./fill.js";
1
2
  import { annotateError } from "../../core/diagnostics.js";
2
3
  import { drawCircleGraphic } from "./circle.js";
3
4
  import { drawLineGraphic } from "./line.js";
@@ -81,7 +82,7 @@ function fillCanvasBackground(context, id, canvas, width, height) {
81
82
  );
82
83
  }
83
84
  context.globalAlpha = 1;
84
- context.fillStyle = canvas.properties.background;
85
+ context.fillStyle = resolveCanvasSolidPaint(canvas.properties.background);
85
86
  context.fillRect(0, 0, width, height);
86
87
  }
87
88
 
@@ -33,6 +33,7 @@ function drawLine(context, child, collectionId) {
33
33
  throw new Error(`Graphic "${graphicId}" requires opacity from 0 to 1.`);
34
34
  }
35
35
 
36
+ if (strokeWidth === 0) return;
36
37
  context.globalAlpha = opacity;
37
38
  applyCanvasStroke(context, properties, properties.strokeDash ?? []);
38
39
  context.beginPath();
@@ -82,7 +82,7 @@ function drawPath(context, child, collectionId) {
82
82
  applyCanvasFill(context, properties.fill, bounds, graphicId);
83
83
  context.fill();
84
84
  }
85
- if (hasStroke) {
85
+ if (hasStroke && strokeWidth > 0) {
86
86
  applyCanvasStroke(context, properties, strokeDash);
87
87
  context.stroke();
88
88
  }
@@ -43,14 +43,17 @@ function drawRect(context, child, collectionId) {
43
43
  bottom: y + height
44
44
  }, graphicId);
45
45
  context.fillRect(x, y, width, height);
46
- applyCanvasStroke(context, properties);
47
- context.beginPath();
48
- context.moveTo(x, y);
49
- context.lineTo(x + width, y);
50
- context.lineTo(x + width, y + height);
51
- context.lineTo(x, y + height);
52
- context.lineTo(x, y);
53
- context.stroke();
46
+ // Canvas ignores lineWidth = 0 and would retain a previous nonzero width.
47
+ if (strokeWidth > 0) {
48
+ applyCanvasStroke(context, properties);
49
+ context.beginPath();
50
+ context.moveTo(x, y);
51
+ context.lineTo(x + width, y);
52
+ context.lineTo(x + width, y + height);
53
+ context.lineTo(x, y + height);
54
+ context.lineTo(x, y);
55
+ context.stroke();
56
+ }
54
57
  }
55
58
 
56
59
  export function drawRectGraphic(context, id, graphic) {
@@ -1,8 +1,9 @@
1
+ import { resolveCanvasSolidPaint } from "./fill.js";
1
2
  import { resolveStrokeDetails } from "../../grammar/strokeStyle.js";
2
3
 
3
4
  export function applyCanvasStroke(context, properties, strokeDash = []) {
4
5
  const details = resolveStrokeDetails(properties);
5
- context.strokeStyle = properties.stroke;
6
+ context.strokeStyle = resolveCanvasSolidPaint(properties.stroke);
6
7
  context.lineWidth = properties.strokeWidth;
7
8
  context.lineCap = details.lineCap;
8
9
  context.lineJoin = details.lineJoin;
@@ -1,3 +1,4 @@
1
+ import { resolveCanvasSolidPaint } from "./fill.js";
1
2
  import { requireFiniteProperty, requireStringProperty } from "./validation.js";
2
3
  import { validateConcreteGraphicProperties } from
3
4
  "../../grammar/schemas/concreteGraphic.js";
@@ -72,15 +73,16 @@ function drawText(context, child, collectionId) {
72
73
  context.save();
73
74
 
74
75
  try {
75
- context.fillStyle = fill;
76
+ context.fillStyle = resolveCanvasSolidPaint(fill);
76
77
  context.globalAlpha = opacity;
77
78
  const rendererFontWeight = normalizeRendererFontWeight(fontWeight);
78
- context.font = `${rendererFontWeight} ${fontSize}px ${fontFamily}`;
79
+ context.font = `${properties.fontStyle === "italic" ? "italic " : ""}${rendererFontWeight} ${fontSize}px ${fontFamily}`;
79
80
  context.textAlign = textAlign;
80
81
  context.textBaseline = textBaseline;
81
82
  context.translate(x, y);
82
83
  context.rotate(rotation);
83
- context.fillText(text, 0, 0);
84
+ if (properties.lines === undefined) context.fillText(text, 0, 0);
85
+ else for (const line of properties.lines) context.fillText(line.text, line.x, line.y);
84
86
  } finally {
85
87
  context.restore();
86
88
  }
@@ -419,13 +419,16 @@ function serializeText(state, graphicId, properties) {
419
419
  ["font-family", fontFamily],
420
420
  ["font-size", formatNumber(fontSize)],
421
421
  ["font-weight", normalizeRendererFontWeight(fontWeight)],
422
+ ["font-style", properties.fontStyle],
422
423
  ["text-anchor", TEXT_ANCHORS[textAlign]],
423
424
  ["dominant-baseline", DOMINANT_BASELINES[textBaseline]],
424
425
  ["opacity", formatNumber(opacity)],
425
426
  ["transform", rotation === 0
426
427
  ? undefined
427
428
  : `rotate(${formatNumber(rotationDegrees)} ${formatNumber(x)} ${formatNumber(y)})`]
428
- ], escapeText(text));
429
+ ], properties.lines === undefined ? escapeText(text) : properties.lines.map(line =>
430
+ element("tspan", [["x", formatNumber(x + line.x)], ["y", formatNumber(y + line.y)]], escapeText(line.text))
431
+ ).join(""));
429
432
  }
430
433
 
431
434
  function serializeConcreteGraphic(state, id, graphic) {
@@ -37,3 +37,22 @@ export function resolveEligibleLayer(program, {
37
37
  if (candidates.length === 0) throw annotateError(new Error(`${label} requires an eligible layer.`), { code: "missing-resource", optionPath: targetOption });
38
38
  throw annotateError(new Error(`${label} ${targetOption} is ambiguous; provide ${targetOption}.`), { code: "ambiguous-resource", optionPath: targetOption, candidates: candidates.map(layer => layer.id) });
39
39
  }
40
+
41
+ // Configured composite plots retain their established owner errors while sharing
42
+ // the explicit/current/unique resolution order.
43
+ export function resolveConfiguredOwner(program, requested, {
44
+ config, operation, kind, article = "a", materialized = false, missing, ambiguous
45
+ }, validateUserId) {
46
+ const eligible = program.semanticSpec.layers.filter(layer => {
47
+ const value = program.markConfigs[layer.id]?.[config];
48
+ return materialized ? value?.materialized === true : value !== undefined;
49
+ });
50
+ const id = requested === undefined ? program.context.currentMark
51
+ : validateUserId(requested, `${kind[0].toUpperCase() + kind.slice(1)}-plot owner id`);
52
+ const owner = eligible.find(layer => layer.id === id);
53
+ if (owner !== undefined) return owner;
54
+ if (requested !== undefined) throw new Error(`Unknown ${kind}-plot owner "${id}".`);
55
+ if (eligible.length === 1) return eligible[0];
56
+ if (eligible.length === 0) throw new Error(missing ?? `${operation} requires ${article} ${kind} plot.`);
57
+ throw new Error(ambiguous ?? `${operation} target is ambiguous; provide target.`);
58
+ }
@@ -2,6 +2,9 @@ import { DEFAULT_FONT_FAMILY } from "../core/font.js";
2
2
  export { DEFAULT_FONT_FAMILY };
3
3
 
4
4
  export const DEFAULT_POINT_RADIUS = 3;
5
+ export const AXIS_LABEL_FONT_SIZES = Object.freeze({ cartesian: 12, polar: 11, parallel: 11 });
6
+ export const AXIS_TITLE_FONT_SIZE = 13;
7
+ export const DEFAULT_VERTICAL_LEGEND_ITEM_GAP = 18;
5
8
 
6
9
  export const DEFAULT_COLORS = Object.freeze({
7
10
  mark: "#4c78a8",
@@ -42,7 +45,8 @@ export const THEME_TOKEN_KEYS = Object.freeze([
42
45
  "referenceBand",
43
46
  "gradientCenter",
44
47
  "highlight",
45
- "fontFamily"
48
+ "fontFamily", "axisLabel", "axisLabelFontFamily", "axisTitleFontFamily",
49
+ "axisLabelFontSize", "axisTitleFontSize"
46
50
  ]);
47
51
 
48
52
  const THEME_DEFINITION_KEYS = Object.freeze(["base", "tokens"]);
@@ -120,7 +124,9 @@ function normalizeThemeTokens(tokens) {
120
124
  validateKeys(tokens, THEME_TOKEN_KEYS, "applyTheme theme.tokens");
121
125
  const normalized = {};
122
126
  for (const [key, value] of Object.entries(tokens)) {
123
- validateNonEmptyString(value, `applyTheme theme.tokens.${key}`);
127
+ if (key.endsWith("FontSize")) {
128
+ if (!Number.isFinite(value) || value <= 0) throw new RangeError(`applyTheme theme.tokens.${key} must be positive and finite.`);
129
+ } else validateNonEmptyString(value, `applyTheme theme.tokens.${key}`);
124
130
  normalized[key] = value;
125
131
  }
126
132
  return own(normalized);
package/src/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Must match package.json; enforced by the snapshot producer-version test.
2
- export const packageVersion = "0.0.16";
2
+ export const packageVersion = "0.0.18";
package/types/basic.d.ts CHANGED
@@ -17,6 +17,7 @@ export type {
17
17
  CompleteAxisOptions,
18
18
  CreateAxesOptions,
19
19
  CreateBarPlotOptions,
20
+ BarRangePositionChannel,
20
21
  CreateDataOptions,
21
22
  CreateGridOptions,
22
23
  CreateGuidesOptions,
package/types/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import type { ChartProgram, CompositionOptions } from "./program.js";
2
2
 
3
3
  export type {
4
+ ArcInnerRadius,
5
+ PieRadiusChannel,
4
6
  TextMetricFontWeight,
5
7
  TextMeasurement,
6
8
  TextMetricsProfile,
@@ -27,6 +29,7 @@ export type {
27
29
  DisplayLabelOptions,
28
30
  BarWidthOptions,
29
31
  BoxPlotOptions,
32
+ BoxPlotSummaryFields,
30
33
  BinnedHeatmapColorOptions,
31
34
  BinnedHeatmapOptions,
32
35
  BinnedHeatmapPositionChannel,
@@ -117,6 +120,7 @@ export type {
117
120
  CreateGuidesOptions,
118
121
  CreateGridOptions,
119
122
  CreateBarPlotOptions,
123
+ BarRangePositionChannel,
120
124
  CreateHeatmapOptions,
121
125
  CreateHistogramOptions,
122
126
  CreatePiePlotOptions,
@@ -166,6 +170,7 @@ export type {
166
170
  StripPixelJitterOptions,
167
171
  CreateParallelCoordinatesOptions,
168
172
  CreateScatterPlotOptions,
173
+ CreateTextPlotOptions,
169
174
  CreateIntervalPlotOptions,
170
175
  CreateRegressionPlotOptions,
171
176
  CreateDotPlotOptions,
@@ -241,6 +246,7 @@ export type {
241
246
  EditHorizonOptions,
242
247
  EditCompositionLayoutOptions,
243
248
  EditFacetHeadersOptions,
249
+ FacetHeadersOptions,
244
250
  EditAxisOptions,
245
251
  EditGradientPlotOptions,
246
252
  EditViolinPlotOptions,
@@ -375,6 +381,7 @@ export type {
375
381
  ScaleType,
376
382
  ShapeScaleOptions,
377
383
  SizeScaleOptions,
384
+ CategoricalSizeScaleOptions,
378
385
  StackMode,
379
386
  StackDataMode,
380
387
  StackDataOptions,
@@ -0,0 +1,17 @@
1
+ import type { BasicChartProgram } from "./basic.js";
2
+ import type { ChartProgram, DatasetSchema } from "./program.js";
3
+ export type ProgramInput = ChartProgram | BasicChartProgram;
4
+ export interface ResourceRef { readonly kind: "data" | "mark" | "scale" | "coordinate" | "guide" | "canvas" | "composition"; readonly id: string; readonly childPath?: readonly string[]; }
5
+ export interface InspectionTarget { readonly kind: "data" | "mark" | "scale" | "coordinate" | "guide"; readonly id: string; readonly childPath?: readonly string[]; }
6
+ export interface Finding { readonly severity: "error" | "warning" | "info"; readonly code: string; readonly reason: string; readonly message: string; readonly resource?: ResourceRef; readonly optionPath?: string; }
7
+ export interface CheckResult { readonly name: string; readonly status: "not_run" | "passed" | "failed" | "not_applicable"; readonly evidencePaths: readonly string[]; readonly findings: readonly Finding[]; }
8
+ export interface ParameterDefinition { readonly path: string; readonly kind: "field" | "enum" | "number" | "string" | "boolean" | "range" | "array" | "object" | "resource"; readonly required: boolean; readonly active: boolean; readonly choices?: readonly (string | number | boolean | null)[]; readonly minimum?: number; readonly maximum?: number; readonly unit?: string; readonly fieldRole?: string; }
9
+ export interface ActionDescription { readonly version: 1; readonly action: string; readonly applicability: "supported" | "needs-input" | "incompatible" | "unsupported" | "unverified"; readonly parameterDefinitions: readonly ParameterDefinition[]; readonly requirements: readonly { readonly reason: string; readonly optionPath?: string }[]; readonly checks: readonly CheckResult[]; readonly findings: readonly Finding[]; }
10
+ export interface Change { readonly resource: ResourceRef; readonly operation: "add" | "remove" | "modify"; readonly effect: "data" | "binding" | "structure" | "scale" | "guide" | "style"; readonly roles: readonly string[]; }
11
+ export interface ProgramComparison { readonly version: 1; readonly equivalence: "equal" | "different" | "unknown"; readonly completeProgramComparison: boolean; readonly changes: readonly Change[]; readonly affected: readonly ResourceRef[]; readonly findings: readonly Finding[]; }
12
+ export interface OwnerInspection { readonly owner: ResourceRef; readonly grain: "item" | "series" | "composite" | "unknown"; readonly rawRows: number | null; readonly logicalDataItems: number | null; readonly drawablePrimitives: number | null; readonly visibleCandidates: number | null; readonly flags: readonly { readonly reason: "opacity-zero" | "zero-size" | "outside-clip" | "missing-skipped"; readonly count: number | null }[]; readonly bounds: Readonly<{x:number;y:number;width:number;height:number}> | null; readonly sourceRefs: readonly ResourceRef[]; readonly checks: readonly CheckResult[]; }
13
+ export interface ProgramInspection { readonly version: 1; readonly views: readonly OwnerInspection[]; readonly calculations: readonly unknown[]; readonly checks: readonly CheckResult[]; readonly findings: readonly Finding[]; readonly coverage: Readonly<{partial:boolean;unsupported:readonly ResourceRef[]}>; }
14
+ export function getDatasetSchema(program: ProgramInput, options: {readonly data:string;readonly childPath?:readonly string[]}): Readonly<{data:string;schema:DatasetSchema}>;
15
+ export function describeAction(program: ProgramInput, options: {readonly action:string;readonly target?:InspectionTarget;readonly options?:Readonly<Record<string,unknown>>}): ActionDescription;
16
+ export function comparePrograms(before: ProgramInput, after: ProgramInput, options?: {readonly target?:InspectionTarget}): ProgramComparison;
17
+ export function inspectProgram(program: ProgramInput, options?: {readonly target?:InspectionTarget}): ProgramInspection;