ggaction 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +12 -1
  3. package/package.json +6 -1
  4. package/src/actions/boxPlots/components.js +3 -3
  5. package/src/actions/boxPlots/edit.js +277 -0
  6. package/src/actions/boxPlots/index.js +2 -0
  7. package/src/actions/boxPlots/materialize.js +12 -1
  8. package/src/actions/boxPlots/options.js +25 -14
  9. package/src/actions/canvas/actions.js +21 -17
  10. package/src/actions/composition/actions.js +247 -0
  11. package/src/actions/composition/index.js +1 -0
  12. package/src/actions/coordinates/actions.js +2 -5
  13. package/src/actions/data/density.js +7 -1
  14. package/src/actions/data/filter.js +3 -1
  15. package/src/actions/encodings/appearance.js +11 -0
  16. package/src/actions/encodings/barWidth.js +5 -3
  17. package/src/actions/encodings/color.js +120 -25
  18. package/src/actions/encodings/index.js +2 -0
  19. package/src/actions/encodings/offset.js +90 -72
  20. package/src/actions/encodings/position/apply.js +66 -0
  21. package/src/actions/encodings/position/policies/arc.js +26 -0
  22. package/src/actions/encodings/position/policies/bar.js +9 -5
  23. package/src/actions/encodings/position/policies/index.js +5 -1
  24. package/src/actions/encodings/position/policies/line.js +20 -3
  25. package/src/actions/encodings/position/resolve.js +57 -8
  26. package/src/actions/encodings/position.js +44 -90
  27. package/src/actions/encodings/ranged.js +28 -6
  28. package/src/actions/encodings/shared.js +17 -21
  29. package/src/actions/encodings/text.js +73 -0
  30. package/src/actions/errorBands/create.js +37 -29
  31. package/src/actions/errorBands/edit.js +206 -0
  32. package/src/actions/errorBands/index.js +9 -0
  33. package/src/actions/errorBands/options.js +39 -0
  34. package/src/actions/errorBars/create.js +50 -43
  35. package/src/actions/errorBars/edit.js +130 -0
  36. package/src/actions/errorBars/index.js +3 -0
  37. package/src/actions/errorBars/options.js +52 -0
  38. package/src/actions/facets/actions.js +162 -0
  39. package/src/actions/facets/derive.js +200 -0
  40. package/src/actions/facets/guides.js +16 -0
  41. package/src/actions/facets/index.js +1 -0
  42. package/src/actions/facets/replay.js +69 -0
  43. package/src/actions/guides/applicability.js +113 -0
  44. package/src/actions/guides/axes/axes.js +70 -16
  45. package/src/actions/guides/axes/edit.js +126 -0
  46. package/src/actions/guides/axes/index.js +12 -0
  47. package/src/actions/guides/axes/labels.js +21 -18
  48. package/src/actions/guides/axes/remove.js +60 -0
  49. package/src/actions/guides/axes/tickGroups.js +7 -5
  50. package/src/actions/guides/axes/ticks.js +9 -4
  51. package/src/actions/guides/axes/titles.js +7 -5
  52. package/src/actions/guides/grids/grid.js +144 -15
  53. package/src/actions/guides/grids/resolve.js +5 -13
  54. package/src/actions/guides/guides.js +17 -75
  55. package/src/actions/guides/index.js +6 -0
  56. package/src/actions/guides/legends/categorical/actions.js +48 -9
  57. package/src/actions/guides/legends/categorical/components.js +16 -16
  58. package/src/actions/guides/legends/categorical/index.js +14 -14
  59. package/src/actions/guides/legends/categorical/layout.js +3 -1
  60. package/src/actions/guides/legends/categorical/options.js +2 -5
  61. package/src/actions/guides/legends/categorical/resolve.js +2 -2
  62. package/src/actions/guides/legends/categorical/symbols.js +14 -14
  63. package/src/actions/guides/legends/continuous/common.js +7 -7
  64. package/src/actions/guides/legends/continuous/interval.js +5 -5
  65. package/src/actions/guides/legends/edit.js +2 -6
  66. package/src/actions/guides/legends/focused.js +63 -0
  67. package/src/actions/guides/legends/index.js +4 -0
  68. package/src/actions/guides/legends/remove.js +64 -0
  69. package/src/actions/guides/legends/size.js +12 -14
  70. package/src/actions/guides/polar/axes/facade.js +219 -0
  71. package/src/actions/guides/polar/axes/labels.js +188 -0
  72. package/src/actions/guides/polar/axes/lines.js +113 -0
  73. package/src/actions/guides/polar/axes/shared.js +112 -0
  74. package/src/actions/guides/polar/axes/ticks.js +164 -0
  75. package/src/actions/guides/polar/axes/titles.js +165 -0
  76. package/src/actions/guides/polar/axes.js +55 -0
  77. package/src/actions/guides/polar/grids.js +252 -0
  78. package/src/actions/guides/polar/index.js +2 -0
  79. package/src/actions/guides/polar/resolve.js +269 -0
  80. package/src/actions/index.js +4 -0
  81. package/src/actions/marks/arc/actions.js +239 -0
  82. package/src/actions/marks/arc/index.js +1 -0
  83. package/src/actions/marks/{area.js → area/actions.js} +51 -38
  84. package/src/actions/marks/area/index.js +4 -0
  85. package/src/actions/marks/bar/create.js +26 -5
  86. package/src/actions/marks/bar/materialize.js +25 -31
  87. package/src/actions/marks/index.js +12 -4
  88. package/src/actions/marks/lifecycle.js +27 -0
  89. package/src/actions/marks/line/actions.js +373 -0
  90. package/src/actions/marks/line/index.js +1 -0
  91. package/src/actions/marks/{point.js → point/actions.js} +67 -108
  92. package/src/actions/marks/point/index.js +1 -0
  93. package/src/actions/marks/rect/actions.js +165 -0
  94. package/src/actions/marks/rect/index.js +1 -0
  95. package/src/actions/marks/remove.js +214 -0
  96. package/src/actions/marks/{rule.js → rule/actions.js} +34 -28
  97. package/src/actions/marks/rule/index.js +1 -0
  98. package/src/actions/marks/shared.js +156 -25
  99. package/src/actions/marks/text/actions.js +192 -0
  100. package/src/actions/marks/text/index.js +1 -0
  101. package/src/actions/primitives/createGraphics.js +13 -6
  102. package/src/actions/primitives/editGraphics.js +10 -3
  103. package/src/actions/primitives/semantic.js +26 -3
  104. package/src/actions/primitives/semanticValue.js +25 -3
  105. package/src/actions/regression/components.js +1 -1
  106. package/src/actions/regression/create.js +18 -1
  107. package/src/actions/regression/edit.js +247 -0
  108. package/src/actions/regression/index.js +3 -0
  109. package/src/actions/regression/resolve.js +3 -1
  110. package/src/actions/scales/consumers.js +15 -6
  111. package/src/actions/scales/definitions.js +13 -4
  112. package/src/actions/scales/edit.js +42 -19
  113. package/src/actions/scales/materialize.js +57 -408
  114. package/src/actions/selection/actions.js +38 -24
  115. package/src/actions/selection/index.js +2 -0
  116. package/src/actions/titles/actions.js +63 -2
  117. package/src/actions/titles/index.js +2 -0
  118. package/src/actions/titles/resolve.js +12 -17
  119. package/src/composition.js +10 -0
  120. package/src/core/ChartProgram.js +200 -2
  121. package/src/core/action.js +10 -0
  122. package/src/core/validation.js +16 -0
  123. package/src/core/vocabulary.js +154 -4
  124. package/src/grammar/arcs.js +154 -0
  125. package/src/grammar/bars/aggregate.js +2 -2
  126. package/src/grammar/bars/geometry.js +3 -3
  127. package/src/grammar/bars/policy.js +7 -1
  128. package/src/grammar/coordinates.js +3 -7
  129. package/src/grammar/density.js +20 -1
  130. package/src/grammar/facets/dependencies.js +185 -0
  131. package/src/grammar/facets/guides.js +257 -0
  132. package/src/grammar/facets/scales.js +254 -0
  133. package/src/grammar/facets.js +122 -0
  134. package/src/grammar/lineSeries.js +111 -3
  135. package/src/grammar/polar.js +102 -0
  136. package/src/grammar/polarGuides.js +243 -0
  137. package/src/grammar/polarLineCommands.js +43 -0
  138. package/src/grammar/polarPaths.js +161 -0
  139. package/src/grammar/positionCompatibility.js +18 -2
  140. package/src/grammar/rects.js +26 -0
  141. package/src/grammar/scales/continuous.js +6 -1
  142. package/src/grammar/scales/fields.js +7 -0
  143. package/src/grammar/scales/ordinal.js +5 -2
  144. package/src/grammar/scales/policies.js +1 -1
  145. package/src/grammar/schemas/graphic.js +1 -1
  146. package/src/grammar/schemas/graphicBounds.js +64 -9
  147. package/src/grammar/schemas/graphicTree.js +44 -10
  148. package/src/grammar/schemas/semanticPath.js +31 -9
  149. package/src/grammar/seriesLayout.js +13 -2
  150. package/src/grammar/text.js +71 -0
  151. package/src/grammar/ticks.js +50 -13
  152. package/src/index.js +1 -0
  153. package/src/layout/composition.js +310 -0
  154. package/src/layout/facets.js +131 -0
  155. package/src/layout/title.js +6 -0
  156. package/src/materialization/bars/aggregate.js +5 -2
  157. package/src/materialization/bars/grouped.js +75 -37
  158. package/src/materialization/composition.js +152 -0
  159. package/src/materialization/compositionSnapshot.js +114 -0
  160. package/src/materialization/dependencies.js +71 -16
  161. package/src/materialization/encodings.js +14 -28
  162. package/src/materialization/facetGuides.js +426 -0
  163. package/src/materialization/facets.js +214 -0
  164. package/src/materialization/graphicHierarchy.js +15 -5
  165. package/src/materialization/guides/resources.js +46 -0
  166. package/src/materialization/marks.js +220 -14
  167. package/src/materialization/planner.js +105 -12
  168. package/src/materialization/rect.js +138 -0
  169. package/src/materialization/rectConfig.js +38 -0
  170. package/src/materialization/rowEncoding.js +33 -0
  171. package/src/materialization/scales/map.js +40 -0
  172. package/src/materialization/scales/policies/arc.js +37 -0
  173. package/src/materialization/scales/policies/bar.js +132 -0
  174. package/src/materialization/scales/policies/series.js +76 -0
  175. package/src/materialization/scales/resolve.js +326 -0
  176. package/src/materialization/selection/items/arc.js +29 -0
  177. package/src/materialization/selection/items/bar.js +255 -0
  178. package/src/materialization/selection/items/common.js +131 -0
  179. package/src/materialization/selection/items/index.js +6 -0
  180. package/src/materialization/selection/items/path.js +59 -0
  181. package/src/materialization/selection/items/point.js +55 -0
  182. package/src/materialization/selection/items/rect.js +23 -0
  183. package/src/materialization/selection/items/rule.js +25 -0
  184. package/src/materialization/selection/policies/arc.js +10 -0
  185. package/src/materialization/selection/policies/area.js +1 -1
  186. package/src/materialization/selection/policies/bar.js +1 -1
  187. package/src/materialization/selection/policies/index.js +5 -1
  188. package/src/materialization/selection/policies/line.js +1 -1
  189. package/src/materialization/selection/policies/point.js +1 -1
  190. package/src/materialization/selection/policies/rect.js +10 -0
  191. package/src/materialization/selection/policies/rule.js +1 -1
  192. package/src/materialization/selection/styles.js +24 -16
  193. package/src/materialization/text.js +156 -0
  194. package/src/renderers/canvas/index.js +68 -22
  195. package/types/extension.d.ts +1 -0
  196. package/types/index.d.ts +56 -1
  197. package/types/program.d.ts +664 -27
  198. package/src/actions/marks/line.js +0 -248
  199. package/src/materialization/selection/items.js +0 -503
@@ -0,0 +1,152 @@
1
+ import { freezeOwned } from "../core/immutable.js";
2
+ import {
3
+ requireSingleOrderedGraphicByType
4
+ } from "../grammar/schemas/graphicTree.js";
5
+ import {
6
+ resolveCompositionLayout,
7
+ resolveCompositionSnapshotPlacement
8
+ } from "../layout/composition.js";
9
+ import { namespaceGraphicSnapshot } from "./compositionSnapshot.js";
10
+
11
+ const ZERO_MARGIN = freezeOwned({ top: 0, right: 0, bottom: 0, left: 0 });
12
+
13
+ export function requireChildCanvas(program, id) {
14
+ const { object } = requireSingleOrderedGraphicByType(program.graphicSpec, "canvas");
15
+ const { width, height } = object.properties ?? {};
16
+ if (!Number.isFinite(width) || width <= 0 || !Number.isFinite(height) || height <= 0) {
17
+ throw new Error(`Composition child "${id}" requires a complete positive Canvas size.`);
18
+ }
19
+ return object;
20
+ }
21
+
22
+ export function compositionChildDescriptor(id, program) {
23
+ const canvas = requireChildCanvas(program, id);
24
+ const size = program.materializationConfigs.canvas?.size ?? {
25
+ width: "explicit",
26
+ height: "explicit"
27
+ };
28
+ return {
29
+ id,
30
+ width: canvas.properties.width,
31
+ height: canvas.properties.height,
32
+ widthMode: size.width,
33
+ heightMode: size.height
34
+ };
35
+ }
36
+
37
+ export function resolveCompositionProgramLayout(program) {
38
+ program._assertCompositionProgram("resolveCompositionProgramLayout");
39
+ const spec = program.compositionSpec;
40
+ return resolveCompositionLayout({
41
+ direction: spec.direction,
42
+ children: spec.children.map(id => compositionChildDescriptor(id, program.children[id])),
43
+ gap: spec.gap,
44
+ align: spec.align,
45
+ padding: spec.padding
46
+ });
47
+ }
48
+
49
+ function resizeChildForSnapshot(program, placement) {
50
+ const canvas = requireSingleOrderedGraphicByType(program.graphicSpec, "canvas");
51
+ const widthChanged = canvas.object.properties.width !== placement.width;
52
+ const heightChanged = canvas.object.properties.height !== placement.height;
53
+ if (!widthChanged && !heightChanged) return program;
54
+
55
+ if (program.compositionSpec !== undefined) {
56
+ return program;
57
+ }
58
+ if (typeof program.editCanvas !== "function") {
59
+ throw new Error("Automatic composition sizing requires child editCanvas support.");
60
+ }
61
+ return program.editCanvas({
62
+ ...(widthChanged ? { width: placement.width } : {}),
63
+ ...(heightChanged ? { height: placement.height } : {})
64
+ });
65
+ }
66
+
67
+ function resolveSnapshotPlacement(program, placement, layout) {
68
+ if (program.compositionSpec === undefined) return placement;
69
+ const canvas = requireSingleOrderedGraphicByType(program.graphicSpec, "canvas");
70
+ return resolveCompositionSnapshotPlacement({
71
+ direction: layout.direction,
72
+ align: layout.align,
73
+ placement,
74
+ width: canvas.object.properties.width,
75
+ height: canvas.object.properties.height
76
+ });
77
+ }
78
+
79
+ function itemDefinitions(object) {
80
+ return object.items.map(item => ({
81
+ type: item.type ?? object.type,
82
+ properties: item.properties
83
+ }));
84
+ }
85
+
86
+ export function attachSnapshotObject(program, snapshot, id, parent) {
87
+ const object = snapshot.objects[id];
88
+ let next = program.createGraphics({
89
+ id,
90
+ type: object.type,
91
+ parent,
92
+ ...(object.items !== undefined && object.type !== "collection"
93
+ ? { length: object.items.length }
94
+ : {})
95
+ });
96
+ for (const [property, value] of Object.entries(object.properties ?? {})) {
97
+ next = next.editGraphics({ target: id, property, value });
98
+ }
99
+ if (object.items !== undefined) {
100
+ next = next.editGraphics({
101
+ target: id,
102
+ property: "items",
103
+ value: itemDefinitions(object)
104
+ });
105
+ }
106
+ for (const childId of object.children ?? []) {
107
+ next = attachSnapshotObject(next, snapshot, childId, id);
108
+ }
109
+ return next;
110
+ }
111
+
112
+ export function clearCompositionChildren(program) {
113
+ const canvas = program.graphicSpec.objects.canvas;
114
+ let next = program;
115
+ for (const id of canvas?.children ?? []) {
116
+ next = next.editGraphics({ target: id, remove: true });
117
+ }
118
+ return next;
119
+ }
120
+
121
+ export function materializeCompositionGraphics(program) {
122
+ program._assertCompositionProgram("materializeCompositionGraphics");
123
+ const layout = resolveCompositionProgramLayout(program);
124
+ let next = clearCompositionChildren(program);
125
+ if (next.graphicSpec.objects.canvas === undefined) {
126
+ next = next.createGraphics({ id: "canvas", type: "canvas" });
127
+ }
128
+ for (const [property, value] of Object.entries({
129
+ width: layout.width,
130
+ height: layout.height,
131
+ background: "white"
132
+ })) {
133
+ next = next.editGraphics({ target: "canvas", property, value });
134
+ }
135
+
136
+ for (const placement of layout.children) {
137
+ const source = program.children[placement.id];
138
+ const child = resizeChildForSnapshot(source, placement);
139
+ const snapshotPlacement = resolveSnapshotPlacement(child, placement, layout);
140
+ const snapshot = namespaceGraphicSnapshot(child.graphicSpec, {
141
+ namespace: `${program.compositionSpec.id}-${placement.id}`,
142
+ x: snapshotPlacement.x,
143
+ y: snapshotPlacement.y
144
+ });
145
+ next = attachSnapshotObject(next, snapshot, snapshot.order[0], "canvas");
146
+ }
147
+
148
+ return next._withCanvasConfig({
149
+ margin: ZERO_MARGIN,
150
+ size: { width: "auto", height: "auto" }
151
+ });
152
+ }
@@ -0,0 +1,114 @@
1
+ import { cloneAndFreeze, freezeOwned, isPlainObject } from "../core/immutable.js";
2
+ import {
3
+ requireSingleOrderedGraphicByType,
4
+ walkGraphicTreeEvents
5
+ } from "../grammar/schemas/graphicTree.js";
6
+
7
+ function validateNamespace(namespace) {
8
+ if (typeof namespace !== "string" || namespace.length === 0) {
9
+ throw new TypeError("Graphic snapshot namespace must be a non-empty string.");
10
+ }
11
+ return namespace;
12
+ }
13
+
14
+ function validatePlacement(value, property) {
15
+ if (!Number.isFinite(value)) {
16
+ throw new TypeError(`Graphic snapshot ${property} must be a finite number.`);
17
+ }
18
+ return value;
19
+ }
20
+
21
+ function validateCanvas(canvas, id) {
22
+ const { width, height } = canvas.properties ?? {};
23
+ if (!Number.isFinite(width) || width <= 0) {
24
+ throw new RangeError(`Child Canvas "${id}" width must be a positive finite number.`);
25
+ }
26
+ if (!Number.isFinite(height) || height <= 0) {
27
+ throw new RangeError(`Child Canvas "${id}" height must be a positive finite number.`);
28
+ }
29
+ }
30
+
31
+ export function namespaceGraphicId(namespace, id) {
32
+ const encode = value => Array.from(value, character =>
33
+ character.codePointAt(0).toString(16).padStart(6, "0")
34
+ ).join("");
35
+ return `g${encode(namespace)}_${encode(id)}`;
36
+ }
37
+
38
+ function rewriteItem(item, namespace) {
39
+ if (!isPlainObject(item) || typeof item.id !== "string" || item.id.length === 0) {
40
+ throw new TypeError("Graphic snapshot items require non-empty string ids.");
41
+ }
42
+ return freezeOwned({
43
+ ...cloneAndFreeze(item),
44
+ id: namespaceGraphicId(namespace, item.id)
45
+ });
46
+ }
47
+
48
+ function rewriteObject(object, namespace, rootCanvasId, placement, extraRoots) {
49
+ const children = object.children ?? [];
50
+ const rewritten = {
51
+ ...cloneAndFreeze(object),
52
+ ...(object.items === undefined
53
+ ? {}
54
+ : { items: freezeOwned(object.items.map(item => rewriteItem(item, namespace))) }),
55
+ ...(children.length === 0 && rootCanvasId === undefined
56
+ ? {}
57
+ : {
58
+ children: freezeOwned([
59
+ ...children.map(id => namespaceGraphicId(namespace, id)),
60
+ ...(rootCanvasId === undefined
61
+ ? []
62
+ : extraRoots.map(id => namespaceGraphicId(namespace, id)))
63
+ ])
64
+ })
65
+ };
66
+ if (rootCanvasId !== undefined) {
67
+ rewritten.properties = freezeOwned({
68
+ ...rewritten.properties,
69
+ x: placement.x,
70
+ y: placement.y
71
+ });
72
+ }
73
+ return freezeOwned(rewritten);
74
+ }
75
+
76
+ export function namespaceGraphicSnapshot(
77
+ graphicSpec,
78
+ { namespace, x = 0, y = 0 } = {}
79
+ ) {
80
+ const resolvedNamespace = validateNamespace(namespace);
81
+ const placement = {
82
+ x: validatePlacement(x, "x"),
83
+ y: validatePlacement(y, "y")
84
+ };
85
+ if (
86
+ !isPlainObject(graphicSpec) ||
87
+ !isPlainObject(graphicSpec.objects) ||
88
+ !Array.isArray(graphicSpec.order)
89
+ ) {
90
+ throw new TypeError("Graphic snapshot requires objects and order.");
91
+ }
92
+ walkGraphicTreeEvents(graphicSpec, {});
93
+ const { id: canvasId, object: canvas } =
94
+ requireSingleOrderedGraphicByType(graphicSpec, "canvas");
95
+ validateCanvas(canvas, canvasId);
96
+ const extraRoots = graphicSpec.order.filter(id => id !== canvasId);
97
+ const objects = Object.fromEntries(Object.entries(graphicSpec.objects).map(
98
+ ([id, object]) => [
99
+ namespaceGraphicId(resolvedNamespace, id),
100
+ rewriteObject(
101
+ object,
102
+ resolvedNamespace,
103
+ id === canvasId ? canvasId : undefined,
104
+ placement,
105
+ id === canvasId ? extraRoots : []
106
+ )
107
+ ]
108
+ ));
109
+ const rootId = namespaceGraphicId(resolvedNamespace, canvasId);
110
+ return freezeOwned({
111
+ objects: freezeOwned(objects),
112
+ order: freezeOwned([rootId])
113
+ });
114
+ }
@@ -1,4 +1,9 @@
1
- import { getMarkMaterializationStep } from "./marks.js";
1
+ import {
2
+ canDeferScaleConsumerApplication,
3
+ getExistingMarkRematerializationStep,
4
+ getMarkMaterializationStep,
5
+ getSourceDependentMarkSteps
6
+ } from "./marks.js";
2
7
  import { requireLayer } from "../selectors/layers.js";
3
8
  import {
4
9
  applyMaterializationPlan,
@@ -8,22 +13,34 @@ import {
8
13
  hasMaterializedLegend,
9
14
  materializedLegendUsesScale
10
15
  } from "./legends.js";
16
+ import { POSITION_ENCODING_CHANNELS } from "../core/vocabulary.js";
11
17
 
12
18
  function usesPositionalScale(program, id) {
13
19
  return program.semanticSpec.layers.some(layer =>
14
- ["x", "y", "x2", "y2", "xOffset"].some(
20
+ POSITION_ENCODING_CHANNELS.some(
15
21
  channel => layer.encoding?.[channel]?.scale === id
16
22
  )
17
23
  );
18
24
  }
19
25
 
26
+ function usesRadialScale(program, id) {
27
+ return program.semanticSpec.layers.some(layer =>
28
+ layer.encoding?.radius?.scale === id
29
+ );
30
+ }
31
+
20
32
  function needsCanvasScaleRematerialization(program, scale) {
21
33
  return (
22
34
  (scale.range === "auto" ||
35
+ usesRadialScale(program, scale.id) ||
23
36
  program.semanticSpec.guides.axis?.x?.scale === scale.id ||
24
37
  program.semanticSpec.guides.axis?.y?.scale === scale.id ||
38
+ program.semanticSpec.guides.axis?.theta?.scale === scale.id ||
39
+ program.semanticSpec.guides.axis?.radius?.scale === scale.id ||
25
40
  program.semanticSpec.guides.grid?.horizontal?.scale === scale.id ||
26
- program.semanticSpec.guides.grid?.vertical?.scale === scale.id) &&
41
+ program.semanticSpec.guides.grid?.vertical?.scale === scale.id ||
42
+ program.semanticSpec.guides.grid?.theta?.scale === scale.id ||
43
+ program.semanticSpec.guides.grid?.radial?.scale === scale.id) &&
27
44
  program.resolvedScales[scale.id] !== undefined &&
28
45
  usesPositionalScale(program, scale.id)
29
46
  );
@@ -37,20 +54,36 @@ function hasTitle(program) {
37
54
  }
38
55
 
39
56
  export function planCanvasRematerialization(program) {
57
+ const marks = [];
58
+ for (const layer of program.semanticSpec.layers) {
59
+ const step = getMarkMaterializationStep(program, layer);
60
+ if (step !== undefined) marks.push(step);
61
+ }
62
+ const deferredMarkIds = new Set(
63
+ marks
64
+ .map(step => step.args.id)
65
+ );
40
66
  const scales = [];
41
67
  for (const scale of program.semanticSpec.scales) {
42
68
  if (needsCanvasScaleRematerialization(program, scale)) {
69
+ const deferredConsumers = program.semanticSpec.layers.filter(layer =>
70
+ canDeferScaleConsumerApplication(layer) &&
71
+ Object.values(layer.encoding ?? {}).some(
72
+ encoding => encoding?.scale === scale.id
73
+ )
74
+ );
75
+ const canDeferMarks = deferredConsumers.length === 0 ||
76
+ deferredConsumers.every(layer => deferredMarkIds.has(layer.id));
43
77
  scales.push({
44
78
  op: "rematerializeScale",
45
- args: { id: scale.id, guides: false }
79
+ args: {
80
+ id: scale.id,
81
+ guides: false,
82
+ ...(canDeferMarks ? { marks: false } : {})
83
+ }
46
84
  });
47
85
  }
48
86
  }
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
87
  const guides = program.semanticSpec.scales.flatMap(scale =>
55
88
  needsCanvasScaleRematerialization(program, scale)
56
89
  ? planScaleGuideRematerialization(program, scale.id)
@@ -87,12 +120,27 @@ export function planScaleGuideRematerialization(program, id) {
87
120
  }
88
121
  }
89
122
  }
123
+ for (const channel of ["theta", "radius"]) {
124
+ const prefix = channel === "theta" ? "Theta" : "Radial";
125
+ for (const component of ["line", "ticks", "labels", "title"]) {
126
+ if (guideConfigs.axis?.[channel]?.[component]?.scale === id) {
127
+ const suffix = component[0].toUpperCase() + component.slice(1);
128
+ guides.push({ op: `edit${prefix}Axis${suffix}` });
129
+ }
130
+ }
131
+ }
90
132
  if (guideConfigs.grid?.horizontal?.scale === id) {
91
133
  guides.push({ op: "rematerializeHorizontalGrid" });
92
134
  }
93
135
  if (guideConfigs.grid?.vertical?.scale === id) {
94
136
  guides.push({ op: "rematerializeVerticalGrid" });
95
137
  }
138
+ if (guideConfigs.grid?.theta?.scale === id) {
139
+ guides.push({ op: "rematerializeThetaGrid" });
140
+ }
141
+ if (guideConfigs.grid?.radial?.scale === id) {
142
+ guides.push({ op: "rematerializeRadialGrid" });
143
+ }
96
144
  if (materializedLegendUsesScale(program, id)) {
97
145
  guides.push({ op: "rematerializeLegend" });
98
146
  }
@@ -106,22 +154,29 @@ export function planLayerDataRematerialization(program, id) {
106
154
  .map(encoding => encoding?.scale)
107
155
  .filter(scale => scale !== undefined)
108
156
  )];
157
+ const markStep = getMarkMaterializationStep(program, layer);
109
158
  const scales = scaleIds.map(scale => ({
110
159
  op: "rematerializeScale",
111
- args: { id: scale, guides: false }
160
+ args: {
161
+ id: scale,
162
+ guides: false,
163
+ ...(markStep === undefined ? {} : { marks: false })
164
+ }
112
165
  }));
113
- const markStep = getMarkMaterializationStep(program, layer);
114
- const marks = markStep === undefined ? [] : [markStep];
166
+ const marks = [
167
+ ...(markStep === undefined ? [] : [markStep]),
168
+ ...getSourceDependentMarkSteps(program, id)
169
+ ];
115
170
  if (scales.length > 0 || marks.length > 0) {
116
171
  const guides = scaleIds.flatMap(scale =>
117
172
  planScaleGuideRematerialization(program, scale)
118
173
  );
119
174
  return buildMaterializationPlan({ scales, marks, guides });
120
175
  }
121
- return buildMaterializationPlan({ marks: layer.mark?.type === "point" &&
122
- program.graphicSpec.objects[layer.id] !== undefined
123
- ? [{ op: "rematerializePointMark", args: { id: layer.id } }]
124
- : [] });
176
+ const existingStep = getExistingMarkRematerializationStep(program, layer);
177
+ return buildMaterializationPlan({
178
+ marks: existingStep === undefined ? [] : [existingStep]
179
+ });
125
180
  }
126
181
 
127
182
  export function applyLayerDataRematerialization(program, id) {
@@ -1,7 +1,10 @@
1
1
  import { hasMaterializedLegend } from "./legends.js";
2
2
  import { requireLayer } from "../selectors/layers.js";
3
- import { getMarkRematerializationStep } from "./marks.js";
3
+ import {
4
+ getEncodingMaterializationStages
5
+ } from "./marks.js";
4
6
  import { buildMaterializationPlan } from "./planner.js";
7
+ import { getSourceDependentMarkSteps } from "./marks.js";
5
8
 
6
9
  export function planEncodingRematerialization(program, {
7
10
  target,
@@ -14,33 +17,16 @@ export function planEncodingRematerialization(program, {
14
17
  `Unknown encoding materialization target "${target}"`
15
18
  );
16
19
 
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
- }
20
+ const { scales, marks: directMarks } = getEncodingMaterializationStages(
21
+ program,
22
+ layer,
23
+ channel,
24
+ scale
25
+ );
26
+ const marks = [
27
+ ...directMarks,
28
+ ...getSourceDependentMarkSteps(program, target)
29
+ ];
44
30
 
45
31
  const guides = [];
46
32
  if (hasMaterializedLegend(program)) {