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,369 @@
1
+ import { action } from "../../core/action.js";
2
+ import { validateUserId } from "../../core/identifiers.js";
3
+ import {
4
+ validateNonEmptyString,
5
+ validateNonNegativeFinite,
6
+ validateUnitInterval
7
+ } from "../../core/validation.js";
8
+ import {
9
+ deriveAreaSeries,
10
+ deriveDensityAreaSeries,
11
+ layoutDensityAreaSeries
12
+ } from "../../grammar/areaSeries.js";
13
+ import { mapContinuousScaleValues, mapOrdinalValues } from "../../grammar/scales.js";
14
+ import {
15
+ assertMarkAvailable,
16
+ resolveMarkId,
17
+ resolveMarkData,
18
+ validateMarkOptions
19
+ } from "./shared.js";
20
+ import { DEFAULT_COLORS } from "../../theme/defaults.js";
21
+ import { findDataset } from "../../selectors/datasets.js";
22
+ import { findLayer } from "../../selectors/layers.js";
23
+ import {
24
+ buildAreaCurvePathCommands,
25
+ validateCurveInterpolation
26
+ } from "../../grammar/curveCommands.js";
27
+ import { buildLinearPathCommands } from "../../grammar/pathCommands.js";
28
+ import { resolveEligibleLayer } from "../../selectors/layers.js";
29
+ import { canMaterializeArea } from "../../materialization/marks.js";
30
+ import { findUpstreamTransform } from "../../materialization/dataProvenance.js";
31
+ import { resolveMarkGraphicPlacement } from
32
+ "../../materialization/graphicHierarchy.js";
33
+
34
+ const CREATE_OPTIONS = Object.freeze([
35
+ "id", "data", "fill", "opacity", "stroke", "strokeWidth", "curve"
36
+ ]);
37
+ const EDIT_OPTIONS = Object.freeze([
38
+ "target", "fill", "opacity", "stroke", "strokeWidth", "curve"
39
+ ]);
40
+ const REMATERIALIZE_OPTIONS = Object.freeze(["id"]);
41
+
42
+ export function validateAreaCreateOutline(args, operation = "createAreaMark") {
43
+ if (Object.hasOwn(args, "strokeWidth") && !Object.hasOwn(args, "stroke")) {
44
+ throw new Error(`${operation} strokeWidth requires stroke.`);
45
+ }
46
+ const stroke = Object.hasOwn(args, "stroke")
47
+ ? validateNonEmptyString(args.stroke, "Area stroke")
48
+ : undefined;
49
+ return {
50
+ stroke,
51
+ strokeWidth: stroke === undefined
52
+ ? undefined
53
+ : validateNonNegativeFinite(args.strokeWidth ?? 1, "Area strokeWidth")
54
+ };
55
+ }
56
+
57
+ const createAreaMark = action(
58
+ {
59
+ op: "createAreaMark",
60
+ description: "Create a semantic area mark and empty path collection."
61
+ },
62
+ function (args = {}) {
63
+ validateMarkOptions(args, CREATE_OPTIONS, "createAreaMark");
64
+ const id = resolveMarkId(this, args.id, {
65
+ defaultId: "area",
66
+ label: "Area mark id",
67
+ markType: "area",
68
+ operation: "createAreaMark"
69
+ });
70
+ const { data } = resolveMarkData(this, args);
71
+ const fill = validateNonEmptyString(args.fill ?? DEFAULT_COLORS.mark, "Area fill");
72
+ const opacity = validateUnitInterval(args.opacity ?? 0.2, "Area opacity");
73
+ const curve = validateCurveInterpolation(args.curve ?? "linear");
74
+ const { stroke, strokeWidth } = validateAreaCreateOutline(args);
75
+ assertMarkAvailable(this, id);
76
+ return this
77
+ .editSemantic({ property: `layer[${id}].mark.type`, value: "area" })
78
+ .editSemantic({ property: `layer[${id}].data`, value: data })
79
+ .createGraphics({
80
+ id,
81
+ type: "path",
82
+ length: 0,
83
+ ...resolveMarkGraphicPlacement(this, { data, markType: "area" })
84
+ })
85
+ ._withMarkConfig(id, {
86
+ fill,
87
+ opacity,
88
+ ...(Object.hasOwn(args, "curve") ? { curve } : {}),
89
+ ...(stroke === undefined ? {} : { stroke, strokeWidth })
90
+ });
91
+ }
92
+ );
93
+
94
+ const rematerializeAreaMark = action(
95
+ {
96
+ op: "rematerializeAreaMark",
97
+ description: "Recompute grouped closed area paths."
98
+ },
99
+ function (args = {}) {
100
+ validateMarkOptions(args, REMATERIALIZE_OPTIONS, "rematerializeAreaMark");
101
+ const id = validateUserId(args.id, "Area mark id");
102
+ const highlights = Object.entries(
103
+ this.materializationConfigs.highlights ?? {}
104
+ ).filter(([, config]) => config.target === id);
105
+ if (highlights.length > 0) {
106
+ let baseline = this;
107
+ for (const [highlightId] of highlights) {
108
+ baseline = baseline._withoutMaterializationConfig(["highlights", highlightId]);
109
+ }
110
+ return baseline
111
+ .editGraphics({ target: id, property: "length", value: 0 })
112
+ .rematerializeAreaMark({ id })
113
+ .rematerializeMarkHighlights({ target: id, highlights });
114
+ }
115
+ const layer = findLayer(this, id);
116
+ const dataset = findDataset(this, layer?.data);
117
+ if (layer?.mark?.type !== "area" || this.graphicSpec.objects[id]?.type !== "path") {
118
+ throw new Error(`Unknown area mark "${id}".`);
119
+ }
120
+ if (dataset === undefined) {
121
+ throw new Error(`Area mark "${id}" requires an existing dataset.`);
122
+ }
123
+ const densityTransform = findUpstreamTransform(
124
+ this,
125
+ dataset,
126
+ "density"
127
+ );
128
+ const xScaleId = layer.encoding?.x?.scale;
129
+ const yScaleId = layer.encoding?.y?.scale;
130
+ const verticalRange = layer.encoding?.y2?.scale === yScaleId;
131
+ const horizontalRange = layer.encoding?.x2?.scale === xScaleId;
132
+ if (
133
+ xScaleId === undefined ||
134
+ yScaleId === undefined ||
135
+ (densityTransform === undefined && verticalRange === horizontalRange)
136
+ ) {
137
+ throw new Error(
138
+ densityTransform === undefined
139
+ ? `Area mark "${id}" requires exactly one shared x/x2 or y/y2 range.`
140
+ : `Density area mark "${id}" requires x and y scales.`
141
+ );
142
+ }
143
+ const rawDerived = densityTransform === undefined
144
+ ? deriveAreaSeries(dataset.values, layer)
145
+ : deriveDensityAreaSeries(dataset.values, layer, densityTransform);
146
+ const colorEncoding = layer.encoding?.color;
147
+ const layout = colorEncoding?.layout ?? "overlay";
148
+ const derived = densityTransform === undefined
149
+ ? rawDerived
150
+ : layoutDensityAreaSeries(rawDerived, layout);
151
+ let resolved = this
152
+ .rematerializeScale({ id: xScaleId })
153
+ .rematerializeScale({ id: yScaleId });
154
+ if (colorEncoding?.scale !== undefined) {
155
+ resolved = resolved.rematerializeScale({ id: colorEncoding.scale });
156
+ }
157
+ const xScale = resolved.resolvedScales[xScaleId];
158
+ const yScale = resolved.resolvedScales[yScaleId];
159
+ const densityScale = densityTransform === undefined
160
+ ? undefined
161
+ : derived.mode === "y-density" ? yScale : xScale;
162
+ if (
163
+ densityScale !== undefined &&
164
+ (densityScale.domain[0] > 0 || densityScale.domain[1] < 0)
165
+ ) {
166
+ throw new Error(`Density area mark "${id}" requires a scale domain containing zero.`);
167
+ }
168
+ const paths = derived.series.map(series => {
169
+ const curve = this.markConfigs[id]?.curve ?? "linear";
170
+ if (densityTransform === undefined && derived.orientation === "horizontal") {
171
+ const y = mapContinuousScaleValues(
172
+ series.values.map(value => value.y),
173
+ yScale
174
+ );
175
+ const lower = mapContinuousScaleValues(
176
+ series.values.map(value => value.x),
177
+ xScale
178
+ );
179
+ const upper = mapContinuousScaleValues(
180
+ series.values.map(value => value.x2),
181
+ xScale
182
+ );
183
+ return buildAreaCurvePathCommands(
184
+ y.map((value, index) => ({ x: lower[index], y: value })),
185
+ y.map((value, index) => ({ x: upper[index], y: value })),
186
+ curve,
187
+ { independentAxis: "y" }
188
+ );
189
+ }
190
+ if (densityTransform !== undefined && derived.mode === "x-density") {
191
+ const y = mapContinuousScaleValues(
192
+ series.values.map(value => value.y),
193
+ yScale
194
+ );
195
+ const upper = mapContinuousScaleValues(
196
+ series.values.map(value => value.x),
197
+ xScale
198
+ );
199
+ const baseline = mapContinuousScaleValues(
200
+ [0],
201
+ densityScale
202
+ )[0];
203
+ if (curve === "linear") {
204
+ return buildLinearPathCommands([
205
+ { x: baseline, y: y[0] },
206
+ ...y.map((value, index) => ({ x: upper[index], y: value })),
207
+ { x: baseline, y: y.at(-1) }
208
+ ], { close: true });
209
+ }
210
+ return buildAreaCurvePathCommands(
211
+ y.map(value => ({ x: baseline, y: value })),
212
+ y.map((value, index) => ({ x: upper[index], y: value })),
213
+ curve,
214
+ { independentAxis: "y" }
215
+ );
216
+ }
217
+ const x = mapContinuousScaleValues(
218
+ series.values.map(value => value.x),
219
+ xScale
220
+ );
221
+ const lowerValues = densityTransform === undefined
222
+ ? series.values.map(value => value.y)
223
+ : derived.mode === "y-density"
224
+ ? series.values.map(value => value.lower)
225
+ : series.values.map(value => value.y);
226
+ const lower = mapContinuousScaleValues(
227
+ lowerValues,
228
+ yScale
229
+ );
230
+ const upperValues = densityTransform === undefined
231
+ ? series.values.map(value => value.y2)
232
+ : series.values.map(value => value.upper);
233
+ const upper = mapContinuousScaleValues(
234
+ upperValues,
235
+ yScale
236
+ );
237
+ if (densityTransform !== undefined && layout === "overlay") {
238
+ const baseline = mapContinuousScaleValues(
239
+ [0],
240
+ yScale
241
+ )[0];
242
+ if (curve === "linear") {
243
+ return buildLinearPathCommands([
244
+ { x: x[0], y: baseline },
245
+ ...x.map((value, index) => ({ x: value, y: upper[index] })),
246
+ { x: x.at(-1), y: baseline }
247
+ ], { close: true });
248
+ }
249
+ return buildAreaCurvePathCommands(
250
+ x.map(value => ({ x: value, y: baseline })),
251
+ x.map((value, index) => ({ x: value, y: upper[index] })),
252
+ curve
253
+ );
254
+ }
255
+ return buildAreaCurvePathCommands(
256
+ x.map((value, index) => ({ x: value, y: lower[index] })),
257
+ x.map((value, index) => ({ x: value, y: upper[index] })),
258
+ curve
259
+ );
260
+ });
261
+ const config = this.markConfigs[id];
262
+ const fills = colorEncoding?.scale === undefined
263
+ ? paths.map(() => config.fill)
264
+ : mapOrdinalValues(
265
+ derived.series.map(series => series.key[colorEncoding.field]),
266
+ resolved.resolvedScales[colorEncoding.scale].domain,
267
+ resolved.resolvedScales[colorEncoding.scale].range
268
+ );
269
+ const existingChildren = resolved.graphicSpec.objects[id].items ?? [];
270
+ const removesOutline = config.stroke === undefined && existingChildren.some(
271
+ child => child.properties.stroke !== undefined
272
+ );
273
+ let next = removesOutline
274
+ ? resolved
275
+ .editGraphics({ target: id, property: "length", value: 0 })
276
+ .editGraphics({ target: id, property: "length", value: paths.length })
277
+ : resolved.editGraphics({ target: id, property: "length", value: paths.length });
278
+ next = next
279
+ .editGraphics({ target: id, property: "commands", value: paths })
280
+ .editGraphics({ target: id, property: "fill", value: fills })
281
+ .editGraphics({ target: id, property: "opacity", value: config.opacity });
282
+ if (config.stroke !== undefined) {
283
+ next = next
284
+ .editGraphics({ target: id, property: "stroke", value: config.stroke })
285
+ .editGraphics({
286
+ target: id,
287
+ property: "strokeWidth",
288
+ value: config.strokeWidth
289
+ });
290
+ }
291
+ return next;
292
+ }
293
+ );
294
+
295
+ const editAreaMark = action(
296
+ {
297
+ op: "editAreaMark",
298
+ description: "Edit area curve and constant appearance."
299
+ },
300
+ function (args = {}) {
301
+ validateMarkOptions(args, EDIT_OPTIONS, "editAreaMark");
302
+ const changes = ["fill", "opacity", "stroke", "strokeWidth", "curve"];
303
+ if (!changes.some(key => Object.hasOwn(args, key))) {
304
+ throw new Error(
305
+ "editAreaMark requires fill, opacity, stroke, strokeWidth, or curve."
306
+ );
307
+ }
308
+ const target = Object.hasOwn(args, "target")
309
+ ? validateUserId(args.target, "Area mark id")
310
+ : undefined;
311
+ const layer = resolveEligibleLayer(this, {
312
+ target,
313
+ predicate: candidate => candidate.mark?.type === "area",
314
+ label: "area mark"
315
+ });
316
+ if (Object.hasOwn(args, "fill") && layer.encoding?.color !== undefined) {
317
+ throw new Error(
318
+ "editAreaMark fill cannot be combined with a color encoding."
319
+ );
320
+ }
321
+ if (args.stroke === false && Object.hasOwn(args, "strokeWidth")) {
322
+ throw new Error("editAreaMark cannot set strokeWidth while removing stroke.");
323
+ }
324
+
325
+ let config = { ...this.markConfigs[layer.id] };
326
+ if (Object.hasOwn(args, "fill")) {
327
+ config.fill = validateNonEmptyString(args.fill, "Area fill");
328
+ }
329
+ if (Object.hasOwn(args, "opacity")) {
330
+ config.opacity = validateUnitInterval(args.opacity, "Area opacity");
331
+ }
332
+ if (Object.hasOwn(args, "curve")) {
333
+ config.curve = validateCurveInterpolation(args.curve);
334
+ }
335
+ if (Object.hasOwn(args, "stroke")) {
336
+ if (args.stroke === false) {
337
+ const { stroke: removedStroke, strokeWidth: removedWidth, ...rest } = config;
338
+ void removedStroke;
339
+ void removedWidth;
340
+ config = rest;
341
+ } else {
342
+ const hadStroke = config.stroke !== undefined;
343
+ config.stroke = validateNonEmptyString(args.stroke, "Area stroke");
344
+ config.strokeWidth = Object.hasOwn(args, "strokeWidth")
345
+ ? validateNonNegativeFinite(args.strokeWidth, "Area strokeWidth")
346
+ : hadStroke ? config.strokeWidth : 1;
347
+ }
348
+ } else if (Object.hasOwn(args, "strokeWidth")) {
349
+ if (config.stroke === undefined) {
350
+ throw new Error("editAreaMark strokeWidth requires an active stroke.");
351
+ }
352
+ config.strokeWidth = validateNonNegativeFinite(
353
+ args.strokeWidth,
354
+ "Area strokeWidth"
355
+ );
356
+ }
357
+
358
+ const next = this._withMarkConfig(layer.id, config);
359
+ return canMaterializeArea(next, layer)
360
+ ? next.rematerializeAreaMark({ id: layer.id })
361
+ : next;
362
+ }
363
+ );
364
+
365
+ export function registerAreaMarkActions(ProgramClass) {
366
+ ProgramClass.prototype.createAreaMark = createAreaMark;
367
+ ProgramClass.prototype.editAreaMark = editAreaMark;
368
+ ProgramClass.prototype.rematerializeAreaMark = rematerializeAreaMark;
369
+ }
@@ -0,0 +1,46 @@
1
+ import { action } from "../../../core/action.js";
2
+ import {
3
+ assertMarkAvailable,
4
+ resolveMarkId,
5
+ resolveMarkData,
6
+ validateMarkOptions
7
+ } from "../shared.js";
8
+ import { resolveMarkGraphicPlacement } from
9
+ "../../../materialization/graphicHierarchy.js";
10
+
11
+ const CREATE_OPTIONS = Object.freeze(["id", "data"]);
12
+
13
+ export const createBarMark = action(
14
+ {
15
+ op: "createBarMark",
16
+ description: "Create a semantic bar mark and empty rect collection."
17
+ },
18
+ function (args = {}) {
19
+ validateMarkOptions(args, CREATE_OPTIONS, "createBarMark");
20
+ const id = resolveMarkId(this, args.id, {
21
+ defaultId: "bar",
22
+ label: "Bar mark id",
23
+ markType: "bar",
24
+ operation: "createBarMark"
25
+ });
26
+ const { data } = resolveMarkData(this, args);
27
+
28
+ assertMarkAvailable(this, id);
29
+
30
+ return this
31
+ .editSemantic({
32
+ property: `layer[${id}].mark.type`,
33
+ value: "bar"
34
+ })
35
+ .editSemantic({
36
+ property: `layer[${id}].data`,
37
+ value: data
38
+ })
39
+ .createGraphics({
40
+ id,
41
+ type: "rect",
42
+ length: 0,
43
+ ...resolveMarkGraphicPlacement(this, { data, markType: "bar" })
44
+ });
45
+ }
46
+ );
@@ -0,0 +1,90 @@
1
+ import { action } from "../../../core/action.js";
2
+ import { validateUserId } from "../../../core/identifiers.js";
3
+ import {
4
+ validateNonEmptyString,
5
+ validateNonNegativeFinite,
6
+ validateUnitInterval
7
+ } from "../../../core/validation.js";
8
+ import { canMaterializeBar } from "../../../materialization/marks.js";
9
+ import { DEFAULT_BAR_STROKE_WIDTH } from "../../../materialization/bars/resolve.js";
10
+ import { resolveEligibleLayer } from "../../../selectors/layers.js";
11
+ import { validateMarkOptions } from "../shared.js";
12
+
13
+ const EDIT_OPTIONS = Object.freeze([
14
+ "target", "fill", "opacity", "stroke", "strokeWidth"
15
+ ]);
16
+
17
+ export const editBarMark = action(
18
+ {
19
+ op: "editBarMark",
20
+ description: "Edit whole-bar fill, opacity, and outline appearance."
21
+ },
22
+ function (args = {}) {
23
+ validateMarkOptions(args, EDIT_OPTIONS, "editBarMark");
24
+ const changes = ["fill", "opacity", "stroke", "strokeWidth"];
25
+ if (!changes.some(key => Object.hasOwn(args, key))) {
26
+ throw new Error(
27
+ "editBarMark requires fill, opacity, stroke, or strokeWidth."
28
+ );
29
+ }
30
+ const requested = Object.hasOwn(args, "target")
31
+ ? validateUserId(args.target, "Bar mark id")
32
+ : undefined;
33
+ const layer = resolveEligibleLayer(this, {
34
+ target: requested,
35
+ predicate: candidate => candidate.mark?.type === "bar",
36
+ label: "bar mark"
37
+ });
38
+ if (Object.hasOwn(args, "fill") && layer.encoding?.color !== undefined) {
39
+ throw new Error(
40
+ "editBarMark fill cannot be combined with a color encoding."
41
+ );
42
+ }
43
+ if (args.stroke === false && Object.hasOwn(args, "strokeWidth")) {
44
+ throw new Error(
45
+ "editBarMark cannot set strokeWidth while removing stroke."
46
+ );
47
+ }
48
+
49
+ const config = { ...this.markConfigs[layer.id] };
50
+ const appearance = { ...config.barAppearance };
51
+ if (Object.hasOwn(args, "fill")) {
52
+ appearance.fill = validateNonEmptyString(args.fill, "Bar fill");
53
+ }
54
+ if (Object.hasOwn(args, "opacity")) {
55
+ appearance.opacity = validateUnitInterval(args.opacity, "Bar opacity");
56
+ }
57
+ if (Object.hasOwn(args, "stroke")) {
58
+ if (args.stroke === false) {
59
+ appearance.stroke = false;
60
+ } else {
61
+ const restoresStroke = appearance.stroke === false;
62
+ appearance.stroke = validateNonEmptyString(args.stroke, "Bar stroke");
63
+ if (Object.hasOwn(args, "strokeWidth")) {
64
+ appearance.strokeWidth = validateNonNegativeFinite(
65
+ args.strokeWidth,
66
+ "Bar strokeWidth"
67
+ );
68
+ } else if (restoresStroke) {
69
+ appearance.strokeWidth = DEFAULT_BAR_STROKE_WIDTH;
70
+ }
71
+ }
72
+ } else if (Object.hasOwn(args, "strokeWidth")) {
73
+ if (appearance.stroke === false) {
74
+ throw new Error("editBarMark strokeWidth requires an active stroke.");
75
+ }
76
+ appearance.strokeWidth = validateNonNegativeFinite(
77
+ args.strokeWidth,
78
+ "Bar strokeWidth"
79
+ );
80
+ }
81
+
82
+ const next = this._withMarkConfig(layer.id, {
83
+ ...config,
84
+ barAppearance: appearance
85
+ });
86
+ return canMaterializeBar(next, layer)
87
+ ? next.rematerializeBarMark({ id: layer.id })
88
+ : next;
89
+ }
90
+ );
@@ -0,0 +1,9 @@
1
+ import { createBarMark } from "./create.js";
2
+ import { editBarMark } from "./edit.js";
3
+ import { rematerializeBarMark } from "./materialize.js";
4
+
5
+ export function registerBarMarkActions(ProgramClass) {
6
+ ProgramClass.prototype.createBarMark = createBarMark;
7
+ ProgramClass.prototype.editBarMark = editBarMark;
8
+ ProgramClass.prototype.rematerializeBarMark = rematerializeBarMark;
9
+ }
@@ -0,0 +1,130 @@
1
+ import { action } from "../../../core/action.js";
2
+ import { validateUserId } from "../../../core/identifiers.js";
3
+ import { validateMarkOptions } from "../shared.js";
4
+ import { deriveAggregateRectangles } from "../../../materialization/bars/aggregate.js";
5
+ import { deriveHistogramRectangles } from "../../../materialization/bars/histogram.js";
6
+ import { requireCompleteBar } from "../../../materialization/bars/resolve.js";
7
+ import { deriveRangedRectangles } from "../../../materialization/bars/ranged.js";
8
+ import { BAR_GRAINS } from "../../../grammar/bars/policy.js";
9
+
10
+ const REMATERIALIZE_OPTIONS = Object.freeze(["id"]);
11
+
12
+ function editRectangles(program, id, rectangles) {
13
+ let next = program
14
+ .editGraphics({ target: id, property: "length", value: rectangles.length })
15
+ .editGraphics({
16
+ target: id,
17
+ property: "x",
18
+ value: rectangles.map(rect => rect.x)
19
+ })
20
+ .editGraphics({
21
+ target: id,
22
+ property: "y",
23
+ value: rectangles.map(rect => rect.y)
24
+ })
25
+ .editGraphics({
26
+ target: id,
27
+ property: "width",
28
+ value: rectangles.map(rect => rect.width)
29
+ })
30
+ .editGraphics({
31
+ target: id,
32
+ property: "height",
33
+ value: rectangles.map(rect => rect.height)
34
+ })
35
+ .editGraphics({
36
+ target: id,
37
+ property: "fill",
38
+ value: rectangles.map(rect => rect.fill)
39
+ })
40
+ .editGraphics({
41
+ target: id,
42
+ property: "stroke",
43
+ value: rectangles.map(rect => rect.stroke)
44
+ })
45
+ .editGraphics({
46
+ target: id,
47
+ property: "strokeWidth",
48
+ value: rectangles.map(rect => rect.strokeWidth)
49
+ });
50
+ if (rectangles.some(rect => rect.opacity !== undefined)) {
51
+ next = next.editGraphics({
52
+ target: id,
53
+ property: "opacity",
54
+ value: rectangles.map(rect => rect.opacity ?? 1)
55
+ });
56
+ }
57
+ return next;
58
+ }
59
+
60
+ export const rematerializeBarMark = action(
61
+ {
62
+ op: "rematerializeBarMark",
63
+ description: "Recompute concrete bar graphics from complete semantics."
64
+ },
65
+ function (args = {}) {
66
+ validateMarkOptions(
67
+ args,
68
+ REMATERIALIZE_OPTIONS,
69
+ "rematerializeBarMark"
70
+ );
71
+ const id = validateUserId(args.id, "Bar mark id");
72
+ const highlights = Object.entries(
73
+ this.materializationConfigs.highlights ?? {}
74
+ ).filter(([, config]) => config.target === id);
75
+ if (highlights.length > 0) {
76
+ let baseline = this;
77
+ for (const [highlightId] of highlights) {
78
+ baseline = baseline._withoutMaterializationConfig([
79
+ "highlights",
80
+ highlightId
81
+ ]);
82
+ }
83
+ return baseline
84
+ .editGraphics({ target: id, property: "length", value: 0 })
85
+ .rematerializeBarMark({ id })
86
+ .rematerializeMarkHighlights({ target: id, highlights });
87
+ }
88
+ const required = requireCompleteBar(this, id);
89
+ let resolved = this
90
+ .rematerializeScale({ id: required.xEncoding.scale })
91
+ .rematerializeScale({ id: required.yEncoding.scale });
92
+
93
+ const colorScaleId = required.layer.encoding?.color?.scale;
94
+ if (colorScaleId !== undefined) {
95
+ resolved = resolved.rematerializeScale({ id: colorScaleId });
96
+ }
97
+
98
+ if (required.materialization === "aggregate") {
99
+ const offsetScaleId = required.layer.encoding?.xOffset?.scale;
100
+ if (offsetScaleId !== undefined) {
101
+ resolved = resolved.rematerializeScale({ id: offsetScaleId });
102
+ }
103
+ const width = resolved.markConfigs[id]?.barWidth;
104
+ if (width === undefined) {
105
+ return resolved.editGraphics({
106
+ target: id,
107
+ property: "length",
108
+ value: 0
109
+ });
110
+ }
111
+ return editRectangles(
112
+ resolved,
113
+ id,
114
+ deriveAggregateRectangles(required, resolved, width)
115
+ );
116
+ }
117
+
118
+ if (required.materialization === BAR_GRAINS.ranged) {
119
+ const width = resolved.markConfigs[id]?.barWidth;
120
+ if (width === undefined) return resolved.editGraphics({ target: id, property: "length", value: 0 });
121
+ return editRectangles(resolved, id, deriveRangedRectangles(required, resolved, width));
122
+ }
123
+
124
+ return editRectangles(
125
+ resolved,
126
+ id,
127
+ deriveHistogramRectangles(required, resolved)
128
+ );
129
+ }
130
+ );
@@ -0,0 +1,13 @@
1
+ import { registerBarMarkActions } from "./bar/index.js";
2
+ import { registerAreaMarkActions } from "./area.js";
3
+ import { registerLineMarkActions } from "./line.js";
4
+ import { registerPointMarkActions } from "./point.js";
5
+ import { registerRuleMarkActions } from "./rule.js";
6
+
7
+ export function registerMarkActions(ProgramClass) {
8
+ registerPointMarkActions(ProgramClass);
9
+ registerAreaMarkActions(ProgramClass);
10
+ registerLineMarkActions(ProgramClass);
11
+ registerBarMarkActions(ProgramClass);
12
+ registerRuleMarkActions(ProgramClass);
13
+ }