apexcharts
Version:
A JavaScript Chart Library
778 lines (777 loc) • 29.1 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 v5.10.6
* (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 Utils = _core.__apex_Utils;
class KeyboardNavigation {
/**
* @param {import('../../types/internal').ChartStateW} w
* @param {import('../../types/internal').ChartContext} ctx
*/
constructor(w, ctx) {
this.w = w;
this.ctx = ctx;
this.seriesIndex = 0;
this.dataPointIndex = 0;
this.active = false;
this._focusedEl = null;
this._hoveredBarEl = null;
this._enlargedScatterMarker = null;
this._onKeyDown = this._onKeyDown.bind(this);
this._onFocus = this._onFocus.bind(this);
this._onBlur = this._onBlur.bind(this);
this._onLegendClick = this._onLegendClick.bind(this);
}
// ─── Public API ───────────────────────────────────────────────────────────
/**
* Called after the chart and tooltip have been fully rendered.
* Attaches event listeners and makes the SVG keyboard-focusable.
*/
init() {
const w = this.w;
const svgEl = w.dom.Paper.node;
if (!svgEl) return;
svgEl.setAttribute("tabindex", "0");
svgEl.addEventListener("focus", this._onFocus);
svgEl.addEventListener("blur", this._onBlur);
svgEl.addEventListener("keydown", this._onKeyDown, { passive: false });
this.ctx.events.addEventListener("legendClick", this._onLegendClick);
}
/**
* Removes all event listeners. Called from chart.destroy().
*/
destroy() {
const w = this.w;
const svgEl = w.dom.Paper && w.dom.Paper.node;
if (!svgEl) return;
svgEl.removeEventListener("focus", this._onFocus);
svgEl.removeEventListener("blur", this._onBlur);
svgEl.removeEventListener("keydown", this._onKeyDown);
this.ctx.events.removeEventListener("legendClick", this._onLegendClick);
}
/**
* Called from Events.js keydown handler. Navigation keys are already handled
* by the direct SVG listener (which can call preventDefault). This entry
* point is intentionally a no-op — Events.js still fires the public keyDown
* callback and fireEvent('keydown') independently.
* @param {Event} _e
*/
handleKey(_e) {
}
// ─── Focus / blur ─────────────────────────────────────────────────────────
_onFocus() {
if (!this._isNavEnabled()) return;
this.active = true;
this._clampCursor();
this._snapToVisibleRange();
this._showCurrentPoint();
}
_onBlur() {
this.active = false;
this._hideFocus();
}
// Called when the user clicks a legend item (collapse/expand a series).
// Hide the keyboard-nav tooltip — the chart is about to re-render and the
// current position may no longer be valid.
_onLegendClick() {
if (!this.active) return;
this.active = false;
this._hideFocus();
}
// ─── Key handler ──────────────────────────────────────────────────────────
/**
* @param {KeyboardEvent} e
*/
_onKeyDown(e) {
if (!this._isNavEnabled() || !this.active) return;
switch (e.key) {
case "ArrowRight":
e.preventDefault();
this._move(0, 1);
break;
case "ArrowLeft":
e.preventDefault();
this._move(0, -1);
break;
case "ArrowUp":
e.preventDefault();
this._move(-1, 0);
break;
case "ArrowDown":
e.preventDefault();
this._move(1, 0);
break;
case "Home":
e.preventDefault();
this.dataPointIndex = 0;
this._skipNullForward();
this._showCurrentPoint();
break;
case "End":
e.preventDefault();
this.dataPointIndex = this._getDataPointCount(this.seriesIndex) - 1;
this._skipNullBackward();
this._showCurrentPoint();
break;
case "Enter":
case " ":
e.preventDefault();
this._fireClick();
break;
case "Escape":
e.preventDefault();
this.active = false;
this._hideFocus();
break;
}
}
// ─── Navigation ───────────────────────────────────────────────────────────
/**
* @param {number} dSeries
* @param {number} dPoint
*/
_move(dSeries, dPoint) {
const w = this.w;
const wrapAround = w.config.chart.accessibility.keyboard.navigation.wrapAround;
if (dSeries !== 0) {
const ttCtx = this.w.globals.tooltip;
if (ttCtx && ttCtx.tConfig && ttCtx.tConfig.shared) {
const j = this.dataPointIndex;
const isActuallyShared = ttCtx.tooltipUtil && ttCtx.tooltipUtil.isXoverlap(j) && ttCtx.tooltipUtil.isInitialSeriesSameLen();
if (isActuallyShared) return;
}
const total = this._getSeriesCount();
let si = this.seriesIndex + dSeries;
let attempts = 0;
while (attempts < total) {
if (si < 0) si = wrapAround ? total - 1 : 0;
if (si >= total) si = wrapAround ? 0 : total - 1;
if (!w.globals.collapsedSeriesIndices.includes(si)) break;
si += dSeries;
attempts++;
}
this.seriesIndex = si;
const dpCount = this._getDataPointCount(si);
if (this.dataPointIndex >= dpCount) {
this.dataPointIndex = dpCount - 1;
}
}
if (dPoint !== 0) {
const dpCount = this._getDataPointCount(this.seriesIndex);
let di = this.dataPointIndex + dPoint;
if (di < 0) di = wrapAround ? dpCount - 1 : 0;
if (di >= dpCount) di = wrapAround ? 0 : dpCount - 1;
this.dataPointIndex = di;
if (dPoint > 0) {
this._skipNullForward();
} else {
this._skipNullBackward();
}
if (!this._isDataPointVisible(this.seriesIndex, this.dataPointIndex)) {
this._snapToVisibleRangeInDirection(dPoint);
}
}
this._showCurrentPoint();
}
/** Advance dataPointIndex forward past any nulls */
_skipNullForward() {
const w = this.w;
const si = this.seriesIndex;
const dpCount = this._getDataPointCount(si);
let di = this.dataPointIndex;
let attempts = 0;
if (!Array.isArray(w.seriesData.series[si])) return;
while (attempts < dpCount && w.seriesData.series[si][di] === null) {
di = (di + 1) % dpCount;
attempts++;
}
this.dataPointIndex = di;
}
/** Retreat dataPointIndex backward past any nulls */
_skipNullBackward() {
const w = this.w;
const si = this.seriesIndex;
const dpCount = this._getDataPointCount(si);
let di = this.dataPointIndex;
let attempts = 0;
if (!Array.isArray(w.seriesData.series[si])) return;
while (attempts < dpCount && w.seriesData.series[si][di] === null) {
di = (di - 1 + dpCount) % dpCount;
attempts++;
}
this.dataPointIndex = di;
}
// ─── Display ──────────────────────────────────────────────────────────────
_showCurrentPoint() {
const { seriesIndex: i, dataPointIndex: j } = this;
const w = this.w;
const ttCtx = w.globals.tooltip;
if (!ttCtx || !ttCtx.ttItems) return;
w.interact.capturedSeriesIndex = i;
w.interact.capturedDataPointIndex = j;
this._applyFocusClass(i, j);
this._showTooltip(
i,
j,
/** @type {any} */
ttCtx
);
}
_hideFocus() {
const w = this.w;
const ttCtx = (
/** @type {any} */
w.globals.tooltip
);
this._removeFocusClass();
this._leaveHoveredBar();
if (!ttCtx) return;
if (ttCtx.marker) {
ttCtx.marker.resetPointsSize();
}
this._enlargedScatterMarker = null;
const tooltipEl = ttCtx.getElTooltip();
if (tooltipEl) {
tooltipEl.classList.remove("apexcharts-active");
if (w.config.chart.accessibility.enabled && w.config.chart.accessibility.announcements.enabled) {
tooltipEl.setAttribute("aria-hidden", "true");
}
}
w.dom.baseEl.classList.remove("apexcharts-tooltip-active");
const xcrosshairs = ttCtx.getElXCrosshairs();
if (xcrosshairs) xcrosshairs.classList.remove("apexcharts-active");
}
// ─── Tooltip display per chart type ───────────────────────────────────────
/**
* @param {number} i
* @param {number} j
* @param {import('../tooltip/Tooltip').default} ttCtx
*/
_showTooltip(i, j, ttCtx) {
const w = this.w;
const type = w.config.chart.type;
const tooltipEl = ttCtx.getElTooltip();
if (!tooltipEl) return;
const cachedDims = ttCtx.getCachedDimensions();
ttCtx.tooltipRect = {
x: 0,
y: 0,
ttWidth: cachedDims.ttWidth || 0,
ttHeight: cachedDims.ttHeight || 0
};
this._setSyntheticEvent(i, j, ttCtx);
w.dom.baseEl.classList.add("apexcharts-tooltip-active");
tooltipEl.classList.add("apexcharts-active");
if (w.config.chart.accessibility.enabled && w.config.chart.accessibility.announcements.enabled) {
tooltipEl.removeAttribute("aria-hidden");
}
if (type === "pie" || type === "donut" || type === "polarArea") {
this._showTooltipNonAxis(i, j, ttCtx, tooltipEl);
} else if (type === "radialBar") {
this._showTooltipRadialBar(i, j, ttCtx, tooltipEl);
} else if (type === "heatmap" || type === "treemap") {
this._showTooltipHeatTree(i, j, ttCtx, tooltipEl, type);
} else if (type === "bar" || type === "candlestick" || type === "boxPlot" || type === "rangeBar") {
this._showTooltipBar(i, j, ttCtx);
} else {
this._showTooltipAxisLine(i, j, ttCtx);
}
}
/**
* Set ttCtx.e to a synthetic mouse-event-like object whose clientX/Y point
* to the centre of the current data-point element. This ensures that any
* positioning helper that reads ttCtx.e (followCursor path in moveTooltip,
* moveStickyTooltipOverBars, moveDynamicPointsOnHover, etc.) gets valid
* coordinates rather than crashing on undefined.
*
* For chart types that don't have a concrete SVG element per data point
* (pie, radialBar) we fall back to the SVG centre.
* @param {number} i
* @param {number} j
* @param {import('../tooltip/Tooltip').default} ttCtx
*/
_setSyntheticEvent(i, j, ttCtx) {
const w = this.w;
const type = w.config.chart.type;
let clientX = 0;
let clientY = 0;
const el = this._getFocusableElement(i, j);
if (el) {
const rect = el.getBoundingClientRect();
clientX = rect.left + rect.width / 2;
clientY = rect.top + rect.height / 2;
} else if (w.globals.pointsArray && w.globals.pointsArray[i] && w.globals.pointsArray[i][j]) {
const pt = w.globals.pointsArray[i][j];
const elGrid = ttCtx.getElGrid && ttCtx.getElGrid();
if (elGrid) {
const gridRect = elGrid.getBoundingClientRect();
clientX = gridRect.left + (pt[0] || 0);
clientY = gridRect.top + (pt[1] || 0);
}
} else {
const svgEl = w.dom.Paper && w.dom.Paper.node;
if (svgEl) {
const svgRect = svgEl.getBoundingClientRect();
clientX = svgRect.left + svgRect.width / 2;
clientY = svgRect.top + svgRect.height / 2;
}
}
if (type === "line" || type === "area" || type === "rangeArea" || type === "scatter" || type === "bubble" || type === "radar") {
if (w.globals.pointsArray && w.globals.pointsArray[i] && w.globals.pointsArray[i][j]) {
const pt = w.globals.pointsArray[i][j];
const elGrid = ttCtx.getElGrid && ttCtx.getElGrid();
if (elGrid) {
const gridRect = elGrid.getBoundingClientRect();
clientX = gridRect.left + (pt[0] || 0);
clientY = gridRect.top + (pt[1] || 0);
}
}
}
ttCtx.e = { type: "mousemove", clientX, clientY };
}
/**
* bar / column / candlestick / boxPlot / rangeBar
* @param {number} i
* @param {number} j
* @param {import('../tooltip/Tooltip').default} ttCtx
*/
_showTooltipBar(i, j, ttCtx) {
var _a, _b, _c, _d;
const w = this.w;
const shared = ttCtx.tConfig.shared && (ttCtx.tooltipUtil.isXoverlap(j) || w.globals.isBarHorizontal) && ttCtx.tooltipUtil.isInitialSeriesSameLen();
const rangeData = (
/** @type {any} */
(_d = (_c = (_b = (_a = w.rangeData.seriesRange) == null ? void 0 : _a[i]) == null ? void 0 : _b[j]) == null ? void 0 : _c.y) == null ? void 0 : _d[0]
);
ttCtx.tooltipLabels.drawSeriesTexts(__spreadProps(__spreadValues(__spreadValues({
ttItems: ttCtx.ttItems,
i,
j
}, (rangeData == null ? void 0 : rangeData.y1) !== void 0 && { y1: rangeData.y1 }), (rangeData == null ? void 0 : rangeData.y2) !== void 0 && { y2: rangeData.y2 }), {
shared
}));
const parent = `.apexcharts-series[data\\:realIndex='${i}']`;
const elPath = w.dom.Paper.findOne(
`${parent} path[j='${j}'], ${parent} circle[j='${j}'], ${parent} rect[j='${j}']`
);
if (elPath) {
this._leaveHoveredBar();
const graphics = new Graphics(this.w, this.ctx);
graphics.pathMouseEnter(elPath, null);
this._hoveredBarEl = elPath;
}
if (w.globals.isBarHorizontal) {
const barDomEl = elPath && elPath.node;
if (barDomEl) {
const wrapRect = w.dom.elWrap.getBoundingClientRect();
const barRect = barDomEl.getBoundingClientRect();
const barCx = barRect.left - wrapRect.left;
const barCy = barRect.top - wrapRect.top;
const bh = barRect.height;
const bw = barRect.width;
const ttWidth = ttCtx.tooltipRect.ttWidth || 0;
const ttHeight = ttCtx.tooltipRect.ttHeight || 0;
const y = barCy + bh / 2 - ttHeight / 2;
let x = barCx + bw;
const baselineX = ttCtx.xyRatios && ttCtx.xyRatios.baseLineInvertedY != null ? ttCtx.xyRatios.baseLineInvertedY : wrapRect.width / 2;
if (barCx < baselineX) {
x = barCx - ttWidth;
}
const tooltipEl = ttCtx.getElTooltip();
if (tooltipEl) {
tooltipEl.style.left = x + "px";
tooltipEl.style.top = y + "px";
}
}
} else {
ttCtx.tooltipPosition.moveStickyTooltipOverBars(j, i);
}
}
/**
* line / area / scatter / bubble / radar / rangeArea
* @param {number} i
* @param {number} j
* @param {import('../tooltip/Tooltip').default} ttCtx
*/
_showTooltipAxisLine(i, j, ttCtx) {
const w = this.w;
const type = w.config.chart.type;
const sharedConfigured = ttCtx.tConfig.shared;
const shared = sharedConfigured && ttCtx.tooltipUtil.isXoverlap(j) && ttCtx.tooltipUtil.isInitialSeriesSameLen();
ttCtx.tooltipLabels.drawSeriesTexts({
ttItems: ttCtx.ttItems,
i,
j,
shared
});
const isScatterLike = type === "scatter" || type === "bubble";
const hasVisibleMarkers = w.globals.markers.largestSize > 0;
if (isScatterLike) {
this._showScatterBubblePoint(i, j, ttCtx);
} else if (hasVisibleMarkers) {
if (shared) {
ttCtx.marker.enlargePoints(j);
} else {
ttCtx.tooltipPosition.moveDynamicPointOnHover(j, i);
}
} else if (shared) {
ttCtx.tooltipPosition.moveDynamicPointsOnHover(j);
} else {
ttCtx.tooltipPosition.moveDynamicPointOnHover(j, i);
}
}
/**
* Scatter / bubble: find the specific marker element for (seriesIndex i,
* dataPointIndex j), resize only that element, and position the tooltip at
* its coordinates — mirroring what Position.moveMarkers does for mouse hover.
*
* Unlike enlargePoints(j) which queries ALL series for rel===j (causing
* multiple bubbles to enlarge and tooltip to land on the wrong one), this
* method queries by both series index AND data-point index for precision.
* @param {number} i
* @param {number} j
* @param {import('../tooltip/Tooltip').default} ttCtx
*/
_showScatterBubblePoint(i, j, ttCtx) {
const baseEl = this.w.dom.baseEl;
if (this._enlargedScatterMarker) {
ttCtx.marker.oldPointSize(this._enlargedScatterMarker);
this._enlargedScatterMarker = null;
}
const seriesEl = baseEl.querySelector(
`.apexcharts-series[data\\:realIndex='${i}']`
);
if (!seriesEl) return;
const markerEl = seriesEl.querySelector(`.apexcharts-marker[rel='${j}']`);
if (!markerEl) return;
ttCtx.marker.enlargeCurrentPoint(j, markerEl);
this._enlargedScatterMarker = markerEl;
}
/**
* pie / donut / polarArea
* @param {number} i
* @param {number} j
* @param {import('../tooltip/Tooltip').default} ttCtx
* @param {HTMLElement} tooltipEl
*/
_showTooltipNonAxis(i, j, ttCtx, tooltipEl) {
var _a, _b;
const w = this.w;
ttCtx.tooltipLabels.drawSeriesTexts({
ttItems: ttCtx.ttItems,
i: j,
shared: false
});
const tooltipBound = tooltipEl.getBoundingClientRect();
const ttWidth = tooltipBound.width || ttCtx.tooltipRect.ttWidth || 0;
const ttHeight = tooltipBound.height || ttCtx.tooltipRect.ttHeight || 0;
const sliceEl = w.dom.baseEl.querySelector(`.apexcharts-pie-area[j='${j}']`);
if (sliceEl) {
const cx = parseFloat((_a = sliceEl.getAttribute("data:cx")) != null ? _a : "");
const cy = parseFloat((_b = sliceEl.getAttribute("data:cy")) != null ? _b : "");
if (!isNaN(cx) && !isNaN(cy)) {
const svgBound = w.dom.Paper.node.getBoundingClientRect();
const wrapBound = w.dom.elWrap.getBoundingClientRect();
const offsetX = svgBound.left - wrapBound.left;
const offsetY = svgBound.top - wrapBound.top;
tooltipEl.style.left = offsetX + cx - ttWidth / 2 + "px";
tooltipEl.style.top = offsetY + cy - ttHeight - 10 + "px";
}
}
}
/**
* radialBar — one ring per series, single value each
* @param {number} i
* @param {any} _j
* @param {import('../tooltip/Tooltip').default} ttCtx
* @param {HTMLElement} tooltipEl
*/
_showTooltipRadialBar(i, _j, ttCtx, tooltipEl) {
var _a;
const w = this.w;
ttCtx.tooltipLabels.drawSeriesTexts({
ttItems: ttCtx.ttItems,
i,
shared: false
});
const { ttWidth = 0, ttHeight = 0 } = ttCtx.getCachedDimensions();
const arcEl = w.dom.baseEl.querySelector(
`.apexcharts-radialbar-series[data\\:realIndex='${i}'] path`
);
if (arcEl) {
const angle = parseFloat((_a = arcEl.getAttribute("data:angle")) != null ? _a : "") || 0;
const initialAngle = w.config.plotOptions.radialBar.startAngle || 0;
const midAngle = initialAngle + angle / 2;
const centerX = w.layout.gridWidth / 2;
const centerY = w.layout.gridHeight / 2;
const radialSize = w.globals.radialSize || Math.min(w.layout.gridWidth, w.layout.gridHeight) / 2;
const seriesCount = w.seriesData.series.length;
const trackSize = radialSize / Math.max(seriesCount, 1);
const outerRadius = radialSize - i * trackSize;
const innerRadius = outerRadius - trackSize;
const ringRadius = (outerRadius + innerRadius) / 2;
const centroid = Utils.polarToCartesian(
centerX,
centerY,
ringRadius,
midAngle
);
const x = centroid.x + (w.layout.translateX || 0);
const y = centroid.y + (w.layout.translateY || 0);
tooltipEl.style.left = x - ttWidth / 2 + "px";
tooltipEl.style.top = y - ttHeight - 10 + "px";
}
}
/**
* heatmap / treemap — position tooltip using element bounding rect
* @param {number} i
* @param {number} j
* @param {import('../tooltip/Tooltip').default} ttCtx
* @param {HTMLElement} tooltipEl
* @param {string} type
*/
_showTooltipHeatTree(i, j, ttCtx, tooltipEl, type) {
var _a, _b;
const w = this.w;
ttCtx.tooltipLabels.drawSeriesTexts({
ttItems: ttCtx.ttItems,
i,
j,
shared: false
});
const tooltipRect = tooltipEl.getBoundingClientRect();
const ttWidth = tooltipRect.width || ttCtx.tooltipRect.ttWidth || 0;
const ttHeight = tooltipRect.height || ttCtx.tooltipRect.ttHeight || 0;
const rectClass = type === "heatmap" ? "apexcharts-heatmap-rect" : "apexcharts-treemap-rect";
const cell = w.dom.baseEl.querySelector(`.${rectClass}[i='${i}'][j='${j}']`);
if (cell) {
const wrapRect = w.dom.elWrap.getBoundingClientRect();
const cellRect = cell.getBoundingClientRect();
const cellCx = cellRect.left - wrapRect.left;
const cellCy = cellRect.top - wrapRect.top;
const cellWidth = cellRect.width;
const cellHeight = cellRect.height;
const cx = parseFloat((_a = cell.getAttribute("cx")) != null ? _a : "");
const cellWidthAttr = parseFloat((_b = cell.getAttribute("width")) != null ? _b : "");
ttCtx.tooltipPosition.moveXCrosshairs(cx + cellWidthAttr / 2);
let x = cellCx + cellWidth + ttWidth / 2;
const y = cellCy + cellHeight / 2 - ttHeight / 2;
if (cellCx + cellWidth > w.layout.gridWidth / 2) {
x = cellCx - ttWidth / 2;
}
tooltipEl.style.left = x + "px";
tooltipEl.style.top = y + "px";
}
}
// ─── Focus class management ───────────────────────────────────────────────
/**
* @param {number} i
* @param {number} j
*/
_applyFocusClass(i, j) {
this._removeFocusClass();
const el = this._getFocusableElement(i, j);
if (el) {
el.classList.add("apexcharts-keyboard-focused");
this._focusedEl = el;
}
}
_removeFocusClass() {
if (this._focusedEl) {
this._focusedEl.classList.remove("apexcharts-keyboard-focused");
this._focusedEl = null;
}
}
_leaveHoveredBar() {
if (this._hoveredBarEl) {
const graphics = new Graphics(this.w, this.ctx);
graphics.pathMouseLeave(this._hoveredBarEl, null);
this._hoveredBarEl = null;
}
}
/**
* @param {number} i
* @param {number} j
*/
_getFocusableElement(i, j) {
const w = this.w;
const type = w.config.chart.type;
const baseEl = w.dom.baseEl;
if (type === "pie" || type === "donut" || type === "polarArea") {
return baseEl.querySelector(`.apexcharts-pie-area[j='${j}']`);
}
if (type === "heatmap") {
return baseEl.querySelector(
`.apexcharts-heatmap-rect[i='${i}'][j='${j}']`
);
}
if (type === "treemap") {
return baseEl.querySelector(
`.apexcharts-treemap-rect[i='${i}'][j='${j}']`
);
}
if (type === "radialBar") {
return baseEl.querySelector(
`.apexcharts-radialbar-series[data\\:realIndex='${i}'] path`
);
}
if (type === "bar" || type === "candlestick" || type === "boxPlot" || type === "rangeBar") {
return baseEl.querySelector(
`.apexcharts-series[data\\:realIndex='${i}'] path[j='${j}']`
);
}
const marker = baseEl.querySelector(
`.apexcharts-series[data\\:realIndex='${i}'] .apexcharts-marker[rel='${j}']`
);
return marker || null;
}
// ─── Click / Enter ────────────────────────────────────────────────────────
_fireClick() {
const w = this.w;
const ttCtx = w.globals.tooltip;
if (!ttCtx) return;
const syntheticEvent = {
type: "mouseup",
clientX: 0,
clientY: 0
};
ttCtx.markerClick(syntheticEvent, this.seriesIndex, this.dataPointIndex);
}
// ─── Helpers ──────────────────────────────────────────────────────────────
_isNavEnabled() {
const a11y = this.w.config.chart.accessibility;
return a11y.enabled && a11y.keyboard.enabled && a11y.keyboard.navigation.enabled;
}
_getSeriesCount() {
const w = this.w;
const type = w.config.chart.type;
if (type === "pie" || type === "donut" || type === "polarArea") {
return 1;
}
return w.seriesData.series.length;
}
/**
* @param {number} si
*/
_getDataPointCount(si) {
const w = this.w;
const type = w.config.chart.type;
if (type === "pie" || type === "donut" || type === "polarArea") {
return w.seriesData.series.length;
}
const series = w.seriesData.series;
return series[si] && Array.isArray(series[si]) ? series[si].length : 0;
}
_clampCursor() {
const seriesCount = this._getSeriesCount();
if (this.seriesIndex >= seriesCount) this.seriesIndex = seriesCount - 1;
if (this.seriesIndex < 0) this.seriesIndex = 0;
const dpCount = this._getDataPointCount(this.seriesIndex);
if (this.dataPointIndex >= dpCount) this.dataPointIndex = dpCount - 1;
if (this.dataPointIndex < 0) this.dataPointIndex = 0;
}
/**
* When the chart is zoomed in, the current dataPointIndex may point to a
* data point that is outside the visible viewport. Snap the cursor to the
* first data point whose x-value falls within [minX, maxX].
*
* Only adjusts when w.seriesData.seriesX is populated (numeric/datetime axes).
* Category-only charts (seriesX entries are strings or auto-indices) are
* unaffected — all points are always visible.
*/
_snapToVisibleRange() {
const w = this.w;
const gl = w.globals;
const si = this.seriesIndex;
if (!w.interact.zoomed) return;
const seriesX = w.seriesData.seriesX && w.seriesData.seriesX[si];
if (!seriesX || !seriesX.length) return;
const minX = gl.minX;
const maxX = gl.maxX;
if (minX === void 0 || maxX === void 0) return;
const currentX = seriesX[this.dataPointIndex];
if (currentX >= minX && currentX <= maxX) return;
const dpCount = seriesX.length;
for (let di = 0; di < dpCount; di++) {
if (seriesX[di] >= minX && seriesX[di] <= maxX) {
this.dataPointIndex = di;
return;
}
}
}
/**
* Snap to the nearest visible data point in the given navigation direction.
* direction > 0 → find the first visible point (left boundary of zoomed range)
* direction < 0 → find the last visible point (right boundary of zoomed range)
* @param {number} direction
*/
_snapToVisibleRangeInDirection(direction) {
const w = this.w;
const gl = w.globals;
const si = this.seriesIndex;
const seriesX = w.seriesData.seriesX && w.seriesData.seriesX[si];
if (!seriesX || !seriesX.length) return;
const minX = gl.minX;
const maxX = gl.maxX;
if (minX === void 0 || maxX === void 0) return;
const dpCount = seriesX.length;
if (direction >= 0) {
for (let di = 0; di < dpCount; di++) {
if (seriesX[di] >= minX && seriesX[di] <= maxX) {
this.dataPointIndex = di;
return;
}
}
} else {
for (let di = dpCount - 1; di >= 0; di--) {
if (seriesX[di] >= minX && seriesX[di] <= maxX) {
this.dataPointIndex = di;
return;
}
}
}
}
/**
* Check whether the data point at (si, di) is within the current visible
* x-axis range. Used to skip out-of-viewport points during keyboard nav.
* @param {number} si
* @param {number} di
*/
_isDataPointVisible(si, di) {
const w = this.w;
const gl = w.globals;
if (!w.interact.zoomed) return true;
const seriesX = w.seriesData.seriesX && w.seriesData.seriesX[si];
if (!seriesX) return true;
const x = seriesX[di];
if (x === void 0) return true;
return x >= gl.minX && x <= gl.maxX;
}
}
_core__default.registerFeatures({ keyboardNavigation: KeyboardNavigation });
export {
default2 as default
};