536 lines
14 KiB
JavaScript
536 lines
14 KiB
JavaScript
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.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 groupIndex = -1 // groupIndex is the index of group buckets (group1, group2, ...)
|
|
this.groupCtx = this
|
|
|
|
w.globals.seriesGroups.forEach((group, gIndex) => {
|
|
if (group.indexOf(w.config.series[i].name) > -1) {
|
|
groupIndex = gIndex
|
|
}
|
|
})
|
|
|
|
if (groupIndex !== -1) {
|
|
this.groupCtx = this[w.globals.seriesGroups[groupIndex]]
|
|
}
|
|
|
|
let xArrValues = []
|
|
let yArrValues = []
|
|
|
|
let realIndex = w.globals.comboCharts ? seriesIndex[i] : i
|
|
|
|
if (this.yRatio.length > 1) {
|
|
this.yaxisIndex = 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
|
|
)
|
|
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((val) => zeroH)
|
|
this.groupCtx.prevYF[0] = this.groupCtx.prevYF[0].map((val) => 0)
|
|
}
|
|
|
|
for (let j = 0; j < w.globals.dataPoints; j++) {
|
|
const strokeWidth = this.barHelpers.getStrokeWidth(i, j, realIndex)
|
|
const commonPathOpts = {
|
|
indexes: { i, j, realIndex, bc },
|
|
strokeWidth,
|
|
x,
|
|
y,
|
|
elSeries,
|
|
groupIndex,
|
|
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[this.yaxisIndex]
|
|
}
|
|
|
|
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)
|
|
|
|
elSeries = this.renderSeries({
|
|
realIndex,
|
|
pathFill,
|
|
j,
|
|
i,
|
|
groupIndex,
|
|
pathFrom: paths.pathFrom,
|
|
pathTo: paths.pathTo,
|
|
strokeWidth,
|
|
elSeries,
|
|
x,
|
|
y,
|
|
series,
|
|
barHeight,
|
|
barWidth,
|
|
elDataLabelsWrap,
|
|
elGoalsMarkers,
|
|
type: 'bar',
|
|
visibleSeries: 0,
|
|
})
|
|
}
|
|
|
|
// 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) {
|
|
let w = this.w
|
|
|
|
let barHeight, barWidth
|
|
if (this.isHorizontal) {
|
|
// height divided into equal parts
|
|
yDivision = w.globals.gridHeight / w.globals.dataPoints
|
|
barHeight = yDivision
|
|
|
|
barHeight =
|
|
(barHeight * parseInt(w.config.plotOptions.bar.barHeight, 10)) / 100
|
|
|
|
if (String(w.config.plotOptions.bar.barHeight).indexOf('%') === -1) {
|
|
barHeight = parseInt(w.config.plotOptions.bar.barHeight, 10)
|
|
}
|
|
zeroW =
|
|
this.baseLineInvertedY +
|
|
w.globals.padHorizontal +
|
|
(this.isReversed ? w.globals.gridWidth : 0) -
|
|
(this.isReversed ? this.baseLineInvertedY * 2 : 0)
|
|
|
|
// 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
|
|
|
|
if (w.globals.isXNumeric && w.globals.dataPoints > 1) {
|
|
// the check (w.globals.dataPoints > 1) fixes apexcharts.js #1617
|
|
xDivision = w.globals.minXDiff / this.xRatio
|
|
barWidth = (xDivision * parseInt(this.barOptions.columnWidth, 10)) / 100
|
|
} else {
|
|
barWidth =
|
|
(barWidth * parseInt(w.config.plotOptions.bar.columnWidth, 10)) / 100
|
|
}
|
|
|
|
if (String(w.config.plotOptions.bar.columnWidth).indexOf('%') === -1) {
|
|
barWidth = parseInt(w.config.plotOptions.bar.columnWidth, 10)
|
|
}
|
|
zeroH =
|
|
w.globals.gridHeight -
|
|
this.baseLineY[this.yaxisIndex] -
|
|
(this.isReversed ? w.globals.gridHeight : 0) +
|
|
(this.isReversed ? this.baseLineY[this.yaxisIndex] * 2 : 0)
|
|
|
|
// initial x position is one third of barWidth
|
|
x = w.globals.padHorizontal + (xDivision - barWidth) / 2
|
|
}
|
|
return {
|
|
x,
|
|
y,
|
|
yDivision,
|
|
xDivision,
|
|
barHeight: w.globals.seriesGroups?.length
|
|
? barHeight / w.globals.seriesGroups.length
|
|
: barHeight,
|
|
barWidth: w.globals.seriesGroups?.length
|
|
? barWidth / w.globals.seriesGroups.length
|
|
: barWidth,
|
|
zeroH,
|
|
zeroW,
|
|
}
|
|
}
|
|
|
|
drawStackedBarPaths({
|
|
indexes,
|
|
barHeight,
|
|
strokeWidth,
|
|
zeroW,
|
|
x,
|
|
y,
|
|
groupIndex,
|
|
seriesGroup,
|
|
yDivision,
|
|
elSeries,
|
|
}) {
|
|
let w = this.w
|
|
let barYPosition = y + (groupIndex !== -1 ? groupIndex * barHeight : 0)
|
|
let barXPosition
|
|
let i = indexes.i
|
|
let j = indexes.j
|
|
|
|
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 (seriesGroup) {
|
|
gsi = seriesGroup.indexOf(w.config.series[i].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,
|
|
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),
|
|
barYPosition,
|
|
x,
|
|
y,
|
|
}
|
|
}
|
|
|
|
drawStackedColumnPaths({
|
|
indexes,
|
|
x,
|
|
y,
|
|
xDivision,
|
|
barWidth,
|
|
zeroH,
|
|
groupIndex,
|
|
seriesGroup,
|
|
elSeries,
|
|
}) {
|
|
let w = this.w
|
|
let i = indexes.i
|
|
let j = indexes.j
|
|
let bc = indexes.bc
|
|
|
|
if (w.globals.isXNumeric) {
|
|
let seriesVal = w.globals.seriesX[i][j]
|
|
if (!seriesVal) seriesVal = 0
|
|
x = (seriesVal - w.globals.minX) / this.xRatio - barWidth / 2
|
|
|
|
if (w.globals.seriesGroups.length) {
|
|
x =
|
|
(seriesVal - w.globals.minX) / this.xRatio -
|
|
(barWidth / 2) * w.globals.seriesGroups.length
|
|
}
|
|
}
|
|
|
|
let barXPosition = x + (groupIndex !== -1 ? groupIndex * barWidth : 0)
|
|
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.config.series[i].name)
|
|
}
|
|
if (
|
|
(gsi > 0 && !w.globals.isXNumeric) ||
|
|
(gsi > 0 &&
|
|
w.globals.isXNumeric &&
|
|
w.globals.seriesX[i - 1][j] === w.globals.seriesX[i][j])
|
|
) {
|
|
let bYP
|
|
let prevYValue
|
|
const p = Math.min(this.yRatio.length + 1, i + 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[this.yaxisIndex] +
|
|
(this.isReversed
|
|
? this.series[i][j] / this.yRatio[this.yaxisIndex]
|
|
: 0) *
|
|
2
|
|
} else {
|
|
// fixes #3610
|
|
y = barYPosition
|
|
}
|
|
|
|
const paths = this.barHelpers.getColumnPaths({
|
|
barXPosition,
|
|
barWidth,
|
|
y1: barYPosition,
|
|
y2: y,
|
|
yRatio: this.yRatio[this.yaxisIndex],
|
|
strokeWidth: this.strokeWidth,
|
|
series: this.series,
|
|
seriesGroup,
|
|
realIndex: indexes.realIndex,
|
|
i,
|
|
j,
|
|
w,
|
|
})
|
|
|
|
this.barHelpers.barBackground({
|
|
bc,
|
|
j,
|
|
i,
|
|
x1: barXPosition,
|
|
x2: barWidth,
|
|
elSeries,
|
|
})
|
|
|
|
x = x + xDivision
|
|
|
|
return {
|
|
pathTo: paths.pathTo,
|
|
pathFrom: paths.pathFrom,
|
|
goalY: this.barHelpers.getGoalValues('y', null, zeroH, i, j),
|
|
barXPosition,
|
|
x: w.globals.isXNumeric ? x - xDivision : x,
|
|
y,
|
|
}
|
|
}
|
|
}
|
|
|
|
export default BarStacked
|