import type { AutoSizedLabelText, BakedLabelSource, BarValueAnchor, BoxBounds, Callback, CallbackParam, CandidateLabelStyle, CandidateStyleResolver, CollideWith, DynamicContext, FitRegion, FontOptions, IsAny, LabelFit, LabelFitDescriptor, LabelPlacement, NormalisedTextOrSegments, OrientationAnchor, Point, PointLabelDatum, PositionedCandidateResolver, PositionedLabelCandidate, RectObstacleSource } from 'ag-charts-core'; import { type NormalisedChartLabelStyleOptions } from 'ag-charts-core'; import type { AgChartLabelOrientation, AgChartLabelStylerParams, AgConeFunnelSeriesLabelPlacement, AgFunnelSeriesLabelPlacement, AgMarkerShape, HighlightState, PaddingOptions, PixelSize, SelectionState } from 'ag-charts-types'; import type { HighlightNodeDatum } from '../core/eventsHub'; import type { ChartRegistry } from '../module/moduleContext'; import type { Text } from '../scene/shape/text'; import { type SectorBoundaries } from '../scene/util/sector'; import { type Label, type LabelPlacementStyle } from './label'; import type { DatumIndex, SeriesNodeDatum } from './series/seriesTypes'; interface SeriesLike { id: string; ctx: DynamicContext; declarationOrder: number; readonly data?: { readonly dataIdKey?: string; }; get visible(): boolean; cachedCallWithContext(fn: F, params: CallbackParam): ReturnType | undefined; isSeriesHighlighted(highlightedDatum: HighlightNodeDatum | undefined): boolean; getHighlightStateString(datum: HighlightNodeDatum | undefined, isHighlight?: boolean, datumIndex?: DatumIndex): HighlightState; getSelectionStateString(datumIndex: DatumIndex | undefined): SelectionState | undefined; getCandidateStateString(datumIndex: DatumIndex | undefined): SelectionState | undefined; } type Bounds = { x: number; y: number; width: number; height: number; }; /** Bar `beside-*` placements offset a label perpendicular to the value axis, to the side of its segment. */ export type BesideBarLabelPlacement = `beside-${'before' | 'after'}-${'start' | 'center' | 'end'}`; export type BarLabelPlacement = 'inside-center' | 'inside-start' | 'inside-end' | 'outside-start' | 'outside-end' | BesideBarLabelPlacement; /** A label's resolved inside/outside placement, selecting which placement-style overrides apply. */ export type ResolvedLabelPlacement = 'inside' | 'outside'; /** The final placement/orientation a series chose for a label, surfaced to `itemStyler` params. */ type ResolvedPlacement = Pick, 'placement' | 'orientation'>; type LabelDatum = Point & { text: NormalisedTextOrSegments; /** * The text the placement engine fitted to the candidate it chose, rendered in place of {@link text}. * Kept separate so {@link text} stays the unfitted source every placement pass re-fits from, rather * than each pass truncating the previous pass's output further. */ fittedText?: NormalisedTextOrSegments; /** Reduced font size the text was fitted at, overriding the styled `fontSize` when the label shrank. */ fittedFontSize?: number; textAlign: CanvasTextAlign; textBaseline: CanvasTextBaseline; /** * The label's resolved placement. Bar-family labels carry the granular {@link BarLabelPlacement} * (coarsened to inside/outside via {@link toResolvedPlacement} when selecting placement styles); * placement-engine series carry the compass {@link LabelPlacement}; the rest carry the coarse * {@link ResolvedLabelPlacement}. Unset applies neither style. */ placement?: ResolvedLabelPlacement | BarLabelPlacement | LabelPlacement | AgFunnelSeriesLabelPlacement | AgConeFunnelSeriesLabelPlacement; /** Rotation in radians applied to the label node; `undefined`/`0` renders upright. */ rotation?: number; /** Translation (px) sliding a region-bound label flush inside its region; `undefined`/`0` leaves it anchored. */ offsetX?: number; offsetY?: number; }; /** * Fits a label's text to its resolved policy and, when the series supplies one, its geometric container * (bar rect, donut hole, …). Each axis is bounded by the tighter of the explicit `maxWidth`/`maxHeight` * and the container extent, so text never overflows its container yet the user can constrain further. * When the policy resolves to "show" (no truncation and no collision avoidance) no bound is applied and * the text renders in full, leaving container series unchanged unless the user opts into truncation. */ export declare function fitLabelToContainer(text: NormalisedTextOrSegments, fit: LabelFit | undefined, font: FontOptions, container: { width: number; height: number; } | undefined, fitOverflow?: LabelFit): NormalisedTextOrSegments; /** * {@link fitLabelToContainer} for a label that may shrink, reporting the reduced size its text was fitted * at so the caller can measure and render it there; `fontSize` is `undefined` at the configured size. */ export declare function fitLabelToContainerAutoSize(text: NormalisedTextOrSegments, fit: LabelFit | undefined, font: FontOptions, container: { width: number; height: number; } | undefined, fitOverflow?: LabelFit): AutoSizedLabelText; /** * Bounds a fit's `maxWidth`/`maxHeight` by a container extent, taking the tighter of the explicit bound * and the container. Returns `fit` unchanged when there is no fit or no container. Series with an * invariant container should call this once at context-build time rather than per datum. */ export declare function boundLabelFit(fit: LabelFit | undefined, container: { width: number; height: number; } | undefined): LabelFit | undefined; /** * Container that keeps an `inside` label within a marker of diameter `markerSize`, sized to the largest * rectangle that fits the marker's shape (analysed once per shape by {@link markerLabelRect}). `threshold` * shrinks that rectangle on every side so the label clears the marker walls by that many pixels. Pair * with {@link resolveInsidePlacement}'s `offset` to position the label at that rectangle, which need not * be marker-centred. */ export declare function insideMarkerContainer(markerSize: number, shape?: AgMarkerShape, threshold?: number): { width: number; height: number; }; /** Placement and size of the rectangle a horizontal sector label fits into. */ export interface SectorLabelRect { /** Centre of the inscribed rectangle — where the horizontal label sits. */ readonly centerX: number; readonly centerY: number; /** Room the label may use at that centre: the wedge's own width there, not the text's. */ readonly width: number; /** The block's height — as measured by the caller when it supplied one, estimated from one line if not. */ readonly height: number; /** The block fits centred on the anchor, so a caller must not recentre it on the room it found. */ readonly anchored?: boolean; } /** Size a sector label's text actually wrapped to, as measured on the node that will draw it. */ export interface BlockSize { readonly width: number; readonly height: number; } /** * Rectangle a horizontal label should fill inside an origin-centred annular wedge. {@link sectorLabelContainer} * sizes a box that fits the wedge centred on `anchor`, but a horizontal label is not symmetric about the sector * bisector, so that box hugs one side. Keeping the (safe) size, we slide it to the middle of the room the wedge * actually offers on each axis — probed with {@link isPointInSector} — so the label sits centred both ways * instead of against an edge. `lineHeight` seeds the size search with a single line's height. * * A multi-line box spans a radial band whose width varies with height, so sizing it symmetrically about the * bisector caps its width at the nearer radial edge and the slide above cannot recover the room the far side * offers — the label ends up hugging one edge. Such a box is instead fitted to the wedge's true horizontal * extent across its own band (see {@link centreSectorLabelInBand}); the symmetric slide is kept for * single-line boxes, whose thin band cannot exhibit this. * * `block` replaces the estimated size once the caller knows what its text wrapped to: a block sized from one * line is placed where a thin box fits, which is not where the block it grew into belongs. A block that fits * on the anchor stays on it — the sector nominates that point for its label, and both searches below trade * centring for room a block this size does not need. */ export declare function fitSectorLabelRect(anchor: Point, sector: { startAngle: number; endAngle: number; innerRadius: number; outerRadius: number; }, lineHeight: number, block?: BlockSize): SectorLabelRect; /** Bounds on the text a wedge can hold, for a caller sizing a label before it lays any text out. */ export interface SectorLabelRoom { /** Total room the wedge's bands add up to. */ readonly area: number; /** Widest total advance the wedge could hold at `lineHeight`, stacking as many whole lines as it fits. */ capacityAt(lineHeight: number): number; } /** * The room a wedge offers a label, measured band by band for a band `lineHeight` tall. Both bounds are * generous — a band is wider than the text centred in it, wrapping on words costs width they do not charge * for, and a band measured for a short line is never narrower than a taller line would get — so text * exceeding either cannot fit however it wraps, which is what makes them safe to reject a font size on. */ export declare function sectorLabelRoom(anchor: Point, sector: SectorBoundaries, lineHeight: number): SectorLabelRoom; /** Inside-marker label geometry resolved from a series' configured placements and marker shape. */ export interface InsidePlacement { /** True when every placement is `inside`, so the text is fitted to the marker up front. */ readonly insideOnly: boolean; /** Centres an `inside` label on the marker's inscribed rectangle; set whenever `inside` is a placement. */ readonly offset: Point | undefined; /** Inscribed-rect size so the engine rejects an oversized `inside` candidate; set only for a mixed list. */ readonly size: { width: number; height: number; } | undefined; } /** * Resolves the inside-marker geometry for a placement list. A mixed list (e.g. `['inside','top']`) keeps * full-size text and instead supplies `size`, so an `inside` candidate too large for the marker fails and * cascades to the next placement; an inside-only list leaves `size` unset and fits the text to the marker. */ export declare function resolveInsidePlacement(placements: readonly LabelPlacement[], shape: AgMarkerShape | undefined): InsidePlacement; /** Selects the style overrides for a label's resolved placement; `undefined` when neither applies. */ export declare function pickPlacementStyle(styles: { insideStyle: LabelPlacementStyle; outsideStyle: LabelPlacementStyle; } | undefined, placement: ResolvedLabelPlacement | undefined): LabelPlacementStyle | undefined; /** Coarsens a granular bar placement to the inside/outside distinction the placement styles select on. */ export declare function toResolvedPlacement(placement: BarLabelPlacement): ResolvedLabelPlacement; /** A label surface carrying the placement-reactive style overrides a candidate style resolves against. */ type PlacementStyledLabel = Label & { insideStyle: LabelPlacementStyle; outsideStyle: LabelPlacementStyle; }; /** * How one candidate's compass placement reads: `style` selects the placement-style overrides, `reported` * is the `placement` the styler params carry. They differ for series that surface the compass placement * itself (line, area, bubble) rather than a coarsened one (range-area's inside/outside band sides). */ export interface CandidatePlacement { readonly style: ResolvedLabelPlacement; readonly reported: ResolvedPlacement['placement']; } /** Maps a candidate's compass placement onto the style and reported placement for `datum`. */ export type CandidatePlacementMapper = (placement: LabelPlacement | undefined, datum: PointLabelDatum) => CandidatePlacement; /** Compass series report the placement they were given; only `inside` sits on the shape. */ export declare const compassCandidatePlacement: CandidatePlacementMapper; /** * The per-candidate style hook the placement engine resolves label geometry through, or `undefined` when * the label has no `itemStyler` and its geometry is therefore placement-invariant. */ export declare function createCandidateStyleResolver(series: SeriesLike, label: PlacementStyledLabel, params: TParams, placementFor: CandidatePlacementMapper, labelPath?: string[]): CandidateStyleResolver | undefined; /** * The styled box a bar label reserves on the orientation-only route, where the series bakes the placement * and the engine resolves only the rotation. One box is reserved for every orientation there, so it is * resolved at `orientation` — the first candidate; a styler that sizes the box per orientation is honoured * in what is drawn but not in the reservation. `undefined` for an unstyled label, leaving the configured * measurement in place. * * Callers must drop a `hidden` result from their label data: unlike the cascade route, where the engine * skips hidden candidates itself, nothing downstream of here would stop a disabled label reserving space. */ export declare function styledBarLabelBox(resolveStyle: BarCandidateStyleResolver | undefined, styleDatum: SeriesNodeDatum | undefined, placement: BarLabelPlacement, orientation: AgChartLabelOrientation, text: NormalisedTextOrSegments): StyledBarLabelBox | undefined; export interface StyledBarLabelBox { readonly size: { width: number; height: number; }; readonly font: FontOptions; readonly boxPadding: Required; /** The styler disabled this label, so it must be left out of the label data entirely. */ readonly hidden: boolean; } /** A bar-family label surface: the placement-styled label every bar/histogram/waterfall/funnel series holds. */ export type BarLabelSurface = Label & { insideStyle: LabelPlacementStyle; outsideStyle: LabelPlacementStyle; }; /** The per-series values a bar-family `getLabelData` resolves once before walking its label data. */ export interface BarLabelDataContext { /** Per-side extent of the drawn box (padding plus border) around the glyph. */ readonly box: Required; readonly alwaysShow: boolean; readonly collideWith: CollideWith; readonly threshold: number; /** Drawn-box footprint of `text` under the configured label font: the measured glyph plus {@link box}. */ readonly measureBox: (text: NormalisedTextOrSegments) => { width: number; height: number; }; /** Per-candidate fit inputs for `text`, or `undefined` when the label opted out of overflow control. */ readonly fitFor: (text: NormalisedTextOrSegments) => LabelFitDescriptor | undefined; } /** * Resolves the collision and fit inputs shared by every label in a bar-family series. Every such series * needs the same six values in the same way, so they are derived here rather than restated per series. */ export declare function barLabelDataContext(label: BarLabelSurface): BarLabelDataContext; /** * The obstacles a bar-family series contributes: its bar rects, plus its own baked labels when they are * not routed through the placement engine. `pick` names the label on each element, which is the only * part that differs between series (a bar hangs its label off the node, a range bar flattens the pair). */ export declare function barLabelObstaclesFor(label: BarLabelSurface, nodeData: readonly RectObstacleSource[] | undefined, labelData: Iterable | undefined, includeBaked: boolean, pick: (element: TElement) => BakedBarLabel | undefined): import("ag-charts-core").LabelObstacle[] | undefined; /** A baked bar label, plus the reduced font size a shrink-to-fit pass left it drawn at. */ type BakedBarLabel = NonNullable & { readonly fittedFontSize?: number; }; /** The `itemStyler` geometry of one bar-label candidate; see {@link buildBarLabelCandidates}. */ export type BarCandidateStyleResolver = (nodeDatum: SeriesNodeDatum, placement: BarLabelPlacement, orientation: AgChartLabelOrientation) => CandidateLabelStyle; /** * The per-candidate style hook for bar-family labels, whose candidates the series positions itself. Unlike * the compass path the datum comes in per call, because these candidates are built while the node data is: * a series holds one resolver for every label it lays out. */ export declare function createBarCandidateStyleResolver(series: SeriesLike, label: PlacementStyledLabel, params: TParams, labelPath?: string[], reportPlacement?: (placement: BarLabelPlacement) => ResolvedPlacement['placement']): BarCandidateStyleResolver | undefined; export declare function getLabelStyles(series: SeriesLike, nodeDatum: SeriesNodeDatum | undefined, params: TParams, label: Label, isHighlight: boolean, activeHighlight: HighlightNodeDatum | undefined, labelPath?: string[], placementStyle?: LabelPlacementStyle, resolvedPlacement?: ResolvedPlacement): NormalisedChartLabelStyleOptions & { fontSize: number; }; export declare function updateLabelNode(series: IsAny extends false ? SeriesLike : never, textNode: IsAny extends false ? Text : never, params: IsAny extends false ? TParams : never, label: IsAny extends false ? Label : never, labelDatum: D | undefined, highlight: { isHighlight: boolean; activeHighlight: HighlightNodeDatum | undefined; }, labelPath?: string[], placementStyle?: LabelPlacementStyle, resolvedPlacement?: ResolvedPlacement): void; /** The value-axis end an inside/beside placement anchors against, so `spacing` reserves its gap there. */ export declare function barValueAnchor(placement: BarLabelPlacement): BarValueAnchor; /** * Containment region and text container for an inside bar label at `placement`: the region reserves the * anchored-side `spacing` gap (nothing for the centred placement), so the gap survives the engine's * flush/containment; the container is that region minus the drawn box, bounding how much text fits. Fit * and containment share the region so fitted text can never overflow the bound the engine contains it * against. */ export declare function insideBarLabelBounds(rect: Bounds, placement: BarLabelPlacement, isUpward: boolean, isVertical: boolean, spacing: number, box: Required): { region: BoxBounds; container: { width: number; height: number; }; }; export declare function adjustLabelPlacement({ isUpward, isVertical, placement, spacing, boxPadding, rect, rotation, labelWidth, labelHeight, crossReversed, }: { placement: BarLabelPlacement; isUpward: boolean; isVertical: boolean; spacing?: PixelSize; boxPadding?: Required; rect: Bounds; rotation?: number; labelWidth?: number; labelHeight?: number; crossReversed?: boolean; }): Omit; /** * A pre-positioned bar label candidate: the generic {@link PositionedLabelCandidate} box the placement * engine cascades over, plus the bar-specific `anchor` and granular `placement` written back onto the * label node when this candidate wins (its coarse inside/outside is derived from `placement`). */ export interface BarPositionedCandidate extends PositionedLabelCandidate { readonly anchor: OrientationAnchor; readonly placement: TPlacement; /** * Inputs {@link createBarPositionedCandidateResolver} rebuilds this candidate from under the style its * `itemStyler` resolves there, once the cascade reaches it. */ readonly deferred?: BarCandidateDeferred; } /** * Per-label half of a candidate's inputs, shared by every candidate in one label's list. A subset of * {@link buildBarLabelCandidates}'s own argument object, which stands in as the spec so that positioning a * candidate costs no allocation of its own. */ interface BarCandidateSpec { readonly isUpward: boolean; readonly isVertical: boolean; readonly spacing: number; readonly crossReversed?: boolean; readonly rect: Bounds; /** Text size under the configured font, used by a candidate no styler resizes. */ readonly textWidth: number; readonly textHeight: number; /** Source text, re-measured under a candidate's styled font; unset leaves the candidate unstyled. */ readonly text?: NormalisedTextOrSegments; readonly fitted?: boolean; readonly shapeAt?: (anchor: OrientationAnchor) => FitRegion | undefined; } /** * What distinguishes one candidate within its label's {@link BarCandidateSpec}: enough to rebuild its * box under whatever style the `itemStyler` resolves at this placement and orientation. */ export interface BarCandidateDeferred { readonly spec: BarCandidateSpec; readonly placement: BarLabelPlacement; readonly reportedPlacement: TPlacement; readonly orientation: AgChartLabelOrientation; readonly insideRegion: BoxBounds | undefined; readonly region: BoxBounds | undefined; /** Drawn-box extent of the configured style at this placement, used when nothing restyles it. */ readonly placementBox: Required; } /** * Builds the ordered candidate list a bar label cascades through, one entry per * `placement` (outer) × `orientation` (inner) — the ordering that yields inside-horizontal → * inside-vertical → outside-horizontal → outside-vertical for the ticket's example. Inside placements * constrain to the inset bar rect; outside placements float (no region). * * With `fitted` set, each candidate also carries the glyph budget its region offers, so the placement * engine re-fits the text per candidate instead of every candidate inheriting one up-front truncation. * * Every candidate is built on the configured style, and a label with an `itemStyler` also carries the * inputs to rebuild itself under the geometry that styler resolves, which the placement engine reaches for * only as the cascade reaches that candidate (see {@link createBarPositionedCandidateResolver}). A label * the engine never sees must resolve its own candidate through {@link resolveBarLabelCandidate}. */ export declare function buildBarLabelCandidates(args: { isUpward: boolean; isVertical: boolean; placements: readonly BarLabelPlacement[]; /** * Public placement values, index-parallel with `placements`, reported on the winning candidate in * place of the bar vocabulary a series maps onto. */ reportedPlacements?: readonly TPlacement[]; orientations: readonly AgChartLabelOrientation[]; spacing: number; label: Label & { insideStyle: LabelPlacementStyle; outsideStyle: LabelPlacementStyle; }; textWidth: number; textHeight: number; rect: Bounds; /** * Supplies the cross-axis extent of the inside regions, for a `rect` with no thickness of its own * (a divider line), whose inside region would otherwise reject every candidate. Containment along * the rect is unaffected. */ insideCrossRegion?: Bounds; crossReversed?: boolean; rejectOutsideStart?: boolean; rejectOutsideEnd?: boolean; /** A label that may be dropped on overflow (`collision.alwaysShow: false`) rather than always rendered. */ hideable?: boolean; plotRegion?: Bounds; /** Attach the per-candidate fit inputs the engine needs to re-fit the text to each candidate. */ fitted?: boolean; /** * The shape an inside candidate's text is fitted to, for a series whose rect is only the bounding box * of a non-rectangular mark. Each line is then wrapped to the width the shape offers where it lands. */ shapeAt?: (anchor: OrientationAnchor) => FitRegion | undefined; /** Source text, re-measured under a candidate's styled font; unset leaves every candidate unstyled. */ text?: NormalisedTextOrSegments; }): BarPositionedCandidate[]; /** * One candidate rebuilt under the style its `itemStyler` resolves there, for a label that never reaches * the placement engine: its caller holds the only candidate, so nothing else would run its styler. * Unchanged when nothing can restyle it. */ export declare function resolveBarLabelCandidate(candidate: BarPositionedCandidate, styleDatum: SeriesNodeDatum, resolveStyle: BarCandidateStyleResolver | undefined): BarPositionedCandidate; /** * The hook the placement engine resolves a bar label's candidates through — the positioned-candidate * counterpart of {@link createCandidateStyleResolver}, and the only route by which a cascading bar label's * `itemStyler` runs. `paramsFor` takes the label's own node, for the series whose styler params carry that * node's values. */ export declare function createBarPositionedCandidateResolver(series: SeriesLike, label: PlacementStyledLabel, paramsFor: (styleDatum: SeriesNodeDatum) => TParams, labelPath?: string[]): PositionedCandidateResolver | undefined; export {};