ggaction 0.0.4 → 0.0.5

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 (157) hide show
  1. package/CHANGELOG.md +30 -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 +31 -35
  6. package/src/actions/boxPlots/materialize.js +3 -0
  7. package/src/actions/boxPlots/options.js +8 -1
  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/coordinates/parallel.js +50 -0
  18. package/src/actions/data/bin2d.js +137 -0
  19. package/src/actions/data/density.js +54 -13
  20. package/src/actions/data/derived.js +22 -0
  21. package/src/actions/data/filter.js +5 -0
  22. package/src/actions/data/gradientProfile.js +65 -0
  23. package/src/actions/data/horizon.js +71 -0
  24. package/src/actions/data/index.js +29 -2
  25. package/src/actions/data/window.js +50 -0
  26. package/src/actions/encodings/color/index.js +17 -1
  27. package/src/actions/encodings/color/layout.js +8 -0
  28. package/src/actions/encodings/density/resolve.js +109 -0
  29. package/src/actions/encodings/density.js +193 -88
  30. package/src/actions/encodings/horizon/resolve.js +283 -0
  31. package/src/actions/encodings/horizon.js +364 -0
  32. package/src/actions/encodings/index.js +6 -0
  33. package/src/actions/encodings/parallel.js +115 -0
  34. package/src/actions/encodings/pathOrder.js +131 -0
  35. package/src/actions/encodings/position/apply.js +73 -4
  36. package/src/actions/encodings/position/index.js +15 -2
  37. package/src/actions/encodings/position/policies/arc.js +27 -4
  38. package/src/actions/encodings/position/policies/area.js +33 -2
  39. package/src/actions/encodings/position/policies/index.js +7 -2
  40. package/src/actions/encodings/position/policies/line.js +18 -2
  41. package/src/actions/encodings/position/resolve.js +24 -2
  42. package/src/actions/encodings/ranged.js +3 -1
  43. package/src/actions/encodings/ruleAppearance.js +88 -10
  44. package/src/actions/facets/actions.js +1 -2
  45. package/src/actions/facets/derive.js +33 -3
  46. package/src/actions/facets/replay.js +7 -36
  47. package/src/actions/gradientPlots/components.js +177 -0
  48. package/src/actions/gradientPlots/create.js +96 -0
  49. package/src/actions/gradientPlots/edit.js +153 -0
  50. package/src/actions/gradientPlots/index.js +24 -0
  51. package/src/actions/gradientPlots/materialize.js +287 -0
  52. package/src/actions/gradientPlots/options.js +120 -0
  53. package/src/actions/gradientPlots/paint.js +107 -0
  54. package/src/actions/gradientPlots/rebind.js +51 -0
  55. package/src/actions/gradientPlots/resolve.js +81 -0
  56. package/src/actions/guides/applicability.js +44 -17
  57. package/src/actions/guides/axes/axes.js +37 -3
  58. package/src/actions/guides/axes/index.js +2 -0
  59. package/src/actions/guides/axes/labels.js +11 -6
  60. package/src/actions/guides/axes/parallel/resolve.js +86 -0
  61. package/src/actions/guides/axes/parallel.js +228 -0
  62. package/src/actions/guides/axes/titles.js +11 -8
  63. package/src/actions/guides/guides.js +60 -5
  64. package/src/actions/guides/legends/categorical/actions.js +48 -11
  65. package/src/actions/guides/legends/edit.js +5 -0
  66. package/src/actions/guides/legends/index.js +2 -0
  67. package/src/actions/guides/legends/remove.js +4 -11
  68. package/src/actions/guides/legends/strokeWidth.js +170 -0
  69. package/src/actions/index.js +6 -0
  70. package/src/actions/marks/area/actions.js +60 -136
  71. package/src/actions/marks/area/materialize.js +163 -0
  72. package/src/actions/marks/index.js +5 -1
  73. package/src/actions/marks/line/actions.js +99 -80
  74. package/src/actions/marks/line/materialize.js +146 -0
  75. package/src/actions/marks/point/create.js +76 -0
  76. package/src/actions/marks/point/edit.js +85 -0
  77. package/src/actions/marks/point/index.js +11 -1
  78. package/src/actions/marks/point/jitter.js +72 -0
  79. package/src/actions/marks/point/{actions.js → materialize.js} +175 -170
  80. package/src/actions/marks/rect/actions.js +3 -0
  81. package/src/actions/marks/remove.js +13 -2
  82. package/src/actions/marks/rule/actions.js +25 -3
  83. package/src/actions/marks/text/actions.js +19 -4
  84. package/src/actions/marks/text/index.js +7 -1
  85. package/src/actions/marks/text/layout.js +321 -0
  86. package/src/actions/primitives/semanticValidation/dataset.js +22 -0
  87. package/src/actions/primitives/semanticValidation/guide.js +43 -0
  88. package/src/actions/primitives/semanticValidation/index.js +23 -0
  89. package/src/actions/primitives/semanticValidation/layer.js +74 -0
  90. package/src/actions/primitives/semanticValidation/scale.js +74 -0
  91. package/src/actions/primitives/semanticValidation/shared.js +5 -0
  92. package/src/actions/primitives/semanticValue.js +1 -204
  93. package/src/actions/regression/edit.js +19 -21
  94. package/src/actions/scales/consumers/common.js +18 -0
  95. package/src/actions/scales/consumers/families.js +14 -3
  96. package/src/actions/scales/definitions.js +30 -0
  97. package/src/actions/scales/edit.js +6 -204
  98. package/src/actions/scales/editPolicy.js +214 -0
  99. package/src/actions/scales/materialize.js +1 -1
  100. package/src/actions/scales/patch.js +26 -0
  101. package/src/actions/selection/actions.js +28 -3
  102. package/src/actions/violinPlots/create.js +198 -0
  103. package/src/actions/violinPlots/index.js +5 -0
  104. package/src/core/textMetrics.js +47 -0
  105. package/src/core/vocabulary.js +7 -2
  106. package/src/grammar/arcs.js +49 -12
  107. package/src/grammar/areaSeries.js +41 -5
  108. package/src/grammar/bin2d.js +333 -0
  109. package/src/grammar/categoricalDensity.js +192 -0
  110. package/src/grammar/coordinates.js +1 -1
  111. package/src/grammar/curveCommands.js +3 -1
  112. package/src/grammar/density.js +252 -26
  113. package/src/grammar/facets/dependencies.js +3 -11
  114. package/src/grammar/facets/index.js +3 -1
  115. package/src/grammar/gradientProfile.js +213 -0
  116. package/src/grammar/horizon.js +455 -0
  117. package/src/grammar/jitter.js +197 -0
  118. package/src/grammar/lineSeries.js +69 -0
  119. package/src/grammar/paint.js +88 -0
  120. package/src/grammar/parallelCoordinates.js +213 -0
  121. package/src/grammar/pathOrder.js +35 -0
  122. package/src/grammar/pointShapes.js +24 -0
  123. package/src/grammar/rules.js +1 -0
  124. package/src/grammar/scales/appearance.js +21 -0
  125. package/src/grammar/schemas/concreteGraphic.js +11 -1
  126. package/src/grammar/schemas/graphicBounds.js +19 -37
  127. package/src/grammar/schemas/semanticPath.js +17 -2
  128. package/src/grammar/transforms.js +100 -11
  129. package/src/grammar/window.js +339 -0
  130. package/src/layout/labels.js +270 -0
  131. package/src/layout/text.js +4 -1
  132. package/src/layout/title.js +10 -20
  133. package/src/materialization/dataProvenance.js +56 -2
  134. package/src/materialization/dependencies.js +9 -14
  135. package/src/materialization/guides/resources.js +74 -24
  136. package/src/materialization/horizon.js +29 -0
  137. package/src/materialization/layout.js +15 -0
  138. package/src/materialization/marks/capabilities.js +21 -1
  139. package/src/materialization/marks/index.js +3 -0
  140. package/src/materialization/marks/pathOrder.js +71 -0
  141. package/src/materialization/marks/policies.js +6 -2
  142. package/src/materialization/scaleGuideDependencies.js +8 -0
  143. package/src/materialization/scales/resolve.js +2 -0
  144. package/src/materialization/selection/items/path.js +27 -1
  145. package/src/materialization/selection/items/point.js +1 -5
  146. package/src/materialization/selection/items/rect.js +21 -0
  147. package/src/materialization/selection/styles.js +20 -9
  148. package/src/materialization/text.js +12 -4
  149. package/src/renderers/canvas/fill.js +33 -0
  150. package/src/renderers/canvas/index.js +7 -0
  151. package/src/renderers/canvas/path.js +7 -4
  152. package/src/renderers/canvas/rect.js +9 -3
  153. package/src/selectors/datasets.js +4 -0
  154. package/src/theme/defaults.js +4 -0
  155. package/types/extension.d.ts +6 -0
  156. package/types/index.d.ts +76 -0
  157. package/types/program.d.ts +632 -10
@@ -0,0 +1,163 @@
1
+ import {
2
+ deriveAreaSeries,
3
+ deriveDensityAreaSeries,
4
+ layoutDensityAreaSeries
5
+ } from "../../../grammar/areaSeries.js";
6
+ import {
7
+ mapContinuousScaleValues,
8
+ mapOrdinalValues
9
+ } from "../../../grammar/scales/index.js";
10
+ import { buildAreaCurvePathCommands } from
11
+ "../../../grammar/curveCommands.js";
12
+ import { buildLinearPathCommands } from
13
+ "../../../grammar/pathCommands.js";
14
+ import { buildCategoricalDensityPaths } from
15
+ "../../../grammar/categoricalDensity.js";
16
+
17
+ function resolveAreaPaths({
18
+ derived,
19
+ densityTransform,
20
+ categoricalDensity,
21
+ xScale,
22
+ yScale,
23
+ densityScale,
24
+ layout,
25
+ curve
26
+ }) {
27
+ if (categoricalDensity) {
28
+ return buildCategoricalDensityPaths(derived, {
29
+ categoryScale: densityTransform.placement.channel === "x" ? xScale : yScale,
30
+ valueScale: densityTransform.placement.channel === "x" ? yScale : xScale,
31
+ curve
32
+ });
33
+ }
34
+ return derived.series.map(series => {
35
+ if (densityTransform === undefined && derived.orientation === "horizontal") {
36
+ const y = mapContinuousScaleValues(
37
+ series.values.map(value => value.y),
38
+ yScale
39
+ );
40
+ const lower = mapContinuousScaleValues(
41
+ series.values.map(value => value.x),
42
+ xScale
43
+ );
44
+ const upper = mapContinuousScaleValues(
45
+ series.values.map(value => value.x2),
46
+ xScale
47
+ );
48
+ return buildAreaCurvePathCommands(
49
+ y.map((value, index) => ({ x: lower[index], y: value })),
50
+ y.map((value, index) => ({ x: upper[index], y: value })),
51
+ curve,
52
+ { independentAxis: "y" }
53
+ );
54
+ }
55
+ if (densityTransform !== undefined && derived.mode === "x-density") {
56
+ const y = mapContinuousScaleValues(
57
+ series.values.map(value => value.y),
58
+ yScale
59
+ );
60
+ const upper = mapContinuousScaleValues(
61
+ series.values.map(value => value.x),
62
+ xScale
63
+ );
64
+ const baseline = mapContinuousScaleValues([0], densityScale)[0];
65
+ if (curve === "linear") {
66
+ return buildLinearPathCommands([
67
+ { x: baseline, y: y[0] },
68
+ ...y.map((value, index) => ({ x: upper[index], y: value })),
69
+ { x: baseline, y: y.at(-1) }
70
+ ], { close: true });
71
+ }
72
+ return buildAreaCurvePathCommands(
73
+ y.map(value => ({ x: baseline, y: value })),
74
+ y.map((value, index) => ({ x: upper[index], y: value })),
75
+ curve,
76
+ { independentAxis: "y" }
77
+ );
78
+ }
79
+ const x = mapContinuousScaleValues(
80
+ series.values.map(value => value.x),
81
+ xScale
82
+ );
83
+ const lowerValues = densityTransform === undefined
84
+ ? series.values.map(value => value.y)
85
+ : derived.mode === "y-density"
86
+ ? series.values.map(value => value.lower)
87
+ : series.values.map(value => value.y);
88
+ const lower = mapContinuousScaleValues(lowerValues, yScale);
89
+ const upperValues = densityTransform === undefined
90
+ ? series.values.map(value => value.y2)
91
+ : series.values.map(value => value.upper);
92
+ const upper = mapContinuousScaleValues(upperValues, yScale);
93
+ if (densityTransform !== undefined && layout === "overlay") {
94
+ const baseline = mapContinuousScaleValues([0], yScale)[0];
95
+ if (curve === "linear") {
96
+ return buildLinearPathCommands([
97
+ { x: x[0], y: baseline },
98
+ ...x.map((value, index) => ({ x: value, y: upper[index] })),
99
+ { x: x.at(-1), y: baseline }
100
+ ], { close: true });
101
+ }
102
+ return buildAreaCurvePathCommands(
103
+ x.map(value => ({ x: value, y: baseline })),
104
+ x.map((value, index) => ({ x: value, y: upper[index] })),
105
+ curve
106
+ );
107
+ }
108
+ return buildAreaCurvePathCommands(
109
+ x.map((value, index) => ({ x: value, y: lower[index] })),
110
+ x.map((value, index) => ({ x: value, y: upper[index] })),
111
+ curve
112
+ );
113
+ });
114
+ }
115
+
116
+ export function resolveAreaMaterialization({
117
+ rows,
118
+ layer,
119
+ densityTransform,
120
+ resolvedScales,
121
+ config
122
+ }) {
123
+ const xScale = resolvedScales[layer.encoding.x.scale];
124
+ const yScale = resolvedScales[layer.encoding.y.scale];
125
+ const rawDerived = densityTransform === undefined
126
+ ? deriveAreaSeries(rows, layer)
127
+ : deriveDensityAreaSeries(rows, layer, densityTransform);
128
+ const colorEncoding = layer.encoding?.color;
129
+ const layout = colorEncoding?.layout ?? "overlay";
130
+ const categoricalDensity = densityTransform?.placement?.type === "category";
131
+ const derived = densityTransform === undefined || categoricalDensity
132
+ ? rawDerived
133
+ : layoutDensityAreaSeries(rawDerived, layout);
134
+ const densityScale = densityTransform === undefined || categoricalDensity
135
+ ? undefined
136
+ : derived.mode === "y-density" ? yScale : xScale;
137
+ if (
138
+ densityScale !== undefined &&
139
+ (densityScale.domain[0] > 0 || densityScale.domain[1] < 0)
140
+ ) {
141
+ throw new Error(`Density area mark "${layer.id}" requires a scale domain containing zero.`);
142
+ }
143
+ const paths = resolveAreaPaths({
144
+ derived,
145
+ densityTransform,
146
+ categoricalDensity,
147
+ xScale,
148
+ yScale,
149
+ densityScale,
150
+ layout,
151
+ curve: config.curve ?? "linear"
152
+ });
153
+ const fills = config.errorBand?.fill !== undefined
154
+ ? paths.map(() => config.errorBand.fill)
155
+ : colorEncoding?.scale === undefined
156
+ ? paths.map(() => config.fill)
157
+ : mapOrdinalValues(
158
+ derived.series.map(series => series.key[colorEncoding.field]),
159
+ resolvedScales[colorEncoding.scale].domain,
160
+ resolvedScales[colorEncoding.scale].range
161
+ );
162
+ return { paths, fills };
163
+ }
@@ -2,7 +2,10 @@ import { registerBarMarkActions } from "./bar/index.js";
2
2
  import { registerAreaMarkActions } from "./area/index.js";
3
3
  import { registerArcMarkActions } from "./arc/index.js";
4
4
  import { registerLineMarkActions } from "./line/index.js";
5
- import { registerPointMarkActions } from "./point/index.js";
5
+ import {
6
+ registerPointJitterActions,
7
+ registerPointMarkActions
8
+ } from "./point/index.js";
6
9
  import { registerRectMarkActions } from "./rect/index.js";
7
10
  import { registerRuleMarkActions } from "./rule/index.js";
8
11
  import { registerTextMarkActions } from "./text/index.js";
@@ -10,6 +13,7 @@ import { removeMark } from "./remove.js";
10
13
 
11
14
  export function registerMarkActions(ProgramClass) {
12
15
  registerPointMarkActions(ProgramClass);
16
+ registerPointJitterActions(ProgramClass);
13
17
  registerRectMarkActions(ProgramClass);
14
18
  registerAreaMarkActions(ProgramClass);
15
19
  registerArcMarkActions(ProgramClass);
@@ -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
+ );