UNPKG

apexcharts

Version:

A JavaScript Chart Library

4,437 lines 151 kB
// TypeScript declarations for ApexCharts.
// The ApexCharts class and a namespace of the same name are merged here so
// that consumers can access sub-types either as named imports
// (`import type { ApexOptions } from 'apexcharts'`) or via the class
// namespace (`ApexCharts.ApexOptions`).
//
// For the full set of supported options, see https://apexcharts.com/docs/options

// ---------------------------------------------------------------------------
// Shared formatter/event opts types
// ---------------------------------------------------------------------------

/**
 * The chart state object passed as `w` to formatters, event opts, and
 * snapshots. Common access patterns:
 *   - `w.config.chart.type` — the merged user options
 *   - `w.globals.seriesNames` — runtime state bag
 *
 * `globals` is intentionally `any` because it is a large, internal surface;
 * prefer the typed `ApexCharts.ChartState` returned by `getState()` for
 * stable access. Other internal slices (`dom`, `formatters`, `interact`,
 * `layout`, etc.) exist on `w` but are not part of the documented API and
 * may change between releases — the index signature documents their
 * presence without committing to a stable shape.
 */
type ApexChartContext = {
  config: ApexCharts.ApexOptions
  globals: any
  [key: string]: any
}

/**
 * Opts object passed to most chart event callbacks (click, mouseMove,
 * keyDown, etc.). For some events (mouseMove, click, keyDown, keyUp)
 * `w` is also spread into the opts object as a convenience, so members
 * of `w` (config, globals, etc.) may be accessed directly on opts. The
 * index signature reflects that.
 */
type ApexChartEventOpts = {
  seriesIndex: number
  dataPointIndex: number
  w: ApexChartContext
  [key: string]: any
}

/**
 * Opts object passed to most value formatters (dataLabels, tooltip y,
 * etc.). `series` is included for tooltip formatters; ignore it elsewhere.
 */
type ApexFormatterOpts = {
  seriesIndex: number
  dataPointIndex: number
  series?: any[][]
  w: ApexChartContext
  // Some formatter call sites spread extra state into the opts object (e.g.
  // the bar total-label formatter spreads `w`), so allow arbitrary reads.
  [key: string]: any
}

/**
 * Opts object passed to legend.formatter and legend.tooltipHoverFormatter.
 */
type ApexLegendFormatterOpts = {
  seriesIndex: number
  w: ApexChartContext
}

/**
 * Opts object passed to `colors[]` when a color is provided as a function.
 */
type ApexColorFormatterOpts = {
  value: number
  seriesIndex: number
  dataPointIndex: number
  w: ApexChartContext
}

/**
 * Opts object passed to `tooltip.custom`. `series` is the parsed series
 * matrix; `y1`/`y2` are populated for range-bar / range-area tooltips.
 */
type ApexTooltipCustomOpts = {
  series: number[][]
  seriesIndex: number
  dataPointIndex: number
  y1?: number
  y2?: number
  w: ApexChartContext
}

declare class ApexCharts {
  constructor(el: HTMLElement, options: ApexCharts.ApexOptions)

  /** Renders the chart. Must be called once after construction. */
  render(): Promise<ApexCharts>

  /**
   * Merges new options into the existing config and re-renders the chart.
   * @param redraw When true, redraws from scratch instead of animating from previous paths.
   * @param animate Whether to animate the update.
   * @param updateSyncedCharts Whether to propagate the update to charts in the same group.
   * @param overwriteInitialConfig When true, replaces the stored initial config used by resetSeries().
   */
  updateOptions(
    options: ApexCharts.ApexOptions,
    redraw?: boolean,
    animate?: boolean,
    updateSyncedCharts?: boolean,
    overwriteInitialConfig?: boolean
  ): Promise<ApexCharts>

  /**
   * Replaces the chart's series data and re-renders.
   * @param overwriteInitialSeries When true, replaces the stored initial series used by resetSeries().
   */
  updateSeries(
    newSeries: ApexAxisChartSeries | ApexNonAxisChartSeries,
    animate?: boolean,
    overwriteInitialSeries?: boolean
  ): Promise<ApexCharts>

  /**
   * Appends a new series to the existing series array and re-renders.
   * @param overwriteInitialSeries When true, replaces the stored initial series used by resetSeries().
   */
  appendSeries(
    newSerie: ApexAxisChartSeries[0] | number,
    animate?: boolean,
    overwriteInitialSeries?: boolean
  ): Promise<ApexCharts>

  /**
   * Appends data points to existing series without replacing them.
   * Each element corresponds to the series at the same index.
   */
  appendData(data: Array<{ data: any[] }>, overwriteInitialSeries?: boolean): Promise<ApexCharts>

  /** Toggles (show/hide) the series by name. Mirrors a click on the legend item. */
  toggleSeries(seriesName: string): object | undefined

  /**
   * Linked Views (#4): clears crossfilter dimming across this chart and every
   * chart in its `chart.group`. No-op unless the `link` feature is bundled.
   */
  clearCrossfilter(): void

  /**
   * Measure ruler (#18): arm a sticky measure-ruler mode. Drag A->B on the
   * plot to read dx/dy/%change/slope. Requires the `measure` feature and
   * `chart.measure.enabled`.
   */
  startMeasure(): void

  /** Measure ruler (#18): leave measure mode. */
  stopMeasure(): void

  /** Measure ruler (#18): remove all pinned measure rulers. */
  clearMeasures(): void

  /** Highlights or un-highlights a series when a legend marker is hovered. */
  highlightSeriesOnLegendHover(e: MouseEvent, targetElement: HTMLElement): void

  /** Makes a previously hidden series visible and re-renders. */
  showSeries(seriesName: string): void

  /** Hides a visible series and re-renders. */
  hideSeries(seriesName: string): void

  /** Highlights (dims all other series) the series identified by name. */
  highlightSeries(seriesName: string): void

  /** Returns whether the series identified by name is currently hidden. */
  isSeriesHidden(seriesName: string): boolean

  /**
   * Resets the chart to its initial series and optionally its initial zoom level.
   * @param shouldUpdateChart When true, triggers a re-render. Default true.
   * @param shouldResetZoom When true, restores the initial zoom level. Default true.
   */
  resetSeries(shouldUpdateChart?: boolean, shouldResetZoom?: boolean): void

  /** Programmatically zooms the x-axis to [min, max]. Requires zoom to be enabled. */
  zoomX(min: number, max: number): void

  /**
   * Programmatically selects or deselects a data point.
   * @returns Updated selectedDataPoints array, or null.
   */
  toggleDataPointSelection(seriesIndex: number, dataPointIndex?: number): number[][] | null

  /**
   * The rows behind this chart's marks, as a unit-chart series.
   *
   * A histogram bar stands for the observations it counted, a box for the
   * sample it summarises. This returns them as one cluster per mark, so a mark
   * can come apart into its own rows:
   *
   * ```js
   * chart.updateOptions({ chart: { type: 'unit' }, series: chart.rowSeries() })
   * ```
   *
   * With the `morph` feature loaded, each dot then leaves from the part of the
   * mark that was standing for it, and collapsing back is the inverse.
   *
   * Returns null when the chart's type cannot name its rows, or when
   * `apexcharts/features/stats` is not loaded.
   *
   * @param opts `maxRows` caps the dots produced (default 3000, matching the
   *   jitter overlay); past it every cluster is thinned by one shared stride so
   *   their relative sizes survive.
   */
  rowSeries(opts?: { maxRows?: number }): ApexUnitRowSeries[] | null

  /** Destroys the chart instance, removing all DOM elements and event listeners. */
  destroy(): void

  /**
   * Switches the active locale, updating all locale-dependent labels.
   * @param localeName Must match a name defined in chart.locales.
   */
  setLocale(localeName: string): void

  /**
   * Subscribes to a chart event by name.
   * Event names mirror the chart.events option keys (e.g. 'mounted', 'updated', 'dataPointMouseEnter').
   */
  addEventListener(name: string, handler: (...args: any[]) => void): void

  /** Removes a previously registered event listener. */
  removeEventListener(name: string, handler: (...args: any[]) => void): void

  /** Adds an x-axis annotation dynamically after render. */
  addXaxisAnnotation(options: XAxisAnnotations, pushToMemory?: boolean, context?: ApexCharts): void

  /** Adds a y-axis annotation dynamically after render. */
  addYaxisAnnotation(options: YAxisAnnotations, pushToMemory?: boolean, context?: ApexCharts): void

  /** Adds a point annotation dynamically after render. */
  addPointAnnotation(options: PointAnnotations, pushToMemory?: boolean, context?: ApexCharts): void

  /** Removes a specific annotation by its id. */
  removeAnnotation(id: string, context?: ApexCharts): void

  /** Removes all annotations from the chart. */
  clearAnnotations(context?: ApexCharts): void

  /**
   * Exports the chart to a data URI.
   * Requires the Exports feature: import 'apexcharts/features/exports'.
   */
  dataURI(options?: { scale?: number; width?: number }): Promise<{ imgURI: string } | { blob: Blob }>

  /**
   * Returns the chart's SVG markup as a string.
   * Requires the Exports feature: import 'apexcharts/features/exports'.
   */
  getSvgString(scale?: number): Promise<string>

  /**
   * Triggers a CSV download of the chart's data.
   * Requires the Exports feature: import 'apexcharts/features/exports'.
   */
  exportToCSV(options?: { series?: ApexAxisChartSeries | ApexNonAxisChartSeries; fileName?: string; columnDelimiter?: string; lineDelimiter?: string }): void

  /** Returns the SVG.js root element (SVG Paper) for the chart. */
  paper(): any

  /**
   * Returns the active series renderer for the last render (Strata #2):
   * `'svg'` (default) or `'canvas'`. Resolves to `'svg'` unless the canvas
   * renderer feature is bundled and no canvas-unsupported feature is in use.
   */
  getActiveRenderer(): 'svg' | 'canvas' | 'gpu'

  /**
   * Facet (#13): re-resolves the `--apx-*` design tokens from the CSS cascade
   * and re-renders. Use after a runtime token change that is not an OS
   * color-scheme flip (e.g. the host app swaps its design-system theme), since
   * tokens are otherwise only re-read when something else triggers a render.
   */
  refreshTokens(): Promise<any>

  /**
   * Drills into the child level referenced by `id` (a `drilldown.series` entry).
   * Requires the Drilldown feature: import 'apexcharts/features/drilldown'.
   */
  drillDown(id: string | number): Promise<ApexCharts>

  /**
   * Navigates back one drilldown level.
   * Requires the Drilldown feature: import 'apexcharts/features/drilldown'.
   */
  drillUp(): Promise<ApexCharts>

  /**
   * Navigates back to the root drilldown level.
   * Requires the Drilldown feature: import 'apexcharts/features/drilldown'.
   */
  drillToRoot(): Promise<ApexCharts>

  /**
   * Drops levels cached from `drilldown.onDrillDown`, so the next drill re-runs
   * the resolver. Call it when the data behind an already-drilled chart
   * changes. Omit `id` to clear every level.
   * Requires the Drilldown feature.
   */
  clearDrilldownCache(id?: string | number): ApexCharts

  /** Returns the inner SVG group element containing all chart graphics. */
  getChartArea(): Element | null

  /** Returns the sum of all data points whose x value falls within [minX, maxX]. */
  getSeriesTotalXRange(minX: number, maxX: number): number[]

  /** Returns the highest y value in the specified series. */
  getHighestValueInSeries(seriesIndex?: number): number

  /** Returns the lowest y value in the specified series. */
  getLowestValueInSeries(seriesIndex?: number): number

  /** Returns the sum of each series (totals used for percentage calculations). */
  getSeriesTotal(): number[]

  /** Returns all charts in the same chart.group, including this instance. */
  getSyncedCharts(): ApexCharts[]

  /** Returns all charts in the same chart.group, excluding this instance. */
  getGroupedCharts(): ApexCharts[]

  /**
   * Returns a stable snapshot of chart state for use in formatters, events,
   * and external integrations. Prefer this over accessing chart.w directly.
   */
  getState(): ApexCharts.ChartState

  /**
   * Trellis: the panels of a trellis host in grid order (empty for a chart
   * that is not a trellis). Requires `import 'apexcharts/features/trellis'`.
   */
  getPanels(): ApexCharts.ApexTrellisPanel[]

  /**
   * Trellis: one panel's own ApexCharts instance by facet key — the escape
   * hatch to every per-chart API the trellis does not re-expose.
   */
  getPanel(key: string): ApexCharts | null

  /**
   * Trellis: expand one panel to the grid's full width (what clicking its
   * header does). No-op on a chart that is not a trellis host.
   */
  promotePanel(key: string): Promise<void>

  /**
   * Trellis: restore the grid from a panel promotion.
   */
  restorePanels(): Promise<void>

  /**
   * Calls a public method on a chart instance identified by chartID.
   * Useful when you don't have a direct reference to the instance.
   */
  static exec(chartID: string, fn: string, ...args: any[]): any

  /** Retrieves a rendered chart instance by its chart.id config value. */
  static getChartByID(chartID: string): ApexCharts | undefined

  /**
   * Trellis: imperative entry point — creates a trellis host (options must
   * carry `trellis.by`) and starts rendering it. `render()` on the returned
   * instance settles with the same in-flight mount. Returns null when the
   * trellis feature is not bundled.
   */
  static trellis(
    el: HTMLElement,
    options: ApexCharts.ApexOptions
  ): ApexCharts | null

  /**
   * Scans the document for elements with data-apexcharts and data-options
   * attributes and renders a chart in each one automatically.
   */
  static initOnLoad(): void

  /** Deep-merges source into target and returns the result. */
  static merge(target: object, source: object): object

  /**
   * Registers chart type constructors for tree-shaking support.
   * Used by sub-entry points (e.g. apexcharts/charts/bar).
   */
  static use(typeMap: Record<string, new (...args: any[]) => any>): void

  /**
   * Registers optional feature modules (Exports, Legend, Toolbar,
   * ZoomPanSelection, KeyboardNavigation, Annotations).
   * Call before rendering any chart.
   */
  static registerFeatures(featureMap: Record<string, new (...args: any[]) => any>): void

  /**
   * Sets the license key that unlocks the gated premium features (storyboard,
   * link / crossfilter, ink, measure, contextMenu, perspectives, history).
   * Without a valid key those features keep working but show an "APEXCHARTS"
   * trial watermark; a valid key removes it. Keys are shared across the
   * ApexCharts family (apexgantt, apextree, apexsankey, apex-grid-enterprise,
   * apexstock). Call before render(); the watermark is re-evaluated on every
   * render/update.
   */
  static setLicense(key: string): typeof ApexCharts

  /**
   * Registers a Weave plugin definition. Available in every bundle; the plugin
   * activates only when the Weave host is bundled and listed in a chart's
   * `plugins` config. Re-registering a name replaces the definition.
   */
  static registerPlugin(def: ApexPlugin): typeof ApexCharts

  /**
   * Removes a registered Weave plugin definition. Charts holding an active
   * instance keep it until their plugins config changes or they are destroyed.
   * Intended for tests and hot-reload flows.
   */
  static unregisterPlugin(name: string): typeof ApexCharts

  /**
   * Registers a non-SVG series renderer (Strata #2). The canvas backend
   * registers itself via `import 'apexcharts/features/renderer-canvas'`.
   */
  static registerRenderer(kind: string, factory: (w: any, ctx: any) => any): void

  /**
   * Removes a registered renderer backend; charts fall back to SVG on their
   * next render. Intended for tests and hot-reload flows.
   */
  static unregisterRenderer(kind: string): void

  /**
   * Registers a custom series type (Marks #11): a `{ renderItem }` definition
   * that draws primitives per datum. Requires the Marks feature to be bundled
   * (`import 'apexcharts/features/marks'`, included in the full bundle).
   * Once registered, reference it via `series[].type` or `chart.type`.
   * Re-registering a custom name replaces it; a built-in chart type name is
   * rejected with a console warning (the registry is global, so shadowing a
   * built-in would affect every chart on the page).
   */
  static registerSeriesType(name: string, def: ApexSeriesTypeDef): typeof ApexCharts

  /**
   * Removes a custom series type registered via registerSeriesType. Built-in
   * chart types cannot be unregistered. Intended for tests and hot-reload.
   */
  static unregisterSeriesType(name: string): typeof ApexCharts

  /**
   * Registers a named theme (Facet #13): a palette + design-token + mode bundle
   * referenceable via `theme: { name }`. Sits below explicit config and CSS
   * `--apx-*` tokens, above the built-in palette/mode defaults.
   */
  static registerTheme(name: string, def: ApexThemeDef): typeof ApexCharts

  /**
   * Removes a registered theme. Charts referencing it via `theme.name` fall
   * back to the built-in defaults on their next render. Intended for tests and
   * hot-reload flows.
   */
  static unregisterTheme(name: string): typeof ApexCharts

  /**
   * Registers a named easing (Cadence #6) referenceable via
   * `chart.animations.easing: '<name>'`, alongside the built-in curves listed
   * on `ApexEasing`. `fn` maps linear progress t in [0,1]
   * to eased progress (back/elastic curves may overshoot 1).
   */
  static registerEasing(name: string, fn: (t: number) => number): typeof ApexCharts

  /**
   * Registers a named unit-chart layout, referenceable via
   * `plotOptions.unit.positions: '<name>'` with `plotOptions.unit.layout:
   * 'custom'`.
   *
   * A layout is objects in, positions out, and knows nothing about animation:
   * the engine already tweens position, radius and colour, and already keeps
   * each mark's identity across a relayout. That is what lets an arrangement
   * the built-in layouts cannot express - a country silhouette, a hex grid, a
   * timeline, a projection supplied by ApexMaps - be a plugin rather than a
   * core change.
   */
  static registerUnitLayout(name: string, fn: ApexUnitLayout): typeof ApexCharts

  /**
   * Removes a layout registered via `registerUnitLayout`. Charts referencing it
   * by name fall back to the grouped layout on their next render.
   */
  static unregisterUnitLayout(name: string): typeof ApexCharts

  /**
   * Registers a named unit-chart MARK (pictogram), referenceable via
   * `plotOptions.unit.pictogram.mark: '<name>'`.
   *
   * The twin of `registerUnitLayout`, and the split between them is the one the
   * unit chart is built on: a LAYOUT is where the marks go, a MARK is what one
   * of them looks like. They compose freely, so neither has to know about the
   * other.
   */
  static registerUnitMark(
    name: string,
    def: string | ApexUnitMarkDef,
  ): typeof ApexCharts

  /**
   * Removes a mark registered via `registerUnitMark`. Charts referencing it by
   * name fall back to `plotOptions.unit.pictogram.fallback`.
   */
  static unregisterUnitMark(name: string): typeof ApexCharts

  /**
   * Registers a row source: given a chart's state, what rows is each of its
   * marks standing for?
   *
   * Most marks cannot answer, because an ordinary bar aggregates rows the
   * library never saw. The types that can are the ones whose series carries raw
   * observations (histogram, boxPlot, violin); their sources ship with
   * `apexcharts/features/stats`.
   *
   * The function returns a unit-chart series - one cluster per mark, one datum
   * per row - in the marks' own draw order (ascending series index, then
   * ascending category), including marks with no rows. That order is a
   * contract: the morph engine maps clusters onto the outgoing marks
   * positionally, so a compacted array sends dots out of the wrong mark.
   */
  static registerRowSource(name: string, fn: ApexRowSource): typeof ApexCharts

  /** Removes a row source registered via `registerRowSource`. */
  static unregisterRowSource(name: string): typeof ApexCharts

  /**
   * Linked Views (#4) Phase 2: get-or-create a crossfilter coordinator by id.
   * Register one shared record set; each participating chart declares a
   * dimension + reduction under `chart.link`, and selecting in one chart
   * re-aggregates the others over the filtered subset. Requires the `link`
   * feature (`import 'apexcharts/features/link'`); returns null without it.
   */
  static crossfilter(opts: { id: string; records?: any[] }): ApexCrossfilter | null

  /** Look up an existing crossfilter coordinator by id (null if none). */
  static getCrossfilter(id: string): ApexCrossfilter | null

  /**
   * Static, pure Perspectives helpers. Available once the feature is imported:
   * `import 'apexcharts/features/perspectives'`.
   */
  static perspectives: {
    decode(str: string): ApexPerspective | null
    fromURL(href?: string): ApexPerspective | null
  }

  /**
   * SSR: render a chart to an SVG string on the server. Available from the
   * `apexcharts/ssr` entry (`import ApexCharts from 'apexcharts/ssr'`).
   */
  static renderToString(
    options: ApexCharts.ApexOptions,
    ssrOptions?: { width?: number; height?: number; scale?: number },
  ): Promise<string>

  /**
   * SSR: render a chart to hydration-ready HTML (SVG wrapped in the chart
   * container). Available from the `apexcharts/ssr` entry.
   */
  static renderToHTML(
    options: ApexCharts.ApexOptions,
    ssrOptions?: {
      width?: number
      height?: number
      scale?: number
      className?: string
    },
  ): Promise<string>

  /**
   * SSR: hydrate a server-rendered chart container into a live, interactive
   * chart. Available from the `apexcharts/client` (or `apexcharts/ssr`) entry.
   */
  static hydrate(el: HTMLElement, clientOptions?: ApexCharts.ApexOptions): ApexCharts

  /**
   * SSR: hydrate every server-rendered chart container matching `selector`
   * (defaults to all ApexCharts containers on the page).
   */
  static hydrateAll(
    selector?: string,
    clientOptions?: ApexCharts.ApexOptions,
  ): ApexCharts[]

  /** SSR: whether a container has already been hydrated. */
  static isHydrated(el: HTMLElement): boolean

  exports: {
    cleanup(): string
    svgUrl(): string
    dataURI(options?: { scale?: number; width?: number }): Promise<{ imgURI: string } | { blob: Blob }>
    exportToSVG(): void
    exportToPng(): void
    exportToCSV(options?: { series?: ApexAxisChartSeries | ApexNonAxisChartSeries; fileName?: string; columnDelimiter?: string; lineDelimiter?: string }): void
    getSvgString(scale?: number): Promise<string>
    triggerDownload(href: string, filename?: string, ext?: string): void
  }

  /**
   * Perspectives (#10): serializable, shareable view state.
   * Requires the Perspectives feature: `import 'apexcharts/features/perspectives'`.
   */
  perspectives: {
    capture(): ApexPerspective
    encode(token?: ApexPerspective): string
    decode(str: string): ApexPerspective | null
    toURL(): string
    apply(token: ApexPerspective | string, opts?: { animate?: boolean; mergeOptions?: ApexCharts.ApexOptions }): void
    save(name: string): string
    list(): { id: string; name: string; token: ApexPerspective }[]
    delete(id: string): void
  }

  /**
   * Storyboard: scroll-driven chart choreography (scrollytelling). Beats are
   * prose elements paired with Perspective views; scrolling a beat across the
   * trigger line applies its view, and scrolling back reverses it.
   * Requires the Storyboard feature, which is NOT in the default bundle:
   * `import 'apexcharts/features/storyboard'` (which includes Perspectives),
   * or add `dist/features/storyboard.js` after apexcharts.js on a script-tag
   * page. Without it `chart.storyboard` is null and `.bind()` throws.
   */
  storyboard: {
    bind(opts?: ApexStoryboardBindOptions): number
    unbind(): void
    goTo(beat: number | string, opts?: { animate?: boolean }): void
    current(): { index: number; key: string | null } | null
  }

  /**
   * Rewind (#3): undo/redo history.
   * Requires the History feature (`import 'apexcharts/features/history'`) and
   * chart.history.enabled: true.
   */
  history: {
    undo(animate?: boolean): void
    redo(animate?: boolean): void
    canUndo(): boolean
    canRedo(): boolean
    jump(id: string, animate?: boolean): void
    clear(): void
    transaction(fn: () => void | Promise<any>, opts?: { label?: string }): Promise<void>
    entries(): ApexHistoryEntry[]
  }
}

interface ApexHistoryEntry {
  id: string
  label: string
  at: number
}

interface ApexViewState {
  v: number
  window: {
    xaxis: { min: number | null; max: number | null } | null
    yaxis: ({ min: number | null; max: number | null } | null)[] | null
  }
  zoomed: boolean
  collapsed: number[]
  ancillaryCollapsed: number[]
  selectedDataPoints: number[][]
  theme: { mode: string | null; palette: string | null } | null
  locale: string | null
  annotations: {
    static: any
    dynamic: { kind: string; params: any }[]
  }
  drill: { path: (string | number)[] } | null
}

interface ApexPerspective {
  v: number
  view: ApexViewState
  options?: Record<string, any>
}

// ── Storyboard: scroll-driven chart choreography ──
interface ApexStoryboardBeatInfo {
  index: number
  key: string | null
  el: Element
  direction: 'up' | 'down'
}

interface ApexStoryboardBeat {
  /** The prose element that triggers the beat (or use `selector`). */
  el?: Element
  selector?: string
  /** Author key for goTo() and events; defaults to data-apex-beat or the index. */
  key?: string
  /**
   * The view to apply: a bare ViewState object (partial is fine; a beat
   * describes the WHOLE target state, e.g. omitting `window` clears the
   * zoom), a full Perspective token, or an encoded token string.
   */
  view?: Partial<ApexViewState> | ApexPerspective | string
  /**
   * updateOptions payload merged into the SAME render as the view, so a beat
   * can restyle or swap chart.type in one animated transition (cross-type
   * morphs play out inside it). updateOptions merges, so each beat should
   * carry every option it depends on, like the view.
   */
  options?: ApexCharts.ApexOptions
  /** Text pushed to the chart's aria-live region when the beat activates. */
  announce?: string
  /** Escape hatch for arbitrary per-beat work. */
  onEnter?(chart: ApexCharts, info: ApexStoryboardBeatInfo): void
}

interface ApexStoryboardBindOptions {
  /**
   * Beats in story order. Omit to auto-discover [data-apex-beat] elements in
   * document order (data-apex-view holds an encoded token, data-apex-announce
   * an announcement).
   */
  beats?: ApexStoryboardBeat[]
  /** Custom scroll container (element or selector); default: the viewport. */
  scroller?: Element | string
  /** 0..1 fraction of the viewport height for the trigger line (default 0.5). */
  offset?: number
  /** Animate beat transitions (default true; prefers-reduced-motion wins). */
  animate?: boolean
}

// ── Weave (#1): public plugin platform ──
type ApexPluginHook =
  | 'afterParse'
  | 'afterScales'
  | 'draw'
  | 'afterUpdate'
  | 'destroy'

interface ApexPluginScales {
  x(v: number): number
  y(v: number, axis?: number): number
  domainX: [number, number]
  domainY(axis?: number): [number, number]
  gridWidth: number
  gridHeight: number
  ratios: any
}

interface ApexPluginSeries {
  name?: string
  hidden: boolean
  color?: string
  points: { x: any; y: any }[]
}

interface ApexPluginLayer {
  readonly node: SVGGElement
  path(opts: {
    d: string
    stroke?: string
    width?: number
    fill?: string
    opacity?: number
    dash?: number
    className?: string
  }): any
  line(opts: {
    x1: number
    y1: number
    x2: number
    y2: number
    stroke?: string
    width?: number
    dash?: number
  }): any
  rect(opts: {
    x?: number
    y?: number
    w?: number
    h?: number
    r?: number
    fill?: string
    stroke?: string
    opacity?: number
  }): any
  circle(opts: {
    cx?: number
    cy?: number
    r?: number
    fill?: string
    stroke?: string
  }): any
  text(opts: {
    x?: number
    y?: number
    text?: string
    color?: string
    size?: string
    anchor?: string
    weight?: string
  }): any
  clear(): ApexPluginLayer
}

interface ApexPluginPayload {
  api: ApexPluginAPI
  scales: ApexPluginScales | null
  data: ApexPluginSeries[]
  pass: 'full' | 'fast' | 'update'
  hook: ApexPluginHook
}

interface ApexPluginAPI {
  readonly name: string
  readonly version: number
  /**
   * Live: refreshed when the chart's `plugins` config changes, so
   * `updateOptions({ plugins: [{ name, options }] })` reconfigures an active
   * plugin in place. The returned object is frozen.
   */
  readonly options: Record<string, any>
  on(hook: ApexPluginHook, fn: (payload: ApexPluginPayload) => void): ApexPluginAPI
  off(hook: ApexPluginHook, fn: (payload: ApexPluginPayload) => void): ApexPluginAPI
  store: Record<string, any>
  /**
   * Call INSIDE each draw handler: plugin layers are wiped at the start of
   * every draw pass, so a handle cached across draws points at a detached node
   * and its writes vanish silently.
   */
  layer(opts?: { z?: 'front' | 'behind'; className?: string }): ApexPluginLayer
  readonly scales: ApexPluginScales | null
  readonly data: ApexPluginSeries[]
  theme: {
    readonly mode: string
    readonly foreColor: string
    seriesColor(i: number): string
    token(name: string): any
  }
  chart: Record<string, (...args: any[]) => any>
  /**
   * Fires on the chart's event bus as `plugin:<pluginName>:<name>` (namespaced
   * so a plugin can never trigger internal lifecycle subscribers). Listen with
   * `chart.addEventListener('plugin:myplugin:myevent', fn)`.
   */
  emit(name: string, detail?: any): void
  readonly el: Element
}

interface ApexPlugin {
  name: string
  apiVersion?: number
  setup(api: ApexPluginAPI): void
  destroy?(api: ApexPluginAPI): void
}

interface ApexPluginActivation {
  name: string
  options?: Record<string, any>
  order?: number
}

/**
 * Marks (#11): the per-datum primitive API passed to `renderItem`. Each call
 * emits a mark (renderer-agnostic: SVG today, canvas above `rendererThreshold`),
 * tags it with the datum identity so tooltip/selection/keyboard work, and adds
 * it to the series group. Coordinates are pixels in series space.
 */
interface ApexMarksAPI {
  path(opts: { d: string; stroke?: string; width?: number; fill?: string; opacity?: number; fillOpacity?: number; strokeOpacity?: number; dash?: number | number[]; lineCap?: string }): any
  line(opts: { x1: number; y1: number; x2: number; y2: number; stroke?: string; width?: number; dash?: number | number[] }): any
  rect(opts: { x?: number; y?: number; w?: number; h?: number; r?: number; fill?: string; stroke?: string; strokeWidth?: number; opacity?: number }): any
  circle(opts: { cx?: number; cy?: number; r?: number; fill?: string; stroke?: string; strokeWidth?: number }): any
  text(opts: { x?: number; y?: number; text?: string | string[]; anchor?: 'start' | 'middle' | 'end'; size?: number; color?: string; weight?: number | string }): any
}

/** Marks (#11): series-space scales (elGraphical-local pixels). */
interface ApexMarksScales {
  /** data x value -> pixel (numeric axes) */
  x(value: number): number
  /**
   * Resolve a datum's x pixel by index and value: numeric axes map by value,
   * categorical band axes (e.g. xaxis.tickPlacement:'between') map by index to
   * the band center. `ctx.x` is `xAt(dataPointIndex, datum.x)`.
   */
  xAt(index: number, value: any): number
  /** data y value -> pixel (optionally a specific y-axis index) */
  y(value: number, axis?: number): number
  gridWidth: number
  gridHeight: number
  /** pixel width of one x step (numeric) or one band (categorical) */
  band: number
}

/** Marks (#11): context passed to `renderItem` for one datum. */
interface ApexMarksItemContext {
  /** the raw datum from `series[].data` */
  datum: any
  /** resolved x pixel of this datum */
  x: number
  /** resolved y pixel of this datum's primary value */
  y: number
  scales: ApexMarksScales
  api: ApexMarksAPI
  seriesIndex: number
  dataPointIndex: number
  /** the series palette colour */
  color: string
}

/** Marks (#11): a custom series type definition for `registerSeriesType`. */
interface ApexSeriesTypeDef {
  /** Draw one datum by returning/emitting primitives via `ctx.api`. */
  renderItem(ctx: ApexMarksItemContext): any
  /**
   * Data shape hint. Default 'xy' (scalar y). Set 'rangeXY' when a datum's `y`
   * is a `[lo, hi]` pair (dumbbell/range mark): both bounds fold into the
   * y-axis scale and the tooltip renders "lo - hi".
   */
  dataType?: 'xy' | 'rangeXY' | 'custom'
  /**
   * Per-datum y-extent override for auto-scaling, when the drawn span is not
   * simply `y` (e.g. a bullet whose target/bands extend past the value).
   * Return the value(s) the datum occupies; the min and max fold into the
   * y-axis scale. Takes precedence over `dataType`.
   */
  yExtent?: (datum: any, dataPointIndex: number) => number | number[]
  /** Tooltip value(s) for a datum. */
  tooltip?: (datum: any) => number | number[] | string
}

declare namespace ApexCharts {
  export interface ChartState {
    // Series data — computed/parsed form used for rendering
    series: number[][] | any[]
    seriesNames: string[]
    colors: string[]
    labels: string[]
    seriesTotals: number[]
    seriesPercent: number[][]
    seriesXvalues: number[][]
    seriesYvalues: number[][]

    // Axis bounds — updated after each render
    minX: number
    maxX: number
    minY: number
    maxY: number
    minYArr: number[]
    maxYArr: number[]
    minXDiff: number
    dataPoints: number

    // Axis scale objects — computed tick/scale results
    xAxisScale: { result: number[]; niceMin: number; niceMax: number } | null
    yAxisScale: { result: number[]; niceMin: number; niceMax: number }[]
    xTickAmount: number

    // Axis type flags
    isXNumeric: boolean

    // Multi-axis series mapping
    seriesYAxisMap: number[][]
    seriesYAxisReverseMap: number[]

    // Chart dimensions — updated after each render/resize
    svgWidth: number
    svgHeight: number
    gridWidth: number
    gridHeight: number

    // Interactive state
    selectedDataPoints: number[][]
    collapsedSeriesIndices: number[]
    zoomed: boolean

    // Chart-type-specific series data (empty arrays when not applicable)
    seriesX: any[][]
    seriesZ: number[][]
    seriesCandleO: number[][]
    seriesCandleH: number[][]
    seriesCandleM: number[][]
    seriesCandleL: number[][]
    seriesCandleC: number[][]
    seriesRangeStart: number[][]
    seriesRangeEnd: number[][]
    seriesGoals: any[][]
  }

  /** A single drilldown level, referenced by a data point's `drilldown` id. */
  export interface ApexDrilldownSeries {
    /** Unique id referenced by a data point's `drilldown` field. */
    id: string | number
    /** Display name used by the breadcrumb and as the (single-series) child series name. */
    name?: string
    /** Child data points for a single-series level. Use this OR `series`. */
    data?: any[]
    /** Full multi-series array for a grouped/stacked drilldown level. Use this OR `data`. */
    series?: ApexAxisChartSeries
    /** Optional chart-type override applied when this level is shown. */
    chart?: Pick<ApexChart, 'type' | 'stacked' | 'stackType'>
    plotOptions?: ApexPlotOptions
    xaxis?: ApexXAxis
    yaxis?: ApexYAxis | ApexYAxis[]
    colors?: Array<string | ((opts: ApexColorFormatterOpts) => string)>
    /** Optional fill override (e.g. a pattern fill to visually distinguish drilled levels). */
    fill?: ApexFill
    /** Optional legend override (e.g. show a legend when a level is a pie/donut). */
    legend?: ApexLegend
  }

  /** Payload passed to drill events (`drillDownStart`, `drillDownEnd`, `drillUp`). */
  export interface ApexDrilldownEvent {
    /** The level id navigated away from. */
    from: string | number
    /** The level id navigated to (`'root'` at the top). */
    to: string | number
    /** The clicked data point (drill-down only). */
    point?: any
    seriesIndex?: number
    dataPointIndex?: number
  }

  /** Context passed to the async `onDrillDown` resolver. */
  export interface ApexDrilldownContext {
    /** The requested level id, i.e. the clicked point's `drilldown` value. */
    id: string | number | null
    point: any
    seriesIndex?: number
    dataPointIndex?: number
  }

  export interface ApexDrilldown {
    /** Master switch. When false the feature stays inert even if imported. */
    enabled?: boolean
    /** Inline child levels referenced by data-point `drilldown` ids. */
    series?: ApexDrilldownSeries[]
    breadcrumb?:
      | false
      | {
          show?: boolean
          position?: 'top-left' | 'top-right'
          separator?: string
          rootLabel?: string
          offsetX?: number
          offsetY?: number
          formatter?(label: string, opts: { index: number; depth: number }): string
        }
    animation?: {
      enabled?: boolean
      /**
       * Anchor the drill transition at the clicked point: the child unfolds
       * outward from it (and settles back on drill-up) instead of the chart
       * simply re-rendering. A gentle scale layered on the SVG. Opt-in.
       * Defaults to false.
       */
      zoomFromPoint?: boolean
      /** Base transition duration in ms when `zoomFromPoint` is true. Default 260. */
      speed?: number
    }
    /**
     * The dot marking a drillable point on a line/area chart drawn without
     * markers. A bar, slice, tile or cell is already a visible, clickable mark;
     * a line point is not, so without this nothing would show that a point can
     * be opened. Only drillable points get one. Set `show: false` to supply your
     * own affordance. Omitted colours inherit the series marker defaults.
     */
    marker?: {
      /** Default true. */
      show?: boolean
      /** Radius in px. Default 6. */
      size?: number
      /** Defaults to the series marker shape. */
      shape?: 'circle' | 'square' | 'rect'
      /** Defaults to the series colour. */
      fillColor?: string
      /** Default '#fff'. */
      strokeColor?: string
    }
    /**
     * Async resolver called when a drillable point has no inline `series` match.
     *
     * Failure never changes state: a throw, a rejection, or a resolved value
     * without a `data` array leaves the chart where it was and fires
     * `drillDownError`. A second click while one is in flight is ignored rather
     * than starting a second request.
     */
    onDrillDown?(
      ctx: ApexDrilldownContext
    ): ApexDrilldownSeries | Promise<ApexDrilldownSeries>
    /**
     * Overlay shown while an async level resolves. `text` is optional; with
     * none, the spinner shows alone and carries "Loading" as its accessible
     * name, so the default ships no user-visible English.
     */
    loading?:
      | false
      | {
          show?: boolean
          text?: string
        }
    /**
     * Cache levels resolved by `onDrillDown`, keyed by id, so drilling back down
     * a branch does not re-fetch. Default true. Clear it with the drilldown
     * module's `clearCache()` when the underlying data changes.
     */
    cache?: boolean
  }

  export interface ApexOptions {
    annotations?: ApexAnnotations
    chart?: ApexChart
    /**
     * Series colors. Each entry is either a CSS color string (hex, rgb, hsl,
     * named) or a function returning one per-datapoint. The list cycles when
     * there are more series than colors.
     */
    colors?: Array<string | ((opts: ApexColorFormatterOpts) => string)>
    dataLabels?: ApexDataLabels
    /** Opt-in drilldown navigation. Requires `import 'apexcharts/features/drilldown'`. */
    drilldown?: ApexDrilldown
    fill?: ApexFill
    forecastDataPoints?: ApexForecastDataPoints
    grid?: ApexGrid
    labels?: string[]
    legend?: ApexLegend
    markers?: ApexMarkers
    noData?: ApexNoData
    /** Weave (#1) plugin activation list. Requires `import 'apexcharts/features/weave'`. */
    plugins?: ApexPluginActivation[]
    /**
     * Trellis (small multiples / faceting): split the series into a grid of
     * pixel-aligned panels by a facet key. Requires
     * `import 'apexcharts/features/trellis'`. NOT in the default bundle; a
     * script-tag page adds `dist/features/trellis.js` after apexcharts.js.
     */
    trellis?: ApexTrellis
    plotOptions?: ApexPlotOptions
    responsive?: ApexResponsive[]
    parsing?: ApexParsing;
    series?: ApexAxisChartSeries | ApexNonAxisChartSeries
    states?: ApexStates
    stroke?: ApexStroke
    subtitle?: ApexTitleSubtitle
    theme?: ApexTheme
    title?: ApexTitleSubtitle
    tooltip?: ApexTooltip
    xaxis?: ApexXAxis
    yaxis?: ApexYAxis | ApexYAxis[]
  }

  // Re-exported sub-types — consumers can use these as:
  //   import type ApexCharts from 'apexcharts'
  //   const yaxis: ApexCharts.ApexYAxis = { ... }
  export type { ApexAnnotations }
  export type { ApexChart }
  export type { ApexEasing }
  export type { ApexDataLabels }
  export type { ApexFill }
  export type { ApexForecastDataPoints }
  export type { ApexGrid }
  export type { ApexLegend }
  export type { ApexMarkers }
  export type { ApexNoData }
  export type { ApexPlotOptions }
  export type { ApexResponsive }
  export type { ApexParsing }
  export type { ApexStates }
  export type { ApexStroke }
  export type { ApexTitleSubtitle }
  export type { ApexTheme }
  export type { ApexTooltip }
  export type { ApexXAxis }
  export type { ApexYAxis }
  export type { ApexAxisChartSeries }
  export type { ApexNonAxisChartSeries }
  export type { ApexLocale }
  export type { ApexDropShadow }
  export type { ApexChartContext }
  export type { ApexChartEventOpts }
  export type { ApexFormatterOpts }
  export type { ApexLegendFormatterOpts }
  export type { ApexColorFormatterOpts }
  export type { ApexTooltipCustomOpts }
  export type { XAxisAnnotations }
  export type { YAxisAnnotations }
  export type { PointAnnotations }
  export type { TextAnnotations }
  export type { ImageAnnotations }
  export type { ApexStoryboardBeat }
  export type { ApexStoryboardBeatInfo }
  export type { ApexStoryboardBindOptions }
  export type { ApexTrellis }
  export type { ApexTrellisPanel }
}

/**
 * Trellis (small multiples / faceting). Requires
 * `import 'apexcharts/features/trellis'`. NOT in the default bundle; a
 * script-tag page adds `dist/features/trellis.js` after apexcharts.js.
 *
 * Setting `by` makes the chart a trellis HOST: the series array is split into
 * one panel per facet-key value, every panel is a real chart of the host's
 * chart.type, and the trellis owns everything shared: the scale domains, the
 * pixel-aligned plot rectangles, the color-by-series-name map, the headers,
 * one legend, one toolbar and the responsive column count. Series WITHOUT the
 * facet key repeat in every panel (reference series).
 */
type ApexTrellis = {
  /**
   * Facet accessor: the name of a key on each series object (the blessed
   * typed field is `facet`, but any key works), or a function returning the
   * key per series.
   */
  by?: string | ((series: any, index: number) => string | number | undefined)
  /**
   * 2-D faceting: the row facet accessor. With `row`/`column` set, the grid
   * is every (row, column) combination in row-major order with a FIXED
   * column count; column labels draw once across the top, row labels once
   * down the left. Mutually exclusive with `by`. A series carrying only the
   * row key repeats across that row (a row-scoped reference series).
   */
  row?: string | ((series: any, index: number) => string | number | undefined)
  /** 2-D faceting: the column facet accessor (see `row`). */
  column?:
    | string
    | ((series: any, index: number) => string | number | undefined)
  /**
   * Missing (row, column) combinations: 'placeholder' (default) mounts a
   * real empty panel at the shared geometry with a quiet "no data" label
   * (`noData.text`); 'skip' keeps the slot with a tinted blank; 'hide'
   * keeps the slot with nothing at all.
   */
  emptyPanels?: 'placeholder' | 'skip' | 'hide'
  /**
   * Tidy-row input, an alternative to `series`: a row table pivoted by the
   * `by` / `x` / `y` / `seriesBy` COLUMN NAMES (all strings in this form).
   * Rows win over `series` when both are given. Duplicate (panel, series, x)
   * rows keep the last and warn; aggregate the rows first for sums or means.
   */
  data?: Record<string, any>[]
  /** x-value column name (tidy form only). */
  x?: string
  /** y-value column name (tidy form only). */
  y?: string
  /** Optional series-name column (tidy form only); absent means one series per panel named after `y`. */
  seriesBy?: string
  /** 'auto' (default) fits `minPanelWidth` columns into the container. */
  columns?: number | 'auto'
  /** Drives 'auto' columns and the responsive collapse. Default 220. */
  minPanelWidth?: number
  /** Gap between cells, px. Default 12. */
  gap?: number
  /** Panel width : height when no explicit height governs. Default 1.6. */
  aspectRatio?: number
  /** Explicit panel height in px; wins over aspectRatio and chart.height. */
  panelHeight?: number
  /** Panel order. Default 'first-seen'. */
  order?:
    | 'first-seen'
    | 'asc'
    | 'desc'
    | string[]
    | ((a: string, b: string) => number)
  /** Render only the first N panels (warns about the rest). */
  limit?: number
  /**
   * 'auto' (default) mounts only the panels intersecting the viewport (plus
   * one row) once the grid exceeds 64 panels; true always virtualizes; false
   * always renders eagerly. Unmounted cells keep their header and a
   * fixed-height skeleton, so page height and scroll position never shift; a
   * panel that scrolls out is destroyed with its view state stashed, and a
   * remount restores its zoom window. getPanel(key) returns null for
   * unmounted panels.
   */
  virtualize?: 'auto' | boolean
  /**
   * Scale resolution per channel. y also takes 'independent-row' /
   * 'independent-column' in a 2-D grid: one shared domain per row or column
   * (comparable along the group, free across groups). Non-shared y still
   * renders pixel-aligned panels (the gutter pass equalizes axis widths);
   * 'independent' and 'independent-column' force their own y labels on
   * every panel, 'independent-row' keeps them on the first column.
   */
  scales?: {
    x?: 'shared' | 'independent'
    y?: 'shared' | 'independent' | 'independent-row' | 'independent-column'
    color?: 'shared'
    size?: 'shared'
  }
  /** Per-cell facet headers. */
  header?: {
    show?: boolean
    formatter?: (
      key: string,
      opts: { dimension?: string; index: number; count: number }
    ) => string
    style?: { fontSize?: string; fontWeight?: string | number; color?: string }
  }
  /**
   * Axis-label policy. 'edges' (default) shows y labels on the first column
   * and x labels on each column's bottom panel; label SPACE is always
   * reserved everywhere so panels stay aligned. 'all' | 'none'.
   */
  axes?: { labels?: 'edges' | 'all' | 'none' }
  /** One legend for the grid (toggles a series name in every panel). */
  legend?: 'shared' | 'none'
  /** One zoom / pan / reset toolbar for the grid. */
  toolbar?: 'shared' | 'none'
  /**
   * 'panel' (default): tooltip card only in the hovered panel while the
   * crosshair sweeps all panels. 'sync': every panel shows its own card.
   * 'grid': ONE card near the cursor with one row per panel at the hovered x
   * (composed from the panels' own tooltips, so every formatter is honored).
   */
  tooltip?: 'panel' | 'sync' | 'grid'
  /** 'sync' (default): a zoom in any panel moves every panel. */
  zoom?: 'sync' | 'none'
  /**
   * Clicking a cell's header expands that panel to the grid's full width,
   * with an "All panels" breadcrumb back (default true). Also available as
   * chart.promotePanel(key) / chart.restorePanels().
   */
  promote?: boolean
  /**
   * Pie/donut/polarArea only: scale each panel's radius so its AREA is
   * proportional to the panel's total (default false). Equal-size pies
   * cannot encode magnitude; this is what makes a pie trellis honest.
   */
  radiusByTotal?: boolean
  /**
   * Tick-interval target for the shared nice y scale (default 3, so at most
   * ~4 labels: a small panel wears few labels well).
   */
  targetTicks?: number
  /** Per-panel option override, applied last. */
  panel?: (
    key: string,
    opts: { index: number; seriesNames: string[] }
  ) => ApexCharts.ApexOptions
}

/** One trellis panel, as returned by chart.getPanels(). */
type ApexTrellisPanel = {
  /** 'North' in 1-D; 'Sales / Q1' in a 2-D grid. */
  key: string
  index: number
  /** The panel's own ApexCharts instance (null before it mounts, and for
   *  virtualized panels currently offscreen). */
  chart: ApexCharts | null
  /** The panel's cell element (header + chart mount). */
  el: HTMLElement | null
}

type ApexDropShadow = {
  enabled?: boolean
  top?: number
  left?: number
  blur?: number
  opacity?: number
  /**
   * Shadow color. A single string applies to all series; an array applies
   * per-series (only respected by `chart.dropShadow`).
   */
  color?: string | string[]
}

/**
 * Easing for the generic tween runner (Cadence #6): data-update value
 * transitions, path morphs, marker animate. Accepts a built-in curve name
 * (the union below is the complete built-in registry), any custom name
 * registered via `ApexCharts.registerEasing` (hence the widened string), a
 * CSS-style cubic-bezier control array `[x1, y1, x2, y2]`, or a function
 * mapping linear progress t in [0,1] to eased progress (back-style curves
 * may overshoot [0,1]).
 */
type ApexEasing =
  | 'linear'
  | 'easeInSine'
  | 'easeOutSine'
  | 'easeInOutSine'
  | 'easeInQuad'
  | 'easeOutQuad'
  | 'easeInOutQuad'
  | 'easeInCubic'
  | 'easeOutCubic'
  | 'easeInOutCubic'
  | 'easeOutBack'
  | 'easeInOutBack'
  | (string & {})
  | [number, number, number, number]
  | ((t: number) => number)

/**
 * Main Chart options
 * See https://apexcharts.com/docs/options/chart/
 */
type ApexChart = {
  width?: string | number
  height?: string | number
  type?:
  | 'line'
  | 'area'
  | 'bar'
  | 'pie'
  | 'donut'
  | 'radialBar'
  | 'scatter'
  | 'bubble'
  | 'heatmap'
  | 'candlestick'
  | 'boxPlot'
  | 'violin'
  | 'histogram'
  | 'radar'
  | 'polarArea'
  | 'rangeBar'
  | 'rangeArea'
  | 'treemap'
  | 'unit'
  | 'waffle'
  | 'sunburst'
  | 'funnel'
  | 'pyramid'
  | 'gauge'
  /**
   * Internal — populated when `type` is a first-class alias (`'funnel'`,
   * `'pyramid'`, `'gauge'`, `'waffle'`, `'histogram'`). The original requested
   * type is preserved here while `type` is normalized to the underlying
   * renderer (`'bar'`, `'radialBar'` or `'unit'`). Read-only for consumers.
   */
  requestedType?: 'funnel' | 'pyramid' | 'gauge' | 'waffle' | 'histogram'
  foreColor?: string
  fontFamily?: string
  background?: string
  offsetX?: number
  offsetY?: number
  dropShadow?: ApexDropShadow & {
    enabledOnSeries?: undefined | number[]
  }
  nonce?: string
  events?: {
    animationEnd?(chart: ApexCharts, options?: ApexChartEventOpts): void
    beforeMount?(chart: ApexCharts, options?: ApexChartEventOpts): void
    mounted?(chart: ApexCharts, options?: ApexChartEventOpts): void
    updated?(chart: ApexCharts, options?: ApexChartEventOpts): void
    mouseMove?(e: MouseEvent, chart?: ApexCharts, options?: ApexChartEventOpts): void
    mouseLeave?(e: MouseEvent, chart?: ApexCharts, options?: ApexChartEventOpts): void
    click?(e: MouseEvent, chart?: ApexCharts, options?: ApexChartEventOpts): void
    xAxisLabelClick?(e: MouseEvent, chart?: ApexCharts, options?: ApexChartEventOpts): void
    legendClick?(chart: ApexCharts, seriesIndex?: number, options?: ApexChartEventOpts): void
    markerClick?(e: MouseEvent, chart?: ApexCharts, options?: ApexChartEventOpts): void
    selection?(chart: ApexCharts, options?: { xaxis?: { min: number; max: number }; yaxis?: { min: number; max: number } }): void
    dataPointSelection?(e: MouseEvent, chart?: ApexCharts, options?: ApexChartEventOpts): void
    dataPointMouseEnter?(e: MouseEvent, chart?: ApexCharts, options?: ApexChartEventOpts): void
    dataPointMouseLeave?(e: MouseEvent, chart?: ApexCharts, options?: ApexChartEventOpts): void
    beforeZoom?(chart: ApexCharts, options?: { xaxis: { min: number; max: number } }): boolean | void
    beforeResetZoom?(chart: ApexCharts, options?: ApexChartEventOpts): boolean | void
    zoomed?(chart: ApexCharts, options?: { xaxis: { min: number; max: number }; yaxis?: { min: number; max: number }[] }): void
    scrolled?(chart: ApexCharts, options?: { xaxis: { min: number; max: number } }): void
    brushScrolled?(chart: ApexCharts, options?: { xaxis: { min: number; max: number }; yaxis?: { min: number; max: number }[] }): void
    /**
     * Linked Views (#4): fired on the source chart when a brush range drives a
     * crossfilter across the group.
     */
    crossFilter?(chart: ApexCharts, options?: { xaxis: { min: number; max: number }; sourceChartID?: string }): void
    /**
     * Linked Views (#4) FILTER mode: fired on the source chart when a click
     * toggles a crossfilter bucket. `options` carries the coordinator state
     * (active filters, filtered/total counts), the source chartID, and the key.
     */
    filterChange?(chart: ApexCharts, options?: { filters: Record<string, any>; filteredCount: number; total: number; sourceChartID?: string; key?: any }): void
    /**
     * Ink Layer (#7): fired after an annotation is dragged or resized. `options`
     * carries the annotation type ('point' | 'xaxis' | 'yaxis'), id/index, and
     * the new data coordinates (x/y, plus x2/y2 for range annotations).
     */
    annotationDragged?(chart: ApexCharts, options?: { type?: 'point' | 'xaxis' | 'yaxis'; id?: string; index: number; x: any; y: any; x2?: any; y2?: any }): void
    /**
     * Ink Layer (#7): fired after a point annotation's label is edited inline.
     * `options` carries the annotation id/index and the new label text.
     */
    annotationEdited?(chart: ApexCharts, options?: { type?: 'point' | 'xaxis' | 'yaxis'; id?: string; index: number; text: string }): void
    /**
     * Ink Layer (#7): fired after an annotation is created by clicking the
     * plot in create mode or from the context menu (note or dashed line).
     * `options` carries the new annotation type/id/index and its x and/or y.
     */
    annotationCreated?(chart: ApexCharts, options?: { type?: 'point' | 'xaxis' | 'yaxis'; id?: string; index: number; x?: any; y?: any }): void
    /**
     * Ink Layer (#7): fired after an annotation is restyled from the floating
     * note editor (accent color, bold, font size, marker size/shape). `options`
     * carries the annotation type/id/index and its current label + marker config.
     */
    annotationStyled?(chart: ApexCharts, options?: { type?: 'point' | 'xaxis' | 'yaxis'; id?: string; index: number; label?: any; marker?: any }): void
    /**
     * Ink Layer (#7): fired after an annotation is deleted from the floating
     * note editor. `options` carries the annotation type/id and the index it
     * occupied before removal.
     */
    annotationDeleted?(chart: ApexCharts, options?: { type?: 'point' | 'xaxis' | 'yaxis'; id?: string; index: number }): void
    /**
     * Measure ruler (#18): fired when a measure ruler is drawn. Requires the
     * `measure` feature. `options` carries the endpoints and the deltas.
     */
    measured?(chart: ApexCharts, options?: { from: { x: any; y: any }; to: { x: any; y: any }; dx: number; dy: number; percentChange: number; slope: number }): void
    /**
     * Storyboard: fired when scrolling (or goTo) activates a beat. Requires
     * the `storyboard` feature and an active chart.storyboard.bind().
     */
    beatChange?(chart: ApexCharts, options?: ApexStoryboardBeatInfo): void
    keyDown?(e: KeyboardEvent, chart?: ApexCharts, options?: ApexChartEventOpts): void
    keyUp?(e: KeyboardEvent, chart?: ApexCharts, options?: ApexChartEventOpts): void
    /** Fired before a drill-down transition begins. Requires the Drilldown feature. */
    drillDownStart?(info: ApexCharts.ApexDrilldownEvent, chart?: ApexCharts, options?: ApexChartEventOpts): void
    /** Fired after a drill-down transition completes. Requires the Drilldown feature. */
    drillDownEnd?(info: ApexCharts.ApexDrilldownEvent, chart?: ApexCharts, options?: ApexChartEventOpts): void
    /** Fired after navigating back up a drilldown level. Requires the Drilldown feature. */
    drillUp?(info: ApexCharts.ApexDrilldownEvent, chart?: ApexCharts, options?: ApexChartEventOpts): void
    /** Fired when an async onDrillDown resolver throws or rejects. Requires the Drilldown feature. */
    drillDownError?(info: { id: string | number | null; error: any }, chart?: ApexCharts, options?: ApexChartEventOpts): void
  }
  brush?: {
    enabled?: boolean
    autoScaleYaxis?: boolean
    target?: string
    targets?: string[]
  }
  /**
   * Linked Views (#4): crossfilter / linked highlighting. Requires the `link`
   * feature (`import 'apexcharts/features/link'`). Two modes:
   *
   * HIGHLIGHT (P1): `enabled` with no `dimension`. Charts sharing a
   * `chart.group` form a set; brushing a range (needs `chart.selection.enabled`)
   * on any member dims every member's marks whose x is outside the range, in
   * place (no re-render).
   *
   * FILTER (P2): set `dimension` (its presence selects this path). Each chart
   * declares a dimension + reduction over a shared record set registered with
   * `ApexCharts.crossfilter({ id, records })`; clicking a bucket re-aggregates
   * every other participating chart over the filtered subset.
   */
  link?: {
    /** @default false */
    enabled?: boolean
    /** Highlight mode (P1) label; filter mode is selected by `dimension`. @default 'highlight' */
    mode?: 'highlight' | 'filter'
    /** Opacity applied to dimmed (unselected / out-of-range) marks. @default 0.2 */
    dimOpacity?: number
    /** FILTER mode: crossfilter coordinator id (defaults to `chart.group`). */
    id?: string
    /**
     * FILTER mode: `(row) => key`. Its presence selects filter mode. For a
     * heatmap (matrix) dimension it returns `[xKey, yKey]`.
     */
    dimension?: (row: any) => any
    /** FILTER mode: reduction over a bucket's rows. @default 'count' */
    reduce?: 'count' | { sum?: string; avg?: string; min?: string; max?: string } | ((rows: any[]) => number)
    /**
     * FILTER mode: bucket kind. Else inferred: `bins` present => 'range', a
     * heatmap chart => 'matrix' (2D), otherwise 'category'.
     */
    type?: 'category' | 'range' | 'matrix'
    /** FILTER mode (range dims): binning spec. */
    bins?: { width?: number; count?: number; thresholds?: number[] }
    /** FILTER mode (category dims): key ordering. @default 'first-seen' */
    order?: 'first-seen' | 'asc' | 'desc' | ((a: any, b: any) => number)
    /** FILTER mode (axis charts): the derived series name. @default 'Count' */
    seriesName?: string
  }
  /**
   * Ink Layer (#7): direct-manipulation annotations. When enabled, every point
   * annotation is draggable (unless it sets `draggable:false`); or opt in per
   * annotation with `annotations.points[].draggable`. Clicking an ink-managed
   * annotation opens a floating editor card anchored to it: rename inline,
   * recolor via accent swatches, toggle bold, step the font size, size/reshape
   * the marker, or delete the note. Axis-line annotations get separate Label
   * and Line color rows, so restyling the label chip never touches the stroke.
   * Requires the `ink` feature (`import 'apexcharts/features/ink'`). Fires the
   * `annotationDragged`, `annotationEdited`, `annotationStyled` and
   * `annotationDeleted` events.
   */
  ink?: {
    /** @default false */
    enabled?: boolean
    /**
     * Show a minimal "add note" tool palette; clicking it arms create mode (the
     * next plot click drops an editable, draggable annotation). @default false
     */
    palette?: boolean
    /**
     * Snap a dragged point / axis-line annotation to the nearest gridline
     * (numeric x + linear y). @default false
     */
    snap?: boolean
    /**
     * Accent swatches offered by the floating note editor. Defaults to a
     * built-in 6-color palette when omitted.
     */
    noteColors?: string[]
  }
  /**
   * Measure ruler (#18): a measure/delta ruler. Requires the `measure`
   * feature, which is NOT in the default bundle:
   * `import 'apexcharts/features/measure'`, or for a script-tag page add
   * `dist/features/measure.js` after apexcharts.js. Hold `key` and drag
   * A->B on the plot, or call `chart.startMeasure()`, to read
   * dx/dy/%change/slope in data space; on release the ruler pins as a
   * data-anchored overlay that re-projects on zoom/resize. Fires `measured`.
   */
  measure?: {
    /** @default false */
    enabled?: boolean
    /**
     * 'span': finance-style vertical band between two x-positions with a
     * change/%/range readout, endpoints snapped to the first series. 'free':
     * a diagonal ruler between two arbitrary points. @default 'span'
     */
    mode?: 'span' | 'free'
    /** Key held to arm a drag when not in sticky mode. @default 'm' */
    key?: string
    /** Pin the ruler as a data-anchored overlay on release. @default true */
    pinOnRelease?: boolean
    /**
     * Semantic colors. Every element also has a stable CSS class and a
     * direction class (apexcharts-measure-up|down|flat) for stylesheet theming.
     */
    colors?: { up?: string; down?: string; neutral?: string; guide?: string }
    /** Span mode: draw the shaded band between the two x-positions. @default true */
    band?: boolean
    /** Span mode: draw the vertical dashed reference lines. @default true */
    guides?: boolean
    /** Draw the endpoint dots on the series line. @default true */
    markers?: boolean
    /** Value formatters for the readout. */
    format?: {
      x?: (x: number) => string
      y?: (y: number) => string
      percent?: (pct: number) => string
    }
    /**
     * Full readout override. Receives the measure info and returns a string or
     * an array of lines. Overrides the default readout text.
     */
    label?: (info: {
      from: { x: any; y: any }
      to: { x: any; y: any }
      dx: number
      dy: number
      percentChange: number
      slope: number
      mode: 'span' | 'free'
    }) => string | string[]
  }
  /**
   * Radial Actions (#chrome): right-click / long-press context menu. Requires
   * the `contextMenu` feature (`import 'apexcharts/features/context-menu'`).
   * Each action receives the clicked data coordinates, so verbs act at that
   * point rather than chart-wide. 'measure' is shown only when the measure tool
   * is enabled. When the ink feature is bundled, 'annotate' drops an
   * ink-managed note that opens its floating editor (rename, restyle, delete),
   * and 'xline' / 'yline' drop ink-managed dashed lines the same way ('xline'
   * vertical at the clicked x, 'yline' horizontal at the clicked y).
   */
  contextMenu?: {
    /** @default false */
    enabled?: boolean
    /**
     * Ordered menu items: built-in ids and/or custom entries. @default
     * ['annotate','xline','yline','measure']
     */
    items?: Array<
      | 'annotate'
      | 'xline'
      | 'yline'
      | 'measure'
      | {
          id?: string
          label?: string
          icon?: string
          onClick?: (
            chart: ApexCharts,
            context: {
              x: any
              y: any
              seriesIndex: number | null
              dataPointIndex: number | null
              clientX: number
              clientY: number
            },
          ) => void
        }
    >
    /** Override the built-in item labels. */
    labels?: { annotate?: string; xline?: string; yline?: string; measure?: string }
    /** Text of the annotation dropped by the built-in 'annotate' item. @default 'Note' */
    noteText?: string
    /**
     * Shared styling for the built-in 'xline' ("Annotate here", vertical at
     * the clicked x) and 'yline' ("Mark this level", horizontal at the
     * clicked y) items. Lines only, never a range rectangle. With the ink
     * feature bundled the line opens the floating editor, whose Label and
     * Line color rows restyle the chip and the stroke independently, and is
     * draggable and undoable, like the note.
     */
    line?: {
      /** Label drawn on the line. @default '' (no label) */
      text?: string
      /** @default 4 */
      strokeDashArray?: number
      /** Line color; omit to keep the annotation default. */
      color?: string
    }
  }
  id?: string
  injectStyleSheet?: boolean
  group?: string
  /**
   * Per-chart license key for the gated premium features (storyboard, link /
   * crossfilter, ink, measure, contextMenu, perspectives, history). Overrides
   * ApexCharts.setLicense() and window.Apex.license for this chart. Without a
   * valid key those features still work but show an "APEXCHARTS" trial
   * watermark. Shared across the ApexCharts family.
   */
  license?: string
  locales?: ApexLocale[]
  defaultLocale?: string
  perspectives?: {
    serializeOptions?: string[]
  }
  history?: {
    enabled?: boolean
    maxDepth?: number
    coalesceMs?: number
    keyboard?: boolean
  }
  /** Strata (#2) series renderer. Requires `import 'apexcharts/features/renderer-canvas'` for non-SVG. */
  renderer?: 'svg' | 'canvas' | 'auto'
  rendererThreshold?: number
  layers?: {
    series?: 'svg' | 'canvas' | 'auto'
    grid?: 'svg'
    annotations?: 'svg'
    dataLabels?: 'svg'
  }
  parentHeightOffset?: number
  redrawOnParentResize?: boolean
  redrawOnWindowResize?: boolean | ((...args: any[]) => boolean)
  sparkline?: {
    enabled?: boolean
  }
  stacked?: boolean
  stackType?: 'normal' | '100%'
  stackOnlyBar?: boolean;
  /**
   * Real-time streaming mode. When enabled, appendData() bounds memory
   * automatically: each series is trimmed to `maxPoints` (when set) or to the
   * visible `xaxis.range` window plus a small off-screen runway. The
   * constant-velocity scroll animation for windowed updates needs no opt-in.
   */
  streaming?: {
    enabled?: boolean
    /** Maximum points kept per series by appendData(). Unset: derived from
     *  `xaxis.range` when that is set; otherwise no trimming occurs. */
    maxPoints?: number
  }
  toolbar?: {
    show?: boolean
    offsetX?: number
    offsetY?: number
    tools?: {
      download?: boolean | string
      selection?: boolean | string
      zoom?: boolean | string
      zoomin?: boolean | string
      zoomout?: boolean | string
      pan?: boolean | string
      reset?: boolean | string
      /**
       * Measure ruler toggle. Shown only when `chart.measure.enabled` is true
       * and the `measure` feature is bundled. `false` keeps the ruler
       * key-driven only; a string supplies a custom SVG icon.
       */
      measure?: boolean | string
      customIcons?: {
        icon?: string
        title?: string
        index?: number
        class?: string
        click?(chart: ApexCharts, options?: ApexChartEventOpts, e?: MouseEvent): void
      }[]
    }
    export?: {
      csv?: {
        filename?: undefined | string
        columnDelimiter?: string
        headerCategory?: string
        headerValue?: string
        categoryFormatter?(value?: string | number): string
        valueFormatter?(value?: string | number): string
      },
      svg?: {
        filename?: undefined | string
      }
      png?: {
        filename?: undefined | string
      }
      width?: number
      scale?: number
      /**
       * Inline the `@font-face` rules for the fonts the chart actually uses
       * into the exported SVG/PNG as base64 data URIs.
       *
       * An exported SVG is a standalone document and cannot reach the page's
       * `@font-face` rules, so without this a custom font is replaced by a
       * generic fallback in the export. Cross-origin font files that deny CORS
       * are skipped and fall back as before.
       *
       * @default true
       */
      embedFonts?: boolean
    }
    autoSelected?: 'zoom' | 'selection' | 'pan' | 'measure'
  }
  zoom?: {
    enabled?: boolean
    type?: 'x' | 'y' | 'xy'
    autoScaleYaxis?: boolean
    /**
     * Cursor-anchored zoom on mouse wheel / trackpad. `'auto'` enables it only
     * when the toolbar's reset button is present, so an unintended scroll-zoom
     * is always undoable; `true` forces it on even with the toolbar hidden.
     * Requires `enabled: true`.
     * @default 'auto'
     */
    allowMouseWheelZoom?: boolean | 'auto'
    /**
     * Momentum: enable two-finger pinch-zoom on touch devices. Zooms the x-axis
     * around the pinch centroid, frame-by-frame. `'auto'` enables it only when
     * the toolbar's reset button is present; `true` forces it on even with the
     * toolbar hidden. Requires `enabled: true`.
     * @default 'auto'
     */
    pinch?: boolean | 'auto'
    zoomedArea?: {
      fill?: {
        color?: string
        opacity?: number
      }
      stroke?: {
        color?: string
        opacity?: number
        width?: number
      }
    }
  }
  /**
   * Momentum: kinetic panning on touch. A one-finger pan released with velocity
   * keeps gliding and decelerates, clamping at the data edges.
   */
  pan?: {
    /** @default true */
    inertia?: boolean
    /** Velocity decay applied each animation frame (0-1). @default 0.92 */
    friction?: number
  }
  selection?: {
    enabled?: boolean
    type?: string
    fill?: {
      color?: string
      opacity?: number
    }
    stroke?: {
      width?: number
      color?: string
      opacity?: number
      dashArray?: number
    }
    xaxis?: {
      min?: number
      max?: number
    }
    yaxis?: {
      min?: number
      max?: number
    }
  }
  animations?: {
    /**
     * Master switch. Each chart type gets a tailored initial-mount animation
     * by default (line/area pen-stroke draw, bar grow, scatter pop, heatmap
     * diagonal wave, treemap largest-first cascade, pie/donut/gauge sweep).
     * Set false to render charts without any animation.
     */
    enabled?: boolean
    /** Animation duration in ms (default 800). */
    speed?: number
    /**
     * Cadence (#6): easing for the generic tweens. See `ApexEasing` for the
     * complete built-in curve list and the accepted forms; register custom
     * names with `ApexCharts.registerEasing`.
     * @default 'easeInOutSine'
     */
    easing?: ApexEasing
    /**
     * Drives per-element stagger across all chart types. When enabled, bars,
     * heatmap cells, scatter points, and treemap tiles reveal in sequence;
     * line/area markers fade in progressively as the line draws.
     */
    animateGradually?: {
      enabled?: boolean
      /** Requested stagger step in ms; auto-capped per chart so total
       *  stagger ≤ ~half the animation speed. */
      delay?: number
    }
    /** Data-change (updateSeries) animation. Independent from initial mount. */
    dynamicAnimation?: {
      enabled?: boolean
      speed?: number
      /**
       * Easing for data-change morphs only (same accepted forms as
       * `animations.easing`; see `ApexEasing`). Unset: inherits the
       * chart-wide easing, except detected streaming scrolls (appendData or
       * a shifted fixed-length window under `xaxis.range`) which default to
       * 'linear' so the window slides at constant velocity.
       */
      easing?: ApexEasing
    }
    /**
     * Cross-type morph (updateOptions changing chart.type). Requires the
     * optional `apexcharts/features/morph` feature to be registered; without
     * that import these settings have no effect. Supported pairs include
     * bar ↔ pie/donut/radialBar/polarArea (and the trivial pie↔donut↔polarArea
     * cases). Falls back to instant snap when types or data shape are
     * incompatible.
     */
    chartTypeMorph?: {
      enabled?: boolean
      speed?: number
    }
    /**
     * When true (default), honors the OS-level prefers-reduced-motion media
     * query — all initial-mount animations are skipped and the chart renders
     * instantly. Set to false to override (e.g. for QA / demo screens).
     */
    respectReducedMotion?: boolean
    /**
     * Above this many data points (default 1000), the per-element morph +
     * stagger — which spins up one JS-driven animation timeline per path — is
     * replaced by a single GPU-composited opacity fade of the whole series.
     * Keeps initial render and zoom transitions smooth on large datasets
     * (e.g. thousands of candlesticks/bars). Set to 0 to always animate
     * per-element regardless of dataset size.
     */
    largeDatasetThreshold?: number
  }
  accessibility?: {
    enabled?: boolean
    description?: string
    announcements?: {
      enabled?: boolean
    }
    keyboard?: {
      enabled?: boolean
      navigation?: {
        enabled?: boolean
        wrapAround?: boolean
      }
    }
  }
  dataReducer?: {
    enabled?: boolean
    algorithm?: 'lttb'
    targetPoints?: number
    threshold?: number
  }
}

type ApexStates = {
  hover?: {
    filter?: {
      type?: 'none' | 'lighten' | 'darken'
      /**
       * Blend strength toward white (lighten) or black (darken), from 0 to 1.
       * Higher means a stronger effect. The shift is proportional to the base
       * color's head-room, so already-light colors are lightened only slightly
       * (and already-dark colors darkened only slightly) and never wash out.
       * @default 0.15
       */
      value?: number
    }
  }
  active?: {
    allowMultipleDataPointsSelection?: boolean
    filter?: {
      type?: 'none' | 'lighten' | 'darken'
      /**
       * Blend strength toward white (lighten) or black (darken), from 0 to 1.
       * Higher means a stronger effect.
       * @default 0.35
       */
      value?: number
    }
  }
}

/**
 * Chart Title options
 * See https://apexcharts.com/docs/options/title/
 */
type ApexTitleSubtitle = {
  text?: string
  align?: 'left' | 'center' | 'right'
  margin?: number
  offsetX?: number
  offsetY?: number
  floating?: boolean
  style?: {
    fontSize?: string
    fontFamily?: string
    fontWeight?: string | number
    color?: string
  }
}

/**
 * Chart Series options.
 * See https://apexcharts.com/docs/options/series/
 */
/**
 * One node of a `children` hierarchy, as read by the partition charts
 * (`treemap`, `sunburst`). A branch may omit its own value and take the sum of
 * its children; a leaf supplies one.
 */
type ApexHierarchyNode = {
  /** The node's label. `name` is accepted as an alias. */
  x?: string | number
  name?: string
  /** The node's value. `value` is accepted as an alias. */
  y?: number | null
  value?: number | null
  color?: string
  fillColor?: string
  /** A second metric driving colour rather than size (`treemap`). */
  colorValue?: number
  meta?: unknown
  children?: ApexHierarchyNode[]
  /**
   * The `id` of a `drilldown.series` entry, read as another level by the
   * sunburst (and by the treemap with
   * `plotOptions.treemap.nested.drilldownAsLevels`).
   */
  drilldown?: string | number
}

type ApexAxisChartSeries = {
 name?: string
 type?: string
 color?: string
 group?: string
 hidden?: boolean
 zIndex?: number
 parsing?: ApexParsing;
 /**
  * Trellis facet key: which panel this series belongs to. The blessed typed
  * field for `trellis.by: 'facet'`; any other key name works from plain JS,
  * and the `trellis.by` function form works from either.
  */
 facet?: string | number
 data:
 | (number | null)[]
 | {
   /**
    * A category label, a timestamp, or a `Date`. On a `datetime` axis all
    * three keep millisecond resolution.
    */
   x: string | number | Date;
   /**
    * A plain value for most charts. For `candlestick`/`boxPlot`, the
    * summary array (`[O,H,L,C]` / `[min,Q1,median,Q3,max]`). For `violin`, an
    * object carrying the precomputed density profile (`[value, weight]` pairs)
    * plus the raw observations rendered as jitter. For a `scatter` strip plot
    * (`plotOptions.scatter.jitter`), the array of observations in this category.
    */
   y:
     | number
     | null
     | number[]
     | { density: [number, number][]; points?: number[] };
   /**
    * Optional raw observations for a `boxPlot` data point, rendered as jitter
    * dots when `plotOptions.boxPlot.points.show` is enabled.
    */
   points?: number[];
   fill?: ApexFill;
   fillColor?: string;
   strokeColor?: string;
   meta?: unknown;
   /**
    * A second metric that drives this point's COLOUR, independent of `y`
    * which drives its size (`treemap`). See
    * `plotOptions.treemap.colorScale.colorValue` to read a different key.
    */
   colorValue?: number;
   /**
    * Nested hierarchy: this point is a branch containing these children,
    * to any depth (`treemap`, `sunburst`). A branch normally omits `y` and
    * takes the sum of its children instead.
    */
   children?: ApexHierarchyNode[];
   /**
    * Drilldown target: the `id` of a `drilldown.series` entry. Clicking this
    * point drills into that level. Requires the Drilldown feature.
    */
   drilldown?: string | number;
   goals?: {
     name?: string,
     value: number,
     strokeHeight?: number;
     strokeWidth?: number;
     strokeColor?: string;
     strokeDashArray?: number;
     strokeLineCap?: 'butt' | 'square' | 'round'
   }[];
   barHeightOffset?: number;
   columnWidthOffset?: number;
 }[]
 | [number, number | null][]
 | [number, (number | null)[]][]
 | number[][]
 // A `children` hierarchy for the partition charts, where a branch carries no
 // value of its own. Listed before the catch-all so authors get completion on
 // the node shape instead of falling through to `Record<string, any>`.
 | ApexHierarchyNode[]
 | Record<string, any>[];
}[]

type ApexNonAxisChartSeries =
  | number[]
  | ApexAxisChartSeries

/**
 * Options for the line drawn on line and area charts.
 * See https://apexcharts.com/docs/options/stroke/
 */
type ApexStroke = {
  show?: boolean
  curve?: 'smooth' | 'straight' | 'stepline' | 'linestep' | 'monotoneCubic' | ('smooth' | 'straight' | 'stepline' | 'linestep' | 'monotoneCubic')[]
  lineCap?: 'butt' | 'square' | 'round'
  colors?: string[]
  width?: number | number[]
  dashArray?: number | number[]
  fill?: ApexFill
}

type ApexAnnotations = {
  yaxis?: YAxisAnnotations[]
  xaxis?: XAxisAnnotations[]
  points?: PointAnnotations[]
  texts?: TextAnnotations[]
  images?: ImageAnnotations[]
}

type AnnotationLabel = {
  borderColor?: string
  borderWidth?: number
  borderRadius?: number
  text?: string | string[]
  textAnchor?: string
  offsetX?: number
  offsetY?: number
  style?: AnnotationStyle
  position?: string
  orientation?: string
  mouseEnter?: (annotation: AnnotationLabel, e: MouseEvent) => void
  mouseLeave?: (annotation: AnnotationLabel, e: MouseEvent) => void
  click?: (annotation: AnnotationLabel, e: MouseEvent) => void
}

type AnnotationStyle = {
  background?: string
  color?: string
  fontFamily?: string
  fontWeight?: string | number
  fontSize?: string
  cssClass?: string
  padding?: {
    left?: number
    right?: number
    top?: number
    bottom?: number
  }
}

type XAxisAnnotations = {
  /**
   * Trellis (#22): which panels this annotation draws in. Absent or
   * 'trellis' means every panel (projected through each panel's own scale);
   * a panel key or list of keys limits it to those panels. Ignored outside a
   * trellis host.
   */
  scope?: 'trellis' | string | string[]
  id?: number | string
  x?: null | number | string
  x2?: null | number | string
  strokeDashArray?: number
  fillColor?: string
  borderColor?: string
  borderWidth?: number
  opacity?: number
  offsetX?: number
  offsetY?: number
  label?: AnnotationLabel
  /**
   * Ink Layer (#7): make this annotation draggable (a line moves along x; a
   * range moves as a whole or resizes from an edge). Requires the `ink` feature.
   */
  draggable?: boolean
}

type YAxisAnnotations = {
  /**
   * Trellis (#22): which panels this annotation draws in. Absent or
   * 'trellis' means every panel (projected through each panel's own scale);
   * a panel key or list of keys limits it to those panels. Ignored outside a
   * trellis host.
   */
  scope?: 'trellis' | string | string[]
  id?: number | string
  y?: null | number | string
  y2?: null | number | string
  strokeDashArray?: number
  fillColor?: string
  borderColor?: string
  borderWidth?: number
  opacity?: number
  offsetX?: number
  offsetY?: number
  width?: number | string
  yAxisIndex?: number
  label?: AnnotationLabel
  /**
   * Ink Layer (#7): make this annotation draggable (a line moves along y; a
   * range moves as a whole). Requires the `ink` feature.
   */
  draggable?: boolean
}

type PointAnnotations = {
  /**
   * Trellis (#22): which panels this annotation draws in. Absent or
   * 'trellis' means every panel (projected through each panel's own scale);
   * a panel key or list of keys limits it to those panels. Ignored outside a
   * trellis host.
   */
  scope?: 'trellis' | string | string[]
  id?: number | string
  x?: number | string
  y?: null | number
  yAxisIndex?: number
  seriesIndex?: number
  /**
   * Ink Layer (#7): make this point annotation draggable. Overrides
   * `chart.ink.enabled`. Requires the `ink` feature.
   */
  draggable?: boolean
  mouseEnter?: (annotation: PointAnnotations, e: MouseEvent) => void
  mouseLeave?: (annotation: PointAnnotations, e: MouseEvent) => void
  click?: (annotation: PointAnnotations, e: MouseEvent) => void
  marker?: {
    size?: number
    fillColor?: string
    strokeColor?: string
    strokeWidth?: number
    shape?: string
    offsetX?: number
    offsetY?: number
    cssClass?: string
  }
  label?: AnnotationLabel
  image?: {
    path?: string
    width?: number
    height?: number
    offsetX?: number
    offsetY?: number
  }
  /**
   * Show a hover tooltip over the annotation marker, like a regular data
   * point. Useful for surfacing more detail than fits in the label.
   */
  tooltip?: {
    enabled?: boolean
    /**
     * Static tooltip content (HTML allowed; an array is joined with line
     * breaks). Falls back to `label.text` when omitted.
     */
    text?: string | string[]
    /**
     * Returns the tooltip markup (HTML). Takes precedence over `text`.
     */
    formatter?: (opts: {
      annotation: PointAnnotations
      seriesIndex?: number
      id?: number | string
      w: any
    }) => string
    /**
     * Tooltip theme. Falls back to the global `tooltip.theme`.
     */
    theme?: 'light' | 'dark'
    offsetX?: number
    offsetY?: number
  }
  /**
   * Render arbitrary SVG markup at the annotation's position. Deprecated in
   * favor of `image`/`marker`, but still supported.
   */
  customSVG?: {
    SVG?: string
    cssClass?: string
    offsetX?: number
    offsetY?: number
  }
}


type TextAnnotations = {
  x?: number
  y?: number
  text?: string
  textAnchor?: string
  foreColor?: string
  fontSize?: string | number
  fontFamily?: undefined | string
  fontWeight?: string | number
  /** CSS selector for the parent element the text is appended to. */
  appendTo?: string
  backgroundColor?: string
  borderColor?: string
  borderRadius?: number
  borderWidth?: number
  paddingLeft?: number
  paddingRight?: number
  paddingTop?: number
  paddingBottom?: number
}

type ImageAnnotations = {
  path?: string
  x?: number,
  y?: number,
  width?: number,
  height?: number,
}

/**
 * Options for localization.
 * See https://apexcharts.com/docs/options/chart/locales
 */
type ApexLocale = {
  name?: string
  options?: {
    months?: string[]
    shortMonths?: string[]
    days?: string[]
    shortDays?: string[]
    toolbar?: {
      download?: string
      selection?: string
      selectionZoom?: string
      zoomIn?: string
      zoomOut?: string
      pan?: string
      reset?: string
      measure?: string
      menu?: string
      exportToSVG?: string
      exportToPNG?: string
      exportToCSV?: string
    }
  }
}

/**
 * PlotOptions for specifying chart-type-specific configuration.
 * See https://apexcharts.com/docs/options/plotoptions/bar/
 */
/**
 * Continuous colour legend: a gradient strip with end labels and a hover
 * indicator arrow, in place of the categorical legend. Available on the chart
 * types that encode a value as colour through a `colorScale` (heatmap,
 * treemap). The strip is drawn from whichever scale the chart itself uses, so
 * the legend always matches the marks.
 */
type ApexGradientLegend = {
  enabled?: boolean
  /**
   * Strip length for horizontal placements (top/bottom). Accepts a number
   * (pixels) or percentage string (e.g. `'70%'`, resolved against the chart's
   * SVG width). Default `'70%'`.
   */
  width?: number | string
  /**
   * Strip length for vertical placements (left/right). Accepts a number
   * (pixels) or percentage string (e.g. `'70%'`, resolved against the chart's
   * SVG height). Default `'70%'`.
   */
  height?: number | string
  /** Strip thickness (short axis) in pixels. Default 12. */
  thickness?: number
  /**
   * Strip alignment within the legend area.
   * - top/bottom: 'start' = left, 'center', 'end' = right
   * - left/right: 'start' = top,  'center', 'end' = bottom
   * Default `'center'`.
   */
  align?: 'start' | 'center' | 'end'
  /**
   * Number of color stops sampled from the shade function when no explicit
   * `ranges` (or continuous scale) supply their own. Default 16.
   */
  stops?: number
  /** Show min/max labels at the ends of the strip. Default true. */
  showLabels?: boolean
  /** Show a value tooltip next to the arrow on mark hover. Default true. */
  showHoverValue?: boolean
  labelStyle?: {
    fontSize?: string
    fontFamily?: string
    colors?: string
  }
  arrow?: {
    size?: number
    color?: string
  }
  /** Formatter for min/max labels and the hover value tooltip. */
  formatter?(value: number): string
}

/**
 * Navigation breadcrumb chrome, shared by every chart that lets the reader move
 * into a hierarchy: the drilldown feature, and the treemap's click-to-zoom.
 * `drilldown.breadcrumb` is the shared block; a chart may override it locally
 * (see `plotOptions.treemap.zoom.breadcrumb`).
 */
type ApexBreadcrumb = {
  show?: boolean
  position?: 'top-left' | 'top-right'
  separator?: string
  /** Label of the leftmost crumb, the "everything" level. Default 'All'. */
  rootLabel?: string
  offsetX?: number
  offsetY?: number
  formatter?(
    label: string,
    opts: { index: number; depth: number; data?: any },
  ): string
}

/**
 * Styling for one level of a nested treemap. `plotOptions.treemap.parents` is
 * the base for every level; `plotOptions.treemap.levels[depth]` overrides it
 * for one depth.
 */
type ApexTreemapLevel = {
  /** Inset between a parent's edge and the children inside it. Default 4. */
  padding?: number
  /** Container fill. Defaults to a neutral tint that deepens with each level. */
  fill?: string
  fillOpacity?: number
  borderColor?: string
  borderWidth?: number
  /** Falls back to `plotOptions.treemap.borderRadius`. */
  borderRadius?: number
  hover?: {
    /** Outline the container on hover. Default true. */
    show?: boolean
    color?: string
    width?: number
  }
  header?: {
    show?: boolean
    /** Height of the strip reserved at the top of the container. Default 22. */
    height?: number
    /**
     * Skip the strip on tiles narrower than this, where no name could be read
     * anyway. Default 40.
     */
    minWidth?: number
    align?: 'left' | 'center' | 'right'
    offsetX?: number
    offsetY?: number
    /** Append the branch's aggregate to its name. Default false. */
    showValue?: boolean
    formatter?(
      name: string,
      opts: {
        value: number
        depth: number
        seriesIndex: number
        node: any
        w: any
      },
    ): string
    style?: {
      fontSize?: string
      fontFamily?: string
      fontWeight?: number | string
      color?: string
      background?: string
      cssClass?: string
    }
  }
}

type ApexPlotOptions = {
  line?: {
    isSlopeChart?: boolean
    colors?: {
      threshold?: number,
      colorAboveThreshold?: string,
      colorBelowThreshold?: string,
    },
  }
  area?: {
    fillTo?: 'origin' | 'end'
  }
  bar?: {
    horizontal?: boolean
    columnWidth?: string | number;
    barHeight?: string | number;
    distributed?: boolean
    borderRadius?: number;
    borderRadiusApplication?: 'around' | 'end';
    hideZeroBarsWhenGrouped?: boolean
    rangeBarOverlap?: boolean
    rangeBarGroupRows?: boolean
    isDumbbell?: boolean;
    dumbbellColors?: string[][];
    isFunnel?: boolean;
    isFunnel3d?: boolean;
    colors?: {
      ranges?: {
        from?: number
        to?: number
        color?: string
      }[]
      backgroundBarColors?: string[]
      backgroundBarOpacity?: number
      backgroundBarRadius?: number
    }
    dataLabels?: {
      maxItems?: number
      hideOverflowingLabels?: boolean
      position?: string
      orientation?: 'horizontal' | 'vertical',
      total?: {
        enabled?: boolean,
        formatter?(val?: string, opts?: ApexFormatterOpts): string,
        offsetX?: number,
        offsetY?: number,
        style?: {
          color?: string,
          fontSize?: string,
          fontFamily?: string,
          fontWeight?: number | string
        }
      }
    }
  }
  bubble?: {
    zScaling?: boolean
    minBubbleRadius?: number
    maxBubbleRadius?: number
    /**
     * Explicit z window for the size scale. Expands the data's own z extent,
     * never clamps it, so several bubble charts can share one size scale
     * (a trellis pushes the union extent through these).
     */
    minZ?: number
    maxZ?: number
  }
  scatter?: {
    /**
     * Spread overlapping points apart ("jitter"). Two uses, one engine:
     *  - Strip plot: supply data as `{ x: 'Category', y: [v1, v2, ...] }`. Each
     *    category becomes a band and the values scatter horizontally within it.
     *  - Overplotting: ordinary `{ x, y }` points get a small random offset so
     *    dense clusters fan out. The underlying data (and tooltip values) stay
     *    exact; only the drawn position moves.
     * Offsets are in axis units and deterministic (stable across re-renders).
     */
    jitter?: {
      enabled?: boolean
      /** Max ± horizontal offset, in x-axis units (1 = one category step). */
      x?: number
      /** Max ± vertical offset, in y-axis units. */
      y?: number
      /** Single series: colour each band differently (by its position). */
      distributed?: boolean
      /** Per-band cap; values beyond this are stride-thinned. */
      maxPoints?: number
    }
  }
  candlestick?: {
    type?: string,
    colors?: {
      upward?: string | string[]
      downward?: string | string[]
    }
    wick?: {
      useFillColor?: boolean
    }
  }
  boxPlot?: {
    colors?: {
      upper?: string | string[]
      lower?: string | string[]
    }
    /**
     * Where the whiskers reach when the summary is DERIVED from raw
     * observations: a datum supplying `points` instead of a five-number `y`,
     * which requires `import 'apexcharts/features/stats'`. A precomputed
     * summary is drawn exactly as given and ignores this.
     *
     * `'minmax'` (default) reaches the extremes, so nothing is hidden.
     * `'tukey'` stops at the last observation within 1.5 * IQR of each
     * quartile; points beyond the fence then fall outside the whisker, so pair
     * it with `points.show` or they become invisible.
     */
    whiskers?: 'minmax' | 'tukey'
    /**
     * Individual observations ("jitter") overlaid on each box. Inert unless a
     * data point supplies a `points: number[]` array; `show` is false by
     * default so existing boxPlot charts are unchanged.
     *
     * `points` is also the sample the five-number summary is derived from when
     * a datum has no `y` (see `whiskers`), so the observations live in one
     * place whether the library summarises them or you do.
     */
    points?: {
      show?: boolean
      shape?: 'circle' | 'square'
      /** Marker radius in pixels. */
      size?: number
      /** 0..1 fraction of the box half-width to scatter within. */
      jitter?: number
      /** Cap per box; observations beyond this are stride-thinned. */
      maxPoints?: number
      opacity?: number
      /**
       * Dot fill colour. Defaults to 'series-dark' (a darker shade of the
       * series colour). Use 'series' for the series colour, or any literal
       * colour string.
       */
      fillColor?: string
      /** Colour of the outline around each dot. Defaults to '#fff'. */
      strokeColor?: string
      /** Width of the dot's outline in pixels. Defaults to 1; 0 disables it. */
      strokeWidth?: number
      /**
       * Colour each dot by its value along a colour ramp (overrides fillColor).
       * Points are bucketed into `steps` shades to keep rendering performant.
       */
      colorScale?: {
        colors: string[]
        min?: number
        max?: number
        steps?: number
      }
    }
  }
  violin?: {
    /**
     * Multiplies the density-derived half-width. 1 maps the density's own
     * maxWeight to half the category slot.
     */
    bandwidthScale?: number
    /**
     * Kernel density estimation, used only when the density is DERIVED from raw
     * observations: a datum supplying `points`, or a flat number array as `y`,
     * which requires `import 'apexcharts/features/stats'`. A precomputed
     * density profile is drawn exactly as given.
     */
    kde?: {
      /**
       * Kernel width in value units. Unset uses Silverman's rule of thumb.
       * This is the statistical parameter; `bandwidthScale` above only scales
       * the drawn width.
       */
      bandwidth?: number
      /** Density samples per violin (default 64). */
      resolution?: number
    }
    /**
     * 'individual' (default): each violin is scaled to its own peak density, so
     * all violins reach the full slot width. 'group': all violins share the
     * densest violin's scale, keeping widths proportional to density across
     * categories.
     */
    normalize?: 'individual' | 'group'
    /** Individual observations ("jitter") overlaid on the violin shape. */
    points?: {
      show?: boolean
      shape?: 'circle' | 'square'
      /** Marker radius in pixels. */
      size?: number
      /** 0..1 fraction of the half-width to scatter within. */
      jitter?: number
      /** Clamp jitter to the density width at each value so points stay inside. */
      constrainToViolin?: boolean
      /** Cap per violin; observations beyond this are stride-thinned. */
      maxPoints?: number
      opacity?: number
      /**
       * Dot fill colour. Defaults to 'series-dark' (a darker shade of each
       * violin's own colour). Use 'series' for the violin's colour as-is, or
       * any literal colour string (e.g. '#fff').
       */
      fillColor?: string
      /** Colour of the ring/outline around each dot. Defaults to '#fff'. */
      strokeColor?: string
      /** Width of the dot's outline in pixels. Defaults to 1; 0 disables it. */
      strokeWidth?: number
      /**
       * Colour each dot by its value along a colour ramp (overrides fillColor).
       * Points are bucketed into `steps` shades to keep rendering performant.
       */
      colorScale?: {
        /** Hex colour stops, low → high (a sequential colour ramp). */
        colors: string[]
        /** Value mapped to the first stop. Defaults to the data minimum. */
        min?: number
        /** Value mapped to the last stop. Defaults to the data maximum. */
        max?: number
        /** Number of shade buckets. Defaults to 24. */
        steps?: number
      }
    }
  }
  /**
   * `chart.type: 'histogram'`. The series carry raw observations (a flat
   * number array, or `{ y }` objects) and are binned into one column per bin;
   * all series share one set of edges so overlaid distributions stay
   * comparable.
   *
   * Requires the optional stats feature. Import from `apexcharts/histogram`,
   * or add `import 'apexcharts/features/stats'` alongside `apexcharts/bar`.
   * The default `apexcharts` bundle already includes it. Without it the chart
   * warns and draws nothing, rather than rendering one bar per observation.
   */
  histogram?: {
    /**
     * How the bin width is chosen: a rule name, or a fixed bin count.
     * `'auto'` takes the narrower of Freedman-Diaconis and Sturges, falling
     * back to Sturges when the IQR is 0.
     */
    bins?: 'auto' | 'fd' | 'sturges' | 'scott' | 'rice' | 'sqrt' | number
    /**
     * Explicit bin width in value units. Wins over `bins`, for when the
     * boundaries carry meaning (decades, 5-minute buckets) rather than being
     * a statistical choice.
     */
    binWidth?: number
    /** `[min, max]` to bin over instead of the data's own extent. */
    range?: number[]
    /**
     * y units: observations per bin, percent of the series total, or
     * `count / (n * binWidth)` so the total area is 1.
     */
    normalize?: 'count' | 'relative' | 'density'
    /** Running total across bins, i.e. a cumulative distribution. */
    cumulative?: boolean
    /**
     * With more than one series, draw every distribution across the full bin
     * so they overlay, instead of dividing the bin between them. Defaults to
     * `true`: all series already share one set of edges, and comparing two
     * shapes is the reason to put them on one axis. Set `false` for
     * side-by-side bars.
     *
     * An overlay also softens the fill and drops the bin separator stroke, so
     * the overlapping region reads. Both remain overridable.
     */
    overlap?: boolean
  }
  heatmap?: {
    radius?: number
    enableShades?: boolean
    shadeIntensity?: number
    reverseNegativeShade?: boolean
    distributed?: boolean
    useFillColorAsStroke?: boolean
    colorScale?: {
      ranges?: {
        from?: number
        to?: number
        color?: string
        foreColor?: string
        name?: string
      }[]
      inverse?: boolean
      min?: number
      max?: number
      /**
       * When enabled, replaces the default categorical legend with a
       * continuous color gradient strip and a hover indicator arrow that
       * tracks the currently hovered mark's value along the spectrum.
       * Follows `legend.position` (top / right / bottom / left); the arrow
       * orientation flips to point at the strip from the chart-facing side.
       */
      gradientLegend?: ApexGradientLegend
    }
  }
  funnel?: {
    /**
     * 'rectangle' (default) preserves the existing centered-rectangle funnel
     * geometry. 'trapezoid' produces continuous sloped sides between
     * consecutive stages (each stage's bottom width matches the next stage's
     * top width).
     */
    shape?: 'rectangle' | 'trapezoid'
    /**
     * For `shape: 'trapezoid'` only — last stage's bottom edge:
     * 'flat' (default, parallel sides) or 'taper' (taper to a point).
     */
    lastShape?: 'flat' | 'taper'
  }
  treemap?: {
    enableShades?: boolean
    shadeIntensity?: number
    distributed?: boolean
    reverseNegativeShade?: boolean
    useFillColorAsStroke?: boolean
    dataLabels?: {
      format?: 'scale' | 'truncate'
      /**
       * Skip a tile's label when it would render below this size in px.
       *
       * With `format: 'scale'` the font size follows the tile's area, so a
       * dense treemap asks for a lot of text only a few pixels tall. Each such
       * label still has to be built and measured against the DOM, which on a
       * large chart dominates the render. Default 4, below the smallest label
       * any bundled sample draws. Set 0 to label every tile regardless.
       */
      minFontSize?: number
    }
    borderRadius?: number
    colorScale?: {
      inverse?: boolean
      ranges?: {
        from?: number
        to?: number
        color?: string
        foreColor?: string
        name?: string
      }[];
      min?: number
      max?: number
      /**
       * Colour a tile by a SECOND metric, independent of the value that sizes
       * it: area is how big something is, colour is how it did. Reads
       * `datum.colorValue` by default; pass a key name to read a different
       * property, or an accessor to compute one.
       */
      colorValue?:
        | string
        | ((
            datum: any,
            opts: { seriesIndex: number; dataPointIndex: number; w: any },
          ) => number)
      /**
       * Continuous interpolation between colour stops, for the metric above.
       * Active as soon as any datum carries a colour metric; `enabled: false`
       * opts out and `true` forces it on. `ranges` is unaffected and still
       * applies wherever it is set.
       */
      gradient?: {
        enabled?: boolean
        /** Domain low. Defaults to the extent of the colour metric. */
        min?: number
        /** Domain high. Defaults to the extent of the colour metric. */
        max?: number
        /**
         * The value the middle colour is pinned to. Defaults to 0 when the
         * domain straddles zero (a diverging metric), otherwise none. Pass
         * `null` to force a plain sequential ramp.
         */
        midpoint?: number | null
        /**
         * With a midpoint, balance the domain around it so equal moves in
         * either direction read as equally saturated. Default true.
         */
        symmetric?: boolean
        /** Low -> mid -> high. Two colours make a sequential ramp. */
        colors?: string[]
        /** Explicit stops; overrides `colors` and `midpoint`. */
        stops?: { value: number; color: string }[]
      }
      /**
       * Continuous colour legend for the metric above: a gradient strip with
       * end labels and a hover indicator, in place of the categorical legend.
       */
      gradientLegend?: ApexGradientLegend
    };
    /**
     * Arbitrary-depth treemap. A datum may carry `children` to any depth;
     * every branch is drawn as a real container with a header strip and its
     * children inset below it.
     */
    nested?: {
      /**
       * Parent containers appear on their own as soon as the data is nested.
       * `false` forces the flat two-level layout.
       */
      enabled?: boolean
      /**
       * Read `drilldown: '<id>'` ids as extra levels instead of as a click
       * target for the drilldown feature. Default false, because on a treemap
       * that id has always meant "descend on click".
       */
      drilldownAsLevels?: boolean
    }
    /**
     * How a branch is drawn once the data is nested. Per-level overrides go in
     * `levels`.
     */
    parents?: ApexTreemapLevel & {
      /** `'auto'` (default): on when the data carries `children`. */
      show?: boolean | 'auto'
      tooltip?: {
        formatter?(opts: {
          name: string
          value: number
          depth: number
          leafCount: number
          percentOfParent: number
          percentOfTotal: number
          node: any
          w: any
        }): string
      }
    }
    /**
     * Per-depth overrides of `parents`, indexed from the outermost group
     * actually drawn (0 = the series, or the first authored level when a
     * single series is unwrapped).
     */
    levels?: ApexTreemapLevel[]
    /**
     * Click a group to fill the canvas with it; a breadcrumb goes back.
     *
     * Ignored when the drilldown feature is active on the same chart: both
     * navigate the hierarchy, and drilldown owns the click there.
     */
    zoom?: {
      enabled?: boolean
      /**
       * Overrides `drilldown.breadcrumb` for this chart only, so a zoomed
       * treemap and a drilled-in chart present the same affordance without
       * importing the drilldown feature.
       */
      breadcrumb?: ApexBreadcrumb
    }
    seriesTitle?: {
      show?: boolean,
      offsetY?: number,
      offsetX?: number,
      borderColor?: string,
      borderWidth?: number,
      borderRadius?: number,
      style?: {
        background?: string,
        color?: string,
        fontSize?: string,
        fontFamily?: string,
        fontWeight?: number | string,
        cssClass?: string,
        padding?: {
          left?: number,
          right?: number,
          top?: number,
          bottom?: number,
        },
      },
    }
  }
  unit?: {
    /**
     * 'grouped' (default): each category is its own cluster, laid out in a row.
     * 'packed': one blob; categories are coloured and (with sortByGroup) ordered
     * smallest-first so the minority group nests in the centre.
     * 'columns': each category is a vertical bar built from stacked dots (a unit
     * / waffle column) whose height encodes the count.
     * 'grid': one lattice of cells filled in category order - a waffle /
     * part-to-whole square "pie" (`chart.type: 'waffle'` presets this layout).
     * 'scatter': beeswarm - each unit placed on a real numeric X value axis by
     * its per-unit value, laned by category on Y (draws its own axis + lanes).
     * 'arc': parliament / hemicycle - seats in concentric arced rows, filled in
     * category order so each category is a contiguous wedge (see `arc`).
     * 'custom': positions come from `positions`.
     */
    layout?:
      | 'grouped'
      | 'packed'
      | 'columns'
      | 'grid'
      | 'scatter'
      | 'arc'
      | 'custom'
    /**
     * `layout: 'custom'` only. The layout provider: either a function returning
     * plot-pixel positions, or the name of one registered with
     * `ApexCharts.registerUnitLayout`.
     *
     * A layout is objects in, positions out. It knows nothing about animation,
     * because the engine already tweens position, radius and colour and already
     * keeps each mark's identity across a relayout, so an arrangement the
     * built-in layouts cannot express needs no new transition code.
     *
     * A mark whose id the provider omits animates out; ids matching no mark are
     * ignored.
     */
    positions?: string | ApexUnitLayout
    /**
     * How dots are matched between renders on an update (which previous dot a
     * new dot tweens from).
     * 'group' (default): keyed per category, so a dot stays in its group and
     * category-level enters/exits fade in and out.
     * 'flow': keyed by global draw order, so the anonymous crowd migrates (and
     * recolours) across a regroup - the circles-to-bars transition.
     * 'identity': keyed by each datum's `id`/`name`, so a SPECIFIC unit migrates
     * across any regroup or relayout keeping its colour and size. Requires the
     * per-unit object form with unique ids/names.
     */
    transition?: 'group' | 'flow' | 'identity'
    /**
     * What ONE unit looks like. Independent of `layout`, which is where the
     * units go, so `positions: heart` with `shape: 'pictogram'` arranges glyphs
     * into a heart and every other pairing is equally valid.
     *
     * `'image'` fetches a raster / multi-colour icon; `'pictogram'` draws a
     * vector glyph (see `pictogram`) and is the one that scales to thousands
     * of units.
     */
    shape?: 'circle' | 'square' | 'image' | 'pictogram'
    /**
     * `shape: 'pictogram'`. A glyph is drawn as one `<path>` per unit, filled
     * in that unit's own colour - no request, no decode, and no recolour
     * filter.
     *
     * There is deliberately no size: a glyph is fitted to the box the dot
     * itself would have occupied, so `size` and `spacing` size a pictogram
     * exactly as they size a dot.
     */
    pictogram?: {
      /**
       * The glyph: a registered name, a `{path, viewBox?, fillRule?}` object,
       * raw path data, or an array (one per series). A datum's own `mark`
       * overrides all of it, so one crowd can mix glyphs.
       */
      mark?: ApexUnitMarkRef | ApexUnitMarkRef[]
      /** Which side of the glyph binds to the dot's box. */
      fit?: 'contain' | 'width' | 'height'
      /** Nudge for glyphs that read light. */
      scale?: number
      /** 0..0.9 of the pitch, opening the lattice up. */
      padding?: number
      /** Drawn when a mark cannot be resolved. */
      fallback?: 'circle' | 'square'
    }
    /** Icon used when `shape: 'image'`. */
    image?: {
      /** Icon URL or data URI. */
      src?: string
      width?: number
      height?: number
      /**
       * Recolour a monochrome icon to the category colour (or a per-unit
       * `fillColor`) so the pictogram matches the legend. Leave off (default)
       * for multi-colour icons that should keep their own colours.
       */
      tint?: boolean
    }
    /** Dot radius in px, or 'auto' to size dots so the largest cluster fits. */
    size?: number | 'auto'
    /**
     * The 'columns' layout can size its dots independently of `size` (which the
     * circle layouts / storyboard beats often pin to a constant so dots do not
     * resize while migrating).
     */
    columns?: {
      /**
       * 'inherit' (default) uses `size`; 'auto' sizes dots to fill the plot
       * height; a number pins a columns-only size. Circle / square only (image
       * icons keep their intrinsic size).
       */
      size?: 'inherit' | 'auto' | number
    }
    /**
     * The 'grid' (waffle) layout: one lattice of cells filled in category order.
     */
    grid?: {
      /** Cells per row. Defaults to 10. */
      columns?: number
      /**
       * Fixed cell budget (e.g. 100 for a percentage waffle); largest-remainder
       * allocates the cells to categories. Leave undefined for one cell per unit
       * (respects unitValue / maxUnits).
       */
      total?: number
      /** First row of the fill: 'bottom' (default) or 'top'. */
      fillFrom?: 'bottom' | 'top'
      /**
       * Small multiples: render ONE mini-waffle per category in a trellis
       * instead of a single shared lattice. Each tile has `total` cells
       * (default 100) and fills value/`max` of them; the rest show as a faint
       * `trackColor` backdrop, and each tile carries its own label.
       */
      split?: boolean
      /** Small-multiple tiles per row; undefined = auto (near-square). */
      tileColumns?: number
      /**
       * Small-multiple value -> filled-cell denominator; undefined = the largest
       * count (leader fills its tile). Set to 100 for true "of 100" percentage tiles.
       */
      max?: number
      /** Small-multiple empty ("track") cell colour; undefined = neutral grey. */
      trackColor?: string
    }
    /**
     * The 'scatter' layout places units on real value axes (needs the object-form
     * data). `y:'lanes'` (default) is a beeswarm (X value axis, Y category lane);
     * `y:'value'` is a 2D value-value scatter (each datum's `x`/`y` on two numeric
     * axes, category = colour). `sizeRange` turns dots into bubbles.
     */
    scatter?: {
      /** 'lanes' (beeswarm, default) or 'value' (2D value-value scatter). */
      y?: 'lanes' | 'value'
      /** 'swarm' (anti-overlap pack, default) or 'jitter' (random lane spread). */
      spread?: 'swarm' | 'jitter'
      /**
       * Beeswarm orientation (1D `y:'lanes'` mode only). 'horizontal' (default):
       * value on X, category lanes stacked on Y. 'vertical': value on Y,
       * category lanes as columns across X. The value-axis config keys
       * (`xMin`/`xMax`/`xTitle`/`xFormatter`/`tickAmount`) describe the value
       * axis in both orientations.
       */
      orientation?: 'horizontal' | 'vertical'
      /** Approximate number of value-axis ticks. Defaults to 5. */
      tickAmount?: number
      /** Fixed X-axis min / max; undefined = derived (nice-numbered) from data. */
      xMin?: number
      xMax?: number
      /** X-axis title drawn under the tick labels. */
      xTitle?: string
      /** X tick-label formatter, `(value) => string`. */
      xFormatter?: (value: number) => string
      /** Approximate number of Y-axis ticks (2D mode). Defaults to 5. */
      yTickAmount?: number
      /** Fixed Y-axis min / max (2D mode); undefined = nice-numbered from data. */
      yMin?: number
      yMax?: number
      /** Y-axis title (2D mode), drawn rotated at the left. */
      yTitle?: string
      /** Y tick-label formatter, `(value) => string`. */
      yFormatter?: (value: number) => string
      /** Datum key holding the bubble size value. Defaults to 'z'. */
      sizeField?: string
      /** `[minRadius, maxRadius]` in px: turns dots into area-scaled bubbles. */
      sizeRange?: [number, number]
      /** Left-gutter width reserved for lane (category) labels (lanes mode). */
      laneLabelWidth?: number
      /** Draw the faint gridlines. Defaults to true. */
      gridlines?: boolean
    }
    /**
     * Opt-in bubble sizing: scale each dot's radius by its per-unit value
     * (requires the object-form data, `series: [{ data: [{ value }] }]`).
     * Circle shape only; the lattice is spaced for the largest bubble so dots
     * never overlap. Ignored when there are no per-unit values.
     */
    sizeByValue?: {
      enabled?: boolean
      /** Radius (px) for the largest value, or 'auto' to fit it to the plot. */
      maxRadius?: number | 'auto'
      /** Radius (px) for the smallest value; defaults to ~35% of maxRadius. */
      minRadius?: number
      /** 'area' (bubble area proportional to value) or 'linear'. */
      scale?: 'area' | 'linear'
    }
    /** Packing gap factor between spiral shells (1 = dots touch). */
    spacing?: number
    /**
     * How marks move between layouts on an update, and where entering marks
     * come from.
     */
    gather?: {
      /**
       * 'spring' settles each mark on a damped spring, so a gather interrupted
       * by the next update carries the marks' velocity into it instead of
       * restarting them from a standstill. 'tween' runs the fixed-duration
       * `easing` below. 'auto' (the default) is spring, unless `easing` was set
       * to something other than the default.
       */
      motion?: 'auto' | 'spring' | 'tween'
      /**
       * Spring character (`motion: 'spring'` only): 'crisp' (default),
       * 'gentle' (softer, for large reflows) or 'snappy' (faster, a hint of
       * settle). Scaled by `chart.animations.speed`.
       */
      spring?: 'crisp' | 'gentle' | 'snappy'
      /** Tween curve: 'outCubic' (default: decelerate and stop), 'inOutCubic' (weighted start), or 'outBack' (overshoot + settle). Setting this implies `motion: 'tween'`. */
      easing?: 'outCubic' | 'inOutCubic' | 'outBack'
      /** Overshoot strength for `easing: 'outBack'`. Defaults to 1.70158 (~10% overshoot). */
      overshoot?: number
      /**
       * Where an ENTERING mark animates from (fresh mount, or a category
       * appearing): 'burst' (default) flies out from the cluster centre,
       * 'fade' materialises in place, 'rise' fades in while drifting gently
       * up into its slot.
       */
      enter?: 'burst' | 'fade' | 'rise'
    }
    /**
     * Options for `layout: 'arc'` (parliament / hemicycle). Angles use the
     * radialBar convention: 0 = top, clockwise. The default sweep is a top
     * semicircle; a full circle is `startAngle: 0, endAngle: 360`.
     */
    arc?: {
      /** Sweep start angle in degrees (0 = top, clockwise). Default -90. */
      startAngle?: number
      /** Sweep end angle in degrees. Default 90 (a top semicircle). */
      endAngle?: number
      /** Donut hole: inner radius as a fraction of the outer radius. Default 0.4. */
      innerRadiusRatio?: number
      /** Number of concentric seat rows, or 'auto' to size dots as large as fit. */
      rows?: number | 'auto'
    }
    /** Corner radius for shape:'square'. */
    borderRadius?: number
    /** 1 dot represents this many units of value (waffle scaling). */
    unitValue?: number
    /** Safety cap on total dots; counts scale down proportionally above it. */
    maxUnits?: number
    /** Packed layout: order categories smallest-first (minority centred). */
    sortByGroup?: boolean
    clusterLabels?: {
      show?: boolean
      /** Label placement relative to the cluster/bar. Defaults to 'top'. A 'bottom' label is always straight (the curved arc rides the top crown only). */
      position?: 'top' | 'bottom'
      curved?: boolean
      fontSize?: string
      fontFamily?: string
      fontWeight?: number | string
      /** Defaults to the cluster's own colour when undefined. */
      color?: string
      offsetY?: number
      /** Return "\n"-separated text to split an outer label over several lines. */
      formatter?(
        name: string,
        opts: { seriesIndex: number; value: number; percent: number; w: any }
      ): string
      /**
       * Outer (name) labels, as pie/donut draw them: the label sits in the margin
       * beside the shape and a leader line joins it to the colour band it names,
       * so the crowd can be read without a legend.
       *
       * `layout: 'custom'` only, and best on a silhouette whose categories stack
       * vertically (the default row ordering): those alternate down the left and
       * right gutters. A column-ordered shape sends each label to the side its own
       * band sits on. The margin is taken off both sides so the shape stays
       * centred, so turning this on makes the silhouette a little smaller.
       */
      external?: {
        show?: boolean
        connector?: {
          show?: boolean
          width?: number
          /** Defaults to the band's own colour when undefined. */
          color?: string
          /** Air between the band's outermost dot and the leader line's bend. */
          gap?: number
          /** Length of the run out to the label. */
          length?: number
        }
        offsetX?: number
        offsetY?: number
      }
    }
    /** Per-unit (per-dot) tooltip. */
    tooltip?: {
      /**
       * Return the tooltip body for a single hovered dot. The dot's category is
       * `seriesIndex` and its index within that category is `dataPointIndex`, so
       * the formatter can index into per-unit data. Return a string or HTML.
       * Defaults to `"#<dataPointIndex+1> of <count>"`.
       */
      formatter?(opts: {
        seriesName: string
        seriesIndex: number
        dataPointIndex: number
        /** Number of dots drawn for this category (after unitValue + maxUnits). */
        count: number
        /** Raw category value (before unitValue scaling). */
        value: number
        unitValue: number
        /**
         * This dot's own datum when the per-unit object form was supplied
         * (`series: [{ name, data: [...] }]`); otherwise undefined.
         */
        datum: any
        color: string
        w: any
      }): string
    }
  }
  pie?: {
    startAngle?: number
    endAngle?: number
    customScale?: number
    offsetX?: number
    offsetY?: number
    expandOnClick?: boolean
    /**
     * How far a clicked slice slides out of the pie (px), measured along its
     * own mid-angle. The slice is translated, not redrawn at a bigger radius,
     * so its shape is unchanged and a gap opens between it and the rest of the
     * pie. Defaults to 10. Ignored for polarArea, and in a drilldown pie/donut
     * where a slice click navigates instead. Set 0 to keep the slice in place
     * on click.
     */
    expandOffset?: number
    /**
     * Hover outline: a translucent band traced just outside the rim of the
     * hovered slice, so the slice keeps its own colour instead of being
     * lightened. Takes the place of the `states.hover` filter for pie, donut
     * and polarArea, and is skipped when `states.hover.filter.type` is
     * `'none'`.
     */
    hoverOutline?: {
      show?: boolean
      /** Band thickness in px. Defaults to 8. */
      size?: number
      /**
       * Extra clearance between the slice rim and the band, in px, on top of
       * the slice stroke (the band always starts at the outer edge of the
       * stroke, never under it). Defaults to 0, since a stroke is normally
       * present and already reads as the separation.
       */
      gap?: number
      /** Band opacity over the slice colour. Defaults to 0.3. */
      opacity?: number
      /** Band colour. Defaults to the hovered slice's colour. */
      color?: string
    }
    /**
     * Rounds the corners of each slice (in px). Applies to pie, donut and
     * polarArea. Defaults to 0 (sharp corners). The value is clamped per
     * slice so corner fillets never cross on thin or narrow slices.
     */
    borderRadius?: number
    /**
     * Gap between adjacent slices (in px). Applies to pie, donut and
     * polarArea. Defaults to 0 (slices touch). Each slice is inset
     * symmetrically, so its mid-angle (data label and hit region) is kept.
     */
    spacing?: number
    dataLabels?: {
      offset?: number
      minAngleToShowLabel?: number
      /**
       * External (outer) labels: render the category/series name outside the
       * slice, joined by a leader (connector) line, so the chart is readable
       * without the legend. The percentage keeps rendering inside the slice.
       * Applies to pie and donut only (ignored for polarArea, where the radial
       * length already encodes the value).
       */
      external?: {
        show?: boolean
        offsetX?: number
        offsetY?: number
        fontSize?: string
        fontFamily?: string
        fontWeight?: string | number
        color?: string
        /**
         * Return a string for a single-line label, or an array of strings to
         * stack multiple lines (e.g. `[name, percent + '%']`).
         */
        formatter?(
          name: string,
          opts: {
            seriesIndex: number
            percent: number
            value: number
            w: ApexChartContext
          }
        ): string | string[]
        /** Leader line from the slice edge to the label. */
        connector?: {
          show?: boolean
          width?: number
          color?: string
          length?: number
          gap?: number
        }
      }
    }
    donut?: {
      size?: string
      background?: string
      labels?: {
        show?: boolean
        name?: {
          show?: boolean
          fontSize?: string
          fontFamily?: string
          fontWeight?: string | number
          color?: string
          offsetY?: number,
          formatter?(val: string): string
        }
        value?: {
          show?: boolean
          fontSize?: string
          fontFamily?: string
          fontWeight?: string | number
          color?: string
          offsetY?: number
          formatter?(val: number | string): string
        }
        total?: {
          show?: boolean
          showAlways?: boolean
          fontFamily?: string
          fontWeight?: string | number
          fontSize?: string
          label?: string
          color?: string
          formatter?(w: ApexChartContext): string
        }
      }
    }
  }
  polarArea?: {
    rings?: {
      strokeWidth?: number
      strokeColor?: string
    }
    spokes?: {
      strokeWidth?: number;
      connectorColors?: string | string[];
    };
  }
  /**
   * Sunburst / nested pie-donut (hierarchical radial). Rings go from the centre
   * hole outward, one per hierarchy level; each child arc is nested inside its
   * parent's angular wedge. Accepts a native `children` hierarchy or an existing
   * `drilldown` config (adapter).
   */
  sunburst?: {
    offsetX?: number
    offsetY?: number
    startAngle?: number
    endAngle?: number
    /** Centre hole radius as a % of the max radius (e.g. '15%'). */
    innerSize?: string
    /** Corner rounding of each arc (px), same semantics as pie borderRadius. */
    borderRadius?: number
    /** Gap between adjacent arcs (px), same semantics as pie spacing. */
    spacing?: number
    /** Draw a shallow branch's leaf to the rim ('extend') or stop it ('stop'). */
    leaf?: 'extend' | 'stop'
    /** Angular partition of a parent's wedge among its children. */
    partition?: 'normalize' | 'strict'
    /** Per-depth lightening of the parent colour (0 = same, 1 = white). */
    tint?: number
    /** Click a wedge to zoom into its branch (breadcrumb to go back). Default true. */
    zoomOnClick?: boolean
    dataLabels?: {
      show?: boolean
      /** Hide the label on any arc narrower than this (degrees). */
      minAngleToShow?: number
      style?: {
        fontSize?: string
        fontFamily?: string
        fontWeight?: string | number
        colors?: string[]
      }
    }
  }
  radar?: {
    size?: number
    offsetX?: number
    offsetY?: number
    polygons?: {
      strokeColors?: string | string[]
      strokeWidth?: number | number[] | string | string[]
      connectorColors?: string | string[]
      fill?: {
        colors?: string[]
      }
    }
  }
  radialBar?: {
    inverseOrder?: boolean
    startAngle?: number
    endAngle?: number
    offsetX?: number
    offsetY?: number
    /**
     * Gauge sub-shape. 'arc' (default) renders the existing filled value-arc
     * gauge; 'needle' replaces the value-arc with a rotating pointer/needle.
     * Bands and ticks are independent and work for both shapes.
     */
    shape?: 'arc' | 'needle'
    /**
     * Value-to-angle mapping (gauge). Defaults: min: 0, max: 100. Override
     * for gauges with a custom domain (e.g. min: 0, max: 240 speedometer).
     */
    min?: number
    max?: number
    /**
     * Threshold bands rendered as colored arc segments along the gauge arc.
     * Each band spans [`from`, `to`] in the gauge's `min..max` domain and is
     * filled with `color`.
     */
    bands?: Array<{
      from: number
      to: number
      color: string
      label?: string
    }>
    bandsStyle?: {
      strokeWidth?: string
      gap?: number
      hideTrackWhenPresent?: boolean
      linecap?: 'butt' | 'round' | 'square'
    }
    ticks?: {
      show?: boolean
      major?: {
        count?: number
        length?: number
        width?: number
        color?: string
        placement?: 'inside' | 'outside'
      }
      minor?: {
        count?: number
        length?: number
        width?: number
        color?: string
        placement?: 'inside' | 'outside'
      }
      labels?: {
        show?: boolean
        offset?: number
        fontSize?: string
        fontFamily?: string
        fontWeight?: string | number
        color?: string
        formatter?: (value: number) => string
      }
    }
    needle?: {
      color?: string
      length?: string | number
      baseWidth?: number
      tipWidth?: number
      /**
       * When true, also render the filled value-arc alongside the needle.
       * Default false preserves needle-only behavior.
       */
      showValueArc?: boolean
      /**
       * px offset from the geometric arc center on Y. Positive values push
       * the needle base down (toward the chord midpoint of a ∩-shape
       * gauge); negative pushes up. The needle rotates around this shifted
       * point.
       */
      offsetY?: number
      animation?: {
        enabled?: boolean
        duration?: number
        easing?: string
      }
    }
    hollow?: {
      margin?: number
      size?: string
      background?: string
      image?: string
      imageWidth?: number
      imageHeight?: number
      imageOffsetX?: number
      imageOffsetY?: number
      imageClipped?: boolean
      position?: 'front' | 'back'
      /**
       * Optional stroke color around the hollow ring. Combined with
       * `strokeDasharray` this produces a dashed indicator circle around
       * the value text.
       */
      stroke?: string
      strokeWidth?: number
      strokeDasharray?: string | number
      dropShadow?: ApexDropShadow
    }
    track?: {
      show?: boolean
      startAngle?: number
      endAngle?: number
      background?: string | string[]
      strokeWidth?: string
      opacity?: number
      margin?: number
      dropShadow?: ApexDropShadow
    }
    dataLabels?: {
      show?: boolean
      name?: {
        show?: boolean
        fontFamily?: string
        fontWeight?: string | number
        fontSize?: string
        color?: string
        offsetY?: number
        formatter?(seriesName: string): string
      }
      value?: {
        show?: boolean
        fontFamily?: string
        fontSize?: string
        fontWeight?: string | number
        color?: string
        offsetY?: number
        formatter?(val: number): string
      }
      total?: {
        show?: boolean
        label?: string
        color?: string
        fontFamily?: string
        fontWeight?: string | number
        fontSize?: string
        formatter?(w: ApexChartContext): string
      }
    }
    barLabels?: {
      enabled?: boolean
      offsetX?: number
      offsetY?: number
      useSeriesColors?: boolean
      fontFamily?: string
      fontWeight?: string | number
      fontSize?: string
      formatter?: (barName: string, opts?: ApexFormatterOpts) => string
      onClick?: (barName: string, opts?: ApexFormatterOpts) => void
    }
  }
}

type ApexColorStop = {
  offset: number
  color: string
  opacity: number
}

type ApexFill = {
  colors?: string[]
  opacity?: number | number[]
  type?: string | string[]
  gradient?: {
    shade?: string
    type?: string
    shadeIntensity?: number
    gradientToColors?: string[]
    inverseColors?: boolean
    opacityFrom?: number | number[]
    opacityTo?: number | number[]
    stops?: number[],
    colorStops?: ApexColorStop[][] | ApexColorStop[]
  }
  image?: {
    src?: string | string[]
    width?: number
    height?: number
  }
  pattern?: {
    style?: string | string[]
    width?: number
    height?: number
    strokeWidth?: number
  }
}

/**
 * Chart Legend configuration options.
 * See https://apexcharts.com/docs/options/legend/
 */
type ApexLegend = {
  show?: boolean
  showForSingleSeries?: boolean
  showForNullSeries?: boolean
  showForZeroSeries?: boolean
  floating?: boolean
  inverseOrder?: boolean
  position?: 'top' | 'right' | 'bottom' | 'left'
  horizontalAlign?: 'left' | 'center' | 'right'
  fontSize?: string
  fontFamily?: string
  fontWeight?: string | number
  width?: number
  height?: number
  offsetX?: number
  offsetY?: number
  formatter?(legendName: string, opts?: ApexLegendFormatterOpts): string
  tooltipHoverFormatter?(legendName: string, opts?: ApexLegendFormatterOpts): string
  customLegendItems?: string[]
  clusterGroupedSeries?: boolean;
  clusterGroupedSeriesOrientation?: 'vertical' | 'horizontal';
  labels?: {
    colors?: string | string[]
    useSeriesColors?: boolean
  }
  markers?: {
    size?: number
    strokeWidth?: number
    fillColors?: string[]
    shape?: ApexMarkerShape
    offsetX?: number
    offsetY?: number
    customHTML?(): string
    onClick?(e: MouseEvent): void
  }
  itemMargin?: {
    horizontal?: number
    vertical?: number
  }
  onItemClick?: {
    toggleDataSeries?: boolean
  }
  onItemHover?: {
    highlightDataSeries?: boolean
  }
}

type MarkerShapeOptions = "circle" | "square" | "rect" | "line" | 'cross' | 'plus' | 'star' | 'sparkle' | 'diamond' | 'triangle'

type ApexMarkerShape = MarkerShapeOptions | MarkerShapeOptions[]

type ApexDiscretePoint = {
  seriesIndex?: number
  dataPointIndex?: number
  fillColor?: string
  strokeColor?: string
  size?: number
  shape?: ApexMarkerShape
}

type ApexMarkers = {
  size?: number | number[]
  colors?: string | string[]
  strokeColors?: string | string[]
  strokeWidth?: number | number[]
  strokeOpacity?: number | number[]
  strokeDashArray?: number | number[]
  fillOpacity?: number | number[]
  discrete?: ApexDiscretePoint[]
  shape?: ApexMarkerShape
  offsetX?: number
  offsetY?: number
  showNullDataPoints?: boolean
  onClick?(e?: MouseEvent): void
  onDblClick?(e?: MouseEvent): void
  hover?: {
    size?: number
    sizeOffset?: number
  }
  /**
   * Opt-in (default 0 = off). Above this many points in a series, that series'
   * markers are drawn as one path element per marker size (a subpath per point)
   * instead of one element per point, which cuts the cost of a marker-heavy
   * render several-fold. Also covers the markers `showNullDataPoints` adds to
   * isolated points, which is what makes a null-heavy series slow even at
   * `size: 0`. Not pixel-identical: overlapping markers are rasterized as one
   * region and lose their individual outlines, so dense clusters read flatter.
   * Only applies where markers are already non-interactive and uniform, and
   * such a series has no `.apexcharts-marker` nodes.
   */
  largeDatasetThreshold?: number
}

type ApexNoData = {
  text?: string
  align?: 'left' | 'right' | 'center'
  verticalAlign?: 'top' | 'middle' | 'bottom'
  offsetX?: number
  offsetY?: number
  style?: {
    color?: string
    fontSize?: string
    fontFamily?: string
  }
}

type ApexParsing = {
  x?: string;
  y?: string | string[];
  z?: string;
}

/**
 * Chart Datalabels options
 * See https://apexcharts.com/docs/options/datalabels/
 */
type ApexDataLabels = {
  enabled?: boolean
  enabledOnSeries?: undefined | number[]
  textAnchor?: 'start' | 'middle' | 'end'
  distributed?: boolean
  /**
   * Horizontal offset of the label. Pass a function to vary the offset per
   * data point, e.g. to separate labels that would otherwise overlap.
   * The function must be pure, as it may be called more than once per label.
   */
  offsetX?: number | ((opts: ApexFormatterOpts) => number)
  /**
   * Vertical offset of the label. Pass a function to vary the offset per
   * data point, e.g. to separate labels that would otherwise overlap.
   * The function must be pure, as it may be called more than once per label.
   */
  offsetY?: number | ((opts: ApexFormatterOpts) => number)
  style?: {
    fontSize?: string
    fontFamily?: string
    fontWeight?: string | number
    colors?: string[]
  }
  background?: {
    enabled?: boolean
    foreColor?: string
    backgroundColor?: string
    borderRadius?: number
    padding?: number
    opacity?: number
    borderWidth?: number
    borderColor?: string
    dropShadow?: ApexDropShadow
  }
  dropShadow?: ApexDropShadow
  /**
   * Ride data labels to their new position on a data-change update instead of
   * snapping. On by default, so labels reflow on the same clock as the bars,
   * markers and axis ticks. Bar/column only; speed and easing follow
   * chart.animations.dynamicAnimation.
   */
  animate?: {
    enabled?: boolean
  }
  /**
   * Count the numeric value up/down from its previous value on update, like
   * countUp.js. Off by default. The formatter runs each frame so number
   * formatting is preserved. Bar/column only.
   */
  countUp?: {
    enabled?: boolean
  }
  formatter?(val: string | number | number[], opts?: ApexFormatterOpts): string | number | (string | number)[]
}

type ApexResponsive = {
  breakpoint?: number
  options?: ApexCharts.ApexOptions
}

type ApexTooltipY = {
  title?: {
    formatter?(seriesName: string, opts?: ApexFormatterOpts): string
  }
  formatter?(val: number, opts?: ApexFormatterOpts): string
}

/**
 * Chart Tooltip options
 * See https://apexcharts.com/docs/options/tooltip/
 */
type ApexTooltip = {
  enabled?: boolean
  enabledOnSeries?: undefined | number[]
  shared?: boolean
  followCursor?: boolean
  intersect?: boolean
  inverseOrder?: boolean
  arrow?: boolean
  /**
   * One tight line instead of a card: the x label sits inline before the
   * value, the marker goes, the padding and font shrink. Meant for panels a
   * normal card would cover (small multiples, sparklines, dashboard tiles).
   * A single-series chart also drops the series-name label; with several
   * series the names stay, since they are what tells the rows apart.
   * @default false
   */
  compact?: boolean
  custom?:
    | ((opts: ApexTooltipCustomOpts) => string | number | Element | { nodeName: string })
    | Array<(opts: ApexTooltipCustomOpts) => string | number | Element | { nodeName: string }>
  fillSeriesColor?: boolean
  theme?: 'light' | 'dark'
  cssClass?: string
  hideEmptySeries?: boolean
  style?: {
    fontSize?: string
    fontFamily?: string
    background?: string
  }
  onDatasetHover?: {
    highlightDataSeries?: boolean
  }
  x?: {
    show?: boolean
    format?: string
    formatter?(val: string | number, opts?: ApexFormatterOpts): string
  }
  y?: ApexTooltipY | ApexTooltipY[]
  z?: {
    title?: string
    formatter?(val: number): string
  }
  marker?: {
    show?: boolean
    fillColors?: string[]
  }
  items?: {
    display?: string
  }
  fixed?: {
    enabled?: boolean
    position?: string // topRight; topLeft; bottomRight; bottomLeft
    offsetX?: number
    offsetY?: number
  }
}

/**
 * X Axis options
 * See https://apexcharts.com/docs/options/xaxis/
 */
type ApexXAxis = {
  type?: 'category' | 'datetime' | 'numeric'
  /**
   * X-axis category labels. Pass a flat array for a single row of labels,
   * or a 2-D array (`[group, label][]`) to render grouped category axes.
   */
  categories?: Array<string | number> | Array<Array<string | number>>;
  overwriteCategories?: number[] | string[] | undefined;
  offsetX?: number;
  offsetY?: number;
  sorted?: boolean;
  labels?: {
    show?: boolean
    rotate?: number
    rotateAlways?: boolean
    hideOverlappingLabels?: boolean
    showDuplicates?: boolean
    trim?: boolean
    minHeight?: number
    maxHeight?: number
    style?: {
      colors?: string | string[]
      fontSize?: string
      fontFamily?: string
      fontWeight?: string | number
      cssClass?: string
    }
    offsetX?: number
    offsetY?: number
    format?: string
    formatter?(value: string | number, timestamp?: number, opts?: ApexFormatterOpts): string | string[]
    datetimeUTC?: boolean
    datetimeFormatter?: {
      year?: string
      month?: string
      day?: string
      hour?: string
      minute?: string
      second?: string
    }
  }
  group?: {
    groups?: { title: string, cols: number }[],
    style?: {
      colors?: string | string[]
      fontSize?: string
      fontFamily?: string
      fontWeight?: string | number
      cssClass?: string
    }
  }
  axisBorder?: {
    show?: boolean
    color?: string
    width?: string | number
    height?: number
    offsetX?: number
    offsetY?: number
  }
  axisTicks?: {
    show?: boolean
    borderType?: 'solid' | 'dotted' | 'dashed'
    color?: string
    height?: number
    offsetX?: number
    offsetY?: number
  }
  tickPlacement?: string
  tickAmount?: number | 'dataPoints'
  stepSize?: number
  min?: number
  max?: number
  range?: number
  floating?: boolean
  decimalsInFloat?: number
  position?: string
  title?: {
    text?: string
    offsetX?: number
    offsetY?: number
    style?: {
      color?: string
      fontFamily?: string
      fontWeight?: string | number
      fontSize?: string
      cssClass?: string
    }
  }
  crosshairs?: {
    show?: boolean
    width?: number | string
    position?: string
    opacity?: number
    stroke?: {
      color?: string
      width?: number
      dashArray?: number
    }
    fill?: {
      type?: string
      color?: string
      gradient?: {
        colorFrom?: string
        colorTo?: string
        stops?: number[]
        opacityFrom?: number
        opacityTo?: number
      }
    }
    dropShadow?: ApexDropShadow
  }
  tooltip?: {
    enabled?: boolean
    offsetY?: number
    formatter?(value: string | number, opts?: ApexFormatterOpts): string
    style?: {
      fontSize?: string
      fontFamily?: string
    }
  }
}

/**
 * Y Axis options
 * See https://apexcharts.com/docs/options/yaxis/
 */

type ApexYAxis = {
  show?: boolean
  showAlways?: boolean
  showForNullSeries?: boolean
  seriesName?: string | string[]
  opposite?: boolean
  reversed?: boolean
  logarithmic?: boolean,
  logBase?: number,
  tickAmount?: number
  stepSize?: number
  forceNiceScale?: boolean
  alignZero?: boolean
  min?: number | ((min: number) => number)
  max?: number | ((max: number) => number)
  floating?: boolean
  decimalsInFloat?: number
  labels?: {
    show?: boolean
    showDuplicates?: boolean
    minWidth?: number
    maxWidth?: number
    offsetX?: number
    offsetY?: number
    rotate?: number
    align?: 'left' | 'center' | 'right'
    padding?: number
    style?: {
      colors?: string | string[]
      fontSize?: string
      fontWeight?: string | number
      fontFamily?: string
      cssClass?: string
    }
    formatter?(val: number, opts?: ApexFormatterOpts): string | string[]
  }
  axisBorder?: {
    show?: boolean
    color?: string
    width?: number
    offsetX?: number
    offsetY?: number
  }
  axisTicks?: {
    show?: boolean
    color?: string
    width?: number
    offsetX?: number
    offsetY?: number
  }
  title?: {
    text?: string
    rotate?: number
    offsetX?: number
    offsetY?: number
    style?: {
      color?: string
      fontSize?: string
      fontWeight?: string | number
      fontFamily?: string
      cssClass?: string
    }
  }
  crosshairs?: {
    show?: boolean
    position?: string
    stroke?: {
      color?: string
      width?: number
      dashArray?: number
    }
  }
  tooltip?: {
    enabled?: boolean
    offsetX?: number
  }
}

type ApexForecastDataPoints = {
  count?: number
  fillOpacity?: number
  strokeWidth?: undefined | number
  dashArray?: number
}

/**
 * Plot X and Y grid options
 * See https://apexcharts.com/docs/options/grid/
 */
type ApexGrid = {
  show?: boolean
  borderColor?: string
  strokeDashArray?: number
  position?: 'front' | 'back'
  xaxis?: {
    lines?: {
      show?: boolean
      offsetX?: number
      offsetY?: number
    }
  }
  yaxis?: {
    lines?: {
      show?: boolean
      offsetX?: number
      offsetY?: number
    }
  }
  row?: {
    colors?: string[]
    opacity?: number
  }
  column?: {
    colors?: string[]
    opacity?: number
  }
  padding?: {
    top?: number
    right?: number
    bottom?: number
    left?: number
  }
}

type ApexTheme = {
  /** '' (the default) inherits / auto-resolves; 'light' | 'dark' force a mode. */
  mode?: 'light' | 'dark' | ''
  palette?: string
  /**
   * Facet (#13): read `--apx-*` CSS design tokens from the cascade
   * (`--apx-accent`, `--apx-fore`, `--apx-grid`, `--apx-surface`,
   * `--apx-series-1..N`). They top the resolution chain, below explicit config.
   * true (default) reads any present (absence is a no-op); false disables.
   * Tokens are re-read on each render; use `chart.refreshTokens()` after a
   * runtime CSS change that does not itself trigger a render.
   */
  tokens?: boolean
  /**
   * Facet (#13): 'os' follows the operating system's `prefers-color-scheme`
   * (light/dark) and `prefers-contrast` reactively, with no JS. SSR-safe.
   */
  follow?: 'os' | false
  /** Facet (#13): a theme registered via `ApexCharts.registerTheme(name, def)`. */
  name?: string
  monochrome?: {
    enabled?: boolean
    color?: string
    shadeTo?: 'light' | 'dark'
    shadeIntensity?: number
  }
  accessibility?: {
    colorBlindMode?: 'deuteranopia' | 'protanopia' | 'tritanopia' | 'highContrast' | ''
  }
}

/** Facet (#13): a named theme definition for `ApexCharts.registerTheme`. */
interface ApexThemeDef {
  mode?: 'light' | 'dark'
  /** Series palette (overrides the built-in palette). */
  palette?: string[]
  /** Design-token values applied as chrome + palette seed. */
  tokens?: { accent?: string; fore?: string; grid?: string; surface?: string; series?: string[] }
  monochrome?: ApexTheme['monochrome']
  accessibility?: ApexTheme['accessibility']
}

/** A `reduce` spec: 'count' (default), a field aggregation, or a custom fn. */
/** One mark handed to a unit-chart layout provider. */
interface ApexUnitObject {
  /**
   * Stable identity. The datum's own `id`/`name` when the per-unit object form
   * supplies one, so a provider can address a specific unit ("Texas",
   * "employee 41") rather than a positional slot; otherwise
   * `"<seriesIndex>:<dataPointIndex>"`.
   */
  id: string
  /** Global draw order across every category. */
  index: number
  /** Category this mark belongs to. */
  seriesIndex: number
  /** Index within its category. */
  dataPointIndex: number
  /** Category label. */
  label: string
  /** The datum's value, when the per-unit object form supplies one. */
  value?: number
  /** The raw per-unit datum, when supplied. */
  datum?: any
  /** The radius the engine would use, so a size-aware provider need not redo it. */
  r: number
}

/** A position returned by a unit-chart layout provider, in plot pixels. */
interface ApexUnitPosition {
  /** Must match an `ApexUnitObject.id`; unknown ids are ignored. */
  id: string
  x: number
  y: number
  /** Overrides the engine's radius for this mark. */
  r?: number
}

/**
 * A unit-chart layout: objects in, positions out.
 *
 * `rect` is the plot area, in the same pixel space as the returned
 * coordinates. Omitting a mark's id removes it, and it animates out through the
 * normal exit path.
 */
/**
 * One glyph a unit can be drawn as. Fill-only: the chart positions a mark with
 * a uniform `scale()` fitted to the radius the layout chose, so any stroke
 * width would scale with it.
 */
interface ApexUnitMarkDef {
  name?: string
  /** Outline path data, in `viewBox` units. */
  path: string
  /** Defaults to `[0, 0, 100, 100]`, the catalog's convention. */
  viewBox?: [number, number, number, number]
  fillRule?: 'nonzero' | 'evenodd'
}

/** A registered mark name, raw path data, or the mark itself. */
type ApexUnitMarkRef = string | ApexUnitMarkDef

type ApexUnitLayout = (
  objects: ApexUnitObject[],
  rect: { x: number; y: number; width: number; height: number },
) => ApexUnitPosition[]

/** One datum of a `rowSeries()` cluster: a single row a mark stood for. */
interface ApexUnitRowDatum {
  /** Stable across relayouts, so `transition: 'identity'` can follow one row. */
  id: string
  /** The cluster's label, repeated per row. */
  x: string
  /** The observation itself, so bubble sizing and colour scales can read it. */
  y: number
  /** The colour of the mark this row came out of. */
  fillColor?: string
}

/** One cluster of `rowSeries()`: the rows behind exactly one mark. */
interface ApexUnitRowSeries {
  name: string
  data: ApexUnitRowDatum[]
}

/**
 * A row source: given a chart's state, the rows each of its marks stands for.
 *
 * Returns one cluster per mark in draw order (ascending series index, then
 * ascending category), including marks with no rows, or null when the chart
 * cannot answer.
 */
type ApexRowSource = (w: any, opts?: { maxRows?: number }) => ApexUnitRowSeries[] | null

type ApexCrossfilterReduce =
  | 'count'
  | { sum?: string; avg?: string; min?: string; max?: string }
  | ((rows: any[]) => number)

/** One chart's aggregation returned by `aggregateFor`. */
interface ApexCrossfilterAggregation {
  type: 'category' | 'range' | 'matrix'
  /** Category/range: bucket labels in stable order (category keys, or bin-start numbers). */
  labels?: any[]
  /** Category/range: reduced value per bucket. */
  values?: number[]
  /** Category/range: category key, or `[lo, hi]` bin range, per bucket. */
  keys?: any[]
  /** Range dimensions only: bin edges (length labels.length + 1). */
  edges?: number[]
  /** Matrix (2D) only: x-axis keys (columns). */
  xLabels?: any[]
  /** Matrix (2D) only: y-axis keys (rows / series). */
  yLabels?: any[]
  /** Matrix (2D) only: reduced value per cell, `matrix[yIndex][xIndex]`. */
  matrix?: number[][]
}

/**
 * Linked Views (#4) Phase 2: the crossfilter coordinator returned by
 * `ApexCharts.crossfilter(...)`. Holds one shared record set and per-chart
 * dimensions; selecting in one chart re-aggregates the others over the
 * filtered subset (a chart never filters itself).
 */
interface ApexCrossfilter {
  id: string
  records: any[]
  /** Swap the dataset and recompute every dimension's domain. */
  setRecords(records: any[]): this
  /** Register (or replace) a chart's dimension + reduction. */
  registerDimension(
    chartId: string,
    spec: {
      dimension: (row: any) => any
      reduce?: ApexCrossfilterReduce
      type?: 'category' | 'range'
      bins?: { width?: number; count?: number; thresholds?: number[] }
      order?: 'first-seen' | 'asc' | 'desc' | ((a: any, b: any) => number)
      filter?: any
    },
  ): this
  removeDimension(chartId: string): this
  /** Whether a chart's dimension is registered. */
  hasDimension(chartId: string): boolean
  /** Replace a chart's filter (keys for category, `[min,max]` for range, null clears). */
  filter(chartId: string, filter: any[] | Set<any> | [number, number] | null): this
  /** Toggle one categorical key (multi-select, OR). */
  toggleKey(chartId: string, key: any): this
  /** Clear one chart's filter. */
  clear(chartId: string): this
  /** Clear all filters. */
  reset(): this
  /** The current filter for a chart (Set/range copy, or null). */
  filterOf(chartId: string): any
  /** Rows passing all OTHER charts' filters (all filters when null/omitted). */
  filteredRecords(exceptChartId?: string | null): any[]
  /** Rows passing every active filter. */
  filteredRows(): any[]
  /** The crossfilter aggregation for one chart. */
  aggregateFor(chartId: string): ApexCrossfilterAggregation
  /** Aggregate every registered chart, keyed by chartId. */
  aggregateAll(): Record<string, ApexCrossfilterAggregation>
  /** Active filters + filtered/total record counts. */
  state(): { filters: Record<string, any[] | [number, number]>; filteredCount: number; total: number }
  /**
   * Bind an HTML table of the filtered rows to `el`; re-renders on every filter
   * change. `columns` may be field-name strings or `{field, label, format}`;
   * omit to derive from the record keys. Returns a refresh()/destroy() handle.
   */
  dataTable(
    el: HTMLElement,
    opts?: {
      columns?: Array<string | { field: string; label?: string; format?: (v: any, row: any) => any }>
      page?: number
      pageSize?: number
    },
  ): { refresh(): void; destroy(): void }
  /** Subscribe to 'change' | 'records'; returns an unsubscribe fn. */
  on(
    event: 'change' | 'records',
    cb: (state: { filters: Record<string, any>; filteredCount: number; total: number }) => void,
  ): () => void
  off(event: string, cb: Function): this
  /** Remove this coordinator from the registry and drop all state. */
  destroy(): void
}

export = ApexCharts;
export as namespace ApexCharts;