UNPKG

apexcharts

Version:

A JavaScript Chart Library

500 lines (426 loc) 12.7 kB
// @ts-check import Bar from './Bar' import Graphics from '../modules/Graphics' import Series from '../modules/Series' import Utils from '../utils/Utils' /** * ApexCharts RangeBar Class responsible for drawing Range/Timeline Bars. * * @module RangeBar **/ class RangeBar extends Bar { /** * @param {any[]} series * @param {number} seriesIndex */ draw(series, seriesIndex) { const w = this.w const graphics = new Graphics(this.w) this.rangeBarOptions = this.w.config.plotOptions.rangeBar this.series = series this.seriesRangeStart = w.rangeData.seriesRangeStart this.seriesRangeEnd = w.rangeData.seriesRangeEnd this.barHelpers.initVariables(series) const ret = graphics.group({ class: 'apexcharts-rangebar-series apexcharts-plot-series', }) for (let i = 0; i < series.length; i++) { let x, y const realIndex = w.globals.comboCharts ? /** @type {any} */ (seriesIndex)[i] : i const { columnGroupIndex } = this.barHelpers.getGroupIndex(realIndex) // el to which series will be drawn const elSeries = graphics.group({ class: `apexcharts-series`, seriesName: Utils.escapeString(w.seriesData.seriesNames[realIndex]), rel: i + 1, 'data:realIndex': realIndex, }) Series.addCollapsedClassToSeries(this.w, elSeries, realIndex) if (series[i].length > 0) { this.visibleI = this.visibleI + 1 } let translationsIndex = 0 if (this.yRatio.length > 1) { this.yaxisIndex = /** @type {any} */ ( w.globals.seriesYAxisReverseMap[realIndex] )[0] translationsIndex = realIndex } const initPositions = this.barHelpers.initialPositions(realIndex) const { y: initY, zeroW, // zeroW is the baseline where 0 meets x axis x: initX, zeroH, // zeroH is the baseline where 0 meets y axis } = initPositions let barWidth = initPositions.barWidth ?? 0 let barHeight = initPositions.barHeight ?? 0 const yDivision = initPositions.yDivision ?? 0 const xDivision = initPositions.xDivision ?? 0 y = initY x = initX // eldatalabels const elDataLabelsWrap = graphics.group({ class: 'apexcharts-datalabels', 'data:realIndex': realIndex, }) const elGoalsMarkers = graphics.group({ class: 'apexcharts-rangebar-goals-markers', }) for (let j = 0; j < w.globals.dataPoints; j++) { const strokeWidth = this.barHelpers.getStrokeWidth(i, j, realIndex) const y1 = this.seriesRangeStart[i][j] const y2 = this.seriesRangeEnd[i][j] let paths = /** @type {any} */ (null) let barXPosition = null let barYPosition = null const params = { x, y, strokeWidth, elSeries } let seriesLen = this.seriesLen if (w.config.plotOptions.bar.rangeBarGroupRows) { seriesLen = 1 } if ( typeof /** @type {Record<string,any>} */ (w.config.series[i]).data?.[j] === 'undefined' ) { // no data exists for further indexes, hence we need to get out the innr loop. // As we are iterating over total datapoints, there is a possiblity the series might not have data for j index break } if (this.isHorizontal) { barYPosition = y + barHeight * /** @type {any} */ (this).visibleI const srty = (yDivision - barHeight * seriesLen) / 2 if (/** @type {Record<string,any>} */ (w.config.series[i]).data?.[j]?.x) { const positions = this.detectOverlappingBars({ i, j, barYPosition, srty, barHeight, yDivision, initPositions, }) barHeight = positions.barHeight barYPosition = positions.barYPosition } paths = this.drawRangeBarPaths({ indexes: { i, j, realIndex }, barHeight, barYPosition, zeroW, yDivision, y1, y2, ...params, }) barWidth = paths.barWidth } else { if (w.axisFlags.isXNumeric) { x = (w.seriesData.seriesX[i][j] - w.globals.minX) / this.xRatio - barWidth / 2 } barXPosition = x + barWidth * /** @type {any} */ (this).visibleI const srtx = (xDivision - barWidth * seriesLen) / 2 if (/** @type {Record<string,any>} */ (w.config.series[i]).data?.[j]?.x) { const positions = this.detectOverlappingBars({ i, j, barXPosition, srtx, barWidth, xDivision, initPositions, }) barWidth = positions.barWidth barXPosition = positions.barXPosition } paths = this.drawRangeColumnPaths({ indexes: { i, j, realIndex, translationsIndex }, barWidth, barXPosition, zeroH, xDivision, ...params, }) barHeight = paths.barHeight } const barGoalLine = this.barHelpers.drawGoalLine({ barXPosition: paths.barXPosition, barYPosition, goalX: paths.goalX, goalY: paths.goalY, barHeight, barWidth, }) if (barGoalLine) { elGoalsMarkers.add(barGoalLine) } y = paths.y x = paths.x const pathFill = this.barHelpers.getPathFillColor( series, i, j, realIndex, ) this.renderSeries({ realIndex, pathFill: pathFill.color, lineFill: pathFill.useRangeColor ? pathFill.color : w.globals.stroke.colors[realIndex], j, i, x, y, y1, y2, pathFrom: paths.pathFrom, pathTo: paths.pathTo, strokeWidth, elSeries, series, barHeight, barWidth, barXPosition, barYPosition, columnGroupIndex, elDataLabelsWrap, elGoalsMarkers, visibleSeries: this.visibleI, type: 'rangebar', }) } ret.add(elSeries) } return ret } /** @param {{ i?: any, j?: any, barYPosition?: any, barXPosition?: any, srty?: any, srtx?: any, barHeight?: any, barWidth?: any, yDivision?: any, xDivision?: any, initPositions?: any }} opts */ detectOverlappingBars({ i, j, barYPosition, barXPosition, srty, srtx, barHeight, barWidth, yDivision, xDivision, initPositions, }) { const w = this.w let overlaps = [] const rangeName = /** @type {Record<string,any>} */ (w.config.series[i]).data?.[j] ?.rangeName const x = /** @type {Record<string,any>} */ (w.config.series[i]).data?.[j]?.x const labelX = Array.isArray(x) ? x.join(' ') : x const rowIndex = w.labelData.labels /** * @param {any} _ */ .map((_) => (Array.isArray(_) ? _.join(' ') : _)) .indexOf(labelX) /** * @param {any} tx */ const overlappedIndex = w.rangeData.seriesRange[i].findIndex( (/** @type {any} */ tx) => tx.x === labelX && tx.overlaps?.size > 0, ) if (this.isHorizontal) { if (w.config.plotOptions.bar.rangeBarGroupRows) { barYPosition = srty + yDivision * rowIndex } else { barYPosition = srty + barHeight * this.visibleI + yDivision * rowIndex } if (overlappedIndex > -1 && !w.config.plotOptions.bar.rangeBarOverlap) { overlaps = Array.from( /** @type {any} */ (w.rangeData.seriesRange[i][overlappedIndex]) .overlaps, ) if (overlaps.indexOf(rangeName) > -1) { barHeight = initPositions.barHeight / overlaps.length barYPosition = barHeight * this.visibleI + (yDivision * (100 - parseInt(this.barOptions.barHeight, 10))) / 100 / 2 + barHeight * (this.visibleI + overlaps.indexOf(rangeName)) + yDivision * rowIndex } } } else { if (rowIndex > -1 && !w.labelData.timescaleLabels.length) { if (w.config.plotOptions.bar.rangeBarGroupRows) { barXPosition = srtx + xDivision * rowIndex } else { barXPosition = srtx + barWidth * this.visibleI + xDivision * rowIndex } } if (overlappedIndex > -1 && !w.config.plotOptions.bar.rangeBarOverlap) { overlaps = Array.from( /** @type {any} */ (w.rangeData.seriesRange[i][overlappedIndex]) .overlaps, ) if (overlaps.indexOf(rangeName) > -1) { barWidth = initPositions.barWidth / overlaps.length barXPosition = barWidth * this.visibleI + (xDivision * (100 - parseInt(this.barOptions.barWidth, 10))) / 100 / 2 + barWidth * (this.visibleI + overlaps.indexOf(rangeName)) + xDivision * rowIndex } } } return { barYPosition, barXPosition, barHeight, barWidth, } } /** @param {{indexes: any, x: any, xDivision: any, barWidth: any, barXPosition: any, zeroH: any}} opts */ drawRangeColumnPaths({ indexes, x, xDivision, barWidth, barXPosition, zeroH, }) { const w = this.w const { i, j, realIndex, translationsIndex } = indexes const yRatio = this.yRatio[translationsIndex] const range = this.getRangeValue(realIndex, j) let y1 = Math.min(range.start, range.end) let y2 = Math.max(range.start, range.end) if ( typeof /** @type {any} */ (this.series)[i]?.[j] === 'undefined' || /** @type {any} */ (this.series)[i]?.[j] === null ) { y1 = zeroH } else { y1 = zeroH - y1 / yRatio y2 = zeroH - y2 / yRatio } const barHeight = Math.abs(y2 - y1) const paths = this.barHelpers.getColumnPaths({ barXPosition, barWidth, y1, y2, strokeWidth: this.strokeWidth, series: this.seriesRangeEnd, realIndex: realIndex, i: realIndex, j, w, }) if (!w.axisFlags.isXNumeric) { x = x + xDivision } else { const xForNumericXAxis = this.getBarXForNumericXAxis({ x, j, realIndex, barWidth, }) x = xForNumericXAxis.x barXPosition = xForNumericXAxis.barXPosition } return { pathTo: paths.pathTo, pathFrom: paths.pathFrom, barHeight, x, y: range.start < 0 && range.end < 0 ? y1 : y2, goalY: this.barHelpers.getGoalValues( 'y', /** @type {any} */ (null), zeroH, i, j, translationsIndex, ), barXPosition, } } /** * @param {number} val */ preventBarOverflow(val) { const w = this.w if (val < 0) { val = 0 } if (val > w.layout.gridWidth) { val = w.layout.gridWidth } return val } /** @param {{indexes: any, y: any, y1: any, y2: any, yDivision: any, barHeight: any, barYPosition: any, zeroW: any}} opts */ drawRangeBarPaths({ indexes, y, y1, y2, yDivision, barHeight, barYPosition, zeroW, }) { const w = this.w const { realIndex, j } = indexes const x1 = this.preventBarOverflow(zeroW + y1 / this.invertedYRatio) const x2 = this.preventBarOverflow(zeroW + y2 / this.invertedYRatio) const range = this.getRangeValue(realIndex, j) const barWidth = Math.abs(x2 - x1) const paths = this.barHelpers.getBarpaths({ barYPosition, barHeight, x1, x2, strokeWidth: this.strokeWidth, series: this.seriesRangeEnd, i: realIndex, realIndex, j, w, }) if (!w.axisFlags.isXNumeric) { y = y + yDivision } return { pathTo: paths.pathTo, pathFrom: paths.pathFrom, barWidth, x: range.start < 0 && range.end < 0 ? x1 : x2, goalX: this.barHelpers.getGoalValues( 'x', zeroW, /** @type {any} */ (null), realIndex, j, 0, ), y, } } /** * @param {number} i * @param {number} j */ getRangeValue(i, j) { const w = this.w return { start: w.rangeData.seriesRangeStart[i][j], end: w.rangeData.seriesRangeEnd[i][j], } } } export default RangeBar