UNPKG

apexcharts

Version:

A JavaScript Chart Library

2,520 lines 101 kB
// @ts-check
/**
 * ApexCharts Options for setting the initial configuration of ApexCharts
 **/
import en from './../../locales/en.json'

export default class Options {
  constructor() {
    this.yAxis = {
      show: true,
      showAlways: false,
      showForNullSeries: true,
      seriesName: undefined,
      opposite: false,
      reversed: false,
      logarithmic: false,
      logBase: 10,
      tickAmount: undefined,
      stepSize: undefined,
      forceNiceScale: false,
      alignZero: false,
      max: undefined,
      min: undefined,
      floating: false,
      decimalsInFloat: undefined,
      labels: {
        show: true,
        showDuplicates: false,
        minWidth: 0,
        maxWidth: 160,
        offsetX: 0,
        offsetY: 0,
        align: undefined,
        rotate: 0,
        padding: 20,
        style: {
          colors: [],
          fontSize: '11px',
          fontWeight: 400,
          fontFamily: undefined,
          cssClass: '',
        },
        formatter: undefined,
      },
      axisBorder: {
        show: false,
        color: '#e0e0e0',
        width: 1,
        offsetX: 0,
        offsetY: 0,
      },
      axisTicks: {
        show: false,
        color: '#e0e0e0',
        width: 6,
        offsetX: 0,
        offsetY: 0,
      },
      title: {
        text: undefined,
        rotate: -90,
        offsetY: 0,
        offsetX: 0,
        style: {
          color: undefined,
          fontSize: '11px',
          fontWeight: 900,
          fontFamily: undefined,
          cssClass: '',
        },
      },
      tooltip: {
        enabled: false,
        offsetX: 0,
      },
      crosshairs: {
        show: true,
        position: 'front',
        stroke: {
          color: '#b6b6b6',
          width: 1,
          dashArray: 0,
        },
      },
    }

    this.pointAnnotation = {
      id: undefined,
      x: 0,
      y: null,
      yAxisIndex: 0,
      seriesIndex: undefined,
      mouseEnter: undefined,
      mouseLeave: undefined,
      click: undefined,
      marker: {
        size: 4,
        fillColor: '#fff',
        strokeWidth: 2,
        strokeColor: '#333',
        shape: 'circle',
        offsetX: 0,
        offsetY: 0,
        // radius: 2, // DEPRECATED
        cssClass: '',
      },
      label: {
        borderColor: '#c2c2c2',
        borderWidth: 1,
        borderRadius: 2,
        text: undefined,
        textAnchor: 'middle',
        offsetX: 0,
        offsetY: 0,
        mouseEnter: undefined,
        mouseLeave: undefined,
        click: undefined,
        style: {
          background: '#fff',
          color: undefined,
          fontSize: '11px',
          fontFamily: undefined,
          fontWeight: 400,
          cssClass: '',
          padding: {
            left: 5,
            right: 5,
            top: 2,
            bottom: 2,
          },
        },
      },
      customSVG: {
        // this will be deprecated in the next major version as it is going to be replaced with a better alternative below (image)
        SVG: undefined,
        cssClass: undefined,
        offsetX: 0,
        offsetY: 0,
      },
      image: {
        path: undefined,
        width: 20,
        height: 20,
        offsetX: 0,
        offsetY: 0,
      },
      tooltip: {
        // Show a hover tooltip over the annotation marker, like a regular
        // data point. Lets you surface richer detail than fits in the label.
        enabled: false,
        // Static tooltip content (HTML allowed; array joined with <br/>).
        // Falls back to label.text when omitted.
        text: undefined,
        // formatter({ annotation, seriesIndex, id, w }) => string (HTML).
        // Takes precedence over `text` when provided.
        formatter: undefined,
        // 'light' | 'dark'. Falls back to the global tooltip.theme.
        theme: undefined,
        offsetX: 0,
        offsetY: 0,
      },
    }

    this.yAxisAnnotation = {
      id: undefined,
      y: 0,
      y2: null,
      strokeDashArray: 1,
      fillColor: '#c2c2c2',
      borderColor: '#c2c2c2',
      borderWidth: 1,
      opacity: 0.3,
      offsetX: 0,
      offsetY: 0,
      width: '100%',
      yAxisIndex: 0,
      label: {
        borderColor: '#c2c2c2',
        borderWidth: 1,
        borderRadius: 2,
        text: undefined,
        textAnchor: 'end',
        position: 'right',
        offsetX: 0,
        offsetY: -3,
        mouseEnter: undefined,
        mouseLeave: undefined,
        click: undefined,
        style: {
          background: '#fff',
          color: undefined,
          fontSize: '11px',
          fontFamily: undefined,
          fontWeight: 400,
          cssClass: '',
          padding: {
            left: 5,
            right: 5,
            top: 2,
            bottom: 2,
          },
        },
      },
    }

    this.xAxisAnnotation = {
      id: undefined,
      x: 0,
      x2: null,
      strokeDashArray: 1,
      fillColor: '#c2c2c2',
      borderColor: '#c2c2c2',
      borderWidth: 1,
      opacity: 0.3,
      offsetX: 0,
      offsetY: 0,
      label: {
        borderColor: '#c2c2c2',
        borderWidth: 1,
        borderRadius: 2,
        text: undefined,
        textAnchor: 'middle',
        orientation: 'vertical',
        position: 'top',
        offsetX: 0,
        offsetY: 0,
        mouseEnter: undefined,
        mouseLeave: undefined,
        click: undefined,
        style: {
          background: '#fff',
          color: undefined,
          fontSize: '11px',
          fontFamily: undefined,
          fontWeight: 400,
          cssClass: '',
          padding: {
            left: 5,
            right: 5,
            top: 2,
            bottom: 2,
          },
        },
      },
    }

    this.text = {
      x: 0,
      y: 0,
      text: '',
      textAnchor: 'start',
      foreColor: undefined,
      fontSize: '13px',
      fontFamily: undefined,
      fontWeight: 400,
      appendTo: '.apexcharts-annotations',
      backgroundColor: 'transparent',
      borderColor: '#c2c2c2',
      borderRadius: 0,
      borderWidth: 0,
      paddingLeft: 4,
      paddingRight: 4,
      paddingTop: 2,
      paddingBottom: 2,
    }
  }
  init() {
    return {
      annotations: {
        yaxis: [this.yAxisAnnotation],
        xaxis: [this.xAxisAnnotation],
        points: [this.pointAnnotation],
        texts: [],
        images: [],
        shapes: [],
      },
      // Weave (#1): public plugin platform. Per-chart activation list:
      // { name, options?, order? }. Requires the Weave host to be bundled
      // (`import 'apexcharts/features/weave'`, included in the full bundle) and
      // the plugin registered via ApexCharts.registerPlugin().
      plugins: [],
      // Trellis (#22): small multiples / faceting. Requires the trellis
      // feature (`import 'apexcharts/features/trellis'`, included in the full
      // bundle). Setting `by` makes this chart a trellis HOST: the series
      // array is split into one panel per facet-key value, each panel is a
      // real chart of this chart.type, and the trellis owns everything shared
      // (scale domains, pixel-aligned plot rects, color-by-series-name, one
      // legend/title/toolbar, headers, responsive columns).
      trellis: {
        // Facet accessor: a series-object key name, or (series, i) => key.
        // Series WITHOUT the key repeat in every panel (reference series).
        by: undefined,
        // 2-D faceting (P4): row and/or column facet accessors, forming a
        // FIXED grid of every (row, column) combination in row-major order
        // (no responsive recolumning; panels shrink instead). Mutually
        // exclusive with `by`. Column labels draw once across the top, row
        // labels once down the left. Reference semantics per dimension: a
        // series with only the row key repeats across that row; only the
        // column key, down that column; neither, everywhere.
        row: undefined,
        column: undefined,
        // Missing (row, column) combinations: 'placeholder' mounts a real
        // empty panel (same scales, same geometry, a quiet "no data" label);
        // 'skip' keeps the slot with a tinted blank; 'hide' keeps the slot
        // with nothing at all.
        emptyPanels: 'placeholder',
        // Tidy-row input (alternative to `series`): a row table pivoted by
        // the `by`/`x`/`y`/`seriesBy` COLUMN NAMES. Rows win over `series`
        // when both are given. Duplicate (panel, series, x) rows keep the
        // last and warn; aggregate the rows first for sums/means.
        data: undefined, // [{ date, region, revenue }, ...]
        x: undefined, // x-value column name (tidy form only)
        y: undefined, // y-value column name (tidy form only)
        seriesBy: undefined, // optional series-name column (tidy form only)
        // Layout
        columns: 'auto', // 'auto' (fit minPanelWidth) | number
        minPanelWidth: 220, // px; drives 'auto' and the responsive collapse
        gap: 12, // px between cells
        aspectRatio: 1.6, // panel w:h when no explicit height governs
        panelHeight: undefined, // px; wins over aspectRatio/chart.height
        order: 'first-seen', // | 'asc' | 'desc' | string[] | comparator
        limit: undefined, // render only the first N panels (warns)
        // Virtualization: 'auto' mounts only the panels intersecting the
        // viewport (plus one row) once the grid exceeds 64 panels; true
        // always virtualizes; false always renders eagerly. Unmounted cells
        // keep their header and a fixed-height skeleton (page height and
        // scroll position never shift); a panel that scrolls out is
        // destroyed with its view state stashed, and a remount restores its
        // zoom window. getPanel(key) returns null for unmounted panels.
        virtualize: 'auto', // 'auto' | true | false
        // Scale resolution per channel: 'shared' | 'independent'; y also
        // takes 'independent-row' | 'independent-column' in a 2-D grid (one
        // shared domain per row/column: comparable along the group, free
        // across groups). Non-shared y still renders pixel-aligned panels
        // (the gutter pass equalizes axis widths); 'independent' and
        // 'independent-column' force their own y labels on every panel,
        // 'independent-row' keeps them on the first column (ticks are
        // identical along a row).
        scales: {
          x: 'shared',
          y: 'shared',
          color: 'shared',
          size: 'shared',
        },
        // Per-cell facet headers.
        header: {
          show: true,
          formatter: undefined, // (key, { dimension, index, count }) => string
          style: {
            fontSize: undefined,
            fontWeight: undefined,
            color: undefined,
          },
        },
        // Axis-label policy: 'edges' shows y labels on the first column and x
        // labels on each column's bottom panel (label SPACE is always
        // reserved everywhere, so panels stay aligned); 'all' | 'none'.
        axes: {
          labels: 'edges',
        },
        legend: 'shared', // 'shared' | 'none' (per-panel legends are hidden)
        toolbar: 'shared', // 'shared' | 'none' (zoom / pan / reset)
        // 'panel': tooltip card only in the hovered panel, crosshair sweeps
        // all panels. 'sync': every panel shows its own card at the hovered x.
        // 'grid': ONE card near the cursor with one row per panel at the
        // hovered x (composed from the panels' own tooltips, so every
        // formatter is honored; unmounted virtualized panels have no row).
        tooltip: 'panel',
        zoom: 'sync', // 'sync' (drag/wheel zoom moves every panel) | 'none'
        // Panel promotion: clicking a cell's header expands that panel to
        // the grid's full width, with an "All panels" breadcrumb back
        // (also chart.promotePanel(key) / chart.restorePanels()).
        promote: true,
        // Pie/donut/polarArea only: scale each panel's radius so its AREA is
        // proportional to the panel's total (equal-size pies cannot encode
        // magnitude, which is the honest objection to a pie trellis).
        radiusByTotal: false,
        // Tick-interval target for the shared nice y scale. Panels are small:
        // 3 intervals (up to ~4 labels) keeps the axis from outweighing the data.
        targetTicks: 3,
        // Per-panel option override, applied last:
        // (key, { index, seriesNames }) => partial options
        panel: undefined,
      },
      chart: {
        animations: {
          // Master switch — set false to render charts without any animation.
          // Each chart type gets a tailored initial-mount animation by default:
          //   line/area/rangeArea/radar: pen-stroke draw + radial fill bloom
          //   bar/stacked/range/funnel:  grow from baseline (+ stagger)
          //   scatter/bubble:            scale-up pop with overshoot
          //   heatmap:                   diagonal-wave cell reveal
          //   treemap:                   largest-tile-first cascade
          //   pie/donut/polar/gauge:     arc sweep + needle settle
          // Speed is controlled by `speed`; per-element stagger by
          // `animateGradually.enabled` / `animateGradually.delay`.
          enabled: true,
          speed: 800,
          // Cadence (#6): easing for the generic tweens (data-update value
          // transitions, path morphs, marker animate). A registered name, a
          // cubic-bezier [x1,y1,x2,y2] array, or a function (t in [0,1]).
          // 'easeInOutSine' is the historical curve, so the default is
          // behavior-neutral. The tuned initial-draw pen/pop easings are fixed.
          easing: 'easeInOutSine',
          animateGradually: {
            // Drives per-element stagger across all chart types. When enabled,
            // bars/heatmap-cells/scatter-points/treemap-tiles reveal in
            // sequence; line/area markers fade in progressively along the
            // draw. `delay` is the requested step in ms (auto-capped per
            // chart so total stagger ≤ ~half the animation duration).
            delay: 150,
            enabled: true,
          },
          dynamicAnimation: {
            // Data-change (updateSeries) animation. Independent from the
            // initial-mount animations above.
            enabled: true,
            speed: 350,
            // Easing for data-change morphs only (same accepted forms as
            // `animations.easing`). Unset -> inherit the chart-wide easing,
            // except detected streaming scrolls (rolling window / append
            // under xaxis.range) which default to 'linear' so the window
            // slides at constant velocity instead of pulsing each tick.
            easing: undefined,
          },
          chartTypeMorph: {
            // Cross-type morph (updateOptions changing chart.type). Bridges
            // the destroy+recreate flicker by sampling source + target paths
            // into N evenly-spaced perimeter points and tweening point-by-point
            // with rotation-search alignment, so the transition is always smooth
            // and non-self-intersecting even between very different shapes (bar
            // rect ↔ pie wedge / radial arc). Supported pairs include bar ↔
            // pie / donut / radialBar / polarArea / funnel / pyramid (plus the
            // trivial pie ↔ donut ↔ polarArea cases). Falls back to instant
            // snap when types or data shape are incompatible.
            enabled: true,
            speed: 600,
          },
          // Honor the OS-level prefers-reduced-motion setting. When true (default)
          // and the user has the accessibility preference enabled, all initial-mount
          // animations are skipped and the chart renders instantly.
          respectReducedMotion: true,
          // Above this many data points, per-element morph + stagger (which
          // spins up one JS-driven animation timeline per path — three chained
          // tweens each in morphSVG) is replaced by a single GPU-composited
          // opacity fade of the whole series. Thousands of candlesticks/bars
          // otherwise jank the main thread on initial render and on every zoom.
          // The fade reuses the existing delayedElements reveal so the result
          // still animates in, just at O(1) cost instead of O(n). Set to 0 to
          // always animate per-element regardless of dataset size.
          largeDatasetThreshold: 1000,
        },
        background: '',
        locales: [en],
        defaultLocale: 'en',
        // Perspectives (#10): serializable/shareable view state. Passive:
        // requires `import 'apexcharts/features/perspectives'`. serializeOptions
        // is the whitelist of function-free option paths stored in a token.
        perspectives: {
          serializeOptions: ['theme', 'xaxis', 'yaxis', 'title', 'subtitle'],
        },
        // Rewind (#3): undo/redo history. Opt-in (bundle + behavior): requires
        // `import 'apexcharts/features/history'` AND chart.history.enabled.
        history: {
          enabled: false,
          maxDepth: 100,
          coalesceMs: 250,
          keyboard: true,
        },
        // Strata (#2): hybrid SVG+canvas series renderer. 'svg' (default) |
        // 'canvas' | 'auto'. 'auto'/'canvas' need the canvas renderer feature
        // (`import 'apexcharts/features/renderer-canvas'`); without it, or with
        // a canvas-unsupported feature (pattern/image fill, color-matrix state
        // filters), selection falls back to 'svg'. Only the series layer is
        // canvas-capable in v1; chrome stays SVG.
        renderer: 'svg',
        rendererThreshold: 8000,
        layers: {
          series: 'auto',
          grid: 'svg',
          annotations: 'svg',
          dataLabels: 'svg',
        },
        dropShadow: {
          enabled: false,
          enabledOnSeries: undefined,
          top: 2,
          left: 2,
          blur: 4,
          color: '#000',
          opacity: 0.7,
        },
        events: {
          animationEnd: undefined,
          beforeMount: undefined,
          mounted: undefined,
          updated: undefined,
          click: undefined,
          mouseMove: undefined,
          mouseLeave: undefined,
          xAxisLabelClick: undefined,
          legendClick: undefined,
          markerClick: undefined,
          selection: undefined,
          dataPointSelection: undefined,
          dataPointMouseEnter: undefined,
          dataPointMouseLeave: undefined,
          beforeZoom: undefined,
          beforeResetZoom: undefined,
          zoomed: undefined,
          scrolled: undefined,
          brushScrolled: undefined,
          crossFilter: undefined,
          filterChange: undefined,
          annotationDragged: undefined,
          annotationEdited: undefined,
          annotationCreated: undefined,
          annotationStyled: undefined,
          annotationDeleted: undefined,
          measured: undefined,
          keyDown: undefined,
          keyUp: undefined,
        },
        foreColor: '#373d3f',
        fontFamily: 'Helvetica, Arial, sans-serif',
        height: 'auto',
        parentHeightOffset: 15,
        redrawOnParentResize: true,
        redrawOnWindowResize: true,
        id: undefined,
        group: undefined,
        nonce: undefined,
        // Per-chart license key override for the gated premium features
        // (storyboard, link/crossfilter, ink, measure, contextMenu,
        // perspectives, history). Most specific wins: chart.license ->
        // ApexCharts.setLicense() -> window.Apex.license -> unlicensed (trial
        // watermark). Shared across the ApexCharts family. See setLicense().
        license: undefined,
        offsetX: 0,
        offsetY: 0,
        injectStyleSheet: true,
        selection: {
          enabled: false,
          type: 'x',
          // selectedPoints: undefined, // default datapoints that should be selected automatically
          fill: {
            color: '#24292e',
            opacity: 0.1,
          },
          stroke: {
            width: 1,
            color: '#24292e',
            opacity: 0.4,
            dashArray: 3,
          },
          xaxis: {
            min: undefined,
            max: undefined,
          },
          yaxis: {
            min: undefined,
            max: undefined,
          },
        },
        sparkline: {
          enabled: false,
        },
        brush: {
          enabled: false,
          autoScaleYaxis: true,
          target: undefined,
          targets: undefined,
        },
        // Linked Views (#4): crossfilter / linked highlighting. Requires the
        // `link` feature. Two modes:
        //   HIGHLIGHT (P1): `enabled` with no `dimension`. Charts sharing a
        //   `chart.group` form a set; brushing a range (needs
        //   `chart.selection.enabled`) dims out-of-range marks in place.
        //   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 the other charts. See docs/spec.
        link: {
          enabled: false,
          mode: 'highlight',
          dimOpacity: 0.2,
          // FILTER-mode config (all optional except dimension):
          id: undefined, // crossfilter coordinator id (defaults to chart.group)
          dimension: undefined, // (row) => key; presence selects filter mode
          reduce: undefined, // 'count' | { sum|avg|min|max: field } | (rows)=>n
          type: undefined, // 'category' | 'range' (else inferred from bins)
          bins: undefined, // range dims: { width } | { count } | { thresholds }
          order: undefined, // category order: 'first-seen' | 'asc' | 'desc' | fn
          seriesName: undefined, // axis-chart series name (default 'Count')
        },
        // 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: rename inline,
        // recolor, 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. Fires annotationDragged,
        // annotationEdited, annotationStyled and annotationDeleted.
        ink: {
          enabled: false,
          // Show a minimal "add note" tool palette; clicking it arms create
          // mode (the next plot click drops an editable, draggable annotation).
          palette: false,
          // Snap a dragged point / axis-line annotation to the nearest gridline
          // (numeric x + linear y). Undo/redo of ink edits is automatic when the
          // history (Rewind) feature is enabled.
          snap: false,
          // Accent swatches offered by the floating note editor; defaults to a
          // built-in 6-color palette when undefined.
          noteColors: undefined,
        },
        // Measure ruler (#18): a measure/delta ruler. Requires the `measure`
        // feature. Hold `key` (default 'm') and drag A->B on the plot, or call
        // chart.startMeasure()/chart.stopMeasure() to arm it. The live ruler
        // reads dx, dy, %change and slope; on release it pins as a data-anchored
        // overlay that re-projects on zoom/resize. Fires `measured`.
        measure: {
          enabled: false,
          // 'span' (default): finance-style vertical band between two x-points
          // with a "change (%) + range" readout, endpoints snapped to the first
          // series. 'free': a diagonal ruler between two arbitrary points.
          mode: 'span',
          key: 'm',
          pinOnRelease: true,
          // Styling tokens. Every element also carries a stable CSS class
          // (apexcharts-measure-band / -vline / -line / -label-bg / -label,
          // group gets apexcharts-measure-up|down|flat). Colors are left
          // undefined so they resolve config -> `--apx-measure-*` CSS custom
          // property -> built-in default; set them here to force a color from JS.
          colors: {
            up: undefined,
            down: undefined,
            neutral: undefined,
            guide: undefined,
          },
          band: true, // span mode: shaded band between the two x-positions
          guides: true, // span mode: vertical dashed reference lines
          markers: true, // endpoint dots on the series line
          // Content: value formatters and a full readout override. `label`
          // receives { from, to, dx, dy, percentChange, slope, mode } and
          // returns a string or string[] (lines).
          format: { x: undefined, y: undefined, percent: undefined },
          label: undefined,
        },
        // Radial Actions (#chrome): right-click / long-press context menu.
        // Requires the `contextMenu` feature. Each action receives the clicked
        // data coordinates, so verbs act at the point (not chart-wide like a
        // toolbar button). `items` is an ordered list of built-in ids
        // ('annotate' | 'xline' | 'yline' | 'measure') and/or custom
        // { id, label, icon, onClick(ctx, { x, y, seriesIndex, dataPointIndex,
        // clientX, clientY }) }. 'measure' is shown only when the measure tool
        // is enabled. `labels` overrides built-in text; `noteText` is the label
        // dropped by 'annotate'.
        contextMenu: {
          enabled: false,
          items: ['annotate', 'xline', 'yline', 'measure'],
          labels: {
            annotate: undefined,
            xline: undefined,
            yline: undefined,
            measure: undefined,
          },
          noteText: 'Note',
          // 'xline' ("Annotate here") drops a dashed vertical LINE at the
          // clicked x; 'yline' ("Mark this level") a dashed horizontal line at
          // the clicked y. Lines only, never a range rectangle. Like the note,
          // a line is ink-managed when the ink feature is bundled: it opens
          // the floating editor (rename; separate Label and Line color rows,
          // so restyling the chip cannot blank the stroke; delete), drags
          // along its axis, and undoes via Rewind. `line` styles both items.
          line: {
            text: '', // label drawn on the line; empty for no label
            strokeDashArray: 4,
            color: undefined, // undefined keeps the annotation default color
          },
        },
        stacked: false,
        stackOnlyBar: true, // mixed chart with stacked bars and line series - incorrect line draw #907
        stackType: 'normal',
        // 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 two-point off-screen
        // runway (so exiting segments slide off the left edge instead of
        // popping). The scroll animation itself needs no opt-in; updates
        // that continue the previous window (appendData, or updateSeries with
        // a shifted fixed-length window) always translate at constant
        // velocity; see modules/animations/StreamScroll.
        streaming: {
          enabled: false,
          maxPoints: undefined,
        },
        toolbar: {
          show: true,
          offsetX: 0,
          offsetY: 0,
          tools: {
            download: true,
            selection: true,
            zoom: true,
            zoomin: true,
            zoomout: true,
            pan: true,
            reset: true,
            // Shown only when the measure ruler is active (chart.measure.enabled
            // and the `measure` feature bundled). Toggles the measure tool; set
            // false to keep the ruler key-driven only. See chart.measure.
            measure: true,
            customIcons: [],
          },
          export: {
            csv: {
              filename: undefined,
              columnDelimiter: ',',
              headerCategory: 'category',
              headerValue: 'value',
              categoryFormatter: undefined,
              valueFormatter: undefined,
            },
            png: {
              filename: undefined,
            },
            svg: {
              filename: undefined,
            },
            scale: undefined,
            width: undefined,
            // An exported SVG is a standalone document: it cannot reach the
            // page's @font-face rules, so a custom font falls back to a
            // generic one in the PNG/SVG. When true, matching @font-face
            // rules are inlined as base64 data URIs. See #3617.
            embedFonts: true,
          },
          autoSelected: 'zoom', // accepts -> zoom, pan, selection, measure
        },
        type: 'line',
        width: '100%',
        zoom: {
          enabled: true,
          type: 'x',
          autoScaleYaxis: false,
          // Wheel and pinch zoom can both be triggered without meaning to (a
          // page scroll or a two-finger swipe over the chart), so 'auto' offers
          // them only when the viewer has a way back: the toolbar's reset
          // button. With the toolbar hidden they stay off, since a zoom nobody
          // asked for and nobody can undo is a trap. Set either to true to
          // force the gesture on regardless (for a page that supplies its own
          // reset control), or false to turn it off outright.
          allowMouseWheelZoom: 'auto',
          // Momentum: two-finger pinch-zoom on touch devices. Zooms the x-axis
          // around the pinch centroid (matching the x-only wheel/toolbar zoom),
          // frame-by-frame rather than the 400ms wheel throttle.
          pinch: 'auto',
          zoomedArea: {
            fill: {
              color: '#90CAF9',
              opacity: 0.4,
            },
            stroke: {
              color: '#0D47A1',
              opacity: 0.4,
              width: 1,
            },
          },
        },
        // Momentum: kinetic panning on touch. When a one-finger pan is released
        // with velocity, the chart keeps gliding and decelerates by `friction`
        // each frame, clamping (no elastic overshoot) at the data edges.
        pan: {
          inertia: true,
          friction: 0.92,
        },
        accessibility: {
          enabled: true,
          description: undefined,
          announcements: {
            enabled: true,
          },
          keyboard: {
            enabled: true,
            navigation: {
              enabled: true,
              wrapAround: false,
            },
          },
        },
        dataReducer: {
          enabled: false,
          algorithm: 'lttb',
          targetPoints: 250,
          threshold: 500,
        },
      },
      parsing: {
        x: undefined,
        y: undefined,
      },
      plotOptions: {
        line: {
          isSlopeChart: false,
          colors: {
            threshold: 0,
            colorAboveThreshold: undefined,
            colorBelowThreshold: undefined,
          },
        },
        area: {
          fillTo: 'origin',
        },
        bar: {
          horizontal: false,
          columnWidth: '70%', // should be in percent 0 - 100
          barHeight: '70%', // should be in percent 0 - 100
          distributed: false,
          borderRadius: 0,
          borderRadiusApplication: 'around', // [around, end]
          rangeBarOverlap: true,
          rangeBarGroupRows: false,
          hideZeroBarsWhenGrouped: false,
          isDumbbell: false,
          dumbbellColors: undefined,
          isFunnel: false,
          isFunnel3d: true,
          colors: {
            ranges: [],
            backgroundBarColors: [],
            backgroundBarOpacity: 1,
            backgroundBarRadius: 0,
          },
          dataLabels: {
            position: 'top', // top, center, bottom
            maxItems: 100,
            hideOverflowingLabels: true,
            orientation: 'horizontal',
            total: {
              enabled: false,
              formatter: undefined,
              offsetX: 0,
              offsetY: 0,
              style: {
                color: '#373d3f',
                fontSize: '12px',
                fontFamily: undefined,
                fontWeight: 600,
              },
            },
          },
        },
        bubble: {
          zScaling: true,
          minBubbleRadius: undefined,
          maxBubbleRadius: undefined,
          // Explicit z window for the size scale. EXPANDS the data's own z
          // extent, never clamps it, so several bubble charts can share one
          // size scale (a trellis pushes the union extent through these).
          minZ: undefined,
          maxZ: undefined,
        },
        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 are scattered
          //    horizontally within it. Marker styling comes from the standard
          //    `markers` / `colors` config.
          //  - 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 (x: 1 = one category step) and are
          // deterministic (stable across re-renders, SSR-safe).
          jitter: {
            enabled: false,
            x: 0, // max ± horizontal offset, in x-axis units
            y: 0, // max ± vertical offset, in y-axis units
            distributed: false, // single series: colour each band differently
            maxPoints: 5000, // per band; excess values are stride-thinned
          },
        },
        candlestick: {
          colors: {
            upward: '#00B746',
            downward: '#EF403C',
          },
          wick: {
            useFillColor: true,
          },
        },
        boxPlot: {
          colors: {
            upper: '#00E396',
            lower: '#008FFB',
          },
          // Where the whiskers reach when the summary is DERIVED from raw
          // observations (a datum supplying `points` instead of a 5-number y,
          // which needs `apexcharts/features/stats`). Ignored for precomputed
          // summaries, which are drawn exactly as given.
          //   'minmax' → the extremes, so nothing is hidden
          //   'tukey'  → the last observation inside 1.5 * IQR of each
          //              quartile. Points beyond the fence fall outside the
          //              whisker, so pair it with `points.show` or they become
          //              invisible.
          whiskers: 'minmax',
          // Optional individual observations ("jitter") overlaid on each box.
          // Inert unless a data point supplies a `points: number[]` array; off
          // by default so existing boxPlot charts are unchanged.
          points: {
            show: false,
            shape: 'circle', // 'circle' | 'square'
            size: 2.5, // radius (px)
            jitter: 0.5, // 0..1 fraction of the box half-width to scatter within
            maxPoints: 3000, // cap per box; excess is stride-thinned
            opacity: 0.9,
            // 'series-dark' (default) → a darker shade of the series colour,
            // 'series' → the series colour, or any literal colour string.
            fillColor: 'series-dark',
            strokeColor: '#fff',
            strokeWidth: 1,
            // Optional `colorScale` (undeclared so a user object merges cleanly)
            // colours each dot by its value: { colors, min, max, steps }
          },
        },
        violin: {
          // Multiply the density-derived half-width. 1 = density's own maxWeight
          // maps to half the category slot.
          bandwidthScale: 1,
          // 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 needs `apexcharts/features/stats`). A
          // precomputed density profile is drawn exactly as given.
          //   bandwidth  → kernel width in value units. Unset uses Silverman's
          //                rule of thumb, which takes the smaller of the
          //                standard deviation and a scaled IQR so one distant
          //                outlier cannot smear the curve flat. Note this is a
          //                statistical parameter, unlike `bandwidthScale`
          //                above, which only scales the drawn width.
          //   resolution → density samples per violin (default 64).
          kde: {
            bandwidth: undefined,
            resolution: 64,
          },
          // 'individual' → every violin uses the full slot width (scaled to its
          // own peak). 'group' → all violins share one scale (the densest in the
          // series), so widths stay proportional to density across categories.
          normalize: 'individual',
          // Individual observations ("jitter") overlaid on the violin shape.
          points: {
            show: true,
            shape: 'circle', // 'circle' | 'square'
            size: 2.5, // radius (px)
            jitter: 0.5, // 0..1 fraction of the half-width to scatter within
            constrainToViolin: true, // clamp jitter to the density width at each value
            maxPoints: 3000, // cap per violin; excess is stride-thinned
            opacity: 0.9,
            // Default: a darker shade of each violin's own colour, with a white
            // outline. fillColor accepts 'series-dark' (default), 'series' (the
            // violin's colour as-is), or any literal colour string.
            fillColor: 'series-dark',
            strokeColor: '#fff',
            strokeWidth: 1,
            // Optional `colorScale` (left undeclared so a user object merges
            // cleanly) colours each dot by its value along a ramp:
            //   { colors: ['#0d0887', … '#f0f921'], min, max, steps }
          },
        },
        histogram: {
          // How the bin width is chosen from the raw observations. A rule name
          // ('auto' | 'fd' | 'sturges' | 'scott' | 'rice' | 'sqrt') or a fixed
          // bin count. 'auto' takes the narrower of Freedman-Diaconis and
          // Sturges, falling back to Sturges when the IQR is 0 (which happens
          // as soon as most values are identical).
          bins: 'auto',
          // Explicit bin width in value units. Wins over `bins` when set: use
          // it when the bin boundaries carry meaning (decades, 5-minute
          // buckets) rather than being a statistical choice.
          binWidth: undefined,
          // [min, max] to bin over, instead of the data's own extent. Lets
          // several histograms share one scale.
          range: undefined,
          // y units: 'count' (observations per bin), 'relative' (percent of
          // the series total), or 'density' (count / (n * binWidth), so the
          // total area is 1 and bins of different widths stay comparable).
          normalize: 'count',
          // Running total across bins, i.e. a cumulative distribution.
          cumulative: false,
          // With more than one series, draw every distribution across the FULL
          // bin so they overlay, instead of dividing the bin between them. All
          // series already share one set of edges, and comparing two shapes is
          // the reason to put them on one axis; splitting the bin makes the
          // columns stop touching, which reads as a clustered bar chart rather
          // than a distribution. Set false for side-by-side bars.
          //
          // An overlay is unreadable opaque, so it also softens the fill and
          // drops the bin separator stroke. Both remain overridable.
          overlap: true,
        },
        heatmap: {
          radius: 2,
          enableShades: true,
          shadeIntensity: 0.5,
          reverseNegativeShade: false,
          distributed: false,
          useFillColorAsStroke: false,
          colorScale: {
            inverse: false,
            ranges: [],
            min: undefined,
            max: undefined,
            // Replaces the default categorical legend with a continuous
            // gradient stripe + a hover indicator arrow. Honors
            // `chart.legend.position` (top / right / bottom / left).
            gradientLegend: {
              enabled: false,
              // Strip length along the legend's long axis. Accepts a number
              // (pixels) or a percentage string. For top/bottom placement the
              // percentage is resolved against the chart's SVG width; for
              // left/right placement, against the SVG height.
              width: '70%',
              height: '70%',
              thickness: 12,
              // Alignment of the strip within the legend area:
              //  - top/bottom: 'start' = left, 'center', 'end' = right
              //  - left/right: 'start' = top,  'center', 'end' = bottom
              align: 'center',
              // Number of gradient stops sampled from the shade function when
              // no explicit `ranges` are provided.
              stops: 16,
              // Show min/max labels at the ends of the strip.
              showLabels: true,
              // Show a value tooltip next to the arrow when hovering a cell.
              showHoverValue: true,
              labelStyle: {
                fontSize: '11px',
                fontFamily: undefined,
                colors: undefined,
              },
              arrow: {
                size: 8,
                color: undefined, // falls back to chart.foreColor
              },
              formatter: undefined, // (val) => string, for min/max + hover value
            },
          },
        },
        funnel: {
          // 'rectangle' 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',
          // For shape: 'trapezoid' only — what to do with the last stage's
          // bottom edge: 'flat' (parallel sides) or 'taper' (taper to a point).
          lastShape: 'flat',
        },
        treemap: {
          enableShades: true,
          shadeIntensity: 0.5,
          distributed: false,
          reverseNegativeShade: false,
          useFillColorAsStroke: false,
          borderRadius: 4,
          dataLabels: {
            format: 'scale', // scale | truncate
            // Skip a tile's label when it would render below this size.
            //
            // With `format: 'scale'` the font size follows the tile's area, so
            // on a dense treemap most tiles ask for text a few pixels tall,
            // which nobody can read. Drawing it is not free: each label has to
            // be built and measured against the DOM, and on a 10k-tile chart
            // that was the entire render cost (~7.5s, versus ~0.1s once the
            // unreadable ones are skipped).
            //
            // The default sits below the smallest label any bundled sample
            // draws, so it only ever removes text that was already illegible.
            // Set 0 to draw a label on every tile regardless of size.
            minFontSize: 4,
          },
          colorScale: {
            inverse: false,
            ranges: [],
            min: undefined,
            max: undefined,
            // Colour a tile by a SECOND metric, independent of the value that
            // sizes it (area = how big, colour = how it did). Reads
            // `datum.colorValue` unless this names another key or supplies an
            // accessor `(datum, {seriesIndex, dataPointIndex, w}) => number`.
            colorValue: undefined,
            // Continuous interpolation between colour stops. 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: undefined,
              // Domain. Defaults to the extent of the colour metric.
              min: undefined,
              max: undefined,
              // The value the middle colour is pinned to. Defaults to 0 when
              // the domain straddles zero (a diverging metric), else none.
              midpoint: undefined,
              // With a midpoint, balance the domain around it so equal moves
              // in either direction read as equally saturated.
              symmetric: true,
              // Low -> mid -> high. Two colours make a sequential ramp.
              colors: undefined,
              // Explicit `[{ value, color }]` stops; overrides colors/midpoint.
              stops: undefined,
            },
            // Continuous colour legend: a gradient strip with end labels and a
            // hover indicator, in place of the categorical legend. Same options
            // as the heatmap's.
            gradientLegend: {
              enabled: false,
              width: '70%',
              height: '70%',
              thickness: 12,
              align: 'center',
              stops: 16,
              showLabels: true,
              showHoverValue: true,
              labelStyle: {
                fontSize: '11px',
                fontFamily: undefined,
                colors: undefined,
              },
              arrow: {
                size: 8,
                color: undefined,
              },
              formatter: undefined,
            },
          },
          // Arbitrary-depth treemap: a datum may carry `children`.
          nested: {
            // Parent containers appear on their own as soon as the data is
            // nested; `false` forces the flat two-level layout.
            enabled: undefined,
            // Read `drilldown: '<id>'` ids as extra levels instead of as a
            // click target for the drilldown feature. Off by default, because
            // on a treemap that id has always meant "descend on click".
            drilldownAsLevels: false,
          },
          // How a parent is drawn once the data is nested. Per-level overrides
          // go in `levels` below.
          parents: {
            // 'auto' (default): on when the data carries `children`.
            show: 'auto',
            // Inset between a parent's edge and the children inside it.
            padding: 4,
            fill: undefined,
            fillOpacity: 1,
            borderColor: undefined,
            borderWidth: 1,
            borderRadius: undefined,
            hover: {
              show: true,
              color: undefined,
              width: 2,
            },
            header: {
              show: true,
              height: 22,
              // Skip the strip on tiles too narrow to show a name.
              minWidth: 40,
              align: 'left',
              offsetX: 0,
              offsetY: 0,
              showValue: false,
              // (name, { value, depth, seriesIndex, node, w }) => string
              formatter: undefined,
              style: {
                fontSize: '12px',
                fontFamily: undefined,
                fontWeight: 600,
                color: undefined,
                background: undefined,
                cssClass: '',
              },
            },
            tooltip: {
              // ({ name, value, depth, leafCount, percentOfParent,
              //    percentOfTotal, node, w }) => html
              formatter: undefined,
            },
          },
          // 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: [],
          // 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: false,
            // Overrides `drilldown.breadcrumb` for this chart only. Same shape
            // (show / position / separator / rootLabel / offsetX / offsetY /
            // formatter), so a zoomed treemap and a drilled-in chart present
            // the same affordance without importing the drilldown feature.
            breadcrumb: undefined,
          },
          seriesTitle: {
            show: true,
            offsetY: 1,
            offsetX: 1,
            borderColor: '#000',
            borderWidth: 1,
            borderRadius: 2,
            style: {
              background: 'rgba(0, 0, 0, 0.6)',
              color: '#fff',
              fontSize: '12px',
              fontFamily: undefined,
              fontWeight: 400,
              cssClass: '',
              padding: {
                left: 6,
                right: 6,
                top: 2,
                bottom: 2,
              },
            },
          },
        },
        unit: {
          // 'grouped' (each category is its own cluster, laid out in a row) |
          // 'packed' (one blob; categories coloured + sorted, minority centred) |
          // 'columns' (each category is a vertical bar built from stacked dots) |
          // 'grid' (one lattice of cells filled in category order - a waffle /
          // part-to-whole square "pie"; `chart.type:'waffle'` presets this) |
          // 'scatter' (units on real value axes: a beeswarm, or a 2D
          // value-value scatter - see the `scatter` block below) |
          // 'arc' (a semicircular fan) |
          // 'custom' (positions come from `positions` below).
          layout: 'grouped',
          // `layout: 'custom'` only. The layout provider: either a function
          // `(objects, rect) => [{id, x, y, r?}]` returning plot pixels, or the
          // name of one registered with `ApexCharts.registerUnitLayout`.
          //
          // This is the whole extension point. 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 this file
          // cannot know about - a country silhouette, a hex grid, a timeline, a
          // projection handed over by ApexMaps - is a plugin, not a core edit.
          //
          // `objects` carries one entry per mark: {id, index, seriesIndex,
          // dataPointIndex, label, value, datum, r}. `id` is the datum's own
          // id/name when the per-unit object form supplies one, so a provider
          // can address a specific unit rather than a positional slot.
          //
          // A mark whose id the provider omits animates out; ids matching no
          // mark are ignored.
          positions: undefined,
          // Update transition, controlling which previous dot each new dot
          // tweens from. 'group' (default): keyed per category, so dots stay in
          // their group and category-level enters/exits fade. 'flow': keyed by
          // global order, so the anonymous crowd migrates across a regroup (the
          // circles-to-bars effect). 'identity': keyed by each datum's id/name,
          // so a SPECIFIC unit migrates across any regroup/relayout keeping its
          // colour and size (needs the object form with unique ids/names).
          transition: 'group',
          // What ONE unit looks like. Independent of `layout`, which is where
          // the units go: `positions:'heart'` with `shape:'pictogram'` arranges
          // glyphs into a heart, and every other pairing is equally valid.
          //
          // 'circle' | 'square' | 'image' (a raster / multi-colour icon,
          // fetched) | 'pictogram' (a vector glyph, drawn - see `pictogram`).
          shape: 'circle',
          // Icon used when shape:'image'. Each unit renders this icon at the
          // given size. Set `tint:true` to recolour a monochrome icon to the
          // category colour (or a per-unit fillColor) so the pictogram matches
          // the legend; leave it off for multi-colour icons that should keep
          // their own colours.
          //
          // Prefer `shape:'pictogram'` for a monochrome glyph. Tinting an
          // <image> needs an SVG filter per colour, and a filter forces an
          // offscreen surface PER ELEMENT on every paint: measured on this
          // repo's cost lab, 2000 tinted icons cost ~10x what 2000 drawn
          // glyphs cost, and the gather drops frames well before 2000.
          image: {
            src: undefined,
            width: 20,
            height: 20,
            tint: false,
          },
          // `shape: 'pictogram'`. A glyph is DRAWN, not fetched: one <path> per
          // unit, filled in that unit's own colour, so it needs no request, no
          // decode and no recolour filter.
          //
          // `mark` is the glyph: the name of one registered with
          // `ApexCharts.registerUnitMark`, a `{path, viewBox?, fillRule?}`
          // object, raw path data, or an ARRAY (one per series). A single datum
          // overrides all of it with its own `mark`, exactly as `fillColor`
          // overrides the category colour - so one crowd can mix glyphs.
          //
          // There is deliberately no size here. A glyph is fitted to the box
          // the dot itself would have occupied, so `size` and `spacing` size a
          // pictogram exactly as they size a dot and swapping circle ->
          // pictogram never re-flows the chart. `fit` picks which side of the
          // glyph binds to that box, `scale` nudges glyphs that read light, and
          // `padding` (0..0.9 of the pitch) opens the lattice up.
          //
          // One caveat worth knowing: a filled glyph is hit-tested over its
          // INK, not its box, so the tooltip tracks the glyph exactly - it
          // closes in the gap between a person's legs and reopens on the next
          // glyph. Chunky glyphs therefore both read and BEHAVE better than
          // fine ones; a hairline glyph reads as flickery while sweeping a
          // crowd. There is no portable fix (`pointer-events: bounding-box` is
          // Chrome only).
          pictogram: {
            mark: undefined,
            fit: 'contain', // 'contain' (longest side) | 'width' | 'height'
            scale: 1,
            padding: 0,
            fallback: 'circle', // drawn when a mark cannot be resolved
          },
          // dot radius in px, or 'auto' to size dots so the largest cluster
          // fits its allotted box.
          size: '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). 'inherit' uses
          // `size`; 'auto' sizes the dots to fill the plot height; a number
          // pins a columns-only size. Circle / square only (image icons keep
          // their intrinsic size).
          columns: {
            size: 'inherit',
          },
          // The 'grid' (waffle) layout: one lattice of cells filled in category
          // order. `columns` = cells per row. `total` (optional) fixes the cell
          // budget - e.g. 100 for a percentage waffle - and largest-remainder
          // allocates the cells to categories; leave it undefined for one cell
          // per unit (respects unitValue / maxUnits). `fillFrom` picks the first
          // row: 'bottom' (default) or 'top'.
          //
          // `split:true` switches to SMALL MULTIPLES: one mini-waffle per
          // category, arranged in a near-square trellis (or `tileColumns` per
          // row). Each tile then has `total` cells (default 100) and fills a
          // fraction equal to the category's value over `max` (default = the
          // largest count, so the leader fills its tile; set `max:100` with
          // percentage data for true "of 100" tiles). The unfilled cells show as
          // a faint `trackColor` backdrop, and each tile gets its own label.
          grid: {
            columns: 10,
            total: undefined,
            fillFrom: 'bottom',
            // small-multiple (one waffle per category) mode + its knobs:
            split: false,
            // tiles per row; undefined = auto (near-square).
            tileColumns: undefined,
            // value -> filled-cell denominator; undefined = the largest count.
            max: undefined,
            // colour of the empty "track" cells; undefined = a neutral grey.
            trackColor: undefined,
          },
          // The 'scatter' layout places each unit on real value axes (needs the
          // object-form data). Two modes via `y`:
          //  - `y:'lanes'` (default): a BEESWARM. X is the per-unit value axis,
          //    Y is a category lane. `spread:'swarm'` packs dots off the centre
          //    line so equal values do not overlap; 'jitter' scatters randomly.
          //  - `y:'value'`: a 2D value-value scatter. X = each datum's `x`, Y =
          //    each datum's `y`, on two numeric axes; category = colour.
          // `sizeRange:[min,max]` turns dots into BUBBLES scaled (by area) from
          // each datum's `sizeField` (default 'z') - a bubble scatter / bubble
          // beeswarm. `tickAmount`/`xMin`/`xMax`/`xTitle`/`xFormatter` control the
          // X axis; the `y*` twins the Y axis (2D only); `laneLabelWidth` the
          // lane-label gutter (lanes mode); `gridlines` the grid.
          scatter: {
            y: 'lanes',
            spread: 'swarm',
            // Beeswarm orientation (1D lanes mode only): 'horizontal' lays the
            // value on the X axis with category lanes stacked on Y (default);
            // 'vertical' lays the value on the Y axis with category lanes as
            // columns across X. Ignored for the 2D value-value scatter (y:'value').
            orientation: 'horizontal',
            tickAmount: 5,
            xMin: undefined,
            xMax: undefined,
            xTitle: undefined,
            // (value) => string
            xFormatter: undefined,
            yTickAmount: 5,
            yMin: undefined,
            yMax: undefined,
            yTitle: undefined,
            // (value) => string
            yFormatter: undefined,
            // bubble sizing: datum key for the size value + [minR, maxR] in px.
            sizeField: 'z',
            sizeRange: undefined,
            laneLabelWidth: undefined,
            gridlines: true,
          },
          // Opt-in bubble sizing: scale each dot's radius by its per-unit value
          // (needs the object-form data). Circle shape only; the lattice is
          // spaced for the largest bubble so dots never overlap.
          sizeByValue: {
            enabled: false,
            // radius (px) for the largest value, or 'auto' to fit the largest
            // bubble to the plot like uniform auto-sizing.
            maxRadius: 'auto',
            // radius (px) for the smallest value; defaults to ~35% of maxRadius.
            minRadius: undefined,
            // 'area' (bubble AREA proportional to value) | 'linear'.
            scale: 'area',
          },
          // packing gap factor between spiral shells (1 = dots touch).
          spacing: 1.05,
          // corner radius for shape:'square'.
          borderRadius: 0,
          // How marks move between layouts on an update, and where entering
          // marks come from.
          //
          // `motion`: '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 - which is
          // what a dragged slider or a scrubbed storyboard does on almost every
          // frame. 'tween' runs the fixed-duration ease below instead. 'auto'
          // (the default) is spring, unless `easing` was set to something other
          // than the default, so an explicit curve keeps working without having
          // to set `motion` as well.
          //
          // `spring`: 'crisp' (default) | 'gentle' (softer, for large reflows)
          // | 'snappy' (faster, a hint of settle). Scaled by
          // `chart.animations.speed`, which stretches the spring in time
          // without making it bouncier.
          //
          // `easing` (tween only): 'outCubic' (default: decelerate to a stop) |
          // 'inOutCubic' (accelerate gently out of rest, weighted travel) |
          // 'outBack' (overshoot each mark past its slot and spring back - a
          // per-mark settle), with `overshoot` tuning the spring strength.
          //
          // `enter`: where a fresh / appearing mark animates FROM - 'burst'
          // (default: fly out from the cluster centre) | 'fade' (materialise in
          // place) | 'rise' (fade in while drifting gently up into the slot).
          //
          // Colour, radius and opacity always stay on the out-cubic, under
          // either motion (a back ease overshoots past 1, which would push RGB
          // channels / radii out of range; and the spring's rest thresholds are
          // absolute, so they are far too coarse for a 0..1 quantity).
          gather: {
            motion: 'auto',
            spring: 'crisp',
            easing: 'outCubic',
            overshoot: 1.70158,
            enter: 'burst',
          },
          // The 'arc' layout arranges marks as a PARLIAMENT / hemicycle: seats in
          // concentric arced rows across an annulus, filled in category order so
          // each category is a contiguous wedge (the classic seating chart). Angles
          // use the radialBar convention (0 = top, clockwise); the default sweep is
          // a top semicircle (a full circle = startAngle 0, endAngle 360).
          // `innerRadiusRatio` is the donut hole (inner radius / outer); `rows`
          // fixes the number of seat rows, or 'auto' to size the dots as large as
          // fit. Like 'packed' the legend carries the category names (no per-wedge
          // labels).
          arc: {
            startAngle: -90,
            endAngle: 90,
            innerRadiusRatio: 0.4,
            rows: 'auto',
          },
          // 1 dot represents this many units of value (waffle scaling).
          unitValue: 1,
          // safety cap on total dots; counts scale down proportionally above it.
          maxUnits: 5000,
          // packed layout: order categories smallest-first so the minority
          // group nests in the centre of the blob.
          sortByGroup: true,
          clusterLabels: {
            show: true,
            // Label placement relative to the cluster/bar: 'top' (default) or
            // 'bottom'. A 'bottom' label is always straight; the curved arc
            // (below) rides the top crown only.
            position: 'top',
            curved: true,
            fontSize: '13px',
            fontFamily: undefined,
            fontWeight: 600,
            // defaults to the cluster's own colour when undefined.
            color: undefined,
            offsetY: 0,
            // (name, { seriesIndex, value, percent, w }) => string.
            // Return "\n"-separated text to split an outer label over lines.
            formatter: undefined,
            // 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 needs no legend to be read.
            //
            // `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,
            // which means turning this on makes the silhouette a little smaller.
            external: {
              show: false,
              connector: {
                show: true,
                width: 1.5,
                // defaults to the band's own colour when undefined.
                color: undefined,
                // air between the band's outermost dot and the line's bend.
                gap: 8,
                // length of the run out to the label.
                length: 22,
              },
              offsetX: 0,
              offsetY: 0,
            },
          },
          tooltip: {
            // Per-unit tooltip body. Each dot carries its category (seriesIndex)
            // and its index within that category (dataPointIndex), so the
            // formatter can look up per-unit data and return a string/HTML.
            // ({ seriesName, seriesIndex, dataPointIndex, count, unitValue,
            //    color, w }) => string
            // Default: "#<dataPointIndex+1> of <count>".
            formatter: undefined,
          },
        },
        radialBar: {
          inverseOrder: false,
          startAngle: 0,
          endAngle: 360,
          offsetX: 0,
          offsetY: 0,
          // 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',
          // Value-to-angle mapping. Defaults to the existing 0..100 range
          // used by radialBar. Override for gauges that need a custom domain
          // (e.g. min: 0, max: 240 for a speedometer).
          min: 0,
          max: 100,
          // Threshold bands rendered as colored arc segments along the gauge
          // arc (e.g. [{from:0,to:30,color:'#FF4560'}, ...]). Bands draw
          // behind the value-arc and tick marks. Set to [] (default) to
          // disable.
          bands: [],
          bandsStyle: {
            // % of arc radius. Slightly less than the value-arc stroke so
            // the value-arc reads on top by default.
            strokeWidth: '40%',
            // px gap between consecutive bands.
            gap: 0,
            // Hide the track when bands cover the full range; the bands
            // themselves act as the visual backdrop.
            hideTrackWhenPresent: true,
          },
          // Tick marks rendered along (outside) the gauge arc.
          ticks: {
            show: false,
            major: {
              count: 11,
              length: 10,
              width: 2,
              color: '#666',
              // 'inside' draws ticks from `radius - length` to `radius`;
              // 'outside' draws from `radius` to `radius + length`.
              placement: 'outside',
            },
            minor: {
              count: 4, // minor ticks BETWEEN each pair of major ticks
              length: 5,
              width: 1,
              color: '#999',
              placement: 'outside',
            },
            labels: {
              show: false,
              offset: 6,
              fontSize: '11px',
              fontFamily: undefined,
              fontWeight: 400,
              color: '#666',
              /** @param {number} v */
              formatter(v) {
                return String(v)
              },
            },
          },
          // Needle/dial configuration. Only applies when `shape: 'needle'`.
          needle: {
            color: '#333',
            // Needle length as a % of the gauge radius (string like '85%')
            // or as an absolute px number.
            length: '85%',
            // px width of the needle line at the base.
            baseWidth: 4,
            // px width of the needle tip (tapered if smaller than baseWidth).
            tipWidth: 1,
            // 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: 0,
            // When true, also render the filled value-arc alongside the
            // needle. Default false preserves the previous needle-only
            // behavior. Useful for gauges that want a progress ring plus a
            // pointer indicator.
            showValueArc: false,
            animation: {
              enabled: true,
              duration: 800,
              easing: 'ease-out',
            },
          },
          hollow: {
            margin: 5,
            size: '50%',
            background: 'transparent',
            image: undefined,
            imageWidth: 150,
            imageHeight: 150,
            imageOffsetX: 0,
            imageOffsetY: 0,
            imageClipped: true,
            position: 'front',
            // Optional stroke around the hollow ring. Combined with
            // `strokeDasharray` this produces a dashed indicator circle
            // around the value text — useful for gauge designs where the
            // value sits inside its own boundary.
            stroke: undefined,
            strokeWidth: 1,
            strokeDasharray: undefined,
            dropShadow: {
              enabled: false,
              top: 0,
              left: 0,
              blur: 3,
              color: '#000',
              opacity: 0.5,
            },
          },
          track: {
            show: true,
            startAngle: undefined,
            endAngle: undefined,
            background: '#f2f2f2',
            strokeWidth: '97%',
            opacity: 1,
            margin: 5, // margin is in pixels
            dropShadow: {
              enabled: false,
              top: 0,
              left: 0,
              blur: 3,
              color: '#000',
              opacity: 0.5,
            },
          },
          dataLabels: {
            show: true,
            name: {
              show: true,
              fontSize: '16px',
              fontFamily: undefined,
              fontWeight: 600,
              color: undefined,
              offsetY: 0,
              /**
               * @param {any} val
               */
              formatter(val) {
                return val
              },
            },
            value: {
              show: true,
              fontSize: '14px',
              fontFamily: undefined,
              fontWeight: 400,
              color: undefined,
              offsetY: 16,
              /**
               * @param {any} val
               */
              formatter(val) {
                return val + '%'
              },
            },
            total: {
              show: false,
              label: 'Total',
              fontSize: '16px',
              fontWeight: 600,
              fontFamily: undefined,
              color: undefined,
              /**
               * @param {import('../../types/internal').ChartStateW} w
               */
              formatter(w) {
                return (
                  /**
                   * @param {number} a
                   * @param {number} b
                   */
                  w.globals.seriesTotals.reduce((a, b) => a + b, 0) /
                    w.seriesData.series.length +
                  '%'
                )
              },
            },
          },
          barLabels: {
            enabled: false,
            offsetX: 0,
            offsetY: 0,
            useSeriesColors: true,
            fontFamily: undefined,
            fontWeight: 600,
            fontSize: '16px',
            /**
             * @param {any} val
             */
            formatter(val) {
              return val
            },
            onClick: undefined,
          },
        },
        pie: {
          customScale: 1,
          offsetX: 0,
          offsetY: 0,
          startAngle: 0,
          endAngle: 360,
          expandOnClick: true,
          // 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 and the quantity it encodes are unchanged and
          // a gap opens between it and the rest of the pie. The inner
          // percentage label (and the outer name label, when enabled) ride
          // along with it. Ignored for polarArea, where the radius is the
          // value, and in a drilldown pie/donut, where a click navigates (the
          // slice would slide out only to be discarded by the drill, and
          // states.active takes the click feedback back over). Set 0 to keep
          // the slice in place.
          expandOffset: 10,
          // 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 / polarArea, and is skipped entirely when
          // states.hover.filter.type is 'none' (that stays the way to turn all
          // hover feedback off). Applies to pie, donut and polarArea.
          hoverOutline: {
            show: true,
            size: 8, // band thickness (px)
            // Extra clearance between the slice rim and the band (px), ON TOP
            // of the slice stroke: the band always starts at the outer edge of
            // the stroke, never under it. Default 0, because a stroke is
            // normally present (stroke.width defaults to 2) and already reads
            // as the separation. Any gap beyond that and the band stops
            // belonging to its slice and starts reading as a ring of its own.
            gap: 0,
            opacity: 0.3, // band opacity, over the slice colour
            color: undefined, // defaults to the hovered slice's colour
          },
          // Rounds the corners of each slice (in px). Applies to pie, donut and
          // polarArea (all rendered by the Pie module). 0 = sharp corners
          // (default, unchanged behavior). The value is clamped per slice so
          // opposing corner fillets never cross on thin/narrow slices.
          borderRadius: 0,
          // Gap between adjacent slices (in px). Applies to pie, donut and
          // polarArea. 0 = slices touch (default). Each slice is inset
          // symmetrically, so its mid-angle (data label + hit region) is kept.
          spacing: 0,
          dataLabels: {
            // These are the percentage values which are displayed on slice
            offset: 0, // offset by which labels will move outside
            minAngleToShowLabel: 10,
            // External (outer) labels: render the category/series name outside
            // the slice, joined to it by a leader (connector) line, so users
            // don't have to map legend colors back to slices. The percentage
            // keeps rendering inside the slice (governed by dataLabels.enabled).
            // Pie + donut only; ignored for polarArea (radial length already
            // encodes the value there).
            external: {
              show: false, // master switch for the external (outer) labels
              offsetX: 0,
              offsetY: 0,
              fontSize: undefined, // falls back to dataLabels.style.fontSize
              fontFamily: undefined, // falls back to dataLabels.style.fontFamily
              fontWeight: undefined, // falls back to dataLabels.style.fontWeight
              color: undefined, // defaults to chart.foreColor (readable text)
              /**
               * Return a string (single line) or an array of strings (stacked
               * lines, e.g. [name, percent + '%']).
               * @param {string} name
               * @param {{ seriesIndex: number, percent: number, value: number, w: any }} opts
               * @returns {string | string[]}
               */
              formatter: undefined,
              connector: {
                show: true,
                width: 1,
                color: undefined, // defaults to the slice color
                length: 16, // horizontal run after the radial elbow (px)
                gap: 6, // radial gap from slice edge to the elbow point (px)
              },
            },
          },
          donut: {
            size: '65%',
            background: 'transparent',
            labels: {
              // These are the inner labels appearing inside donut
              show: false,
              name: {
                show: true,
                fontSize: '16px',
                fontFamily: undefined,
                fontWeight: 600,
                color: undefined,
                offsetY: -10,
                /**
                 * @param {any} val
                 */
                formatter(val) {
                  return val
                },
              },
              value: {
                show: true,
                fontSize: '20px',
                fontFamily: undefined,
                fontWeight: 400,
                color: undefined,
                offsetY: 10,
                /**
                 * @param {any} val
                 */
                formatter(val) {
                  return val
                },
              },
              total: {
                show: false,
                showAlways: false,
                label: 'Total',
                fontSize: '16px',
                fontWeight: 400,
                fontFamily: undefined,
                color: undefined,
                /**
                 * @param {import('../../types/internal').ChartStateW} w
                 */
                formatter(w) {
                  /**
                   * @param {number} a
                   * @param {number} b
                   */
                  return w.globals.seriesTotals.reduce((a, b) => a + b, 0)
                },
              },
            },
          },
        },
        polarArea: {
          rings: {
            strokeWidth: 1,
            strokeColor: '#e8e8e8',
          },
          spokes: {
            strokeWidth: 1,
            connectorColors: '#e8e8e8',
          },
        },
        sunburst: {
          // 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.
          offsetX: 0,
          offsetY: 0,
          startAngle: 0,
          endAngle: 360,
          // Centre hole radius, as a % of the max radius (like donut size).
          innerSize: '15%',
          // Corner rounding + inter-arc gap (px), same semantics as the pie
          // family (see plotOptions.pie.borderRadius / .spacing).
          borderRadius: 0,
          spacing: 1,
          // How a branch that bottoms out before the deepest level is drawn:
          // 'extend' stretches the leaf arc out to the rim (default, matches
          // d3); 'stop' leaves the outer rings empty behind it.
          leaf: 'extend',
          // Angular partition of a parent's wedge among its children:
          // 'normalize' splits by each child's share of its siblings (safe for
          // any data); 'strict' expects children to sum to the parent's value.
          partition: 'normalize',
          // Each depth level is tinted this much lighter than its parent colour
          // (0 = keep parent colour, 1 = white). A per-node `color` overrides.
          tint: 0.14,
          // Click a wedge to zoom into that branch (its subtree fills the
          // chart; a breadcrumb walks back). Click the focused inner ring to
          // zoom out. Set false to disable.
          zoomOnClick: true,
          dataLabels: {
            show: true,
            // Hide the label on any arc narrower than this (degrees), so tiny
            // wedges do not overflow with text.
            minAngleToShow: 8,
            style: {
              fontSize: '12px',
              fontFamily: undefined,
              fontWeight: 400,
              colors: undefined,
            },
          },
        },
        radar: {
          size: undefined,
          offsetX: 0,
          offsetY: 0,
          polygons: {
            // strokeColor: '#e8e8e8', // should be deprecated in the minor version i.e 3.2
            strokeWidth: 1,
            strokeColors: '#e8e8e8',
            connectorColors: '#e8e8e8',
            fill: {
              colors: undefined,
            },
          },
        },
      },
      colors: undefined,
      dataLabels: {
        enabled: true,
        enabledOnSeries: undefined,
        /**
         * @param {any} val
         */
        formatter(val) {
          return val !== null ? val : ''
        },
        textAnchor: 'middle',
        distributed: false,
        offsetX: 0,
        offsetY: 0,
        style: {
          fontSize: '12px',
          fontFamily: undefined,
          fontWeight: 600,
          colors: undefined,
        },
        background: {
          enabled: true,
          foreColor: '#fff',
          backgroundColor: undefined,
          borderRadius: 2,
          padding: 4,
          opacity: 0.9,
          borderWidth: 1,
          borderColor: '#fff',
          dropShadow: {
            enabled: false,
            top: 1,
            left: 1,
            blur: 1,
            color: '#000',
            opacity: 0.8,
          },
        },
        dropShadow: {
          enabled: false,
          top: 1,
          left: 1,
          blur: 1,
          color: '#000',
          opacity: 0.8,
        },
        // Ride data labels to their new position on a data-change update
        // instead of snapping. ON by default: the bars, the markers and the
        // axis ticks all already reflow on one clock, so a label that jumps to
        // its final slot on the first frame is the odd one out, it arrives
        // several hundred ms before the bar it belongs to. Speed/easing follow
        // chart.animations.dynamicAnimation, and a label that has not moved is
        // a per-label no-op. Bar/column only.
        animate: {
          enabled: true,
        },
        // Count the numeric value up/down from its previous value on update,
        // like countUp.js. Off by default. The dataLabels.formatter runs each
        // frame, so number formatting (decimals, separators, prefixes) is
        // preserved. Bar/column only.
        countUp: {
          enabled: false,
        },
      },
      fill: {
        type: 'solid',
        colors: undefined, // array of colors
        opacity: 0.85,
        gradient: {
          shade: 'dark',
          type: 'horizontal',
          shadeIntensity: 0.5,
          gradientToColors: undefined,
          inverseColors: true,
          opacityFrom: 1,
          opacityTo: 1,
          stops: [0, 50, 100],
          colorStops: [],
        },
        image: {
          src: [],
          width: undefined, // optional
          height: undefined, // optional
        },
        pattern: {
          style: 'squares', // String | Array of Strings
          width: 6,
          height: 6,
          strokeWidth: 2,
        },
      },
      forecastDataPoints: {
        count: 0,
        fillOpacity: 0.5,
        strokeWidth: undefined,
        dashArray: 4,
      },
      grid: {
        show: true,
        borderColor: '#e0e0e0',
        strokeDashArray: 0,
        position: 'back',
        xaxis: {
          lines: {
            show: false,
          },
        },
        yaxis: {
          lines: {
            show: true,
          },
        },
        row: {
          colors: undefined, // takes as array which will be repeated on rows
          opacity: 0.5,
        },
        column: {
          colors: undefined, // takes an array which will be repeated on columns
          opacity: 0.5,
        },
        padding: {
          top: 0,
          right: 10,
          bottom: 0,
          left: 12,
        },
      },
      labels: [],
      drilldown: {
        // Opt-in. When false, the Drilldown feature module stays inert even if
        // it was imported. Requires `import 'apexcharts/features/drilldown'`.
        enabled: false,
        // Child levels referenced by a data point's `drilldown: '<id>'` field.
        // Each: { id, name?, data, chart?, xaxis?, yaxis?, colors?, plotOptions? }
        series: [],
        breadcrumb: {
          show: true,
          position: 'top-left', // 'top-left' | 'top-right'
          separator: ' / ',
          rootLabel: 'All',
          offsetX: 0,
          offsetY: 0,
          // formatter: (label, { index, depth }) => label,
        },
        // Animation is delegated to the chart's update pipeline; `enabled`
        // gates whether the drill transition animates at all.
        animation: {
          enabled: true,
          // 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.
          zoomFromPoint: false,
          // Base duration (ms) of the transition, used only when zoomFromPoint
          // is true. The fade-out phase runs a little shorter than this.
          speed: 260,
        },
        // Optional async resolver, called when a drillable point has no inline
        // match in `series`. Receives ({ id, point, seriesIndex, dataPointIndex })
        // and returns a level (or a promise of one). A throw, a rejection, or a
        // resolved value without a `data` array leaves the chart exactly where
        // it was and fires `drillDownError` - a failed fetch is ordinary, not
        // a reason to strand the view.
        // onDrillDown: undefined,
        // Overlay shown while an async level resolves. `text` is optional: with
        // none, the spinner shows alone (and carries 'Loading' as its
        // accessible name), which keeps the default free of any language.
        loading: {
          show: true,
          // text: 'Loading…',
        },
        // Cache levels resolved by `onDrillDown`, keyed by id, so drilling back
        // down a branch does not re-fetch it. Call `chart.drillDown` module's
        // clearCache() when the data behind an already-drilled chart changes.
        cache: true,
        // The dot that marks 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 the chart would
        // give no sign that anything can be opened. Only drillable points get
        // one, which is what makes them read as openable. Set `show: false` to
        // supply your own affordance (turning `markers.size` on, for instance).
        // Omitted colours inherit the series marker defaults.
        marker: {
          show: true,
          size: 6,
          // shape: undefined,      // 'circle' | 'square' | 'rect'
          // fillColor: undefined,  // defaults to the series colour
          strokeColor: '#fff',
        },
      },
      legend: {
        show: true,
        showForSingleSeries: false,
        showForNullSeries: true,
        showForZeroSeries: true,
        floating: false,
        position: 'bottom', // whether to position legends in 1 of 4
        // direction - top, bottom, left, right
        horizontalAlign: 'center', // when position top/bottom, you can specify whether to align legends left, right or center
        inverseOrder: false,
        fontSize: '12px',
        fontFamily: undefined,
        fontWeight: 400,
        width: undefined,
        height: undefined,
        formatter: undefined,
        tooltipHoverFormatter: undefined,
        offsetX: -20,
        offsetY: 4,
        customLegendItems: [],
        clusterGroupedSeries: true,
        clusterGroupedSeriesOrientation: 'vertical',
        labels: {
          colors: undefined,
          useSeriesColors: false,
        },
        markers: {
          size: 7,
          fillColors: undefined,
          strokeWidth: 1,
          shape: undefined,
          offsetX: 0,
          offsetY: 0,
          customHTML: undefined,
          onClick: undefined,
        },
        itemMargin: {
          horizontal: 5,
          vertical: 4,
        },
        onItemClick: {
          toggleDataSeries: true,
        },
        onItemHover: {
          highlightDataSeries: true,
        },
      },
      markers: {
        discrete: [],
        size: 0,
        colors: undefined,
        strokeColors: '#fff',
        strokeWidth: 2,
        strokeOpacity: 0.9,
        strokeDashArray: 0,
        fillOpacity: 1,
        shape: 'circle',
        offsetX: 0,
        offsetY: 0,
        showNullDataPoints: true,
        onClick: undefined,
        onDblClick: undefined,
        hover: {
          size: undefined,
          sizeOffset: 3,
        },
        // OPT-IN (0 = off). Above this many points in a series, that series'
        // markers are drawn as ONE path element per marker size, carrying a
        // subpath per point, instead of one element per point. Each per-point
        // element costs a node, ~16 attribute writes and an appendChild, which
        // is why markers dominate a large render: 2000 of them take 15ms of an
        // 18ms render, and batched they take 1ms.
        //
        // This covers the markers `showNullDataPoints` adds implicitly, not
        // just the ones asked for: every point beside a null is isolated and
        // gets its own dot, so a 2000-point series with half its values null
        // built ~1000 elements even at markers.size 0. Batched that render goes
        // from 15ms to 3.7ms, and 5000 points from 37ms to 5.9ms. Those dots
        // are a different size from the configured ones, hence one path per
        // size rather than one per series.
        //
        // Off by default because it is NOT pixel-identical where markers
        // overlap, and above ~1000 points in a normal-width chart they always
        // do. One path is rasterized as a single region, so overlapping markers
        // lose their individual outlines: all the fills paint, then all the
        // strokes, and the seams between neighbours disappear. Dense clusters
        // read flatter (measured 1-9% of pixels, scaling with density). Sparse
        // markers that do not touch are unaffected.
        //
        // Only applies where markers are already non-interactive and uniform (a
        // plain line/area with the default sweep tooltip, no discrete markers,
        // no per-point colours, no marker click handlers, no dataPointSelection
        // handler). A batched series has no `.apexcharts-marker` nodes, so the
        // hover dot is served by the tooltip's own marker (the same one
        // markers.size: 0 charts use), the keyboard focus ring lands on that
        // marker, and the per-marker reveal / stream ride is replaced by the
        // series-level fade.
        largeDatasetThreshold: 0,
      },
      noData: {
        text: undefined,
        align: 'center',
        offsetX: 0,
        offsetY: 0,
        style: {
          color: undefined,
          fontSize: '14px',
          fontFamily: undefined,
        },
      },
      responsive: [], // breakpoints should follow ascending order 400, then 700, then 1000
      series: undefined,
      states: {
        hover: {
          filter: {
            type: 'lighten',
            value: 0.15,
          },
        },
        active: {
          allowMultipleDataPointsSelection: false,
          filter: {
            type: 'darken',
            value: 0.35,
          },
        },
      },
      title: {
        text: undefined,
        align: 'left',
        margin: 5,
        offsetX: 0,
        offsetY: 0,
        floating: false,
        style: {
          fontSize: '14px',
          fontWeight: 900,
          fontFamily: undefined,
          color: undefined,
        },
      },
      subtitle: {
        text: undefined,
        align: 'left',
        margin: 5,
        offsetX: 0,
        offsetY: 30,
        floating: false,
        style: {
          fontSize: '12px',
          fontWeight: 400,
          fontFamily: undefined,
          color: undefined,
        },
      },
      stroke: {
        show: true,
        curve: 'smooth', // "smooth" / "straight" / "monotoneCubic" / "stepline" / "linestep"
        lineCap: 'butt', // round, butt , square
        width: 2,
        colors: undefined, // array of colors
        dashArray: 0, // single value or array of values
        fill: {
          type: 'solid',
          colors: undefined, // array of colors
          opacity: 0.85,
          gradient: {
            shade: 'dark',
            type: 'horizontal',
            shadeIntensity: 0.5,
            gradientToColors: undefined,
            inverseColors: true,
            opacityFrom: 1,
            opacityTo: 1,
            stops: [0, 50, 100],
            colorStops: [],
          },
        },
      },
      tooltip: {
        enabled: true,
        enabledOnSeries: undefined,
        shared: true,
        hideEmptySeries: false,
        followCursor: false, // when disabled, the tooltip will show on top of the series instead of mouse position
        intersect: false, // when enabled, tooltip will only show when user directly hovers over point
        inverseOrder: false,
        arrow: true,
        // One tight line instead of a card: the x label sits inline before
        // the value, the marker goes, the padding and font shrink. For panels
        // a normal card would cover (small multiples, sparklines, tiles). A
        // single-series chart drops the series-name label too; with several
        // series the names stay, because they are what tells the rows apart.
        compact: false,
        custom: undefined,
        fillSeriesColor: false,
        theme: 'light',
        cssClass: '',
        style: {
          fontSize: '12px',
          fontFamily: undefined,
          background: undefined,
        },
        onDatasetHover: {
          highlightDataSeries: false,
        },
        x: {
          // x value
          show: true,
          format: 'dd MMM', // dd/MM, dd MMM yy, dd MMM yyyy
          formatter: undefined, // a custom user supplied formatter function
        },
        y: {
          formatter: undefined,
          title: {
            /**
             * @param {string} seriesName
             */
            formatter(seriesName) {
              return seriesName ? seriesName + ': ' : ''
            },
          },
        },
        z: {
          formatter: undefined,
          title: 'Size: ',
        },
        marker: {
          show: true,
          fillColors: undefined,
        },
        items: {
          display: 'flex',
        },
        fixed: {
          enabled: false,
          position: 'topRight', // topRight, topLeft, bottomRight, bottomLeft
          offsetX: 0,
          offsetY: 0,
        },
      },
      xaxis: {
        type: 'category',
        categories: [],
        convertedCatToNumeric: false, // internal property which should not be altered outside
        offsetX: 0,
        offsetY: 0,
        overwriteCategories: undefined,
        labels: {
          show: true,
          rotate: -45,
          rotateAlways: false,
          hideOverlappingLabels: true,
          trim: false,
          minHeight: undefined,
          maxHeight: 120,
          showDuplicates: true,
          style: {
            colors: [],
            fontSize: '12px',
            fontWeight: 400,
            fontFamily: undefined,
            cssClass: '',
          },
          offsetX: 0,
          offsetY: 0,
          format: undefined,
          formatter: undefined, // custom formatter function which will override format
          datetimeUTC: true,
          datetimeFormatter: {
            // Base format per interval unit. TimeScale.formatDates folds in
            // coarser context automatically when the data range spans it
            // (e.g. month-scale across years → 'MMM yyyy', hour-scale across
            // days → 'dd MMM HH:mm'). Customizing a base format that already
            // includes the higher-unit token disables the auto-expansion for
            // that level.
            year: 'yyyy',
            month: 'MMM',
            day: 'dd MMM',
            hour: 'HH:mm',
            minute: 'HH:mm',
            second: 'HH:mm:ss',
          },
        },
        group: {
          groups: [],
          style: {
            colors: [],
            fontSize: '12px',
            fontWeight: 400,
            fontFamily: undefined,
            cssClass: '',
          },
        },
        axisBorder: {
          show: true,
          color: '#e0e0e0',
          width: '100%',
          height: 1,
          offsetX: 0,
          offsetY: 0,
        },
        axisTicks: {
          show: true,
          color: '#e0e0e0',
          height: 6,
          offsetX: 0,
          offsetY: 0,
        },
        stepSize: undefined,
        tickAmount: undefined,
        tickPlacement: 'on',
        min: undefined,
        max: undefined,
        range: undefined,
        floating: false,
        decimalsInFloat: undefined,
        position: 'bottom',
        title: {
          text: undefined,
          offsetX: 0,
          offsetY: 0,
          style: {
            color: undefined,
            fontSize: '12px',
            fontWeight: 900,
            fontFamily: undefined,
            cssClass: '',
          },
        },
        crosshairs: {
          show: true,
          width: 1, // tickWidth/barWidth or an integer
          position: 'back',
          opacity: 0.9,
          stroke: {
            color: '#b6b6b6',
            width: 1,
            dashArray: 3,
          },
          fill: {
            type: 'solid', // solid, gradient
            color: '#B1B9C4',
            gradient: {
              colorFrom: '#D8E3F0',
              colorTo: '#BED1E6',
              stops: [0, 100],
              opacityFrom: 0.4,
              opacityTo: 0.5,
            },
          },
          dropShadow: {
            enabled: false,
            left: 0,
            top: 0,
            blur: 1,
            opacity: 0.8,
          },
        },
        tooltip: {
          enabled: false,
          offsetY: 0,
          formatter: undefined,
          style: {
            fontSize: '12px',
            fontFamily: undefined,
          },
        },
      },
      yaxis: this.yAxis,
      theme: {
        mode: '',
        palette: 'palette1', // If defined, it will overwrite globals.colors variable
        // Facet (#13): read `--apx-*` CSS design tokens from the cascade
        // (accent/fore/grid/surface + series-1..N). true (default) reads any
        // present (absence is a no-op); false disables. Tokens top the
        // resolution chain below explicit config. Tokens are re-read on every
        // render; call chart.refreshTokens() to pick up a runtime CSS change
        // that does not itself trigger a render.
        tokens: true,
        // Facet (#13): 'os' follows prefers-color-scheme + prefers-contrast
        // reactively (SSR-safe, cleaned up on destroy). false disables.
        follow: false, // 'os' | false
        // Facet (#13): a theme registered via ApexCharts.registerTheme(name, def)
        name: '', // '' | registered theme name
        monochrome: {
          // monochrome allows you to select just 1 color and fill out the rest with light/dark shade (intensity can be selected)
          enabled: false,
          color: '#008FFB',
          shadeTo: 'light',
          shadeIntensity: 0.65,
        },
        accessibility: {
          colorBlindMode: '', // '' | 'deuteranopia' | 'protanopia' | 'tritanopia' | 'highContrast'
        },
      },
    }
  }
}