ggaction 0.0.1

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 (222) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/LICENSE +21 -0
  3. package/README.md +108 -0
  4. package/package.json +84 -0
  5. package/src/AGENTS.md +119 -0
  6. package/src/ChartProgram.js +10 -0
  7. package/src/actions/boxPlots/components.js +88 -0
  8. package/src/actions/boxPlots/create.js +84 -0
  9. package/src/actions/boxPlots/index.js +10 -0
  10. package/src/actions/boxPlots/materialize.js +183 -0
  11. package/src/actions/boxPlots/options.js +147 -0
  12. package/src/actions/boxPlots/resolve.js +38 -0
  13. package/src/actions/canvas/actions.js +144 -0
  14. package/src/actions/canvas/index.js +6 -0
  15. package/src/actions/coordinates/actions.js +85 -0
  16. package/src/actions/coordinates/index.js +5 -0
  17. package/src/actions/data/box.js +40 -0
  18. package/src/actions/data/create.js +33 -0
  19. package/src/actions/data/density.js +62 -0
  20. package/src/actions/data/derived.js +52 -0
  21. package/src/actions/data/filter.js +168 -0
  22. package/src/actions/data/index.js +32 -0
  23. package/src/actions/data/interval.js +69 -0
  24. package/src/actions/data/regression.js +71 -0
  25. package/src/actions/data/shared.js +25 -0
  26. package/src/actions/encodings/appearance.js +243 -0
  27. package/src/actions/encodings/barWidth.js +60 -0
  28. package/src/actions/encodings/color.js +329 -0
  29. package/src/actions/encodings/density.js +225 -0
  30. package/src/actions/encodings/histogram.js +50 -0
  31. package/src/actions/encodings/index.js +23 -0
  32. package/src/actions/encodings/offset.js +104 -0
  33. package/src/actions/encodings/position/policies/area.js +24 -0
  34. package/src/actions/encodings/position/policies/bar.js +110 -0
  35. package/src/actions/encodings/position/policies/common.js +17 -0
  36. package/src/actions/encodings/position/policies/index.js +25 -0
  37. package/src/actions/encodings/position/policies/line.js +74 -0
  38. package/src/actions/encodings/position/policies/point.js +19 -0
  39. package/src/actions/encodings/position/policies/rule.js +17 -0
  40. package/src/actions/encodings/position/resolve.js +170 -0
  41. package/src/actions/encodings/position.js +176 -0
  42. package/src/actions/encodings/ranged.js +276 -0
  43. package/src/actions/encodings/ruleAppearance.js +47 -0
  44. package/src/actions/encodings/shared.js +164 -0
  45. package/src/actions/encodings/strokeDash.js +145 -0
  46. package/src/actions/errorBands/components.js +81 -0
  47. package/src/actions/errorBands/create.js +159 -0
  48. package/src/actions/errorBands/index.js +7 -0
  49. package/src/actions/errorBands/resolve.js +28 -0
  50. package/src/actions/errorBars/components.js +62 -0
  51. package/src/actions/errorBars/create.js +144 -0
  52. package/src/actions/errorBars/index.js +7 -0
  53. package/src/actions/errorBars/resolve.js +25 -0
  54. package/src/actions/guides/axes/axes.js +204 -0
  55. package/src/actions/guides/axes/axis.js +131 -0
  56. package/src/actions/guides/axes/index.js +17 -0
  57. package/src/actions/guides/axes/labels.js +231 -0
  58. package/src/actions/guides/axes/lines.js +149 -0
  59. package/src/actions/guides/axes/policy.js +155 -0
  60. package/src/actions/guides/axes/tickGroups.js +149 -0
  61. package/src/actions/guides/axes/ticks.js +179 -0
  62. package/src/actions/guides/axes/titles.js +264 -0
  63. package/src/actions/guides/grids/grid.js +228 -0
  64. package/src/actions/guides/grids/index.js +1 -0
  65. package/src/actions/guides/grids/resolve.js +258 -0
  66. package/src/actions/guides/guides.js +138 -0
  67. package/src/actions/guides/index.js +11 -0
  68. package/src/actions/guides/legends/categorical/actions.js +329 -0
  69. package/src/actions/guides/legends/categorical/components.js +140 -0
  70. package/src/actions/guides/legends/categorical/index.js +49 -0
  71. package/src/actions/guides/legends/categorical/layout.js +445 -0
  72. package/src/actions/guides/legends/categorical/options.js +188 -0
  73. package/src/actions/guides/legends/categorical/recipes.js +120 -0
  74. package/src/actions/guides/legends/categorical/resolve.js +131 -0
  75. package/src/actions/guides/legends/categorical/symbols.js +280 -0
  76. package/src/actions/guides/legends/categorical/validation.js +27 -0
  77. package/src/actions/guides/legends/continuous/common.js +273 -0
  78. package/src/actions/guides/legends/continuous/gradient.js +363 -0
  79. package/src/actions/guides/legends/continuous/index.js +25 -0
  80. package/src/actions/guides/legends/continuous/interval.js +234 -0
  81. package/src/actions/guides/legends/continuous/opacity.js +382 -0
  82. package/src/actions/guides/legends/edit.js +314 -0
  83. package/src/actions/guides/legends/index.js +9 -0
  84. package/src/actions/guides/legends/size.js +220 -0
  85. package/src/actions/guides/tickValues.js +84 -0
  86. package/src/actions/index.js +31 -0
  87. package/src/actions/intervals/resolve.js +352 -0
  88. package/src/actions/marks/area.js +369 -0
  89. package/src/actions/marks/bar/create.js +46 -0
  90. package/src/actions/marks/bar/edit.js +90 -0
  91. package/src/actions/marks/bar/index.js +9 -0
  92. package/src/actions/marks/bar/materialize.js +130 -0
  93. package/src/actions/marks/index.js +13 -0
  94. package/src/actions/marks/line.js +248 -0
  95. package/src/actions/marks/point.js +420 -0
  96. package/src/actions/marks/rule.js +287 -0
  97. package/src/actions/marks/shared.js +112 -0
  98. package/src/actions/primitives/createGraphics.js +163 -0
  99. package/src/actions/primitives/editGraphics.js +208 -0
  100. package/src/actions/primitives/index.js +9 -0
  101. package/src/actions/primitives/semantic.js +168 -0
  102. package/src/actions/primitives/semanticValue.js +182 -0
  103. package/src/actions/regression/components.js +216 -0
  104. package/src/actions/regression/create.js +102 -0
  105. package/src/actions/regression/index.js +23 -0
  106. package/src/actions/regression/resolve.js +57 -0
  107. package/src/actions/scales/consumers.js +283 -0
  108. package/src/actions/scales/create.js +148 -0
  109. package/src/actions/scales/definitions.js +319 -0
  110. package/src/actions/scales/edit.js +290 -0
  111. package/src/actions/scales/index.js +11 -0
  112. package/src/actions/scales/materialize.js +466 -0
  113. package/src/actions/scales/quantitativeColor.js +45 -0
  114. package/src/actions/selection/actions.js +384 -0
  115. package/src/actions/selection/index.js +23 -0
  116. package/src/actions/titles/actions.js +235 -0
  117. package/src/actions/titles/index.js +19 -0
  118. package/src/actions/titles/resolve.js +321 -0
  119. package/src/core/ChartProgram.js +316 -0
  120. package/src/core/action.js +120 -0
  121. package/src/core/identifiers.js +28 -0
  122. package/src/core/immutable.js +94 -0
  123. package/src/core/specs.js +29 -0
  124. package/src/core/textMetrics.js +23 -0
  125. package/src/core/validation.js +54 -0
  126. package/src/core/vocabulary.js +58 -0
  127. package/src/extension.js +2 -0
  128. package/src/grammar/aggregate.js +259 -0
  129. package/src/grammar/areaSeries.js +217 -0
  130. package/src/grammar/bars/aggregate.js +98 -0
  131. package/src/grammar/bars/geometry.js +54 -0
  132. package/src/grammar/bars/policy.js +90 -0
  133. package/src/grammar/boxPlot.js +140 -0
  134. package/src/grammar/coordinates.js +25 -0
  135. package/src/grammar/curveCommands.js +356 -0
  136. package/src/grammar/density.js +296 -0
  137. package/src/grammar/filter.js +169 -0
  138. package/src/grammar/histogram.js +332 -0
  139. package/src/grammar/interval.js +343 -0
  140. package/src/grammar/lineSeries.js +197 -0
  141. package/src/grammar/markFilter.js +46 -0
  142. package/src/grammar/markSelection.js +326 -0
  143. package/src/grammar/palettes.js +208 -0
  144. package/src/grammar/pathCommands.js +86 -0
  145. package/src/grammar/pointShapes.js +164 -0
  146. package/src/grammar/positionCompatibility.js +32 -0
  147. package/src/grammar/regression.js +578 -0
  148. package/src/grammar/ruleAppearance.js +12 -0
  149. package/src/grammar/rules.js +90 -0
  150. package/src/grammar/scales/appearance.js +185 -0
  151. package/src/grammar/scales/color.js +349 -0
  152. package/src/grammar/scales/continuous.js +111 -0
  153. package/src/grammar/scales/definition.js +153 -0
  154. package/src/grammar/scales/discretized.js +159 -0
  155. package/src/grammar/scales/fields.js +133 -0
  156. package/src/grammar/scales/index.js +12 -0
  157. package/src/grammar/scales/mapping.js +27 -0
  158. package/src/grammar/scales/ordinal.js +221 -0
  159. package/src/grammar/scales/policies.js +50 -0
  160. package/src/grammar/scales/temporal.js +53 -0
  161. package/src/grammar/scales/transformed.js +236 -0
  162. package/src/grammar/scales/types.js +102 -0
  163. package/src/grammar/scales/validation.js +66 -0
  164. package/src/grammar/scales.js +1 -0
  165. package/src/grammar/schemas/concreteGraphic.js +73 -0
  166. package/src/grammar/schemas/graphic.js +131 -0
  167. package/src/grammar/schemas/graphicBounds.js +146 -0
  168. package/src/grammar/schemas/graphicTree.js +133 -0
  169. package/src/grammar/schemas/mark.js +4 -0
  170. package/src/grammar/schemas/semanticPath.js +174 -0
  171. package/src/grammar/seriesLayout.js +76 -0
  172. package/src/grammar/ticks.js +149 -0
  173. package/src/grammar/transforms.js +37 -0
  174. package/src/index.js +2 -0
  175. package/src/layout/canvas.js +114 -0
  176. package/src/layout/grid.js +17 -0
  177. package/src/layout/legend.js +51 -0
  178. package/src/layout/text.js +62 -0
  179. package/src/layout/title.js +107 -0
  180. package/src/materialization/bars/aggregate.js +184 -0
  181. package/src/materialization/bars/grouped.js +117 -0
  182. package/src/materialization/bars/histogram.js +163 -0
  183. package/src/materialization/bars/ranged.js +53 -0
  184. package/src/materialization/bars/resolve.js +56 -0
  185. package/src/materialization/dataProvenance.js +15 -0
  186. package/src/materialization/density.js +29 -0
  187. package/src/materialization/dependencies.js +134 -0
  188. package/src/materialization/encodings.js +50 -0
  189. package/src/materialization/graphicHierarchy.js +104 -0
  190. package/src/materialization/legends.js +20 -0
  191. package/src/materialization/marks.js +110 -0
  192. package/src/materialization/planner.js +38 -0
  193. package/src/materialization/selection/filter.js +40 -0
  194. package/src/materialization/selection/items.js +503 -0
  195. package/src/materialization/selection/path.js +37 -0
  196. package/src/materialization/selection/point.js +118 -0
  197. package/src/materialization/selection/policies/area.js +10 -0
  198. package/src/materialization/selection/policies/bar.js +10 -0
  199. package/src/materialization/selection/policies/index.js +40 -0
  200. package/src/materialization/selection/policies/line.js +10 -0
  201. package/src/materialization/selection/policies/point.js +10 -0
  202. package/src/materialization/selection/policies/rule.js +10 -0
  203. package/src/materialization/selection/state.js +67 -0
  204. package/src/materialization/selection/styles.js +183 -0
  205. package/src/renderers/canvas/circle.js +62 -0
  206. package/src/renderers/canvas/index.js +132 -0
  207. package/src/renderers/canvas/line.js +54 -0
  208. package/src/renderers/canvas/path.js +98 -0
  209. package/src/renderers/canvas/rect.js +57 -0
  210. package/src/renderers/canvas/text.js +96 -0
  211. package/src/renderers/canvas/validation.js +23 -0
  212. package/src/renderers/png.js +29 -0
  213. package/src/selectors/coordinates.js +13 -0
  214. package/src/selectors/datasets.js +13 -0
  215. package/src/selectors/index.js +4 -0
  216. package/src/selectors/layers.js +32 -0
  217. package/src/selectors/scales.js +21 -0
  218. package/src/theme/defaults.js +13 -0
  219. package/types/extension.d.ts +14 -0
  220. package/types/index.d.ts +81 -0
  221. package/types/png.d.ts +14 -0
  222. package/types/program.d.ts +1096 -0
@@ -0,0 +1,29 @@
1
+ import { getMarkMaterializationStep } from "./marks.js";
2
+ import { requireLayer } from "../selectors/layers.js";
3
+ import { buildMaterializationPlan } from "./planner.js";
4
+
5
+ export function planDensityRematerialization(program, target) {
6
+ const layer = requireLayer(
7
+ program,
8
+ target,
9
+ `Unknown density materialization target "${target}"`
10
+ );
11
+ const scaleIds = new Set([
12
+ layer.encoding?.x?.scale,
13
+ layer.encoding?.y?.scale
14
+ ].filter(Boolean));
15
+ const orderedLayers = [
16
+ layer,
17
+ ...program.semanticSpec.layers.filter(candidate => candidate.id !== target)
18
+ ];
19
+ const plan = [];
20
+ for (const candidate of orderedLayers) {
21
+ const usesAffectedScale = Object.values(candidate.encoding ?? {}).some(
22
+ encoding => scaleIds.has(encoding?.scale)
23
+ );
24
+ if (!usesAffectedScale) continue;
25
+ const step = getMarkMaterializationStep(program, candidate);
26
+ if (step !== undefined) plan.push(step);
27
+ }
28
+ return buildMaterializationPlan({ marks: plan });
29
+ }
@@ -0,0 +1,134 @@
1
+ import { getMarkMaterializationStep } from "./marks.js";
2
+ import { requireLayer } from "../selectors/layers.js";
3
+ import {
4
+ applyMaterializationPlan,
5
+ buildMaterializationPlan
6
+ } from "./planner.js";
7
+ import {
8
+ hasMaterializedLegend,
9
+ materializedLegendUsesScale
10
+ } from "./legends.js";
11
+
12
+ function usesPositionalScale(program, id) {
13
+ return program.semanticSpec.layers.some(layer =>
14
+ ["x", "y", "x2", "y2", "xOffset"].some(
15
+ channel => layer.encoding?.[channel]?.scale === id
16
+ )
17
+ );
18
+ }
19
+
20
+ function needsCanvasScaleRematerialization(program, scale) {
21
+ return (
22
+ (scale.range === "auto" ||
23
+ program.semanticSpec.guides.axis?.x?.scale === scale.id ||
24
+ program.semanticSpec.guides.axis?.y?.scale === scale.id ||
25
+ program.semanticSpec.guides.grid?.horizontal?.scale === scale.id ||
26
+ program.semanticSpec.guides.grid?.vertical?.scale === scale.id) &&
27
+ program.resolvedScales[scale.id] !== undefined &&
28
+ usesPositionalScale(program, scale.id)
29
+ );
30
+ }
31
+
32
+ function hasTitle(program) {
33
+ return (
34
+ program.semanticSpec.title.text !== undefined &&
35
+ program.titleConfig !== undefined
36
+ );
37
+ }
38
+
39
+ export function planCanvasRematerialization(program) {
40
+ const scales = [];
41
+ for (const scale of program.semanticSpec.scales) {
42
+ if (needsCanvasScaleRematerialization(program, scale)) {
43
+ scales.push({
44
+ op: "rematerializeScale",
45
+ args: { id: scale.id, guides: false }
46
+ });
47
+ }
48
+ }
49
+ const marks = [];
50
+ for (const layer of program.semanticSpec.layers) {
51
+ const step = getMarkMaterializationStep(program, layer);
52
+ if (step !== undefined) marks.push(step);
53
+ }
54
+ const guides = program.semanticSpec.scales.flatMap(scale =>
55
+ needsCanvasScaleRematerialization(program, scale)
56
+ ? planScaleGuideRematerialization(program, scale.id)
57
+ : []
58
+ );
59
+ if (hasMaterializedLegend(program)) {
60
+ guides.push({ op: "rematerializeLegend" });
61
+ }
62
+ const layout = hasTitle(program) ? [{ op: "rematerializeTitle" }] : [];
63
+ return buildMaterializationPlan({ scales, marks, guides, layout });
64
+ }
65
+
66
+ export function planScaleGuideRematerialization(program, id) {
67
+ const guides = [];
68
+ const objects = program.graphicSpec?.objects ?? {};
69
+ const guideConfigs = program.guideConfigs ?? {};
70
+ if (
71
+ objects.xAxisLine &&
72
+ program.semanticSpec.guides.axis?.x?.scale === id
73
+ ) {
74
+ guides.push({ op: "editXAxisLine" });
75
+ }
76
+ if (
77
+ objects.yAxisLine &&
78
+ program.semanticSpec.guides.axis?.y?.scale === id
79
+ ) {
80
+ guides.push({ op: "editYAxisLine" });
81
+ }
82
+ for (const channel of ["x", "y"]) {
83
+ for (const component of ["ticks", "labels", "title"]) {
84
+ if (guideConfigs.axis?.[channel]?.[component]?.scale === id) {
85
+ const suffix = component[0].toUpperCase() + component.slice(1);
86
+ guides.push({ op: `edit${channel.toUpperCase()}Axis${suffix}` });
87
+ }
88
+ }
89
+ }
90
+ if (guideConfigs.grid?.horizontal?.scale === id) {
91
+ guides.push({ op: "rematerializeHorizontalGrid" });
92
+ }
93
+ if (guideConfigs.grid?.vertical?.scale === id) {
94
+ guides.push({ op: "rematerializeVerticalGrid" });
95
+ }
96
+ if (materializedLegendUsesScale(program, id)) {
97
+ guides.push({ op: "rematerializeLegend" });
98
+ }
99
+ return buildMaterializationPlan({ guides });
100
+ }
101
+
102
+ export function planLayerDataRematerialization(program, id) {
103
+ const layer = requireLayer(program, id);
104
+ const scaleIds = [...new Set(
105
+ Object.values(layer.encoding ?? {})
106
+ .map(encoding => encoding?.scale)
107
+ .filter(scale => scale !== undefined)
108
+ )];
109
+ const scales = scaleIds.map(scale => ({
110
+ op: "rematerializeScale",
111
+ args: { id: scale, guides: false }
112
+ }));
113
+ const markStep = getMarkMaterializationStep(program, layer);
114
+ const marks = markStep === undefined ? [] : [markStep];
115
+ if (scales.length > 0 || marks.length > 0) {
116
+ const guides = scaleIds.flatMap(scale =>
117
+ planScaleGuideRematerialization(program, scale)
118
+ );
119
+ return buildMaterializationPlan({ scales, marks, guides });
120
+ }
121
+ return buildMaterializationPlan({ marks: layer.mark?.type === "point" &&
122
+ program.graphicSpec.objects[layer.id] !== undefined
123
+ ? [{ op: "rematerializePointMark", args: { id: layer.id } }]
124
+ : [] });
125
+ }
126
+
127
+ export function applyLayerDataRematerialization(program, id) {
128
+ return applyMaterializationPlan(
129
+ program,
130
+ planLayerDataRematerialization(program, id)
131
+ );
132
+ }
133
+
134
+ export { applyMaterializationPlan } from "./planner.js";
@@ -0,0 +1,50 @@
1
+ import { hasMaterializedLegend } from "./legends.js";
2
+ import { requireLayer } from "../selectors/layers.js";
3
+ import { getMarkRematerializationStep } from "./marks.js";
4
+ import { buildMaterializationPlan } from "./planner.js";
5
+
6
+ export function planEncodingRematerialization(program, {
7
+ target,
8
+ channel,
9
+ scale
10
+ }) {
11
+ const layer = requireLayer(
12
+ program,
13
+ target,
14
+ `Unknown encoding materialization target "${target}"`
15
+ );
16
+
17
+ const scales = [];
18
+ if (layer.mark?.type === "point" && scale !== undefined) {
19
+ scales.push({
20
+ op: "rematerializeScale",
21
+ args: { id: scale, guides: false }
22
+ });
23
+ }
24
+
25
+ const marks = [];
26
+ if (
27
+ layer.mark?.type === "area" &&
28
+ channel === "color" &&
29
+ scale !== undefined
30
+ ) {
31
+ for (const candidate of program.semanticSpec.layers) {
32
+ if (
33
+ candidate.mark?.type === "area" &&
34
+ candidate.encoding?.color?.scale === scale
35
+ ) {
36
+ const step = getMarkRematerializationStep(candidate);
37
+ if (step !== undefined) marks.push(step);
38
+ }
39
+ }
40
+ } else {
41
+ const step = getMarkRematerializationStep(layer);
42
+ if (step !== undefined) marks.push(step);
43
+ }
44
+
45
+ const guides = [];
46
+ if (hasMaterializedLegend(program)) {
47
+ guides.push({ op: "rematerializeLegend" });
48
+ }
49
+ return buildMaterializationPlan({ scales, marks, guides });
50
+ }
@@ -0,0 +1,104 @@
1
+ import {
2
+ findGraphicParent,
3
+ graphicSiblings
4
+ } from "../grammar/schemas/graphicTree.js";
5
+ import { findDataset } from "../selectors/datasets.js";
6
+
7
+ export const CANVAS_GRAPHIC_ID = "canvas";
8
+ export const PLOT_GRAPHIC_ID = "plot-main";
9
+
10
+ export function assertCanvasHierarchyAvailable(program) {
11
+ const canvasByType = Object.entries(program.graphicSpec.objects).find(
12
+ ([, graphic]) => graphic.type === "canvas"
13
+ );
14
+ if (canvasByType !== undefined) {
15
+ throw new Error("createCanvas requires a program without a canvas.");
16
+ }
17
+ for (const id of [CANVAS_GRAPHIC_ID, PLOT_GRAPHIC_ID]) {
18
+ if (program.graphicSpec.objects[id] !== undefined) {
19
+ throw new Error(`createCanvas requires the reserved graphic id "${id}".`);
20
+ }
21
+ }
22
+ }
23
+
24
+ export function resolvePlotGraphicPlacement(program, relative = {}) {
25
+ const canvas = program.graphicSpec.objects[CANVAS_GRAPHIC_ID];
26
+ const plot = program.graphicSpec.objects[PLOT_GRAPHIC_ID];
27
+
28
+ if (canvas === undefined && plot === undefined) return relative;
29
+ if (canvas?.type !== "canvas") {
30
+ throw new Error("Ordinary mark attachment requires the canonical canvas.");
31
+ }
32
+ if (plot?.type !== "collection") {
33
+ throw new Error("Ordinary mark attachment requires the canonical plot container.");
34
+ }
35
+ const parent = findGraphicParent(program.graphicSpec, PLOT_GRAPHIC_ID);
36
+ if (parent?.kind !== "parent" || parent.id !== CANVAS_GRAPHIC_ID) {
37
+ throw new Error("The canonical plot container must be attached to the canvas.");
38
+ }
39
+ return { parent: PLOT_GRAPHIC_ID, ...relative };
40
+ }
41
+
42
+ export function resolveMarkGraphicPlacement(program, { data, markType }) {
43
+ const dataset = findDataset(program, data);
44
+ const transform = dataset?.transform?.length === 1
45
+ ? dataset.transform[0]
46
+ : undefined;
47
+ if (transform?.type !== "regression") {
48
+ const axis = program.graphicSpec.objects[PLOT_GRAPHIC_ID]?.children?.find(
49
+ id => /^(x|y)Axis/.test(id)
50
+ );
51
+ return resolvePlotGraphicPlacement(
52
+ program,
53
+ axis === undefined ? {} : { before: axis }
54
+ );
55
+ }
56
+ const eligible = program.semanticSpec.layers.filter(layer =>
57
+ layer.data === dataset.source &&
58
+ program.graphicSpec.objects[layer.id] !== undefined
59
+ );
60
+ const namespaced = eligible.filter(layer =>
61
+ `${layer.id}RegressionData` === dataset.id
62
+ );
63
+ const sources = namespaced.length === 1 ? namespaced : eligible;
64
+ if (sources.length !== 1) {
65
+ throw new Error(
66
+ "Regression graphic placement requires one source mark layer."
67
+ );
68
+ }
69
+ const anchor = sources[0].id;
70
+ return resolvePlotGraphicPlacement(
71
+ program,
72
+ markType === "area" ? { before: anchor } : { after: anchor }
73
+ );
74
+ }
75
+
76
+ export function resolveCanvasGraphicPlacement(program, relative = {}) {
77
+ const canvas = program.graphicSpec.objects[CANVAS_GRAPHIC_ID];
78
+ if (canvas?.type !== "canvas") {
79
+ throw new Error("Canvas-owned graphics require the canonical canvas.");
80
+ }
81
+ return { parent: CANVAS_GRAPHIC_ID, ...relative };
82
+ }
83
+
84
+ export function resolveLegendGraphicPlacement(program, relative = {}) {
85
+ const title = program.graphicSpec.objects[CANVAS_GRAPHIC_ID]?.children?.find(
86
+ id => id === "chartTitle" || id === "chartSubtitle"
87
+ );
88
+ const hasRelativeAnchor = relative.before !== undefined ||
89
+ relative.after !== undefined;
90
+ return resolveCanvasGraphicPlacement(program, {
91
+ ...(title === undefined || hasRelativeAnchor ? {} : { before: title }),
92
+ ...relative
93
+ });
94
+ }
95
+
96
+ export function preserveGraphicPlacement(program, id) {
97
+ const parent = findGraphicParent(program.graphicSpec, id);
98
+ const siblings = graphicSiblings(program.graphicSpec, id);
99
+ const next = siblings[siblings.indexOf(id) + 1];
100
+ return {
101
+ ...(parent?.kind === "parent" ? { parent: parent.id } : {}),
102
+ ...(next === undefined ? {} : { before: next })
103
+ };
104
+ }
@@ -0,0 +1,20 @@
1
+ import { LEGEND_CONFIG_KINDS } from "../core/vocabulary.js";
2
+
3
+ export function hasMaterializedLegend(program) {
4
+ return LEGEND_CONFIG_KINDS.some(kind => {
5
+ const config = program.guideConfigs?.legend?.[kind];
6
+ if (config === undefined) return false;
7
+ if (kind === "series" || kind === "color") {
8
+ return program.semanticSpec.guides.legend?.[kind] !== undefined;
9
+ }
10
+ return true;
11
+ });
12
+ }
13
+
14
+ export function materializedLegendUsesScale(program, id) {
15
+ return LEGEND_CONFIG_KINDS.some(kind => {
16
+ const config = program.guideConfigs?.legend?.[kind];
17
+ if (config === undefined) return false;
18
+ return config.scale === id || config.scales?.includes(id);
19
+ });
20
+ }
@@ -0,0 +1,110 @@
1
+ import { findDataset } from "../selectors/datasets.js";
2
+ import { isAggregate } from "../grammar/aggregate.js";
3
+ import { BAR_GRAINS, resolveBarGrain } from "../grammar/bars/policy.js";
4
+ import { resolveRuleMode } from "../grammar/rules.js";
5
+ import { findUpstreamTransform } from "./dataProvenance.js";
6
+
7
+ function hasPositionScales(layer) {
8
+ return (
9
+ layer.encoding?.x?.scale !== undefined &&
10
+ layer.encoding?.y?.scale !== undefined
11
+ );
12
+ }
13
+
14
+ export function canMaterializePoint(_program, layer) {
15
+ return layer.mark?.type === "point" && hasPositionScales(layer);
16
+ }
17
+
18
+ export function canMaterializeLine(program, layer) {
19
+ const dataset = findDataset(program, layer.data);
20
+ const interval = dataset?.transform?.some(item => item.type === "interval");
21
+ return (
22
+ layer.mark?.type === "line" &&
23
+ hasPositionScales(layer) &&
24
+ (isAggregate(layer.encoding.y.aggregate) ||
25
+ (layer.encoding.x.fieldType === "quantitative" &&
26
+ layer.encoding.y.fieldType === "quantitative") ||
27
+ ((interval || layer.encoding.y.aggregate === undefined) &&
28
+ ["quantitative", "temporal"].includes(layer.encoding.x.fieldType) &&
29
+ ["quantitative", "temporal"].includes(layer.encoding.y.fieldType) &&
30
+ layer.encoding.x.fieldType !== layer.encoding.y.fieldType))
31
+ );
32
+ }
33
+
34
+ export function canMaterializeArea(program, layer) {
35
+ if (layer.mark?.type !== "area" || !hasPositionScales(layer)) {
36
+ return false;
37
+ }
38
+
39
+ const dataset = findDataset(program, layer.data);
40
+ const densityTransform = findUpstreamTransform(program, dataset, "density");
41
+ const completeDensity =
42
+ densityTransform !== undefined &&
43
+ (densityTransform.groupBy === undefined ||
44
+ layer.encoding?.group?.field === densityTransform.groupBy);
45
+
46
+ return (
47
+ completeDensity ||
48
+ layer.encoding?.y2?.scale === layer.encoding.y.scale ||
49
+ layer.encoding?.x2?.scale === layer.encoding.x.scale
50
+ );
51
+ }
52
+
53
+ export function canMaterializeBar(program, layer) {
54
+ if (layer.mark?.type !== "bar" || !hasPositionScales(layer)) {
55
+ return false;
56
+ }
57
+ const grain = resolveBarGrain(layer);
58
+ return grain === BAR_GRAINS.histogram || (
59
+ [BAR_GRAINS.aggregate, BAR_GRAINS.ranged].includes(grain) &&
60
+ program.markConfigs[layer.id]?.barWidth !== undefined
61
+ );
62
+ }
63
+
64
+ export function canMaterializeRule(program, layer) {
65
+ const fixedSpan = program.markConfigs[layer.id]?.fixedSpan;
66
+ const boxSpanOwner = program.markConfigs[layer.id]?.boxSpanOwner;
67
+ return layer.mark?.type === "rule" && (
68
+ resolveRuleMode(layer) !== undefined ||
69
+ (boxSpanOwner !== undefined && hasPositionScales(layer)) ||
70
+ (fixedSpan !== undefined && hasPositionScales(layer))
71
+ );
72
+ }
73
+
74
+ const MARK_MATERIALIZATION_POLICIES = Object.freeze({
75
+ point: Object.freeze({
76
+ canMaterialize: canMaterializePoint,
77
+ op: "rematerializePointMark"
78
+ }),
79
+ line: Object.freeze({
80
+ canMaterialize: canMaterializeLine,
81
+ op: "rematerializeLineMark"
82
+ }),
83
+ area: Object.freeze({
84
+ canMaterialize: canMaterializeArea,
85
+ op: "rematerializeAreaMark"
86
+ }),
87
+ bar: Object.freeze({
88
+ canMaterialize: canMaterializeBar,
89
+ op: "rematerializeBarMark"
90
+ }),
91
+ rule: Object.freeze({
92
+ canMaterialize: canMaterializeRule,
93
+ op: "rematerializeRuleMark"
94
+ })
95
+ });
96
+
97
+ export function getMarkRematerializationStep(layer) {
98
+ const policy = MARK_MATERIALIZATION_POLICIES[layer.mark?.type];
99
+ return policy === undefined
100
+ ? undefined
101
+ : { op: policy.op, args: { id: layer.id } };
102
+ }
103
+
104
+ export function getMarkMaterializationStep(program, layer) {
105
+ const policy = MARK_MATERIALIZATION_POLICIES[layer.mark?.type];
106
+ if (policy === undefined || !policy.canMaterialize(program, layer)) {
107
+ return undefined;
108
+ }
109
+ return getMarkRematerializationStep(layer);
110
+ }
@@ -0,0 +1,38 @@
1
+ const STAGES = Object.freeze([
2
+ "scales",
3
+ "marks",
4
+ "guides",
5
+ "layout",
6
+ "highlights"
7
+ ]);
8
+
9
+ function stepKey(step) {
10
+ return JSON.stringify([step.op, step.args ?? null]);
11
+ }
12
+
13
+ export function buildMaterializationPlan(stages = {}) {
14
+ const plan = [];
15
+ const seen = new Set();
16
+
17
+ for (const stage of STAGES) {
18
+ for (const step of stages[stage] ?? []) {
19
+ if (step === undefined) continue;
20
+ const key = stepKey(step);
21
+ if (seen.has(key)) continue;
22
+ seen.add(key);
23
+ plan.push(step);
24
+ }
25
+ }
26
+
27
+ return plan;
28
+ }
29
+
30
+ export function applyMaterializationPlan(program, plan) {
31
+ let next = program;
32
+ for (const step of buildMaterializationPlan({ marks: plan })) {
33
+ next = step.args === undefined
34
+ ? next[step.op]()
35
+ : next[step.op](step.args);
36
+ }
37
+ return next;
38
+ }
@@ -0,0 +1,40 @@
1
+ import { isPlainObject } from "../../core/immutable.js";
2
+ import {
3
+ normalizeMarkSelector,
4
+ selectMarkItemKeys
5
+ } from "../../grammar/markSelection.js";
6
+ import { findDataset } from "../../selectors/datasets.js";
7
+ import { findLayer } from "../../selectors/layers.js";
8
+ import { resolveMarkSelection } from "./state.js";
9
+
10
+ function resolvePointRows(program, layer, selector) {
11
+ const dataset = findDataset(program, layer.data);
12
+ if (dataset === undefined) {
13
+ throw new Error(`Point mark "${layer.id}" requires an existing dataset.`);
14
+ }
15
+ const items = dataset.values.map((row, index) => ({
16
+ key: `${layer.id}/point/${index}`,
17
+ fields: isPlainObject(row) ? { ...row } : {},
18
+ channels: {},
19
+ properties: {},
20
+ members: [row]
21
+ }));
22
+ return {
23
+ selector,
24
+ items,
25
+ keys: selectMarkItemKeys(items, selector)
26
+ };
27
+ }
28
+
29
+ export function resolveMarkFilterSelection(program, target, selector) {
30
+ const normalized = normalizeMarkSelector(selector);
31
+ const layer = findLayer(program, target);
32
+ if (
33
+ layer?.mark?.type === "point" &&
34
+ normalized.grain === "item" &&
35
+ normalized.field !== undefined
36
+ ) {
37
+ return resolvePointRows(program, layer, normalized);
38
+ }
39
+ return resolveMarkSelection(program, target, normalized);
40
+ }