UNPKG

apexcharts

Version:

A JavaScript Chart Library

570 lines (500 loc) 16.1 kB
import CoreUtils from '../modules/CoreUtils' import Bar from './Bar' import Graphics from '../modules/Graphics' import Utils from '../utils/Utils' /** * ApexCharts BarStacked Class responsible for drawing both Stacked Columns and Bars. * * @module BarStacked * The whole calculation for stacked bar/column is different from normal bar/column, * hence it makes sense to derive a new class for it extending most of the props of Parent Bar **/ class BarStacked extends Bar { draw(series, seriesIndex) { let w = this.w this.graphics = new Graphics(this.ctx) this.bar = new Bar(this.ctx, this.xyRatios) const coreUtils = new CoreUtils(this.ctx, w) series = coreUtils.getLogSeries(series) this.yRatio = coreUtils.getLogYRatios(this.yRatio) this.barHelpers.initVariables(series) if (w.config.chart.stackType === '100%') { series = w.globals.comboCharts ? seriesIndex.map((_) => w.globals.seriesPercent[_]) : w.globals.seriesPercent.slice() } this.series = series this.barHelpers.initializeStackedPrevVars(this) let ret = this.graphics.group({ class: 'apexcharts-bar-series apexcharts-plot-series', }) let x = 0 let y = 0 for (let i = 0, bc = 0; i < series.length; i++, bc++) { let xDivision // xDivision is the GRIDWIDTH divided by number of datapoints (columns) let yDivision // yDivision is the GRIDHEIGHT divided by number of datapoints (bars) let zeroH // zeroH is the baseline where 0 meets y axis let zeroW // zeroW is the baseline where 0 meets x axis let realIndex = w.globals.comboCharts ? seriesIndex[i] : i let { groupIndex, columnGroupIndex } = this.barHelpers.getGroupIndex(realIndex) this.groupCtx = this[w.globals.seriesGroups[groupIndex]] let xArrValues = [] let yArrValues = [] let translationsIndex = 0 if (this.yRatio.length > 1) { this.yaxisIndex = w.globals.seriesYAxisReverseMap[realIndex][0] translationsIndex = realIndex } this.isReversed = w.config.yaxis[this.yaxisIndex] && w.config.yaxis[this.yaxisIndex].reversed // el to which series will be drawn let elSeries = this.graphics.group({ class: `apexcharts-series`, seriesName: Utils.escapeString(w.globals.seriesNames[realIndex]), rel: i + 1, 'data:realIndex': realIndex, }) this.ctx.series.addCollapsedClassToSeries(elSeries, realIndex) // eldatalabels let elDataLabelsWrap = this.graphics.group({ class: 'apexcharts-datalabels', 'data:realIndex': realIndex, }) let elGoalsMarkers = this.graphics.group({ class: 'apexcharts-bar-goals-markers', }) let barHeight = 0 let barWidth = 0 let initPositions = this.initialPositions( x, y, xDivision, yDivision, zeroH, zeroW, translationsIndex ) y = initPositions.y barHeight = initPositions.barHeight yDivision = initPositions.yDivision zeroW = initPositions.zeroW x = initPositions.x barWidth = initPositions.barWidth xDivision = initPositions.xDivision zeroH = initPositions.zeroH w.globals.barHeight = barHeight w.globals.barWidth = barWidth this.barHelpers.initializeStackedXYVars(this) // where all stack bar disappear after collapsing the first series if ( this.groupCtx.prevY.length === 1 && this.groupCtx.prevY[0].every((val) => isNaN(val)) ) { this.groupCtx.prevY[0] = this.groupCtx.prevY[0].map(() => zeroH) this.groupCtx.prevYF[0] = this.groupCtx.prevYF[0].map(() => 0) } for (let j = 0; j < w.globals.dataPoints; j++) { const strokeWidth = this.barHelpers.getStrokeWidth(i, j, realIndex) const commonPathOpts = { indexes: { i, j, realIndex, translationsIndex, bc }, strokeWidth, x, y, elSeries, columnGroupIndex, seriesGroup: w.globals.seriesGroups[groupIndex], } let paths = null if (this.isHorizontal) { paths = this.drawStackedBarPaths({ ...commonPathOpts, zeroW, barHeight, yDivision, }) barWidth = this.series[i][j] / this.invertedYRatio } else { paths = this.drawStackedColumnPaths({ ...commonPathOpts, xDivision, barWidth, zeroH, }) barHeight = this.series[i][j] / this.yRatio[translationsIndex] } const barGoalLine = this.barHelpers.drawGoalLine({ barXPosition: paths.barXPosition, barYPosition: paths.barYPosition, goalX: paths.goalX, goalY: paths.goalY, barHeight, barWidth, }) if (barGoalLine) { elGoalsMarkers.add(barGoalLine) } y = paths.y x = paths.x xArrValues.push(x) yArrValues.push(y) let pathFill = this.barHelpers.getPathFillColor(series, i, j, realIndex) let classes = '' const flipClass = w.globals.isBarHorizontal ? 'apexcharts-flip-x' : 'apexcharts-flip-y' if ( (this.barHelpers.arrBorderRadius[realIndex][j] === 'bottom' && w.globals.series[realIndex][j] > 0) || (this.barHelpers.arrBorderRadius[realIndex][j] === 'top' && w.globals.series[realIndex][j] < 0) ) { classes = flipClass } elSeries = this.renderSeries({ realIndex, pathFill: pathFill.color, ...(pathFill.useRangeColor ? { lineFill: pathFill.color } : {}), j, i, columnGroupIndex, pathFrom: paths.pathFrom, pathTo: paths.pathTo, strokeWidth, elSeries, x, y, series, barHeight, barWidth, elDataLabelsWrap, elGoalsMarkers, type: 'bar', visibleSeries: columnGroupIndex, classes, }) } // push all x val arrays into main xArr w.globals.seriesXvalues[realIndex] = xArrValues w.globals.seriesYvalues[realIndex] = yArrValues // push all current y values array to main PrevY Array this.groupCtx.prevY.push(this.groupCtx.yArrj) this.groupCtx.prevYF.push(this.groupCtx.yArrjF) this.groupCtx.prevYVal.push(this.groupCtx.yArrjVal) this.groupCtx.prevX.push(this.groupCtx.xArrj) this.groupCtx.prevXF.push(this.groupCtx.xArrjF) this.groupCtx.prevXVal.push(this.groupCtx.xArrjVal) ret.add(elSeries) } return ret } initialPositions( x, y, xDivision, yDivision, zeroH, zeroW, translationsIndex ) { let w = this.w let barHeight, barWidth if (this.isHorizontal) { // height divided into equal parts yDivision = w.globals.gridHeight / w.globals.dataPoints let userBarHeight = w.config.plotOptions.bar.barHeight if (String(userBarHeight).indexOf('%') === -1) { barHeight = parseInt(userBarHeight, 10) } else { barHeight = (yDivision * parseInt(userBarHeight, 10)) / 100 } zeroW = w.globals.padHorizontal + (this.isReversed ? w.globals.gridWidth - this.baseLineInvertedY : this.baseLineInvertedY) // initial y position is half of barHeight * half of number of Bars y = (yDivision - barHeight) / 2 } else { // width divided into equal parts xDivision = w.globals.gridWidth / w.globals.dataPoints barWidth = xDivision let userColumnWidth = w.config.plotOptions.bar.columnWidth if (w.globals.isXNumeric && w.globals.dataPoints > 1) { xDivision = w.globals.minXDiff / this.xRatio barWidth = (xDivision * parseInt(this.barOptions.columnWidth, 10)) / 100 } else if (String(userColumnWidth).indexOf('%') === -1) { barWidth = parseInt(userColumnWidth, 10) } else { barWidth *= parseInt(userColumnWidth, 10) / 100 } if (this.isReversed) { zeroH = this.baseLineY[translationsIndex] } else { zeroH = w.globals.gridHeight - this.baseLineY[translationsIndex] } // initial x position is the left-most edge of the first bar relative to // the left-most side of the grid area. x = w.globals.padHorizontal + (xDivision - barWidth) / 2 } // Up to this point, barWidth is the width that will accommodate all bars // at each datapoint or category. // The crude subdivision here assumes the series within each group are // stacked. If there is no stacking then the barWidth/barHeight is // further divided later by the number of series in the group. So, eg, two // groups of three series would become six bars side-by-side unstacked, // or two bars stacked. let subDivisions = w.globals.barGroups.length || 1 return { x, y, yDivision, xDivision, barHeight: barHeight / subDivisions, barWidth: barWidth / subDivisions, zeroH, zeroW, } } drawStackedBarPaths({ indexes, barHeight, strokeWidth, zeroW, x, y, columnGroupIndex, seriesGroup, yDivision, elSeries, }) { let w = this.w let barYPosition = y + columnGroupIndex * barHeight let barXPosition let i = indexes.i let j = indexes.j let realIndex = indexes.realIndex let translationsIndex = indexes.translationsIndex let prevBarW = 0 for (let k = 0; k < this.groupCtx.prevXF.length; k++) { prevBarW = prevBarW + this.groupCtx.prevXF[k][j] } let gsi = i // an index to keep track of the series inside a group if (w.config.series[realIndex].name) { gsi = seriesGroup.indexOf(w.config.series[realIndex].name) } if (gsi > 0) { let bXP = zeroW if (this.groupCtx.prevXVal[gsi - 1][j] < 0) { bXP = this.series[i][j] >= 0 ? this.groupCtx.prevX[gsi - 1][j] + prevBarW - (this.isReversed ? prevBarW : 0) * 2 : this.groupCtx.prevX[gsi - 1][j] } else if (this.groupCtx.prevXVal[gsi - 1][j] >= 0) { bXP = this.series[i][j] >= 0 ? this.groupCtx.prevX[gsi - 1][j] : this.groupCtx.prevX[gsi - 1][j] - prevBarW + (this.isReversed ? prevBarW : 0) * 2 } barXPosition = bXP } else { // the first series will not have prevX values barXPosition = zeroW } if (this.series[i][j] === null) { x = barXPosition } else { x = barXPosition + this.series[i][j] / this.invertedYRatio - (this.isReversed ? this.series[i][j] / this.invertedYRatio : 0) * 2 } const paths = this.barHelpers.getBarpaths({ barYPosition, barHeight, x1: barXPosition, x2: x, strokeWidth, isReversed: this.isReversed, series: this.series, realIndex: indexes.realIndex, seriesGroup, i, j, w, }) this.barHelpers.barBackground({ j, i, y1: barYPosition, y2: barHeight, elSeries, }) y = y + yDivision return { pathTo: paths.pathTo, pathFrom: paths.pathFrom, goalX: this.barHelpers.getGoalValues( 'x', zeroW, null, i, j, translationsIndex ), barXPosition, barYPosition, x, y, } } drawStackedColumnPaths({ indexes, x, y, xDivision, barWidth, zeroH, columnGroupIndex, seriesGroup, elSeries, }) { let w = this.w let i = indexes.i let j = indexes.j let bc = indexes.bc let realIndex = indexes.realIndex let translationsIndex = indexes.translationsIndex if (w.globals.isXNumeric) { let seriesVal = w.globals.seriesX[realIndex][j] if (!seriesVal) seriesVal = 0 // TODO: move the barWidth factor to barXPosition x = (seriesVal - w.globals.minX) / this.xRatio - (barWidth / 2) * w.globals.barGroups.length } let barXPosition = x + columnGroupIndex * barWidth let barYPosition let prevBarH = 0 for (let k = 0; k < this.groupCtx.prevYF.length; k++) { // fix issue #1215 // in case where this.groupCtx.prevYF[k][j] is NaN, use 0 instead prevBarH = prevBarH + (!isNaN(this.groupCtx.prevYF[k][j]) ? this.groupCtx.prevYF[k][j] : 0) } let gsi = i // an index to keep track of the series inside a group if (seriesGroup) { gsi = seriesGroup.indexOf(w.globals.seriesNames[realIndex]) } if ( (gsi > 0 && !w.globals.isXNumeric) || (gsi > 0 && w.globals.isXNumeric && w.globals.seriesX[realIndex - 1][j] === w.globals.seriesX[realIndex][j]) ) { let bYP let prevYValue const p = Math.min(this.yRatio.length + 1, realIndex + 1) if ( this.groupCtx.prevY[gsi - 1] !== undefined && this.groupCtx.prevY[gsi - 1].length ) { for (let ii = 1; ii < p; ii++) { if (!isNaN(this.groupCtx.prevY[gsi - ii]?.[j])) { // find the previous available value to give prevYValue prevYValue = this.groupCtx.prevY[gsi - ii][j] // if found it, break the loop break } } } for (let ii = 1; ii < p; ii++) { // find the previous available value(non-NaN) to give bYP if (this.groupCtx.prevYVal[gsi - ii]?.[j] < 0) { bYP = this.series[i][j] >= 0 ? prevYValue - prevBarH + (this.isReversed ? prevBarH : 0) * 2 : prevYValue // found it? break the loop break } else if (this.groupCtx.prevYVal[gsi - ii]?.[j] >= 0) { bYP = this.series[i][j] >= 0 ? prevYValue : prevYValue + prevBarH - (this.isReversed ? prevBarH : 0) * 2 // found it? break the loop break } } if (typeof bYP === 'undefined') bYP = w.globals.gridHeight // if this.prevYF[0] is all 0 resulted from line #486 // AND every arr starting from the second only contains NaN if ( this.groupCtx.prevYF[0]?.every((val) => val === 0) && this.groupCtx.prevYF .slice(1, gsi) .every((arr) => arr.every((val) => isNaN(val))) ) { barYPosition = zeroH } else { // Nothing special barYPosition = bYP } } else { // the first series will not have prevY values, also if the prev index's // series X doesn't matches the current index's series X, then start from // zero barYPosition = zeroH } if (this.series[i][j]) { y = barYPosition - this.series[i][j] / this.yRatio[translationsIndex] + (this.isReversed ? this.series[i][j] / this.yRatio[translationsIndex] : 0) * 2 } else { // fixes #3610 y = barYPosition } const paths = this.barHelpers.getColumnPaths({ barXPosition, barWidth, y1: barYPosition, y2: y, yRatio: this.yRatio[translationsIndex], strokeWidth: this.strokeWidth, isReversed: this.isReversed, series: this.series, seriesGroup, realIndex: indexes.realIndex, i, j, w, }) this.barHelpers.barBackground({ bc, j, i, x1: barXPosition, x2: barWidth, elSeries, }) return { pathTo: paths.pathTo, pathFrom: paths.pathFrom, goalY: this.barHelpers.getGoalValues('y', null, zeroH, i, j), barXPosition, x: w.globals.isXNumeric ? x : x + xDivision, y, } } } export default BarStacked