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,165 @@
1
+ import { action } from "../../../core/action.js";
2
+ import { validateUserId } from "../../../core/identifiers.js";
3
+ import {
4
+ DEFAULT_RECT_MARK,
5
+ normalizeRectMarkConfig
6
+ } from "../../../materialization/rectConfig.js";
7
+ import {
8
+ canMaterializeRect
9
+ } from "../../../materialization/marks.js";
10
+ import { resolveRectGraphicItems } from "../../../materialization/rect.js";
11
+ import { findDataset } from "../../../selectors/datasets.js";
12
+ import { findLayer, resolveEligibleLayer } from "../../../selectors/layers.js";
13
+ import {
14
+ assertMarkAvailable,
15
+ applyLayeredMarkInheritance,
16
+ materializeInheritedMark,
17
+ resolveLayeredMarkInheritance,
18
+ resolveMarkData,
19
+ resolveMarkId,
20
+ validateMarkOptions
21
+ } from "../shared.js";
22
+ import { resolveMarkGraphicPlacement } from
23
+ "../../../materialization/graphicHierarchy.js";
24
+ import { rematerializeHighlightBaseline } from "../lifecycle.js";
25
+
26
+ const STYLE_OPTIONS = Object.freeze([
27
+ "fill", "opacity", "stroke", "strokeWidth"
28
+ ]);
29
+ const CREATE_OPTIONS = Object.freeze(["id", "data", ...STYLE_OPTIONS]);
30
+ const EDIT_OPTIONS = Object.freeze(["target", ...STYLE_OPTIONS]);
31
+ const REMATERIALIZE_OPTIONS = Object.freeze(["id", "scales"]);
32
+
33
+ function requireRectLayer(program, requested, operation) {
34
+ const target = requested === undefined
35
+ ? undefined
36
+ : validateUserId(requested, "Rect mark id");
37
+ return resolveEligibleLayer(program, {
38
+ target,
39
+ predicate: layer => layer.mark?.type === "rect",
40
+ label: `${operation} rect mark`
41
+ });
42
+ }
43
+
44
+ const createRectMark = action(
45
+ { op: "createRectMark", description: "Create a semantic rectangular cell layer." },
46
+ function (args = {}) {
47
+ validateMarkOptions(args, CREATE_OPTIONS, "createRectMark");
48
+ const id = resolveMarkId(this, args.id, {
49
+ defaultId: "rect",
50
+ label: "Rect mark id",
51
+ markType: "rect",
52
+ operation: "createRectMark"
53
+ });
54
+ const inherited = resolveLayeredMarkInheritance(this, args, "rect");
55
+ const { data } = resolveMarkData(this, {
56
+ ...args,
57
+ ...(args.data === undefined && this.context.currentData === undefined &&
58
+ inherited?.data !== undefined ? { data: inherited.data } : {})
59
+ });
60
+ assertMarkAvailable(this, id);
61
+
62
+ let next = this
63
+ .editSemantic({ property: `layer[${id}].mark.type`, value: "rect" })
64
+ .editSemantic({ property: `layer[${id}].data`, value: data });
65
+ next = applyLayeredMarkInheritance(next, id, inherited)
66
+ .createGraphics({
67
+ id,
68
+ type: "rect",
69
+ length: 0,
70
+ ...resolveMarkGraphicPlacement(next, { data, markType: "rect" })
71
+ })
72
+ ._withMarkConfig(id, { ...DEFAULT_RECT_MARK, fillExplicit: false });
73
+ next = materializeInheritedMark(next, id);
74
+ const style = Object.fromEntries(
75
+ STYLE_OPTIONS.filter(option => Object.hasOwn(args, option))
76
+ .map(option => [option, args[option]])
77
+ );
78
+ return Object.keys(style).length === 0
79
+ ? next
80
+ : next.editRectMark({ target: id, ...style });
81
+ }
82
+ );
83
+
84
+ const rematerializeRectMark = action(
85
+ { op: "rematerializeRectMark", description: "Recompute concrete rectangular cells." },
86
+ function (args = {}) {
87
+ validateMarkOptions(args, REMATERIALIZE_OPTIONS, "rematerializeRectMark");
88
+ const id = validateUserId(args.id, "Rect mark id");
89
+ if (args.scales !== undefined && typeof args.scales !== "boolean") {
90
+ throw new TypeError("rematerializeRectMark scales must be a boolean.");
91
+ }
92
+ const highlighted = rematerializeHighlightBaseline(this, {
93
+ target: id,
94
+ operation: "rematerializeRectMark",
95
+ resetProperty: "items",
96
+ resetValue: []
97
+ });
98
+ if (highlighted !== undefined) return highlighted;
99
+ const layer = findLayer(this, id);
100
+ const graphic = this.graphicSpec.objects[id];
101
+ if (layer?.mark?.type !== "rect") throw new Error(`Unknown rect mark "${id}".`);
102
+ if (graphic?.type !== "rect" || !Array.isArray(graphic.items)) {
103
+ throw new Error(`Rect mark "${id}" requires rect collection graphics.`);
104
+ }
105
+ const dataset = findDataset(this, layer.data);
106
+ if (dataset === undefined) {
107
+ throw new Error(`Rect mark "${id}" requires an existing dataset.`);
108
+ }
109
+ if (!canMaterializeRect(this, layer)) {
110
+ return graphic.items.length === 0
111
+ ? this
112
+ : this.editGraphics({ target: id, property: "items", value: [] });
113
+ }
114
+ let next = this;
115
+ if (args.scales !== false) {
116
+ const ids = new Set(["x", "y", "x2", "y2", "color"].flatMap(channel => {
117
+ const scale = layer.encoding?.[channel]?.scale;
118
+ return scale === undefined ? [] : [scale];
119
+ }));
120
+ for (const scale of ids) {
121
+ next = next.rematerializeScale({ id: scale, marks: false });
122
+ }
123
+ }
124
+ return next.editGraphics({
125
+ target: id,
126
+ property: "items",
127
+ value: resolveRectGraphicItems(next, layer, dataset)
128
+ });
129
+ }
130
+ );
131
+
132
+ const editRectMark = action(
133
+ { op: "editRectMark", description: "Edit rectangular cell appearance." },
134
+ function (args = {}) {
135
+ validateMarkOptions(args, EDIT_OPTIONS, "editRectMark");
136
+ if (!STYLE_OPTIONS.some(option => Object.hasOwn(args, option))) {
137
+ throw new Error("editRectMark requires at least one editable property.");
138
+ }
139
+ const layer = requireRectLayer(this, args.target, "editRectMark");
140
+ if (Object.hasOwn(args, "fill") && layer.encoding?.color !== undefined) {
141
+ throw new Error("editRectMark fill cannot be combined with a color encoding.");
142
+ }
143
+ const next = this._withMarkConfig(
144
+ layer.id,
145
+ {
146
+ ...normalizeRectMarkConfig(
147
+ args,
148
+ this.markConfigs[layer.id] ?? DEFAULT_RECT_MARK
149
+ ),
150
+ fillExplicit: Object.hasOwn(args, "fill")
151
+ ? true
152
+ : this.markConfigs[layer.id]?.fillExplicit ?? false
153
+ }
154
+ );
155
+ return canMaterializeRect(next, layer)
156
+ ? next.rematerializeRectMark({ id: layer.id })
157
+ : next;
158
+ }
159
+ );
160
+
161
+ export function registerRectMarkActions(ProgramClass) {
162
+ ProgramClass.prototype.createRectMark = createRectMark;
163
+ ProgramClass.prototype.editRectMark = editRectMark;
164
+ ProgramClass.prototype.rematerializeRectMark = rematerializeRectMark;
165
+ }
@@ -0,0 +1 @@
1
+ export { registerRectMarkActions } from "./actions.js";
@@ -0,0 +1,214 @@
1
+ import { action } from "../../core/action.js";
2
+ import { validateUserId } from "../../core/identifiers.js";
3
+ import { validateKeys } from "../../core/validation.js";
4
+ import { findDataset } from "../../selectors/datasets.js";
5
+ import { findLayer } from "../../selectors/layers.js";
6
+ import {
7
+ getPositionChannelDefinition,
8
+ POSITION_CHANNELS
9
+ } from "../../core/vocabulary.js";
10
+
11
+ const OPTIONS = Object.freeze(["target"]);
12
+ const REMOVE_AXIS = Object.freeze({
13
+ x: "removeXAxis",
14
+ y: "removeYAxis",
15
+ theta: "removeThetaAxis",
16
+ radius: "removeRadialAxis"
17
+ });
18
+
19
+ function ownedChildren(program, id) {
20
+ const config = program.markConfigs[id] ?? {};
21
+ return [
22
+ config.errorBar?.lowerCapId,
23
+ config.errorBar?.upperCapId,
24
+ config.errorBand?.lowerBoundaryId,
25
+ config.errorBand?.upperBoundaryId,
26
+ config.regression?.bandId,
27
+ config.regression?.lineId,
28
+ config.boxPlot?.whiskerId,
29
+ config.boxPlot?.medianId,
30
+ config.boxPlot?.outlierId
31
+ ].concat(
32
+ program.semanticSpec.layers
33
+ .filter(layer => layer.source === id)
34
+ .map(layer => layer.id)
35
+ ).filter(child => child !== undefined && findLayer(program, child) !== undefined);
36
+ }
37
+
38
+ function ownership(program) {
39
+ const ownerByChild = new Map();
40
+ for (const layer of program.semanticSpec.layers) {
41
+ for (const child of ownedChildren(program, layer.id)) {
42
+ ownerByChild.set(child, layer.id);
43
+ }
44
+ }
45
+ return ownerByChild;
46
+ }
47
+
48
+ function resolveOwner(program, requested) {
49
+ const ownerByChild = ownership(program);
50
+ const stable = program.semanticSpec.layers.filter(
51
+ layer => !ownerByChild.has(layer.id)
52
+ );
53
+ if (requested !== undefined) {
54
+ const id = validateUserId(requested, "Mark owner id");
55
+ const layer = findLayer(program, id);
56
+ if (layer === undefined) throw new Error(`Unknown mark target "${id}".`);
57
+ if (ownerByChild.has(id)) {
58
+ throw new Error(
59
+ `Mark "${id}" is owned by "${ownerByChild.get(id)}"; remove its stable owner.`
60
+ );
61
+ }
62
+ return layer;
63
+ }
64
+ const current = findLayer(program, program.context.currentMark);
65
+ if (current !== undefined && stable.includes(current)) return current;
66
+ if (stable.length === 1) return stable[0];
67
+ if (stable.length === 0) throw new Error("removeMark requires an existing mark.");
68
+ throw new Error("removeMark target is ambiguous; provide target.");
69
+ }
70
+
71
+ function collectClosure(program, root) {
72
+ const result = [];
73
+ const visit = id => {
74
+ for (const child of ownedChildren(program, id)) visit(child);
75
+ if (!result.includes(id)) result.push(id);
76
+ };
77
+ visit(root);
78
+ return result;
79
+ }
80
+
81
+ function ownedDerivedData(program, ids) {
82
+ const candidates = new Set();
83
+ for (const id of ids) {
84
+ const layer = findLayer(program, id);
85
+ const config = program.markConfigs[id] ?? {};
86
+ for (const candidate of [
87
+ config.errorBar?.data,
88
+ config.errorBand?.data,
89
+ config.regression?.dataId,
90
+ config.boxPlot?.summaryId,
91
+ config.boxPlot?.outlierDataId
92
+ ]) {
93
+ if (candidate !== undefined) candidates.add(candidate);
94
+ }
95
+ const dataset = findDataset(program, layer?.data);
96
+ if (dataset?.transform?.[0]?.type === "markFilter") {
97
+ candidates.add(dataset.id);
98
+ }
99
+ }
100
+ return candidates;
101
+ }
102
+
103
+ function usedPositionScales(program, ids) {
104
+ const scales = Object.fromEntries(
105
+ POSITION_CHANNELS.map(channel => [channel, new Set()])
106
+ );
107
+ for (const id of ids) {
108
+ const layer = findLayer(program, id);
109
+ for (const channel of POSITION_CHANNELS) {
110
+ const scale = layer?.encoding?.[channel]?.scale;
111
+ if (scale !== undefined) scales[channel].add(scale);
112
+ }
113
+ }
114
+ return scales;
115
+ }
116
+
117
+ function cleanupSelectionState(program, ids) {
118
+ const targets = new Set(ids);
119
+ const selectionIds = Object.entries(program.materializationConfigs.selections ?? {})
120
+ .filter(([, config]) => targets.has(config.target))
121
+ .map(([id]) => id);
122
+ let next = program;
123
+ for (const [id, config] of Object.entries(
124
+ program.materializationConfigs.highlights ?? {}
125
+ )) {
126
+ if (targets.has(config.target) || selectionIds.includes(config.selection)) {
127
+ next = next._withoutMaterializationConfig(["highlights", id]);
128
+ }
129
+ }
130
+ for (const id of selectionIds) {
131
+ next = next._withoutMaterializationConfig(["selections", id]);
132
+ }
133
+ return {
134
+ program: next,
135
+ selectionIds
136
+ };
137
+ }
138
+
139
+ function hasScaleConsumer(program, channel, scale) {
140
+ return program.semanticSpec.layers.some(
141
+ layer => layer.encoding?.[channel]?.scale === scale
142
+ );
143
+ }
144
+
145
+ function cleanupPositionGuides(program, scales) {
146
+ let next = program;
147
+ for (const channel of POSITION_CHANNELS) {
148
+ const definition = getPositionChannelDefinition(channel);
149
+ const axis = next.semanticSpec.guides.axis?.[channel];
150
+ if (
151
+ axis !== undefined &&
152
+ scales[channel].has(axis.scale) &&
153
+ !hasScaleConsumer(next, channel, axis.scale)
154
+ ) {
155
+ next = next[REMOVE_AXIS[channel]]({
156
+ scale: axis.scale,
157
+ ...(axis.coordinate === undefined ? {} : { coordinate: axis.coordinate })
158
+ });
159
+ }
160
+ const grid = next.semanticSpec.guides.grid?.[definition.gridDirection];
161
+ if (
162
+ grid !== undefined &&
163
+ scales[channel].has(grid.scale) &&
164
+ !hasScaleConsumer(next, channel, grid.scale)
165
+ ) {
166
+ next = next.removeGrid({ [definition.gridDirection]: true });
167
+ }
168
+ }
169
+ return next;
170
+ }
171
+
172
+ export const removeMark = action(
173
+ { op: "removeMark", description: "Remove one stable mark owner and owned state." },
174
+ function (args = {}) {
175
+ validateKeys(args, OPTIONS, "removeMark");
176
+ const owner = resolveOwner(this, args.target);
177
+ const ids = collectClosure(this, owner.id);
178
+ const derived = ownedDerivedData(this, ids);
179
+ const positionScales = usedPositionScales(this, ids);
180
+ let next = this;
181
+
182
+ const legendTargets = [...new Set(Object.values(next.guideConfigs.legend ?? {})
183
+ .filter(config => ids.includes(config?.target))
184
+ .map(config => config.target))];
185
+ for (const target of legendTargets) {
186
+ next = next.removeLegend({ target });
187
+ }
188
+
189
+ const selectionCleanup = cleanupSelectionState(next, ids);
190
+ next = selectionCleanup.program;
191
+ for (const id of ids) {
192
+ if (findLayer(next, id) !== undefined) {
193
+ next = next.editSemantic({ property: `layer[${id}]`, remove: true });
194
+ }
195
+ if (next.graphicSpec.objects[id] !== undefined) {
196
+ next = next.editGraphics({ target: id, remove: true });
197
+ }
198
+ next = next._withoutMaterializationConfig(["marks", id]);
199
+ }
200
+
201
+ next = cleanupPositionGuides(next, positionScales);
202
+ for (const id of derived) {
203
+ if (findDataset(next, id)?.source !== undefined) {
204
+ next = next.releaseDerivedData({ id });
205
+ }
206
+ }
207
+ return next._withContext({
208
+ ...(ids.includes(this.context.currentMark) ? { currentMark: undefined } : {}),
209
+ ...(selectionCleanup.selectionIds.includes(this.context.currentSelection)
210
+ ? { currentSelection: undefined }
211
+ : {})
212
+ });
213
+ }
214
+ );
@@ -1,25 +1,29 @@
1
- import { action } from "../../core/action.js";
2
- import { validateUserId } from "../../core/identifiers.js";
3
- import { validatePositiveFinite } from "../../core/validation.js";
1
+ import { action } from "../../../core/action.js";
2
+ import { validateUserId } from "../../../core/identifiers.js";
3
+ import { validatePositiveFinite } from "../../../core/validation.js";
4
4
  import {
5
5
  mapContinuousScaleValues,
6
6
  mapOrdinalPositionValues,
7
7
  mapOrdinalValues,
8
8
  normalizeStrokeDashPattern
9
- } from "../../grammar/scales.js";
10
- import { deriveRuleValues, resolveRuleMode } from "../../grammar/rules.js";
11
- import { resolveGraphicBounds } from "../../layout/canvas.js";
12
- import { findDataset } from "../../selectors/datasets.js";
13
- import { findLayer } from "../../selectors/layers.js";
14
- import { DEFAULT_COLORS } from "../../theme/defaults.js";
9
+ } from "../../../grammar/scales.js";
10
+ import { deriveRuleValues, resolveRuleMode } from "../../../grammar/rules.js";
11
+ import { resolveGraphicBounds } from "../../../layout/canvas.js";
12
+ import { findDataset } from "../../../selectors/datasets.js";
13
+ import { findLayer } from "../../../selectors/layers.js";
14
+ import { DEFAULT_COLORS } from "../../../theme/defaults.js";
15
15
  import {
16
16
  assertMarkAvailable,
17
+ applyLayeredMarkInheritance,
18
+ materializeInheritedMark,
19
+ resolveLayeredMarkInheritance,
17
20
  resolveMarkData,
18
21
  resolveMarkId,
19
22
  validateMarkOptions
20
- } from "./shared.js";
23
+ } from "../shared.js";
21
24
  import { resolveMarkGraphicPlacement } from
22
- "../../materialization/graphicHierarchy.js";
25
+ "../../../materialization/graphicHierarchy.js";
26
+ import { rematerializeHighlightBaseline } from "../lifecycle.js";
23
27
 
24
28
  const CREATE_OPTIONS = Object.freeze(["id", "data"]);
25
29
  const REMATERIALIZE_OPTIONS = Object.freeze(["id"]);
@@ -81,19 +85,27 @@ const createRuleMark = action(
81
85
  markType: "rule",
82
86
  operation: "createRuleMark"
83
87
  });
84
- const { data } = resolveMarkData(this, args);
88
+ const inherited = resolveLayeredMarkInheritance(this, args, "rule");
89
+ const { data } = resolveMarkData(this, {
90
+ ...args,
91
+ ...(args.data === undefined && this.context.currentData === undefined &&
92
+ inherited?.data !== undefined ? { data: inherited.data } : {})
93
+ });
85
94
  assertMarkAvailable(this, id);
86
95
 
87
- return this
96
+ let created = this
88
97
  .editSemantic({ property: `layer[${id}].mark.type`, value: "rule" })
89
- .editSemantic({ property: `layer[${id}].data`, value: data })
98
+ .editSemantic({ property: `layer[${id}].data`, value: data });
99
+ created = applyLayeredMarkInheritance(created, id, inherited);
100
+ created = created
90
101
  .createGraphics({
91
102
  id,
92
103
  type: "line",
93
104
  length: 0,
94
- ...resolveMarkGraphicPlacement(this, { data, markType: "rule" })
105
+ ...resolveMarkGraphicPlacement(created, { data, markType: "rule" })
95
106
  })
96
107
  ._withMarkConfig(id, DEFAULT_RULE_CONFIG);
108
+ return materializeInheritedMark(created, id);
97
109
  }
98
110
  );
99
111
 
@@ -105,19 +117,13 @@ const rematerializeRuleMark = action(
105
117
  function (args = {}) {
106
118
  validateMarkOptions(args, REMATERIALIZE_OPTIONS, "rematerializeRuleMark");
107
119
  const id = validateUserId(args.id, "Rule mark id");
108
- const highlights = Object.entries(
109
- this.materializationConfigs.highlights ?? {}
110
- ).filter(([, config]) => config.target === id);
111
- if (highlights.length > 0) {
112
- let baseline = this;
113
- for (const [highlightId] of highlights) {
114
- baseline = baseline._withoutMaterializationConfig(["highlights", highlightId]);
115
- }
116
- return baseline
117
- .editGraphics({ target: id, property: "length", value: 0 })
118
- .rematerializeRuleMark({ id })
119
- .rematerializeMarkHighlights({ target: id, highlights });
120
- }
120
+ const highlighted = rematerializeHighlightBaseline(this, {
121
+ target: id,
122
+ operation: "rematerializeRuleMark",
123
+ resetProperty: "length",
124
+ resetValue: 0
125
+ });
126
+ if (highlighted !== undefined) return highlighted;
121
127
  const { dataset, graphic, layer } = requireRule(this, id);
122
128
  validateEndpointBindings(layer);
123
129
  const config = this.markConfigs[id] ?? DEFAULT_RULE_CONFIG;
@@ -0,0 +1 @@
1
+ export { registerRuleMarkActions } from "./actions.js";