apexcharts
Version:
A JavaScript Chart Library
1,726 lines • 65.5 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
/*!
* ApexCharts v7.0.0
* (c) 2018-2026 ApexCharts
*/
import * as _core from "apexcharts/core";
import _core__default from "apexcharts/core";
import { default as default2 } from "apexcharts/core";
const Environment = _core.__apex_Environment_Environment;
const BrowserAPIs = _core.__apex_BrowserAPIs_BrowserAPIs;
const prefersReducedMotion = _core.__apex_Animations_prefersReducedMotion;
const parsePath = _core.__apex_PathMorphing_parsePath;
function gridDivideRect(bbox, count) {
if (!(count > 0)) return [];
if (count === 1) return [__spreadValues({}, bbox)];
const horizontal = bbox.width >= bbox.height;
const rowExtent = horizontal ? bbox.width : bbox.height;
const colExtent = horizontal ? bbox.height : bbox.width;
const ratio = colExtent > 0 ? rowExtent / colExtent : count;
let rows = Math.max(1, Math.ceil(Math.sqrt(ratio * count)));
if (rows > count) rows = count;
const baseCols = Math.floor(count / rows);
let remainder = count - baseCols * rows;
const cells = [];
const rowSize = rowExtent / rows;
let rowStart = 0;
for (let r = 0; r < rows; r++) {
const cols = baseCols + (remainder > 0 ? 1 : 0);
if (remainder > 0) remainder--;
const colSize = cols > 0 ? colExtent / cols : 0;
for (let c = 0; c < cols; c++) {
cells.push(
horizontal ? {
x: bbox.x + rowStart,
y: bbox.y + c * colSize,
width: rowSize,
height: colSize
} : {
x: bbox.x + c * colSize,
y: bbox.y + rowStart,
width: colSize,
height: rowSize
}
);
}
rowStart += rowSize;
}
return cells;
}
function gridDivideShape(bbox, count, intervalsAt) {
if (!(count > 0)) return [];
const horizontal = bbox.width >= bbox.height;
const rowExtent = horizontal ? bbox.width : bbox.height;
const colExtent = horizontal ? bbox.height : bbox.width;
const minorLo = horizontal ? bbox.y : bbox.x;
const minorHi = minorLo + colExtent;
const ratio = colExtent > 0 ? rowExtent / colExtent : count;
let rows = Math.max(
Math.ceil(Math.sqrt(ratio * count)),
Math.ceil(rowExtent / 16)
);
if (!(rows >= 1)) rows = 1;
if (rows > count) rows = count;
const baseCols = Math.floor(count / rows);
let remainder = count - baseCols * rows;
const cells = [];
const rowSize = rowExtent / rows;
let rowStart = horizontal ? bbox.x : bbox.y;
for (let r = 0; r < rows; r++) {
const cols = baseCols + (remainder > 0 ? 1 : 0);
if (remainder > 0) remainder--;
const spans = [];
const raw = intervalsAt(rowStart, rowStart + rowSize, horizontal);
if (Array.isArray(raw)) {
for (let s = 0; s < raw.length; s++) {
const iv = raw[s];
if (!iv) continue;
const lo = Math.max(minorLo, Math.min(iv[0], iv[1]));
const hi = Math.min(minorHi, Math.max(iv[0], iv[1]));
if (hi > lo) spans.push([lo, hi]);
}
}
if (!spans.length) spans.push([minorLo, minorHi]);
const totalLen = spans.reduce((a, s) => a + (s[1] - s[0]), 0);
const exact = spans.map(
(s) => totalLen > 0 ? (s[1] - s[0]) / totalLen * cols : cols / spans.length
);
const share = exact.map((v) => Math.floor(v));
let used = share.reduce((a, b) => a + b, 0);
const byFrac = exact.map((v, i) => ({ i, frac: v - Math.floor(v) })).sort((a, b) => b.frac - a.frac);
for (let k = 0; used < cols; k++, used++) {
share[byFrac[k % byFrac.length].i]++;
}
for (let s = 0; s < spans.length; s++) {
const n = share[s];
if (n <= 0) continue;
const lo = spans[s][0];
const colSize = (spans[s][1] - lo) / n;
for (let c = 0; c < n; c++) {
cells.push(
horizontal ? { x: rowStart, y: lo + c * colSize, width: rowSize, height: colSize } : { x: lo + c * colSize, y: rowStart, width: colSize, height: rowSize }
);
}
}
rowStart += rowSize;
}
return cells;
}
function hilbertIndex(x, y, minX, minY, maxX, maxY) {
let ix = maxX === minX ? 0 : Math.round(32767 * ((x - minX) / (maxX - minX)));
let iy = maxY === minY ? 0 : Math.round(32767 * ((y - minY) / (maxY - minY)));
let d = 0;
for (let s = 32768; s >= 1; s /= 2) {
const rx = (ix & s) > 0 ? 1 : 0;
const ry = (iy & s) > 0 ? 1 : 0;
d += s * s * (3 * rx ^ ry);
if (ry === 0) {
if (rx === 1) {
ix = s - 1 - ix;
iy = s - 1 - iy;
}
const t = ix;
ix = iy;
iy = t;
}
}
return d;
}
function sortByHilbert(items, getXY) {
let minX = Infinity;
let minY = Infinity;
let maxX = -Infinity;
let maxY = -Infinity;
const pts = items.map((it) => {
const [x, y] = getXY(it);
if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (y < minY) minY = y;
if (y > maxY) maxY = y;
return [x, y];
});
return items.map((item, k) => ({
item,
d: hilbertIndex(pts[k][0], pts[k][1], minX, minY, maxX, maxY)
})).sort((a, b) => a.d - b.d).map((e) => e.item);
}
function parseColor(str) {
if (!str || typeof str !== "string") return null;
const s = str.trim();
if (s[0] === "#") {
const hex = s.slice(1);
if (hex.length === 3) {
return [
parseInt(hex[0] + hex[0], 16),
parseInt(hex[1] + hex[1], 16),
parseInt(hex[2] + hex[2], 16),
1
];
}
if (hex.length === 6 || hex.length === 8) {
return [
parseInt(hex.slice(0, 2), 16),
parseInt(hex.slice(2, 4), 16),
parseInt(hex.slice(4, 6), 16),
hex.length === 8 ? parseInt(hex.slice(6, 8), 16) / 255 : 1
];
}
return null;
}
const m = s.match(/^rgba?\(([^)]+)\)$/i);
if (m) {
const parts = m[1].split(",").map((p) => parseFloat(p));
if (parts.length < 3 || parts.some((v) => !isFinite(v))) return null;
return [parts[0], parts[1], parts[2], parts.length > 3 ? parts[3] : 1];
}
return null;
}
function makeColorLerp(from, to) {
const a = parseColor(from);
const b = parseColor(to);
if (!a || !b) return null;
return (t) => {
const r = Math.round(a[0] + (b[0] - a[0]) * t);
const g = Math.round(a[1] + (b[1] - a[1]) * t);
const bl = Math.round(a[2] + (b[2] - a[2]) * t);
const al = a[3] + (b[3] - a[3]) * t;
return al >= 1 ? `rgb(${r},${g},${bl})` : `rgba(${r},${g},${bl},${al})`;
};
}
function easeInOutCubic(t) {
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
}
function runPieceTween({ pieces, duration, onPieceDone, onAllDone }) {
let cancelled = false;
const start = Date.now();
const dur = Math.max(1, duration);
const write = (p, e) => {
const f = p.from;
const t = p.to;
const el = p.el;
el.setAttribute("x", String(f.x + (t.x - f.x) * e));
el.setAttribute("y", String(f.y + (t.y - f.y) * e));
el.setAttribute("width", String(Math.max(0, f.width + (t.width - f.width) * e)));
el.setAttribute("height", String(Math.max(0, f.height + (t.height - f.height) * e)));
el.setAttribute("rx", String(Math.max(0, f.rx + (t.rx - f.rx) * e)));
if (p.fill) el.setAttribute("fill", p.fill(e));
else if (e >= 1 && p.fillEnd) el.setAttribute("fill", p.fillEnd);
};
const frame = () => {
if (cancelled) return;
const elapsed = Date.now() - start;
let live = false;
for (let k = 0; k < pieces.length; k++) {
const p = pieces[k];
if (
/** @type {any} */
p._done
) continue;
const raw = (elapsed - p.delay) / dur;
if (raw < 1) live = true;
if (raw <= 0) continue;
const t = Math.min(1, raw);
write(p, easeInOutCubic(t));
if (t >= 1) {
p._done = true;
if (onPieceDone) onPieceDone(p);
}
}
if (live) BrowserAPIs.requestAnimationFrame(frame);
else if (onAllDone) onAllDone();
};
BrowserAPIs.requestAnimationFrame(frame);
return () => {
cancelled = true;
};
}
const BAR_FAMILY = /* @__PURE__ */ new Set(["bar", "funnel", "pyramid", "histogram"]);
const RADIAL_FAMILY = /* @__PURE__ */ new Set(["pie", "donut", "polarArea", "radialBar", "gauge"]);
const UNIT_FAMILY = /* @__PURE__ */ new Set(["unit", "waffle"]);
const PARTITION_FAMILY = /* @__PURE__ */ new Set(["treemap", "sunburst"]);
const SUMMARY_FAMILY = /* @__PURE__ */ new Set(["boxPlot", "violin"]);
const GHOST_FADE_FRACTION = 0.55;
const PIECE_BUDGET = 1500;
const PIECE_STAGGER_MAX = 300;
function familyOf(type) {
if (BAR_FAMILY.has(type)) return "bar";
if (RADIAL_FAMILY.has(type)) return "radial";
if (UNIT_FAMILY.has(type)) return "unit";
if (PARTITION_FAMILY.has(type)) return "partition";
if (SUMMARY_FAMILY.has(type)) return "summary";
return null;
}
class MorphTypeChange {
/**
* @param {import('../types/internal').ChartStateW} w
* @param {import('../types/internal').ChartContext} ctx
*/
constructor(w, ctx) {
this.w = w;
this.ctx = ctx;
this._snapshot = null;
this._ghost = null;
this._pieceLayer = null;
this._pieceCancel = null;
}
/**
* @param {string} fromType
* @param {string} toType
* @returns {boolean}
*/
canMorphTypes(fromType, toType) {
if (fromType === toType) return false;
const ff = familyOf(fromType);
const tf = familyOf(toType);
if (!ff || !tf) return false;
if (ff === "partition" !== (tf === "partition")) {
return ff !== "unit" && tf !== "unit";
}
return true;
}
/**
* @param {string} fromType
* @param {string} toType
* @param {any} newSeries
* @returns {boolean}
*/
isCompatibleSeriesShape(fromType, toType, newSeries) {
if (!Array.isArray(newSeries) || newSeries.length === 0) return false;
const ff = familyOf(fromType);
const tf = familyOf(toType);
if (tf === "unit") {
if (newSeries.every((v) => typeof v === "number")) return true;
return newSeries.every(
(s) => s && typeof s === "object" && Array.isArray(s.data)
);
}
if (tf === "partition") {
return true;
}
if (tf === "radial") {
if (newSeries.every((v) => typeof v === "number")) return true;
return newSeries.length === 1 && newSeries[0] && typeof newSeries[0] === "object" && Array.isArray(newSeries[0].data);
}
if (tf === "bar" || tf === "summary") {
return newSeries.every(
(s) => s && typeof s === "object" && Array.isArray(s.data)
);
}
return ff !== null && tf !== null;
}
/**
* Capture the live DOM of the *current* (outgoing) chart and stash it on
* this module. Called from `apexcharts._updateOptions` before the config
* merge that flips `chart.type`.
*
* Returns true if a morph is queued — caller doesn't need the value, but
* tests use it.
*
* @param {{ fromType: string, toType: string, newSeries: any }} args
* @returns {boolean}
*/
captureBeforeDestroy({ fromType, toType, newSeries }) {
this._snapshot = null;
this._removeGhost();
this._cancelPieces();
if (!Environment.isBrowser()) return false;
const animCfg = this.w.config.chart.animations;
if (!animCfg || animCfg.enabled === false) return false;
if (animCfg.chartTypeMorph && animCfg.chartTypeMorph.enabled === false)
return false;
if (animCfg.respectReducedMotion && prefersReducedMotion()) return false;
if (!this.canMorphTypes(fromType, toType)) return false;
if (!this.isCompatibleSeriesShape(fromType, toType, newSeries)) return false;
const { marks, branches, unitDots } = this._captureFromDOM(fromType);
if (!marks.length) return false;
const mapping = this._buildMapping(
marks,
fromType,
toType,
newSeries,
branches
);
if (mapping.size === 0) return false;
this._snapshot = {
fromType,
toType,
mapping,
oldLayout: {
translateX: this.w.layout.translateX || 0,
translateY: this.w.layout.translateY || 0
}
};
const ff = familyOf(fromType);
const tf = familyOf(toType);
const canShape = this._canProbePaths();
const pieceFamilies = ff === "bar" || ff === "summary" || ff === "radial" && canShape;
if (tf === "unit" && pieceFamilies) {
const total = this._countUnitSeries(newSeries);
this._snapshot.pieceOut = total > 0 && total <= PIECE_BUDGET;
} else if (ff === "unit" && (tf === "bar" || tf === "summary" || tf === "radial" && canShape)) {
let total = 0;
unitDots.forEach((list) => {
total += list.length;
});
if (total > 0 && total <= PIECE_BUDGET) {
this._snapshot.pieceIn = true;
this._snapshot.sourceDots = unitDots;
const keyOrder = [];
if (tf === "radial") {
(Array.isArray(newSeries) ? newSeries : []).forEach(
(_v, i) => {
keyOrder.push(`${i}:0`);
}
);
} else {
(Array.isArray(newSeries) ? newSeries : []).forEach(
(s, seriesIdx) => {
const data = s && Array.isArray(s.data) ? s.data : [];
for (let j = 0; j < data.length; j++) {
keyOrder.push(`${seriesIdx}:${j}`);
}
}
);
}
this._snapshot.keyOrder = keyOrder;
}
}
if (this._needsGhost(fromType, toType) && !this._snapshot.pieceOut && !this._snapshot.pieceIn) {
this._captureGhost();
}
this.w.globals.previousPaths = [];
return true;
}
/**
* Whether the outgoing marks need an exit animation of their own.
*
* Most pairs do not. bar → pie hands every wedge the exact `d` of the bar it
* replaces, and treemap → sunburst does the same for its tiles: the outgoing
* mark IS the incoming mark's first frame, so it never needs to leave, and
* drawing a copy of it would only double the image at t=0.
*
* The unit pairs are the exception, in both directions, because the
* correspondence is not 1:1. Going in, one bar becomes N dots, so the bar has
* no successor to become. Coming out, N dots become one bar: the bar does
* grow from the cloud's footprint, but no individual dot has anywhere to go.
* Either way something on screen simply stops existing, which is exactly the
* hard cut that made these pairs read as "the old chart vanished and the new
* one animated" rather than as a morph.
*
* @param {string} fromType
* @param {string} toType
* @returns {boolean}
*/
_needsGhost(fromType, toType) {
return familyOf(fromType) === "unit" || familyOf(toType) === "unit";
}
/**
* Take a detached copy of the outgoing chart's marks, to be mounted over the
* incoming chart once it exists (see `_mountGhost`).
*
* The whole `<svg>` is cloned and the chrome then removed from the copy,
* rather than lifting the series groups out on their own: every mark's
* position depends on the transforms of the groups above it, and cloning
* from the root is what keeps those intact without re-deriving any geometry.
*
* The chrome is dropped because `applyChromeFade` already fades the incoming
* axes, grid and legend in from zero. Keeping the outgoing set as well would
* put two sets of axis labels on screen at half opacity each.
*/
_captureGhost() {
var _a, _b;
const paper = (_a = this.w.dom) == null ? void 0 : _a.Paper;
const node = paper && paper.node;
if (!node || typeof node.cloneNode !== "function") return;
const clone = node.cloneNode(true);
const drop = [
".apexcharts-xaxis",
".apexcharts-yaxis",
".apexcharts-grid",
".apexcharts-gridlines-horizontal",
".apexcharts-gridlines-vertical",
".apexcharts-legend",
".apexcharts-title-text",
".apexcharts-subtitle-text",
".apexcharts-annotations",
".apexcharts-zoom-rect",
".apexcharts-selection-rect",
".apexcharts-xcrosshairs",
".apexcharts-ycrosshairs"
];
if (typeof clone.querySelectorAll === "function") {
drop.forEach((sel) => {
clone.querySelectorAll(sel).forEach((el) => {
if (el.parentNode) el.parentNode.removeChild(el);
});
});
}
if (typeof clone.querySelectorAll === "function") {
clone.querySelectorAll("[id]").forEach((el) => {
el.removeAttribute("id");
});
}
(_b = clone.removeAttribute) == null ? void 0 : _b.call(clone, "id");
this._ghost = clone;
}
/**
* Mount the captured copy over the newly-rendered chart and fade it out.
*
* It goes ON TOP of the live svg, which is what makes both directions read
* as one motion rather than as a swap. Going into a unit chart the bars
* dissolve and the dots are uncovered already in flight, having left from
* inside the bar that held them. Coming out of one, the dots are still there
* to fade while the bar grows underneath them; behind the incoming mark they
* would be hidden on the first frame, because that mark starts out exactly
* the size of the cloud it is replacing.
*
* The fade runs over a fraction of the morph so the outgoing marks are gone
* before the incoming ones settle. Holding them for the full duration leaves
* two charts overlapping right at the moment the eye is reading the final
* shape, which looks like a rendering fault rather than a transition.
*/
_mountGhost() {
var _a, _b, _c;
const ghost = this._ghost;
if (!ghost || !Environment.isBrowser()) return;
const wrap = (_a = this.w.dom) == null ? void 0 : _a.elWrap;
if (!wrap || typeof wrap.appendChild !== "function") {
this._ghost = null;
return;
}
const style = ghost.style;
if (style) {
style.position = "absolute";
style.left = "0";
style.top = "0";
style.background = "transparent";
style.pointerEvents = "none";
style.opacity = "1";
}
(_b = ghost.setAttribute) == null ? void 0 : _b.call(ghost, "aria-hidden", "true");
(_c = ghost.setAttribute) == null ? void 0 : _c.call(ghost, "class", "apexcharts-morph-ghost");
wrap.appendChild(ghost);
const speed = this.getSpeed();
const fade = Math.max(120, Math.round(speed * GHOST_FADE_FRACTION));
BrowserAPIs.requestAnimationFrame(() => {
if (!style) return;
style.transition = `opacity ${fade}ms ease-in`;
style.opacity = "0";
});
setTimeout(() => this._removeGhost(), fade + 60);
}
/** Detach the ghost if one is mounted. Safe to call at any time. */
_removeGhost() {
const ghost = this._ghost;
this._ghost = null;
if (ghost && ghost.parentNode) ghost.parentNode.removeChild(ghost);
}
/* ------------------------------------------------------------------ *
* The piece layer (see MorphPieces for the geometry).
* ------------------------------------------------------------------ */
/**
* Object count of an incoming unit series: one datum per dot in the object
* form. The numeric form ([3, 5]) scales values by `plotOptions.unit
* .unitValue`, which is not resolvable pre-merge, so it counts as zero and
* keeps the burst-and-ghost behaviour.
*
* @param {any} newSeries
* @returns {number}
*/
_countUnitSeries(newSeries) {
if (!Array.isArray(newSeries)) return 0;
let total = 0;
for (const s of newSeries) {
if (!s || typeof s !== "object" || !Array.isArray(s.data)) return 0;
total += s.data.length;
}
return total;
}
/**
* Whether the incoming unit chart should hold its dots for the piece layer:
* render them at their final slots, hidden, and let the pieces do the
* flying. The reveal happens per dot as its piece lands.
*
* Consulted by the unit renderer during its draw, which runs after
* `captureBeforeDestroy` and before `applyChromeFade`, so the decision was
* already made from the same series the renderer is now drawing.
*
* @returns {boolean}
*/
usesPieceTakeover() {
return !!(this._snapshot && this._snapshot.pieceOut);
}
/**
* Whether the piece layer claims the incoming mark at (realIndex, j): a
* source cluster's dots will fly to it and tile it, so it must render
* hidden and reveal only when its mosaic is complete. Consulted by the bar
* renderer (boxPlot and violin render through it).
*
* @param {number|string} realIndex
* @param {number|string} j
* @returns {boolean}
*/
claimsTargetMark(realIndex, j) {
return !!(this._snapshot && this._snapshot.pieceIn && this._snapshot.mapping.has(`${realIndex}:${j}`));
}
/**
* Create the overlay group the pieces are driven in. It lives INSIDE the
* new chart's elGraphical so every coordinate matches the marks' own local
* space, and it never takes a pointer event.
* @returns {any} the <g> node, or null
*/
_makePieceLayer() {
var _a, _b;
const graph = (_a = this.w.dom) == null ? void 0 : _a.elGraphical;
const host = graph && graph.node;
if (!host || typeof host.appendChild !== "function") return null;
const g = BrowserAPIs.createElementNS("http://www.w3.org/2000/svg", "g");
if (!g) return null;
g.setAttribute("class", "apexcharts-morph-pieces");
g.setAttribute("pointer-events", "none");
const cuid = (_b = this.w.globals) == null ? void 0 : _b.cuid;
if (cuid) g.setAttribute("clip-path", `url(#gridRectBarMask${cuid})`);
host.appendChild(g);
this._pieceLayer = g;
return g;
}
/**
* Reveal everything a piece takeover hid, whether or not the pieces ran.
* The attribute is plain (no namespace colon) so it stays selectable
* everywhere.
*/
_revealPieceHidden() {
var _a;
const baseEl = (_a = this.w.globals.dom) == null ? void 0 : _a.baseEl;
if (!baseEl || typeof baseEl.querySelectorAll !== "function") return;
baseEl.querySelectorAll("[data-piece-hidden]").forEach(
(el) => {
el.removeAttribute("opacity");
el.removeAttribute("data-piece-hidden");
}
);
}
/** Stop the piece run, drop the overlay, and reveal anything still hidden. */
_cancelPieces() {
if (this._pieceCancel) {
this._pieceCancel();
this._pieceCancel = null;
}
const layer = this._pieceLayer;
this._pieceLayer = null;
if (layer && layer.parentNode) layer.parentNode.removeChild(layer);
this._revealPieceHidden();
}
/**
* Whether this environment can hit-test path geometry at all. Decided
* before the ghost clone is taken, because a family whose cells are only
* honest when probed (radial) must keep the fade rather than fall back to a
* rectangular grid it cannot justify. jsdom answers no.
* @returns {boolean}
*/
_canProbePaths() {
if (!Environment.isBrowser()) return false;
const probe = BrowserAPIs.createElementNS(
"http://www.w3.org/2000/svg",
"path"
);
return !!probe && typeof /** @type {any} */
probe.isPointInFill === "function";
}
/**
* A per-band ink prober over a mark's path, for gridDivideShape: given a
* major-axis band it measures where the mark actually has ink across the
* minor axis, so a violin's cells follow its density outline, a boxPlot's
* whisker rows collapse to slivers, and a wedge's rows stop at the wedge
* instead of spanning the bounding box (which stamped a rectangle over the
* mark at frame one).
*
* The probe path is mounted (hidden) inside the piece layer so its user
* space is exactly the space the cells are laid out in. The stroke test
* catches zero-area subpaths (a boxPlot's whisker line has no fill to
* hit), and its width is what a whisker's slivers will measure.
*
* Returns null when the environment cannot hit-test path geometry (jsdom);
* the caller then keeps the plain grid.
*
* @param {string} d - path data, in piece-layer coordinates
* @param {{x:number,y:number,width:number,height:number}} bbox
* @param {any} layer - the mounted piece layer
* @returns {{ intervalsAt: (bandLo: number, bandHi: number, horizontal: boolean) => Array<[number, number]> | null, dispose: () => void } | null}
*/
_makeExtentProber(d, bbox, layer) {
const doc = layer.ownerDocument;
const probe = doc.createElementNS("http://www.w3.org/2000/svg", "path");
probe.setAttribute("d", d);
probe.setAttribute("fill", "#000");
probe.setAttribute("stroke", "#000");
probe.setAttribute("stroke-width", "3");
probe.setAttribute("visibility", "hidden");
layer.appendChild(probe);
const svg = probe.ownerSVGElement;
if (typeof /** @type {any} */
probe.isPointInFill !== "function" || !svg || typeof svg.createSVGPoint !== "function") {
layer.removeChild(probe);
return null;
}
const pt = svg.createSVGPoint();
const hit = (x, y) => {
pt.x = x;
pt.y = y;
const p = (
/** @type {any} */
probe
);
return p.isPointInFill(pt) || typeof p.isPointInStroke === "function" && p.isPointInStroke(pt);
};
const SCAN = 48;
const intervalsAt = (bandLo, bandHi, horizontal) => {
const lo = horizontal ? bbox.y : bbox.x;
const hi = lo + (horizontal ? bbox.height : bbox.width);
if (!(hi > lo)) return null;
const at = (v, major) => horizontal ? hit(major, v) : hit(v, major);
const majors = [
bandLo + (bandHi - bandLo) * 0.1,
(bandLo + bandHi) / 2,
bandHi - (bandHi - bandLo) * 0.1
];
const edge = (inside, outside, major) => {
let a = outside;
let b = inside;
for (let it = 0; it < 6; it++) {
const m = (a + b) / 2;
if (at(m, major)) b = m;
else a = m;
}
return (a + b) / 2;
};
const step = (hi - lo) / SCAN;
const proof = new Array(SCAN + 1).fill(null);
let any = false;
for (const major of majors) {
for (let s = 0; s <= SCAN; s++) {
if (proof[s] !== null) continue;
if (at(lo + s * step, major)) {
proof[s] = major;
any = true;
}
}
}
if (!any) return null;
const out = [];
let runStart = -1;
for (let s = 0; s <= SCAN + 1; s++) {
const inside = s <= SCAN && proof[s] !== null;
if (inside && runStart < 0) runStart = s;
if (!inside && runStart >= 0) {
const last = s - 1;
const major = (
/** @type {number} */
proof[runStart]
);
const left = runStart === 0 ? lo : edge(lo + runStart * step, lo + (runStart - 1) * step, major);
const right = last === SCAN ? hi : edge(
lo + last * step,
lo + (last + 1) * step,
/** @type {number} */
proof[last]
);
if (right > left) out.push([left, right]);
runStart = -1;
}
}
return out.length ? out : null;
};
return {
intervalsAt,
dispose: () => {
if (probe.parentNode) probe.parentNode.removeChild(probe);
}
};
}
/**
* mark -> objects. Cut each captured mark into one cell per dot and fly
* every cell to its dot, corners rounding off and fill blending on the way.
* The real dots (rendered hidden by the unit chart, see usesPieceTakeover)
* are revealed one by one as their piece lands, so the handoff is
* geometrically exact and nothing ever fades.
*/
_separatePieces() {
var _a;
const snap = this._snapshot;
const baseEl = (_a = this.w.globals.dom) == null ? void 0 : _a.baseEl;
if (!snap || !baseEl) return this._revealPieceHidden();
const byCluster = /* @__PURE__ */ new Map();
let total = 0;
baseEl.querySelectorAll(".apexcharts-unit-area").forEach((dot) => {
var _a2, _b, _c, _d, _e, _f, _g;
const i = parseInt((_a2 = dot.getAttribute("i")) != null ? _a2 : "", 10);
if (isNaN(i)) return;
const cxAttr = dot.getAttribute("cx");
let x;
let y;
let r = 3;
if (cxAttr != null) {
x = parseFloat(cxAttr);
y = parseFloat((_b = dot.getAttribute("cy")) != null ? _b : "");
r = parseFloat((_c = dot.getAttribute("r")) != null ? _c : "3") || 3;
} else {
const wAttr = parseFloat((_d = dot.getAttribute("width")) != null ? _d : "0") || 0;
const hAttr = parseFloat((_e = dot.getAttribute("height")) != null ? _e : "0") || 0;
x = parseFloat((_f = dot.getAttribute("x")) != null ? _f : "") + wAttr / 2;
y = parseFloat((_g = dot.getAttribute("y")) != null ? _g : "") + hAttr / 2;
r = Math.max(wAttr, hAttr) / 2 || 3;
}
if (!isFinite(x) || !isFinite(y)) return;
let list = byCluster.get(i);
if (!list) {
list = [];
byCluster.set(i, list);
}
list.push({ el: dot, x, y, r, fill: dot.getAttribute("fill") });
total++;
});
if (total === 0 || total > PIECE_BUDGET) return this._revealPieceHidden();
const layer = this._makePieceLayer();
if (!layer) return this._revealPieceHidden();
const pieces = [];
const doc = layer.ownerDocument;
const sourceFam = familyOf(snap.fromType);
const shapedSource = sourceFam === "summary" || sourceFam === "radial";
Array.from(byCluster.keys()).sort((a, b) => a - b).forEach((i) => {
var _a2;
const dots = (
/** @type {any[]} */
byCluster.get(i)
);
const box = this.getInitialBBoxFor(i);
const entry = snap.mapping.get(`${i}:0`);
if (!box || !entry) {
dots.forEach((d) => {
d.el.removeAttribute("opacity");
d.el.removeAttribute("data-piece-hidden");
});
return;
}
const markFill = entry.fill && entry.fill.indexOf("url(") !== 0 ? entry.fill : ((_a2 = this.w.globals.colors) == null ? void 0 : _a2[i]) || dots[0].fill;
let prober = null;
if (shapedSource) {
const shifted = this.getInitialPathFor(i, 0);
if (shifted) prober = this._makeExtentProber(shifted, box, layer);
}
const divided = prober ? gridDivideShape(box, dots.length, prober.intervalsAt) : gridDivideRect(box, dots.length);
if (prober) prober.dispose();
const cells = sortByHilbert(divided, (c) => [
c.x + c.width / 2,
c.y + c.height / 2
]);
const ordered = sortByHilbert(dots, (d) => [d.x, d.y]);
for (let k = 0; k < ordered.length; k++) {
const cell = cells[k];
const dot = ordered[k];
const el = doc.createElementNS("http://www.w3.org/2000/svg", "rect");
el.setAttribute("data-i", String(i));
el.setAttribute("x", String(cell.x));
el.setAttribute("y", String(cell.y));
el.setAttribute("width", String(cell.width));
el.setAttribute("height", String(cell.height));
el.setAttribute("rx", "0");
el.setAttribute("fill", String(markFill));
layer.appendChild(el);
pieces.push({
el,
from: { x: cell.x, y: cell.y, width: cell.width, height: cell.height, rx: 0 },
to: {
x: dot.x - dot.r,
y: dot.y - dot.r,
width: dot.r * 2,
height: dot.r * 2,
rx: dot.r
},
fill: makeColorLerp(markFill, dot.fill),
fillEnd: dot.fill,
delay: 0,
meta: { dotEl: dot.el }
});
}
});
if (!pieces.length) return this._cancelPieces();
this._runPieces(pieces, (piece) => {
const dotEl = piece.meta.dotEl;
dotEl.removeAttribute("opacity");
dotEl.removeAttribute("data-piece-hidden");
if (piece.el.parentNode) piece.el.parentNode.removeChild(piece.el);
});
}
/**
* objects -> mark. Each captured outgoing dot flies to one cell of the
* incoming mark, squaring off and blending towards the mark's fill; the
* mark itself (rendered hidden, see claimsTargetMark) is revealed the
* moment its last piece lands and the mosaic is complete, which is also the
* moment the seams disappear.
*/
_combinePieces() {
var _a, _b;
const snap = this._snapshot;
const baseEl = (_a = this.w.globals.dom) == null ? void 0 : _a.baseEl;
if (!snap || !snap.sourceDots || !snap.keyOrder || !baseEl) {
return this._revealPieceHidden();
}
const targets = this._collectTargetMarks(snap.toType);
if (!targets.size) return this._revealPieceHidden();
const dx = snap.oldLayout.translateX - (this.w.layout.translateX || 0);
const dy = snap.oldLayout.translateY - (this.w.layout.translateY || 0);
const clusterIdx = Array.from(snap.sourceDots.keys()).sort((a, b) => a - b);
const layer = this._makePieceLayer();
if (!layer) return this._revealPieceHidden();
const doc = layer.ownerDocument;
const pieces = [];
const targetFam = familyOf(snap.toType);
const shapedTarget = targetFam === "summary" || targetFam === "radial";
for (let k = 0; k < clusterIdx.length; k++) {
const dots = (
/** @type {any[]} */
snap.sourceDots.get(clusterIdx[k])
);
const key = snap.keyOrder[k];
const target = key ? targets.get(key) : null;
if (!target || !dots || !dots.length) {
if (target) {
target.els.forEach((el) => {
el.removeAttribute("opacity");
el.removeAttribute("data-piece-hidden");
});
}
continue;
}
const markFill = target.fill && target.fill.indexOf("url(") !== 0 ? target.fill : ((_b = this.w.globals.colors) == null ? void 0 : _b[target.realIndex]) || dots[0].fill;
let prober = null;
if (shapedTarget) {
const d = target.d || target.els.map(
(p) => p.getAttribute("pathTo") || p.getAttribute("d")
).filter(Boolean).join(" ");
if (d) prober = this._makeExtentProber(d, target.bbox, layer);
}
const divided = prober ? gridDivideShape(target.bbox, dots.length, prober.intervalsAt) : gridDivideRect(target.bbox, dots.length);
if (prober) prober.dispose();
const cells = sortByHilbert(divided, (c) => [
c.x + c.width / 2,
c.y + c.height / 2
]);
const ordered = sortByHilbert(dots, (d) => [d.x, d.y]);
const markState = { remaining: ordered.length, els: target.els, tiles: (
/** @type {any[]} */
[]
) };
for (let m = 0; m < ordered.length; m++) {
const dot = ordered[m];
const cell = cells[m];
const el = doc.createElementNS("http://www.w3.org/2000/svg", "rect");
const fx = dot.x + dx - dot.r;
const fy = dot.y + dy - dot.r;
el.setAttribute("data-key", key);
el.setAttribute("x", String(fx));
el.setAttribute("y", String(fy));
el.setAttribute("width", String(dot.r * 2));
el.setAttribute("height", String(dot.r * 2));
el.setAttribute("rx", String(dot.r));
el.setAttribute("fill", String(dot.fill || markFill));
layer.appendChild(el);
markState.tiles.push(el);
pieces.push({
el,
from: { x: fx, y: fy, width: dot.r * 2, height: dot.r * 2, rx: dot.r },
to: { x: cell.x, y: cell.y, width: cell.width, height: cell.height, rx: 0 },
fill: makeColorLerp(dot.fill, markFill),
fillEnd: String(markFill),
delay: 0,
meta: { markState }
});
}
}
if (!pieces.length) return this._cancelPieces();
this._runPieces(pieces, (piece) => {
const state = piece.meta.markState;
state.remaining--;
if (state.remaining === 0) {
state.els.forEach((el) => {
el.removeAttribute("opacity");
el.removeAttribute("data-piece-hidden");
});
state.tiles.forEach((t) => {
if (t.parentNode) t.parentNode.removeChild(t);
});
}
});
}
/**
* Stagger and start a piece run. Delays sweep the (already spatially
* sorted) list front to back, and the last piece still lands within the
* configured morph speed.
*
* @param {import('./MorphPieces').Piece[]} pieces
* @param {(piece: import('./MorphPieces').Piece) => void} onPieceDone
*/
_runPieces(pieces, onPieceDone) {
const speed = this.getSpeed();
const stagger = Math.min(PIECE_STAGGER_MAX, speed * 0.35);
const flight = Math.max(180, speed - stagger);
for (let k = 0; k < pieces.length; k++) {
pieces[k].delay = pieces.length > 1 ? k / (pieces.length - 1) * stagger : 0;
}
this._pieceCancel = runPieceTween({
pieces,
duration: flight,
onPieceDone,
onAllDone: () => {
this._pieceCancel = null;
this._cancelPieces();
}
});
}
/**
* The incoming chart's marks, read live: every `path[pathTo]` grouped into
* one mark per (realIndex, j), with the union bbox of its final geometry.
* Bar marks are one path each; summary marks (boxPlot, violin) may be
* several, walked exactly like the capture branch walks the outgoing ones.
*
* @param {string} toType
* @returns {Map<string, { realIndex: number, j: number, bbox: {x:number,y:number,width:number,height:number}, fill: string|null, els: any[], d?: string }>}
*/
_collectTargetMarks(toType) {
var _a;
const baseEl = (_a = this.w.globals.dom) == null ? void 0 : _a.baseEl;
const out = /* @__PURE__ */ new Map();
if (!baseEl) return out;
const fam = familyOf(toType);
if (fam === "radial") {
baseEl.querySelectorAll(".apexcharts-pie-series .apexcharts-pie-area").forEach((p, i) => {
const d = p.getAttribute("data:pathFinal") || p.getAttribute("d");
if (!d || !d.trim()) return;
const box = this._pathBBox(d);
if (!box) return;
out.set(`${i}:0`, {
realIndex: i,
j: 0,
d,
bbox: {
x: box.minX,
y: box.minY,
width: box.maxX - box.minX,
height: box.maxY - box.minY
},
fill: p.getAttribute("fill"),
els: [p]
});
});
return out;
}
const wrapClass = fam === "summary" ? `.apexcharts-${toType}-series` : ".apexcharts-bar-series";
baseEl.querySelectorAll(`${wrapClass} .apexcharts-series`).forEach((group) => {
var _a2;
const realIndex = parseInt((_a2 = group.getAttribute("data:realIndex")) != null ? _a2 : "0", 10) || 0;
let order = 0;
group.querySelectorAll("path[pathTo]").forEach((p) => {
var _a3;
const d = p.getAttribute("pathTo") || p.getAttribute("d");
if (!d || !d.trim()) return;
const jAttr = parseInt((_a3 = p.getAttribute("j")) != null ? _a3 : "", 10);
const j = isNaN(jAttr) ? order++ : jAttr;
const box = this._pathBBox(d);
if (!box) return;
const key = `${realIndex}:${j}`;
const prev = out.get(key);
if (prev) {
prev.els.push(p);
prev.bbox = {
x: Math.min(prev.bbox.x, box.minX),
y: Math.min(prev.bbox.y, box.minY),
width: Math.max(prev.bbox.x + prev.bbox.width, box.maxX) - Math.min(prev.bbox.x, box.minX),
height: Math.max(prev.bbox.y + prev.bbox.height, box.maxY) - Math.min(prev.bbox.y, box.minY)
};
} else {
out.set(key, {
realIndex,
j,
bbox: {
x: box.minX,
y: box.minY,
width: box.maxX - box.minX,
height: box.maxY - box.minY
},
fill: p.getAttribute("fill"),
els: [p]
});
}
});
});
return out;
}
/**
* Walk the outgoing chart's DOM and collect path `d` strings keyed by
* (realIndex, j). The selectors are scoped to the chart family — bar
* elements have `pathTo` set; pie/radial elements use their final `d`.
*
* @param {string} fromType
* @returns {{ marks: Array<{ realIndex: number, j: number, d: string, fill: string|null, key?: string|null }>, branches: Array<{ key: string, d: string, fill: string|null }>, unitDots: Map<number, Array<{x:number,y:number,r:number,fill:string|null}>> }}
*/
_captureFromDOM(fromType) {
var _a;
const baseEl = (_a = this.w.globals.dom) == null ? void 0 : _a.baseEl;
if (!baseEl) return { marks: [], branches: [], unitDots: /* @__PURE__ */ new Map() };
const captured = [];
const branches = [];
const unitDots = /* @__PURE__ */ new Map();
const fam = familyOf(fromType);
if (fam === "bar") {
const seriesNodes = baseEl.querySelectorAll(
".apexcharts-bar-series .apexcharts-series"
);
seriesNodes.forEach((seriesNode) => {
var _a2;
const realIndex = parseInt(
(_a2 = seriesNode.getAttribute("data:realIndex")) != null ? _a2 : "0",
10
);
const paths = seriesNode.querySelectorAll("path[pathTo]");
paths.forEach((p, j) => {
const d = p.getAttribute("pathTo") || p.getAttribute("d");
if (!d) return;
captured.push({
realIndex,
j,
d,
fill: p.getAttribute("fill")
});
});
});
} else if (fam === "summary") {
const byMark = /* @__PURE__ */ new Map();
baseEl.querySelectorAll(`.apexcharts-${fromType}-area`).forEach((p) => {
var _a2, _b;
const j = parseInt((_a2 = p.getAttribute("j")) != null ? _a2 : "", 10);
if (isNaN(j)) return;
const d = p.getAttribute("pathTo") || p.getAttribute("d");
if (!d || !d.trim()) return;
const group = typeof p.closest === "function" ? p.closest(".apexcharts-series") : null;
const realIndex = parseInt((_b = group == null ? void 0 : group.getAttribute("data:realIndex")) != null ? _b : "0", 10) || 0;
const key = `${realIndex}:${j}`;
const prev = byMark.get(key);
if (prev) prev.d += ` ${d}`;
else byMark.set(key, { realIndex, j, d, fill: p.getAttribute("fill") });
});
Array.from(byMark.values()).sort((a, b) => a.realIndex - b.realIndex || a.j - b.j).forEach((m) => captured.push(m));
} else if (fam === "partition") {
if (fromType === "treemap") {
const rectPath = (el) => {
var _a2, _b, _c, _d;
const x = parseFloat((_a2 = el.getAttribute("x")) != null ? _a2 : "");
const y = parseFloat((_b = el.getAttribute("y")) != null ? _b : "");
const width = parseFloat((_c = el.getAttribute("width")) != null ? _c : "");
const height = parseFloat((_d = el.getAttribute("height")) != null ? _d : "");
if (![x, y, width, height].every((v) => isFinite(v))) return null;
return `M ${x} ${y} L ${x + width} ${y} L ${x + width} ${y + height} L ${x} ${y + height} Z`;
};
const tiles = baseEl.querySelectorAll(".apexcharts-treemap-rect");
tiles.forEach((t) => {
var _a2, _b;
const d = rectPath(t);
if (!d) return;
captured.push({
realIndex: parseInt((_a2 = t.getAttribute("i")) != null ? _a2 : "0", 10) || 0,
j: parseInt((_b = t.getAttribute("j")) != null ? _b : "0", 10) || 0,
d,
fill: t.getAttribute("fill"),
key: t.getAttribute("data:key") || null
});
});
const containers = baseEl.querySelectorAll(
".apexcharts-treemap-parent-rect"
);
containers.forEach((c) => {
const d = rectPath(c);
const key = c.getAttribute("data:key");
if (!d || !key) return;
branches.push({ key, d, fill: c.getAttribute("fill") });
});
} else {
const arcs = baseEl.querySelectorAll(".apexcharts-sunburst-arc");
const leaves = [];
arcs.forEach((a) => {
if (a.getAttribute("data:leaf") === "true") leaves.push(a);
});
const source = leaves.length ? leaves : Array.from(arcs);
source.forEach((a, i) => {
const d = a.getAttribute("d");
if (!d || !d.trim()) return;
captured.push({
realIndex: i,
j: 0,
d,
fill: a.getAttribute("fill"),
key: a.getAttribute("data:key") || null
});
});
arcs.forEach((a) => {
if (a.getAttribute("data:leaf") === "true") return;
const d = a.getAttribute("d");
const key = a.getAttribute("data:key");
if (!d || !d.trim() || !key) return;
branches.push({ key, d, fill: a.getAttribute("fill") });
});
}
} else if (fam === "unit") {
const dots = baseEl.querySelectorAll(".apexcharts-unit-area");
const boxes = /* @__PURE__ */ new Map();
dots.forEach((dot) => {
var _a2, _b, _c, _d, _e, _f, _g;
const i = parseInt((_a2 = dot.getAttribute("i")) != null ? _a2 : "", 10);
if (isNaN(i)) return;
const cxAttr = dot.getAttribute("cx");
let x;
let y;
let r = 3;
if (cxAttr != null) {
x = parseFloat(cxAttr);
y = parseFloat((_b = dot.getAttribute("cy")) != null ? _b : "");
r = parseFloat((_c = dot.getAttribute("r")) != null ? _c : "3") || 3;
} else {
const wAttr = parseFloat((_d = dot.getAttribute("width")) != null ? _d : "0") || 0;
const hAttr = parseFloat((_e = dot.getAttribute("height")) != null ? _e : "0") || 0;
x = parseFloat((_f = dot.getAttribute("x")) != null ? _f : "") + wAttr / 2;
y = parseFloat((_g = dot.getAttribute("y")) != null ? _g : "") + hAttr / 2;
r = Math.max(wAttr, hAttr) / 2 || 3;
}
if (!isFinite(x) || !isFinite(y)) return;
let list = unitDots.get(i);
if (!list) {
list = [];
unitDots.set(i, list);
}
list.push({ x, y, r, fill: dot.getAttribute("fill") });
const box = boxes.get(i);
if (!box) {
boxes.set(i, {
minX: x,
minY: y,
maxX: x,
maxY: y,
fill: dot.getAttribute("fill")
});
return;
}
if (x < box.minX) box.minX = x;
if (x > box.maxX) box.maxX = x;
if (y < box.minY) box.minY = y;
if (y > box.maxY) box.maxY = y;
});
Array.from(boxes.keys()).sort((a, b) => a - b).forEach((i) => {
const b = (
/** @type {any} */
boxes.get(i)
);
const pad = 2;
const x1 = b.minX - pad;
const y1 = b.minY - pad;
const x2 = b.maxX + pad;
const y2 = b.maxY + pad;
captured.push({
realIndex: i,
j: 0,
d: `M ${x1} ${y1} L ${x2} ${y1} L ${x2} ${y2} L ${x1} ${y2} Z`,
fill: b.fill
});
});
} else if (fam === "radial") {
if (fromType === "radialBar" || fromType === "gauge") {
const centerX = this.w.layout.gridWidth / 2;
const centerY = Math.min(this.w.layout.gridWidth, this.w.layout.gridHeight) / 2;
const rings = baseEl.querySelectorAll(
".apexcharts-radial-series .apexcharts-radialbar-area"
);
rings.forEach((p) => {
var _a2;
const parent = (
/** @type {Element|null} */
p.parentElement
);
const realIndex = parseInt(
(_a2 = parent == null ? void 0 : parent.getAttribute("data:realIndex")) != null ? _a2 : "0",
10
);
const rawD = p.getAttribute("d");
if (!rawD) return;
const strokeWidth = parseFloat(p.getAttribute("stroke-width") || "0");
const d = strokeWidth > 1 ? this._radialArcToFilledSegment(
rawD,
strokeWidth,
centerX,
centerY
) || rawD : rawD;
captured.push({
realIndex,
j: 0,
d,
fill: p.getAttribute("stroke")
});
});
} else {
const slices = baseEl.querySelectorAll(
".apexcharts-pie-series .apexcharts-pie-area"
);
slices.forEach(
(p, i) => {
const d = p.getAttribute("d");
if (!d) return;
captured.push({
realIndex: i,
j: 0,
d,
fill: p.getAttribute("fill")
});
}
);
}
}
return { marks: captured, branches, unitDots };
}
/**
* Convert a radialBar's stroked open-arc `d` ("M x1 y1 A r r 0 large sweep
* x2 y2") into a closed donut-segment polygon whose FILLED rendering
* visually matches the original stroked arc — needed because the morph
* target (pie/donut/polarArea) renders by fill, not stroke. Returns null
* if the input doesn't match the expected M-then-A shape.
*
* @param {string} rawD
* @param {number} strokeWidth
* @param {number} centerX
* @param {number} centerY
* @returns {string | null}
*/
_radialArcToFilledSegment(rawD, strokeWidth, centerX, centerY) {
const m = rawD.match(
/M\s*(-?[\d.]+)\s+(-?[\d.]+)\s+A\s*(-?[\d.]+)\s+(?:-?[\d.]+)\s+(?:-?[\d.]+)\s+(\d)\s+(\d)\s+(-?[\d.]+)\s+(-?[\d.]+)/
);
if (!m) return null;
const x1 = parseFloat(m[1]);
const y1 = parseFloat(m[2]);
const r = parseFloat(m[3]);
const large = parseInt(m[4], 10);
const sweep = parseInt(m[5], 10);
const x2 = parseFloat(m[6]);
const y2 = parseFloat(m[7]);
if (!isFinite(r) || r <= 0) return null;
const half = strokeWidth / 2;
const rOuter = r + half;
const rInner = Math.max(0, r - half);
const proj = (px, py, newR) => {
const dx = px - centerX;
const dy = py - centerY;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist === 0) return { x: centerX, y: centerY };
const k = newR / dist;
return { x: centerX + dx * k, y: centerY + dy * k };
};
const o1 = proj(x1, y1, rOuter);
const o2 = proj(x2, y2, rOuter);
const i1 = proj(x1, y1, rInner);
const i2 = proj(x2, y2, rInner);
const sweepBack = sweep ? 0 : 1;
return `M ${o1.x} ${o1.y} A ${rOuter} ${rOuter} 0 ${large} ${sweep} ${o2.x} ${o2.y} L ${i2.x} ${i2.y} A ${rInner} ${rInner} 0 ${large} ${sweepBack} ${i1.x} ${i1.y} Z`;
}
/**
* Build a closed donut-segment path for the given polar arc geometry. Used
* by Radial.drawArcs when morphing FROM a filled wedge (pie/donut/polarArea)
* TO a radialBar arc: the final radialBar is rendered as a stroked open arc,
* but during the morph we tween d toward this closed-segment form (which
* looks identical to the stroked arc when filled with the same color) so
* the in-between frames remain visually consistent filled shapes rather
* than a thick-outlined wedge.
*
* @param {number} centerX
* @param {number} centerY
* @param {number} ringRadius - centerline radius of the radialBar ring
* @param {number} strokeWidth - the ring's stroke thickness
* @param {number} startAngleDeg - in degrees, 0° = top (12 o'clock)
* @param {number} endAngleDeg
* @returns {string}
*/
buildRingSegmentPath(centerX, centerY, ringRadius, strokeWidth, startAngleDeg, endAngleDeg) {
const halfStroke = strokeWidth / 2;
const rOuter = ringRadius + halfStroke;
const rInner = Math.max(0, ringRadius - halfStroke);
const sRad = (startAngleDeg - 90) * Math.PI / 180;
const eRad = (endAngleDeg - 90) * Math.PI / 180;
const oStart = {
x: centerX + rOuter * Math.cos(sRad),
y: centerY + rOuter * Math.sin(sRad)
};
const oEnd = {
x: centerX + rOuter * Math.cos(eRad),
y: centerY + rOuter * Math.sin(eRad)
};
const iStart = {
x: centerX + rInner * Math.cos(sRad),
y: centerY + rInner * Math.sin(sRad)
};
const iEnd = {
x: centerX + rInner * Math.cos(eRad),
y: centerY + rInner * Math.sin(eRad)
};
const sweep = endAngleDeg > startAngleDeg ? 1 : 0;
const large = Math.abs(endAngleDeg - startAngleDeg) > 180 ? 1 : 0;
return `M ${oStart.x} ${oStart.y} A ${rOuter} ${rOuter} 0 ${large} ${sweep} ${oEnd.x} ${oEnd.y} L ${iEnd.x} ${iEnd.y} A ${rInner} ${rInner} 0 ${large} ${1 - sweep} ${iStart.x} ${iStart.y} Z`;
}
/**
* @returns {string | null} the chart-type the active snapshot was captured
* from, or null when no morph is in flight.
*/
getFromType() {
return this._snapshot ? this._snapshot.fromType : null;
}
/**
* Build a (targetKey → captured) map. The targetKey matches the lookup
* pattern each chart-type renderer uses when it asks
* `getInitialPathFor(realIndex, j)`.
*
* Strategy: flatten the captured items into a linear sequence (matching the
* source chart's natural DOM iteration order: series-then-point for bar,
* ring-by-ring for radial), then walk the target's iteration positions in
* the same order and pair them up 1:1. This handles every supported shape
* without per-pair branching:
*
* - bar (1 series, N pts) ↔ radial-family (N items) → linear[k] ↔ k
* - bar (M series, 1 pt) ↔ radial-family (M items) → linear[k] ↔ k
* - radial-family (N items) ↔ bar (any matching shape) → linear[k] ↔ flat target
* - radial-family ↔ radial-family → linear[k] ↔ k
*
* @param {Array<{ realIndex: number, j: number, d: string, fill: string|null, key?: string|null }>} captured
* @param {string} _fromType
* @param {string} toType
* @param {any} newSeries - the series array being passed to the new chart;
* used only to derive the bar target's (realIndex, j) iteration positions.
* @param {Array<{ key: string, d: string, fill: string|null }>} [branches]
* non-leaf marks, for the key-based partition pairing.
*/
_buildMapping(captured, _fromType, toType, newSeries, branches) {
const map = /* @__PURE__ */ new Map();
const tf = familyOf(toType);
const flat = captured.slice().sort((a, b) => a.realIndex - b.realIndex || a.j - b.j);
if (tf === "partition" && branches && branches.length) {
const keyedMarks = flat.filter((c) => c.key);
if (keyedMarks.length === flat.length) {
keyedMarks.forEach((c) => {
map.set(`key:${c.key}`, { d: c.d, fill: c.fill });
});
branches.forEach((br) => {
map.set(`key:${br.key}`, { d: br.d, fill: br.fill });
});
}
}
if (tf === "radial" || tf === "unit" || tf === "partition") {
flat.forEach((c, i) => {
map.set(`${i}:0`, { d: c.d, fill: c.fill });
});
return map;
}
if (tf === "bar" || tf === "summary") {
const positions = [];
const series = Array.isArray(newSeries) ? newSeries : [];
series.forEach((s, seriesIdx) => {
const data = s && Array.isArray(s.data) ? s.data : [];
for (let j = 0; j < data.length; j++) {
positions.push({ realIndex: seriesIdx, j });
}
});
flat.forEach((c, i) => {
const pos = positions[i];
if (pos) {
map.set(`${pos.realIndex}:${pos.j}`, { d: c.d, fill: c.fill });
}
});
return map;
}
return map;
}
isActive() {
return this._snapshot !== null;
}
/**
* @param {number|string} realIndex
* @param {number|string} j
* @returns {string | null}
*/
getInitialPathFor(realIndex, j) {
if (!this._snapshot) return null;
const entry = this._snapshot.mapping.get(`${realIndex}:${j}`);
if (!entry) return null;
const dx = this._snapshot.oldLayout.translateX - (this.w.layout.translateX || 0);
const dy = this._snapshot.oldLayout.translateY - (this.w.layout.translateY || 0);
return dx === 0 && dy === 0 ? entry.d : this._translatePathD(entry.d, dx, dy);
}
/**
* Offset every absolute coordinate in an SVG path `d` by (dx, dy).
*
* Assumes the path uses only uppercase (absolute) commands — every path
* ApexCharts generates does. Relative-command paths would pass through
* unchanged at the lowercase, which is also semantically correct (deltas
* don't shift under a parent translate).
*
* @param {string} d
* @param {number} dx
* @param {number} dy
* @returns {string}
*/
_translatePathD(d, dx, dy) {
if (dx === 0 && dy === 0) return d;
const commands = parsePath(d);
return commands.map(
/** @param {any[]} c */
(c) => {
const cmd = c[0];
if (cmd === "Z") return "Z";
if (cmd === "M" || cmd === "L" || cmd === "T") {
return `${cmd} ${c[1] + dx} ${c[2] + dy}`;
}
if (cmd === "H") return `${cmd} ${c[1] + dx}`;
if (cmd === "V") return `${cmd} ${c[1] + dy}`;
if (cmd === "C") {
return `${cmd} ${c[1] + dx} ${c[2] + dy} ${c[3] + dx} ${c[4] + dy} ${c[5] + dx} ${c[6] + dy}`;
}
if (cmd === "S" || cmd === "Q") {
return `${cmd} ${c[1] + dx} ${c[2] + dy} ${c[3] + dx} ${c[4] + dy}`;
}
if (cmd === "A") {
return `${cmd} ${c[1]} ${c[2]} ${c[3]} ${c[4]} ${c[5]} ${c[6] + dx} ${c[7] + dy}`;
}
return c.join(" ");
}
).join(" ");
}
/**
* The centre point (in the NEW chart's screen space) of the captured shape
* for cluster `i`. Kept for callers that only need a point; the unit renderer
* uses getInitialBBoxFor so its dots fill the shape rather than stack on a
* single point.
* @param {number} i
* @returns {{ x: number, y: number } | null}
*/
getInitialCenterFor(i) {
const box = this.getInitialBBoxFor(i);
if (!box) return null;
return { x: box.x + box.width / 2, y: box.y + box.height / 2 };
}
/**
* The `k`-th captured path in draw order, already shifted into the NEW
* chart's coordinate space.
*
* For marks that pair up by position rather than by a (series, point) grid:
* a treemap's tiles and a sunburst's leaves are each one mark per row, laid
* out in the same reading order, so the k-th of one becomes the k-th of the
* other.
*
* @param {number} k
* @returns {string | null}
*/
getInitialPathAt(k) {
return this.getInitialPathFor(k, 0);
}
/**
* The captured shape for a branch identity (charts/common/Hierarchy.morphKey),
* or null when the outgoing chart had no mark for that branch.
*
* This is what lets a partition morph pair at every level: a sector, an
* industry and a company each find the arc or tile that stood for the same
* branch, instead of leaves pairing by draw order while the containers pop.
*
* @param {string} key
* @returns {string | null}
*/
getInitialPathForKey(key) {
if (!this._snapshot || !key) return null;
return this.getInitialPathFor("key", key);
}
/** True when the active snapshot can pair by branch key. */
hasKeyedMarks() {
if (!this._snapshot) return false;
for (const k of this._snapshot.mapping.keys()) {
if (typeof k === "string" && k.startsWith("key:")) return true;
}
return false;
}
/**
* Where the `j`-th of `n` objects in cluster `i` starts, INSIDE the shape it
* came out of.
*
* An aggregate mark stands for a quantity, and its extent is that quantity: a
* bar of height h representing n units gives its k-th unit the height
* fraction (k + 0.5)/n. So a bar does not spray its dots from a single point,
* it comes apart along its own length, bottom-up, and each dot leaves from
* the part of the bar that was standing for it. The reverse direction reads
* the same geometry, so explode and collapse are inverses.
*
* The distribution follows the captured shape's LONGER axis, which is what
* makes one function serve both marks: a bar's box is tall and thin, so the
* dots leave in a column; a wedge's box is squat, so they leave in a row
* across it.
*
* @param {number} i - cluster index
* @param {number} j - the object's rank within its cluster
* @param {number} n - objects in the cluster
* @returns {{ x: number, y: number } | null}
*/
getInitialSlotFor(i, j, n) {
const box = this.getInitialBBoxFor(i);
if (!box) return null;
const cx = box.x + box.width / 2;
const cy = box.y + box.height / 2;
if (!(n > 1) || !(j >= 0)) return { x: cx, y: cy };
const t = (Math.min(j, n - 1) + 0.5) / n;
if (box.height >= box.width) {
return { x: cx, y: box.y + box.height * (1 - t) };
}
return { x: box.x + box.width * t, y: cy };
}
/**
* The bounding box (in the NEW chart's screen space) of the captured shape
* for cluster `i`. `getInitialSlotFor` distributes a cluster's objects across
* this box as their start positions, so a tall bar visibly breaks apart into
* a tall column of dots that then swarm into the cluster.
* @param {number} i
* @returns {{ x: number, y: number, width: number, height: number } | null}
*/
getInitialBBoxFor(i) {
if (!this._snapshot) return null;
const entry = this._snapshot.mapping.get(`${i}:0`);
if (!entry) return null;
const box = this._pathBBox(entry.d);
if (!box) return null;
const dx = this._snapshot.oldLayout.translateX - (this.w.layout.translateX || 0);
const dy = this._snapshot.oldLayout.translateY - (this.w.layout.translateY || 0);
return {
x: box.minX + dx,
y: box.minY + dy,
width: box.maxX - box.minX,
height: box.maxY - box.minY
};
}
/**
* Bounding box of an absolute-command SVG path `d`. Good enough as the burst
* footprint (we only need where the shape sat, not exact geometry).
* @param {string} d
* @returns {{ minX:number, minY:number, maxX:number, maxY:number } | null}
*/
_pathBBox(d) {
const commands = parsePath(d);
let minX = Infinity;
let minY = Infinity;
let maxX = -Infinity;
let maxY = -Infinity;
let seen = false;
commands.forEach(
/** @param {any[]} c */
(c) => {
const cmd = c[0];
if (cmd === "Z") return;
let pairs = [];
if (cmd === "H") pairs = [[c[1], (minY + maxY) / 2 || c[1]]];
else if (cmd === "V") pairs = [[(minX + maxX) / 2 || c[1], c[1]]];
else if (cmd === "A") pairs = [[c[6], c[7]]];
else {
for (let k = 1; k + 1 < c.length; k += 2) pairs.push([c[k], c[k + 1]]);
}
pairs.forEach(([x, y]) => {
if (!isFinite(x) || !isFinite(y)) return;
seen = true;
if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (y < minY) minY = y;
if (y > maxY) maxY = y;
});
}
);
if (!seen) return null;
return { minX, minY, maxX, maxY };
}
/**
* @param {number} realIndex
* @param {number} j
* @returns {string | null}
*/
getInitialFillFor(realIndex, j) {
if (!this._snapshot) return null;
const entry = this._snapshot.mapping.get(`${realIndex}:${j}`);
return entry ? entry.fill : null;
}
/** @returns {number} */
getSpeed() {
const animCfg = this.w.config.chart.animations;
return animCfg.chartTypeMorph && animCfg.chartTypeMorph.speed || animCfg.speed || 600;
}
/**
* Fade newly-mounted axes / grid / legend / titles from opacity 0 → 1 in
* parallel with the morph. Without this the chart's chrome would pop in
* abruptly while the series elements are still mid-tween, which reads as a
* jarring layout shift.
*/
applyChromeFade() {
var _a;
if (!this._snapshot || !Environment.isBrowser()) return;
const baseEl = (_a = this.w.globals.dom) == null ? void 0 : _a.baseEl;
if (!baseEl) return;
if (this._snapshot.pieceOut) this._separatePieces();
else if (this._snapshot.pieceIn) this._combinePieces();
else this._mountGhost();
const speed = this.getSpeed();
const chromeSelectors = [
".apexcharts-xaxis",
".apexcharts-yaxis",
".apexcharts-grid",
".apexcharts-gridlines-horizontal",
".apexcharts-gridlines-vertical",
".apexcharts-legend",
".apexcharts-title-text",
".apexcharts-subtitle-text"
];
chromeSelectors.forEach((sel) => {
baseEl.querySelectorAll(sel).forEach((el) => {
if (!el.style) return;
el.style.opacity = "0";
el.style.transition = `opacity ${speed}ms ease-out`;
BrowserAPIs.requestAnimationFrame(() => {
el.style.opacity = "1";
});
setTimeout(() => {
el.style.transition = "";
el.style.opacity = "";
}, speed + 80);
});
});
setTimeout(() => this.cleanup(), speed + 100);
}
cleanup() {
this._snapshot = null;
this._removeGhost();
this._cancelPieces();
}
}
_core__default.registerFeatures({ morphTypeChange: MorphTypeChange });
export {
default2 as default
};