apexcharts
Version:
A JavaScript Chart Library
1,251 lines (1,250 loc) • 201 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.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(/ /g, " "));
});
});
}
/**
* @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") {