ggaction 0.0.20 → 0.0.22

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 (254) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/README.md +3 -2
  3. package/knowledge/action-cards.json +1 -1
  4. package/knowledge/intent-taxonomy.json +1 -1
  5. package/knowledge/mcp-resources.json +1 -1
  6. package/package.json +6 -2
  7. package/src/BasicChartProgram.js +1 -1
  8. package/src/accessibility.js +1 -0
  9. package/src/actions/boxPlots/components.js +1 -96
  10. package/src/actions/boxPlots/create.js +6 -1
  11. package/src/actions/boxPlots/edit.js +8 -0
  12. package/src/actions/boxPlots/materialize.js +3 -0
  13. package/src/actions/boxPlots/options.js +1 -1
  14. package/src/actions/canvas/actions.js +2 -1
  15. package/src/actions/canvas/index.js +3 -0
  16. package/src/actions/canvas/plotBorder.js +2 -1
  17. package/src/actions/canvas/plotOrder.js +14 -0
  18. package/src/actions/charts/area.js +1 -1
  19. package/src/actions/charts/bar.js +4 -4
  20. package/src/actions/charts/density.js +1 -1
  21. package/src/actions/charts/ecdf.js +1 -1
  22. package/src/actions/charts/endpoints.js +54 -5
  23. package/src/actions/charts/heatmap.js +1 -1
  24. package/src/actions/charts/histogram.js +1 -1
  25. package/src/actions/charts/horizon.js +1 -1
  26. package/src/actions/charts/index.js +2 -1
  27. package/src/actions/charts/interval-regression.js +1 -1
  28. package/src/actions/charts/line.js +1 -1
  29. package/src/actions/charts/radar.js +63 -3
  30. package/src/actions/charts/raincloud.js +37 -6
  31. package/src/actions/charts/rug-strip.js +2 -2
  32. package/src/actions/charts/scatter.js +3 -3
  33. package/src/actions/charts/text.js +1 -1
  34. package/src/actions/composition/actions.js +10 -3
  35. package/src/actions/coordinates/actions.js +2 -0
  36. package/src/actions/data/basic.js +6 -0
  37. package/src/actions/data/derived.js +6 -2
  38. package/src/actions/data/edit.js +21 -35
  39. package/src/actions/data/filter.js +1 -376
  40. package/src/actions/data/flatten.js +19 -0
  41. package/src/actions/data/index.js +6 -0
  42. package/src/actions/data/lookup.js +50 -0
  43. package/src/actions/data/raincloudFilter.js +56 -0
  44. package/src/actions/data/shared.js +10 -0
  45. package/src/actions/distributions/revision.js +3 -1
  46. package/src/actions/encodings/angle.js +2 -1
  47. package/src/actions/encodings/appearance.js +38 -13
  48. package/src/actions/encodings/channels.js +14 -3
  49. package/src/actions/encodings/color/continuous.js +26 -10
  50. package/src/actions/encodings/color/index.js +50 -20
  51. package/src/actions/encodings/color/policy.js +57 -4
  52. package/src/actions/encodings/parallel.js +4 -0
  53. package/src/actions/encodings/position/policies/bar.js +10 -6
  54. package/src/actions/encodings/position/policies/line.js +1 -152
  55. package/src/actions/encodings/position/resolve.js +4 -1
  56. package/src/actions/encodings/ranged.js +46 -0
  57. package/src/actions/encodings/remove.js +1 -1
  58. package/src/actions/encodings/ruleAppearance.js +7 -6
  59. package/src/actions/encodings/shared.js +14 -2
  60. package/src/actions/encodings/stroke.js +20 -14
  61. package/src/actions/encodings/strokeDash.js +9 -11
  62. package/src/actions/encodings/text.js +30 -2
  63. package/src/actions/errorBars/edit.js +24 -4
  64. package/src/actions/facets/actions.js +1 -793
  65. package/src/actions/facets/derive.js +34 -5
  66. package/src/actions/facets/replay.js +1 -38
  67. package/src/actions/gradientPlots/components.js +21 -9
  68. package/src/actions/gradientPlots/create.js +4 -2
  69. package/src/actions/gradientPlots/edit.js +18 -1
  70. package/src/actions/gradientPlots/materialize.js +35 -36
  71. package/src/actions/gradientPlots/options.js +1 -1
  72. package/src/actions/gradientPlots/paint.js +13 -7
  73. package/src/actions/guides/applicability.js +2 -0
  74. package/src/actions/guides/axes/axes.js +39 -0
  75. package/src/actions/guides/axes/axis.js +9 -5
  76. package/src/actions/guides/axes/edit.js +11 -8
  77. package/src/actions/guides/axes/labels.js +24 -8
  78. package/src/actions/guides/axes/parallel/policy.js +3 -5
  79. package/src/actions/guides/axes/parallel/resolve.js +4 -3
  80. package/src/actions/guides/axes/policy.js +22 -0
  81. package/src/actions/guides/axes/tickGroups.js +4 -2
  82. package/src/actions/guides/axes/ticks.js +22 -4
  83. package/src/actions/guides/axes/titles.js +19 -6
  84. package/src/actions/guides/facadeAxes.js +3 -0
  85. package/src/actions/guides/grids/grid.js +3 -1
  86. package/src/actions/guides/grids/resolve.js +7 -11
  87. package/src/actions/guides/guides.js +21 -1
  88. package/src/actions/guides/legends/categorical/actions.js +1 -515
  89. package/src/actions/guides/legends/categorical/layout.js +5 -2
  90. package/src/actions/guides/legends/categorical/options.js +4 -0
  91. package/src/actions/guides/legends/categorical/resolve.js +8 -3
  92. package/src/actions/guides/legends/categorical/symbols.js +3 -3
  93. package/src/actions/guides/legends/colorSize.js +2 -0
  94. package/src/actions/guides/legends/continuous/common.js +1 -1
  95. package/src/actions/guides/legends/continuous/gradient.js +1 -360
  96. package/src/actions/guides/legends/continuous/interval.js +1 -1
  97. package/src/actions/guides/legends/continuous/opacity.js +15 -1
  98. package/src/actions/guides/legends/edit.js +1 -1
  99. package/src/actions/guides/legends/focused.js +1 -1
  100. package/src/actions/guides/legends/lane.js +1 -1
  101. package/src/actions/guides/legends/sampling.js +1 -140
  102. package/src/actions/guides/legends/strokeWidth.js +3 -2
  103. package/src/actions/guides/legends/transition.js +19 -2
  104. package/src/actions/guides/polar/axes/facade.js +5 -4
  105. package/src/actions/guides/polar/axes/labels.js +5 -4
  106. package/src/actions/guides/polar/axes/titles.js +5 -4
  107. package/src/actions/guides/polar/resolve.js +2 -1
  108. package/src/actions/guides/tickValues.js +7 -7
  109. package/src/actions/marks/arc/actions.js +5 -7
  110. package/src/actions/marks/area/actions.js +9 -3
  111. package/src/actions/marks/bar/create.js +4 -2
  112. package/src/actions/marks/bar/edit.js +9 -6
  113. package/src/actions/marks/bar/materialize.js +5 -3
  114. package/src/actions/marks/dependentReferences.js +87 -0
  115. package/src/actions/marks/index.js +2 -0
  116. package/src/actions/marks/lifecycle.js +2 -1
  117. package/src/actions/marks/line/actions.js +24 -6
  118. package/src/actions/marks/line/materialize.js +7 -3
  119. package/src/actions/marks/point/create.js +5 -4
  120. package/src/actions/marks/point/edit.js +6 -3
  121. package/src/actions/marks/point/jitter.js +6 -6
  122. package/src/actions/marks/point/materialize.js +36 -39
  123. package/src/actions/marks/rect/actions.js +16 -4
  124. package/src/actions/marks/references.js +183 -35
  125. package/src/actions/marks/rule/actions.js +20 -9
  126. package/src/actions/marks/shared.js +2 -1
  127. package/src/actions/marks/text/actions.js +10 -3
  128. package/src/actions/marks/tick/actions.js +11 -2
  129. package/src/actions/primitives/editGraphics.js +28 -3
  130. package/src/actions/primitives/index.js +9 -0
  131. package/src/actions/primitives/semanticAction.js +13 -7
  132. package/src/actions/primitives/semanticValidation/layer.js +20 -0
  133. package/src/actions/primitives/semanticValidation/scale.js +8 -1
  134. package/src/actions/scales/channels.js +1 -1
  135. package/src/actions/scales/consumers/common.js +19 -1
  136. package/src/actions/scales/consumers/families.js +2 -2
  137. package/src/actions/scales/consumers/seriesLayout.js +4 -3
  138. package/src/actions/scales/edit.js +11 -0
  139. package/src/actions/scales/editPolicy.js +5 -3
  140. package/src/actions/scales/preview.js +2 -1
  141. package/src/actions/scales/quantitativeColor.js +2 -4
  142. package/src/actions/selection/actions.js +10 -1
  143. package/src/actions/theme/reconcile.js +37 -9
  144. package/src/core/compositionState.js +21 -4
  145. package/src/core/datasetReferences.js +17 -0
  146. package/src/core/immutable.js +1 -0
  147. package/src/core/resourceReferences.js +5 -3
  148. package/src/core/validation.js +4 -0
  149. package/src/core/vocabulary.js +24 -2
  150. package/src/grammar/aggregate.js +1 -0
  151. package/src/grammar/arcs.js +1 -1
  152. package/src/grammar/areaSeries.js +1 -1
  153. package/src/grammar/axisTextPlacement.js +1 -1
  154. package/src/grammar/bars/policy.js +2 -2
  155. package/src/grammar/datasetDependencies.js +97 -0
  156. package/src/grammar/datasetSchema.js +31 -1
  157. package/src/grammar/facetHeaders.js +1 -0
  158. package/src/grammar/facets/dependencies.js +6 -2
  159. package/src/grammar/facets/legendIntent.js +6 -2
  160. package/src/grammar/facets/scales.js +27 -7
  161. package/src/grammar/flatten.js +85 -0
  162. package/src/grammar/histogram.js +13 -1
  163. package/src/grammar/horizon.js +1 -494
  164. package/src/grammar/interval.js +4 -3
  165. package/src/grammar/jitter.js +2 -2
  166. package/src/grammar/legendSpacing.js +45 -0
  167. package/src/grammar/lineMissing.js +90 -0
  168. package/src/grammar/lineSeries.js +1 -376
  169. package/src/grammar/lookup.js +97 -0
  170. package/src/grammar/markFilter.js +4 -2
  171. package/src/grammar/markLabels.js +8 -3
  172. package/src/grammar/numeric.js +1 -1
  173. package/src/grammar/pathCommands.js +1 -1
  174. package/src/grammar/pathSeries.js +1 -109
  175. package/src/grammar/pointPolygon.js +81 -0
  176. package/src/grammar/pointShapes.js +15 -15
  177. package/src/grammar/rects.js +1 -1
  178. package/src/grammar/regression/derive.js +1 -195
  179. package/src/grammar/scales/appearance.js +1 -1
  180. package/src/grammar/scales/color.js +1 -1
  181. package/src/grammar/scales/colorConsumers.js +1 -63
  182. package/src/grammar/scales/continuous.js +58 -1
  183. package/src/grammar/scales/definition.js +1 -1
  184. package/src/grammar/scales/ordinal.js +5 -2
  185. package/src/grammar/scales/policies.js +3 -3
  186. package/src/grammar/schemas/graphicBounds.js +40 -12
  187. package/src/grammar/schemas/graphicClip.js +23 -0
  188. package/src/grammar/schemas/graphicTree.js +16 -0
  189. package/src/grammar/statisticalReference.js +22 -0
  190. package/src/grammar/text.js +65 -1
  191. package/src/grammar/ticks.js +92 -1
  192. package/src/grammar/transformTopology.js +3 -1
  193. package/src/grammar/transforms.js +24 -0
  194. package/src/inspection.js +1 -495
  195. package/src/inspectionDescriptors.js +1 -1
  196. package/src/layout/canvas.js +1 -1
  197. package/src/layout/facetContentFlow.js +101 -0
  198. package/src/layout/facetHeaderTitles.js +1 -0
  199. package/src/layout/facetTopLane.js +29 -0
  200. package/src/layout/facets.js +1 -1
  201. package/src/layout/legendGroup.js +1 -1
  202. package/src/layout/legendItems.js +26 -10
  203. package/src/layout/legendSpacing.js +66 -0
  204. package/src/layout/plotDrawOrder.js +17 -0
  205. package/src/materialization/bars/histogram.js +8 -2
  206. package/src/materialization/bars/resolve.js +1 -1
  207. package/src/materialization/composition.js +1 -0
  208. package/src/materialization/compositionSnapshot.js +22 -2
  209. package/src/materialization/dataProvenance.js +1 -1
  210. package/src/materialization/facetGuides/layout.js +1 -1
  211. package/src/materialization/facetGuides/placement.js +23 -11
  212. package/src/materialization/facetGuides/preparation.js +43 -24
  213. package/src/materialization/facetHeaders.js +1 -1
  214. package/src/materialization/facetTopLane.js +25 -0
  215. package/src/materialization/facets.js +1 -1
  216. package/src/materialization/graphicHierarchy.js +5 -0
  217. package/src/materialization/graphicTranslation.js +2 -0
  218. package/src/materialization/guides/layout.js +1 -1
  219. package/src/materialization/guides/resources.js +1 -1
  220. package/src/materialization/legends.js +1 -1
  221. package/src/materialization/markClip.js +26 -0
  222. package/src/materialization/marks/capabilities.js +3 -2
  223. package/src/materialization/marks/grouping.js +7 -5
  224. package/src/materialization/marks/index.js +4 -2
  225. package/src/materialization/marks/policies.js +4 -0
  226. package/src/materialization/plotDrawOrder.js +44 -0
  227. package/src/materialization/rect.js +11 -3
  228. package/src/materialization/scaleGuideDependencies.js +13 -5
  229. package/src/materialization/selection/items/common.js +1 -1
  230. package/src/materialization/selection/items/path.js +1 -0
  231. package/src/materialization/selection/policies/index.js +2 -1
  232. package/src/materialization/text.js +10 -5
  233. package/src/mcp/cli.js +0 -0
  234. package/src/persistence/validation.js +108 -18
  235. package/src/persistence.js +4 -2
  236. package/src/renderers/canvas/index.js +16 -0
  237. package/src/renderers/canvas/isolated.js +11 -1
  238. package/src/renderers/canvas/native.js +7 -0
  239. package/src/renderers/canvas/scene.js +66 -0
  240. package/src/renderers/canvas/text.js +3 -2
  241. package/src/renderers/svg.js +1 -617
  242. package/src/renderers/svgSerialization.js +643 -0
  243. package/src/renderers/transitionScene.js +67 -0
  244. package/src/scene.js +89 -0
  245. package/src/selectors/axes.js +1 -1
  246. package/src/selectors/datasets.js +1 -0
  247. package/src/selectors/grids.js +9 -0
  248. package/src/selectors/markClip.js +28 -0
  249. package/src/selectors/raincloud.js +7 -0
  250. package/src/version.js +1 -1
  251. package/types/basic.d.ts +7 -0
  252. package/types/index.d.ts +8 -0
  253. package/types/program.d.ts +270 -60
  254. package/types/scene.d.ts +35 -0
@@ -1,4 +1,5 @@
1
1
  import {findLayer} from '../../../selectors/layers.js';
2
+ import {prepareLegendSpacingEdit} from '../../../grammar/legendSpacing.js';
2
3
  import {findCoordinate} from '../../../selectors/coordinates.js';
3
4
  import {legendBlockChannels} from '../../../grammar/legendBlocks.js';
4
5
  import {mergeLegendTitleStyle} from '../../../grammar/legendTitleStyle.js';
@@ -91,6 +92,7 @@ export function createColorSizeLegendFromConfig(program,config) {
91
92
  }
92
93
 
93
94
  export function resolveColorSizeEdit(program,previous,args) {
95
+ ({previous,args}=prepareLegendSpacingEdit(previous,args));
94
96
  validateOptionObject(args,[...ITEM_LEGEND_OPTIONS.filter(key=>key!=='overflow'),'symbol'],'Color-size legend edit');
95
97
  const {count:_count,values:_values,title:_title,...layoutArgs}=args;
96
98
  const title=args.title==='auto'?previous.field:typeof args.title==='string'?args.title:previous.title;
@@ -1 +1 @@
1
- import{normalizeLegendClipHeight as O}from"../../../../grammar/legendClip.js";import{applyLegendClipGraphics as $}from"../../../../materialization/legendClip.js";import{normalizeGradientDirection as H}from"../../../../grammar/legendGradient.js";import{normalizeLegendBorderDash as R}from"../../../../grammar/legendBorder.js";import{materializeLegendBorder as q}from"../../../../materialization/legendBorder.js";import{LEGEND_TITLE_STYLE_KEYS as M,mergeLegendTitleStyle as V,validateLegendTitleAlignment as v}from"../../../../grammar/legendTitleStyle.js";import{PLOT_LEGEND_POSITIONS as Y,isPlotLegend as m}from"../../../../layout/legend.js";import{isContinuousColorScaleType as K}from"../../../../grammar/scales/types.js";import{isPlainObject as I}from"../../../../core/immutable.js";import{resolveTextBounds as X}from"../../../../core/textMetrics.js";import{resolveLegendTitleText as J}from"../../../../layout/legendText.js";import{materializeLegendTitleLines as Q}from"../../../../materialization/legendText.js";import{validateKeys as G,validateFontWeight as Z,validateOptionObject as z,validateNonEmptyString as y,validateNonNegativeFinite as ee,validatePositiveFinite as te,validateGeneratedItemLimit as ne}from"../../../../core/validation.js";import{formatTimeTick as ie}from"../../../../grammar/ticks.js";import{formatValue as oe,validateValueFormat as C}from"../../../../grammar/valueFormat.js";import{formatDistinctNumericSamples as re,sampleNumericRange as le}from"../../../../grammar/numeric.js";import{resolveGraphicBounds as de,canvasOverflowError as _}from"../../../../layout/canvas.js";import{isHorizontalEdgeLegend as N}from"../../../../layout/legendLane.js";import{DEFAULT_COLORS as h,DEFAULT_FONT_FAMILY as F,DEFAULT_VERTICAL_LEGEND_ITEM_GAP as w}from"../../../../theme/defaults.js";import{findLayer as se}from"../../../../selectors/layers.js";import{isOpacityLegendLayer as ae}from"../../../../materialization/legends.js";import{findCanvasGraphic as ce}from"../../../../materialization/graphicHierarchy.js";import{normalizeLegendSampling as fe}from"../sampling.js";import{editGraphicProperties as p}from"../../../primitives/graphicProperties.js";const pe=["clipHeight","target","channels","position","align","offset","title","count","values","gradient","symbol","labels","titleStyle","itemGap","border","direction","columns","titlePosition"],Re=Object.freeze(["clipHeight","target","count","values","position","layout","align","direction","columns","titlePosition","offset","itemGap","title","labels","titleStyle","border","overflow"]),ue=["offset","color","fontSize","fontFamily","fontWeight","format"],me=["color","lineWidth","padding","background","strokeDash"],P=["right","left","top","bottom",...Y],x={offset:12,color:h.text,fontSize:12,fontFamily:F,fontWeight:"normal"},D={color:h.text,fontSize:13,fontFamily:F,fontWeight:600},ge=Object.freeze(x),he=Object.freeze({...D,color:h.strongText});function B(e,t){return e!==void 0&&e!==!1&&y(e,"Legend title"),{title:e===!1||e===void 0?t:e,inferredTitle:e===!1||e===void 0,titleVisible:e!==!1}}function qe(e,t,n){const i=["right","left"].includes(e.position??"right")||m(e),r=(e.direction??(i?"vertical":"horizontal"))==="vertical"?w:n;return{...e.clipHeight===void 0?{}:{clipHeight:O(e.clipHeight)},...ye({...e,itemGap:e.itemGap??r}),...B(e.title,t.field),labels:T(e.labels,"createLegend.labels",ge),titleStyle:A(e.titleStyle,"createLegend.titleStyle",he),border:j(e.border)}}const Le={color:h.border,lineWidth:1,padding:12,background:"transparent"},E=te,g=ee;function T(e,t,n){if(e===void 0)return{...n};if(!I(e))throw new TypeError(`${t} must be a plain object.`);G(e,ue,t);const i={...n,...e};Object.hasOwn(i,"offset")&&g(i.offset,`${t} offset`),E(i.fontSize,`${t} fontSize`);for(const o of["color","fontFamily"])y(i[o],`${t} ${o}`);return Z(i.fontWeight,`${t} fontWeight`,"finite number"),Object.hasOwn(i,"format")&&(i.format=C(i.format,`${t} format`)),i}function A(e,t,n,i=!1){e!==void 0&&z(e,M,t);const o=V(n,e,{edit:i}),{align:r,...d}=o;return{...T(d,t,{}),...r===void 0?{}:{align:r}}}function j(e){if(e===void 0||e===!1)return!1;if(e!==!0&&!I(e))throw new TypeError("createLegend.border must be a boolean or plain object.");e!==!0&&G(e,me,"createLegend.border");const t={...Le,...e===!0?{}:e};for(const i of["color","background"])y(t[i],`Legend border ${i}`);g(t.lineWidth,"Legend border lineWidth"),g(t.padding,"Legend border padding");const n=R(t.strokeDash);return{...t,...n===void 0?{}:{strokeDash:n}}}function Me(e,t){z(e,pe,"createLegend");const n=e.position??"right";if(!P.includes(n))throw new Error(`Unsupported legend position "${n}".`);const i=e.align??"center";if(!["left","center","right"].includes(i))throw new Error(`Unsupported legend alignment "${i}".`);if(["left","right"].includes(n)&&i!=="center")throw new Error("Side continuous legends require center alignment.");if(t==="gradient"&&Object.hasOwn(e,"values"))throw new Error("Gradient legends do not support exact sample values.");const o=t==="opacity"?fe(e,{operation:"create",label:"Opacity legend"}):void 0,r=t==="gradient"?e.count??5:void 0;if(t==="gradient"){if(!Number.isInteger(r)||r<2)throw new RangeError("Continuous legend count must be an integer of at least 2.");ne(r,"Continuous legend count")}const d=e.offset??(m({position:n})?8:30);g(d,"Legend offset");const l=e.titlePosition??"top";if(v(e.titleStyle,l),!(t==="gradient"?["top","left","bottom"]:["top","left"]).includes(l))throw new Error(`Unsupported legend titlePosition "${l}".`);if(l==="left"&&(!["top","bottom"].includes(n)||t!=="opacity"))throw new Error("Only horizontal opacity legends support left titlePosition.");const a=e.itemGap??(t==="opacity"&&(["right","left"].includes(n)||m({position:n}))?w:l==="left"?20:28);if(E(a,"Legend itemGap"),t==="gradient"){for(const s of["symbol","columns","itemGap"])if(Object.hasOwn(e,s))throw new Error(`Gradient legend does not accept ${s}.`)}else for(const s of["columns","direction","gradient"])if(Object.hasOwn(e,s))throw new Error(`Opacity legend does not accept ${s}.`);return{...e.clipHeight===void 0?{}:{clipHeight:O(e.clipHeight)},target:e.target,position:n,align:i,offset:d,...t==="gradient"?{count:r,...e.direction===void 0?{}:{gradientDirection:H(e.direction)}}:{sampling:o},...B(e.title),labels:T(e.labels,"createLegend.labels",l==="left"?{...x,offset:8}:x),titleStyle:A(e.titleStyle,"createLegend.titleStyle",D),itemGap:a,titlePosition:l,border:j(e.border)}}function U(e,t,n){const i=e.semanticSpec.layers.filter(n);if(t===void 0)return i.length===1?i[0]:void 0;const o=se(e,t);return i.includes(o)?o:void 0}function Ve(e,t,n){const i=U(e,t,n==="opacity"?ae:o=>o.mark?.type==="point"&&o.encoding?.[n]?.scale!==void 0);if(i===void 0)throw new Error(t===void 0?`${n} legend requires one eligible ${n==="opacity"?"point, line or bar":"point"} mark.`:`Unknown ${n} legend target "${t}".`);return i}function Ye(e,t,n="color"){const i=U(e,t,o=>(n==="stroke"?["point","line","area","bar","rect","arc","rule","tick"]:["point","bar","rect","text","area"]).includes(o.mark?.type)&&o.encoding?.[n]?.scale!==void 0&&(n!=="color"||["quantitative","temporal"].includes(o.encoding[n].fieldType)));if(i===void 0)throw new Error(t===void 0?n==="color"?"color legend requires one eligible point, bar, rect, text, or area mark.":"stroke legend requires one eligible mark.":`Unknown ${n} legend target "${t}".`);return i}function Ke(e,t,n){const i=e.resolvedScales[t];if(n==="sequential"?!K(i?.type):i?.type!==n)throw new Error(`Legend requires resolved ${n} scale "${t}".`);return i}function Xe(e,t="Continuous legend layout requires Canvas bounds."){const n=de(e),i=ce(e);if(n===void 0||![n.x,n.y,n.width,n.height].every(Number.isFinite)||i?.type!=="canvas"||!Number.isFinite(i.properties.width)||!Number.isFinite(i.properties.height))throw new Error(t);return{plot:n,canvas:i.properties}}function Je(e,t){return le(e[0],e[1],t,"Continuous legend domain")}function Qe(e,t,n,i="auto"){const o=C(i,"Legend label format");return o==="auto"?n==="temporal"?e.map(r=>ie(r,t)):re(e):e.map(r=>oe(r,{format:o,valueType:n==="temporal"?"temporal":"quantitative",label:"Legend label format"}))}function k(e,t,n,{align:i="left"}={}){const o=p(e,t,{fill:n.color,fontSize:n.fontSize,fontFamily:n.fontFamily,fontWeight:n.fontWeight,textAlign:i,textBaseline:"middle"});return t.endsWith("Title")?Q(o,t,o.graphicSpec.objects[t].properties.text,n):o}function Ze(e,t,n,i,{hardBreaks:o=!1}={}){return X({x:e.x,y:e.y,text:String(t),...n,...o?{lines:J(t,n,i).lines}:{},textAlign:e.align??"left",textBaseline:"middle"},i)}function et(e,t,n,i){if(!(i?.groupIntrinsic||N(i)||["left","right"].includes(i?.position))&&e.some(o=>o.left<0||o.right>t.width||o.top<0||o.bottom>t.height))throw _(`${n} requires more Canvas margin space.`,e,t)}function tt(e,t,n,i,o){if(t===!1)return;const r=t.lineWidth/2;let d=1/0,l=1/0,a=-1/0,s=-1/0;for(const f of e)d=Math.min(d,f.left),l=Math.min(l,f.top),a=Math.max(a,f.right),s=Math.max(s,f.bottom);if(d-=t.padding,l-=t.padding,a+=t.padding,s+=t.padding,!o?.groupIntrinsic&&!N(o)&&!["left","right"].includes(o?.position)&&(d-r<0||l-r<0||a+r>n.width||s+r>n.height))throw _(`${i} background requires more Canvas margin space.`,[{left:d-r,right:a+r,top:l-r,bottom:s+r}],n);return{x:d,y:l,width:a-d,height:s-l}}function be(e,t,n,i){return n===void 0?e:q(e,t,n,i)}function ye(e,{multiColumnSide:t=!1}={}){const n=e.position??"right";if(!P.includes(n))throw new Error(`Unsupported legend position "${n}".`);const i=["left","right"].includes(n)||m({position:n}),o=e.layout===void 0?"edge":e.layout;if(o!=="edge")throw new Error('This legend requires layout "edge".');const r=e.align??"center";if(!["left","center","right"].includes(r))throw new Error(`Unsupported legend alignment "${r}".`);const d=e.direction??(i?"vertical":"horizontal");if(!["horizontal","vertical"].includes(d))throw new Error(`Unsupported legend direction "${d}".`);const l=e.columns;if(l!==void 0&&(!Number.isInteger(l)||l<1))throw new RangeError("Legend columns must be a positive integer.");const a=e.titlePosition??"top";if(v(e.titleStyle,a),!["top","left"].includes(a))throw new Error(`Unsupported legend titlePosition "${a}".`);if(i&&(r!=="center"||a!=="top"||!t&&(d!=="vertical"||l!==void 0&&l!==1)))throw new Error("Side item legends require vertical direction, center alignment, top title and one column.");const s=e.offset??(m({position:n})?8:30),f=e.itemGap??(d==="vertical"?w:28);return g(s,"Legend offset"),E(f,"Legend itemGap"),{position:n,layout:o,align:r,direction:d,columns:l,titlePosition:a,offset:s,itemGap:f}}function nt(e,t,n,i,{symbols:o,optional:r={},text:d=i.labels,labels:l=i.config.labels,titleStyle:a=i.config.titleStyle}){const s=`${t}Legend`,f=Object.values(o).find(Array.isArray)?.length??d.length;let c=e.editSemantic({property:`guide.legend.${t}.scale`,value:n.scale}).editSemantic({property:`guide.legend.${t}.title`,value:n.title})._withLegendConfig(t,n);c=p(c,`${s}Symbols`,{length:f,...o});const L=i.labelItemIndices,b=u=>L===void 0||!Array.isArray(u)?u:L.map(W=>u[W]);c=p(c,`${s}Labels`,{length:L?.length??d.length,x:b(i.labelX),y:b(i.itemY),text:b(d)}),c=be(c,`${s}Background`,i.background,n.border),c=p(c,`${s}Symbols`,Object.fromEntries(Object.entries(r).filter(([,u])=>u!==void 0))),c=k(c,`${s}Labels`,l);const S=i.config;return S.titleVisible===!1?$(c):(c=p(c,`${s}Title`,{x:i.title.x,y:i.title.y,text:S.title}),$(k(c,`${s}Title`,a,{align:i.title.align})))}function it(e,t,n,i,o,r){const d=`${t}Legend`;let l=e.editSemantic({property:`guide.legend.${t}.scale`,value:n.scale}).editSemantic({property:`guide.legend.${t}.title`,value:n.title})._withLegendConfig(t,n);return n.border!==!1&&(l=l.createGraphics({id:`${d}Background`,type:"rect",...r})),l=l.createGraphics({id:`${d}Symbols`,type:o,length:i,...r}).createGraphics({id:`${d}Labels`,type:"text",length:i,...r}),n.titleVisible!==!1&&(l=l.createGraphics({id:`${d}Title`,type:"text",...r})),l}export{ge as ITEM_LEGEND_LABELS,Re as ITEM_LEGEND_OPTIONS,he as ITEM_LEGEND_TITLE_STYLE,et as assertLegendBoundsInsideCanvas,it as createItemLegendGraphics,p as editGraphicProperties,be as editLegendBackground,Qe as formatContinuousValues,nt as materializeItemLegend,Me as normalizeContinuousLegend,B as normalizeInitialLegendTitle,qe as normalizeItemLegendConfig,ye as normalizeItemLegendLayout,j as normalizeLegendBorder,T as normalizeLegendTextOptions,A as normalizeLegendTitleOptions,Ke as requireResolvedLegendScale,Xe as resolveContinuousBounds,Ye as resolveContinuousColorLayer,Ve as resolveContinuousLegendLayer,tt as resolveLegendBackgroundFromBounds,Ze as resolveLegendTextBounds,Je as sampleContinuousValues,U as selectLegendLayer,k as styleContinuousText,g as validateNonNegative,E as validatePositive};
1
+ import{normalizeLegendClipHeight as O}from"../../../../grammar/legendClip.js";import{normalizeLegendSpacing as $}from"../../../../grammar/legendSpacing.js";import{applyLegendClipGraphics as v}from"../../../../materialization/legendClip.js";import{normalizeGradientDirection as R}from"../../../../grammar/legendGradient.js";import{normalizeLegendBorderDash as q}from"../../../../grammar/legendBorder.js";import{materializeLegendBorder as M}from"../../../../materialization/legendBorder.js";import{LEGEND_TITLE_STYLE_KEYS as V,mergeLegendTitleStyle as Y,validateLegendTitleAlignment as I}from"../../../../grammar/legendTitleStyle.js";import{PLOT_LEGEND_POSITIONS as K,isPlotLegend as m}from"../../../../layout/legend.js";import{isContinuousColorScaleType as X}from"../../../../grammar/scales/types.js";import{isPlainObject as G}from"../../../../core/immutable.js";import{resolveTextBounds as J}from"../../../../core/textMetrics.js";import{resolveLegendTitleText as Q}from"../../../../layout/legendText.js";import{materializeLegendTitleLines as Z}from"../../../../materialization/legendText.js";import{validateKeys as z,validateFontWeight as ee,validateOptionObject as C,validateNonEmptyString as y,validateNonNegativeFinite as te,validatePositiveFinite as ie,validateGeneratedItemLimit as ne}from"../../../../core/validation.js";import{formatTimeTick as oe}from"../../../../grammar/ticks.js";import{formatValue as re,validateValueFormat as P}from"../../../../grammar/valueFormat.js";import{formatDistinctNumericSamples as le,sampleNumericRange as de}from"../../../../grammar/numeric.js";import{resolveGraphicBounds as ae,canvasOverflowError as _}from"../../../../layout/canvas.js";import{isHorizontalEdgeLegend as N}from"../../../../layout/legendLane.js";import{DEFAULT_COLORS as h,DEFAULT_FONT_FAMILY as F,DEFAULT_VERTICAL_LEGEND_ITEM_GAP as w}from"../../../../theme/defaults.js";import{findLayer as se}from"../../../../selectors/layers.js";import{isOpacityLegendLayer as ce}from"../../../../materialization/legends.js";import{findCanvasGraphic as fe}from"../../../../materialization/graphicHierarchy.js";import{normalizeLegendSampling as pe}from"../sampling.js";import{editGraphicProperties as p}from"../../../primitives/graphicProperties.js";const ue=["clipHeight","target","channels","position","align","offset","title","count","values","gradient","symbol","labels","titleStyle","itemGap","rowPadding","titlePadding","border","direction","columns","titlePosition"],Me=Object.freeze(["clipHeight","target","count","values","position","layout","align","direction","columns","titlePosition","offset","itemGap","rowPadding","titlePadding","title","labels","titleStyle","border","overflow"]),me=["offset","color","fontSize","fontFamily","fontWeight","format"],ge=["color","lineWidth","padding","background","strokeDash"],D=["right","left","top","bottom",...K],x={offset:12,color:h.text,fontSize:12,fontFamily:F,fontWeight:"normal"},B={color:h.text,fontSize:13,fontFamily:F,fontWeight:600},he=Object.freeze(x),Le=Object.freeze({...B,color:h.strongText});function A(e,t){return e!==void 0&&e!==!1&&y(e,"Legend title"),{title:e===!1||e===void 0?t:e,inferredTitle:e===!1||e===void 0,titleVisible:e!==!1}}function Ve(e,t,i){const n=["right","left"].includes(e.position??"right")||m(e),r=(e.direction??(n?"vertical":"horizontal"))==="vertical"?w:i;return{...e.clipHeight===void 0?{}:{clipHeight:O(e.clipHeight)},...we({...e,itemGap:e.itemGap??r}),...A(e.title,t.field),labels:T(e.labels,"createLegend.labels",he),titleStyle:j(e.titleStyle,"createLegend.titleStyle",Le),border:k(e.border)}}const be={color:h.border,lineWidth:1,padding:12,background:"transparent"},E=ie,g=te;function T(e,t,i){if(e===void 0)return{...i};if(!G(e))throw new TypeError(`${t} must be a plain object.`);z(e,me,t);const n={...i,...e};Object.hasOwn(n,"offset")&&g(n.offset,`${t} offset`),E(n.fontSize,`${t} fontSize`);for(const o of["color","fontFamily"])y(n[o],`${t} ${o}`);return ee(n.fontWeight,`${t} fontWeight`,"finite number"),Object.hasOwn(n,"format")&&(n.format=P(n.format,`${t} format`)),n}function j(e,t,i,n=!1){e!==void 0&&C(e,V,t);const o=Y(i,e,{edit:n}),{align:r,...d}=o;return{...T(d,t,{}),...r===void 0?{}:{align:r}}}function k(e){if(e===void 0||e===!1)return!1;if(e!==!0&&!G(e))throw new TypeError("createLegend.border must be a boolean or plain object.");e!==!0&&z(e,ge,"createLegend.border");const t={...be,...e===!0?{}:e};for(const n of["color","background"])y(t[n],`Legend border ${n}`);g(t.lineWidth,"Legend border lineWidth"),g(t.padding,"Legend border padding");const i=q(t.strokeDash);return{...t,...i===void 0?{}:{strokeDash:i}}}function Ye(e,t){C(e,ue,"createLegend");const i=e.position??"right";if(!D.includes(i))throw new Error(`Unsupported legend position "${i}".`);const n=e.align??"center";if(!["left","center","right"].includes(n))throw new Error(`Unsupported legend alignment "${n}".`);if(["left","right"].includes(i)&&n!=="center")throw new Error("Side continuous legends require center alignment.");const o=t==="opacity"||Object.hasOwn(e,"values")?pe(e,{operation:"create",label:t==="gradient"?"Gradient legend":"Opacity legend"}):void 0,r=t==="gradient"?o?.count??e.count??5:void 0;if(t==="gradient"){if(!Number.isInteger(r)||r<2)throw new RangeError("Continuous legend count must be an integer of at least 2.");ne(r,"Continuous legend count")}const d=e.offset??(m({position:i})?8:30);g(d,"Legend offset");const l=e.titlePosition??"top";if(I(e.titleStyle,l),!(t==="gradient"?["top","left","bottom"]:["top","left"]).includes(l))throw new Error(`Unsupported legend titlePosition "${l}".`);if(l==="left"&&(!["top","bottom"].includes(i)||t!=="opacity"))throw new Error("Only horizontal opacity legends support left titlePosition.");const s=e.itemGap??(t==="opacity"&&(["right","left"].includes(i)||m({position:i}))?w:l==="left"?20:28);if(E(s,"Legend itemGap"),t==="gradient"){for(const a of["symbol","columns","itemGap"])if(Object.hasOwn(e,a))throw new Error(`Gradient legend does not accept ${a}.`)}else for(const a of["columns","direction","gradient"])if(Object.hasOwn(e,a))throw new Error(`Opacity legend does not accept ${a}.`);return{...e.clipHeight===void 0?{}:{clipHeight:O(e.clipHeight)},target:e.target,...$(e),position:i,align:n,offset:d,...t==="gradient"?{count:r,...o?.mode==="values"?{sampling:o}:{},...e.direction===void 0?{}:{gradientDirection:R(e.direction)}}:{sampling:o},...A(e.title),labels:T(e.labels,"createLegend.labels",l==="left"?{...x,offset:8}:x),titleStyle:j(e.titleStyle,"createLegend.titleStyle",B),itemGap:s,titlePosition:l,border:k(e.border)}}function U(e,t,i){const n=e.semanticSpec.layers.filter(i);if(t===void 0)return n.length===1?n[0]:void 0;const o=se(e,t);return n.includes(o)?o:void 0}function Ke(e,t,i){const n=U(e,t,i==="opacity"?ce:o=>o.mark?.type==="point"&&o.encoding?.[i]?.scale!==void 0);if(n===void 0)throw new Error(t===void 0?`${i} legend requires one eligible ${i==="opacity"?"point, line or bar":"point"} mark.`:`Unknown ${i} legend target "${t}".`);return n}function Xe(e,t,i="color"){const n=U(e,t,o=>(i==="stroke"?["point","line","area","bar","rect","arc","rule","tick"]:["point","line","bar","rect","text","area","arc","tick"]).includes(o.mark?.type)&&o.encoding?.[i]?.scale!==void 0&&(i!=="color"||["quantitative","temporal"].includes(o.encoding[i].fieldType)));if(n===void 0)throw new Error(t===void 0?i==="color"?"color legend requires one eligible point, line, bar, rect, text, area, arc, or tick mark.":"stroke legend requires one eligible mark.":`Unknown ${i} legend target "${t}".`);return n}function Je(e,t,i){const n=e.resolvedScales[t];if(i==="sequential"?!X(n?.type):n?.type!==i)throw new Error(`Legend requires resolved ${i} scale "${t}".`);return n}function Qe(e,t="Continuous legend layout requires Canvas bounds."){const i=ae(e),n=fe(e);if(i===void 0||![i.x,i.y,i.width,i.height].every(Number.isFinite)||n?.type!=="canvas"||!Number.isFinite(n.properties.width)||!Number.isFinite(n.properties.height))throw new Error(t);return{plot:i,canvas:n.properties}}function Ze(e,t){return de(e[0],e[1],t,"Continuous legend domain")}function et(e,t,i,n="auto"){const o=P(n,"Legend label format");return o==="auto"?i==="temporal"?e.map(r=>oe(r,t)):le(e):e.map(r=>re(r,{format:o,valueType:i==="temporal"?"temporal":"quantitative",label:"Legend label format"}))}function W(e,t,i,{align:n="left"}={}){const o=p(e,t,{fill:i.color,fontSize:i.fontSize,fontFamily:i.fontFamily,fontWeight:i.fontWeight,textAlign:n,textBaseline:"middle"});return t.endsWith("Title")?Z(o,t,o.graphicSpec.objects[t].properties.text,i):o}function tt(e,t,i,n,{hardBreaks:o=!1}={}){return J({x:e.x,y:e.y,text:String(t),...i,...o?{lines:Q(t,i,n).lines}:{},textAlign:e.align??"left",textBaseline:"middle"},n)}function it(e,t,i,n){if(!(n?.groupIntrinsic||N(n)||["left","right"].includes(n?.position))&&e.some(o=>o.left<0||o.right>t.width||o.top<0||o.bottom>t.height))throw _(`${i} requires more Canvas margin space.`,e,t)}function nt(e,t,i,n,o){if(t===!1)return;const r=t.lineWidth/2;let d=1/0,l=1/0,s=-1/0,a=-1/0;for(const f of e)d=Math.min(d,f.left),l=Math.min(l,f.top),s=Math.max(s,f.right),a=Math.max(a,f.bottom);if(d-=t.padding,l-=t.padding,s+=t.padding,a+=t.padding,!o?.groupIntrinsic&&!N(o)&&!["left","right"].includes(o?.position)&&(d-r<0||l-r<0||s+r>i.width||a+r>i.height))throw _(`${n} background requires more Canvas margin space.`,[{left:d-r,right:s+r,top:l-r,bottom:a+r}],i);return{x:d,y:l,width:s-d,height:a-l}}function ye(e,t,i,n){return i===void 0?e:M(e,t,i,n)}function we(e,{multiColumnSide:t=!1}={}){const i=e.position??"right";if(!D.includes(i))throw new Error(`Unsupported legend position "${i}".`);const n=["left","right"].includes(i)||m({position:i}),o=e.layout===void 0?"edge":e.layout;if(o!=="edge")throw new Error('This legend requires layout "edge".');const r=e.align??"center";if(!["left","center","right"].includes(r))throw new Error(`Unsupported legend alignment "${r}".`);const d=e.direction??(n?"vertical":"horizontal");if(!["horizontal","vertical"].includes(d))throw new Error(`Unsupported legend direction "${d}".`);const l=e.columns;if(l!==void 0&&(!Number.isInteger(l)||l<1))throw new RangeError("Legend columns must be a positive integer.");const s=e.titlePosition??"top";if(I(e.titleStyle,s),!["top","left"].includes(s))throw new Error(`Unsupported legend titlePosition "${s}".`);if(n&&(r!=="center"||s!=="top"||!t&&(d!=="vertical"||l!==void 0&&l!==1)))throw new Error("Side item legends require vertical direction, center alignment, top title and one column.");const a=e.offset??(m({position:i})?8:30),f=e.itemGap??(d==="vertical"?w:28);return g(a,"Legend offset"),E(f,"Legend itemGap"),{position:i,layout:o,align:r,direction:d,columns:l,titlePosition:s,offset:a,itemGap:f,...$(e)}}function ot(e,t,i,n,{symbols:o,optional:r={},text:d=n.labels,labels:l=n.config.labels,titleStyle:s=n.config.titleStyle}){const a=`${t}Legend`,f=Object.values(o).find(Array.isArray)?.length??d.length;let c=e.editSemantic({property:`guide.legend.${t}.scale`,value:i.scale}).editSemantic({property:`guide.legend.${t}.title`,value:i.title})._withLegendConfig(t,i);c=p(c,`${a}Symbols`,{length:f,...o});const L=n.labelItemIndices,b=u=>L===void 0||!Array.isArray(u)?u:L.map(H=>u[H]);c=p(c,`${a}Labels`,{length:L?.length??d.length,x:b(n.labelX),y:b(n.itemY),text:b(d)}),c=ye(c,`${a}Background`,n.background,i.border),c=p(c,`${a}Symbols`,Object.fromEntries(Object.entries(r).filter(([,u])=>u!==void 0))),c=W(c,`${a}Labels`,l);const S=n.config;return S.titleVisible===!1?v(c):(c=p(c,`${a}Title`,{x:n.title.x,y:n.title.y,text:S.title}),v(W(c,`${a}Title`,s,{align:n.title.align})))}function rt(e,t,i,n,o,r){const d=`${t}Legend`;let l=e.editSemantic({property:`guide.legend.${t}.scale`,value:i.scale}).editSemantic({property:`guide.legend.${t}.title`,value:i.title})._withLegendConfig(t,i);return i.border!==!1&&(l=l.createGraphics({id:`${d}Background`,type:"rect",...r})),l=l.createGraphics({id:`${d}Symbols`,type:o,length:n,...r}).createGraphics({id:`${d}Labels`,type:"text",length:n,...r}),i.titleVisible!==!1&&(l=l.createGraphics({id:`${d}Title`,type:"text",...r})),l}export{he as ITEM_LEGEND_LABELS,Me as ITEM_LEGEND_OPTIONS,Le as ITEM_LEGEND_TITLE_STYLE,it as assertLegendBoundsInsideCanvas,rt as createItemLegendGraphics,p as editGraphicProperties,ye as editLegendBackground,et as formatContinuousValues,ot as materializeItemLegend,Ye as normalizeContinuousLegend,A as normalizeInitialLegendTitle,Ve as normalizeItemLegendConfig,we as normalizeItemLegendLayout,k as normalizeLegendBorder,T as normalizeLegendTextOptions,j as normalizeLegendTitleOptions,Je as requireResolvedLegendScale,Qe as resolveContinuousBounds,Xe as resolveContinuousColorLayer,Ke as resolveContinuousLegendLayer,nt as resolveLegendBackgroundFromBounds,tt as resolveLegendTextBounds,Ze as sampleContinuousValues,U as selectLegendLayer,W as styleContinuousText,g as validateNonNegative,E as validatePositive};
@@ -1,360 +1 @@
1
- import{validateLegendClip}from"../../../../grammar/legendClip.js";
2
- import{hasLegendClipGraphics}from"../../../../materialization/legendClip.js";
3
- import { normalizeColorSymbols, resolveColorSymbolsLayout, materializeColorSymbols } from "./colorSymbols.js";
4
- import { alignLegendTitle, resolveLegendTitleText } from "../../../../layout/legendText.js";
5
- import { intrinsicLegendConfig } from "../../../../materialization/legendGroups.js";
6
- import { mapContinuousScaleValues, transformedTicks } from "../../../../grammar/scales/index.js";
7
- import { action, closedAction } from "../../../../core/action.js";
8
- import { isPlainObject } from "../../../../core/immutable.js";
9
- import { normalizeLegendGradient, DEFAULT_LEGEND_GRADIENT } from "../../../../grammar/legendGradient.js";
10
- import { mapScaleConsumerValues } from "../../../../materialization/scales/map.js";
11
- import { inverseLerp, interpolateNumber } from "../../../../grammar/numeric.js";
12
- import { DEFAULT_COLORS } from "../../../../theme/defaults.js";
13
- import {
14
- assertLegendBoundsInsideCanvas,
15
- editLegendBackground,
16
- editGraphicProperties,
17
- formatContinuousValues,
18
- normalizeContinuousLegend,
19
- requireResolvedLegendScale,
20
- resolveContinuousBounds,
21
- resolveContinuousColorLayer,
22
- resolveLegendBackgroundFromBounds,
23
- resolveLegendTextBounds,
24
- sampleContinuousValues,
25
- styleContinuousText
26
- } from "./common.js";
27
- import { resolveLegendGraphicPlacement } from
28
- "../../../../materialization/graphicHierarchy.js";
29
- import { resolveEffectiveLegendBlockConfig } from "../blocks.js";
30
-
31
- export const DEFAULT_GRADIENT_SIZE = DEFAULT_LEGEND_GRADIENT;
32
-
33
- export function resolveGradientLayout(program, config, scale, label = "Gradient legend", stripStrokeWidth = 0) {
34
- validateLegendClip("gradient",config);
35
- if (config.representation === "symbols") return resolveColorSymbolsLayout(program, config, scale);
36
- config = intrinsicLegendConfig(program, config);
37
- const { plot, canvas } = resolveContinuousBounds(program);
38
- const vertical = config.gradientDirection === undefined
39
- ? ["right", "left"].includes(config.position) : config.gradientDirection === "vertical";
40
- const stripWidth = vertical ? config.gradient.thickness : config.gradient.length;
41
- const stripHeight = vertical ? config.gradient.length : config.gradient.thickness;
42
- const length = config.gradient.length;
43
- const thickness = config.gradient.thickness;
44
- const titleText = config.titleVisible === false ? { height: 0, extraHeight: 0 }
45
- : resolveLegendTitleText(config.title, config.titleStyle, program.materializationConfigs.textMetrics);
46
- const titleGap = config.blockGap ?? 12;
47
- let x;
48
- let y;
49
- if (config.position === "right") {
50
- x = plot.x + plot.width + config.offset;
51
- y = plot.y + 46 + titleGap - 12 + titleText.extraHeight / 2;
52
- } else if (config.position === "left") {
53
- x = plot.x - config.offset - stripWidth;
54
- y = plot.y + 46 + titleGap - 12 + titleText.extraHeight / 2;
55
- } else {
56
- x = config.align === "left" ? plot.x
57
- : config.align === "right" ? plot.x + plot.width - stripWidth
58
- : plot.x + (plot.width - stripWidth) / 2;
59
- y = config.position === "top"
60
- ? plot.y - config.offset - stripHeight - config.labels.offset -
61
- config.labels.fontSize
62
- : plot.y + plot.height + config.offset +
63
- (config.titleVisible === false ? 0 : titleText.height + titleGap);
64
- }
65
- let title = vertical
66
- ? { x, y: ["right", "left"].includes(config.position) ? plot.y + 20
67
- : y - titleGap - titleText.height / 2, align: "left" }
68
- : {
69
- x: x + length / 2,
70
- y: y - titleGap - titleText.height / 2,
71
- align: "center"
72
- };
73
- const transformed = ["log", "pow", "sqrt", "symlog"].includes(scale.type);
74
- const values = transformed
75
- ? transformedTicks(scale.type, scale.domain, config.count,
76
- scale.type === "log" ? {base: scale.base}
77
- : scale.type === "pow" ? {exponent: scale.exponent}
78
- : scale.type === "symlog" ? {constant: scale.constant} : {})
79
- : [...sampleContinuousValues(scale.domain, config.count)];
80
- if (scale.midpoint !== undefined && !values.includes(scale.midpoint)) {
81
- values.push(scale.midpoint);
82
- values.sort((a, b) => scale.domain[1] > scale.domain[0] ? a - b : b - a);
83
- }
84
- const texts = formatContinuousValues(
85
- values,
86
- scale.domain,
87
- config.fieldType,
88
- config.labels.format
89
- );
90
- const fractions = transformed ? mapContinuousScaleValues(values, {...scale, range: [0, 1]})
91
- : values.map((value, index) => scale.midpoint === undefined
92
- ? index / (values.length - 1)
93
- : inverseLerp(value, ...scale.domain));
94
- const labelOffset = config.labels.offset;
95
- const labels = vertical
96
- ? fractions.map(fraction => ({
97
- x: config.position === "left"
98
- ? x - labelOffset : x + thickness + labelOffset,
99
- y: y + length * (1 - fraction),
100
- align: config.position === "left" ? "right" : "left"
101
- }))
102
- : fractions.map(fraction => ({
103
- x: x + length * fraction,
104
- y: y + thickness + labelOffset + config.labels.fontSize / 2,
105
- align: "center"
106
- }));
107
- const ticks = vertical
108
- ? labels.map(label => ({
109
- x1: config.position === "left" ? x : x + thickness,
110
- y1: label.y,
111
- x2: config.position === "left" ? x - 6 : x + thickness + 6,
112
- y2: label.y
113
- }))
114
- : labels.map(label => ({
115
- x1: label.x,
116
- y1: y + thickness,
117
- x2: label.x,
118
- y2: y + thickness + 6
119
- }));
120
- const stripBounds = {
121
- left: x,
122
- right: x + (vertical ? thickness : length),
123
- top: y,
124
- bottom: y + (vertical ? length : thickness)
125
- };
126
- const tickBounds = ticks.map(tick => ({
127
- left: Math.min(tick.x1, tick.x2) - 0.5,
128
- right: Math.max(tick.x1, tick.x2) + 0.5,
129
- top: Math.min(tick.y1, tick.y2) - 0.5,
130
- bottom: Math.max(tick.y1, tick.y2) + 0.5
131
- }));
132
- const labelBounds = labels.map((label, index) =>
133
- resolveLegendTextBounds(label, texts[index], config.labels, program.materializationConfigs.textMetrics)
134
- );
135
- if (config.titlePosition === "bottom" && config.titleVisible !== false) {
136
- const bottom = Math.max(stripBounds.bottom + stripStrokeWidth / 2,
137
- ...tickBounds.map(bounds => bounds.bottom), ...labelBounds.map(bounds => bounds.bottom));
138
- title = { x: vertical ? x : x + length / 2,
139
- y: bottom + titleGap + titleText.height / 2, align: vertical ? "left" : "center" };
140
- }
141
- if (config.titleVisible !== false) title = alignLegendTitle(title, config.title, config.titleStyle,
142
- [{ left: stripBounds.left - stripStrokeWidth / 2, right: stripBounds.right + stripStrokeWidth / 2,
143
- top: stripBounds.top - stripStrokeWidth / 2, bottom: stripBounds.bottom + stripStrokeWidth / 2 },
144
- ...tickBounds, ...labelBounds], program.materializationConfigs.textMetrics);
145
- const titleBounds = config.titleVisible === false ? undefined : resolveLegendTextBounds(
146
- title,
147
- config.title,
148
- config.titleStyle
149
- , program.materializationConfigs.textMetrics, { hardBreaks: true });
150
- const occupiedBounds = [stripBounds, ...tickBounds, ...labelBounds,
151
- ...(config.titleVisible === false ? [] : [titleBounds])];
152
- assertLegendBoundsInsideCanvas(
153
- occupiedBounds,
154
- canvas,
155
- `${label} layout`, config
156
- );
157
- const background = resolveLegendBackgroundFromBounds(
158
- occupiedBounds,
159
- config.border,
160
- canvas,
161
- label, config
162
- );
163
- return {
164
- vertical, x, y, length, thickness, values, texts, labels, ticks, title,
165
- background
166
- };
167
- }
168
-
169
- export function resolveGradientConfig(program, config, channel = "color") {
170
- const layer = resolveContinuousColorLayer(program, config.target, channel);
171
- const encoding = layer.encoding[channel];
172
- if (!["quantitative", "temporal"].includes(encoding.fieldType)) {
173
- throw new Error(`${channel === "color" ? "Gradient" : "Stroke gradient"} legend requires quantitative or temporal ${channel}.`);
174
- }
175
- const scale = requireResolvedLegendScale(
176
- program,
177
- encoding.scale,
178
- "sequential"
179
- );
180
- return {
181
- layer,
182
- encoding,
183
- scale,
184
- config: {
185
- ...config,
186
- target: layer.id,
187
- scale: encoding.scale,
188
- fieldType: encoding.fieldType,
189
- title: config.inferredTitle ? encoding.field : config.title,
190
- domain: scale.domain
191
- }
192
- };
193
- }
194
-
195
- // Both channels use the same positions and width policy. Their paint and
196
- // semantic/graphic namespaces remain explicit channel-owned choices.
197
- export function materializeGradientLegend(program, resolved, appearance) {
198
- const stroke = appearance !== undefined;
199
- const channel = stroke ? "stroke" : "color";
200
- const kind = stroke ? "strokeGradient" : "gradient";
201
- const prefix = `${channel}Gradient`;
202
- const { scale, encoding, config: currentConfig } = resolved;
203
- const config = resolveEffectiveLegendBlockConfig(program, kind, currentConfig);
204
- if (!stroke && config.representation === "symbols") return materializeColorSymbols(program, resolved, config);
205
- const layout = resolveGradientLayout(program, config, scale,
206
- stroke ? "Stroke gradient legend" : "Gradient legend", appearance?.strokeWidth ?? 0);
207
- const stripCount = 60;
208
- const stripSize = layout.length / stripCount;
209
- const strips = Array.from({ length: stripCount }, (_, index) => {
210
- const fraction = (index + 0.5) / stripCount;
211
- const position = layout.vertical ? 1 - fraction : fraction;
212
- const samplingScale = scale.midpoint === undefined ? { ...scale, type: "sequential", domain: [0, 1] } : scale;
213
- const value = scale.midpoint === undefined ? position : interpolateNumber(...scale.domain, position);
214
- const [color] = mapScaleConsumerValues([value], samplingScale, channel);
215
- return {
216
- x: layout.x + (layout.vertical ? 0 : index * stripSize),
217
- y: layout.y + (layout.vertical ? index * stripSize : 0),
218
- width: layout.vertical ? layout.thickness : stripSize,
219
- height: layout.vertical ? stripSize : layout.thickness,
220
- fill: stroke ? appearance.fill : color,
221
- stroke: color,
222
- strokeWidth: stroke ? appearance.strokeWidth : 0
223
- };
224
- });
225
- let next = program
226
- .editSemantic({ property: `guide.legend.${channel}.scale`, value: encoding.scale })
227
- .editSemantic({ property: `guide.legend.${channel}.title`, value: config.title })
228
- ._withLegendConfig(kind, currentConfig);
229
- // Preserve each channel's established primitive trace order.
230
- if (!stroke) next = next.editGraphics({ target: `${prefix}Strips`, property: "length", value: strips.length });
231
- next = editLegendBackground(next, `${prefix}Background`, layout.background, config.border);
232
- if (stroke) next = next.editGraphics({ target: `${prefix}Strips`, property: "length", value: strips.length });
233
- for (const property of ["x", "y", "width", "height", "fill", "stroke", "strokeWidth"]) {
234
- next = next.editGraphics({ target: `${prefix}Strips`, property, value: strips.map(strip => strip[property]) });
235
- }
236
- if (config.gradient.opacity !== undefined) {
237
- next = next.editGraphics({ target: `${prefix}Strips`, property: "opacity", value: config.gradient.opacity });
238
- }
239
- next = next.editGraphics({ target: `${prefix}Ticks`, property: "length", value: layout.ticks.length });
240
- for (const property of ["x1", "y1", "x2", "y2"]) {
241
- next = next.editGraphics({ target: `${prefix}Ticks`, property, value: layout.ticks.map(tick => tick[property]) });
242
- }
243
- next = editGraphicProperties(next, `${prefix}Ticks`, { stroke: DEFAULT_COLORS.mutedText, strokeWidth: 1 });
244
- next = editGraphicProperties(next, `${prefix}Labels`, {
245
- length: layout.labels.length, x: layout.labels.map(label => label.x),
246
- y: layout.labels.map(label => label.y), text: layout.texts
247
- });
248
- next = styleContinuousText(next, `${prefix}Labels`, config.labels, { align: layout.labels[0].align });
249
- if (config.titleVisible === false) return next;
250
- next = editGraphicProperties(next, `${prefix}Title`, { x: layout.title.x, y: layout.title.y, text: config.title });
251
- return styleContinuousText(next, `${prefix}Title`, config.titleStyle, { align: layout.title.align });
252
- }
253
-
254
- export const rematerializeGradientLegend = /* @__PURE__ */ closedAction(
255
- {
256
- op: "rematerializeGradientLegend",
257
- description: "Rematerialize a continuous color gradient legend."
258
- }, [],
259
- function (args = {}) {
260
- if(hasLegendClipGraphics(this))return this.rematerializeLegend();
261
- const stored = this.guideConfigs.legend?.gradient;
262
- if (stored === undefined) {
263
- throw new Error("Gradient legend requires stored configuration.");
264
- }
265
- return materializeGradientLegend(this, resolveGradientConfig(this, stored));
266
- }
267
- );
268
-
269
-
270
- export function resolveGradientLegendCreation(program, args = {}, channel = "color") {
271
- const { representation, ...options } = args;
272
- if (representation !== undefined && !["gradient", "symbols"].includes(representation)) throw new Error("Legend representation must be gradient or symbols.");
273
- if (channel !== "color" && representation !== undefined) throw new Error("Legend representation requires continuous color.");
274
- const config = representation === "symbols" ? normalizeColorSymbols(options) : normalizeContinuousLegend(options, "gradient");
275
- if (args.channels !== undefined && (
276
- !Array.isArray(args.channels) ||
277
- args.channels.length !== 1 ||
278
- args.channels[0] !== channel
279
- )) {
280
- throw new Error(`Gradient legend requires channels: ["${channel}"].`);
281
- }
282
- if (args.gradient !== undefined && !isPlainObject(args.gradient)) {
283
- throw new TypeError("createLegend.gradient must be a plain object.");
284
- }
285
- if (config.representation !== "symbols") config.gradient = normalizeLegendGradient(args.gradient);
286
- const resolved = resolveGradientConfig(program, config, channel);
287
- return resolved;
288
- }
289
-
290
- export function createGradientLegendFromConfig(program, config, channel = "color") {
291
- const prefix = `${channel}Gradient`;
292
- const kind = channel === "color" ? "gradient" : "strokeGradient";
293
- const resolved = resolveGradientConfig(program, config, channel);
294
- resolveGradientLayout(program, resolved.config, resolved.scale, channel === "color" ? "Gradient legend" : "Stroke gradient legend");
295
- let next = program
296
- .editSemantic({
297
- property: `guide.legend.${channel}.scale`,
298
- value: resolved.encoding.scale
299
- })
300
- .editSemantic({
301
- property: `guide.legend.${channel}.title`,
302
- value: resolved.config.title
303
- })
304
- ._withLegendConfig(kind, resolved.config);
305
- if (resolved.config.border !== false) {
306
- next = next.createGraphics({
307
- id: `${prefix}Background`,
308
- type: "rect",
309
- ...resolveLegendGraphicPlacement(next)
310
- });
311
- }
312
- if (resolved.config.representation === "symbols") {
313
- next = next.createGraphics({ id: `${prefix}Symbols`, type: "circle", length: 0, ...resolveLegendGraphicPlacement(next) });
314
- } else next = next
315
- .createGraphics({
316
- id: `${prefix}Strips`,
317
- type: "rect",
318
- length: 0,
319
- ...resolveLegendGraphicPlacement(next, resolved.config.border === false
320
- ? {}
321
- : { after: `${prefix}Background` })
322
- })
323
- .createGraphics({
324
- id: `${prefix}Ticks`,
325
- type: "line",
326
- length: 0,
327
- ...resolveLegendGraphicPlacement(next)
328
- });
329
- next = next.createGraphics({
330
- id: `${prefix}Labels`,
331
- type: "text",
332
- length: 0,
333
- ...resolveLegendGraphicPlacement(next)
334
- });
335
- if (resolved.config.titleVisible !== false) {
336
- next = next.createGraphics({
337
- id: `${prefix}Title`,
338
- type: "text",
339
- ...resolveLegendGraphicPlacement(next)
340
- });
341
- }
342
- return next[channel === "color" ? "rematerializeGradientLegend" : "rematerializeStrokeGradientLegend"]();
343
- }
344
-
345
- export const createGradientLegend = /* @__PURE__ */ action(
346
- {
347
- op: "createGradientLegend",
348
- description: "Create a continuous color gradient legend."
349
- },
350
- function (args = {}) {
351
- const resolved = resolveGradientLegendCreation(this, args);
352
- resolveGradientLayout(this, resolved.config, resolved.scale);
353
- if (this.guideConfigs.legend?.gradient !== undefined) {
354
- throw new Error(
355
- "createGradientLegend requires a missing gradient legend."
356
- );
357
- }
358
- return createGradientLegendFromConfig(this, resolved.config);
359
- }
360
- );
1
+ import{validateLegendClip as H}from"../../../../grammar/legendClip.js";import{hasLegendClipGraphics as I}from"../../../../materialization/legendClip.js";import{normalizeColorSymbols as N,resolveColorSymbolsLayout as U,materializeColorSymbols as j}from"./colorSymbols.js";import{alignLegendTitle as Z,resolveLegendTitleText as J}from"../../../../layout/legendText.js";import{resolveLegendContentSpacing as K}from"../../../../layout/legendSpacing.js";import{validateLegendSpacing as Q}from"../../../../grammar/legendSpacing.js";import{intrinsicLegendConfig as X}from"../../../../materialization/legendGroups.js";import{mapContinuousScaleValues as Y,transformedTicks as W}from"../../../../grammar/scales/index.js";import{action as ee,closedAction as te}from"../../../../core/action.js";import{isPlainObject as ie}from"../../../../core/immutable.js";import{normalizeLegendGradient as ne,DEFAULT_LEGEND_GRADIENT as re}from"../../../../grammar/legendGradient.js";import{mapScaleConsumerValues as oe}from"../../../../materialization/scales/map.js";import{inverseLerp as se,interpolateNumber as le}from"../../../../grammar/numeric.js";import{DEFAULT_COLORS as ae}from"../../../../theme/defaults.js";import{assertLegendBoundsInsideCanvas as de,editLegendBackground as pe,editGraphicProperties as M,formatContinuousValues as ue,normalizeContinuousLegend as me,requireResolvedLegendScale as ge,resolveContinuousBounds as ce,resolveContinuousColorLayer as fe,resolveLegendBackgroundFromBounds as ye,resolveLegendTextBounds as F,sampleContinuousValues as he,styleContinuousText as P}from"./common.js";import{resolveLegendGraphicPlacement as C}from"../../../../materialization/graphicHierarchy.js";import{resolveEffectiveLegendBlockConfig as xe}from"../blocks.js";import{readLegendSampling as be,validateSamplingAgainstScale as ve}from"../sampling.js";const He=re;function _(d,e,t,l="Gradient legend",a=0){if(H("gradient",e),Q("gradient",e),e.representation==="symbols")return U(d,e,t);e=X(d,e);const{plot:n,canvas:r}=ce(d),g=e.gradientDirection===void 0?["right","left"].includes(e.position):e.gradientDirection==="vertical",$=g?e.gradient.thickness:e.gradient.length,z=g?e.gradient.length:e.gradient.thickness,m=e.gradient.length,s=e.gradient.thickness,y=e.titleVisible===!1?{height:0,extraHeight:0}:J(e.title,e.titleStyle,d.materializationConfigs.textMetrics),f=e.blockGap??12;let p,i;e.position==="right"?(p=n.x+n.width+e.offset,i=n.y+46+f-12+y.extraHeight/2):e.position==="left"?(p=n.x-e.offset-$,i=n.y+46+f-12+y.extraHeight/2):(p=e.align==="left"?n.x:e.align==="right"?n.x+n.width-$:n.x+(n.width-$)/2,i=e.position==="top"?n.y-e.offset-z-e.labels.offset-e.labels.fontSize:n.y+n.height+e.offset+(e.titleVisible===!1?0:y.height+f));let u=g?{x:p,y:["right","left"].includes(e.position)?n.y+20:i-f-y.height/2,align:"left"}:{x:p+m/2,y:i-f-y.height/2,align:"center"};const h=["log","pow","sqrt","symlog"].includes(t.type),S=ve(be(e),t,l),L=S.mode==="values",x=L?[...S.values]:h?W(t.type,t.domain,e.count,t.type==="log"?{base:t.base}:t.type==="pow"?{exponent:t.exponent}:t.type==="symlog"?{constant:t.constant}:{}):[...he(t.domain,e.count)];!L&&t.midpoint!==void 0&&!x.includes(t.midpoint)&&(x.push(t.midpoint),x.sort((o,c)=>t.domain[1]>t.domain[0]?o-c:c-o));const T=ue(x,t.domain,e.fieldType,e.labels.format),k=h||L?Y(x,{...t,range:[0,1]}):x.map((o,c)=>!L&&t.midpoint===void 0?c/(x.length-1):se(o,...t.domain)),q=e.labels.offset;let G=g?k.map(o=>({x:e.position==="left"?p-q:p+s+q,y:i+m*(1-o),align:e.position==="left"?"right":"left"})):k.map(o=>({x:p+m*o,y:i+s+q+e.labels.fontSize/2,align:"center"})),B=g?G.map(o=>({x1:e.position==="left"?p:p+s,y1:o.y,x2:e.position==="left"?p-6:p+s+6,y2:o.y})):G.map(o=>({x1:o.x,y1:i+s,x2:o.x,y2:i+s+6}));const b={left:p,right:p+(g?s:m),top:i,bottom:i+(g?m:s)},E=B.map(o=>({left:Math.min(o.x1,o.x2)-.5,right:Math.max(o.x1,o.x2)+.5,top:Math.min(o.y1,o.y2)-.5,bottom:Math.max(o.y1,o.y2)+.5})),V=G.map((o,c)=>F(o,T[c],e.labels,d.materializationConfigs.textMetrics));if(e.titlePosition==="bottom"&&e.titleVisible!==!1){const o=Math.max(b.bottom+a/2,...E.map(c=>c.bottom),...V.map(c=>c.bottom));u={x:g?p:p+m/2,y:o+f+y.height/2,align:g?"left":"center"}}e.titleVisible!==!1&&(u=Z(u,e.title,e.titleStyle,[{left:b.left-a/2,right:b.right+a/2,top:b.top-a/2,bottom:b.bottom+a/2},...E,...V],d.materializationConfigs.textMetrics));const O=e.titleVisible===!1?void 0:F(u,e.title,e.titleStyle,d.materializationConfigs.textMetrics,{hardBreaks:!0});let A=[b,...E,...V,...e.titleVisible===!1?[]:[O]];const w=e.titlePadding===void 0?void 0:K(e,[{row:0,x:p,y:i,bounds:[{left:b.left-a/2,right:b.right+a/2,top:b.top-a/2,bottom:b.bottom+a/2},...E,...V.filter((o,c)=>T[c]!=="")]}],O);if(w!==void 0){const{dx:o,dy:c}=w.offsets[0];p+=o,i+=c,G=G.map(v=>({...v,x:v.x+o,y:v.y+c})),B=B.map(v=>({...v,x1:v.x1+o,x2:v.x2+o,y1:v.y1+c,y2:v.y2+c})),u={...u,x:u.x+w.titleOffset.dx,y:u.y+w.titleOffset.dy},A=w.bounds}de(A,r,`${l} layout`,e);const R=ye(A,e.border,r,l,e);return{vertical:g,x:p,y:i,length:m,thickness:s,values:x,texts:T,labels:G,ticks:B,title:u,background:R}}function D(d,e,t="color"){const l=fe(d,e.target,t),a=l.encoding[t];if(!["quantitative","temporal"].includes(a.fieldType))throw new Error(`${t==="color"?"Gradient":"Stroke gradient"} legend requires quantitative or temporal ${t}.`);const n=ge(d,a.scale,"sequential");return{layer:l,encoding:a,scale:n,config:{...e,target:l.id,scale:a.scale,fieldType:a.fieldType,title:e.inferredTitle?a.field:e.title,domain:n.domain}}}function Le(d,e,t){const l=t!==void 0,a=l?"stroke":"color",n=l?"strokeGradient":"gradient",r=`${a}Gradient`,{scale:g,encoding:$,config:z}=e,m=xe(d,n,z);if(!l&&m.representation==="symbols")return j(d,e,m);const s=_(d,m,g,l?"Stroke gradient legend":"Gradient legend",t?.strokeWidth??0),y=60,f=s.length/y,p=Array.from({length:y},(u,h)=>{const S=(h+.5)/y,L=s.vertical?1-S:S,x=g.midpoint===void 0?{...g,type:"sequential",domain:[0,1]}:g,T=g.midpoint===void 0?L:le(...g.domain,L),[k]=oe([T],x,a);return{x:s.x+(s.vertical?0:h*f),y:s.y+(s.vertical?h*f:0),width:s.vertical?s.thickness:f,height:s.vertical?f:s.thickness,fill:l?t.fill:k,stroke:k,strokeWidth:l?t.strokeWidth:0}});let i=d.editSemantic({property:`guide.legend.${a}.scale`,value:$.scale}).editSemantic({property:`guide.legend.${a}.title`,value:m.title})._withLegendConfig(n,z);l||(i=i.editGraphics({target:`${r}Strips`,property:"length",value:p.length})),i=pe(i,`${r}Background`,s.background,m.border),l&&(i=i.editGraphics({target:`${r}Strips`,property:"length",value:p.length}));for(const u of["x","y","width","height","fill","stroke","strokeWidth"])i=i.editGraphics({target:`${r}Strips`,property:u,value:p.map(h=>h[u])});m.gradient.opacity!==void 0&&(i=i.editGraphics({target:`${r}Strips`,property:"opacity",value:m.gradient.opacity})),i=i.editGraphics({target:`${r}Ticks`,property:"length",value:s.ticks.length});for(const u of["x1","y1","x2","y2"])i=i.editGraphics({target:`${r}Ticks`,property:u,value:s.ticks.map(h=>h[u])});return i=M(i,`${r}Ticks`,{stroke:ae.mutedText,strokeWidth:1}),i=M(i,`${r}Labels`,{length:s.labels.length,x:s.labels.map(u=>u.x),y:s.labels.map(u=>u.y),text:s.texts}),i=P(i,`${r}Labels`,m.labels,{align:s.labels[0].align}),m.titleVisible===!1?i:(i=M(i,`${r}Title`,{x:s.title.x,y:s.title.y,text:m.title}),P(i,`${r}Title`,m.titleStyle,{align:s.title.align}))}const Ie=te({op:"rematerializeGradientLegend",description:"Rematerialize a continuous color gradient legend."},[],function(d={}){if(I(this))return this.rematerializeLegend();const e=this.guideConfigs.legend?.gradient;if(e===void 0)throw new Error("Gradient legend requires stored configuration.");return Le(this,D(this,e))});function Ge(d,e={},t="color"){const{representation:l,...a}=e;if(l!==void 0&&!["gradient","symbols"].includes(l))throw new Error("Legend representation must be gradient or symbols.");if(t!=="color"&&l!==void 0)throw new Error("Legend representation requires continuous color.");const n=l==="symbols"?N(a):me(a,"gradient");if(e.channels!==void 0&&(!Array.isArray(e.channels)||e.channels.length!==1||e.channels[0]!==t))throw new Error(`Gradient legend requires channels: ["${t}"].`);if(e.gradient!==void 0&&!ie(e.gradient))throw new TypeError("createLegend.gradient must be a plain object.");return n.representation!=="symbols"&&(n.gradient=ne(e.gradient)),D(d,n,t)}function Ce(d,e,t="color"){const l=`${t}Gradient`,a=t==="color"?"gradient":"strokeGradient",n=D(d,e,t);_(d,n.config,n.scale,t==="color"?"Gradient legend":"Stroke gradient legend");let r=d.editSemantic({property:`guide.legend.${t}.scale`,value:n.encoding.scale}).editSemantic({property:`guide.legend.${t}.title`,value:n.config.title})._withLegendConfig(a,n.config);return n.config.border!==!1&&(r=r.createGraphics({id:`${l}Background`,type:"rect",...C(r)})),n.config.representation==="symbols"?r=r.createGraphics({id:`${l}Symbols`,type:"circle",length:0,...C(r)}):r=r.createGraphics({id:`${l}Strips`,type:"rect",length:0,...C(r,n.config.border===!1?{}:{after:`${l}Background`})}).createGraphics({id:`${l}Ticks`,type:"line",length:0,...C(r)}),r=r.createGraphics({id:`${l}Labels`,type:"text",length:0,...C(r)}),n.config.titleVisible!==!1&&(r=r.createGraphics({id:`${l}Title`,type:"text",...C(r)})),r[t==="color"?"rematerializeGradientLegend":"rematerializeStrokeGradientLegend"]()}const Ne=ee({op:"createGradientLegend",description:"Create a continuous color gradient legend."},function(d={}){const e=Ge(this,d);if(_(this,e.config,e.scale),this.guideConfigs.legend?.gradient!==void 0)throw new Error("createGradientLegend requires a missing gradient legend.");return Ce(this,e.config)});export{He as DEFAULT_GRADIENT_SIZE,Ne as createGradientLegend,Ce as createGradientLegendFromConfig,Le as materializeGradientLegend,Ie as rematerializeGradientLegend,D as resolveGradientConfig,_ as resolveGradientLayout,Ge as resolveGradientLegendCreation};
@@ -32,7 +32,7 @@ import { resolveEffectiveLegendBlockConfig } from "../blocks.js";
32
32
 
33
33
  const OPTIONS = [
34
34
  "target", "channels", "position", "align", "offset", "title",
35
- "symbol", "labels", "titleStyle", "itemGap", "direction", "border", "layout", "columns", "titlePosition"
35
+ "symbol", "labels", "titleStyle", "itemGap", "rowPadding", "titlePadding", "direction", "border", "layout", "columns", "titlePosition"
36
36
  ];
37
37
  const SYMBOL_OPTIONS = [
38
38
  "width", "height", "stroke", "strokeWidth"
@@ -2,6 +2,8 @@ import{validateLegendClip}from"../../../../grammar/legendClip.js";
2
2
  import{hasLegendClipGraphics,applyLegendClipGraphics}from"../../../../materialization/legendClip.js";
3
3
  import { intrinsicLegendConfig } from "../../../../materialization/legendGroups.js";
4
4
  import { alignLegendTitle, resolveLegendTitleText } from "../../../../layout/legendText.js";
5
+ import {resolveLegendContentSpacing} from "../../../../layout/legendSpacing.js";
6
+ import {validateLegendSpacing} from "../../../../grammar/legendSpacing.js";
5
7
  import { editGraphicProperties } from "../../../primitives/graphicProperties.js";
6
8
  import { action, closedAction } from "../../../../core/action.js";
7
9
  import { isPlainObject } from "../../../../core/immutable.js";
@@ -91,6 +93,7 @@ function resolveOpacityConfig(program, config) {
91
93
 
92
94
  function resolveOpacityLayout(program, config, scale) {
93
95
  validateLegendClip("opacity",config);
96
+ validateLegendSpacing("opacity",config);
94
97
  config = intrinsicLegendConfig(program, config);
95
98
  const { plot, canvas } = resolveContinuousBounds(program);
96
99
  const vertical = ["right", "left"].includes(config.position);
@@ -210,7 +213,18 @@ function resolveOpacityLayout(program, config, scale) {
210
213
  config.title,
211
214
  config.titleStyle
212
215
  , program.materializationConfigs.textMetrics, { hardBreaks: true });
213
- const occupiedBounds = [...(config.titleVisible === false ? [] : [titleBounds]), ...labelBounds, ...symbolBounds];
216
+ let occupiedBounds = [...(config.titleVisible === false ? [] : [titleBounds]), ...labelBounds, ...symbolBounds];
217
+ const spacing = config.rowPadding === undefined && config.titlePadding === undefined ? undefined
218
+ : resolveLegendContentSpacing(config,symbols.map((symbol,index) => ({
219
+ row:vertical ? index : 0,x:symbol.x,y:symbol.y,
220
+ bounds:[symbolBounds[index],...(texts[index] === "" ? [] : [labelBounds[index]])]
221
+ })),titleBounds);
222
+ if (spacing !== undefined) {
223
+ symbols = symbols.map((point,index) => ({...point,x:point.x+spacing.offsets[index].dx,y:point.y+spacing.offsets[index].dy}));
224
+ labels = labels.map((point,index) => ({...point,x:point.x+spacing.offsets[index].dx,y:point.y+spacing.offsets[index].dy}));
225
+ title = {...title,x:title.x+spacing.titleOffset.dx,y:title.y+spacing.titleOffset.dy};
226
+ occupiedBounds = spacing.bounds;
227
+ }
214
228
  assertLegendBoundsInsideCanvas(
215
229
  occupiedBounds,
216
230
  canvas,