ggaction 0.0.4 → 0.0.6

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 (169) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +35 -13
  3. package/package.json +18 -7
  4. package/src/actions/boxPlots/create.js +13 -7
  5. package/src/actions/boxPlots/edit.js +484 -36
  6. package/src/actions/boxPlots/materialize.js +3 -0
  7. package/src/actions/boxPlots/options.js +11 -4
  8. package/src/actions/boxPlots/resolve.js +10 -2
  9. package/src/actions/charts/bar.js +50 -0
  10. package/src/actions/charts/heatmap.js +205 -0
  11. package/src/actions/charts/histogram.js +58 -0
  12. package/src/actions/charts/index.js +15 -0
  13. package/src/actions/charts/line.js +69 -0
  14. package/src/actions/charts/parallel.js +76 -0
  15. package/src/actions/charts/scatter.js +56 -0
  16. package/src/actions/charts/shared.js +121 -0
  17. package/src/actions/composition/actions.js +13 -3
  18. package/src/actions/coordinates/parallel.js +50 -0
  19. package/src/actions/data/bin2d.js +260 -0
  20. package/src/actions/data/density.js +54 -13
  21. package/src/actions/data/derived.js +22 -0
  22. package/src/actions/data/filter.js +5 -0
  23. package/src/actions/data/gradientProfile.js +65 -0
  24. package/src/actions/data/horizon.js +71 -0
  25. package/src/actions/data/index.js +34 -2
  26. package/src/actions/data/intervalEdit.js +76 -0
  27. package/src/actions/data/window.js +50 -0
  28. package/src/actions/distributions/revision.js +326 -0
  29. package/src/actions/encodings/appearance.js +35 -1
  30. package/src/actions/encodings/color/index.js +17 -1
  31. package/src/actions/encodings/color/layout.js +8 -0
  32. package/src/actions/encodings/density/resolve.js +109 -0
  33. package/src/actions/encodings/density.js +330 -90
  34. package/src/actions/encodings/horizon/resolve.js +283 -0
  35. package/src/actions/encodings/horizon.js +364 -0
  36. package/src/actions/encodings/index.js +8 -0
  37. package/src/actions/encodings/parallel.js +115 -0
  38. package/src/actions/encodings/pathOrder.js +131 -0
  39. package/src/actions/encodings/position/apply.js +73 -4
  40. package/src/actions/encodings/position/index.js +15 -2
  41. package/src/actions/encodings/position/policies/arc.js +27 -4
  42. package/src/actions/encodings/position/policies/area.js +33 -2
  43. package/src/actions/encodings/position/policies/index.js +7 -2
  44. package/src/actions/encodings/position/policies/line.js +18 -2
  45. package/src/actions/encodings/position/resolve.js +24 -2
  46. package/src/actions/encodings/ranged.js +3 -1
  47. package/src/actions/encodings/remove.js +268 -0
  48. package/src/actions/encodings/ruleAppearance.js +88 -10
  49. package/src/actions/errorBands/edit.js +121 -13
  50. package/src/actions/errorBars/edit.js +45 -8
  51. package/src/actions/errorBars/options.js +7 -1
  52. package/src/actions/facets/actions.js +137 -3
  53. package/src/actions/facets/derive.js +33 -3
  54. package/src/actions/facets/replay.js +7 -36
  55. package/src/actions/gradientPlots/components.js +177 -0
  56. package/src/actions/gradientPlots/create.js +96 -0
  57. package/src/actions/gradientPlots/edit.js +501 -0
  58. package/src/actions/gradientPlots/index.js +24 -0
  59. package/src/actions/gradientPlots/materialize.js +287 -0
  60. package/src/actions/gradientPlots/options.js +120 -0
  61. package/src/actions/gradientPlots/paint.js +107 -0
  62. package/src/actions/gradientPlots/rebind.js +51 -0
  63. package/src/actions/gradientPlots/resolve.js +81 -0
  64. package/src/actions/guides/applicability.js +44 -17
  65. package/src/actions/guides/axes/axes.js +37 -3
  66. package/src/actions/guides/axes/edit.js +129 -47
  67. package/src/actions/guides/axes/index.js +2 -0
  68. package/src/actions/guides/axes/labels.js +11 -6
  69. package/src/actions/guides/axes/parallel/resolve.js +86 -0
  70. package/src/actions/guides/axes/parallel.js +228 -0
  71. package/src/actions/guides/axes/titles.js +11 -8
  72. package/src/actions/guides/guides.js +60 -5
  73. package/src/actions/guides/legends/categorical/actions.js +67 -19
  74. package/src/actions/guides/legends/continuous/common.js +3 -3
  75. package/src/actions/guides/legends/edit.js +63 -0
  76. package/src/actions/guides/legends/index.js +2 -0
  77. package/src/actions/guides/legends/remove.js +124 -30
  78. package/src/actions/guides/legends/strokeWidth.js +198 -0
  79. package/src/actions/index.js +6 -0
  80. package/src/actions/marks/arc/actions.js +46 -14
  81. package/src/actions/marks/area/actions.js +60 -136
  82. package/src/actions/marks/area/materialize.js +163 -0
  83. package/src/actions/marks/index.js +5 -1
  84. package/src/actions/marks/line/actions.js +99 -80
  85. package/src/actions/marks/line/materialize.js +146 -0
  86. package/src/actions/marks/point/create.js +76 -0
  87. package/src/actions/marks/point/edit.js +97 -0
  88. package/src/actions/marks/point/index.js +11 -1
  89. package/src/actions/marks/point/jitter.js +72 -0
  90. package/src/actions/marks/point/{actions.js → materialize.js} +183 -172
  91. package/src/actions/marks/rect/actions.js +3 -0
  92. package/src/actions/marks/remove.js +13 -2
  93. package/src/actions/marks/rule/actions.js +29 -4
  94. package/src/actions/marks/text/actions.js +19 -4
  95. package/src/actions/marks/text/index.js +7 -1
  96. package/src/actions/marks/text/layout.js +321 -0
  97. package/src/actions/primitives/semanticValidation/dataset.js +22 -0
  98. package/src/actions/primitives/semanticValidation/guide.js +43 -0
  99. package/src/actions/primitives/semanticValidation/index.js +23 -0
  100. package/src/actions/primitives/semanticValidation/layer.js +74 -0
  101. package/src/actions/primitives/semanticValidation/scale.js +74 -0
  102. package/src/actions/primitives/semanticValidation/shared.js +5 -0
  103. package/src/actions/primitives/semanticValue.js +1 -204
  104. package/src/actions/regression/edit.js +247 -88
  105. package/src/actions/scales/consumers/common.js +18 -0
  106. package/src/actions/scales/consumers/families.js +14 -3
  107. package/src/actions/scales/definitions.js +30 -0
  108. package/src/actions/scales/edit.js +6 -204
  109. package/src/actions/scales/editPolicy.js +214 -0
  110. package/src/actions/scales/materialize.js +1 -1
  111. package/src/actions/scales/patch.js +26 -0
  112. package/src/actions/selection/actions.js +150 -5
  113. package/src/actions/selection/index.js +6 -0
  114. package/src/actions/violinPlots/create.js +198 -0
  115. package/src/actions/violinPlots/index.js +5 -0
  116. package/src/core/textMetrics.js +47 -0
  117. package/src/core/vocabulary.js +14 -2
  118. package/src/grammar/arcs.js +49 -12
  119. package/src/grammar/areaSeries.js +41 -5
  120. package/src/grammar/bin2d.js +333 -0
  121. package/src/grammar/categoricalDensity.js +192 -0
  122. package/src/grammar/coordinates.js +1 -1
  123. package/src/grammar/curveCommands.js +3 -1
  124. package/src/grammar/density.js +252 -26
  125. package/src/grammar/facets/dependencies.js +3 -11
  126. package/src/grammar/facets/index.js +3 -1
  127. package/src/grammar/gradientProfile.js +213 -0
  128. package/src/grammar/horizon.js +455 -0
  129. package/src/grammar/jitter.js +197 -0
  130. package/src/grammar/lineSeries.js +69 -0
  131. package/src/grammar/paint.js +88 -0
  132. package/src/grammar/parallelCoordinates.js +213 -0
  133. package/src/grammar/pathOrder.js +35 -0
  134. package/src/grammar/pointShapes.js +24 -0
  135. package/src/grammar/rules.js +1 -0
  136. package/src/grammar/scales/appearance.js +21 -0
  137. package/src/grammar/schemas/concreteGraphic.js +11 -1
  138. package/src/grammar/schemas/graphicBounds.js +19 -37
  139. package/src/grammar/schemas/semanticPath.js +17 -2
  140. package/src/grammar/transforms.js +100 -11
  141. package/src/grammar/window.js +339 -0
  142. package/src/layout/labels.js +270 -0
  143. package/src/layout/text.js +4 -1
  144. package/src/layout/title.js +10 -20
  145. package/src/materialization/dataProvenance.js +56 -2
  146. package/src/materialization/dependencies.js +9 -14
  147. package/src/materialization/guides/resources.js +74 -24
  148. package/src/materialization/horizon.js +29 -0
  149. package/src/materialization/layout.js +15 -0
  150. package/src/materialization/marks/capabilities.js +21 -1
  151. package/src/materialization/marks/index.js +3 -0
  152. package/src/materialization/marks/pathOrder.js +71 -0
  153. package/src/materialization/marks/policies.js +6 -2
  154. package/src/materialization/scaleGuideDependencies.js +8 -0
  155. package/src/materialization/scales/resolve.js +2 -0
  156. package/src/materialization/selection/items/path.js +27 -1
  157. package/src/materialization/selection/items/point.js +1 -5
  158. package/src/materialization/selection/items/rect.js +21 -0
  159. package/src/materialization/selection/styles.js +20 -9
  160. package/src/materialization/text.js +12 -4
  161. package/src/renderers/canvas/fill.js +33 -0
  162. package/src/renderers/canvas/index.js +7 -0
  163. package/src/renderers/canvas/path.js +7 -4
  164. package/src/renderers/canvas/rect.js +9 -3
  165. package/src/selectors/datasets.js +4 -0
  166. package/src/theme/defaults.js +4 -0
  167. package/types/extension.d.ts +6 -0
  168. package/types/index.d.ts +77 -0
  169. package/types/program.d.ts +703 -17
@@ -1,6 +1,4 @@
1
1
  import { action } from "../../../core/action.js";
2
- import { deriveLineSeries } from "../../../grammar/lineSeries.js";
3
- import { mapContinuousScaleValues, mapOrdinalValues } from "../../../grammar/scales/index.js";
4
2
  import { validateUserId } from "../../../core/identifiers.js";
5
3
  import {
6
4
  validateNonEmptyString,
@@ -19,18 +17,17 @@ import {
19
17
  import { DEFAULT_COLORS } from "../../../theme/defaults.js";
20
18
  import { findDataset } from "../../../selectors/datasets.js";
21
19
  import { findLayer, resolveEligibleLayer } from "../../../selectors/layers.js";
22
- import {
23
- buildCurvePathCommands,
24
- validateCurveInterpolation
25
- } from "../../../grammar/curveCommands.js";
26
- import { buildPolarLinePathCommands } from "../../../grammar/polarLineCommands.js";
27
- import { resolvePolarFrame } from "../../../grammar/polar.js";
20
+ import { validateCurveInterpolation } from "../../../grammar/curveCommands.js";
28
21
  import { resolveGraphicBounds } from "../../../layout/canvas.js";
29
- import { normalizeStrokeDashPattern } from "../../../grammar/scales/index.js";
30
22
  import { canMaterializeLine } from "../../../materialization/marks/index.js";
31
23
  import { resolveMarkGraphicPlacement } from
32
24
  "../../../materialization/graphicHierarchy.js";
33
25
  import { rematerializeHighlightBaseline } from "../lifecycle.js";
26
+ import { validateParallelRows } from "../../../grammar/parallelCoordinates.js";
27
+ import {
28
+ resolveParallelLineMaterialization,
29
+ resolvePositionedLineMaterialization
30
+ } from "./materialize.js";
34
31
 
35
32
  const DEFAULT_LINE_STROKE = DEFAULT_COLORS.mark;
36
33
  const DEFAULT_LINE_WIDTH = 2;
@@ -54,7 +51,17 @@ function isPolarLine(layer) {
54
51
  layer.encoding?.radius !== undefined;
55
52
  }
56
53
 
54
+ function isParallelLine(layer) {
55
+ return layer.encoding?.parallel !== undefined;
56
+ }
57
+
57
58
  function validatePolarLineConfig(layer, config) {
59
+ if (isParallelLine(layer)) {
60
+ if ((config.curve ?? "linear") !== "linear" || config.closed === true) {
61
+ throw new Error("Parallel lines require curve \"linear\" and closed false.");
62
+ }
63
+ return;
64
+ }
58
65
  if (!isPolarLine(layer)) return;
59
66
  if ((config.curve ?? "linear") !== "linear") {
60
67
  throw new Error("Polar line position currently requires curve \"linear\".");
@@ -84,9 +91,6 @@ const createLineMark = action(
84
91
  args.strokeWidth ?? DEFAULT_LINE_WIDTH,
85
92
  "Line strokeWidth"
86
93
  );
87
- if (args.scales !== undefined && typeof args.scales !== "boolean") {
88
- throw new TypeError("rematerializeLineMark scales must be a boolean.");
89
- }
90
94
  const curve = validateCurveInterpolation(args.curve ?? "linear");
91
95
  const closed = Object.hasOwn(args, "closed")
92
96
  ? validateClosed(args.closed)
@@ -176,6 +180,7 @@ const rematerializeLineMark = action(
176
180
  if (highlighted !== undefined) return highlighted;
177
181
  const { dataset, layer } = requireLine(this, id);
178
182
  const existingChildren = this.graphicSpec.objects[id].items;
183
+ const parallel = layer.encoding?.parallel;
179
184
  const xScaleId = layer.encoding?.x?.scale;
180
185
  const yScaleId = layer.encoding?.y?.scale;
181
186
  const thetaScaleId = layer.encoding?.theta?.scale;
@@ -184,6 +189,64 @@ const rematerializeLineMark = action(
184
189
  const config = this.markConfigs[id] ?? {};
185
190
  validatePolarLineConfig(layer, config);
186
191
 
192
+ if (parallel !== undefined) {
193
+ validateParallelRows(dataset.values, parallel.dimensions, parallel);
194
+ let resolved = this;
195
+ if (args.scales !== false) {
196
+ for (const dimension of parallel.dimensions) {
197
+ resolved = resolved.rematerializeScale({ id: dimension.scale });
198
+ }
199
+ }
200
+ for (const channel of args.scales === false
201
+ ? []
202
+ : ["color", "strokeDash", "strokeWidth"]) {
203
+ const scaleId = layer.encoding?.[channel]?.scale;
204
+ if (scaleId !== undefined) {
205
+ resolved = resolved.rematerializeScale({ id: scaleId });
206
+ }
207
+ }
208
+ const materialization = resolveParallelLineMaterialization({
209
+ rows: dataset.values,
210
+ parallel,
211
+ layer,
212
+ resolvedScales: resolved.resolvedScales,
213
+ bounds: resolveGraphicBounds(resolved),
214
+ config,
215
+ existingChildren,
216
+ defaults: { stroke: DEFAULT_LINE_STROKE, strokeWidth: DEFAULT_LINE_WIDTH }
217
+ });
218
+ let next = resolved
219
+ .editGraphics({
220
+ target: id,
221
+ property: "length",
222
+ value: materialization.commands.length
223
+ })
224
+ .editGraphics({
225
+ target: id,
226
+ property: "commands",
227
+ value: materialization.commands
228
+ })
229
+ .editGraphics({ target: id, property: "stroke", value: materialization.strokes })
230
+ .editGraphics({
231
+ target: id,
232
+ property: "strokeWidth",
233
+ value: materialization.strokeWidths
234
+ })
235
+ .editGraphics({
236
+ target: id,
237
+ property: "strokeDash",
238
+ value: materialization.strokeDashes
239
+ });
240
+ if (config.opacity !== undefined) {
241
+ next = next.editGraphics({
242
+ target: id,
243
+ property: "opacity",
244
+ value: config.opacity
245
+ });
246
+ }
247
+ return next;
248
+ }
249
+
187
250
  if (
188
251
  polar
189
252
  ? thetaScaleId === undefined || radiusScaleId === undefined
@@ -206,85 +269,37 @@ const rematerializeLineMark = action(
206
269
  .rematerializeScale({ id: xScaleId })
207
270
  .rematerializeScale({ id: yScaleId });
208
271
 
209
- for (const channel of args.scales === false ? [] : ["color", "strokeDash"]) {
272
+ for (const channel of args.scales === false ? [] : ["color", "strokeDash", "strokeWidth"]) {
210
273
  const scaleId = layer.encoding?.[channel]?.scale;
211
274
  if (scaleId !== undefined) {
212
275
  resolved = resolved.rematerializeScale({ id: scaleId });
213
276
  }
214
277
  }
215
278
 
216
- const derived = deriveLineSeries(
217
- dataset.values,
279
+ const materialization = resolvePositionedLineMaterialization({
280
+ rows: dataset.values,
218
281
  layer,
219
- polar
220
- ? { thetaDomain: resolved.resolvedScales[thetaScaleId].domain }
221
- : undefined
222
- );
223
- const commands = polar
224
- ? derived.series.map(series => buildPolarLinePathCommands({
225
- series: series.values,
226
- thetaFieldType: derived.thetaFieldType,
227
- thetaScale: resolved.resolvedScales[thetaScaleId],
228
- radiusScale: resolved.resolvedScales[radiusScaleId],
229
- frame: resolvePolarFrame(resolveGraphicBounds(resolved)),
230
- closed: config.closed ?? false
231
- }))
232
- : derived.series.map(series => {
233
- const x = mapContinuousScaleValues(
234
- series.values.map(value => value.x),
235
- resolved.resolvedScales[xScaleId]
236
- );
237
- const y = mapContinuousScaleValues(
238
- series.values.map(value => value.y),
239
- resolved.resolvedScales[yScaleId]
240
- );
241
-
242
- return buildCurvePathCommands(
243
- series.values.map((_, index) => ({ x: x[index], y: y[index] })),
244
- config.curve ?? "linear"
245
- );
246
- });
247
- const colorEncoding = layer.encoding?.color;
248
- const dashEncoding = layer.encoding?.strokeDash;
249
- const strokes = colorEncoding?.scale === undefined
250
- ? commands.map(
251
- (_, index) =>
252
- config.stroke ??
253
- existingChildren[index]?.properties.stroke ??
254
- DEFAULT_LINE_STROKE
255
- )
256
- : mapOrdinalValues(
257
- derived.series.map(series => series.key[colorEncoding.field]),
258
- resolved.resolvedScales[colorEncoding.scale].domain,
259
- resolved.resolvedScales[colorEncoding.scale].range
260
- );
261
- const strokeWidths = commands.map(
262
- (_, index) =>
263
- config.strokeWidth ??
264
- existingChildren[index]?.properties.strokeWidth ??
265
- DEFAULT_LINE_WIDTH
266
- );
267
- const strokeDashes = dashEncoding?.datum !== undefined
268
- ? commands.map(() => normalizeStrokeDashPattern(dashEncoding.datum))
269
- : dashEncoding?.scale === undefined
270
- ? commands.map(
271
- (_, index) => existingChildren[index]?.properties.strokeDash ?? []
272
- )
273
- : mapOrdinalValues(
274
- derived.series.map(series => series.key[dashEncoding.field]),
275
- resolved.resolvedScales[dashEncoding.scale].domain,
276
- resolved.resolvedScales[dashEncoding.scale].range
277
- );
282
+ resolvedScales: resolved.resolvedScales,
283
+ bounds: resolveGraphicBounds(resolved),
284
+ config,
285
+ existingChildren,
286
+ polar,
287
+ defaults: { stroke: DEFAULT_LINE_STROKE, strokeWidth: DEFAULT_LINE_WIDTH }
288
+ });
278
289
 
279
290
  let next = resolved
280
- .editGraphics({ target: id, property: "length", value: commands.length })
281
- .editGraphics({ target: id, property: "commands", value: commands })
282
- .editGraphics({ target: id, property: "stroke", value: strokes })
283
- .editGraphics({ target: id, property: "strokeWidth", value: strokeWidths })
291
+ .editGraphics({
292
+ target: id,
293
+ property: "length",
294
+ value: materialization.commands.length
295
+ })
296
+ .editGraphics({ target: id, property: "commands", value: materialization.commands })
297
+ .editGraphics({ target: id, property: "stroke", value: materialization.strokes })
298
+ .editGraphics({ target: id, property: "strokeWidth", value: materialization.strokeWidths })
284
299
  .editGraphics({
285
300
  target: id,
286
301
  property: "strokeDash",
287
- value: strokeDashes
302
+ value: materialization.strokeDashes
288
303
  });
289
304
  if (config.opacity !== undefined) {
290
305
  next = next.editGraphics({
@@ -329,13 +344,17 @@ const editLineMark = action(
329
344
  );
330
345
  }
331
346
  if (Object.hasOwn(args, "closed") && args.closed === true &&
332
- layer.encoding?.x !== undefined) {
347
+ (layer.encoding?.x !== undefined || isParallelLine(layer))) {
333
348
  throw new Error("Line closed requires theta/radius Polar position encodings.");
334
349
  }
335
350
  if (Object.hasOwn(args, "curve") && isPolarLine(layer) &&
336
351
  args.curve !== "linear") {
337
352
  throw new Error("Polar line position currently requires curve \"linear\".");
338
353
  }
354
+ if (Object.hasOwn(args, "curve") && isParallelLine(layer) &&
355
+ args.curve !== "linear") {
356
+ throw new Error("Parallel lines require curve \"linear\".");
357
+ }
339
358
  const config = {
340
359
  ...this.markConfigs[layer.id],
341
360
  ...(Object.hasOwn(args, "stroke")
@@ -0,0 +1,146 @@
1
+ import {
2
+ deriveLineSeries,
3
+ deriveLineSeriesFieldValues
4
+ } from "../../../grammar/lineSeries.js";
5
+ import {
6
+ mapContinuousScaleValues,
7
+ mapOrdinalValues,
8
+ normalizeStrokeDashPattern
9
+ } from "../../../grammar/scales/index.js";
10
+ import { buildCurvePathCommands } from
11
+ "../../../grammar/curveCommands.js";
12
+ import { buildPolarLinePathCommands } from
13
+ "../../../grammar/polarLineCommands.js";
14
+ import { resolvePolarFrame } from "../../../grammar/polar.js";
15
+ import { materializeParallelRows } from
16
+ "../../../grammar/parallelCoordinates.js";
17
+ import { mapScaleConsumerValues } from
18
+ "../../../materialization/scales/map.js";
19
+
20
+ function existingValue(children, index, property, fallback) {
21
+ return children[index]?.properties[property] ?? fallback;
22
+ }
23
+
24
+ export function resolveParallelLineMaterialization({
25
+ rows,
26
+ parallel,
27
+ layer,
28
+ resolvedScales,
29
+ bounds,
30
+ config,
31
+ existingChildren,
32
+ defaults
33
+ }) {
34
+ const items = materializeParallelRows(
35
+ rows,
36
+ parallel.dimensions,
37
+ resolvedScales,
38
+ bounds,
39
+ parallel
40
+ );
41
+ const sourceRows = items.map(item => rows[item.sourceRowIndex]);
42
+ const mapAppearance = (channel, fallback) => {
43
+ const encoding = layer.encoding?.[channel];
44
+ if (encoding?.datum !== undefined) {
45
+ return items.map(() => channel === "strokeDash"
46
+ ? normalizeStrokeDashPattern(encoding.datum)
47
+ : encoding.datum);
48
+ }
49
+ if (encoding?.scale === undefined) return items.map(fallback);
50
+ return mapScaleConsumerValues(
51
+ sourceRows.map(row => row[encoding.field]),
52
+ resolvedScales[encoding.scale],
53
+ channel
54
+ );
55
+ };
56
+ return {
57
+ commands: items.map(item => item.commands),
58
+ strokes: mapAppearance(
59
+ "color",
60
+ (_, index) => config.stroke ??
61
+ existingValue(existingChildren, index, "stroke", defaults.stroke)
62
+ ),
63
+ strokeWidths: mapAppearance(
64
+ "strokeWidth",
65
+ (_, index) => config.strokeWidth ??
66
+ existingValue(existingChildren, index, "strokeWidth", defaults.strokeWidth)
67
+ ),
68
+ strokeDashes: mapAppearance(
69
+ "strokeDash",
70
+ (_, index) => existingValue(existingChildren, index, "strokeDash", [])
71
+ )
72
+ };
73
+ }
74
+
75
+ export function resolvePositionedLineMaterialization({
76
+ rows,
77
+ layer,
78
+ resolvedScales,
79
+ bounds,
80
+ config,
81
+ existingChildren,
82
+ polar,
83
+ defaults
84
+ }) {
85
+ const xScaleId = layer.encoding?.x?.scale;
86
+ const yScaleId = layer.encoding?.y?.scale;
87
+ const thetaScaleId = layer.encoding?.theta?.scale;
88
+ const radiusScaleId = layer.encoding?.radius?.scale;
89
+ const derived = deriveLineSeries(
90
+ rows,
91
+ layer,
92
+ polar ? { thetaDomain: resolvedScales[thetaScaleId].domain } : undefined
93
+ );
94
+ const commands = polar
95
+ ? derived.series.map(series => buildPolarLinePathCommands({
96
+ series: series.values,
97
+ thetaFieldType: derived.thetaFieldType,
98
+ thetaScale: resolvedScales[thetaScaleId],
99
+ radiusScale: resolvedScales[radiusScaleId],
100
+ frame: resolvePolarFrame(bounds),
101
+ closed: config.closed ?? false
102
+ }))
103
+ : derived.series.map(series => {
104
+ const x = mapContinuousScaleValues(
105
+ series.values.map(value => value.x),
106
+ resolvedScales[xScaleId]
107
+ );
108
+ const y = mapContinuousScaleValues(
109
+ series.values.map(value => value.y),
110
+ resolvedScales[yScaleId]
111
+ );
112
+ return buildCurvePathCommands(
113
+ series.values.map((_, index) => ({ x: x[index], y: y[index] })),
114
+ config.curve ?? "linear"
115
+ );
116
+ });
117
+ const colorEncoding = layer.encoding?.color;
118
+ const dashEncoding = layer.encoding?.strokeDash;
119
+ const widthEncoding = layer.encoding?.strokeWidth;
120
+ const strokes = colorEncoding?.scale === undefined
121
+ ? commands.map((_, index) => config.stroke ??
122
+ existingValue(existingChildren, index, "stroke", defaults.stroke))
123
+ : mapOrdinalValues(
124
+ derived.series.map(series => series.key[colorEncoding.field]),
125
+ resolvedScales[colorEncoding.scale].domain,
126
+ resolvedScales[colorEncoding.scale].range
127
+ );
128
+ const strokeWidths = widthEncoding?.scale === undefined
129
+ ? commands.map((_, index) => config.strokeWidth ??
130
+ existingValue(existingChildren, index, "strokeWidth", defaults.strokeWidth))
131
+ : mapContinuousScaleValues(
132
+ deriveLineSeriesFieldValues(rows, layer, derived, widthEncoding.field),
133
+ resolvedScales[widthEncoding.scale]
134
+ );
135
+ const strokeDashes = dashEncoding?.datum !== undefined
136
+ ? commands.map(() => normalizeStrokeDashPattern(dashEncoding.datum))
137
+ : dashEncoding?.scale === undefined
138
+ ? commands.map((_, index) =>
139
+ existingValue(existingChildren, index, "strokeDash", []))
140
+ : mapOrdinalValues(
141
+ derived.series.map(series => series.key[dashEncoding.field]),
142
+ resolvedScales[dashEncoding.scale].domain,
143
+ resolvedScales[dashEncoding.scale].range
144
+ );
145
+ return { commands, strokes, strokeWidths, strokeDashes };
146
+ }
@@ -0,0 +1,76 @@
1
+ import { action } from "../../../core/action.js";
2
+ import { validatePointShape } from "../../../grammar/pointShapes.js";
3
+ import { getPointGraphicType } from "../../../grammar/schemas/mark.js";
4
+ import { resolveMarkGraphicPlacement } from
5
+ "../../../materialization/graphicHierarchy.js";
6
+ import {
7
+ applyLayeredMarkInheritance,
8
+ assertMarkAvailable,
9
+ materializeInheritedMark,
10
+ resolveLayeredMarkInheritance,
11
+ resolveMarkData,
12
+ resolveMarkId,
13
+ validateMarkOptions
14
+ } from "../shared.js";
15
+
16
+ const OPTIONS = Object.freeze([
17
+ "id", "data", "shape", "fill", "opacity", "stroke", "strokeWidth"
18
+ ]);
19
+
20
+ export const createPointMark = action(
21
+ {
22
+ op: "createPointMark",
23
+ description: "Create a semantic point mark and concrete point graphics."
24
+ },
25
+ function (args = {}) {
26
+ validateMarkOptions(args, OPTIONS, "createPointMark");
27
+ const id = resolveMarkId(this, args.id, {
28
+ defaultId: "point",
29
+ label: "Point mark id",
30
+ markType: "point",
31
+ operation: "createPointMark"
32
+ });
33
+ const inherited = resolveLayeredMarkInheritance(this, args, "point");
34
+ const { data, dataset } = resolveMarkData(this, {
35
+ ...args,
36
+ ...(args.data === undefined &&
37
+ this.context.currentData === undefined &&
38
+ inherited?.data !== undefined
39
+ ? { data: inherited.data }
40
+ : {})
41
+ });
42
+ const shape = Object.hasOwn(args, "shape") ? args.shape : "circle";
43
+ validatePointShape(shape);
44
+ const resolvedType = getPointGraphicType(shape);
45
+ const graphicType = resolvedType === "path" ? "circle" : resolvedType;
46
+
47
+ assertMarkAvailable(this, id);
48
+
49
+ let next = this
50
+ .editSemantic({
51
+ property: `layer[${id}].mark.type`,
52
+ value: "point"
53
+ })
54
+ .editSemantic({
55
+ property: `layer[${id}].data`,
56
+ value: data
57
+ });
58
+ next = applyLayeredMarkInheritance(next, id, inherited)
59
+ .createGraphics({
60
+ id,
61
+ type: graphicType,
62
+ length: dataset.values.length,
63
+ ...resolveMarkGraphicPlacement(next, { data, markType: "point" })
64
+ })
65
+ ._withMarkConfig(id, { shape });
66
+ const created = materializeInheritedMark(next, id);
67
+ const appearance = Object.fromEntries(
68
+ ["fill", "opacity", "stroke", "strokeWidth"]
69
+ .filter(property => Object.hasOwn(args, property))
70
+ .map(property => [property, args[property]])
71
+ );
72
+ return Object.keys(appearance).length === 0
73
+ ? created
74
+ : created.editPointMark({ target: id, ...appearance });
75
+ }
76
+ );
@@ -0,0 +1,97 @@
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 { validatePointShape } from "../../../grammar/pointShapes.js";
9
+ import { findLayer } from "../../../selectors/layers.js";
10
+ import { rematerializeExistingLegend } from "../../encodings/shared.js";
11
+ import { validateMarkOptions } from "../shared.js";
12
+
13
+ const OPTIONS = Object.freeze([
14
+ "target", "shape", "fill", "opacity", "stroke", "strokeWidth"
15
+ ]);
16
+
17
+ export const editPointMark = action(
18
+ {
19
+ op: "editPointMark",
20
+ description: "Edit constant point-mark appearance."
21
+ },
22
+ function (args = {}) {
23
+ validateMarkOptions(args, OPTIONS, "editPointMark");
24
+ const editable = ["shape", "fill", "opacity", "stroke", "strokeWidth"];
25
+ if (!editable.some(property => Object.hasOwn(args, property))) {
26
+ throw new Error(
27
+ "editPointMark requires shape, fill, opacity, stroke, or strokeWidth."
28
+ );
29
+ }
30
+ const candidates = this.semanticSpec.layers.filter(
31
+ layer => layer.mark?.type === "point"
32
+ );
33
+ const current = findLayer(this, this.context.currentMark);
34
+ const requested = args.target ??
35
+ (current?.mark?.type === "point" ? current.id : undefined);
36
+ const inferred = requested ?? (candidates.length === 1
37
+ ? candidates[0].id
38
+ : undefined);
39
+ const id = validateUserId(inferred, "Point mark id");
40
+ const layer = findLayer(this, id);
41
+ if (layer?.mark?.type !== "point") {
42
+ throw new Error(`Unknown point mark "${id}".`);
43
+ }
44
+ if (Object.hasOwn(args, "shape") && layer.encoding?.shape !== undefined) {
45
+ throw new Error(
46
+ "editPointMark shape cannot be combined with a shape encoding."
47
+ );
48
+ }
49
+ if (Object.hasOwn(args, "fill") && layer.encoding?.color !== undefined) {
50
+ throw new Error(
51
+ "editPointMark fill cannot be combined with a color encoding."
52
+ );
53
+ }
54
+ const config = { ...this.markConfigs[id] };
55
+ if (Object.hasOwn(args, "shape")) {
56
+ config.shape = validatePointShape(args.shape);
57
+ }
58
+ if (Object.hasOwn(args, "fill")) {
59
+ config.fill = validateNonEmptyString(args.fill, "Point fill");
60
+ }
61
+ if (Object.hasOwn(args, "opacity")) {
62
+ config.opacity = validateUnitInterval(args.opacity, "Point opacity");
63
+ }
64
+ if (args.stroke === false && Object.hasOwn(args, "strokeWidth")) {
65
+ throw new Error(
66
+ "editPointMark cannot set strokeWidth while removing stroke."
67
+ );
68
+ }
69
+ if (Object.hasOwn(args, "stroke")) {
70
+ if (args.stroke === false) {
71
+ config.stroke = false;
72
+ delete config.strokeWidth;
73
+ } else {
74
+ const restoresStroke = config.stroke === false;
75
+ config.stroke = validateNonEmptyString(args.stroke, "Point stroke");
76
+ if (restoresStroke) config.strokeWidth = 1;
77
+ else config.strokeWidth ??= 1;
78
+ }
79
+ }
80
+ if (Object.hasOwn(args, "strokeWidth")) {
81
+ if (typeof config.stroke !== "string") {
82
+ throw new Error("Point strokeWidth requires an active stroke.");
83
+ }
84
+ config.strokeWidth = validateNonNegativeFinite(
85
+ args.strokeWidth,
86
+ "Point strokeWidth"
87
+ );
88
+ }
89
+ const next = this
90
+ ._withMarkConfig(id, config)
91
+ .rematerializePointMark({ id });
92
+ const legend = next.guideConfigs.legend?.series;
93
+ return legend?.target === id && legend.channels.includes("shape")
94
+ ? rematerializeExistingLegend(next)
95
+ : next;
96
+ }
97
+ );
@@ -1 +1,11 @@
1
- export { registerPointMarkActions } from "./actions.js";
1
+ import { createPointMark } from "./create.js";
2
+ import { editPointMark } from "./edit.js";
3
+ import { rematerializePointMark } from "./materialize.js";
4
+
5
+ export { registerPointJitterActions } from "./jitter.js";
6
+
7
+ export function registerPointMarkActions(ProgramClass) {
8
+ ProgramClass.prototype.createPointMark = createPointMark;
9
+ ProgramClass.prototype.editPointMark = editPointMark;
10
+ ProgramClass.prototype.rematerializePointMark = rematerializePointMark;
11
+ }
@@ -0,0 +1,72 @@
1
+ import { action } from "../../../core/action.js";
2
+ import { validateUserId } from "../../../core/identifiers.js";
3
+ import { validateKeys } from "../../../core/validation.js";
4
+ import { normalizePointJitterPolicy } from "../../../grammar/jitter.js";
5
+ import { findCoordinate } from "../../../selectors/coordinates.js";
6
+ import { resolveEligibleLayer } from "../../../selectors/layers.js";
7
+
8
+ const JITTER_OPTIONS = Object.freeze([
9
+ "target", "channel", "maxOffset", "seed", "key"
10
+ ]);
11
+ const REMOVE_OPTIONS = Object.freeze(["target"]);
12
+
13
+ function isCartesianPoint(program, layer, channel) {
14
+ return layer.mark?.type === "point" &&
15
+ layer.encoding?.[channel]?.scale !== undefined &&
16
+ layer.encoding?.[channel === "x" ? "y" : "x"]?.scale !== undefined &&
17
+ findCoordinate(program, layer.coordinate)?.type === "cartesian";
18
+ }
19
+
20
+ function resolveJitterTarget(program, target, channel) {
21
+ const requested = target === undefined
22
+ ? undefined
23
+ : validateUserId(target, "Point jitter target");
24
+ return resolveEligibleLayer(program, {
25
+ target: requested,
26
+ predicate: layer => isCartesianPoint(program, layer, channel),
27
+ label: "point jitter"
28
+ });
29
+ }
30
+
31
+ export const jitterPoints = action(
32
+ {
33
+ op: "jitterPoints",
34
+ description: "Apply deterministic bounded graphical jitter to a point mark."
35
+ },
36
+ function (args = {}) {
37
+ validateKeys(args, JITTER_OPTIONS, "jitterPoints");
38
+ const policy = normalizePointJitterPolicy(args);
39
+ const layer = resolveJitterTarget(this, args.target, policy.channel);
40
+ return this
41
+ ._withMaterializationConfig(["jitters", layer.id], policy)
42
+ .rematerializePointMark({ id: layer.id });
43
+ }
44
+ );
45
+
46
+ export const removeJitter = action(
47
+ {
48
+ op: "removeJitter",
49
+ description: "Remove point jitter and restore semantic scale positions."
50
+ },
51
+ function (args = {}) {
52
+ validateKeys(args, REMOVE_OPTIONS, "removeJitter");
53
+ const requested = args.target === undefined
54
+ ? undefined
55
+ : validateUserId(args.target, "Point jitter target");
56
+ const layer = resolveEligibleLayer(this, {
57
+ target: requested,
58
+ predicate: candidate =>
59
+ candidate.mark?.type === "point" &&
60
+ this.materializationConfigs.jitters?.[candidate.id] !== undefined,
61
+ label: "point jitter"
62
+ });
63
+ return this
64
+ ._withoutMaterializationConfig(["jitters", layer.id])
65
+ .rematerializePointMark({ id: layer.id });
66
+ }
67
+ );
68
+
69
+ export function registerPointJitterActions(ProgramClass) {
70
+ ProgramClass.prototype.jitterPoints = jitterPoints;
71
+ ProgramClass.prototype.removeJitter = removeJitter;
72
+ }