diff --git a/Documents/GeneratedContext/xDashboard-x-chart-20260821_211240.txt b/Documents/GeneratedContext/xDashboard-x-chart-20260821_211240.txt new file mode 100644 index 0000000..8e30fa6 --- /dev/null +++ b/Documents/GeneratedContext/xDashboard-x-chart-20260821_211240.txt @@ -0,0 +1,3879 @@ + +### FILE: C:\Users\SaherElm\Documents\Projects\xSaherelmWorkspace\Modules\xFrameworkComponentsHolder\projects\x-framework-components\src\lib\x-chart\x-chart.component.html + + + + + + + + +
+ +
+ + +
+ +
+
+
+ + + + +
+
+
+
+ +### 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('autoUpdate'), + ); + if (isAutoUpdateChanged) { + // + if (isNullOrUndefined(this.autoUpdate)) { + this.autoUpdate = this.propertyProvider.autoUpdate; + } + } + + // + // Action Provider Changed ... + const isActionProviderChanged = changeKeys.includes( + nameof('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('options'), + ); + if (isOptionsChanged) { + // + if (isNullOrUndefined(this.options)) { + this.options = this.propertyProvider.options; + } + + // + await this.handleChartUpdate(); + } + + // + // Chart Type ... + const isTypeChanged = changeKeys.includes( + nameof('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('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`. + | XChartHierarchyNode[] + | Record[]; +}[]; + +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 + * `":"`. + */ + 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: ''` 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 `"# of "`. + */ + 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 | Array>; + 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; + plotOptions?: XChartPlotOptions; + xaxis?: XChartXAxis; + yaxis?: XChartYAxis | XChartYAxis[]; + colors?: Array 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; + /** + * 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; + 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)>; + 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; + 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 {} + +export type XChartSeriesType = XChartAxisChartSeries | XChartNonAxisChartSeries; +export interface IXChartContainer extends IXComponentCardContainer { + // + // Commons ... + actionProvider: Subject; + cssClass: XStandardType>; + + // + // Chart Props ... + autoUpdate: XStandardType; + options: XStandardType; + series: XStandardType; + + // + type: XStandardType; + + // + // Events ... + chartRendered: EventEmitter; + dataPointSelected: EventEmitter; + zoomed: EventEmitter; +} + +@Injectable({ + providedIn: 'root', +}) +export class XChartContainer + extends XComponentCardContainer + implements Readonly +{ + // + readonly options: XStandardType; + readonly series: XStandardType; + readonly autoUpdate: XStandardType = true; + readonly cssClass: XStandardType>; + readonly actionProvider = new Subject(); + readonly type: XStandardType = XChartType.Line; + + // + readonly zoomed = new EventEmitter(); + readonly chartRendered = new EventEmitter(); + readonly dataPointSelected = new EventEmitter(); +} + +@Injectable() +@Component({ template: `` }) +export abstract class XChartContainerComponent + extends XComponentCardContainerComponent + implements IXChartContainer +{ + // + //#region Props ... + @Input() + actionProvider: Subject = + this.propertyProvider.actionProvider; + + @Input() + cssClass: XStandardType> = + this.propertyProvider.cssClass; + + /** + * Specifiy Type of Chart Drawing ... + */ + @Input() + type: XStandardType = this.propertyProvider.type; + + @Input() + options: XStandardType = this.propertyProvider.options; + + @Input() + series: XStandardType = this.propertyProvider.series; + + @Input() + autoUpdate: XStandardType = this.propertyProvider.autoUpdate; + + // + // Events ... + @Output() + chartRendered: EventEmitter = new EventEmitter(); + + @Output() + dataPointSelected: EventEmitter = new EventEmitter(); + + @Output() + zoomed: EventEmitter = new EventEmitter(); + + // + //#region ComponentCard Props ... + @Input() + isInPage: XStandardType = this.propertyProvider.isInPage; + + @Input() + wrapWithCard: XStandardType = this.propertyProvider.wrapWithCard; + + @Input() + cardCssClass: XStandardType> = + this.propertyProvider.cardCssClass; + + @Input() + cardColor: XStandardType = this.propertyProvider.cardColor; + + @Input() + cardForegroundColor: XStandardType = + this.propertyProvider.cardForegroundColor; + + @Input() + cardTitle: XStandardType = + this.propertyProvider.cardTitle; + + @Input() + cardSubTitle: XStandardType = + this.propertyProvider.cardSubTitle; + + @Input() + cardShowFooter: XStandardType = this.propertyProvider.cardShowFooter; + + @Input() + cardShowActions: XStandardType = + this.propertyProvider.cardShowActions; + + @Input() + cardActionProvider: Subject = + 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 { + 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 +} diff --git a/Modules/xFrameworkComponentsHolder b/Modules/xFrameworkComponentsHolder index 0f37e94..e768ba2 160000 --- a/Modules/xFrameworkComponentsHolder +++ b/Modules/xFrameworkComponentsHolder @@ -1 +1 @@ -Subproject commit 0f37e94ac9c5b3dbab4f77b726a5e84b1c5da7da +Subproject commit e768ba219c783bdb81d222d14c5fd03dac67957d