ggaction 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +35 -13
  3. package/package.json +18 -7
  4. package/src/actions/boxPlots/create.js +13 -7
  5. package/src/actions/boxPlots/edit.js +31 -35
  6. package/src/actions/boxPlots/materialize.js +3 -0
  7. package/src/actions/boxPlots/options.js +8 -1
  8. package/src/actions/boxPlots/resolve.js +10 -2
  9. package/src/actions/charts/bar.js +50 -0
  10. package/src/actions/charts/heatmap.js +205 -0
  11. package/src/actions/charts/histogram.js +58 -0
  12. package/src/actions/charts/index.js +15 -0
  13. package/src/actions/charts/line.js +69 -0
  14. package/src/actions/charts/parallel.js +76 -0
  15. package/src/actions/charts/scatter.js +56 -0
  16. package/src/actions/charts/shared.js +121 -0
  17. package/src/actions/coordinates/parallel.js +50 -0
  18. package/src/actions/data/bin2d.js +137 -0
  19. package/src/actions/data/density.js +54 -13
  20. package/src/actions/data/derived.js +22 -0
  21. package/src/actions/data/filter.js +5 -0
  22. package/src/actions/data/gradientProfile.js +65 -0
  23. package/src/actions/data/horizon.js +71 -0
  24. package/src/actions/data/index.js +29 -2
  25. package/src/actions/data/window.js +50 -0
  26. package/src/actions/encodings/color/index.js +17 -1
  27. package/src/actions/encodings/color/layout.js +8 -0
  28. package/src/actions/encodings/density/resolve.js +109 -0
  29. package/src/actions/encodings/density.js +193 -88
  30. package/src/actions/encodings/horizon/resolve.js +283 -0
  31. package/src/actions/encodings/horizon.js +364 -0
  32. package/src/actions/encodings/index.js +6 -0
  33. package/src/actions/encodings/parallel.js +115 -0
  34. package/src/actions/encodings/pathOrder.js +131 -0
  35. package/src/actions/encodings/position/apply.js +73 -4
  36. package/src/actions/encodings/position/index.js +15 -2
  37. package/src/actions/encodings/position/policies/arc.js +27 -4
  38. package/src/actions/encodings/position/policies/area.js +33 -2
  39. package/src/actions/encodings/position/policies/index.js +7 -2
  40. package/src/actions/encodings/position/policies/line.js +18 -2
  41. package/src/actions/encodings/position/resolve.js +24 -2
  42. package/src/actions/encodings/ranged.js +3 -1
  43. package/src/actions/encodings/ruleAppearance.js +88 -10
  44. package/src/actions/facets/actions.js +1 -2
  45. package/src/actions/facets/derive.js +33 -3
  46. package/src/actions/facets/replay.js +7 -36
  47. package/src/actions/gradientPlots/components.js +177 -0
  48. package/src/actions/gradientPlots/create.js +96 -0
  49. package/src/actions/gradientPlots/edit.js +153 -0
  50. package/src/actions/gradientPlots/index.js +24 -0
  51. package/src/actions/gradientPlots/materialize.js +287 -0
  52. package/src/actions/gradientPlots/options.js +120 -0
  53. package/src/actions/gradientPlots/paint.js +107 -0
  54. package/src/actions/gradientPlots/rebind.js +51 -0
  55. package/src/actions/gradientPlots/resolve.js +81 -0
  56. package/src/actions/guides/applicability.js +44 -17
  57. package/src/actions/guides/axes/axes.js +37 -3
  58. package/src/actions/guides/axes/index.js +2 -0
  59. package/src/actions/guides/axes/labels.js +11 -6
  60. package/src/actions/guides/axes/parallel/resolve.js +86 -0
  61. package/src/actions/guides/axes/parallel.js +228 -0
  62. package/src/actions/guides/axes/titles.js +11 -8
  63. package/src/actions/guides/guides.js +60 -5
  64. package/src/actions/guides/legends/categorical/actions.js +48 -11
  65. package/src/actions/guides/legends/edit.js +5 -0
  66. package/src/actions/guides/legends/index.js +2 -0
  67. package/src/actions/guides/legends/remove.js +4 -11
  68. package/src/actions/guides/legends/strokeWidth.js +170 -0
  69. package/src/actions/index.js +6 -0
  70. package/src/actions/marks/area/actions.js +60 -136
  71. package/src/actions/marks/area/materialize.js +163 -0
  72. package/src/actions/marks/index.js +5 -1
  73. package/src/actions/marks/line/actions.js +99 -80
  74. package/src/actions/marks/line/materialize.js +146 -0
  75. package/src/actions/marks/point/create.js +76 -0
  76. package/src/actions/marks/point/edit.js +85 -0
  77. package/src/actions/marks/point/index.js +11 -1
  78. package/src/actions/marks/point/jitter.js +72 -0
  79. package/src/actions/marks/point/{actions.js → materialize.js} +175 -170
  80. package/src/actions/marks/rect/actions.js +3 -0
  81. package/src/actions/marks/remove.js +13 -2
  82. package/src/actions/marks/rule/actions.js +25 -3
  83. package/src/actions/marks/text/actions.js +19 -4
  84. package/src/actions/marks/text/index.js +7 -1
  85. package/src/actions/marks/text/layout.js +321 -0
  86. package/src/actions/primitives/semanticValidation/dataset.js +22 -0
  87. package/src/actions/primitives/semanticValidation/guide.js +43 -0
  88. package/src/actions/primitives/semanticValidation/index.js +23 -0
  89. package/src/actions/primitives/semanticValidation/layer.js +74 -0
  90. package/src/actions/primitives/semanticValidation/scale.js +74 -0
  91. package/src/actions/primitives/semanticValidation/shared.js +5 -0
  92. package/src/actions/primitives/semanticValue.js +1 -204
  93. package/src/actions/regression/edit.js +19 -21
  94. package/src/actions/scales/consumers/common.js +18 -0
  95. package/src/actions/scales/consumers/families.js +14 -3
  96. package/src/actions/scales/definitions.js +30 -0
  97. package/src/actions/scales/edit.js +6 -204
  98. package/src/actions/scales/editPolicy.js +214 -0
  99. package/src/actions/scales/materialize.js +1 -1
  100. package/src/actions/scales/patch.js +26 -0
  101. package/src/actions/selection/actions.js +28 -3
  102. package/src/actions/violinPlots/create.js +198 -0
  103. package/src/actions/violinPlots/index.js +5 -0
  104. package/src/core/textMetrics.js +47 -0
  105. package/src/core/vocabulary.js +7 -2
  106. package/src/grammar/arcs.js +49 -12
  107. package/src/grammar/areaSeries.js +41 -5
  108. package/src/grammar/bin2d.js +333 -0
  109. package/src/grammar/categoricalDensity.js +192 -0
  110. package/src/grammar/coordinates.js +1 -1
  111. package/src/grammar/curveCommands.js +3 -1
  112. package/src/grammar/density.js +252 -26
  113. package/src/grammar/facets/dependencies.js +3 -11
  114. package/src/grammar/facets/index.js +3 -1
  115. package/src/grammar/gradientProfile.js +213 -0
  116. package/src/grammar/horizon.js +455 -0
  117. package/src/grammar/jitter.js +197 -0
  118. package/src/grammar/lineSeries.js +69 -0
  119. package/src/grammar/paint.js +88 -0
  120. package/src/grammar/parallelCoordinates.js +213 -0
  121. package/src/grammar/pathOrder.js +35 -0
  122. package/src/grammar/pointShapes.js +24 -0
  123. package/src/grammar/rules.js +1 -0
  124. package/src/grammar/scales/appearance.js +21 -0
  125. package/src/grammar/schemas/concreteGraphic.js +11 -1
  126. package/src/grammar/schemas/graphicBounds.js +19 -37
  127. package/src/grammar/schemas/semanticPath.js +17 -2
  128. package/src/grammar/transforms.js +100 -11
  129. package/src/grammar/window.js +339 -0
  130. package/src/layout/labels.js +270 -0
  131. package/src/layout/text.js +4 -1
  132. package/src/layout/title.js +10 -20
  133. package/src/materialization/dataProvenance.js +56 -2
  134. package/src/materialization/dependencies.js +9 -14
  135. package/src/materialization/guides/resources.js +74 -24
  136. package/src/materialization/horizon.js +29 -0
  137. package/src/materialization/layout.js +15 -0
  138. package/src/materialization/marks/capabilities.js +21 -1
  139. package/src/materialization/marks/index.js +3 -0
  140. package/src/materialization/marks/pathOrder.js +71 -0
  141. package/src/materialization/marks/policies.js +6 -2
  142. package/src/materialization/scaleGuideDependencies.js +8 -0
  143. package/src/materialization/scales/resolve.js +2 -0
  144. package/src/materialization/selection/items/path.js +27 -1
  145. package/src/materialization/selection/items/point.js +1 -5
  146. package/src/materialization/selection/items/rect.js +21 -0
  147. package/src/materialization/selection/styles.js +20 -9
  148. package/src/materialization/text.js +12 -4
  149. package/src/renderers/canvas/fill.js +33 -0
  150. package/src/renderers/canvas/index.js +7 -0
  151. package/src/renderers/canvas/path.js +7 -4
  152. package/src/renderers/canvas/rect.js +9 -3
  153. package/src/selectors/datasets.js +4 -0
  154. package/src/theme/defaults.js +4 -0
  155. package/types/extension.d.ts +6 -0
  156. package/types/index.d.ts +76 -0
  157. package/types/program.d.ts +632 -10
@@ -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
  );
@@ -284,6 +284,11 @@ export const editLegend = action(
284
284
  if (configs.interval?.target === target) {
285
285
  return editInterval(this, configs.interval, args);
286
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
+ }
287
292
  const continuousKind = ["gradient", "opacity"].find(
288
293
  kind => configs[kind]?.target === target
289
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,20 +1,11 @@
1
1
  import { action } from "../../../core/action.js";
2
2
  import { validateKeys } from "../../../core/validation.js";
3
- import { legendGraphicIds } from
3
+ import { legendGraphicIds, legendResourcePolicy } from
4
4
  "../../../materialization/guides/resources.js";
5
5
  import { resolveLegendTarget } from "./target.js";
6
6
 
7
7
  const OPTIONS = Object.freeze(["target"]);
8
8
 
9
- const SEMANTIC_KIND = Object.freeze({
10
- series: "series",
11
- color: "color",
12
- size: "size",
13
- gradient: "color",
14
- interval: "color",
15
- opacity: "opacity"
16
- });
17
-
18
9
  export const removeLegend = action(
19
10
  { op: "removeLegend", description: "Remove every legend block owned by one mark." },
20
11
  function (args = {}) {
@@ -23,7 +14,9 @@ export const removeLegend = action(
23
14
  const kinds = Object.entries(this.guideConfigs.legend ?? {})
24
15
  .filter(([, config]) => config?.target === target)
25
16
  .map(([kind]) => kind);
26
- const semanticKinds = new Set(kinds.map(kind => SEMANTIC_KIND[kind]));
17
+ const semanticKinds = new Set(
18
+ kinds.map(kind => legendResourcePolicy(kind).semanticKind)
19
+ );
27
20
  let next = this;
28
21
  for (const kind of semanticKinds) {
29
22
  if (next.semanticSpec.guides.legend?.[kind] !== undefined) {
@@ -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
+ }
@@ -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
  }
@@ -5,12 +5,6 @@ import {
5
5
  validateNonNegativeFinite,
6
6
  validateUnitInterval
7
7
  } from "../../../core/validation.js";
8
- import {
9
- deriveAreaSeries,
10
- deriveDensityAreaSeries,
11
- layoutDensityAreaSeries
12
- } from "../../../grammar/areaSeries.js";
13
- import { mapContinuousScaleValues, mapOrdinalValues } from "../../../grammar/scales/index.js";
14
8
  import {
15
9
  assertMarkAvailable,
16
10
  applyLayeredMarkInheritance,
@@ -23,17 +17,14 @@ import {
23
17
  import { DEFAULT_COLORS } from "../../../theme/defaults.js";
24
18
  import { findDataset } from "../../../selectors/datasets.js";
25
19
  import { findLayer } from "../../../selectors/layers.js";
26
- import {
27
- buildAreaCurvePathCommands,
28
- validateCurveInterpolation
29
- } from "../../../grammar/curveCommands.js";
30
- import { buildLinearPathCommands } from "../../../grammar/pathCommands.js";
20
+ import { validateCurveInterpolation } from "../../../grammar/curveCommands.js";
31
21
  import { resolveEligibleLayer } from "../../../selectors/layers.js";
32
22
  import { canMaterializeArea } from "../../../materialization/marks/index.js";
33
23
  import { findUpstreamTransform } from "../../../materialization/dataProvenance.js";
34
24
  import { resolveMarkGraphicPlacement } from
35
25
  "../../../materialization/graphicHierarchy.js";
36
26
  import { rematerializeHighlightBaseline } from "../lifecycle.js";
27
+ import { resolveAreaMaterialization } from "./materialize.js";
37
28
 
38
29
  const CREATE_OPTIONS = Object.freeze([
39
30
  "id", "data", "fill", "opacity", "stroke", "strokeWidth", "curve"
@@ -42,6 +33,7 @@ const EDIT_OPTIONS = Object.freeze([
42
33
  "target", "fill", "opacity", "stroke", "strokeWidth", "curve"
43
34
  ]);
44
35
  const REMATERIALIZE_OPTIONS = Object.freeze(["id", "scales"]);
36
+ const STROKE_FROM_FILL_OPTIONS = Object.freeze(["id", "strokeWidth"]);
45
37
 
46
38
  export function validateAreaCreateOutline(args, operation = "createAreaMark") {
47
39
  if (Object.hasOwn(args, "strokeWidth") && !Object.hasOwn(args, "stroke")) {
@@ -103,6 +95,35 @@ const createAreaMark = action(
103
95
  }
104
96
  );
105
97
 
98
+ const configureAreaStrokeFromFill = action(
99
+ {
100
+ op: "configureAreaStrokeFromFill",
101
+ description: "Use each area path fill as its outline color."
102
+ },
103
+ function (args = {}) {
104
+ validateMarkOptions(
105
+ args,
106
+ STROKE_FROM_FILL_OPTIONS,
107
+ "configureAreaStrokeFromFill"
108
+ );
109
+ const id = validateUserId(args.id, "Area mark id");
110
+ const layer = findLayer(this, id);
111
+ if (layer?.mark?.type !== "area") {
112
+ throw new Error(`Unknown area mark "${id}".`);
113
+ }
114
+ const { stroke: _stroke, ...config } = this.markConfigs[id] ?? {};
115
+ void _stroke;
116
+ return this._withMarkConfig(id, {
117
+ ...config,
118
+ strokeFromFill: true,
119
+ strokeWidth: validateNonNegativeFinite(
120
+ args.strokeWidth ?? 1,
121
+ "Area strokeWidth"
122
+ )
123
+ });
124
+ }
125
+ );
126
+
106
127
  const rematerializeAreaMark = action(
107
128
  {
108
129
  op: "rematerializeAreaMark",
@@ -149,14 +170,7 @@ const rematerializeAreaMark = action(
149
170
  : `Density area mark "${id}" requires x and y scales.`
150
171
  );
151
172
  }
152
- const rawDerived = densityTransform === undefined
153
- ? deriveAreaSeries(dataset.values, layer)
154
- : deriveDensityAreaSeries(dataset.values, layer, densityTransform);
155
173
  const colorEncoding = layer.encoding?.color;
156
- const layout = colorEncoding?.layout ?? "overlay";
157
- const derived = densityTransform === undefined
158
- ? rawDerived
159
- : layoutDensityAreaSeries(rawDerived, layout);
160
174
  let resolved = args.scales === false
161
175
  ? this
162
176
  : this
@@ -165,122 +179,17 @@ const rematerializeAreaMark = action(
165
179
  if (args.scales !== false && colorEncoding?.scale !== undefined) {
166
180
  resolved = resolved.rematerializeScale({ id: colorEncoding.scale });
167
181
  }
168
- const xScale = resolved.resolvedScales[xScaleId];
169
- const yScale = resolved.resolvedScales[yScaleId];
170
- const densityScale = densityTransform === undefined
171
- ? undefined
172
- : derived.mode === "y-density" ? yScale : xScale;
173
- if (
174
- densityScale !== undefined &&
175
- (densityScale.domain[0] > 0 || densityScale.domain[1] < 0)
176
- ) {
177
- throw new Error(`Density area mark "${id}" requires a scale domain containing zero.`);
178
- }
179
- const paths = derived.series.map(series => {
180
- const curve = this.markConfigs[id]?.curve ?? "linear";
181
- if (densityTransform === undefined && derived.orientation === "horizontal") {
182
- const y = mapContinuousScaleValues(
183
- series.values.map(value => value.y),
184
- yScale
185
- );
186
- const lower = mapContinuousScaleValues(
187
- series.values.map(value => value.x),
188
- xScale
189
- );
190
- const upper = mapContinuousScaleValues(
191
- series.values.map(value => value.x2),
192
- xScale
193
- );
194
- return buildAreaCurvePathCommands(
195
- y.map((value, index) => ({ x: lower[index], y: value })),
196
- y.map((value, index) => ({ x: upper[index], y: value })),
197
- curve,
198
- { independentAxis: "y" }
199
- );
200
- }
201
- if (densityTransform !== undefined && derived.mode === "x-density") {
202
- const y = mapContinuousScaleValues(
203
- series.values.map(value => value.y),
204
- yScale
205
- );
206
- const upper = mapContinuousScaleValues(
207
- series.values.map(value => value.x),
208
- xScale
209
- );
210
- const baseline = mapContinuousScaleValues(
211
- [0],
212
- densityScale
213
- )[0];
214
- if (curve === "linear") {
215
- return buildLinearPathCommands([
216
- { x: baseline, y: y[0] },
217
- ...y.map((value, index) => ({ x: upper[index], y: value })),
218
- { x: baseline, y: y.at(-1) }
219
- ], { close: true });
220
- }
221
- return buildAreaCurvePathCommands(
222
- y.map(value => ({ x: baseline, y: value })),
223
- y.map((value, index) => ({ x: upper[index], y: value })),
224
- curve,
225
- { independentAxis: "y" }
226
- );
227
- }
228
- const x = mapContinuousScaleValues(
229
- series.values.map(value => value.x),
230
- xScale
231
- );
232
- const lowerValues = densityTransform === undefined
233
- ? series.values.map(value => value.y)
234
- : derived.mode === "y-density"
235
- ? series.values.map(value => value.lower)
236
- : series.values.map(value => value.y);
237
- const lower = mapContinuousScaleValues(
238
- lowerValues,
239
- yScale
240
- );
241
- const upperValues = densityTransform === undefined
242
- ? series.values.map(value => value.y2)
243
- : series.values.map(value => value.upper);
244
- const upper = mapContinuousScaleValues(
245
- upperValues,
246
- yScale
247
- );
248
- if (densityTransform !== undefined && layout === "overlay") {
249
- const baseline = mapContinuousScaleValues(
250
- [0],
251
- yScale
252
- )[0];
253
- if (curve === "linear") {
254
- return buildLinearPathCommands([
255
- { x: x[0], y: baseline },
256
- ...x.map((value, index) => ({ x: value, y: upper[index] })),
257
- { x: x.at(-1), y: baseline }
258
- ], { close: true });
259
- }
260
- return buildAreaCurvePathCommands(
261
- x.map(value => ({ x: value, y: baseline })),
262
- x.map((value, index) => ({ x: value, y: upper[index] })),
263
- curve
264
- );
265
- }
266
- return buildAreaCurvePathCommands(
267
- x.map((value, index) => ({ x: value, y: lower[index] })),
268
- x.map((value, index) => ({ x: value, y: upper[index] })),
269
- curve
270
- );
271
- });
272
182
  const config = this.markConfigs[id];
273
- const fills = config.errorBand?.fill !== undefined
274
- ? paths.map(() => config.errorBand.fill)
275
- : colorEncoding?.scale === undefined
276
- ? paths.map(() => config.fill)
277
- : mapOrdinalValues(
278
- derived.series.map(series => series.key[colorEncoding.field]),
279
- resolved.resolvedScales[colorEncoding.scale].domain,
280
- resolved.resolvedScales[colorEncoding.scale].range
281
- );
183
+ const { paths, fills } = resolveAreaMaterialization({
184
+ rows: dataset.values,
185
+ layer,
186
+ densityTransform,
187
+ resolvedScales: resolved.resolvedScales,
188
+ config
189
+ });
282
190
  const existingChildren = resolved.graphicSpec.objects[id].items ?? [];
283
- const removesOutline = config.stroke === undefined && existingChildren.some(
191
+ const hasOutline = config.stroke !== undefined || config.strokeFromFill === true;
192
+ const removesOutline = !hasOutline && existingChildren.some(
284
193
  child => child.properties.stroke !== undefined
285
194
  );
286
195
  let next = removesOutline
@@ -292,9 +201,13 @@ const rematerializeAreaMark = action(
292
201
  .editGraphics({ target: id, property: "commands", value: paths })
293
202
  .editGraphics({ target: id, property: "fill", value: fills })
294
203
  .editGraphics({ target: id, property: "opacity", value: config.opacity });
295
- if (config.stroke !== undefined) {
204
+ if (hasOutline) {
296
205
  next = next
297
- .editGraphics({ target: id, property: "stroke", value: config.stroke })
206
+ .editGraphics({
207
+ target: id,
208
+ property: "stroke",
209
+ value: config.strokeFromFill === true ? fills : config.stroke
210
+ })
298
211
  .editGraphics({
299
212
  target: id,
300
213
  property: "strokeWidth",
@@ -347,12 +260,21 @@ const editAreaMark = action(
347
260
  }
348
261
  if (Object.hasOwn(args, "stroke")) {
349
262
  if (args.stroke === false) {
350
- const { stroke: removedStroke, strokeWidth: removedWidth, ...rest } = config;
263
+ const {
264
+ stroke: removedStroke,
265
+ strokeFromFill: removedStrokeFromFill,
266
+ strokeWidth: removedWidth,
267
+ ...rest
268
+ } = config;
351
269
  void removedStroke;
270
+ void removedStrokeFromFill;
352
271
  void removedWidth;
353
272
  config = rest;
354
273
  } else {
355
- const hadStroke = config.stroke !== undefined;
274
+ const hadStroke = config.stroke !== undefined || config.strokeFromFill === true;
275
+ const { strokeFromFill: _strokeFromFill, ...withoutFillStroke } = config;
276
+ void _strokeFromFill;
277
+ config = withoutFillStroke;
356
278
  config.stroke = validateNonEmptyString(args.stroke, "Area stroke");
357
279
  config.strokeWidth = Object.hasOwn(args, "strokeWidth")
358
280
  ? validateNonNegativeFinite(args.strokeWidth, "Area strokeWidth")
@@ -377,6 +299,8 @@ const editAreaMark = action(
377
299
 
378
300
  export function registerAreaMarkActions(ProgramClass) {
379
301
  ProgramClass.prototype.createAreaMark = createAreaMark;
302
+ ProgramClass.prototype.configureAreaStrokeFromFill =
303
+ configureAreaStrokeFromFill;
380
304
  ProgramClass.prototype.editAreaMark = editAreaMark;
381
305
  ProgramClass.prototype.rematerializeAreaMark = rematerializeAreaMark;
382
306
  }