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,4 +1,5 @@
1
1
  import { editGraphicProperties } from "../../../primitives/graphicProperties.js";
2
+ import { materializeLegendTitleLines } from "../../../../materialization/legendText.js";
2
3
  import { action } from "../../../../core/action.js";
3
4
  import {
4
5
  activeConfig,
@@ -19,15 +20,17 @@ export const rematerializeLegendLabels = /* @__PURE__ */ action(
19
20
  throw new Error("rematerializeLegendLabels requires existing legend labels.");
20
21
  }
21
22
  const layout = resolveLayout(this, config);
23
+ const labels = categoricalLegendLabels(config);
24
+ const visible = labels.flatMap((text, index) => text === "" ? [] : [index]);
22
25
  return editGraphicProperties(editGraphicProperties(this, id, {
23
- length: config.domain.length,
24
- x: layout.labelX,
25
- y: layout.itemY
26
+ length: visible.length,
27
+ x: visible.map(index => layout.labelX[index]),
28
+ y: visible.map(index => layout.itemY[index])
26
29
  })
27
30
  .editGraphics({
28
31
  target: id,
29
32
  property: "text",
30
- value: categoricalLegendLabels(config)
33
+ value: visible.map(index => labels[index])
31
34
  }), id, {
32
35
  fill: config.labels.color,
33
36
  fontSize: config.labels.fontSize,
@@ -52,7 +55,7 @@ export const createLegendLabels = /* @__PURE__ */ action(
52
55
  .createGraphics({
53
56
  id,
54
57
  type: "text",
55
- length: config.domain.length,
58
+ length: categoricalLegendLabels(config).length,
56
59
  ...resolveCategoricalLegendPlacement(this)
57
60
  })
58
61
  .rematerializeLegendLabels({ kind: config.kind });
@@ -69,7 +72,7 @@ export const rematerializeLegendTitle = /* @__PURE__ */ action(
69
72
  throw new Error("rematerializeLegendTitle requires an existing legend title.");
70
73
  }
71
74
  const layout = resolveLayout(this, config);
72
- return editGraphicProperties(this, id, {
75
+ const next = editGraphicProperties(this, id, {
73
76
  x: layout.titleX,
74
77
  y: layout.titleY,
75
78
  text: config.title,
@@ -81,12 +84,13 @@ export const rematerializeLegendTitle = /* @__PURE__ */ action(
81
84
  .editGraphics({
82
85
  target: id,
83
86
  property: "textAlign",
84
- value: ["top", "bottom"].includes(config.position) &&
87
+ value: config.titleStyle.align ?? (["top", "bottom"].includes(config.position) &&
85
88
  config.titlePosition === "top"
86
89
  ? "center"
87
- : "left"
90
+ : "left")
88
91
  })
89
92
  .editGraphics({ target: id, property: "textBaseline", value: "middle" });
93
+ return materializeLegendTitleLines(next, id, config.title, config.titleStyle);
90
94
  }
91
95
  );
92
96
 
@@ -29,6 +29,7 @@ import {
29
29
  } from "./symbols.js";
30
30
  import { activeConfig, noOptions, symbolGraphic } from "./layout.js";
31
31
  import {
32
+ rematerializeLegendGroups,
32
33
  rematerializeHorizontalLegendLane,
33
34
  rematerializeSideLegendLane
34
35
  } from "../lane.js";
@@ -68,6 +69,10 @@ function createLegendHighlightAction() {
68
69
  },
69
70
  function (args = {}) {
70
71
  noOptions(args, "rematerializeLegendHighlights");
72
+ if (this.context.deferGuideLayoutValidation !== true &&
73
+ Object.values(this.guideConfigs.legend ?? {}).some(config => ["left", "right"].includes(config.position))) {
74
+ return this.rematerializeLegend();
75
+ }
71
76
  const kinds = args.kind === undefined
72
77
  ? ["series", "color", "stroke"].filter(
73
78
  kind => this.guideConfigs.legend?.[kind] !== undefined
@@ -134,6 +139,7 @@ export function registerBasicCategoricalLegendActions(ProgramClass) {
134
139
  ProgramClass.prototype.createLegendTitle = createLegendTitle;
135
140
  ProgramClass.prototype.rematerializeLegendTitle = rematerializeLegendTitle;
136
141
  ProgramClass.prototype.rematerializeLegend = rematerializeLegend;
142
+ ProgramClass.prototype.rematerializeLegendGroups = rematerializeLegendGroups;
137
143
  ProgramClass.prototype.rematerializeSideLegendLane =
138
144
  rematerializeSideLegendLane;
139
145
  ProgramClass.prototype.rematerializeHorizontalLegendLane =
@@ -1,3 +1,6 @@
1
+ import { findLayer } from "../../../../selectors/layers.js";
2
+ import { isLinearGradientPaint } from "../../../../grammar/paint.js";
3
+ import { intrinsicLegendConfig } from "../../../../materialization/legendGroups.js";
1
4
  import { validateOptionObject } from "../../../../core/validation.js";
2
5
  import { mapOrdinalValues } from "../../../../grammar/scales/index.js";
3
6
  import { DEFAULT_COLORS } from "../../../../theme/defaults.js";
@@ -78,12 +81,20 @@ export function symbolWidth(config) {
78
81
  }));
79
82
  }
80
83
 
84
+ export function categoricalLegendView(config) {
85
+ if (config.hiddenCount !== undefined || !config.overflow || config.domain.length <= config.overflow.maxItems) return config;
86
+ return { ...config, domain: config.domain.slice(0, config.overflow.maxItems),
87
+ hiddenCount: config.domain.length - config.overflow.maxItems };
88
+ }
89
+
81
90
  export function categoricalLegendLabels(config) {
82
- return config.domain.map(value => resolveDisplayLabel(
91
+ config = categoricalLegendView(config);
92
+ const labels = config.domain.map(value => resolveDisplayLabel(
83
93
  value,
84
94
  config.labelMap,
85
95
  formatVisibleText
86
96
  ));
97
+ return config.hiddenCount > 0 ? [...labels, `…${config.hiddenCount} entries`] : labels;
87
98
  }
88
99
 
89
100
  function resolveSampleBounds(program, config, width) {
@@ -96,7 +107,7 @@ function resolveSampleBounds(program, config, width) {
96
107
  stroke: layer.stroke ?? (config.channels.includes("stroke")
97
108
  ? appearance.strokes[index]
98
109
  : appearance.colors[index]),
99
- strokeWidth: layer.lineWidth, opacity: layer.opacity,
110
+ strokeWidth: layer.lineWidth, opacity: layer.opacity ?? appearance.opacity,
100
111
  ...requestedStrokeDetails(layer, "Legend line symbol") } };
101
112
  }
102
113
  if (layer.type === "swatch") {
@@ -106,21 +117,21 @@ function resolveSampleBounds(program, config, width) {
106
117
  stroke: config.channels.includes("stroke")
107
118
  ? appearance.strokes[index]
108
119
  : layer.stroke,
109
- opacity: layer.opacity,
120
+ opacity: layer.opacity ?? appearance.opacity,
110
121
  ...requestedStrokeDetails(details, "Legend swatch symbol")
111
- }, details.cornerRadius ?? 0);
122
+ }, details);
112
123
  }
113
124
  if (config.channels.includes("shape")) {
114
125
  return createPointShapeGraphic({ shape: appearance.shapes[index], x: width / 2, y: 0,
115
126
  area: Math.PI * layer.size ** 2, fill: layer.fill ?? appearance.colors[index],
116
- stroke: layer.stroke, strokeWidth: layer.strokeWidth, opacity: layer.opacity,
127
+ stroke: layer.stroke, strokeWidth: layer.strokeWidth, opacity: layer.opacity ?? appearance.opacity,
117
128
  ...requestedStrokeDetails(layer, "Legend point symbol") });
118
129
  }
119
130
  return { type: "circle", properties: { x: width / 2, y: 0, radius: layer.size,
120
131
  stroke: config.channels.includes("stroke")
121
132
  ? appearance.strokes[index]
122
133
  : layer.stroke,
123
- strokeWidth: layer.strokeWidth, opacity: layer.opacity,
134
+ strokeWidth: layer.strokeWidth, opacity: layer.opacity ?? appearance.opacity,
124
135
  ...requestedStrokeDetails(layer, "Legend point symbol") } };
125
136
  });
126
137
  return items.map((item, itemIndex) => {
@@ -147,6 +158,8 @@ function resolveSampleBounds(program, config, width) {
147
158
  }
148
159
 
149
160
  export function resolveLayout(program, config) {
161
+ config = intrinsicLegendConfig(program, config);
162
+ config = categoricalLegendView(config);
150
163
  const { plot, canvas } = resolveContinuousBounds(program,
151
164
  "Legend layout requires Canvas bounds, width, and height.");
152
165
  const width = symbolWidth(config);
@@ -160,7 +173,14 @@ export function resolveLayout(program, config) {
160
173
  }
161
174
 
162
175
  export function resolveAppearance(program, config) {
163
- let colors = config.domain.map(() => DEFAULT_COLORS.mark);
176
+ const target = findLayer(program, config.target);
177
+ const gradientFallback = target?.mark?.type === "area" &&
178
+ target.encoding.color === undefined &&
179
+ !config.channels.includes("color") &&
180
+ isLinearGradientPaint(program.markConfigs[target.id]?.fill);
181
+ const fallback = gradientFallback
182
+ ? program.markConfigs[target.id].fill : DEFAULT_COLORS.mark;
183
+ let colors = config.domain.map(() => fallback);
164
184
  let strokes = config.domain.map(() => "white");
165
185
  let dashes = config.domain.map(() => []);
166
186
  let shapes = config.domain.map(() => "circle");
@@ -172,7 +192,8 @@ export function resolveAppearance(program, config) {
172
192
  if (config.channels[index] === "strokeDash") dashes = values;
173
193
  if (config.channels[index] === "shape") shapes = values;
174
194
  }
175
- return { colors, strokes, dashes, shapes };
195
+ return { colors, strokes, dashes, shapes,
196
+ ...(gradientFallback ? { opacity: program.markConfigs[target.id].opacity } : {}) };
176
197
  }
177
198
 
178
199
  export function noOptions(args, operation) {
@@ -189,3 +210,11 @@ export function layerFor(config, type) {
189
210
  }
190
211
  return layer;
191
212
  }
213
+
214
+
215
+ export function resolveSymbolLayout(program, config) {
216
+ const view = categoricalLegendView(config);
217
+ const layout = resolveLayout(program, view);
218
+ return { ...layout, symbolX: layout.symbolX.slice(0, view.domain.length),
219
+ itemY: layout.itemY.slice(0, view.domain.length) };
220
+ }
@@ -1,3 +1,5 @@
1
+ import { LEGEND_TITLE_STYLE_KEYS, validateLegendTitleAlignment } from "../../../../grammar/legendTitleStyle.js";
2
+ import { PLOT_LEGEND_POSITIONS, isPlotLegend } from "../../../../layout/legend.js";
1
3
  import { validateOptionObject } from "../../../../core/validation.js";
2
4
  import { CATEGORICAL_LEGEND_CHANNELS } from "../../../../core/vocabulary.js";
3
5
  import { normalizeRecipe } from "./recipes.js";
@@ -9,7 +11,7 @@ import {
9
11
  validateKeys,
10
12
  validateObject
11
13
  } from "./validation.js";
12
- import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
14
+ import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY, DEFAULT_VERTICAL_LEGEND_ITEM_GAP } from
13
15
  "../../../../theme/defaults.js";
14
16
 
15
17
  export const CHANNELS = CATEGORICAL_LEGEND_CHANNELS;
@@ -29,7 +31,8 @@ const OPTIONS = Object.freeze([
29
31
  "labels",
30
32
  "titleStyle",
31
33
  "itemGap",
32
- "border"
34
+ "border",
35
+ "overflow"
33
36
  ]);
34
37
  const TEXT_OPTIONS = Object.freeze([
35
38
  "offset",
@@ -39,12 +42,19 @@ const TEXT_OPTIONS = Object.freeze([
39
42
  "fontWeight",
40
43
  "format"
41
44
  ]);
42
- const TITLE_OPTIONS = Object.freeze([
43
- "color",
44
- "fontSize",
45
- "fontFamily",
46
- "fontWeight"
47
- ]);
45
+
46
+ export function normalizeLegendOverflow(value) {
47
+ if (value === undefined || value === false) return value;
48
+ validateObject(value, ["maxItems", "summary"], "Legend overflow");
49
+ if (!Number.isInteger(value.maxItems) || value.maxItems < 1) {
50
+ throw new RangeError("Legend overflow.maxItems must be a positive integer.");
51
+ }
52
+ if (value.summary !== undefined && value.summary !== "ellipsis-count") {
53
+ throw new Error("Legend overflow.summary must be ellipsis-count.");
54
+ }
55
+ return { maxItems: value.maxItems, summary: "ellipsis-count" };
56
+ }
57
+
48
58
  const BORDER_OPTIONS = Object.freeze([
49
59
  "color",
50
60
  "lineWidth",
@@ -99,7 +109,7 @@ export function normalizeOptions(args, kind, { internalSymbol = false } = {}) {
99
109
  if (Object.hasOwn(args, "titleStyle")) {
100
110
  validateObject(
101
111
  args.titleStyle,
102
- TITLE_OPTIONS,
112
+ LEGEND_TITLE_STYLE_KEYS,
103
113
  "createLegend.titleStyle"
104
114
  );
105
115
  }
@@ -121,13 +131,15 @@ export function normalizeOptions(args, kind, { internalSymbol = false } = {}) {
121
131
  };
122
132
  const position = args.position ?? defaults.position;
123
133
  const align = args.align ?? defaults.align;
134
+ const inside = isPlotLegend({ position });
124
135
  const side = ["left", "right"].includes(position);
125
- const direction = args.direction ?? (side ? "vertical" : "horizontal");
136
+ const direction = args.direction ?? (side || inside ? "vertical" : "horizontal");
126
137
  const columns = args.columns;
127
138
  const offset = args.offset ?? 8;
128
139
  const titlePosition = args.titlePosition ?? "top";
129
- const itemGap = args.itemGap ?? (["right", "left"].includes(position)
130
- ? 28
140
+ validateLegendTitleAlignment(titleStyle, titlePosition);
141
+ const itemGap = args.itemGap ?? (direction === "vertical"
142
+ ? DEFAULT_VERTICAL_LEGEND_ITEM_GAP
131
143
  : position === "top" ? 24 : 20);
132
144
  const layout = args.layout === undefined ? "edge" : args.layout;
133
145
  if (!["edge", "legacy-bottom"].includes(layout)) {
@@ -142,14 +154,14 @@ export function normalizeOptions(args, kind, { internalSymbol = false } = {}) {
142
154
  throw new Error('Legend columns, vertical direction, left title and custom offset require layout "edge".');
143
155
  }
144
156
 
145
- if (!["right", "left", "bottom", "top"].includes(position)) {
157
+ if (!["right", "left", "bottom", "top", ...PLOT_LEGEND_POSITIONS].includes(position)) {
146
158
  throw new Error(`Unsupported legend position "${position}".`);
147
159
  }
148
160
  if (!["left", "center", "right"].includes(align)) {
149
161
  throw new Error(`Unsupported legend alignment "${align}".`);
150
162
  }
151
- if (["right", "left"].includes(position) && align !== "center") {
152
- throw new Error("Side legends currently require center alignment.");
163
+ if ((side || inside) && align !== "center") {
164
+ throw new Error(inside ? "In-plot legends require center alignment." : "Side legends currently require center alignment.");
153
165
  }
154
166
  if (!["horizontal", "vertical"].includes(direction)) {
155
167
  throw new Error(`Unsupported legend direction "${direction}".`);
@@ -157,11 +169,8 @@ export function normalizeOptions(args, kind, { internalSymbol = false } = {}) {
157
169
  if (side && direction !== "vertical") {
158
170
  throw new Error("Side legends require vertical direction.");
159
171
  }
160
- if (side && columns !== undefined && columns !== 1) {
161
- throw new Error("Side legends require one column.");
162
- }
163
- if (side && titlePosition !== "top") {
164
- throw new Error("Side legends require a top title.");
172
+ if ((side || inside) && titlePosition !== "top") {
173
+ throw new Error(inside ? "In-plot legends require a top title." : "Side legends require a top title.");
165
174
  }
166
175
  if (columns !== undefined && (!Number.isInteger(columns) || columns <= 0)) {
167
176
  throw new RangeError("Legend columns must be a positive integer.");
@@ -181,7 +190,9 @@ export function normalizeOptions(args, kind, { internalSymbol = false } = {}) {
181
190
  validateFontWeight(titleStyle.fontWeight, "Legend title fontWeight");
182
191
  positive(itemGap, "Legend itemGap");
183
192
 
193
+ const overflow = normalizeLegendOverflow(args.overflow);
184
194
  return {
195
+ ...(overflow === undefined ? {} : { overflow }),
185
196
  target: args.target,
186
197
  channels: args.channels,
187
198
  position,
@@ -17,7 +17,7 @@ function isCategoricalTarget(layer) {
17
17
  channel => layer.encoding?.[channel]?.scale !== undefined
18
18
  );
19
19
  }
20
- return ["bar", "area", "arc", "rect", "rule", "tick"].includes(
20
+ return ["bar", "area", "arc", "rect", "rule", "tick", "text"].includes(
21
21
  layer?.mark?.type
22
22
  ) && ["color", "stroke"].some(
23
23
  channel => layer.encoding?.[channel]?.scale !== undefined
@@ -49,7 +49,7 @@ export const sameValues = sameOrderedValues;
49
49
 
50
50
  function resolveLegendKind(layer, requestedChannels) {
51
51
  if (sameValues(requestedChannels, ["stroke"])) return "stroke";
52
- if (["bar", "area", "arc", "rect"].includes(layer.mark.type)) return "color";
52
+ if (["bar", "area", "arc", "rect", "text"].includes(layer.mark.type)) return "color";
53
53
  if (
54
54
  layer.mark.type === "point" &&
55
55
  sameValues(requestedChannels, ["color"])
@@ -2,17 +2,19 @@ import { editGraphicProperties } from "../../../primitives/graphicProperties.js"
2
2
  import { action } from "../../../../core/action.js";
3
3
  import {
4
4
  activeConfig,
5
+ categoricalLegendView,
5
6
  graphic,
6
7
  layerFor,
7
8
  noOptions,
8
9
  resolveAppearance,
9
- resolveLayout,
10
+ resolveSymbolLayout as resolveLayout,
10
11
  symbolGraphic,
11
12
  symbolWidth
12
13
  } from "./layout.js";
13
14
  import { resolveCategoricalLegendPlacement } from "../lifecycle.js";
14
15
  import { createPointShapeGraphic } from "../../../../grammar/pointShapes.js";
15
16
  import {
17
+ hasRectRadius,
16
18
  materializeRectItem,
17
19
  requestedRectStyleDetails
18
20
  } from "../../../../grammar/roundedRect.js";
@@ -32,11 +34,12 @@ function makeEditSymbol(type) {
32
34
  { op, description: `Rematerialize categorical legend ${type} symbols.` },
33
35
  function (args = {}) {
34
36
  noOptions(args, op);
35
- const { config } = activeConfig(this, args.kind);
37
+ const { config: stored } = activeConfig(this, args.kind);
38
+ const config = categoricalLegendView(stored);
36
39
  const layer = layerFor(config, type);
37
40
  const id = symbolGraphic(config, type);
38
41
  const dynamicPoint = type === "point" && config.channels.includes("shape");
39
- const styledSwatch = type === "swatch" && Object.hasOwn(layer, "cornerRadius");
42
+ const styledSwatch = type === "swatch" && hasRectRadius(layer);
40
43
  const expected = dynamicPoint || styledSwatch
41
44
  ? "collection"
42
45
  : { line: "line", point: "circle", swatch: "rect" }[type];
@@ -81,7 +84,7 @@ function makeEditSymbol(type) {
81
84
  )) {
82
85
  next = next.editGraphics({ target: id, property, value });
83
86
  }
84
- return applyLayerOpacity(next, id, layer.opacity);
87
+ return applyLayerOpacity(next, id, layer.opacity ?? appearance.opacity);
85
88
  }
86
89
  if (type === "point") {
87
90
  const x = layout.symbolX.map(value => value + symbolWidth(config) / 2);
@@ -98,7 +101,7 @@ function makeEditSymbol(type) {
98
101
  ? appearance.strokes[index]
99
102
  : layer.stroke,
100
103
  strokeWidth: layer.strokeWidth,
101
- opacity: layer.opacity,
104
+ opacity: layer.opacity ?? appearance.opacity,
102
105
  ...requestedStrokeDetails(layer, "Legend point symbol")
103
106
  });
104
107
  });
@@ -127,7 +130,7 @@ function makeEditSymbol(type) {
127
130
  )) {
128
131
  next = next.editGraphics({ target: id, property, value });
129
132
  }
130
- return applyLayerOpacity(next, id, layer.opacity);
133
+ return applyLayerOpacity(next, id, layer.opacity ?? appearance.opacity);
131
134
  }
132
135
  const x = layout.symbolX.map(
133
136
  value => value + (symbolWidth(config) - layer.width) / 2
@@ -151,9 +154,9 @@ function makeEditSymbol(type) {
151
154
  fill: fills[index],
152
155
  stroke: strokes[index],
153
156
  strokeWidth: layer.strokeWidth,
154
- ...(layer.opacity === undefined ? {} : { opacity: layer.opacity }),
157
+ ...((layer.opacity ?? appearance.opacity) === undefined ? {} : { opacity: layer.opacity ?? appearance.opacity }),
155
158
  ...requestedStrokeDetails(details, "Legend swatch symbol")
156
- }, details.cornerRadius ?? 0))
159
+ }, details))
157
160
  );
158
161
  }
159
162
  );
@@ -166,14 +169,15 @@ function makeCreateSymbol(type, edit) {
166
169
  { op, description: `Create categorical legend ${type} symbols.` },
167
170
  function (args = {}) {
168
171
  noOptions(args, op);
169
- const { config } = activeConfig(this, args.kind);
172
+ const { config: stored } = activeConfig(this, args.kind);
173
+ const config = categoricalLegendView(stored);
170
174
  const layer = layerFor(config, type);
171
175
  const id = symbolGraphic(config, type);
172
176
  if (this.graphicSpec.objects[id] !== undefined) {
173
177
  throw new Error(`${op} requires missing ${type} symbols.`);
174
178
  }
175
179
  const graphicType = type === "point" && config.channels.includes("shape") ||
176
- type === "swatch" && (layer.cornerRadius ?? 0) > 0
180
+ type === "swatch" && hasRectRadius(layer)
177
181
  ? "collection"
178
182
  : { line: "line", point: "circle", swatch: "rect" }[type];
179
183
  return this
@@ -213,7 +217,8 @@ export const createLegendSymbols = /* @__PURE__ */ action(
213
217
  { op: "createLegendSymbols", description: "Create layered legend symbols." },
214
218
  function (args = {}) {
215
219
  noOptions(args, "createLegendSymbols");
216
- const { config } = activeConfig(this, args.kind);
220
+ const { config: stored } = activeConfig(this, args.kind);
221
+ const config = categoricalLegendView(stored);
217
222
  let next = this;
218
223
  for (const layer of config.symbol.layers) {
219
224
  const operation = {
@@ -231,7 +236,8 @@ export const rematerializeLegendSymbols = /* @__PURE__ */ action(
231
236
  { op: "rematerializeLegendSymbols", description: "Rematerialize layered legend symbols." },
232
237
  function (args = {}) {
233
238
  noOptions(args, "rematerializeLegendSymbols");
234
- const { config } = activeConfig(this, args.kind);
239
+ const { config: stored } = activeConfig(this, args.kind);
240
+ const config = categoricalLegendView(stored);
235
241
  let next = this;
236
242
  for (const layer of config.symbol.layers) {
237
243
  const operation = {
@@ -19,9 +19,4 @@ export const nonEmptyString = validateNonEmptyString;
19
19
  export const nonNegative = validateNonNegativeFinite;
20
20
  export const positive = validatePositiveFinite;
21
21
 
22
- export function validateFontWeight(value, label) {
23
- if (typeof value !== "string" && !Number.isFinite(value)) {
24
- throw new TypeError(`${label} must be a string or number.`);
25
- }
26
- return value;
27
- }
22
+ export { validateFontWeight } from "../../../../core/validation.js";
@@ -0,0 +1 @@
1
+ import{validateOptionObject as h,validatePositiveFinite as k,validateNonNegativeFinite as x,validateNonEmptyString as v}from"../../../../core/validation.js";import{intrinsicLegendConfig as L}from"../../../../materialization/legendGroups.js";import{mapScaleConsumerValues as C}from"../../../../materialization/scales/map.js";import{transformedTicks as G}from"../../../../grammar/scales/index.js";import{resolveLegendItemLayout as _}from"../../../../layout/legendItems.js";import{editGraphicProperties as u}from"../../../primitives/graphicProperties.js";import{normalizeLegendSampling as z,readLegendSampling as W,validateSamplingAgainstScale as w,resolveLegendSampleValues as B}from"../sampling.js";import{normalizeContinuousLegend as T,normalizeItemLegendLayout as P,resolveContinuousBounds as V,formatContinuousValues as E,assertLegendBoundsInsideCanvas as F,resolveLegendBackgroundFromBounds as I,editLegendBackground as N,styleContinuousText as y}from"./common.js";function q(t){if(t===void 0||t==="auto")return{type:"point",radius:7,strokeWidth:1};if(h(t,["type","radius","stroke","strokeWidth"],"Sampled color symbol"),t.type!==void 0&&t.type!=="point")throw new Error("Sampled color symbol requires type point.");const e={type:"point",radius:7,strokeWidth:1,...t};return k(e.radius,"Sampled color symbol radius"),x(e.strokeWidth,"Sampled color symbol strokeWidth"),e.stroke!==void 0&&v(e.stroke,"Sampled color symbol stroke"),e}function Q(t,e,i="create"){if(t.gradient!==void 0)throw new Error("Symbol color legends do not accept gradient dimensions.");const{representation:l,values:r,count:s,symbol:o,columns:d,direction:n,itemGap:a,layout:p,titlePosition:c,...m}=t,b=T(m,"gradient"),g=["left","right","top-left","top-right","bottom-left","bottom-right"].includes(t.position??"right"),S=P({...t,itemGap:t.itemGap??(g?18:20)},{multiColumnSide:!0}),{count:X,...f}=b;return{...f,...S,representation:"symbols",sampling:z(t,{previous:e,operation:i,label:"Sampled color legend"}),symbol:q(t.symbol)}}function O(t,e,i){e=L(t,e);const{plot:l,canvas:r}=V(t),s=w(W(e),i,"Sampled color legend");let o=s.mode==="auto"&&["log","sqrt","symlog"].includes(i.type)?G(i.type,i.domain,s.count,i.type==="log"?{base:i.base}:i.type==="symlog"?{constant:i.constant}:{}):B(e,i,"Sampled color legend");s.mode==="auto"&&i.midpoint!==void 0&&!o.includes(i.midpoint)&&(o=[...o,i.midpoint].sort((c,m)=>c-m));let d=E(o,i.domain,e.fieldType,e.labels.format);const n=e.symbol.radius+e.symbol.strokeWidth/2,a=_(l,e,d,{width:n*2,height:n*2},r,t.materializationConfigs.textMetrics);F(a.bounds,r,"Sampled color legend",e);const p=I(a.bounds,e.border,r,"Sampled color legend",e);return{...a,background:p,values:o,texts:d,extent:n,config:e}}function R(t,e,i){const l=i,{scale:r,encoding:s}=e,o=O(t,l,r),d=C(o.values,r,"color");let n=t.editSemantic({property:"guide.legend.color.scale",value:s.scale}).editSemantic({property:"guide.legend.color.title",value:l.title})._withLegendConfig("gradient",e.config);return n=u(n,"colorGradientSymbols",{length:o.values.length,x:o.symbolX.map(a=>a+o.extent),y:o.itemY,radius:l.symbol.radius,fill:d,stroke:l.symbol.stroke??d,strokeWidth:l.symbol.strokeWidth,opacity:l.blockSymbol?.opacity??1}),n=u(n,"colorGradientLabels",{length:o.values.length,x:o.labelX,y:o.itemY,text:o.texts}),n=N(n,"colorGradientBackground",o.background,l.border),n=y(n,"colorGradientLabels",l.labels),l.titleVisible===!1?n:(n=u(n,"colorGradientTitle",{x:o.title.x,y:o.title.y,text:l.title}),y(n,"colorGradientTitle",l.titleStyle,{align:o.title.align}))}export{R as materializeColorSymbols,q as normalizeColorSymbol,Q as normalizeColorSymbols,O as resolveColorSymbolsLayout};