import * as echarts from 'echarts'; import { ColorMode, ColorTheme, StateColors } from '@bndynet/color-hub'; interface TitleOptions { text: string; /** Horizontal alignment. Default: 'center' */ align?: 'left' | 'center' | 'right'; /** Font size in px. Default: 16 */ fontSize?: number; /** Vertical whitespace above and below the title text in px. Default: 8 */ padding?: number; } /** * Rich-text style object passed through to ECharts `*.rich.`. * * Exposes common style keys as typed fields while keeping an index signature * so callers can use additional ECharts rich-text properties without waiting * for a library type update. */ interface RichTextStyle { color?: string; fontStyle?: 'normal' | 'italic' | 'oblique'; fontWeight?: string | number; fontFamily?: string; fontSize?: number; lineHeight?: number; overflow?: 'none' | 'truncate' | 'break' | 'breakAll'; lineOverflow?: 'truncate'; ellipsis?: string; width?: number; height?: number; align?: 'left' | 'center' | 'right'; verticalAlign?: 'top' | 'middle' | 'bottom'; padding?: number | number[]; /** Shorthand for ECharts rich-text background image (`backgroundColor: { image }`). */ backgroundImage?: string | Record; backgroundColor?: string | Record; borderColor?: string; borderWidth?: number; borderRadius?: number | number[]; shadowColor?: string; shadowBlur?: number; shadowOffsetX?: number; shadowOffsetY?: number; [key: string]: unknown; } /** * One rich-text fragment. `text` is required; style is optional. * * - `style: string` references a key in `RichTextSpec.styles`. * - `style: RichTextStyle` inlines the style for this segment. * - `width` / `align` / `verticalAlign` are shorthand for the same rich style * fields and merge on top of `style`. */ interface RichTextSegment { text: string; style?: string | RichTextStyle; width?: number; align?: 'left' | 'center' | 'right'; verticalAlign?: 'top' | 'middle' | 'bottom'; } /** * Structured rich-text payload that the library compiles into ECharts' * `{key|text}` formatter string plus matching `rich` style map. */ interface RichTextSpec { segments: RichTextSegment[]; styles?: Record; } /** Formatter input accepted by rich-text aware formatters. */ type RichTextInput = string | RichTextSpec; interface LegendOptions { show?: boolean; position?: 'top' | 'bottom' | 'left' | 'right'; /** * Legend layout height in pixels. * * - `position: 'top' | 'bottom'`: used as the legend slot reserve height * (fallback: `LEGEND_RESERVE`). * - `position: 'left' | 'right'`: forwarded to ECharts but reserve width is * still controlled by `width` (or text measurement when `width` is unset). */ height?: number; /** * Legend layout width in pixels. * * - `position: 'left' | 'right'`: used as the legend slot reserve width * (fallback: measured widest label + non-text budget). * - `position: 'top' | 'bottom'`: forwarded to ECharts; reserve height is * still controlled by `height` (or `LEGEND_RESERVE` when unset). */ width?: number; /** * How the legend behaves when the entries don't fit on a single row/column. * * - `'scroll'` (default): one row/column with paging arrows. Keeps the * layout reserve helpers (`getLegendReserve` / `buildGrid`) accurate * because they assume a single-line slot — extra series add a "›" button, * not extra rows that would overlap the chart body. * - `'plain'`: ECharts' native behavior — wraps onto extra rows/columns * when overflowing. Callers must ensure the legend won't wrap (or bump * `padding` / move the legend to a side edge), otherwise wrapped rows * land on top of the plot area. */ type?: 'scroll' | 'plain'; /** * Customize the text rendered for each legend entry. * * Receives the **series / slice / node / category name** (the same string * the library passes as `legend.data[i]`) plus its zero-based index and * returns the display label. Accepts plain strings OR a structured * {@link RichTextSpec}; the latter is auto-compiled to ECharts rich text * (`{key|text}` + `legend.textStyle.rich`) by `buildLegend`. * * Common use cases: * - append realtime values, units, or status to each entry, e.g. * `(n) => `${n} ${valueByName[n] ?? '—'}` ` * - localize / truncate long names * - return {@link RichTextSpec} segments so callers can align columns / * widths without hand-authoring rich-text keys * - inject raw ECharts rich-text segments (`{key|text}`) manually when * paired with `options.echarts.legend.textStyle.rich` * * Constraints: * - Text-only (string or `RichTextSpec`) — no DOM / HTML. Legend text is * rendered on canvas in ECharts; HTML belongs in `tooltip.customHtml`. * - Newlines (`\n`) in the returned string DO render, but the layout * reserve helpers (`getLegendReserve` / `buildGrid`) assume a single * legend row (LEGEND_RESERVE = 36 px). Multi-line labels will overlap * the chart body — increase `padding` or move the legend to a side * edge in that case. * - Side-edge legends (`position: 'left' | 'right'`) automatically * re-measure with the **formatted** label width so long values don't * bleed into the chart body. * - When `formatLabel` throws, the entry falls back to the raw name * (so a single bad lookup can't blank out the entire legend). */ formatLabel?: (name: string, index: number) => RichTextInput; } interface GridOptions { /** Whether to render the grid background/border. Default: true (race defaults to false). */ show?: boolean; top?: number; right?: number; bottom?: number; left?: number; } interface AxisOptions { /** * Whether to render this axis. Default: true. */ show?: boolean; name?: string; /** * Customize the text rendered for each axis tick label. * * Receives the **tick value** (category name for `type: 'category'`, raw * numeric value for `type: 'value'`, ms timestamp for `type: 'time'`) plus * its zero-based index and returns the display label. Accepts plain * strings OR a structured {@link RichTextSpec}; the latter is auto- * compiled to ECharts rich text (`{key|text}` + `axisLabel.rich`) by * `buildXAxis` / `buildYAxis`. * * Common use cases: * - inject icons / images next to category names (flags, avatars, * status pills) via `RichTextStyle.backgroundImage` * - localize / abbreviate / truncate long values * - return raw rich-text segments (`{key|text}`) when paired with * `options.echarts.[xy]Axis.axisLabel.rich` * * **RichText support is currently limited to category axes** (vertical * bar / line / area x-axis with named `data.categories`, horizontal-bar * and bar-race y-axis). Value and time axes pick their tick values at * runtime, so we cannot pre-register the `axisLabel.rich` style map * upfront — `RichTextSpec` returns are rendered as plain concatenated * text in those cases (segment styles are dropped). Plain string returns * always work everywhere. * * Constraints: * - Text-only (string or `RichTextSpec`) — no DOM / HTML. Axis labels * are rendered on canvas; HTML belongs in `tooltip.customHtml`. * - When `formatLabel` throws, the entry falls back to the raw value * (so a single bad lookup can't blank out the whole axis). */ formatLabel?: (value: string | number, index: number) => RichTextInput; /** * Date/time format for time-axis tick labels. * Uses dayjs / Moment.js compatible tokens: * YYYY year (4-digit), YY year (2-digit), * MM month, DD day, HH hour (24 h), mm minute, ss second * Example: 'YYYY-MM-DD', 'MM/DD', 'HH:mm', 'MM-DD HH:mm' */ dateFormat?: string; /** * Date/time format for the axis-pointer cursor label (the callout shown on * the x-axis while hovering). Uses the same dayjs tokens as `dateFormat`. * Falls back to `dateFormat` when omitted. * Example: 'YYYY-MM-DD HH:mm' (more detail than the tick labels) */ cursorFormat?: string; /** * Pin the axis lower bound. Accepts a number, a date-parseable string, or * one of ECharts' magic strings (`'dataMin'`, `'dataMax'`). * * Mainly useful for the `race` variant to lock the visible domain so the * line doesn't "compress" each frame as new categories arrive. Ignored by * category axes — those derive their domain from `data`. */ min?: number | string; /** Pin the axis upper bound. See {@link AxisOptions.min}. */ max?: number | string; } interface SeriesOptions { type?: 'line' | 'bar'; smooth?: boolean | number; lineWidth?: number; lineStyle?: 'solid' | 'dashed' | 'dotted'; showLabel?: boolean; labelPosition?: 'inside' | 'outside' | 'center'; showPoints?: boolean; yAxisIndex?: number; markLines?: ('average' | 'max' | 'min')[]; markPoints?: ('max' | 'min')[]; } /** * Normalized context for {@link TooltipOptions.customHtml} across chart types. * Narrow with `ctx.kind` (`'axis'` | `'item'` | `'edge'`). */ interface TooltipContextAxis { kind: 'axis'; /** * Same label shown in the tooltip header — formatted with `dateFormat` when * the x-axis is time-based and `dateFormat` is set; otherwise the raw category. */ axisValueLabel: string; /** Index along the x-axis for this hover. */ dataIndex: number; /** Raw axis value (timestamp number, ISO string, etc.). */ rawAxisValue: string | number | undefined; /** One entry per series at this axis position. */ series: Array<{ name: string; value: number | string; marker?: string; /** * Resolved hex/rgb color of this series at the current axis position — * the same value ECharts uses for the marker swatch. `undefined` only * when the underlying ECharts `params` entry didn't expose a color * (rare; mostly a defensive guard for custom adapters). */ color?: string; }>; } /** Pie slice, Sankey / Chord / Network / Tree node, word-cloud word, and other single-item hovers. */ interface TooltipContextItem { kind: 'item'; dataIndex: number; name: string; value: number | string; /** Pie: ECharts percent of total. */ percent?: number; marker?: string; /** * Resolved hex/rgb color of this item, taken from the chart's resolved * palette (respects `options.colors` / `options.colorMap` / `node.color` / * theme palette). `undefined` only when the chart type does not expose a * stable per-item color (e.g. gauge) or for custom adapters that haven't * threaded a `nameToColor` map into `sankeyChordParamsToTooltipContext`. */ color?: string; } /** Sankey, Chord, or Network link / edge hover. */ interface TooltipContextEdge { kind: 'edge'; dataIndex: number; source: string; target: string; value: number | string; /** * Resolved color of the link's **source node**, looked up via the chart's * `name → color` map (NOT `params.color` — ECharts reports the literal * string `"gradient"` for sankey/chord links when the series uses a * source→target gradient, which is the default). * * Edge cases: * - Per-link `lineStyle.color` overrides on `data.links[i]` are not * reflected here; this field always reports the source node's color. * - Network with categories: the category color the source node * belongs to. Without categories: the per-node color from the * resolved palette / `node.color`. * - `undefined` for custom adapters that haven't threaded a * `nameToColor` map into `sankeyChordParamsToTooltipContext`. */ sourceColor?: string; /** See {@link sourceColor}. Resolved color of the link's **target node**. */ targetColor?: string; } type TooltipContext = TooltipContextAxis | TooltipContextItem | TooltipContextEdge; /** Mouse interaction kinds forwarded to {@link ChartEventHandlers}. */ type ChartEventType = 'click' | 'dblclick' | 'mouseover' | 'mouseout'; /** * Normalized payload passed to every {@link ChartEventHandlers} callback. * * The interaction layer mirrors the tooltip layer: `data` reuses the same * `TooltipContextItem` / `TooltipContextEdge` normalization, so a click on a * pie slice, a sankey node, or a sankey link surfaces the same shape your * `tooltip.customHtml` already receives. Axis-trigger has no click equivalent * in ECharts (clicks always land on a single data item), so `data` is never * the `'axis'` kind. * * `data` is `undefined` when the interaction didn't hit a data item — e.g. a * click on empty canvas, the legend, or a title. Use `componentType` / * `seriesType` to disambiguate, and `raw` for anything the normalized shape * doesn't cover. */ interface ChartEventContext { /** Which handler fired this context. */ type: ChartEventType; /** * Normalized data context, reusing the tooltip item/edge shapes. `undefined` * when the hit wasn't on a series data item (empty canvas, legend, title, …). */ data?: TooltipContextItem | TooltipContextEdge; /** ECharts `params.componentType` — e.g. `'series'`, `'markPoint'`, `'title'`. */ componentType?: string; /** ECharts `params.seriesType` — e.g. `'line'`, `'pie'`, `'sankey'`. */ seriesType?: string; /** ECharts `params.seriesIndex`, when the hit was on a series. */ seriesIndex?: number; /** * Raw ECharts event params — the escape hatch for fields the normalized * shape doesn't expose. Shape varies by `componentType`; treat as unknown * and narrow as needed. */ raw: unknown; } /** A single chart interaction handler. */ type ChartEventHandler = (ctx: ChartEventContext) => void; /** * Typed mouse-event handlers, set via `ChartOptions.events`. Each maps to the * ECharts event of the same intent and receives a normalized * {@link ChartEventContext}. The engine binds/unbinds these on the underlying * instance across every render (and clears them on `dispose()`), so handlers * stay current with `update({ events })` without stacking listeners. * * For events not covered here (e.g. `legendselectchanged`, `datazoom`), reach * for `getEChartsInstance().on(...)` directly. */ interface ChartEventHandlers { /** Fired on a single click. Maps to ECharts `'click'`. */ onClick?: ChartEventHandler; /** Fired on a double click. Maps to ECharts `'dblclick'`. */ onDoubleClick?: ChartEventHandler; /** Fired when the pointer enters an element. Maps to ECharts `'mouseover'`. */ onMouseOver?: ChartEventHandler; /** Fired when the pointer leaves an element. Maps to ECharts `'mouseout'`. */ onMouseOut?: ChartEventHandler; } /** * Options for `createAsyncTooltipFormatter` — chart-agnostic async tooltip * built on ECharts’ `(params, ticket, callback)` protocol. */ interface CreateAsyncTooltipFormatterOptions { /** Synchronous tooltip body from raw ECharts `params` (axis array or single item). */ formatSync: (params: unknown) => string; /** * Returns extra HTML appended below `formatSync` output (after a separator). * Receives the same `params` as ECharts passed to the formatter. */ customHtml: (params: unknown) => Promise; /** * Shown while `customHtml` is pending. Plain text; HTML special characters are escaped. * @default 'Loading…' */ placeholder?: string; /** * Identity used to cache resolved tooltip HTML and dedupe concurrent * `customHtml` loads. ECharts re-invokes the formatter on every mouse * move — without caching, hovering inside a single slice / node / axis * column reruns `customHtml` continuously and the user sees a repeating * "loading" flicker. * * - Default extractor: keys axis-trigger payloads by `axisValue` and * item / edge payloads by `(dataType, seriesIndex, dataIndex, name)`. * This covers every built-in chart type that wires * {@link TooltipOptions.customHtml}. * - Pass a function to customize (e.g. include extra fields the default * key elides, or coarsen across series). * - Pass `false` to disable caching entirely — every formatter invocation * will re-fire `customHtml`. Use only when the async result genuinely * varies across mouse moves for the same data point. * * The cache lives for the lifetime of the formatter closure, which is * recreated on every `setOption` resolve in the adapter pipeline. Stale * data thus self-clears on chart updates. */ cacheKey?: ((params: unknown) => string) | false; } interface TooltipOptions { enabled?: boolean; formatValue?: (value: number | string, name: string) => string; /** * Date/time format for the tooltip header when using a time x-axis. * Uses dayjs / Moment.js compatible tokens: * YYYY year (4-digit), YY year (2-digit), * MM month, DD day, HH hour (24 h), mm minute, ss second * Example: 'YYYY-MM-DD', 'YYYY-MM-DD HH:mm' * If omitted, ECharts auto-selects a format based on data granularity. */ dateFormat?: string; /** * Replace the chart's default synchronous tooltip body with custom * asynchronously loaded HTML. The user-supplied function fully owns the * tooltip body — the built-in name / value / percent / axis row is **not** * rendered when this hook is set. Use {@link appendHtml} instead when you * want to keep the default body and add extras below it. * * Receives a normalized {@link TooltipContext} — use `ctx.kind` to * distinguish axis (`line` / `bar` / `area`), item (`pie`, node in `sankey` / * `chord` / `network` / `tree`, word in `word-cloud`), or edge (link in * `sankey` / `chord` / `network`). * * Can be combined with {@link appendHtml}: `customHtml` provides the body, * `appendHtml` is rendered below it with a thin separator. * * Not applied to spark charts or when `tooltip.enabled` is false. If * `echarts.tooltip.formatter` is merged later and replaces `formatter`, this * hook has no effect. */ customHtml?: (ctx: TooltipContext) => Promise; /** * Append asynchronously loaded HTML below the synchronous tooltip body. * * The "synchronous body" is whichever of these the chart resolves to: * - the chart's built-in default sync row (when {@link customHtml} is * not set), or * - the HTML returned by {@link customHtml} (when it is set). * * `appendHtml`'s output is rendered below that body, separated by a thin * dashed rule. This is the option to reach for when you want to keep the * default tooltip layout and just **add extras** (latency, owner, last- * updated timestamp, …) — unlike {@link customHtml} which fully replaces * the default body. * * Receives the same normalized {@link TooltipContext} as `customHtml` and * shares the same {@link placeholder} while pending. * * Not applied to spark charts or when `tooltip.enabled` is false. If * `echarts.tooltip.formatter` is merged later and replaces `formatter`, * this hook has no effect. */ appendHtml?: (ctx: TooltipContext) => Promise; /** * Shown while `customHtml` / `appendHtml` is pending. * @default 'Loading…' */ placeholder?: string; /** * Pixel gap between the cursor (or anchored data point) and the * nearest edge of the tooltip box. * * **Defaults (when omitted):** * - `variant: 'spark'` (line / area / bar) → 6 px — small enough * for a 96×48 KPI card to feel "next to the cursor" rather * than "in another zip code". * - All other charts → ECharts' built-in 20 px (hardcoded in * `refixTooltipPosition` inside ECharts' `TooltipView`). We do * not override it so charts you've already styled keep the * exact spacing they had. * * **When to set:** override either default when the chart's pixel * dimensions are unusual (e.g. very wide hero charts where 6 px * looks crowded, or non-spark charts on a small card where 20 px * is still too much). `0` is meaningful — tooltip sits right at * the cursor. * * **Implementation note:** the library translates this into a * `tooltip.position` callback that mirrors ECharts' built-in * edge-flip logic (tooltip flips to the opposite side of the cursor * when it would overflow the chart viewport), only with your `gap` * substituted for the hardcoded 20. Setting * `options.echarts.tooltip.position` (passthrough) still wins via * the final `deepMerge` if you need full custom positioning. */ cursorGap?: number; /** * Attach the tooltip DOM to `` instead of the chart container, * so it can escape ancestors with `overflow: hidden` (common with * card / KPI / dialog containers). * * **Defaults:** * - Light DOM containers (e.g. `createChart(divEl, ...)`): `true` — * the tooltip can render anywhere on screen without being clipped * by a parent card. * - Shadow DOM containers (e.g. the `` web component): * `false` — keeping the tooltip inside the shadow root preserves * the component's style encapsulation and keeps the tooltip in the * same stacking context as the host element. * * Pass an explicit `true` / `false` to override the auto-detected * default. Most consumers should leave this unset. * * Edge cases where you may want to override: * - `` rendered inside a Vue `` / portal where * you need the tooltip in `` regardless of shadow — set * `appendToBody: true`. * - Light-DOM chart inside a stacking-context that you want the * tooltip to stay glued to (e.g. some custom popper logic) — set * `appendToBody: false`. */ appendToBody?: boolean; } declare enum ChartType { Line = "line", Bar = "bar", Area = "area", Map = "map", Pie = "pie", Gauge = "gauge", LiquidProgress = "liquidprogress", Sankey = "sankey", Chord = "chord", Radar = "radar", Network = "network", Tree = "tree", Treemap = "treemap", WordCloud = "wordcloud" } /** * Cross-cutting options shared by every chart type. * * Only fields actually consulted by every built-in adapter live here. Anything * chart-specific (axes, stacking, variants, slice/gauge/race namespaces, * legend/grid for charts that don't render them) belongs on the per-chart * `XxxChartOptions` subtype. * * See [AGENTS.md](../../AGENTS.md) for the rule and rationale. */ interface ChartOptions { theme?: string; /** Chart title. Pass a plain string as shorthand or a TitleOptions object for full control. */ title?: string | TitleOptions; /** * Outer whitespace (px) between chart content and all canvas edges. * Applies to title, legend, and the plot area. Default: 12. */ padding?: number; colors?: string[]; colorMap?: Record; /** * Global font size (px) for data labels and edge labels across every * chart type that renders them. * * **Applies to:** * - line / bar / area `series.label` (when `showLabel: true`) and * line-race `endLabel` * - pie slice labels * - sankey / chord / tree node labels (parent + leaf for tree) * - network node `series.label` and `series.edgeLabel` * * **Does NOT apply to:** * - gauge `title` / `detail` inner text (container-auto-sized by the * gauge `percentage` variant) * - radar `axisName` (indicator labels, not data labels) * - `markPoint.label` * * Default: 12. */ labelFontSize?: number; tooltip?: TooltipOptions; /** * Typed mouse-interaction handlers (`onClick` / `onDoubleClick` / * `onMouseOver` / `onMouseOut`). Each receives a normalized * {@link ChartEventHandlers} `ChartEventContext` — the same item/edge shape * the tooltip hooks get — instead of raw ECharts `params`. The engine binds * them on the live instance and keeps them in sync across `update()`. */ events?: ChartEventHandlers; /** Raw ECharts options merged last — escape hatch for advanced users */ echarts?: Record; } type LineVariant = 'default' | 'spark' | 'race'; /** * Line, bar, and area share the same runtime shape ({@link XYData}). Each * chart still gets its own named alias so adapters and call sites can declare * intent explicitly, matching the per-chart `*ChartOptions` convention. */ type LineData = XYData; /** * Options for the line `race` variant. * * Kept as a separate named type — not flattened — because these fields only * apply when `variant === 'race'`. * * The library renders a single frame per call; the consumer drives the * animation by calling `chart.update(nextFrame)` on their own interval. * Each frame is a regular {@link LineData} whose `series` names must stay * stable across frames (ECharts diffs series by name to animate the * transition). Each frame typically extends `categories` and each series * `data` by one more point, but any monotonically-growing shape works. */ interface LineRaceOptions { /** * Transition duration (ms) between consecutive frames. * * Leave unset — the library auto-measures the interval between your * `chart.update(frame)` calls and uses that as the animation duration * (clamped to [80, 3000] ms). Pass an explicit value only to override * the measured cadence (e.g. to deliberately slow down a fast stream * for readability). * * Default for the very first transition (no prior tick to measure): 500ms. */ frameDuration?: number; /** * Show an animated end-of-line label that tracks each series's latest * value (uses ECharts `series.endLabel` + `valueAnimation`). * Default: true. */ showValueLabel?: boolean; } interface LineChartOptions extends XYChartOptions { variant?: LineVariant; /** * Line `race` variant options. Only consulted when `variant === 'race'`. * Kept as a sub-object because the fields are variant-specific. * See {@link LineRaceOptions}. */ race?: LineRaceOptions; } type BarVariant = 'default' | 'horizontal' | 'spark' | 'race'; type BarData = XYData; /** * Options for the bar `race` variant. * * Kept as a separate named type — not flattened — because these fields only * apply when `variant === 'race'`. Bar chart's own general options (sizing, * `colorByCategory`) live flat on {@link BarChartOptions}. * * The library renders a single frame per call; the consumer drives the * animation by calling `chart.update(nextFrame)` on their own interval. * Each frame is a regular {@link BarData} whose `categories` (racer names) * must stay stable across frames — only `series[0].data` (values) changes. */ interface BarRaceOptions { /** * Show only the top N racers. Maps to `yAxis.max = topN - 1`. * Omit to show every racer in the dataset. */ topN?: number; /** * Transition duration (ms) between consecutive frames. * * Leave unset — the library auto-measures the interval between your * `chart.update(frame)` calls and uses that as the animation duration * (clamped to [80, 3000] ms). Pass an explicit value only to override * the measured cadence (e.g. to deliberately slow down a fast stream * for readability). * * Default for the very first transition (no prior tick to measure): 500ms. */ frameDuration?: number; /** * Show an animated value label at the end of each bar. Default: true. */ showValueLabel?: boolean; } interface BarChartOptions extends XYChartOptions { variant?: BarVariant; /** Bar thickness, e.g. `24` or `'60%'`. Maps to ECharts `series.barWidth`. */ barWidth?: number | string; /** Cap on bar thickness. Maps to ECharts `series.barMaxWidth`. */ barMaxWidth?: number | string; /** Floor on bar thickness. Maps to ECharts `series.barMinWidth`. */ barMinWidth?: number | string; /** * Gap between bars of different series at the same category, e.g. `'30%'` * or a px number. Negative values overlap. Maps to ECharts `series.barGap`. */ barGap?: number | string; /** * Gap between bar groups at adjacent categories, e.g. `'20%'`. * Maps to ECharts `series.barCategoryGap`. */ barCategoryGap?: number | string; /** * Color each bar by its category name (or per racer in the `race` variant) * using the same name → color pipeline as series colors (`colorMap` → theme * palette → `consistentColors`). Default: `false`. * * When `true`, the legend is forced hidden because the legend marker would * keep showing the series's default color and conflict with the per-bar * coloring shown in the plot. * * Best suited for single-series bar charts and the `race` variant. Silently * ignored when `stacked: true` or when the chart has more than one series. */ colorByCategory?: boolean; /** * Bar `race` variant options. Only consulted when `variant === 'race'`. * Kept as a sub-object because the fields are variant-specific. * See {@link BarRaceOptions}. */ race?: BarRaceOptions; } type AreaVariant = 'default' | 'spark'; type AreaData = XYData; interface AreaChartOptions extends XYChartOptions { variant?: AreaVariant; } interface MapDataItem { /** Region name, matched against the registered map feature name. */ name: string; /** Scalar value used by visualMap and tooltip display. */ value: number; /** * Per-region fixed color override. * When provided, wins over palette / colorMap resolution. */ color?: string; } /** * Map data set — one value per named region. */ type MapData = MapDataItem[]; /** * Structural type guard for map data. */ declare function isMapData(data: ChartData): data is MapData; interface MapVisualMapOptions { /** Show visualMap component. */ show?: boolean; /** Explicit minimum for the visual domain. Default: data minimum. */ min?: number; /** Explicit maximum for the visual domain. Default: data maximum. */ max?: number; /** visualMap orientation. Default: `'vertical'`. */ orient?: 'horizontal' | 'vertical'; /** visualMap x-position. Default: `left: 'right'`. */ left?: string | number; /** visualMap y-position. Default: `bottom: 12`. */ top?: string | number; /** visualMap y-position (alternative to `top`). */ bottom?: string | number; /** Number formatter for visualMap labels. */ formatter?: string | ((value: number) => string); /** Piecewise visualMap bins. Default is continuous mode. */ pieces?: Array<{ min?: number; max?: number; label?: string; color?: string; }>; /** Precision used by continuous visualMap labels. */ precision?: number; /** * Text labels shown at the two ends of the color bar (max end, min end). * Default: auto-formatted min/max values are shown. * Set to `[null, null]` or supply an explicit pair to override. */ text?: [string | null, string | null]; /** * Explicit color ramp for continuous visualMap. * * Default (when omitted): * - two-stop ramp based on the library's normal map color resolution * (`resolveColors`): * - low stop: base color blended over theme `surface` at 20% * - high stop: base color (100%) */ inRangeColors?: string[]; } interface MapChartOptions extends ChartOptions { /** * Registered map resource name (must be pre-registered via `registerMap`). */ mapName: string; /** * Name field in GeoJSON feature properties. Default: `'name'`. */ nameProperty?: string; /** Show region labels. Default: `false`. */ showLabel?: boolean; /** * Automatically hide region labels that cannot fit inside their region box. * * When enabled, the adapter wires a `labelLayout` callback that compares * ECharts' computed region rect and label rect, then hides labels whose * width/height exceed the region bounds. It also hides labels for regions * without a usable numeric value (`NaN` / missing). * * Default: `false`. */ autoHideOverflowLabel?: boolean; /** * Map interaction mode. * - `false` (default): static map * - `true`: pan + zoom * - `'move'`: pan only * - `'scale'`: zoom only */ roam?: boolean | 'move' | 'scale'; /** * Center point `[lng, lat]`. * Requires map data with a geographic coordinate system. */ center?: [number, number]; /** * Initial zoom level. Default follows ECharts map series default. */ zoom?: number; /** * visualMap config for choropleth coloring. * When omitted, the adapter auto-enables visualMap if map data has numeric values. * Set `visualMap: { show: false }` for a plain single-color map. */ visualMap?: MapVisualMapOptions; } type PieVariant = 'default' | 'doughnut' | 'half-doughnut' | 'nightingale'; interface PieDataItem { name: string; value: number; } type PieData = PieDataItem[]; type PieCenterLabel = string | RichTextSpec; interface PieChartOptions extends ChartOptions { variant?: PieVariant; innerRadius?: string | number; outerRadius?: string | number; /** When false, slices keep their data order; otherwise sorted by value desc. Default: true. */ autoSort?: boolean; /** * Whether to render the outside slice labels (`{name}: {percent}%` * leader-line text). Defaults to `!showLegend` — when the legend is * shown it already names every slice and the outside labels would * duplicate it (and steal radius headroom). Set to `true` to force * them alongside a legend, or to `false` to hide them even when the * legend is hidden. */ showSliceLabel?: boolean; /** Border radius of every slice in px. Maps to ECharts `series.itemStyle.borderRadius`. */ sliceBorderRadius?: number; /** Border color of every slice. Maps to ECharts `series.itemStyle.borderColor`. */ sliceBorderColor?: string; /** Gap between adjacent slices in degrees. Maps to ECharts `series.padAngle`. */ sliceGap?: number; /** * Pie is the only non-XY chart that renders a legend, so the field lives * here rather than on the base {@link ChartOptions}. Gauge / sankey / chord * deliberately do not expose `legend`. */ legend?: LegendOptions; /** Center labels for doughnut / pie variants (multi-line). */ centerLabels?: PieCenterLabel[]; /** * Pixel offset applied to center labels after layout: [x, y]. * Useful for fine-grained visual calibration. */ centerLabelOffset?: [number, number]; } type GaugeVariant = 'default' | 'percentage'; interface GaugeData { value: number; max?: number; label?: string; } interface GaugeChartOptions extends ChartOptions { variant?: GaugeVariant; /** Arc thickness in px. Default: 18 (default variant) / 20 (percentage variant). */ gaugeWidth?: number; } type LiquidProgressVariant = 'default'; type LiquidProgressData = GaugeData; /** * Liquid progress data shape. * * Note: this is structurally identical to `GaugeData`. * The chart `type` string selects the adapter. */ declare function isLiquidProgressData(data: ChartData): data is LiquidProgressData; /** * Merge a partial liquid-progress update into the previous frame. * * - `value` is always taken from `patch`. * - `max` / `label` are kept from `prev` unless the key is present on * `patch` (including explicit empty values — `label: ''` clears the * caption; `max: undefined` drops a prior custom max so the adapter * falls back to its default). */ declare function mergeLiquidProgressData(prev: LiquidProgressData, patch: LiquidProgressData): LiquidProgressData; interface LiquidProgressChartOptions extends ChartOptions { variant?: LiquidProgressVariant; /** * Radius of the liquid container. * Accepts ECharts percent or px number. Default: `'70%'`. */ radius?: string | number; /** * Wave count for the liquid fill. * Default: `3`. */ waveCount?: number; /** * Border thickness around the liquid container in px. * Default: `2`. */ borderWidth?: number; } type SankeyVariant = 'default' | 'vertical'; interface SankeyNode { name: string; /** Optional fixed color for this specific node */ color?: string; } interface SankeyLink { source: string; target: string; value: number; } interface SankeyData { nodes: SankeyNode[]; links: SankeyLink[]; } declare function isSankeyData(data: ChartData): data is SankeyData; interface SankeyChartOptions extends ChartOptions { variant?: SankeyVariant; } interface ChordNode { name: string; /** Optional fixed color for this node's arc and outgoing ribbons */ color?: string; /** * Relative weight of the node arc. * When omitted the arc size is derived from the sum of connected link values. */ value?: number; } interface ChordLink { source: string; target: string; value: number; } interface ChordData { nodes: ChordNode[]; links: ChordLink[]; } /** * ChordData and SankeyData share the same runtime shape ({ nodes, links }). * The chart type — not the data shape — determines which adapter is used. * This guard validates the structural contract for ChordData. */ declare function isChordData(data: ChartData): data is ChordData; /** * Chord-chart-specific options. * * No chord-specific knobs today (chord has no variants and no extra fields * beyond {@link ChartOptions}); kept as a named subtype so consumers and * adapters can express intent and we have a stable home for future * chord-only knobs. */ type ChordChartOptions = ChartOptions; type RadarVariant = 'default' | 'circle'; /** * One axis of the radar chart. ECharts calls this an "indicator". * * `max` / `min` are optional — when omitted ECharts auto-scales each axis to * the data range. Provide them when you want consistent scales across * indicators (e.g. when comparing different metrics on the same chart). */ interface RadarIndicator { name: string; max?: number; min?: number; } /** * One polygon drawn on the radar. `values[i]` is plotted on * `indicators[i]` — so the two arrays must line up by index and length. */ interface RadarDataSeries { name: string; values: number[]; } interface RadarData { indicators: RadarIndicator[]; series: RadarDataSeries[]; } declare function isRadarData(data: ChartData): data is RadarData; interface RadarChartOptions extends ChartOptions { variant?: RadarVariant; /** Fill the polygon area for every series. Default: true. */ filled?: boolean; /** Radar radius — same syntax as ECharts `radar.radius`. Default: '65%'. */ radius?: string | number; /** * Radar is a non-XY chart that benefits from a legend (one entry per * polygon), so the field lives on this subtype rather than the base * {@link ChartOptions}. */ legend?: LegendOptions; } /** * Layout strategies for the network chart. * * - `default`: physics-based force-directed layout. Nodes repel each other * while edges pull them together; the simulator runs until it stabilises. * Good for "show me the structure" of an unknown graph. Internally this * maps to ECharts' `graph.layout: 'force'`. When nodes carry `x` / `y` * they are used as initial positions; combine with `fixed: true` to lock * specific nodes in place. * - `circular`: nodes are placed evenly around a circle in input order. * Predictable layout for chord-style adjacency without needing per-node * coordinates. Maps to ECharts' `graph.layout: 'circular'`. * * Need fully manual node positions (no algorithm at all)? Drop down to * the `echarts` escape hatch: `options.echarts = { series: [{ layout: 'none' }] }` * and provide `x` / `y` on every node. */ type NetworkVariant = 'default' | 'circular'; interface NetworkNode { name: string; /** * Optional category name. Categories drive the legend and the default * per-node color (every node in the same category gets the same palette * slot). Unknown category names — names not present in `data.categories` * (or, when omitted, not seen elsewhere in `nodes`) — are dropped. */ category?: string; /** * Numeric value associated with the node. When `size` is omitted the * adapter scales the rendered marker size from the value range. */ value?: number; /** * Per-node fixed color override. Wins over the category palette and any * `colorMap` / `colors` resolution. */ color?: string; /** * Per-node marker size override (px). When omitted the adapter derives a * size from `value` (scaled into {@link NetworkChartOptions.nodeSizeRange}) * and falls back to {@link NetworkChartOptions.nodeSize} for nodes * without a value. */ size?: number; /** * Optional initial layout coordinate (px on the chart canvas). Used as the * starting position by the `default` (force) layout; ignored by `circular` * which derives positions from input order. */ x?: number; y?: number; /** * When true, keep the node at `x` / `y` during the force simulation * (i.e. it acts as an anchor while other nodes settle around it). * Ignored by `circular`. */ fixed?: boolean; } interface NetworkLink { source: string; target: string; /** Optional weight. Surfaced in tooltips; force layout ignores it today. */ value?: number; /** * Per-link curveness override. Same scale as chart-wide * {@link NetworkChartOptions.edgeCurveness} (`0` = straight, `0.3` ≈ * gently curved). Negative values bend the line the *other* way, which * is the canonical fix for **bidirectional edges**: give `A → B` a * positive curveness and `B → A` an equal negative one so the two * arcs don't overlap. * * Wins over the chart-level default (and over any explicit * `options.edgeCurveness`). Omit to inherit the chart-wide value. */ curveness?: number; } interface NetworkData { nodes: NetworkNode[]; links: NetworkLink[]; /** * Explicit category list — drives legend ordering. When omitted, derived * from unique `node.category` values in input order. */ categories?: string[]; } /** * NetworkData shares the `{ nodes, links }` runtime shape with SankeyData and * ChordData. The chart `type` string — not the data shape — selects the * adapter; this guard validates the structural contract for NetworkData. */ declare function isNetworkData(data: ChartData): data is NetworkData; interface NetworkChartOptions extends ChartOptions { variant?: NetworkVariant; /** Enable wheel zoom. Default: `false`. */ enableZoom?: boolean; /** Enable drag-to-pan. Default: `true`. */ enablePan?: boolean; /** Allow dragging individual nodes. Default: true for `default` (force), false for `circular`. */ draggable?: boolean; /** Show node name labels. Default: true. */ showNodeLabel?: boolean; /** Show edge labels (rendered from `link.value`). Default: false. */ showLinkLabel?: boolean; /** * Force every node + edge label to render, bypassing **all** of the * adapter's label-pruning strategies in one shot: * 1. `labelLayout: { hideOverlap: true }` (the default overlap * culling that keeps dense force layouts readable). * 2. The {@link labelMinNodeSize} size threshold that hides labels * on small markers. * * Default `false` keeps both strategies active — labels at the edges * of a cluster show, the ones inside / on tiny nodes collapse first. * This trades completeness for legibility and is usually the right * answer. * * Set `true` when every node name matters (small graphs, screenshots, * presentations) and you'd rather accept overlap than hide a name. * * Has no effect when {@link showNodeLabel} is `false` (no labels to show). */ showAllLabels?: boolean; /** * Resolved marker size (px) strictly below this value hides that * single node's label. Lets you scale nodes by `value` without the * smallest dots drowning under their own captions — a common * complaint on circular layouts where `labelLayout: { hideOverlap }` * does nothing (radial labels rarely physically overlap). * * Default: `14`. Tuned for the default {@link nodeSizeRange} of * `[10, 30]`: nodes whose `value` lands in the bottom ~20 % of the * range scale into the `< 14 px` bucket and lose their label, while * mid- and large-value nodes keep theirs. Set `0` to disable the * threshold entirely (pre-v6.2 behavior). Raise (e.g. `18`) for more * aggressive pruning. * * Compared against the **resolved** per-node size — whichever path * produced it: explicit `node.size`, value-scaled into * {@link nodeSizeRange}, or the container-aware no-value fallback. * * Interaction with the other label flags: * - {@link showNodeLabel} `false` wins → no labels render; threshold * becomes meaningless. * - {@link showAllLabels} `true` wins → threshold is skipped along * with overlap culling; every label renders. */ labelMinNodeSize?: number; /** * Fallback marker size (px) for nodes that have no `value` and no * per-node `size`. When omitted the adapter auto-sizes from the * rendered container — `min(width, height) / sqrt(nodeCount) * 0.10`, * clamped to `[8, 40]` — so sparse graphs in big cards get noticeable * markers and dense graphs shrink markers automatically. SSR / hidden * containers (no usable dims) fall back to 10 px. Pass an explicit * number to disable auto-sizing. */ nodeSize?: number; /** * Min / max marker size (px) used when scaling nodes by `value`. * Default: `[10, 30]`. */ nodeSizeRange?: [number, number]; /** Force-layout repulsion strength. Default: 100. */ repulsion?: number; /** * Force-layout edge length (px) — the spring's "rest length" between * connected nodes. When omitted the adapter auto-sizes from the * **body** (the area outside the title, legend, and padding reserves): * `min(bodyWidth, bodyHeight) / sqrt(nodeCount) * 0.6`, clamped to * `[30, 250]`. This means the cluster fills the available body * automatically: a tall title or a side legend that shrinks the body * also shrinks the springs, so the layout stays inside the reserved * area instead of bleeding into the title bar. The 0.6 multiplier is * calibrated so a 16-node graph with a body of ~400 px (the canonical * forceData demo body, after subtracting title + 5-cat top legend + * padding) lands exactly on 60 px — the pre-auto-sizing static * default — so existing demos render unchanged. SSR / hidden * containers (no usable dims) fall back to 60 px. Pass an explicit * number to disable auto-sizing. Only consulted when * `variant === 'default'`. */ edgeLength?: number; /** Force-layout gravity (pull toward center). Default: 0.1. */ gravity?: number; /** * Edge curveness (0 = straight, 0.3 = gently curved). * * Default depends on the variant: * - `default` (force) → `0` — physics already separates edges nicely. * - `circular` → `0.3` — straight edges through a circular layout * converge at the center and become unreadable; matches ECharts' * own circular-layout example. * * Pass an explicit value to override either default. */ edgeCurveness?: number; /** * Network renders a category legend (one entry per category). Lives on * this subtype — base {@link ChartOptions} stays legend-free. */ legend?: LegendOptions; } /** * Growth direction of the tree. * * - `'LR'` (default) — root on the left, leaves expand to the right. * - `'RL'` — root on the right, leaves expand to the left. * - `'TB'` — root on top, leaves expand downward. * - `'BT'` — root on bottom, leaves expand upward. * * The adapter flips the node-label position so labels always point away * from the tree body (parents toward the root edge, leaves toward the * opposite edge), and for vertical directions (`'TB'` / `'BT'`) rotates * labels 90° so the reading direction tracks the tree's growth. */ type TreeDirection = 'LR' | 'RL' | 'TB' | 'BT'; /** * A single node in the tree. Minimal by design — the only required field * is `name`. Internal nodes carry `children`; leaves omit it. `value` is * optional metadata surfaced in tooltips (the tree adapter does not size * nodes by value). */ interface TreeNode { /** Display name; also serves as the color-lookup key. */ name: string; /** Optional numeric metadata. Shown in tooltips, not used for sizing. */ value?: number; /** Child nodes. Omit (or pass an empty array) for a leaf. */ children?: TreeNode[]; /** * Per-node fixed color override. Wins over `colorMap` / `colors` / * theme palette resolution. Applied via `itemStyle.color` during the * tree walk so the override survives any `options.echarts` deep-merge. */ color?: string; /** * When `true`, the sub-tree below this node renders collapsed; the user * can click to expand. Default: `false` (expanded). Combine with * `options.initialTreeDepth` for depth-based collapsing across the * whole tree. */ collapsed?: boolean; } /** * The tree's root node. Tree data is just a single {@link TreeNode}. * * Example: * ```ts * const data: TreeData = { * name: 'root', * children: [ * { name: 'A', children: [{ name: 'A1' }, { name: 'A2' }] }, * { name: 'B', value: 42 }, * ], * }; * ``` */ type TreeData = TreeNode; /** * Context passed to {@link TreeChartOptions.formatNodeLabel}. */ interface TreeLabelFormatterContext { /** Raw node object from the user-supplied tree data. */ node: TreeNode; /** Convenience alias for `node.name`. */ name: string; /** Depth from the root (`0` for root). */ depth: number; /** `true` when the node has no children. */ isLeaf: boolean; } /** * Icon payload for {@link TreeChartOptions.formatNodeIcon}. */ interface TreeNodeIconSpec { /** Avatar / icon image URL (without `image://` prefix). */ image: string; /** Node symbol width in px. */ width?: number; /** Node symbol height in px. Defaults to `width`. */ height?: number; /** Node symbol shape. Default: `'square'`. */ shape?: 'square' | 'circle'; /** * Border thickness in px. **Opt-in** — when omitted (or set to `0`), * the icon renders without any border on either shape. Set to a * positive number to: * * - **`shape: 'circle'`** → stroke a ring along the inside edge * of the circular clip in the avatar PNG. * - **`shape: 'square'`** → stroke a rectangular frame inside the * image's bounding box. Square + border switches the underlying * ECharts symbol from `image://...` (which can't render * `itemStyle.border*` natively) to a canvas-baked PNG so the * frame is part of the bitmap. * * Trade-off note: the canvas pipeline reserves `borderWidth * 2` px * for the stroke before contain-fitting the avatar, so bumping this * past `~width / 7` (e.g. 3 px on a 20 px icon, 5 px on a 36 px * icon) starts visibly shrinking the image inside the frame. * Adjust `width` proportionally if you want a thicker border. */ borderWidth?: number; /** * Border color. Honored only when `borderWidth` is set to a * positive value (color without width is silently ignored — there's * no border to paint). Defaults to the node's resolved palette * color (or the per-node `node.color` override when present), so a * single `borderWidth: 2` opt-in produces the classic * "framed-in-node-color" look without forcing the user to duplicate * the palette. Specify a CSS color string (e.g. `'#10b981'`) to pin * every node's frame to a brand accent. */ borderColor?: string; } /** * Structural type guard for tree data. * * The check is intentionally conservative: a single object with a string * `name` field is enough, but we also exclude shapes that belong to * other built-in chart types (gauge's `value`-only payload, sankey / * chord / network's `{nodes, links}`, XY's `{categories, series}`, * radar's `{indicators, series}`) so that mistakenly handing the wrong * data to `createChart(el, 'tree', ...)` fails fast instead of silently * rendering nothing. */ declare function isTreeData(data: ChartData): data is TreeData; /** * Tree-chart-specific options. * * Per the AGENTS.md convention, every chart-specific knob lives flat on * the subtype — no wrapping sub-object. Only `direction` is unique to * tree; the other fields are mild ECharts pass-throughs (`enableZoom`, * `nodeSize`, …) named consistently with sibling adapters. */ interface TreeChartOptions extends ChartOptions { /** * Tree growth direction. See {@link TreeDirection}. Default: `'LR'`. */ direction?: TreeDirection; /** * Initial expansion depth — the adapter starts with this many levels * visible and collapses everything below. `-1` (the default) expands * the whole tree. Use `2` for a typical "show root + one level" * starting state on large trees. */ initialTreeDepth?: number; /** * Enable wheel zoom. Default: `false`. */ enableZoom?: boolean; /** * Enable drag-to-pan. Default: `true`. */ enablePan?: boolean; /** Show node-name labels. Default: `true`. */ showNodeLabel?: boolean; /** * Tree edge rendering style. Maps to ECharts `series.edgeShape`. * Default: `'polyline'`. Use `'polyline'` for orthogonal elbow connectors, * `'curve'` for smooth links. */ lineStyle?: 'curve' | 'polyline'; /** * Diameter (px) of every node marker. Default: `7` — matches the * ECharts `tree-basic` example, which is calibrated so labels and * markers don't collide at the default 12 px font. */ nodeSize?: number; /** * Click an internal node to collapse / expand its sub-tree. Default: * `true`. Set `false` to render a fully static tree (useful for * snapshot exports). */ expandAndCollapse?: boolean; /** * Disable automatic label rotation for vertical directions (`'TB'` / `'BT'`). * * - `false` (default): uses direction-aware rotation (`-90` for `'TB'`, * `+90` for `'BT'`). * - `true`: forces both parent and leaf label `rotate` to `0`. */ disableLabelRotate?: boolean; /** * Customize node labels. * * Receives the current node context and returns plain text or * `RichTextSpec`. Rich text is compiled to ECharts formatter tokens and * injected into both `series.label.rich` and `series.leaves.label.rich`. * If this callback throws or returns an invalid value, the adapter falls * back to the raw `node.name`. */ formatNodeLabel?: (ctx: TreeLabelFormatterContext) => RichTextInput; /** * Customize the icon shown before each node label. * * Return: * - `string` → treated as `image` URL. * - `TreeNodeIconSpec` → full icon style control. * - `null` / `undefined` → no icon for this node. * * When enabled, the adapter replaces that node's marker symbol with an * image symbol (`symbol: 'image://...'`, `symbolSize` from `width/height`). */ formatNodeIcon?: (ctx: TreeLabelFormatterContext) => string | TreeNodeIconSpec | null | undefined; } /** * A single treemap node. Internal nodes carry `children`; leaves omit it. * * - `value` is required on leaves (drives rectangle area). Internal nodes * may omit it — ECharts sums the children's values automatically. * - `color` is an optional per-node fix; wins over `colorMap` / `colors` * palette resolution. */ interface TreemapDataItem { /** Display name; also serves as the color-lookup key. */ name: string; /** * Rectangle area. Required on leaves. Omit on internal nodes to let * ECharts sum the descendant values automatically. */ value?: number; /** Child nodes. Omit (or pass an empty array) for a leaf. */ children?: TreemapDataItem[]; /** * Per-node fixed color override. Wins over `colorMap` / `colors` / * theme palette resolution. Applied via `itemStyle.color` so the * override survives any `options.echarts` deep-merge. */ color?: string; } /** * Treemap data — an array of one or more hierarchical roots. * * Matches ECharts' native `series.data` shape so the library remains a * thin wrapper over the underlying treemap series. * * Example: * ```ts * const data: TreemapData = [ * { * name: 'flare', * children: [ * { name: 'analytics', value: 120, children: [...] }, * { name: 'data', value: 80 }, * ], * }, * ]; * ``` */ type TreemapData = TreemapDataItem[]; /** * Structural type guard for treemap data. * * Treemap data shares the `[{ name, value }]` skeleton with `PieData` and * `WordCloudData`; the chart `type` string selects the right adapter at * runtime, so the guard's job is just to verify the array-of-named-items * shape (matching the pie/word-cloud guards). */ declare function isTreemapData(data: ChartData): data is TreemapData; /** * Treemap-chart-specific options. * * Deliberately minimal — the field set is the smallest one that maps a * pleasant default treemap onto the user's intent while leaving every * advanced ECharts knob reachable through `options.echarts` for * power users. */ interface TreemapChartOptions extends ChartOptions { /** * Show the drill-down breadcrumb (the "you are here" trail rendered * at the bottom of the chart when the user zooms into a sub-tree). * Default: `true`. */ showBreadcrumb?: boolean; /** * Show node labels (the text drawn inside each rectangle). Default: * `true`. When `false`, the chart renders as a pure heat-grid of * unlabelled rectangles. */ showNodeLabel?: boolean; /** * Maximum visible depth at first paint. Deeper nodes start collapsed * and reveal when the user zooms / drills in. `undefined` (the default) * renders every level at once. Useful for very deep hierarchies — pass * `2` to start with "root + one level". */ leafDepth?: number; /** * Enable click-to-drill-down (and zoom-to-node) navigation. Default: * `true`. Set `false` for a fully static treemap (useful for snapshot * exports or read-only dashboards). */ drilldown?: boolean; /** * Enable wheel zoom + drag pan inside the active node. Default: `false`. * Note: drill-down (clicking a node) is governed by `drilldown`, not * this flag. */ enableRoam?: boolean; } type WordCloudVariant = 'default' | 'diamond' | 'poster' | 'compact-diamond'; interface WordCloudDataItem { name: string; value: number; /** * Per-word fixed color override. * When provided, wins over palette / colorMap resolution. */ color?: string; } /** * Word cloud data set. * * Note: this is structurally similar to `PieData` (`{name, value}[]`). * The chart `type` string selects the adapter. */ type WordCloudData = WordCloudDataItem[]; declare function isWordCloudData(data: ChartData): data is WordCloudData; interface WordCloudChartOptions extends ChartOptions { /** * Built-in layout presets. * - `default`: balanced general-purpose cloud * - `diamond`: denser diamond layout with horizontal labels * - `poster`: high-contrast, bold visual style for hero cards * - `compact-diamond`: deprecated alias of `diamond` */ variant?: WordCloudVariant; /** Text size range in px. Default: `[12, 60]`. */ sizeRange?: [number, number]; /** * Word cloud silhouette. Built-in presets are from the word-cloud extension. * Default: `'circle'`. */ shape?: 'circle' | 'cardioid' | 'diamond' | 'triangle-forward' | 'triangle' | 'pentagon' | 'star'; /** Rotation range in degrees. Default: `[-90, 90]`. */ rotationRange?: [number, number]; /** Rotation step in degrees. Default: `45`. */ rotationStep?: number; /** Grid size in px. Larger values create more gap between words. Default: `8`. */ gridSize?: number; /** Keep `maskImage` aspect ratio. Default: `false`. */ keepAspect?: boolean; /** Allow drawing outside the layout body. Default: `false`. */ drawOutOfBound?: boolean; /** Shrink words to fit when layout is dense. Default: `false`. */ shrinkToFit?: boolean; /** Enable placement animation. Default: `true`. */ layoutAnimation?: boolean; /** Sort data by value descending before layout. Default: `true`. */ autoSort?: boolean; /** Optional mask image for custom cloud shapes. */ maskImage?: HTMLImageElement | HTMLCanvasElement; } type ChartVariant = LineVariant | BarVariant | AreaVariant | PieVariant | GaugeVariant | LiquidProgressVariant | SankeyVariant | RadarVariant | NetworkVariant | WordCloudVariant; type ChartData = XYData | MapData | PieData | GaugeData | LiquidProgressData | SankeyData | ChordData | RadarData | NetworkData | TreeData | TreemapData | WordCloudData; /** * Discriminated union of every built-in chart's options type. * * Used as the public parameter type for `createChart`, the `IChart` * constructor, and the `` web component's `options` property so that * a chart-specific literal (e.g. `{ innerRadius: '50%' }`) type-checks as * `PieChartOptions` without forcing the caller to import and annotate the * subtype explicitly. */ type AnyChartOptions = ChartOptions | XYChartOptions | LineChartOptions | BarChartOptions | AreaChartOptions | MapChartOptions | PieChartOptions | GaugeChartOptions | LiquidProgressChartOptions | SankeyChartOptions | ChordChartOptions | RadarChartOptions | NetworkChartOptions | TreeChartOptions | TreemapChartOptions | WordCloudChartOptions; /** * Open registry mapping each chart `type` string to its `data` + `options` * pair. This is the single source of truth that lets `createChart` and * `IChartInstance` infer the exact data / options / variant types from the * `type` argument alone. * * Built-in types are declared below. Consumers who add a custom type via * {@link registerAdapter} can fold it into this map with TypeScript * declaration merging so `createChart('myType', data, options)` type-checks * with full inference — no `as any` casts: * * ```ts * declare module '@bndynet/icharts' { * interface ChartTypeRegistry { * scatter: { data: ScatterData; options: ScatterChartOptions }; * } * } * ``` */ interface ChartTypeRegistry { line: { data: LineData; options: LineChartOptions; }; bar: { data: BarData; options: BarChartOptions; }; area: { data: AreaData; options: AreaChartOptions; }; map: { data: MapData; options: MapChartOptions; }; pie: { data: PieData; options: PieChartOptions; }; gauge: { data: GaugeData; options: GaugeChartOptions; }; liquidprogress: { data: LiquidProgressData; options: LiquidProgressChartOptions; }; sankey: { data: SankeyData; options: SankeyChartOptions; }; chord: { data: ChordData; options: ChordChartOptions; }; radar: { data: RadarData; options: RadarChartOptions; }; network: { data: NetworkData; options: NetworkChartOptions; }; tree: { data: TreeData; options: TreeChartOptions; }; treemap: { data: TreemapData; options: TreemapChartOptions; }; wordcloud: { data: WordCloudData; options: WordCloudChartOptions; }; } /** Union of every registered chart `type` string (built-in + augmented). */ type RegisteredChartType = keyof ChartTypeRegistry; /** * Data type for a given chart `type`. Resolves to the registered shape for a * known key, or the broad {@link ChartData} union for an arbitrary string * (dynamic types / custom types not folded into {@link ChartTypeRegistry}). */ type ChartDataFor = T extends keyof ChartTypeRegistry ? ChartTypeRegistry[T]['data'] : ChartData; /** * Options type for a given chart `type`. Resolves to the registered shape for * a known key, or the broad {@link AnyChartOptions} union otherwise. */ type ChartOptionsFor = T extends keyof ChartTypeRegistry ? ChartTypeRegistry[T]['options'] : AnyChartOptions; /** * Chart instance contract. The optional `T` type parameter (the chart `type` * string) narrows `update`'s `data` / `options` to the matching registered * shapes. Defaults to `string`, which resolves the broad * {@link ChartData} / {@link AnyChartOptions} unions — so existing untyped * usage is unaffected. */ interface IChartInstance { update(data?: ChartDataFor, options?: ChartOptionsFor): void; /** Switch ECharts theme without disposing the instance (ECharts 6+). Updates series colors from the active palette. */ setTheme(theme: string): void; resize(): void; dispose(): void; getEChartsInstance(): echarts.ECharts; } interface XYDataSeries { name: string; data: number[]; } interface XYData { categories: (string | number)[]; series: XYDataSeries[]; } /** * Shared options for XY-family charts (line, bar, area). * * Holds the fields every XY chart actually uses (axes, stacking, per-series * overrides, grid, legend) so the concrete per-chart subtypes only add their * own variant union and any chart-specific knobs. * * `grid` and `legend` live here — not on the base — because only XY charts * (and pie, separately) consult them. Gauge / sankey / chord ignore both. */ interface XYChartOptions extends ChartOptions { stacked?: boolean; xAxis?: AxisOptions; yAxis?: AxisOptions; /** Per-series overrides keyed by series name (or `'*'` for all). */ series?: Record; legend?: LegendOptions; grid?: GridOptions; } /** * Core chart engine that manages an ECharts instance and provides the * full {@link IChartInstance} contract. Used by both the `` * web component and the imperative `createChart` helper. */ declare class IChart implements IChartInstance { private ecInstance; private _type; private _data; private _options; private _activeTheme; /** * Wall-clock time (ms since page-load) of the previous `update()` call. * Used to compute `RenderContext.observedFrameMs` so race / streaming * adapters can auto-size their animation duration to the consumer's * actual tick cadence — no need for the caller to mirror their * `setInterval` value as `race.frameDuration`. * * `null` until the first `update()`. Reset never; the timer reflects the * actual rendering pipeline regardless of theme switches or option merges. */ private _lastUpdateAt; /** * Largest `grid.right` (px) any frame's resolved option has emitted on * this chart. Fed to the adapter via `RenderContext.maxRaceGridRight` so * race adapters can keep the value/end-label headroom monotonic across * frames (see `resolveRaceLabelHeadroom`). Without this, every tick that * grew or shrank a digit would relayout the plot area and jitter the * line/bar positions. * * Tracked for every chart type (not just race) because the field lives * on the resolved option, not on the adapter contract — non-race * adapters simply emit the same value each frame, so the high-water * mark equals the current value, and nothing changes. */ private _maxGridRight; /** * `true` when the chart container's root is a `ShadowRoot` (i.e. the * `` web component path; plain `createChart(divEl, ...)` * leaves this `false`). * * Sampled once at construction time because: * - `getRootNode()` is the only cheap way to ask this question * ("am I in shadow DOM?") and the answer doesn't change for a * given mounted instance — moving the host element between * shadow / light DOM after `echarts.init` isn't a supported * scenario (ECharts would need a re-init anyway). * - Doing it once avoids paying the cost every `update()` / * `setTheme()` call when the value can never change. * * Threaded through every `_apply()` call via {@link RenderContext}. * Adapters consume it from `buildTooltip` / `buildSparkTooltip` (and * the inline tooltip blocks in pie / sankey / chord / radar) to * default `tooltip.appendToBody`: `true` in light DOM so the tooltip * escapes `overflow: hidden` ancestors like cards / dialogs, `false` * inside shadow DOM so the tooltip stays inside the web component * for style encapsulation. Users can still override via * `options.tooltip.appendToBody`. * * Conservative on non-DOM platforms (SSR / tests without * `ShadowRoot`): when the `ShadowRoot` global is undefined we treat * the container as light DOM, which matches the dominant CSR path * and keeps tooltips functional. */ private _inShadowDom; /** * Set to `true` by the first successful {@link dispose} call. Guards * the dispose body against double-execution — necessary because the * `` web component's `disconnectedCallback` and our own * sentinel-driven auto-dispose can both race to tear down the same * instance when an `` element is removed from the document. * ECharts' own dispose is idempotent internally but emits a console * warning on the second call; skipping the redundant work is cleaner. */ private _disposed; /** * Sentinel handle returned by {@link installSentinel}; held so * {@link dispose} can detach the sentinel without triggering its own * disconnect callback (which would re-enter dispose). `null` in * environments without `customElements` / `document` — those rely on * the registry's `pruneDetachedCharts` walk during the next theme or * `consistentColors` change. */ private _sentinel; /** * Reference to the async-tooltip `dismiss` callback currently bound to * the ECharts `hideTip` event. Re-bound on every `_apply()` because the * adapter pipeline creates a fresh formatter closure (and a fresh * `dismiss`) each resolve — we have to `off()` the previous reference * before `on()`-ing the new one to avoid stacking listeners. * * When set, ECharts fires this on every tooltip dismissal so the * formatter's HTML cache lives exactly for the duration of one * tooltip session — a new hover after dismissal re-fetches fresh * data, while rapid cursor motion within a session still dedupes * down to a single `customHtml` call. * * `null` when the resolved option has no async-tooltip formatter * (e.g. spark charts, gauge, or chart types whose adapter doesn't * call `createAsyncTooltipFormatter`). */ private _asyncTooltipDismiss; /** * Teardown callback returned by the adapter's most recent `onInit` * (see {@link ChartTeardown}). The engine owns its lifecycle so adapters * that wire `ResizeObserver` / listeners / timers in `onInit` get a * deterministic cleanup point: we run it before the next `_apply()`'s * `onInit` and once more on `dispose()`. `null` when the last `onInit` * returned nothing (or no adapter wires one). */ private _applyCleanup; /** * ECharts event wrappers currently bound for `options.events` handlers. * Re-derived on every `_apply()` (handlers can change via `update`), so we * detach the previous wrappers before attaching new ones to avoid stacking * listeners. Each entry pairs the ECharts event name with the wrapper we * registered so we can `off()` exactly what we `on()`-ed. */ private _boundEvents; constructor(container: HTMLElement, type: string, data: ChartData, options?: AnyChartOptions); update(newData?: ChartData, newOptions?: AnyChartOptions): void; setTheme(theme: string): void; resize(): void; dispose(): void; getEChartsInstance(): echarts.ECharts; /** Change the chart type (used by the web component when the `type` property changes). */ setType(type: string): void; private _apply; /** * Bind `options.events` handlers to the underlying ECharts instance, * normalizing each raw `params` into a {@link ChartEventContext} via * {@link buildChartEventContext}. Mirrors `_rebindAsyncTooltipDismiss`: * re-derived on every `_apply()` (handlers can change via `update`), so we * detach the previous wrappers first and never stack listeners. A throwing * user handler is swallowed so one bad callback can't break ECharts' * internal event dispatch. */ private _rebindEvents; /** Run and clear the pending adapter teardown, swallowing its errors. */ private _runApplyCleanup; /** * Wire `formatter.dismiss` (from {@link createAsyncTooltipFormatter}) to * ECharts' `hideTip` event so the formatter's per-slice HTML cache is * cleared whenever the tooltip closes. The cache then only deduplicates * `customHtml` calls *within* a single tooltip session — next hover * re-fetches fresh data. * * Idempotent across repeated `_apply()` calls: we detach the previous * formatter's listener before attaching the new one, so re-renders * (`update()`, `setTheme()`, `resize()`) never stack listeners. */ private _rebindAsyncTooltipDismiss; /** * Lift the high-water mark for `grid.right` from the resolved option so * the next `update()` can feed it back via {@link RenderContext}. Race * adapters use this to keep label headroom monotonic — see * `resolveRaceLabelHeadroom`. Tolerant of options without a grid (e.g. * pie, gauge) and of `grid` being either an object or an array. */ private _observeGridRight; } /** * Create a chart imperatively. * * The `type` argument drives full type inference: passing a registered type * literal (e.g. `'pie'`) narrows `data` to its data shape (`PieData`) and * `options` to its options shape (`PieChartOptions`, including the narrowed * `variant`), so mismatches are caught at compile time and editors offer * accurate completions. Passing an arbitrary `string` (dynamic type, or a * custom type not folded into {@link ChartTypeRegistry}) falls back to the * broad {@link ChartData} / {@link AnyChartOptions} unions. * * Custom chart types registered via `registerAdapter` become first-class by * augmenting {@link ChartTypeRegistry} via declaration merging — see its * docs for the pattern. * * @example * ```ts * const chart = createChart(document.getElementById('app')!, 'line', { * categories: ['Jan', 'Feb', 'Mar'], * series: [{ name: 'Sales', data: [10, 20, 30] }], * }); * chart.resize(); * chart.dispose(); * ``` */ declare function createChart(el: HTMLElement, type: T, data: ChartDataFor, options?: ChartOptionsFor): IChartInstance; interface MapGeoJsonSource { type: string; features: unknown[]; [key: string]: unknown; } interface MapSvgSource { svg: string; } interface MapSourceObject { geoJSON?: MapGeoJsonSource; geoJson?: MapGeoJsonSource; svg?: string; [key: string]: unknown; } /** * Register a map resource for the built-in `map` chart type. * * Accepts either: * - raw GeoJSON (`FeatureCollection`) object, * - `{ geoJSON | geoJson, specialAreas? }`, * - `{ svg }` (ECharts SVG map mode). */ declare function registerMap(name: string, source: MapGeoJsonSource | MapSvgSource | MapSourceObject, specialAreas?: Record): void; /** * Options forwarded to ECharts' SSR-mode `init` and `renderToSVGString` * for {@link renderChartToSVGString}. * * `width` and `height` are required because, unlike a browser chart * that can read its own container dimensions, a headless instance has * no DOM to size against — the caller must declare the SVG viewport * up front. */ interface RenderChartToSVGStringOptions { /** Width of the rendered SVG viewport in pixels. */ width: number; /** Height of the rendered SVG viewport in pixels. */ height: number; /** * Locale forwarded to ECharts for date / time / number formatters. * Defaults to ECharts' built-in locale (`'EN'`). */ locale?: string; /** * Controls whether the root `` element carries a `viewBox` * attribute. Both modes always emit `width` + `height` attrs. * * - `true` (default, matches ECharts) — root SVG has * `width="W" height="H" viewBox="0 0 W H"`. The intrinsic * dimensions are still `W × H`, but the `viewBox` lets a host * stylesheet override `width` / `height` (or wrap the SVG in a * responsive container) without distorting the chart geometry. * This is the right default for HTML / web embeds. * - `false` — root SVG has only `width="W" height="H"` (no * `viewBox`). The output renders at exactly the input dimensions * and ignores any CSS resizing of the host element. Use this for * strictly fixed-size output: print, PDF rasterizers that resolve * intrinsic dims, screenshot diffs. * * Either way, the chart's internal coordinate system is laid out * against `ssr.width × ssr.height`, so font sizes / strokes / symbol * radii stay constant regardless of how the SVG is later scaled. * * Forwarded directly to ECharts' `renderToSVGString({ useViewBox })`. */ useViewBox?: boolean; } /** * Render a chart to a full `...` string in a server / Node * runtime. * * Uses ECharts' built-in SSR mode (`ssr: true` + `renderer: 'svg'`) so * the whole call path is DOM-free and canvas-free — safe to invoke * from Next.js / Nuxt / Astro / SvelteKit / Vite SSR / serverless / * CLI scripts. The returned string is suitable for: * * - Embedding inline in server-rendered HTML pages, emails, PDFs. * - Saving to disk: `fs.writeFileSync('chart.svg', svg)`. * - Converting to PNG via a standalone library, e.g. * [`sharp`](https://sharp.pixelplumbing.com/) or * [`@resvg/resvg-js`](https://github.com/yisibl/resvg-js). * icharts deliberately stays SVG-only so SSR consumers don't pay * for a native canvas dependency they may not need. * * The underlying ECharts instance is created and `dispose()`d entirely * inside this function (`try`/`finally`), so calling this in a hot * request loop doesn't leak engine resources. * * **Plugin-backed chart types.** `liquidprogress` requires the * `@echarts-x/custom-liquid-fill` plugin to be registered with the * shared ECharts global. The main entry (`@bndynet/icharts`) * registers it via side-effect on import, but the SSR-safe entry * (`@bndynet/icharts/core`) does NOT — server consumers opt in via * the {@link installLiquidProgress} helper, which keeps the * `echarts` and `@echarts-x/*` packages internal to this library: * * ```ts * import { installLiquidProgress, renderChartToSVGString } from '@bndynet/icharts/core'; * installLiquidProgress(); // once at boot; idempotent * const svg = renderChartToSVGString( * 'liquidprogress', * { value: 0.65 }, * { width: 400, height: 400 }, * ); * ``` * * `wordcloud` is **not** SSR-renderable, no matter how you register * it. The `@echarts-x/custom-word-cloud` package touches `window` at * module-load and `canvas.addEventListener` at render-time — both * fundamental browser dependencies. Render wordcloud on the client * only, via `createChart('wordcloud', ...)` from the main * `@bndynet/icharts` entry. See `src/ssr-plugins.ts` for the full * rationale. * * **SSR fallbacks** (vs. browser rendering): * * - `canvas.measureText` is unavailable, so adapters that measure * label widths fall back to a `chars × 7px` estimate. Race * `grid.right` headroom and tree label widths can drift by 1–2 px. * - `ResizeObserver` is unavailable, so the pie adapter's resize- * driven center/radius recompute is replaced by the static * percent fallback (still legible, slightly less centered). * - SVG renderer text width is approximated, not measured against a * real canvas, so very long labels can wrap differently than in * the browser. Pad your widths slightly if you depend on exact * parity. * * @example * ```ts * import { renderChartToSVGString } from '@bndynet/icharts/core'; * import { writeFileSync } from 'node:fs'; * * const svg = renderChartToSVGString( * 'line', * { * categories: ['Jan', 'Feb', 'Mar'], * series: [{ name: 'Sales', data: [10, 20, 30] }], * }, * { width: 800, height: 400 }, * { title: 'Q1 Sales' }, * ); * writeFileSync('chart.svg', svg); * ``` * * @param type Chart type string (e.g. `'line'`, `'bar'`, `'pie'`). * Must match a registered adapter. * @param data Chart data — same shape consumed by `createChart`. * @param ssr Required SSR-render options (`width`, `height`, * `locale?`, `useViewBox?`). Replaces the DOM * container that the browser API would take. * @param options Optional `XxxChartOptions` (theme, title, legend, * tooltip, …). Same shape as the fourth argument to * `createChart`. * @returns Full `...` markup as a single string. * @throws Error when `ssr.width` / `ssr.height` are missing or * non-finite, when `type` has no registered adapter, or when * `data` fails the adapter's `validate` check (same errors * the browser path would surface). */ declare function renderChartToSVGString(type: string, data: ChartData, ssr: RenderChartToSVGStringOptions, options?: AnyChartOptions): string; /** * Register the `@echarts-x/custom-liquid-fill` plugin so * {@link renderChartToSVGString} can render `'liquidprogress'` charts * on the server. * * Call this **once, before the first `renderChartToSVGString` call** * that targets `'liquidprogress'`. Subsequent calls are idempotent * (ECharts dedupes plugin registrations by class identity), so * sprinkling it at the top of every handler is safe but unnecessary. * * The wrapper exists so SSR consumers never have to import `echarts` * or `@echarts-x/*` directly — the public surface stays * `@bndynet/icharts/core`. The implementation is intentionally tiny * (a single `echarts.use(...)` call); the value is the API boundary, * not the code. * * @example * ```ts * import { installLiquidProgress, renderChartToSVGString } from '@bndynet/icharts/core'; * * installLiquidProgress(); * * const svg = renderChartToSVGString( * 'liquidprogress', * { value: 0.65 }, * { width: 400, height: 400 }, * { title: 'CPU' }, * ); * ``` * * @example Express handler with per-request rendering * ```ts * import express from 'express'; * import { installLiquidProgress, renderChartToSVGString } from '@bndynet/icharts/core'; * * // Register once at boot time — idempotent, but no reason to repeat. * installLiquidProgress(); * * app.get('/chart.svg', (req, res) => { * const svg = renderChartToSVGString('liquidprogress', { value: Number(req.query.v) }, { width: 400, height: 400 }); * res.type('image/svg+xml').send(svg); * }); * ``` */ declare function installLiquidProgress(): void; /** * Escape minimal HTML entities for safe inline text (errors, placeholders). */ declare function escapeTooltipHtml(s: string): string; /** * ECharts tooltip formatter callable + a `dismiss()` hook the engine wires * to ECharts' `hideTip` event. See {@link createAsyncTooltipFormatter} for * the full lifecycle rationale. */ interface AsyncTooltipFormatter { (params: unknown, asyncTicket: string, callback?: (ticket: string, html: string | HTMLElement | HTMLElement[]) => void): string; /** * Drop every cached and in-flight tooltip HTML this formatter holds. The * engine wires this to ECharts' `hideTip` event so the cache only lives * for a single tooltip session — the next hover always re-fetches fresh * data, while rapid cursor motion within a single session still dedupes * down to one `customHtml` call. * * Idempotent; safe to invoke when nothing is cached. */ dismiss(): void; } /** * Build an ECharts tooltip `formatter` that shows synchronous HTML first, then * appends content from an async `customHtml` call. Works for **any** chart type: * pass your own `formatSync` (e.g. pie item, sankey link, or * {@link formatAxisTooltipSyncHtml} for axis charts) and use the same `params` * shape ECharts provides. * * Use with `ChartOptions.echarts.tooltip.formatter` when not using the built-in * `tooltip.customHtml` shortcut (axis line/bar/area only). * * **Cache lifecycle.** ECharts re-invokes the formatter on every mouse move * (not just when the hovered data point changes). To prevent `customHtml` * from firing — and the placeholder from flickering — on every pixel of * cursor motion, the formatter caches resolved HTML by a stable identity * derived from `params` (see {@link defaultTooltipCacheKey}). Concurrent * invocations for the same key share a single in-flight promise. * * The cache is cleared on: * - **`hideTip`** — `IChart._apply()` registers a listener that calls * `formatter.dismiss()`, so each new tooltip session re-fetches fresh * data (cache only deduplicates *within* a session). * - **Adapter re-resolve** — every `chart.update()` / `setTheme()` / * `resize()` rebuilds the formatter closure with a fresh empty cache. * * Pass `cacheKey: false` in {@link CreateAsyncTooltipFormatterOptions} to * disable caching entirely when the async result genuinely varies across * mouse moves for the same data point. * * @example * ```ts * echarts: { * tooltip: { * trigger: 'item', * formatter: createAsyncTooltipFormatter({ * formatSync: (p) => { * const x = p as { name: string; value: number }; * return `${x.name}: ${x.value}`; * }, * customHtml: async (p) => { * const x = p as { dataIndex: number }; * const r = await fetch(`/meta/${x.dataIndex}`); * return (await r.json()).note; * }, * }), * }, * } * ``` */ declare function createAsyncTooltipFormatter(options: CreateAsyncTooltipFormatterOptions): AsyncTooltipFormatter; /** * Map ECharts pie `item` tooltip params to {@link TooltipContextItem}. * * Reads `params.color` for the resolved slice color — ECharts populates * this with the same hex/rgb value it used to paint the slice, so it * already reflects `options.colors` / `options.colorMap` / theme palette * after our color pipeline has run. */ declare function pieParamsToTooltipContext(params: unknown): TooltipContextItem; /** * Default synchronous pie tooltip HTML (before `customHtml` append). */ declare function formatPieTooltipSyncHtml(params: unknown, options: ChartOptions): string; /** * Map Sankey / Chord / Network / Tree tooltip params to {@link TooltipContext}. * * `nameToColor` is optional but **required for color fields to populate**: * * - node hover (`kind: 'item'`) → `color = nameToColor.get(name)` * (falls back to `params.color` when the map doesn't have an entry — * ECharts' own `params.color` is a real hex on the node side, never * `"gradient"`). * - edge hover (`kind: 'edge'`) → `sourceColor = nameToColor.get(source)`, * `targetColor = nameToColor.get(target)`. There is no `params.color` * fallback here because ECharts reports the literal string * `"gradient"` for sankey/chord links by default. * * Without the map both color fields are `undefined` and the rest of the * context still works — this keeps the function callable from custom * adapters that don't have a name→color lookup handy. */ declare function sankeyChordParamsToTooltipContext(params: unknown, nameToColor?: ReadonlyMap): TooltipContext; /** * Shared canvas-based text measurement primitive. */ declare const DEFAULT_LABEL_FONT_SIZE = 12; declare function getLabelFontSize(options: ChartOptions): number; /** * Structural shape consumed by legend helpers. * * `legend` sits on chart-option subtypes that render a legend; this keeps * helper signatures concise without adding `legend` back to base ChartOptions. */ type WithLegend = ChartOptions & { legend?: LegendOptions; }; interface EdgeReserves { top: number; bottom: number; left: number; right: number; } declare function getTitleReserve(options: ChartOptions): EdgeReserves; declare const LEGEND_RESERVE = 36; declare function getLegendReserve(options: WithLegend, showLegend: boolean, extraGap?: number, names?: ReadonlyArray): EdgeReserves; declare const STACKED_TEXT_DEFAULT_VISIBLE_GAP_PX = 12; declare const STACKED_TEXT_DEFAULT_GLYPH_PADDING_EM = 0.15; interface StackedTextOffsetsOptions { primaryFontSize: number; secondaryFontSize: number; visibleGapPx?: number; glyphPaddingEm?: number; showSecondary?: boolean; } interface StackedTextOffsets { primaryOffsetY: number; secondaryOffsetY: number; } declare function computeStackedTextOffsets(opts: StackedTextOffsetsOptions): StackedTextOffsets; /** * Cleanup callback an adapter's `onInit` may return. The engine runs it on * the next re-render (before the next `onInit`) and on `dispose()`, so an * adapter that wires `ResizeObserver` / event listeners / timers in `onInit` * has a deterministic teardown point — no need to stash state on the chart * instance or poll `isDisposed()`. */ type ChartTeardown = () => void; /** * Result returned by an adapter's resolve method. * * `option` -- full ECharts option ready for setOption(). * `onInit` -- optional hook called after the instance is initialised and * setOption() has been called (e.g. for event listeners). It * fires on every render pass (initial + every update / theme / * resize). May return a {@link ChartTeardown} cleanup; the * engine invokes the previous pass's cleanup before the next * `onInit`, and the final cleanup on `dispose()`. * `notMerge` -- forwarded to ECharts `setOption(option, notMerge)`. Defaults * to `true` (full replace). Adapters that depend on cross-call * state transitions (e.g. bar `race` needs ECharts to animate * value/position changes between successive `chart.update()` * calls) set this to `false` so ECharts merges with the * previous option instead of replacing it. */ interface ChartSetupResult { option: Record; onInit?: (instance: echarts.ECharts) => void | ChartTeardown; notMerge?: boolean; } /** * Per-render context the engine passes to adapters alongside data/options. * * Carries lightweight signals derived from prior render passes — or from * the chart container itself — so adapters can make better decisions * without holding their own state or doing their own DOM lookups: * * - `observedFrameMs` — wall-clock gap between the last two `chart.update()` * calls. Race / streaming adapters use this to auto-size * `animationDurationUpdate` so callers don't have to mirror their own * `setInterval` value as `race.frameDuration`. `undefined` on the very * first `update()` (no prior call to measure against). * - `maxRaceGridRight` — high-water mark of the largest `grid.right` any * prior frame asked for. Race adapters mix this into their adaptive * label-headroom calculation (see `resolveRaceLabelHeadroom`) so the * reserved space grows monotonically as labels widen and never shrinks * back, avoiding plot-area jitter when label digits flip frame to frame. * - `inShadowDom` — `true` when the chart container's root is a * `ShadowRoot` (e.g. the `` web component). Tooltip helpers * (`buildTooltip` / `buildSparkTooltip`) use this to decide the default * value of ECharts' `appendToBody`: `false` inside shadow DOM (so the * tooltip stays inside the shadow root for style encapsulation) and * `true` in light DOM (so it can escape ancestors with `overflow: * hidden` like card / KPI containers). Users can still override via * `options.tooltip.appendToBody`. The engine sets this flag once at * construction time — moving the host between shadow / light DOM * after `init` isn't a supported scenario. * - `containerWidth` / `containerHeight` — px reported by * `ecInstance.getWidth()` / `getHeight()` at render time. Threaded * through every `_apply()` (including resize-triggered re-renders) * so adapters that need pixel-derived sizing can react to the * actual rendered viewport. Today the gauge `percentage` variant is * the canonical consumer: ECharts' `axisLine.lineStyle.width`, * `progress.width`, and `detail.fontSize` are pixel-only (no native * `%` support), so the adapter computes them from * `min(containerWidth, containerHeight)` when the consumer hasn't * set them. Both fields are `undefined` when the instance reports a * zero / non-finite size (SSR, display:none ancestors, jsdom * environments without layout) so adapters can fall back to static * defaults and keep snapshots stable. * * Frame-derived fields (`observedFrameMs`, `maxRaceGridRight`) are * `undefined` during the initial render from the constructor; * container-derived fields (`inShadowDom`, `containerWidth`, * `containerHeight`) are populated from the very first render. */ interface RenderContext { observedFrameMs?: number; maxRaceGridRight?: number; inShadowDom?: boolean; containerWidth?: number; containerHeight?: number; } /** * Contract every chart adapter must satisfy. * * `validate` -- returns true when `data` has the shape this adapter expects. * `resolve` -- builds the ECharts option (and optional onInit hook). * `ctx` is optional; adapters that don't need it ignore the arg. * * `mergeData` -- optional. When provided, the engine calls it on `update()` * to fold the next data into the previous frame instead of * replacing it wholesale. This lets live-updating charts * (e.g. gauge, liquid-progress) animate value transitions * across successive `chart.update({ value })` calls — the * caller can send a partial patch and the adapter decides how * to carry forward fields it isn't given. The engine only * invokes it when BOTH the previous and next data pass * `validate`, so an adapter's `mergeData` can safely narrow to * its own data shape. When omitted, the engine replaces data * (the default). * * `clearOnThemeChange` -- optional. When `true`, the engine calls * `instance.clear()` before re-applying on a `setTheme()` * switch. Needed by custom-series renderers (e.g. wordcloud) * whose diff/merge can leave stale display elements behind on * a theme repaint. Defaults to `false`. */ interface ChartAdapter { validate(data: ChartData): boolean; resolve(data: ChartData, options: ChartOptions, ctx?: RenderContext): ChartSetupResult; mergeData?(prev: ChartData, next: ChartData): ChartData; clearOnThemeChange?: boolean; } /** * Register a chart adapter for a given type string. * Built-in adapters are registered at module load time. * Users can call this to add custom chart types. * * Overriding a **built-in** type emits a `console.warn` (the override still * takes effect). This is almost always an accidental type-string collision; * prefer a distinct string for custom charts. Re-registering a custom type is * silent. */ declare function registerAdapter(type: string, adapter: ChartAdapter): void; /** * Look up the adapter registered for a given chart type, or `undefined` when * none is registered. Used by the engine to consult optional adapter * capabilities (`mergeData`, `clearOnThemeChange`) without hardcoding * per-type behavior. */ declare function getAdapter(type: string): ChartAdapter | undefined; /** * Whether an adapter is registered for `type` (built-in or custom). Cheap * pre-flight check before `createChart` so callers can branch without a * try/catch around the engine's "Unsupported chart type" throw. */ declare function hasAdapter(type: string): boolean; /** * The type strings of every registered adapter (built-in + custom), in * registration order. Useful for diagnostics, building a type picker, or * asserting a custom adapter registered as expected. */ declare function listAdapters(): string[]; /** * Remove a previously registered adapter. Returns `true` if one was removed, * `false` if no adapter was registered for `type`. Primarily for tests and * hot-reload scenarios; removing a built-in is allowed but leaves that type * unusable until re-registered. */ declare function unregisterAdapter(type: string): boolean; declare function formatAxisTooltipSyncHtml(params: unknown, tooltip: TooltipOptions, isTimeAxis: boolean): string; declare function buildAxisTooltipContext(params: unknown, tooltip: TooltipOptions, isTimeAxis: boolean): TooltipContextAxis; interface CompiledRichText { text: string; plainText: string; rich?: Record; measuredWidthPx?: number; } declare function compileRichText(input: RichTextInput, keyPrefix: string): CompiledRichText; /** * Semantic UI color tokens for chart rendering. * * These tokens cover every non-series visual element in a chart. * Series / palette colors are intentionally excluded — those are managed * exclusively by ColorHub's `palette` field. * * Design rules: * - Each token has one clear responsibility; no two tokens describe the same thing. * - Tokens that share a visual role share the same value in presets (DRY). * - Names are semantic ("what it is used for"), not literal ("what color it is"). */ interface ChartThemeColors { /** Chart canvas / container background. */ background: string; /** * Floating surface background — tooltip, popover, and axis-pointer * callout label. Typically contrasts with `background` for legibility. */ surface: string; /** Foreground text rendered on `surface` (tooltip text, callout text). */ surfaceText: string; /** * Primary label color. * Used for: chart title, legend text, pie/gauge/data labels, markPoint labels, * bar/line value labels (including race value labels and line-race endLabels). */ textPrimary: string; /** * Secondary label color — visually quieter than `textPrimary`. * Used for: axis tick labels. */ textSecondary: string; /** * Grid lines (splitLine). * Should be subtle — barely visible rules that aid reading without * competing with the data. */ gridLine: string; /** * Axis spine, tick marks, and cursor crosshair / axis-pointer line. * Slightly more prominent than `gridLine` to frame the plot area. */ axisLine: string; /** * Color of the thin 1 px stroke between adjacent series data items — * pie/doughnut/nightingale slices today, and future sunburst sectors, * treemap cells, sankey/network node borders, etc. * * Should typically equal the chart's visible backdrop (usually the * card / panel background) so adjacent items appear separated by a * real gap rather than a coloured ring around each item. * * Falls back to {@link surface} when omitted — the built-in light / * dark presets explicitly set this to the same value as `surface` * (matching the typical card background) so the relationship is * documented even though the result is identical. Override on themes * whose card background differs from `surface` (e.g. glassmorphism * panels where `surface` describes the *tooltip* surface, not the * card). */ itemDivider?: string; /** * Tooltip popup background color. * Falls back to `surface` when omitted. */ tooltipBackground?: string; /** * Tooltip popup border color. * Falls back to `axisLine` when omitted. */ tooltipBorderColor?: string; /** * Tooltip body text color — series names and values. * Falls back to `surfaceText` when omitted. */ tooltipTextColor?: string; /** * Tooltip title text color — the category / date / x-axis label at the top. * Typically slightly quieter than `tooltipTextColor`. * Falls back to `tooltipTextColor` → `surfaceText` when omitted. */ tooltipTitleColor?: string; /** Positive / healthy state indicator. */ success: string; /** Cautionary / threshold-approaching state indicator. */ warning: string; /** Critical / error state indicator. */ danger: string; /** Informational / neutral highlight. */ info: string; } /** * A complete chart theme: semantic UI tokens (`colors`) + series palette, * stored as a `ColorTheme` so ColorHub can manage * palette assignment for series colors. */ type ChartTheme = ColorTheme; /** * User-facing config for registering a custom chart theme. * * Only specify the color tokens you want to override — the rest are * automatically inherited from the built-in `light` or `dark` base theme * based on `colorMode` (defaults to `'light'` when omitted). * * @example * ```ts * registerTheme({ * name: 'ocean', * colorMode: 'dark', * colors: { background: '#001f3f', textPrimary: '#e0f2fe' }, * palette: ['#0ea5e9', '#06b6d4', '#14b8a6'], * }); * ``` */ type ChartThemeConfig = { name: string; colorMode?: ColorMode; colors?: Partial; palette?: string[]; }; /** * Switch the active theme, clear that theme's *auto-assigned* color slots * (the palette restarts at index 0 for the next chart), and re-apply the * theme to every live chart. * * Pinned entries from {@link setColorMap} are preserved — they live in a * dedicated `pins` map inside {@link PaletteRegistry}, untouched by the auto * reset, so a `setColorMap` pin survives every `switchTheme` cycle. * * This is the canonical way for SPA pages to get a clean palette without * leaking name → color assignments from a previously visited page. * Consumers no longer need to call {@link resetColorMap} manually before * each page mount; `switchTheme` does the right thing automatically. */ declare function switchTheme(name: string): void; /** * State colors ({@link StateColors}) for `name` in the active theme. * * The base (`default`) color is resolved through {@link PaletteRegistry} — the * same source the chart resolver uses — so `getSeriesColor(name).default` * always equals the consistentColors color assigned to that name. The * hover / active / disabled variants are derived from the base with the * color-math helpers (matching ColorHub's default state recipe: * lighten 0.05 / darken 0.1 / alpha 0.4). */ declare function getSeriesColor(name: string): StateColors; declare function getCurrentTheme(): ChartTheme; declare function getThemeColors(): ChartThemeColors | undefined; /** * Register a custom chart theme with both ColorHub and ECharts. * * Missing `colors` tokens are automatically inherited from the built-in * `light` or `dark` base theme according to `colorMode` (defaults to `light`). * A missing `palette` is also inherited from the base. */ declare function registerTheme(config: ChartThemeConfig): void; /** * Pre-bind name → color mappings ("pins"). * When `themeName` is provided, applies only to that theme. * When omitted, applies to every registered theme. * * Pins are sticky: they survive {@link switchTheme} and {@link resetColorMap} * because they live in {@link PaletteRegistry}'s dedicated `pins` map, which * the auto-reset paths never touch. Calling `setColorMap` again with the same * name overwrites the existing pin; pins are additive across calls (no public * API to remove a single pin — start a fresh session if you really need to). * * Only meaningful when `consistentColors` is enabled — without it, the * resolver path is positional ({@link resolveColorsByPosition}) and never * reads the registry's stored map. */ declare function setColorMap(map: Record, themeName?: string): void; /** * Clear auto-assigned name → color slots. * * - When `themeName` is omitted, clears every theme's auto assignments so the * next chart starts cleanly from `palette[0]` in whichever theme it renders * against. * - When `themeName` is provided, only that theme's auto slots are cleared. * * In both cases, entries previously written by {@link setColorMap} are * **preserved** — they live in {@link PaletteRegistry}'s `pins` map. Call * {@link setColorMap} again to add new pins; there is no public API to remove * a pin (use a fresh app load if you really need that — pins are * intentionally sticky). * * Since `switchTheme(name)` now automatically clears the target theme's * auto-assigned entries, most consumers don't need to call this directly. * It remains useful for: * - Manually wiping state mid-page without changing theme. * - Wiping ALL themes' auto-assignments at once (the no-arg form). */ declare function resetColorMap(themeName?: string): void; interface IChartsConfig { consistentColors: boolean; fontFamily?: string; theme?: ChartThemeConfig; } declare function configure(opts: Partial): void; /** Reset configure() state back to library defaults. */ declare function resetConfiguration(): void; declare function formatNumber(value?: number | string, options?: FormatNumberOptions): string; interface FormatNumberOptions extends Intl.NumberFormatOptions { compact?: boolean; locale?: string; } /** * Resolve a color for each name using the active theme, `options.colors`, * `options.colorMap`, and `configure({ consistentColors })`. * * Returns an array of the same length as `names`. Returns an empty array * when `names` is empty (used by chart types without named series, e.g. gauge). */ declare function resolveColors(names: ReadonlyArray, options: ChartOptions): string[]; /** * Resolve colors for graph nodes (sankey, chord, or any custom graph type). * Honors `node.color` first, then falls through to the same rules as * {@link resolveColors}. Always returns a non-empty hex string per node * (`'#888888'` as the final fallback when the palette is empty). */ declare function resolveColorsForNodes(nodes: ReadonlyArray<{ name: string; color?: string; }>, options: ChartOptions): string[]; export { type AnyChartOptions, type AreaChartOptions, type AreaData, type AreaVariant, type AxisOptions, type BarChartOptions, type BarData, type BarRaceOptions, type BarVariant, type ChartAdapter, type ChartData, type ChartDataFor, type ChartEventContext, type ChartEventHandler, type ChartEventHandlers, type ChartEventType, type ChartOptions, type ChartOptionsFor, type ChartSetupResult, type ChartTeardown, type ChartThemeConfig, ChartType, type ChartTypeRegistry, type ChartVariant, type ChordChartOptions, type ChordData, type ChordLink, type ChordNode, type CompiledRichText, type CreateAsyncTooltipFormatterOptions, DEFAULT_LABEL_FONT_SIZE, type EdgeReserves, type FormatNumberOptions, type GaugeChartOptions, type GaugeData, type GaugeVariant, type GridOptions, IChart, type IChartInstance, type IChartsConfig, LEGEND_RESERVE, type LegendOptions, type LineChartOptions, type LineData, type LineRaceOptions, type LineVariant, type LiquidProgressChartOptions, type LiquidProgressData, type LiquidProgressVariant, type MapChartOptions, type MapData, type MapDataItem, type MapGeoJsonSource, type MapSourceObject, type MapVisualMapOptions, type NetworkChartOptions, type NetworkData, type NetworkLink, type NetworkNode, type NetworkVariant, type PieChartOptions, type PieData, type PieDataItem, type PieVariant, type RadarChartOptions, type RadarData, type RadarDataSeries, type RadarIndicator, type RadarVariant, type RegisteredChartType, type RenderChartToSVGStringOptions, type RichTextInput, type RichTextSegment, type RichTextSpec, type RichTextStyle, STACKED_TEXT_DEFAULT_GLYPH_PADDING_EM, STACKED_TEXT_DEFAULT_VISIBLE_GAP_PX, type SankeyChartOptions, type SankeyData, type SankeyLink, type SankeyNode, type SankeyVariant, type SeriesOptions, type StackedTextOffsets, type StackedTextOffsetsOptions, type TooltipContext, type TooltipContextAxis, type TooltipContextEdge, type TooltipContextItem, type TooltipOptions, type TreeChartOptions, type TreeData, type TreeDirection, type TreeLabelFormatterContext, type TreeNode, type TreeNodeIconSpec, type TreemapChartOptions, type TreemapData, type TreemapDataItem, type WordCloudChartOptions, type WordCloudData, type WordCloudDataItem, type WordCloudVariant, type XYChartOptions, type XYData, type XYDataSeries, buildAxisTooltipContext, compileRichText, computeStackedTextOffsets, configure, createAsyncTooltipFormatter, createChart, escapeTooltipHtml, formatAxisTooltipSyncHtml, formatNumber, formatPieTooltipSyncHtml, getAdapter, getCurrentTheme, getLabelFontSize, getLegendReserve, getSeriesColor, getThemeColors, getTitleReserve, hasAdapter, installLiquidProgress, isChordData, isLiquidProgressData, isMapData, isNetworkData, isRadarData, isSankeyData, isTreeData, isTreemapData, isWordCloudData, listAdapters, mergeLiquidProgressData, pieParamsToTooltipContext, registerAdapter, registerMap, registerTheme, renderChartToSVGString, resetColorMap, resetConfiguration, resolveColors, resolveColorsForNodes, sankeyChordParamsToTooltipContext, setColorMap, switchTheme, unregisterAdapter };