ggaction 0.0.3 → 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 (223) hide show
  1. package/CHANGELOG.md +58 -3
  2. package/README.md +117 -83
  3. package/package.json +27 -11
  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/regression.js +1 -1
  26. package/src/actions/data/window.js +50 -0
  27. package/src/actions/encodings/appearance.js +1 -1
  28. package/src/actions/encodings/color/continuous.js +137 -0
  29. package/src/actions/encodings/color/index.js +167 -0
  30. package/src/actions/encodings/color/layout.js +99 -0
  31. package/src/actions/encodings/color/policy.js +88 -0
  32. package/src/actions/encodings/density/resolve.js +109 -0
  33. package/src/actions/encodings/density.js +193 -88
  34. package/src/actions/encodings/horizon/resolve.js +283 -0
  35. package/src/actions/encodings/horizon.js +364 -0
  36. package/src/actions/encodings/index.js +8 -2
  37. package/src/actions/encodings/offset.js +1 -1
  38. package/src/actions/encodings/parallel.js +115 -0
  39. package/src/actions/encodings/pathOrder.js +131 -0
  40. package/src/actions/encodings/position/apply.js +73 -4
  41. package/src/actions/encodings/{position.js → position/index.js} +21 -8
  42. package/src/actions/encodings/position/policies/arc.js +27 -4
  43. package/src/actions/encodings/position/policies/area.js +33 -2
  44. package/src/actions/encodings/position/policies/index.js +7 -2
  45. package/src/actions/encodings/position/policies/line.js +18 -2
  46. package/src/actions/encodings/position/resolve.js +25 -3
  47. package/src/actions/encodings/ranged.js +5 -3
  48. package/src/actions/encodings/ruleAppearance.js +88 -10
  49. package/src/actions/encodings/strokeDash.js +1 -1
  50. package/src/actions/encodings/text.js +1 -1
  51. package/src/actions/errorBands/options.js +1 -1
  52. package/src/actions/errorBars/options.js +1 -1
  53. package/src/actions/facets/actions.js +2 -3
  54. package/src/actions/facets/derive.js +35 -5
  55. package/src/actions/facets/guides.js +1 -1
  56. package/src/actions/facets/replay.js +7 -36
  57. package/src/actions/gradientPlots/components.js +177 -0
  58. package/src/actions/gradientPlots/create.js +96 -0
  59. package/src/actions/gradientPlots/edit.js +153 -0
  60. package/src/actions/gradientPlots/index.js +24 -0
  61. package/src/actions/gradientPlots/materialize.js +287 -0
  62. package/src/actions/gradientPlots/options.js +120 -0
  63. package/src/actions/gradientPlots/paint.js +107 -0
  64. package/src/actions/gradientPlots/rebind.js +51 -0
  65. package/src/actions/gradientPlots/resolve.js +81 -0
  66. package/src/actions/guides/applicability.js +44 -17
  67. package/src/actions/guides/axes/axes.js +37 -3
  68. package/src/actions/guides/axes/index.js +2 -0
  69. package/src/actions/guides/axes/labels.js +12 -7
  70. package/src/actions/guides/axes/parallel/resolve.js +86 -0
  71. package/src/actions/guides/axes/parallel.js +228 -0
  72. package/src/actions/guides/axes/ticks.js +1 -1
  73. package/src/actions/guides/axes/titles.js +12 -9
  74. package/src/actions/guides/grids/resolve.js +1 -1
  75. package/src/actions/guides/guides.js +60 -5
  76. package/src/actions/guides/legends/categorical/actions.js +48 -11
  77. package/src/actions/guides/legends/categorical/layout.js +4 -2
  78. package/src/actions/guides/legends/continuous/interval.js +1 -1
  79. package/src/actions/guides/legends/continuous/opacity.js +1 -1
  80. package/src/actions/guides/legends/edit.js +7 -20
  81. package/src/actions/guides/legends/index.js +2 -0
  82. package/src/actions/guides/legends/remove.js +6 -31
  83. package/src/actions/guides/legends/size.js +1 -1
  84. package/src/actions/guides/legends/strokeWidth.js +170 -0
  85. package/src/actions/guides/legends/target.js +25 -0
  86. package/src/actions/guides/polar/{axes.js → axes/index.js} +5 -5
  87. package/src/actions/guides/polar/index.js +1 -1
  88. package/src/actions/guides/polar/resolve.js +1 -1
  89. package/src/actions/guides/tickValues.js +1 -1
  90. package/src/actions/index.js +6 -0
  91. package/src/actions/intervals/resolve.js +1 -1
  92. package/src/actions/marks/arc/actions.js +2 -2
  93. package/src/actions/marks/area/actions.js +61 -137
  94. package/src/actions/marks/area/materialize.js +163 -0
  95. package/src/actions/marks/bar/edit.js +1 -1
  96. package/src/actions/marks/index.js +5 -1
  97. package/src/actions/marks/line/actions.js +100 -81
  98. package/src/actions/marks/line/materialize.js +146 -0
  99. package/src/actions/marks/point/create.js +76 -0
  100. package/src/actions/marks/point/edit.js +85 -0
  101. package/src/actions/marks/point/index.js +11 -1
  102. package/src/actions/marks/point/jitter.js +72 -0
  103. package/src/actions/marks/point/{actions.js → materialize.js} +175 -170
  104. package/src/actions/marks/rect/actions.js +4 -1
  105. package/src/actions/marks/remove.js +13 -2
  106. package/src/actions/marks/rule/actions.js +26 -4
  107. package/src/actions/marks/shared.js +1 -1
  108. package/src/actions/marks/text/actions.js +20 -5
  109. package/src/actions/marks/text/index.js +7 -1
  110. package/src/actions/marks/text/layout.js +321 -0
  111. package/src/actions/primitives/semanticValidation/dataset.js +22 -0
  112. package/src/actions/primitives/semanticValidation/guide.js +43 -0
  113. package/src/actions/primitives/semanticValidation/index.js +23 -0
  114. package/src/actions/primitives/semanticValidation/layer.js +74 -0
  115. package/src/actions/primitives/semanticValidation/scale.js +74 -0
  116. package/src/actions/primitives/semanticValidation/shared.js +5 -0
  117. package/src/actions/primitives/semanticValue.js +1 -204
  118. package/src/actions/regression/components.js +1 -1
  119. package/src/actions/regression/create.js +1 -1
  120. package/src/actions/regression/edit.js +20 -22
  121. package/src/actions/scales/consumers/common.js +109 -0
  122. package/src/actions/scales/consumers/families.js +58 -0
  123. package/src/actions/scales/consumers/index.js +41 -0
  124. package/src/actions/scales/{consumers.js → consumers/seriesLayout.js} +10 -132
  125. package/src/actions/scales/create.js +1 -1
  126. package/src/actions/scales/definitions.js +32 -2
  127. package/src/actions/scales/edit.js +8 -206
  128. package/src/actions/scales/editPolicy.js +214 -0
  129. package/src/actions/scales/materialize.js +3 -3
  130. package/src/actions/scales/patch.js +26 -0
  131. package/src/actions/selection/actions.js +28 -3
  132. package/src/actions/violinPlots/create.js +198 -0
  133. package/src/actions/violinPlots/index.js +5 -0
  134. package/src/core/ChartProgram.js +22 -236
  135. package/src/core/compositionState.js +169 -0
  136. package/src/core/materializationState.js +65 -0
  137. package/src/core/programState.js +28 -0
  138. package/src/core/textMetrics.js +47 -0
  139. package/src/core/vocabulary.js +7 -2
  140. package/src/grammar/arcs.js +50 -13
  141. package/src/grammar/areaSeries.js +42 -6
  142. package/src/grammar/bars/aggregate.js +1 -1
  143. package/src/grammar/bin2d.js +333 -0
  144. package/src/grammar/categoricalDensity.js +192 -0
  145. package/src/grammar/coordinates.js +1 -1
  146. package/src/grammar/curveCommands.js +3 -1
  147. package/src/grammar/density.js +253 -27
  148. package/src/grammar/facets/dependencies.js +3 -11
  149. package/src/grammar/{facets.js → facets/index.js} +8 -6
  150. package/src/grammar/gradientProfile.js +213 -0
  151. package/src/grammar/horizon.js +455 -0
  152. package/src/grammar/interval.js +2 -96
  153. package/src/grammar/jitter.js +197 -0
  154. package/src/grammar/lineSeries.js +70 -1
  155. package/src/grammar/paint.js +88 -0
  156. package/src/grammar/palettes.js +5 -7
  157. package/src/grammar/parallelCoordinates.js +213 -0
  158. package/src/grammar/pathOrder.js +35 -0
  159. package/src/grammar/pointShapes.js +24 -0
  160. package/src/grammar/polarLineCommands.js +1 -1
  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 +2 -1
  166. package/src/grammar/scales/appearance.js +21 -0
  167. package/src/grammar/scales/color.js +4 -2
  168. package/src/grammar/schemas/concreteGraphic.js +11 -1
  169. package/src/grammar/schemas/graphicBounds.js +19 -37
  170. package/src/grammar/schemas/semanticPath.js +17 -2
  171. package/src/grammar/statistics/studentT.js +98 -0
  172. package/src/grammar/transforms.js +101 -12
  173. package/src/grammar/window.js +339 -0
  174. package/src/layout/labels.js +270 -0
  175. package/src/layout/text.js +4 -1
  176. package/src/layout/title.js +10 -20
  177. package/src/materialization/bars/aggregate.js +1 -1
  178. package/src/materialization/bars/grouped.js +1 -1
  179. package/src/materialization/bars/histogram.js +1 -1
  180. package/src/materialization/bars/ranged.js +1 -1
  181. package/src/materialization/dataProvenance.js +56 -2
  182. package/src/materialization/density.js +1 -1
  183. package/src/materialization/dependencies.js +15 -101
  184. package/src/materialization/encodings.js +2 -2
  185. package/src/materialization/facetGuides/index.js +2 -0
  186. package/src/materialization/facetGuides/legacyCategorical.js +125 -0
  187. package/src/materialization/facetGuides/placement.js +126 -0
  188. package/src/materialization/facetGuides/preparation.js +188 -0
  189. package/src/materialization/facets.js +1 -1
  190. package/src/materialization/guides/resources.js +74 -24
  191. package/src/materialization/horizon.js +29 -0
  192. package/src/materialization/layout.js +15 -0
  193. package/src/materialization/marks/capabilities.js +161 -0
  194. package/src/materialization/marks/index.js +108 -0
  195. package/src/materialization/marks/pathOrder.js +71 -0
  196. package/src/materialization/marks/policies.js +91 -0
  197. package/src/materialization/rect.js +1 -1
  198. package/src/materialization/rowEncoding.js +1 -1
  199. package/src/materialization/scaleGuideDependencies.js +102 -0
  200. package/src/materialization/scales/map.js +1 -1
  201. package/src/materialization/scales/policies/series.js +1 -1
  202. package/src/materialization/scales/resolve.js +3 -1
  203. package/src/materialization/selection/items/bar.js +1 -1
  204. package/src/materialization/selection/items/path.js +27 -1
  205. package/src/materialization/selection/items/point.js +1 -5
  206. package/src/materialization/selection/items/rect.js +21 -0
  207. package/src/materialization/selection/styles.js +21 -10
  208. package/src/materialization/text.js +12 -4
  209. package/src/renderers/canvas/fill.js +33 -0
  210. package/src/renderers/canvas/index.js +7 -0
  211. package/src/renderers/canvas/path.js +7 -4
  212. package/src/renderers/canvas/rect.js +9 -3
  213. package/src/renderers/canvas/text.js +14 -1
  214. package/src/selectors/datasets.js +4 -0
  215. package/src/theme/defaults.js +4 -0
  216. package/types/extension.d.ts +10 -1
  217. package/types/index.d.ts +76 -0
  218. package/types/program.d.ts +632 -10
  219. package/src/actions/encodings/color.js +0 -424
  220. package/src/grammar/regression.js +0 -578
  221. package/src/grammar/scales.js +0 -1
  222. package/src/materialization/facetGuides.js +0 -426
  223. package/src/materialization/marks.js +0 -316
@@ -5,6 +5,9 @@ import {
5
5
  resolveAutomaticGridOptions,
6
6
  resolveGuideApplicability
7
7
  } from "./applicability.js";
8
+ import { findDataset } from "../../selectors/datasets.js";
9
+ import { findUpstreamTransform } from
10
+ "../../materialization/dataProvenance.js";
8
11
 
9
12
  const OPTIONS = Object.freeze(["axes", "grid", "legend"]);
10
13
 
@@ -21,16 +24,51 @@ function validateOptions(args) {
21
24
  validateGuideOption(args.legend, "createGuides legend");
22
25
  }
23
26
 
24
- function inferAxesOptions(program) {
27
+ function inferAxesOptions(program, applicability) {
25
28
  const horizontalInterval = program.semanticSpec.layers.some(layer =>
26
29
  layer.mark?.type === "rule" &&
27
30
  layer.encoding?.x?.fieldType === "quantitative" &&
28
31
  layer.encoding?.x2?.fieldType === "quantitative" &&
29
32
  ["nominal", "ordinal", "temporal"].includes(layer.encoding?.y?.fieldType)
30
33
  );
31
- return horizontalInterval
34
+ const inferred = horizontalInterval
32
35
  ? { x: { ticksAndLabels: { count: 7 } } }
33
36
  : {};
37
+ const directions = applicability.axes.directions;
38
+ const hasStoredY = program.semanticSpec.layers.some(
39
+ layer => layer.encoding?.y !== undefined
40
+ );
41
+ if (!directions.y && hasStoredY) inferred.y = false;
42
+ const horizonLayers = program.semanticSpec.layers.filter(layer =>
43
+ findUpstreamTransform(
44
+ program,
45
+ findDataset(program, layer.data),
46
+ "horizon"
47
+ ) !== undefined
48
+ );
49
+ if (horizonLayers.length === 1 && directions.x && !directions.y) {
50
+ const scaleId = horizonLayers[0].encoding?.x?.scale;
51
+ const domain = program.resolvedScales?.[scaleId]?.domain;
52
+ if (
53
+ Array.isArray(domain) &&
54
+ domain.length === 2 &&
55
+ domain.every(Number.isFinite) &&
56
+ domain[0] !== domain[1]
57
+ ) {
58
+ const low = Math.min(...domain);
59
+ const high = Math.max(...domain);
60
+ inferred.x = {
61
+ ...(inferred.x ?? {}),
62
+ ticksAndLabels: {
63
+ values: Array.from(
64
+ { length: 6 },
65
+ (_, index) => low + (high - low) * index / 5
66
+ )
67
+ }
68
+ };
69
+ }
70
+ }
71
+ return inferred;
34
72
  }
35
73
 
36
74
  function selectOption(explicit, applicable) {
@@ -39,6 +77,17 @@ function selectOption(explicit, applicable) {
39
77
  return applicable ? {} : undefined;
40
78
  }
41
79
 
80
+ function mergeInferredOptions(inferred, explicit) {
81
+ if (!isPlainObject(inferred) || !isPlainObject(explicit)) return explicit;
82
+ const merged = { ...inferred };
83
+ for (const [key, value] of Object.entries(explicit)) {
84
+ merged[key] = isPlainObject(value) && isPlainObject(inferred[key])
85
+ ? mergeInferredOptions(inferred[key], value)
86
+ : value;
87
+ }
88
+ return merged;
89
+ }
90
+
42
91
  const createGuides = action(
43
92
  {
44
93
  op: "createGuides",
@@ -47,10 +96,16 @@ const createGuides = action(
47
96
  function (args = {}) {
48
97
  validateOptions(args);
49
98
  const applicability = resolveGuideApplicability(this);
50
- const hasAxes = applicability.axes.cartesian || applicability.axes.polar;
99
+ const hasAxes = applicability.axes.cartesian || applicability.axes.polar ||
100
+ applicability.axes.parallel;
101
+ const inferredAxes = applicability.axes.cartesian
102
+ ? inferAxesOptions(this, applicability)
103
+ : {};
51
104
  const axes = args.axes === undefined && applicability.axes.cartesian
52
- ? inferAxesOptions(this)
53
- : selectOption(args.axes, hasAxes);
105
+ ? inferredAxes
106
+ : args.axes !== undefined && args.axes !== false && hasAxes
107
+ ? mergeInferredOptions(inferredAxes, args.axes)
108
+ : selectOption(args.axes, hasAxes);
54
109
  const grid = args.grid === undefined &&
55
110
  (applicability.grid.cartesian || applicability.grid.polar)
56
111
  ? resolveAutomaticGridOptions(this)
@@ -5,6 +5,9 @@ import { normalizeOptions } from "./options.js";
5
5
  import { findLayer } from "../../../../selectors/layers.js";
6
6
  import { findSemanticScale } from "../../../../selectors/scales.js";
7
7
  import { isSizeLegendPoint } from "../size.js";
8
+ import { isStrokeWidthLegendLayer } from "../strokeWidth.js";
9
+ import { legendResourcePolicies } from
10
+ "../../../../materialization/guides/resources.js";
8
11
  import {
9
12
  resolveCurrentDefinition,
10
13
  resolveDefinition,
@@ -80,17 +83,13 @@ export const rematerializeLegend = action(
80
83
  ).some(highlight => highlight.target === config.target);
81
84
  if (hasHighlight) next = next.rematerializeLegendHighlights();
82
85
  }
83
- if (this.guideConfigs.legend?.size !== undefined) {
84
- next = next.rematerializeSizeLegend();
85
- }
86
- if (this.guideConfigs.legend?.gradient !== undefined) {
87
- next = next.rematerializeGradientLegend();
88
- }
89
- if (this.guideConfigs.legend?.interval !== undefined) {
90
- next = next.rematerializeIntervalLegend();
91
- }
92
- if (this.guideConfigs.legend?.opacity !== undefined) {
93
- next = next.rematerializeOpacityLegend();
86
+ for (const policy of legendResourcePolicies()) {
87
+ if (
88
+ policy.rematerializeOp !== undefined &&
89
+ this.guideConfigs.legend?.[policy.kind] !== undefined
90
+ ) {
91
+ next = next[policy.rematerializeOp]();
92
+ }
94
93
  }
95
94
  return next;
96
95
  }
@@ -177,6 +176,8 @@ export const createLegend = action(
177
176
  throw new TypeError("createLegend channels must be an array.");
178
177
  }
179
178
  const explicitSize = channels?.length === 1 && channels[0] === "size";
179
+ const explicitStrokeWidth = channels?.length === 1 &&
180
+ channels[0] === "strokeWidth";
180
181
  const requestedSizeLayer = args.target === undefined
181
182
  ? undefined
182
183
  : findLayer(this, args.target);
@@ -216,6 +217,42 @@ export const createLegend = action(
216
217
  ...(count === undefined ? {} : { count })
217
218
  });
218
219
  }
220
+ const strokeWidthCandidates = this.semanticSpec.layers.filter(
221
+ isStrokeWidthLegendLayer
222
+ );
223
+ const requestedStrokeWidthLayer = args.target === undefined
224
+ ? undefined
225
+ : findLayer(this, args.target);
226
+ const hasOtherStrokeWidthLegendCandidate = this.semanticSpec.layers.some(
227
+ layer => ["color", "shape", "strokeDash", "size", "opacity"].some(
228
+ channel => layer.encoding?.[channel]?.scale !== undefined
229
+ )
230
+ );
231
+ const inferredStrokeWidth = channels === undefined && (
232
+ (args.target !== undefined &&
233
+ isStrokeWidthLegendLayer(requestedStrokeWidthLayer) &&
234
+ !hasNonSizeLegendEncoding(requestedStrokeWidthLayer)) ||
235
+ (args.target === undefined && strokeWidthCandidates.length === 1 &&
236
+ !hasOtherStrokeWidthLegendCandidate)
237
+ );
238
+ if (explicitStrokeWidth || inferredStrokeWidth) {
239
+ const { target, count, position, channels: _channels, ...unsupported } = args;
240
+ const unsupportedKeys = Object.keys(unsupported);
241
+ if (unsupportedKeys.length > 0) {
242
+ throw new Error(
243
+ `Standalone stroke-width legend does not support option "${unsupportedKeys[0]}".`
244
+ );
245
+ }
246
+ if (position !== undefined && position !== "right") {
247
+ throw new Error(
248
+ 'Standalone stroke-width legends currently require position "right".'
249
+ );
250
+ }
251
+ return this.createStrokeWidthLegend({
252
+ ...(target === undefined ? {} : { target }),
253
+ ...(count === undefined ? {} : { count })
254
+ });
255
+ }
219
256
  const opacityCandidates = this.semanticSpec.layers.filter(layer =>
220
257
  layer.mark?.type === "point" && layer.encoding?.opacity?.scale !== undefined
221
258
  );
@@ -1,6 +1,6 @@
1
1
  import { isPlainObject } from "../../../../core/immutable.js";
2
2
  import { noOptions } from "../../../../core/validation.js";
3
- import { mapOrdinalValues } from "../../../../grammar/scales.js";
3
+ import { mapOrdinalValues } from "../../../../grammar/scales/index.js";
4
4
  import { resolveGraphicBounds } from "../../../../layout/canvas.js";
5
5
  import { unionConcreteGraphicBounds } from "../../../../grammar/schemas/graphicBounds.js";
6
6
  import { DEFAULT_COLORS } from "../../../../theme/defaults.js";
@@ -382,7 +382,9 @@ export function resolveLayout(program, config) {
382
382
  const count = config.domain.length;
383
383
  const width = symbolWidth(config);
384
384
  if (config.position === "right") {
385
- const symbolX = Array(count).fill(bounds.x + bounds.width + 30);
385
+ const symbolX = Array(count).fill(
386
+ bounds.x + bounds.width + config.offset
387
+ );
386
388
  const itemY = Array.from(
387
389
  { length: count },
388
390
  (_, index) => bounds.y + 52 + index * config.itemGap
@@ -4,7 +4,7 @@ import {
4
4
  validateKeys,
5
5
  validateOptionObject
6
6
  } from "../../../../core/validation.js";
7
- import { formatDiscretizedIntervals } from "../../../../grammar/scales.js";
7
+ import { formatDiscretizedIntervals } from "../../../../grammar/scales/index.js";
8
8
  import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
9
9
  "../../../../theme/defaults.js";
10
10
  import {
@@ -1,7 +1,7 @@
1
1
  import { action } from "../../../../core/action.js";
2
2
  import { isPlainObject } from "../../../../core/immutable.js";
3
3
  import { validateKeys } from "../../../../core/validation.js";
4
- import { mapLinearValues } from "../../../../grammar/scales.js";
4
+ import { mapLinearValues } from "../../../../grammar/scales/index.js";
5
5
  import { DEFAULT_COLORS } from "../../../../theme/defaults.js";
6
6
  import {
7
7
  assertLegendInsideCanvas,
@@ -1,5 +1,4 @@
1
1
  import { action } from "../../../core/action.js";
2
- import { validateUserId } from "../../../core/identifiers.js";
3
2
  import { validateOptionObject } from "../../../core/validation.js";
4
3
  import { normalizeOptions } from "./categorical/options.js";
5
4
  import { symbolGraphic } from "./categorical/layout.js";
@@ -12,6 +11,7 @@ import { normalizeIntervalLegend } from "./continuous/interval.js";
12
11
  import { findLayer } from "../../../selectors/layers.js";
13
12
  import { resolveLegendGraphicPlacement } from
14
13
  "../../../materialization/graphicHierarchy.js";
14
+ import { resolveLegendTarget } from "./target.js";
15
15
 
16
16
  const OPTIONS = Object.freeze([
17
17
  "target", "position", "align", "direction", "columns", "offset",
@@ -31,24 +31,6 @@ function mergeBorder(previous, patch) {
31
31
  return previous === false ? patch : { ...previous, ...patch };
32
32
  }
33
33
 
34
- function resolveTarget(program, requested) {
35
- const configs = program.guideConfigs.legend ?? {};
36
- const targets = [...new Set(Object.values(configs)
37
- .map(config => config?.target)
38
- .filter(Boolean))];
39
- if (requested !== undefined) {
40
- const target = validateUserId(requested, "Legend target id");
41
- if (!targets.includes(target)) {
42
- throw new Error(`Unknown legend target "${target}".`);
43
- }
44
- return target;
45
- }
46
- if (targets.length !== 1) {
47
- throw new Error("editLegend requires target when the legend is ambiguous.");
48
- }
49
- return targets[0];
50
- }
51
-
52
34
  function reconcileGraphic(program, id, shouldExist, definition) {
53
35
  const exists = program.graphicSpec.objects[id] !== undefined;
54
36
  if (exists && !shouldExist) return program.editGraphics({ target: id, remove: true });
@@ -285,7 +267,7 @@ export const editLegend = action(
285
267
  )) {
286
268
  throw new TypeError('editLegend title must be a non-empty string, "auto", or false.');
287
269
  }
288
- const target = resolveTarget(this, args.target);
270
+ const target = resolveLegendTarget(this, args.target, "editLegend");
289
271
  const configs = this.guideConfigs.legend ?? {};
290
272
  const categoricalKind = ["series", "color"].find(
291
273
  kind => configs[kind]?.target === target
@@ -302,6 +284,11 @@ export const editLegend = action(
302
284
  if (configs.interval?.target === target) {
303
285
  return editInterval(this, configs.interval, args);
304
286
  }
287
+ if (configs.strokeWidth?.target === target) {
288
+ throw new Error(
289
+ "editLegend does not yet support stroke-width legends; edit the owning scale instead."
290
+ );
291
+ }
305
292
  const continuousKind = ["gradient", "opacity"].find(
306
293
  kind => configs[kind]?.target === target
307
294
  );
@@ -1,6 +1,7 @@
1
1
  import { registerCategoricalLegendActions } from "./categorical/index.js";
2
2
  import { registerContinuousLegendActions } from "./continuous/index.js";
3
3
  import { registerSizeLegendActions } from "./size.js";
4
+ import { registerStrokeWidthLegendActions } from "./strokeWidth.js";
4
5
  import { registerFocusedLegendActions } from "./focused.js";
5
6
  import { removeLegend } from "./remove.js";
6
7
 
@@ -8,6 +9,7 @@ export function registerLegendActions(ProgramClass) {
8
9
  registerCategoricalLegendActions(ProgramClass);
9
10
  registerContinuousLegendActions(ProgramClass);
10
11
  registerSizeLegendActions(ProgramClass);
12
+ registerStrokeWidthLegendActions(ProgramClass);
11
13
  registerFocusedLegendActions(ProgramClass);
12
14
  ProgramClass.prototype.removeLegend = removeLegend;
13
15
  }
@@ -1,47 +1,22 @@
1
1
  import { action } from "../../../core/action.js";
2
- import { validateUserId } from "../../../core/identifiers.js";
3
2
  import { validateKeys } from "../../../core/validation.js";
4
- import { legendGraphicIds } from
3
+ import { legendGraphicIds, legendResourcePolicy } from
5
4
  "../../../materialization/guides/resources.js";
5
+ import { resolveLegendTarget } from "./target.js";
6
6
 
7
7
  const OPTIONS = Object.freeze(["target"]);
8
8
 
9
- function resolveTarget(program, requested) {
10
- const targets = [...new Set(Object.values(program.guideConfigs.legend ?? {})
11
- .map(config => config?.target)
12
- .filter(Boolean))];
13
- if (requested !== undefined) {
14
- const target = validateUserId(requested, "Legend target id");
15
- if (!targets.includes(target)) {
16
- throw new Error(`Unknown legend target "${target}".`);
17
- }
18
- return target;
19
- }
20
- if (targets.length === 0) throw new Error("removeLegend requires an existing legend.");
21
- if (targets.length > 1) {
22
- throw new Error("removeLegend requires target when the legend is ambiguous.");
23
- }
24
- return targets[0];
25
- }
26
-
27
- const SEMANTIC_KIND = Object.freeze({
28
- series: "series",
29
- color: "color",
30
- size: "size",
31
- gradient: "color",
32
- interval: "color",
33
- opacity: "opacity"
34
- });
35
-
36
9
  export const removeLegend = action(
37
10
  { op: "removeLegend", description: "Remove every legend block owned by one mark." },
38
11
  function (args = {}) {
39
12
  validateKeys(args, OPTIONS, "removeLegend");
40
- const target = resolveTarget(this, args.target);
13
+ const target = resolveLegendTarget(this, args.target, "removeLegend");
41
14
  const kinds = Object.entries(this.guideConfigs.legend ?? {})
42
15
  .filter(([, config]) => config?.target === target)
43
16
  .map(([kind]) => kind);
44
- const semanticKinds = new Set(kinds.map(kind => SEMANTIC_KIND[kind]));
17
+ const semanticKinds = new Set(
18
+ kinds.map(kind => legendResourcePolicy(kind).semanticKind)
19
+ );
45
20
  let next = this;
46
21
  for (const kind of semanticKinds) {
47
22
  if (next.semanticSpec.guides.legend?.[kind] !== undefined) {
@@ -1,6 +1,6 @@
1
1
  import { action } from "../../../core/action.js";
2
2
  import { validateKeys } from "../../../core/validation.js";
3
- import { mapLinearValues } from "../../../grammar/scales.js";
3
+ import { mapLinearValues } from "../../../grammar/scales/index.js";
4
4
  import { resolveGraphicBounds } from "../../../layout/canvas.js";
5
5
  import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
6
6
  "../../../theme/defaults.js";
@@ -0,0 +1,170 @@
1
+ import { action } from "../../../core/action.js";
2
+ import { validateKeys } from "../../../core/validation.js";
3
+ import { mapContinuousScaleValues } from "../../../grammar/scales/index.js";
4
+ import { resolveGraphicBounds } from "../../../layout/canvas.js";
5
+ import { resolveLegendGraphicPlacement } from
6
+ "../../../materialization/graphicHierarchy.js";
7
+ import { findLayer } from "../../../selectors/layers.js";
8
+ import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
9
+ "../../../theme/defaults.js";
10
+
11
+ const OPTIONS = Object.freeze(["target", "count"]);
12
+
13
+ export function isStrokeWidthLegendLayer(layer) {
14
+ return ["line", "rule"].includes(layer?.mark?.type) &&
15
+ layer.encoding?.strokeWidth?.scale !== undefined;
16
+ }
17
+
18
+ function resolveLayer(program, requested) {
19
+ const candidates = program.semanticSpec.layers.filter(isStrokeWidthLegendLayer);
20
+ const layer = requested === undefined
21
+ ? candidates.length === 1 ? candidates[0] : undefined
22
+ : (() => {
23
+ const candidate = findLayer(program, requested);
24
+ return candidates.includes(candidate) ? candidate : undefined;
25
+ })();
26
+ if (layer === undefined) {
27
+ throw new Error(
28
+ requested === undefined
29
+ ? "Stroke-width legend requires one eligible line or rule mark or an explicit target."
30
+ : `Unknown stroke-width legend target "${requested}".`
31
+ );
32
+ }
33
+ return layer;
34
+ }
35
+
36
+ function requireScale(program, id) {
37
+ const scale = program.resolvedScales[id];
38
+ if (scale === undefined || !["linear", "log", "pow", "sqrt", "symlog"].includes(scale.type)) {
39
+ throw new Error(`Stroke-width legend requires resolved quantitative scale "${id}".`);
40
+ }
41
+ return scale;
42
+ }
43
+
44
+ function styleText(program, id, { title = false } = {}) {
45
+ return program
46
+ .editGraphics({ target: id, property: "fill", value: title
47
+ ? DEFAULT_COLORS.strongText
48
+ : DEFAULT_COLORS.text })
49
+ .editGraphics({ target: id, property: "fontSize", value: title ? 13 : 12 })
50
+ .editGraphics({ target: id, property: "fontFamily", value: DEFAULT_FONT_FAMILY })
51
+ .editGraphics({ target: id, property: "fontWeight", value: title ? 600 : "normal" })
52
+ .editGraphics({ target: id, property: "textAlign", value: "left" })
53
+ .editGraphics({ target: id, property: "textBaseline", value: "middle" });
54
+ }
55
+
56
+ export const rematerializeStrokeWidthLegend = action(
57
+ {
58
+ op: "rematerializeStrokeWidthLegend",
59
+ description: "Rematerialize a quantitative stroke-width legend."
60
+ },
61
+ function (args = {}) {
62
+ validateKeys(args, [], "rematerializeStrokeWidthLegend");
63
+ const config = this.guideConfigs.legend?.strokeWidth;
64
+ if (config === undefined) {
65
+ throw new Error("Stroke-width legend requires stored configuration.");
66
+ }
67
+ const layer = findLayer(this, config.target);
68
+ const encoding = layer?.encoding?.strokeWidth;
69
+ if (encoding?.scale === undefined) {
70
+ throw new Error("Stroke-width legend target requires a strokeWidth encoding.");
71
+ }
72
+ const scale = requireScale(this, encoding.scale);
73
+ const plot = resolveGraphicBounds(this);
74
+ if (plot === undefined) {
75
+ throw new Error("Stroke-width legend requires resolved plot bounds.");
76
+ }
77
+ const values = Array.from(
78
+ { length: config.count },
79
+ (_, index) => scale.domain[0] +
80
+ index / (config.count - 1) * (scale.domain[1] - scale.domain[0])
81
+ );
82
+ const widths = mapContinuousScaleValues(values, scale);
83
+ const originX = plot.x + plot.width + 30;
84
+ const titleY = plot.y + 28;
85
+ const y = values.map((_, index) => titleY + 34 + index * 32);
86
+ const title = config.inferredTitle === true ? encoding.field : config.title;
87
+ let next = this
88
+ .editSemantic({ property: "guide.legend.strokeWidth.scale", value: encoding.scale })
89
+ .editSemantic({ property: "guide.legend.strokeWidth.title", value: title })
90
+ ._withLegendConfig("strokeWidth", {
91
+ ...config,
92
+ scale: encoding.scale,
93
+ title,
94
+ domain: scale.domain
95
+ })
96
+ .editGraphics({ target: "strokeWidthLegendSymbols", property: "length", value: values.length })
97
+ .editGraphics({ target: "strokeWidthLegendSymbols", property: "x1", value: values.map(() => originX) })
98
+ .editGraphics({ target: "strokeWidthLegendSymbols", property: "x2", value: values.map(() => originX + 32) })
99
+ .editGraphics({ target: "strokeWidthLegendSymbols", property: "y1", value: y })
100
+ .editGraphics({ target: "strokeWidthLegendSymbols", property: "y2", value: y })
101
+ .editGraphics({ target: "strokeWidthLegendSymbols", property: "stroke", value: DEFAULT_COLORS.mark })
102
+ .editGraphics({ target: "strokeWidthLegendSymbols", property: "strokeWidth", value: widths })
103
+ .editGraphics({ target: "strokeWidthLegendLabels", property: "length", value: values.length })
104
+ .editGraphics({ target: "strokeWidthLegendLabels", property: "x", value: values.map(() => originX + 44) })
105
+ .editGraphics({ target: "strokeWidthLegendLabels", property: "y", value: y })
106
+ .editGraphics({
107
+ target: "strokeWidthLegendLabels",
108
+ property: "text",
109
+ value: values.map(value => String(+value.toPrecision(3)))
110
+ })
111
+ .editGraphics({ target: "strokeWidthLegendTitle", property: "x", value: originX })
112
+ .editGraphics({ target: "strokeWidthLegendTitle", property: "y", value: titleY })
113
+ .editGraphics({ target: "strokeWidthLegendTitle", property: "text", value: title });
114
+ next = styleText(next, "strokeWidthLegendLabels");
115
+ return styleText(next, "strokeWidthLegendTitle", { title: true });
116
+ }
117
+ );
118
+
119
+ export const createStrokeWidthLegend = action(
120
+ {
121
+ op: "createStrokeWidthLegend",
122
+ description: "Create a quantitative stroke-width legend."
123
+ },
124
+ function (args = {}) {
125
+ validateKeys(args, OPTIONS, "createStrokeWidthLegend");
126
+ const layer = resolveLayer(this, args.target);
127
+ const encoding = layer.encoding.strokeWidth;
128
+ requireScale(this, encoding.scale);
129
+ const count = args.count ?? 5;
130
+ if (!Number.isInteger(count) || count < 2) {
131
+ throw new RangeError(
132
+ "Stroke-width legend count must be an integer of at least 2."
133
+ );
134
+ }
135
+ return this
136
+ .editSemantic({ property: "guide.legend.strokeWidth.scale", value: encoding.scale })
137
+ .editSemantic({ property: "guide.legend.strokeWidth.title", value: encoding.field })
138
+ ._withLegendConfig("strokeWidth", {
139
+ target: layer.id,
140
+ scale: encoding.scale,
141
+ title: encoding.field,
142
+ inferredTitle: true,
143
+ count
144
+ })
145
+ .createGraphics({
146
+ id: "strokeWidthLegendSymbols",
147
+ type: "line",
148
+ length: count,
149
+ ...resolveLegendGraphicPlacement(this)
150
+ })
151
+ .createGraphics({
152
+ id: "strokeWidthLegendLabels",
153
+ type: "text",
154
+ length: count,
155
+ ...resolveLegendGraphicPlacement(this)
156
+ })
157
+ .createGraphics({
158
+ id: "strokeWidthLegendTitle",
159
+ type: "text",
160
+ ...resolveLegendGraphicPlacement(this)
161
+ })
162
+ .rematerializeStrokeWidthLegend();
163
+ }
164
+ );
165
+
166
+ export function registerStrokeWidthLegendActions(ProgramClass) {
167
+ ProgramClass.prototype.createStrokeWidthLegend = createStrokeWidthLegend;
168
+ ProgramClass.prototype.rematerializeStrokeWidthLegend =
169
+ rematerializeStrokeWidthLegend;
170
+ }
@@ -0,0 +1,25 @@
1
+ import { validateUserId } from "../../../core/identifiers.js";
2
+
3
+ export function legendTargets(program) {
4
+ return [...new Set(Object.values(program.guideConfigs.legend ?? {})
5
+ .map(config => config?.target)
6
+ .filter(Boolean))];
7
+ }
8
+
9
+ export function resolveLegendTarget(program, requested, operation) {
10
+ const targets = legendTargets(program);
11
+ if (requested !== undefined) {
12
+ const target = validateUserId(requested, "Legend target id");
13
+ if (!targets.includes(target)) {
14
+ throw new Error(`Unknown legend target "${target}".`);
15
+ }
16
+ return target;
17
+ }
18
+ if (operation === "removeLegend" && targets.length === 0) {
19
+ throw new Error("removeLegend requires an existing legend.");
20
+ }
21
+ if (targets.length !== 1) {
22
+ throw new Error(`${operation} requires target when the legend is ambiguous.`);
23
+ }
24
+ return targets[0];
25
+ }
@@ -3,31 +3,31 @@ import {
3
3
  createThetaAxis,
4
4
  editRadialAxis,
5
5
  editThetaAxis
6
- } from "./axes/facade.js";
6
+ } from "./facade.js";
7
7
  import {
8
8
  createRadialAxisLabels,
9
9
  createThetaAxisLabels,
10
10
  editRadialAxisLabels,
11
11
  editThetaAxisLabels
12
- } from "./axes/labels.js";
12
+ } from "./labels.js";
13
13
  import {
14
14
  createRadialAxisLine,
15
15
  createThetaAxisLine,
16
16
  editRadialAxisLine,
17
17
  editThetaAxisLine
18
- } from "./axes/lines.js";
18
+ } from "./lines.js";
19
19
  import {
20
20
  createRadialAxisTicks,
21
21
  createThetaAxisTicks,
22
22
  editRadialAxisTicks,
23
23
  editThetaAxisTicks
24
- } from "./axes/ticks.js";
24
+ } from "./ticks.js";
25
25
  import {
26
26
  createRadialAxisTitle,
27
27
  createThetaAxisTitle,
28
28
  editRadialAxisTitle,
29
29
  editThetaAxisTitle
30
- } from "./axes/titles.js";
30
+ } from "./titles.js";
31
31
 
32
32
  export function registerPolarAxisActions(ProgramClass) {
33
33
  Object.assign(ProgramClass.prototype, {
@@ -1,2 +1,2 @@
1
1
  export { registerPolarGridActions } from "./grids.js";
2
- export { registerPolarAxisActions } from "./axes.js";
2
+ export { registerPolarAxisActions } from "./axes/index.js";
@@ -10,7 +10,7 @@ import {
10
10
  isTransformedScaleType,
11
11
  mapContinuousScaleValues,
12
12
  mapOrdinalPositionValues
13
- } from "../../../grammar/scales.js";
13
+ } from "../../../grammar/scales/index.js";
14
14
  import { formatTimeTick, formatTimeTicks } from "../../../grammar/ticks.js";
15
15
  import { resolveGraphicBounds } from "../../../layout/canvas.js";
16
16
  import { findCoordinate } from "../../../selectors/coordinates.js";
@@ -3,7 +3,7 @@ import {
3
3
  isTransformedScaleType,
4
4
  readQuantitativeField,
5
5
  transformedTicks
6
- } from "../../grammar/scales.js";
6
+ } from "../../grammar/scales/index.js";
7
7
  import { niceTicks, timeTicks } from "../../grammar/ticks.js";
8
8
  import { findDataset } from "../../selectors/datasets.js";
9
9
  import { findSemanticScale } from "../../selectors/scales.js";
@@ -1,4 +1,5 @@
1
1
  import { registerCanvasActions } from "./canvas/index.js";
2
+ import { registerChartActions } from "./charts/index.js";
2
3
  import { registerCoordinateActions } from "./coordinates/index.js";
3
4
  import { registerCompositionActions } from "./composition/index.js";
4
5
  import { registerDataActions } from "./data/index.js";
@@ -8,12 +9,14 @@ import { registerErrorBarActions } from "./errorBars/index.js";
8
9
  import { registerFacetActions } from "./facets/index.js";
9
10
  import { registerBoxPlotActions } from "./boxPlots/index.js";
10
11
  import { registerGuideActions } from "./guides/index.js";
12
+ import { registerGradientPlotActions } from "./gradientPlots/index.js";
11
13
  import { registerMarkActions } from "./marks/index.js";
12
14
  import { registerPrimitiveActions } from "./primitives/index.js";
13
15
  import { registerRegressionActions } from "./regression/index.js";
14
16
  import { registerScaleActions } from "./scales/index.js";
15
17
  import { registerTitleActions } from "./titles/index.js";
16
18
  import { registerSelectionActions } from "./selection/index.js";
19
+ import { registerViolinPlotActions } from "./violinPlots/index.js";
17
20
 
18
21
  export function registerActions(ProgramClass) {
19
22
  registerPrimitiveActions(ProgramClass);
@@ -31,5 +34,8 @@ export function registerActions(ProgramClass) {
31
34
  registerErrorBarActions(ProgramClass);
32
35
  registerErrorBandActions(ProgramClass);
33
36
  registerBoxPlotActions(ProgramClass);
37
+ registerGradientPlotActions(ProgramClass);
38
+ registerViolinPlotActions(ProgramClass);
39
+ registerChartActions(ProgramClass);
34
40
  registerSelectionActions(ProgramClass);
35
41
  }
@@ -3,7 +3,7 @@ import {
3
3
  resolveOptionalUserId,
4
4
  validateUserId
5
5
  } from "../../core/identifiers.js";
6
- import { readQuantitativeField } from "../../grammar/scales.js";
6
+ import { readQuantitativeField } from "../../grammar/scales/index.js";
7
7
  import { findDataset } from "../../selectors/datasets.js";
8
8
  import { hasLayer, resolveEligibleLayer } from "../../selectors/layers.js";
9
9
  import { findSemanticScale } from "../../selectors/scales.js";