ggaction 0.0.17 → 0.0.19

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 (315) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +6 -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 +20 -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 +8 -7
  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/derived.js +11 -1
  35. package/src/actions/data/edit.js +3 -1
  36. package/src/actions/data/index.js +4 -0
  37. package/src/actions/data/regression.js +25 -16
  38. package/src/actions/data/regressionStatistics.js +73 -0
  39. package/src/actions/data/shared.js +8 -4
  40. package/src/actions/data/stack.js +3 -1
  41. package/src/actions/distributions/revision.js +24 -18
  42. package/src/actions/encodings/appearance.js +37 -29
  43. package/src/actions/encodings/barWidth.js +36 -11
  44. package/src/actions/encodings/channels.js +0 -1
  45. package/src/actions/encodings/color/continuous.js +1 -1
  46. package/src/actions/encodings/color/index.js +1 -1
  47. package/src/actions/encodings/color/policy.js +10 -3
  48. package/src/actions/encodings/density/resolve.js +1 -6
  49. package/src/actions/encodings/position/policies/arc.js +12 -10
  50. package/src/actions/encodings/position/policies/area.js +3 -3
  51. package/src/actions/encodings/position/policies/bar.js +15 -6
  52. package/src/actions/encodings/position/policies/line.js +17 -6
  53. package/src/actions/encodings/position/resolve.js +3 -2
  54. package/src/actions/encodings/ranged.js +17 -0
  55. package/src/actions/encodings/remove.js +14 -8
  56. package/src/actions/encodings/seriesLayout.js +1 -0
  57. package/src/actions/encodings/shared.js +26 -24
  58. package/src/actions/encodings/text.js +6 -3
  59. package/src/actions/errorBands/create.js +2 -2
  60. package/src/actions/errorBands/edit.js +2 -1
  61. package/src/actions/facets/actions.js +176 -247
  62. package/src/actions/facets/guides.js +2 -1
  63. package/src/actions/gradientPlots/resolve.js +2 -17
  64. package/src/actions/guides/applicability.js +1 -1
  65. package/src/actions/guides/axes/axes.js +30 -11
  66. package/src/actions/guides/axes/axis.js +72 -10
  67. package/src/actions/guides/axes/components.js +13 -13
  68. package/src/actions/guides/axes/edit.js +36 -24
  69. package/src/actions/guides/axes/labels.js +155 -71
  70. package/src/actions/guides/axes/lines.js +27 -24
  71. package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
  72. package/src/actions/guides/axes/parallel/policy.js +8 -7
  73. package/src/actions/guides/axes/remove.js +13 -8
  74. package/src/actions/guides/axes/tickGroups.js +11 -7
  75. package/src/actions/guides/axes/ticks.js +36 -24
  76. package/src/actions/guides/axes/titles.js +107 -69
  77. package/src/actions/guides/basic.js +2 -0
  78. package/src/actions/guides/facadeAxes.js +33 -24
  79. package/src/actions/guides/facadeLegend.js +12 -4
  80. package/src/actions/guides/grids/grid.js +8 -2
  81. package/src/actions/guides/grids/resolve.js +102 -58
  82. package/src/actions/guides/legends/blocks.js +46 -20
  83. package/src/actions/guides/legends/categorical/actions.js +95 -19
  84. package/src/actions/guides/legends/categorical/components.js +15 -18
  85. package/src/actions/guides/legends/categorical/index.js +8 -0
  86. package/src/actions/guides/legends/categorical/layout.js +45 -20
  87. package/src/actions/guides/legends/categorical/options.js +39 -22
  88. package/src/actions/guides/legends/categorical/recipes.js +7 -3
  89. package/src/actions/guides/legends/categorical/resolve.js +11 -4
  90. package/src/actions/guides/legends/categorical/symbols.js +38 -18
  91. package/src/actions/guides/legends/categorical/validation.js +1 -6
  92. package/src/actions/guides/legends/colorSize.js +110 -0
  93. package/src/actions/guides/legends/combine.js +37 -0
  94. package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
  95. package/src/actions/guides/legends/continuous/common.js +1 -447
  96. package/src/actions/guides/legends/continuous/gradient.js +73 -46
  97. package/src/actions/guides/legends/continuous/interval.js +4 -4
  98. package/src/actions/guides/legends/continuous/opacity.js +33 -21
  99. package/src/actions/guides/legends/creation.js +2 -0
  100. package/src/actions/guides/legends/edit.js +1 -604
  101. package/src/actions/guides/legends/focused.js +8 -7
  102. package/src/actions/guides/legends/group.js +1 -0
  103. package/src/actions/guides/legends/lane.js +1 -516
  104. package/src/actions/guides/legends/lifecycle.js +2 -1
  105. package/src/actions/guides/legends/remove.js +9 -3
  106. package/src/actions/guides/legends/sampling.js +1 -1
  107. package/src/actions/guides/legends/size.js +65 -47
  108. package/src/actions/guides/legends/strokeWidth.js +13 -29
  109. package/src/actions/guides/legends/target.js +2 -79
  110. package/src/actions/guides/legends/transition.js +44 -11
  111. package/src/actions/guides/polar/axes/labels.js +4 -2
  112. package/src/actions/guides/polar/axes/titles.js +7 -5
  113. package/src/actions/guides/polar/resolve.js +6 -14
  114. package/src/actions/guides/tickValues.js +8 -5
  115. package/src/actions/index.js +16 -16
  116. package/src/actions/marks/arc/actions.js +3 -10
  117. package/src/actions/marks/area/actions.js +8 -5
  118. package/src/actions/marks/area/materialize.js +8 -9
  119. package/src/actions/marks/bar/create.js +10 -4
  120. package/src/actions/marks/bar/edit.js +29 -14
  121. package/src/actions/marks/bar/materialize.js +15 -7
  122. package/src/actions/marks/line/actions.js +11 -4
  123. package/src/actions/marks/line/materialize.js +9 -5
  124. package/src/actions/marks/point/edit.js +1 -1
  125. package/src/actions/marks/point/materialize.js +5 -1
  126. package/src/actions/marks/rect/actions.js +3 -3
  127. package/src/actions/marks/remove.js +12 -10
  128. package/src/actions/marks/rule/actions.js +7 -4
  129. package/src/actions/marks/text/actions.js +17 -5
  130. package/src/actions/marks/text/layout.js +3 -0
  131. package/src/actions/primitives/createGraphics.js +3 -2
  132. package/src/actions/primitives/editGraphics.js +15 -11
  133. package/src/actions/primitives/graphicProperties.js +1 -6
  134. package/src/actions/primitives/semanticAction.js +27 -1
  135. package/src/actions/primitives/semanticValidation/guide.js +11 -1
  136. package/src/actions/primitives/semanticValidation/layer.js +8 -0
  137. package/src/actions/regression/create.js +1 -1
  138. package/src/actions/regression/edit.js +18 -6
  139. package/src/actions/regression/resolve.js +2 -6
  140. package/src/actions/scales/channels.js +9 -4
  141. package/src/actions/scales/consumers/families.js +16 -5
  142. package/src/actions/scales/create.js +10 -5
  143. package/src/actions/scales/definitions.js +1 -425
  144. package/src/actions/scales/edit.js +3 -3
  145. package/src/actions/scales/editPolicy.js +12 -3
  146. package/src/actions/scales/materialize.js +1 -1
  147. package/src/actions/scales/patch.js +1 -1
  148. package/src/actions/scales/preview.js +21 -1
  149. package/src/actions/scales/quantitativeColor.js +8 -3
  150. package/src/actions/selection/actions.js +1 -13
  151. package/src/actions/theme/reconcile.js +126 -45
  152. package/src/actions/theme/state.js +12 -0
  153. package/src/actions/titles/actions.js +5 -18
  154. package/src/actions/titles/resolve.js +41 -19
  155. package/src/actions/violinPlots/edit.js +3 -20
  156. package/src/core/compositionState.js +6 -3
  157. package/src/core/font.js +9 -2
  158. package/src/core/graphicProperties.js +1 -0
  159. package/src/core/optionRequirements.js +1 -0
  160. package/src/core/programState.js +4 -0
  161. package/src/core/resourceReferences.js +5 -2
  162. package/src/core/textMetricProfile.js +3 -2
  163. package/src/core/textMetrics.js +41 -4
  164. package/src/core/validation.js +35 -0
  165. package/src/core/vocabulary.js +7 -0
  166. package/src/grammar/aggregate.js +4 -18
  167. package/src/grammar/arcs.js +1 -386
  168. package/src/grammar/areaPaint.js +10 -0
  169. package/src/grammar/areaSeries.js +18 -10
  170. package/src/grammar/axisTextPlacement.js +1 -0
  171. package/src/grammar/bars/geometry.js +30 -3
  172. package/src/grammar/bars/policy.js +12 -1
  173. package/src/grammar/bin.js +2 -12
  174. package/src/grammar/bin2d.js +1 -6
  175. package/src/grammar/boxPlot.js +56 -9
  176. package/src/grammar/categoryOrder.js +1 -6
  177. package/src/grammar/colorSizeLegend.js +39 -0
  178. package/src/grammar/complete.js +15 -19
  179. package/src/grammar/computed.js +115 -26
  180. package/src/grammar/curveCommands.js +29 -16
  181. package/src/grammar/datasetSchema.js +13 -9
  182. package/src/grammar/displayLabels.js +36 -0
  183. package/src/grammar/ecdf.js +2 -9
  184. package/src/grammar/facets/dependencies.js +6 -2
  185. package/src/grammar/facets/guides.js +58 -30
  186. package/src/grammar/facets/index.js +2 -5
  187. package/src/grammar/facets/legendIntent.js +65 -0
  188. package/src/grammar/fold.js +2 -12
  189. package/src/grammar/gradientProfile.js +1 -6
  190. package/src/grammar/guideDash.js +1 -0
  191. package/src/grammar/impute.js +21 -48
  192. package/src/grammar/interval.js +2 -9
  193. package/src/grammar/legendBlocks.js +1 -0
  194. package/src/grammar/legendBorder.js +9 -0
  195. package/src/grammar/legendClip.js +19 -0
  196. package/src/grammar/legendCombinations.js +45 -0
  197. package/src/grammar/legendGradient.js +27 -0
  198. package/src/grammar/legendGroups.js +1 -0
  199. package/src/grammar/legendOpacity.js +6 -0
  200. package/src/grammar/legendTitleStyle.js +22 -0
  201. package/src/grammar/lineSeries.js +32 -17
  202. package/src/grammar/markLabels.js +5 -2
  203. package/src/grammar/normalize.js +4 -54
  204. package/src/grammar/numeric.js +1 -290
  205. package/src/grammar/parallelCoordinates.js +1 -6
  206. package/src/grammar/parseTime.js +41 -0
  207. package/src/grammar/pathCommands.js +11 -4
  208. package/src/grammar/pointShapes.js +16 -2
  209. package/src/grammar/polar.js +1 -184
  210. package/src/grammar/positionCompatibility.js +3 -3
  211. package/src/grammar/regression/derive.js +16 -2
  212. package/src/grammar/regression/models.js +142 -54
  213. package/src/grammar/regression/parameters.js +23 -4
  214. package/src/grammar/regression/statistics.js +64 -0
  215. package/src/grammar/roundedRect.js +42 -54
  216. package/src/grammar/scales/appearance.js +1 -206
  217. package/src/grammar/scales/color.js +1 -382
  218. package/src/grammar/scales/colorConsumers.js +5 -2
  219. package/src/grammar/scales/definition.js +1 -197
  220. package/src/grammar/scales/discretized.js +1 -195
  221. package/src/grammar/scales/size.js +1 -456
  222. package/src/grammar/scales/temporal.js +2 -13
  223. package/src/grammar/scales/transformed.js +1 -383
  224. package/src/grammar/scales/types.js +1 -1
  225. package/src/grammar/scales/validation.js +1 -66
  226. package/src/grammar/schemas/concreteGraphic.js +14 -0
  227. package/src/grammar/schemas/graphic.js +6 -0
  228. package/src/grammar/schemas/graphicBounds.js +31 -7
  229. package/src/grammar/schemas/semanticPath.js +19 -3
  230. package/src/grammar/stack.js +2 -10
  231. package/src/grammar/statisticalReference.js +1 -6
  232. package/src/grammar/summary.js +4 -27
  233. package/src/grammar/text.js +19 -0
  234. package/src/grammar/timeUnit.js +22 -11
  235. package/src/grammar/transformKeys.js +47 -0
  236. package/src/grammar/transformTopology.js +1 -0
  237. package/src/grammar/transforms.js +15 -2
  238. package/src/grammar/valueFormat.js +17 -5
  239. package/src/grammar/weightedStatistics.js +1 -6
  240. package/src/grammar/window.js +4 -58
  241. package/src/inspectionDescriptors.js +1 -1
  242. package/src/layout/canvas.js +1 -110
  243. package/src/layout/facets.js +1 -244
  244. package/src/layout/guideCollisions.js +9 -1
  245. package/src/layout/labels.js +1 -0
  246. package/src/layout/legend.js +13 -0
  247. package/src/layout/legendGroup.js +1 -0
  248. package/src/layout/legendItems.js +59 -15
  249. package/src/layout/legendLane.js +1 -441
  250. package/src/layout/legendText.js +1 -0
  251. package/src/layout/title.js +35 -13
  252. package/src/materialization/bars/aggregate.js +11 -5
  253. package/src/materialization/bars/histogram.js +4 -1
  254. package/src/materialization/bars/ranged.js +19 -6
  255. package/src/materialization/bars/resolve.js +22 -1
  256. package/src/materialization/compositionSnapshot.js +2 -7
  257. package/src/materialization/coordinateBounds.js +21 -0
  258. package/src/materialization/dependencies.js +4 -44
  259. package/src/materialization/facetGuides/edit.js +30 -0
  260. package/src/materialization/facetGuides/layout.js +1 -0
  261. package/src/materialization/facetGuides/owner.js +125 -0
  262. package/src/materialization/facetGuides/ownerLayout.js +71 -0
  263. package/src/materialization/facetGuides/placement.js +71 -14
  264. package/src/materialization/facetGuides/preparation.js +22 -7
  265. package/src/materialization/facetHeaders.js +1 -347
  266. package/src/materialization/facets.js +1 -185
  267. package/src/materialization/graphicHierarchy.js +5 -1
  268. package/src/materialization/graphicTranslation.js +73 -0
  269. package/src/materialization/guides/layout.js +1 -57
  270. package/src/materialization/guides/resources.js +1 -123
  271. package/src/materialization/layout.js +53 -0
  272. package/src/materialization/legendBorder.js +28 -0
  273. package/src/materialization/legendClip.js +76 -0
  274. package/src/materialization/legendGroups.js +1 -0
  275. package/src/materialization/legendText.js +1 -0
  276. package/src/materialization/legends.js +2 -2
  277. package/src/materialization/marks/capabilities.js +5 -6
  278. package/src/materialization/rect.js +1 -1
  279. package/src/materialization/scaleGuideDependencies.js +14 -15
  280. package/src/materialization/scales/map.js +6 -0
  281. package/src/materialization/scales/policies/arc.js +8 -7
  282. package/src/materialization/scales/policies/bar.js +19 -7
  283. package/src/materialization/scales/policies/series.js +2 -1
  284. package/src/materialization/scales/resolve.js +7 -3
  285. package/src/materialization/selection/items/arc.js +1 -1
  286. package/src/materialization/selection/items/bar.js +7 -6
  287. package/src/materialization/selection/items/path.js +7 -6
  288. package/src/materialization/selection/items/rect.js +2 -2
  289. package/src/materialization/text.js +13 -4
  290. package/src/materialization/textLines.js +15 -0
  291. package/src/materialization/typography.js +10 -2
  292. package/src/persistence/codec.js +37 -6
  293. package/src/persistence/validation.js +220 -0
  294. package/src/persistence.js +19 -6
  295. package/src/renderers/canvas/circle.js +6 -3
  296. package/src/renderers/canvas/fill.js +7 -1
  297. package/src/renderers/canvas/index.js +22 -11
  298. package/src/renderers/canvas/isolated.js +166 -0
  299. package/src/renderers/canvas/limits.js +3 -0
  300. package/src/renderers/canvas/line.js +1 -0
  301. package/src/renderers/canvas/path.js +1 -1
  302. package/src/renderers/canvas/rect.js +11 -8
  303. package/src/renderers/canvas/stroke.js +2 -1
  304. package/src/renderers/canvas/text.js +5 -3
  305. package/src/renderers/pdf.js +2 -2
  306. package/src/renderers/png.js +2 -2
  307. package/src/renderers/svg.js +14 -4
  308. package/src/selectors/axes.js +1 -0
  309. package/src/selectors/graphicCapacity.js +38 -0
  310. package/src/selectors/layers.js +19 -0
  311. package/src/theme/defaults.js +8 -2
  312. package/src/version.js +1 -1
  313. package/types/basic.d.ts +3 -0
  314. package/types/index.d.ts +12 -0
  315. package/types/program.d.ts +536 -129
@@ -1,28 +1,33 @@
1
+ import{validateLegendClip,clippedSampleBounds}from"../../../grammar/legendClip.js";
2
+ import{hasLegendClipGraphics}from"../../../materialization/legendClip.js";
3
+ import { resolveDisplayLabel } from "../../../grammar/displayLabels.js";
4
+ import { intrinsicLegendConfig, legendGroup } from "../../../materialization/legendGroups.js";
5
+ import { formatVisibleText } from "../../../core/textMetrics.js";
1
6
  import { action, closedAction } from "../../../core/action.js";
2
7
  import {
3
- validateNonEmptyString,
4
8
  validateKeys
5
9
  } from "../../../core/validation.js";
6
10
  import {
7
11
  formatDiscretizedIntervals,
8
12
  isDiscreteSizeScaleType,
13
+ isEnumeratedSizeScaleType,
9
14
  isSizeScaleType,
10
15
  mapSizeValues
11
16
  } from "../../../grammar/scales/index.js";
12
17
  import { resolveLegendItemLayout } from "../../../layout/legendItems.js";
13
- import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
18
+ import { DEFAULT_COLORS } from
14
19
  "../../../theme/defaults.js";
15
20
  import { findLayer } from "../../../selectors/layers.js";
16
21
  import { resolveLegendGraphicPlacement } from
17
22
  "../../../materialization/graphicHierarchy.js";
18
23
  import {
24
+ ITEM_LEGEND_LABELS as SIZE_LEGEND_LABELS,
25
+ ITEM_LEGEND_TITLE_STYLE as SIZE_LEGEND_TITLE_STYLE,
26
+ normalizeItemLegendConfig,
27
+ ITEM_LEGEND_OPTIONS as SIZE_OPTIONS,
19
28
  assertLegendBoundsInsideCanvas,
20
29
  materializeItemLegend,
21
30
  createItemLegendGraphics,
22
- normalizeItemLegendLayout,
23
- normalizeLegendBorder,
24
- normalizeLegendTextOptions,
25
- normalizeLegendTitleOptions,
26
31
  resolveContinuousBounds,
27
32
  resolveLegendBackgroundFromBounds,
28
33
  formatContinuousValues,
@@ -35,23 +40,11 @@ import {
35
40
  resolveLegendSampleValues
36
41
  } from "./sampling.js";
37
42
  import { resolveEffectiveLegendBlockConfig } from "./blocks.js";
43
+ import { normalizeLegendOverflow } from "./categorical/options.js";
38
44
 
39
- const SIZE_OPTIONS = Object.freeze(["target", "count", "values", "position", "layout", "align",
40
- "direction", "columns", "titlePosition", "offset", "itemGap", "title", "labels", "titleStyle", "border"]);
41
45
 
42
- export const SIZE_LEGEND_LABELS = Object.freeze({
43
- offset: 12,
44
- color: DEFAULT_COLORS.text,
45
- fontSize: 12,
46
- fontFamily: DEFAULT_FONT_FAMILY,
47
- fontWeight: "normal"
48
- });
49
- export const SIZE_LEGEND_TITLE_STYLE = Object.freeze({
50
- color: DEFAULT_COLORS.strongText,
51
- fontSize: 13,
52
- fontFamily: DEFAULT_FONT_FAMILY,
53
- fontWeight: 600
54
- });
46
+
47
+ export { SIZE_LEGEND_LABELS, SIZE_LEGEND_TITLE_STYLE };
55
48
 
56
49
  export function isSizeLegendPoint(layer) {
57
50
  return layer?.mark?.type === "point" &&
@@ -79,6 +72,8 @@ function requireScale(program, id) {
79
72
  }
80
73
 
81
74
  export function resolveSizeLegendLayout(program, config) {
75
+ validateLegendClip("size",config);
76
+ config = intrinsicLegendConfig(program, config);
82
77
  const scale = requireScale(program, config.scale);
83
78
  const categorical = [
84
79
  program.guideConfigs.legend?.series,
@@ -96,23 +91,38 @@ export function resolveSizeLegendLayout(program, config) {
96
91
  });
97
92
  const labels = effective.labels;
98
93
  const titleStyle = effective.titleStyle;
99
- const position = categorical?.position ?? config.position;
100
- if (categorical?.layout === "legacy-bottom") {
94
+ // Group ownership replaces placement inheritance, never shared typography.
95
+ const placementCategorical = config.blockLocalPosition === undefined && legendGroup(program, config.target) === undefined &&
96
+ (config.inheritPosition !== false || categorical?.position === config.position)
97
+ ? categorical : undefined;
98
+ const position = placementCategorical?.position ?? config.position;
99
+ if (placementCategorical?.layout === "legacy-bottom") {
101
100
  throw new Error('Combined size legends require layout "edge".');
102
101
  }
103
- const horizontal = categorical !== undefined && ["top", "bottom"].includes(position);
102
+ const horizontal = placementCategorical !== undefined && ["top", "bottom"].includes(position);
104
103
  const geometry = horizontal ? {
105
104
  position, align: categorical.align, direction: categorical.direction,
106
105
  columns: categorical.columns, titlePosition: categorical.titlePosition,
107
106
  offset: categorical.offset, itemGap: effective.blockGap ?? categorical.itemGap
108
107
  } : { position };
108
+ const ordinalSize = scale.type === "ordinal";
109
+ if (ordinalSize && config.labels.format !== undefined && config.labels.format !== "auto") {
110
+ throw new Error("Categorical size legends retain original category labels.");
111
+ }
109
112
  const discrete = isDiscreteSizeScaleType(scale.type);
110
- if (discrete && readLegendSampling(config).mode === "values") {
113
+ if ((discrete || ordinalSize) && readLegendSampling(config).mode === "values") {
111
114
  throw new Error("Discrete size legends do not support exact values.");
112
115
  }
113
- const values = discrete
116
+ const allValues = discrete
114
117
  ? undefined
115
- : resolveLegendSampleValues(effective, scale, "Size legend");
118
+ : ordinalSize ? scale.domain : resolveLegendSampleValues(effective, scale, "Size legend");
119
+ const hiddenCount = ordinalSize && effective.overflow &&
120
+ allValues.length > effective.overflow.maxItems
121
+ ? allValues.length - effective.overflow.maxItems
122
+ : 0;
123
+ const values = hiddenCount > 0
124
+ ? allValues.slice(0, effective.overflow.maxItems)
125
+ : allValues;
116
126
  const areas = discrete
117
127
  ? scale.range
118
128
  : mapSizeValues(values, scale);
@@ -120,38 +130,46 @@ export function resolveSizeLegendLayout(program, config) {
120
130
  const radius = Math.max(...radii);
121
131
  const width = Math.max(32, radius * 2);
122
132
  const { plot, canvas } = resolveContinuousBounds(program);
123
- const text = discrete
133
+ const itemText = discrete
124
134
  ? formatDiscretizedIntervals(scale.thresholds, config.labels.format)
125
- : formatContinuousValues(
135
+ : ordinalSize ? values.map(formatVisibleText) : formatContinuousValues(
126
136
  values,
127
137
  scale.domain,
128
138
  "quantitative",
129
139
  config.labels.format
130
140
  );
141
+ const mappedText = !discrete && !ordinalSize && effective.labelMap !== undefined
142
+ ? itemText.map((label, index) => resolveDisplayLabel(values[index], effective.labelMap, () => label))
143
+ : itemText;
144
+ const text = hiddenCount > 0 ? [...mappedText, `…${hiddenCount} entries`] : mappedText;
131
145
  const symbolStroke = effective.blockSymbol?.strokeWidth ?? 0;
132
146
  const symbolExtent = symbolStroke / 2;
133
147
  const sampleWidth = width + symbolStroke;
134
148
  const layout = resolveLegendItemLayout(plot, { ...effective, ...geometry, labels, titleStyle }, text, {
135
- width: sampleWidth, height: radius * 2 + symbolStroke,
136
- itemBounds: radii.map(r => ({
149
+ width: sampleWidth, height: Math.min(radius * 2 + symbolStroke, effective.clipHeight??Infinity),
150
+ itemBounds: radii.map(r => clippedSampleBounds({
137
151
  left: sampleWidth / 2 - r - symbolExtent,
138
152
  right: sampleWidth / 2 + r + symbolExtent,
139
153
  top: -r - symbolExtent,
140
154
  bottom: r + symbolExtent
141
- }))
155
+ },effective.clipHeight))
142
156
  }, undefined, program.materializationConfigs.textMetrics);
143
157
  assertLegendBoundsInsideCanvas(layout.bounds, canvas, "Size legend layout", { ...effective, ...geometry });
144
158
  const background = resolveLegendBackgroundFromBounds(layout.bounds, effective.border, canvas, "Size legend", { ...effective, ...geometry });
145
- return { ...layout, symbolX: layout.symbolX.map(x => x + sampleWidth / 2), radii, text,
146
- labels, titleStyle, background, config: effective };
159
+ return { ...layout, symbolX: layout.symbolX.slice(0, radii.length).map(x => x + sampleWidth / 2),
160
+ symbolY: layout.itemY.slice(0, radii.length), radii, text,
161
+ ...(effective.labelMap === undefined ? {} : {
162
+ labelItemIndices: text.flatMap((label, index) => label === "" ? [] : [index])
163
+ }), labels, titleStyle, background, config: effective };
147
164
  }
148
165
 
149
166
  export const rematerializeSizeLegend = /* @__PURE__ */ closedAction(
150
167
  {
151
168
  op: "rematerializeSizeLegend",
152
- description: "Rematerialize a quantitative point-size legend."
169
+ description: "Rematerialize a point-size legend."
153
170
  }, [],
154
171
  function (args = {}) {
172
+ if(hasLegendClipGraphics(this))return this.rematerializeLegend();
155
173
  const config = this.guideConfigs.legend?.size;
156
174
  if (config === undefined) throw new Error("Size legend requires stored configuration.");
157
175
  const layer = findLayer(this, config.target);
@@ -171,13 +189,14 @@ export const rematerializeSizeLegend = /* @__PURE__ */ closedAction(
171
189
  return materializeItemLegend(this, "size", currentConfig, layout, {
172
190
  text: layout.text, labels: layout.labels, titleStyle: layout.titleStyle,
173
191
  symbols: {
174
- x: layout.symbolX, y: layout.itemY, radius: layout.radii,
192
+ x: layout.symbolX, y: layout.symbolY, radius: layout.radii,
175
193
  fill: layout.config.blockSymbol?.fill ?? DEFAULT_COLORS.sizeSymbol,
176
194
  opacity: layout.config.blockSymbol?.opacity ?? 0.7
177
195
  },
178
196
  optional: {
179
197
  stroke: layout.config.blockSymbol?.stroke,
180
- strokeWidth: layout.config.blockSymbol?.strokeWidth
198
+ strokeWidth: layout.config.blockSymbol?.strokeWidth ??
199
+ (layout.config.blockSymbol?.stroke === undefined ? undefined : 0)
181
200
  }
182
201
  });
183
202
  }
@@ -186,14 +205,16 @@ export const rematerializeSizeLegend = /* @__PURE__ */ closedAction(
186
205
 
187
206
  export function resolveSizeLegendConfig(program, args = {}) {
188
207
  validateKeys(args, [...SIZE_OPTIONS, "inheritAppearance"], "createSizeLegend");
189
- if (args.title !== undefined) validateNonEmptyString(args.title, "Legend title");
190
208
  const layer = resolveSizeLegendPoint(program, args.target);
191
209
  const encoding = layer.encoding?.size;
192
210
  if (encoding?.scale === undefined) {
193
211
  throw new Error(`Point mark "${layer.id}" requires a size encoding.`);
194
212
  }
195
213
  const scale = requireScale(program, encoding.scale);
196
- const discrete = isDiscreteSizeScaleType(scale.type);
214
+ const discrete = isEnumeratedSizeScaleType(scale.type);
215
+ if (args.overflow !== undefined && scale.type !== "ordinal") {
216
+ throw new Error("Size legend overflow requires an ordinal scale.");
217
+ }
197
218
  if (discrete && (Object.hasOwn(args, "count") || Object.hasOwn(args, "values"))) {
198
219
  throw new Error("Discrete size legends do not support count or exact values.");
199
220
  }
@@ -201,20 +222,17 @@ export function resolveSizeLegendConfig(program, args = {}) {
201
222
  operation: "create",
202
223
  label: "Size legend"
203
224
  });
204
- const count = discrete ? scale.range.length : undefined;
225
+ const count = discrete ? (scale.type === "ordinal" ? scale.domain.length : scale.range.length) : undefined;
205
226
  return {
206
227
  target: layer.id,
207
228
  scale: encoding.scale,
208
- ...normalizeItemLegendLayout({ ...args, itemGap: args.itemGap ?? 40 }),
209
- title: args.title ?? encoding.field,
210
- inferredTitle: args.title === undefined,
229
+ ...normalizeItemLegendConfig(args, encoding, 40),
230
+ ...(args.overflow === undefined ? {} : { overflow: normalizeLegendOverflow(args.overflow) }),
211
231
  domain: scale.domain,
212
232
  ...(discrete ? { count } : { sampling }),
213
233
  inheritAppearance: args.inheritAppearance === true,
214
- labels: normalizeLegendTextOptions(args.labels, "createLegend.labels", SIZE_LEGEND_LABELS),
215
- titleStyle: normalizeLegendTitleOptions(args.titleStyle, "createLegend.titleStyle", SIZE_LEGEND_TITLE_STYLE),
216
- border: normalizeLegendBorder(args.border),
217
- titleVisible: true
234
+ inheritPosition: args.position === undefined,
235
+
218
236
  };
219
237
  }
220
238
 
@@ -1,6 +1,7 @@
1
+ import {validateLegendClip} from "../../../grammar/legendClip.js";
2
+ import { intrinsicLegendConfig } from "../../../materialization/legendGroups.js";
1
3
  import { action, closedAction } from "../../../core/action.js";
2
4
  import {
3
- validateNonEmptyString,
4
5
  validateKeys
5
6
  } from "../../../core/validation.js";
6
7
  import { mapContinuousScaleValues } from "../../../grammar/scales/index.js";
@@ -8,16 +9,16 @@ import { resolveLegendItemLayout } from "../../../layout/legendItems.js";
8
9
  import { resolveLegendGraphicPlacement } from
9
10
  "../../../materialization/graphicHierarchy.js";
10
11
  import { findLayer } from "../../../selectors/layers.js";
11
- import { DEFAULT_COLORS, DEFAULT_FONT_FAMILY } from
12
+ import { DEFAULT_COLORS } from
12
13
  "../../../theme/defaults.js";
13
14
  import {
15
+ ITEM_LEGEND_LABELS as STROKE_WIDTH_LEGEND_LABELS,
16
+ ITEM_LEGEND_TITLE_STYLE as STROKE_WIDTH_LEGEND_TITLE_STYLE,
17
+ normalizeItemLegendConfig,
18
+ ITEM_LEGEND_OPTIONS as OPTIONS,
14
19
  assertLegendBoundsInsideCanvas,
15
20
  materializeItemLegend,
16
21
  createItemLegendGraphics,
17
- normalizeItemLegendLayout,
18
- normalizeLegendBorder,
19
- normalizeLegendTextOptions,
20
- normalizeLegendTitleOptions,
21
22
  resolveContinuousBounds,
22
23
  resolveLegendBackgroundFromBounds,
23
24
  formatContinuousValues,
@@ -29,29 +30,16 @@ import {
29
30
  } from "./sampling.js";
30
31
  import { resolveEffectiveLegendBlockConfig } from "./blocks.js";
31
32
 
32
- const OPTIONS = Object.freeze(["target", "count", "values", "position", "layout", "align",
33
- "direction", "columns", "titlePosition", "offset", "itemGap", "title", "labels", "titleStyle", "border"]);
34
33
 
35
- export const STROKE_WIDTH_LEGEND_LABELS = Object.freeze({
36
- offset: 12,
37
- color: DEFAULT_COLORS.text,
38
- fontSize: 12,
39
- fontFamily: DEFAULT_FONT_FAMILY,
40
- fontWeight: "normal"
41
- });
42
34
 
43
- export const STROKE_WIDTH_LEGEND_TITLE_STYLE = Object.freeze({
44
- color: DEFAULT_COLORS.strongText,
45
- fontSize: 13,
46
- fontFamily: DEFAULT_FONT_FAMILY,
47
- fontWeight: 600
48
- });
35
+ export { STROKE_WIDTH_LEGEND_LABELS, STROKE_WIDTH_LEGEND_TITLE_STYLE };
49
36
 
50
37
  export function isStrokeWidthLegendLayer(layer) {
51
38
  return ["line", "rule"].includes(layer?.mark?.type) &&
52
39
  layer.encoding?.strokeWidth?.scale !== undefined;
53
40
  }
54
41
 
42
+
55
43
  function resolveLayer(program, requested) {
56
44
  const layer = selectLegendLayer(
57
45
  program,
@@ -77,6 +65,8 @@ function requireScale(program, id) {
77
65
  }
78
66
 
79
67
  export function resolveStrokeWidthLegendLayout(program, config) {
68
+ validateLegendClip("strokeWidth", config);
69
+ config = intrinsicLegendConfig(program, config);
80
70
  const effective = resolveEffectiveLegendBlockConfig(program, "strokeWidth", config);
81
71
  const scale = requireScale(program, config.scale);
82
72
  const { plot, canvas } = resolveContinuousBounds(program);
@@ -134,7 +124,6 @@ export const rematerializeStrokeWidthLegend = /* @__PURE__ */ closedAction(
134
124
 
135
125
  export function resolveStrokeWidthLegendConfig(program, args = {}) {
136
126
  validateKeys(args, OPTIONS, "createStrokeWidthLegend");
137
- if (args.title !== undefined) validateNonEmptyString(args.title, "Legend title");
138
127
  const layer = resolveLayer(program, args.target);
139
128
  const encoding = layer.encoding.strokeWidth;
140
129
  requireScale(program, encoding.scale);
@@ -145,14 +134,9 @@ export function resolveStrokeWidthLegendConfig(program, args = {}) {
145
134
  return {
146
135
  target: layer.id,
147
136
  scale: encoding.scale,
148
- ...normalizeItemLegendLayout({ ...args, itemGap: args.itemGap ?? 32 }),
149
- title: args.title ?? encoding.field,
150
- inferredTitle: args.title === undefined,
137
+ ...normalizeItemLegendConfig(args, encoding, 32),
151
138
  sampling,
152
- labels: normalizeLegendTextOptions(args.labels, "createLegend.labels", STROKE_WIDTH_LEGEND_LABELS),
153
- titleStyle: normalizeLegendTitleOptions(args.titleStyle, "createLegend.titleStyle", STROKE_WIDTH_LEGEND_TITLE_STYLE),
154
- border: normalizeLegendBorder(args.border),
155
- titleVisible: true
139
+
156
140
  };
157
141
  }
158
142
 
@@ -3,18 +3,8 @@ import {
3
3
  LEGEND_CHANNELS,
4
4
  LEGEND_CONFIG_KINDS
5
5
  } from "../../../core/vocabulary.js";
6
- import { isDiscreteSizeScaleType } from "../../../grammar/scales/index.js";
7
-
8
- export function validateLegendChannels(channels, operation) {
9
- if (!Array.isArray(channels)) throw new TypeError(`${operation} channels must be an array.`);
10
- if (channels.length === 0) throw new Error(`${operation} channels must select at least one channel.`);
11
- const seen = new Set();
12
- for (const channel of channels) {
13
- if (!LEGEND_CHANNELS.includes(channel)) throw new Error(`Unsupported legend channel "${channel}".`);
14
- if (seen.has(channel)) throw new Error(`${operation} channels contains duplicate "${channel}".`);
15
- seen.add(channel);
16
- }
17
- }
6
+ import { validateLegendChannels, describeLegendBlock } from "../../../grammar/legendBlocks.js";
7
+ export { validateLegendChannels, describeLegendBlock, legendBlockKey, legendBlockChannels } from "../../../grammar/legendBlocks.js";
18
8
 
19
9
  export function legendTargets(program) {
20
10
  return [...new Set(Object.values(program.guideConfigs.legend ?? {})
@@ -40,73 +30,6 @@ export function resolveLegendTarget(program, requested, operation) {
40
30
  return targets[0];
41
31
  }
42
32
 
43
- const CATEGORICAL_KINDS = Object.freeze(["series", "color", "stroke"]);
44
- const SINGLE_CHANNELS = Object.freeze({
45
- size: "size",
46
- opacity: "opacity",
47
- strokeWidth: "strokeWidth",
48
- gradient: "color",
49
- interval: "color",
50
- strokeGradient: "stroke",
51
- strokeInterval: "stroke"
52
- });
53
-
54
- const asciiCompare = (left, right) => left < right ? -1 : left > right ? 1 : 0;
55
-
56
- export function legendBlockChannels(kind, config) {
57
- const channels = CATEGORICAL_KINDS.includes(kind)
58
- ? config?.channels
59
- : SINGLE_CHANNELS[kind] === undefined
60
- ? undefined
61
- : [SINGLE_CHANNELS[kind]];
62
- if (!Array.isArray(channels) || channels.length === 0) {
63
- throw new Error(`Legend block ${kind} requires at least one channel.`);
64
- }
65
- validateLegendChannels(channels, `Legend block ${kind}`);
66
- return Object.freeze([...channels].sort(asciiCompare));
67
- }
68
-
69
- export function legendBlockKey(channels) {
70
- if (!Array.isArray(channels) || channels.length === 0) {
71
- throw new TypeError("Legend block key requires a non-empty channel array.");
72
- }
73
- return JSON.stringify([...channels].sort(asciiCompare));
74
- }
75
-
76
- export function describeLegendBlock(program, kind, config) {
77
- if (!LEGEND_CONFIG_KINDS.includes(kind)) {
78
- throw new Error(`Unknown legend block kind "${kind}".`);
79
- }
80
- const channels = legendBlockChannels(kind, config);
81
- const scaleIds = CATEGORICAL_KINDS.includes(kind)
82
- ? config?.scales
83
- : [config?.scale];
84
- if (!Array.isArray(scaleIds) || scaleIds.length === 0 ||
85
- scaleIds.some(id => typeof id !== "string" || program.resolvedScales[id] === undefined)) {
86
- throw new Error(`Legend block ${kind} requires resolved scales.`);
87
- }
88
- const family = CATEGORICAL_KINDS.includes(kind)
89
- ? "categorical"
90
- : kind === "size"
91
- ? isDiscreteSizeScaleType(program.resolvedScales[scaleIds[0]].type)
92
- ? "discrete-size"
93
- : "sampled"
94
- : ["opacity", "strokeWidth"].includes(kind)
95
- ? "sampled"
96
- : ["gradient", "strokeGradient"].includes(kind)
97
- ? "gradient"
98
- : "interval";
99
- return Object.freeze({
100
- target: config.target,
101
- kind,
102
- family,
103
- key: legendBlockKey(channels),
104
- channels,
105
- scaleIds: Object.freeze([...scaleIds]),
106
- config
107
- });
108
- }
109
-
110
33
  export function legendBlockDescriptors(program, target) {
111
34
  const requested = validateUserId(target, "Legend target id");
112
35
  const descriptors = LEGEND_CONFIG_KINDS.flatMap(kind => {
@@ -1,3 +1,5 @@
1
+ import { legendResourcePolicy } from "../../../materialization/guides/resources.js";
2
+ import { isContinuousColorScaleType } from "../../../grammar/scales/types.js";
1
3
  import { isDiscretizedColorScaleType } from "../../../grammar/scales/types.js";
2
4
  import { normalizeContinuousLegend } from "./continuous/common.js";
3
5
  import { DEFAULT_GRADIENT_SIZE } from "./continuous/gradient.js";
@@ -8,8 +10,9 @@ import {
8
10
  resolveLegendBlock
9
11
  } from "./target.js";
10
12
  import { validateLegendBlockOverride } from "./blocks.js";
13
+ import { DEFAULT_VERTICAL_LEGEND_ITEM_GAP } from "../../../theme/defaults.js";
11
14
 
12
- const family = (channel, type) => type === "sequential"
15
+ const family = (channel, type) => isContinuousColorScaleType(type)
13
16
  ? channel === "stroke" ? "strokeGradient" : "gradient"
14
17
  : isDiscretizedColorScaleType(type)
15
18
  ? channel === "stroke" ? "strokeInterval" : "interval"
@@ -21,7 +24,7 @@ const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
21
24
  function categoricalFamilyCustom(program, kind, stored) {
22
25
  const side = ["left", "right"].includes(stored.position);
23
26
  const direction = side ? "vertical" : "horizontal";
24
- const itemGap = side ? 28 : stored.position === "top" ? 24 : 20;
27
+ const itemGap = side ? DEFAULT_VERTICAL_LEGEND_ITEM_GAP : stored.position === "top" ? 24 : 20;
25
28
  const order = program.semanticSpec.guides.legend?.[kind]?.order;
26
29
  return stored.inferredSymbol !== true ||
27
30
  stored.direction !== direction ||
@@ -40,7 +43,9 @@ function planChannelLegendTransition(program, scale, nextType, channel) {
40
43
  if (overrideKeys.some(key => key !== oldDescriptor.key)) {
41
44
  throw new Error(`${channel} legend transition found stale legend block overrides.`);
42
45
  }
43
- const blockOverride = stored.blockOverrides?.[oldDescriptor.key];
46
+ const storedOverride = stored.blockOverrides?.[oldDescriptor.key];
47
+ const { symbol: _sampleSymbol, labelMap: _sampleMap, gap: _sampleGap, ...commonOverride } = storedOverride ?? {};
48
+ const blockOverride = stored.representation === "symbols" && storedOverride !== undefined ? commonOverride : storedOverride;
44
49
  const side = ["left", "right"].includes(stored.position);
45
50
  const categorical = ["color", "stroke"].includes(from);
46
51
  const gradientFamily = from.toLowerCase().endsWith("gradient");
@@ -49,10 +54,11 @@ function planChannelLegendTransition(program, scale, nextType, channel) {
49
54
  const comparableSymbol = channel === "stroke" && stored.inferredStrokeWidth === true
50
55
  ? { ...stored.symbol, strokeWidth: defaults.symbol?.strokeWidth }
51
56
  : stored.symbol;
52
- const custom = categorical
57
+ const sampledColor = from === "gradient" && stored.representation === "symbols";
58
+ const custom = sampledColor ? false : categorical
53
59
  ? categoricalFamilyCustom(program, from, stored)
54
60
  : gradientFamily
55
- ? stored.count !== defaults.count || !same(stored.gradient, DEFAULT_GRADIENT_SIZE) || stored.itemGap !== defaults.itemGap
61
+ ? stored.gradientDirection !== undefined || stored.count !== defaults.count || !same({ ...stored.gradient, opacity: stored.gradient?.opacity ?? 1 }, { ...DEFAULT_GRADIENT_SIZE, opacity: 1 }) || stored.itemGap !== defaults.itemGap
56
62
  : !same(comparableSymbol, defaults.symbol) || stored.itemGap !== defaults.itemGap ||
57
63
  stored.direction !== defaults.direction || (stored.columns !== undefined && !(side && stored.columns === 1));
58
64
  if (custom) {
@@ -70,7 +76,7 @@ function planChannelLegendTransition(program, scale, nextType, channel) {
70
76
  const args = {
71
77
  target: stored.target, channels: [channel], position: stored.position,
72
78
  labels: stored.labels, titleStyle: stored.titleStyle, border: stored.border,
73
- align: stored.align, offset: stored.offset, titlePosition: stored.titlePosition,
79
+ align: stored.align, offset: stored.offset, titlePosition: sampledColor ? "top" : stored.titlePosition,
74
80
  ...(stored.inferredTitle ? {} : { title: stored.title })
75
81
  };
76
82
  try {
@@ -83,7 +89,7 @@ function planChannelLegendTransition(program, scale, nextType, channel) {
83
89
  } catch (error) {
84
90
  throw new Error(`${channel} legend transition has incompatible layout or style: ${error.message}`);
85
91
  }
86
- return { args, channel, titleVisible: stored.titleVisible !== false, blockOverride };
92
+ return { args, channel, titleVisible: stored.titleVisible !== false, inferredOffset: stored.inferredOffset, blockOverride };
87
93
  }
88
94
 
89
95
  export function planColorLegendTransitions(program, scale, nextType) {
@@ -95,6 +101,11 @@ export function planColorLegendTransitions(program, scale, nextType) {
95
101
 
96
102
  export function applyColorLegendTransition(program, plan) {
97
103
  let next = program.createLegend(plan.args);
104
+ for (const [kind, config] of Object.entries(next.guideConfigs.legend ?? {})) {
105
+ if (config.target === plan.args.target && legendResourcePolicy(kind).semanticKind === plan.channel) {
106
+ next = next._withLegendConfig(kind, { ...config, inferredOffset: plan.inferredOffset === true });
107
+ }
108
+ }
98
109
  if (!plan.titleVisible) {
99
110
  next = next.editLegend({ target: plan.args.target, title: false });
100
111
  }
@@ -150,7 +161,7 @@ function withoutBlockOverrides(config) {
150
161
  return rest;
151
162
  }
152
163
 
153
- export function planLegendBlockTransitions(program, target, plans) {
164
+ export function planLegendBlockTransitions(program, target, plans, position) {
154
165
  const oldDescriptors = legendBlockDescriptors(program, target);
155
166
  const nextDescriptors = plans.map(plan =>
156
167
  describeLegendBlock(program, plan.kind, plan.config));
@@ -158,19 +169,41 @@ export function planLegendBlockTransitions(program, target, plans) {
158
169
  const descriptor = nextDescriptors[index];
159
170
  const incoming = oldDescriptors.filter(old => intersects(old, descriptor));
160
171
  let override;
161
- if (incoming.length === 1 && incoming[0].key === descriptor.key) {
172
+ if (["size-shape","color-size"].includes(descriptor.config.combine) && incoming.length > 1) {
173
+ const positions = incoming.map(old=>old.config.blockOverrides?.[old.key]?.position);
174
+ if (position === undefined && !positions.every(value=>value===positions[0])) transitionError(target, incoming, descriptor, "position");
175
+ override = position === undefined && positions[0] !== undefined ? {position: positions[0]} : undefined;
176
+ } else if (incoming.length === 1 && incoming[0].key === descriptor.key) {
162
177
  override = incoming[0].config.blockOverrides?.[incoming[0].key];
178
+ if (incoming[0].config.representation === "symbols" && descriptor.kind === "gradient" && descriptor.config.representation !== "symbols" && override !== undefined) {
179
+ const { symbol: _symbol, labelMap: _labelMap, ...common } = override; override = common;
180
+ }
163
181
  } else if (incoming.length === 1) {
164
182
  const old = incoming[0];
165
183
  override = old.config.blockOverrides?.[old.key];
184
+ if (["size-shape","color-size"].includes(incoming[0].config.combine) && override !== undefined) {
185
+ const {title: _title, ...rest} = override; override = Object.keys(rest).length ? rest : undefined;
186
+ }
187
+ if(old.config.combine==='color-size'&&descriptor.kind==='gradient'&&override!==undefined){
188
+ const {symbol:_symbol,labelMap:_map,gap:_gap,...common}=override;override=Object.keys(common).length?common:undefined;
189
+ }
190
+ if(old.config.combine==='color-size'&&descriptor.kind==='size'){
191
+ const {type:_type,...style}=old.config.symbol;
192
+ override={...override,symbol:{...style,...override?.symbol}};
193
+ }
166
194
  if (override?.title !== undefined) {
167
195
  transitionError(target, incoming, descriptor, "title");
168
196
  }
169
- if (override?.labelMap !== undefined) {
197
+ if (override?.labelMap !== undefined && !(old.config.combine==='color-size'&&descriptor.kind==='size')) {
170
198
  transitionError(target, incoming, descriptor, "labelMap");
171
199
  }
172
200
  } else if (incoming.length > 1) {
173
- const overrides = incoming.map(old => old.config.blockOverrides?.[old.key]);
201
+ const overrides = incoming.map(old => {
202
+ const override = old.config.blockOverrides?.[old.key];
203
+ if (position === undefined || override === undefined) return override;
204
+ const {position: _local, ...rest} = override;
205
+ return Object.keys(rest).length ? rest : undefined;
206
+ });
174
207
  if (!overrides.every(value => sameBlockValue(value, overrides[0]))) {
175
208
  transitionError(target, incoming, descriptor, "block override");
176
209
  }
@@ -1,3 +1,4 @@
1
+ import { axisThemeTypography } from "../../../theme/state.js";
1
2
  import { action } from "../../../../core/action.js";
2
3
  import { validateNonNegativeFinite } from "../../../../core/validation.js";
3
4
  import {
@@ -99,6 +100,7 @@ function resolveLabelConfig(program, kind, args, resources, previous) {
99
100
  } else {
100
101
  mode = normalizePolarTickMode(program, resources.scale, {}, count);
101
102
  }
103
+ const typography = axisThemeTypography(program.materializationConfigs.theme, "labels");
102
104
  const config = {
103
105
  ...(previous ?? {}),
104
106
  scale: resources.scale,
@@ -110,9 +112,9 @@ function resolveLabelConfig(program, kind, args, resources, previous) {
110
112
  format: args.format ?? previous?.format ??
111
113
  POLAR_AXIS_DEFAULTS.labels.format,
112
114
  color: args.color ?? previous?.color ?? POLAR_AXIS_DEFAULTS.labels.color,
113
- fontSize: args.fontSize ?? previous?.fontSize ??
115
+ fontSize: args.fontSize ?? previous?.fontSize ?? typography.fontSize ??
114
116
  POLAR_AXIS_DEFAULTS.labels.fontSize,
115
- fontFamily: args.fontFamily ?? previous?.fontFamily ??
117
+ fontFamily: args.fontFamily ?? previous?.fontFamily ?? typography.fontFamily ??
116
118
  POLAR_AXIS_DEFAULTS.labels.fontFamily,
117
119
  fontWeight: args.fontWeight ?? previous?.fontWeight ??
118
120
  POLAR_AXIS_DEFAULTS.labels.fontWeight
@@ -1,3 +1,4 @@
1
+ import { axisThemeTypography } from "../../../theme/state.js";
1
2
  import { action } from "../../../../core/action.js";
2
3
  import {
3
4
  validateNonEmptyString,
@@ -55,7 +56,8 @@ function validateTitleGeometry(program, kind, config, geometry, text) {
55
56
  }, program.materializationConfigs.textMetrics);
56
57
  }
57
58
 
58
- function resolveTitleConfig(kind, args, resources, previous) {
59
+ function resolveTitleConfig(program, kind, args, resources, previous) {
60
+ const typography = axisThemeTypography(program.materializationConfigs.theme, "title");
59
61
  const config = {
60
62
  ...(previous ?? {}),
61
63
  scale: resources.scale,
@@ -65,9 +67,9 @@ function resolveTitleConfig(kind, args, resources, previous) {
65
67
  ? POLAR_AXIS_DEFAULTS.title.thetaOffset
66
68
  : POLAR_AXIS_DEFAULTS.title.radiusOffset),
67
69
  color: args.color ?? previous?.color ?? POLAR_AXIS_DEFAULTS.title.color,
68
- fontSize: args.fontSize ?? previous?.fontSize ??
70
+ fontSize: args.fontSize ?? previous?.fontSize ?? typography.fontSize ??
69
71
  POLAR_AXIS_DEFAULTS.title.fontSize,
70
- fontFamily: args.fontFamily ?? previous?.fontFamily ??
72
+ fontFamily: args.fontFamily ?? previous?.fontFamily ?? typography.fontFamily ??
71
73
  POLAR_AXIS_DEFAULTS.title.fontFamily,
72
74
  fontWeight: args.fontWeight ?? previous?.fontWeight ??
73
75
  POLAR_AXIS_DEFAULTS.title.fontWeight
@@ -106,7 +108,7 @@ function makeEditTitle(kind) {
106
108
  scale: previous.scale,
107
109
  coordinate: previous.coordinate
108
110
  };
109
- const config = resolveTitleConfig(kind, args, resources, previous);
111
+ const config = resolveTitleConfig(this, kind, args, resources, previous);
110
112
  const text = Object.hasOwn(args, "text")
111
113
  ? validateNonEmptyString(args.text, "Polar axis title text")
112
114
  : config.inferredText
@@ -150,7 +152,7 @@ function makeCreateTitle(kind) {
150
152
  }
151
153
  const resources = componentResources(this, kind, args, operation.create);
152
154
  const angle = resolveAngle(this, kind, args);
153
- const config = resolveTitleConfig(kind, args, resources);
155
+ const config = resolveTitleConfig(this, kind, args, resources);
154
156
  const text = validateNonEmptyString(
155
157
  args.text ?? inferAxisTitleText(this, names.channel, resources.scale),
156
158
  "Polar axis title text"