apexcharts
Version:
A JavaScript Chart Library
553 lines (552 loc) • 16.9 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 v6.6.1
* (c) 2018-2026 ApexCharts
*/
import ApexCharts from "apexcharts/core";
import { default as default2 } from "apexcharts/core";
const REGISTRY_KEY = "__apexcharts_plugins__";
function getRegistry() {
const g = (
/** @type {any} */
globalThis
);
if (!g[REGISTRY_KEY]) g[REGISTRY_KEY] = {};
return g[REGISTRY_KEY];
}
function getPlugin(name) {
return getRegistry()[name] || null;
}
const WEAVE_API_VERSION = 1;
const PLUGIN_CHART_METHODS = [
"updateOptions",
"updateSeries",
"appendData",
"appendSeries",
"toggleSeries",
"showSeries",
"hideSeries",
"highlightSeries",
"isSeriesHidden",
"zoomX",
"addXaxisAnnotation",
"addYaxisAnnotation",
"addPointAnnotation",
"clearAnnotations",
"removeAnnotation",
"dataURI",
"exportToCSV"
];
function buildBoundPublicMethods(ctx) {
const out = {};
PLUGIN_CHART_METHODS.forEach((m) => {
if (typeof ctx[m] === "function") out[m] = ctx[m].bind(ctx);
});
return Object.freeze(out);
}
function makeLayerHandle(g, graphics) {
const add = (el) => {
if (el) g.add(el);
return el;
};
const handle = {
get node() {
return g.node;
},
/** @param {any} opts */
path(opts = {}) {
const {
d = "",
stroke = "#000",
width = 1,
fill = "none",
opacity = 1,
dash = 0,
className = ""
} = opts;
return add(
graphics.drawPath({
d,
stroke,
strokeWidth: width,
fill,
fillOpacity: fill === "none" ? 0 : opacity,
strokeOpacity: opacity,
strokeDashArray: dash,
classes: className
})
);
},
/** @param {any} opts */
line(opts = {}) {
const { x1, y1, x2, y2, stroke = "#000", width = 1, dash = 0 } = opts;
return add(graphics.drawLine(x1, y1, x2, y2, stroke, dash, width));
},
/** @param {any} opts */
rect(opts = {}) {
const {
x = 0,
y = 0,
w = 0,
h = 0,
r = 0,
fill = "#000",
stroke = null,
opacity = 1
} = opts;
return add(
graphics.drawRect(
x,
y,
w,
h,
r,
fill,
opacity,
stroke != null ? 1 : null,
stroke
)
);
},
/** @param {any} opts */
circle(opts = {}) {
const { cx = 0, cy = 0, r = 0, fill = "#000", stroke = null } = opts;
return add(
graphics.drawCircle(r, { cx, cy, fill, stroke: stroke || "none" })
);
},
/** @param {any} opts */
text(opts = {}) {
const {
x = 0,
y = 0,
text = "",
color,
size,
anchor = "start",
weight
} = opts;
return add(
graphics.drawText({
x,
y,
text,
textAnchor: anchor,
fontSize: size,
foreColor: color,
fontWeight: weight
})
);
},
clear() {
const node = g.node;
while (node.firstChild) node.removeChild(node.firstChild);
return handle;
}
};
return handle;
}
function buildPluginAPI(host, record) {
const ctx = host.ctx;
const w = host.w;
const api = {
name: record.def.name,
version: WEAVE_API_VERSION,
// Live: reconcile refreshes record.options when the chart's plugins config
// changes, so updateOptions({ plugins: [{ name, options }] }) reconfigures
// an active plugin in place. The returned object is frozen.
get options() {
return record.options;
},
// ── lifecycle subscription ──
/**
* @param {string} hook
* @param {Function} fn
*/
on(hook, fn) {
const m = record.handlers;
if (!m.has(hook)) m.set(hook, []);
m.get(hook).push(fn);
return api;
},
/**
* @param {string} hook
* @param {Function} fn
*/
off(hook, fn) {
const a = record.handlers.get(hook);
if (a) {
const i = a.indexOf(fn);
if (i > -1) a.splice(i, 1);
}
return api;
},
// ── per-plugin, per-chart scratch state (survives updates, dropped on
// destroy). The api object is frozen, but this object is mutable. ──
store: {},
// ── drawing (renderer-agnostic) ──
// Call this INSIDE each draw handler: the host wipes plugin layers at the
// start of every draw pass, so a handle cached across draws points at a
// detached node and its writes vanish silently.
/** @param {any} [opts] */
layer(opts) {
return host._layer(record.def.name, opts || {});
},
// ── reads ──
get scales() {
return host._currentScales;
},
// Served from the per-dispatch snapshot when one exists (invalidated at
// every dispatch), so reading api.data in a loop does not rebuild the
// point arrays on each property access.
get data() {
return host._lastData || (host._lastData = host._dataSnapshot());
},
theme: Object.freeze({
get mode() {
return w.config.theme.mode;
},
get foreColor() {
return w.config.chart.foreColor;
},
/** @param {number} i */
seriesColor(i) {
return w.globals.colors[i];
},
/** @param {string} name */
token(name) {
return host._token(name);
}
}),
// ── curated actions (bound public methods only; NEVER raw w) ──
chart: buildBoundPublicMethods(ctx),
// ── custom events out to the host app ──
/**
* Fires as `plugin:<pluginName>:<name>` on the chart's event bus. The
* namespace is not optional: the bus also carries the internal lifecycle
* events ('updated', 'mounted', ...), and an un-namespaced emit could
* trigger every internal subscriber (history capture, re-render hooks).
* Listen with chart.addEventListener('plugin:myplugin:myevent', fn).
* @param {string} name
* @param {any} [detail]
*/
emit(name, detail) {
ctx.events.fireEvent(`plugin:${record.def.name}:${name}`, [ctx, detail]);
},
// ── host element (read; lazy: baseEl is not set until render) ──
get el() {
return w.dom.baseEl;
}
};
return Object.freeze(api);
}
class WeaveHost {
/**
* @param {import('../../types/internal').ChartStateW} w
* @param {import('../../types/internal').ChartContext} ctx
*/
constructor(w, ctx) {
this.w = w;
this.ctx = ctx;
this.active = [];
this._layers = /* @__PURE__ */ new Map();
this._currentScales = null;
this._lastPluginsRef = null;
this._lastData = null;
this._updatedWired = false;
this._onUpdated = this._onUpdated.bind(this);
this._init();
}
_init() {
const list = this.w.config.plugins || [];
list.map((entry, i) => ({
entry,
order: entry.order != null ? entry.order : i
})).sort((a, b) => a.order - b.order).forEach((o) => this._activate(o.entry));
this._lastPluginsRef = this.w.config.plugins;
this._wireUpdated();
}
_wireUpdated() {
if (this._updatedWired) return;
this.ctx.addEventListener("updated", this._onUpdated);
this._updatedWired = true;
}
_onUpdated() {
this.dispatch("afterUpdate", { pass: "update" });
}
/**
* @param {any} entry { name, options?, order? }
*/
_activate(entry) {
const def = getPlugin(entry.name);
if (!def) {
console.error(`[apexcharts] plugin "${entry.name}" is not registered.`);
return;
}
const v = def.apiVersion != null ? def.apiVersion : 1;
if (Math.trunc(v) !== WEAVE_API_VERSION) {
console.error(
`[apexcharts] plugin "${def.name}" targets Weave API v${v}, host is v${WEAVE_API_VERSION}; skipped.`
);
return;
}
const record = {
def,
options: Object.freeze(__spreadValues({}, entry.options || {})),
handlers: /* @__PURE__ */ new Map(),
disabled: false,
failures: 0,
api: null
};
record.api = buildPluginAPI(this, record);
this.active.push(record);
this._guard(record, "setup", () => def.setup(record.api));
}
/**
* @param {string} hook
* @param {{ pass?: string, xyRatios?: any }} [extra]
*/
dispatch(hook, extra) {
if (hook === "draw") {
this._reconcile();
this._resetLayers();
}
this._lastData = null;
if (!this.active.length) return;
if (hook === "afterParse") {
this._currentScales = null;
} else if (extra && "xyRatios" in extra) {
this._setScales(extra.xyRatios);
}
const pass = extra && extra.pass || "full";
let data = null;
for (const record of this.active) {
if (record.disabled) continue;
const fns = record.handlers.get(hook);
if (!fns || !fns.length) continue;
if (data === null) {
data = this._dataSnapshot();
this._lastData = data;
}
const payload = {
api: record.api,
scales: this._currentScales,
data,
pass,
hook
};
for (const fn of fns.slice()) {
this._guard(record, hook, () => fn(payload));
}
}
}
/**
* @param {any} record
* @param {string} where
* @param {Function} fn
*/
_guard(record, where, fn) {
if (record.disabled) return;
try {
fn();
} catch (e) {
console.error(
`[apexcharts] plugin "${record.def.name}" threw in "${where}":`,
e
);
record.failures = (record.failures || 0) + 1;
if (record.failures >= 3) {
record.disabled = true;
console.error(
`[apexcharts] plugin "${record.def.name}" disabled after repeated errors.`
);
}
}
}
// ─── Scales facade ──────────────────────────────────────────────────────
/**
* Build api.scales from the SAME xyRatios the series were drawn with, so
* plugin pixels align with series pixels by construction.
* @param {any} xyRatios
*/
_setScales(xyRatios) {
const w = this.w;
const gl = w.globals;
const L = w.layout;
if (!xyRatios || !gl.axisCharts) {
this._currentScales = null;
return;
}
const xRatio = xyRatios.xRatio;
const yRatio = xyRatios.yRatio || [];
const yr = (axis) => yRatio[axis] != null ? yRatio[axis] : yRatio[0];
const maxY = (axis) => gl.maxYArr[axis] != null ? gl.maxYArr[axis] : gl.maxY;
const minY = (axis) => gl.minYArr[axis] != null ? gl.minYArr[axis] : gl.minY;
this._currentScales = {
/** @param {number} v */
x: (v) => L.translateX + (v - gl.minX) / xRatio,
/**
* @param {number} v
* @param {number} [axis]
*/
y: (v, axis = 0) => L.translateY + (maxY(axis) - v) / yr(axis),
domainX: [gl.minX, gl.maxX],
/** @param {number} [axis] */
domainY: (axis = 0) => [minY(axis), maxY(axis)],
gridWidth: L.gridWidth,
gridHeight: L.gridHeight,
ratios: xyRatios
};
}
// ─── Read-only data snapshot ────────────────────────────────────────────
/**
* @returns {any[]} defensive per-series snapshot (never the live slice)
*/
_dataSnapshot() {
const w = this.w;
const gl = w.globals;
const series = w.seriesData.series || [];
const seriesX = w.seriesData.seriesX || [];
return series.map((sData, i) => {
const xs = seriesX[i] || [];
const points = (sData || []).map((y, j) => ({
x: xs[j] != null ? xs[j] : j,
y
}));
return {
name: gl.seriesNames ? gl.seriesNames[i] : void 0,
hidden: (gl.collapsedSeriesIndices || []).includes(i),
color: gl.colors ? gl.colors[i] : void 0,
points
};
});
}
// ─── Theme tokens ───────────────────────────────────────────────────────
/**
* @param {string} name
* @returns {any}
*/
_token(name) {
const w = this.w;
const gl = w.globals;
switch (name) {
case "foreColor":
return w.config.chart.foreColor;
case "background":
return w.config.chart.background;
case "accent":
case "primary":
return gl.colors ? gl.colors[0] : void 0;
default:
if (/^series-\d+$/.test(name)) {
return gl.colors ? gl.colors[Number(name.split("-")[1])] : void 0;
}
return void 0;
}
}
// ─── Layers ─────────────────────────────────────────────────────────────
/**
* @param {string} name
* @param {{ z?: 'front'|'behind', className?: string }} opts
*/
_layer(name, { z = "front", className = "" } = {}) {
let g = this._layers.get(name);
if (!g) {
g = this.ctx.graphics.group({
class: `apexcharts-plugin-${name} ${className}`.trim()
});
const parent = this.w.dom.elGraphical.node;
if (z === "behind") parent.insertBefore(g.node, parent.firstChild);
else parent.appendChild(g.node);
g.node.setAttribute("aria-hidden", "true");
this._layers.set(name, g);
}
return makeLayerHandle(g, this.ctx.graphics);
}
/**
* Remove all plugin layers. Run at the start of every `draw` because
* fastUpdate only removes series/data-label groups (not arbitrary plugin
* groups), so without this, fast-path redraws would duplicate plugin output.
*/
_resetLayers() {
const el = this.w.dom.elGraphical;
const parent = el && el.node;
if (parent) {
const groups = parent.querySelectorAll('g[class*="apexcharts-plugin-"]');
Array.prototype.forEach.call(groups, (n) => n.remove());
}
this._layers.clear();
}
// ─── Config-change reconciliation ───────────────────────────────────────
/**
* Diff w.config.plugins by name: teardown removed, activate added; unchanged
* plugins keep their instance + store, but their `options` are refreshed from
* the new entry (api.options is a live getter), so
* updateOptions({ plugins: [{ name, options }] }) reconfigures in place.
* Skipped when the plugins array reference is unchanged (fast redraws), so it
* costs nothing on hover.
*/
_reconcile() {
const plugins = this.w.config.plugins || [];
if (plugins === this._lastPluginsRef) return;
this._lastPluginsRef = plugins;
const desired = new Map(
plugins.map((e, i) => [
e.name,
{ entry: e, order: e.order != null ? e.order : i }
])
);
for (let i = this.active.length - 1; i >= 0; i--) {
const r = this.active[i];
const want = desired.get(r.def.name);
if (!want) {
this._guard(r, "destroy", () => r.def.destroy && r.def.destroy(r.api));
this.active.splice(i, 1);
} else {
r.options = Object.freeze(__spreadValues({}, want.entry.options || {}));
}
}
const activeNames = new Set(this.active.map((r) => r.def.name));
const toAdd = [];
desired.forEach((v, name) => {
if (!activeNames.has(name)) toAdd.push(v);
});
toAdd.sort((a, b) => a.order - b.order).forEach((v) => this._activate(v.entry));
}
/**
* @param {boolean} [isUpdating]
*/
teardown(isUpdating) {
if (!isUpdating) {
this.dispatch("destroy");
for (const record of this.active) {
this._guard(record, "destroy", () => record.def.destroy && record.def.destroy(record.api));
}
this.active = [];
if (this._updatedWired) {
this.ctx.removeEventListener && this.ctx.removeEventListener("updated", this._onUpdated);
this._updatedWired = false;
}
}
this._layers.clear();
}
}
ApexCharts.registerFeatures({ weave: WeaveHost });
export {
default2 as default
};