import Bar from './Bar' import Graphics from '../modules/Graphics' import Utils from '../utils/Utils' import DateTime from '../utils/DateTime' /** * 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 // 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 if (this.yRatio.length > 1) { this.yaxisIndex = realIndex } let initPositions = this.barHelpers.initialPositions() y = initPositions.y zeroW = initPositions.zeroW x = initPositions.x barWidth = initPositions.barWidth xDivision = initPositions.xDivision zeroH = initPositions.zeroH // eldatalabels let elDataLabelsWrap = graphics.group({ class: 'apexcharts-datalabels', 'data:realIndex': realIndex }) let elGoalsMarkers = graphics.group({ class: 'apexcharts-rangebar-goals-markers', style: `pointer-events: none` }) 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 barYPosition = null const params = { x, y, strokeWidth, elSeries } yDivision = initPositions.yDivision barHeight = initPositions.barHeight if (this.isHorizontal) { barYPosition = y + barHeight * this.visibleI let seriesLen = this.seriesLen if (w.config.plotOptions.bar.rangeBarGroupRows) { seriesLen = 1 } let srty = (yDivision - barHeight * seriesLen) / 2 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 (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 { paths = this.drawRangeColumnPaths({ indexes: { i, j, realIndex }, zeroH, barWidth, 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) let lineFill = w.globals.stroke.colors[realIndex] this.renderSeries({ realIndex, pathFill, lineFill, j, i, x, y, y1, y2, pathFrom: paths.pathFrom, pathTo: paths.pathTo, strokeWidth, elSeries, series, barHeight, barYPosition, barWidth, elDataLabelsWrap, elGoalsMarkers, visibleSeries: this.visibleI, type: 'rangebar' }) } ret.add(elSeries) } return ret } detectOverlappingBars({ i, j, barYPosition, srty, barHeight, yDivision, initPositions }) { const w = this.w let overlaps = [] let rangeName = w.config.series[i].data[j].rangeName const labelX = w.config.series[i].data[j].x const rowIndex = w.globals.labels.indexOf(labelX) const overlappedIndex = w.globals.seriesRangeBar[i].findIndex( (tx) => tx.x === labelX && tx.overlaps.length > 0 ) 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.seriesRangeBar[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 } } return { barYPosition, barHeight } } drawRangeColumnPaths({ indexes, x, strokeWidth, xDivision, barWidth, zeroH }) { let w = this.w let i = indexes.i let j = indexes.j const yRatio = this.yRatio[this.yaxisIndex] let realIndex = indexes.realIndex const range = this.getRangeValue(realIndex, j) let y1 = Math.min(range.start, range.end) let y2 = Math.max(range.start, range.end) if (w.globals.isXNumeric) { x = (w.globals.seriesX[i][j] - w.globals.minX) / this.xRatio - barWidth / 2 } let barXPosition = x + barWidth * this.visibleI 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: indexes.realIndex, i: realIndex, j, w }) if (!w.globals.isXNumeric) { x = x + xDivision } return { pathTo: paths.pathTo, pathFrom: paths.pathFrom, barHeight, x, y: y2, goalY: this.barHelpers.getGoalValues('y', null, zeroH, i, j), barXPosition } } drawRangeBarPaths({ indexes, y, y1, y2, yDivision, barHeight, barYPosition, zeroW }) { let w = this.w const x1 = zeroW + y1 / this.invertedYRatio const x2 = zeroW + y2 / this.invertedYRatio const barWidth = Math.abs(x2 - x1) const paths = this.barHelpers.getBarpaths({ barYPosition, barHeight, x1, x2, strokeWidth: this.strokeWidth, series: this.seriesRangeEnd, i: indexes.realIndex, realIndex: indexes.realIndex, j: indexes.j, w }) if (!w.globals.isXNumeric) { y = y + yDivision } return { pathTo: paths.pathTo, pathFrom: paths.pathFrom, barWidth, x: x2, goalX: this.barHelpers.getGoalValues( 'x', zeroW, null, indexes.realIndex, indexes.j ), y } } getRangeValue(i, j) { const w = this.w return { start: w.globals.seriesRangeStart[i][j], end: w.globals.seriesRangeEnd[i][j] } } getTooltipValues({ ctx, seriesIndex, dataPointIndex, y1, y2, w }) { let start = w.globals.seriesRangeStart[seriesIndex][dataPointIndex] let end = w.globals.seriesRangeEnd[seriesIndex][dataPointIndex] let ylabel = w.globals.labels[dataPointIndex] let seriesName = w.config.series[seriesIndex].name ? w.config.series[seriesIndex].name : '' const yLbFormatter = w.config.tooltip.y.formatter const yLbTitleFormatter = w.config.tooltip.y.title.formatter const opts = { w, seriesIndex, dataPointIndex, start, end } if (typeof yLbTitleFormatter === 'function') { seriesName = yLbTitleFormatter(seriesName, opts) } if (Number.isFinite(y1) && Number.isFinite(y2)) { start = y1 end = y2 if (w.config.series[seriesIndex].data[dataPointIndex].x) { ylabel = w.config.series[seriesIndex].data[dataPointIndex].x + ':' } if (typeof yLbFormatter === 'function') { ylabel = yLbFormatter(ylabel, opts) } } let startVal = '' let endVal = '' const color = w.globals.colors[seriesIndex] if (w.config.tooltip.x.formatter === undefined) { if (w.config.xaxis.type === 'datetime') { let datetimeObj = new DateTime(ctx) startVal = datetimeObj.formatDate( datetimeObj.getDate(start), w.config.tooltip.x.format ) endVal = datetimeObj.formatDate( datetimeObj.getDate(end), w.config.tooltip.x.format ) } else { startVal = start endVal = end } } else { startVal = w.config.tooltip.x.formatter(start) endVal = w.config.tooltip.x.formatter(end) } return { start, end, startVal, endVal, ylabel, color, seriesName } } buildCustomTooltipHTML({ color, seriesName, ylabel, start, end }) { return ( '
' + '
' + (seriesName ? seriesName : '') + '
' + '
' + ylabel + ' ' + start + ' - ' + end + '
' + '
' ) } } export default RangeBar