ggaction 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/LICENSE +21 -0
  3. package/README.md +108 -0
  4. package/package.json +84 -0
  5. package/src/AGENTS.md +119 -0
  6. package/src/ChartProgram.js +10 -0
  7. package/src/actions/boxPlots/components.js +88 -0
  8. package/src/actions/boxPlots/create.js +84 -0
  9. package/src/actions/boxPlots/index.js +10 -0
  10. package/src/actions/boxPlots/materialize.js +183 -0
  11. package/src/actions/boxPlots/options.js +147 -0
  12. package/src/actions/boxPlots/resolve.js +38 -0
  13. package/src/actions/canvas/actions.js +144 -0
  14. package/src/actions/canvas/index.js +6 -0
  15. package/src/actions/coordinates/actions.js +85 -0
  16. package/src/actions/coordinates/index.js +5 -0
  17. package/src/actions/data/box.js +40 -0
  18. package/src/actions/data/create.js +33 -0
  19. package/src/actions/data/density.js +62 -0
  20. package/src/actions/data/derived.js +52 -0
  21. package/src/actions/data/filter.js +168 -0
  22. package/src/actions/data/index.js +32 -0
  23. package/src/actions/data/interval.js +69 -0
  24. package/src/actions/data/regression.js +71 -0
  25. package/src/actions/data/shared.js +25 -0
  26. package/src/actions/encodings/appearance.js +243 -0
  27. package/src/actions/encodings/barWidth.js +60 -0
  28. package/src/actions/encodings/color.js +329 -0
  29. package/src/actions/encodings/density.js +225 -0
  30. package/src/actions/encodings/histogram.js +50 -0
  31. package/src/actions/encodings/index.js +23 -0
  32. package/src/actions/encodings/offset.js +104 -0
  33. package/src/actions/encodings/position/policies/area.js +24 -0
  34. package/src/actions/encodings/position/policies/bar.js +110 -0
  35. package/src/actions/encodings/position/policies/common.js +17 -0
  36. package/src/actions/encodings/position/policies/index.js +25 -0
  37. package/src/actions/encodings/position/policies/line.js +74 -0
  38. package/src/actions/encodings/position/policies/point.js +19 -0
  39. package/src/actions/encodings/position/policies/rule.js +17 -0
  40. package/src/actions/encodings/position/resolve.js +170 -0
  41. package/src/actions/encodings/position.js +176 -0
  42. package/src/actions/encodings/ranged.js +276 -0
  43. package/src/actions/encodings/ruleAppearance.js +47 -0
  44. package/src/actions/encodings/shared.js +164 -0
  45. package/src/actions/encodings/strokeDash.js +145 -0
  46. package/src/actions/errorBands/components.js +81 -0
  47. package/src/actions/errorBands/create.js +159 -0
  48. package/src/actions/errorBands/index.js +7 -0
  49. package/src/actions/errorBands/resolve.js +28 -0
  50. package/src/actions/errorBars/components.js +62 -0
  51. package/src/actions/errorBars/create.js +144 -0
  52. package/src/actions/errorBars/index.js +7 -0
  53. package/src/actions/errorBars/resolve.js +25 -0
  54. package/src/actions/guides/axes/axes.js +204 -0
  55. package/src/actions/guides/axes/axis.js +131 -0
  56. package/src/actions/guides/axes/index.js +17 -0
  57. package/src/actions/guides/axes/labels.js +231 -0
  58. package/src/actions/guides/axes/lines.js +149 -0
  59. package/src/actions/guides/axes/policy.js +155 -0
  60. package/src/actions/guides/axes/tickGroups.js +149 -0
  61. package/src/actions/guides/axes/ticks.js +179 -0
  62. package/src/actions/guides/axes/titles.js +264 -0
  63. package/src/actions/guides/grids/grid.js +228 -0
  64. package/src/actions/guides/grids/index.js +1 -0
  65. package/src/actions/guides/grids/resolve.js +258 -0
  66. package/src/actions/guides/guides.js +138 -0
  67. package/src/actions/guides/index.js +11 -0
  68. package/src/actions/guides/legends/categorical/actions.js +329 -0
  69. package/src/actions/guides/legends/categorical/components.js +140 -0
  70. package/src/actions/guides/legends/categorical/index.js +49 -0
  71. package/src/actions/guides/legends/categorical/layout.js +445 -0
  72. package/src/actions/guides/legends/categorical/options.js +188 -0
  73. package/src/actions/guides/legends/categorical/recipes.js +120 -0
  74. package/src/actions/guides/legends/categorical/resolve.js +131 -0
  75. package/src/actions/guides/legends/categorical/symbols.js +280 -0
  76. package/src/actions/guides/legends/categorical/validation.js +27 -0
  77. package/src/actions/guides/legends/continuous/common.js +273 -0
  78. package/src/actions/guides/legends/continuous/gradient.js +363 -0
  79. package/src/actions/guides/legends/continuous/index.js +25 -0
  80. package/src/actions/guides/legends/continuous/interval.js +234 -0
  81. package/src/actions/guides/legends/continuous/opacity.js +382 -0
  82. package/src/actions/guides/legends/edit.js +314 -0
  83. package/src/actions/guides/legends/index.js +9 -0
  84. package/src/actions/guides/legends/size.js +220 -0
  85. package/src/actions/guides/tickValues.js +84 -0
  86. package/src/actions/index.js +31 -0
  87. package/src/actions/intervals/resolve.js +352 -0
  88. package/src/actions/marks/area.js +369 -0
  89. package/src/actions/marks/bar/create.js +46 -0
  90. package/src/actions/marks/bar/edit.js +90 -0
  91. package/src/actions/marks/bar/index.js +9 -0
  92. package/src/actions/marks/bar/materialize.js +130 -0
  93. package/src/actions/marks/index.js +13 -0
  94. package/src/actions/marks/line.js +248 -0
  95. package/src/actions/marks/point.js +420 -0
  96. package/src/actions/marks/rule.js +287 -0
  97. package/src/actions/marks/shared.js +112 -0
  98. package/src/actions/primitives/createGraphics.js +163 -0
  99. package/src/actions/primitives/editGraphics.js +208 -0
  100. package/src/actions/primitives/index.js +9 -0
  101. package/src/actions/primitives/semantic.js +168 -0
  102. package/src/actions/primitives/semanticValue.js +182 -0
  103. package/src/actions/regression/components.js +216 -0
  104. package/src/actions/regression/create.js +102 -0
  105. package/src/actions/regression/index.js +23 -0
  106. package/src/actions/regression/resolve.js +57 -0
  107. package/src/actions/scales/consumers.js +283 -0
  108. package/src/actions/scales/create.js +148 -0
  109. package/src/actions/scales/definitions.js +319 -0
  110. package/src/actions/scales/edit.js +290 -0
  111. package/src/actions/scales/index.js +11 -0
  112. package/src/actions/scales/materialize.js +466 -0
  113. package/src/actions/scales/quantitativeColor.js +45 -0
  114. package/src/actions/selection/actions.js +384 -0
  115. package/src/actions/selection/index.js +23 -0
  116. package/src/actions/titles/actions.js +235 -0
  117. package/src/actions/titles/index.js +19 -0
  118. package/src/actions/titles/resolve.js +321 -0
  119. package/src/core/ChartProgram.js +316 -0
  120. package/src/core/action.js +120 -0
  121. package/src/core/identifiers.js +28 -0
  122. package/src/core/immutable.js +94 -0
  123. package/src/core/specs.js +29 -0
  124. package/src/core/textMetrics.js +23 -0
  125. package/src/core/validation.js +54 -0
  126. package/src/core/vocabulary.js +58 -0
  127. package/src/extension.js +2 -0
  128. package/src/grammar/aggregate.js +259 -0
  129. package/src/grammar/areaSeries.js +217 -0
  130. package/src/grammar/bars/aggregate.js +98 -0
  131. package/src/grammar/bars/geometry.js +54 -0
  132. package/src/grammar/bars/policy.js +90 -0
  133. package/src/grammar/boxPlot.js +140 -0
  134. package/src/grammar/coordinates.js +25 -0
  135. package/src/grammar/curveCommands.js +356 -0
  136. package/src/grammar/density.js +296 -0
  137. package/src/grammar/filter.js +169 -0
  138. package/src/grammar/histogram.js +332 -0
  139. package/src/grammar/interval.js +343 -0
  140. package/src/grammar/lineSeries.js +197 -0
  141. package/src/grammar/markFilter.js +46 -0
  142. package/src/grammar/markSelection.js +326 -0
  143. package/src/grammar/palettes.js +208 -0
  144. package/src/grammar/pathCommands.js +86 -0
  145. package/src/grammar/pointShapes.js +164 -0
  146. package/src/grammar/positionCompatibility.js +32 -0
  147. package/src/grammar/regression.js +578 -0
  148. package/src/grammar/ruleAppearance.js +12 -0
  149. package/src/grammar/rules.js +90 -0
  150. package/src/grammar/scales/appearance.js +185 -0
  151. package/src/grammar/scales/color.js +349 -0
  152. package/src/grammar/scales/continuous.js +111 -0
  153. package/src/grammar/scales/definition.js +153 -0
  154. package/src/grammar/scales/discretized.js +159 -0
  155. package/src/grammar/scales/fields.js +133 -0
  156. package/src/grammar/scales/index.js +12 -0
  157. package/src/grammar/scales/mapping.js +27 -0
  158. package/src/grammar/scales/ordinal.js +221 -0
  159. package/src/grammar/scales/policies.js +50 -0
  160. package/src/grammar/scales/temporal.js +53 -0
  161. package/src/grammar/scales/transformed.js +236 -0
  162. package/src/grammar/scales/types.js +102 -0
  163. package/src/grammar/scales/validation.js +66 -0
  164. package/src/grammar/scales.js +1 -0
  165. package/src/grammar/schemas/concreteGraphic.js +73 -0
  166. package/src/grammar/schemas/graphic.js +131 -0
  167. package/src/grammar/schemas/graphicBounds.js +146 -0
  168. package/src/grammar/schemas/graphicTree.js +133 -0
  169. package/src/grammar/schemas/mark.js +4 -0
  170. package/src/grammar/schemas/semanticPath.js +174 -0
  171. package/src/grammar/seriesLayout.js +76 -0
  172. package/src/grammar/ticks.js +149 -0
  173. package/src/grammar/transforms.js +37 -0
  174. package/src/index.js +2 -0
  175. package/src/layout/canvas.js +114 -0
  176. package/src/layout/grid.js +17 -0
  177. package/src/layout/legend.js +51 -0
  178. package/src/layout/text.js +62 -0
  179. package/src/layout/title.js +107 -0
  180. package/src/materialization/bars/aggregate.js +184 -0
  181. package/src/materialization/bars/grouped.js +117 -0
  182. package/src/materialization/bars/histogram.js +163 -0
  183. package/src/materialization/bars/ranged.js +53 -0
  184. package/src/materialization/bars/resolve.js +56 -0
  185. package/src/materialization/dataProvenance.js +15 -0
  186. package/src/materialization/density.js +29 -0
  187. package/src/materialization/dependencies.js +134 -0
  188. package/src/materialization/encodings.js +50 -0
  189. package/src/materialization/graphicHierarchy.js +104 -0
  190. package/src/materialization/legends.js +20 -0
  191. package/src/materialization/marks.js +110 -0
  192. package/src/materialization/planner.js +38 -0
  193. package/src/materialization/selection/filter.js +40 -0
  194. package/src/materialization/selection/items.js +503 -0
  195. package/src/materialization/selection/path.js +37 -0
  196. package/src/materialization/selection/point.js +118 -0
  197. package/src/materialization/selection/policies/area.js +10 -0
  198. package/src/materialization/selection/policies/bar.js +10 -0
  199. package/src/materialization/selection/policies/index.js +40 -0
  200. package/src/materialization/selection/policies/line.js +10 -0
  201. package/src/materialization/selection/policies/point.js +10 -0
  202. package/src/materialization/selection/policies/rule.js +10 -0
  203. package/src/materialization/selection/state.js +67 -0
  204. package/src/materialization/selection/styles.js +183 -0
  205. package/src/renderers/canvas/circle.js +62 -0
  206. package/src/renderers/canvas/index.js +132 -0
  207. package/src/renderers/canvas/line.js +54 -0
  208. package/src/renderers/canvas/path.js +98 -0
  209. package/src/renderers/canvas/rect.js +57 -0
  210. package/src/renderers/canvas/text.js +96 -0
  211. package/src/renderers/canvas/validation.js +23 -0
  212. package/src/renderers/png.js +29 -0
  213. package/src/selectors/coordinates.js +13 -0
  214. package/src/selectors/datasets.js +13 -0
  215. package/src/selectors/index.js +4 -0
  216. package/src/selectors/layers.js +32 -0
  217. package/src/selectors/scales.js +21 -0
  218. package/src/theme/defaults.js +13 -0
  219. package/types/extension.d.ts +14 -0
  220. package/types/index.d.ts +81 -0
  221. package/types/png.d.ts +14 -0
  222. package/types/program.d.ts +1096 -0
@@ -0,0 +1,329 @@
1
+ import { action } from "../../../../core/action.js";
2
+ import { isPlainObject } from "../../../../core/immutable.js";
3
+ import { noOptions, resolveLayout, activeConfig } from "./layout.js";
4
+ import { normalizeOptions } from "./options.js";
5
+ import { findLayer } from "../../../../selectors/layers.js";
6
+ import { findSemanticScale } from "../../../../selectors/scales.js";
7
+ import {
8
+ resolveCurrentDefinition,
9
+ resolveDefinition,
10
+ resolveLegendKind,
11
+ resolveTarget,
12
+ sameValues
13
+ } from "./resolve.js";
14
+
15
+ export const rematerializeLegend = action(
16
+ { op: "rematerializeLegend", description: "Rematerialize every existing legend component." },
17
+ function (args = {}) {
18
+ noOptions(args, "rematerializeLegend");
19
+ let next = this;
20
+ const hasCategorical =
21
+ this.guideConfigs.legend?.series !== undefined ||
22
+ this.guideConfigs.legend?.color !== undefined;
23
+ if (hasCategorical) {
24
+ const { kind, config } = activeConfig(this);
25
+ const definition = resolveCurrentDefinition(this, config);
26
+ const changed =
27
+ !sameValues(config.channels, definition.channels) ||
28
+ !sameValues(config.domain, definition.domain) ||
29
+ !sameValues(config.scales, definition.scales) ||
30
+ config.field !== definition.field ||
31
+ config.title !== definition.title;
32
+ next = changed
33
+ ? this._withLegendConfig(kind, {
34
+ ...config,
35
+ channels: definition.channels,
36
+ scales: definition.scales,
37
+ field: definition.field,
38
+ title: definition.title,
39
+ domain: definition.domain
40
+ })
41
+ : this;
42
+ if (kind === "series") {
43
+ if (!sameValues(
44
+ this.semanticSpec.guides.legend.series.scales,
45
+ definition.scales
46
+ )) {
47
+ next = next.editSemantic({
48
+ property: "guide.legend.series.scales",
49
+ value: definition.scales
50
+ });
51
+ }
52
+ if (this.semanticSpec.guides.legend.series.title !== definition.title) {
53
+ next = next.editSemantic({
54
+ property: "guide.legend.series.title",
55
+ value: definition.title
56
+ });
57
+ }
58
+ } else {
59
+ if (this.semanticSpec.guides.legend.color.scale !== definition.scales[0]) {
60
+ next = next.editSemantic({
61
+ property: "guide.legend.color.scale",
62
+ value: definition.scales[0]
63
+ });
64
+ }
65
+ if (this.semanticSpec.guides.legend.color.title !== definition.title) {
66
+ next = next.editSemantic({
67
+ property: "guide.legend.color.title",
68
+ value: definition.title
69
+ });
70
+ }
71
+ }
72
+ if (config.border !== false) next = next.editLegendBackground();
73
+ next = next
74
+ .editLegendSymbols()
75
+ .editLegendLabels();
76
+ if (config.titleVisible !== false) next = next.editLegendTitle();
77
+ const hasHighlight = Object.values(
78
+ next.materializationConfigs.highlights ?? {}
79
+ ).some(highlight => highlight.target === config.target);
80
+ if (hasHighlight) next = next.rematerializeLegendHighlights();
81
+ }
82
+ if (this.guideConfigs.legend?.size !== undefined) {
83
+ next = next.rematerializeSizeLegend();
84
+ }
85
+ if (this.guideConfigs.legend?.gradient !== undefined) {
86
+ next = next.rematerializeGradientLegend();
87
+ }
88
+ if (this.guideConfigs.legend?.interval !== undefined) {
89
+ next = next.rematerializeIntervalLegend();
90
+ }
91
+ if (this.guideConfigs.legend?.opacity !== undefined) {
92
+ next = next.rematerializeOpacityLegend();
93
+ }
94
+ return next;
95
+ }
96
+ );
97
+
98
+ export const createCategoricalLegend = action(
99
+ { op: "createCategoricalLegend", description: "Create one categorical legend block." },
100
+ function (args = {}) {
101
+ const layer = resolveTarget(this, args.target);
102
+ const kind = resolveLegendKind(layer, args.channels);
103
+ const options = normalizeOptions(args, kind);
104
+ if (
105
+ this.semanticSpec.guides.legend?.series !== undefined ||
106
+ this.semanticSpec.guides.legend?.color !== undefined
107
+ ) {
108
+ throw new Error("createCategoricalLegend requires a missing legend.");
109
+ }
110
+ const definition = resolveDefinition(
111
+ this,
112
+ layer,
113
+ options.channels,
114
+ options.title
115
+ );
116
+ const config = {
117
+ target: layer.id,
118
+ ...definition,
119
+ inferredTitle: !Object.hasOwn(args, "title"),
120
+ position: options.position,
121
+ align: options.align,
122
+ direction: options.direction,
123
+ columns: options.columns,
124
+ offset: options.offset,
125
+ titlePosition: options.titlePosition,
126
+ symbol: options.symbol,
127
+ labels: options.labels,
128
+ titleStyle: options.titleStyle,
129
+ itemGap: options.itemGap,
130
+ bottomGrid: options.bottomGrid,
131
+ border: options.border,
132
+ titleVisible: true
133
+ };
134
+ resolveLayout(this, config);
135
+ let next = this;
136
+ if (kind === "series") {
137
+ next = next
138
+ .editSemantic({
139
+ property: "guide.legend.series.channels",
140
+ value: definition.channels
141
+ })
142
+ .editSemantic({
143
+ property: "guide.legend.series.scales",
144
+ value: definition.scales
145
+ })
146
+ .editSemantic({
147
+ property: "guide.legend.series.title",
148
+ value: definition.title
149
+ });
150
+ } else {
151
+ next = next
152
+ .editSemantic({
153
+ property: "guide.legend.color.scale",
154
+ value: definition.scales[0]
155
+ })
156
+ .editSemantic({
157
+ property: "guide.legend.color.title",
158
+ value: definition.title
159
+ });
160
+ }
161
+ next = next._withLegendConfig(kind, config);
162
+ if (config.border !== false) next = next.createLegendBackground();
163
+ return next
164
+ .createLegendSymbols()
165
+ .createLegendLabels()
166
+ .createLegendTitle();
167
+ }
168
+ );
169
+
170
+ export const createLegend = action(
171
+ { op: "createLegend", description: "Create an inferred legend for selected channels." },
172
+ function (args = {}) {
173
+ if (!isPlainObject(args)) {
174
+ throw new TypeError("createLegend options must be a plain object.");
175
+ }
176
+ const channels = args.channels;
177
+ if (channels !== undefined && !Array.isArray(channels)) {
178
+ throw new TypeError("createLegend channels must be an array.");
179
+ }
180
+ const opacityCandidates = this.semanticSpec.layers.filter(layer =>
181
+ layer.mark?.type === "point" && layer.encoding?.opacity?.scale !== undefined
182
+ );
183
+ const hasOtherLegendCandidate = this.semanticSpec.layers.some(layer =>
184
+ ["color", "shape", "strokeDash", "size"].some(channel =>
185
+ layer.encoding?.[channel]?.scale !== undefined
186
+ )
187
+ );
188
+ if (
189
+ (channels?.length === 1 && channels[0] === "opacity") ||
190
+ (channels === undefined && opacityCandidates.length === 1 &&
191
+ !hasOtherLegendCandidate)
192
+ ) {
193
+ return this.createOpacityLegend(args);
194
+ }
195
+ const continuousColorCandidates = this.semanticSpec.layers.filter(layer => {
196
+ const encoding = ["point", "bar"].includes(layer.mark?.type)
197
+ ? layer.encoding?.color
198
+ : undefined;
199
+ const scale = findSemanticScale(this, encoding?.scale);
200
+ return scale?.type === "sequential";
201
+ });
202
+ const continuousColor = args.target === undefined
203
+ ? continuousColorCandidates.length === 1
204
+ ? continuousColorCandidates[0]
205
+ : undefined
206
+ : (() => {
207
+ const layer = findLayer(this, args.target);
208
+ return continuousColorCandidates.includes(layer) ? layer : undefined;
209
+ })();
210
+ if (
211
+ (channels?.length === 1 && channels[0] === "color" && continuousColor) ||
212
+ (channels === undefined && continuousColor)
213
+ ) {
214
+ return this.createGradientLegend(args);
215
+ }
216
+ const intervalColorCandidates = this.semanticSpec.layers.filter(layer => {
217
+ const encoding = layer.mark?.type === "point" ? layer.encoding?.color : undefined;
218
+ const scale = findSemanticScale(this, encoding?.scale);
219
+ return ["quantize", "quantile", "threshold"].includes(scale?.type);
220
+ });
221
+ const intervalColor = args.target === undefined
222
+ ? intervalColorCandidates.length === 1
223
+ ? intervalColorCandidates[0]
224
+ : undefined
225
+ : (() => {
226
+ const layer = findLayer(this, args.target);
227
+ return intervalColorCandidates.includes(layer) ? layer : undefined;
228
+ })();
229
+ if (
230
+ (channels?.length === 1 && channels[0] === "color" &&
231
+ intervalColorCandidates.length > 0) ||
232
+ (channels === undefined && intervalColorCandidates.length > 0)
233
+ ) {
234
+ return this.createIntervalLegend(args);
235
+ }
236
+ const wantsShape = channels?.includes("shape") === true;
237
+ const pointCandidates = this.semanticSpec.layers.filter(layer =>
238
+ layer.mark?.type === "point" &&
239
+ layer.encoding?.shape?.scale !== undefined &&
240
+ (wantsShape || layer.encoding?.color?.scale !== undefined)
241
+ );
242
+ const requestedPoint = args.target === undefined
243
+ ? pointCandidates.length === 1 ? pointCandidates[0] : undefined
244
+ : (() => {
245
+ const layer = findLayer(this, args.target);
246
+ return pointCandidates.includes(layer) ? layer : undefined;
247
+ })();
248
+ if (requestedPoint !== undefined) {
249
+ const { count, ...categoricalArgs } = args;
250
+ if (
251
+ requestedPoint.encoding?.size?.scale !== undefined &&
252
+ categoricalArgs.position !== undefined &&
253
+ !["right", "left"].includes(categoricalArgs.position)
254
+ ) {
255
+ throw new Error(
256
+ "Combined point series and size legends currently require a side position."
257
+ );
258
+ }
259
+ const hasMatchingLine = this.semanticSpec.layers.some(candidate =>
260
+ candidate.mark?.type === "line" &&
261
+ candidate.encoding?.color?.field === requestedPoint.encoding.color.field &&
262
+ candidate.encoding.color.scale === requestedPoint.encoding.color.scale
263
+ );
264
+ const symbol = categoricalArgs.symbol ?? {
265
+ layers: [
266
+ ...(hasMatchingLine
267
+ ? [{ type: "line", length: 32, lineWidth: 3 }]
268
+ : []),
269
+ {
270
+ type: "point",
271
+ size: Math.sqrt(64 / Math.PI),
272
+ stroke: "white",
273
+ strokeWidth: 0
274
+ }
275
+ ]
276
+ };
277
+ const inferredChannels = ["color", "shape"].filter(
278
+ channel => requestedPoint.encoding?.[channel]?.scale !== undefined
279
+ );
280
+ let next = this.createCategoricalLegend({
281
+ ...categoricalArgs,
282
+ target: requestedPoint.id,
283
+ channels: categoricalArgs.channels ?? inferredChannels,
284
+ symbol
285
+ });
286
+ if (requestedPoint.encoding?.size?.scale !== undefined) {
287
+ next = next.createSizeLegend({
288
+ target: requestedPoint.id,
289
+ ...(count === undefined ? {} : { count }),
290
+ inheritAppearance:
291
+ categoricalArgs.position === "left" ||
292
+ categoricalArgs.labels !== undefined ||
293
+ categoricalArgs.titleStyle !== undefined
294
+ });
295
+ next = next.rematerializeLegend();
296
+ }
297
+ return next;
298
+ }
299
+ return this.createCategoricalLegend(args);
300
+ }
301
+ );
302
+
303
+ export const removeCategoricalLegend = action(
304
+ {
305
+ op: "removeCategoricalLegend",
306
+ description: "Remove the active categorical legend and its concrete components."
307
+ },
308
+ function (args = {}) {
309
+ noOptions(args, "removeCategoricalLegend");
310
+ const entries = ["series", "color"]
311
+ .filter(kind => this.guideConfigs.legend?.[kind] !== undefined);
312
+ if (entries.length === 0) return this;
313
+ if (entries.length !== 1) {
314
+ throw new Error("removeCategoricalLegend requires one active categorical legend.");
315
+ }
316
+ const kind = entries[0];
317
+ const prefix = kind === "series" ? "seriesLegend" : "colorLegend";
318
+ const targets = Object.keys(this.graphicSpec.objects)
319
+ .filter(id => id.startsWith(prefix));
320
+ let next = this.editSemantic({
321
+ property: `guide.legend.${kind}`,
322
+ remove: true
323
+ });
324
+ for (const target of targets) {
325
+ next = next.editGraphics({ target, remove: true });
326
+ }
327
+ return next._withoutMaterializationConfig(["guides", "legend", kind]);
328
+ }
329
+ );
@@ -0,0 +1,140 @@
1
+ import { action } from "../../../../core/action.js";
2
+ import { activeConfig, graphic, noOptions, resolveLayout } from "./layout.js";
3
+ import { resolveLegendGraphicPlacement } from
4
+ "../../../../materialization/graphicHierarchy.js";
5
+
6
+ export const editLegendLabels = action(
7
+ { op: "editLegendLabels", description: "Rematerialize categorical legend labels." },
8
+ function (args = {}) {
9
+ noOptions(args, "editLegendLabels");
10
+ const { config } = activeConfig(this);
11
+ const id = graphic(config, "Labels");
12
+ if (this.graphicSpec.objects[id]?.type !== "text") {
13
+ throw new Error("editLegendLabels requires existing legend labels.");
14
+ }
15
+ const layout = resolveLayout(this, config);
16
+ return this
17
+ .editGraphics({ target: id, property: "length", value: config.domain.length })
18
+ .editGraphics({ target: id, property: "x", value: layout.labelX })
19
+ .editGraphics({ target: id, property: "y", value: layout.itemY })
20
+ .editGraphics({ target: id, property: "text", value: config.domain.map(String) })
21
+ .editGraphics({ target: id, property: "fill", value: config.labels.color })
22
+ .editGraphics({ target: id, property: "fontSize", value: config.labels.fontSize })
23
+ .editGraphics({ target: id, property: "fontFamily", value: config.labels.fontFamily })
24
+ .editGraphics({ target: id, property: "fontWeight", value: config.labels.fontWeight })
25
+ .editGraphics({ target: id, property: "textAlign", value: "left" })
26
+ .editGraphics({ target: id, property: "textBaseline", value: "middle" });
27
+ }
28
+ );
29
+
30
+ export const createLegendLabels = action(
31
+ { op: "createLegendLabels", description: "Create categorical legend labels." },
32
+ function (args = {}) {
33
+ noOptions(args, "createLegendLabels");
34
+ const { config } = activeConfig(this);
35
+ const id = graphic(config, "Labels");
36
+ if (this.graphicSpec.objects[id] !== undefined) {
37
+ throw new Error("createLegendLabels requires missing legend labels.");
38
+ }
39
+ return this
40
+ .createGraphics({
41
+ id,
42
+ type: "text",
43
+ length: config.domain.length,
44
+ ...resolveLegendGraphicPlacement(this)
45
+ })
46
+ .editLegendLabels();
47
+ }
48
+ );
49
+
50
+ export const editLegendTitle = action(
51
+ { op: "editLegendTitle", description: "Rematerialize the categorical legend title." },
52
+ function (args = {}) {
53
+ noOptions(args, "editLegendTitle");
54
+ const { config } = activeConfig(this);
55
+ const id = graphic(config, "Title");
56
+ if (this.graphicSpec.objects[id]?.type !== "text") {
57
+ throw new Error("editLegendTitle requires an existing legend title.");
58
+ }
59
+ const layout = resolveLayout(this, config);
60
+ return this
61
+ .editGraphics({ target: id, property: "x", value: layout.titleX })
62
+ .editGraphics({ target: id, property: "y", value: layout.titleY })
63
+ .editGraphics({ target: id, property: "text", value: config.title })
64
+ .editGraphics({ target: id, property: "fill", value: config.titleStyle.color })
65
+ .editGraphics({ target: id, property: "fontSize", value: config.titleStyle.fontSize })
66
+ .editGraphics({ target: id, property: "fontFamily", value: config.titleStyle.fontFamily })
67
+ .editGraphics({ target: id, property: "fontWeight", value: config.titleStyle.fontWeight })
68
+ .editGraphics({
69
+ target: id,
70
+ property: "textAlign",
71
+ value: ["top", "bottom"].includes(config.position) &&
72
+ config.titlePosition === "top"
73
+ ? "center"
74
+ : "left"
75
+ })
76
+ .editGraphics({ target: id, property: "textBaseline", value: "middle" });
77
+ }
78
+ );
79
+
80
+ export const createLegendTitle = action(
81
+ { op: "createLegendTitle", description: "Create the categorical legend title." },
82
+ function (args = {}) {
83
+ noOptions(args, "createLegendTitle");
84
+ const { config } = activeConfig(this);
85
+ const id = graphic(config, "Title");
86
+ if (this.graphicSpec.objects[id] !== undefined) {
87
+ throw new Error("createLegendTitle requires a missing legend title.");
88
+ }
89
+ return this
90
+ .createGraphics({
91
+ id,
92
+ type: "text",
93
+ ...resolveLegendGraphicPlacement(this)
94
+ })
95
+ .editLegendTitle();
96
+ }
97
+ );
98
+
99
+ export const editLegendBackground = action(
100
+ { op: "editLegendBackground", description: "Rematerialize the legend background." },
101
+ function (args = {}) {
102
+ noOptions(args, "editLegendBackground");
103
+ const { config } = activeConfig(this);
104
+ if (config.border === false) {
105
+ throw new Error("editLegendBackground requires border configuration.");
106
+ }
107
+ const id = graphic(config, "Background");
108
+ if (this.graphicSpec.objects[id]?.type !== "rect") {
109
+ throw new Error("editLegendBackground requires an existing background.");
110
+ }
111
+ const layout = resolveLayout(this, config).background;
112
+ return this
113
+ .editGraphics({ target: id, property: "x", value: layout.x })
114
+ .editGraphics({ target: id, property: "y", value: layout.y })
115
+ .editGraphics({ target: id, property: "width", value: layout.width })
116
+ .editGraphics({ target: id, property: "height", value: layout.height })
117
+ .editGraphics({ target: id, property: "fill", value: config.border.background })
118
+ .editGraphics({ target: id, property: "stroke", value: config.border.color })
119
+ .editGraphics({ target: id, property: "strokeWidth", value: config.border.lineWidth });
120
+ }
121
+ );
122
+
123
+ export const createLegendBackground = action(
124
+ { op: "createLegendBackground", description: "Create the legend background rect." },
125
+ function (args = {}) {
126
+ noOptions(args, "createLegendBackground");
127
+ const { config } = activeConfig(this);
128
+ const id = graphic(config, "Background");
129
+ if (this.graphicSpec.objects[id] !== undefined) {
130
+ throw new Error("createLegendBackground requires a missing background.");
131
+ }
132
+ return this
133
+ .createGraphics({
134
+ id,
135
+ type: "rect",
136
+ ...resolveLegendGraphicPlacement(this)
137
+ })
138
+ .editLegendBackground();
139
+ }
140
+ );
@@ -0,0 +1,49 @@
1
+ import {
2
+ createCategoricalLegend,
3
+ createLegend,
4
+ removeCategoricalLegend,
5
+ rematerializeLegend
6
+ } from "./actions.js";
7
+ import {
8
+ createLegendBackground,
9
+ createLegendLabels,
10
+ createLegendTitle,
11
+ editLegendBackground,
12
+ editLegendLabels,
13
+ editLegendTitle
14
+ } from "./components.js";
15
+ import {
16
+ createLegendSymbolLines,
17
+ createLegendSymbolPoints,
18
+ createLegendSymbolSwatches,
19
+ createLegendSymbols,
20
+ editLegendSymbolLines,
21
+ editLegendSymbolPoints,
22
+ editLegendSymbolSwatches,
23
+ editLegendSymbols,
24
+ rematerializeLegendHighlights
25
+ } from "./symbols.js";
26
+ import { editLegend } from "../edit.js";
27
+
28
+ export function registerCategoricalLegendActions(ProgramClass) {
29
+ ProgramClass.prototype.createLegend = createLegend;
30
+ ProgramClass.prototype.editLegend = editLegend;
31
+ ProgramClass.prototype.createCategoricalLegend = createCategoricalLegend;
32
+ ProgramClass.prototype.removeCategoricalLegend = removeCategoricalLegend;
33
+ ProgramClass.prototype.createLegendBackground = createLegendBackground;
34
+ ProgramClass.prototype.editLegendBackground = editLegendBackground;
35
+ ProgramClass.prototype.createLegendSymbols = createLegendSymbols;
36
+ ProgramClass.prototype.editLegendSymbols = editLegendSymbols;
37
+ ProgramClass.prototype.rematerializeLegendHighlights = rematerializeLegendHighlights;
38
+ ProgramClass.prototype.createLegendSymbolLines = createLegendSymbolLines;
39
+ ProgramClass.prototype.editLegendSymbolLines = editLegendSymbolLines;
40
+ ProgramClass.prototype.createLegendSymbolPoints = createLegendSymbolPoints;
41
+ ProgramClass.prototype.editLegendSymbolPoints = editLegendSymbolPoints;
42
+ ProgramClass.prototype.createLegendSymbolSwatches = createLegendSymbolSwatches;
43
+ ProgramClass.prototype.editLegendSymbolSwatches = editLegendSymbolSwatches;
44
+ ProgramClass.prototype.createLegendLabels = createLegendLabels;
45
+ ProgramClass.prototype.editLegendLabels = editLegendLabels;
46
+ ProgramClass.prototype.createLegendTitle = createLegendTitle;
47
+ ProgramClass.prototype.editLegendTitle = editLegendTitle;
48
+ ProgramClass.prototype.rematerializeLegend = rematerializeLegend;
49
+ }