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,74 @@
1
+ import {
2
+ validateContinuousColorInterpolation,
3
+ validateScalePropertyForType,
4
+ validateSemanticScaleDomain,
5
+ validateSemanticScaleRange,
6
+ validateSemanticScaleType
7
+ } from "../../../grammar/scales/index.js";
8
+ import { findSemanticScale } from "../../../selectors/scales.js";
9
+
10
+ function validateOwnedProperty(existing, property) {
11
+ if (existing?.type !== undefined) {
12
+ validateScalePropertyForType(existing.type, property);
13
+ }
14
+ }
15
+
16
+ export function validateScaleSemanticValue(program, parsed, value) {
17
+ const property = parsed.path.join(".");
18
+ const existing = findSemanticScale(program, parsed.id);
19
+ if (property === "type") {
20
+ validateSemanticScaleType(value);
21
+ for (const owned of [
22
+ "nice", "zero", "clamp", "base", "exponent", "constant",
23
+ "paddingInner", "paddingOuter", "padding", "align"
24
+ ]) {
25
+ if (existing?.[owned] !== undefined) validateScalePropertyForType(value, owned);
26
+ }
27
+ return;
28
+ }
29
+ if (property === "domain") return validateSemanticScaleDomain(value);
30
+ if (property === "range") return validateSemanticScaleRange(value);
31
+ if (["nice", "zero", "clamp", "reverse"].includes(property)) {
32
+ if (typeof value !== "boolean") {
33
+ throw new TypeError(`Scale ${property} must be a boolean.`);
34
+ }
35
+ if (property !== "reverse") validateOwnedProperty(existing, property);
36
+ return;
37
+ }
38
+ if (["base", "exponent", "constant"].includes(property)) {
39
+ if (!Number.isFinite(value) || value <= 0) {
40
+ throw new RangeError(`Scale ${property} must be a positive finite number.`);
41
+ }
42
+ if (property === "base" && value === 1) {
43
+ throw new RangeError("Scale base must not equal 1.");
44
+ }
45
+ validateOwnedProperty(existing, property);
46
+ return;
47
+ }
48
+ if (property === "interpolate") {
49
+ validateContinuousColorInterpolation(value);
50
+ return;
51
+ }
52
+ if (property === "paddingInner") {
53
+ if (!Number.isFinite(value) || value < 0 || value >= 1) {
54
+ throw new RangeError(
55
+ "Scale paddingInner must be from 0 (inclusive) to 1 (exclusive)."
56
+ );
57
+ }
58
+ validateOwnedProperty(existing, property);
59
+ return;
60
+ }
61
+ if (property === "paddingOuter" || property === "padding") {
62
+ if (!Number.isFinite(value) || value < 0) {
63
+ throw new RangeError(`Scale ${property} must be a non-negative finite number.`);
64
+ }
65
+ validateOwnedProperty(existing, property);
66
+ return;
67
+ }
68
+ if (property === "align") {
69
+ if (!Number.isFinite(value) || value < 0 || value > 1) {
70
+ throw new RangeError("Scale align must be between 0 and 1.");
71
+ }
72
+ validateOwnedProperty(existing, property);
73
+ }
74
+ }
@@ -0,0 +1,5 @@
1
+ export function validateNonEmptySemanticString(value, label) {
2
+ if (typeof value !== "string" || value.length === 0) {
3
+ throw new TypeError(`${label} must be a non-empty string.`);
4
+ }
5
+ }
@@ -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,7 +9,8 @@ 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";
14
15
 
15
16
  const OPTIONS = Object.freeze([
@@ -117,14 +118,6 @@ 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}`;
126
- }
127
-
128
121
  function removeBand(program, id) {
129
122
  return program
130
123
  .editSemantic({ property: `layer[${id}]`, remove: true })
@@ -165,9 +158,20 @@ export const editRegression = action(
165
158
  : hadBand || bandPatch !== undefined || current.parameters.method === "loess");
166
159
  let dataId = current.dataId;
167
160
  let next = this;
161
+ const revision = changesStatistics
162
+ ? planDerivedDataRevision(this, {
163
+ owner: owner.id,
164
+ role: "RegressionData",
165
+ previous: current.dataId,
166
+ consumers: [
167
+ current.lineId,
168
+ ...(hadBand ? [current.bandId] : [])
169
+ ]
170
+ })
171
+ : undefined;
168
172
 
169
173
  if (changesStatistics) {
170
- dataId = nextRevisionId(this, owner.id);
174
+ dataId = revision.id;
171
175
  next = next.createRegressionData({
172
176
  id: dataId,
173
177
  source: current.source,
@@ -176,6 +180,9 @@ export const editRegression = action(
176
180
  ...(current.groupBy === undefined ? {} : { groupBy: current.groupBy }),
177
181
  ...parameters
178
182
  });
183
+ for (const rebind of revision.rebinds) {
184
+ next = next.rebindLayerData(rebind);
185
+ }
179
186
  }
180
187
 
181
188
  let bandId = current.bandId;
@@ -198,21 +205,12 @@ export const editRegression = action(
198
205
  });
199
206
  } else if (hadBand && changesStatistics) {
200
207
  const bandConfig = next.markConfigs[bandId];
201
- next = next
202
- .editSemantic({ property: `layer[${bandId}].data`, value: dataId })
203
- ._withMarkConfig(bandId, {
208
+ next = next._withMarkConfig(bandId, {
204
209
  ...bandConfig,
205
210
  errorBand: { ...bandConfig.errorBand, data: dataId }
206
211
  });
207
212
  }
208
213
 
209
- if (changesStatistics) {
210
- next = next.editSemantic({
211
- property: `layer[${current.lineId}].data`,
212
- value: dataId
213
- });
214
- }
215
-
216
214
  if (bandId !== undefined) {
217
215
  if (
218
216
  bandPatch !== undefined &&
@@ -232,7 +230,7 @@ export const editRegression = action(
232
230
  }
233
231
 
234
232
  if (changesStatistics) {
235
- next = next.releaseDerivedData({ id: current.dataId });
233
+ next = next.releaseDerivedData(revision.release);
236
234
  }
237
235
  return next._withMarkConfig(owner.id, {
238
236
  ...next.markConfigs[owner.id],
@@ -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) {
@@ -13,6 +13,7 @@ import {
13
13
  validateShapeRange,
14
14
  validateSizeRange,
15
15
  validateStrokeDashRange,
16
+ validateStrokeWidthRange,
16
17
  validateSequentialColorRange,
17
18
  validateDiscretizedColorDomain,
18
19
  validateDiscretizedColorRange,
@@ -314,6 +315,35 @@ export function resolveOpacityScaleDefinition(program, options) {
314
315
  return withScaleUnknown(scale, { ...existing, ...options }, "opacity");
315
316
  }
316
317
 
318
+ export function resolveStrokeWidthScaleDefinition(program, options) {
319
+ optionsObject(options);
320
+ validateKeys(options, OPACITY_OPTIONS, "scale");
321
+ const id = validateUserId(options.id ?? "strokeWidth", "Scale id");
322
+ const existing = findSemanticScale(program, id);
323
+ const type = options.type ?? existing?.type ?? "linear";
324
+ validateScaleTypeForRole(type, SCALE_ROLES.quantitativePosition);
325
+ for (const property of ["nice", "zero", "clamp", "reverse"]) {
326
+ if (options[property] !== undefined && typeof options[property] !== "boolean") {
327
+ throw new TypeError(`Scale ${property} must be a boolean.`);
328
+ }
329
+ }
330
+ const domain = validateScaleDomain(options.domain ?? existing?.domain ?? "auto");
331
+ if (domain !== "auto" && domain.some(value => value < 0)) {
332
+ throw new RangeError("StrokeWidth scale domain cannot contain negative values.");
333
+ }
334
+ const scale = {
335
+ id,
336
+ type,
337
+ domain,
338
+ range: validateStrokeWidthRange(options.range ?? existing?.range ?? "auto")
339
+ };
340
+ for (const property of ["nice", "zero", "clamp", "reverse", "base", "exponent", "constant"]) {
341
+ const value = options[property] ?? existing?.[property];
342
+ if (value !== undefined) scale[property] = value;
343
+ }
344
+ return scale;
345
+ }
346
+
317
347
  export function resolveOffsetScaleDefinition(program, options, channel = "xOffset") {
318
348
  optionsObject(options);
319
349
  validateKeys(options, [...BASE_OPTIONS, "unknown"], "scale");