
### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-chart\x-chart.component.html

<!-- Component Template -->
<ng-container *ngIf="getValue(wrapWithCard) | async; else componentsRef">
  <x-card
    class="x-card"
    [showActions]="true"
    [color]="getValue(cardColor) | async"
    [ngClass]="getValue(cssClass) | async"
    [isInPage]="getValue(isInPage) | async"
    [showHeader]="
      (notEmptyValue(cardTitle) | async) ||
      (notEmptyValue(cardSubTitle) | async)
    "
    [subTitle]="getResourceValue(cardSubTitle)"
    [title]="getResourceValue(cardTitle) | async"
    [showFooter]="getValue(cardShowFooter) | async"
    [showActions]="getValue(cardShowActions) | async"
    [foregroundColor]="getValue(cardForegroundColor) | async"
  >
    <!-- Content -->
    <ng-container *ngTemplateOutlet="componentsRef"></ng-container>

    <!-- Actions -->
    <div class="x-actions" [ngClass]="getValue(cssClass) | async" actions>
      <ng-content select="[actions]"></ng-content>
    </div>

    <!-- Footer -->
    <div footer *ngIf="getValue(cardShowFooter) | async">
      <ng-content select="[footer]"></ng-content>
    </div>
  </x-card>
</ng-container>

<!-- Component's itSelf Template -->
<ng-template #componentsRef>
  <!-- Component Box ... -->
  <div
    class="x-chart x-wrapper"
    [class.mobile]="isMobileUi$ | async"
    [ngClass]="getValue(cssClass) | async"
  >
    <div #chartContainer class="x-chart-container"></div>
  </div>
</ng-template>

### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-chart\x-chart.component.scss

:host {
  display: block;
  width: 100%;
  height: 100%;
}

.x-chart-wrapper {
  width: 100%;
  height: 100%;
  min-height: 300px; /* ارتفاع پیش‌فرض برای جلوگیری از فروپاشی layout */
}

.x-chart-container {
  width: 100%;
  height: 100%;
}

### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-chart\x-chart.component.ts

import {
  Component,
  ViewChild,
  ElementRef,
  ChangeDetectionStrategy,
} from '@angular/core';
import {
  isChartType,
  XChartAction,
  XChartOptions,
  XChartSeriesType,
  XChartContainerComponent,
} from './x-chart.typings';
import { Subscription } from 'rxjs';
import ApexCharts from 'apexcharts';
import { isNullOrUndefined, nameof } from 'x-framework-core';

@Component({
  selector: 'x-chart',
  templateUrl: './x-chart.component.html',
  styleUrls: ['./x-chart.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class XChartComponent extends XChartContainerComponent {
  //
  //#region Props ...
  //
  private CHART_CONTAINER: ElementRef | undefined;

  @ViewChild('chartContainer', { static: false })
  set chartContainer(el: ElementRef | undefined) {
    //
    if (isNullOrUndefined(el)) {
      //
      this.destroyComponent();
      return;
    }

    //
    this.CHART_CONTAINER = el;
    this.initializeComponent();
  }

  get chartContainer() {
    return this.CHART_CONTAINER;
  }

  //
  private CHART_OPTIONS: XChartOptions = null;

  //
  private chartInstance: ApexCharts = null;

  //
  private ACTION_PROVIDER_SUBSCRIPTION: Subscription;
  //#endregion

  //
  //#region LifeCycles ...
  async afterViewInit() {
    super.afterViewInit();

    //
    await this.initializeComponent();
  }

  async onChange(changeKeys: string[]) {
    super.onChange(changeKeys);

    //
    // Auto Update ...
    const isAutoUpdateChanged = changeKeys.includes(
      nameof<XChartComponent>('autoUpdate'),
    );
    if (isAutoUpdateChanged) {
      //
      if (isNullOrUndefined(this.autoUpdate)) {
        this.autoUpdate = this.propertyProvider.autoUpdate;
      }
    }

    //
    // Action Provider Changed ...
    const isActionProviderChanged = changeKeys.includes(
      nameof<XChartComponent>('actionProvider'),
    );
    if (isActionProviderChanged) {
      this.registerActionProvider();
    }

    //
    // Check Auto Update ...
    var autoUpdate = await this.getValueAsync(this.autoUpdate);
    if (
      autoUpdate &&
      !isNullOrUndefined(this.chartInstance) &&
      !isNullOrUndefined(this.chartContainer) &&
      !isNullOrUndefined(this.chartContainer.nativeElement)
    ) {
      //
      // Options Changed ...
      const isOptionsChanged = changeKeys.includes(
        nameof<XChartComponent>('options'),
      );
      if (isOptionsChanged) {
        //
        if (isNullOrUndefined(this.options)) {
          this.options = this.propertyProvider.options;
        }

        //
        await this.handleChartUpdate();
      }

      //
      // Chart Type ...
      const isTypeChanged = changeKeys.includes(
        nameof<XChartComponent>('type'),
      );
      if (isTypeChanged) {
        //
        if (isNullOrUndefined(this.type)) {
          this.type = this.propertyProvider.type;
        }

        //
        var type = await this.getValueAsync(this.type);
        if (!isChartType(type)) {
          //
          type = await this.getValueAsync(this.propertyProvider.type);
          this.type = type;
        }

        //
        await this.handleChartUpdate();
      }

      //
      // Series Changed ...
      const isSeriesChanged = changeKeys.includes(
        nameof<XChartComponent>('series'),
      );
      if (isSeriesChanged) {
        //
        if (isNullOrUndefined(this.series)) {
          this.series = this.propertyProvider.series;
        }

        //
        await this.handleChartUpdate();
      }
    }

    //
    this.detectChanges();
  }

  onDestroy() {
    super.onDestroy();

    //
    this.destroyComponent();
    this.unregisterActionProvider();
  }
  //#endregion

  //
  //#region Private ...
  private async initializeComponent() {
    //
    if (isNullOrUndefined(this.CHART_CONTAINER)) {
      return;
    }

    //
    // Reading Requirement Properties ...
    const type = await this.getValueAsync(this.type);
    const chartId = await this.getValueAsync(this.uuid);
    const series = await this.getValueAsync(this.series);
    const options = await this.getValueAsync(this.options);

    //
    const computedStyle = getComputedStyle(document.documentElement);
    const primaryColor =
      computedStyle.getPropertyValue('--x-color-primary').trim() || '#008ffb';
    const textColor =
      computedStyle.getPropertyValue('--x-color-dark-contrast').trim() ||
      '#373d3f';
    const gridColor =
      computedStyle.getPropertyValue('--x-color-light-shade').trim() ||
      '#e0e0e0';

    //
    this.CHART_OPTIONS = {
      ...options,
      series: series || options.series,
      chart: {
        ...options.chart,
        id: chartId,
        type: type as any,
        foreColor: textColor,
        events: {
          ...options.chart?.events,
          mounted: (chartContext, config) => {
            this.chartRendered.emit();
            options.chart?.events?.mounted?.(chartContext, config);
          },
          dataPointSelection: (event, chartContext, config) => {
            this.dataPointSelected.emit({ event, chartContext, config });
            options.chart?.events?.dataPointSelection?.(
              event,
              chartContext,
              config,
            );
          },
          zoomed: (chartContext, { xaxis, yaxis }) => {
            this.zoomed.emit({ xaxis, yaxis });
            options.chart?.events?.zoomed?.(chartContext, { xaxis, yaxis });
          },
        },
      },
      theme: {
        ...options.theme,
        palette: options.theme?.palette || 'palette1',
      },
      colors: options.colors || [primaryColor],
      grid: {
        ...options.grid,
        borderColor: gridColor,
      },
    };

    //
    this.chartInstance = new ApexCharts(
      this.chartContainer.nativeElement,
      this.CHART_OPTIONS,
    );

    //
    await this.chartInstance.render();
  }

  private async handleChartUpdate() {
    //
    if (
      isNullOrUndefined(this.chartInstance) ||
      isNullOrUndefined(this.CHART_OPTIONS) ||
      isNullOrUndefined(this.chartContainer) ||
      isNullOrUndefined(this.chartContainer.nativeElement)
    ) {
      return;
    }

    //
    // Reading Requirement Properties ...
    const type = await this.getValueAsync(this.type);
    const series = await this.getValueAsync(this.series);

    //
    const options = await this.getValueAsync(this.options);
    this.CHART_OPTIONS = {
      ...options,
      ...this.CHART_OPTIONS,
      chart: {
        ...this.CHART_OPTIONS.chart,
        type: type as any,
      },
    };

    //
    await this.chartInstance.updateOptions(this.CHART_OPTIONS);
    await this.chartInstance.updateSeries(series);
  }

  private destroyComponent() {
    //
    if (isNullOrUndefined(this.chartInstance)) {
      return;
    }

    //
    this.chartInstance.destroy();
    this.chartInstance = null;
  }

  private registerActionProvider() {
    //
    this.unregisterActionProvider();

    //
    if (!this.actionProvider) {
      this.actionProvider = this.propertyProvider.actionProvider;
    }

    //
    this.ACTION_PROVIDER_SUBSCRIPTION = this.actionProvider
      .asObservable()
      .subscribe(async (model) => {
        //
        switch (model.action) {
          //
          case XChartAction.Refresh:
            this.detectChanges();
            break;

          //
          case XChartAction.Destroy:
            this.onDestroy();
            break;

          //
          case XChartAction.UpdateSeries:
            //
            if (
              !isNullOrUndefined(model.payload) ||
              !isNullOrUndefined(this.chartInstance)
            ) {
              //
              this.series = await this.getValueAsync(
                model.payload as XChartSeriesType,
              );
              await this.chartInstance.updateSeries(this.series);
              this.detectChanges();
            }
            break;

          //
          case XChartAction.UpdateOptions:
            //
            if (
              !isNullOrUndefined(model.payload) ||
              !isNullOrUndefined(this.chartInstance)
            ) {
              //
              this.options = await this.getValueAsync(
                model.payload as XChartOptions,
              );
              await this.chartInstance.updateOptions(this.options);
              this.detectChanges();
            }
            break;
        }
      });
  }

  private unregisterActionProvider() {
    //
    if (!this.ACTION_PROVIDER_SUBSCRIPTION) {
      return;
    }

    //
    this.ACTION_PROVIDER_SUBSCRIPTION.unsubscribe();
    this.ACTION_PROVIDER_SUBSCRIPTION = null;
  }
  //#endregion
}

### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-chart\x-chart.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { XCardModule } from '../x-card/x-card.module';
import { XIconModule } from '../x-icon/x-icon.module';
import { XChartComponent } from './x-chart.component';
import { XSlotterModule } from '../x-slotter/x-slotter.module';
import { XBaseComponentModule } from '../modules/x-base-component.module';

@NgModule({
  providers: [],
  entryComponents: [],
  declarations: [XChartComponent],
  imports: [
    XCardModule,
    XIconModule,
    CommonModule,
    XSlotterModule,
    XBaseComponentModule,
  ],
  exports: [
    XCardModule,
    XIconModule,
    CommonModule,
    XSlotterModule,
    XChartComponent,
    XBaseComponentModule,
  ],
})
export class XChartModule {}

### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-chart\x-chart.typings.ts

import {
  Input,
  Inject,
  NgZone,
  Output,
  Component,
  Renderer2,
  ElementRef,
  Injectable,
  EventEmitter,
  ChangeDetectorRef,
} from '@angular/core';
import {
  XStandardType,
  isValueInEnum,
  XOneOrManyType,
  XColorIdentifier,
  XResourceIdentifier,
} from 'x-framework-core';
import {
  XCardActionModel,
  XComponentCardContainer,
  IXComponentCardContainer,
  XComponentCardContainerComponent,
} from '../x-card/x-card.typings';
import { concatMap, map } from 'rxjs/operators';
import { forkJoin, Observable, Subject } from 'rxjs';
import { ViewportRuler } from '@angular/cdk/overlay';
import { XManagerService } from 'x-framework-services';
import { XBaseActionModel } from '../classes/x-base-action.model';
import { X_FRAMEWORK_COMPONENTS_CONFIG } from '../tokens/x-injectable-tokens';
import { XFrameworkComponentsConfig } from '../config/x-framework-components.config';

//
//#region Chart ...
export type XChartAnnotationStyle = {
  background?: string;
  color?: string;
  fontFamily?: string;
  fontWeight?: string | number;
  fontSize?: string;
  cssClass?: string;
  padding?: {
    left?: number;
    right?: number;
    top?: number;
    bottom?: number;
  };
};

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

export type XCahrtAxisBaseAnnotations = {
  id?: number | string;
  strokeDashArray?: number;
  fillColor?: string;
  borderColor?: string;
  borderWidth?: number;
  opacity?: number;
  offsetX?: number;
  offsetY?: number;
  label?: XChartAnnotationLabel;
  draggable?: boolean;
};

export type XChartYAxisAnnotations = XCahrtAxisBaseAnnotations & {
  y?: null | number | string;
  y2?: null | number | string;
  width?: number | string;
  yAxisIndex?: number;
};

export type XChartXAxisAnnotations = XCahrtAxisBaseAnnotations & {
  x?: null | number | string;
  x2?: null | number | string;
};

export type XChartPointAnnotations = {
  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: XChartPointAnnotations, e: MouseEvent) => void;
  mouseLeave?: (annotation: XChartPointAnnotations, e: MouseEvent) => void;
  click?: (annotation: XChartPointAnnotations, e: MouseEvent) => void;
  marker?: {
    size?: number;
    fillColor?: string;
    strokeColor?: string;
    strokeWidth?: number;
    shape?: string;
    offsetX?: number;
    offsetY?: number;
    cssClass?: string;
  };
  label?: XChartAnnotationLabel;
  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: XChartPointAnnotations;
      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;
  };
};

export type XChartTextAnnotations = {
  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;
};

export type XChartImageAnnotations = {
  path?: string;
  x?: number;
  y?: number;
  width?: number;
  height?: number;
};

export type XChartAnnotations = {
  texts?: XChartTextAnnotations[];
  yaxis?: XChartYAxisAnnotations[];
  xaxis?: XChartXAxisAnnotations[];
  points?: XChartPointAnnotations[];
  images?: XChartImageAnnotations[];
};

export type XChartContext = {
  config: XChartOptions;
  globals: any;
  [key: string]: any;
};

export type XChartColorFormatterOpts = {
  value: number;
  seriesIndex: number;
  dataPointIndex: number;
  w: XChartContext;
};

export type XChartFormatterOpts = {
  seriesIndex: number;
  dataPointIndex: number;
  series?: any[][];
  w: XChartContext;
  [key: string]: any;
};

export type XChartDropShadow = {
  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[];
};

export type XChartDataLabels = {
  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: XChartFormatterOpts) => 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: XChartFormatterOpts) => 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?: XChartDropShadow;
  };
  dropShadow?: XChartDropShadow;
  /**
   * Ride data labels to their new position on a data-change update (e.g. a bar
   * chart race) instead of snapping. Off by default. 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?: XChartFormatterOpts,
  ): string | number | (string | number)[];
};

export type XChartHierarchyNode = {
  /** 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?: XChartHierarchyNode[];
  /**
   * 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;
};

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

export type XChartColorStop = {
  offset: number;
  color: string;
  opacity: number;
};

export type XChartFill = {
  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?: XChartColorStop[][] | XChartColorStop[];
  };
  image?: {
    src?: string | string[];
    width?: number;
    height?: number;
  };
  pattern?: {
    style?: string | string[];
    width?: number;
    height?: number;
    strokeWidth?: number;
  };
};

export type XChartAxisChartSeries = {
  name?: string;
  type?: string;
  color?: string;
  group?: string;
  hidden?: boolean;
  zIndex?: number;
  parsing?: XChartParsing;
  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?: XChartFill;
        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?: XChartHierarchyNode[];
        /**
         * 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>`.
    | XChartHierarchyNode[]
    | Record<string, any>[];
}[];

export type XChartNonAxisChartSeries = number[] | XChartAxisChartSeries;

export type XChartGradientLegend = {
  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;
};

export type XChartTreemapLevel = {
  /** 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;
    };
  };
};

export type XChartBreadcrumb = {
  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;
};

export interface XChartUnitObject {
  /**
   * 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;
}

export interface XChartUnitPosition {
  /** 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;
}

export type XChartUnitLayout = (
  objects: XChartUnitObject[],
  rect: { x: number; y: number; width: number; height: number },
) => XChartUnitPosition[];

export type XChartPlotOptions = {
  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';
    borderRadiusWhenStacked?: 'all' | 'last';
    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?: XChartFormatterOpts): string;
        offsetX?: number;
        offsetY?: number;
        style?: {
          color?: string;
          fontSize?: string;
          fontFamily?: string;
          fontWeight?: number | string;
        };
      };
    };
  };
  bubble?: {
    zScaling?: boolean;
    minBubbleRadius?: number;
    maxBubbleRadius?: 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?: XChartGradientLegend;
    };
  };
  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?: XChartGradientLegend;
    };
    /**
     * 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?: XChartTreemapLevel & {
      /** `'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?: XChartTreemapLevel[];
    /**
     * 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?: XChartBreadcrumb;
    };
    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 | XChartUnitLayout;
    /**
     * 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';
    /** Mark shape for each unit. `'image'` renders an icon (isotype pictogram). */
    shape?: 'circle' | 'square' | 'image';
    /** 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: XChartContext;
          },
        ): 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: XChartContext): 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?: XChartDropShadow;
    };
    track?: {
      show?: boolean;
      startAngle?: number;
      endAngle?: number;
      background?: string | string[];
      strokeWidth?: string;
      opacity?: number;
      margin?: number;
      dropShadow?: XChartDropShadow;
    };
    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: XChartContext): string;
      };
    };
    barLabels?: {
      enabled?: boolean;
      offsetX?: number;
      offsetY?: number;
      useSeriesColors?: boolean;
      fontFamily?: string;
      fontWeight?: string | number;
      fontSize?: string;
      formatter?: (barName: string, opts?: XChartFormatterOpts) => string;
      onClick?: (barName: string, opts?: XChartFormatterOpts) => void;
    };
  };
};

export type XChartXAxis = {
  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?: XChartFormatterOpts,
    ): 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?: XChartDropShadow;
  };
  tooltip?: {
    enabled?: boolean;
    offsetY?: number;
    formatter?(value: string | number, opts?: XChartFormatterOpts): string;
    style?: {
      fontSize?: string;
      fontFamily?: string;
    };
  };
};

export type XChartYAxis = {
  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?: XChartFormatterOpts): 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;
  };
};

export type XChartLegendFormatterOpts = {
  seriesIndex: number;
  w: XChartContext;
};

export type XChartMarkerShapeOptions =
  | 'circle'
  | 'square'
  | 'rect'
  | 'line'
  | 'cross'
  | 'plus'
  | 'star'
  | 'sparkle'
  | 'diamond'
  | 'triangle';

export type XChartMarkerShape =
  | XChartMarkerShapeOptions
  | XChartMarkerShapeOptions[];

export type XChartDiscretePoint = {
  seriesIndex?: number;
  dataPointIndex?: number;
  fillColor?: string;
  strokeColor?: string;
  size?: number;
  shape?: XChartMarkerShape;
};

export type XChartMarkers = {
  size?: number | number[];
  colors?: string | string[];
  strokeColors?: string | string[];
  strokeWidth?: number | number[];
  strokeOpacity?: number | number[];
  strokeDashArray?: number | number[];
  fillOpacity?: number | number[];
  discrete?: XChartDiscretePoint[];
  shape?: XChartMarkerShape;
  offsetX?: number;
  offsetY?: number;
  showNullDataPoints?: boolean;
  onClick?(e?: MouseEvent): void;
  onDblClick?(e?: MouseEvent): void;
  hover?: {
    size?: number;
    sizeOffset?: number;
  };
};

export type XChartLegend = {
  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?: XChartLegendFormatterOpts): string;
  tooltipHoverFormatter?(
    legendName: string,
    opts?: XChartLegendFormatterOpts,
  ): string;
  customLegendItems?: string[];
  clusterGroupedSeries?: boolean;
  clusterGroupedSeriesOrientation?: 'vertical' | 'horizontal';
  labels?: {
    colors?: string | string[];
    useSeriesColors?: boolean;
  };
  markers?: {
    size?: number;
    strokeWidth?: number;
    fillColors?: string[];
    shape?: XChartMarkerShape;
    offsetX?: number;
    offsetY?: number;
    customHTML?(): string;
    onClick?(e: MouseEvent): void;
  };
  itemMargin?: {
    horizontal?: number;
    vertical?: number;
  };
  onItemClick?: {
    toggleDataSeries?: boolean;
  };
  onItemHover?: {
    highlightDataSeries?: boolean;
  };
};

export type XChartEasing =
  | 'linear'
  | 'easeInSine'
  | 'easeOutSine'
  | 'easeInOutSine'
  | 'easeInQuad'
  | 'easeOutQuad'
  | 'easeInOutQuad'
  | 'easeInCubic'
  | 'easeOutCubic'
  | 'easeInOutCubic'
  | 'easeOutBack'
  | 'easeInOutBack'
  | (string & {})
  | [number, number, number, number]
  | ((t: number) => number);

export type XChartEventOpts = {
  seriesIndex: number;
  dataPointIndex: number;
  w: XChartContext;
  [key: string]: any;
};

export type XChartLocale = {
  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;
    };
  };
};

export interface XChartStoryboardBeatInfo {
  index: number;
  key: string | null;
  el: Element;
  direction: 'up' | 'down';
}

export interface XChartDrilldownSeries {
  /** 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?: XChartAxisChartSeries;
  /** Optional chart-type override applied when this level is shown. */
  chart?: Pick<XChart, 'type' | 'stacked' | 'stackType'>;
  plotOptions?: XChartPlotOptions;
  xaxis?: XChartXAxis;
  yaxis?: XChartYAxis | XChartYAxis[];
  colors?: Array<string | ((opts: XChartColorFormatterOpts) => string)>;
  /** Optional fill override (e.g. a pattern fill to visually distinguish drilled levels). */
  fill?: XChartFill;
  /** Optional legend override (e.g. show a legend when a level is a pie/donut). */
  legend?: XChartLegend;
}

export interface XChartDrilldownEvent {
  /** 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;
}

export interface XChartDrilldownContext {
  /** The requested level id, i.e. the clicked point's `drilldown` value. */
  id: string | number | null;
  point: any;
  seriesIndex?: number;
  dataPointIndex?: number;
}

export interface XChartDrilldown {
  /** Master switch. When false the feature stays inert even if imported. */
  enabled?: boolean;
  /** Inline child levels referenced by data-point `drilldown` ids. */
  series?: XChartDrilldownSeries[];
  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: XChartDrilldownContext,
  ): XChartDrilldownSeries | Promise<XChartDrilldownSeries>;
  /**
   * 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 type XChartForecastDataPoints = {
  count?: number;
  fillOpacity?: number;
  strokeWidth?: undefined | number;
  dashArray?: number;
};

export type XChartGrid = {
  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;
  };
};

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

export interface XChartPluginActivation {
  name: string;
  options?: Record<string, any>;
  order?: number;
}

export type XChartResponsive = {
  breakpoint?: number;
  options?: XChartOptions;
};

export type XChartStates = {
  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;
    };
  };
};

export type XChartStroke = {
  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?: XChartFill;
};

export type XChartTitleSubtitle = {
  text?: string;
  align?: 'left' | 'center' | 'right';
  margin?: number;
  offsetX?: number;
  offsetY?: number;
  floating?: boolean;
  style?: {
    fontSize?: string;
    fontFamily?: string;
    fontWeight?: string | number;
    color?: string;
  };
};

export type XChartTheme = {
  /** '' (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'
      | '';
  };
};

export type XChartTooltipCustomOpts = {
  series: number[][];
  seriesIndex: number;
  dataPointIndex: number;
  y1?: number;
  y2?: number;
  w: XChartContext;
};

export type XChartTooltipY = {
  title?: {
    formatter?(seriesName: string, opts?: XChartFormatterOpts): string;
  };
  formatter?(val: number, opts?: XChartFormatterOpts): string;
};

export type XChartTooltip = {
  enabled?: boolean;
  enabledOnSeries?: undefined | number[];
  shared?: boolean;
  followCursor?: boolean;
  intersect?: boolean;
  inverseOrder?: boolean;
  arrow?: boolean;
  custom?:
    | ((
        opts: XChartTooltipCustomOpts,
      ) => string | number | Element | { nodeName: string })
    | Array<
        (
          opts: XChartTooltipCustomOpts,
        ) => 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?: XChartFormatterOpts): string;
  };
  y?: XChartTooltipY | XChartTooltipY[];
  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;
  };
};

export interface XChartOptions {
  annotations?: XChartAnnotations;
  chart?: XChart;
  /**
   * 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: XChartColorFormatterOpts) => string)>;
  dataLabels?: XChartDataLabels;
  /** Opt-in drilldown navigation. Requires `import 'apexcharts/features/drilldown'`. */
  drilldown?: XChartDrilldown;
  fill?: XChartFill;
  forecastDataPoints?: XChartForecastDataPoints;
  grid?: XChartGrid;
  labels?: string[];
  legend?: XChartLegend;
  markers?: XChartMarkers;
  noData?: XChartNoData;
  /** Weave (#1) plugin activation list. Requires `import 'apexcharts/features/weave'`. */
  plugins?: XChartPluginActivation[];
  plotOptions?: XChartPlotOptions;
  responsive?: XChartResponsive[];
  parsing?: XChartParsing;
  series?: XChartSeriesType;
  states?: XChartStates;
  stroke?: XChartStroke;
  subtitle?: XChartTitleSubtitle;
  theme?: XChartTheme;
  title?: XChartTitleSubtitle;
  tooltip?: XChartTooltip;
  xaxis?: XChartXAxis;
  yaxis?: XChartYAxis | XChartYAxis[];
}

export type XChart = {
  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?: XChartDropShadow & {
    enabledOnSeries?: undefined | number[];
  };
  nonce?: string;
  events?: {
    animationEnd?(chart: ApexCharts, options?: XChartEventOpts): void;
    beforeMount?(chart: ApexCharts, options?: XChartEventOpts): void;
    mounted?(chart: ApexCharts, options?: XChartEventOpts): void;
    updated?(chart: ApexCharts, options?: XChartEventOpts): void;
    mouseMove?(
      e: MouseEvent,
      chart?: ApexCharts,
      options?: XChartEventOpts,
    ): void;
    mouseLeave?(
      e: MouseEvent,
      chart?: ApexCharts,
      options?: XChartEventOpts,
    ): void;
    click?(e: MouseEvent, chart?: ApexCharts, options?: XChartEventOpts): void;
    xAxisLabelClick?(
      e: MouseEvent,
      chart?: ApexCharts,
      options?: XChartEventOpts,
    ): void;
    legendClick?(
      chart: ApexCharts,
      seriesIndex?: number,
      options?: XChartEventOpts,
    ): void;
    markerClick?(
      e: MouseEvent,
      chart?: ApexCharts,
      options?: XChartEventOpts,
    ): void;
    selection?(
      chart: ApexCharts,
      options?: {
        xaxis?: { min: number; max: number };
        yaxis?: { min: number; max: number };
      },
    ): void;
    dataPointSelection?(
      e: MouseEvent,
      chart?: ApexCharts,
      options?: XChartEventOpts,
    ): void;
    dataPointMouseEnter?(
      e: MouseEvent,
      chart?: ApexCharts,
      options?: XChartEventOpts,
    ): void;
    dataPointMouseLeave?(
      e: MouseEvent,
      chart?: ApexCharts,
      options?: XChartEventOpts,
    ): void;
    beforeZoom?(
      chart: ApexCharts,
      options?: { xaxis: { min: number; max: number } },
    ): boolean | void;
    beforeResetZoom?(
      chart: ApexCharts,
      options?: XChartEventOpts,
    ): 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?: XChartStoryboardBeatInfo): void;
    keyDown?(
      e: KeyboardEvent,
      chart?: ApexCharts,
      options?: XChartEventOpts,
    ): void;
    keyUp?(
      e: KeyboardEvent,
      chart?: ApexCharts,
      options?: XChartEventOpts,
    ): void;
    /** Fired before a drill-down transition begins. Requires the Drilldown feature. */
    drillDownStart?(
      info: ApexCharts.ApexDrilldownEvent,
      chart?: ApexCharts,
      options?: XChartEventOpts,
    ): void;
    /** Fired after a drill-down transition completes. Requires the Drilldown feature. */
    drillDownEnd?(
      info: ApexCharts.ApexDrilldownEvent,
      chart?: ApexCharts,
      options?: XChartEventOpts,
    ): void;
    /** Fired after navigating back up a drilldown level. Requires the Drilldown feature. */
    drillUp?(
      info: ApexCharts.ApexDrilldownEvent,
      chart?: ApexCharts,
      options?: XChartEventOpts,
    ): 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?: XChartEventOpts,
    ): 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 (`import 'apexcharts/features/measure'`). 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?: XChartLocale[];
  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?: XChartEventOpts,
          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?: XChartEasing;
    /**
     * 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?: XChartEasing;
    };
    /**
     * 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;
  };
};
//#endregion

export enum XChartType {
  Bar = 'bar',
  Pie = 'pie',
  Line = 'line',
  Area = 'area',
  Unit = 'unit',
  Gauge = 'gauge',
  Donut = 'donut',
  Radar = 'radar',
  Waffle = 'waffle',
  Funnel = 'funnel',
  Violin = 'violin',
  Bubble = 'bubble',
  Scatter = 'scatter',
  Heatmap = 'heatmap',
  BoxPlot = 'boxPlot',
  Pyramid = 'pyramid',
  Treemap = 'treemap',
  Sunburst = 'sunburst',
  RangeBar = 'rangeBar',
  RadialBar = 'radialBar',
  Histogram = 'histogram',
  PolarArea = 'polarArea',
  RangeArea = 'rangeArea',
  Candlestick = 'candlestick',
}
export type XChartTypeIdentifier = XChartType | string;

export function isChartType(value: string) {
  return isValueInEnum(value, Object.assign({}, XChartType));
}

export enum XChartAction {
  Refresh = 'refresh',
  Destroy = 'destroy',
  UpdateSeries = 'update_series',
  UpdateOptions = 'update_options',
}

export type XChartActionIdentifier = XChartAction | string;

export interface XChartActionModel extends XBaseActionModel<XChartActionIdentifier> {}

export type XChartSeriesType = XChartAxisChartSeries | XChartNonAxisChartSeries;
export interface IXChartContainer extends IXComponentCardContainer {
  //
  // Commons ...
  actionProvider: Subject<XChartActionModel>;
  cssClass: XStandardType<XOneOrManyType<string>>;

  //
  // Chart Props ...
  autoUpdate: XStandardType<boolean>;
  options: XStandardType<XChartOptions>;
  series: XStandardType<XChartSeriesType>;

  //
  type: XStandardType<XChartTypeIdentifier>;

  //
  // Events ...
  chartRendered: EventEmitter<void>;
  dataPointSelected: EventEmitter<any>;
  zoomed: EventEmitter<any>;
}

@Injectable({
  providedIn: 'root',
})
export class XChartContainer
  extends XComponentCardContainer
  implements Readonly<IXChartContainer>
{
  //
  readonly options: XStandardType<XChartOptions>;
  readonly series: XStandardType<XChartSeriesType>;
  readonly autoUpdate: XStandardType<boolean> = true;
  readonly cssClass: XStandardType<XOneOrManyType<string>>;
  readonly actionProvider = new Subject<XChartActionModel>();
  readonly type: XStandardType<XChartTypeIdentifier> = XChartType.Line;

  //
  readonly zoomed = new EventEmitter<any>();
  readonly chartRendered = new EventEmitter<void>();
  readonly dataPointSelected = new EventEmitter<any>();
}

@Injectable()
@Component({ template: `` })
export abstract class XChartContainerComponent
  extends XComponentCardContainerComponent
  implements IXChartContainer
{
  //
  //#region Props ...
  @Input()
  actionProvider: Subject<XChartActionModel> =
    this.propertyProvider.actionProvider;

  @Input()
  cssClass: XStandardType<XOneOrManyType<string>> =
    this.propertyProvider.cssClass;

  /**
   * Specifiy Type of Chart Drawing ...
   */
  @Input()
  type: XStandardType<XChartTypeIdentifier> = this.propertyProvider.type;

  @Input()
  options: XStandardType<XChartOptions> = this.propertyProvider.options;

  @Input()
  series: XStandardType<XChartSeriesType> = this.propertyProvider.series;

  @Input()
  autoUpdate: XStandardType<boolean> = this.propertyProvider.autoUpdate;

  //
  // Events ...
  @Output()
  chartRendered: EventEmitter<void> = new EventEmitter();

  @Output()
  dataPointSelected: EventEmitter<any> = new EventEmitter();

  @Output()
  zoomed: EventEmitter<any> = new EventEmitter();

  //
  //#region ComponentCard Props ...
  @Input()
  isInPage: XStandardType<boolean> = this.propertyProvider.isInPage;

  @Input()
  wrapWithCard: XStandardType<boolean> = this.propertyProvider.wrapWithCard;

  @Input()
  cardCssClass: XStandardType<XOneOrManyType<string>> =
    this.propertyProvider.cardCssClass;

  @Input()
  cardColor: XStandardType<XColorIdentifier> = this.propertyProvider.cardColor;

  @Input()
  cardForegroundColor: XStandardType<boolean> =
    this.propertyProvider.cardForegroundColor;

  @Input()
  cardTitle: XStandardType<XResourceIdentifier> =
    this.propertyProvider.cardTitle;

  @Input()
  cardSubTitle: XStandardType<XResourceIdentifier> =
    this.propertyProvider.cardSubTitle;

  @Input()
  cardShowFooter: XStandardType<boolean> = this.propertyProvider.cardShowFooter;

  @Input()
  cardShowActions: XStandardType<boolean> =
    this.propertyProvider.cardShowActions;

  @Input()
  cardActionProvider: Subject<XCardActionModel> =
    this.propertyProvider.cardActionProvider;
  //#endregion
  //#endregion

  //
  //#region Constructor ...
  constructor(
    @Inject(X_FRAMEWORK_COMPONENTS_CONFIG)
    public config: XFrameworkComponentsConfig,
    public managerService: XManagerService,
    public element: ElementRef,
    public renderer: Renderer2,
    public ruler: ViewportRuler,
    public zone: NgZone,
    public changeDetector: ChangeDetectorRef,
    protected propertyProvider: XChartContainer,
  ) {
    super(
      config,
      managerService,
      element,
      renderer,
      ruler,
      zone,
      changeDetector,
      propertyProvider,
    );
  }
  //#endregionّ

  //
  //#region Abstract ...
  public extractProps(
    container?: IXChartContainer,
  ): Observable<IXChartContainer> {
    return super
      .extractProps(container ? container : this)
      .pipe(
        concatMap((superData) => {
          return forkJoin({
            //
            cssClass: this.getArrayValue(
              container ? container.cssClass : this.cssClass,
            ),

            //
            type: this.getValue(container ? container.type : this.type),
            options: this.getValue(
              container ? container.options : this.options,
            ),
            series: this.getValue(container ? container.series : this.series),
            autoUpdate: this.getValue(
              container ? container.autoUpdate : this.autoUpdate,
            ),
          }).pipe(
            map((data) => {
              return {
                superData,
                data,
              };
            }),
          );
        }),
      )
      .pipe(
        map((info) => ({
          ...info.data,
          ...info.superData,
          actionProvider: container
            ? container.actionProvider
            : this.actionProvider,
          chartRendered: this.chartRendered,
          dataPointSelected: this.dataPointSelected,
          zoomed: this.zoomed,
        })),
      );
  }
  //#endregion
}
