apexcharts
Version:
A JavaScript Chart Library
457 lines (381 loc) • 11.4 kB
JavaScript
import Bar from './Bar'
import Graphics from '../modules/Graphics'
import Utils from '../utils/Utils'
/**
* ApexCharts RangeBar Class responsible for drawing Range/Timeline Bars.
*
* @module RangeBar
**/
class RangeBar extends Bar {
draw(series, seriesIndex) {
let w = this.w
let graphics = new Graphics(this.ctx)
this.rangeBarOptions = this.w.config.plotOptions.rangeBar
this.series = series
this.seriesRangeStart = w.globals.seriesRangeStart
this.seriesRangeEnd = w.globals.seriesRangeEnd
this.barHelpers.initVariables(series)
let ret = graphics.group({
class: 'apexcharts-rangebar-series apexcharts-plot-series',
})
for (let i = 0; i < series.length; i++) {
let x,
y,
xDivision, // xDivision is the GRIDWIDTH divided by number of datapoints (columns)
yDivision, // yDivision is the GRIDHEIGHT divided by number of datapoints (bars)
zeroH, // zeroH is the baseline where 0 meets y axis
zeroW // zeroW is the baseline where 0 meets x axis
let realIndex = w.globals.comboCharts ? seriesIndex[i] : i
let { columnGroupIndex } = this.barHelpers.getGroupIndex(realIndex)
// el to which series will be drawn
let elSeries = graphics.group({
class: `apexcharts-series`,
seriesName: Utils.escapeString(w.globals.seriesNames[realIndex]),
rel: i + 1,
'data:realIndex': realIndex,
})
this.ctx.series.addCollapsedClassToSeries(elSeries, realIndex)
if (series[i].length > 0) {
this.visibleI = this.visibleI + 1
}
let barHeight = 0
let barWidth = 0
let translationsIndex = 0
if (this.yRatio.length > 1) {
this.yaxisIndex = w.globals.seriesYAxisReverseMap[realIndex][0]
translationsIndex = realIndex
}
let initPositions = this.barHelpers.initialPositions(realIndex)
y = initPositions.y
zeroW = initPositions.zeroW
x = initPositions.x
barWidth = initPositions.barWidth
barHeight = initPositions.barHeight
xDivision = initPositions.xDivision
yDivision = initPositions.yDivision
zeroH = initPositions.zeroH
// eldatalabels
let elDataLabelsWrap = graphics.group({
class: 'apexcharts-datalabels',
'data:realIndex': realIndex,
})
let 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 = 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 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 * this.visibleI
let srty = (yDivision - barHeight * seriesLen) / 2
if (w.config.series[i].data[j].x) {
let 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.globals.isXNumeric) {
x =
(w.globals.seriesX[i][j] - w.globals.minX) / this.xRatio -
barWidth / 2
}
barXPosition = x + barWidth * this.visibleI
let srtx = (xDivision - barWidth * seriesLen) / 2
if (w.config.series[i].data[j].x) {
let 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
let 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
}
detectOverlappingBars({
i,
j,
barYPosition,
barXPosition,
srty,
srtx,
barHeight,
barWidth,
yDivision,
xDivision,
initPositions,
}) {
const w = this.w
let overlaps = []
let rangeName = w.config.series[i].data[j].rangeName
const x = w.config.series[i].data[j].x
const labelX = Array.isArray(x) ? x.join(' ') : x
const rowIndex = w.globals.labels
.map((_) => (Array.isArray(_) ? _.join(' ') : _))
.indexOf(labelX)
const overlappedIndex = w.globals.seriesRange[i].findIndex(
(tx) => tx.x === labelX && tx.overlaps.length > 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 = w.globals.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.globals.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 = w.globals.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,
}
}
drawRangeColumnPaths({
indexes,
x,
xDivision,
barWidth,
barXPosition,
zeroH,
}) {
let 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 this.series[i][j] === 'undefined' ||
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.globals.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',
null,
zeroH,
i,
j,
translationsIndex
),
barXPosition,
}
}
preventBarOverflow(val) {
const w = this.w
if (val < 0) {
val = 0
}
if (val > w.globals.gridWidth) {
val = w.globals.gridWidth
}
return val
}
drawRangeBarPaths({
indexes,
y,
y1,
y2,
yDivision,
barHeight,
barYPosition,
zeroW,
}) {
let w = this.w
const { realIndex, j } = indexes
let x1 = this.preventBarOverflow(zeroW + y1 / this.invertedYRatio)
let 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.globals.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, null, realIndex, j),
y,
}
}
getRangeValue(i, j) {
const w = this.w
return {
start: w.globals.seriesRangeStart[i][j],
end: w.globals.seriesRangeEnd[i][j],
}
}
}
export default RangeBar