ggaction 0.0.9 → 0.0.11

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 (222) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +5 -4
  3. package/knowledge/action-card.schema.json +1 -0
  4. package/knowledge/action-cards.json +1 -10941
  5. package/knowledge/action-cards.schema.json +1 -0
  6. package/knowledge/extension-authoring.md +248 -0
  7. package/knowledge/intent-taxonomy.json +1 -183
  8. package/knowledge/intent-taxonomy.schema.json +1 -0
  9. package/knowledge/mcp-resources.json +1 -95
  10. package/knowledge/mcp-resources.schema.json +1 -0
  11. package/knowledge/task-packet.schema.json +1 -159
  12. package/knowledge/task-resolver.js +1 -1230
  13. package/package.json +19 -2
  14. package/src/actions/boxPlots/edit.js +45 -298
  15. package/src/actions/boxPlots/options.js +46 -77
  16. package/src/actions/boxPlots/resolve.js +1 -1
  17. package/src/actions/charts/bar.js +3 -0
  18. package/src/actions/charts/heatmap.js +73 -83
  19. package/src/actions/charts/index.js +1 -1
  20. package/src/actions/charts/parallel.js +35 -1
  21. package/src/actions/charts/shared.js +20 -0
  22. package/src/actions/composition/actions.js +46 -49
  23. package/src/actions/data/bin2d.js +105 -2
  24. package/src/actions/data/box.js +17 -17
  25. package/src/actions/data/density.js +42 -78
  26. package/src/actions/data/gradientProfile.js +22 -52
  27. package/src/actions/data/horizon.js +16 -41
  28. package/src/actions/data/index.js +10 -10
  29. package/src/actions/data/interval.js +16 -38
  30. package/src/actions/data/intervalEdit.js +55 -0
  31. package/src/actions/data/regression.js +7 -17
  32. package/src/actions/data/shared.js +47 -0
  33. package/src/actions/data/timeUnit.js +14 -38
  34. package/src/actions/data/window.js +14 -38
  35. package/src/actions/distributions/revision.js +154 -34
  36. package/src/actions/encodings/angle.js +9 -10
  37. package/src/actions/encodings/appearance.js +17 -51
  38. package/src/actions/encodings/density/resolve.js +3 -1
  39. package/src/actions/encodings/density.js +1 -15
  40. package/src/actions/encodings/horizon/resolve.js +13 -3
  41. package/src/actions/encodings/horizon.js +8 -7
  42. package/src/actions/encodings/index.js +9 -9
  43. package/src/actions/encodings/offset.js +8 -24
  44. package/src/actions/encodings/parallel.js +9 -10
  45. package/src/actions/encodings/pathOrder.js +6 -13
  46. package/src/actions/encodings/position/policies/index.js +4 -4
  47. package/src/actions/encodings/position/policies/line.js +47 -17
  48. package/src/actions/encodings/position/resolve.js +24 -5
  49. package/src/actions/encodings/ranged.js +18 -23
  50. package/src/actions/encodings/remove.js +15 -25
  51. package/src/actions/encodings/ruleAppearance.js +9 -24
  52. package/src/actions/encodings/shared.js +37 -0
  53. package/src/actions/encodings/strokeDash.js +8 -23
  54. package/src/actions/encodings/text.js +9 -10
  55. package/src/actions/errorBands/create.js +105 -39
  56. package/src/actions/errorBands/edit.js +89 -93
  57. package/src/actions/errorBands/index.js +1 -2
  58. package/src/actions/errorBars/create.js +78 -27
  59. package/src/actions/errorBars/edit.js +81 -48
  60. package/src/actions/errorBars/index.js +1 -2
  61. package/src/actions/facets/actions.js +45 -69
  62. package/src/actions/facets/derive.js +38 -53
  63. package/src/actions/facets/replay.js +0 -1
  64. package/src/actions/gradientPlots/components.js +59 -48
  65. package/src/actions/gradientPlots/edit.js +35 -216
  66. package/src/actions/gradientPlots/index.js +11 -10
  67. package/src/actions/gradientPlots/materialize.js +21 -35
  68. package/src/actions/gradientPlots/options.js +5 -7
  69. package/src/actions/gradientPlots/paint.js +9 -6
  70. package/src/actions/gradientPlots/resolve.js +1 -1
  71. package/src/actions/guides/axes/index.js +3 -3
  72. package/src/actions/guides/axes/labels.js +68 -26
  73. package/src/actions/guides/axes/lines.js +22 -2
  74. package/src/actions/guides/axes/policy.js +28 -8
  75. package/src/actions/guides/axes/ticks.js +27 -9
  76. package/src/actions/guides/axes/titles.js +113 -65
  77. package/src/actions/guides/grids/resolve.js +14 -0
  78. package/src/actions/guides/guides.js +2 -4
  79. package/src/actions/guides/index.js +2 -2
  80. package/src/actions/guides/legends/categorical/actions.js +44 -68
  81. package/src/actions/guides/legends/categorical/components.js +6 -1
  82. package/src/actions/guides/legends/categorical/index.js +2 -2
  83. package/src/actions/guides/legends/categorical/layout.js +148 -175
  84. package/src/actions/guides/legends/categorical/recipes.js +5 -0
  85. package/src/actions/guides/legends/categorical/resolve.js +8 -1
  86. package/src/actions/guides/legends/continuous/common.js +117 -66
  87. package/src/actions/guides/legends/continuous/gradient.js +38 -37
  88. package/src/actions/guides/legends/continuous/interval.js +97 -76
  89. package/src/actions/guides/legends/continuous/opacity.js +28 -38
  90. package/src/actions/guides/legends/edit.js +14 -1
  91. package/src/actions/guides/legends/index.js +1 -1
  92. package/src/actions/guides/legends/lane.js +2 -1
  93. package/src/actions/guides/legends/remove.js +8 -0
  94. package/src/actions/guides/legends/size.js +36 -43
  95. package/src/actions/guides/legends/strokeWidth.js +20 -26
  96. package/src/actions/guides/polar/axes/labels.js +17 -1
  97. package/src/actions/guides/polar/axes/shared.js +7 -1
  98. package/src/actions/guides/polar/axes/titles.js +19 -2
  99. package/src/actions/guides/polar/grids.js +4 -0
  100. package/src/actions/guides/polar/resolve.js +6 -0
  101. package/src/actions/index.js +13 -13
  102. package/src/actions/intervals/resolve.js +87 -117
  103. package/src/actions/marks/arc/actions.js +31 -36
  104. package/src/actions/marks/area/actions.js +23 -31
  105. package/src/actions/marks/index.js +6 -6
  106. package/src/actions/marks/line/actions.js +33 -52
  107. package/src/actions/marks/line/materialize.js +7 -5
  108. package/src/actions/marks/point/materialize.js +5 -3
  109. package/src/actions/marks/remove.js +21 -0
  110. package/src/actions/marks/rule/actions.js +53 -53
  111. package/src/actions/marks/shared.js +8 -4
  112. package/src/actions/marks/text/actions.js +14 -6
  113. package/src/actions/marks/text/layout.js +21 -45
  114. package/src/actions/marks/tick/actions.js +22 -21
  115. package/src/actions/primitives/createGraphics.js +4 -0
  116. package/src/actions/primitives/editGraphics.js +4 -0
  117. package/src/actions/primitives/index.js +3 -3
  118. package/src/actions/primitives/semantic.js +1 -1
  119. package/src/actions/primitives/semanticValidation/index.js +2 -2
  120. package/src/actions/primitives/semanticValidation/layer.js +1 -1
  121. package/src/actions/regression/edit.js +3 -43
  122. package/src/actions/scales/consumers/families.js +24 -4
  123. package/src/actions/scales/consumers/index.js +1 -1
  124. package/src/actions/scales/consumers/seriesLayout.js +17 -0
  125. package/src/actions/scales/definitions.js +100 -103
  126. package/src/actions/scales/materialize.js +5 -0
  127. package/src/actions/selection/actions.js +79 -121
  128. package/src/actions/titles/actions.js +48 -94
  129. package/src/actions/titles/resolve.js +14 -27
  130. package/src/actions/violinPlots/create.js +37 -44
  131. package/src/core/action.js +20 -5
  132. package/src/core/extensionRegistry.js +104 -0
  133. package/src/core/textMetrics.js +74 -37
  134. package/src/core/validation.js +18 -0
  135. package/src/extension.js +8 -1
  136. package/src/grammar/aggregate.js +48 -28
  137. package/src/grammar/arcs.js +58 -11
  138. package/src/grammar/areaSeries.js +14 -16
  139. package/src/grammar/bin2d.js +37 -23
  140. package/src/grammar/boxPlot.js +14 -13
  141. package/src/grammar/categoricalDensity.js +14 -14
  142. package/src/grammar/categoryOrder.js +8 -14
  143. package/src/grammar/curveCommands.js +87 -20
  144. package/src/grammar/density.js +162 -141
  145. package/src/grammar/facets/index.js +14 -0
  146. package/src/grammar/facets/scales.js +7 -4
  147. package/src/grammar/gradientProfile.js +12 -15
  148. package/src/grammar/histogram.js +145 -83
  149. package/src/grammar/horizon.js +60 -23
  150. package/src/grammar/interval.js +14 -13
  151. package/src/grammar/lineSeries.js +91 -61
  152. package/src/grammar/numeric.js +290 -0
  153. package/src/grammar/paint.js +3 -4
  154. package/src/grammar/palettes.js +21 -32
  155. package/src/grammar/pathCommands.js +6 -0
  156. package/src/grammar/polarPaths.js +5 -5
  157. package/src/grammar/regression/derive.js +48 -9
  158. package/src/grammar/regression/models.js +248 -69
  159. package/src/grammar/regression/parameters.js +8 -0
  160. package/src/grammar/scales/color.js +35 -26
  161. package/src/grammar/scales/continuous.js +71 -24
  162. package/src/grammar/scales/definition.js +1 -1
  163. package/src/grammar/scales/discretized.js +42 -12
  164. package/src/grammar/scales/fields.js +21 -4
  165. package/src/grammar/scales/ordinal.js +117 -61
  166. package/src/grammar/scales/temporal.js +40 -11
  167. package/src/grammar/scales/transformed.js +199 -52
  168. package/src/grammar/schemas/graphicBounds.js +270 -79
  169. package/src/grammar/seriesLayout.js +50 -18
  170. package/src/grammar/ticks.js +123 -53
  171. package/src/grammar/timeUnit.js +18 -8
  172. package/src/grammar/transforms.js +4 -1
  173. package/src/grammar/window.js +70 -57
  174. package/src/index.js +1 -1
  175. package/src/layout/canvas.js +4 -8
  176. package/src/layout/composition.js +39 -40
  177. package/src/layout/facets.js +10 -14
  178. package/src/layout/labels.js +91 -15
  179. package/src/layout/legend.js +22 -20
  180. package/src/layout/legendLane.js +54 -40
  181. package/src/layout/title.js +7 -7
  182. package/src/materialization/bars/aggregate.js +14 -40
  183. package/src/materialization/bars/grouped.js +7 -18
  184. package/src/materialization/bars/histogram.js +2 -19
  185. package/src/materialization/bars/ranged.js +20 -15
  186. package/src/materialization/bars/resolve.js +21 -0
  187. package/src/materialization/facetGuides/legacyCategorical.js +2 -1
  188. package/src/materialization/facetGuides/placement.js +34 -19
  189. package/src/materialization/facetGuides/preparation.js +9 -5
  190. package/src/materialization/facets.js +39 -27
  191. package/src/materialization/guides/resources.js +2 -1
  192. package/src/materialization/marks/capabilities.js +5 -2
  193. package/src/materialization/marks/index.js +24 -4
  194. package/src/materialization/scales/policies/bar.js +61 -59
  195. package/src/materialization/scales/policies/binnedPosition.js +34 -0
  196. package/src/materialization/scales/policies/series.js +13 -7
  197. package/src/materialization/scales/resolve.js +4 -5
  198. package/src/materialization/selection/items/arc.js +16 -2
  199. package/src/materialization/selection/items/bar.js +4 -2
  200. package/src/materialization/selection/items/common.js +21 -1
  201. package/src/materialization/selection/items/path.js +19 -2
  202. package/src/materialization/selection/items/point.js +4 -16
  203. package/src/materialization/selection/items/tick.js +13 -21
  204. package/src/materialization/selection/point.js +11 -4
  205. package/src/materialization/selection/styles.js +19 -27
  206. package/src/materialization/text.js +4 -1
  207. package/src/mcp/adapter.js +8 -3
  208. package/src/renderers/canvas/fill.js +2 -2
  209. package/src/renderers/canvas/index.js +35 -2
  210. package/src/renderers/canvas/native.js +160 -0
  211. package/src/renderers/pdf.js +14 -0
  212. package/src/renderers/svg.js +37 -4
  213. package/types/extension.d.ts +19 -0
  214. package/types/index.d.ts +3 -0
  215. package/types/program.d.ts +509 -131
  216. package/types/svg.d.ts +1 -0
  217. package/src/actions/errorBands/components.js +0 -81
  218. package/src/actions/errorBands/options.js +0 -39
  219. package/src/actions/errorBands/resolve.js +0 -28
  220. package/src/actions/errorBars/components.js +0 -62
  221. package/src/actions/errorBars/options.js +0 -58
  222. package/src/actions/errorBars/resolve.js +0 -25
@@ -22,49 +22,70 @@ import {
22
22
  SCALE_ROLES,
23
23
  validateScalePropertyForType,
24
24
  validateScaleTypeForRole,
25
- isDiscretePositionScaleType
25
+ isDiscretePositionScaleType,
26
+ withScaleUnknown
26
27
  } from "../../grammar/scales/index.js";
27
28
  import {
28
29
  validateRadialRange,
29
30
  validateThetaRange
30
31
  } from "../../grammar/polar.js";
31
- import { withScaleUnknown } from "../../grammar/scales/index.js";
32
32
  import { findSemanticScale } from "../../selectors/scales.js";
33
33
 
34
- const BASE_OPTIONS = Object.freeze(["id", "type", "domain", "range"]);
35
- const POSITION_OPTIONS = Object.freeze([
34
+ const BASE_OPTIONS = ["id", "type", "domain", "range"];
35
+ const UNKNOWN_OPTIONS = [...BASE_OPTIONS, "unknown"];
36
+ const CLAMP_REVERSE = ["clamp", "reverse"];
37
+ const BOOLEAN_OPTIONS = ["nice", "zero", ...CLAMP_REVERSE];
38
+ const TRANSFORM_OPTIONS = ["base", "exponent", "constant"];
39
+ const POSITION_OPTIONS = [
36
40
  ...BASE_OPTIONS,
37
- "nice",
38
- "zero",
39
- "clamp",
40
- "reverse",
41
- "base",
42
- "exponent",
43
- "constant",
41
+ ...BOOLEAN_OPTIONS,
42
+ ...TRANSFORM_OPTIONS,
44
43
  "paddingInner",
45
44
  "paddingOuter",
46
45
  "padding",
47
46
  "align",
48
47
  "unknown"
49
- ]);
50
- const COLOR_OPTIONS = Object.freeze([...BASE_OPTIONS, "palette", "unknown"]);
51
- const SEQUENTIAL_COLOR_OPTIONS = Object.freeze([
48
+ ];
49
+ const COLOR_OPTIONS = [...BASE_OPTIONS, "palette", "unknown"];
50
+ const SEQUENTIAL_COLOR_OPTIONS = [
52
51
  ...COLOR_OPTIONS,
53
52
  "interpolate",
54
- "clamp",
55
- "reverse"
56
- ]);
57
- const OPACITY_OPTIONS = Object.freeze([
58
- ...POSITION_OPTIONS,
59
- "clamp",
60
- "reverse"
61
- ]);
53
+ ...CLAMP_REVERSE
54
+ ];
55
+ const OPACITY_OPTIONS = [...BASE_OPTIONS, ...BOOLEAN_OPTIONS, "unknown"];
56
+ const STROKE_WIDTH_OPTIONS = [
57
+ ...BASE_OPTIONS, ...BOOLEAN_OPTIONS, ...TRANSFORM_OPTIONS
58
+ ];
62
59
 
63
60
  function optionsObject(options) {
64
61
  if (!isPlainObject(options)) {
65
62
  throw new TypeError("Encoding scale must be a plain object.");
66
63
  }
67
- return options;
64
+ }
65
+
66
+ function validateBooleanOptions(options, properties, type) {
67
+ for (const property of properties) {
68
+ if (options[property] !== undefined && typeof options[property] !== "boolean") {
69
+ throw new TypeError(`Scale ${property} must be a boolean.`);
70
+ }
71
+ if (type !== undefined && options[property] !== undefined) {
72
+ validateScalePropertyForType(type, property);
73
+ }
74
+ }
75
+ }
76
+
77
+ function assignOptions(scale, options, existing, properties) {
78
+ for (const property of properties) {
79
+ const value = options[property] ?? existing?.[property];
80
+ if (value !== undefined) scale[property] = value;
81
+ }
82
+ return scale;
83
+ }
84
+
85
+ function validatePaletteRange(options) {
86
+ if (options.palette !== undefined && options.range !== undefined) {
87
+ throw new Error("Color scale cannot specify both palette and range.");
88
+ }
68
89
  }
69
90
 
70
91
  export function resolvePositionScaleDefinition(
@@ -78,14 +99,15 @@ export function resolvePositionScaleDefinition(
78
99
  validateKeys(options, POSITION_OPTIONS, "scale");
79
100
  const id = validateUserId(options.id ?? channel, "Scale id");
80
101
  const existing = findSemanticScale(program, id);
102
+ const discrete = ["ordinal", "nominal"].includes(fieldType);
81
103
  const expectedType = fieldType === "temporal"
82
104
  ? "time"
83
- : ["ordinal", "nominal"].includes(fieldType)
105
+ : discrete
84
106
  ? defaults.discreteType ?? "point"
85
107
  : "linear";
86
108
  const type = options.type ?? existing?.type ?? expectedType;
87
109
  if (fieldType === "temporal") validateTimeScaleType(type);
88
- else if (["ordinal", "nominal"].includes(fieldType)) {
110
+ else if (discrete) {
89
111
  if (!isDiscretePositionScaleType(type)) {
90
112
  throw new Error(
91
113
  `Scale type "${type}" is not valid for discrete position.`
@@ -104,14 +126,17 @@ export function resolvePositionScaleDefinition(
104
126
  ...(existing === undefined && defaults.nice !== undefined
105
127
  ? { nice: defaults.nice }
106
128
  : {}),
107
- ...(existing === undefined && defaults.zero !== undefined
129
+ ...(
130
+ existing === undefined &&
131
+ defaults.zero !== undefined &&
132
+ (type !== "log" || defaults.zero)
108
133
  ? { zero: defaults.zero }
109
134
  : {})
110
135
  },
111
136
  retainCoreOnTypeChange: true,
112
137
  retainCompatibleOnTypeChange: true,
113
138
  validateDomain: (_scaleType, value) =>
114
- ["ordinal", "nominal"].includes(fieldType)
139
+ discrete
115
140
  ? validateOrdinalDomain(value)
116
141
  : validateScaleDomain(value),
117
142
  validateRange: (_scaleType, value) => channel === "theta"
@@ -127,9 +152,7 @@ export function resolvePositionScaleDefinition(
127
152
  export function resolveColorScaleDefinition(program, options) {
128
153
  optionsObject(options);
129
154
  validateKeys(options, COLOR_OPTIONS, "scale");
130
- if (options.palette !== undefined && options.range !== undefined) {
131
- throw new Error("Color scale cannot specify both palette and range.");
132
- }
155
+ validatePaletteRange(options);
133
156
  const id = validateUserId(options.id ?? "color", "Scale id");
134
157
  const existing = findSemanticScale(program, id);
135
158
  const range = options.palette === undefined
@@ -165,20 +188,14 @@ export function resolveSequentialColorScaleDefinition(
165
188
  ) {
166
189
  optionsObject(options);
167
190
  validateKeys(options, SEQUENTIAL_COLOR_OPTIONS, "scale");
168
- if (options.palette !== undefined && options.range !== undefined) {
169
- throw new Error("Color scale cannot specify both palette and range.");
170
- }
191
+ validatePaletteRange(options);
171
192
  const id = validateUserId(options.id ?? "color", "Scale id");
172
193
  const existing = findSemanticScale(program, id);
173
194
  const type = options.type ?? existing?.type ?? "sequential";
174
195
  if (type !== "sequential") {
175
196
  throw new Error(`Unsupported continuous color scale type "${type}".`);
176
197
  }
177
- for (const property of ["clamp", "reverse"]) {
178
- if (options[property] !== undefined && typeof options[property] !== "boolean") {
179
- throw new TypeError(`Scale ${property} must be a boolean.`);
180
- }
181
- }
198
+ validateBooleanOptions(options, CLAMP_REVERSE);
182
199
  const requestedRange = options.palette === undefined
183
200
  ? options.range
184
201
  : { palette: options.palette };
@@ -193,11 +210,11 @@ export function resolveSequentialColorScaleDefinition(
193
210
  options.interpolate ?? existing?.interpolate ?? "rgb"
194
211
  )
195
212
  };
196
- const clamp = options.clamp ?? existing?.clamp;
197
- const reverse = options.reverse ?? existing?.reverse;
198
- if (clamp !== undefined) scale.clamp = clamp;
199
- if (reverse !== undefined) scale.reverse = reverse;
200
- return withScaleUnknown(scale, { ...existing, ...options }, "color");
213
+ return withScaleUnknown(
214
+ assignOptions(scale, options, existing, CLAMP_REVERSE),
215
+ { ...existing, ...options },
216
+ "color"
217
+ );
201
218
  }
202
219
 
203
220
  export function resolveQuantitativeColorScaleDefinition(
@@ -213,26 +230,16 @@ export function resolveQuantitativeColorScaleDefinition(
213
230
  if (type === "sequential") {
214
231
  return resolveSequentialColorScaleDefinition(program, fieldType, options);
215
232
  }
216
- validateKeys(options, [
217
- ...COLOR_OPTIONS,
218
- "clamp",
219
- "reverse"
220
- ], "scale");
233
+ validateKeys(options, [...COLOR_OPTIONS, ...CLAMP_REVERSE], "scale");
221
234
  if (fieldType !== "quantitative") {
222
235
  throw new Error(`Scale type "${type}" requires quantitative color.`);
223
236
  }
224
237
  validateScaleTypeForRole(type, SCALE_ROLES.discretizedColor);
225
- if (options.palette !== undefined && options.range !== undefined) {
226
- throw new Error("Color scale cannot specify both palette and range.");
227
- }
228
- for (const property of ["clamp", "reverse"]) {
229
- if (options[property] !== undefined && typeof options[property] !== "boolean") {
230
- throw new TypeError(`Scale ${property} must be a boolean.`);
231
- }
232
- if (options[property] !== undefined) validateScalePropertyForType(type, property);
233
- }
238
+ validatePaletteRange(options);
239
+ validateBooleanOptions(options, CLAMP_REVERSE, type);
234
240
  const id = validateUserId(options.id ?? "color", "Scale id");
235
241
  const existing = findSemanticScale(program, id);
242
+ const previous = existing?.type === type ? existing : undefined;
236
243
  const requestedRange = options.palette === undefined
237
244
  ? options.range
238
245
  : { palette: options.palette };
@@ -241,35 +248,41 @@ export function resolveQuantitativeColorScaleDefinition(
241
248
  type,
242
249
  domain: validateDiscretizedColorDomain(
243
250
  type,
244
- options.domain ?? (existing?.type === type ? existing.domain : "auto")
251
+ options.domain ?? previous?.domain ?? "auto"
245
252
  ),
246
253
  range: validateDiscretizedColorRange(
247
- requestedRange ?? (existing?.type === type ? existing.range : { palette: "viridis" })
254
+ requestedRange ?? previous?.range ?? { palette: "viridis" }
248
255
  )
249
256
  };
250
- const clamp = options.clamp ?? (existing?.type === type ? existing.clamp : undefined);
251
- const reverse = options.reverse ?? (existing?.type === type ? existing.reverse : undefined);
252
- if (clamp !== undefined) scale.clamp = clamp;
253
- if (reverse !== undefined) scale.reverse = reverse;
254
- return withScaleUnknown(scale, { ...existing, ...options }, "color");
257
+ return withScaleUnknown(
258
+ assignOptions(scale, options, previous, CLAMP_REVERSE),
259
+ { ...existing, ...options },
260
+ "color"
261
+ );
255
262
  }
256
263
 
257
- export function resolveStrokeDashScaleDefinition(program, options) {
264
+ function resolveOrdinalScaleDefinition(program, options, channel, rangeValidator) {
258
265
  optionsObject(options);
259
- validateKeys(options, [...BASE_OPTIONS, "unknown"], "scale");
260
- const id = validateUserId(options.id ?? "strokeDash", "Scale id");
266
+ validateKeys(options, UNKNOWN_OPTIONS, "scale");
267
+ const id = validateUserId(options.id ?? channel, "Scale id");
261
268
  const existing = findSemanticScale(program, id);
262
269
  return withScaleUnknown({
263
270
  id,
264
271
  type: validateOrdinalScaleType(options.type ?? existing?.type ?? "ordinal"),
265
272
  domain: validateOrdinalDomain(options.domain ?? existing?.domain ?? "auto"),
266
- range: validateStrokeDashRange(options.range ?? existing?.range ?? "auto")
267
- }, { ...existing, ...options }, "strokeDash");
273
+ range: rangeValidator(options.range ?? existing?.range ?? "auto")
274
+ }, { ...existing, ...options }, channel);
275
+ }
276
+
277
+ export function resolveStrokeDashScaleDefinition(program, options) {
278
+ return resolveOrdinalScaleDefinition(
279
+ program, options, "strokeDash", validateStrokeDashRange
280
+ );
268
281
  }
269
282
 
270
283
  export function resolveAppearanceScaleDefinition(program, channel, options) {
271
284
  optionsObject(options);
272
- validateKeys(options, [...BASE_OPTIONS, "unknown"], "scale");
285
+ validateKeys(options, UNKNOWN_OPTIONS, "scale");
273
286
  const id = validateUserId(options.id ?? channel, "Scale id");
274
287
  const existing = findSemanticScale(program, id);
275
288
  const shape = channel === "shape";
@@ -293,38 +306,31 @@ export function resolveOpacityScaleDefinition(program, options) {
293
306
  const id = validateUserId(options.id ?? "opacity", "Scale id");
294
307
  const existing = findSemanticScale(program, id);
295
308
  const type = validateLinearScaleType(options.type ?? existing?.type ?? "linear");
296
- for (const property of ["nice", "zero", "clamp", "reverse"]) {
297
- if (options[property] !== undefined && typeof options[property] !== "boolean") {
298
- throw new TypeError(`Scale ${property} must be a boolean.`);
299
- }
300
- }
309
+ validateBooleanOptions(options, BOOLEAN_OPTIONS, type);
301
310
  const scale = {
302
311
  id,
303
312
  type,
304
313
  domain: validateScaleDomain(options.domain ?? existing?.domain ?? "auto"),
305
314
  range: validateOpacityRange(options.range ?? existing?.range ?? "auto")
306
315
  };
307
- const nice = options.nice ?? existing?.nice;
308
- const zero = options.zero ?? existing?.zero;
309
- const clamp = options.clamp ?? existing?.clamp;
310
- const reverse = options.reverse ?? existing?.reverse;
311
- if (nice !== undefined) scale.nice = nice;
312
- if (zero !== undefined) scale.zero = zero;
313
- if (clamp !== undefined) scale.clamp = clamp;
314
- if (reverse !== undefined) scale.reverse = reverse;
315
- return withScaleUnknown(scale, { ...existing, ...options }, "opacity");
316
+ return withScaleUnknown(
317
+ assignOptions(scale, options, existing, BOOLEAN_OPTIONS),
318
+ { ...existing, ...options },
319
+ "opacity"
320
+ );
316
321
  }
317
322
 
318
323
  export function resolveStrokeWidthScaleDefinition(program, options) {
319
324
  optionsObject(options);
320
- validateKeys(options, OPACITY_OPTIONS, "scale");
325
+ validateKeys(options, STROKE_WIDTH_OPTIONS, "scale");
321
326
  const id = validateUserId(options.id ?? "strokeWidth", "Scale id");
322
327
  const existing = findSemanticScale(program, id);
323
328
  const type = options.type ?? existing?.type ?? "linear";
324
329
  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.`);
330
+ validateBooleanOptions(options, BOOLEAN_OPTIONS, type);
331
+ for (const property of TRANSFORM_OPTIONS) {
332
+ if (options[property] !== undefined) {
333
+ validateScalePropertyForType(type, property);
328
334
  }
329
335
  }
330
336
  const domain = validateScaleDomain(options.domain ?? existing?.domain ?? "auto");
@@ -337,22 +343,13 @@ export function resolveStrokeWidthScaleDefinition(program, options) {
337
343
  domain,
338
344
  range: validateStrokeWidthRange(options.range ?? existing?.range ?? "auto")
339
345
  };
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;
346
+ return assignOptions(
347
+ scale, options, existing, [...BOOLEAN_OPTIONS, ...TRANSFORM_OPTIONS]
348
+ );
345
349
  }
346
350
 
347
351
  export function resolveOffsetScaleDefinition(program, options, channel = "xOffset") {
348
- optionsObject(options);
349
- validateKeys(options, [...BASE_OPTIONS, "unknown"], "scale");
350
- const id = validateUserId(options.id ?? channel, "Scale id");
351
- const existing = findSemanticScale(program, id);
352
- return withScaleUnknown({
353
- id,
354
- type: validateOrdinalScaleType(options.type ?? existing?.type ?? "ordinal"),
355
- domain: validateOrdinalDomain(options.domain ?? existing?.domain ?? "auto"),
356
- range: validateScaleRange(options.range ?? existing?.range ?? "auto")
357
- }, { ...existing, ...options }, channel);
352
+ return resolveOrdinalScaleDefinition(
353
+ program, options, channel, validateScaleRange
354
+ );
358
355
  }
@@ -100,6 +100,11 @@ export const rematerializeScale = action(
100
100
  continue;
101
101
  }
102
102
  if (materializationMode === "defer") continue;
103
+ if (Object.values(this.materializationConfigs.highlights ?? {})
104
+ .some(config => config.target === consumer.layer.id)) {
105
+ next = next.rematerializePointMark({ id: consumer.layer.id });
106
+ continue;
107
+ }
103
108
  next = next.editGraphics({
104
109
  target: consumer.layer.id,
105
110
  property: channel === "color" ? "fill" : channel,
@@ -121,6 +121,57 @@ function selectedKeys(args, resolved) {
121
121
  return args.keys;
122
122
  }
123
123
 
124
+ function selectedGraphicItems(program, resolved, keys) {
125
+ const selected = new Set(keys);
126
+ const graphic = program.graphicSpec.objects[resolved.definition.target];
127
+ const keyByGraphic = new Map(resolved.items.flatMap(item =>
128
+ item.graphicIds.map(id => [id, item.key])
129
+ ));
130
+ return {
131
+ selected,
132
+ target: resolved.definition.target,
133
+ items: graphic.items.map(child => ({
134
+ key: keyByGraphic.get(child.id),
135
+ child: { type: child.type ?? graphic.type, properties: child.properties }
136
+ }))
137
+ };
138
+ }
139
+
140
+ function editSelectedItems(
141
+ program,
142
+ resolved,
143
+ keys,
144
+ transform,
145
+ complement = false
146
+ ) {
147
+ const { selected, target, items } = selectedGraphicItems(program, resolved, keys);
148
+ return program.editGraphics({
149
+ target,
150
+ property: "items",
151
+ value: items.map(item => selected.has(item.key) !== complement
152
+ ? transform(item.child)
153
+ : item.child)
154
+ });
155
+ }
156
+
157
+ function applyHighlight(program, config, keys) {
158
+ let next = program[requireSelectionPolicy(config.markType).applyHighlightOp]({
159
+ selection: config.selection,
160
+ style: config.style,
161
+ keys
162
+ });
163
+ if (config.dimOthers !== false) {
164
+ next = next.dimUnselectedMarkItems({
165
+ selection: config.selection,
166
+ opacity: config.dimOthers.opacity,
167
+ keys
168
+ });
169
+ }
170
+ return config.bringToFront
171
+ ? next.placeSelectedMarkItemsLast({ selection: config.selection, keys })
172
+ : next;
173
+ }
174
+
124
175
  export const selectMarks = action(
125
176
  { op: "selectMarks", description: "Create one reusable selection over final mark items." },
126
177
  function (args = {}) {
@@ -217,24 +268,9 @@ export const applyPointHighlight = action(
217
268
  throw new Error("applyPointHighlight requires a point selection.");
218
269
  }
219
270
  if (keys.length === 0) return this;
220
- const selected = new Set(keys);
221
- const graphic = this.graphicSpec.objects[resolved.definition.target];
222
- const keyByGraphic = new Map(resolved.items.flatMap(item =>
223
- item.graphicIds.map(id => [id, item.key])
224
- ));
225
- const items = graphic.items.map(child =>
226
- selected.has(keyByGraphic.get(child.id))
227
- ? transformPointHighlightChild({
228
- type: child.type ?? graphic.type,
229
- properties: child.properties
230
- }, args.style)
231
- : { type: child.type ?? graphic.type, properties: child.properties }
271
+ return editSelectedItems(this, resolved, keys, item =>
272
+ transformPointHighlightChild(item, args.style)
232
273
  );
233
- return this.editGraphics({
234
- target: resolved.definition.target,
235
- property: "items",
236
- value: items
237
- });
238
274
  }
239
275
  );
240
276
 
@@ -265,21 +301,12 @@ function applyRectangularHighlight(
265
301
  throw new Error(`${operation} requires a ${markType} selection.`);
266
302
  }
267
303
  if (keys.length === 0) return program;
268
- const selected = new Set(keys);
269
- const graphic = program.graphicSpec.objects[resolved.definition.target];
270
- const keyByGraphic = new Map(resolved.items.flatMap(item =>
271
- item.graphicIds.map(id => [id, item.key])
272
- ));
273
- return program.editGraphics({
274
- target: resolved.definition.target,
275
- property: "items",
276
- value: graphic.items.map(child => ({
277
- type: child.type ?? graphic.type,
278
- properties: selected.has(keyByGraphic.get(child.id))
279
- ? transformRectangularProperties(child.properties, args.style, offset)
280
- : child.properties
281
- }))
282
- });
304
+ return editSelectedItems(program, resolved, keys, item => ({
305
+ ...item,
306
+ properties: transformRectangularProperties(
307
+ item.properties, args.style, offset
308
+ )
309
+ }));
283
310
  }
284
311
 
285
312
  export const applyBarHighlight = action(
@@ -316,21 +343,10 @@ export const applyPathHighlight = action(
316
343
  throw new Error("applyPathHighlight requires a path selection policy.");
317
344
  }
318
345
  if (keys.length === 0) return this;
319
- const selected = new Set(keys);
320
- const graphic = this.graphicSpec.objects[resolved.definition.target];
321
- const keyByGraphic = new Map(resolved.items.flatMap(item =>
322
- item.graphicIds.map(id => [id, item.key])
323
- ));
324
- return this.editGraphics({
325
- target: resolved.definition.target,
326
- property: "items",
327
- value: graphic.items.map(child => ({
328
- type: child.type ?? graphic.type,
329
- properties: selected.has(keyByGraphic.get(child.id))
330
- ? transformPathHighlightProperties(child.properties, args.style)
331
- : child.properties
332
- }))
333
- });
346
+ return editSelectedItems(this, resolved, keys, item => ({
347
+ ...item,
348
+ properties: transformPathHighlightProperties(item.properties, args.style)
349
+ }));
334
350
  }
335
351
  );
336
352
 
@@ -347,21 +363,10 @@ export const applyRuleHighlight = action(
347
363
  throw new Error("applyRuleHighlight requires a rule or Tick selection.");
348
364
  }
349
365
  if (keys.length === 0) return this;
350
- const selected = new Set(keys);
351
- const graphic = this.graphicSpec.objects[resolved.definition.target];
352
- const keyByGraphic = new Map(resolved.items.flatMap(item =>
353
- item.graphicIds.map(id => [id, item.key])
354
- ));
355
- return this.editGraphics({
356
- target: resolved.definition.target,
357
- property: "items",
358
- value: graphic.items.map(child => ({
359
- type: child.type ?? graphic.type,
360
- properties: selected.has(keyByGraphic.get(child.id))
361
- ? transformRuleHighlightProperties(child.properties, args.style)
362
- : child.properties
363
- }))
364
- });
366
+ return editSelectedItems(this, resolved, keys, item => ({
367
+ ...item,
368
+ properties: transformRuleHighlightProperties(item.properties, args.style)
369
+ }));
365
370
  }
366
371
  );
367
372
 
@@ -371,21 +376,11 @@ export const dimUnselectedMarkItems = action(
371
376
  validateKeys(args, INTERNAL_DIM_OPTIONS, "dimUnselectedMarkItems");
372
377
  const opacity = validateUnitInterval(args.opacity, "Dim opacity");
373
378
  const resolved = resolveStoredSelection(this, args.selection);
374
- const selected = new Set(selectedKeys(args, resolved));
375
- const graphic = this.graphicSpec.objects[resolved.definition.target];
376
- const keyByGraphic = new Map(resolved.items.flatMap(item =>
377
- item.graphicIds.map(id => [id, item.key])
378
- ));
379
- return this.editGraphics({
380
- target: resolved.definition.target,
381
- property: "items",
382
- value: graphic.items.map(child => ({
383
- type: child.type ?? graphic.type,
384
- properties: selected.has(keyByGraphic.get(child.id))
385
- ? child.properties
386
- : { ...child.properties, opacity }
387
- }))
388
- });
379
+ const keys = selectedKeys(args, resolved);
380
+ return editSelectedItems(this, resolved, keys, item => ({
381
+ ...item,
382
+ properties: { ...item.properties, opacity }
383
+ }), true);
389
384
  }
390
385
  );
391
386
 
@@ -398,17 +393,9 @@ export const placeSelectedMarkItemsLast = action(
398
393
  if (keys.length === 0 || keys.length === resolved.items.length) {
399
394
  return this;
400
395
  }
401
- const selected = new Set(keys);
402
- const graphic = this.graphicSpec.objects[resolved.definition.target];
403
- const keyByGraphic = new Map(resolved.items.flatMap(item =>
404
- item.graphicIds.map(id => [id, item.key])
405
- ));
406
- const items = graphic.items.map(child => ({
407
- key: keyByGraphic.get(child.id),
408
- child: { type: child.type ?? graphic.type, properties: child.properties }
409
- }));
396
+ const { selected, target, items } = selectedGraphicItems(this, resolved, keys);
410
397
  return this.editGraphics({
411
- target: resolved.definition.target,
398
+ target,
412
399
  property: "items",
413
400
  value: [
414
401
  ...items.filter(item => !selected.has(item.key)),
@@ -433,26 +420,7 @@ export const rematerializeMarkHighlights = action(
433
420
  }));
434
421
  let next = this;
435
422
  for (const { id, config, keys } of prepared) {
436
- const policy = requireSelectionPolicy(config.markType);
437
- next = next[policy.applyHighlightOp]({
438
- selection: config.selection,
439
- style: config.style,
440
- keys
441
- });
442
- if (config.dimOthers !== false) {
443
- next = next.dimUnselectedMarkItems({
444
- selection: config.selection,
445
- opacity: config.dimOthers.opacity,
446
- keys
447
- });
448
- }
449
- if (config.bringToFront) {
450
- next = next.placeSelectedMarkItemsLast({
451
- selection: config.selection,
452
- keys
453
- });
454
- }
455
- next = next._withHighlightConfig(id, config);
423
+ next = applyHighlight(next, config, keys)._withHighlightConfig(id, config);
456
424
  }
457
425
  return prepared.some(({ config }) =>
458
426
  hasLegendSelection(next, config.target, config.selection)
@@ -519,26 +487,16 @@ export const highlightMarks = action(
519
487
  next = next._withoutMaterializationConfig(["highlights", selection]);
520
488
  next = rebuildTargetHighlights(next, resolved.definition.target);
521
489
  }
522
- const policy = requireSelectionPolicy(layer.mark.type);
523
- next = next[policy.applyHighlightOp]({ selection, style, keys });
524
- if (dimOthers !== false) {
525
- next = next.dimUnselectedMarkItems({
526
- selection,
527
- opacity: dimOthers.opacity,
528
- keys
529
- });
530
- }
531
- if (bringToFront) {
532
- next = next.placeSelectedMarkItemsLast({ selection, keys });
533
- }
534
- next = next._withHighlightConfig(selection, {
490
+ const config = {
535
491
  target: resolved.definition.target,
536
492
  selection,
537
493
  markType: layer.mark.type,
538
494
  style,
539
495
  dimOthers,
540
496
  bringToFront
541
- });
497
+ };
498
+ next = applyHighlight(next, config, keys)
499
+ ._withHighlightConfig(selection, config);
542
500
  return hasLegendSelection(next, resolved.definition.target, selection)
543
501
  ? next.rematerializeLegendHighlights()
544
502
  : next;