ggaction 0.0.17 → 0.0.18

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 (263) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +2 -2
  3. package/knowledge/action-card.schema.json +1 -1
  4. package/knowledge/action-cards.json +1 -1
  5. package/knowledge/intent-taxonomy.json +1 -1
  6. package/knowledge/mcp-resources.json +1 -1
  7. package/knowledge/task-resolver.js +1 -1
  8. package/package.json +3 -3
  9. package/src/BasicChartProgram.js +1 -1
  10. package/src/accessibility.js +3 -2
  11. package/src/actions/boxPlots/create.js +14 -4
  12. package/src/actions/boxPlots/edit.js +39 -33
  13. package/src/actions/boxPlots/materialize.js +7 -3
  14. package/src/actions/boxPlots/options.js +37 -11
  15. package/src/actions/canvas/actions.js +70 -95
  16. package/src/actions/canvas/fitting.js +16 -1
  17. package/src/actions/canvas/index.js +3 -0
  18. package/src/actions/canvas/plotBorder.js +28 -0
  19. package/src/actions/charts/area.js +2 -2
  20. package/src/actions/charts/bar.js +34 -7
  21. package/src/actions/charts/ecdf.js +4 -18
  22. package/src/actions/charts/endpoints.js +4 -18
  23. package/src/actions/charts/heatmap.js +4 -4
  24. package/src/actions/charts/index.js +2 -0
  25. package/src/actions/charts/interval-regression.js +2 -2
  26. package/src/actions/charts/line.js +1 -1
  27. package/src/actions/charts/pie.js +3 -47
  28. package/src/actions/charts/radial.js +34 -12
  29. package/src/actions/charts/scatter.js +16 -2
  30. package/src/actions/charts/shared.js +2 -2
  31. package/src/actions/charts/text.js +35 -0
  32. package/src/actions/composition/actions.js +6 -2
  33. package/src/actions/data/box.js +2 -1
  34. package/src/actions/data/edit.js +1 -1
  35. package/src/actions/data/regression.js +2 -1
  36. package/src/actions/data/shared.js +3 -2
  37. package/src/actions/data/stack.js +3 -1
  38. package/src/actions/encodings/appearance.js +37 -29
  39. package/src/actions/encodings/barWidth.js +36 -11
  40. package/src/actions/encodings/channels.js +0 -1
  41. package/src/actions/encodings/color/continuous.js +1 -1
  42. package/src/actions/encodings/color/index.js +1 -1
  43. package/src/actions/encodings/color/policy.js +10 -3
  44. package/src/actions/encodings/density/resolve.js +1 -6
  45. package/src/actions/encodings/position/policies/arc.js +12 -10
  46. package/src/actions/encodings/position/policies/area.js +3 -3
  47. package/src/actions/encodings/position/policies/bar.js +15 -6
  48. package/src/actions/encodings/position/policies/line.js +17 -6
  49. package/src/actions/encodings/position/resolve.js +3 -2
  50. package/src/actions/encodings/ranged.js +17 -0
  51. package/src/actions/encodings/remove.js +3 -2
  52. package/src/actions/encodings/seriesLayout.js +1 -0
  53. package/src/actions/encodings/text.js +6 -3
  54. package/src/actions/errorBands/create.js +2 -2
  55. package/src/actions/errorBands/edit.js +2 -1
  56. package/src/actions/facets/actions.js +158 -240
  57. package/src/actions/gradientPlots/resolve.js +2 -17
  58. package/src/actions/guides/applicability.js +1 -1
  59. package/src/actions/guides/axes/axis.js +16 -4
  60. package/src/actions/guides/axes/edit.js +4 -4
  61. package/src/actions/guides/axes/labels.js +120 -46
  62. package/src/actions/guides/axes/lines.js +6 -5
  63. package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
  64. package/src/actions/guides/axes/parallel/policy.js +8 -7
  65. package/src/actions/guides/axes/remove.js +3 -2
  66. package/src/actions/guides/axes/tickGroups.js +5 -2
  67. package/src/actions/guides/axes/ticks.js +6 -3
  68. package/src/actions/guides/axes/titles.js +71 -26
  69. package/src/actions/guides/basic.js +2 -0
  70. package/src/actions/guides/facadeLegend.js +10 -2
  71. package/src/actions/guides/grids/grid.js +7 -1
  72. package/src/actions/guides/grids/resolve.js +59 -12
  73. package/src/actions/guides/legends/blocks.js +3 -1
  74. package/src/actions/guides/legends/categorical/actions.js +70 -17
  75. package/src/actions/guides/legends/categorical/components.js +12 -8
  76. package/src/actions/guides/legends/categorical/index.js +6 -0
  77. package/src/actions/guides/legends/categorical/layout.js +37 -8
  78. package/src/actions/guides/legends/categorical/options.js +31 -20
  79. package/src/actions/guides/legends/categorical/resolve.js +2 -2
  80. package/src/actions/guides/legends/categorical/symbols.js +18 -12
  81. package/src/actions/guides/legends/categorical/validation.js +1 -6
  82. package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
  83. package/src/actions/guides/legends/continuous/common.js +1 -447
  84. package/src/actions/guides/legends/continuous/gradient.js +47 -34
  85. package/src/actions/guides/legends/continuous/interval.js +4 -4
  86. package/src/actions/guides/legends/continuous/opacity.js +19 -12
  87. package/src/actions/guides/legends/edit.js +1 -604
  88. package/src/actions/guides/legends/focused.js +2 -2
  89. package/src/actions/guides/legends/group.js +1 -0
  90. package/src/actions/guides/legends/lane.js +1 -516
  91. package/src/actions/guides/legends/remove.js +3 -0
  92. package/src/actions/guides/legends/sampling.js +1 -1
  93. package/src/actions/guides/legends/size.js +51 -43
  94. package/src/actions/guides/legends/strokeWidth.js +11 -29
  95. package/src/actions/guides/legends/target.js +3 -3
  96. package/src/actions/guides/legends/transition.js +21 -7
  97. package/src/actions/guides/polar/axes/labels.js +4 -2
  98. package/src/actions/guides/polar/axes/titles.js +7 -5
  99. package/src/actions/guides/polar/resolve.js +6 -14
  100. package/src/actions/index.js +16 -16
  101. package/src/actions/marks/arc/actions.js +3 -10
  102. package/src/actions/marks/area/actions.js +8 -5
  103. package/src/actions/marks/area/materialize.js +8 -9
  104. package/src/actions/marks/bar/create.js +10 -4
  105. package/src/actions/marks/bar/edit.js +8 -4
  106. package/src/actions/marks/bar/materialize.js +9 -6
  107. package/src/actions/marks/line/actions.js +11 -4
  108. package/src/actions/marks/line/materialize.js +9 -5
  109. package/src/actions/marks/point/edit.js +1 -1
  110. package/src/actions/marks/rect/actions.js +3 -3
  111. package/src/actions/marks/remove.js +2 -0
  112. package/src/actions/marks/rule/actions.js +7 -4
  113. package/src/actions/marks/text/actions.js +17 -5
  114. package/src/actions/marks/text/layout.js +3 -0
  115. package/src/actions/primitives/semanticValidation/layer.js +4 -0
  116. package/src/actions/regression/create.js +1 -1
  117. package/src/actions/regression/edit.js +3 -3
  118. package/src/actions/regression/resolve.js +2 -6
  119. package/src/actions/scales/channels.js +9 -4
  120. package/src/actions/scales/consumers/families.js +16 -5
  121. package/src/actions/scales/create.js +8 -3
  122. package/src/actions/scales/definitions.js +1 -425
  123. package/src/actions/scales/edit.js +2 -2
  124. package/src/actions/scales/editPolicy.js +9 -3
  125. package/src/actions/scales/materialize.js +1 -1
  126. package/src/actions/scales/preview.js +21 -1
  127. package/src/actions/scales/quantitativeColor.js +3 -3
  128. package/src/actions/selection/actions.js +1 -13
  129. package/src/actions/theme/reconcile.js +81 -27
  130. package/src/actions/theme/state.js +12 -0
  131. package/src/actions/titles/actions.js +5 -18
  132. package/src/actions/titles/resolve.js +41 -19
  133. package/src/actions/violinPlots/edit.js +3 -20
  134. package/src/core/compositionState.js +4 -1
  135. package/src/core/font.js +9 -2
  136. package/src/core/optionRequirements.js +1 -0
  137. package/src/core/resourceReferences.js +3 -0
  138. package/src/core/textMetricProfile.js +3 -2
  139. package/src/core/textMetrics.js +41 -4
  140. package/src/core/validation.js +26 -0
  141. package/src/core/vocabulary.js +4 -0
  142. package/src/grammar/aggregate.js +4 -18
  143. package/src/grammar/arcs.js +1 -386
  144. package/src/grammar/areaPaint.js +10 -0
  145. package/src/grammar/areaSeries.js +18 -10
  146. package/src/grammar/bars/geometry.js +30 -3
  147. package/src/grammar/bars/policy.js +12 -1
  148. package/src/grammar/bin.js +2 -12
  149. package/src/grammar/bin2d.js +1 -6
  150. package/src/grammar/boxPlot.js +56 -9
  151. package/src/grammar/categoryOrder.js +1 -6
  152. package/src/grammar/complete.js +15 -19
  153. package/src/grammar/computed.js +92 -25
  154. package/src/grammar/curveCommands.js +29 -16
  155. package/src/grammar/datasetSchema.js +3 -7
  156. package/src/grammar/displayLabels.js +28 -0
  157. package/src/grammar/ecdf.js +2 -9
  158. package/src/grammar/facets/dependencies.js +6 -2
  159. package/src/grammar/facets/guides.js +5 -0
  160. package/src/grammar/facets/index.js +2 -5
  161. package/src/grammar/fold.js +2 -12
  162. package/src/grammar/gradientProfile.js +1 -6
  163. package/src/grammar/impute.js +21 -48
  164. package/src/grammar/interval.js +2 -9
  165. package/src/grammar/legendGradient.js +18 -0
  166. package/src/grammar/legendGroups.js +1 -0
  167. package/src/grammar/legendTitleStyle.js +22 -0
  168. package/src/grammar/lineSeries.js +32 -17
  169. package/src/grammar/markLabels.js +5 -2
  170. package/src/grammar/normalize.js +4 -54
  171. package/src/grammar/numeric.js +1 -290
  172. package/src/grammar/parallelCoordinates.js +1 -6
  173. package/src/grammar/parseTime.js +41 -0
  174. package/src/grammar/pathCommands.js +11 -4
  175. package/src/grammar/polar.js +1 -184
  176. package/src/grammar/positionCompatibility.js +3 -3
  177. package/src/grammar/regression/derive.js +7 -1
  178. package/src/grammar/regression/models.js +83 -55
  179. package/src/grammar/regression/parameters.js +14 -3
  180. package/src/grammar/roundedRect.js +42 -54
  181. package/src/grammar/scales/appearance.js +1 -206
  182. package/src/grammar/scales/color.js +1 -382
  183. package/src/grammar/scales/colorConsumers.js +5 -2
  184. package/src/grammar/scales/definition.js +1 -197
  185. package/src/grammar/scales/discretized.js +1 -195
  186. package/src/grammar/scales/size.js +1 -456
  187. package/src/grammar/scales/temporal.js +2 -13
  188. package/src/grammar/scales/transformed.js +1 -383
  189. package/src/grammar/scales/types.js +1 -1
  190. package/src/grammar/scales/validation.js +1 -66
  191. package/src/grammar/schemas/concreteGraphic.js +11 -0
  192. package/src/grammar/schemas/graphic.js +3 -0
  193. package/src/grammar/schemas/semanticPath.js +2 -1
  194. package/src/grammar/stack.js +2 -10
  195. package/src/grammar/statisticalReference.js +1 -6
  196. package/src/grammar/summary.js +3 -26
  197. package/src/grammar/text.js +19 -0
  198. package/src/grammar/timeUnit.js +16 -7
  199. package/src/grammar/transformKeys.js +47 -0
  200. package/src/grammar/transforms.js +4 -1
  201. package/src/grammar/valueFormat.js +17 -5
  202. package/src/grammar/weightedStatistics.js +1 -6
  203. package/src/grammar/window.js +4 -58
  204. package/src/inspectionDescriptors.js +1 -1
  205. package/src/layout/canvas.js +1 -110
  206. package/src/layout/facets.js +1 -244
  207. package/src/layout/labels.js +1 -0
  208. package/src/layout/legend.js +13 -0
  209. package/src/layout/legendGroup.js +1 -0
  210. package/src/layout/legendItems.js +59 -15
  211. package/src/layout/legendLane.js +1 -441
  212. package/src/layout/legendText.js +1 -0
  213. package/src/layout/title.js +35 -13
  214. package/src/materialization/bars/aggregate.js +11 -5
  215. package/src/materialization/bars/histogram.js +4 -1
  216. package/src/materialization/bars/ranged.js +19 -6
  217. package/src/materialization/bars/resolve.js +22 -1
  218. package/src/materialization/coordinateBounds.js +8 -0
  219. package/src/materialization/dependencies.js +4 -44
  220. package/src/materialization/facetGuides/layout.js +1 -0
  221. package/src/materialization/facetGuides/placement.js +19 -8
  222. package/src/materialization/facetGuides/preparation.js +15 -7
  223. package/src/materialization/facetHeaders.js +1 -347
  224. package/src/materialization/facets.js +1 -185
  225. package/src/materialization/graphicHierarchy.js +1 -1
  226. package/src/materialization/guides/layout.js +1 -57
  227. package/src/materialization/guides/resources.js +1 -123
  228. package/src/materialization/layout.js +53 -0
  229. package/src/materialization/legendGroups.js +1 -0
  230. package/src/materialization/legendText.js +1 -0
  231. package/src/materialization/legends.js +1 -1
  232. package/src/materialization/marks/capabilities.js +5 -6
  233. package/src/materialization/rect.js +1 -1
  234. package/src/materialization/scaleGuideDependencies.js +1 -1
  235. package/src/materialization/scales/map.js +3 -0
  236. package/src/materialization/scales/policies/arc.js +8 -7
  237. package/src/materialization/scales/policies/bar.js +19 -7
  238. package/src/materialization/scales/policies/series.js +2 -1
  239. package/src/materialization/scales/resolve.js +5 -3
  240. package/src/materialization/selection/items/arc.js +1 -1
  241. package/src/materialization/selection/items/bar.js +7 -6
  242. package/src/materialization/selection/items/path.js +7 -6
  243. package/src/materialization/selection/items/rect.js +2 -2
  244. package/src/materialization/text.js +13 -4
  245. package/src/materialization/textLines.js +15 -0
  246. package/src/persistence/codec.js +37 -6
  247. package/src/persistence/validation.js +47 -0
  248. package/src/persistence.js +18 -5
  249. package/src/renderers/canvas/circle.js +6 -3
  250. package/src/renderers/canvas/fill.js +7 -1
  251. package/src/renderers/canvas/index.js +2 -1
  252. package/src/renderers/canvas/line.js +1 -0
  253. package/src/renderers/canvas/path.js +1 -1
  254. package/src/renderers/canvas/rect.js +11 -8
  255. package/src/renderers/canvas/stroke.js +2 -1
  256. package/src/renderers/canvas/text.js +5 -3
  257. package/src/renderers/svg.js +4 -1
  258. package/src/selectors/layers.js +19 -0
  259. package/src/theme/defaults.js +8 -2
  260. package/src/version.js +1 -1
  261. package/types/basic.d.ts +1 -0
  262. package/types/index.d.ts +7 -0
  263. package/types/program.d.ts +347 -85
@@ -1,604 +1 @@
1
- import { withGuideLayoutValidation } from "../../../materialization/guides/layout.js";
2
- import { resolveLegendStepConfig } from "./creation.js";
3
- import { normalizeLegendOrder } from "../../../grammar/categoryOrder.js";
4
- import { resolveDefinition } from "./categorical/resolve.js";
5
- import { action } from "../../../core/action.js";
6
- import {
7
- validateOptionObject
8
- } from "../../../core/validation.js";
9
- import { normalizeOptions } from "./categorical/options.js";
10
- import { resolveLegendSymbol } from "./categorical/recipes.js";
11
- import { resolveLayout } from "./categorical/layout.js";
12
- import { resolveLegendCreationPlan } from "./categorical/actions.js";
13
- import { categoricalSymbolIds, reconcileCategoricalSymbols, createCategoricalLegendFromConfig, resolveCategoricalLegendRevision, removeLegendKinds } from "./lifecycle.js";
14
- import { createGradientLegendFromConfig } from "./continuous/gradient.js";
15
- import {
16
- normalizeItemLegendLayout,
17
- normalizeLegendBorder,
18
- normalizeLegendTextOptions,
19
- normalizeLegendTitleOptions,
20
- normalizeContinuousLegend,
21
- validatePositive
22
- } from "./continuous/common.js";
23
- import { normalizeOpacitySymbol, createOpacityLegendFromConfig } from "./continuous/opacity.js";
24
- import { normalizeIntervalLegend, resolveIntervalConfig, resolveIntervalLayout, createIntervalLegendFromConfig } from "./continuous/interval.js";
25
- import {
26
- createStrokeGradientLegendFromConfig,
27
- createStrokeIntervalLegendFromConfig,
28
- resolveStrokeIntervalConfig,
29
- resolveStrokeIntervalLayout
30
- } from "./continuous/stroke.js";
31
- import { findLayer } from "../../../selectors/layers.js";
32
- import { resolveLegendGraphicPlacement } from
33
- "../../../materialization/graphicHierarchy.js";
34
- import { resolveLegendTarget, validateLegendChannels } from "./target.js";
35
- import { isDiscreteSizeScaleType } from "../../../grammar/scales/index.js";
36
- import { SIZE_LEGEND_LABELS, SIZE_LEGEND_TITLE_STYLE, resolveSizeLegendLayout, createSizeLegendFromConfig } from "./size.js";
37
- import {
38
- STROKE_WIDTH_LEGEND_LABELS,
39
- STROKE_WIDTH_LEGEND_TITLE_STYLE,
40
- resolveStrokeWidthLegendLayout,
41
- createStrokeWidthLegendFromConfig
42
- } from "./strokeWidth.js";
43
- import { normalizeLegendSampling } from "./sampling.js";
44
- import { planLegendBlockTransitions } from "./transition.js";
45
-
46
- const OPTIONS = Object.freeze([
47
- "target", "channels", "position", "layout", "align", "direction", "columns", "offset",
48
- "titlePosition", "title", "symbol", "labels", "titleStyle", "itemGap",
49
- "border", "count", "values", "gradient", "order"
50
- ]);
51
-
52
- function mergeObject(previous, patch) {
53
- return patch === undefined
54
- ? previous
55
- : { ...previous, ...patch };
56
- }
57
-
58
- function mergeBorder(previous, patch) {
59
- if (patch === undefined) return previous;
60
- if (patch === false || patch === true) return patch;
61
- return previous === false ? patch : { ...previous, ...patch };
62
- }
63
-
64
- function reconcileGraphic(program, id, shouldExist, definition) {
65
- const exists = program.graphicSpec.objects[id] !== undefined;
66
- if (exists && !shouldExist) return program.editGraphics({ target: id, remove: true });
67
- if (!exists && shouldExist) {
68
- return program.createGraphics({
69
- id,
70
- ...definition,
71
- ...resolveLegendGraphicPlacement(program, {
72
- ...(definition.before === undefined ? {} : { before: definition.before }),
73
- ...(definition.after === undefined ? {} : { after: definition.after })
74
- })
75
- });
76
- }
77
- return program;
78
- }
79
-
80
- function resolveContinuousEdit(program, kind, previous, args) {
81
- const gradient = ["gradient", "strokeGradient"].includes(kind);
82
- const allowed = gradient
83
- ? ["target", "position", "align", "offset", "title", "labels",
84
- "titleStyle", "titlePosition", "border", "count", "gradient"]
85
- : ["target", "position", "align", "offset", "title", "symbol", "labels",
86
- "titleStyle", "titlePosition", "itemGap", "border", "count", "values"];
87
- for (const key of Object.keys(args)) {
88
- if (!allowed.includes(key)) throw new Error(`${kind} legend does not accept ${key}.`);
89
- }
90
- const titleMode = args.title;
91
- const inferredTitle = titleMode === "auto"
92
- ? true
93
- : typeof titleMode === "string" ? false : previous.inferredTitle;
94
- const titleVisible = titleMode === false ? false
95
- : titleMode === undefined ? previous.titleVisible !== false : true;
96
- const layer = findLayer(program, previous.target);
97
- const channel = kind === "gradient" ? "color"
98
- : kind === "strokeGradient" ? "stroke" : "opacity";
99
- const inferred = layer?.encoding?.[channel]?.field;
100
- const title = titleMode === "auto"
101
- ? inferred
102
- : typeof titleMode === "string" ? titleMode : previous.title;
103
- const titlePosition = args.titlePosition ?? previous.titlePosition;
104
- const enteringInline = kind === "opacity" &&
105
- titlePosition === "left" && previous.titlePosition !== "left";
106
- const labels = mergeObject(previous.labels, args.labels);
107
- const sampling = gradient ? undefined : normalizeLegendSampling(args, {
108
- previous,
109
- operation: "edit",
110
- label: "Opacity legend"
111
- });
112
- const normalized = normalizeContinuousLegend({
113
- target: previous.target,
114
- position: args.position ?? previous.position,
115
- align: args.align ?? previous.align,
116
- offset: args.offset ?? previous.offset,
117
- ...(gradient ? { count: args.count ?? previous.count } : {}),
118
- titlePosition,
119
- title,
120
- labels: enteringInline && args.labels?.offset === undefined
121
- ? { ...labels, offset: 8 }
122
- : labels,
123
- titleStyle: mergeObject(previous.titleStyle, args.titleStyle),
124
- border: mergeBorder(previous.border, args.border),
125
- ...(kind === "opacity" ? {
126
- itemGap: args.itemGap ?? (enteringInline ? 20 : previous.itemGap),
127
- symbol: args.symbol ?? previous.symbol
128
- } : {})
129
- }, gradient ? "gradient" : kind);
130
- const { count: _legacyCount, ...canonicalPrevious } = previous;
131
- const config = {
132
- ...(gradient ? previous : canonicalPrevious),
133
- ...normalized,
134
- ...(gradient ? {} : { sampling }),
135
- inferredTitle,
136
- titleVisible
137
- };
138
- if (gradient) {
139
- const gradient = mergeObject(previous.gradient, args.gradient);
140
- validatePositive(gradient.length, "Gradient length");
141
- validatePositive(gradient.thickness, "Gradient thickness");
142
- config.gradient = gradient;
143
- } else {
144
- config.symbol = normalizeOpacitySymbol(args.symbol ?? previous.symbol);
145
- }
146
- return { config, titleMode, title, titleVisible };
147
- }
148
-
149
- function editContinuous(program, kind, previous, args) {
150
- const { config, titleMode, title, titleVisible } = resolveContinuousEdit(program, kind, previous, args);
151
- const prefix = kind === "gradient" ? "colorGradient"
152
- : kind === "strokeGradient" ? "strokeGradient" : "opacityLegend";
153
- const semanticKind = kind === "gradient" ? "color"
154
- : kind === "strokeGradient" ? "stroke" : "opacity";
155
- let next = program;
156
- if (titleMode === "auto" || typeof titleMode === "string") {
157
- next = next.editSemantic({
158
- property: `guide.legend.${semanticKind}.title`,
159
- value: title
160
- });
161
- }
162
- next = next._withLegendConfig(kind, config);
163
- next = reconcileGraphic(next, `${prefix}Background`, config.border !== false, {
164
- type: "rect",
165
- before: ["gradient", "strokeGradient"].includes(kind)
166
- ? `${prefix}Strips`
167
- : "opacityLegendSymbols"
168
- });
169
- next = reconcileGraphic(next, `${prefix}Title`, titleVisible, { type: "text" });
170
- return next.rematerializeLegend();
171
- }
172
-
173
- function resolveIntervalEdit(program, previous, args, channel = "color") {
174
- for (const key of Object.keys(args)) {
175
- if (![
176
- "target", "position", "align", "direction", "offset", "title",
177
- "symbol", "labels", "titleStyle", "itemGap", "border", "layout", "columns", "titlePosition"
178
- ].includes(key)) {
179
- throw new Error(`interval legend does not accept ${key}.`);
180
- }
181
- }
182
- const titleMode = args.title;
183
- const layer = findLayer(program, previous.target);
184
- const inferredTitle = titleMode === "auto"
185
- ? true
186
- : typeof titleMode === "string" ? false : previous.inferredTitle;
187
- const titleVisible = titleMode === false
188
- ? false
189
- : titleMode === undefined ? previous.titleVisible !== false : true;
190
- const title = titleMode === "auto"
191
- ? layer?.encoding?.[channel]?.field
192
- : typeof titleMode === "string" ? titleMode : previous.title;
193
- const normalized = normalizeIntervalLegend({
194
- target: previous.target,
195
- position: args.position ?? previous.position,
196
- align: args.align ?? previous.align,
197
- direction: args.direction ?? (args.position !== undefined && args.position !== previous.position
198
- ? undefined : previous.direction),
199
- layout: args.layout === undefined ? previous.layout : args.layout,
200
- columns: args.columns ?? previous.columns,
201
- titlePosition: args.titlePosition ?? previous.titlePosition,
202
- offset: args.offset ?? previous.offset,
203
- title,
204
- symbol: mergeObject(previous.symbol, args.symbol),
205
- labels: mergeObject(previous.labels, args.labels),
206
- titleStyle: mergeObject(previous.titleStyle, args.titleStyle),
207
- itemGap: args.itemGap ?? previous.itemGap,
208
- border: mergeBorder(previous.border, args.border)
209
- });
210
- const config = {
211
- ...previous,
212
- ...normalized,
213
- inferredTitle,
214
- titleVisible,
215
- ...(channel === "stroke" && args.symbol?.strokeWidth !== undefined
216
- ? { inferredStrokeWidth: false }
217
- : {})
218
- };
219
- const resolved = channel === "stroke"
220
- ? resolveStrokeIntervalConfig(program, config)
221
- : resolveIntervalConfig(program, config);
222
- (channel === "stroke" ? resolveStrokeIntervalLayout : resolveIntervalLayout)(
223
- program,
224
- resolved.config,
225
- resolved.scale
226
- );
227
- return { normalized, config, titleMode, title, titleVisible };
228
- }
229
-
230
- function editInterval(program, previous, args, channel = "color") {
231
- const kind = channel === "stroke" ? "strokeInterval" : "interval";
232
- const prefix = channel === "stroke" ? "strokeInterval" : "colorLegend";
233
- const { normalized, config, titleMode, title, titleVisible } = resolveIntervalEdit(program, previous, args, channel);
234
- let next = program._withLegendConfig(kind, config);
235
- if (titleMode === "auto" || typeof titleMode === "string") {
236
- next = next.editSemantic({
237
- property: `guide.legend.${channel}.title`,
238
- value: title
239
- });
240
- }
241
- next = reconcileGraphic(next, `${prefix}Title`, titleVisible, {
242
- type: "text"
243
- });
244
- next = reconcileGraphic(
245
- next,
246
- `${prefix}Background`,
247
- normalized.border !== false,
248
- { type: "rect", before: `${prefix}Symbols` }
249
- );
250
- return next.rematerializeLegend();
251
- }
252
-
253
- function resolveSampledLegendEdit(program, kind, previous, args) {
254
- const size = kind === "size";
255
- const label = size ? "size" : "stroke-width";
256
- const allowed = ["target", "title", "count", "values", "labels", "titleStyle",
257
- "position", "layout", "align", "direction", "columns", "titlePosition", "offset", "itemGap", "border"];
258
- for (const key of Object.keys(args)) {
259
- if (!allowed.includes(key)) {
260
- throw new Error(`${label} legend does not accept ${key}.`);
261
- }
262
- }
263
- const discrete = size &&
264
- isDiscreteSizeScaleType(program.resolvedScales[previous.scale]?.type);
265
- if (discrete && (args.count !== undefined || args.values !== undefined)) {
266
- throw new Error("Discrete size legends do not support count or exact values.");
267
- }
268
- const sampling = discrete ? undefined : normalizeLegendSampling(args, {
269
- previous,
270
- operation: "edit",
271
- label: `${size ? "Size" : "Stroke-width"} legend`
272
- });
273
- const layer = findLayer(program, previous.target);
274
- const titleMode = args.title;
275
- const inferredTitle = titleMode === "auto"
276
- ? true
277
- : typeof titleMode === "string" ? false : previous.inferredTitle;
278
- const titleVisible = titleMode === false
279
- ? false
280
- : titleMode === undefined ? previous.titleVisible !== false : true;
281
- const title = titleMode === "auto"
282
- ? layer?.encoding?.[kind]?.field
283
- : typeof titleMode === "string" ? titleMode : previous.title;
284
- const { count: _legacyCount, ...continuousPrevious } = previous;
285
- const canonicalPrevious = discrete ? previous : continuousPrevious;
286
- const config = {
287
- ...canonicalPrevious,
288
- title,
289
- inferredTitle,
290
- titleVisible,
291
- ...(discrete ? {} : { sampling }),
292
- ...(size ? { inheritAppearance: false } : {}),
293
- labels: normalizeLegendTextOptions(
294
- args.labels,
295
- "editLegend.labels",
296
- previous.labels ?? (size ? SIZE_LEGEND_LABELS : STROKE_WIDTH_LEGEND_LABELS)
297
- ),
298
- titleStyle: normalizeLegendTitleOptions(
299
- args.titleStyle,
300
- "editLegend.titleStyle",
301
- previous.titleStyle ?? (size ? SIZE_LEGEND_TITLE_STYLE : STROKE_WIDTH_LEGEND_TITLE_STYLE)
302
- )
303
- };
304
- Object.assign(config, normalizeItemLegendLayout({ ...previous, ...args,
305
- direction: args.direction ?? (args.position !== undefined && args.position !== previous.position
306
- ? undefined : previous.direction) }));
307
- config.border = normalizeLegendBorder(mergeBorder(previous.border, args.border));
308
- (size ? resolveSizeLegendLayout : resolveStrokeWidthLegendLayout)(program, config);
309
- return { config, titleMode, title, titleVisible };
310
- }
311
-
312
- function editSampledLegend(program, kind, previous, args) {
313
- const { config, titleMode, title, titleVisible } = resolveSampledLegendEdit(program, kind, previous, args);
314
- const prefix = kind === "size" ? "sizeLegend" : "strokeWidthLegend";
315
- let next = program;
316
- if (titleMode === "auto" || typeof titleMode === "string") {
317
- next = next.editSemantic({
318
- property: `guide.legend.${kind}.title`,
319
- value: title
320
- });
321
- }
322
- next = next._withLegendConfig(kind, config);
323
- next = reconcileGraphic(next, `${prefix}Background`, config.border !== false, {
324
- type: "rect", before: `${prefix}Symbols`
325
- });
326
- next = reconcileGraphic(next, `${prefix}Title`, titleVisible, {
327
- type: "text", after: `${prefix}Labels`
328
- });
329
- return next.rematerializeLegend();
330
- }
331
-
332
- function resolveCompanionSizeEdit(program, previous, size, args) {
333
- if (size === undefined) return undefined;
334
- const discrete = isDiscreteSizeScaleType(
335
- program.resolvedScales[size.scale]?.type
336
- );
337
- if (discrete && (args.count !== undefined || args.values !== undefined)) {
338
- throw new Error("Discrete size legends do not support count or exact values.");
339
- }
340
- const sampling = discrete ? undefined : normalizeLegendSampling(args, {
341
- previous: size,
342
- operation: "edit",
343
- label: "Size legend"
344
- });
345
- const { count: _legacyCount, ...continuousSize } = size;
346
- const canonicalSize = discrete ? size : continuousSize;
347
- const config = {
348
- ...canonicalSize,
349
- ...(discrete ? {} : { sampling })
350
- };
351
- if (args.labels === undefined && args.titleStyle === undefined) return config;
352
- const labels = size.inheritAppearance
353
- ? { ...previous.labels, offset: size.labels.offset } : size.labels ?? SIZE_LEGEND_LABELS;
354
- const titleStyle = size.inheritAppearance
355
- ? previous.titleStyle : size.titleStyle ?? SIZE_LEGEND_TITLE_STYLE;
356
- return { ...config, inheritAppearance: false,
357
- labels: normalizeLegendTextOptions(args.labels, "editLegend.labels", labels),
358
- titleStyle: normalizeLegendTitleOptions(args.titleStyle, "editLegend.titleStyle", titleStyle) };
359
- }
360
-
361
- function resolveCategoricalEdit(program, kind, previous, size, args, storedOrder = program.semanticSpec.guides.legend?.[kind]?.order) {
362
- if (args.values !== undefined) {
363
- throw new Error(
364
- "Combined and categorical legends require a channel block selector for exact values."
365
- );
366
- }
367
- if (args.gradient !== undefined) {
368
- throw new Error("Categorical legends do not accept gradient.");
369
- }
370
- if (args.count !== undefined && size === undefined) {
371
- throw new Error("Legend count requires an existing size legend.");
372
- }
373
- const titleMode = args.title;
374
- const inferredTitle = titleMode === "auto"
375
- ? true
376
- : typeof titleMode === "string" ? false : previous.inferredTitle;
377
- const titleVisible = titleMode === false ? false
378
- : titleMode === undefined ? previous.titleVisible !== false : true;
379
- const title = titleMode === "auto"
380
- ? previous.field
381
- : typeof titleMode === "string" ? titleMode : previous.title;
382
- const mergedLabels = mergeObject(previous.labels, args.labels);
383
- if (mergedLabels.format !== undefined && mergedLabels.format !== "auto" && size === undefined) {
384
- throw new Error("Categorical legend labels do not accept format.");
385
- }
386
- const { format: _sizeFormat, ...categoricalLabels } = mergedLabels;
387
- const inferredSymbol = args.symbol === undefined
388
- ? previous.inferredSymbol
389
- : args.symbol === "auto";
390
- const normalized = normalizeOptions({
391
- target: previous.target,
392
- channels: previous.channels,
393
- layout: args.layout === undefined ? previous.layout : args.layout,
394
- position: args.position ?? previous.position,
395
- align: args.align ?? previous.align,
396
- direction: args.direction ?? (args.position !== undefined && args.position !== previous.position
397
- ? undefined : previous.direction),
398
- ...(args.columns === undefined && previous.columns === undefined
399
- ? {} : { columns: args.columns ?? previous.columns }),
400
- offset: args.offset ?? previous.offset,
401
- titlePosition: args.titlePosition ?? previous.titlePosition,
402
- title,
403
- symbol: args.symbol === undefined ? previous.symbol
404
- : resolveLegendSymbol(
405
- program,
406
- findLayer(program, previous.target),
407
- previous.channels,
408
- args.symbol,
409
- kind
410
- ),
411
- labels: categoricalLabels,
412
- titleStyle: mergeObject(previous.titleStyle, args.titleStyle),
413
- itemGap: args.itemGap ?? previous.itemGap,
414
- border: mergeBorder(previous.border, args.border)
415
- }, kind, { internalSymbol: inferredSymbol });
416
- const config = {
417
- ...previous,
418
- ...normalized,
419
- inferredTitle,
420
- inferredSymbol,
421
- titleVisible
422
- };
423
- const order = args.order === undefined
424
- ? storedOrder
425
- : normalizeLegendOrder(args.order);
426
- // Resolve the final domain before changing semantic/config/graphic state.
427
- resolveDefinition(program, findLayer(program, previous.target), previous.channels, title, order);
428
- return { config, order, titleMode, title, titleVisible, sizeConfig: resolveCompanionSizeEdit(program, previous, size, args) };
429
- }
430
-
431
- function editCategorical(program, kind, previous, size, args) {
432
- const { config, order, titleMode, title, titleVisible, sizeConfig } = resolveCategoricalEdit(program, kind, previous, size, args);
433
- const newSymbols = categoricalSymbolIds(config);
434
- let next = program;
435
- if (titleMode === "auto" || typeof titleMode === "string") {
436
- next = next.editSemantic({
437
- property: `guide.legend.${kind}.title`,
438
- value: title
439
- });
440
- }
441
- if (args.order !== undefined) {
442
- if (order === "scale") {
443
- if (program.semanticSpec.guides.legend?.[kind]?.order !== undefined) {
444
- next = next.editSemantic({ property: `guide.legend.${kind}.order`, remove: true });
445
- }
446
- } else {
447
- next = next.editSemantic({ property: `guide.legend.${kind}.order`, value: order });
448
- }
449
- }
450
- next = next._withLegendConfig(kind, config);
451
- next = reconcileCategoricalSymbols(next, previous, config);
452
- next = reconcileGraphic(next, `${kind}LegendBackground`,
453
- config.border !== false, {
454
- type: "rect",
455
- before: [...newSymbols][0]
456
- });
457
- const titleId = `${kind}LegendTitle`;
458
- next = reconcileGraphic(next, titleId, titleVisible, {
459
- type: "text",
460
- ...(next.graphicSpec.objects.sizeLegendSymbols === undefined
461
- ? {} : { before: next.graphicSpec.objects.sizeLegendBackground === undefined
462
- ? "sizeLegendSymbols" : "sizeLegendBackground" })
463
- });
464
- if (size !== undefined) {
465
- next = next._withLegendConfig("size", sizeConfig);
466
- }
467
- return next.rematerializeLegend();
468
- }
469
-
470
-
471
- function editLegendContent(program, target, args) {
472
- validateLegendChannels(args.channels, "editLegend");
473
- const { channels, target: _target, ...patch } = args;
474
- const previous = program.guideConfigs.legend ?? {};
475
- const removed = Object.keys(previous).filter(kind => previous[kind].target === target);
476
- let view = removed.reduce((next, kind) => next._withoutMaterializationConfig(["guides", "legend", kind]), program);
477
- const creation = resolveLegendCreationPlan(view, { target, channels });
478
- const oldCategorical = removed.find(kind =>
479
- ["series", "color", "stroke"].includes(kind)
480
- );
481
- let plans = creation.steps.map(step => {
482
- const descriptor = resolveLegendStepConfig(view, step);
483
- const { kind } = descriptor;
484
- if (previous[kind] !== undefined && previous[kind].target !== target) {
485
- throw new Error(`Legend ${kind} content already belongs to another target.`);
486
- }
487
- if (["series", "color", "stroke"].includes(kind) && oldCategorical !== undefined) {
488
- const order = patch.order === undefined ? program.semanticSpec.guides.legend?.[oldCategorical]?.order
489
- : normalizeLegendOrder(patch.order);
490
- return { ...resolveCategoricalLegendRevision(view, oldCategorical, previous[oldCategorical],
491
- descriptor.config.channels, { order, validateLayout: false }), kind };
492
- }
493
- return { ...descriptor, config: previous[kind]?.target === target ? previous[kind] : descriptor.config };
494
- });
495
- const categorical = plans.find(plan =>
496
- ["series", "color", "stroke"].includes(plan.kind)
497
- );
498
- const size = plans.find(plan => plan.kind === "size");
499
- if (categorical !== undefined) {
500
- if (size !== undefined && previous.size?.target !== target) {
501
- size.config = { ...size.config, inheritAppearance: categorical.config.position === "left" ||
502
- patch.position === "left" || patch.labels !== undefined || patch.titleStyle !== undefined };
503
- }
504
- const edited = resolveCategoricalEdit(view, categorical.kind, categorical.config, size?.config, patch, categorical.order);
505
- categorical.config = edited.config;
506
- categorical.order = edited.order;
507
- if (size !== undefined) {
508
- size.config = edited.sizeConfig;
509
- if (categorical.config.layout === "legacy-bottom") {
510
- throw new Error('Combined size legends require layout "edge".');
511
- }
512
- }
513
- } else {
514
- const plan = plans[0];
515
- if (plan.kind === "size" || plan.kind === "strokeWidth") {
516
- plan.config = resolveSampledLegendEdit(view, plan.kind, plan.config, patch).config;
517
- } else if (["interval", "strokeInterval"].includes(plan.kind)) {
518
- plan.config = resolveIntervalEdit(
519
- view,
520
- plan.config,
521
- patch,
522
- plan.kind === "strokeInterval" ? "stroke" : "color"
523
- ).config;
524
- }
525
- else plan.config = resolveContinuousEdit(view, plan.kind, plan.config, patch).config;
526
- }
527
- plans = planLegendBlockTransitions(program, target, plans);
528
- // The complete final content and styles are validated before removing resources.
529
- for (const { kind, config } of plans) view = view._withLegendConfig(kind, config);
530
- if (categorical !== undefined) resolveLayout(view, categorical.config);
531
- let next = removeLegendKinds(program, removed);
532
- for (const { kind, config, order } of plans) {
533
- if (["series", "color", "stroke"].includes(kind)) {
534
- next = createCategoricalLegendFromConfig(next, config, order);
535
- }
536
- else next = ({ size: createSizeLegendFromConfig, strokeWidth: createStrokeWidthLegendFromConfig,
537
- gradient: createGradientLegendFromConfig, opacity: createOpacityLegendFromConfig,
538
- interval: createIntervalLegendFromConfig,
539
- strokeGradient: createStrokeGradientLegendFromConfig,
540
- strokeInterval: createStrokeIntervalLegendFromConfig })[kind](next, config);
541
- }
542
- return next.rematerializeLegend();
543
- }
544
-
545
- export const editLegend = /* @__PURE__ */ action(
546
- { op: "editLegend", description: "Edit one stable legend content, layout or appearance." },
547
- withGuideLayoutValidation(function (args = {}) {
548
- validateOptionObject(args, OPTIONS, "editLegend");
549
- const changes = Object.keys(args).filter(key => key !== "target");
550
- if (changes.length === 0) {
551
- throw new Error("editLegend requires at least one change.");
552
- }
553
- if (args.title !== undefined && args.title !== false && args.title !== "auto" && (
554
- typeof args.title !== "string" || args.title.length === 0
555
- )) {
556
- throw new TypeError('editLegend title must be a non-empty string, "auto", or false.');
557
- }
558
- const target = resolveLegendTarget(this, args.target, "editLegend");
559
- if (args.channels !== undefined) return editLegendContent(this, target, args);
560
- const configs = this.guideConfigs.legend ?? {};
561
- const ownedKinds = Object.keys(configs).filter(
562
- kind => configs[kind]?.target === target
563
- );
564
- if (args.values !== undefined && ownedKinds.length > 1) {
565
- throw new Error(
566
- "A target with multiple legend blocks requires a channel block selector for exact values."
567
- );
568
- }
569
- const categoricalKinds = ["series", "color", "stroke"].filter(
570
- kind => configs[kind]?.target === target
571
- );
572
- if (categoricalKinds.length > 1) {
573
- throw new Error(
574
- "editLegend requires channels when a target owns multiple categorical legend blocks."
575
- );
576
- }
577
- const categoricalKind = categoricalKinds[0];
578
- if (categoricalKind !== undefined) {
579
- return editCategorical(
580
- this,
581
- categoricalKind,
582
- configs[categoricalKind],
583
- configs.size?.target === target ? configs.size : undefined,
584
- args
585
- );
586
- }
587
- if (configs.interval?.target === target) {
588
- return editInterval(this, configs.interval, args);
589
- }
590
- if (configs.strokeInterval?.target === target) {
591
- return editInterval(this, configs.strokeInterval, args, "stroke");
592
- }
593
- if (configs.strokeWidth?.target === target) {
594
- return editSampledLegend(this, "strokeWidth", configs.strokeWidth, args);
595
- }
596
- if (configs.size?.target === target) {
597
- return editSampledLegend(this, "size", configs.size, args);
598
- }
599
- const continuousKind = ["gradient", "strokeGradient", "opacity"].find(
600
- kind => configs[kind]?.target === target
601
- );
602
- return editContinuous(this, continuousKind, configs[continuousKind], args);
603
- })
604
- );
1
+ import{mergeLegendTitleStyle as G}from"../../../grammar/legendTitleStyle.js";import{recordLegendOffsetIntent as X}from"./group.js";import{prepareLegendGroup as v}from"./group.js";import{PLOT_LEGEND_POSITIONS as $}from"../../../layout/legend.js";import{withGuideLayoutValidation as ee}from"../../../materialization/guides/layout.js";import{resolveLegendStepConfig as te}from"./creation.js";import{normalizeLegendOrder as j}from"../../../grammar/categoryOrder.js";import{resolveDefinition as ne}from"./categorical/resolve.js";import{action as oe}from"../../../core/action.js";import{validateOptionObject as x}from"../../../core/validation.js";import{normalizeLegendOverflow as V,normalizeOptions as ie}from"./categorical/options.js";import{resolveLegendSymbol as le}from"./categorical/recipes.js";import{resolveLayout as re}from"./categorical/layout.js";import{resolveLegendCreationPlan as de}from"./categorical/actions.js";import{categoricalSymbolIds as ce,reconcileCategoricalSymbols as se,createCategoricalLegendFromConfig as fe,resolveCategoricalLegendRevision as ae,removeLegendKinds as W}from"./lifecycle.js";import{normalizeColorSymbols as ue}from"./continuous/colorSymbols.js";import{readLegendSampling as me}from"./sampling.js";import{createGradientLegendFromConfig as M}from"./continuous/gradient.js";import{normalizeLegendGradient as B}from"../../../grammar/legendGradient.js";import{normalizeItemLegendLayout as ge,normalizeLegendBorder as ye,normalizeLegendTextOptions as q,normalizeLegendTitleOptions as D,normalizeContinuousLegend as A}from"./continuous/common.js";import{normalizeOpacitySymbol as be,createOpacityLegendFromConfig as pe}from"./continuous/opacity.js";import{normalizeIntervalLegend as Le,resolveIntervalConfig as Se,resolveIntervalLayout as he,createIntervalLegendFromConfig as we}from"./continuous/interval.js";import{createStrokeGradientLegendFromConfig as ke,createStrokeIntervalLegendFromConfig as ze,resolveStrokeIntervalConfig as Ee,resolveStrokeIntervalLayout as Ce}from"./continuous/stroke.js";import{findLayer as z}from"../../../selectors/layers.js";import{resolveLegendGraphicPlacement as Ge}from"../../../materialization/graphicHierarchy.js";import{resolveLegendTarget as Oe,validateLegendChannels as _e}from"./target.js";import{isEnumeratedSizeScaleType as F}from"../../../grammar/scales/index.js";import{SIZE_LEGEND_LABELS as K,SIZE_LEGEND_TITLE_STYLE as N,resolveSizeLegendLayout as Te,createSizeLegendFromConfig as Ie}from"./size.js";import{STROKE_WIDTH_LEGEND_LABELS as Pe,STROKE_WIDTH_LEGEND_TITLE_STYLE as $e,resolveStrokeWidthLegendLayout as je,createStrokeWidthLegendFromConfig as xe}from"./strokeWidth.js";import{normalizeLegendSampling as O}from"./sampling.js";import{planLegendBlockTransitions as Ve}from"./transition.js";const We=Object.freeze(["target","channels","position","layout","align","direction","columns","offset","titlePosition","title","symbol","labels","titleStyle","itemGap","border","count","values","gradient","representation","order","overflow","blockLayout","at"]);function E(o,t){return t===void 0?o:{...o,...t}}function C(o,t){return t===void 0?o:t===!1||t===!0||o===!1?t:{...o,...t}}function k(o,t,e,n){const l=o.graphicSpec.objects[t]!==void 0;return l&&!e?o.editGraphics({target:t,remove:!0}):!l&&e?o.createGraphics({id:t,...n,...Ge(o,{...n.before===void 0?{}:{before:n.before},...n.after===void 0?{}:{after:n.after}})}):o}function Me(o,t,e){const n=e.representation??t.representation??"gradient";if(!["gradient","symbols"].includes(n))throw new Error("Legend representation must be gradient or symbols.");const l=["target","representation","position","align","offset","title","labels","titleStyle","titlePosition","border","count",...n==="symbols"?["symbol","values","itemGap","layout","direction","columns"]:["gradient"]];for(const y of Object.keys(e))if(!l.includes(y))throw new Error(`Color ${n} legend does not accept ${y}.`);const d=(t.representation??"gradient")!==n,i=e.title,m=i==="auto"?z(o,t.target)?.encoding?.color?.field:typeof i=="string"?i:t.title,s={target:t.target,position:e.position??t.position,align:e.align??t.align,offset:e.offset??t.offset,title:m,labels:E(t.labels,e.labels),titleStyle:G(t.titleStyle,e.titleStyle,{edit:!0}),border:C(t.border,e.border),titlePosition:e.titlePosition??(d&&n==="gradient"?"top":t.titlePosition)};let u;if(n==="symbols"){e.symbol!==void 0&&e.symbol!=="auto"&&x(e.symbol,["type","radius","stroke","strokeWidth"],"Sampled color symbol");const y=d?{count:t.count}:{};for(const w of["values","count"])Object.hasOwn(e,w)&&(y[w]=e[w]);Object.hasOwn(e,"values")&&delete y.count,Object.hasOwn(e,"count")&&(y.count=e.count),u=ue({...s,...y,layout:e.layout??(d?void 0:t.layout),direction:e.direction??(d||e.position!==void 0&&e.position!==t.position?void 0:t.direction),columns:e.columns??(d?void 0:t.columns),itemGap:e.itemGap??(d?void 0:t.itemGap),symbol:e.symbol==="auto"?"auto":e.symbol===void 0?d?void 0:t.symbol:{...d?{}:t.symbol,...e.symbol}},d?{count:t.count}:t,"edit")}else{u=A({...s,count:e.count??(d?me(t).count:t.count)},"gradient");const y=B(e.gradient,d?void 0:t.gradient);u.gradient=y}const{representation:f,sampling:a,symbol:c,layout:b,direction:S,columns:h,gradient:r,count:p,...g}=t,L=d&&n==="gradient"&&t.blockOverrides!==void 0?Object.fromEntries(Object.entries(t.blockOverrides).map(([y,w])=>{const{symbol:qe,labelMap:De,...U}=w;return[y,U]})):t.blockOverrides;return{config:{...g,...u,...L===void 0?{}:{blockOverrides:L},inferredTitle:i==="auto"?!0:typeof i=="string"?!1:t.inferredTitle,titleVisible:i===!1?!1:i===void 0?t.titleVisible!==!1:!0},titleMode:i,title:m,titleVisible:i===!1?!1:i===void 0?t.titleVisible!==!1:!0}}function _(o,t,e,n){if(t==="gradient"&&(e.representation==="symbols"||n.representation!==void 0))return Me(o,e,n);const l=["gradient","strokeGradient"].includes(t),d=l?["target","position","align","offset","title","labels","titleStyle","titlePosition","border","count","gradient","representation"]:["target","position","align","offset","title","symbol","labels","titleStyle","titlePosition","itemGap","border","count","values"];for(const w of Object.keys(n))if(!d.includes(w))throw new Error(`${t} legend does not accept ${w}.`);const i=n.title,m=i==="auto"?!0:typeof i=="string"?!1:e.inferredTitle,s=i===!1?!1:i===void 0?e.titleVisible!==!1:!0,u=z(o,e.target),f=t==="gradient"?"color":t==="strokeGradient"?"stroke":"opacity",a=u?.encoding?.[f]?.field,c=i==="auto"?a:typeof i=="string"?i:e.title,b=n.titlePosition??e.titlePosition,S=t==="opacity"&&b==="left"&&e.titlePosition!=="left",h=E(e.labels,n.labels),r=l?void 0:O(n,{previous:e,operation:"edit",label:"Opacity legend"}),p=A({target:e.target,position:n.position??e.position,align:n.align??e.align,offset:n.offset??e.offset,...l?{count:n.count??e.count}:{},titlePosition:b,title:c,labels:S&&n.labels?.offset===void 0?{...h,offset:8}:h,titleStyle:G(e.titleStyle,n.titleStyle,{edit:!0}),border:C(e.border,n.border),...t==="opacity"?{itemGap:n.itemGap??(S?20:e.itemGap),symbol:n.symbol??e.symbol}:{}},l?"gradient":t),{count:g,...L}=e,y={...l?e:L,...p,...l?{}:{sampling:r},inferredTitle:m,titleVisible:s};if(l){const w=B(n.gradient,e.gradient);y.gradient=w}else y.symbol=be(n.symbol??e.symbol);return{config:y,titleMode:i,title:c,titleVisible:s}}function T(o,t,e,n){const{config:l,titleMode:d,title:i,titleVisible:m}=_(o,t,e,n);if(t==="gradient"&&(e.representation??"gradient")!==(l.representation??"gradient"))return M(W(o,[t]),l).rematerializeLegend();const s=t==="gradient"?"colorGradient":t==="strokeGradient"?"strokeGradient":"opacityLegend",u=t==="gradient"?"color":t==="strokeGradient"?"stroke":"opacity";let f=o;return(d==="auto"||typeof d=="string")&&(f=f.editSemantic({property:`guide.legend.${u}.title`,value:i})),f=f._withLegendConfig(t,l),f=k(f,`${s}Background`,l.border!==!1,{type:"rect",before:["gradient","strokeGradient"].includes(t)?`${s}${l.representation==="symbols"?"Symbols":"Strips"}`:"opacityLegendSymbols"}),f=k(f,`${s}Title`,m,{type:"text"}),f.rematerializeLegend()}function R(o,t,e,n="color"){for(const c of Object.keys(e))if(!["target","position","align","direction","offset","title","symbol","labels","titleStyle","itemGap","border","layout","columns","titlePosition"].includes(c))throw new Error(`interval legend does not accept ${c}.`);const l=e.title,d=z(o,t.target),i=l==="auto"?!0:typeof l=="string"?!1:t.inferredTitle,m=l===!1?!1:l===void 0?t.titleVisible!==!1:!0,s=l==="auto"?d?.encoding?.[n]?.field:typeof l=="string"?l:t.title,u=Le({target:t.target,position:e.position??t.position,align:e.align??t.align,direction:e.direction??(e.position!==void 0&&e.position!==t.position?void 0:t.direction),layout:e.layout===void 0?t.layout:e.layout,columns:e.columns??t.columns,titlePosition:e.titlePosition??t.titlePosition,offset:e.offset??t.offset,title:s,symbol:E(t.symbol,e.symbol),labels:E(t.labels,e.labels),titleStyle:G(t.titleStyle,e.titleStyle,{edit:!0}),itemGap:e.itemGap??t.itemGap,border:C(t.border,e.border)}),f={...t,...u,inferredTitle:i,titleVisible:m,...n==="stroke"&&e.symbol?.strokeWidth!==void 0?{inferredStrokeWidth:!1}:{}},a=n==="stroke"?Ee(o,f):Se(o,f);return(n==="stroke"?Ce:he)(o,a.config,a.scale),{normalized:u,config:f,titleMode:l,title:s,titleVisible:m}}function I(o,t,e,n="color"){const l=n==="stroke"?"strokeInterval":"interval",d=n==="stroke"?"strokeInterval":"colorLegend",{normalized:i,config:m,titleMode:s,title:u,titleVisible:f}=R(o,t,e,n);let a=o._withLegendConfig(l,m);return(s==="auto"||typeof s=="string")&&(a=a.editSemantic({property:`guide.legend.${n}.title`,value:u})),a=k(a,`${d}Title`,f,{type:"text"}),a=k(a,`${d}Background`,i.border!==!1,{type:"rect",before:`${d}Symbols`}),a.rematerializeLegend()}function H(o,t,e,n,l=!1){const d=t==="size",i=d?"size":"stroke-width",m=["target","title","count","values","labels","titleStyle","position","layout","align","direction","columns","titlePosition","offset","itemGap","border"];d&&m.push("overflow");for(const y of Object.keys(n))if(!m.includes(y))throw new Error(`${i} legend does not accept ${y}.`);const s=d&&F(o.resolvedScales[e.scale]?.type),u=d&&o.resolvedScales[e.scale]?.type==="ordinal";if(n.overflow!==void 0&&!u)throw new Error("Size legend overflow requires an ordinal scale.");if(s&&(n.count!==void 0||n.values!==void 0))throw new Error("Discrete size legends do not support count or exact values.");const f=s?void 0:O(n,{previous:e,operation:"edit",label:`${d?"Size":"Stroke-width"} legend`}),a=z(o,e.target),c=n.title,b=c==="auto"?!0:typeof c=="string"?!1:e.inferredTitle,S=c===!1?!1:c===void 0?e.titleVisible!==!1:!0,h=c==="auto"?a?.encoding?.[t]?.field:typeof c=="string"?c:e.title,{count:r,...p}=e,L={...s?e:p,title:h,inferredTitle:b,titleVisible:S,...s?{}:{sampling:f},...u&&(n.overflow!==void 0||e.overflow!==void 0)?{overflow:V(n.overflow===void 0?e.overflow:n.overflow)}:{},...d?{inheritAppearance:l&&e.inheritAppearance===!0,...n.position===void 0?{}:{inheritPosition:!1}}:{},labels:q(n.labels,"editLegend.labels",e.labels??(d?K:Pe)),titleStyle:D(n.titleStyle,"editLegend.titleStyle",e.titleStyle??(d?N:$e),!0)};return Object.assign(L,ge({...e,...n,titleStyle:L.titleStyle,direction:n.direction??(n.position!==void 0&&n.position!==e.position?void 0:e.direction)})),L.border=ye(C(e.border,n.border)),(d?Te:je)(o,L),{config:L,titleMode:c,title:h,titleVisible:S}}function P(o,t,e,n,l=!1){const{config:d,titleMode:i,title:m,titleVisible:s}=H(o,t,e,n,l),u=t==="size"?"sizeLegend":"strokeWidthLegend";let f=o;return(i==="auto"||typeof i=="string")&&(f=f.editSemantic({property:`guide.legend.${t}.title`,value:m})),f=f._withLegendConfig(t,d),f=k(f,`${u}Background`,d.border!==!1,{type:"rect",before:`${u}Symbols`}),f=k(f,`${u}Title`,s,{type:"text",after:`${u}Labels`}),f.rematerializeLegend()}function Be(o,t,e,n){if(e===void 0)return;const l=F(o.resolvedScales[e.scale]?.type);if(l&&(n.count!==void 0||n.values!==void 0))throw new Error("Discrete size legends do not support count or exact values.");const d=l?void 0:O(n,{previous:e,operation:"edit",label:"Size legend"}),{count:i,...m}=e,u={...l?e:m,...l?{}:{sampling:d},...n.overflow!==void 0&&o.resolvedScales[e.scale]?.type==="ordinal"?{overflow:V(n.overflow)}:{}};if(n.labels===void 0&&n.titleStyle===void 0)return u;const f=e.inheritAppearance?{...t.labels,offset:e.labels.offset}:e.labels??K,a=e.inheritAppearance?t.titleStyle:e.titleStyle??N;return{...u,inheritAppearance:!1,labels:q(n.labels,"editLegend.labels",f),titleStyle:D(n.titleStyle,"editLegend.titleStyle",a,!0)}}function Y(o,t,e,n,l,d=o.semanticSpec.guides.legend?.[t]?.order){if(l.values!==void 0)throw new Error("Combined and categorical legends require a channel block selector for exact values.");if(l.gradient!==void 0)throw new Error("Categorical legends do not accept gradient.");if(l.count!==void 0&&n===void 0)throw new Error("Legend count requires an existing size legend.");const i=l.title,m=i==="auto"?!0:typeof i=="string"?!1:e.inferredTitle,s=i===!1?!1:i===void 0?e.titleVisible!==!1:!0,u=i==="auto"?e.field:typeof i=="string"?i:e.title,f=E(e.labels,l.labels);if(f.format!==void 0&&f.format!=="auto"&&n===void 0)throw new Error("Categorical legend labels do not accept format.");const{format:a,...c}=f,b=l.symbol===void 0?e.inferredSymbol:l.symbol==="auto",S=ie({target:e.target,channels:e.channels,...l.overflow===void 0&&e.overflow===void 0?{}:{overflow:l.overflow===void 0?e.overflow:l.overflow},layout:l.layout===void 0?e.layout:l.layout,position:l.position??e.position,align:l.align??e.align,direction:l.direction??(l.position!==void 0&&l.position!==e.position?void 0:e.direction),...l.columns===void 0&&e.columns===void 0?{}:{columns:l.columns??e.columns},offset:l.offset??e.offset,titlePosition:l.titlePosition??e.titlePosition,title:u,symbol:l.symbol===void 0?e.symbol:le(o,z(o,e.target),e.channels,l.symbol,t),labels:c,titleStyle:G(e.titleStyle,l.titleStyle,{edit:!0}),itemGap:l.itemGap??e.itemGap,border:C(e.border,l.border)},t,{internalSymbol:b}),h={...e,...S,inferredTitle:m,inferredSymbol:b,titleVisible:s},r=l.order===void 0?d:j(l.order);return ne(o,z(o,e.target),e.channels,u,r),{config:h,order:r,titleMode:i,title:u,titleVisible:s,sizeConfig:Be(o,e,n,l)}}function Z(o,t,e,n,l){const{config:d,order:i,titleMode:m,title:s,titleVisible:u,sizeConfig:f}=Y(o,t,e,n,l),a=ce(d);let c=o;(m==="auto"||typeof m=="string")&&(c=c.editSemantic({property:`guide.legend.${t}.title`,value:s})),l.order!==void 0&&(i==="scale"?o.semanticSpec.guides.legend?.[t]?.order!==void 0&&(c=c.editSemantic({property:`guide.legend.${t}.order`,remove:!0})):c=c.editSemantic({property:`guide.legend.${t}.order`,value:i})),c=c._withLegendConfig(t,d),c=se(c,e,d),c=k(c,`${t}LegendBackground`,d.border!==!1,{type:"rect",before:[...a][0]});const b=`${t}LegendTitle`;return c=k(c,b,u,{type:"text",...c.graphicSpec.objects.sizeLegendSymbols===void 0?{}:{before:c.graphicSpec.objects.sizeLegendBackground===void 0?"sizeLegendSymbols":"sizeLegendBackground"}}),n!==void 0&&(c=c._withLegendConfig("size",f)),c.rematerializeLegend()}function J(o,t,e){_e(e.channels,"editLegend");const{channels:n,target:l,...d}=e,i=o.guideConfigs.legend??{},m=Object.keys(i).filter(r=>i[r].target===t);let s=m.reduce((r,p)=>r._withoutMaterializationConfig(["guides","legend",p]),o);const u=d.representation??(n.includes("color")&&i.gradient?.target===t?i.gradient.representation:void 0);if(d.representation!==void 0&&!n.includes("color"))throw new Error("Legend representation requires selected continuous color.");const f=de(s,{target:t,channels:n,...u===void 0?{}:{representation:u}}),a=m.find(r=>["series","color","stroke"].includes(r));let c=f.steps.map(r=>{const p=te(s,r),{kind:g}=p;if(i[g]!==void 0&&i[g].target!==t)throw new Error(`Legend ${g} content already belongs to another target.`);if(["series","color","stroke"].includes(g)&&a!==void 0){const L=d.order===void 0?o.semanticSpec.guides.legend?.[a]?.order:j(d.order);return{...ae(s,a,i[a],p.config.channels,{order:L,validateLayout:!1}),kind:g}}return{...p,config:i[g]?.target===t?i[g]:p.config}});const b=c.find(r=>["series","color","stroke"].includes(r.kind)),S=c.find(r=>r.kind==="size");if(d.representation!==void 0||c.some(r=>r.kind==="gradient"&&r.config.representation==="symbols")){const r=c.find(p=>p.kind==="gradient");if(r===void 0)throw new Error("Legend representation requires continuous color.");r.config=_(s,"gradient",r.config,d).config}else if(b!==void 0){S!==void 0&&i.size?.target!==t&&(S.config={...S.config,inheritAppearance:b.config.position==="left"||d.position==="left"||d.labels!==void 0||d.titleStyle!==void 0});const r=Y(s,b.kind,b.config,S?.config,d,b.order);if(b.config=r.config,b.order=r.order,S!==void 0&&(S.config=r.sizeConfig,b.config.layout==="legacy-bottom"))throw new Error('Combined size legends require layout "edge".')}else{const r=c[0];r.kind==="size"||r.kind==="strokeWidth"?r.config=H(s,r.kind,r.config,d).config:["interval","strokeInterval"].includes(r.kind)?r.config=R(s,r.config,d,r.kind==="strokeInterval"?"stroke":"color").config:r.config=_(s,r.kind,r.config,d).config}c=Ve(o,t,c);for(const{kind:r,config:p}of c)s=s._withLegendConfig(r,p);b!==void 0&&re(s,b.config);let h=W(o,m);for(const{kind:r,config:p,order:g}of c)["series","color","stroke"].includes(r)?h=fe(h,p,g):h={size:Ie,strokeWidth:xe,gradient:M,opacity:pe,interval:we,strokeGradient:ke,strokeInterval:ze}[r](h,p);return h.rematerializeLegend()}function Q(o={},t=!1){if(x(o,We,"editLegend"),Object.keys(o).filter(a=>a!=="target").length===0)throw new Error("editLegend requires at least one change.");if(o.title!==void 0&&o.title!==!1&&o.title!=="auto"&&(typeof o.title!="string"||o.title.length===0))throw new TypeError('editLegend title must be a non-empty string, "auto", or false.');const n=Oe(this,o.target,"editLegend"),l=this.guideConfigs.legendGroups?.[n];if(!t&&(l!==void 0||o.blockLayout!==void 0||o.at!==void 0||$.includes(o.position)&&Object.values(this.guideConfigs.legend??{}).some(a=>a.target===n&&a.kind===void 0))){const a=v(this,o,n,{corner:$.includes(o.position)}),{channels:c,...b}=a.options;if(c!==void 0)return J(a.program,n,a.options);const S=["position","layout","align","direction","columns","offset","titlePosition","itemGap"];if(Object.keys(b).every(r=>r==="target"||S.includes(r))){let r=a.program;const p=Object.entries(r.guideConfigs.legend??{}).filter(([,g])=>g.target===n);for(const[g]of p){const L=r.guideConfigs.legend[g],y={...b};if(["gradient","strokeGradient","opacity"].includes(g)&&L.representation!=="symbols")for(const w of["layout","columns","direction"]){if(Object.hasOwn(o,w))throw new Error(`${g} legend does not accept ${w}.`);delete y[w]}["gradient","strokeGradient"].includes(g)&&L.representation!=="symbols"&&delete y.itemGap,["series","color","stroke"].includes(g)?r=Z(r,g,L,void 0,y):["size","strokeWidth"].includes(g)?r=P(r,g,L,y,!0):["interval","strokeInterval"].includes(g)?r=I(r,L,y,g==="strokeInterval"?"stroke":"color"):r=T(r,g,L,y)}return r.rematerializeLegend()}return Q.call(a.program,a.options,!0)}if(o.channels!==void 0)return J(this,n,o);const i=this.guideConfigs.legend??{},m=Object.keys(i).filter(a=>i[a]?.target===n);if(o.representation!==void 0){if(i.gradient?.target!==n)throw new Error("Legend representation requires continuous color.");return T(this,"gradient",i.gradient,o)}if(i.gradient?.target===n&&i.gradient.representation==="symbols"&&m.length>1)throw new Error("Multiple legend blocks require editLegendBlock with an explicit channel for appearance or samples.");if(o.values!==void 0&&m.length>1)throw new Error("A target with multiple legend blocks requires a channel block selector for exact values.");const s=["series","color","stroke"].filter(a=>i[a]?.target===n);if(s.length>1)throw new Error("editLegend requires channels when a target owns multiple categorical legend blocks.");const u=s[0];if(u!==void 0)return Z(this,u,i[u],i.size?.target===n?i.size:void 0,o);if(i.interval?.target===n)return I(this,i.interval,o);if(i.strokeInterval?.target===n)return I(this,i.strokeInterval,o,"stroke");if(i.strokeWidth?.target===n)return P(this,"strokeWidth",i.strokeWidth,o);if(i.size?.target===n)return P(this,"size",i.size,o);const f=["gradient","strokeGradient","opacity"].find(a=>i[a]?.target===n);return T(this,f,i[f],o)}const St=oe({op:"editLegend",description:"Edit one stable legend content, layout or appearance."},ee(function(o={}){return X(this,Q.call(this,o),o)}));export{St as editLegend};
@@ -20,7 +20,7 @@ function makeFocusedLegendEdit({ name, options, map }) {
20
20
  const editLegendLayout = /* @__PURE__ */ makeFocusedLegendEdit({
21
21
  name: "editLegendLayout",
22
22
  options: [
23
- "position", "layout", "align", "direction", "columns", "offset",
23
+ "position", "layout", "align", "direction", "columns", "offset", "blockLayout", "at",
24
24
  "titlePosition", "itemGap"
25
25
  ],
26
26
  map: args => args
@@ -34,7 +34,7 @@ const editLegendLabels = /* @__PURE__ */ makeFocusedLegendEdit({
34
34
 
35
35
  const editLegendTitle = /* @__PURE__ */ makeFocusedLegendEdit({
36
36
  name: "editLegendTitle",
37
- options: ["title", "color", "fontSize", "fontFamily", "fontWeight"],
37
+ options: ["title", "color", "fontSize", "fontFamily", "fontWeight", "align"],
38
38
  map: ({ target, title, ...titleStyle }) => ({
39
39
  target,
40
40
  ...(title === undefined ? {} : { title }),