ggaction 0.0.9 → 0.0.11

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 +84 -0
  2. package/README.md +5 -4
  3. package/knowledge/action-card.schema.json +1 -0
  4. package/knowledge/action-cards.json +1 -10941
  5. package/knowledge/action-cards.schema.json +1 -0
  6. package/knowledge/extension-authoring.md +248 -0
  7. package/knowledge/intent-taxonomy.json +1 -183
  8. package/knowledge/intent-taxonomy.schema.json +1 -0
  9. package/knowledge/mcp-resources.json +1 -95
  10. package/knowledge/mcp-resources.schema.json +1 -0
  11. package/knowledge/task-packet.schema.json +1 -159
  12. package/knowledge/task-resolver.js +1 -1230
  13. package/package.json +19 -2
  14. package/src/actions/boxPlots/edit.js +45 -298
  15. package/src/actions/boxPlots/options.js +46 -77
  16. package/src/actions/boxPlots/resolve.js +1 -1
  17. package/src/actions/charts/bar.js +3 -0
  18. package/src/actions/charts/heatmap.js +73 -83
  19. package/src/actions/charts/index.js +1 -1
  20. package/src/actions/charts/parallel.js +35 -1
  21. package/src/actions/charts/shared.js +20 -0
  22. package/src/actions/composition/actions.js +46 -49
  23. package/src/actions/data/bin2d.js +105 -2
  24. package/src/actions/data/box.js +17 -17
  25. package/src/actions/data/density.js +42 -78
  26. package/src/actions/data/gradientProfile.js +22 -52
  27. package/src/actions/data/horizon.js +16 -41
  28. package/src/actions/data/index.js +10 -10
  29. package/src/actions/data/interval.js +16 -38
  30. package/src/actions/data/intervalEdit.js +55 -0
  31. package/src/actions/data/regression.js +7 -17
  32. package/src/actions/data/shared.js +47 -0
  33. package/src/actions/data/timeUnit.js +14 -38
  34. package/src/actions/data/window.js +14 -38
  35. package/src/actions/distributions/revision.js +154 -34
  36. package/src/actions/encodings/angle.js +9 -10
  37. package/src/actions/encodings/appearance.js +17 -51
  38. package/src/actions/encodings/density/resolve.js +3 -1
  39. package/src/actions/encodings/density.js +1 -15
  40. package/src/actions/encodings/horizon/resolve.js +13 -3
  41. package/src/actions/encodings/horizon.js +8 -7
  42. package/src/actions/encodings/index.js +9 -9
  43. package/src/actions/encodings/offset.js +8 -24
  44. package/src/actions/encodings/parallel.js +9 -10
  45. package/src/actions/encodings/pathOrder.js +6 -13
  46. package/src/actions/encodings/position/policies/index.js +4 -4
  47. package/src/actions/encodings/position/policies/line.js +47 -17
  48. package/src/actions/encodings/position/resolve.js +24 -5
  49. package/src/actions/encodings/ranged.js +18 -23
  50. package/src/actions/encodings/remove.js +15 -25
  51. package/src/actions/encodings/ruleAppearance.js +9 -24
  52. package/src/actions/encodings/shared.js +37 -0
  53. package/src/actions/encodings/strokeDash.js +8 -23
  54. package/src/actions/encodings/text.js +9 -10
  55. package/src/actions/errorBands/create.js +105 -39
  56. package/src/actions/errorBands/edit.js +89 -93
  57. package/src/actions/errorBands/index.js +1 -2
  58. package/src/actions/errorBars/create.js +78 -27
  59. package/src/actions/errorBars/edit.js +81 -48
  60. package/src/actions/errorBars/index.js +1 -2
  61. package/src/actions/facets/actions.js +45 -69
  62. package/src/actions/facets/derive.js +38 -53
  63. package/src/actions/facets/replay.js +0 -1
  64. package/src/actions/gradientPlots/components.js +59 -48
  65. package/src/actions/gradientPlots/edit.js +35 -216
  66. package/src/actions/gradientPlots/index.js +11 -10
  67. package/src/actions/gradientPlots/materialize.js +21 -35
  68. package/src/actions/gradientPlots/options.js +5 -7
  69. package/src/actions/gradientPlots/paint.js +9 -6
  70. package/src/actions/gradientPlots/resolve.js +1 -1
  71. package/src/actions/guides/axes/index.js +3 -3
  72. package/src/actions/guides/axes/labels.js +68 -26
  73. package/src/actions/guides/axes/lines.js +22 -2
  74. package/src/actions/guides/axes/policy.js +28 -8
  75. package/src/actions/guides/axes/ticks.js +27 -9
  76. package/src/actions/guides/axes/titles.js +113 -65
  77. package/src/actions/guides/grids/resolve.js +14 -0
  78. package/src/actions/guides/guides.js +2 -4
  79. package/src/actions/guides/index.js +2 -2
  80. package/src/actions/guides/legends/categorical/actions.js +44 -68
  81. package/src/actions/guides/legends/categorical/components.js +6 -1
  82. package/src/actions/guides/legends/categorical/index.js +2 -2
  83. package/src/actions/guides/legends/categorical/layout.js +148 -175
  84. package/src/actions/guides/legends/categorical/recipes.js +5 -0
  85. package/src/actions/guides/legends/categorical/resolve.js +8 -1
  86. package/src/actions/guides/legends/continuous/common.js +117 -66
  87. package/src/actions/guides/legends/continuous/gradient.js +38 -37
  88. package/src/actions/guides/legends/continuous/interval.js +97 -76
  89. package/src/actions/guides/legends/continuous/opacity.js +28 -38
  90. package/src/actions/guides/legends/edit.js +14 -1
  91. package/src/actions/guides/legends/index.js +1 -1
  92. package/src/actions/guides/legends/lane.js +2 -1
  93. package/src/actions/guides/legends/remove.js +8 -0
  94. package/src/actions/guides/legends/size.js +36 -43
  95. package/src/actions/guides/legends/strokeWidth.js +20 -26
  96. package/src/actions/guides/polar/axes/labels.js +17 -1
  97. package/src/actions/guides/polar/axes/shared.js +7 -1
  98. package/src/actions/guides/polar/axes/titles.js +19 -2
  99. package/src/actions/guides/polar/grids.js +4 -0
  100. package/src/actions/guides/polar/resolve.js +6 -0
  101. package/src/actions/index.js +13 -13
  102. package/src/actions/intervals/resolve.js +87 -117
  103. package/src/actions/marks/arc/actions.js +31 -36
  104. package/src/actions/marks/area/actions.js +23 -31
  105. package/src/actions/marks/index.js +6 -6
  106. package/src/actions/marks/line/actions.js +33 -52
  107. package/src/actions/marks/line/materialize.js +7 -5
  108. package/src/actions/marks/point/materialize.js +5 -3
  109. package/src/actions/marks/remove.js +21 -0
  110. package/src/actions/marks/rule/actions.js +53 -53
  111. package/src/actions/marks/shared.js +8 -4
  112. package/src/actions/marks/text/actions.js +14 -6
  113. package/src/actions/marks/text/layout.js +21 -45
  114. package/src/actions/marks/tick/actions.js +22 -21
  115. package/src/actions/primitives/createGraphics.js +4 -0
  116. package/src/actions/primitives/editGraphics.js +4 -0
  117. package/src/actions/primitives/index.js +3 -3
  118. package/src/actions/primitives/semantic.js +1 -1
  119. package/src/actions/primitives/semanticValidation/index.js +2 -2
  120. package/src/actions/primitives/semanticValidation/layer.js +1 -1
  121. package/src/actions/regression/edit.js +3 -43
  122. package/src/actions/scales/consumers/families.js +24 -4
  123. package/src/actions/scales/consumers/index.js +1 -1
  124. package/src/actions/scales/consumers/seriesLayout.js +17 -0
  125. package/src/actions/scales/definitions.js +100 -103
  126. package/src/actions/scales/materialize.js +5 -0
  127. package/src/actions/selection/actions.js +79 -121
  128. package/src/actions/titles/actions.js +48 -94
  129. package/src/actions/titles/resolve.js +14 -27
  130. package/src/actions/violinPlots/create.js +37 -44
  131. package/src/core/action.js +20 -5
  132. package/src/core/extensionRegistry.js +104 -0
  133. package/src/core/textMetrics.js +74 -37
  134. package/src/core/validation.js +18 -0
  135. package/src/extension.js +8 -1
  136. package/src/grammar/aggregate.js +48 -28
  137. package/src/grammar/arcs.js +58 -11
  138. package/src/grammar/areaSeries.js +14 -16
  139. package/src/grammar/bin2d.js +37 -23
  140. package/src/grammar/boxPlot.js +14 -13
  141. package/src/grammar/categoricalDensity.js +14 -14
  142. package/src/grammar/categoryOrder.js +8 -14
  143. package/src/grammar/curveCommands.js +87 -20
  144. package/src/grammar/density.js +162 -141
  145. package/src/grammar/facets/index.js +14 -0
  146. package/src/grammar/facets/scales.js +7 -4
  147. package/src/grammar/gradientProfile.js +12 -15
  148. package/src/grammar/histogram.js +145 -83
  149. package/src/grammar/horizon.js +60 -23
  150. package/src/grammar/interval.js +14 -13
  151. package/src/grammar/lineSeries.js +91 -61
  152. package/src/grammar/numeric.js +290 -0
  153. package/src/grammar/paint.js +3 -4
  154. package/src/grammar/palettes.js +21 -32
  155. package/src/grammar/pathCommands.js +6 -0
  156. package/src/grammar/polarPaths.js +5 -5
  157. package/src/grammar/regression/derive.js +48 -9
  158. package/src/grammar/regression/models.js +248 -69
  159. package/src/grammar/regression/parameters.js +8 -0
  160. package/src/grammar/scales/color.js +35 -26
  161. package/src/grammar/scales/continuous.js +71 -24
  162. package/src/grammar/scales/definition.js +1 -1
  163. package/src/grammar/scales/discretized.js +42 -12
  164. package/src/grammar/scales/fields.js +21 -4
  165. package/src/grammar/scales/ordinal.js +117 -61
  166. package/src/grammar/scales/temporal.js +40 -11
  167. package/src/grammar/scales/transformed.js +199 -52
  168. package/src/grammar/schemas/graphicBounds.js +270 -79
  169. package/src/grammar/seriesLayout.js +50 -18
  170. package/src/grammar/ticks.js +123 -53
  171. package/src/grammar/timeUnit.js +18 -8
  172. package/src/grammar/transforms.js +4 -1
  173. package/src/grammar/window.js +70 -57
  174. package/src/index.js +1 -1
  175. package/src/layout/canvas.js +4 -8
  176. package/src/layout/composition.js +39 -40
  177. package/src/layout/facets.js +10 -14
  178. package/src/layout/labels.js +91 -15
  179. package/src/layout/legend.js +22 -20
  180. package/src/layout/legendLane.js +54 -40
  181. package/src/layout/title.js +7 -7
  182. package/src/materialization/bars/aggregate.js +14 -40
  183. package/src/materialization/bars/grouped.js +7 -18
  184. package/src/materialization/bars/histogram.js +2 -19
  185. package/src/materialization/bars/ranged.js +20 -15
  186. package/src/materialization/bars/resolve.js +21 -0
  187. package/src/materialization/facetGuides/legacyCategorical.js +2 -1
  188. package/src/materialization/facetGuides/placement.js +34 -19
  189. package/src/materialization/facetGuides/preparation.js +9 -5
  190. package/src/materialization/facets.js +39 -27
  191. package/src/materialization/guides/resources.js +2 -1
  192. package/src/materialization/marks/capabilities.js +5 -2
  193. package/src/materialization/marks/index.js +24 -4
  194. package/src/materialization/scales/policies/bar.js +61 -59
  195. package/src/materialization/scales/policies/binnedPosition.js +34 -0
  196. package/src/materialization/scales/policies/series.js +13 -7
  197. package/src/materialization/scales/resolve.js +4 -5
  198. package/src/materialization/selection/items/arc.js +16 -2
  199. package/src/materialization/selection/items/bar.js +4 -2
  200. package/src/materialization/selection/items/common.js +21 -1
  201. package/src/materialization/selection/items/path.js +19 -2
  202. package/src/materialization/selection/items/point.js +4 -16
  203. package/src/materialization/selection/items/tick.js +13 -21
  204. package/src/materialization/selection/point.js +11 -4
  205. package/src/materialization/selection/styles.js +19 -27
  206. package/src/materialization/text.js +4 -1
  207. package/src/mcp/adapter.js +8 -3
  208. package/src/renderers/canvas/fill.js +2 -2
  209. package/src/renderers/canvas/index.js +35 -2
  210. package/src/renderers/canvas/native.js +160 -0
  211. package/src/renderers/pdf.js +14 -0
  212. package/src/renderers/svg.js +37 -4
  213. package/types/extension.d.ts +19 -0
  214. package/types/index.d.ts +3 -0
  215. package/types/program.d.ts +509 -131
  216. package/types/svg.d.ts +1 -0
  217. package/src/actions/errorBands/components.js +0 -81
  218. package/src/actions/errorBands/options.js +0 -39
  219. package/src/actions/errorBands/resolve.js +0 -28
  220. package/src/actions/errorBars/components.js +0 -62
  221. package/src/actions/errorBars/options.js +0 -58
  222. package/src/actions/errorBars/resolve.js +0 -25
@@ -1,12 +1,18 @@
1
1
  import { isPlainObject } from "../../../../core/immutable.js";
2
+ import { resolveTextBounds } from "../../../../core/textMetrics.js";
2
3
  import {
3
4
  validateKeys,
4
5
  validateOptionObject,
5
6
  validateNonEmptyString,
6
7
  validateNonNegativeFinite,
7
- validatePositiveFinite
8
+ validatePositiveFinite,
9
+ validateGeneratedItemLimit
8
10
  } from "../../../../core/validation.js";
9
11
  import { formatTimeTick } from "../../../../grammar/ticks.js";
12
+ import {
13
+ formatDistinctNumericSamples,
14
+ sampleNumericRange
15
+ } from "../../../../grammar/numeric.js";
10
16
  import { resolveGraphicBounds } from "../../../../layout/canvas.js";
11
17
  import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
12
18
  "../../../../theme/defaults.js";
@@ -14,41 +20,48 @@ import { findLayer } from "../../../../selectors/layers.js";
14
20
  import { findCanvasGraphic } from
15
21
  "../../../../materialization/graphicHierarchy.js";
16
22
 
17
- const OPTIONS = Object.freeze([
23
+ const OPTIONS = [
18
24
  "target", "channels", "position", "align", "offset", "title", "count",
19
25
  "gradient", "symbol", "labels", "titleStyle", "itemGap", "border",
20
26
  "direction", "columns", "titlePosition"
21
- ]);
22
- const TEXT_OPTIONS = Object.freeze([
27
+ ];
28
+ const TEXT_OPTIONS = [
23
29
  "offset", "color", "fontSize", "fontFamily", "fontWeight"
24
- ]);
25
- const BORDER_OPTIONS = Object.freeze([
30
+ ];
31
+ const BORDER_OPTIONS = [
26
32
  "color", "lineWidth", "padding", "background"
27
- ]);
28
- const POSITIONS = Object.freeze(["right", "left", "top", "bottom"]);
29
- const DEFAULT_LABELS = Object.freeze({
33
+ ];
34
+ const POSITIONS = ["right", "left", "top", "bottom"];
35
+ const DEFAULT_LABELS = {
30
36
  offset: 12,
31
37
  color: DEFAULT_COLORS.text,
32
38
  fontSize: 12,
33
39
  fontFamily: DEFAULT_FONT_FAMILY,
34
40
  fontWeight: "normal"
35
- });
36
- const DEFAULT_TITLE = Object.freeze({
41
+ };
42
+ const DEFAULT_TITLE = {
37
43
  color: DEFAULT_COLORS.text,
38
44
  fontSize: 13,
39
45
  fontFamily: DEFAULT_FONT_FAMILY,
40
46
  fontWeight: 600
41
- });
42
- const DEFAULT_BORDER = Object.freeze({
47
+ };
48
+ const DEFAULT_BORDER = {
43
49
  color: DEFAULT_COLORS.border,
44
50
  lineWidth: 1,
45
51
  padding: 12,
46
52
  background: "transparent"
47
- });
53
+ };
48
54
 
49
55
  export const validatePositive = validatePositiveFinite;
50
56
  export const validateNonNegative = validateNonNegativeFinite;
51
57
 
58
+ export function editGraphicProperties(program, target, properties) {
59
+ for (const [property, value] of Object.entries(properties)) {
60
+ program = program.editGraphics({ target, property, value });
61
+ }
62
+ return program;
63
+ }
64
+
52
65
  export function normalizeLegendTextOptions(value, label, defaults) {
53
66
  if (value === undefined) return { ...defaults };
54
67
  if (!isPlainObject(value)) {
@@ -72,7 +85,7 @@ export function normalizeLegendTextOptions(value, label, defaults) {
72
85
  return result;
73
86
  }
74
87
 
75
- function normalizeBorder(value) {
88
+ export function normalizeLegendBorder(value) {
76
89
  if (value === undefined || value === false) return false;
77
90
  if (value !== true && !isPlainObject(value)) {
78
91
  throw new TypeError("createLegend.border must be a boolean or plain object.");
@@ -103,6 +116,7 @@ export function normalizeContinuousLegend(args, kind) {
103
116
  "Continuous legend count must be an integer of at least 2."
104
117
  );
105
118
  }
119
+ validateGeneratedItemLimit(count, "Continuous legend count");
106
120
  const offset = args.offset ?? 30;
107
121
  validateNonNegative(offset, "Legend offset");
108
122
  const titlePosition = args.titlePosition ?? "top";
@@ -155,21 +169,26 @@ export function normalizeContinuousLegend(args, kind) {
155
169
  ),
156
170
  itemGap,
157
171
  titlePosition,
158
- border: normalizeBorder(args.border)
172
+ border: normalizeLegendBorder(args.border)
159
173
  };
160
174
  }
161
175
 
176
+ export function selectLegendLayer(program, requested, predicate) {
177
+ const candidates = program.semanticSpec.layers.filter(predicate);
178
+ if (requested === undefined) return candidates.length === 1
179
+ ? candidates[0]
180
+ : undefined;
181
+ const candidate = findLayer(program, requested);
182
+ return candidates.includes(candidate) ? candidate : undefined;
183
+ }
184
+
162
185
  export function resolveContinuousPoint(program, requested, channel) {
163
- const candidates = program.semanticSpec.layers.filter(layer =>
164
- layer.mark?.type === "point" &&
165
- layer.encoding?.[channel]?.scale !== undefined
186
+ const layer = selectLegendLayer(
187
+ program,
188
+ requested,
189
+ candidate => candidate.mark?.type === "point" &&
190
+ candidate.encoding?.[channel]?.scale !== undefined
166
191
  );
167
- const layer = requested === undefined
168
- ? candidates.length === 1 ? candidates[0] : undefined
169
- : (() => {
170
- const candidate = findLayer(program, requested);
171
- return candidates.includes(candidate) ? candidate : undefined;
172
- })();
173
192
  if (layer === undefined) {
174
193
  throw new Error(
175
194
  requested === undefined
@@ -181,16 +200,12 @@ export function resolveContinuousPoint(program, requested, channel) {
181
200
  }
182
201
 
183
202
  export function resolveContinuousColorLayer(program, requested) {
184
- const candidates = program.semanticSpec.layers.filter(layer =>
185
- ["point", "bar", "rect"].includes(layer.mark?.type) &&
186
- layer.encoding?.color?.scale !== undefined
203
+ const layer = selectLegendLayer(
204
+ program,
205
+ requested,
206
+ candidate => ["point", "bar", "rect"].includes(candidate.mark?.type) &&
207
+ candidate.encoding?.color?.scale !== undefined
187
208
  );
188
- const layer = requested === undefined
189
- ? candidates.length === 1 ? candidates[0] : undefined
190
- : (() => {
191
- const candidate = findLayer(program, requested);
192
- return candidates.includes(candidate) ? candidate : undefined;
193
- })();
194
209
  if (layer === undefined) {
195
210
  throw new Error(
196
211
  requested === undefined
@@ -209,7 +224,10 @@ export function requireResolvedLegendScale(program, id, type) {
209
224
  return scale;
210
225
  }
211
226
 
212
- export function resolveContinuousBounds(program) {
227
+ export function resolveContinuousBounds(
228
+ program,
229
+ message = "Continuous legend layout requires Canvas bounds."
230
+ ) {
213
231
  const plot = resolveGraphicBounds(program);
214
232
  const canvas = findCanvasGraphic(program);
215
233
  if (
@@ -219,22 +237,24 @@ export function resolveContinuousBounds(program) {
219
237
  !Number.isFinite(canvas.properties.width) ||
220
238
  !Number.isFinite(canvas.properties.height)
221
239
  ) {
222
- throw new Error("Continuous legend layout requires Canvas bounds.");
240
+ throw new Error(message);
223
241
  }
224
242
  return { plot, canvas: canvas.properties };
225
243
  }
226
244
 
227
245
  export function sampleContinuousValues(domain, count) {
228
- return Array.from({ length: count }, (_, index) =>
229
- domain[0] + index / (count - 1) * (domain[1] - domain[0])
246
+ return sampleNumericRange(
247
+ domain[0],
248
+ domain[1],
249
+ count,
250
+ "Continuous legend domain"
230
251
  );
231
252
  }
232
253
 
233
254
  export function formatContinuousValues(values, domain, fieldType) {
234
- return values.map(value => fieldType === "temporal"
235
- ? formatTimeTick(value, domain)
236
- : String(+value.toPrecision(3))
237
- );
255
+ return fieldType === "temporal"
256
+ ? values.map(value => formatTimeTick(value, domain))
257
+ : formatDistinctNumericSamples(values);
238
258
  }
239
259
 
240
260
  export function styleContinuousText(
@@ -243,30 +263,63 @@ export function styleContinuousText(
243
263
  style,
244
264
  { align = "left" } = {}
245
265
  ) {
246
- return program
247
- .editGraphics({ target: id, property: "fill", value: style.color })
248
- .editGraphics({ target: id, property: "fontSize", value: style.fontSize })
249
- .editGraphics({ target: id, property: "fontFamily", value: style.fontFamily })
250
- .editGraphics({ target: id, property: "fontWeight", value: style.fontWeight })
251
- .editGraphics({ target: id, property: "textAlign", value: align })
252
- .editGraphics({ target: id, property: "textBaseline", value: "middle" });
266
+ return editGraphicProperties(program, id, {
267
+ fill: style.color,
268
+ fontSize: style.fontSize,
269
+ fontFamily: style.fontFamily,
270
+ fontWeight: style.fontWeight,
271
+ textAlign: align,
272
+ textBaseline: "middle"
273
+ });
274
+ }
275
+
276
+ export function resolveLegendTextBounds(position, text, style) {
277
+ return resolveTextBounds({
278
+ x: position.x,
279
+ y: position.y,
280
+ text: String(text),
281
+ ...style,
282
+ textAlign: position.align ?? "left",
283
+ textBaseline: "middle"
284
+ });
253
285
  }
254
286
 
255
- export function assertLegendInsideCanvas(items, canvas, label) {
256
- if (items.some(item =>
257
- item.x < 0 || item.y < 0 || item.x > canvas.width || item.y > canvas.height
287
+ export function assertLegendBoundsInsideCanvas(bounds, canvas, label) {
288
+ if (bounds.some(item =>
289
+ item.left < 0 || item.right > canvas.width ||
290
+ item.top < 0 || item.bottom > canvas.height
258
291
  )) {
259
292
  throw new Error(`${label} requires more Canvas margin space.`);
260
293
  }
261
294
  }
262
295
 
263
- export function resolveLegendBackgroundBounds(points, border, canvas, label) {
296
+ export function resolveLegendBackgroundFromBounds(
297
+ bounds,
298
+ border,
299
+ canvas,
300
+ label
301
+ ) {
264
302
  if (border === false) return undefined;
265
- const x = Math.min(...points.map(point => point.x)) - border.padding;
266
- const y = Math.min(...points.map(point => point.y)) - border.padding;
267
- const right = Math.max(...points.map(point => point.x)) + border.padding;
268
- const bottom = Math.max(...points.map(point => point.y)) + border.padding;
269
- if (x < 0 || y < 0 || right > canvas.width || bottom > canvas.height) {
303
+ const strokeExtent = border.lineWidth / 2;
304
+ let x = Infinity;
305
+ let y = Infinity;
306
+ let right = -Infinity;
307
+ let bottom = -Infinity;
308
+ for (const item of bounds) {
309
+ x = Math.min(x, item.left);
310
+ y = Math.min(y, item.top);
311
+ right = Math.max(right, item.right);
312
+ bottom = Math.max(bottom, item.bottom);
313
+ }
314
+ x -= border.padding;
315
+ y -= border.padding;
316
+ right += border.padding;
317
+ bottom += border.padding;
318
+ if (
319
+ x - strokeExtent < 0 || y - strokeExtent < 0 ||
320
+ right + strokeExtent > canvas.width ||
321
+ bottom + strokeExtent > canvas.height
322
+ ) {
270
323
  throw new Error(`${label} background requires more Canvas margin space.`);
271
324
  }
272
325
  return { x, y, width: right - x, height: bottom - y };
@@ -274,12 +327,10 @@ export function resolveLegendBackgroundBounds(points, border, canvas, label) {
274
327
 
275
328
  export function editLegendBackground(program, id, bounds, border) {
276
329
  if (bounds === undefined) return program;
277
- return program
278
- .editGraphics({ target: id, property: "x", value: bounds.x })
279
- .editGraphics({ target: id, property: "y", value: bounds.y })
280
- .editGraphics({ target: id, property: "width", value: bounds.width })
281
- .editGraphics({ target: id, property: "height", value: bounds.height })
282
- .editGraphics({ target: id, property: "fill", value: border.background })
283
- .editGraphics({ target: id, property: "stroke", value: border.color })
284
- .editGraphics({ target: id, property: "strokeWidth", value: border.lineWidth });
330
+ return editGraphicProperties(program, id, {
331
+ ...bounds,
332
+ fill: border.background,
333
+ stroke: border.color,
334
+ strokeWidth: border.lineWidth
335
+ });
285
336
  }
@@ -4,14 +4,15 @@ import { validateKeys } from "../../../../core/validation.js";
4
4
  import { interpolateColorStops } from "../../../../grammar/scales/color.js";
5
5
  import { DEFAULT_COLORS } from "../../../../theme/defaults.js";
6
6
  import {
7
- assertLegendInsideCanvas,
7
+ assertLegendBoundsInsideCanvas,
8
8
  editLegendBackground,
9
9
  formatContinuousValues,
10
10
  normalizeContinuousLegend,
11
11
  requireResolvedLegendScale,
12
12
  resolveContinuousBounds,
13
13
  resolveContinuousColorLayer,
14
- resolveLegendBackgroundBounds,
14
+ resolveLegendBackgroundFromBounds,
15
+ resolveLegendTextBounds,
15
16
  sampleContinuousValues,
16
17
  styleContinuousText,
17
18
  validatePositive
@@ -52,6 +53,7 @@ function resolveGradientLayout(program, config, scale) {
52
53
  align: "center"
53
54
  };
54
55
  const values = sampleContinuousValues(scale.domain, config.count);
56
+ const texts = formatContinuousValues(values, scale.domain, config.fieldType);
55
57
  const fractions = values.map((_, index) => index / (values.length - 1));
56
58
  const labelOffset = config.labels.offset;
57
59
  const labels = vertical
@@ -80,38 +82,41 @@ function resolveGradientLayout(program, config, scale) {
80
82
  x2: label.x,
81
83
  y2: y + thickness + 6
82
84
  }));
83
- assertLegendInsideCanvas([
84
- title,
85
- ...labels,
86
- ...ticks.flatMap(tick => [
87
- { x: tick.x1, y: tick.y1 },
88
- { x: tick.x2, y: tick.y2 }
89
- ])
90
- ], canvas, "Gradient legend layout");
91
- if (
92
- x < 0 ||
93
- y < 0 ||
94
- x + (vertical ? thickness : length) > canvas.width ||
95
- y + (vertical ? length : thickness) > canvas.height
96
- ) {
97
- throw new Error("Gradient legend layout requires more Canvas margin space.");
98
- }
99
- const background = resolveLegendBackgroundBounds([
100
- { x, y },
101
- {
102
- x: x + (vertical ? thickness : length),
103
- y: y + (vertical ? length : thickness)
104
- },
85
+ const stripBounds = {
86
+ left: x,
87
+ right: x + (vertical ? thickness : length),
88
+ top: y,
89
+ bottom: y + (vertical ? length : thickness)
90
+ };
91
+ const tickBounds = ticks.map(tick => ({
92
+ left: Math.min(tick.x1, tick.x2) - 0.5,
93
+ right: Math.max(tick.x1, tick.x2) + 0.5,
94
+ top: Math.min(tick.y1, tick.y2) - 0.5,
95
+ bottom: Math.max(tick.y1, tick.y2) + 0.5
96
+ }));
97
+ const labelBounds = labels.map((label, index) =>
98
+ resolveLegendTextBounds(label, texts[index], config.labels)
99
+ );
100
+ const titleBounds = resolveLegendTextBounds(
105
101
  title,
106
- ...labels.map(label => ({
107
- x: label.x + (
108
- label.align === "left" ? 42 : label.align === "right" ? -42 : 0
109
- ),
110
- y: label.y
111
- }))
112
- ], config.border, canvas, "Gradient legend");
102
+ config.title,
103
+ config.titleStyle
104
+ );
105
+ const occupiedBounds = [stripBounds, ...tickBounds, ...labelBounds, titleBounds];
106
+ assertLegendBoundsInsideCanvas(
107
+ occupiedBounds,
108
+ canvas,
109
+ "Gradient legend layout"
110
+ );
111
+ const background = resolveLegendBackgroundFromBounds(
112
+ occupiedBounds,
113
+ config.border,
114
+ canvas,
115
+ "Gradient legend"
116
+ );
113
117
  return {
114
- vertical, x, y, length, thickness, values, labels, ticks, title, background
118
+ vertical, x, y, length, thickness, values, texts, labels, ticks, title,
119
+ background
115
120
  };
116
121
  }
117
122
 
@@ -244,11 +249,7 @@ export const rematerializeGradientLegend = action(
244
249
  .editGraphics({
245
250
  target: "colorGradientLabels",
246
251
  property: "text",
247
- value: formatContinuousValues(
248
- layout.values,
249
- scale.domain,
250
- config.fieldType
251
- )
252
+ value: layout.texts
252
253
  });
253
254
  next = styleContinuousText(
254
255
  next,