ggaction 0.0.2 → 0.0.4

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 (242) hide show
  1. package/CHANGELOG.md +55 -2
  2. package/README.md +95 -72
  3. package/package.json +15 -5
  4. package/src/actions/boxPlots/components.js +3 -3
  5. package/src/actions/boxPlots/edit.js +277 -0
  6. package/src/actions/boxPlots/index.js +2 -0
  7. package/src/actions/boxPlots/materialize.js +12 -1
  8. package/src/actions/boxPlots/options.js +25 -14
  9. package/src/actions/canvas/actions.js +21 -17
  10. package/src/actions/composition/actions.js +247 -0
  11. package/src/actions/composition/index.js +1 -0
  12. package/src/actions/coordinates/actions.js +2 -5
  13. package/src/actions/data/density.js +7 -1
  14. package/src/actions/data/filter.js +3 -1
  15. package/src/actions/data/regression.js +1 -1
  16. package/src/actions/encodings/appearance.js +12 -1
  17. package/src/actions/encodings/barWidth.js +5 -3
  18. package/src/actions/encodings/color/continuous.js +137 -0
  19. package/src/actions/encodings/color/index.js +151 -0
  20. package/src/actions/encodings/color/layout.js +91 -0
  21. package/src/actions/encodings/color/policy.js +88 -0
  22. package/src/actions/encodings/index.js +4 -2
  23. package/src/actions/encodings/offset.js +91 -73
  24. package/src/actions/encodings/position/apply.js +66 -0
  25. package/src/actions/encodings/position/index.js +130 -0
  26. package/src/actions/encodings/position/policies/arc.js +26 -0
  27. package/src/actions/encodings/position/policies/bar.js +9 -5
  28. package/src/actions/encodings/position/policies/index.js +5 -1
  29. package/src/actions/encodings/position/policies/line.js +20 -3
  30. package/src/actions/encodings/position/resolve.js +58 -9
  31. package/src/actions/encodings/ranged.js +30 -8
  32. package/src/actions/encodings/shared.js +17 -21
  33. package/src/actions/encodings/strokeDash.js +1 -1
  34. package/src/actions/encodings/text.js +73 -0
  35. package/src/actions/errorBands/create.js +37 -29
  36. package/src/actions/errorBands/edit.js +206 -0
  37. package/src/actions/errorBands/index.js +9 -0
  38. package/src/actions/errorBands/options.js +39 -0
  39. package/src/actions/errorBars/create.js +50 -43
  40. package/src/actions/errorBars/edit.js +130 -0
  41. package/src/actions/errorBars/index.js +3 -0
  42. package/src/actions/errorBars/options.js +52 -0
  43. package/src/actions/facets/actions.js +162 -0
  44. package/src/actions/facets/derive.js +200 -0
  45. package/src/actions/facets/guides.js +16 -0
  46. package/src/actions/facets/index.js +1 -0
  47. package/src/actions/facets/replay.js +69 -0
  48. package/src/actions/guides/applicability.js +113 -0
  49. package/src/actions/guides/axes/axes.js +70 -16
  50. package/src/actions/guides/axes/edit.js +126 -0
  51. package/src/actions/guides/axes/index.js +12 -0
  52. package/src/actions/guides/axes/labels.js +22 -19
  53. package/src/actions/guides/axes/remove.js +60 -0
  54. package/src/actions/guides/axes/tickGroups.js +7 -5
  55. package/src/actions/guides/axes/ticks.js +10 -5
  56. package/src/actions/guides/axes/titles.js +8 -6
  57. package/src/actions/guides/grids/grid.js +144 -15
  58. package/src/actions/guides/grids/resolve.js +6 -14
  59. package/src/actions/guides/guides.js +17 -75
  60. package/src/actions/guides/index.js +6 -0
  61. package/src/actions/guides/legends/categorical/actions.js +48 -9
  62. package/src/actions/guides/legends/categorical/components.js +16 -16
  63. package/src/actions/guides/legends/categorical/index.js +14 -14
  64. package/src/actions/guides/legends/categorical/layout.js +7 -3
  65. package/src/actions/guides/legends/categorical/options.js +2 -5
  66. package/src/actions/guides/legends/categorical/resolve.js +2 -2
  67. package/src/actions/guides/legends/categorical/symbols.js +14 -14
  68. package/src/actions/guides/legends/continuous/common.js +7 -7
  69. package/src/actions/guides/legends/continuous/interval.js +6 -6
  70. package/src/actions/guides/legends/continuous/opacity.js +1 -1
  71. package/src/actions/guides/legends/edit.js +4 -26
  72. package/src/actions/guides/legends/focused.js +63 -0
  73. package/src/actions/guides/legends/index.js +4 -0
  74. package/src/actions/guides/legends/remove.js +46 -0
  75. package/src/actions/guides/legends/size.js +13 -15
  76. package/src/actions/guides/legends/target.js +25 -0
  77. package/src/actions/guides/polar/axes/facade.js +219 -0
  78. package/src/actions/guides/polar/axes/index.js +55 -0
  79. package/src/actions/guides/polar/axes/labels.js +188 -0
  80. package/src/actions/guides/polar/axes/lines.js +113 -0
  81. package/src/actions/guides/polar/axes/shared.js +112 -0
  82. package/src/actions/guides/polar/axes/ticks.js +164 -0
  83. package/src/actions/guides/polar/axes/titles.js +165 -0
  84. package/src/actions/guides/polar/grids.js +252 -0
  85. package/src/actions/guides/polar/index.js +2 -0
  86. package/src/actions/guides/polar/resolve.js +269 -0
  87. package/src/actions/guides/tickValues.js +1 -1
  88. package/src/actions/index.js +4 -0
  89. package/src/actions/intervals/resolve.js +1 -1
  90. package/src/actions/marks/arc/actions.js +239 -0
  91. package/src/actions/marks/arc/index.js +1 -0
  92. package/src/actions/marks/{area.js → area/actions.js} +51 -38
  93. package/src/actions/marks/area/index.js +4 -0
  94. package/src/actions/marks/bar/create.js +26 -5
  95. package/src/actions/marks/bar/edit.js +1 -1
  96. package/src/actions/marks/bar/materialize.js +25 -31
  97. package/src/actions/marks/index.js +12 -4
  98. package/src/actions/marks/lifecycle.js +27 -0
  99. package/src/actions/marks/line/actions.js +373 -0
  100. package/src/actions/marks/line/index.js +1 -0
  101. package/src/actions/marks/{point.js → point/actions.js} +67 -108
  102. package/src/actions/marks/point/index.js +1 -0
  103. package/src/actions/marks/rect/actions.js +165 -0
  104. package/src/actions/marks/rect/index.js +1 -0
  105. package/src/actions/marks/remove.js +214 -0
  106. package/src/actions/marks/{rule.js → rule/actions.js} +34 -28
  107. package/src/actions/marks/rule/index.js +1 -0
  108. package/src/actions/marks/shared.js +156 -25
  109. package/src/actions/marks/text/actions.js +192 -0
  110. package/src/actions/marks/text/index.js +1 -0
  111. package/src/actions/primitives/createGraphics.js +13 -6
  112. package/src/actions/primitives/editGraphics.js +10 -3
  113. package/src/actions/primitives/semantic.js +26 -3
  114. package/src/actions/primitives/semanticValue.js +26 -4
  115. package/src/actions/regression/components.js +2 -2
  116. package/src/actions/regression/create.js +19 -2
  117. package/src/actions/regression/edit.js +247 -0
  118. package/src/actions/regression/index.js +3 -0
  119. package/src/actions/regression/resolve.js +3 -1
  120. package/src/actions/scales/consumers/common.js +91 -0
  121. package/src/actions/scales/consumers/families.js +47 -0
  122. package/src/actions/scales/consumers/index.js +41 -0
  123. package/src/actions/scales/{consumers.js → consumers/seriesLayout.js} +14 -127
  124. package/src/actions/scales/create.js +1 -1
  125. package/src/actions/scales/definitions.js +15 -6
  126. package/src/actions/scales/edit.js +45 -22
  127. package/src/actions/scales/materialize.js +58 -409
  128. package/src/actions/selection/actions.js +38 -24
  129. package/src/actions/selection/index.js +2 -0
  130. package/src/actions/titles/actions.js +63 -2
  131. package/src/actions/titles/index.js +2 -0
  132. package/src/actions/titles/resolve.js +12 -17
  133. package/src/composition.js +10 -0
  134. package/src/core/ChartProgram.js +49 -65
  135. package/src/core/action.js +10 -0
  136. package/src/core/compositionState.js +169 -0
  137. package/src/core/materializationState.js +65 -0
  138. package/src/core/programState.js +28 -0
  139. package/src/core/validation.js +16 -0
  140. package/src/core/vocabulary.js +154 -4
  141. package/src/grammar/arcs.js +154 -0
  142. package/src/grammar/areaSeries.js +1 -1
  143. package/src/grammar/bars/aggregate.js +3 -3
  144. package/src/grammar/bars/geometry.js +3 -3
  145. package/src/grammar/bars/policy.js +7 -1
  146. package/src/grammar/coordinates.js +3 -7
  147. package/src/grammar/density.js +21 -2
  148. package/src/grammar/facets/dependencies.js +185 -0
  149. package/src/grammar/facets/guides.js +257 -0
  150. package/src/grammar/facets/index.js +122 -0
  151. package/src/grammar/facets/scales.js +254 -0
  152. package/src/grammar/interval.js +2 -96
  153. package/src/grammar/lineSeries.js +112 -4
  154. package/src/grammar/palettes.js +5 -7
  155. package/src/grammar/polar.js +102 -0
  156. package/src/grammar/polarGuides.js +243 -0
  157. package/src/grammar/polarLineCommands.js +43 -0
  158. package/src/grammar/polarPaths.js +161 -0
  159. package/src/grammar/positionCompatibility.js +18 -2
  160. package/src/grammar/rects.js +26 -0
  161. package/src/grammar/regression/derive.js +97 -0
  162. package/src/grammar/regression/index.js +22 -0
  163. package/src/grammar/regression/models.js +282 -0
  164. package/src/grammar/regression/parameters.js +106 -0
  165. package/src/grammar/rules.js +1 -1
  166. package/src/grammar/scales/color.js +4 -2
  167. package/src/grammar/scales/continuous.js +6 -1
  168. package/src/grammar/scales/fields.js +7 -0
  169. package/src/grammar/scales/ordinal.js +5 -2
  170. package/src/grammar/scales/policies.js +1 -1
  171. package/src/grammar/schemas/graphic.js +1 -1
  172. package/src/grammar/schemas/graphicBounds.js +64 -9
  173. package/src/grammar/schemas/graphicTree.js +44 -10
  174. package/src/grammar/schemas/semanticPath.js +31 -9
  175. package/src/grammar/seriesLayout.js +13 -2
  176. package/src/grammar/statistics/studentT.js +98 -0
  177. package/src/grammar/text.js +71 -0
  178. package/src/grammar/ticks.js +50 -13
  179. package/src/grammar/transforms.js +1 -1
  180. package/src/index.js +1 -0
  181. package/src/layout/composition.js +310 -0
  182. package/src/layout/facets.js +131 -0
  183. package/src/layout/title.js +6 -0
  184. package/src/materialization/bars/aggregate.js +6 -3
  185. package/src/materialization/bars/grouped.js +75 -37
  186. package/src/materialization/bars/histogram.js +1 -1
  187. package/src/materialization/bars/ranged.js +1 -1
  188. package/src/materialization/composition.js +152 -0
  189. package/src/materialization/compositionSnapshot.js +114 -0
  190. package/src/materialization/density.js +1 -1
  191. package/src/materialization/dependencies.js +47 -73
  192. package/src/materialization/encodings.js +14 -28
  193. package/src/materialization/facetGuides/index.js +2 -0
  194. package/src/materialization/facetGuides/legacyCategorical.js +125 -0
  195. package/src/materialization/facetGuides/placement.js +126 -0
  196. package/src/materialization/facetGuides/preparation.js +188 -0
  197. package/src/materialization/facets.js +214 -0
  198. package/src/materialization/graphicHierarchy.js +15 -5
  199. package/src/materialization/guides/resources.js +46 -0
  200. package/src/materialization/marks/capabilities.js +141 -0
  201. package/src/materialization/marks/index.js +105 -0
  202. package/src/materialization/marks/policies.js +87 -0
  203. package/src/materialization/planner.js +105 -12
  204. package/src/materialization/rect.js +138 -0
  205. package/src/materialization/rectConfig.js +38 -0
  206. package/src/materialization/rowEncoding.js +33 -0
  207. package/src/materialization/scaleGuideDependencies.js +94 -0
  208. package/src/materialization/scales/map.js +40 -0
  209. package/src/materialization/scales/policies/arc.js +37 -0
  210. package/src/materialization/scales/policies/bar.js +132 -0
  211. package/src/materialization/scales/policies/series.js +76 -0
  212. package/src/materialization/scales/resolve.js +326 -0
  213. package/src/materialization/selection/items/arc.js +29 -0
  214. package/src/materialization/selection/items/bar.js +255 -0
  215. package/src/materialization/selection/items/common.js +131 -0
  216. package/src/materialization/selection/items/index.js +6 -0
  217. package/src/materialization/selection/items/path.js +59 -0
  218. package/src/materialization/selection/items/point.js +55 -0
  219. package/src/materialization/selection/items/rect.js +23 -0
  220. package/src/materialization/selection/items/rule.js +25 -0
  221. package/src/materialization/selection/policies/arc.js +10 -0
  222. package/src/materialization/selection/policies/area.js +1 -1
  223. package/src/materialization/selection/policies/bar.js +1 -1
  224. package/src/materialization/selection/policies/index.js +5 -1
  225. package/src/materialization/selection/policies/line.js +1 -1
  226. package/src/materialization/selection/policies/point.js +1 -1
  227. package/src/materialization/selection/policies/rect.js +10 -0
  228. package/src/materialization/selection/policies/rule.js +1 -1
  229. package/src/materialization/selection/styles.js +25 -17
  230. package/src/materialization/text.js +156 -0
  231. package/src/renderers/canvas/index.js +68 -22
  232. package/src/renderers/canvas/text.js +14 -1
  233. package/types/extension.d.ts +5 -1
  234. package/types/index.d.ts +56 -1
  235. package/types/program.d.ts +664 -27
  236. package/src/actions/encodings/color.js +0 -329
  237. package/src/actions/encodings/position.js +0 -176
  238. package/src/actions/marks/line.js +0 -248
  239. package/src/grammar/regression.js +0 -578
  240. package/src/grammar/scales.js +0 -1
  241. package/src/materialization/marks.js +0 -110
  242. package/src/materialization/selection/items.js +0 -503
@@ -0,0 +1,247 @@
1
+ import { action } from "../../core/action.js";
2
+ import { isPlainObject } from "../../core/immutable.js";
3
+ import { validateUserId } from "../../core/identifiers.js";
4
+ import {
5
+ validateKeys,
6
+ validateNonEmptyString,
7
+ validateNonNegativeFinite,
8
+ validateUnitInterval
9
+ } from "../../core/validation.js";
10
+ import { normalizeRegressionParameters } from "../../grammar/regression/index.js";
11
+ import { validateCurveInterpolation } from "../../grammar/curveCommands.js";
12
+ import { hasDataset } from "../../selectors/index.js";
13
+ import { findLayer } from "../../selectors/layers.js";
14
+
15
+ 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"
20
+ ]);
21
+ const BAND_OPTIONS = Object.freeze([
22
+ "color", "opacity", "stroke", "strokeWidth", "curve"
23
+ ]);
24
+ const LINE_OPTIONS = Object.freeze(["strokeWidth", "curve"]);
25
+
26
+ function resolveRegressionOwner(program, requested) {
27
+ const eligible = program.semanticSpec.layers.filter(
28
+ layer => program.markConfigs[layer.id]?.regression !== undefined
29
+ );
30
+ if (requested !== undefined) {
31
+ const id = validateUserId(requested, "Regression owner id");
32
+ const layer = findLayer(program, id);
33
+ if (layer === undefined || !eligible.includes(layer)) {
34
+ throw new Error(`Unknown regression owner "${id}".`);
35
+ }
36
+ return layer;
37
+ }
38
+ const current = findLayer(program, program.context.currentMark);
39
+ if (current !== undefined && eligible.includes(current)) return current;
40
+ if (eligible.length === 1) return eligible[0];
41
+ if (eligible.length === 0) throw new Error("No regression owner is available.");
42
+ throw new Error("Regression owner is ambiguous; provide target.");
43
+ }
44
+
45
+ function requirePatch(value, keys, label) {
46
+ if (!isPlainObject(value)) {
47
+ throw new TypeError(`${label} must be a plain object.`);
48
+ }
49
+ validateKeys(value, keys, label);
50
+ return value;
51
+ }
52
+
53
+ function validateBandPatch(value) {
54
+ if (value === false) return false;
55
+ const patch = requirePatch(value, BAND_OPTIONS, "editRegression band");
56
+ if (Object.hasOwn(patch, "color")) {
57
+ validateNonEmptyString(patch.color, "Regression band color");
58
+ }
59
+ if (Object.hasOwn(patch, "opacity")) {
60
+ validateUnitInterval(patch.opacity, "Regression band opacity");
61
+ }
62
+ if (Object.hasOwn(patch, "stroke")) {
63
+ if (patch.stroke !== false) {
64
+ validateNonEmptyString(patch.stroke, "Regression band stroke");
65
+ }
66
+ }
67
+ if (Object.hasOwn(patch, "strokeWidth")) {
68
+ validateNonNegativeFinite(
69
+ patch.strokeWidth,
70
+ "Regression band strokeWidth"
71
+ );
72
+ }
73
+ if (patch.stroke === false && Object.hasOwn(patch, "strokeWidth")) {
74
+ throw new Error(
75
+ "editRegression band cannot set strokeWidth while removing stroke."
76
+ );
77
+ }
78
+ if (Object.hasOwn(patch, "curve")) {
79
+ validateCurveInterpolation(patch.curve);
80
+ }
81
+ return patch;
82
+ }
83
+
84
+ function validateLinePatch(value) {
85
+ const patch = requirePatch(value, LINE_OPTIONS, "editRegression line");
86
+ if (Object.hasOwn(patch, "strokeWidth")) {
87
+ validateNonNegativeFinite(
88
+ patch.strokeWidth,
89
+ "Regression line strokeWidth"
90
+ );
91
+ }
92
+ if (Object.hasOwn(patch, "curve")) {
93
+ validateCurveInterpolation(patch.curve);
94
+ }
95
+ return patch;
96
+ }
97
+
98
+ function resolveParameters(previous, args) {
99
+ const method = args.method ?? previous.method;
100
+ const raw = { method };
101
+ for (const key of ["degree", "span", "confidence", "interval"]) {
102
+ if (Object.hasOwn(args, key)) raw[key] = args[key];
103
+ }
104
+ if (method === previous.method) {
105
+ for (const key of ["degree", "span", "confidence", "interval"]) {
106
+ if (!Object.hasOwn(raw, key) && Object.hasOwn(previous, key)) {
107
+ raw[key] = previous[key];
108
+ }
109
+ }
110
+ } else if (method !== "loess" && previous.method !== "loess") {
111
+ for (const key of ["confidence", "interval"]) {
112
+ if (!Object.hasOwn(raw, key) && Object.hasOwn(previous, key)) {
113
+ raw[key] = previous[key];
114
+ }
115
+ }
116
+ }
117
+ return normalizeRegressionParameters(raw);
118
+ }
119
+
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
+ function removeBand(program, id) {
129
+ return program
130
+ .editSemantic({ property: `layer[${id}]`, remove: true })
131
+ .editGraphics({ target: id, remove: true })
132
+ ._withoutMaterializationConfig(["marks", id]);
133
+ }
134
+
135
+ export const editRegression = action(
136
+ {
137
+ op: "editRegression",
138
+ description: "Revise a regression model and its owned visible components."
139
+ },
140
+ function (args = {}) {
141
+ validateKeys(args, OPTIONS, "editRegression");
142
+ if (!OPTIONS.slice(1).some(key => Object.hasOwn(args, key))) {
143
+ throw new Error("editRegression requires a statistical or component option.");
144
+ }
145
+ const owner = resolveRegressionOwner(this, args.target);
146
+ const current = this.markConfigs[owner.id].regression;
147
+ 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);
151
+ const bandPatch = Object.hasOwn(args, "band")
152
+ ? validateBandPatch(args.band)
153
+ : undefined;
154
+ const linePatch = Object.hasOwn(args, "line")
155
+ ? validateLinePatch(args.line)
156
+ : undefined;
157
+ if (parameters.method === "loess" && bandPatch !== undefined && bandPatch !== false) {
158
+ throw new Error("LOESS regression does not support a band object.");
159
+ }
160
+
161
+ const hadBand = current.bandId !== undefined;
162
+ const wantsBand = parameters.method !== "loess" &&
163
+ (bandPatch === false
164
+ ? false
165
+ : 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
+ }
180
+
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 }
206
+ });
207
+ }
208
+
209
+ if (changesStatistics) {
210
+ next = next.editSemantic({
211
+ property: `layer[${current.lineId}].data`,
212
+ value: dataId
213
+ });
214
+ }
215
+
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 });
224
+ } else if (changesStatistics) {
225
+ next = next.rematerializeAreaMark({ id: bandId });
226
+ }
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
+
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
244
+ }
245
+ });
246
+ }
247
+ );
@@ -5,6 +5,7 @@ import {
5
5
  editRegressionLine
6
6
  } from "./components.js";
7
7
  import { createRegression } from "./create.js";
8
+ import { editRegression } from "./edit.js";
8
9
 
9
10
  export {
10
11
  createRegressionBand,
@@ -13,6 +14,7 @@ export {
13
14
  editRegressionLine
14
15
  } from "./components.js";
15
16
  export { createRegression } from "./create.js";
17
+ export { editRegression } from "./edit.js";
16
18
 
17
19
  export function registerRegressionActions(ProgramClass) {
18
20
  ProgramClass.prototype.createRegressionBand = createRegressionBand;
@@ -20,4 +22,5 @@ export function registerRegressionActions(ProgramClass) {
20
22
  ProgramClass.prototype.editRegressionBand = editRegressionBand;
21
23
  ProgramClass.prototype.editRegressionLine = editRegressionLine;
22
24
  ProgramClass.prototype.createRegression = createRegression;
25
+ ProgramClass.prototype.editRegression = editRegression;
23
26
  }
@@ -47,7 +47,9 @@ export function inferRegressionGroup(layer, args) {
47
47
  }
48
48
  const candidates = [...new Set(
49
49
  [layer.encoding?.color, layer.encoding?.shape]
50
- .filter(encoding => encoding?.fieldType === "nominal")
50
+ .filter(encoding =>
51
+ ["nominal", "ordinal"].includes(encoding?.fieldType)
52
+ )
51
53
  .map(encoding => encoding.field)
52
54
  )];
53
55
  if (candidates.length > 1) {
@@ -0,0 +1,91 @@
1
+ import {
2
+ readNominalField,
3
+ readQuantitativeField,
4
+ readScaleField,
5
+ readTemporalField
6
+ } from "../../../grammar/scales/index.js";
7
+ import { findDataset } from "../../../selectors/datasets.js";
8
+ import { requireSemanticScale } from "../../../selectors/scales.js";
9
+ import { SCALED_ENCODING_CHANNELS } from "../../../core/vocabulary.js";
10
+
11
+ export function findScale(program, id) {
12
+ return requireSemanticScale(program, id);
13
+ }
14
+
15
+ export function findScaleConsumers(program, id) {
16
+ const consumers = [];
17
+ for (const layer of program.semanticSpec.layers) {
18
+ for (const channel of SCALED_ENCODING_CHANNELS) {
19
+ const encoding = layer.encoding?.[channel];
20
+ if (encoding?.scale === id) consumers.push({ layer, channel, encoding });
21
+ }
22
+ }
23
+ return consumers;
24
+ }
25
+
26
+ export function requireConsumerDataset(program, consumer) {
27
+ const dataset = findDataset(program, consumer.layer.data);
28
+ if (dataset === undefined) {
29
+ throw new Error(
30
+ `Mark "${consumer.layer.id}" references unknown dataset "${consumer.layer.data}".`
31
+ );
32
+ }
33
+ return dataset;
34
+ }
35
+
36
+ export function isDirectCategoricalConsumer(consumer) {
37
+ return ["color", "strokeDash", "xOffset", "yOffset", "shape"].includes(
38
+ consumer.channel
39
+ ) && consumer.encoding.fieldType === "nominal";
40
+ }
41
+
42
+ export function readConsumerFieldValues(
43
+ program,
44
+ consumer,
45
+ dataset,
46
+ scale = findScale(program, consumer.encoding.scale)
47
+ ) {
48
+ const allowUnknown = Object.hasOwn(scale, "unknown");
49
+ if (isDirectCategoricalConsumer(consumer)) {
50
+ return allowUnknown
51
+ ? readScaleField(
52
+ dataset.values,
53
+ consumer.encoding.field,
54
+ consumer.encoding.fieldType,
55
+ { allowUnknown: true }
56
+ )
57
+ : readNominalField(dataset.values, consumer.encoding.field);
58
+ }
59
+ if (consumer.encoding.fieldType === "temporal") {
60
+ return allowUnknown
61
+ ? readScaleField(dataset.values, consumer.encoding.field, "temporal", {
62
+ allowUnknown: true
63
+ })
64
+ : readTemporalField(dataset.values, consumer.encoding.field);
65
+ }
66
+ if (["nominal", "ordinal"].includes(consumer.encoding.fieldType)) {
67
+ if (!["ordinal", "band", "point"].includes(scale.type)) {
68
+ throw new Error(
69
+ `Scale materialization requires a quantitative encoding on mark "${consumer.layer.id}".`
70
+ );
71
+ }
72
+ return allowUnknown
73
+ ? readScaleField(
74
+ dataset.values,
75
+ consumer.encoding.field,
76
+ consumer.encoding.fieldType,
77
+ { allowUnknown: true }
78
+ )
79
+ : readNominalField(dataset.values, consumer.encoding.field);
80
+ }
81
+ if (consumer.encoding.fieldType !== "quantitative") {
82
+ throw new Error(
83
+ `Scale materialization requires a quantitative encoding on mark "${consumer.layer.id}".`
84
+ );
85
+ }
86
+ return allowUnknown
87
+ ? readScaleField(dataset.values, consumer.encoding.field, "quantitative", {
88
+ allowUnknown: true
89
+ })
90
+ : readQuantitativeField(dataset.values, consumer.encoding.field);
91
+ }
@@ -0,0 +1,47 @@
1
+ import { deriveBarAggregates } from "../../../grammar/bars/aggregate.js";
2
+ import {
3
+ BAR_GRAINS,
4
+ resolveBarGrain
5
+ } from "../../../grammar/bars/policy.js";
6
+ import { deriveLineSeries } from "../../../grammar/lineSeries.js";
7
+ import { isAggregate } from "../../../grammar/aggregate.js";
8
+ import {
9
+ resolveRectConsumerValues
10
+ } from "../../../materialization/rect.js";
11
+
12
+ export function resolveMarkFamilyConsumerValues(consumer, dataset) {
13
+ if (consumer.layer.mark?.type === "rect") {
14
+ return {
15
+ matched: true,
16
+ values: resolveRectConsumerValues(
17
+ consumer.layer,
18
+ dataset,
19
+ consumer.channel
20
+ )
21
+ };
22
+ }
23
+ if (
24
+ consumer.layer.mark?.type === "line" &&
25
+ isAggregate(consumer.layer.encoding?.y?.aggregate)
26
+ ) {
27
+ const derived = deriveLineSeries(dataset.values, consumer.layer);
28
+ return {
29
+ matched: true,
30
+ values: consumer.channel === "x" ? derived.xValues : derived.yValues
31
+ };
32
+ }
33
+ if (resolveBarGrain(consumer.layer) === BAR_GRAINS.aggregate) {
34
+ const derived = deriveBarAggregates(dataset.values, consumer.layer);
35
+ if (consumer.channel === "color") {
36
+ return {
37
+ matched: true,
38
+ values: derived.values.map(value => value.color)
39
+ };
40
+ }
41
+ return {
42
+ matched: true,
43
+ values: consumer.channel === "x" ? derived.xValues : derived.yValues
44
+ };
45
+ }
46
+ return { matched: false };
47
+ }
@@ -0,0 +1,41 @@
1
+ import { normalizeRuleDatum } from "../../../grammar/rules.js";
2
+ import {
3
+ findScale,
4
+ isDirectCategoricalConsumer,
5
+ readConsumerFieldValues,
6
+ requireConsumerDataset
7
+ } from "./common.js";
8
+ import { resolveMarkFamilyConsumerValues } from "./families.js";
9
+
10
+ export { findScale, findScaleConsumers } from "./common.js";
11
+ export {
12
+ resolveHistogramCountValues,
13
+ resolveSeriesLayoutScaleValues
14
+ } from "./seriesLayout.js";
15
+
16
+ export function resolveConsumerValues(program, consumer) {
17
+ const dataset = requireConsumerDataset(program, consumer);
18
+ if (Object.hasOwn(consumer.encoding, "datum")) {
19
+ return [normalizeRuleDatum(
20
+ consumer.encoding.datum,
21
+ consumer.encoding.fieldType,
22
+ consumer.channel
23
+ )];
24
+ }
25
+ if (
26
+ consumer.channel === "strokeDash" &&
27
+ !["line", "rule"].includes(consumer.layer.mark?.type)
28
+ ) {
29
+ throw new Error(
30
+ "strokeDash scale materialization requires a line mark or rule mark."
31
+ );
32
+ }
33
+ const scale = findScale(program, consumer.encoding.scale);
34
+ if (isDirectCategoricalConsumer(consumer)) {
35
+ return readConsumerFieldValues(program, consumer, dataset, scale);
36
+ }
37
+ const family = resolveMarkFamilyConsumerValues(consumer, dataset);
38
+ return family.matched
39
+ ? family.values
40
+ : readConsumerFieldValues(program, consumer, dataset, scale);
41
+ }
@@ -1,138 +1,26 @@
1
- import { deriveBarAggregates } from "../../grammar/bars/aggregate.js";
1
+ import { deriveBarAggregates } from "../../../grammar/bars/aggregate.js";
2
2
  import {
3
3
  BAR_GRAINS,
4
4
  resolveBarChannels,
5
5
  resolveBarColorLayout,
6
6
  resolveBarGrain
7
- } from "../../grammar/bars/policy.js";
7
+ } from "../../../grammar/bars/policy.js";
8
8
  import {
9
9
  countHistogramBins,
10
10
  findHistogramBinIndex,
11
11
  resolveHistogramBins
12
- } from "../../grammar/histogram.js";
13
- import { deriveDensityAreaSeries } from "../../grammar/areaSeries.js";
14
- import { deriveLineSeries } from "../../grammar/lineSeries.js";
15
- import { resolveSeriesLayoutDomainValues } from "../../grammar/seriesLayout.js";
12
+ } from "../../../grammar/histogram.js";
13
+ import { deriveDensityAreaSeries } from "../../../grammar/areaSeries.js";
14
+ import {
15
+ resolveSeriesLayoutDomainValues
16
+ } from "../../../grammar/seriesLayout.js";
16
17
  import {
17
18
  readNominalField,
18
19
  readQuantitativeField,
19
- readScaleField,
20
- readTemporalField,
21
20
  resolveOrdinalDomain
22
- } from "../../grammar/scales.js";
23
- import { findDataset } from "../../selectors/datasets.js";
24
- import { requireSemanticScale } from "../../selectors/scales.js";
25
- import { isAggregate } from "../../grammar/aggregate.js";
26
- import { normalizeRuleDatum } from "../../grammar/rules.js";
27
-
28
- export function findScale(program, id) {
29
- return requireSemanticScale(program, id);
30
- }
31
-
32
- export function findScaleConsumers(program, id) {
33
- const consumers = [];
34
- for (const layer of program.semanticSpec.layers) {
35
- for (const channel of [
36
- "x", "y", "x2", "y2", "xOffset", "color", "strokeDash", "size", "shape",
37
- "opacity"
38
- ]) {
39
- const encoding = layer.encoding?.[channel];
40
- if (encoding?.scale === id) consumers.push({ layer, channel, encoding });
41
- }
42
- }
43
- return consumers;
44
- }
45
-
46
- export function resolveConsumerValues(program, consumer) {
47
- const dataset = findDataset(program, consumer.layer.data);
48
- if (dataset === undefined) {
49
- throw new Error(
50
- `Mark "${consumer.layer.id}" references unknown dataset "${consumer.layer.data}".`
51
- );
52
- }
53
-
54
- if (Object.hasOwn(consumer.encoding, "datum")) {
55
- return [normalizeRuleDatum(
56
- consumer.encoding.datum,
57
- consumer.encoding.fieldType,
58
- consumer.channel
59
- )];
60
- }
61
-
62
- if (
63
- consumer.channel === "strokeDash" &&
64
- !["line", "rule"].includes(consumer.layer.mark?.type)
65
- ) {
66
- throw new Error("strokeDash scale materialization requires a line mark or rule mark.");
67
- }
68
- const scale = findScale(program, consumer.encoding.scale);
69
- const allowUnknown = Object.hasOwn(scale, "unknown");
70
-
71
- if (
72
- ["color", "strokeDash", "xOffset", "shape"].includes(consumer.channel) &&
73
- consumer.encoding.fieldType === "nominal"
74
- ) {
75
- return allowUnknown
76
- ? readScaleField(
77
- dataset.values,
78
- consumer.encoding.field,
79
- consumer.encoding.fieldType,
80
- { allowUnknown: true }
81
- )
82
- : readNominalField(dataset.values, consumer.encoding.field);
83
- }
84
-
85
- if (
86
- consumer.layer.mark?.type === "line" &&
87
- isAggregate(consumer.layer.encoding?.y?.aggregate)
88
- ) {
89
- const derived = deriveLineSeries(dataset.values, consumer.layer);
90
- return consumer.channel === "x" ? derived.xValues : derived.yValues;
91
- }
92
-
93
- if (
94
- resolveBarGrain(consumer.layer) === BAR_GRAINS.aggregate
95
- ) {
96
- const derived = deriveBarAggregates(dataset.values, consumer.layer);
97
- if (consumer.channel === "color") {
98
- return derived.values.map(value => value.color);
99
- }
100
- return consumer.channel === "x" ? derived.xValues : derived.yValues;
101
- }
102
-
103
- if (consumer.encoding.fieldType === "temporal") {
104
- return allowUnknown
105
- ? readScaleField(dataset.values, consumer.encoding.field, "temporal", {
106
- allowUnknown: true
107
- })
108
- : readTemporalField(dataset.values, consumer.encoding.field);
109
- }
110
- if (["nominal", "ordinal"].includes(consumer.encoding.fieldType)) {
111
- if (!["ordinal", "band", "point"].includes(scale.type)) {
112
- throw new Error(
113
- `Scale materialization requires a quantitative encoding on mark "${consumer.layer.id}".`
114
- );
115
- }
116
- return allowUnknown
117
- ? readScaleField(
118
- dataset.values,
119
- consumer.encoding.field,
120
- consumer.encoding.fieldType,
121
- { allowUnknown: true }
122
- )
123
- : readNominalField(dataset.values, consumer.encoding.field);
124
- }
125
- if (consumer.encoding.fieldType !== "quantitative") {
126
- throw new Error(
127
- `Scale materialization requires a quantitative encoding on mark "${consumer.layer.id}".`
128
- );
129
- }
130
- return allowUnknown
131
- ? readScaleField(dataset.values, consumer.encoding.field, "quantitative", {
132
- allowUnknown: true
133
- })
134
- : readQuantitativeField(dataset.values, consumer.encoding.field);
135
- }
21
+ } from "../../../grammar/scales/index.js";
22
+ import { findDataset } from "../../../selectors/datasets.js";
23
+ import { findScale } from "./common.js";
136
24
 
137
25
  export function resolveHistogramCountValues(program, consumer) {
138
26
  const xEncoding = consumer.layer.encoding?.x;
@@ -140,7 +28,6 @@ export function resolveHistogramCountValues(program, consumer) {
140
28
  ? undefined
141
29
  : findScale(program, xEncoding.scale);
142
30
  const dataset = findDataset(program, consumer.layer.data);
143
-
144
31
  if (xEncoding?.bin === undefined || xScale === undefined) {
145
32
  throw new Error(
146
33
  `Histogram mark "${consumer.layer.id}" requires a binned x scale.`
@@ -151,7 +38,6 @@ export function resolveHistogramCountValues(program, consumer) {
151
38
  `Histogram mark "${consumer.layer.id}" requires an existing dataset.`
152
39
  );
153
40
  }
154
-
155
41
  const xValues = readQuantitativeField(dataset.values, xEncoding.field);
156
42
  const bins = resolveHistogramBins({
157
43
  values: xValues,
@@ -180,7 +66,6 @@ function resolveHistogramPartitions(program, consumer) {
180
66
  if (colorEncoding?.scale === undefined) {
181
67
  return countHistogramBins(xValues, bins.boundaries).map(value => [value]);
182
68
  }
183
-
184
69
  const colorScale = findScale(program, colorEncoding.scale);
185
70
  const colorValues = readNominalField(dataset.values, colorEncoding.field);
186
71
  const colorDomain = resolveOrdinalDomain(colorScale.domain, colorValues);
@@ -208,14 +93,16 @@ function resolveAggregatePartitions(program, consumer) {
208
93
  : derived.yValues;
209
94
  const categoryDomain = resolveOrdinalDomain(categoryScale.domain, categoryValues);
210
95
  const colorEncoding = layer.encoding?.color;
211
- if (colorEncoding?.scale === undefined) {
96
+ if (
97
+ colorEncoding?.scale === undefined ||
98
+ colorEncoding.fieldType === "quantitative"
99
+ ) {
212
100
  const byCategory = new Map(derived.values.map(value => [
213
101
  value[channels.category],
214
102
  value[channels.measure]
215
103
  ]));
216
104
  return categoryDomain.map(value => [byCategory.get(value) ?? 0]);
217
105
  }
218
-
219
106
  const colorScale = findScale(program, colorEncoding.scale);
220
107
  const colorValues = readNominalField(dataset.values, colorEncoding.field);
221
108
  const colorDomain = resolveOrdinalDomain(colorScale.domain, colorValues);
@@ -15,7 +15,7 @@ import {
15
15
  isContinuousColorScaleType,
16
16
  isDiscretizedColorScaleType,
17
17
  normalizeScaleDefinition
18
- } from "../../grammar/scales.js";
18
+ } from "../../grammar/scales/index.js";
19
19
  import { findSemanticScale } from "../../selectors/scales.js";
20
20
 
21
21
  const CREATE_SCALE_OPTIONS = Object.freeze([