ggaction 0.0.4 → 0.0.6

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 (169) hide show
  1. package/CHANGELOG.md +55 -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 +484 -36
  6. package/src/actions/boxPlots/materialize.js +3 -0
  7. package/src/actions/boxPlots/options.js +11 -4
  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/composition/actions.js +13 -3
  18. package/src/actions/coordinates/parallel.js +50 -0
  19. package/src/actions/data/bin2d.js +260 -0
  20. package/src/actions/data/density.js +54 -13
  21. package/src/actions/data/derived.js +22 -0
  22. package/src/actions/data/filter.js +5 -0
  23. package/src/actions/data/gradientProfile.js +65 -0
  24. package/src/actions/data/horizon.js +71 -0
  25. package/src/actions/data/index.js +34 -2
  26. package/src/actions/data/intervalEdit.js +76 -0
  27. package/src/actions/data/window.js +50 -0
  28. package/src/actions/distributions/revision.js +326 -0
  29. package/src/actions/encodings/appearance.js +35 -1
  30. package/src/actions/encodings/color/index.js +17 -1
  31. package/src/actions/encodings/color/layout.js +8 -0
  32. package/src/actions/encodings/density/resolve.js +109 -0
  33. package/src/actions/encodings/density.js +330 -90
  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 -0
  37. package/src/actions/encodings/parallel.js +115 -0
  38. package/src/actions/encodings/pathOrder.js +131 -0
  39. package/src/actions/encodings/position/apply.js +73 -4
  40. package/src/actions/encodings/position/index.js +15 -2
  41. package/src/actions/encodings/position/policies/arc.js +27 -4
  42. package/src/actions/encodings/position/policies/area.js +33 -2
  43. package/src/actions/encodings/position/policies/index.js +7 -2
  44. package/src/actions/encodings/position/policies/line.js +18 -2
  45. package/src/actions/encodings/position/resolve.js +24 -2
  46. package/src/actions/encodings/ranged.js +3 -1
  47. package/src/actions/encodings/remove.js +268 -0
  48. package/src/actions/encodings/ruleAppearance.js +88 -10
  49. package/src/actions/errorBands/edit.js +121 -13
  50. package/src/actions/errorBars/edit.js +45 -8
  51. package/src/actions/errorBars/options.js +7 -1
  52. package/src/actions/facets/actions.js +137 -3
  53. package/src/actions/facets/derive.js +33 -3
  54. package/src/actions/facets/replay.js +7 -36
  55. package/src/actions/gradientPlots/components.js +177 -0
  56. package/src/actions/gradientPlots/create.js +96 -0
  57. package/src/actions/gradientPlots/edit.js +501 -0
  58. package/src/actions/gradientPlots/index.js +24 -0
  59. package/src/actions/gradientPlots/materialize.js +287 -0
  60. package/src/actions/gradientPlots/options.js +120 -0
  61. package/src/actions/gradientPlots/paint.js +107 -0
  62. package/src/actions/gradientPlots/rebind.js +51 -0
  63. package/src/actions/gradientPlots/resolve.js +81 -0
  64. package/src/actions/guides/applicability.js +44 -17
  65. package/src/actions/guides/axes/axes.js +37 -3
  66. package/src/actions/guides/axes/edit.js +129 -47
  67. package/src/actions/guides/axes/index.js +2 -0
  68. package/src/actions/guides/axes/labels.js +11 -6
  69. package/src/actions/guides/axes/parallel/resolve.js +86 -0
  70. package/src/actions/guides/axes/parallel.js +228 -0
  71. package/src/actions/guides/axes/titles.js +11 -8
  72. package/src/actions/guides/guides.js +60 -5
  73. package/src/actions/guides/legends/categorical/actions.js +67 -19
  74. package/src/actions/guides/legends/continuous/common.js +3 -3
  75. package/src/actions/guides/legends/edit.js +63 -0
  76. package/src/actions/guides/legends/index.js +2 -0
  77. package/src/actions/guides/legends/remove.js +124 -30
  78. package/src/actions/guides/legends/strokeWidth.js +198 -0
  79. package/src/actions/index.js +6 -0
  80. package/src/actions/marks/arc/actions.js +46 -14
  81. package/src/actions/marks/area/actions.js +60 -136
  82. package/src/actions/marks/area/materialize.js +163 -0
  83. package/src/actions/marks/index.js +5 -1
  84. package/src/actions/marks/line/actions.js +99 -80
  85. package/src/actions/marks/line/materialize.js +146 -0
  86. package/src/actions/marks/point/create.js +76 -0
  87. package/src/actions/marks/point/edit.js +97 -0
  88. package/src/actions/marks/point/index.js +11 -1
  89. package/src/actions/marks/point/jitter.js +72 -0
  90. package/src/actions/marks/point/{actions.js → materialize.js} +183 -172
  91. package/src/actions/marks/rect/actions.js +3 -0
  92. package/src/actions/marks/remove.js +13 -2
  93. package/src/actions/marks/rule/actions.js +29 -4
  94. package/src/actions/marks/text/actions.js +19 -4
  95. package/src/actions/marks/text/index.js +7 -1
  96. package/src/actions/marks/text/layout.js +321 -0
  97. package/src/actions/primitives/semanticValidation/dataset.js +22 -0
  98. package/src/actions/primitives/semanticValidation/guide.js +43 -0
  99. package/src/actions/primitives/semanticValidation/index.js +23 -0
  100. package/src/actions/primitives/semanticValidation/layer.js +74 -0
  101. package/src/actions/primitives/semanticValidation/scale.js +74 -0
  102. package/src/actions/primitives/semanticValidation/shared.js +5 -0
  103. package/src/actions/primitives/semanticValue.js +1 -204
  104. package/src/actions/regression/edit.js +247 -88
  105. package/src/actions/scales/consumers/common.js +18 -0
  106. package/src/actions/scales/consumers/families.js +14 -3
  107. package/src/actions/scales/definitions.js +30 -0
  108. package/src/actions/scales/edit.js +6 -204
  109. package/src/actions/scales/editPolicy.js +214 -0
  110. package/src/actions/scales/materialize.js +1 -1
  111. package/src/actions/scales/patch.js +26 -0
  112. package/src/actions/selection/actions.js +150 -5
  113. package/src/actions/selection/index.js +6 -0
  114. package/src/actions/violinPlots/create.js +198 -0
  115. package/src/actions/violinPlots/index.js +5 -0
  116. package/src/core/textMetrics.js +47 -0
  117. package/src/core/vocabulary.js +14 -2
  118. package/src/grammar/arcs.js +49 -12
  119. package/src/grammar/areaSeries.js +41 -5
  120. package/src/grammar/bin2d.js +333 -0
  121. package/src/grammar/categoricalDensity.js +192 -0
  122. package/src/grammar/coordinates.js +1 -1
  123. package/src/grammar/curveCommands.js +3 -1
  124. package/src/grammar/density.js +252 -26
  125. package/src/grammar/facets/dependencies.js +3 -11
  126. package/src/grammar/facets/index.js +3 -1
  127. package/src/grammar/gradientProfile.js +213 -0
  128. package/src/grammar/horizon.js +455 -0
  129. package/src/grammar/jitter.js +197 -0
  130. package/src/grammar/lineSeries.js +69 -0
  131. package/src/grammar/paint.js +88 -0
  132. package/src/grammar/parallelCoordinates.js +213 -0
  133. package/src/grammar/pathOrder.js +35 -0
  134. package/src/grammar/pointShapes.js +24 -0
  135. package/src/grammar/rules.js +1 -0
  136. package/src/grammar/scales/appearance.js +21 -0
  137. package/src/grammar/schemas/concreteGraphic.js +11 -1
  138. package/src/grammar/schemas/graphicBounds.js +19 -37
  139. package/src/grammar/schemas/semanticPath.js +17 -2
  140. package/src/grammar/transforms.js +100 -11
  141. package/src/grammar/window.js +339 -0
  142. package/src/layout/labels.js +270 -0
  143. package/src/layout/text.js +4 -1
  144. package/src/layout/title.js +10 -20
  145. package/src/materialization/dataProvenance.js +56 -2
  146. package/src/materialization/dependencies.js +9 -14
  147. package/src/materialization/guides/resources.js +74 -24
  148. package/src/materialization/horizon.js +29 -0
  149. package/src/materialization/layout.js +15 -0
  150. package/src/materialization/marks/capabilities.js +21 -1
  151. package/src/materialization/marks/index.js +3 -0
  152. package/src/materialization/marks/pathOrder.js +71 -0
  153. package/src/materialization/marks/policies.js +6 -2
  154. package/src/materialization/scaleGuideDependencies.js +8 -0
  155. package/src/materialization/scales/resolve.js +2 -0
  156. package/src/materialization/selection/items/path.js +27 -1
  157. package/src/materialization/selection/items/point.js +1 -5
  158. package/src/materialization/selection/items/rect.js +21 -0
  159. package/src/materialization/selection/styles.js +20 -9
  160. package/src/materialization/text.js +12 -4
  161. package/src/renderers/canvas/fill.js +33 -0
  162. package/src/renderers/canvas/index.js +7 -0
  163. package/src/renderers/canvas/path.js +7 -4
  164. package/src/renderers/canvas/rect.js +9 -3
  165. package/src/selectors/datasets.js +4 -0
  166. package/src/theme/defaults.js +4 -0
  167. package/types/extension.d.ts +6 -0
  168. package/types/index.d.ts +77 -0
  169. package/types/program.d.ts +703 -17
@@ -1,46 +1,140 @@
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
+ import { LEGEND_CHANNELS } from "../../../core/vocabulary.js";
5
6
  import { resolveLegendTarget } from "./target.js";
6
7
 
7
- const OPTIONS = Object.freeze(["target"]);
8
+ const OPTIONS = Object.freeze(["target", "channels"]);
8
9
 
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
- });
10
+ function legendKindChannels(kind, config) {
11
+ if (["series", "color"].includes(kind)) return config.channels;
12
+ return {
13
+ size: ["size"],
14
+ gradient: ["color"],
15
+ interval: ["color"],
16
+ opacity: ["opacity"],
17
+ strokeWidth: ["strokeWidth"]
18
+ }[kind];
19
+ }
20
+
21
+ function validateRequestedChannels(channels) {
22
+ if (!Array.isArray(channels)) {
23
+ throw new TypeError("removeLegend channels must be an array.");
24
+ }
25
+ if (channels.length === 0) {
26
+ throw new Error("removeLegend channels must select at least one channel.");
27
+ }
28
+ const seen = new Set();
29
+ for (const channel of channels) {
30
+ if (!LEGEND_CHANNELS.includes(channel)) {
31
+ throw new Error(`Unsupported legend channel "${channel}".`);
32
+ }
33
+ if (seen.has(channel)) {
34
+ throw new Error(`removeLegend channels contains duplicate "${channel}".`);
35
+ }
36
+ seen.add(channel);
37
+ }
38
+ }
39
+
40
+ function resolveRequestedKinds(program, target, channels) {
41
+ validateRequestedChannels(channels);
42
+ const requested = new Set(channels);
43
+ const matched = new Set();
44
+ const kinds = [];
45
+ for (const [kind, config] of Object.entries(program.guideConfigs.legend ?? {})) {
46
+ if (config?.target !== target) continue;
47
+ const owned = legendKindChannels(kind, config);
48
+ const overlap = owned.filter(channel => requested.has(channel));
49
+ if (overlap.length > 0 && overlap.length !== owned.length) {
50
+ throw new Error(
51
+ `Legend channels ${owned.map(channel => `"${channel}"`).join(", ")} ` +
52
+ "belong to one combined block and must be removed together."
53
+ );
54
+ }
55
+ if (overlap.length === owned.length) {
56
+ kinds.push(kind);
57
+ for (const channel of owned) matched.add(channel);
58
+ }
59
+ }
60
+ const missing = channels.filter(channel => !matched.has(channel));
61
+ if (missing.length > 0) {
62
+ throw new Error(
63
+ `Legend target "${target}" has no complete block for channel "${missing[0]}".`
64
+ );
65
+ }
66
+ return kinds;
67
+ }
68
+
69
+ export function removeLegendKinds(program, kinds) {
70
+ const removed = new Set(kinds);
71
+ const retainedSemanticKinds = new Set(
72
+ Object.entries(program.guideConfigs.legend ?? {})
73
+ .filter(([kind]) => !removed.has(kind))
74
+ .map(([kind]) => legendResourcePolicy(kind).semanticKind)
75
+ );
76
+ const semanticKinds = new Set(
77
+ kinds.map(kind => legendResourcePolicy(kind).semanticKind)
78
+ );
79
+ let next = program;
80
+ for (const kind of semanticKinds) {
81
+ if (
82
+ !retainedSemanticKinds.has(kind) &&
83
+ next.semanticSpec.guides.legend?.[kind] !== undefined
84
+ ) {
85
+ next = next.editSemantic({
86
+ property: `guide.legend.${kind}`,
87
+ remove: true
88
+ });
89
+ }
90
+ }
91
+ for (const id of new Set(kinds.flatMap(legendGraphicIds))) {
92
+ if (next.graphicSpec.objects[id] !== undefined) {
93
+ next = next.editGraphics({ target: id, remove: true });
94
+ }
95
+ }
96
+ for (const kind of kinds) {
97
+ next = next._withoutMaterializationConfig(["guides", "legend", kind]);
98
+ }
99
+ return next;
100
+ }
17
101
 
18
102
  export const removeLegend = action(
19
103
  { op: "removeLegend", description: "Remove every legend block owned by one mark." },
20
104
  function (args = {}) {
21
105
  validateKeys(args, OPTIONS, "removeLegend");
22
106
  const target = resolveLegendTarget(this, args.target, "removeLegend");
23
- const kinds = Object.entries(this.guideConfigs.legend ?? {})
107
+ const targetKinds = Object.entries(this.guideConfigs.legend ?? {})
24
108
  .filter(([, config]) => config?.target === target)
25
109
  .map(([kind]) => kind);
26
- const semanticKinds = new Set(kinds.map(kind => SEMANTIC_KIND[kind]));
27
- let next = this;
28
- for (const kind of semanticKinds) {
29
- if (next.semanticSpec.guides.legend?.[kind] !== undefined) {
30
- next = next.editSemantic({
31
- property: `guide.legend.${kind}`,
32
- remove: true
33
- });
34
- }
35
- }
36
- for (const id of new Set(kinds.flatMap(legendGraphicIds))) {
37
- if (next.graphicSpec.objects[id] !== undefined) {
38
- next = next.editGraphics({ target: id, remove: true });
39
- }
40
- }
41
- for (const kind of kinds) {
42
- next = next._withoutMaterializationConfig(["guides", "legend", kind]);
43
- }
44
- return next;
110
+ const kinds = args.channels === undefined
111
+ ? targetKinds
112
+ : resolveRequestedKinds(this, target, args.channels);
113
+ let next = removeLegendKinds(this, kinds);
114
+ const retainedKinds = Object.keys(next.guideConfigs.legend ?? {});
115
+ if (args.channels === undefined) {
116
+ const removedSemanticKinds = new Set(
117
+ kinds.map(kind => legendResourcePolicy(kind).semanticKind)
118
+ );
119
+ const hasSharedSemanticKind = retainedKinds.some(kind =>
120
+ removedSemanticKinds.has(legendResourcePolicy(kind).semanticKind)
121
+ );
122
+ return hasSharedSemanticKind ? next.rematerializeLegend() : next;
123
+ }
124
+ const remainingTargetKinds = targetKinds.filter(kind => !kinds.includes(kind));
125
+ const removedCategorical = kinds.some(kind => ["series", "color"].includes(kind));
126
+ if (
127
+ removedCategorical &&
128
+ remainingTargetKinds.includes("size") &&
129
+ next.guideConfigs.legend.size.inheritAppearance === true
130
+ ) {
131
+ next = next._withLegendConfig("size", {
132
+ ...next.guideConfigs.legend.size,
133
+ inheritAppearance: false
134
+ });
135
+ }
136
+ return retainedKinds.length === 0
137
+ ? next
138
+ : next.rematerializeLegend();
45
139
  }
46
140
  );
@@ -0,0 +1,198 @@
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 const STROKE_WIDTH_LEGEND_LABELS = Object.freeze({
14
+ offset: 12,
15
+ color: DEFAULT_COLORS.text,
16
+ fontSize: 12,
17
+ fontFamily: DEFAULT_FONT_FAMILY,
18
+ fontWeight: "normal"
19
+ });
20
+
21
+ export const STROKE_WIDTH_LEGEND_TITLE_STYLE = Object.freeze({
22
+ color: DEFAULT_COLORS.strongText,
23
+ fontSize: 13,
24
+ fontFamily: DEFAULT_FONT_FAMILY,
25
+ fontWeight: 600
26
+ });
27
+
28
+ export function isStrokeWidthLegendLayer(layer) {
29
+ return ["line", "rule"].includes(layer?.mark?.type) &&
30
+ layer.encoding?.strokeWidth?.scale !== undefined;
31
+ }
32
+
33
+ function resolveLayer(program, requested) {
34
+ const candidates = program.semanticSpec.layers.filter(isStrokeWidthLegendLayer);
35
+ const layer = requested === undefined
36
+ ? candidates.length === 1 ? candidates[0] : undefined
37
+ : (() => {
38
+ const candidate = findLayer(program, requested);
39
+ return candidates.includes(candidate) ? candidate : undefined;
40
+ })();
41
+ if (layer === undefined) {
42
+ throw new Error(
43
+ requested === undefined
44
+ ? "Stroke-width legend requires one eligible line or rule mark or an explicit target."
45
+ : `Unknown stroke-width legend target "${requested}".`
46
+ );
47
+ }
48
+ return layer;
49
+ }
50
+
51
+ function requireScale(program, id) {
52
+ const scale = program.resolvedScales[id];
53
+ if (scale === undefined || !["linear", "log", "pow", "sqrt", "symlog"].includes(scale.type)) {
54
+ throw new Error(`Stroke-width legend requires resolved quantitative scale "${id}".`);
55
+ }
56
+ return scale;
57
+ }
58
+
59
+ function styleText(program, id, style) {
60
+ return program
61
+ .editGraphics({ target: id, property: "fill", value: style.color })
62
+ .editGraphics({ target: id, property: "fontSize", value: style.fontSize })
63
+ .editGraphics({ target: id, property: "fontFamily", value: style.fontFamily })
64
+ .editGraphics({ target: id, property: "fontWeight", value: style.fontWeight })
65
+ .editGraphics({ target: id, property: "textAlign", value: "left" })
66
+ .editGraphics({ target: id, property: "textBaseline", value: "middle" });
67
+ }
68
+
69
+ export const rematerializeStrokeWidthLegend = action(
70
+ {
71
+ op: "rematerializeStrokeWidthLegend",
72
+ description: "Rematerialize a quantitative stroke-width legend."
73
+ },
74
+ function (args = {}) {
75
+ validateKeys(args, [], "rematerializeStrokeWidthLegend");
76
+ const stored = this.guideConfigs.legend?.strokeWidth;
77
+ if (stored === undefined) {
78
+ throw new Error("Stroke-width legend requires stored configuration.");
79
+ }
80
+ const config = {
81
+ ...stored,
82
+ labels: stored.labels ?? { ...STROKE_WIDTH_LEGEND_LABELS },
83
+ titleStyle: stored.titleStyle ?? { ...STROKE_WIDTH_LEGEND_TITLE_STYLE },
84
+ titleVisible: stored.titleVisible !== false
85
+ };
86
+ const layer = findLayer(this, config.target);
87
+ const encoding = layer?.encoding?.strokeWidth;
88
+ if (encoding?.scale === undefined) {
89
+ throw new Error("Stroke-width legend target requires a strokeWidth encoding.");
90
+ }
91
+ const scale = requireScale(this, encoding.scale);
92
+ const plot = resolveGraphicBounds(this);
93
+ if (plot === undefined) {
94
+ throw new Error("Stroke-width legend requires resolved plot bounds.");
95
+ }
96
+ const values = Array.from(
97
+ { length: config.count },
98
+ (_, index) => scale.domain[0] +
99
+ index / (config.count - 1) * (scale.domain[1] - scale.domain[0])
100
+ );
101
+ const widths = mapContinuousScaleValues(values, scale);
102
+ const originX = plot.x + plot.width + 30;
103
+ const titleY = plot.y + 28;
104
+ const y = values.map((_, index) => titleY + 34 + index * 32);
105
+ const title = config.inferredTitle === true ? encoding.field : config.title;
106
+ let next = this
107
+ .editSemantic({ property: "guide.legend.strokeWidth.scale", value: encoding.scale })
108
+ .editSemantic({ property: "guide.legend.strokeWidth.title", value: title })
109
+ ._withLegendConfig("strokeWidth", {
110
+ ...config,
111
+ scale: encoding.scale,
112
+ title,
113
+ domain: scale.domain
114
+ })
115
+ .editGraphics({ target: "strokeWidthLegendSymbols", property: "length", value: values.length })
116
+ .editGraphics({ target: "strokeWidthLegendSymbols", property: "x1", value: values.map(() => originX) })
117
+ .editGraphics({ target: "strokeWidthLegendSymbols", property: "x2", value: values.map(() => originX + 32) })
118
+ .editGraphics({ target: "strokeWidthLegendSymbols", property: "y1", value: y })
119
+ .editGraphics({ target: "strokeWidthLegendSymbols", property: "y2", value: y })
120
+ .editGraphics({ target: "strokeWidthLegendSymbols", property: "stroke", value: DEFAULT_COLORS.mark })
121
+ .editGraphics({ target: "strokeWidthLegendSymbols", property: "strokeWidth", value: widths })
122
+ .editGraphics({ target: "strokeWidthLegendLabels", property: "length", value: values.length })
123
+ .editGraphics({
124
+ target: "strokeWidthLegendLabels",
125
+ property: "x",
126
+ value: values.map(() => originX + 32 + config.labels.offset)
127
+ })
128
+ .editGraphics({ target: "strokeWidthLegendLabels", property: "y", value: y })
129
+ .editGraphics({
130
+ target: "strokeWidthLegendLabels",
131
+ property: "text",
132
+ value: values.map(value => String(+value.toPrecision(3)))
133
+ });
134
+ next = styleText(next, "strokeWidthLegendLabels", config.labels);
135
+ if (config.titleVisible === false) return next;
136
+ next = next
137
+ .editGraphics({ target: "strokeWidthLegendTitle", property: "x", value: originX })
138
+ .editGraphics({ target: "strokeWidthLegendTitle", property: "y", value: titleY })
139
+ .editGraphics({ target: "strokeWidthLegendTitle", property: "text", value: title });
140
+ return styleText(next, "strokeWidthLegendTitle", config.titleStyle);
141
+ }
142
+ );
143
+
144
+ export const createStrokeWidthLegend = action(
145
+ {
146
+ op: "createStrokeWidthLegend",
147
+ description: "Create a quantitative stroke-width legend."
148
+ },
149
+ function (args = {}) {
150
+ validateKeys(args, OPTIONS, "createStrokeWidthLegend");
151
+ const layer = resolveLayer(this, args.target);
152
+ const encoding = layer.encoding.strokeWidth;
153
+ requireScale(this, encoding.scale);
154
+ const count = args.count ?? 5;
155
+ if (!Number.isInteger(count) || count < 2) {
156
+ throw new RangeError(
157
+ "Stroke-width legend count must be an integer of at least 2."
158
+ );
159
+ }
160
+ return this
161
+ .editSemantic({ property: "guide.legend.strokeWidth.scale", value: encoding.scale })
162
+ .editSemantic({ property: "guide.legend.strokeWidth.title", value: encoding.field })
163
+ ._withLegendConfig("strokeWidth", {
164
+ target: layer.id,
165
+ scale: encoding.scale,
166
+ title: encoding.field,
167
+ inferredTitle: true,
168
+ count,
169
+ labels: { ...STROKE_WIDTH_LEGEND_LABELS },
170
+ titleStyle: { ...STROKE_WIDTH_LEGEND_TITLE_STYLE },
171
+ titleVisible: true
172
+ })
173
+ .createGraphics({
174
+ id: "strokeWidthLegendSymbols",
175
+ type: "line",
176
+ length: count,
177
+ ...resolveLegendGraphicPlacement(this)
178
+ })
179
+ .createGraphics({
180
+ id: "strokeWidthLegendLabels",
181
+ type: "text",
182
+ length: count,
183
+ ...resolveLegendGraphicPlacement(this)
184
+ })
185
+ .createGraphics({
186
+ id: "strokeWidthLegendTitle",
187
+ type: "text",
188
+ ...resolveLegendGraphicPlacement(this)
189
+ })
190
+ .rematerializeStrokeWidthLegend();
191
+ }
192
+ );
193
+
194
+ export function registerStrokeWidthLegendActions(ProgramClass) {
195
+ ProgramClass.prototype.createStrokeWidthLegend = createStrokeWidthLegend;
196
+ ProgramClass.prototype.rematerializeStrokeWidthLegend =
197
+ rematerializeStrokeWidthLegend;
198
+ }
@@ -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
  }
@@ -46,8 +46,8 @@ function validateInnerRadius(value) {
46
46
  return value;
47
47
  }
48
48
 
49
- function normalizeConfig(args, previous = {}) {
50
- return {
49
+ function normalizeConfig(args, previous = {}, { allowStrokeRemoval = false } = {}) {
50
+ let config = {
51
51
  ...previous,
52
52
  ...(Object.hasOwn(args, "innerRadius")
53
53
  ? { innerRadius: validateInnerRadius(args.innerRadius) }
@@ -61,18 +61,28 @@ function normalizeConfig(args, previous = {}) {
61
61
  ...(Object.hasOwn(args, "opacity")
62
62
  ? { opacity: validateUnitInterval(args.opacity, "Arc opacity") }
63
63
  : {}),
64
- ...(Object.hasOwn(args, "stroke")
64
+ ...(Object.hasOwn(args, "stroke") && args.stroke !== false
65
65
  ? { stroke: validateNonEmptyString(args.stroke, "Arc stroke") }
66
- : {}),
67
- ...(Object.hasOwn(args, "strokeWidth")
68
- ? {
69
- strokeWidth: validateNonNegativeFinite(
70
- args.strokeWidth,
71
- "Arc strokeWidth"
72
- )
73
- }
74
66
  : {})
75
67
  };
68
+ if (args.stroke === false) {
69
+ if (!allowStrokeRemoval) {
70
+ throw new TypeError("Arc stroke must be a non-empty string.");
71
+ }
72
+ config.stroke = false;
73
+ delete config.strokeWidth;
74
+ } else if (Object.hasOwn(args, "stroke")) {
75
+ if (previous.stroke === false && !Object.hasOwn(args, "strokeWidth")) {
76
+ config.strokeWidth = 1;
77
+ }
78
+ }
79
+ if (Object.hasOwn(args, "strokeWidth")) {
80
+ config.strokeWidth = validateNonNegativeFinite(
81
+ args.strokeWidth,
82
+ "Arc strokeWidth"
83
+ );
84
+ }
85
+ return config;
76
86
  }
77
87
 
78
88
  const createArcMark = action(
@@ -189,8 +199,16 @@ const rematerializeArcMark = action(
189
199
  .editGraphics({ target: id, property: "commands", value: commands })
190
200
  .editGraphics({ target: id, property: "fill", value: fills })
191
201
  .editGraphics({ target: id, property: "opacity", value: config.opacity ?? 1 })
192
- .editGraphics({ target: id, property: "stroke", value: config.stroke ?? "#ffffff" })
193
- .editGraphics({ target: id, property: "strokeWidth", value: config.strokeWidth ?? 1 })
202
+ .editGraphics({
203
+ target: id,
204
+ property: "stroke",
205
+ value: config.stroke === false ? "transparent" : config.stroke ?? "#ffffff"
206
+ })
207
+ .editGraphics({
208
+ target: id,
209
+ property: "strokeWidth",
210
+ value: config.stroke === false ? 0 : config.strokeWidth ?? 1
211
+ })
194
212
  .editGraphics({
195
213
  target: id,
196
214
  property: "strokeDash",
@@ -222,9 +240,23 @@ const editArcMark = action(
222
240
  if (Object.hasOwn(args, "fill") && layer.encoding?.color !== undefined) {
223
241
  throw new Error("editArcMark fill cannot be combined with a color encoding.");
224
242
  }
243
+ if (args.stroke === false && Object.hasOwn(args, "strokeWidth")) {
244
+ throw new Error(
245
+ "editArcMark cannot set strokeWidth while removing stroke."
246
+ );
247
+ }
248
+ if (
249
+ Object.hasOwn(args, "strokeWidth") &&
250
+ !Object.hasOwn(args, "stroke") &&
251
+ this.markConfigs[layer.id]?.stroke === false
252
+ ) {
253
+ throw new Error("editArcMark strokeWidth requires an active stroke.");
254
+ }
225
255
  const next = this._withMarkConfig(
226
256
  layer.id,
227
- normalizeConfig(args, this.markConfigs[layer.id])
257
+ normalizeConfig(args, this.markConfigs[layer.id], {
258
+ allowStrokeRemoval: true
259
+ })
228
260
  );
229
261
  return canMaterializeArc(next, layer)
230
262
  ? next.rematerializeArcMark({ id: layer.id })