UNPKG

apexcharts

Version:

A JavaScript Chart Library

1,251 lines (1,250 loc) 201 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"; 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 AxesUtils = _core.__apex_axes_AxesUtils; const Data = _core.__apex_Data; const Series = _core.__apex_Series; const Utils = _core.__apex_Utils; const Environment = _core.__apex_Environment_Environment; class Exports { /** * @param {import('../types/internal').ChartStateW} w * @param {import('../types/internal').ChartContext} ctx */ constructor(w, ctx) { this.w = w; this.ctx = ctx; } /** * @param {string} svgString */ svgStringToNode(svgString) { const parser = new DOMParser(); const svgDoc = parser.parseFromString(svgString, "image/svg+xml"); return svgDoc.documentElement; } /** * @param {any} svg * @param {number} scale */ scaleSvgNode(svg, scale) { const svgWidth = parseFloat(svg.getAttributeNS(null, "width")); const svgHeight = parseFloat(svg.getAttributeNS(null, "height")); svg.setAttributeNS(null, "width", svgWidth * scale); svg.setAttributeNS(null, "height", svgHeight * scale); svg.setAttributeNS(null, "viewBox", "0 0 " + svgWidth + " " + svgHeight); } /** * @param {number} [_scale] */ getSvgString(_scale) { return new Promise((resolve) => { const w = this.w; let scale = _scale || w.config.chart.toolbar.export.scale || w.config.chart.toolbar.export.width / w.globals.svgWidth; if (!scale) { scale = 1; } const width = w.globals.svgWidth * scale; const height = w.globals.svgHeight * scale; const clonedNode = ( /** @type {HTMLElement} */ w.dom.elWrap.cloneNode(true) ); clonedNode.style.width = width + "px"; clonedNode.style.height = height + "px"; const serializedNode = new XMLSerializer().serializeToString(clonedNode); const shouldIncludeLegendStyles = w.config.legend.show && w.dom.elLegendWrap && w.dom.elLegendWrap.children.length > 0; let exportStyles = ` .apexcharts-tooltip, .apexcharts-toolbar, .apexcharts-xaxistooltip, .apexcharts-yaxistooltip, .apexcharts-xcrosshairs, .apexcharts-ycrosshairs, .apexcharts-zoom-rect, .apexcharts-selection-rect { display: none; } `; if (shouldIncludeLegendStyles) { exportStyles += apexchartsLegendCSS; } let svgString = ` <svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" class="apexcharts-svg" xmlns:data="ApexChartsNS" transform="translate(0, 0)" width="${w.globals.svgWidth}px" height="${w.globals.svgHeight}px"> <foreignObject width="100%" height="100%"> <div xmlns="http://www.w3.org/1999/xhtml" style="width:${width}px; height:${height}px;"> <style type="text/css"> ${exportStyles} </style> ${serializedNode} </div> </foreignObject> </svg> `; const svgNode = this.svgStringToNode(svgString); if (scale !== 1) { this.scaleSvgNode(svgNode, scale); } this.convertImagesToBase64(svgNode).then(() => { svgString = new XMLSerializer().serializeToString(svgNode); resolve(svgString.replace(/&nbsp;/g, "&#160;")); }); }); } /** * @param {any} svgNode */ convertImagesToBase64(svgNode) { const images = svgNode.getElementsByTagName("image"); const promises = Array.from(images).map((img) => { const href = img.getAttributeNS("http://www.w3.org/1999/xlink", "href"); if (href && !href.startsWith("data:")) { return this.getBase64FromUrl(href).then((base64) => { img.setAttributeNS("http://www.w3.org/1999/xlink", "href", base64); }).catch((error) => { console.error("Error converting image to base64:", error); }); } return Promise.resolve(); }); return Promise.all(promises); } /** * @param {string} url */ getBase64FromUrl(url) { if (Environment.isSSR()) return Promise.resolve(url); return new Promise((resolve, reject) => { const img = new Image(); img.crossOrigin = "Anonymous"; img.onload = () => { const canvas = document.createElement("canvas"); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext("2d"); if (ctx) ctx.drawImage(img, 0, 0); resolve(canvas.toDataURL()); }; img.onerror = reject; img.src = url; }); } svgUrl() { return new Promise((resolve) => { this.getSvgString().then((svgData) => { const svgBlob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" }); resolve(URL.createObjectURL(svgBlob)); }); }); } /** * @param {Record<string, any> | undefined} options */ dataURI(options) { if (Environment.isSSR()) return Promise.resolve({ imgURI: "" }); return new Promise((resolve) => { const w = this.w; const scale = options ? options.scale || options.width / w.globals.svgWidth : 1; const canvas = document.createElement("canvas"); canvas.width = w.globals.svgWidth * scale; canvas.height = parseInt(w.dom.elWrap.style.height, 10) * scale; const canvasBg = w.config.chart.background === "transparent" || !w.config.chart.background ? "#fff" : w.config.chart.background; const ctx = canvas.getContext("2d"); if (!ctx) return; ctx.fillStyle = canvasBg; ctx.fillRect(0, 0, canvas.width * scale, canvas.height * scale); this.getSvgString(scale).then((svgData) => { const svgUrl = "data:image/svg+xml," + encodeURIComponent(svgData); const img = new Image(); img.crossOrigin = "anonymous"; img.onload = () => { ctx.drawImage(img, 0, 0); const edgeCanvas = canvas; if (edgeCanvas.msToBlob) { const blob = edgeCanvas.msToBlob(); resolve({ blob }); } else { const imgURI = canvas.toDataURL("image/png"); resolve({ imgURI }); } }; img.src = svgUrl; }); }); } exportToSVG() { this.svgUrl().then((url) => { this.triggerDownload( url, this.w.config.chart.toolbar.export.svg.filename, ".svg" ); }); } exportToPng() { const scale = this.w.config.chart.toolbar.export.scale; const width = this.w.config.chart.toolbar.export.width; const option = scale ? { scale } : width ? { width } : void 0; this.dataURI(option).then(({ imgURI, blob }) => { if (blob) { navigator.msSaveOrOpenBlob(blob, this.w.globals.chartID + ".png"); } else { this.triggerDownload( imgURI, this.w.config.chart.toolbar.export.png.filename, ".png" ); } }); } /** @param {{ series?: any, fileName?: any, columnDelimiter?: string, lineDelimiter?: string }} opts */ exportToCSV({ series, fileName, columnDelimiter = ",", lineDelimiter = "\n" }) { const w = this.w; if (!series) series = w.config.series; let columns = []; const rows = []; let result = ""; const universalBOM = "\uFEFF"; const gSeries = w.seriesData.series.map((s, i) => { return w.globals.collapsedSeriesIndices.indexOf(i) === -1 ? s : []; }); const getFormattedCategory = (cat) => { if (typeof w.config.chart.toolbar.export.csv.categoryFormatter === "function") { return w.config.chart.toolbar.export.csv.categoryFormatter(cat); } if (w.config.xaxis.type === "datetime" && String(cat).length >= 10) { return new Date(cat).toDateString(); } return Utils.isNumber(cat) ? cat : cat.split(columnDelimiter).join(""); }; const getFormattedValue = (value) => { return typeof w.config.chart.toolbar.export.csv.valueFormatter === "function" ? w.config.chart.toolbar.export.csv.valueFormatter(value) : value; }; const seriesMaxDataLength = Math.max( ...series.map((s) => { return s.data ? s.data.length : 0; }) ); const dataFormat = new Data(this.w); const axesUtils = new AxesUtils(this.w, { theme: this.ctx.theme, timeScale: this.ctx.timeScale }); const getCat = (i) => { let cat = ""; if (!w.globals.axisCharts) { cat = w.config.labels[i]; } else { if (w.config.xaxis.type === "category" || w.config.xaxis.convertedCatToNumeric) { if (w.globals.isBarHorizontal) { const lbFormatter = w.formatters.yLabelFormatters[0]; const sr = new Series(this.ctx.w); const activeSeries = sr.getActiveConfigSeriesIndex(); cat = lbFormatter(w.labelData.labels[i], { seriesIndex: activeSeries, dataPointIndex: i, w }); } else { cat = axesUtils.getLabel( w.labelData.labels, w.labelData.timescaleLabels, 0, i ).text; } } if (w.config.xaxis.type === "datetime") { if (w.config.xaxis.categories.length) { cat = w.config.xaxis.categories[i]; } else if (w.config.labels.length) { cat = w.config.labels[i]; } } } if (cat === null) return "nullvalue"; if (Array.isArray(cat)) { cat = cat.join(" "); } return Utils.isNumber(cat) ? cat : cat.split(columnDelimiter).join(""); }; const getEmptyDataForCsvColumn = () => { return [...Array(seriesMaxDataLength)].map(() => ""); }; const handleAxisRowsColumns = (s, sI) => { var _a, _b, _c, _d, _e, _f; if (columns.length && sI === 0) { rows.push(columns.join(columnDelimiter)); } if (s.data) { s.data = s.data.length && s.data || getEmptyDataForCsvColumn(); for (let i = 0; i < s.data.length; i++) { columns = []; let cat = getCat(i); if (cat === "nullvalue") continue; if (!cat) { if (dataFormat.isFormatXY()) { cat = series[sI].data[i].x; } else if (dataFormat.isFormat2DArray()) { cat = series[sI].data[i] ? series[sI].data[i][0] : ""; } } if (sI === 0) { columns.push(getFormattedCategory(cat)); for (let ci = 0; ci < w.seriesData.series.length; ci++) { const value = dataFormat.isFormatXY() ? (_a = series[ci].data[i]) == null ? void 0 : _a.y : gSeries[ci][i]; columns.push(getFormattedValue(value)); } } if (w.config.chart.type === "candlestick" || s.type && s.type === "candlestick") { columns.pop(); columns.push(w.candleData.seriesCandleO[sI][i]); columns.push(w.candleData.seriesCandleH[sI][i]); columns.push(w.candleData.seriesCandleL[sI][i]); columns.push(w.candleData.seriesCandleC[sI][i]); } if (w.config.chart.type === "boxPlot" || s.type && s.type === "boxPlot") { columns.pop(); columns.push(w.candleData.seriesCandleO[sI][i]); columns.push(w.candleData.seriesCandleH[sI][i]); columns.push(w.candleData.seriesCandleM[sI][i]); columns.push(w.candleData.seriesCandleL[sI][i]); columns.push(w.candleData.seriesCandleC[sI][i]); } if (w.config.chart.type === "rangeBar") { columns.pop(); columns.push(w.rangeData.seriesRangeStart[sI][i]); columns.push(w.rangeData.seriesRangeEnd[sI][i]); } if (w.config.chart.type === "violin" || s.type && s.type === "violin") { columns.pop(); columns.push((_b = w.violinData.seriesViolinMin[sI]) == null ? void 0 : _b[i]); columns.push((_c = w.violinData.seriesViolinMax[sI]) == null ? void 0 : _c[i]); columns.push((_f = (_e = (_d = w.violinData.seriesViolinPoints[sI]) == null ? void 0 : _d[i]) == null ? void 0 : _e.length) != null ? _f : 0); } if (columns.length) { rows.push(columns.join(columnDelimiter)); } } } }; const handleUnequalXValues = () => { const categories = /* @__PURE__ */ new Set(); const data = {}; series.forEach((s, sI) => { s == null ? void 0 : s.data.forEach((dataItem) => { let cat, value; if (dataFormat.isFormatXY()) { cat = dataItem.x; value = dataItem.y; } else if (dataFormat.isFormat2DArray()) { cat = dataItem[0]; value = dataItem[1]; } else { return; } if (!/** @type {Record<string,any>} */ data[cat]) { data[cat] = Array( series.length ).fill(""); } data[cat][sI] = getFormattedValue(value); categories.add(cat); }); }); if (columns.length) { rows.push(columns.join(columnDelimiter)); } Array.from(categories).sort().forEach((cat) => { rows.push([ getFormattedCategory(cat), /** @type {Record<string,any>} */ data[cat].join(columnDelimiter) ]); }); }; columns.push(w.config.chart.toolbar.export.csv.headerCategory); if (w.config.chart.type === "boxPlot") { columns.push("minimum"); columns.push("q1"); columns.push("median"); columns.push("q3"); columns.push("maximum"); } else if (w.config.chart.type === "candlestick") { columns.push("open"); columns.push("high"); columns.push("low"); columns.push("close"); } else if (w.config.chart.type === "rangeBar") { columns.push("minimum"); columns.push("maximum"); } else if (w.config.chart.type === "violin") { columns.push("minimum"); columns.push("maximum"); columns.push("observations"); } else { series.map((s, sI) => { const sname = (s.name ? s.name : `series-${sI}`) + ""; if (w.globals.axisCharts) { columns.push( sname.split(columnDelimiter).join("") ? sname.split(columnDelimiter).join("") : `series-${sI}` ); } }); } if (!w.globals.axisCharts) { columns.push(w.config.chart.toolbar.export.csv.headerValue); rows.push(columns.join(columnDelimiter)); } if (!w.globals.allSeriesHasEqualX && w.globals.axisCharts && !w.config.xaxis.categories.length && !w.config.labels.length) { handleUnequalXValues(); } else { series.map((s, sI) => { if (w.globals.axisCharts) { handleAxisRowsColumns(s, sI); } else { columns = []; columns.push(getFormattedCategory(w.labelData.labels[sI])); columns.push(getFormattedValue(gSeries[sI])); rows.push(columns.join(columnDelimiter)); } }); } result += rows.join(lineDelimiter); this.triggerDownload( "data:text/csv; charset=utf-8," + encodeURIComponent(universalBOM + result), fileName ? fileName : w.config.chart.toolbar.export.csv.filename, ".csv" ); } /** * @param {string} href * @param {string} filename * @param {string} ext */ triggerDownload(href, filename, ext) { if (Environment.isSSR()) return; const downloadLink = document.createElement("a"); downloadLink.href = href; downloadLink.download = (filename ? filename : this.w.globals.chartID) + ext; document.body.appendChild(downloadLink); downloadLink.click(); document.body.removeChild(downloadLink); } } _core__default.registerFeatures({ exports: Exports }); const CoreUtils = _core.__apex_CoreUtils; const Dimensions = _core.__apex_dimensions_Dimensions; const Graphics = _core.__apex_Graphics; let Helpers$1 = 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") {