UNPKG

apexcharts

Version:

A JavaScript Chart Library

1,179 lines 58.3 kB
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.15.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 CoreUtils = _core.__apex_CoreUtils; const Dimensions = _core.__apex_dimensions_Dimensions; const Graphics = _core.__apex_Graphics; const Series = _core.__apex_Series; const Utils = _core.__apex_Utils; const apexchartsLegendCSS = ".apexcharts-flip-y {\n transform: scaleY(-1) translateY(-100%);\n transform-origin: top;\n transform-box: fill-box;\n}\n.apexcharts-flip-x {\n transform: scaleX(-1);\n transform-origin: center;\n transform-box: fill-box;\n}\n.apexcharts-legend {\n display: flex;\n overflow: auto;\n padding: 0 10px;\n}\n.apexcharts-legend.apexcharts-legend-group-horizontal {\n flex-direction: column;\n}\n.apexcharts-legend-group {\n display: flex;\n}\n.apexcharts-legend-group-vertical {\n flex-direction: column-reverse;\n}\n.apexcharts-legend.apx-legend-position-bottom, .apexcharts-legend.apx-legend-position-top {\n flex-wrap: wrap\n}\n.apexcharts-legend.apx-legend-position-right, .apexcharts-legend.apx-legend-position-left {\n flex-direction: column;\n bottom: 0;\n}\n.apexcharts-legend.apx-legend-position-bottom.apexcharts-align-left, .apexcharts-legend.apx-legend-position-top.apexcharts-align-left, .apexcharts-legend.apx-legend-position-right, .apexcharts-legend.apx-legend-position-left {\n justify-content: flex-start;\n align-items: flex-start;\n}\n.apexcharts-legend.apx-legend-position-bottom.apexcharts-align-center, .apexcharts-legend.apx-legend-position-top.apexcharts-align-center {\n justify-content: center;\n align-items: center;\n}\n.apexcharts-legend.apx-legend-position-bottom.apexcharts-align-right, .apexcharts-legend.apx-legend-position-top.apexcharts-align-right {\n justify-content: flex-end;\n align-items: flex-end;\n}\n.apexcharts-legend-series {\n cursor: pointer;\n line-height: normal;\n display: flex;\n align-items: center;\n}\n.apexcharts-legend-text {\n position: relative;\n font-size: 14px;\n}\n.apexcharts-legend-text *, .apexcharts-legend-marker * {\n pointer-events: none;\n}\n.apexcharts-legend-marker {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n margin-right: 1px;\n}\n\n.apexcharts-legend-series.apexcharts-no-click {\n cursor: auto;\n}\n.apexcharts-legend .apexcharts-hidden-zero-series, .apexcharts-legend .apexcharts-hidden-null-series {\n display: none !important;\n}\n.apexcharts-inactive-legend {\n opacity: 0.45;\n} "; const Environment = _core.__apex_Environment_Environment; class Helpers { /** * @param {import('./Legend').default} lgCtx */ constructor(lgCtx) { this.w = lgCtx.w; this.lgCtx = lgCtx; } getLegendStyles() { if (Environment.isSSR()) return null; const stylesheet = document.createElement("style"); stylesheet.setAttribute("type", "text/css"); const nonce = this.w.config.chart.nonce; if (nonce) { stylesheet.setAttribute("nonce", nonce); } const rule = document.createTextNode(apexchartsLegendCSS); stylesheet.appendChild(rule); return stylesheet; } getLegendDimensions() { const w = this.w; const currLegendsWrap = w.dom.baseEl.querySelector(".apexcharts-legend"); if (!currLegendsWrap) { return { clwh: 0, clww: 0 }; } const { width: currLegendsWrapWidth, height: currLegendsWrapHeight } = currLegendsWrap.getBoundingClientRect(); return { clwh: currLegendsWrapHeight, clww: currLegendsWrapWidth }; } appendToForeignObject() { var _a; const legendStyles = this.getLegendStyles(); if (this.w.config.chart.injectStyleSheet !== false && legendStyles) { (_a = this.w.dom.elLegendForeign) == null ? void 0 : _a.appendChild(legendStyles); } } /** * @param {number} seriesCnt * @param {boolean} isHidden */ toggleDataSeries(seriesCnt, isHidden) { var _a, _b; const w = this.w; if (w.globals.axisCharts || w.config.chart.type === "radialBar") { w.globals.resized = true; let seriesEl = null; let realIndex = null; w.globals.risingSeries = []; if (w.globals.axisCharts) { seriesEl = w.dom.baseEl.querySelector( `.apexcharts-series[data\\:realIndex='${seriesCnt}']` ); if (!seriesEl) return; realIndex = parseInt((_a = seriesEl.getAttribute("data:realIndex")) != null ? _a : "", 10); } else { seriesEl = w.dom.baseEl.querySelector( `.apexcharts-series[rel='${seriesCnt + 1}']` ); if (!seriesEl) return; realIndex = parseInt((_b = seriesEl.getAttribute("rel")) != null ? _b : "", 10) - 1; } if (isHidden) { const seriesToMakeVisible = [ { cs: w.globals.collapsedSeries, csi: w.globals.collapsedSeriesIndices }, { cs: w.globals.ancillaryCollapsedSeries, csi: w.globals.ancillaryCollapsedSeriesIndices } ]; seriesToMakeVisible.forEach((r) => { const cs = ( /** @type {any} */ r.cs ); const csi = ( /** @type {any} */ r.csi ); this.riseCollapsedSeries( cs, csi, /** @type {number} */ realIndex ); }); } else { this.hideSeries({ seriesEl, realIndex }); } if (w.config.chart.accessibility.enabled) { const legendItem = w.dom.baseEl.querySelector( `.apexcharts-legend-series[rel="${seriesCnt + 1}"]` ); if (legendItem) { const isCollapsed = w.globals.collapsedSeriesIndices.includes(realIndex) || w.globals.ancillaryCollapsedSeriesIndices.includes(realIndex); legendItem.setAttribute( "aria-pressed", isCollapsed ? "true" : "false" ); const legendTextEl = legendItem.querySelector( ".apexcharts-legend-text" ); const seriesName = legendTextEl ? legendTextEl.textContent : w.seriesData.seriesNames[seriesCnt]; const statusText = isCollapsed ? "hidden" : "visible"; legendItem.setAttribute( "aria-label", `${seriesName}, ${statusText}. Press Enter or Space to toggle.` ); } } } else { const seriesEl = w.dom.Paper.findOne( ` .apexcharts-series[rel='${seriesCnt + 1}'] path` ); const type = w.config.chart.type; if (type === "pie" || type === "polarArea" || type === "donut") { const dataLabels = w.config.plotOptions.pie.donut.labels; const graphics = new Graphics(this.w); graphics.pathMouseDown(seriesEl, null); this.lgCtx.printDataLabelsInner(seriesEl.node, dataLabels); } if (w.config.chart.accessibility.enabled) { const legendItem = w.dom.baseEl.querySelector( `.apexcharts-legend-series[rel="${seriesCnt + 1}"]` ); if (legendItem) { const isCollapsed = w.globals.collapsedSeriesIndices.includes(seriesCnt); legendItem.setAttribute( "aria-pressed", isCollapsed ? "true" : "false" ); const legendTextEl = legendItem.querySelector( ".apexcharts-legend-text" ); const seriesName = legendTextEl ? legendTextEl.textContent : w.seriesData.seriesNames[seriesCnt]; const statusText = isCollapsed ? "hidden" : "visible"; legendItem.setAttribute( "aria-label", `${seriesName}, ${statusText}. Press Enter or Space to toggle.` ); } } } } /** @param {{realIndex: any}} opts */ getSeriesAfterCollapsing({ realIndex }) { var _a; const w = this.w; const gl = w.globals; const series = Utils.clone(w.config.series); if (gl.axisCharts) { const yaxis = w.config.yaxis[gl.seriesYAxisReverseMap[realIndex]]; const collapseData = { index: realIndex, data: series[realIndex].data.slice(), type: series[realIndex].type || w.config.chart.type }; if (yaxis && yaxis.show && yaxis.showAlways) { if (gl.ancillaryCollapsedSeriesIndices.indexOf(realIndex) < 0) { gl.ancillaryCollapsedSeries.push(collapseData); gl.ancillaryCollapsedSeriesIndices.push(realIndex); } } else { if (gl.collapsedSeriesIndices.indexOf(realIndex) < 0) { gl.collapsedSeries.push(collapseData); gl.collapsedSeriesIndices.push(realIndex); const removeIndexOfRising = gl.risingSeries.indexOf(realIndex); gl.risingSeries.splice(removeIndexOfRising, 1); } } } else { gl.collapsedSeries.push({ index: realIndex, data: series[realIndex], type: ( /** @type {any} */ (_a = w.config.series[realIndex].type) != null ? _a : "line" ) }); gl.collapsedSeriesIndices.push(realIndex); } gl.allSeriesCollapsed = gl.collapsedSeries.length + gl.ancillaryCollapsedSeries.length === w.config.series.length; return this._getSeriesBasedOnCollapsedState(series); } /** @param {{seriesEl: any, realIndex: any}} opts */ hideSeries({ seriesEl, realIndex }) { const w = this.w; const series = this.getSeriesAfterCollapsing({ realIndex }); const seriesChildren = seriesEl.childNodes; for (let sc = 0; sc < seriesChildren.length; sc++) { if (seriesChildren[sc].classList.contains("apexcharts-series-markers-wrap")) { if (seriesChildren[sc].classList.contains("apexcharts-hide")) { seriesChildren[sc].classList.remove("apexcharts-hide"); } else { seriesChildren[sc].classList.add("apexcharts-hide"); } } } this.lgCtx.updateSeries( series, w.config.chart.animations.dynamicAnimation.enabled ); } /** * @param {any[]} collapsedSeries * @param {number[]} seriesIndices * @param {number} realIndex */ riseCollapsedSeries(collapsedSeries, seriesIndices, realIndex) { const w = this.w; let series = Utils.clone(w.config.series); if (collapsedSeries.length > 0) { for (let c = 0; c < collapsedSeries.length; c++) { if (collapsedSeries[c].index === realIndex) { if (w.globals.axisCharts) { series[realIndex].data = collapsedSeries[c].data.slice(); } else { series[realIndex] = collapsedSeries[c].data; } if (typeof series[realIndex] !== "number") { series[realIndex].hidden = false; } collapsedSeries.splice(c, 1); seriesIndices.splice(c, 1); w.globals.risingSeries.push(realIndex); c--; } } series = this._getSeriesBasedOnCollapsedState(series); this.lgCtx.updateSeries( series, w.config.chart.animations.dynamicAnimation.enabled ); } } /** * @param {any[]} series */ _getSeriesBasedOnCollapsedState(series) { const w = this.w; let collapsed = 0; if (w.globals.axisCharts) { series.forEach((s, sI) => { if (!(w.globals.collapsedSeriesIndices.indexOf(sI) < 0 && w.globals.ancillaryCollapsedSeriesIndices.indexOf(sI) < 0)) { series[sI].data = []; collapsed++; } }); } else { series.forEach((s, sI) => { if (!(w.globals.collapsedSeriesIndices.indexOf(sI) < 0)) { series[sI] = 0; collapsed++; } }); } w.globals.allSeriesCollapsed = collapsed === series.length; return series; } } const BrowserAPIs = _core.__apex_BrowserAPIs_BrowserAPIs; const SVG_NS = "http://www.w3.org/2000/svg"; class HeatmapGradientLegend { /** * @param {import('../../types/internal').ChartStateW} w * @param {import('../../types/internal').ChartContext} ctx */ constructor(w, ctx) { this.w = w; this.ctx = ctx; this.svgEl = null; this.arrowEl = null; this.hoverValueEl = null; this._min = 0; this._max = 0; this._geom = null; this._bandHitEls = []; this._activeBandIndex = -1; this._onCellEnter = this._onCellEnter.bind(this); this._onCellLeave = this._onCellLeave.bind(this); this._onBandEnter = this._onBandEnter.bind(this); this._onBandLeave = this._onBandLeave.bind(this); } /** Default value formatter for min/max labels and the hover tooltip. */ _getFormatter() { const cfg = this.w.config.plotOptions.heatmap.colorScale.gradientLegend; if (typeof cfg.formatter === "function") return cfg.formatter; return (v) => { if (!Number.isFinite(v)) return String(v); const abs = Math.abs(v); if (abs >= 1e3) return v.toFixed(0); if (abs >= 10) return v.toFixed(1); return v.toFixed(2); }; } /** * True when the user has opted into the gradient legend variant. * @param {any} w */ static isEnabled(w) { var _a, _b, _c, _d; const cfg = (_d = (_c = (_b = (_a = w == null ? void 0 : w.config) == null ? void 0 : _a.plotOptions) == null ? void 0 : _b.heatmap) == null ? void 0 : _c.colorScale) == null ? void 0 : _d.gradientLegend; return !!(cfg && cfg.enabled); } /** * Build the gradient legend DOM into `elLegendWrap`. * Caller is responsible for clearing the wrap first. */ draw() { var _a, _b, _c, _d, _e, _f, _g, _h; const w = this.w; const elLegendWrap = ( /** @type {HTMLElement} */ w.dom.elLegendWrap ); if (!elLegendWrap) return; const cfg = w.config.plotOptions.heatmap.colorScale.gradientLegend; const position = w.config.legend.position; const isVertical = position === "left" || position === "right"; const arrowSize = (_b = (_a = cfg.arrow) == null ? void 0 : _a.size) != null ? _b : 8; const arrowGutter = arrowSize + 4; const labelPadAlongStrip = cfg.showLabels ? 28 : 4; const labelPadAcrossStrip = cfg.showLabels ? 20 : 4; const minLabelWidth = cfg.showLabels ? 44 : 0; const stripLength = this._resolveStripLength(isVertical ? cfg.height : cfg.width, isVertical); const stripThickness = cfg.thickness; const svgWidth = isVertical ? Math.max(stripThickness + arrowGutter + 4, minLabelWidth) : stripLength + labelPadAlongStrip * 2; const svgHeight = isVertical ? stripLength + labelPadAcrossStrip * 2 : stripThickness + arrowGutter + 4; const verticalGroupWidth = stripThickness + arrowGutter; const verticalGroupLeftPad = (svgWidth - verticalGroupWidth) / 2; const stripX = isVertical ? position === "left" ? verticalGroupLeftPad : verticalGroupLeftPad + arrowGutter : labelPadAlongStrip; const stripY = isVertical ? labelPadAcrossStrip : position === "top" ? arrowGutter : 4; const svg = BrowserAPIs.createElementNS(SVG_NS, "svg"); svg.setAttribute("class", "apexcharts-heatmap-gradient-legend"); svg.setAttribute("width", String(svgWidth)); svg.setAttribute("height", String(svgHeight)); svg.setAttribute("overflow", "visible"); const defs = BrowserAPIs.createElementNS(SVG_NS, "defs"); const gradId = `apexcharts-heatmap-gradient-${w.globals.cuid}`; const linearGrad = BrowserAPIs.createElementNS(SVG_NS, "linearGradient"); linearGrad.setAttribute("id", gradId); if (isVertical) { linearGrad.setAttribute("x1", "0"); linearGrad.setAttribute("y1", "1"); linearGrad.setAttribute("x2", "0"); linearGrad.setAttribute("y2", "0"); } else { linearGrad.setAttribute("x1", "0"); linearGrad.setAttribute("y1", "0"); linearGrad.setAttribute("x2", "1"); linearGrad.setAttribute("y2", "0"); } const { min, max, stops, bands } = this._computeStops(); this._min = min; this._max = max; stops.forEach((s) => { const stopEl = BrowserAPIs.createElementNS(SVG_NS, "stop"); stopEl.setAttribute("offset", `${(s.percent * 100).toFixed(2)}%`); stopEl.setAttribute("stop-color", s.color); linearGrad.appendChild(stopEl); }); defs.appendChild(linearGrad); svg.appendChild(defs); const rect = BrowserAPIs.createElementNS(SVG_NS, "rect"); rect.setAttribute("x", String(stripX)); rect.setAttribute("y", String(stripY)); rect.setAttribute("width", String(isVertical ? stripThickness : stripLength)); rect.setAttribute("height", String(isVertical ? stripLength : stripThickness)); rect.setAttribute("rx", "2"); rect.setAttribute("fill", `url(#${gradId})`); svg.appendChild(rect); if (cfg.showLabels) { const labelColor = ((_c = cfg.labelStyle) == null ? void 0 : _c.colors) || (Array.isArray(w.config.legend.labels.colors) ? w.config.legend.labels.colors[0] : w.config.legend.labels.colors) || w.config.chart.foreColor; const labelFontSize = ((_d = cfg.labelStyle) == null ? void 0 : _d.fontSize) || "11px"; const labelFontFamily = ((_e = cfg.labelStyle) == null ? void 0 : _e.fontFamily) || w.config.chart.fontFamily; const fmt = this._getFormatter(); const makeLabel = (text, x, y, anchor) => { const t = BrowserAPIs.createElementNS(SVG_NS, "text"); t.setAttribute("x", String(x)); t.setAttribute("y", String(y)); t.setAttribute("text-anchor", anchor); t.setAttribute("dominant-baseline", "middle"); t.setAttribute("fill", labelColor); t.setAttribute("font-size", labelFontSize); if (labelFontFamily) t.setAttribute("font-family", labelFontFamily); t.textContent = String(text); return t; }; if (isVertical) { const midX = stripX + stripThickness / 2; svg.appendChild(makeLabel(fmt(min), midX, stripY + stripLength + 10, "middle")); svg.appendChild(makeLabel(fmt(max), midX, stripY - 10, "middle")); } else { const midY = stripY + stripThickness / 2; svg.appendChild(makeLabel(fmt(min), stripX - 6, midY, "end")); svg.appendChild(makeLabel(fmt(max), stripX + stripLength + 6, midY, "start")); } } const arrowColor = ((_f = cfg.arrow) == null ? void 0 : _f.color) || w.config.chart.foreColor; const arrow = this._buildArrow(arrowSize, arrowColor, position); svg.appendChild(arrow); this.arrowEl = arrow; this._bandHitEls = []; if (w.config.legend.onItemHover.highlightDataSeries && bands.length > 0) { bands.forEach((b) => { const hit = BrowserAPIs.createElementNS(SVG_NS, "rect"); if (isVertical) { const yTop = stripY + stripLength - b.p2 * stripLength; const yBot = stripY + stripLength - b.p1 * stripLength; hit.setAttribute("x", String(stripX)); hit.setAttribute("y", String(yTop)); hit.setAttribute("width", String(stripThickness)); hit.setAttribute("height", String(Math.max(0, yBot - yTop))); } else { hit.setAttribute("x", String(stripX + b.p1 * stripLength)); hit.setAttribute("y", String(stripY)); hit.setAttribute( "width", String(Math.max(0, (b.p2 - b.p1) * stripLength)) ); hit.setAttribute("height", String(stripThickness)); } hit.setAttribute("fill", "transparent"); hit.setAttribute("class", "apexcharts-heatmap-gradient-band"); hit.setAttribute("data:range-index", String(b.index)); hit.style.cursor = "pointer"; svg.appendChild(hit); this._bandHitEls.push(hit); }); } this._geom = { isVertical, position, stripX, stripY, stripLength, stripThickness, arrowSize, svgWidth, svgHeight }; if (cfg.showHoverValue) { const tt = BrowserAPIs.createElement("div"); tt.classList.add("apexcharts-heatmap-gradient-legend-value"); tt.style.position = "absolute"; tt.style.fontSize = ((_g = cfg.labelStyle) == null ? void 0 : _g.fontSize) || "11px"; tt.style.fontFamily = ((_h = cfg.labelStyle) == null ? void 0 : _h.fontFamily) || w.config.chart.fontFamily || ""; tt.style.color = w.config.chart.foreColor; tt.style.background = "rgba(0,0,0,0.65)"; tt.style.color = "#fff"; tt.style.padding = "2px 6px"; tt.style.borderRadius = "3px"; tt.style.pointerEvents = "none"; tt.style.whiteSpace = "nowrap"; tt.style.opacity = "0"; tt.style.transition = "opacity 120ms ease"; this.hoverValueEl = tt; } elLegendWrap.classList.add("apexcharts-heatmap-gradient-legend-wrap"); elLegendWrap.classList.add( "apx-legend-position-" + position ); elLegendWrap.appendChild(svg); if (this.hoverValueEl) elLegendWrap.appendChild(this.hoverValueEl); this.svgEl = svg; this._applyWrapAlignment(elLegendWrap, position, isVertical, svgWidth, svgHeight); this._attachHoverListeners(); this._attachBandHoverListeners(); } /** * Resolve a configured length (number = px, string ending in '%' = * percentage of the chart's SVG width/height) to a pixel length. * @param {number|string} value * @param {boolean} isVertical * @returns {number} */ _resolveStripLength(value, isVertical) { const w = this.w; const basis = isVertical ? w.globals.svgHeight || w.config.chart.height || 300 : w.globals.svgWidth || w.config.chart.width || 600; if (typeof value === "string") { const trimmed = value.trim(); if (trimmed.endsWith("%")) { const pct = parseFloat(trimmed) || 0; return Math.max(20, basis * pct / 100); } const n = parseFloat(trimmed); return Number.isFinite(n) ? n : 200; } if (typeof value === "number" && Number.isFinite(value)) return value; return 200; } /** * Position the legend wrap and align the gradient strip within it. The * wrap spans the chart's long axis (full width for top/bottom; full * height for left/right) and uses flexbox to honor the `align` config. * Bypasses the standard `setLegendWrapXY` which sizes the wrap to its * content. * @param {HTMLElement} elLegendWrap * @param {'top'|'right'|'bottom'|'left'} position * @param {boolean} isVertical * @param {number} svgWidth * @param {number} svgHeight */ _applyWrapAlignment(elLegendWrap, position, isVertical, svgWidth, svgHeight) { const w = this.w; const cfg = w.config.plotOptions.heatmap.colorScale.gradientLegend; const align = cfg.align || "center"; const edgePad = 12; const chartWidth = w.globals.svgWidth || w.config.chart.width || 600; const chartHeight = w.globals.svgHeight || w.config.chart.height || 300; const userOffsetX = w.config.legend.offsetX || 0; const userOffsetY = w.config.legend.offsetY || 0; elLegendWrap.style.position = "absolute"; elLegendWrap.style.display = "block"; elLegendWrap.style.overflow = "visible"; elLegendWrap.style.padding = "0"; elLegendWrap.style.width = svgWidth + "px"; elLegendWrap.style.height = svgHeight + "px"; elLegendWrap.style.right = "auto"; elLegendWrap.style.bottom = "auto"; if (isVertical) { const availableHeight = chartHeight - svgHeight - edgePad * 2; let y; if (align === "start") y = edgePad; else if (align === "end") y = edgePad + Math.max(0, availableHeight); else y = edgePad + Math.max(0, availableHeight) / 2; elLegendWrap.style.top = y + userOffsetY + "px"; if (position === "left") { elLegendWrap.style.left = edgePad + userOffsetX + "px"; } else { elLegendWrap.style.left = chartWidth - svgWidth - edgePad + userOffsetX + "px"; } } else { const availableWidth = chartWidth - svgWidth - edgePad * 2; let x; if (align === "start") x = edgePad; else if (align === "end") x = edgePad + Math.max(0, availableWidth); else x = edgePad + Math.max(0, availableWidth) / 2; elLegendWrap.style.left = x + userOffsetX + "px"; if (position === "top") { elLegendWrap.style.top = edgePad + userOffsetY + "px"; } else { elLegendWrap.style.top = chartHeight - svgHeight - edgePad + userOffsetY + "px"; } } } /** * Re-position the strip once the final layout is known. * * `_applyWrapAlignment` (called during `draw()`, before `plotCoords()`) can * only pin to the chart's outer edge. This runs after layout — when * `translateX/Y`, `gridWidth/Height` and `xAxisHeight` are populated — and: * - centers the strip within its reserved band on the perpendicular axis * (between the title and the plot for `top`; the x-axis and the chart * bottom for `bottom`; the chart edge and the plot for `left`/`right`), * so the slack is split evenly instead of dumped on one side, and * - aligns it along the plot's own extent (so `align: 'center'` centers * over the heatmap, not the whole canvas). * Honors `legend.offsetX/offsetY` for user nudging. Safe to call repeatedly. */ repositionToPlot() { var _a, _b; if (!Environment.isBrowser()) return; const w = this.w; const g = w.globals; const wrap = ( /** @type {HTMLElement} */ w.dom.elLegendWrap ); if (!wrap || !this._geom) return; if (!Number.isFinite(g.gridWidth) || !Number.isFinite(g.gridHeight)) return; const { isVertical, position, svgWidth, svgHeight, stripX, stripY, stripThickness } = this._geom; const align = w.config.plotOptions.heatmap.colorScale.gradientLegend.align || "center"; const ox = w.config.legend.offsetX || 0; const oy = w.config.legend.offsetY || 0; const dimHelpers = (_b = (_a = this.ctx) == null ? void 0 : _a.dimensions) == null ? void 0 : _b.dimHelpers; const titleArea = dimHelpers ? dimHelpers.getTitleSubtitleCoords("title").height + dimHelpers.getTitleSubtitleCoords("subtitle").height : 0; const xAxisArea = w.layout.xAxisHeight || 0; const alongOffset = (extent, size) => { const avail = Math.max(0, extent - size); if (align === "start") return 0; if (align === "end") return avail; return avail / 2; }; if (isVertical) { wrap.style.top = g.translateY + alongOffset(g.gridHeight, svgHeight) + oy + "px"; const bandStart = position === "left" ? 0 : g.translateX + g.gridWidth; const bandEnd = position === "left" ? g.translateX : g.svgWidth; const stripCenter = (bandStart + bandEnd) / 2; wrap.style.left = stripCenter - stripX - stripThickness / 2 + ox + "px"; } else { wrap.style.left = g.translateX + alongOffset(g.gridWidth, svgWidth) + ox + "px"; const bandStart = position === "top" ? titleArea : g.translateY + g.gridHeight + xAxisArea; const bandEnd = position === "top" ? g.translateY : g.svgHeight; const stripCenter = (bandStart + bandEnd) / 2; wrap.style.top = stripCenter - stripY - stripThickness / 2 + oy + "px"; } BrowserAPIs.requestAnimationFrame(() => this._enforceMinPlotGap()); } /** * Guarantee a minimum gap between the strip's chart-facing edge and the plot. * Measured in viewport space (immune to the wrap↔SVG coordinate offset) and * applied as a *relative* shift to the wrap's current position, so it only * nudges a strip that ended up too close — placements with ample room are * left exactly where centering put them. Runs post-paint (see caller). */ _enforceMinPlotGap() { const w = this.w; const wrap = ( /** @type {HTMLElement} */ w.dom.elLegendWrap ); const strip = this.svgEl && this.svgEl.querySelector("rect"); const grid = w.dom.baseEl.querySelector(".apexcharts-grid"); if (!wrap || !strip || !grid || !this._geom) return; const s = strip.getBoundingClientRect(); const gr = grid.getBoundingClientRect(); if (!s.width || !s.height || !gr.width || !gr.height) return; const MIN_GAP = 16; const { isVertical, position } = this._geom; if (isVertical) { const gap = position === "left" ? gr.left - s.right : s.left - gr.right; if (gap < MIN_GAP) { const curLeft = parseFloat(wrap.style.left) || 0; const shift = MIN_GAP - gap; wrap.style.left = curLeft + (position === "left" ? -shift : shift) + "px"; } } else { const gap = position === "top" ? gr.top - s.bottom : s.top - gr.bottom; if (gap < MIN_GAP) { const curTop = parseFloat(wrap.style.top) || 0; const shift = MIN_GAP - gap; wrap.style.top = curTop + (position === "top" ? -shift : shift) + "px"; } } } /** * Tear down listeners (called before re-render). */ destroy() { var _a, _b, _c, _d, _e, _f, _g; for (let i = 0; i < this._bandHitEls.length; i++) { const el = this._bandHitEls[i]; (_a = el.removeEventListener) == null ? void 0 : _a.call(el, "mousemove", this._onBandEnter); (_b = el.removeEventListener) == null ? void 0 : _b.call(el, "mouseout", this._onBandLeave); } this._bandHitEls = []; this._activeBandIndex = -1; if (!((_c = this.ctx) == null ? void 0 : _c.events)) return; try { (_e = (_d = this.ctx.events).removeEventListener) == null ? void 0 : _e.call( _d, "dataPointMouseEnter", this._onCellEnter ); (_g = (_f = this.ctx.events).removeEventListener) == null ? void 0 : _g.call( _f, "dataPointMouseLeave", this._onCellLeave ); } catch (_) { } } /** Wire mousemove/mouseout on each per-band hit-region (ranges mode). */ _attachBandHoverListeners() { if (!Environment.isBrowser()) return; for (let i = 0; i < this._bandHitEls.length; i++) { const el = this._bandHitEls[i]; el.addEventListener("mousemove", this._onBandEnter); el.addEventListener("mouseout", this._onBandLeave); } } /** * Hovering a gradient band highlights its cells and dims the rest. Guarded * so the repeated mousemove stream only re-applies on an actual band change. * @param {Event} e */ _onBandEnter(e) { var _a, _b, _c, _d; const w = this.w; const target = ( /** @type {Element} */ e.currentTarget ); const idx = parseInt((_a = target.getAttribute("data:range-index")) != null ? _a : "-1", 10); if (idx < 0 || idx === this._activeBandIndex) return; this._activeBandIndex = idx; (_d = (_c = (_b = this.ctx) == null ? void 0 : _b.events) == null ? void 0 : _c.fireEvent) == null ? void 0 : _d.call(_c, "legendHover", [this.ctx, idx, w]); new Series(w).highlightRangeInSeries(idx, "highlight"); } /** Leaving a band clears the highlight. */ _onBandLeave() { if (this._activeBandIndex < 0) return; const idx = this._activeBandIndex; this._activeBandIndex = -1; new Series(this.w).highlightRangeInSeries(idx, "reset"); } _attachHoverListeners() { var _a, _b; if (!Environment.isBrowser()) return; if (!((_b = (_a = this.ctx) == null ? void 0 : _a.events) == null ? void 0 : _b.addEventListener)) return; this.ctx.events.addEventListener( "dataPointMouseEnter", this._onCellEnter ); this.ctx.events.addEventListener( "dataPointMouseLeave", this._onCellLeave ); } /** * dataPointMouseEnter fires as `(e, ctx, { seriesIndex, dataPointIndex, w })`. * Graphics._fireEvent forwards listener args in the same shape. * @param {...any} args */ _onCellEnter(...args) { var _a, _b; const w = this.w; if (!this.arrowEl) return; const opts = args[args.length - 1]; if (!opts || typeof opts !== "object") return; const i = opts.seriesIndex; const j = opts.dataPointIndex; if (typeof i !== "number" || typeof j !== "number") return; if (w.config.chart.type !== "heatmap") return; const row = (_b = (_a = w.seriesData) == null ? void 0 : _a.series) == null ? void 0 : _b[i]; const val = row == null ? void 0 : row[j]; if (val == null || Number.isNaN(val)) return; this._positionArrow(val); } _onCellLeave() { if (!this.arrowEl) return; this.arrowEl.setAttribute("opacity", "0"); if (this.hoverValueEl) { this.hoverValueEl.style.opacity = "0"; } } /** * Move the arrow to the position corresponding to `val` along the strip. * @param {number} val */ _positionArrow(val) { if (!this.arrowEl || !this._geom) return; const { isVertical, position, stripX, stripY, stripLength, stripThickness, arrowSize } = this._geom; const min = this._min; const max = this._max; const span = max - min; let pct; if (span === 0) { pct = 0.5; } else { pct = (val - min) / span; } if (pct < 0) pct = 0; if (pct > 1) pct = 1; if (isVertical) { const yCenter = stripY + stripLength - pct * stripLength; let tipX, baseX; if (position === "left") { tipX = stripX + stripThickness; baseX = tipX + arrowSize; } else { tipX = stripX; baseX = tipX - arrowSize; } const points = [ `${tipX},${yCenter}`, `${baseX},${yCenter - arrowSize / 2}`, `${baseX},${yCenter + arrowSize / 2}` ].join(" "); this.arrowEl.setAttribute("points", points); } else { const xCenter = stripX + pct * stripLength; let tipY, baseY; if (position === "top") { tipY = stripY + stripThickness; baseY = tipY + arrowSize; } else { tipY = stripY; baseY = tipY - arrowSize; } const points = [ `${xCenter},${tipY}`, `${xCenter - arrowSize / 2},${baseY}`, `${xCenter + arrowSize / 2},${baseY}` ].join(" "); this.arrowEl.setAttribute("points", points); } this.arrowEl.setAttribute("opacity", "1"); if (this.hoverValueEl) { const fmt = this._getFormatter(); this.hoverValueEl.textContent = fmt(val); if (isVertical) { const yCenter = stripY + stripLength - pct * stripLength; if (position === "left") { this.hoverValueEl.style.left = `${stripX + stripThickness + arrowSize + 8}px`; } else { this.hoverValueEl.style.left = `${stripX - arrowSize - 8}px`; this.hoverValueEl.style.transform = "translateX(-100%)"; } this.hoverValueEl.style.top = `${yCenter - 9}px`; } else { const xCenter = stripX + pct * stripLength; this.hoverValueEl.style.left = `${xCenter}px`; this.hoverValueEl.style.transform = "translateX(-50%)"; if (position === "top") { this.hoverValueEl.style.top = `${stripY + stripThickness + arrowSize + 8}px`; } else { this.hoverValueEl.style.top = `${stripY - arrowSize - 18}px`; } } this.hoverValueEl.style.opacity = "1"; } } /** * @param {number} size * @param {string} color * @param {'top'|'right'|'bottom'|'left'} _position */ _buildArrow(size, color, _position) { const polygon = BrowserAPIs.createElementNS(SVG_NS, "polygon"); polygon.setAttribute("fill", color); polygon.setAttribute("opacity", "0"); polygon.setAttribute("class", "apexcharts-heatmap-gradient-arrow"); polygon.setAttribute("points", "0,0 0,0 0,0"); polygon.setAttribute("pointer-events", "none"); return polygon; } /** * Build gradient stops + return effective min/max. * - If `colorScale.ranges` is set, stops are placed at each range boundary * so the gradient reflects the user's discrete palette. * - Otherwise, samples N stops from the same shadeColor function the cells * use, so the strip visually matches the heatmap. * @returns {{ min: number, max: number, stops: Array<{percent:number,color:string}>, bands: Array<{index:number,p1:number,p2:number}> }} */ _computeStops() { var _a, _b; const w = this.w; const cs = w.config.plotOptions.heatmap.colorScale; const cfg = cs.gradientLegend; let dataMin = Infinity; let dataMax = -Infinity; const rows = ((_a = w.seriesData) == null ? void 0 : _a.series) || []; for (let i = 0; i < rows.length; i++) { const row = rows[i]; if (!row) continue; for (let j = 0; j < row.length; j++) { const v = row[j]; if (v == null || Number.isNaN(v)) continue; if (v < dataMin) dataMin = v; if (v > dataMax) dataMax = v; } } if (!Number.isFinite(dataMin)) dataMin = 0; if (!Number.isFinite(dataMax)) dataMax = 0; let min = dataMin; let max = dataMax; if (typeof cs.min !== "undefined") { min = cs.min < dataMin ? cs.min : dataMin; } if (typeof cs.max !== "undefined") { max = cs.max > dataMax ? cs.max : dataMax; } const stops = []; const bands = []; if (cs.ranges && cs.ranges.length > 0) { const ranges = cs.ranges.map((r, originalIndex) => __spreadProps(__spreadValues({}, r), { _originalIndex: originalIndex })).sort((a, b) => a.from - b.from); const lo = ranges[0].from; const hi = ranges[ranges.length - 1].to; min = lo; max = hi; const span = hi - lo || 1; ranges.forEach((r) => { const p1 = (r.from - lo) / span; const p2 = (r.to - lo) / span; stops.push({ percent: (p1 + p2) / 2, color: r.color }); bands.push({ index: r._originalIndex, p1, p2 }); }); } else { const baseColor = w.globals.colors[0] || "#008FFB"; const utils = new Utils(); const shadeIntensity = (_b = w.config.plotOptions.heatmap.shadeIntensity) != null ? _b : 0.5; const hasNegs = ( /** @type {any} */ w.globals.hasNegs ); const n = Math.max(2, cfg.stops || 16); for (let s = 0; s < n; s++) { const t = s / (n - 1); const v = min + t * (max - min); const total = Math.abs(max) + Math.abs(min); const percent_v = total === 0 ? 0 : 100 * v / total; let colorShadePercent; if (hasNegs) { if (w.config.plotOptions.heatmap.reverseNegativeShade) { colorShadePercent = percent_v < 0 ? percent_v / 100 * (shadeIntensity * 1.25) : (1 - percent_v / 100) * (shadeIntensity * 1.25); } else { colorShadePercent = percent_v <= 0 ? 1 - (1 + percent_v / 100) * shadeIntensity : (1 - percent_v / 100) * shadeIntensity; } } else { colorShadePercent = 1 - percent_v / 100; } if (colorShadePercent > 1) colorShadePercent = 1; if (colorShadePercent < -1) colorShadePercent = -1; const shaded = w.config.plotOptions.heatmap.enableShades ? utils.shadeColor( w.config.theme.mode === "dark" ? colorShadePercent * -1 : colorShadePercent, baseColor ) : baseColor; stops.push({ percent: t, color: shaded }); } } return { min, max, stops, bands }; } } const Markers = _core.__apex_Markers; class Legend { /** * @param {import('../../types/internal').ChartStateW} w * @param {import('../../types/internal').ChartContext} ctx */ constructor(w, ctx) { this.w = w; this.ctx = ctx; this.printDataLabelsInner = (...a) => { var _a; return (_a = ctx.pie) == null ? void 0 : _a.printDataLabelsInner(...a); }; this.updateSeries = (...a) => ctx.updateHelpers._updateSeries(...a); this.onLegendClick = this.onLegendClick.bind(this); this.onLegendHovered = this.onLegendHovered.bind(this); this.isBarsDistributed = this.w.config.chart.type === "bar" && this.w.config.plotOptions.bar.distributed && this.w.config.series.length === 1; this.legendHelpers = new Helpers(this); } init() { const w = this.w; const gl = w.globals; const cnf = w.config; const showLegendAlways = cnf.legend.showForSingleSeries && this.w.seriesData.series.length === 1 || this.isBarsDistributed || // Heatmap legends are colorScale-driven (discrete ranges or the // gradient strip), not series-driven, so they must render even for a // single-row heatmap. cnf.chart.type === "heatmap" || this.w.seriesData.series.length > 1; this.legendHelpers.appendToForeignObject(); if ((showLegendAlways || !gl.axisCharts) && cnf.legend.show) { const elLegendWrap = ( /** @type {HTMLElement} */ w.dom.elLegendWrap ); while (elLegendWrap.firstChild) { elLegendWrap.removeChild(elLegendWrap.firstChild); } if (this.heatmapGradientLegend) { this.heatmapGradientLegend.destroy(); this.heatmapGradientLegend = null; } if (cnf.chart.type === "heatmap" && HeatmapGradientLegend.isEnabled(w)) { this.heatmapGradientLegend = new HeatmapGradientLegend(w, this.ctx); this.heatmapGradientLegend.draw(); } else { this.drawLegends(); if (cnf.legend.position === "bottom" || cnf.legend.position === "top") { this.legendAlignHorizontal(); } else if (cnf.legend.position === "right" || cnf.legend.position === "left") { this.legendAlignVertical(); } } } } createLegendMarker({ i, fillcolor }) { const w = this.w; const elMarker = BrowserAPIs.createElement("span"); elMarker.classList.add("apexcharts-legend-marker"); const mShape = w.config.legend.markers.shape || w.config.markers.shape; let shape = mShape; if (Array.isArray(mShape)) { shape = mShape[i]; } const mSize = Array.isArray(w.config.legend.markers.size) ? parseFloat(w.config.legend.markers.size[i]) : parseFloat(w.config.legend.markers.size); const mOffsetX = Array.isArray(w.config.legend.markers.offsetX) ? parseFloat(w.config.legend.markers.offsetX[i]) : parseFloat(w.config.legend.markers.offsetX); const mOffsetY = Array.isArray(w.config.legend.markers.offsetY) ? parseFloat(w.config.legend.markers.offsetY[i]) : parseFloat(w.config.legend.markers.offsetY); const mBorderWidth = Array.isArray(w.config.legend.markers.strokeWidth) ? parseFloat(w.config.legend.markers.strokeWidth[i]) : parseFloat(w.config.legend.markers.strokeWidth); const mStyle = elMarker.style; mStyle.height = (mSize + mBorderWidth) * 2 + "px"; mStyle.width = (mSize + mBorderWidth) * 2 + "px"; mStyle.left = mOffsetX + "px"; mStyle.top = mOffsetY + "px"; if (w.config.legend.markers.customHTML) { mStyle.background = "transparent"; mStyle.color = fillcolor[i]; if (Array.isArray(w.config.legend.markers.customHTML)) { if (w.config.legend.markers.customHTML[i]) { elMarker.innerHTML = w.config.legend.markers.customHTML[i](); } } else { elMarker.innerHTML = w.config.legend.markers.customHTML(); } } else { const markers = new Markers(this.ctx.w, this.ctx); const markerConfig = markers.getMarkerConfig({ cssClass: `apexcharts-legend-marker apexcharts-marker apexcharts-marker-${shape}`, seriesIndex: i, strokeWidth: mBorderWidth, size: mSize }); const SVGLib = Environment.isBrowser() ? ( /** @type {any} */ window.SVG ) : ( /** @type {any} */ global.SVG ); const SVGMarker = SVGLib().addTo(elMarker).size("100%", "100%"); const marker = new Graphics(this.w).drawMarker(0, 0, __spreadProps(__spreadValues({}, markerConfig), { pointFillColor: Array.isArray(fillcolor) ? fillcolor[i] : markerConfig.pointFillColor, shape })); const shapesEls = w.dom.Paper.find( ".apexcharts-legend-marker.apexcharts-marker" ); shapesEls.forEach((shapeEl) => { if (shapeEl.node.classList.contains("apexcharts-marker-triangle")) { shapeEl.node.style.transform = "translate(50%, 45%)"; } else { shapeEl.node.style.transform = "translate(50%, 50%)"; } }); SVGMarker.add(marker); } return elMarker; } drawLegends() { var _a; const me = this; const w = this.w; const elLegendWrap = ( /** @type {HTMLElement} */ w.dom.elLegendWrap ); const fontFamily = w.config.legend.fontFamily; let legendNames = w.seriesData.seriesNames; let fillcolor = w.config.legend.markers.fillColors ? w.config.legend.markers.fillColors.slice() : w.globals.colors.slice(); if (w.config.chart.type === "heatmap") { const ranges = w.config.plotOptions.heatmap.colorScale.ranges; legendNames = ranges.map((colorScale) => { return colorScale.name ? colorScale.name : colorScale.from + " - " + colorScale.to; }); fillcolor = ranges.map((color) => color.color); } else if (this.isBarsDistributed) { legendNames = w.labelData.labels.slice(); } if (w.config.legend.customLegendItems.length) { legendNames = w.config.legend.customLegendItems; } const legendFormatter = w.formatters.legendFormatter; const isLegendInversed = w.config.legend.inverseOrder; const legendGroups = []; if (w.labelData.seriesGroups.length > 1 && w.config.legend.clusterGroupedSeries) { w.labelData.seriesGroups.forEach((_, gi) => { legendGroups[gi] = BrowserAPIs.createElement("div"); legendGroups[gi].classList.add( "apexcharts-legend-group", `apexcharts-legend-group-${gi}` ); if (w.config.legend.clusterGroupedSeriesOrientation === "horizontal") { elLegendWrap.classList.add("apexcharts-legend-group-horizontal"); } else { legendGroups[gi].classList.add("apexcharts-legend-group-vertical"); } }); } for (let i = isLegendInversed ? legendNames.length - 1 : 0; isLegendInversed ? i >= 0 : i <= legendNames.length - 1; isLegendInversed ? i-- : i++) { const text = legendFormatter(legendNames[i], { seriesIndex: i, w }); let collapsedSeries = false; let ancillaryCollapsedSeries = false; if (w.globals.collapsedSeries.length > 0) { for (let c = 0; c < w.globals.collapsedSeries.length; c++) { if (w.globals.collapsedSeries[c].index === i) { collapsedSeries = true; } } } if (w.globals.ancillaryCollapsedSeriesIndices.length > 0) { for (let c = 0; c < w.globals.ancillaryCollapsedSeriesIndices.length; c++) { if (w.globals.ancillaryCollapsedSeriesIndices[c] === i) { ancillaryCollapsedSeries = true; } } } const elMarker = this.createLegendMarker({ i, fillcolor }); Graphics.setAttrs(elMarker, { rel: i + 1, "data:collapsed": collapsedSeries || ancillaryCollapsedSeries }); if (collapsedSeries || ancillaryCollapsedSeries) { elMarker.classList.add("apexcharts-inactive-legend"); } const elLegend = BrowserAPIs.createElement("div"); if (w.config.chart.accessibility.enabled && w.config.chart.accessibility.keyboard.enabled) { elLegend.setAttribute("role", "button"); elLegend.setAttribute("tabindex", "0"); const seriesName = Array.isArray(text) ? text.join(" ") : text; const isCollapsed = collapsedSeries || ancillaryCollapsedSeries; const statusText = isCollapsed ? "hidden" : "visible"; elLegend.setAttribute( "aria-label", `${seriesName}, ${statusText}. Press Enter or Space to toggle.` ); elLegend.setAttribute("aria-pressed", isCollapsed ? "true" : "false"); } const elLegendText = BrowserAPIs.createElement("span"); elLegendText.classList.add("apexcharts-legend-text"); elLegendText.innerHTML = Array.isArray(text) ? text.join(" ") : text; let textColor = w.config.legend.labels.useSeriesColors ? w.globals.colors[i] : Array.isArray(w.config.legend.labels.colors) ? (_a = w.config.legend.labels.colors) == null ? void 0 : _a[i] : w.config.legend.labels.colors; if (!textColor) { textColor = w.config.chart.foreColor; } elLegendText.style.color = textColor; elLegendText.style.fontSize = w.config.legend.fontSize; elLegendText.style.fontWeight = w.config.legend.fontWeight; elLegendText.style.fontFamily = fontFamily || w.config.chart.fontFamily; Graphics.setAttrs(elLegendText, { rel: i + 1, i, "data:default-text": encodeURIComponent(text), "data:collapsed": collapsedSeries || ancillaryCollapsedSeries }); elLegend.appendChild(elMarker); elLegend.appendChild(elLegendText); const coreUtils = new CoreUtils(this.w); if (!w.config.legend.showForZeroSeries) { const total = coreUtils.getSeriesTotalByIndex(i); if (total === 0 && coreUtils.seriesHaveSameValues(i) && !coreUtils.isSeriesNull(i) && w.globals.collapsedSeriesIndices.indexOf(i) === -1 && w.globals.ancillaryCollapsedSeriesIndices.indexOf(i) === -1) { elLegend.classList.add("apexcharts-hidden-zero-series"); } } if (!w.config.legend.showForNullSeries) { if (coreUtils.isSe