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,103 +1,32 @@
1
1
  import { action } from "../../../core/action.js";
2
2
  import { validateUserId } from "../../../core/identifiers.js";
3
- import {
4
- validateNonEmptyString,
5
- validateNonNegativeFinite,
6
- validateUnitInterval
7
- } from "../../../core/validation.js";
8
3
  import { getPointGraphicType } from "../../../grammar/schemas/mark.js";
9
4
  import {
10
5
  createPointShapeGraphic,
6
+ resolvePointShapeExtent,
11
7
  validatePointShape
12
8
  } from "../../../grammar/pointShapes.js";
9
+ import {
10
+ canonicalJitterScalar,
11
+ normalizePointJitterPolicy,
12
+ resolvePointJitter
13
+ } from "../../../grammar/jitter.js";
13
14
  import { polarToCartesian, resolvePolarFrame } from "../../../grammar/polar.js";
14
15
  import { resolveGraphicBounds } from "../../../layout/canvas.js";
16
+ import { validateMarkOptions } from "../shared.js";
15
17
  import {
16
- assertMarkAvailable,
17
- applyLayeredMarkInheritance,
18
- materializeInheritedMark,
19
- resolveLayeredMarkInheritance,
20
- resolveMarkId,
21
- resolveMarkData,
22
- validateMarkOptions
23
- } from "../shared.js";
24
- import { DEFAULT_COLORS } from "../../../theme/defaults.js";
18
+ DEFAULT_COLORS,
19
+ DEFAULT_POINT_RADIUS
20
+ } from "../../../theme/defaults.js";
25
21
  import { findDataset } from "../../../selectors/datasets.js";
26
22
  import { findLayer } from "../../../selectors/layers.js";
27
- import { rematerializeExistingLegend } from "../../encodings/shared.js";
28
- import { resolveMarkGraphicPlacement } from
29
- "../../../materialization/graphicHierarchy.js";
30
23
  import { rematerializeHighlightBaseline } from "../lifecycle.js";
31
24
  import { resolveRowEncodingValues } from
32
25
  "../../../materialization/rowEncoding.js";
33
26
 
34
- const POINT_MARK_OPTIONS = Object.freeze([
35
- "id", "data", "shape", "fill", "opacity", "stroke", "strokeWidth"
36
- ]);
37
- const EDIT_POINT_OPTIONS = Object.freeze([
38
- "target", "shape", "fill", "opacity", "stroke", "strokeWidth"
39
- ]);
40
27
  const REMATERIALIZE_OPTIONS = Object.freeze(["id"]);
41
28
  const DEFAULT_POINT_FILL = DEFAULT_COLORS.mark;
42
29
 
43
- const createPointMark = action(
44
- {
45
- op: "createPointMark",
46
- description: "Create a semantic point mark and concrete point graphics."
47
- },
48
- function (args = {}) {
49
- validateMarkOptions(args, POINT_MARK_OPTIONS, "createPointMark");
50
- const id = resolveMarkId(this, args.id, {
51
- defaultId: "point",
52
- label: "Point mark id",
53
- markType: "point",
54
- operation: "createPointMark"
55
- });
56
- const inherited = resolveLayeredMarkInheritance(this, args, "point");
57
- const { data, dataset } = resolveMarkData(this, {
58
- ...args,
59
- ...(args.data === undefined &&
60
- this.context.currentData === undefined &&
61
- inherited?.data !== undefined
62
- ? { data: inherited.data }
63
- : {})
64
- });
65
- const shape = Object.hasOwn(args, "shape") ? args.shape : "circle";
66
- validatePointShape(shape);
67
- const resolvedType = getPointGraphicType(shape);
68
- const graphicType = resolvedType === "path" ? "circle" : resolvedType;
69
-
70
- assertMarkAvailable(this, id);
71
-
72
- let next = this
73
- .editSemantic({
74
- property: `layer[${id}].mark.type`,
75
- value: "point"
76
- })
77
- .editSemantic({
78
- property: `layer[${id}].data`,
79
- value: data
80
- });
81
- next = applyLayeredMarkInheritance(next, id, inherited)
82
- .createGraphics({
83
- id,
84
- type: graphicType,
85
- length: dataset.values.length,
86
- ...resolveMarkGraphicPlacement(next, { data, markType: "point" })
87
- })
88
- ._withMarkConfig(id, { shape });
89
- const created = materializeInheritedMark(next, id);
90
- const appearance = Object.fromEntries(
91
- ["fill", "opacity", "stroke", "strokeWidth"]
92
- .filter(property => Object.hasOwn(args, property))
93
- .map(property => [property, args[property]])
94
- );
95
- return Object.keys(appearance).length === 0
96
- ? created
97
- : created.editPointMark({ target: id, ...appearance });
98
- }
99
- );
100
-
101
30
  function resolvePointPositions(program, layer, dataset) {
102
31
  const hasPolar = layer.encoding?.theta !== undefined ||
103
32
  layer.encoding?.radius !== undefined;
@@ -154,7 +83,124 @@ function existingArea(child, parentType) {
154
83
  return undefined;
155
84
  }
156
85
 
157
- const rematerializePointMark = action(
86
+ function resolvedPointArea(area, index, config, existingChildren, graphicType) {
87
+ return area?.[index] ?? (
88
+ config.radius === undefined
89
+ ? existingArea(existingChildren[index], graphicType) ??
90
+ Math.PI * DEFAULT_POINT_RADIUS ** 2
91
+ : Math.PI * config.radius ** 2
92
+ );
93
+ }
94
+
95
+ function resolveJitterEntries({
96
+ policy,
97
+ dataset,
98
+ x,
99
+ y,
100
+ area,
101
+ shapes,
102
+ config,
103
+ graphic,
104
+ existingChildren
105
+ }) {
106
+ const seen = new Set();
107
+ return dataset.values.flatMap((row, index) => {
108
+ if (!Number.isFinite(x?.[index]) || !Number.isFinite(y?.[index])) return [];
109
+ const identity = policy.key === undefined ? index : row[policy.key];
110
+ const canonical = canonicalJitterScalar(
111
+ identity,
112
+ policy.key === undefined
113
+ ? "Point jitter source index"
114
+ : `Point jitter key field "${policy.key}"`
115
+ );
116
+ if (seen.has(canonical)) {
117
+ throw new Error(
118
+ `Point jitter key field "${policy.key}" must be unique for materialized items.`
119
+ );
120
+ }
121
+ seen.add(canonical);
122
+ const resolvedArea = resolvedPointArea(
123
+ area,
124
+ index,
125
+ config,
126
+ existingChildren,
127
+ graphic.type
128
+ );
129
+ const extent = resolvePointShapeExtent({
130
+ shape: shapes[index],
131
+ area: resolvedArea,
132
+ strokeWidth: typeof config.stroke === "string"
133
+ ? config.strokeWidth ?? 1
134
+ : 0
135
+ });
136
+ return [{
137
+ index,
138
+ identity,
139
+ base: policy.channel === "x" ? x[index] : y[index],
140
+ halfExtent: extent[policy.channel]
141
+ }];
142
+ });
143
+ }
144
+
145
+ function applyPointJitter(program, {
146
+ id,
147
+ layer,
148
+ dataset,
149
+ x,
150
+ y,
151
+ area,
152
+ shapes,
153
+ config,
154
+ graphic,
155
+ existingChildren
156
+ }) {
157
+ const stored = program.materializationConfigs.jitters?.[id];
158
+ if (stored === undefined) return { program, x, y };
159
+ const policy = normalizePointJitterPolicy(stored);
160
+ const encoding = layer.encoding?.[policy.channel];
161
+ const scale = program.resolvedScales[encoding?.scale];
162
+ if (scale === undefined) {
163
+ throw new Error(
164
+ `Point jitter on "${id}" requires a resolved ${policy.channel} scale.`
165
+ );
166
+ }
167
+ const bounds = resolveGraphicBounds(program);
168
+ const entries = resolveJitterEntries({
169
+ policy,
170
+ dataset,
171
+ x,
172
+ y,
173
+ area,
174
+ shapes,
175
+ config,
176
+ graphic,
177
+ existingChildren
178
+ });
179
+ const resolution = resolvePointJitter({
180
+ target: id,
181
+ policy,
182
+ scale,
183
+ entries,
184
+ plotMinimum: policy.channel === "x" ? bounds.x : bounds.y,
185
+ plotMaximum: policy.channel === "x"
186
+ ? bounds.x + bounds.width
187
+ : bounds.y + bounds.height
188
+ });
189
+ const values = [...(policy.channel === "x" ? x : y)];
190
+ for (const item of resolution.items) {
191
+ values[item.index] += item.finalOffset;
192
+ }
193
+ return {
194
+ program: program._withMaterializationConfig(["jitters", id], {
195
+ ...policy,
196
+ resolved: resolution
197
+ }),
198
+ x: policy.channel === "x" ? values : x,
199
+ y: policy.channel === "y" ? values : y
200
+ };
201
+ }
202
+
203
+ export const rematerializePointMark = action(
158
204
  {
159
205
  op: "rematerializePointMark",
160
206
  description: "Recompute concrete point geometry and appearance."
@@ -183,7 +229,7 @@ const rematerializePointMark = action(
183
229
  throw new Error(`Point mark "${id}" requires an existing dataset.`);
184
230
  }
185
231
 
186
- const { x, y } = resolvePointPositions(this, layer, dataset);
232
+ const positions = resolvePointPositions(this, layer, dataset);
187
233
  const mappedFill = resolveRowEncodingValues(this, layer, dataset, "color");
188
234
  const area = resolveRowEncodingValues(this, layer, dataset, "size");
189
235
  const encodedShape = resolveRowEncodingValues(this, layer, dataset, "shape");
@@ -197,6 +243,19 @@ const rematerializePointMark = action(
197
243
  encodedShape !== undefined ||
198
244
  graphic.type === "collection" ||
199
245
  getPointGraphicType(constantShape) !== graphic.type;
246
+ const jittered = applyPointJitter(this, {
247
+ id,
248
+ layer,
249
+ dataset,
250
+ x: positions.x,
251
+ y: positions.y,
252
+ area,
253
+ shapes,
254
+ config,
255
+ graphic,
256
+ existingChildren
257
+ });
258
+ const { x, y } = jittered;
200
259
 
201
260
  if (requiresMixedCollection) {
202
261
  const items = dataset.values.map((_, index) => {
@@ -205,11 +264,13 @@ const rematerializePointMark = action(
205
264
  const color = mappedFill?.[index] ?? config.fill ??
206
265
  existing.fill ?? DEFAULT_POINT_FILL;
207
266
  const opacity = encodedOpacity?.[index] ?? config.opacity ?? existing.opacity;
208
- const resolvedArea = area?.[index] ??
209
- (config.radius === undefined
210
- ? existingArea(existingChildren[index], graphic.type)
211
- :
212
- Math.PI * config.radius ** 2);
267
+ const resolvedArea = resolvedPointArea(
268
+ area,
269
+ index,
270
+ config,
271
+ existingChildren,
272
+ graphic.type
273
+ );
213
274
  const centerX = x?.[index] ?? existing.x;
214
275
  const centerY = y?.[index] ?? existing.y;
215
276
  if (
@@ -230,19 +291,23 @@ const rematerializePointMark = action(
230
291
  y: centerY,
231
292
  area: resolvedArea,
232
293
  fill: color,
233
- ...(config.stroke === undefined ? {} : { stroke: config.stroke }),
294
+ ...(typeof config.stroke === "string" ? { stroke: config.stroke } : {}),
234
295
  ...(config.strokeWidth === undefined
235
296
  ? {}
236
297
  : { strokeWidth: config.strokeWidth }),
237
298
  opacity
238
299
  });
239
300
  });
240
- return this.editGraphics({ target: id, property: "items", value: items });
301
+ return jittered.program.editGraphics({
302
+ target: id,
303
+ property: "items",
304
+ value: items
305
+ });
241
306
  }
242
307
 
243
308
  let next = graphic.items.length === dataset.values.length
244
- ? this
245
- : this.editGraphics({
309
+ ? jittered.program
310
+ : jittered.program.editGraphics({
246
311
  target: id,
247
312
  property: "length",
248
313
  value: dataset.values.length
@@ -254,14 +319,32 @@ const rematerializePointMark = action(
254
319
  }
255
320
  if (graphic.type === "circle") {
256
321
  const radii = area === undefined
257
- ? config.radius
322
+ ? config.radius ?? (
323
+ x !== undefined && y !== undefined
324
+ ? dataset.values.map((_, index) => {
325
+ const existing = existingChildren[index]?.properties?.radius;
326
+ return Number.isFinite(existing)
327
+ ? existing
328
+ : DEFAULT_POINT_RADIUS;
329
+ })
330
+ : undefined
331
+ )
258
332
  : area.map(value => Math.sqrt(value / Math.PI));
259
333
  if (radii !== undefined) {
260
334
  next = next.editGraphics({ target: id, property: "radius", value: radii });
261
335
  }
262
- } else if (area !== undefined || config.radius !== undefined) {
336
+ } else if (
337
+ area !== undefined ||
338
+ config.radius !== undefined ||
339
+ (x !== undefined && y !== undefined)
340
+ ) {
263
341
  const sides = area === undefined
264
- ? config.radius * Math.sqrt(Math.PI)
342
+ ? dataset.values.map((_, index) => {
343
+ const existing = existingArea(existingChildren[index], graphic.type);
344
+ return Math.sqrt(
345
+ existing ?? Math.PI * (config.radius ?? DEFAULT_POINT_RADIUS) ** 2
346
+ );
347
+ })
265
348
  : area.map(value => Math.sqrt(value));
266
349
  const centersX = x ?? existingChildren.map(child => child.properties.x);
267
350
  const centersY = y ?? existingChildren.map(child => child.properties.y);
@@ -290,7 +373,11 @@ const rematerializePointMark = action(
290
373
  } else if (encodedOpacity !== undefined) {
291
374
  next = next.editGraphics({ target: id, property: "opacity", value: encodedOpacity });
292
375
  }
293
- if (config.stroke !== undefined) {
376
+ if (config.stroke === false) {
377
+ next = next
378
+ .editGraphics({ target: id, property: "stroke", value: "transparent" })
379
+ .editGraphics({ target: id, property: "strokeWidth", value: 0 });
380
+ } else if (config.stroke !== undefined) {
294
381
  next = next.editGraphics({ target: id, property: "stroke", value: config.stroke });
295
382
  next = next.editGraphics({
296
383
  target: id,
@@ -301,79 +388,3 @@ const rematerializePointMark = action(
301
388
  return next;
302
389
  }
303
390
  );
304
-
305
- const editPointMark = action(
306
- {
307
- op: "editPointMark",
308
- description: "Edit constant point-mark appearance."
309
- },
310
- function (args = {}) {
311
- validateMarkOptions(args, EDIT_POINT_OPTIONS, "editPointMark");
312
- const editable = ["shape", "fill", "opacity", "stroke", "strokeWidth"];
313
- if (!editable.some(property => Object.hasOwn(args, property))) {
314
- throw new Error(
315
- "editPointMark requires shape, fill, opacity, stroke, or strokeWidth."
316
- );
317
- }
318
- const candidates = this.semanticSpec.layers.filter(
319
- layer => layer.mark?.type === "point"
320
- );
321
- const current = findLayer(this, this.context.currentMark);
322
- const requested = args.target ??
323
- (current?.mark?.type === "point" ? current.id : undefined);
324
- const inferred = requested ?? (candidates.length === 1
325
- ? candidates[0].id
326
- : undefined);
327
- const id = validateUserId(inferred, "Point mark id");
328
- const layer = findLayer(this, id);
329
- if (layer?.mark?.type !== "point") {
330
- throw new Error(`Unknown point mark "${id}".`);
331
- }
332
- if (Object.hasOwn(args, "shape") && layer.encoding?.shape !== undefined) {
333
- throw new Error(
334
- "editPointMark shape cannot be combined with a shape encoding."
335
- );
336
- }
337
- if (Object.hasOwn(args, "fill") && layer.encoding?.color !== undefined) {
338
- throw new Error(
339
- "editPointMark fill cannot be combined with a color encoding."
340
- );
341
- }
342
- const config = { ...this.markConfigs[id] };
343
- if (Object.hasOwn(args, "shape")) {
344
- config.shape = validatePointShape(args.shape);
345
- }
346
- if (Object.hasOwn(args, "fill")) {
347
- config.fill = validateNonEmptyString(args.fill, "Point fill");
348
- }
349
- if (Object.hasOwn(args, "opacity")) {
350
- config.opacity = validateUnitInterval(args.opacity, "Point opacity");
351
- }
352
- if (Object.hasOwn(args, "stroke")) {
353
- config.stroke = validateNonEmptyString(args.stroke, "Point stroke");
354
- config.strokeWidth ??= 1;
355
- }
356
- if (Object.hasOwn(args, "strokeWidth")) {
357
- if (config.stroke === undefined) {
358
- throw new Error("Point strokeWidth requires an active stroke.");
359
- }
360
- config.strokeWidth = validateNonNegativeFinite(
361
- args.strokeWidth,
362
- "Point strokeWidth"
363
- );
364
- }
365
- const next = this
366
- ._withMarkConfig(id, config)
367
- .rematerializePointMark({ id });
368
- const legend = next.guideConfigs.legend?.series;
369
- return legend?.target === id && legend.channels.includes("shape")
370
- ? rematerializeExistingLegend(next)
371
- : next;
372
- }
373
- );
374
-
375
- export function registerPointMarkActions(ProgramClass) {
376
- ProgramClass.prototype.createPointMark = createPointMark;
377
- ProgramClass.prototype.editPointMark = editPointMark;
378
- ProgramClass.prototype.rematerializePointMark = rematerializePointMark;
379
- }
@@ -102,6 +102,9 @@ const rematerializeRectMark = action(
102
102
  if (graphic?.type !== "rect" || !Array.isArray(graphic.items)) {
103
103
  throw new Error(`Rect mark "${id}" requires rect collection graphics.`);
104
104
  }
105
+ if (this.markConfigs[id]?.gradientPlot?.materialized === true) {
106
+ return this.materializeGradientPlotFill({ id });
107
+ }
105
108
  const dataset = findDataset(this, layer.data);
106
109
  if (dataset === undefined) {
107
110
  throw new Error(`Rect mark "${id}" requires an existing dataset.`);
@@ -27,7 +27,8 @@ function ownedChildren(program, id) {
27
27
  config.regression?.lineId,
28
28
  config.boxPlot?.whiskerId,
29
29
  config.boxPlot?.medianId,
30
- config.boxPlot?.outlierId
30
+ config.boxPlot?.outlierId,
31
+ config.gradientPlot?.centerId
31
32
  ].concat(
32
33
  program.semanticSpec.layers
33
34
  .filter(layer => layer.source === id)
@@ -88,7 +89,8 @@ function ownedDerivedData(program, ids) {
88
89
  config.errorBand?.data,
89
90
  config.regression?.dataId,
90
91
  config.boxPlot?.summaryId,
91
- config.boxPlot?.outlierDataId
92
+ config.boxPlot?.outlierDataId,
93
+ config.gradientPlot?.profileId
92
94
  ]) {
93
95
  if (candidate !== undefined) candidates.add(candidate);
94
96
  }
@@ -189,6 +191,13 @@ export const removeMark = action(
189
191
  const selectionCleanup = cleanupSelectionState(next, ids);
190
192
  next = selectionCleanup.program;
191
193
  for (const id of ids) {
194
+ const labelLayout = next.materializationConfigs.labelLayouts?.[id];
195
+ if (
196
+ labelLayout?.leaderId !== undefined &&
197
+ next.graphicSpec.objects[labelLayout.leaderId] !== undefined
198
+ ) {
199
+ next = next.editGraphics({ target: labelLayout.leaderId, remove: true });
200
+ }
192
201
  if (findLayer(next, id) !== undefined) {
193
202
  next = next.editSemantic({ property: `layer[${id}]`, remove: true });
194
203
  }
@@ -196,6 +205,8 @@ export const removeMark = action(
196
205
  next = next.editGraphics({ target: id, remove: true });
197
206
  }
198
207
  next = next._withoutMaterializationConfig(["marks", id]);
208
+ next = next._withoutMaterializationConfig(["jitters", id]);
209
+ next = next._withoutMaterializationConfig(["labelLayouts", id]);
199
210
  }
200
211
 
201
212
  next = cleanupPositionGuides(next, positionScales);
@@ -104,7 +104,17 @@ const createRuleMark = action(
104
104
  length: 0,
105
105
  ...resolveMarkGraphicPlacement(created, { data, markType: "rule" })
106
106
  })
107
- ._withMarkConfig(id, DEFAULT_RULE_CONFIG);
107
+ ._withMarkConfig(id, {
108
+ ...DEFAULT_RULE_CONFIG,
109
+ ...(inherited === undefined
110
+ ? {}
111
+ : {
112
+ inheritedPosition: {
113
+ source: inherited.source,
114
+ channels: Object.keys(inherited.encoding)
115
+ }
116
+ })
117
+ });
108
118
  return materializeInheritedMark(created, id);
109
119
  }
110
120
  );
@@ -143,7 +153,7 @@ const rematerializeRuleMark = action(
143
153
  }
144
154
 
145
155
  const scaleIds = [...new Set(
146
- ["x", "y", "x2", "y2", "strokeDash", "opacity"]
156
+ ["x", "y", "x2", "y2", "strokeDash", "strokeWidth", "opacity"]
147
157
  .map(channel => layer.encoding?.[channel]?.scale)
148
158
  .filter(scale => scale !== undefined)
149
159
  )];
@@ -153,6 +163,11 @@ const rematerializeRuleMark = action(
153
163
  }
154
164
 
155
165
  const derived = deriveRuleValues(dataset.values, layer);
166
+ if (derived.values.strokeWidth?.some(value => value < 0)) {
167
+ throw new RangeError(
168
+ `Rule strokeWidth field "${layer.encoding.strokeWidth.field}" cannot contain negative values.`
169
+ );
170
+ }
156
171
  const mapped = {};
157
172
  for (const channel of ["x", "y", "x2", "y2"]) {
158
173
  const encoding = layer.encoding?.[channel];
@@ -223,9 +238,13 @@ const rematerializeRuleMark = action(
223
238
  }
224
239
  }
225
240
 
226
- const resolvedConfig = resolved.markConfigs[id] ?? DEFAULT_RULE_CONFIG;
241
+ const resolvedConfig = {
242
+ ...DEFAULT_RULE_CONFIG,
243
+ ...resolved.markConfigs[id]
244
+ };
227
245
  const dashEncoding = layer.encoding?.strokeDash;
228
246
  const opacityEncoding = layer.encoding?.opacity;
247
+ const strokeWidthEncoding = layer.encoding?.strokeWidth;
229
248
  const strokeDash = dashEncoding?.scale === undefined
230
249
  ? Array.from(
231
250
  { length: derived.length },
@@ -244,6 +263,12 @@ const rematerializeRuleMark = action(
244
263
  derived.values.opacity,
245
264
  resolved.resolvedScales[opacityEncoding.scale]
246
265
  );
266
+ const strokeWidth = strokeWidthEncoding?.scale === undefined
267
+ ? resolvedConfig.strokeWidth
268
+ : mapContinuousScaleValues(
269
+ derived.values.strokeWidth,
270
+ resolved.resolvedScales[strokeWidthEncoding.scale]
271
+ );
247
272
 
248
273
  return resolved
249
274
  .editGraphics({ target: id, property: "length", value: derived.length })
@@ -255,7 +280,7 @@ const rematerializeRuleMark = action(
255
280
  .editGraphics({
256
281
  target: id,
257
282
  property: "strokeWidth",
258
- value: resolvedConfig.strokeWidth
283
+ value: strokeWidth
259
284
  })
260
285
  .editGraphics({ target: id, property: "strokeDash", value: strokeDash })
261
286
  .editGraphics({ target: id, property: "opacity", value: opacity });
@@ -24,14 +24,19 @@ const STYLE_OPTIONS = Object.freeze([
24
24
  ]);
25
25
  const CREATE_OPTIONS = Object.freeze(["id", "data", "text", ...STYLE_OPTIONS]);
26
26
  const EDIT_OPTIONS = Object.freeze(["target", ...STYLE_OPTIONS]);
27
- const REMATERIALIZE_OPTIONS = Object.freeze(["id"]);
27
+ const REMATERIALIZE_OPTIONS = Object.freeze(["id", "replayLayout"]);
28
28
  const SOURCE_TYPES = new Set(["point", "bar", "rule", "rect"]);
29
29
 
30
+ function sourceMatchesData(program, layer, requestedData) {
31
+ if (requestedData === undefined || layer?.data === requestedData) return true;
32
+ return program.markConfigs[layer.id]?.gradientPlot?.source === requestedData;
33
+ }
34
+
30
35
  function eligibleSource(program, layer, requestedData) {
31
36
  if (
32
37
  !SOURCE_TYPES.has(layer?.mark?.type) ||
33
38
  layer.data === undefined ||
34
- (requestedData !== undefined && layer.data !== requestedData)
39
+ !sourceMatchesData(program, layer, requestedData)
35
40
  ) return false;
36
41
  const encodings = resolveCompatibleEncodings(program, layer, "text");
37
42
  return encodings.x !== undefined && encodings.y !== undefined;
@@ -133,6 +138,10 @@ const rematerializeTextMark = action(
133
138
  function (args = {}) {
134
139
  validateMarkOptions(args, REMATERIALIZE_OPTIONS, "rematerializeTextMark");
135
140
  const id = validateUserId(args.id, "Text mark id");
141
+ const replayLayout = args.replayLayout ?? true;
142
+ if (typeof replayLayout !== "boolean") {
143
+ throw new TypeError("rematerializeTextMark replayLayout must be a boolean.");
144
+ }
136
145
  const layer = findLayer(this, id);
137
146
  const graphic = this.graphicSpec.objects[id];
138
147
  if (layer?.mark?.type !== "text") throw new Error(`Unknown text mark "${id}".`);
@@ -140,11 +149,14 @@ const rematerializeTextMark = action(
140
149
  throw new Error(`Text mark "${id}" requires text collection graphics.`);
141
150
  }
142
151
  if (!canMaterializeText(this, layer)) {
143
- return graphic.items.length === 0
152
+ const next = graphic.items.length === 0
144
153
  ? this
145
154
  : this.editGraphics({ target: id, property: "length", value: 0 });
155
+ return replayLayout && next.materializationConfigs.labelLayouts?.[id] !== undefined
156
+ ? next.materializeLabelLayout({ id, rematerializeBase: false })
157
+ : next;
146
158
  }
147
- return this.editGraphics({
159
+ const next = this.editGraphics({
148
160
  target: id,
149
161
  property: "items",
150
162
  value: resolveTextGraphicItems(
@@ -153,6 +165,9 @@ const rematerializeTextMark = action(
153
165
  this.markConfigs[id] ?? DEFAULT_TEXT_MARK
154
166
  )
155
167
  });
168
+ return replayLayout && next.materializationConfigs.labelLayouts?.[id] !== undefined
169
+ ? next.materializeLabelLayout({ id, rematerializeBase: false })
170
+ : next;
156
171
  }
157
172
  );
158
173
 
@@ -1 +1,7 @@
1
- export { registerTextMarkActions } from "./actions.js";
1
+ import { registerTextMarkActions as registerTextCoreActions } from "./actions.js";
2
+ import { registerTextLabelLayoutActions } from "./layout.js";
3
+
4
+ export function registerTextMarkActions(ProgramClass) {
5
+ registerTextCoreActions(ProgramClass);
6
+ registerTextLabelLayoutActions(ProgramClass);
7
+ }