apexcharts
Version:
A JavaScript Chart Library
801 lines (800 loc) • 27.2 kB
JavaScript
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
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;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
/*!
* ApexCharts v6.6.1
* (c) 2018-2026 ApexCharts
*/
import * as _core from "apexcharts/core";
import _core__default from "apexcharts/core";
import { default as default2 } from "apexcharts/core";
const Graphics = _core.__apex_Graphics;
const Environment = _core.__apex_Environment_Environment;
class Measure {
/**
* @param {import('../../types/internal').ChartStateW} w
* @param {import('../../types/internal').ChartContext} ctx
*/
constructor(w, ctx) {
this.w = w;
this.ctx = ctx;
this.graphics = new Graphics(w);
this.pins = [];
this.drag = null;
this.armed = false;
this.persistent = false;
this.pane = null;
this._seedActive = false;
this._onKeyDown = this._onKeyDown.bind(this);
this._onKeyUp = this._onKeyUp.bind(this);
this._onDown = this._onDown.bind(this);
this._onMove = this._onMove.bind(this);
this._onUp = this._onUp.bind(this);
this._onSeedDown = this._onSeedDown.bind(this);
this._onSeedKey = this._onSeedKey.bind(this);
this._afterRender = this._afterRender.bind(this);
ctx.addEventListener("mounted", this._afterRender);
ctx.addEventListener("updated", this._afterRender);
this._bindKeys();
}
_cfg() {
return this.w.config.chart.measure || {};
}
_enabled() {
return this.w.globals.axisCharts && this._cfg().enabled === true;
}
_mode() {
return this._cfg().mode === "free" ? "free" : "span";
}
/**
* Nearest first-series data point to a data x (used to snap span endpoints
* onto the series line).
* @param {number} dataX
* @returns {{x:number,y:number}|null}
*/
_snapToSeries(dataX) {
const s0 = (
/** @type {any} */
(this.w.config.series || [])[0]
);
if (!s0) return null;
const pts = this._points(s0.data || []);
if (!pts.length) return null;
let best = pts[0];
let bd = Math.abs(pts[0].x - dataX);
for (let i = 1; i < pts.length; i++) {
const d = Math.abs(pts[i].x - dataX);
if (d < bd) {
bd = d;
best = pts[i];
}
}
return best;
}
/**
* Resolve a raw projected point to the endpoint that is actually drawn: span
* mode snaps x to the nearest first-series data point (y follows the line);
* free mode keeps the raw point.
* @param {{x:number,y:number,gx:number,gy:number}} raw
*/
_resolve(raw) {
if (this._mode() === "free") return raw;
const snapped = this._snapToSeries(raw.x);
if (!snapped) return raw;
const g = this._dataToGrid(snapped.x, snapped.y);
return { x: snapped.x, y: snapped.y, gx: g.gx, gy: g.gy };
}
_doc() {
return this.w.dom.baseEl && this.w.dom.baseEl.ownerDocument;
}
/** Bind the measure-key listeners once on the owner document. */
_bindKeys() {
if (this._keysBound) return;
const doc = this._doc();
if (!doc) return;
doc.addEventListener("keydown", this._onKeyDown);
doc.addEventListener("keyup", this._onKeyUp);
this._keysBound = true;
}
/** @param {any} e */
_onKeyDown(e) {
if (!this._enabled() || this.persistent) return;
const key = this._cfg().key || "m";
if (e.key && e.key.toLowerCase() === String(key).toLowerCase()) {
this._arm();
} else if (e.key === "Escape") {
this._cancelDrag();
}
}
/** @param {any} e */
_onKeyUp(e) {
if (this.persistent) return;
const key = this._cfg().key || "m";
if (e.key && e.key.toLowerCase() === String(key).toLowerCase()) {
if (!this.drag) this._disarm();
}
}
/** Public: arm a sticky measure mode (survives key release) until stopped. */
startMeasure() {
if (!this._enabled()) return;
this.persistent = true;
this._arm();
}
/** Public: leave measure mode. */
stopMeasure() {
this.persistent = false;
this._cancelDrag();
this._disarm();
}
/**
* Public: begin a measurement anchored at a client-space point (used by the
* context menu's "Measure from here"). Endpoint A is fixed at (cx,cy), the
* ruler follows the cursor, and the next pointer press sets B and pins it.
* Escape cancels. No-op unless the measure tool is enabled.
* @param {number} cx @param {number} cy
*/
seedFromClient(cx, cy) {
if (!this._enabled()) return;
this._cancelDrag();
this._endSeed(false);
const a = this._project(cx, cy);
this.drag = { a, b: a };
this._seedActive = true;
const doc = this._doc();
if (doc) {
doc.addEventListener("mousemove", this._onMove);
doc.addEventListener("touchmove", this._onMove, { passive: false });
doc.addEventListener("mousedown", this._onSeedDown, true);
doc.addEventListener("touchstart", this._onSeedDown, true);
doc.addEventListener("keydown", this._onSeedKey, true);
}
this._renderLive();
}
/** @param {any} e */
_onSeedDown(e) {
if (!this._seedActive || !this.drag) return;
e.preventDefault();
e.stopPropagation();
const { cx, cy } = this._clientXY(e);
this.drag.b = this._project(cx, cy);
this._endSeed(true);
}
/** @param {any} e */
_onSeedKey(e) {
if (e.key === "Escape") this._endSeed(false);
}
/**
* Finish (commit) or cancel a "measure from here" seed and detach listeners.
* @param {boolean} commit
*/
_endSeed(commit) {
var _a, _b;
if (!this._seedActive) return;
this._seedActive = false;
const doc = this._doc();
if (doc) {
doc.removeEventListener("mousemove", this._onMove);
doc.removeEventListener("touchmove", this._onMove);
doc.removeEventListener("mousedown", this._onSeedDown, true);
doc.removeEventListener("touchstart", this._onSeedDown, true);
doc.removeEventListener("keydown", this._onSeedKey, true);
}
const d = this.drag;
this.drag = null;
this._clearLive();
if (!commit || !d) return;
const a = this._resolve(d.a);
const b = this._resolve(d.b);
if (Math.abs(a.gx - b.gx) < 2 && Math.abs(a.gy - b.gy) < 2) return;
const mode = this._mode();
if (this._cfg().pinOnRelease !== false) {
this.pins.push({ xa: a.x, ya: a.y, xb: b.x, yb: b.y, mode });
this._renderPins();
(_b = (_a = this.ctx.history) == null ? void 0 : _a.snapshot) == null ? void 0 : _b.call(_a, "measure");
}
this._fireMeasured(a, b);
}
/** Public: remove all pinned rulers. */
clearMeasures() {
var _a, _b;
this.pins = [];
this._renderPins();
(_b = (_a = this.ctx.history) == null ? void 0 : _a.snapshot) == null ? void 0 : _b.call(_a, "clear measures");
}
/**
* Snapshot of the pinned rulers as JSON-safe plain data. This is the piece of
* state ViewState / Perspectives (shareable URL) / Rewind (undo) persist:
* pins already live in data space, so they round-trip and re-project.
* Returns a deep copy so callers cannot mutate ours.
* @returns {Array<{xa:number,ya:number,xb:number,yb:number,mode:string}>}
*/
getPins() {
return this.pins.map((p) => ({
xa: p.xa,
ya: p.ya,
xb: p.xb,
yb: p.yb,
mode: p.mode === "free" ? "free" : "span"
}));
}
/**
* Replace the pinned rulers with a restored set and redraw. Accepts the shape
* getPins() returns; a nullish / non-array value (or an old token without
* measure state) clears all pins. Non-finite entries are dropped. Does NOT
* record a Rewind step (the restore itself is the caller's undo boundary).
* @param {any} pins
*/
setPins(pins) {
this.pins = Array.isArray(pins) ? pins.filter(
(p) => p && isFinite(p.xa) && isFinite(p.ya) && isFinite(p.xb) && isFinite(p.yb)
).map((p) => ({
xa: +p.xa,
ya: +p.ya,
xb: +p.xb,
yb: +p.yb,
mode: p.mode === "free" ? "free" : "span"
})) : [];
this._renderPins();
}
/**
* Normalize a series `data` array into {x,y} points (numeric/datetime x or
* category index). Non-finite / non-scalar (range/candle) points drop out.
* @param {any[]} data
* @returns {Array<{x:number,y:number}>}
*/
_points(data) {
const out = [];
for (let i = 0; i < data.length; i++) {
const p = data[i];
let x;
let y;
if (Array.isArray(p)) {
x = +p[0];
y = +p[1];
} else if (p && typeof p === "object") {
x = +p.x;
y = +p.y;
} else {
x = i;
y = +p;
}
if (isFinite(x) && isFinite(y)) out.push({ x, y });
}
return out;
}
/** Lay the transparent capture pane over the plot so our pointer handlers own
* the drag (ZoomPanSelection never sees it). */
_arm() {
if (this.armed || !this._enabled()) return;
const w = this.w;
const parent = w.dom.elGraphical;
if (!parent) return;
this.armed = true;
const pane = this.graphics.drawRect(0, 0, w.layout.gridWidth, w.layout.gridHeight);
pane.node.setAttribute("class", "apexcharts-measure-capture");
pane.node.setAttribute("fill", "transparent");
pane.node.style.cursor = "crosshair";
pane.node.style.pointerEvents = "all";
pane.node.addEventListener("mousedown", this._onDown);
pane.node.addEventListener("touchstart", this._onDown, { passive: false });
parent.add(pane);
this.pane = pane;
}
_disarm() {
this.armed = false;
if (this.pane) {
this.pane.node.removeEventListener("mousedown", this._onDown);
this.pane.node.removeEventListener("touchstart", this._onDown);
const p = this.pane.node;
if (p.parentNode) p.parentNode.removeChild(p);
this.pane = null;
}
}
/** @param {any} e @returns {{cx:number,cy:number}} */
_clientXY(e) {
const t = e.touches && e.touches[0] ? e.touches[0] : e;
return { cx: t.clientX, cy: t.clientY };
}
_gridRect() {
const g = this.w.dom.baseEl.querySelector(".apexcharts-grid");
return g ? g.getBoundingClientRect() : null;
}
/** [min,max] for the primary y-axis, preferring the rendered nice scale. */
_yRange() {
const g = this.w.globals;
const s = g.yAxisScale && g.yAxisScale[0];
if (s && isFinite(s.niceMin) && isFinite(s.niceMax) && s.niceMax !== s.niceMin) {
return [s.niceMin, s.niceMax];
}
return [g.minY, g.maxY];
}
/**
* Client pixel -> { x, y (data), gx, gy (grid-local SVG units) }.
* @param {number} cx @param {number} cy
*/
_project(cx, cy) {
const w = this.w;
const rect = this._gridRect();
const gw = w.layout.gridWidth;
const gh = w.layout.gridHeight;
const clamp = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
const fx = rect ? clamp((cx - rect.left) / rect.width) : 0;
const fy = rect ? clamp((cy - rect.top) / rect.height) : 0;
const [ymin, ymax] = this._yRange();
const x = w.globals.minX + fx * (w.globals.maxX - w.globals.minX);
const y = ymax - fy * (ymax - ymin);
return { x, y, gx: fx * gw, gy: fy * gh };
}
/**
* Data (x,y) -> grid-local SVG coords, for redrawing pinned rulers.
* @param {number} x @param {number} y
*/
_dataToGrid(x, y) {
const w = this.w;
const gw = w.layout.gridWidth;
const gh = w.layout.gridHeight;
const xr = w.globals.maxX - w.globals.minX || 1;
const [ymin, ymax] = this._yRange();
const yr = ymax - ymin || 1;
return {
gx: (x - w.globals.minX) / xr * gw,
gy: gh - (y - ymin) / yr * gh
};
}
/** @param {any} e */
_onDown(e) {
if (!this.armed) return;
e.preventDefault();
e.stopPropagation();
const { cx, cy } = this._clientXY(e);
const a = this._project(cx, cy);
this.drag = { a, b: a };
const doc = this._doc();
if (doc) {
doc.addEventListener("mousemove", this._onMove);
doc.addEventListener("mouseup", this._onUp);
doc.addEventListener("touchmove", this._onMove, { passive: false });
doc.addEventListener("touchend", this._onUp);
}
this._renderLive();
}
/** @param {any} e */
_onMove(e) {
if (!this.drag) return;
if (e.cancelable) e.preventDefault();
const { cx, cy } = this._clientXY(e);
this.drag.b = this._project(cx, cy);
this._renderLive();
}
/** @param {any} _e */
_onUp(_e) {
var _a, _b;
const doc = this._doc();
if (doc) {
doc.removeEventListener("mousemove", this._onMove);
doc.removeEventListener("mouseup", this._onUp);
doc.removeEventListener("touchmove", this._onMove);
doc.removeEventListener("touchend", this._onUp);
}
if (!this.drag) return;
const rawA = this.drag.a;
const rawB = this.drag.b;
this.drag = null;
this._clearLive();
if (Math.abs(rawA.gx - rawB.gx) < 2 && Math.abs(rawA.gy - rawB.gy) < 2) {
if (!this.persistent) this._disarm();
return;
}
const mode = this._mode();
const a = this._resolve(rawA);
const b = this._resolve(rawB);
if (this._cfg().pinOnRelease !== false) {
this.pins.push({ xa: a.x, ya: a.y, xb: b.x, yb: b.y, mode });
this._renderPins();
(_b = (_a = this.ctx.history) == null ? void 0 : _a.snapshot) == null ? void 0 : _b.call(_a, "measure");
}
this._fireMeasured(a, b);
if (!this.persistent) this._disarm();
}
_cancelDrag() {
const doc = this._doc();
if (doc) {
doc.removeEventListener("mousemove", this._onMove);
doc.removeEventListener("mouseup", this._onUp);
doc.removeEventListener("touchmove", this._onMove);
doc.removeEventListener("touchend", this._onUp);
}
this.drag = null;
this._clearLive();
}
/**
* @param {{x:number,y:number}} a @param {{x:number,y:number}} b
* @returns {{dx:number,dy:number,pct:number,slope:number}}
*/
_stats(a, b) {
const dx = b.x - a.x;
const dy = b.y - a.y;
return {
dx,
dy,
pct: a.y !== 0 ? dy / Math.abs(a.y) * 100 : NaN,
slope: dx !== 0 ? dy / dx : NaN
};
}
/** @param {number} v */
_fmt(v) {
if (!isFinite(v)) return "n/a";
const a = Math.abs(v);
if (a !== 0 && (a < 0.01 || a >= 1e6)) return v.toExponential(2);
return String(Math.round(v * 100) / 100);
}
/** getComputedStyle of the graphical layer (browser only), for CSS vars. */
_computedStyle() {
if (!Environment.isBrowser()) return null;
const node = this.w.dom.elGraphical && this.w.dom.elGraphical.node;
if (!node || typeof getComputedStyle !== "function") return null;
try {
return getComputedStyle(node);
} catch (e) {
return null;
}
}
/**
* Resolve a color: explicit config value, else a `--apx-measure-*` CSS custom
* property, else the built-in default.
* @param {any} cfgVal @param {string} varName @param {string} fallback
* @param {CSSStyleDeclaration|null} [cs]
*/
_resolveColor(cfgVal, varName, fallback, cs) {
if (cfgVal) return cfgVal;
const style = cs !== void 0 ? cs : this._computedStyle();
const v = style ? style.getPropertyValue(varName).trim() : "";
return v || fallback;
}
/** Resolved semantic colors (config -> CSS var -> built-in default). */
_colors() {
const c = this._cfg().colors || {};
const cs = this._computedStyle();
return {
up: this._resolveColor(c.up, "--apx-measure-up", "#16a34a", cs),
down: this._resolveColor(c.down, "--apx-measure-down", "#dc2626", cs),
neutral: this._resolveColor(c.neutral, "--apx-measure-neutral", "#64748b", cs),
guide: this._resolveColor(c.guide, "--apx-measure-guide", "#94a3b8", cs)
};
}
/** @param {number} dy */
_dirColor(dy) {
const c = this._colors();
return dy === 0 ? c.neutral : dy > 0 ? c.up : c.down;
}
/** @param {number} dy */
_dirClass(dy) {
return dy === 0 ? "apexcharts-measure-flat" : dy > 0 ? "apexcharts-measure-up" : "apexcharts-measure-down";
}
/**
* Format a percentage, via `measure.format.percent` when set.
* @param {number} p
*/
_fmtPct(p) {
if (!isFinite(p)) return "n/a";
const f = this._cfg().format && this._cfg().format.percent;
if (typeof f === "function") {
try {
const s = f(p);
if (s != null) return String(s);
} catch (e) {
}
}
return (p >= 0 ? "+" : "") + this._fmt(p) + "%";
}
/**
* The readout lines for a ruler: `measure.label` override, else the default
* per-mode text.
* @param {{x:number,y:number}} a @param {{x:number,y:number}} b
* @param {{dx:number,dy:number,pct:number,slope:number}} st @param {string} mode
* @returns {string[]}
*/
_label(a, b, st, mode) {
const fn = this._cfg().label;
if (typeof fn === "function") {
const out = fn({
from: { x: a.x, y: a.y },
to: { x: b.x, y: b.y },
dx: st.dx,
dy: st.dy,
percentChange: st.pct,
slope: st.slope,
mode
});
return Array.isArray(out) ? out.map(String) : [String(out)];
}
if (mode === "span") {
const arrow = st.dy === 0 ? "" : st.dy > 0 ? " ↑" : " ↓";
const pct = isFinite(st.pct) ? "(" + this._fmtPct(st.pct) + ")" : "";
return [this._fmtY(st.dy) + " " + pct + arrow, this._fmtX(a.x) + " to " + this._fmtX(b.x)];
}
return ["Δx " + this._fmtDx(st.dx), "Δy " + this._fmtY(st.dy), this._fmtPct(st.pct)];
}
/**
* Format an x delta, treating datetime x as a day count.
* @param {number} dx
*/
_fmtDx(dx) {
if (this.w.config.xaxis.type === "datetime") {
const days = dx / 864e5;
return Math.round(days * 10) / 10 + "d";
}
return this._fmt(dx);
}
/**
* Format a y value (a delta) via the y-axis label formatter when present.
* @param {number} v
*/
_fmtY(v) {
const cf = this._cfg().format && this._cfg().format.y;
if (typeof cf === "function") {
try {
const s = cf(v);
if (s != null) return String(s);
} catch (e) {
}
}
const f = this.w.globals.yLabelFormatters && this.w.globals.yLabelFormatters[0];
if (typeof f === "function") {
try {
const s = f(v, { seriesIndex: 0, dataPointIndex: -1, w: this.w });
if (s != null && s !== "") return String(s);
} catch (e) {
}
}
return this._fmt(v);
}
/**
* Format an x value: a short date for datetime x, else the x-label formatter
* or a plain number.
* @param {number} x
*/
_fmtX(x) {
const w = this.w;
const cf = this._cfg().format && this._cfg().format.x;
if (typeof cf === "function") {
try {
const s = cf(x);
if (s != null) return String(s);
} catch (e) {
}
}
if (w.config.xaxis.type === "datetime") {
const d = new Date(x);
return d.toLocaleDateString(void 0, {
day: "numeric",
month: "short",
year: "numeric"
});
}
const f = w.globals.xLabelFormatter;
if (typeof f === "function") {
try {
const s = f(x, { w });
if (s != null && s !== "") return String(s);
} catch (e) {
}
}
return this._fmt(x);
}
/** The live overlay group, created lazily inside elGraphical. */
_liveGroup() {
const w = this.w;
if (this._live && this._live.node && this._live.node.parentNode) {
return this._live;
}
const g = this.graphics.group({ class: "apexcharts-measure-live" });
g.node.style.pointerEvents = "none";
if (w.dom.elGraphical) w.dom.elGraphical.add(g);
this._live = g;
return g;
}
_clearLive() {
if (this._live && this._live.node) {
const n = this._live.node;
if (n.parentNode) n.parentNode.removeChild(n);
}
this._live = null;
}
_renderLive() {
if (!this.drag) return;
const g = this._liveGroup();
while (g.node.firstChild) g.node.removeChild(g.node.firstChild);
const a = this._resolve(this.drag.a);
const b = this._resolve(this.drag.b);
this._drawRuler(g, a, b, false, this._mode());
}
/** Redraw all pinned rulers into a fresh group (called after each render). */
_renderPins() {
const w = this.w;
const old = w.dom.baseEl && w.dom.baseEl.querySelector(".apexcharts-measure-pins");
if (old && old.parentNode) old.parentNode.removeChild(old);
if (!this.pins.length || !w.dom.elGraphical) return;
const g = this.graphics.group({ class: "apexcharts-measure-pins" });
g.node.style.pointerEvents = "none";
w.dom.elGraphical.add(g);
this.pins.forEach((p) => {
const a = __spreadProps(__spreadValues({}, this._dataToGrid(p.xa, p.ya)), { x: p.xa, y: p.ya });
const b = __spreadProps(__spreadValues({}, this._dataToGrid(p.xb, p.yb)), { x: p.xb, y: p.yb });
this._drawRuler(g, a, b, true, p.mode || "span");
});
}
/**
* Draw one ruler into `g`, dispatching on style.
* @param {any} g
* @param {{gx:number,gy:number,x:number,y:number}} a
* @param {{gx:number,gy:number,x:number,y:number}} b
* @param {boolean} [pinned]
* @param {string} [mode] 'span' (finance-style band, default) | 'free'
*/
_drawRuler(g, a, b, pinned, mode) {
const st = this._stats(a, b);
const rg = this.graphics.group({
class: "apexcharts-measure-ruler " + this._dirClass(st.dy) + (pinned ? " apexcharts-measure-pinned" : "")
});
if ((mode || this._mode()) === "free") this._drawFree(rg, a, b, st);
else this._drawSpan(rg, a, b, st);
g.add(rg);
}
/** Endpoint dots on the series line (skipped when markers:false).
* @param {any} g @param {{gx:number,gy:number}} p @param {string} color */
_dot(g, p, color) {
if (this._cfg().markers === false) return;
const dot = this.graphics.drawMarker(p.gx, p.gy, {
pSize: 4,
shape: "circle",
pointFillColor: color,
pointFillOpacity: 1,
pointStrokeColor: "#fff",
pointStrokeWidth: 2,
pointStrokeOpacity: 1
});
g.add(dot);
}
/** Draw the readout label box + text into `g` at (bx,by).
* @param {any} g @param {string[]} lines @param {number} bx @param {number} by
* @param {number} boxW @param {number} boxH @param {string} color */
_readout(g, lines, bx, by, boxW, boxH, color) {
const box = this.graphics.drawRect(bx, by, boxW, boxH, 4);
box.node.setAttribute("class", "apexcharts-measure-label-bg");
box.attr({ fill: "#ffffff", "fill-opacity": 0.95, stroke: color, "stroke-width": 1 });
g.add(box);
const label = this.graphics.drawText({
x: bx + 9,
y: by + 15,
text: lines,
textAnchor: "start",
fontSize: "11px",
foreColor: "#1e293b",
cssClass: "apexcharts-measure-label"
});
g.add(label);
}
/**
* Finance-style ruler: vertical guides + shaded band + endpoints on the
* series line + a top readout. Guides/band/markers are individually
* toggleable via config.
* @param {any} g
* @param {{gx:number,gy:number,x:number,y:number}} a
* @param {{gx:number,gy:number,x:number,y:number}} b
* @param {{dx:number,dy:number,pct:number,slope:number}} st
*/
_drawSpan(g, a, b, st) {
const w = this.w;
const cfg = this._cfg();
const gh = w.layout.gridHeight;
const color = this._dirColor(st.dy);
const lx = Math.min(a.gx, b.gx);
const rx = Math.max(a.gx, b.gx);
if (cfg.band !== false) {
const band = this.graphics.drawRect(lx, 0, Math.max(0, rx - lx), gh, 0);
band.node.setAttribute("class", "apexcharts-measure-band");
band.attr({ fill: color, "fill-opacity": 0.09, stroke: "none" });
g.add(band);
}
if (cfg.guides !== false) {
[a, b].forEach((p) => {
const vline = this.graphics.drawLine(p.gx, 0, p.gx, gh, this._colors().guide, 4, 1);
vline.node.setAttribute("class", "apexcharts-measure-vline");
g.add(vline);
});
}
[a, b].forEach((p) => this._dot(g, p, color));
const lines = this._label(a, b, st, "span");
const longest = lines.reduce((m, s) => Math.max(m, s.length), 0);
const boxW = Math.max(148, longest * 6.4);
const boxH = 20 + lines.length * 15;
let bx = (lx + rx) / 2 - boxW / 2;
bx = Math.max(2, Math.min(bx, w.layout.gridWidth - boxW - 2));
this._readout(g, lines, bx, 4, boxW, boxH, color);
}
/**
* Free 2D ruler: a diagonal line between two arbitrary points + a readout.
* @param {any} g
* @param {{gx:number,gy:number,x:number,y:number}} a
* @param {{gx:number,gy:number,x:number,y:number}} b
* @param {{dx:number,dy:number,pct:number,slope:number}} st
*/
_drawFree(g, a, b, st) {
const color = this._dirColor(st.dy);
const line = this.graphics.drawLine(a.gx, a.gy, b.gx, b.gy, color, 0, 2);
line.node.setAttribute("class", "apexcharts-measure-line");
g.add(line);
[a, b].forEach((p) => this._dot(g, p, color));
const lines = this._label(a, b, st, "free");
const longest = lines.reduce((m, s) => Math.max(m, s.length), 0);
const boxW = Math.max(72, longest * 6.2);
const boxH = 16 + lines.length * 15;
let bx = (a.gx + b.gx) / 2 + 8;
let by = (a.gy + b.gy) / 2 - boxH / 2;
bx = Math.max(2, Math.min(bx, this.w.layout.gridWidth - boxW - 2));
by = Math.max(2, Math.min(by, this.w.layout.gridHeight - boxH - 2));
this._readout(g, lines, bx, by, boxW, boxH, color);
}
/**
* @param {{x:number,y:number}} a @param {{x:number,y:number}} b
*/
_fireMeasured(a, b) {
const st = this._stats(a, b);
const payload = {
from: { x: a.x, y: a.y },
to: { x: b.x, y: b.y },
dx: st.dx,
dy: st.dy,
percentChange: st.pct,
slope: st.slope
};
const fn = this.w.config.chart.events.measured;
if (typeof fn === "function") fn(this.ctx, payload);
this.ctx.events.fireEvent("measured", [this.ctx, payload]);
}
/** Re-project the measure pins after each render. */
_afterRender() {
if (!this._enabled()) return;
if (this.w.interact.measureEnabled && !this.persistent) {
this.persistent = true;
}
if (this.pins.length) this._renderPins();
if (this.persistent && !this.drag) {
this._disarm();
this._arm();
}
}
teardown() {
this._cancelDrag();
this._endSeed(false);
this._disarm();
const doc = this._doc();
if (doc && this._keysBound) {
doc.removeEventListener("keydown", this._onKeyDown);
doc.removeEventListener("keyup", this._onKeyUp);
}
this._keysBound = false;
this.pins = [];
}
}
_core__default.registerFeatures({ measure: Measure });
export {
default2 as default
};