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,204 +1 @@
1
- import { validateUserId } from "../../core/identifiers.js";
2
- import { isPlainObject } from "../../core/immutable.js";
3
- import {
4
- COLOR_LAYOUTS,
5
- STACK_MODES,
6
- CATEGORICAL_LEGEND_CHANNELS,
7
- MARK_TYPES
8
- } from "../../core/vocabulary.js";
9
- import { validateAggregate } from "../../grammar/aggregate.js";
10
- import { findLayer, findSemanticScale, hasDataset } from "../../selectors/index.js";
11
- import { validateCoordinateType } from "../../grammar/coordinates.js";
12
- import {
13
- normalizeHistogramBin,
14
- validateHistogramBinBoundaries,
15
- validateHistogramBinStep
16
- } from "../../grammar/histogram.js";
17
- import { validateDatasetTransforms } from "../../grammar/transforms.js";
18
- import {
19
- validateSemanticFieldType,
20
- validateContinuousColorInterpolation,
21
- validateScalePropertyForType,
22
- validateSemanticScaleDomain,
23
- validateSemanticScaleRange,
24
- validateSemanticScaleType
25
- } from "../../grammar/scales/index.js";
26
-
27
- function nonEmptyString(value, label) {
28
- if (typeof value !== "string" || value.length === 0) {
29
- throw new TypeError(`${label} must be a non-empty string.`);
30
- }
31
- }
32
-
33
- function validateLegend(property, value) {
34
- if (property === "title") {
35
- nonEmptyString(value, "Legend title");
36
- return;
37
- }
38
- if (!Array.isArray(value) || value.length === 0) {
39
- throw new TypeError(`Legend ${property} must be a non-empty array.`);
40
- }
41
- if (new Set(value).size !== value.length) {
42
- throw new Error(`Legend ${property} must not contain duplicates.`);
43
- }
44
- if (property === "channels") {
45
- if (!value.every(channel => CATEGORICAL_LEGEND_CHANNELS.includes(channel))) {
46
- throw new Error("Legend channels support only color, strokeDash, and shape.");
47
- }
48
- return;
49
- }
50
- for (const id of value) validateUserId(id, "Legend scale id");
51
- }
52
-
53
- export function validateSemanticValue(program, parsed, value) {
54
- if (parsed.kind === "dataset" && parsed.path[0] === "values") {
55
- if (!Array.isArray(value) || !value.every(isPlainObject)) {
56
- throw new TypeError("Dataset values must be an array of plain row objects.");
57
- }
58
- }
59
- if (parsed.kind === "dataset" && parsed.path[0] === "source") {
60
- validateUserId(value, "Dataset source id");
61
- if (!hasDataset(program, value)) {
62
- throw new Error(`Unknown source dataset "${value}".`);
63
- }
64
- }
65
- if (parsed.kind === "dataset" && parsed.path[0] === "transform") {
66
- validateDatasetTransforms(value);
67
- }
68
- if (
69
- parsed.kind === "layer" &&
70
- parsed.path.join(".") === "mark.type" &&
71
- !MARK_TYPES.includes(value)
72
- ) {
73
- throw new Error(`Unknown mark type "${value}".`);
74
- }
75
- if (parsed.kind === "layer") {
76
- const property = parsed.path.join(".");
77
- if (property === "source") {
78
- validateUserId(value, "Layer source id");
79
- if (value === parsed.id) {
80
- throw new Error("A layer cannot use itself as its source.");
81
- }
82
- const source = findLayer(program, value);
83
- if (source === undefined) {
84
- throw new Error(`Unknown source layer "${value}".`);
85
- }
86
- if (!["point", "bar", "rule", "rect"].includes(source.mark?.type)) {
87
- throw new Error(
88
- `Layer source "${value}" must be a point, bar, rule, or rect mark.`
89
- );
90
- }
91
- }
92
- if (property.endsWith(".title")) {
93
- nonEmptyString(value, "Encoding title");
94
- }
95
- if (property.endsWith(".fieldType")) validateSemanticFieldType(value);
96
- if (property.endsWith(".aggregate")) validateAggregate(value);
97
- if (property.endsWith(".bin.maxBins")) normalizeHistogramBin({ maxBins: value });
98
- if (property.endsWith(".bin.step")) validateHistogramBinStep(value);
99
- if (property.endsWith(".bin.boundaries")) {
100
- validateHistogramBinBoundaries(value);
101
- }
102
- if (
103
- property.endsWith(".stack") &&
104
- value !== null &&
105
- !STACK_MODES.includes(value)
106
- ) {
107
- throw new Error(`Unsupported stack "${value}".`);
108
- }
109
- if (
110
- property === "encoding.color.layout" &&
111
- !COLOR_LAYOUTS.includes(value)
112
- ) {
113
- throw new Error(`Unsupported color layout "${value}".`);
114
- }
115
- }
116
- if (parsed.kind === "scale") {
117
- const property = parsed.path.join(".");
118
- const existing = findSemanticScale(program, parsed.id);
119
- if (property === "type") {
120
- validateSemanticScaleType(value);
121
- for (const owned of [
122
- "nice", "zero", "clamp", "base", "exponent", "constant",
123
- "paddingInner", "paddingOuter", "padding", "align"
124
- ]) {
125
- if (existing?.[owned] !== undefined) {
126
- validateScalePropertyForType(value, owned);
127
- }
128
- }
129
- } else if (property === "domain") validateSemanticScaleDomain(value);
130
- else if (property === "range") validateSemanticScaleRange(value);
131
- else if (property === "nice") {
132
- if (typeof value !== "boolean") throw new TypeError("Scale nice must be a boolean.");
133
- if (existing?.type !== undefined) {
134
- validateScalePropertyForType(existing.type, "nice");
135
- }
136
- } else if (property === "zero") {
137
- if (typeof value !== "boolean") throw new TypeError("Scale zero must be a boolean.");
138
- if (existing?.type !== undefined) {
139
- validateScalePropertyForType(existing.type, "zero");
140
- }
141
- } else if (property === "clamp") {
142
- if (typeof value !== "boolean") throw new TypeError("Scale clamp must be a boolean.");
143
- if (existing?.type !== undefined) {
144
- validateScalePropertyForType(existing.type, "clamp");
145
- }
146
- } else if (property === "reverse") {
147
- if (typeof value !== "boolean") throw new TypeError("Scale reverse must be a boolean.");
148
- } else if (["base", "exponent", "constant"].includes(property)) {
149
- if (!Number.isFinite(value) || value <= 0) {
150
- throw new RangeError(`Scale ${property} must be a positive finite number.`);
151
- }
152
- if (property === "base" && value === 1) {
153
- throw new RangeError("Scale base must not equal 1.");
154
- }
155
- if (existing?.type !== undefined) {
156
- validateScalePropertyForType(existing.type, property);
157
- }
158
- } else if (property === "interpolate") {
159
- validateContinuousColorInterpolation(value);
160
- } else if (property === "paddingInner") {
161
- if (!Number.isFinite(value) || value < 0 || value >= 1) {
162
- throw new RangeError(
163
- "Scale paddingInner must be from 0 (inclusive) to 1 (exclusive)."
164
- );
165
- }
166
- if (existing?.type !== undefined) {
167
- validateScalePropertyForType(existing.type, property);
168
- }
169
- } else if (property === "paddingOuter" || property === "padding") {
170
- if (!Number.isFinite(value) || value < 0) {
171
- throw new RangeError(`Scale ${property} must be a non-negative finite number.`);
172
- }
173
- if (existing?.type !== undefined) {
174
- validateScalePropertyForType(existing.type, property);
175
- }
176
- } else if (property === "align") {
177
- if (!Number.isFinite(value) || value < 0 || value > 1) {
178
- throw new RangeError("Scale align must be between 0 and 1.");
179
- }
180
- if (existing?.type !== undefined) {
181
- validateScalePropertyForType(existing.type, property);
182
- }
183
- }
184
- }
185
- if (parsed.kind === "coordinate" && parsed.path[0] === "type") {
186
- validateCoordinateType(value);
187
- }
188
- if (parsed.kind === "guide" && parsed.id === "legend.series") {
189
- validateLegend(parsed.path.at(-1), value);
190
- }
191
- if (parsed.kind === "guide" && (
192
- parsed.id.startsWith("grid.") || parsed.id.startsWith("axis.")
193
- )) {
194
- const property = parsed.path.at(-1);
195
- if (property === "title") nonEmptyString(value, "Axis title");
196
- else {
197
- const guideKind = parsed.id.startsWith("grid.") ? "Grid" : "Axis";
198
- validateUserId(value, `${guideKind} ${property} id`);
199
- }
200
- }
201
- if (parsed.kind === "title") {
202
- nonEmptyString(value, `Chart title ${parsed.path[0]}`);
203
- }
204
- }
1
+ export { validateSemanticValue } from "./semanticValidation/index.js";
@@ -9,14 +9,15 @@ import {
9
9
  } from "../../core/validation.js";
10
10
  import { normalizeRegressionParameters } from "../../grammar/regression/index.js";
11
11
  import { validateCurveInterpolation } from "../../grammar/curveCommands.js";
12
- import { hasDataset } from "../../selectors/index.js";
12
+ import { planDerivedDataRevision } from
13
+ "../../materialization/dataProvenance.js";
13
14
  import { findLayer } from "../../selectors/layers.js";
15
+ import { removeLegendKinds } from "../guides/legends/remove.js";
16
+ import { requireRegressionField } from "./resolve.js";
14
17
 
15
18
  const OPTIONS = Object.freeze([
16
- "target", "method", "degree", "span", "confidence", "interval", "band", "line"
17
- ]);
18
- const STATISTICAL_OPTIONS = Object.freeze([
19
- "method", "degree", "span", "confidence", "interval"
19
+ "target", "data", "x", "y", "groupBy", "method", "degree", "span",
20
+ "confidence", "interval", "band", "line"
20
21
  ]);
21
22
  const BAND_OPTIONS = Object.freeze([
22
23
  "color", "opacity", "stroke", "strokeWidth", "curve"
@@ -117,19 +118,137 @@ function resolveParameters(previous, args) {
117
118
  return normalizeRegressionParameters(raw);
118
119
  }
119
120
 
120
- function nextRevisionId(program, ownerId) {
121
- let revision = 1;
122
- while (hasDataset(program, `${ownerId}RegressionDataRevision${revision}`)) {
123
- revision += 1;
124
- }
125
- return `${ownerId}RegressionDataRevision${revision}`;
121
+ function removeOwnedColorLegends(program, target) {
122
+ const kinds = Object.entries(program.guideConfigs.legend ?? {})
123
+ .filter(([kind, config]) =>
124
+ config?.target === target &&
125
+ (
126
+ config.channels?.includes("color") ||
127
+ ["gradient", "interval"].includes(kind)
128
+ )
129
+ )
130
+ .map(([kind]) => kind);
131
+ return kinds.length === 0 ? program : removeLegendKinds(program, kinds);
126
132
  }
127
133
 
128
134
  function removeBand(program, id) {
129
- return program
135
+ const selectionIds = Object.entries(
136
+ program.materializationConfigs.selections ?? {}
137
+ ).filter(([, config]) => config.target === id).map(([selection]) => selection);
138
+ let next = program;
139
+ for (const [highlight, config] of Object.entries(
140
+ program.materializationConfigs.highlights ?? {}
141
+ )) {
142
+ if (config.target === id || selectionIds.includes(config.selection)) {
143
+ next = next._withoutMaterializationConfig(["highlights", highlight]);
144
+ }
145
+ }
146
+ for (const selection of selectionIds) {
147
+ next = next._withoutMaterializationConfig(["selections", selection]);
148
+ }
149
+ return next
130
150
  .editSemantic({ property: `layer[${id}]`, remove: true })
131
151
  .editGraphics({ target: id, remove: true })
132
- ._withoutMaterializationConfig(["marks", id]);
152
+ ._withoutMaterializationConfig(["marks", id])
153
+ ._withContext({
154
+ ...(program.context.currentMark === id ? { currentMark: undefined } : {}),
155
+ ...(selectionIds.includes(program.context.currentSelection)
156
+ ? { currentSelection: undefined }
157
+ : {})
158
+ });
159
+ }
160
+
161
+ function regressionColorScale(owner, current, groupBy) {
162
+ if (groupBy === undefined || groupBy === current.groupBy) {
163
+ return current.colorScale;
164
+ }
165
+ return owner.encoding?.color?.field === groupBy
166
+ ? owner.encoding.color.scale
167
+ : `${owner.id}RegressionColor`;
168
+ }
169
+
170
+ function updateRegressionLineSemantics(program, current, {
171
+ x,
172
+ y,
173
+ groupBy,
174
+ colorScale
175
+ }) {
176
+ let next = program
177
+ .editSemantic({
178
+ property: `layer[${current.lineId}].encoding.x.field`,
179
+ value: x
180
+ })
181
+ .editSemantic({
182
+ property: `layer[${current.lineId}].encoding.y.field`,
183
+ value: y
184
+ });
185
+ if (groupBy === undefined) {
186
+ if (findLayer(next, current.lineId).encoding?.group !== undefined) {
187
+ next = next.editSemantic({
188
+ property: `layer[${current.lineId}].encoding.group`,
189
+ remove: true
190
+ });
191
+ }
192
+ if (findLayer(next, current.lineId).encoding?.color !== undefined) {
193
+ next = next.editSemantic({
194
+ property: `layer[${current.lineId}].encoding.color`,
195
+ remove: true
196
+ });
197
+ next = removeOwnedColorLegends(next, current.lineId);
198
+ }
199
+ return next;
200
+ }
201
+ next = findLayer(next, current.lineId).encoding?.group === undefined
202
+ ? next.encodeGroup({ target: current.lineId, field: groupBy })
203
+ : next.editSemantic({
204
+ property: `layer[${current.lineId}].encoding.group.field`,
205
+ value: groupBy
206
+ });
207
+ const color = findLayer(next, current.lineId).encoding?.color;
208
+ if (color === undefined || color.scale !== colorScale) {
209
+ next = next.encodeColor({
210
+ target: current.lineId,
211
+ field: groupBy,
212
+ scale: { id: colorScale }
213
+ });
214
+ } else {
215
+ next = next.editSemantic({
216
+ property: `layer[${current.lineId}].encoding.color.field`,
217
+ value: groupBy
218
+ });
219
+ }
220
+ return next;
221
+ }
222
+
223
+ function updateRegressionBandSemantics(program, bandId, { x, groupBy }) {
224
+ if (bandId === undefined) return program;
225
+ let next = program.editSemantic({
226
+ property: `layer[${bandId}].encoding.x.field`,
227
+ value: x
228
+ });
229
+ const layer = findLayer(next, bandId);
230
+ if (groupBy === undefined && layer.encoding?.group !== undefined) {
231
+ next = next.editSemantic({
232
+ property: `layer[${bandId}].encoding.group`,
233
+ remove: true
234
+ });
235
+ } else if (groupBy !== undefined) {
236
+ next = layer.encoding?.group === undefined
237
+ ? next.encodeGroup({ target: bandId, field: groupBy })
238
+ : next.editSemantic({
239
+ property: `layer[${bandId}].encoding.group.field`,
240
+ value: groupBy
241
+ });
242
+ }
243
+ const config = next.markConfigs[bandId];
244
+ return next._withMarkConfig(bandId, {
245
+ ...config,
246
+ errorBand: {
247
+ ...config.errorBand,
248
+ position: { ...config.errorBand.position, field: x },
249
+ groupBy
250
+ }
251
+ });
133
252
  }
134
253
 
135
254
  export const editRegression = action(
@@ -144,10 +263,27 @@ export const editRegression = action(
144
263
  }
145
264
  const owner = resolveRegressionOwner(this, args.target);
146
265
  const current = this.markConfigs[owner.id].regression;
266
+ const source = Object.hasOwn(args, "data")
267
+ ? validateUserId(args.data, "Regression data id")
268
+ : current.source;
269
+ const x = Object.hasOwn(args, "x")
270
+ ? requireRegressionField(args.x, "Regression x")
271
+ : current.x;
272
+ const y = Object.hasOwn(args, "y")
273
+ ? requireRegressionField(args.y, "Regression y")
274
+ : current.y;
275
+ const groupBy = Object.hasOwn(args, "groupBy")
276
+ ? args.groupBy === false
277
+ ? undefined
278
+ : requireRegressionField(args.groupBy, "Regression groupBy")
279
+ : current.groupBy;
280
+ const colorScale = regressionColorScale(owner, current, groupBy);
147
281
  const parameters = resolveParameters(current.parameters, args);
148
- const changesStatistics = STATISTICAL_OPTIONS.some(
149
- key => Object.hasOwn(args, key)
150
- ) && JSON.stringify(parameters) !== JSON.stringify(current.parameters);
282
+ const changesProvenance = source !== current.source ||
283
+ x !== current.x || y !== current.y || groupBy !== current.groupBy;
284
+ const changesParameters = JSON.stringify(parameters) !==
285
+ JSON.stringify(current.parameters);
286
+ const changesStatistics = changesProvenance || changesParameters;
151
287
  const bandPatch = Object.hasOwn(args, "band")
152
288
  ? validateBandPatch(args.band)
153
289
  : undefined;
@@ -163,85 +299,108 @@ export const editRegression = action(
163
299
  (bandPatch === false
164
300
  ? false
165
301
  : hadBand || bandPatch !== undefined || current.parameters.method === "loess");
166
- let dataId = current.dataId;
167
- let next = this;
168
-
169
- if (changesStatistics) {
170
- dataId = nextRevisionId(this, owner.id);
171
- next = next.createRegressionData({
172
- id: dataId,
173
- source: current.source,
174
- x: current.x,
175
- y: current.y,
176
- ...(current.groupBy === undefined ? {} : { groupBy: current.groupBy }),
177
- ...parameters
178
- });
179
- }
302
+ const revision = changesStatistics
303
+ ? planDerivedDataRevision(this, {
304
+ owner: owner.id,
305
+ role: "RegressionData",
306
+ previous: current.dataId,
307
+ consumers: [
308
+ current.lineId,
309
+ ...(hadBand ? [current.bandId] : [])
310
+ ]
311
+ })
312
+ : undefined;
180
313
 
181
- let bandId = current.bandId;
182
- if (hadBand && !wantsBand) {
183
- next = removeBand(next, current.bandId);
184
- bandId = undefined;
185
- } else if (!hadBand && wantsBand) {
186
- bandId = `${owner.id}RegressionBands`;
187
- next = next.createRegressionBand({
188
- id: bandId,
189
- data: dataId,
190
- x: current.x,
191
- lower: "__regression_ci_lower",
192
- upper: "__regression_ci_upper",
193
- ...(current.groupBy === undefined ? {} : { groupBy: current.groupBy }),
194
- coordinate: current.coordinate,
195
- xScale: current.xScale,
196
- yScale: current.yScale,
197
- ...(bandPatch === undefined || bandPatch === false ? {} : bandPatch)
198
- });
199
- } else if (hadBand && changesStatistics) {
200
- const bandConfig = next.markConfigs[bandId];
201
- next = next
202
- .editSemantic({ property: `layer[${bandId}].data`, value: dataId })
203
- ._withMarkConfig(bandId, {
204
- ...bandConfig,
205
- errorBand: { ...bandConfig.errorBand, data: dataId }
314
+ const applyEdit = program => {
315
+ let dataId = current.dataId;
316
+ let next = program;
317
+ if (changesStatistics) {
318
+ dataId = revision.id;
319
+ next = next.createRegressionData({
320
+ id: dataId,
321
+ source,
322
+ x,
323
+ y,
324
+ ...(groupBy === undefined ? {} : { groupBy }),
325
+ ...parameters
206
326
  });
207
- }
327
+ for (const rebind of revision.rebinds) {
328
+ next = next.rebindLayerData(rebind);
329
+ }
330
+ }
208
331
 
209
- if (changesStatistics) {
210
- next = next.editSemantic({
211
- property: `layer[${current.lineId}].data`,
212
- value: dataId
213
- });
214
- }
332
+ let bandId = current.bandId;
333
+ if (hadBand && !wantsBand) {
334
+ next = removeBand(next, current.bandId);
335
+ bandId = undefined;
336
+ } else if (!hadBand && wantsBand) {
337
+ bandId = `${owner.id}RegressionBands`;
338
+ next = next.createRegressionBand({
339
+ id: bandId,
340
+ data: dataId,
341
+ x,
342
+ lower: "__regression_ci_lower",
343
+ upper: "__regression_ci_upper",
344
+ ...(groupBy === undefined ? {} : { groupBy }),
345
+ coordinate: current.coordinate,
346
+ xScale: current.xScale,
347
+ yScale: current.yScale,
348
+ ...(bandPatch === undefined || bandPatch === false ? {} : bandPatch)
349
+ });
350
+ } else if (hadBand && changesStatistics) {
351
+ const bandConfig = next.markConfigs[bandId];
352
+ next = next._withMarkConfig(bandId, {
353
+ ...bandConfig,
354
+ errorBand: { ...bandConfig.errorBand, data: dataId }
355
+ });
356
+ }
215
357
 
216
- if (bandId !== undefined) {
217
- if (
218
- bandPatch !== undefined &&
219
- bandPatch !== false &&
220
- Object.keys(bandPatch).length > 0 &&
221
- hadBand
222
- ) {
223
- next = next.editRegressionBand({ target: bandId, ...bandPatch });
358
+ if (changesProvenance) {
359
+ next = updateRegressionLineSemantics(next, current, {
360
+ x,
361
+ y,
362
+ groupBy,
363
+ colorScale
364
+ });
365
+ next = updateRegressionBandSemantics(next, bandId, { x, groupBy });
366
+ }
367
+ if (bandId !== undefined) {
368
+ if (
369
+ bandPatch !== undefined &&
370
+ bandPatch !== false &&
371
+ Object.keys(bandPatch).length > 0 &&
372
+ hadBand
373
+ ) {
374
+ next = next.editRegressionBand({ target: bandId, ...bandPatch });
375
+ } else if (changesStatistics) {
376
+ next = next.rematerializeAreaMark({ id: bandId });
377
+ }
378
+ }
379
+ if (linePatch !== undefined && Object.keys(linePatch).length > 0) {
380
+ next = next.editRegressionLine({ target: current.lineId, ...linePatch });
224
381
  } else if (changesStatistics) {
225
- next = next.rematerializeAreaMark({ id: bandId });
382
+ next = next.rematerializeLineMark({ id: current.lineId });
226
383
  }
227
- }
228
- if (linePatch !== undefined && Object.keys(linePatch).length > 0) {
229
- next = next.editRegressionLine({ target: current.lineId, ...linePatch });
230
- } else if (changesStatistics) {
231
- next = next.rematerializeLineMark({ id: current.lineId });
232
- }
233
384
 
234
- if (changesStatistics) {
235
- next = next.releaseDerivedData({ id: current.dataId });
236
- }
237
- return next._withMarkConfig(owner.id, {
238
- ...next.markConfigs[owner.id],
239
- regression: {
240
- ...current,
241
- dataId,
242
- bandId,
243
- parameters
385
+ if (changesStatistics) {
386
+ next = next.releaseDerivedData(revision.release);
244
387
  }
245
- });
388
+ return next._withMarkConfig(owner.id, {
389
+ ...next.markConfigs[owner.id],
390
+ regression: {
391
+ ...current,
392
+ source,
393
+ x,
394
+ y,
395
+ groupBy,
396
+ colorScale,
397
+ dataId,
398
+ bandId,
399
+ parameters
400
+ }
401
+ });
402
+ };
403
+ if (changesStatistics || hadBand !== wantsBand) applyEdit(this);
404
+ return applyEdit(this);
246
405
  }
247
406
  );
@@ -19,6 +19,16 @@ export function findScaleConsumers(program, id) {
19
19
  const encoding = layer.encoding?.[channel];
20
20
  if (encoding?.scale === id) consumers.push({ layer, channel, encoding });
21
21
  }
22
+ for (const dimension of layer.encoding?.parallel?.dimensions ?? []) {
23
+ if (dimension.scale === id) {
24
+ consumers.push({
25
+ layer,
26
+ channel: "y",
27
+ encoding: dimension,
28
+ role: "parallelDimension"
29
+ });
30
+ }
31
+ }
22
32
  }
23
33
  return consumers;
24
34
  }
@@ -46,6 +56,14 @@ export function readConsumerFieldValues(
46
56
  scale = findScale(program, consumer.encoding.scale)
47
57
  ) {
48
58
  const allowUnknown = Object.hasOwn(scale, "unknown");
59
+ if (consumer.role === "parallelDimension") {
60
+ return readScaleField(
61
+ dataset.values,
62
+ consumer.encoding.field,
63
+ consumer.encoding.fieldType,
64
+ { allowUnknown: true }
65
+ );
66
+ }
49
67
  if (isDirectCategoricalConsumer(consumer)) {
50
68
  return allowUnknown
51
69
  ? readScaleField(
@@ -3,7 +3,10 @@ import {
3
3
  BAR_GRAINS,
4
4
  resolveBarGrain
5
5
  } from "../../../grammar/bars/policy.js";
6
- import { deriveLineSeries } from "../../../grammar/lineSeries.js";
6
+ import {
7
+ deriveLineSeries,
8
+ deriveLineSeriesFieldValues
9
+ } from "../../../grammar/lineSeries.js";
7
10
  import { isAggregate } from "../../../grammar/aggregate.js";
8
11
  import {
9
12
  resolveRectConsumerValues
@@ -22,12 +25,20 @@ export function resolveMarkFamilyConsumerValues(consumer, dataset) {
22
25
  }
23
26
  if (
24
27
  consumer.layer.mark?.type === "line" &&
25
- isAggregate(consumer.layer.encoding?.y?.aggregate)
28
+ (isAggregate(consumer.layer.encoding?.y?.aggregate) ||
29
+ consumer.channel === "strokeWidth")
26
30
  ) {
27
31
  const derived = deriveLineSeries(dataset.values, consumer.layer);
28
32
  return {
29
33
  matched: true,
30
- values: consumer.channel === "x" ? derived.xValues : derived.yValues
34
+ values: consumer.channel === "strokeWidth"
35
+ ? deriveLineSeriesFieldValues(
36
+ dataset.values,
37
+ consumer.layer,
38
+ derived,
39
+ consumer.encoding.field
40
+ )
41
+ : consumer.channel === "x" ? derived.xValues : derived.yValues
31
42
  };
32
43
  }
33
44
  if (resolveBarGrain(consumer.layer) === BAR_GRAINS.aggregate) {