ggaction 0.0.24 → 0.0.26

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 (217) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +2 -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/knowledge/task-resolver.js +1 -1
  7. package/package.json +1 -1
  8. package/src/accessibility.js +13 -0
  9. package/src/actions/canvas/actions.js +2 -1
  10. package/src/actions/canvas/fitting.js +68 -13
  11. package/src/actions/charts/beeswarm.js +2 -1
  12. package/src/actions/charts/ecdf.js +2 -1
  13. package/src/actions/charts/endpoints.js +25 -16
  14. package/src/actions/charts/heatmap.js +5 -3
  15. package/src/actions/charts/missing.js +17 -0
  16. package/src/actions/charts/radar.js +70 -33
  17. package/src/actions/charts/raincloud.js +18 -4
  18. package/src/actions/charts/rug-strip.js +2 -2
  19. package/src/actions/charts/shared.js +17 -0
  20. package/src/actions/composition/actions.js +24 -7
  21. package/src/actions/composition/adjacent.js +90 -0
  22. package/src/actions/composition/index.js +11 -1
  23. package/src/actions/composition/layer.js +42 -0
  24. package/src/actions/composition/layerPlan.js +176 -0
  25. package/src/actions/composition/targets.js +105 -0
  26. package/src/actions/data/derived.js +3 -0
  27. package/src/actions/data/filter.js +1 -1
  28. package/src/actions/data/raincloudFilter.js +3 -1
  29. package/src/actions/encodings/appearance.js +26 -7
  30. package/src/actions/encodings/channels.js +1 -0
  31. package/src/actions/encodings/color/continuous.js +37 -34
  32. package/src/actions/encodings/color/index.js +57 -43
  33. package/src/actions/encodings/color/policy.js +15 -0
  34. package/src/actions/encodings/density/resolve.js +3 -2
  35. package/src/actions/encodings/density.js +2 -2
  36. package/src/actions/encodings/offset.js +2 -0
  37. package/src/actions/encodings/pathOrder.js +2 -0
  38. package/src/actions/encodings/position/areaAlignment.js +42 -0
  39. package/src/actions/encodings/position/index.js +37 -3
  40. package/src/actions/encodings/position/policies/line.js +1 -1
  41. package/src/actions/encodings/ranged.js +2 -0
  42. package/src/actions/encodings/remove.js +13 -10
  43. package/src/actions/encodings/ruleAppearance.js +14 -2
  44. package/src/actions/facets/actions.js +1 -1
  45. package/src/actions/guides/axes/axes.js +2 -1
  46. package/src/actions/guides/axes/edit.js +1 -1
  47. package/src/actions/guides/axes/labels.js +2 -1
  48. package/src/actions/guides/axes/parallel/lifecycle.js +11 -4
  49. package/src/actions/guides/axes/parallel/policy.js +5 -2
  50. package/src/actions/guides/axes/parallel/resolve.js +3 -1
  51. package/src/actions/guides/axes/parallel.js +9 -4
  52. package/src/actions/guides/axes/titles.js +1 -1
  53. package/src/actions/guides/facadeLegend.js +3 -2
  54. package/src/actions/guides/grids/grid.js +3 -2
  55. package/src/actions/guides/grids/resolve.js +2 -1
  56. package/src/actions/guides/guides.js +3 -2
  57. package/src/actions/guides/legends/automatic.js +28 -0
  58. package/src/actions/guides/legends/blocks.js +15 -21
  59. package/src/actions/guides/legends/categorical/actions.js +1 -1
  60. package/src/actions/guides/legends/categorical/components.js +4 -3
  61. package/src/actions/guides/legends/categorical/layout.js +5 -2
  62. package/src/actions/guides/legends/categorical/options.js +9 -14
  63. package/src/actions/guides/legends/categorical/recipes.js +3 -0
  64. package/src/actions/guides/legends/categorical/resolve.js +7 -5
  65. package/src/actions/guides/legends/colorSize.js +51 -14
  66. package/src/actions/guides/legends/continuous/common.js +1 -1
  67. package/src/actions/guides/legends/continuous/gradient.js +1 -1
  68. package/src/actions/guides/legends/continuous/opacity.js +2 -0
  69. package/src/actions/guides/legends/edit.js +1 -1
  70. package/src/actions/guides/legends/focused.js +2 -2
  71. package/src/actions/guides/legends/group.js +1 -1
  72. package/src/actions/guides/legends/lane.js +1 -1
  73. package/src/actions/guides/legends/lifecycle.js +2 -2
  74. package/src/actions/guides/legends/remove.js +18 -4
  75. package/src/actions/guides/legends/size.js +1 -1
  76. package/src/actions/guides/legends/transition.js +15 -6
  77. package/src/actions/guides/polar/axes/facade.js +4 -2
  78. package/src/actions/guides/polar/axes/labels.js +6 -1
  79. package/src/actions/guides/polar/axes/shared.js +4 -4
  80. package/src/actions/guides/polar/axes/titles.js +13 -2
  81. package/src/actions/guides/polar/grids.js +6 -3
  82. package/src/actions/guides/tickValues.js +7 -1
  83. package/src/actions/index.js +2 -0
  84. package/src/actions/marks/area/actions.js +9 -2
  85. package/src/actions/marks/area/materialize.js +1 -1
  86. package/src/actions/marks/bar/materialize.js +4 -4
  87. package/src/actions/marks/lifecycle.js +14 -0
  88. package/src/actions/marks/line/actions.js +3 -0
  89. package/src/actions/marks/line/materialize.js +4 -2
  90. package/src/actions/marks/point/edit.js +2 -0
  91. package/src/actions/marks/point/jitter.js +2 -0
  92. package/src/actions/marks/point/materialize.js +5 -4
  93. package/src/actions/marks/point/packing.js +2 -0
  94. package/src/actions/marks/references.js +5 -3
  95. package/src/actions/marks/remove.js +16 -0
  96. package/src/actions/marks/shared.js +2 -1
  97. package/src/actions/marks/text/actions.js +5 -2
  98. package/src/actions/overlays/index.js +168 -0
  99. package/src/actions/overlays/snapshot.js +45 -0
  100. package/src/actions/primitives/editGraphics.js +21 -27
  101. package/src/actions/primitives/semanticAction.js +4 -5
  102. package/src/actions/primitives/semanticValidation/guide.js +1 -0
  103. package/src/actions/primitives/semanticValidation/layer.js +7 -0
  104. package/src/actions/scales/consumers/common.js +10 -0
  105. package/src/actions/scales/consumers/families.js +1 -1
  106. package/src/actions/scales/preview.js +1 -1
  107. package/src/actions/selection/actions.js +63 -11
  108. package/src/actions/titles/actions.js +3 -14
  109. package/src/actions/titles/resolve.js +12 -9
  110. package/src/core/action.js +41 -25
  111. package/src/core/compositionScales.js +12 -0
  112. package/src/core/compositionState.js +16 -2
  113. package/src/core/immutable.js +11 -6
  114. package/src/core/programState.js +16 -0
  115. package/src/core/resourceReferences.js +19 -3
  116. package/src/core/validation.js +7 -0
  117. package/src/core/vocabulary.js +8 -2
  118. package/src/grammar/actionTargets.js +24 -0
  119. package/src/grammar/bin2d.js +7 -0
  120. package/src/grammar/colorSizeLegend.js +56 -5
  121. package/src/grammar/displayLabels.js +10 -0
  122. package/src/grammar/facetHeaders.js +1 -1
  123. package/src/grammar/facets/dependencies.js +10 -3
  124. package/src/grammar/facets/guides.js +2 -1
  125. package/src/grammar/facets/legendIntent.js +7 -5
  126. package/src/grammar/legendBlocks.js +1 -1
  127. package/src/grammar/legendCombinations.js +1 -1
  128. package/src/grammar/legendGroups.js +1 -1
  129. package/src/grammar/legendItemsOffset.js +12 -0
  130. package/src/grammar/legendOverflow.js +25 -0
  131. package/src/grammar/lineSeries.js +1 -1
  132. package/src/grammar/markSelection.js +23 -0
  133. package/src/grammar/overlay.js +51 -0
  134. package/src/grammar/parallelCoordinates.js +1 -208
  135. package/src/grammar/pathCommands.js +8 -2
  136. package/src/grammar/pathSeries.js +1 -1
  137. package/src/grammar/plotComposition.js +152 -0
  138. package/src/grammar/pointShapes.js +18 -0
  139. package/src/grammar/scales/transformed.js +1 -1
  140. package/src/grammar/schemas/concreteGraphic.js +8 -1
  141. package/src/grammar/schemas/graphicBounds.js +8 -1
  142. package/src/grammar/schemas/graphicTree.js +18 -2
  143. package/src/grammar/schemas/semanticPath.js +3 -0
  144. package/src/grammar/strokeStyle.js +14 -8
  145. package/src/grammar/text.js +72 -0
  146. package/src/inspection.js +1 -1
  147. package/src/inspectionDescriptors.js +1 -1
  148. package/src/layout/composition.js +30 -4
  149. package/src/layout/compositionLegend.js +91 -0
  150. package/src/layout/facetHeaderTitles.js +1 -1
  151. package/src/layout/labels.js +55 -27
  152. package/src/layout/legend.js +3 -2
  153. package/src/layout/legendGroup.js +1 -1
  154. package/src/layout/legendItems.js +7 -6
  155. package/src/layout/legendLane.js +1 -1
  156. package/src/layout/legendText.js +1 -1
  157. package/src/materialization/adjacentSource.js +27 -0
  158. package/src/materialization/composition.js +165 -52
  159. package/src/materialization/compositionLegends.js +120 -0
  160. package/src/materialization/compositionScales.js +125 -0
  161. package/src/materialization/compositionTitle.js +96 -0
  162. package/src/materialization/facetGuides/edit.js +22 -2
  163. package/src/materialization/facetGuides/layout.js +1 -1
  164. package/src/materialization/facetGuides/owner.js +4 -3
  165. package/src/materialization/facetGuides/ownerLayout.js +12 -6
  166. package/src/materialization/facetGuides/placement.js +19 -22
  167. package/src/materialization/facetGuides/preparation.js +17 -5
  168. package/src/materialization/facetHeaders.js +5 -1
  169. package/src/materialization/facets.js +1 -1
  170. package/src/materialization/guides/layout.js +1 -1
  171. package/src/materialization/guides/resources.js +1 -1
  172. package/src/materialization/guides/visibility.js +105 -0
  173. package/src/materialization/legendClip.js +1 -1
  174. package/src/materialization/legendGroups.js +1 -1
  175. package/src/materialization/legendItemsOffset.js +68 -0
  176. package/src/materialization/legendSuppression.js +22 -0
  177. package/src/materialization/legends.js +3 -2
  178. package/src/materialization/marks/capabilities.js +4 -0
  179. package/src/materialization/marks/index.js +5 -1
  180. package/src/materialization/marks/pathOrder.js +1 -1
  181. package/src/materialization/marks/policies.js +2 -1
  182. package/src/materialization/overlayItems.js +154 -0
  183. package/src/materialization/scales/policies/binnedPosition.js +43 -5
  184. package/src/materialization/scales/policies/series.js +12 -4
  185. package/src/materialization/scales/resolve.js +2 -0
  186. package/src/materialization/selection/items/bar.js +21 -3
  187. package/src/materialization/selection/items/common.js +18 -5
  188. package/src/materialization/selection/items/overlay.js +15 -0
  189. package/src/materialization/selection/items/path.js +1 -0
  190. package/src/materialization/selection/items/point.js +2 -3
  191. package/src/materialization/selection/items/tick.js +3 -12
  192. package/src/materialization/selection/path.js +2 -2
  193. package/src/materialization/selection/policies/bar.js +2 -0
  194. package/src/materialization/selection/policies/index.js +35 -1
  195. package/src/materialization/selection/policies/point.js +2 -0
  196. package/src/materialization/selection/policies/text.js +17 -0
  197. package/src/materialization/selection/rows.js +103 -0
  198. package/src/materialization/selection/state.js +19 -3
  199. package/src/materialization/selection/styles.js +8 -0
  200. package/src/materialization/text.js +6 -489
  201. package/src/materialization/textGeometry.js +503 -0
  202. package/src/materialization/textLines.js +20 -0
  203. package/src/persistence/validation.js +97 -24
  204. package/src/renderers/canvas/index.js +12 -7
  205. package/src/renderers/canvas/text.js +5 -5
  206. package/src/renderers/nativeBackend.js +25 -0
  207. package/src/renderers/pdf.js +3 -3
  208. package/src/renderers/png.js +4 -3
  209. package/src/renderers/svgSerialization.js +1 -643
  210. package/src/scene.js +5 -3
  211. package/src/selectors/plotComposition.js +115 -0
  212. package/src/selectors/selectionTargets.js +19 -0
  213. package/src/version.js +1 -1
  214. package/types/basic.d.ts +2 -2
  215. package/types/index.d.ts +11 -0
  216. package/types/inspection.d.ts +34 -0
  217. package/types/program.d.ts +553 -365
@@ -1 +1 @@
1
- import{normalizeFacetAxes as R,validateFacetAxisPolicy as x}from"../../core/facetAxisPolicy.js";import{DEFAULT_ACTION_METHODS as te}from"../../core/vocabulary.js";import{axisGraphicIds as ie}from"../../materialization/guides/resources.js";import{cartesianAxisOwners as ne,axisTargetArgs as ae}from"../../selectors/axes.js";import{resolveGraphicBounds as oe}from"../../layout/canvas.js";import{action as z,closedAction as T,getWrappedActionMetadata as ce}from"../../core/action.js";import{registerFacetDataRevision as se,reviseUnitData as re}from"../data/revise.js";import{applyMaterializationPlan as de}from"../../materialization/dependencies.js";import{getMarkMaterializationStep as le}from"../../materialization/marks/index.js";import{buildMaterializationPlan as fe}from"../../materialization/planner.js";import{freezeOwned as A,isPlainObject as S}from"../../core/immutable.js";import{validateUserId as P}from"../../core/identifiers.js";import{validateNonEmptyString as $,validateNonNegativeFinite as pe,validateOptionObject as w,validatePositiveFinite as ue}from"../../core/validation.js";import{resolveFacetDefinition as G,resolveFacetGridDefinition as I}from"../../grammar/facets/index.js";import{collectFacetScaleBindings as N,resolveFacetFamily as k}from"../../grammar/facets/dependencies.js";import{FACET_SCALE_CHANNELS as _,normalizeFacetScalePolicies as j}from"../../grammar/facets/scales.js";import{resolveFacetLayout as he}from"../../layout/facets.js";import{compositionChildDescriptor as me}from"../../materialization/composition.js";import{deriveFacetChildren as H}from"./derive.js";import{resolveFacetChildrenScales as ge}from"./derive.js";import{replayDerivedData as Se}from"./replay.js";import{composeFacetGuides as we}from"./guides.js";import{applyCompositionState as q}from"../composition/actions.js";import{replayCompositionThemeState as ye}from"../theme/composition.js";import{findDataset as ve}from"../../selectors/datasets.js";import{findLayer as U}from"../../selectors/layers.js";import{findSemanticScale as Fe}from"../../selectors/scales.js";import{normalizeDisplayLabelMap as W}from"../../grammar/displayLabels.js";import{resolveRotation as be}from"../../grammar/rotation.js";import{normalizeFacetHeaderTitle as Ce}from"../../grammar/facetHeaders.js";import{createDefaultFacetHeaders as Ee,normalizeFacetHeadersConfig as K,resolveFacetHeaderConfig as Oe}from"../../materialization/facetHeaders.js";const xe=Object.freeze(["id","field","data","values","columns","gap","spacing","topLane","align","padding","scales","guides","headers"]),ze=Object.freeze(["id","data","rows","columns","combinations","gap","spacing","topLane","align","padding","scales","guides","headers"]),Te=Object.freeze(["id","target","channel","fields","columns","gap","spacing","topLane","align","padding","scales","guides","headers"]),$e=new Set(te),ke=Object.freeze(["program","action"]),V=Object.freeze(["axes","legend"]),B=Object.freeze(["fontSize","fontFamily","fontWeight","color","offset","role","labelMap","side","align","rotation","title","anchor"]),Y=Object.freeze(["fontSize","fontFamily","fontWeight","color","offset","align","rotation","anchor"]);function F(t,e=!1){if(t===void 0)return{axes:"each",legend:!1};w(t,V,"facet.guides");const n=R(t.axes),i=t.legend??!1;if(i!==!1&&i!=="shared"&&!S(i))throw new Error('facet guides.legend must be false, "shared", or legend options.');if(!e&&S(i)&&Object.hasOwn(i,"type"))throw new Error("Facet legend options do not accept internal type tags.");return{axes:n,legend:i}}function b(t,e){if(t._assertCompositionProgram(e),t.compositionSpec.type!=="facet")throw new Error(`${e} requires a facet composition.`)}function C(t,e){const n=new Set(N(t.semanticSpec).map(i=>i.policyKey));return Object.fromEntries(_.flatMap(i=>n.has(i)&&e[i]!==void 0?[[i,e[i]]]:[]))}function E(t,e=!1){const n=t.compositionSpec.children.find(l=>t.children[l]?.semanticSpec.layers.length>0)??t.compositionSpec.children[0],i=t.children[n];if(i===void 0)throw new Error(`Facet "${t.compositionSpec.id}" requires a retained child.`);const{facets:s,guides:o,...d}=t.materializationConfigs,{legend:c,...r}=t.semanticSpec.guides,a={...i.materializationConfigs.guides,axis:o?.axis??i.guideConfigs.axis,grid:o?.grid??i.guideConfigs.grid};return e&&delete d.title,new t.constructor({semanticSpec:e?{...t.semanticSpec,title:{},guides:{...r,...i.semanticSpec.guides.legend===void 0?{}:{legend:i.semanticSpec.guides.legend}}}:t.semanticSpec,graphicSpec:i.graphicSpec,resolvedScales:t.resolvedScales,materializationConfigs:A({...d,...i.materializationConfigs.theme===void 0?{}:{theme:i.materializationConfigs.theme},...a===void 0?{}:{guides:a},canvas:i.materializationConfigs.canvas}),children:{},context:t.context,trace:t.trace,actionStack:t.actionStack,actionSequence:t._actionSequence})}function D(t,{scales:e,guides:n}){const i=t.compositionSpec;if(i.facet.repeat!==void 0){const f=E(t),u=Q(f,{id:i.id,...i.facet.repeat}),m=Z(f,u,C(f,e),!0);return q(t,{children:m.children,compositionSpec:{...i,facet:{...i.facet,scales:e,guides:n}}},i.children)}const s=i.facet.grid===void 0?G(t.semanticSpec,{id:i.id,data:i.facet.data,field:i.facet.field,values:i.facet.values}):I(t.semanticSpec,{id:i.id,data:i.facet.data,rows:i.facet.grid.rows,columns:i.facet.grid.columns,combinations:i.facet.grid.combinations}),o=C(t,e),d=j(t.semanticSpec,o),c=E(t),r=H(c,s,{closeInheritedAction:!0,stripTitle:!0,scales:o}),a={...i,facet:{...i.facet,scales:d.channels,guides:n}};let l=t._withoutMaterializationConfig(["guides","legend"]);for(const[f,u]of Object.entries(c.guideConfigs.legend??{}))l=l._withLegendConfig(f,u);return q(l,{children:r.children,compositionSpec:a},a.children)}function L(t,e,n,i="editFacetHeaders"){w(e,B,i,{allowEmpty:!1,emptyMessage:`${i} requires at least one change.`});const s=e.role??"all";if(!["all","row","column"].includes(s))throw new Error(`Unknown facet header role "${s}".`);if(s==="all"&&Object.hasOwn(e,"side"))throw new Error(`${i} side requires an explicit row or column role.`);if(s==="all"&&Object.hasOwn(e,"title"))throw new Error(`${i} title requires an explicit row or column role.`);if(s==="row"&&n===void 0)throw new Error(`${i} row role requires a row-column facet grid.`);if(Object.hasOwn(e,"side")){const a=s==="row"?["left","right"]:n===void 0?["top","bottom","left","right"]:["top","bottom"];if(!a.includes(e.side))throw new Error(`${i} ${s} side must be ${a.join(" or ")}.`)}if(Object.hasOwn(e,"align")&&!["start","center","end"].includes(e.align))throw new Error(`${i} align must be start, center, or end.`);if(s==="all"&&Object.keys(e).every(a=>a==="role"))throw new Error(`${i} requires at least one change.`);const o=K(t),d=s==="all"?"common":s,c={...o[d]};if(Object.hasOwn(e,"title")){const a=Ce(c.title,e.title);a===void 0?delete c.title:c.title=a}for(const a of Y)Object.hasOwn(e,a)&&(c[a]=e[a]);Object.hasOwn(e,"rotation")&&(c.rotation=be(e.rotation,`${i} ${s} rotation`)),Object.hasOwn(e,"side")&&(c.side=e.side),Object.hasOwn(e,"labelMap")&&(e.labelMap==="auto"?delete c.labelMap:c.labelMap=W(e.labelMap,`${i} ${s} labelMap`));const r={...o,...s==="all"?{}:{mode:"roles"},[d]:c};for(const a of["all","row","column"]){const l=Oe(r,a);if(ue(l.fontSize,`Facet ${a} header fontSize`),$(l.fontFamily,`Facet ${a} header fontFamily`),$(l.color,`Facet ${a} header color`),pe(l.offset,`Facet ${a} header offset`),l.rotation!==void 0&&!Number.isFinite(l.rotation))throw new TypeError(`Facet ${a} header rotation must resolve to finite radians.`);if(!(typeof l.fontWeight=="string"&&l.fontWeight.length>0||Number.isFinite(l.fontWeight)))throw new TypeError(`Facet ${a} header fontWeight must be a non-empty string or number.`);if(!["start","center","end"].includes(l.align))throw new Error(`Facet ${a} header align must be start, center, or end.`);l.labelMap!==void 0&&W(l.labelMap,`Facet ${a} header labelMap`)}return r}function _e(t,e){let n=Ee();if(t===void 0)return n;w(t,[...Y,"labelMap","row","column"],"facet.headers");const{row:i,column:s,...o}=t;Object.keys(o).length>0&&(n=L(n,o,e,"facet.headers"));for(const[d,c]of[["row",i],["column",s]])c!==void 0&&(w(c,B.filter(r=>r!=="role"),`facet.headers.${d}`),n=L(n,{...c,role:d},e,`facet.headers.${d}`));return n}function X(t,e,{definition:n,derived:i,guides:s,scalePolicies:o,facet:d}){const c=t.materializationConfigs.facets?.[n.id]?.headers,r=e.headers===void 0&&c!==void 0?K(c):_e(e.headers,n.grid),a=he({spacing:e.spacing,topLane:e.topLane,plots:Object.entries(i.children).map(([f,u])=>({id:f,...oe(u)})),children:n.cells.map(f=>({...me(f.id,i.children[f.id]),value:f.value,...n.grid===void 0?{}:{row:f.row,column:f.column}})),columns:n.grid?.columns.values.length??e.columns,gap:e.gap,align:e.align,padding:e.padding,sharedLegend:s.legend==="shared"}),l={id:n.id,type:"facet",children:n.cells.map(f=>f.id),columns:a.columns,gap:a.gap,align:a.align,padding:a.padding,...a.spacing===void 0?{}:{spacing:a.spacing},...a.topLane===void 0?{}:{topLane:a.topLane},facet:{data:n.data,...d,scales:o.channels,guides:s}};return q(t._withMaterializationConfig(["facets",n.id],{headers:r}),{children:i.children,compositionSpec:l},l.children)}function J(t,e,n){return T({op:t,description:e},t==="facet"?xe:ze,function(i={}){const s=F(i.guides),o=n(this.semanticSpec,i);x(s.axes,o.family);const d=j(this.semanticSpec,i.scales??{}),c=H(this,o,{closeInheritedAction:!0,stripTitle:!0,scales:i.scales??{}}),r=o.grid===void 0?{field:o.field,values:o.values}:{values:o.cells.map(a=>a.value),grid:{...o.grid,cells:o.cells.map(a=>({id:a.id,row:a.row,column:a.column,rowValue:a.rowValue,columnValue:a.columnValue,empty:a.empty}))}};return X(this,i,{definition:o,derived:c,guides:s,scalePolicies:d,facet:r})})}const je=J("facet","Repeat one direct-source chart by field value.",G);function Q(t,e){if(!S(e))throw new TypeError("repeatCharts options must be a plain object.");const n=P(e.id??"repeat","Repeat id"),i=S(e.channel);if(i){const p=Object.keys(e.channel);if(p.length!==1||p[0]!=="parallelDimension"||typeof e.channel.parallelDimension!="string"||e.channel.parallelDimension.length===0)throw new Error("repeatCharts Parallel channel must contain exactly one non-empty parallelDimension.")}else if(!["x","y","theta","r"].includes(e.channel))throw new Error('repeatCharts channel must be "x", "y", "theta", "r", or one parallelDimension.');if(!Array.isArray(e.fields)||e.fields.length===0||e.fields.some(p=>typeof p!="string"||p.length===0))throw new TypeError("repeatCharts fields must be a non-empty array of field names.");if(new Set(e.fields).size!==e.fields.length)throw new Error("repeatCharts fields must be unique.");const s=k(t.semanticSpec).family,o=e.channel==="r"?"radius":e.channel,d=t.semanticSpec.layers.filter(p=>{if(i){const v=(p.encoding?.parallel?.dimensions??[]).filter(y=>y.field===e.channel.parallelDimension);return s==="parallel"&&p.mark?.type==="line"&&v.length===1}if(["x","y"].includes(e.channel))return s==="cartesian"&&p.encoding?.x?.scale!==void 0&&p.encoding?.y?.scale!==void 0&&p.encoding?.[e.channel]?.field!==void 0&&["point","line","area","bar","rule","tick","rect"].includes(p.mark?.type);const g=t.markConfigs[p.id]?.compositionRole;return s!=="polar"||["pie","radar"].includes(g)?!1:["point","line","arc"].includes(p.mark?.type)&&p.encoding?.[o]?.field!==void 0});let c;if(e.target!==void 0){if(c=P(e.target,"Repeat target"),!d.some(p=>p.id===c))throw new Error(`repeatCharts target "${c}" is not eligible for the requested field role.`)}else if(d.length===1)c=d[0].id;else throw new Error(d.length===0?"repeatCharts requires one eligible complete mark for the requested field role.":"repeatCharts target is ambiguous; provide target.");if(t.semanticSpec.layers.filter(p=>{if(p.id===c)return!1;const g=p.mark?.type==="text"&&p.source===c,v=t.markConfigs[p.id]?.statisticalReference?.source===c;return!g&&!v}).length>0)throw new Error("repeatCharts currently supports one direct mark only.");const a=t.markConfigs[c]??{},l=["boxPlot","gradientPlot","violinPlot","regressionPlot","endpointPlot","ecdfPlot","raincloudPlot","intervalPlot"].find(p=>a[p]!==void 0);if(l!==void 0)throw new Error(`repeatCharts does not replace the ${l} composite role.`);const f=U(t,c),u=ve(t,f.data);if(u?.transform?.length>0&&u.transform[0].type!=="lookup")throw new Error("repeatCharts does not rewrite a derived dataset dependency.");const m=i?f.encoding.parallel:f.encoding[o],h=i?m.dimensions.findIndex(p=>p.field===e.channel.parallelDimension):void 0;return{id:n,target:c,channel:i?{...e.channel}:e.channel,policyKey:i?"parallelDimensions":e.channel,fields:[...e.fields],data:f.data,encoding:m,...h===void 0?{}:{dimensionIndex:h},cells:e.fields.map((p,g)=>({id:`${n}-field-${g+1}`,field:p,value:p}))}}function qe(t,e){const n=t.encoding;return{target:t.target,field:e,fieldType:n.fieldType,...n.temporalUnit===void 0?{}:{temporalUnit:n.temporalUnit},...n.aggregate===void 0?{}:{aggregate:n.aggregate},...n.bin===void 0?{}:{bin:n.bin},...n.stack===void 0?{}:{stack:n.stack},...n.weight===void 0?{}:{weight:n.weight},scale:{id:n.scale}}}function De(t,e,n){return e.encoding.dimensions.map((i,s)=>{const o=Fe(t,i.scale);if(o===void 0)throw new Error(`repeatCharts Parallel dimension scale "${i.scale}" is missing.`);const{id:d,...c}=o,r=s===e.dimensionIndex;return{field:r?n:i.field,fieldType:i.fieldType,title:r&&i.title===i.field?n:i.title,scale:c}})}function Le(t,e,n){if(S(e.channel))return t.encodeParallelCoordinates({target:e.target,coordinate:U(t,e.target).coordinate,dimensions:De(t,e,n),...e.encoding.key===void 0?{}:{key:e.encoding.key},missing:e.encoding.missing});const i={x:"encodeX",y:"encodeY",theta:"encodeTheta",r:"encodeR"}[e.channel];return t[i](qe(e,n))}function Z(t,e,n,i){const s=t.semanticSpec.title.text===void 0?t:t.removeTitle(),o=Object.fromEntries(e.cells.map(d=>[d.id,Le(s,e,d.field)]));return ge(s,e.cells.map(d=>d.id),o,n,i)}const Me=T({op:"repeatCharts",description:"Repeat one direct chart across an ordered field-role list."},Te,function(t={}){const e=F(t.guides),n=Q(this,t);x(e.axes,k(this.semanticSpec).family,!0);const i={...t.scales??{},[n.policyKey]:t.scales?.[n.policyKey]??"independent"},s=j(this.semanticSpec,i),o=Z(this,n,i,!0);return X(this,t,{definition:n,derived:o,guides:e,scalePolicies:s,facet:{values:n.fields,repeat:{target:n.target,channel:n.channel,fields:n.fields}}})}),Re=J("facetGrid","Repeat one direct-source Cartesian chart across a row and column field grid.",I),Ae=z({op:"editFacetHeaders",description:"Edit parent-owned facet header appearance.",scope:"composition"},function(t={}){b(this,"editFacetHeaders");const e=this.compositionSpec.id,n=this.materializationConfigs.facets?.[e];if(!S(n?.headers))throw new Error(`Facet "${e}" requires header configuration.`);const i=L(n.headers,t,this.compositionSpec.facet.grid);return this._withMaterializationConfig(["facets",e],{...n,headers:i}).materializeComposition()}),Pe=z({op:"editFacetScales",description:"Edit facet scale-resolution policies and rederive every cell.",scope:"composition"},function(t={}){b(this,"editFacetScales"),w(t,_,"editFacetScales",{allowEmpty:!1,emptyMessage:"editFacetScales requires at least one channel policy change."});for(const i of Object.keys(t))if(!N(this.semanticSpec).some(s=>s.policyKey===i))throw new Error(`Facet scale channel "${i}" is not used by an affected layer.`);const e=this.compositionSpec.facet,n={...e.scales,...t};if(_.every(i=>n[i]===e.scales[i]))throw new Error("editFacetScales requires at least one channel policy change.");return D(this,{scales:n,guides:e.guides})}),Ge=z({op:"editFacetGuides",description:"Edit facet guide ownership and rederive every cell.",scope:"composition"},function(t={}){b(this,"editFacetGuides"),w(t,V,"editFacetGuides",{allowEmpty:!1,emptyMessage:"editFacetGuides requires at least one guide policy."});const e=this.compositionSpec.facet;Object.hasOwn(t,"legend")&&F({legend:t.legend});const n=F({...e.guides,...t,...Object.hasOwn(t,"axes")?{axes:R(t.axes,e.guides.axes)}:{}},!0),i=k(this.semanticSpec).family;return x(n.axes,i,e.repeat!==void 0),D(this,{scales:e.scales,guides:n})});function Ie(t,e,n=!1){if(!(t instanceof e.constructor))throw new TypeError("editFacetSource program must be a ChartProgram.");if(t.compositionSpec!==void 0)throw new Error("editFacetSource program must be a complete unit ChartProgram.");if(t.actionStack.length!==0)throw new Error("editFacetSource program has an unfinished action stack.");const i=e.compositionSpec.children.find(a=>e.children[a]?.semanticSpec.layers.length>0)??e.compositionSpec.children[0],s=e.children[i]?.materializationConfigs??{},{canvas:o,theme:d,facets:c,...r}=s;return new e.constructor({semanticSpec:t.semanticSpec,graphicSpec:t.graphicSpec,resolvedScales:t.resolvedScales,materializationConfigs:A({...n?{...r,...t.materializationConfigs}:{...t.materializationConfigs,...r},...t.materializationConfigs.canvas===void 0?{}:{canvas:t.materializationConfigs.canvas},...t.materializationConfigs.theme===void 0?{}:{theme:t.materializationConfigs.theme}}),children:{},context:t.context,trace:e.trace,actionStack:e.actionStack,actionSequence:e._actionSequence})}const Ne=T({op:"editFacetSource",description:"Reapply one facet, grid, or repeat recipe to a revised complete unit program.",scope:"composition"},ke,function(t={}){b(this,"editFacetSource");const e=this.compositionSpec,n=this.materializationConfigs.facets?.[e.id];if(Object.hasOwn(t,"program")===Object.hasOwn(t,"action"))throw new Error("editFacetSource requires exactly one of program or action.");let i=t.program,s=this;if(t.action!==void 0){w(t.action,["method","options"],"editFacetSource action");const l=$(t.action.method,"editFacetSource action method");let f=E(this,!0);const u=typeof f[l]=="function"?ce(f[l]):void 0;if(!u||!$e.has(l)||u.scope==="composition"||/^(?:_|materialize|rematerialize|replay|editSemantic$|editGraphics$|createGraphics$)/.test(l))throw new Error("editFacetSource action requires a unit chart-authoring action.");f=de(f,fe({marks:f.semanticSpec.layers.map(h=>le(f,h))}));for(const h of ne(f)){const p=f.guideConfigs.axis?.[h.id]??{},g=ie(h.channel,h.id);for(const[v,y]of["line","ticks","labels","title"].entries()){if(!p[y]||f.graphicSpec.objects[g[v]])continue;const{mode:M,inferredValues:He,inferredText:Ue,inferredRotation:We,inferredOffset:Ke,...O}=p[y];M==="values"&&delete O.count,M==="count"&&delete O.values;const ee=y[0].toUpperCase()+y.slice(1);f=f[`create${h.channel.toUpperCase()}Axis${ee}`]({...O,...ae(h.id,h.channel)})}}const m=f[l](t.action.options??{});s=new this.constructor({...this,trace:m.trace,actionStack:m.actionStack,actionSequence:m._actionSequence}),i=new this.constructor({...m,actionStack:[],actionSequence:m._actionSequence})}let o=Ie(i,s,t.action!==void 0);n!==void 0&&(o=o._withMaterializationConfig(["facets",e.id],n));const d=S(e.facet.guides.legend),c=d?{...e.facet.guides,legend:!1}:e.facet.guides;let r;const a=C(o,C(this,e.facet.scales));if(e.facet.grid!==void 0?r=o.facetGrid({id:e.id,data:e.facet.data,rows:e.facet.grid.rows,columns:e.facet.grid.columns,combinations:e.facet.grid.combinations,gap:e.gap,...e.spacing===void 0?{}:{spacing:e.spacing},...e.topLane===void 0?{}:{topLane:e.topLane},align:e.align,padding:e.padding,scales:a,guides:c}):e.facet.repeat!==void 0?r=o.repeatCharts({id:e.id,...e.facet.repeat,columns:e.columns,gap:e.gap,...e.spacing===void 0?{}:{spacing:e.spacing},...e.topLane===void 0?{}:{topLane:e.topLane},align:e.align,padding:e.padding,scales:a,guides:c}):r=o.facet({id:e.id,data:e.facet.data,field:e.facet.field,values:e.facet.values,columns:e.columns,gap:e.gap,...e.spacing===void 0?{}:{spacing:e.spacing},...e.topLane===void 0?{}:{topLane:e.topLane},align:e.align,padding:e.padding,scales:a,guides:c}),d){const l=r.compositionSpec;r=r._withCompositionState({children:r.children,compositionSpec:{...l,facet:{...l.facet,guides:e.facet.guides}}})}return n!==void 0&&(r=r._withMaterializationConfig(["facets",e.id],n).materializeComposition()),r.semanticSpec.title.text!==void 0&&(r=r.removeTitle()),this.semanticSpec.title.text!==void 0&&(r=r.createTitle({...this.titleConfig,...this.semanticSpec.title})),ye(r,this.materializationConfigs.theme)});function Et(t){se((e,n)=>{const i=E(e),s=i.semanticSpec.title.text===void 0?i:i.removeTitle(),{program:o,plan:d}=re(s,n),c=e.compositionSpec,r=new e.constructor({...o,semanticSpec:{...o.semanticSpec,title:e.semanticSpec.title},graphicSpec:e.graphicSpec,children:e.children,materializationConfigs:{...o.materializationConfigs,facets:e.materializationConfigs.facets,...e.titleConfig===void 0?{}:{title:e.titleConfig}},actionSequence:o._actionSequence,compositionSpec:{...c,facet:{...c.facet,data:d.replacements.get(c.facet.data)??c.facet.data}}});return D(r,c.facet)}),t.prototype.replayDerivedData=Se,t.prototype.composeFacetGuides=we,t.prototype.facet=je,t.prototype.facetGrid=Re,t.prototype.repeatCharts=Me,t.prototype.editFacetHeaders=Ae,t.prototype.editFacetScales=Pe,t.prototype.editFacetGuides=Ge,t.prototype.editFacetSource=Ne}export{Ge as editFacetGuides,Ae as editFacetHeaders,Pe as editFacetScales,Ne as editFacetSource,je as facet,Re as facetGrid,Et as registerFacetActions,Me as repeatCharts};
1
+ import{normalizeFacetAxes as P,validateFacetAxisPolicy as k}from"../../core/facetAxisPolicy.js";import{DEFAULT_ACTION_METHODS as ne}from"../../core/vocabulary.js";import{axisGraphicIds as ae}from"../../materialization/guides/resources.js";import{facetLegendIntent as oe}from"../../materialization/facetGuides/owner.js";import{legendBlockChannels as ce}from"../../grammar/legendBlocks.js";import{cartesianAxisOwners as se,axisTargetArgs as re}from"../../selectors/axes.js";import{resolveGraphicBounds as de}from"../../layout/canvas.js";import{action as T,closedAction as $,getWrappedActionMetadata as le}from"../../core/action.js";import{registerFacetDataRevision as fe,reviseUnitData as pe}from"../data/revise.js";import{applyMaterializationPlan as ue}from"../../materialization/dependencies.js";import{getMarkMaterializationStep as he}from"../../materialization/marks/index.js";import{buildMaterializationPlan as me}from"../../materialization/planner.js";import{freezeOwned as G,isPlainObject as b}from"../../core/immutable.js";import{validateUserId as I}from"../../core/identifiers.js";import{validateNonEmptyString as j,validateNonNegativeFinite as ge,validateOptionObject as v,validatePositiveFinite as Se}from"../../core/validation.js";import{resolveFacetDefinition as N,resolveFacetGridDefinition as H}from"../../grammar/facets/index.js";import{collectFacetScaleBindings as U,resolveFacetFamily as _}from"../../grammar/facets/dependencies.js";import{FACET_SCALE_CHANNELS as L,normalizeFacetScalePolicies as q}from"../../grammar/facets/scales.js";import{resolveFacetLayout as we}from"../../layout/facets.js";import{compositionChildDescriptor as ye}from"../../materialization/composition.js";import{deriveFacetChildren as W}from"./derive.js";import{resolveFacetChildrenScales as be}from"./derive.js";import{replayDerivedData as ve}from"./replay.js";import{composeFacetGuides as Ce}from"./guides.js";import{applyCompositionState as D}from"../composition/actions.js";import{replayCompositionThemeState as Fe}from"../theme/composition.js";import{findDataset as Ee}from"../../selectors/datasets.js";import{findLayer as K}from"../../selectors/layers.js";import{findSemanticScale as Oe}from"../../selectors/scales.js";import{normalizeDisplayLabelMap as V}from"../../grammar/displayLabels.js";import{resolveRotation as xe}from"../../grammar/rotation.js";import{normalizeFacetHeaderTitle as ze}from"../../grammar/facetHeaders.js";import{createDefaultFacetHeaders as ke,normalizeFacetHeadersConfig as B,resolveFacetHeaderConfig as Te}from"../../materialization/facetHeaders.js";const $e=Object.freeze(["id","field","data","values","columns","gap","spacing","topLane","align","padding","scales","guides","headers"]),je=Object.freeze(["id","data","rows","columns","combinations","gap","spacing","topLane","align","padding","scales","guides","headers"]),_e=Object.freeze(["id","target","channel","fields","columns","gap","spacing","topLane","align","padding","scales","guides","headers"]),Le=new Set(ne),qe=Object.freeze(["program","action"]),Y=Object.freeze(["axes","legend"]),X=Object.freeze(["fontSize","fontFamily","fontWeight","color","offset","role","labelMap","side","align","rotation","title","anchor"]),J=Object.freeze(["fontSize","fontFamily","fontWeight","color","offset","align","rotation","anchor"]);function F(t,e=!1){if(t===void 0)return{axes:"each",legend:!1};v(t,Y,"facet.guides");const n=P(t.axes),i=t.legend??!1;if(i!==!1&&i!=="shared"&&!b(i))throw new Error('facet guides.legend must be false, "shared", or legend options.');if(!e&&b(i)&&Object.hasOwn(i,"type"))throw new Error("Facet legend options do not accept internal type tags.");return{axes:n,legend:i}}function E(t,e){if(t._assertCompositionProgram(e),t.compositionSpec.type!=="facet")throw new Error(`${e} requires a facet composition.`)}function O(t,e){const n=new Set(U(t.semanticSpec).map(i=>i.policyKey));return Object.fromEntries(L.flatMap(i=>n.has(i)&&e[i]!==void 0?[[i,e[i]]]:[]))}function x(t,e=!1){const n=t.compositionSpec.children.find(f=>t.children[f]?.semanticSpec.layers.length>0)??t.compositionSpec.children[0],i=t.children[n];if(i===void 0)throw new Error(`Facet "${t.compositionSpec.id}" requires a retained child.`);const{facets:s,guides:o,...d}=t.materializationConfigs,{legend:c,...p}=t.semanticSpec.guides,a={...i.materializationConfigs.guides,axis:o?.axis??i.guideConfigs.axis,grid:o?.grid??i.guideConfigs.grid};return e&&delete d.title,new t.constructor({semanticSpec:e?{...t.semanticSpec,title:{},guides:{...p,...i.semanticSpec.guides.legend===void 0?{}:{legend:i.semanticSpec.guides.legend}}}:t.semanticSpec,graphicSpec:i.graphicSpec,resolvedScales:t.resolvedScales,materializationConfigs:G({...d,...i.materializationConfigs.theme===void 0?{}:{theme:i.materializationConfigs.theme},...a===void 0?{}:{guides:a},canvas:i.materializationConfigs.canvas}),children:{},context:t.context,trace:t.trace,actionStack:t.actionStack,actionSequence:t._actionSequence})}function M(t,{scales:e,guides:n}){const i=t.compositionSpec;if(i.facet.repeat!==void 0){const l=x(t),u=ee(l,{id:i.id,...i.facet.repeat}),w=te(l,u,O(l,e),!0);return D(t,{children:w.children,compositionSpec:{...i,facet:{...i.facet,scales:e,guides:n}}},i.children)}const s=i.facet.grid===void 0?N(t.semanticSpec,{id:i.id,data:i.facet.data,field:i.facet.field,values:i.facet.values}):H(t.semanticSpec,{id:i.id,data:i.facet.data,rows:i.facet.grid.rows,columns:i.facet.grid.columns,combinations:i.facet.grid.combinations}),o=O(t,e),d=q(t.semanticSpec,o),c=x(t),p=W(c,s,{closeInheritedAction:!0,stripTitle:!0,scales:o}),a={...i,facet:{...i.facet,scales:d.channels,guides:n}};let f=t._withoutMaterializationConfig(["guides","legend"]);for(const[l,u]of Object.entries(c.guideConfigs.legend??{}))f=f._withLegendConfig(l,u);return D(f,{children:p.children,compositionSpec:a},a.children)}function R(t,e,n,i="editFacetHeaders"){v(e,X,i,{allowEmpty:!1,emptyMessage:`${i} requires at least one change.`});const s=e.role??"all";if(!["all","row","column"].includes(s))throw new Error(`Unknown facet header role "${s}".`);if(s==="all"&&Object.hasOwn(e,"side"))throw new Error(`${i} side requires an explicit row or column role.`);if(s==="all"&&Object.hasOwn(e,"title"))throw new Error(`${i} title requires an explicit row or column role.`);if(s==="row"&&n===void 0)throw new Error(`${i} row role requires a row-column facet grid.`);if(Object.hasOwn(e,"side")){const a=s==="row"?["left","right"]:n===void 0?["top","bottom","left","right"]:["top","bottom"];if(!a.includes(e.side))throw new Error(`${i} ${s} side must be ${a.join(" or ")}.`)}if(Object.hasOwn(e,"align")&&!["start","center","end"].includes(e.align))throw new Error(`${i} align must be start, center, or end.`);if(s==="all"&&Object.keys(e).every(a=>a==="role"))throw new Error(`${i} requires at least one change.`);const o=B(t),d=s==="all"?"common":s,c={...o[d]};if(Object.hasOwn(e,"title")){const a=ze(c.title,e.title);a===void 0?delete c.title:c.title=a}for(const a of J)Object.hasOwn(e,a)&&(c[a]=e[a]);Object.hasOwn(e,"rotation")&&(c.rotation=xe(e.rotation,`${i} ${s} rotation`)),Object.hasOwn(e,"side")&&(c.side=e.side),Object.hasOwn(e,"labelMap")&&(e.labelMap==="auto"?delete c.labelMap:c.labelMap=V(e.labelMap,`${i} ${s} labelMap`));const p={...o,...s==="all"?{}:{mode:"roles"},[d]:c};for(const a of["all","row","column"]){const f=Te(p,a);if(Se(f.fontSize,`Facet ${a} header fontSize`),j(f.fontFamily,`Facet ${a} header fontFamily`),j(f.color,`Facet ${a} header color`),ge(f.offset,`Facet ${a} header offset`),f.rotation!==void 0&&!Number.isFinite(f.rotation))throw new TypeError(`Facet ${a} header rotation must resolve to finite radians.`);if(!(typeof f.fontWeight=="string"&&f.fontWeight.length>0||Number.isFinite(f.fontWeight)))throw new TypeError(`Facet ${a} header fontWeight must be a non-empty string or number.`);if(!["start","center","end"].includes(f.align))throw new Error(`Facet ${a} header align must be start, center, or end.`);f.labelMap!==void 0&&V(f.labelMap,`Facet ${a} header labelMap`)}return p}function De(t,e){let n=ke();if(t===void 0)return n;v(t,[...J,"labelMap","row","column"],"facet.headers");const{row:i,column:s,...o}=t;Object.keys(o).length>0&&(n=R(n,o,e,"facet.headers"));for(const[d,c]of[["row",i],["column",s]])c!==void 0&&(v(c,X.filter(p=>p!=="role"),`facet.headers.${d}`),n=R(n,{...c,role:d},e,`facet.headers.${d}`));return n}function Q(t,e,{definition:n,derived:i,guides:s,scalePolicies:o,facet:d}){const c=t.materializationConfigs.facets?.[n.id]?.headers,p=e.headers===void 0&&c!==void 0?B(c):De(e.headers,n.grid),a=we({spacing:e.spacing,topLane:e.topLane,plots:Object.entries(i.children).map(([l,u])=>({id:l,...de(u)})),children:n.cells.map(l=>({...ye(l.id,i.children[l.id]),value:l.value,...n.grid===void 0?{}:{row:l.row,column:l.column}})),columns:n.grid?.columns.values.length??e.columns,gap:e.gap,align:e.align,padding:e.padding,sharedLegend:s.legend==="shared"}),f={id:n.id,type:"facet",children:n.cells.map(l=>l.id),columns:a.columns,gap:a.gap,align:a.align,padding:a.padding,...a.spacing===void 0?{}:{spacing:a.spacing},...a.topLane===void 0?{}:{topLane:a.topLane},facet:{data:n.data,...d,scales:o.channels,guides:s}};return D(t._withMaterializationConfig(["facets",n.id],{headers:p}),{children:i.children,compositionSpec:f},f.children)}function Z(t,e,n){return $({op:t,description:e},t==="facet"?$e:je,function(i={}){const s=F(i.guides),o=n(this.semanticSpec,i);k(s.axes,o.family);const d=q(this.semanticSpec,i.scales??{}),c=W(this,o,{closeInheritedAction:!0,stripTitle:!0,scales:i.scales??{}}),p=o.grid===void 0?{field:o.field,values:o.values}:{values:o.cells.map(a=>a.value),grid:{...o.grid,cells:o.cells.map(a=>({id:a.id,row:a.row,column:a.column,rowValue:a.rowValue,columnValue:a.columnValue,empty:a.empty}))}};return Q(this,i,{definition:o,derived:c,guides:s,scalePolicies:d,facet:p})})}const Me=Z("facet","Repeat one direct-source chart by field value.",N);function ee(t,e){if(!b(e))throw new TypeError("repeatCharts options must be a plain object.");const n=I(e.id??"repeat","Repeat id"),i=b(e.channel);if(i){const r=Object.keys(e.channel);if(r.length!==1||r[0]!=="parallelDimension"||typeof e.channel.parallelDimension!="string"||e.channel.parallelDimension.length===0)throw new Error("repeatCharts Parallel channel must contain exactly one non-empty parallelDimension.")}else if(!["x","y","theta","r"].includes(e.channel))throw new Error('repeatCharts channel must be "x", "y", "theta", "r", or one parallelDimension.');if(!Array.isArray(e.fields)||e.fields.length===0||e.fields.some(r=>typeof r!="string"||r.length===0))throw new TypeError("repeatCharts fields must be a non-empty array of field names.");if(new Set(e.fields).size!==e.fields.length)throw new Error("repeatCharts fields must be unique.");const s=_(t.semanticSpec).family,o=e.channel==="r"?"radius":e.channel,d=t.semanticSpec.layers.filter(r=>{if(i){const g=(r.encoding?.parallel?.dimensions??[]).filter(S=>S.field===e.channel.parallelDimension);return s==="parallel"&&r.mark?.type==="line"&&g.length===1}if(["x","y"].includes(e.channel))return s==="cartesian"&&r.encoding?.x?.scale!==void 0&&r.encoding?.y?.scale!==void 0&&r.encoding?.[e.channel]?.field!==void 0&&["point","line","area","bar","rule","tick","rect"].includes(r.mark?.type);const h=t.markConfigs[r.id]?.compositionRole;return s!=="polar"||["pie","radar"].includes(h)?!1:["point","line","arc"].includes(r.mark?.type)&&r.encoding?.[o]?.field!==void 0});let c;if(e.target!==void 0){if(c=I(e.target,"Repeat target"),!d.some(r=>r.id===c))throw new Error(`repeatCharts target "${c}" is not eligible for the requested field role.`)}else if(d.length===1)c=d[0].id;else throw new Error(d.length===0?"repeatCharts requires one eligible complete mark for the requested field role.":"repeatCharts target is ambiguous; provide target.");if(t.semanticSpec.layers.filter(r=>{if(r.id===c)return!1;const h=r.mark?.type==="text"&&r.source===c,g=t.markConfigs[r.id]?.statisticalReference?.source===c;return!h&&!g}).length>0)throw new Error("repeatCharts currently supports one direct mark only.");const a=t.markConfigs[c]??{},f=["boxPlot","gradientPlot","violinPlot","regressionPlot","endpointPlot","ecdfPlot","raincloudPlot","intervalPlot"].find(r=>a[r]!==void 0);if(f!==void 0)throw new Error(`repeatCharts does not replace the ${f} composite role.`);const l=K(t,c),u=Ee(t,l.data);if(u?.transform?.length>0&&u.transform[0].type!=="lookup")throw new Error("repeatCharts does not rewrite a derived dataset dependency.");const w=i?l.encoding.parallel:l.encoding[o],m=i?w.dimensions.findIndex(r=>r.field===e.channel.parallelDimension):void 0;return{id:n,target:c,channel:i?{...e.channel}:e.channel,policyKey:i?"parallelDimensions":e.channel,fields:[...e.fields],data:l.data,encoding:w,...m===void 0?{}:{dimensionIndex:m},cells:e.fields.map((r,h)=>({id:`${n}-field-${h+1}`,field:r,value:r}))}}function Re(t,e){const n=t.encoding;return{target:t.target,field:e,fieldType:n.fieldType,...n.temporalUnit===void 0?{}:{temporalUnit:n.temporalUnit},...n.aggregate===void 0?{}:{aggregate:n.aggregate},...n.bin===void 0?{}:{bin:n.bin},...n.stack===void 0?{}:{stack:n.stack},...n.weight===void 0?{}:{weight:n.weight},scale:{id:n.scale}}}function Ae(t,e,n){return e.encoding.dimensions.map((i,s)=>{const o=Oe(t,i.scale);if(o===void 0)throw new Error(`repeatCharts Parallel dimension scale "${i.scale}" is missing.`);const{id:d,...c}=o,p=s===e.dimensionIndex;return{field:p?n:i.field,fieldType:i.fieldType,title:p&&i.title===i.field?n:i.title,scale:c}})}function Pe(t,e,n){if(b(e.channel))return t.encodeParallelCoordinates({target:e.target,coordinate:K(t,e.target).coordinate,dimensions:Ae(t,e,n),...e.encoding.key===void 0?{}:{key:e.encoding.key},missing:e.encoding.missing});const i={x:"encodeX",y:"encodeY",theta:"encodeTheta",r:"encodeR"}[e.channel];return t[i](Re(e,n))}function te(t,e,n,i){const s=t.semanticSpec.title.text===void 0?t:t.removeTitle(),o=Object.fromEntries(e.cells.map(d=>[d.id,Pe(s,e,d.field)]));return be(s,e.cells.map(d=>d.id),o,n,i)}const Ge=$({op:"repeatCharts",description:"Repeat one direct chart across an ordered field-role list."},_e,function(t={}){const e=F(t.guides),n=ee(this,t);k(e.axes,_(this.semanticSpec).family,!0);const i={...t.scales??{},[n.policyKey]:t.scales?.[n.policyKey]??"independent"},s=q(this.semanticSpec,i),o=te(this,n,i,!0);return Q(this,t,{definition:n,derived:o,guides:e,scalePolicies:s,facet:{values:n.fields,repeat:{target:n.target,channel:n.channel,fields:n.fields}}})}),Ie=Z("facetGrid","Repeat one direct-source Cartesian chart across a row and column field grid.",H),Ne=T({op:"editFacetHeaders",description:"Edit parent-owned facet header appearance.",scope:"composition"},function(t={}){E(this,"editFacetHeaders");const e=this.compositionSpec.id,n=this.materializationConfigs.facets?.[e];if(!b(n?.headers))throw new Error(`Facet "${e}" requires header configuration.`);const i=R(n.headers,t,this.compositionSpec.facet.grid);return this._withMaterializationConfig(["facets",e],{...n,headers:i}).materializeComposition()}),He=T({op:"editFacetScales",description:"Edit facet scale-resolution policies and rederive every cell.",scope:"composition"},function(t={}){E(this,"editFacetScales"),v(t,L,"editFacetScales",{allowEmpty:!1,emptyMessage:"editFacetScales requires at least one channel policy change."});for(const i of Object.keys(t))if(!U(this.semanticSpec).some(s=>s.policyKey===i))throw new Error(`Facet scale channel "${i}" is not used by an affected layer.`);const e=this.compositionSpec.facet,n={...e.scales,...t};if(L.every(i=>n[i]===e.scales[i]))throw new Error("editFacetScales requires at least one channel policy change.");return M(this,{scales:n,guides:e.guides})}),Ue=T({op:"editFacetGuides",description:"Edit facet guide ownership and rederive every cell.",scope:"composition"},function(t={}){E(this,"editFacetGuides"),v(t,Y,"editFacetGuides",{allowEmpty:!1,emptyMessage:"editFacetGuides requires at least one guide policy."});const e=this.compositionSpec.facet;Object.hasOwn(t,"legend")&&F({legend:t.legend});const n=F({...e.guides,...t,...Object.hasOwn(t,"axes")?{axes:P(t.axes,e.guides.axes)}:{}},!0),i=_(this.semanticSpec).family;return k(n.axes,i,e.repeat!==void 0),M(this,{scales:e.scales,guides:n})});function We(t,e,n=!1){if(!(t instanceof e.constructor))throw new TypeError("editFacetSource program must be a ChartProgram.");if(t.compositionSpec!==void 0)throw new Error("editFacetSource program must be a complete unit ChartProgram.");if(t.actionStack.length!==0)throw new Error("editFacetSource program has an unfinished action stack.");const i=e.compositionSpec.children.find(a=>e.children[a]?.semanticSpec.layers.length>0)??e.compositionSpec.children[0],s=e.children[i]?.materializationConfigs??{},{canvas:o,theme:d,facets:c,...p}=s;return new e.constructor({semanticSpec:t.semanticSpec,graphicSpec:t.graphicSpec,resolvedScales:t.resolvedScales,materializationConfigs:G({...n?{...p,...t.materializationConfigs}:{...t.materializationConfigs,...p},...t.materializationConfigs.canvas===void 0?{}:{canvas:t.materializationConfigs.canvas},...t.materializationConfigs.theme===void 0?{}:{theme:t.materializationConfigs.theme}}),children:{},context:t.context,trace:e.trace,actionStack:e.actionStack,actionSequence:e._actionSequence})}const Ke=$({op:"editFacetSource",description:"Reapply one facet, grid, or repeat recipe to a revised complete unit program.",scope:"composition"},qe,function(t={}){E(this,"editFacetSource");const e=this.compositionSpec,n=this.materializationConfigs.facets?.[e.id];if(Object.hasOwn(t,"program")===Object.hasOwn(t,"action"))throw new Error("editFacetSource requires exactly one of program or action.");let i=t.program,s=this,o=e.facet.guides;if(t.action!==void 0){v(t.action,["method","options"],"editFacetSource action");const l=j(t.action.method,"editFacetSource action method");let u=x(this,!0);const w=typeof u[l]=="function"?le(u[l]):void 0;if(!w||!Le.has(l)||w.scope==="composition"||/^(?:_|materialize|rematerialize|replay|editSemantic$|editGraphics$|createGraphics$)/.test(l))throw new Error("editFacetSource action requires a unit chart-authoring action.");u=ue(u,me({marks:u.semanticSpec.layers.map(r=>he(u,r))}));for(const r of se(u)){const h=u.guideConfigs.axis?.[r.id]??{},g=ae(r.channel,r.id);for(const[S,y]of["line","ticks","labels","title"].entries()){if(!h[y]||u.graphicSpec.objects[g[S]])continue;const{mode:C,inferredValues:A,inferredText:Ve,inferredRotation:Be,inferredOffset:Ye,...z}=h[y];C==="values"&&delete z.count,C==="count"&&delete z.values;const ie=y[0].toUpperCase()+y.slice(1);u=u[`create${r.channel.toUpperCase()}Axis${ie}`]({...z,...re(r.id,r.channel)})}}const m=u[l](t.action.options??{});if(l==="createLegend"&&o.legend?.type==="authored-legend"){const r=Object.entries(u.guideConfigs.suppressedLegends??{}).flatMap(([h,{channels:g}])=>g.filter(S=>!m.guideConfigs.suppressedLegends?.[h]?.channels.includes(S)).map(S=>[h,S]));if(r.length){const h=oe(m),g=Object.fromEntries(Object.entries(h.blocks).filter(([S,{config:y}])=>r.some(([C,A])=>y.target===C&&ce(S,y).includes(A))));o={...o,legend:{...o.legend,blocks:{...o.legend.blocks,...g}}}}}s=new this.constructor({...this,trace:m.trace,actionStack:m.actionStack,actionSequence:m._actionSequence}),i=new this.constructor({...m,actionStack:[],actionSequence:m._actionSequence})}let d=We(i,s,t.action!==void 0);n!==void 0&&(d=d._withMaterializationConfig(["facets",e.id],n));const c=b(o.legend),p=c?{...o,legend:!1}:o;let a;const f=O(d,O(this,e.facet.scales));if(e.facet.grid!==void 0?a=d.facetGrid({id:e.id,data:e.facet.data,rows:e.facet.grid.rows,columns:e.facet.grid.columns,combinations:e.facet.grid.combinations,gap:e.gap,...e.spacing===void 0?{}:{spacing:e.spacing},...e.topLane===void 0?{}:{topLane:e.topLane},align:e.align,padding:e.padding,scales:f,guides:p}):e.facet.repeat!==void 0?a=d.repeatCharts({id:e.id,...e.facet.repeat,columns:e.columns,gap:e.gap,...e.spacing===void 0?{}:{spacing:e.spacing},...e.topLane===void 0?{}:{topLane:e.topLane},align:e.align,padding:e.padding,scales:f,guides:p}):a=d.facet({id:e.id,data:e.facet.data,field:e.facet.field,values:e.facet.values,columns:e.columns,gap:e.gap,...e.spacing===void 0?{}:{spacing:e.spacing},...e.topLane===void 0?{}:{topLane:e.topLane},align:e.align,padding:e.padding,scales:f,guides:p}),c){const l=a.compositionSpec;a=a._withCompositionState({children:a.children,compositionSpec:{...l,facet:{...l.facet,guides:o}}})}return n!==void 0&&(a=a._withMaterializationConfig(["facets",e.id],n).materializeComposition()),a.semanticSpec.title.text!==void 0&&(a=a.removeTitle()),this.semanticSpec.title.text!==void 0&&(a=a.createTitle({...this.titleConfig,...this.semanticSpec.title})),Fe(a,this.materializationConfigs.theme)});function Tt(t){fe((e,n)=>{const i=x(e),s=i.semanticSpec.title.text===void 0?i:i.removeTitle(),{program:o,plan:d}=pe(s,n),c=e.compositionSpec,p=new e.constructor({...o,semanticSpec:{...o.semanticSpec,title:e.semanticSpec.title},graphicSpec:e.graphicSpec,children:e.children,materializationConfigs:{...o.materializationConfigs,facets:e.materializationConfigs.facets,...e.titleConfig===void 0?{}:{title:e.titleConfig}},actionSequence:o._actionSequence,compositionSpec:{...c,facet:{...c.facet,data:d.replacements.get(c.facet.data)??c.facet.data}}});return M(p,c.facet)}),t.prototype.replayDerivedData=ve,t.prototype.composeFacetGuides=Ce,t.prototype.facet=Me,t.prototype.facetGrid=Ie,t.prototype.repeatCharts=Ge,t.prototype.editFacetHeaders=Ne,t.prototype.editFacetScales=He,t.prototype.editFacetGuides=Ue,t.prototype.editFacetSource=Ke}export{Ue as editFacetGuides,Ne as editFacetHeaders,He as editFacetScales,Ke as editFacetSource,Me as facet,Ie as facetGrid,x as facetUnitTemplate,Tt as registerFacetActions,Ge as repeatCharts};
@@ -156,7 +156,7 @@ function resolvePolarAxisArgs(program, layers, channel, option, descriptor) {
156
156
  }
157
157
 
158
158
  function hasChannel(layers, channel) {
159
- return layers.some(layer => layer.encoding?.[channel] !== undefined);
159
+ return layers.some(layer => layer.encoding?.[channel] !== undefined && !(layer.mark?.type === "tick" && Object.hasOwn(layer.encoding[channel], "datum")));
160
160
  }
161
161
 
162
162
  function resolveAxisArgs(layers, channel, option) {
@@ -170,6 +170,7 @@ function resolveAxisArgs(layers, channel, option) {
170
170
  const encodedScaleIds = [
171
171
  ...new Set(
172
172
  layers
173
+ .filter(layer => !(layer.mark?.type === "tick" && Object.hasOwn(layer.encoding?.[channel] ?? {}, "datum")))
173
174
  .map(layer => layer.encoding?.[channel]?.scale)
174
175
  .filter(id => id !== undefined)
175
176
  )
@@ -1 +1 @@
1
- import{resolveAxisTickEditConfig as x}from"./ticks.js";import{resolveAxisLabelEditConfig as L}from"./labels.js";import{validateAxisTickGroupEditConfig as T}from"./tickGroups.js";import{selectCartesianAxis as w,axisTargetArgs as k,assertCartesianAxisSideAvailable as C}from"../../../selectors/axes.js";import{validateAxisPosition as $,resolveAxisFrameConfig as h}from"./policy.js";import{withGuideLayoutValidation as E,withPendingRelativeAxisTitle as j}from"../../../materialization/guides/layout.js";import{action as S}from"../../../core/action.js";import{applyMaterializationPlan as N}from"../../../materialization/planner.js";import{isPlainObject as u}from"../../../core/immutable.js";import{isLabelRelativeTitleOffset as O}from"../../../grammar/axisTextPlacement.js";import{validateKeys as P,validateOptionObject as g,validateNonNegativeFinite as y}from"../../../core/validation.js";import{AXIS_COMPONENTS as z,axisComponentId as M,hasAxisComponent as R,assertRemovableAxisComponent as m,removeAxisComponent as W}from"./components.js";const _=Object.freeze(["target","position","line","ticks","labels","ticksAndLabels","title","offset","underlay"]),G=Object.freeze(["color","lineWidth"]),F=Object.freeze(["count","values","rounding","length","color","lineWidth","alongOffset","strokeDash","strokeDashMap"]),D=Object.freeze(["count","values","rounding","offset","format","color","fontSize","fontFamily","fontWeight","rotation","maxWidth","wrap","lineHeight","overlap","alongOffset","labelMap","flush","flushThreshold","colorMap","align"]),X=Object.freeze(["count","values","rounding","ticks","labels"]),Y=Object.freeze(["text","at","offset","rotation","color","fontSize","fontFamily","fontWeight","alongOffset","align","baseline","overlap"]);function a(e,i,t,n,{allowFalse:o=!1}={}){if(Object.hasOwn(e,i)&&!(o&&e[i]===!1)){if(!u(e[i])){const f=o?"false or a plain object":"a plain object";throw new TypeError(`${n}.${i} must be ${f}.`)}P(e[i],t,`${n}.${i}`)}}function q(e,i){Object.hasOwn(e,"offset")&&y(e.offset,"Axis frame offset"),h(void 0,e),g(e,_,i,{allowEmpty:!1,emptyMessage:`${i} requires at least one axis change.`,emptyError:Error});const t={allowFalse:!0};if(a(e,"line",G,i,t),a(e,"ticks",F,i,t),a(e,"labels",D,i,t),a(e,"ticksAndLabels",X,i,t),a(e,"title",Y,i,t),e.ticksAndLabels!==void 0&&(e.ticks!==void 0||e.labels!==void 0))throw new Error(`${i} cannot combine ticksAndLabels with ticks or labels.`);e.ticksAndLabels!==!1&&e.ticksAndLabels?.ticks!==void 0&&a(e.ticksAndLabels,"ticks",["length","color","lineWidth","alongOffset","strokeDash","strokeDashMap"],`${i}.ticksAndLabels`),e.ticksAndLabels!==!1&&e.ticksAndLabels?.labels!==void 0&&a(e.ticksAndLabels,"labels",["offset","format","color","fontSize","fontFamily","fontWeight","rotation","maxWidth","wrap","lineHeight","overlap","alongOffset","labelMap","flush","flushThreshold","colorMap","align"],`${i}.ticksAndLabels`)}function H(e){const i=e==="x"?"X":"Y";return{operation:`edit${i}Axis`,line:`edit${i}AxisLine`,ticks:`edit${i}AxisTicks`,labels:`edit${i}AxisLabels`,group:`edit${i}AxisTicksAndLabels`,title:`edit${i}AxisTitle`,remove:`remove${i}Axis`}}function K(e,i,t,n){u(t.ticks)&&x(e,i,t.ticks,n),u(t.labels)&&L(e,i,t.labels,n),u(t.ticksAndLabels)&&T(e,i,t.ticksAndLabels,n)}function B(e,i,t,n,o=i){const f={...k(o,i),...Object.hasOwn(t,"position")?{position:t.position}:{}},l=[],d=(c,s)=>{const r=t[c];r===!1?(m(e,i,c,n.operation,o),l.push({kind:"remove",component:c})):r!==void 0?l.push({kind:"edit",operation:s,args:{...f,...r}}):(t.position!==void 0||Object.hasOwn(t,"offset")||Object.hasOwn(t,"underlay"))&&R(e,i,c,o)&&l.push({kind:"edit",operation:s,args:f})};if(d("line",n.line),t.ticksAndLabels===!1?(m(e,i,"ticks",n.operation,o),m(e,i,"labels",n.operation,o),l.push({kind:"remove",component:"ticks"},{kind:"remove",component:"labels"})):t.ticksAndLabels!==void 0?l.push({kind:"edit",operation:n.group,args:{...f,...t.ticksAndLabels}}):(d("ticks",n.ticks),d("labels",n.labels)),d("title",n.title),l.length===0)throw new Error(`${n.operation} requires an existing ${i}-axis component.`);return l}function p(e,i,t,n,o=i){if(O(e.guideConfigs.axis?.[o]?.title?.offset)&&t.some(s=>s.kind==="edit"&&s.operation===n.title)&&e.context.pendingRelativeTitleAxis!==o)return j(e,o,s=>p(s,i,t,n,o));let l=e;const d=O(e.guideConfigs.axis?.[o]?.title?.offset)&&t.some(s=>s.kind==="remove"&&s.component==="title")?[...t.filter(s=>s.kind==="remove"&&s.component==="title"),...t.filter(s=>!(s.kind==="remove"&&s.component==="title"))]:t;for(let s=0;s<d.length;){const r=d[s];if(r.kind==="remove"){l=W(l,i,r.component,o),s+=1;continue}const b=[];for(;s<d.length&&d[s].kind==="edit";){const A=d[s++];b.push({op:A.operation,args:A.args})}l=N(l,b)}return!z.some(s=>l.graphicSpec.objects[M(i,s,o)]!==void 0)&&(l.semanticSpec.guides.axis?.[o]!==void 0||l.guideConfigs.axis?.[o]!==void 0)&&(l=l[n.remove](o===i?void 0:k(o,i))),l}function v(e){const i=H(e);return S({op:i.operation,description:`Edit selected existing ${e}-axis components.`},E(function(t={}){q(t,i.operation);const n=w(this,e,t,{whole:!0}),o=n.id;t=n.options,K(this,e,t,o);let f=this;if(n.owner?.named&&t.position!==void 0){const c=$(e,t.position);C(this,{id:o,channel:e,coordinate:n.owner.coordinate,position:c}),f=f.editSemantic({property:`guide.axis.${o}.position`,value:c})}const l=h(f.guideConfigs.axis?.[o]?.frame,t);f=l===void 0?f._withoutMaterializationConfig(["guides","axis",o,"frame"]):f._withGuideConfig(o,"frame",l);const d=B(f,e,t,i,o);return p(f,e,d,i,o),p(f,e,d,i,o)}))}const U=v("x"),V=v("y");function de(e){e.prototype.editXAxis=U,e.prototype.editYAxis=V}export{de as registerCompleteAxisEditActions};
1
+ import{splitGuideVisibility as L,patchGuideVisibility as T}from"../../../materialization/guides/visibility.js";import{resolveAxisTickEditConfig as w}from"./ticks.js";import{resolveAxisLabelEditConfig as C}from"./labels.js";import{validateAxisTickGroupEditConfig as $}from"./tickGroups.js";import{selectCartesianAxis as j,axisTargetArgs as A,assertCartesianAxisSideAvailable as y}from"../../../selectors/axes.js";import{validateAxisPosition as E,resolveAxisFrameConfig as h}from"./policy.js";import{withGuideLayoutValidation as S,withPendingRelativeAxisTitle as N}from"../../../materialization/guides/layout.js";import{action as P}from"../../../core/action.js";import{applyMaterializationPlan as g}from"../../../materialization/planner.js";import{isPlainObject as u}from"../../../core/immutable.js";import{isLabelRelativeTitleOffset as O}from"../../../grammar/axisTextPlacement.js";import{validateKeys as z,validateOptionObject as M,validateNonNegativeFinite as G}from"../../../core/validation.js";import{AXIS_COMPONENTS as R,axisComponentId as W,hasAxisComponent as _,assertRemovableAxisComponent as m,removeAxisComponent as F}from"./components.js";const D=Object.freeze(["target","position","line","ticks","labels","ticksAndLabels","title","offset","underlay"]),X=Object.freeze(["color","lineWidth"]),V=Object.freeze(["count","values","rounding","length","color","lineWidth","alongOffset","strokeDash","strokeDashMap"]),Y=Object.freeze(["count","values","rounding","offset","format","color","fontSize","fontFamily","fontWeight","rotation","maxWidth","wrap","lineHeight","overlap","alongOffset","labelMap","flush","flushThreshold","colorMap","align"]),q=Object.freeze(["count","values","rounding","ticks","labels"]),H=Object.freeze(["text","at","offset","rotation","color","fontSize","fontFamily","fontWeight","alongOffset","align","baseline","overlap"]);function a(e,i,t,l,{allowFalse:s=!1}={}){if(Object.hasOwn(e,i)&&!(s&&e[i]===!1)){if(!u(e[i])){const f=s?"false or a plain object":"a plain object";throw new TypeError(`${l}.${i} must be ${f}.`)}z(e[i],t,`${l}.${i}`)}}function v(e,i){Object.hasOwn(e,"offset")&&G(e.offset,"Axis frame offset"),h(void 0,e),M(e,D,i,{allowEmpty:!1,emptyMessage:`${i} requires at least one axis change.`,emptyError:Error});const t={allowFalse:!0};if(a(e,"line",X,i,t),a(e,"ticks",V,i,t),a(e,"labels",Y,i,t),a(e,"ticksAndLabels",q,i,t),a(e,"title",H,i,t),e.ticksAndLabels!==void 0&&(e.ticks!==void 0||e.labels!==void 0))throw new Error(`${i} cannot combine ticksAndLabels with ticks or labels.`);e.ticksAndLabels!==!1&&e.ticksAndLabels?.ticks!==void 0&&a(e.ticksAndLabels,"ticks",["length","color","lineWidth","alongOffset","strokeDash","strokeDashMap"],`${i}.ticksAndLabels`),e.ticksAndLabels!==!1&&e.ticksAndLabels?.labels!==void 0&&a(e.ticksAndLabels,"labels",["offset","format","color","fontSize","fontFamily","fontWeight","rotation","maxWidth","wrap","lineHeight","overlap","alongOffset","labelMap","flush","flushThreshold","colorMap","align"],`${i}.ticksAndLabels`)}function K(e){const i=e==="x"?"X":"Y";return{operation:`edit${i}Axis`,line:`edit${i}AxisLine`,ticks:`edit${i}AxisTicks`,labels:`edit${i}AxisLabels`,group:`edit${i}AxisTicksAndLabels`,title:`edit${i}AxisTitle`,remove:`remove${i}Axis`}}function B(e,i,t,l){u(t.ticks)&&w(e,i,t.ticks,l),u(t.labels)&&C(e,i,t.labels,l),u(t.ticksAndLabels)&&$(e,i,t.ticksAndLabels,l)}function U(e,i,t,l,s=i){const f={...A(s,i),...Object.hasOwn(t,"position")?{position:t.position}:{}},n=[],d=(c,o)=>{const r=t[c];r===!1?(m(e,i,c,l.operation,s),n.push({kind:"remove",component:c})):r!==void 0?n.push({kind:"edit",operation:o,args:{...f,...r}}):(t.position!==void 0||Object.hasOwn(t,"offset")||Object.hasOwn(t,"underlay"))&&_(e,i,c,s)&&n.push({kind:"edit",operation:o,args:f})};if(d("line",l.line),t.ticksAndLabels===!1?(m(e,i,"ticks",l.operation,s),m(e,i,"labels",l.operation,s),n.push({kind:"remove",component:"ticks"},{kind:"remove",component:"labels"})):t.ticksAndLabels!==void 0?n.push({kind:"edit",operation:l.group,args:{...f,...t.ticksAndLabels}}):(d("ticks",l.ticks),d("labels",l.labels)),d("title",l.title),n.length===0)throw new Error(`${l.operation} requires an existing ${i}-axis component.`);return n}function p(e,i,t,l,s=i){if(O(e.guideConfigs.axis?.[s]?.title?.offset)&&t.some(o=>o.kind==="edit"&&o.operation===l.title)&&e.context.pendingRelativeTitleAxis!==s)return N(e,s,o=>p(o,i,t,l,s));let n=e;const d=O(e.guideConfigs.axis?.[s]?.title?.offset)&&t.some(o=>o.kind==="remove"&&o.component==="title")?[...t.filter(o=>o.kind==="remove"&&o.component==="title"),...t.filter(o=>!(o.kind==="remove"&&o.component==="title"))]:t;for(let o=0;o<d.length;){const r=d[o];if(r.kind==="remove"){n=F(n,i,r.component,s),o+=1;continue}const b=[];for(;o<d.length&&d[o].kind==="edit";){const k=d[o++];b.push({op:k.operation,args:k.args})}n=g(n,b)}return!R.some(o=>n.graphicSpec.objects[W(i,o,s)]!==void 0)&&(n.semanticSpec.guides.axis?.[s]!==void 0||n.guideConfigs.axis?.[s]!==void 0)&&(n=n[l.remove](s===i?void 0:A(s,i))),n}function x(e){const i=K(e);return P({op:i.operation,description:`Edit selected existing ${e}-axis components.`},S(function(t={}){const l=L(t);t=l.args,Object.keys(t).some(o=>o!=="target")&&v(t,i.operation);const s=j(this,e,t,{whole:!0}),f=s.id;t=s.options,B(this,e,t,f);let n=T(this,"axis",f,l.visibility);if(!Object.keys(t).some(o=>o!=="target"))return Object.keys(l.visibility).length||v(t,i.operation),n;if(s.owner?.named&&t.position!==void 0){const o=E(e,t.position);y(this,{id:f,channel:e,coordinate:s.owner.coordinate,position:o}),n=n.editSemantic({property:`guide.axis.${f}.position`,value:o})}const d=h(n.guideConfigs.axis?.[f]?.frame,t);n=d===void 0?n._withoutMaterializationConfig(["guides","axis",f,"frame"]):n._withGuideConfig(f,"frame",d);const c=U(n,e,t,i,f);return p(n,e,c,i,f),p(n,e,c,i,f)}))}const J=x("x"),Q=x("y");function re(e){e.prototype.editXAxis=J,e.prototype.editYAxis=Q}export{re as registerCompleteAxisEditActions};
@@ -1 +1,2 @@
1
- import{axisTitleAllowsLabelOverlap as ie,axisTitleFollowsLabels as N}from"../../../grammar/axisTextPlacement.js";import{selectCartesianAxis as V,axisTargetArgs as G}from"../../../selectors/axes.js";import{axisGraphicIds as C,hasPendingAxisTitleRematerialization as se}from"../../../materialization/guides/resources.js";import{axisThemeTypography as re}from"../../theme/state.js";import{validateAxisLabelFlushThreshold as H,validateAxisTextPlacement as _}from"../../../grammar/axisTextPlacement.js";import{canvasOverflowError as ae}from"../../../layout/canvas.js";import{withGuideLayoutValidation as X}from"../../../materialization/guides/layout.js";import{action as Y}from"../../../core/action.js";import{validateUserId as ne}from"../../../core/identifiers.js";import{sameOrderedValues as le,validateGeneratedItemLimit as F,validateTextStyle as P,validateOptionObject as ue,validateNonNegativeFinite as de,validatePositiveFinite as fe}from"../../../core/validation.js";import{resolveCartesianAxisFrameBounds as pe}from"../../../materialization/coordinateBounds.js";import{isTransformedScaleType as R,mapContinuousScaleValues as he,formatTransformedTick as me,mapOrdinalPositionValues as ce}from"../../../grammar/scales/index.js";import{formatTimeTick as xe,formatTimeTicks as be,validateTickRounding as we}from"../../../grammar/ticks.js";import{resolveRotation as ye}from"../../../grammar/rotation.js";import{valuesFromTickConfig as ve}from"../tickValues.js";import{AXIS_LABEL_FONT_SIZES as Ae,DEFAULT_COLORS as Oe,DEFAULT_FONT_FAMILY as Le}from"../../../theme/defaults.js";import{defaultAxisPosition as Te,formatAxisValue as Se,formatNumericAxisTicks as Ee,resolveAxisLabelGeometry as ge,validateAxisFormat as Me,validateAxisPosition as Ce}from"./policy.js";import{findCanvasGraphic as Fe,resolvePlotGraphicPlacement as ze}from"../../../materialization/graphicHierarchy.js";import{resolveTextBounds as D,textBoundsFitCanvas as je,textBoundsIntersect as j}from"../../../core/textMetrics.js";import{resolveConcreteGraphicBounds as We}from"../../../grammar/schemas/graphicBounds.js";import{wrapText as $e}from"../../../layout/text.js";import{normalizeDisplayLabelMap as Be,normalizeDisplayColorMap as ke,resolveDisplayLabelColor as Ne,resolveDisplayLabel as Ve}from"../../../grammar/displayLabels.js";const q=["target","scale","position","count","values","rounding","offset","format","color","fontSize","fontFamily","fontWeight","rotation","maxWidth","wrap","lineHeight","overlap","labelMap","alongOffset","flush","flushThreshold","colorMap","align"],S={count:5,color:Oe.text,fontSize:Ae.cartesian,fontFamily:Le,fontWeight:"normal",rotation:0,overlap:"error"};function U(o,e,t){const s={...o};if(s.rotation=Object.hasOwn(e,"rotation")?ye(e.rotation,`${t} rotation`):s.rotation??0,s.overlap??="error",e.maxWidth===!1){if(Object.hasOwn(e,"wrap")||Object.hasOwn(e,"lineHeight"))throw new Error(`${t} cannot combine maxWidth false with wrap or lineHeight.`);delete s.maxWidth,delete s.wrap,delete s.lineHeight}else s.maxWidth!==void 0&&(s.wrap??="word");return s}function W(o,e,t){if(we(o.rounding,!Object.hasOwn(o,"rounding")),Object.hasOwn(o,"rounding")&&Object.hasOwn(o,"values"))throw new Error(`${e} cannot use rounding and values together.`);if(_(o),H(o.flushThreshold,{allowReset:!0}),ue(o,t?q:q.filter(s=>s!=="scale"),e),Object.hasOwn(o,"flush")&&typeof o.flush!="boolean")throw new TypeError("Label flush must be a boolean.");if(Object.hasOwn(o,"alongOffset")&&!Number.isFinite(o.alongOffset))throw new TypeError("Label alongOffset must be a finite number.");if(Object.hasOwn(o,"count")&&Object.hasOwn(o,"values"))throw new Error(`${e} cannot use count and values together.`)}function I(o,e){if(_(e),Ce(o,e.position),e.mode==="count"){if(!Number.isInteger(e.count)||e.count<=0)throw new RangeError("Label count must be a positive integer.");F(e.count,"Label count")}if(e.mode==="values"&&(!Array.isArray(e.values)||!e.values.every(t=>typeof t=="string"||typeof t=="boolean"||typeof t=="number"&&Number.isFinite(t))))throw new TypeError("Label values must be nominal values or finite numbers.");if(e.mode==="values"&&F(e.values.length,"Label value count"),e.alongOffset!==void 0&&!Number.isFinite(e.alongOffset))throw new TypeError("Label alongOffset must be a finite number.");if(de(e.offset,"Label offset"),P(e,"Label"),Me(e.format),H(e.flushThreshold),e.flush===!0&&e.rotation!==0)throw new Error("Label flush requires unrotated Cartesian labels.");if(!Number.isFinite(e.rotation))throw new TypeError("Label rotation must resolve to finite radians.");if(e.maxWidth!==void 0){if(fe(e.maxWidth,"Label maxWidth"),!["word","character"].includes(e.wrap))throw new Error(`Unsupported axis label wrap "${e.wrap}".`)}else if(e.wrap!==void 0||e.lineHeight!==void 0)throw new Error("Axis label wrap and lineHeight require maxWidth.");if(e.lineHeight!==void 0&&(!Number.isFinite(e.lineHeight)||e.lineHeight<e.fontSize))throw new RangeError("Axis label lineHeight must cover fontSize.");if(!["error","allow"].includes(e.overlap))throw new Error(`Unsupported axis label overlap policy "${e.overlap}".`)}function K(o,e,t,s){const i={...e};t.flushThreshold==="auto"&&delete i.flushThreshold;for(const[u,d]of[["labelMap",Be],["colorMap",ke]])Object.hasOwn(t,u)&&(t[u]==="auto"?delete i[u]:i[u]=d(t[u],`${s} ${u}`));return Z(i,o.resolvedScales[e.scale],s),i}function Z(o,e,t="Axis"){if(!["ordinal","band","point"].includes(e?.type)){for(const s of["labelMap","colorMap"])if(o[s]!==void 0&&!o[s].every(i=>Number.isFinite(i.value)))throw new TypeError(`${t} continuous ${s} values must be finite numbers or timestamps.`)}}function J(o,e,t){if(o){if(o.scale!==e.scale||o.mode!==e.mode)throw new Error(`${t} conflicts with axis ticks.`);if(e.mode==="count"&&o.count!==e.count)throw new Error(`${t} conflicts with axis ticks.`);if(e.mode==="count"&&(o.rounding??"ceiling")!==(e.rounding??"ceiling"))throw new Error(`${t} conflicts with axis tick rounding.`);if(e.mode==="values"&&!le(o.values,e.values))throw new Error(`${t} conflicts with axis ticks.`)}}function Ge(o,e,t,s,i){if(t.maxWidth===void 0)return{...o,text:e,groups:e.map((f,h)=>h)};const u={fontSize:t.fontSize,fontFamily:t.fontFamily,fontWeight:t.fontWeight},d=t.lineHeight??t.fontSize*1.2,l={text:[],x:[],y:[],groups:[]};for(let f=0;f<e.length;f+=1){const h=$e(e[f],{maxWidth:t.maxWidth,mode:t.wrap,style:u,profile:i});F(l.text.length+h.length,"Axis label line count");const x=Array.isArray(o.x)?o.x[f]:o.x,c=Array.isArray(o.y)?o.y[f]:o.y;for(let b=0;b<h.length;b+=1)l.text.push(h[b]),l.x.push(x),l.y.push(s==="x"?c+(o.textBaseline==="top"?b:-b)*d:c+(b-(h.length-1)/2)*d),l.groups.push(f)}return{...o,...l}}function $(o){const e=new Map;for(const{bounds:t,group:s}of o){const i=e.get(s);e.set(s,i===void 0?t:{left:Math.min(i.left,t.left),right:Math.max(i.right,t.right),top:Math.min(i.top,t.top),bottom:Math.max(i.bottom,t.bottom)})}return[...e.values()]}function Q(o,e,t,s=e){const i=o.resolvedScales[t.scale],u=pe(o,e,t.position,t.scale,s),d=["ordinal","band","point"].includes(i?.type);if(Z(t,i),!["linear","time","ordinal","band","point"].includes(i?.type)&&!R(i?.type)||!u)throw new Error("Axis labels require a supported resolved scale and Canvas bounds.");if(d&&t.mode!=="values")throw new Error("Discrete axis labels require explicit or inferred values, not count.");let l=ve(o,t);if(F(l.length,"Label value count"),d){const n=new Set(i.domain);if(!l.every(r=>n.has(r)))throw new RangeError("Label values must be inside the scale domain.")}else{const n=Math.min(...i.domain),r=Math.max(...i.domain);if(!l.every(m=>m>=n&&m<=r))throw new RangeError("Label values must be inside the scale domain.")}let f=d?ce(l,i):he(l,i);const h=n=>Se(n,i.type,t.format,r=>i.type==="time"?xe(r,i.domain):R(i.type)?me(i.type,r):String(r)),x=t.format==="ticks"?Ee(l,i.type):i.type==="time"&&t.format==="auto"?be(l,i.domain):void 0;let c=l.map((n,r)=>Ve(n,t.labelMap,m=>x?.[r]??h(m)));if(c.includes("")){const n=c.map((r,m)=>r===""?-1:m).filter(r=>r!==-1);l=n.map(r=>l[r]),f=n.map(r=>f[r]),c=n.map(r=>c[r])}const b={values:l,...ge({bounds:u,channel:e,position:t.position,positions:f.map(n=>n+(t.alongOffset??0)),offset:t.offset})},a=Ge(b,c,t,e,o.materializationConfigs.textMetrics);if(e==="x"&&Math.abs(Math.sin(t.rotation))>1e-9){const n=t.position==="bottom"?1:-1;a.textAlign=Math.sin(t.rotation)*n<0?"right":"left",a.textBaseline="middle"}const E=Fe(o)?.properties,y=()=>a.text.map((n,r)=>D({x:Array.isArray(a.x)?a.x[r]:a.x,y:Array.isArray(a.y)?a.y[r]:a.y,text:n,fontSize:t.fontSize,fontFamily:t.fontFamily,fontWeight:t.fontWeight,textAlign:a.textAlign,textBaseline:a.textBaseline,rotation:t.rotation},o.materializationConfigs.textMetrics));let v=y();if(o.materializationConfigs.canvas?.plot!==void 0||t.rotation!==0){const n=e==="x",r=["bottom","right"].includes(t.position),m=n?u.y+(r?u.height:0):u.x+(r?u.width:0),O=n?r?"top":"bottom":r?"left":"right",g=$(v.map((p,w)=>({bounds:p,group:a.groups[w]}))).map(p=>m+(r?t.offset:-t.offset)-p[O]),M=n?"y":"x",T=a[M];a[M]=a.text.map((p,w)=>(Array.isArray(T)?T[w]:T)+g[a.groups[w]]),v=v.map((p,w)=>{const A=g[a.groups[w]];return n?{...p,top:p.top+A,bottom:p.bottom+A}:{...p,left:p.left+A,right:p.right+A}})}if(t.flush===!0){const n=e==="x"?"x":"y",r=e==="x"?"left":"top",m=e==="x"?"right":"bottom",O=Math.min(...i.range)+(t.alongOffset??0),L=Math.max(...i.range)+(t.alongOffset??0),M=$(v.map((p,w)=>({bounds:p,group:a.groups[w]}))).map((p,w)=>{const A=f[w]+(t.alongOffset??0);if(t.flushThreshold!==void 0&&Math.min(Math.abs(A-O),Math.abs(A-L))>t.flushThreshold)return 0;if(p[m]-p[r]>L-O+1e-9)throw new Error(`The ${e}-axis label cannot flush inside the scale span.`);return p[r]<O?O-p[r]:p[m]>L?L-p[m]:0}),T=a[n];a[n]=a.text.map((p,w)=>(Array.isArray(T)?T[w]:T)+M[a.groups[w]]),v=v.map((p,w)=>{const A=M[a.groups[w]];return{...p,[r]:p[r]+A,[m]:p[m]+A}})}t.align!==void 0&&(a.textAlign=t.align,v=y());const B=t.rotation!==0,oe=B?a.text.map((n,r)=>{const m=Array.isArray(a.x)?a.x[r]:a.x,O=Array.isArray(a.y)?a.y[r]:a.y,L=Math.cos(t.rotation),g=Math.sin(t.rotation);return D({x:m*L+O*g,y:-m*g+O*L,text:n,fontSize:t.fontSize,fontFamily:t.fontFamily,fontWeight:t.fontWeight,textAlign:a.textAlign,textBaseline:a.textBaseline},o.materializationConfigs.textMetrics)}):v,z=$(oe.map((n,r)=>({bounds:n,group:a.groups[r]}))).sort((n,r)=>e==="x"?n.left-r.left:n.top-r.top);if(!E||!v.every(n=>je(n,E)))throw ae(`The ${e}-axis labels do not fit the Canvas margin.`,v,E);if(t.overlap==="error"&&z.some((n,r)=>B?z.slice(0,r).some(m=>j(m,n)):r>0&&j(z[r-1],n)))throw new Error(`The ${e}-axis labels overlap each other.`);const k=o.graphicSpec.objects[C(e,s)[3]]?We(o.graphicSpec,C(e,s)[3],o.materializationConfigs.textMetrics):void 0;if(k&&!ie(o.guideConfigs.axis?.[s]?.title)&&!N(o.guideConfigs.axis?.[s]?.title)&&!se(o,e,s)&&v.some(n=>j(n,k)))throw new Error(`The ${e}-axis labels overlap the axis title.`);return a}function He(o,e,t,s=e){const i=e==="x"?"editXAxisLabels":"editYAxisLabels";W(t,i,!1);const u=o.guideConfigs.axis?.[s]?.labels;if(!u)throw new Error(`${i} requires label configuration.`);const d=Object.hasOwn(t,"values")||Object.hasOwn(t,"count"),l=o.guideConfigs.axis?.[s]?.ticks,f=!d&&u.inferredValues===!0&&l?.mode==="values"?l.values:void 0,h=Object.hasOwn(t,"values")||f!==void 0?"values":Object.hasOwn(t,"count")?"count":u.mode;let x=U({...u,...t,...f===void 0?{}:{values:f},...d?{inferredValues:!1}:{},mode:h},t,i);return x=K(o,x,t,i),h==="values"?(delete x.count,Object.hasOwn(t,"rounding")||delete x.rounding):delete x.values,I(e,x),x}function ee(o){const e=o==="x"?"editXAxisLabels":"editYAxisLabels";return Y({op:e,description:`Edit concrete ${o}-axis labels.`},X(function(t={}){W(t,e,!1);const s=V(this,o,t,{create:!1}),i=s.id;t=s.options;const u=C(o,i)[2];if(this.graphicSpec.objects[u]?.type!=="text")throw new Error(`${e} requires existing axis labels.`);const d=He(this,o,t,i);J(this.guideConfigs.axis?.[i]?.ticks,d,e);const l=Q(this,o,d,i);let f=this._withGuideConfig(i,"labels",d);const h={length:l.text.length,x:l.x,y:l.y,text:l.text,fill:d.colorMap===void 0?d.color:l.text.map((c,b)=>Ne(l.values[l.groups[b]],d.colorMap,d.color)),fontSize:d.fontSize,fontFamily:d.fontFamily,fontWeight:d.fontWeight,textAlign:l.textAlign,textBaseline:l.textBaseline};(d.rotation!==0||this.graphicSpec.objects[u].items.some(c=>Object.hasOwn(c.properties,"rotation")))&&(h.rotation=d.rotation);for(const[c,b]of Object.entries(h))f=f.editGraphics({target:u,property:c,value:b});const x=f.guideConfigs.axis?.[i]?.title;return N(x)&&f.context.pendingRelativeTitleAxis!==i&&(f=f[o==="x"?"editXAxisTitle":"editYAxisTitle"](i===o?void 0:G(i,o))),f}))}const _e=ee("x"),Xe=ee("y");function te(o){const e=o==="x"?"createXAxisLabels":"createYAxisLabels",t=o==="x"?"editXAxisLabels":"editYAxisLabels";return Y({op:e,description:`Create concrete ${o}-axis labels.`},X(function(s={}){W(s,e,!0);const i=V(this,o,s,{create:!0}),u=i.id;s=i.options;const d=ne(s.scale??o,"Scale id"),l=this.semanticSpec.guides.axis?.[u]?.scale;if(l&&l!==d)throw new Error(`${e} conflicts with the existing axis scale.`);const f=C(o,u)[2];if(this.graphicSpec.objects[f])throw new Error(`${e} requires missing axis labels.`);const h=this.guideConfigs.axis?.[u]?.ticks,x=Object.hasOwn(s,"values"),c=Object.hasOwn(s,"count"),b=this.resolvedScales[d],a=!x&&!c&&h===void 0&&["ordinal","band","point"].includes(b?.type)?b.domain:void 0,E=x||a!==void 0?"values":c?"count":h?.mode??"count";let y=U({scale:d,position:Te(o),offset:o==="x"?18:12,format:"auto",color:S.color,fontSize:S.fontSize,fontFamily:S.fontFamily,fontWeight:S.fontWeight,...re(this.materializationConfigs.theme,"labels"),rotation:S.rotation,overlap:S.overlap,...h?.rounding===void 0?{}:{rounding:h.rounding},...s,inferredValues:!x&&!c&&(h?.inferredValues===!0||a!==void 0),mode:E},s,e);return y=K(this,y,s,e),E==="values"?(y.values??=h?.values??a,Object.hasOwn(s,"rounding")||delete y.rounding):y.count??=h?.count??S.count,I(o,y),J(h,y,e),Q(this,o,y,u),this.editSemantic({property:`guide.axis.${u}.scale`,value:d}).createGraphics({id:f,type:"text",length:0,...ze(this)})._withGuideConfig(u,"labels",y)[t](u===o?void 0:G(u,o))}))}const Ye=te("x"),Pe=te("y");function mt(o){o.prototype.editXAxisLabels=_e,o.prototype.editYAxisLabels=Xe,o.prototype.createXAxisLabels=Ye,o.prototype.createYAxisLabels=Pe}export{mt as registerAxisLabelActions,He as resolveAxisLabelEditConfig,P as validateAxisTextStyle};
1
+ import{axisTitleAllowsLabelOverlap as oe,axisTitleFollowsLabels as N}from"../../../grammar/axisTextPlacement.js";import{selectCartesianAxis as V,axisTargetArgs as G}from"../../../selectors/axes.js";import{axisGraphicIds as C,hasPendingAxisTitleRematerialization as se}from"../../../materialization/guides/resources.js";import{axisThemeTypography as re}from"../../theme/state.js";import{validateAxisLabelFlushThreshold as H,validateAxisTextPlacement as _}from"../../../grammar/axisTextPlacement.js";import{canvasOverflowError as ae}from"../../../layout/canvas.js";import{withGuideLayoutValidation as X}from"../../../materialization/guides/layout.js";import{action as Y}from"../../../core/action.js";import{validateUserId as ne}from"../../../core/identifiers.js";import{sameOrderedValues as le,validateGeneratedItemLimit as F,validateTextStyle as P,validateOptionObject as ue,validateNonNegativeFinite as de,validatePositiveFinite as fe}from"../../../core/validation.js";import{resolveCartesianAxisFrameBounds as pe}from"../../../materialization/coordinateBounds.js";import{isTransformedScaleType as R,mapContinuousScaleValues as he,formatTransformedTick as me,mapOrdinalPositionValues as ce}from"../../../grammar/scales/index.js";import{formatTimeTick as xe,formatTimeTicks as be,validateTickRounding as we}from"../../../grammar/ticks.js";import{resolveRotation as ye}from"../../../grammar/rotation.js";import{valuesFromTickConfig as ve}from"../tickValues.js";import{AXIS_LABEL_FONT_SIZES as Ae,DEFAULT_COLORS as Oe,DEFAULT_FONT_FAMILY as Le}from"../../../theme/defaults.js";import{defaultAxisPosition as Te,formatAxisValue as Se,formatNumericAxisTicks as Ee,resolveAxisLabelGeometry as Me,validateAxisFormat as ge,validateAxisPosition as Ce}from"./policy.js";import{findCanvasGraphic as Fe,resolvePlotGraphicPlacement as ze}from"../../../materialization/graphicHierarchy.js";import{resolveTextBounds as D,textBoundsFitCanvas as je,textBoundsIntersect as j}from"../../../core/textMetrics.js";import{resolveConcreteGraphicBounds as We}from"../../../grammar/schemas/graphicBounds.js";import{wrapText as $e}from"../../../layout/text.js";import{normalizeDisplayLabelMap as Be,normalizeDisplayColorMap as ke,resolveDisplayLabelColor as Ne,resolveDisplayLabel as Ve}from"../../../grammar/displayLabels.js";const q=["target","scale","position","count","values","rounding","offset","format","color","fontSize","fontFamily","fontWeight","rotation","maxWidth","wrap","lineHeight","overlap","labelMap","alongOffset","flush","flushThreshold","colorMap","align"],S={count:5,color:Oe.text,fontSize:Ae.cartesian,fontFamily:Le,fontWeight:"normal",rotation:0,overlap:"error"};function U(i,e,t){const s={...i};if(s.rotation=Object.hasOwn(e,"rotation")?ye(e.rotation,`${t} rotation`):s.rotation??0,s.overlap??="error",e.maxWidth===!1){if(Object.hasOwn(e,"wrap")||Object.hasOwn(e,"lineHeight"))throw new Error(`${t} cannot combine maxWidth false with wrap or lineHeight.`);delete s.maxWidth,delete s.wrap,delete s.lineHeight}else s.maxWidth!==void 0&&(s.wrap??="word");return s}function W(i,e,t){if(we(i.rounding,!Object.hasOwn(i,"rounding")),Object.hasOwn(i,"rounding")&&Object.hasOwn(i,"values"))throw new Error(`${e} cannot use rounding and values together.`);if(_(i),H(i.flushThreshold,{allowReset:!0}),ue(i,t?q:q.filter(s=>s!=="scale"),e),Object.hasOwn(i,"flush")&&typeof i.flush!="boolean")throw new TypeError("Label flush must be a boolean.");if(Object.hasOwn(i,"alongOffset")&&!Number.isFinite(i.alongOffset))throw new TypeError("Label alongOffset must be a finite number.");if(Object.hasOwn(i,"count")&&Object.hasOwn(i,"values"))throw new Error(`${e} cannot use count and values together.`)}function I(i,e){if(_(e),Ce(i,e.position),e.mode==="count"){if(!Number.isInteger(e.count)||e.count<=0)throw new RangeError("Label count must be a positive integer.");F(e.count,"Label count")}if(e.mode==="values"&&(!Array.isArray(e.values)||!e.values.every(t=>typeof t=="string"||typeof t=="boolean"||typeof t=="number"&&Number.isFinite(t))))throw new TypeError("Label values must be nominal values or finite numbers.");if(e.mode==="values"&&F(e.values.length,"Label value count"),e.alongOffset!==void 0&&!Number.isFinite(e.alongOffset))throw new TypeError("Label alongOffset must be a finite number.");if(de(e.offset,"Label offset"),P(e,"Label"),ge(e.format),H(e.flushThreshold),e.flush===!0&&e.rotation!==0)throw new Error("Label flush requires unrotated Cartesian labels.");if(!Number.isFinite(e.rotation))throw new TypeError("Label rotation must resolve to finite radians.");if(e.maxWidth!==void 0){if(fe(e.maxWidth,"Label maxWidth"),!["word","character"].includes(e.wrap))throw new Error(`Unsupported axis label wrap "${e.wrap}".`)}else if(e.wrap!==void 0||e.lineHeight!==void 0)throw new Error("Axis label wrap and lineHeight require maxWidth.");if(e.lineHeight!==void 0&&(!Number.isFinite(e.lineHeight)||e.lineHeight<e.fontSize))throw new RangeError("Axis label lineHeight must cover fontSize.");if(!["error","allow"].includes(e.overlap))throw new Error(`Unsupported axis label overlap policy "${e.overlap}".`)}function K(i,e,t,s){const o={...e};t.flushThreshold==="auto"&&delete o.flushThreshold;for(const[u,f]of[["labelMap",Be],["colorMap",ke]])Object.hasOwn(t,u)&&(t[u]==="auto"?delete o[u]:o[u]=f(t[u],`${s} ${u}`));return Z(o,i.resolvedScales[e.scale],s),o}function Z(i,e,t="Axis"){if(!["ordinal","band","point"].includes(e?.type)){for(const s of["labelMap","colorMap"])if(i[s]!==void 0&&!i[s].every(o=>Number.isFinite(o.value)))throw new TypeError(`${t} continuous ${s} values must be finite numbers or timestamps.`)}}function J(i,e,t){if(i){if(i.scale!==e.scale||i.mode!==e.mode)throw new Error(`${t} conflicts with axis ticks.`);if(e.mode==="count"&&i.count!==e.count)throw new Error(`${t} conflicts with axis ticks.`);if(e.mode==="count"&&(i.rounding??"ceiling")!==(e.rounding??"ceiling"))throw new Error(`${t} conflicts with axis tick rounding.`);if(e.mode==="values"&&!le(i.values,e.values))throw new Error(`${t} conflicts with axis ticks.`)}}function Ge(i,e,t,s,o){if(t.maxWidth===void 0&&!e.some(d=>d.includes(`
2
+ `)))return{...i,text:e,groups:e.map((d,h)=>h)};const u={fontSize:t.fontSize,fontFamily:t.fontFamily,fontWeight:t.fontWeight},f=t.lineHeight??t.fontSize*1.2,l={text:[],x:[],y:[],groups:[]};for(let d=0;d<e.length;d+=1){const h=t.maxWidth===void 0?e[d].split(/\r?\n/u):$e(e[d],{maxWidth:t.maxWidth,mode:t.wrap,style:u,profile:o});F(l.text.length+h.length,"Axis label line count");const x=Array.isArray(i.x)?i.x[d]:i.x,c=Array.isArray(i.y)?i.y[d]:i.y;for(let b=0;b<h.length;b+=1)l.text.push(h[b]),l.x.push(x),l.y.push(s==="x"?c+(i.textBaseline==="top"?b:-b)*f:c+(b-(h.length-1)/2)*f),l.groups.push(d)}return{...i,...l}}function $(i){const e=new Map;for(const{bounds:t,group:s}of i){const o=e.get(s);e.set(s,o===void 0?t:{left:Math.min(o.left,t.left),right:Math.max(o.right,t.right),top:Math.min(o.top,t.top),bottom:Math.max(o.bottom,t.bottom)})}return[...e.values()]}function Q(i,e,t,s=e){const o=i.resolvedScales[t.scale],u=pe(i,e,t.position,t.scale,s),f=["ordinal","band","point"].includes(o?.type);if(Z(t,o),!["linear","time","ordinal","band","point"].includes(o?.type)&&!R(o?.type)||!u)throw new Error("Axis labels require a supported resolved scale and Canvas bounds.");if(f&&t.mode!=="values")throw new Error("Discrete axis labels require explicit or inferred values, not count.");let l=ve(i,t);if(F(l.length,"Label value count"),f){const n=new Set(o.domain);if(!l.every(r=>n.has(r)))throw new RangeError("Label values must be inside the scale domain.")}else{const n=Math.min(...o.domain),r=Math.max(...o.domain);if(!l.every(m=>m>=n&&m<=r))throw new RangeError("Label values must be inside the scale domain.")}let d=f?ce(l,o):he(l,o);const h=n=>Se(n,o.type,t.format,r=>o.type==="time"?xe(r,o.domain):R(o.type)?me(o.type,r):String(r)),x=t.format==="ticks"?Ee(l,o.type):o.type==="time"&&t.format==="auto"?be(l,o.domain):void 0;let c=l.map((n,r)=>Ve(n,t.labelMap,m=>x?.[r]??h(m)));if(c.includes("")){const n=c.map((r,m)=>r===""?-1:m).filter(r=>r!==-1);l=n.map(r=>l[r]),d=n.map(r=>d[r]),c=n.map(r=>c[r])}const b={values:l,...Me({bounds:u,channel:e,position:t.position,positions:d.map(n=>n+(t.alongOffset??0)),offset:t.offset})},a=Ge(b,c,t,e,i.materializationConfigs.textMetrics);if(e==="x"&&Math.abs(Math.sin(t.rotation))>1e-9){const n=t.position==="bottom"?1:-1;a.textAlign=Math.sin(t.rotation)*n<0?"right":"left",a.textBaseline="middle"}const E=Fe(i)?.properties,y=()=>a.text.map((n,r)=>D({x:Array.isArray(a.x)?a.x[r]:a.x,y:Array.isArray(a.y)?a.y[r]:a.y,text:n,fontSize:t.fontSize,fontFamily:t.fontFamily,fontWeight:t.fontWeight,textAlign:a.textAlign,textBaseline:a.textBaseline,rotation:t.rotation},i.materializationConfigs.textMetrics));let v=y();if(i.materializationConfigs.canvas?.plot!==void 0||t.rotation!==0){const n=e==="x",r=["bottom","right"].includes(t.position),m=n?u.y+(r?u.height:0):u.x+(r?u.width:0),O=n?r?"top":"bottom":r?"left":"right",M=$(v.map((p,w)=>({bounds:p,group:a.groups[w]}))).map(p=>m+(r?t.offset:-t.offset)-p[O]),g=n?"y":"x",T=a[g];a[g]=a.text.map((p,w)=>(Array.isArray(T)?T[w]:T)+M[a.groups[w]]),v=v.map((p,w)=>{const A=M[a.groups[w]];return n?{...p,top:p.top+A,bottom:p.bottom+A}:{...p,left:p.left+A,right:p.right+A}})}if(t.flush===!0){const n=e==="x"?"x":"y",r=e==="x"?"left":"top",m=e==="x"?"right":"bottom",O=Math.min(...o.range)+(t.alongOffset??0),L=Math.max(...o.range)+(t.alongOffset??0),g=$(v.map((p,w)=>({bounds:p,group:a.groups[w]}))).map((p,w)=>{const A=d[w]+(t.alongOffset??0);if(t.flushThreshold!==void 0&&Math.min(Math.abs(A-O),Math.abs(A-L))>t.flushThreshold)return 0;if(p[m]-p[r]>L-O+1e-9)throw new Error(`The ${e}-axis label cannot flush inside the scale span.`);return p[r]<O?O-p[r]:p[m]>L?L-p[m]:0}),T=a[n];a[n]=a.text.map((p,w)=>(Array.isArray(T)?T[w]:T)+g[a.groups[w]]),v=v.map((p,w)=>{const A=g[a.groups[w]];return{...p,[r]:p[r]+A,[m]:p[m]+A}})}t.align!==void 0&&(a.textAlign=t.align,v=y());const B=t.rotation!==0,ie=B?a.text.map((n,r)=>{const m=Array.isArray(a.x)?a.x[r]:a.x,O=Array.isArray(a.y)?a.y[r]:a.y,L=Math.cos(t.rotation),M=Math.sin(t.rotation);return D({x:m*L+O*M,y:-m*M+O*L,text:n,fontSize:t.fontSize,fontFamily:t.fontFamily,fontWeight:t.fontWeight,textAlign:a.textAlign,textBaseline:a.textBaseline},i.materializationConfigs.textMetrics)}):v,z=$(ie.map((n,r)=>({bounds:n,group:a.groups[r]}))).sort((n,r)=>e==="x"?n.left-r.left:n.top-r.top);if(!E||!v.every(n=>je(n,E)))throw ae(`The ${e}-axis labels do not fit the Canvas margin.`,v,E);if(t.overlap==="error"&&z.some((n,r)=>B?z.slice(0,r).some(m=>j(m,n)):r>0&&j(z[r-1],n)))throw new Error(`The ${e}-axis labels overlap each other.`);const k=i.graphicSpec.objects[C(e,s)[3]]?We(i.graphicSpec,C(e,s)[3],i.materializationConfigs.textMetrics):void 0;if(k&&!oe(i.guideConfigs.axis?.[s]?.title)&&!N(i.guideConfigs.axis?.[s]?.title)&&!se(i,e,s)&&v.some(n=>j(n,k)))throw new Error(`The ${e}-axis labels overlap the axis title.`);return a}function He(i,e,t,s=e){const o=e==="x"?"editXAxisLabels":"editYAxisLabels";W(t,o,!1);const u=i.guideConfigs.axis?.[s]?.labels;if(!u)throw new Error(`${o} requires label configuration.`);const f=Object.hasOwn(t,"values")||Object.hasOwn(t,"count"),l=i.guideConfigs.axis?.[s]?.ticks,d=!f&&u.inferredValues===!0&&l?.mode==="values"?l.values:void 0,h=Object.hasOwn(t,"values")||d!==void 0?"values":Object.hasOwn(t,"count")?"count":u.mode;let x=U({...u,...t,...d===void 0?{}:{values:d},...f?{inferredValues:!1}:{},mode:h},t,o);return x=K(i,x,t,o),h==="values"?(delete x.count,Object.hasOwn(t,"rounding")||delete x.rounding):delete x.values,I(e,x),x}function ee(i){const e=i==="x"?"editXAxisLabels":"editYAxisLabels";return Y({op:e,description:`Edit concrete ${i}-axis labels.`},X(function(t={}){W(t,e,!1);const s=V(this,i,t,{create:!1}),o=s.id;t=s.options;const u=C(i,o)[2];if(this.graphicSpec.objects[u]?.type!=="text")throw new Error(`${e} requires existing axis labels.`);const f=He(this,i,t,o);J(this.guideConfigs.axis?.[o]?.ticks,f,e);const l=Q(this,i,f,o);let d=this._withGuideConfig(o,"labels",f);const h={length:l.text.length,x:l.x,y:l.y,text:l.text,fill:f.colorMap===void 0?f.color:l.text.map((c,b)=>Ne(l.values[l.groups[b]],f.colorMap,f.color)),fontSize:f.fontSize,fontFamily:f.fontFamily,fontWeight:f.fontWeight,textAlign:l.textAlign,textBaseline:l.textBaseline};(f.rotation!==0||this.graphicSpec.objects[u].items.some(c=>Object.hasOwn(c.properties,"rotation")))&&(h.rotation=f.rotation);for(const[c,b]of Object.entries(h))d=d.editGraphics({target:u,property:c,value:b});const x=d.guideConfigs.axis?.[o]?.title;return N(x)&&d.context.pendingRelativeTitleAxis!==o&&(d=d[i==="x"?"editXAxisTitle":"editYAxisTitle"](o===i?void 0:G(o,i))),d}))}const _e=ee("x"),Xe=ee("y");function te(i){const e=i==="x"?"createXAxisLabels":"createYAxisLabels",t=i==="x"?"editXAxisLabels":"editYAxisLabels";return Y({op:e,description:`Create concrete ${i}-axis labels.`},X(function(s={}){W(s,e,!0);const o=V(this,i,s,{create:!0}),u=o.id;s=o.options;const f=ne(s.scale??i,"Scale id"),l=this.semanticSpec.guides.axis?.[u]?.scale;if(l&&l!==f)throw new Error(`${e} conflicts with the existing axis scale.`);const d=C(i,u)[2];if(this.graphicSpec.objects[d])throw new Error(`${e} requires missing axis labels.`);const h=this.guideConfigs.axis?.[u]?.ticks,x=Object.hasOwn(s,"values"),c=Object.hasOwn(s,"count"),b=this.resolvedScales[f],a=!x&&!c&&h===void 0&&["ordinal","band","point"].includes(b?.type)?b.domain:void 0,E=x||a!==void 0?"values":c?"count":h?.mode??"count";let y=U({scale:f,position:Te(i),offset:i==="x"?18:12,format:"auto",color:S.color,fontSize:S.fontSize,fontFamily:S.fontFamily,fontWeight:S.fontWeight,...re(this.materializationConfigs.theme,"labels"),rotation:S.rotation,overlap:S.overlap,...h?.rounding===void 0?{}:{rounding:h.rounding},...s,inferredValues:!x&&!c&&(h?.inferredValues===!0||a!==void 0),mode:E},s,e);return y=K(this,y,s,e),E==="values"?(y.values??=h?.values??a,Object.hasOwn(s,"rounding")||delete y.rounding):y.count??=h?.count??S.count,I(i,y),J(h,y,e),Q(this,i,y,u),this.editSemantic({property:`guide.axis.${u}.scale`,value:f}).createGraphics({id:d,type:"text",length:0,...ze(this)})._withGuideConfig(u,"labels",y)[t](u===i?void 0:G(u,i))}))}const Ye=te("x"),Pe=te("y");function mt(i){i.prototype.editXAxisLabels=_e,i.prototype.editYAxisLabels=Xe,i.prototype.createXAxisLabels=Ye,i.prototype.createYAxisLabels=Pe}export{mt as registerAxisLabelActions,He as resolveAxisLabelEditConfig,Q as resolveAxisLabels,P as validateAxisTextStyle};
@@ -1,6 +1,8 @@
1
+ import { splitGuideVisibility } from "../../../../materialization/guides/visibility.js";
1
2
  import { action, closedAction } from "../../../../core/action.js";
2
3
  import { validateNonEmptyString } from "../../../../core/validation.js";
3
4
  import { requireParallelAxisLayer, resolveParallelAxisTarget } from "./resolve.js";
5
+ import { withGuideLayoutValidation } from "../../../../materialization/guides/layout.js";
4
6
  import {
5
7
  defaultParallelAxis, hasParallelAxisParts, PARALLEL_AXIS_GRAPHICS,
6
8
  PARALLEL_AXIS_PARTS, patchParallelAxis, resolveParallelAxisConfigs, validateParallelAxisOptions
@@ -62,13 +64,18 @@ const createParallelAxes = /* @__PURE__ */ closedAction({
62
64
  function makeAxisAction(create) {
63
65
  const operation = create ? "createParallelAxis" : "editParallelAxis";
64
66
  return action({ op: operation, description: `${create ? "Create" : "Edit"} selected components of a Parallel field axis.` }, function (args = {}) {
65
- validateParallelAxisOptions(args, operation, create);
67
+ const split = create ? { args, visibility: {} } : splitGuideVisibility(args);
68
+ args = split.args;
69
+ const onlyVisibility = !create && Object.keys(split.visibility).length && !Object.keys(args).some(key => !["target", "field"].includes(key));
70
+ if (!onlyVisibility) validateParallelAxisOptions(args, operation, create);
71
+ else validateNonEmptyString(args.field, "Parallel axis field");
66
72
  const resolved = owner(this, args, operation, !create);
67
73
  const config = resolved.stored === undefined
68
74
  ? { mode: "selected", dimensions: resolved.dimensions.map(dimension => ({ field: dimension.field })) }
69
75
  : resolveParallelAxisConfigs(this, resolved.dimensions);
70
76
  const previous = config.dimensions.find(dimension => dimension.field === args.field);
71
- const updated = patchParallelAxis(previous, args, create, operation, this.materializationConfigs.theme);
77
+ const updated = onlyVisibility ? { ...previous } : patchParallelAxis(previous, args, create, operation, this.materializationConfigs.theme);
78
+ if (Object.keys(split.visibility).length) updated.visibility = { ...previous?.visibility, ...split.visibility };
72
79
  const next = withTitle(withOwner(this, resolved, {
73
80
  ...config, dimensions: config.dimensions.map(dimension => dimension.field === args.field ? updated : dimension)
74
81
  }), args, create);
@@ -93,7 +100,7 @@ const removeParallelAxis = /* @__PURE__ */ closedAction({
93
100
 
94
101
  const removeParallelAxes = /* @__PURE__ */ closedAction({
95
102
  op: "removeParallelAxes", description: "Remove all Parallel axis semantics, recipes and graphics."
96
- }, ["target", "coordinate"], function (args = {}) {
103
+ }, ["target", "coordinate"], withGuideLayoutValidation(function (args = {}) {
97
104
 
98
105
  const stored = this.semanticSpec.guides.axis?.parallel;
99
106
  if (stored === undefined) throw new Error("removeParallelAxes requires existing Parallel axes.");
@@ -106,7 +113,7 @@ const removeParallelAxes = /* @__PURE__ */ closedAction({
106
113
  if (next.graphicSpec.objects[id] !== undefined) next = next.editGraphics({ target: id, remove: true });
107
114
  }
108
115
  return next._withoutMaterializationConfig(["guides", "axis", "parallel"]);
109
- });
116
+ }));
110
117
 
111
118
  export function registerParallelAxisLifecycleActions(ProgramClass) {
112
119
  ProgramClass.prototype.createParallelAxes = createParallelAxes;
@@ -1,3 +1,4 @@
1
+ import { normalizeDisplayLabelMap } from "../../../../grammar/displayLabels.js";
1
2
  import { axisThemeTypography } from "../../../theme/state.js";
2
3
  import {
3
4
  validateGeneratedItemLimit,
@@ -15,8 +16,8 @@ export { PARALLEL_AXIS_PARTS, PARALLEL_AXIS_GRAPHICS };
15
16
  const STYLE_OPTIONS = Object.freeze({
16
17
  line: ["color", "lineWidth"],
17
18
  ticks: ["count", "values", "length", "color", "lineWidth"],
18
- labels: ["count", "values", "offset", "format", "color", "fontSize", "fontFamily", "fontWeight"],
19
- title: ["text", "offset", "color", "fontSize", "fontFamily", "fontWeight"]
19
+ labels: ["labelMap", "count", "values", "offset", "format", "color", "fontSize", "fontFamily", "fontWeight", "align"],
20
+ title: ["text", "offset", "color", "fontSize", "fontFamily", "fontWeight", "align"]
20
21
  });
21
22
 
22
23
  export function defaultParallelAxis(field, theme) {
@@ -74,6 +75,8 @@ function patchComponent(previous, defaults, options, part, create, operation) {
74
75
  if (options === false) return undefined;
75
76
  const next = { ...(create ? defaults : previous), ...options };
76
77
  delete next.text;
78
+ if (options.labelMap === "auto") delete next.labelMap;
79
+ else if (Object.hasOwn(options, "labelMap")) next.labelMap = normalizeDisplayLabelMap(options.labelMap);
77
80
  if (Object.hasOwn(options, "count")) { next.mode = "count"; delete next.values; }
78
81
  if (Object.hasOwn(options, "values")) { next.mode = "values"; delete next.count; }
79
82
  validateNonEmptyString(next.color, "Parallel axis color");
@@ -1,3 +1,4 @@
1
+ import { resolveDisplayLabel } from "../../../../grammar/displayLabels.js";
1
2
  import { validateUserId } from "../../../../core/identifiers.js";
2
3
  import {
3
4
  isTransformedScaleType,
@@ -76,12 +77,13 @@ function componentValues(program, dimension, config) {
76
77
  : !Number.isFinite(value) || value < Math.min(...scale.domain) || value > Math.max(...scale.domain))) {
77
78
  throw new Error(`Parallel axis values must be distinct values inside the scale domain for "${dimension.field}".`);
78
79
  }
80
+ if (config.labelMap !== undefined && !discrete) throw new Error("Parallel labelMap requires a categorical axis.");
79
81
  return {
80
82
  values,
81
83
  y: discrete ? mapOrdinalPositionValues(values, scale) : mapContinuousScaleValues(values, scale),
82
84
  text: config.format === undefined ? undefined
83
85
  : config.format === "ticks" ? formatNumericAxisTicks(values, scale.type)
84
- : values.map(value => formatAxisValue(value, scale.type, config.format, formatValue))
86
+ : values.map(value => resolveDisplayLabel(value, config.labelMap, () => formatAxisValue(value, scale.type, config.format, formatValue)))
85
87
  };
86
88
  }
87
89
 
@@ -1,9 +1,11 @@
1
+ import { materializeHardTextLines } from "../../../materialization/textLines.js";
1
2
  import { closedAction } from "../../../core/action.js";
2
3
  import { validateGeneratedItemLimit } from "../../../core/validation.js";
3
4
  import { resolvePlotGraphicPlacement } from "../../../materialization/graphicHierarchy.js";
4
5
  import { requireParallelAxisLayer, resolveParallelAxisTarget, resolveStyledParallelAxes } from "./parallel/resolve.js";
5
6
  import { PARALLEL_AXIS_GRAPHICS, PARALLEL_AXIS_PARTS, hasParallelAxisParts } from "./parallel/policy.js";
6
7
  import { registerParallelAxisLifecycleActions } from "./parallel/lifecycle.js";
8
+ import { withGuideLayoutValidation } from "../../../materialization/guides/layout.js";
7
9
 
8
10
  function uniform(values) {
9
11
  return values.length > 0 && values.every(value => value === values[0]) ? values[0] : values;
@@ -25,7 +27,7 @@ function collectionProperties(part, axes, bounds) {
25
27
  fontSize: uniform(axes.map(axis => axis.config.title.fontSize)),
26
28
  fontFamily: uniform(axes.map(axis => axis.config.title.fontFamily)),
27
29
  fontWeight: uniform(axes.map(axis => axis.config.title.fontWeight)),
28
- textAlign: "center", textBaseline: "middle"
30
+ textAlign: uniform(axes.map(axis => axis.config.title.align ?? "center")), textBaseline: "middle"
29
31
  };
30
32
  const rows = axes.flatMap(axis => axis[part].values.map((value, index) => ({
31
33
  x: axis.x, y: axis[part].y[index], text: axis[part].text?.[index], config: axis.config[part]
@@ -43,14 +45,14 @@ function collectionProperties(part, axes, bounds) {
43
45
  fontSize: uniform(rows.map(row => row.config.fontSize)),
44
46
  fontFamily: uniform(rows.map(row => row.config.fontFamily)),
45
47
  fontWeight: uniform(rows.map(row => row.config.fontWeight)),
46
- textAlign: "right", textBaseline: "middle"
48
+ textAlign: uniform(rows.map(row => row.config.align ?? "right")), textBaseline: "middle"
47
49
  };
48
50
  }
49
51
 
50
52
  export const rematerializeParallelAxes = /* @__PURE__ */ closedAction({
51
53
  op: "rematerializeParallelAxes",
52
54
  description: "Recompute concrete Parallel dimension axes."
53
- }, ["target"], function (args = {}) {
55
+ }, ["target"], withGuideLayoutValidation(function (args = {}) {
54
56
 
55
57
  const stored = this.semanticSpec.guides.axis?.parallel;
56
58
  if (stored === undefined) throw new Error("rematerializeParallelAxes requires existing Parallel axes.");
@@ -64,6 +66,8 @@ export const rematerializeParallelAxes = /* @__PURE__ */ closedAction({
64
66
  const properties = Object.fromEntries(PARALLEL_AXIS_PARTS.map(part => {
65
67
  const selected = axes.filter(axis => axis.config[part] !== undefined);
66
68
  const value = collectionProperties(part, selected, bounds);
69
+ if (selected.some(axis => axis.config.visibility !== undefined)) value.opacity = selected.flatMap(axis => Array.from({ length: ["line", "title"].includes(part) ? 1 : axis[part].values.length },
70
+ () => axis.config.visibility?.all === false || axis.config.visibility?.[part] === false ? 0 : 1));
67
71
  validateGeneratedItemLimit(value.length, "Parallel axis item count");
68
72
  return [part, { enabled: selected.length > 0, value }];
69
73
  }));
@@ -94,9 +98,10 @@ export const rematerializeParallelAxes = /* @__PURE__ */ closedAction({
94
98
  for (const [property, item] of Object.entries(value)) {
95
99
  next = next.editGraphics({ target: id, property, value: item });
96
100
  }
101
+ if (["title", "labels"].includes(part)) next = materializeHardTextLines(next, id);
97
102
  }
98
103
  return next._withGuideConfig("parallel", "axes", { target, scales, ...configs });
99
- });
104
+ }));
100
105
 
101
106
  export function registerParallelAxisActions(ProgramClass) {
102
107
  ProgramClass.prototype.rematerializeParallelAxes = rematerializeParallelAxes;
@@ -1 +1 @@
1
- import{selectCartesianAxis as E,axisTargetArgs as J}from"../../../selectors/axes.js";import{axisGraphicIds as h}from"../../../materialization/guides/resources.js";import{materializeTextLines as Q}from"../../../materialization/textLines.js";import{axisTitleTextLines as z,validateAxisTextPlacement as g,axisTitleAllowsLabelOverlap as I,isLabelRelativeTitleOffset as M,axisLabelHasText as tt}from"../../../grammar/axisTextPlacement.js";import{axisThemeTypography as et}from"../../theme/state.js";import{canvasOverflowError as it}from"../../../layout/canvas.js";import{isSourceOwnedText as ot}from"../../../grammar/text.js";import{withGuideLayoutValidation as F}from"../../../materialization/guides/layout.js";import{action as $}from"../../../core/action.js";import{validateUserId as st}from"../../../core/identifiers.js";import{validateKeys as j,validateNonEmptyString as rt}from"../../../core/validation.js";import{axisScaleBindings as nt,resolveCartesianAxisFrameBounds as L}from"../../../materialization/coordinateBounds.js";import{isTransformedScaleType as at,mapContinuousScaleValues as lt,mapOrdinalPositionValues as ft}from"../../../grammar/scales/index.js";import{AXIS_TITLE_FONT_SIZE as dt,DEFAULT_COLORS as ct,DEFAULT_FONT_FAMILY as ut}from"../../../theme/defaults.js";import{findDataset as pt}from"../../../selectors/datasets.js";import{findUpstreamTransform as mt}from"../../../materialization/dataProvenance.js";import{formatAggregateTitle as R}from"../../../grammar/aggregate.js";import{finiteMidpoint as xt}from"../../../grammar/numeric.js";import{defaultAxisPosition as bt,defaultAxisTitleRotation as B,resolveAxisTitleGeometry as w,validateAxisPosition as yt}from"./policy.js";import{findCanvasGraphic as q,resolvePlotGraphicPlacement as vt}from"../../../materialization/graphicHierarchy.js";import{resolveTextBounds as Tt,textBoundsFitCanvas as ht,textBoundsIntersect as G}from"../../../core/textMetrics.js";import{resolveConcreteGraphicBounds as C}from"../../../grammar/schemas/graphicBounds.js";import{validateAxisTextStyle as wt}from"./labels.js";import{resolveRotation as P}from"../../../grammar/rotation.js";function S(t,e,i,o){return Tt({...t,text:i,lines:z(i,e),fontSize:e.fontSize,fontFamily:e.fontFamily,fontWeight:e.fontWeight,textAlign:e.align??"center",textBaseline:e.baseline??"middle",rotation:e.rotation},o)}function _(t,e){const i=["ordinal","band","point"].includes(t?.type);let o;if(e.at==="center")o=xt(t.range[0],t.range[1]);else if(e.at==="start"||e.at==="end")o=t.range[e.at==="start"?0:1];else if(i){if(!t.domain.includes(e.at))throw new RangeError("Axis title at value must be inside the scale domain.");o=ft([e.at],t)[0]}else{const s=Math.min(...t.domain),a=Math.max(...t.domain);if(e.at<s||e.at>a)throw new RangeError("Axis title at value must be inside the scale domain.");o=lt([e.at],t)[0]}return o+(e.alongOffset??0)}function N(t,e,i=e){const o=h(e,i)[2],s=t.graphicSpec.objects[o],a=f=>C(t.graphicSpec,f,t.materializationConfigs.textMetrics);return s?.items?.map(f=>a(f.id))??(s?[a(o)]:[])}function W(t,e,i,o=e){const s=L(t,e,i.position,i.scale,o),a=q(t)?.properties,f=t.graphicSpec.objects[h(e,o)[2]]?C(t.graphicSpec,h(e,o)[2],t.materializationConfigs.textMetrics):void 0,n=e==="x"?42:52;if(!s||!a||!f)return n;const u=e==="x",r=i.position===(u?"bottom":"right"),d=u?"top":"left",c=u?"bottom":"right",p=S(w({bounds:s,channel:e,position:i.position,along:0,offset:n}),i,t.semanticSpec.guides.axis?.[o]?.title??"",t.materializationConfigs.textMetrics),x=r?f[c]+4-p[d]:p[c]-f[d]+4,l=r?a[u?"height":"width"]-p[c]:p[d],m=e==="y"&&Math.abs(Math.cos(i.rotation))<1e-10?i.fontSize/4:0;if(t.materializationConfigs.canvas?.plot!==void 0)return n+Math.max(0,x);const y=Math.min(l,Math.max(0,x));if(m===0)return n+y;const v=Math.min(l-m,Math.max(0,x)),b=S(w({bounds:s,channel:e,position:i.position,along:_(t.resolvedScales[i.scale],i),offset:n+v}),i,t.semanticSpec.guides.axis?.[o]?.title??"",t.materializationConfigs.textMetrics);return n+(N(t,e,o).some(T=>T&&G(b,T))?y:v)}function St(t,e,i,o,s,a){const f=t.graphicSpec.objects[h(e,o)[2]];if(t.guideConfigs.axis?.[o]?.labels?.position!==i.position)throw new Error("Label-relative title offset requires labels on the same axis side.");const u=(f?.items??(f?[f]:[])).filter(l=>tt(l.properties)).map(l=>C(t.graphicSpec,l.id,t.materializationConfigs.textMetrics));if(u.length===0)throw new Error("Label-relative title offset requires nonempty axis labels.");const r=e==="x"?"top":"left",d=e==="x"?"bottom":"right",c=i.position===(e==="x"?"bottom":"right"),p=S(w({bounds:s,channel:e,position:i.position,along:a,offset:0}),i,t.semanticSpec.guides.axis?.[o]?.title??"",t.materializationConfigs.textMetrics),x=c?Math.max(...u.map(l=>l[d]))+i.offset.gap-p[r]:p[d]-Math.min(...u.map(l=>l[r]))+i.offset.gap;if(!Number.isFinite(x))throw new RangeError("Label-relative title offset must resolve to a finite distance.");return x}const k=["target","text","scale","position","at","offset","rotation","color","fontSize","fontFamily","fontWeight","alongOffset","align","baseline","overlap"],At=k.filter(t=>t!=="scale"),A={color:ct.text,fontSize:dt,fontFamily:ut,fontWeight:600};function O(t){return rt(t,"Axis title text")}function D(t,e){if(yt(t,e.position),!["start","center","end"].includes(e.at)&&!Number.isFinite(e.at))throw new TypeError("Axis title at must be start, center, end, or a finite number.");if(g(e,{title:!0}),!Number.isFinite(e.rotation))throw new TypeError("Axis title rotation must be finite radians.");wt(e,"Axis title")}function X(t,e,i){const o=new Set,s=new Set;for(const a of t.semanticSpec.layers){if(ot(a)||t.markConfigs[a.id]?.statisticalReference!==void 0)continue;const f=a.encoding?.[e],n=a.mark?.type==="area"&&Object.hasOwn(f??{},"datum")?a.encoding?.[`${e}2`]:f;if(e==="radius"&&a.mark?.type==="arc"&&n?.scale===i&&n.aggregate==="count"&&n.field===void 0){o.add(n.title??"count");continue}if(n?.scale===i&&typeof n.field=="string"&&n.field.length){const u=pt(t,a.data),r=mt(t,u,["density","ecdf","interval","boxSummary","gradientProfile","summary"]);let d;if(r?.type==="density")d=n.field===r.as?.[0]?r.field:n.field===r.as?.[1]?"Density":void 0;else if(r?.type==="ecdf")d=n.field===r.as.value?r.field:n.field===r.as.probability?"Cumulative probability":n.field===r.as.cumulative?"Cumulative count":void 0;else if(r?.type==="interval"&&Object.values(r.as).includes(n.field))d=`${r.center}(${r.field})`;else if(r?.type==="boxSummary"&&Object.values(r.as).includes(n.field))d=r.field;else if(r?.type==="gradientProfile"&&[r.as.lower,r.as.upper,r.as.center].includes(n.field))d=r.field;else if(r?.type==="summary"){const p=r.aggregates.find(x=>x.as===n.field);p&&(d=R(p.op,p.field))}const c=n.title??d??(n.aggregate===void 0?n.field:R(n.aggregate,n.field));o.add(c),a.mark?.type==="point"&&s.add(c)}}if(s.size===1)return[...s][0];if(o.size===0&&nt(t,i).some(a=>a.channel===e))return i;if(o.size!==1)throw new Error(`Axis title text cannot be inferred for scale "${i}".`);return[...o][0]}function Y(t,e,i,o=e){const s=t.resolvedScales[i.scale],a=L(t,e,i.position,i.scale,o);if(!["linear","time","ordinal","band","point"].includes(s?.type)&&!at(s?.type)||!a)throw new Error("Axis title requires a supported resolved scale and Canvas bounds.");const f=_(s,i),n=w({bounds:a,channel:e,position:i.position,along:f,offset:M(i.offset)?St(t,e,i,o,a,f):i.offset}),u=S(n,i,t.semanticSpec.guides.axis?.[o]?.title??"",t.materializationConfigs.textMetrics),r=q(t)?.properties;if(!r||!ht(u,r))throw M(i.offset)?new RangeError(`The ${e}-axis label-relative title does not fit the Canvas margin.`):it(`The ${e}-axis title does not fit the Canvas margin.`,[u],r);const d=N(t,e,o);if(!I(i)&&d.some(c=>c&&G(u,c)))throw new Error(`The ${e}-axis title overlaps the axis labels.`);return n}function U(t){const e=t==="x"?"X":"Y";return{create:`create${e}AxisTitle`,edit:`edit${e}AxisTitle`}}function V(t){const e=U(t);return $({op:e.edit,description:`Edit the ${t}-axis title.`},F(function(i={}){j(i,At,e.edit);const o=E(this,t,i,{create:!1}),s=o.id;i=o.options;const a=h(t,s)[3];if(g(i,{title:!0}),this.graphicSpec.objects[a]?.type!=="text")throw new Error(`${e.edit} requires an existing axis title.`);const f=this.guideConfigs.axis?.[s]?.title;if(!f)throw new Error(`${e.edit} requires title configuration.`);const{text:n,rotation:u,...r}=i,d=Object.hasOwn(i,"text"),c=Object.hasOwn(i,"rotation"),p=r.position??f.position,x=c?!1:f.inferredRotation===!0,l={...f,...r,position:p,rotation:x?B(t,p):c?P(u,"Axis title rotation"):f.rotation,inferredRotation:x,inferredOffset:Object.hasOwn(i,"offset")?!1:f.inferredOffset===!0,...d?{inferredText:!1}:{}};D(t,l);let m=this;const y=d?O(n):l.inferredText===!0?X(this,t,l.scale):this.semanticSpec.guides.axis?.[s]?.title;y!==this.semanticSpec.guides.axis?.[s]?.title&&(m=m.editSemantic({property:`guide.axis.${s}.title`,value:O(y)}));const v=O(m.semanticSpec.guides.axis?.[s]?.title);l.inferredOffset&&(l.offset=W(m,t,l,s));const b=Y(m,t,l,s);m=m._withGuideConfig(s,"title",l);const T={x:b.x,y:b.y,text:v,fill:l.color,fontSize:l.fontSize,fontFamily:l.fontFamily,fontWeight:l.fontWeight,textAlign:l.align??"center",textBaseline:l.baseline??"middle",rotation:l.rotation};for(const[K,Z]of Object.entries(T))m=m.editGraphics({target:a,property:K,value:Z});return Q(m,a,z(v,l))}))}const Ot=V("x"),gt=V("y");function H(t){const e=U(t);return $({op:e.create,description:`Create the ${t}-axis title.`},F(function(i={}){j(i,k,e.create);const o=E(this,t,i,{create:!0}),s=o.id;i=o.options;const a=h(t,s)[3];g(i,{title:!0});const{text:f,scale:n,position:u,rotation:r,...d}=i,c=st(n??t,"Scale id"),p=this.semanticSpec.guides.axis?.[s]?.scale;if(p&&p!==c)throw new Error(`${e.create} conflicts with the existing axis scale.`);if(this.graphicSpec.objects[a])throw new Error(`${e.create} requires a missing axis title.`);const x=!Object.hasOwn(i,"text"),l=O(f??X(this,t,c)),m=u??bt(t),y=!Object.hasOwn(i,"rotation"),v=!Object.hasOwn(i,"offset"),b={scale:c,inferredText:x,position:m,at:"center",offset:t==="x"?42:52,rotation:y?B(t,m):P(r,"Axis title rotation"),inferredRotation:y,inferredOffset:v,color:A.color,fontSize:A.fontSize,fontFamily:A.fontFamily,fontWeight:A.fontWeight,...et(this.materializationConfigs.theme,"title"),...d};let T=this.editSemantic({property:`guide.axis.${s}.scale`,value:c}).editSemantic({property:`guide.axis.${s}.title`,value:l});return v&&(b.offset=W(T,t,b,s)),D(t,b),Y(T,t,b,s),T.createGraphics({id:a,type:"text",...vt(this)})._withGuideConfig(s,"title",b)[e.edit](s===t?void 0:J(s,t))}))}const Ct=H("x"),Et=H("y");function Qt(t){t.prototype.createXAxisTitle=Ct,t.prototype.createYAxisTitle=Et,t.prototype.editXAxisTitle=Ot,t.prototype.editYAxisTitle=gt}export{X as inferAxisTitleText,Qt as registerAxisTitleActions};
1
+ import{compositionChannel as z}from"../../../selectors/plotComposition.js";import{selectCartesianAxis as E,axisTargetArgs as I}from"../../../selectors/axes.js";import{axisGraphicIds as h}from"../../../materialization/guides/resources.js";import{materializeTextLines as tt}from"../../../materialization/textLines.js";import{axisTitleTextLines as M,validateAxisTextPlacement as g,axisTitleAllowsLabelOverlap as et,isLabelRelativeTitleOffset as F,axisLabelHasText as it}from"../../../grammar/axisTextPlacement.js";import{axisThemeTypography as ot}from"../../theme/state.js";import{canvasOverflowError as j}from"../../../layout/canvas.js";import{isSourceOwnedText as st}from"../../../grammar/text.js";import{withGuideLayoutValidation as $}from"../../../materialization/guides/layout.js";import{action as L}from"../../../core/action.js";import{validateUserId as nt}from"../../../core/identifiers.js";import{validateKeys as R,validateNonEmptyString as rt}from"../../../core/validation.js";import{axisScaleBindings as at,resolveCartesianAxisFrameBounds as B}from"../../../materialization/coordinateBounds.js";import{isTransformedScaleType as lt,mapContinuousScaleValues as ft,mapOrdinalPositionValues as dt}from"../../../grammar/scales/index.js";import{AXIS_TITLE_FONT_SIZE as ct,DEFAULT_COLORS as ut,DEFAULT_FONT_FAMILY as pt}from"../../../theme/defaults.js";import{findDataset as mt}from"../../../selectors/datasets.js";import{findUpstreamTransform as xt}from"../../../materialization/dataProvenance.js";import{formatAggregateTitle as q}from"../../../grammar/aggregate.js";import{finiteMidpoint as vt}from"../../../grammar/numeric.js";import{defaultAxisPosition as yt,defaultAxisTitleRotation as G,resolveAxisTitleGeometry as S,validateAxisPosition as bt}from"./policy.js";import{findCanvasGraphic as P,resolvePlotGraphicPlacement as Tt}from"../../../materialization/graphicHierarchy.js";import{resolveTextBounds as ht,textBoundsFitCanvas as St,textBoundsIntersect as _}from"../../../core/textMetrics.js";import{resolveConcreteGraphicBounds as C}from"../../../grammar/schemas/graphicBounds.js";import{validateAxisTextStyle as wt}from"./labels.js";import{resolveRotation as N}from"../../../grammar/rotation.js";function w(t,e,i,n){return ht({...t,text:i,lines:M(i,e),fontSize:e.fontSize,fontFamily:e.fontFamily,fontWeight:e.fontWeight,textAlign:e.align??"center",textBaseline:e.baseline??"middle",rotation:e.rotation},n)}function W(t,e){const i=["ordinal","band","point"].includes(t?.type);let n;if(e.at==="center")n=vt(t.range[0],t.range[1]);else if(e.at==="start"||e.at==="end")n=t.range[e.at==="start"?0:1];else if(i){if(!t.domain.includes(e.at))throw new RangeError("Axis title at value must be inside the scale domain.");n=dt([e.at],t)[0]}else{const a=Math.min(...t.domain),s=Math.max(...t.domain);if(e.at<a||e.at>s)throw new RangeError("Axis title at value must be inside the scale domain.");n=ft([e.at],t)[0]}return n+(e.alongOffset??0)}function k(t,e,i=e){const n=h(e,i)[2],a=t.graphicSpec.objects[n],s=f=>C(t.graphicSpec,f,t.materializationConfigs.textMetrics);return a?.items?.map(f=>s(f.id))??(a?[s(n)]:[])}function D(t,e,i,n=e){const a=B(t,e,i.position,i.scale,n),s=P(t)?.properties,f=t.graphicSpec.objects[h(e,n)[2]]?C(t.graphicSpec,h(e,n)[2],t.materializationConfigs.textMetrics):void 0,o=e==="x"?42:52;if(!a||!s||!f)return o;const c=e==="x",r=i.position===(c?"bottom":"right"),d=c?"top":"left",u=c?"bottom":"right",p=w(S({bounds:a,channel:e,position:i.position,along:0,offset:o}),i,t.semanticSpec.guides.axis?.[n]?.title??"",t.materializationConfigs.textMetrics),x=r?f[u]+4-p[d]:p[u]-f[d]+4,l=r?s[c?"height":"width"]-p[u]:p[d],m=e==="y"&&Math.abs(Math.cos(i.rotation))<1e-10?i.fontSize/4:0;if(t.materializationConfigs.canvas?.plot!==void 0)return o+Math.max(0,x);const y=Math.min(l,Math.max(0,x));if(m===0)return o+y;const b=Math.min(l-m,Math.max(0,x)),v=w(S({bounds:a,channel:e,position:i.position,along:W(t.resolvedScales[i.scale],i),offset:o+b}),i,t.semanticSpec.guides.axis?.[n]?.title??"",t.materializationConfigs.textMetrics);return o+(k(t,e,n).some(T=>T&&_(v,T))?y:b)}function At(t,e,i,n,a,s){const f=t.graphicSpec.objects[h(e,n)[2]];if(t.guideConfigs.axis?.[n]?.labels?.position!==i.position)throw new Error("Label-relative title offset requires labels on the same axis side.");const c=(f?.items??(f?[f]:[])).filter(l=>it(l.properties)).map(l=>C(t.graphicSpec,l.id,t.materializationConfigs.textMetrics));if(c.length===0)throw new Error("Label-relative title offset requires nonempty axis labels.");const r=e==="x"?"top":"left",d=e==="x"?"bottom":"right",u=i.position===(e==="x"?"bottom":"right"),p=w(S({bounds:a,channel:e,position:i.position,along:s,offset:0}),i,t.semanticSpec.guides.axis?.[n]?.title??"",t.materializationConfigs.textMetrics),x=u?Math.max(...c.map(l=>l[d]))+i.offset.gap-p[r]:p[d]-Math.min(...c.map(l=>l[r]))+i.offset.gap;if(!Number.isFinite(x))throw new RangeError("Label-relative title offset must resolve to a finite distance.");return x}const X=["target","text","scale","position","at","offset","rotation","color","fontSize","fontFamily","fontWeight","alongOffset","align","baseline","overlap"],Ot=X.filter(t=>t!=="scale"),A={color:ut.text,fontSize:ct,fontFamily:pt,fontWeight:600};function O(t){return rt(t,"Axis title text")}function Y(t,e){if(bt(t,e.position),!["start","center","end"].includes(e.at)&&!Number.isFinite(e.at))throw new TypeError("Axis title at must be start, center, end, or a finite number.");if(g(e,{title:!0}),!Number.isFinite(e.rotation))throw new TypeError("Axis title rotation must be finite radians.");wt(e,"Axis title")}function U(t,e,i){const n=new Set,a=new Set;for(const s of t.semanticSpec.layers){if(st(s)||s.derivedBindings?.overlay||t.markConfigs[s.id]?.statisticalReference!==void 0)continue;const f=s.encoding?.[e],o=s.mark?.type==="area"&&Object.hasOwn(f??{},"datum")?s.encoding?.[`${e}2`]:f;if(e==="radius"&&s.mark?.type==="arc"&&o?.scale===i&&o.aggregate==="count"&&o.field===void 0){n.add(o.title??"count");continue}if(o?.scale===i&&typeof o.field=="string"&&o.field.length){const c=mt(t,s.data),r=xt(t,c,["density","ecdf","interval","boxSummary","gradientProfile","summary"]);let d;if(r?.type==="density")d=o.field===r.as?.[0]?r.field:o.field===r.as?.[1]?"Density":void 0;else if(r?.type==="ecdf")d=o.field===r.as.value?r.field:o.field===r.as.probability?"Cumulative probability":o.field===r.as.cumulative?"Cumulative count":void 0;else if(r?.type==="interval"&&Object.values(r.as).includes(o.field))d=`${r.center}(${r.field})`;else if(r?.type==="boxSummary"&&Object.values(r.as).includes(o.field))d=r.field;else if(r?.type==="gradientProfile"&&[r.as.lower,r.as.upper,r.as.center].includes(o.field))d=r.field;else if(r?.type==="summary"){const p=r.aggregates.find(x=>x.as===o.field);p&&(d=q(p.op,p.field))}const u=o.title??d??(o.aggregate===void 0?o.field:q(o.aggregate,o.field));n.add(u),s.mark?.type==="point"&&a.add(u)}}if(a.size===1)return[...a][0];if(n.size===0&&at(t,i).some(s=>s.channel===e))return i;if(n.size>1){const s=t.semanticSpec.layers.filter(o=>o.encoding?.[e]?.scale===i).map(o=>z(t,o.encoding[e],o.data)).filter(Boolean),f=new Set(s.map(o=>JSON.stringify([o.field??o.fields,o.measurement.unit])));if(s.length&&f.size===1)return s[0].field??(s[0].measurement.unit==="dimensions"?"Dimension":"Value");if(s.length&&s.every(o=>o.field&&o.measurement.unit==="value")&&!t.semanticSpec.layers.some(o=>o.encoding?.[e]?.scale===i&&o.encoding[e].title!==void 0&&o.encoding[e].title!==z(t,o.encoding[e],o.data)?.field))return[...new Set(s.map(o=>o.field))].join(" / ")}if(n.size!==1)throw new Error(`Axis title text cannot be inferred for scale "${i}".`);return[...n][0]}function V(t,e,i,n=e){const a=t.resolvedScales[i.scale],s=B(t,e,i.position,i.scale,n);if(!["linear","time","ordinal","band","point"].includes(a?.type)&&!lt(a?.type)||!s)throw new Error("Axis title requires a supported resolved scale and Canvas bounds.");const f=W(a,i),o=S({bounds:s,channel:e,position:i.position,along:f,offset:F(i.offset)?At(t,e,i,n,s,f):i.offset}),c=w(o,i,t.semanticSpec.guides.axis?.[n]?.title??"",t.materializationConfigs.textMetrics),r=P(t)?.properties;if(!r||!St(c,r))throw F(i.offset)?j(`The ${e}-axis label-relative title does not fit the Canvas margin.`,[c],r):j(`The ${e}-axis title does not fit the Canvas margin.`,[c],r);const d=k(t,e,n);if(!et(i)&&d.some(u=>u&&_(c,u)))throw new Error(`The ${e}-axis title overlaps the axis labels.`);return o}function H(t){const e=t==="x"?"X":"Y";return{create:`create${e}AxisTitle`,edit:`edit${e}AxisTitle`}}function J(t){const e=H(t);return L({op:e.edit,description:`Edit the ${t}-axis title.`},$(function(i={}){R(i,Ot,e.edit);const n=E(this,t,i,{create:!1}),a=n.id;i=n.options;const s=h(t,a)[3];if(g(i,{title:!0}),this.graphicSpec.objects[s]?.type!=="text")throw new Error(`${e.edit} requires an existing axis title.`);const f=this.guideConfigs.axis?.[a]?.title;if(!f)throw new Error(`${e.edit} requires title configuration.`);const{text:o,rotation:c,...r}=i,d=Object.hasOwn(i,"text"),u=Object.hasOwn(i,"rotation"),p=r.position??f.position,x=u?!1:f.inferredRotation===!0,l={...f,...r,position:p,rotation:x?G(t,p):u?N(c,"Axis title rotation"):f.rotation,inferredRotation:x,inferredOffset:Object.hasOwn(i,"offset")?!1:f.inferredOffset===!0,...d?{inferredText:!1}:{}};Y(t,l);let m=this;const y=d?O(o):l.inferredText===!0?U(this,t,l.scale):this.semanticSpec.guides.axis?.[a]?.title;y!==this.semanticSpec.guides.axis?.[a]?.title&&(m=m.editSemantic({property:`guide.axis.${a}.title`,value:O(y)}));const b=O(m.semanticSpec.guides.axis?.[a]?.title);l.inferredOffset&&(l.offset=D(m,t,l,a));const v=V(m,t,l,a);m=m._withGuideConfig(a,"title",l);const T={x:v.x,y:v.y,text:b,fill:l.color,fontSize:l.fontSize,fontFamily:l.fontFamily,fontWeight:l.fontWeight,textAlign:l.align??"center",textBaseline:l.baseline??"middle",rotation:l.rotation};for(const[Z,Q]of Object.entries(T))m=m.editGraphics({target:s,property:Z,value:Q});return tt(m,s,M(b,l))}))}const gt=J("x"),Ct=J("y");function K(t){const e=H(t);return L({op:e.create,description:`Create the ${t}-axis title.`},$(function(i={}){R(i,X,e.create);const n=E(this,t,i,{create:!0}),a=n.id;i=n.options;const s=h(t,a)[3];g(i,{title:!0});const{text:f,scale:o,position:c,rotation:r,...d}=i,u=nt(o??t,"Scale id"),p=this.semanticSpec.guides.axis?.[a]?.scale;if(p&&p!==u)throw new Error(`${e.create} conflicts with the existing axis scale.`);if(this.graphicSpec.objects[s])throw new Error(`${e.create} requires a missing axis title.`);const x=!Object.hasOwn(i,"text"),l=O(f??U(this,t,u)),m=c??yt(t),y=!Object.hasOwn(i,"rotation"),b=!Object.hasOwn(i,"offset"),v={scale:u,inferredText:x,position:m,at:"center",offset:t==="x"?42:52,rotation:y?G(t,m):N(r,"Axis title rotation"),inferredRotation:y,inferredOffset:b,color:A.color,fontSize:A.fontSize,fontFamily:A.fontFamily,fontWeight:A.fontWeight,...ot(this.materializationConfigs.theme,"title"),...d};let T=this.editSemantic({property:`guide.axis.${a}.scale`,value:u}).editSemantic({property:`guide.axis.${a}.title`,value:l});return b&&(v.offset=D(T,t,v,a)),Y(t,v),V(T,t,v,a),T.createGraphics({id:s,type:"text",...Tt(this)})._withGuideConfig(a,"title",v)[e.edit](a===t?void 0:I(a,t))}))}const zt=K("x"),Et=K("y");function te(t){t.prototype.createXAxisTitle=zt,t.prototype.createYAxisTitle=Et,t.prototype.editXAxisTitle=gt,t.prototype.editYAxisTitle=Ct}export{U as inferAxisTitleText,te as registerAxisTitleActions};
@@ -1,11 +1,12 @@
1
+ import {colorSizeScaleIds} from "../../grammar/colorSizeLegend.js";
1
2
  import { resolveLegendCreationPlan } from "./legends/categorical/actions.js";
2
3
  import { resolveLegendStepConfig } from "./legends/creation.js";
3
4
  import { legendResourcePolicy } from "../../materialization/guides/resources.js";
4
5
  import { assertGuideOptions, guideConflict, sameGuideValue } from "./reuse.js";
5
6
 
6
7
  function compatibleLegend(program, kind, requested, existing, explicit) {
7
- const expectedScales = kind === "colorSize" ? [requested.colorScale,requested.sizeScale] : requested.scales ?? [requested.scale];
8
- const actualScales = kind === "colorSize" ? [existing.colorScale,existing.sizeScale] : existing.scales ?? [existing.scale];
8
+ const expectedScales = kind === "colorSize" ? colorSizeScaleIds(requested) : requested.scales ?? [requested.scale];
9
+ const actualScales = kind === "colorSize" ? colorSizeScaleIds(existing) : existing.scales ?? [existing.scale];
9
10
  if (!sameGuideValue(expectedScales, actualScales) ||
10
11
  !sameGuideValue(requested.channels, existing.channels)) {
11
12
  guideConflict(`${kind} legend uses different channels or scales`);
@@ -1,3 +1,4 @@
1
+ import { withGuideVisibilityPaint } from "../../../materialization/guides/visibility.js";
1
2
  import { editGraphicProperties } from "../../primitives/graphicProperties.js";
2
3
  import { action, closedAction } from "../../../core/action.js";
3
4
  import { isPlainObject } from "../../../core/immutable.js";
@@ -45,7 +46,7 @@ function makeRematerialize(direction) {
45
46
  op: operation.rematerialize,
46
47
  description: `Recompute concrete ${direction} grid lines.`
47
48
  },
48
- function (args = {}) {
49
+ withGuideVisibilityPaint(function (args = {}) {
49
50
  noOptions(args, operation.rematerialize);
50
51
  const storedConfig = this.guideConfigs.grid?.[direction];
51
52
  const semantic = this.semanticSpec.guides.grid?.[direction];
@@ -89,7 +90,7 @@ function makeRematerialize(direction) {
89
90
  value: geometry.strokeDash
90
91
  });
91
92
  return hasPlotUnderlays(next) ? next.rematerializePlotDrawOrder() : next;
92
- }
93
+ })
93
94
  );
94
95
  }
95
96
 
@@ -34,7 +34,7 @@ import {
34
34
  const GRID_OPTIONS = Object.freeze([
35
35
  "scale", "coordinate", "count", "values", "color", "lineWidth", "strokeDash", "strokeDashMap", "zIndex", "bandPosition", "alongOffset"
36
36
  ]);
37
- const GRID_EDIT_OPTIONS = Object.freeze([
37
+ const GRID_EDIT_OPTIONS = Object.freeze(["visible",
38
38
  "count", "values", "color", "lineWidth", "strokeDash", "strokeDashMap", "zIndex", "bandPosition", "alongOffset"
39
39
  ]);
40
40
  const DEFAULT_STYLE = Object.freeze({
@@ -82,6 +82,7 @@ export function validateGridCreateArgs(args, operation) {
82
82
  }
83
83
 
84
84
  export function validateGridEditArgs(args, operation) {
85
+ if (Object.hasOwn(args, "visible") && typeof args.visible !== "boolean") throw new TypeError("Grid visible must be a boolean.");
85
86
  if (Object.hasOwn(args, "strokeDashMap")) normalizeGuideDashMap(args.strokeDashMap);
86
87
  validateOptionObject(args, GRID_EDIT_OPTIONS, operation, {
87
88
  allowEmpty: false
@@ -9,6 +9,7 @@ import { findDataset } from "../../selectors/datasets.js";
9
9
  import { findUpstreamTransform } from
10
10
  "../../materialization/dataProvenance.js";
11
11
  import { sampleNumericRange } from "../../grammar/numeric.js";
12
+ import { automaticLegendOptions } from "./legends/automatic.js";
12
13
 
13
14
  const OPTIONS = Object.freeze(["axes", "grid", "legend"]);
14
15
 
@@ -121,10 +122,10 @@ export function resolveGuideOptions(program, args = {}, layers = program.semanti
121
122
  args.grid,
122
123
  applicability.grid.cartesian || applicability.grid.polar
123
124
  );
124
- const legend = selectOption(
125
+ const legend = automaticLegendOptions(program, selectOption(
125
126
  args.legend,
126
127
  applicability.legend
127
- );
128
+ ), layers);
128
129
  return { axes, grid, legend };
129
130
  }
130
131
 
@@ -0,0 +1,28 @@
1
+ import { resolveLegendCreationPlan } from "./categorical/actions.js";
2
+ import { resolveLegendStepConfig } from "./creation.js";
3
+ import { legendBlockChannels } from "../../../grammar/legendBlocks.js";
4
+
5
+ // Resolve the ordinary legend inference first, then exclude only suppressed
6
+ // owner/channel pairs. Explicit createLegend bypasses this automatic policy.
7
+ export function automaticLegendOptions(program, args, layers = program.semanticSpec.layers) {
8
+ if (args === undefined || !Object.keys(program.guideConfigs.suppressedLegends ?? {}).length) return args;
9
+ const plan = resolveLegendCreationPlan(program, args, layers);
10
+ const owners = new Map();
11
+ let changed = false;
12
+ for (const step of plan.steps) {
13
+ const { kind, config } = resolveLegendStepConfig(program, step);
14
+ const suppressed = program.guideConfigs.suppressedLegends?.[config.target]?.channels ?? [];
15
+ const channels = legendBlockChannels(kind, config);
16
+ const retained = channels.filter(channel => !suppressed.includes(channel));
17
+ changed ||= retained.length !== channels.length;
18
+ if (!owners.has(config.target)) owners.set(config.target, new Set());
19
+ retained.forEach(channel => owners.get(config.target).add(channel));
20
+ }
21
+ if (!changed) return args;
22
+ const active = [...owners].filter(([, channels]) => channels.size);
23
+ if (!active.length) return undefined;
24
+ if (active.length !== 1) throw new Error("Automatic legend requires one unambiguous target.");
25
+ const [target, channels] = active[0];
26
+ const { combine: _combine, ...options } = args;
27
+ return { ...options, target, channels: [...channels] };
28
+ }