UNPKG

apexcharts

Version:

A JavaScript Chart Library

692 lines (622 loc) 18.4 kB
import Graphics from '../../../modules/Graphics' import DataLabels from '../../../modules/DataLabels' export default class BarDataLabels { constructor(barCtx) { this.w = barCtx.w this.barCtx = barCtx this.totalFormatter = this.w.config.plotOptions.bar.dataLabels.total.formatter if (!this.totalFormatter) { this.totalFormatter = this.w.config.dataLabels.formatter } } /** handleBarDataLabels is used to calculate the positions for the data-labels * It also sets the element's data attr for bars and calls drawCalculatedBarDataLabels() * After calculating, it also calls the function to draw data labels * @memberof Bar * @param {object} {barProps} most of the bar properties used throughout the bar * drawing function * @return {object} dataLabels node-element which you can append later **/ handleBarDataLabels(opts) { let { x, y, y1, y2, i, j, realIndex, columnGroupIndex, series, barHeight, barWidth, barXPosition, barYPosition, visibleSeries, } = opts let w = this.w let graphics = new Graphics(this.barCtx.ctx) let strokeWidth = Array.isArray(this.barCtx.strokeWidth) ? this.barCtx.strokeWidth[realIndex] : this.barCtx.strokeWidth let bcx let bcy if (w.globals.isXNumeric && !w.globals.isBarHorizontal) { bcx = x + parseFloat(barWidth * (visibleSeries + 1)) bcy = y + parseFloat(barHeight * (visibleSeries + 1)) - strokeWidth } else { bcx = x + parseFloat(barWidth * visibleSeries) bcy = y + parseFloat(barHeight * visibleSeries) } let dataLabels = null let totalDataLabels = null let dataLabelsX = x let dataLabelsY = y let dataLabelsPos = {} let dataLabelsConfig = w.config.dataLabels let barDataLabelsConfig = this.barCtx.barOptions.dataLabels let barTotalDataLabelsConfig = this.barCtx.barOptions.dataLabels.total if (typeof barYPosition !== 'undefined' && this.barCtx.isRangeBar) { bcy = barYPosition dataLabelsY = barYPosition } if ( typeof barXPosition !== 'undefined' && this.barCtx.isVerticalGroupedRangeBar ) { bcx = barXPosition dataLabelsX = barXPosition } const offX = dataLabelsConfig.offsetX const offY = dataLabelsConfig.offsetY let textRects = { width: 0, height: 0, } if (w.config.dataLabels.enabled) { const yLabel = w.globals.series[i][j] textRects = graphics.getTextRects( w.config.dataLabels.formatter ? w.config.dataLabels.formatter(yLabel, { ...w, seriesIndex: i, dataPointIndex: j, w, }) : w.globals.yLabelFormatters[0](yLabel), parseFloat(dataLabelsConfig.style.fontSize) ) } const params = { x, y, i, j, realIndex, columnGroupIndex, bcx, bcy, barHeight, barWidth, textRects, strokeWidth, dataLabelsX, dataLabelsY, dataLabelsConfig, barDataLabelsConfig, barTotalDataLabelsConfig, offX, offY, } if (this.barCtx.isHorizontal) { dataLabelsPos = this.calculateBarsDataLabelsPosition(params) } else { dataLabelsPos = this.calculateColumnsDataLabelsPosition(params) } dataLabels = this.drawCalculatedDataLabels({ x: dataLabelsPos.dataLabelsX, y: dataLabelsPos.dataLabelsY, val: this.barCtx.isRangeBar ? [y1, y2] : w.config.chart.stackType === '100%' ? series[realIndex][j] : w.globals.series[realIndex][j], i: realIndex, j, barWidth, barHeight, textRects, dataLabelsConfig, }) if (w.config.chart.stacked && barTotalDataLabelsConfig.enabled) { totalDataLabels = this.drawTotalDataLabels({ x: dataLabelsPos.totalDataLabelsX, y: dataLabelsPos.totalDataLabelsY, barWidth, barHeight, realIndex, textAnchor: dataLabelsPos.totalDataLabelsAnchor, val: this.getStackedTotalDataLabel({ realIndex, j }), dataLabelsConfig, barTotalDataLabelsConfig, }) } return { dataLabelsPos, dataLabels, totalDataLabels, } } getStackedTotalDataLabel({ realIndex, j }) { const w = this.w let val = this.barCtx.stackedSeriesTotals[j] if (this.totalFormatter) { val = this.totalFormatter(val, { ...w, seriesIndex: realIndex, dataPointIndex: j, w, }) } return val } calculateColumnsDataLabelsPosition(opts) { const w = this.w let { i, j, realIndex, columnGroupIndex, y, bcx, barWidth, barHeight, textRects, dataLabelsX, dataLabelsY, dataLabelsConfig, barDataLabelsConfig, barTotalDataLabelsConfig, strokeWidth, offX, offY, } = opts let totalDataLabelsY let totalDataLabelsX let totalDataLabelsAnchor = 'middle' let totalDataLabelsBcx = bcx barHeight = Math.abs(barHeight) let vertical = w.config.plotOptions.bar.dataLabels.orientation === 'vertical' const { zeroEncounters } = this.barCtx.barHelpers.getZeroValueEncounters({ i, j, }) bcx = bcx - strokeWidth / 2 let dataPointsDividedWidth = w.globals.gridWidth / w.globals.dataPoints if (this.barCtx.isVerticalGroupedRangeBar) { dataLabelsX += barWidth / 2 } else { if (w.globals.isXNumeric) { dataLabelsX = bcx - barWidth / 2 + offX } else { dataLabelsX = bcx - dataPointsDividedWidth + barWidth / 2 + offX } if ( !w.config.chart.stacked && zeroEncounters > 0 && w.config.plotOptions.bar.hideZeroBarsWhenGrouped ) { dataLabelsX -= barWidth * zeroEncounters } } if (vertical) { const offsetDLX = 2 dataLabelsX = dataLabelsX + textRects.height / 2 - strokeWidth / 2 - offsetDLX } let valIsNegative = w.globals.series[i][j] < 0 let newY = y if (this.barCtx.isReversed) { newY = y + (valIsNegative ? barHeight : -barHeight) } switch (barDataLabelsConfig.position) { case 'center': if (vertical) { if (valIsNegative) { dataLabelsY = newY - barHeight / 2 + offY } else { dataLabelsY = newY + barHeight / 2 - offY } } else { if (valIsNegative) { dataLabelsY = newY - barHeight / 2 + textRects.height / 2 + offY } else { dataLabelsY = newY + barHeight / 2 + textRects.height / 2 - offY } } break case 'bottom': if (vertical) { if (valIsNegative) { dataLabelsY = newY - barHeight + offY } else { dataLabelsY = newY + barHeight - offY } } else { if (valIsNegative) { dataLabelsY = newY - barHeight + textRects.height + strokeWidth + offY } else { dataLabelsY = newY + barHeight - textRects.height / 2 + strokeWidth - offY } } break case 'top': if (vertical) { if (valIsNegative) { dataLabelsY = newY + offY } else { dataLabelsY = newY - offY } } else { if (valIsNegative) { dataLabelsY = newY - textRects.height / 2 - offY } else { dataLabelsY = newY + textRects.height + offY } } break } let lowestPrevY = newY w.globals.seriesGroups.forEach((sg) => { this.barCtx[sg.join(',')]?.prevY.forEach((arr) => { if (valIsNegative) { lowestPrevY = Math.max(arr[j], lowestPrevY) } else { lowestPrevY = Math.min(arr[j], lowestPrevY) } }) }) if ( this.barCtx.lastActiveBarSerieIndex === realIndex && barTotalDataLabelsConfig.enabled ) { const ADDITIONAL_OFFY = 18 const graphics = new Graphics(this.barCtx.ctx) const totalLabeltextRects = graphics.getTextRects( this.getStackedTotalDataLabel({ realIndex, j }), dataLabelsConfig.fontSize ) if (valIsNegative) { totalDataLabelsY = lowestPrevY - totalLabeltextRects.height / 2 - offY - barTotalDataLabelsConfig.offsetY + ADDITIONAL_OFFY } else { totalDataLabelsY = lowestPrevY + totalLabeltextRects.height + offY + barTotalDataLabelsConfig.offsetY - ADDITIONAL_OFFY } // width divided into equal parts let xDivision = dataPointsDividedWidth totalDataLabelsX = totalDataLabelsBcx + (w.globals.isXNumeric ? (-barWidth * w.globals.barGroups.length) / 2 : (w.globals.barGroups.length * barWidth) / 2 - (w.globals.barGroups.length - 1) * barWidth - xDivision) + barTotalDataLabelsConfig.offsetX } if (!w.config.chart.stacked) { if (dataLabelsY < 0) { dataLabelsY = 0 + strokeWidth } else if (dataLabelsY + textRects.height / 3 > w.globals.gridHeight) { dataLabelsY = w.globals.gridHeight - strokeWidth } } return { bcx, bcy: y, dataLabelsX, dataLabelsY, totalDataLabelsX, totalDataLabelsY, totalDataLabelsAnchor, } } calculateBarsDataLabelsPosition(opts) { const w = this.w let { x, i, j, realIndex, bcy, barHeight, barWidth, textRects, dataLabelsX, strokeWidth, dataLabelsConfig, barDataLabelsConfig, barTotalDataLabelsConfig, offX, offY, } = opts let dataPointsDividedHeight = w.globals.gridHeight / w.globals.dataPoints const { zeroEncounters } = this.barCtx.barHelpers.getZeroValueEncounters({ i, j, }) barWidth = Math.abs(barWidth) let dataLabelsY = bcy - (this.barCtx.isRangeBar ? 0 : dataPointsDividedHeight) + barHeight / 2 + textRects.height / 2 + offY - 3 if ( !w.config.chart.stacked && zeroEncounters > 0 && w.config.plotOptions.bar.hideZeroBarsWhenGrouped ) { dataLabelsY -= barHeight * zeroEncounters } let totalDataLabelsX let totalDataLabelsY let totalDataLabelsAnchor = 'start' let valIsNegative = w.globals.series[i][j] < 0 let newX = x if (this.barCtx.isReversed) { newX = x + (valIsNegative ? -barWidth : barWidth) totalDataLabelsAnchor = valIsNegative ? 'start' : 'end' } switch (barDataLabelsConfig.position) { case 'center': if (valIsNegative) { dataLabelsX = newX + barWidth / 2 - offX } else { dataLabelsX = Math.max(textRects.width / 2, newX - barWidth / 2) + offX } break case 'bottom': if (valIsNegative) { dataLabelsX = newX + barWidth - strokeWidth - offX } else { dataLabelsX = newX - barWidth + strokeWidth + offX } break case 'top': if (valIsNegative) { dataLabelsX = newX - strokeWidth - offX } else { dataLabelsX = newX - strokeWidth + offX } break } let lowestPrevX = newX w.globals.seriesGroups.forEach((sg) => { this.barCtx[sg.join(',')]?.prevX.forEach((arr) => { if (valIsNegative) { lowestPrevX = Math.min(arr[j], lowestPrevX) } else { lowestPrevX = Math.max(arr[j], lowestPrevX) } }) }) if ( this.barCtx.lastActiveBarSerieIndex === realIndex && barTotalDataLabelsConfig.enabled ) { const graphics = new Graphics(this.barCtx.ctx) const totalLabeltextRects = graphics.getTextRects( this.getStackedTotalDataLabel({ realIndex, j }), dataLabelsConfig.fontSize ) if (valIsNegative) { totalDataLabelsX = lowestPrevX - strokeWidth - offX - barTotalDataLabelsConfig.offsetX totalDataLabelsAnchor = 'end' } else { totalDataLabelsX = lowestPrevX + offX + barTotalDataLabelsConfig.offsetX + (this.barCtx.isReversed ? -(barWidth + strokeWidth) : strokeWidth) } totalDataLabelsY = dataLabelsY - textRects.height / 2 + totalLabeltextRects.height / 2 + barTotalDataLabelsConfig.offsetY + strokeWidth if (w.globals.barGroups.length > 1) { totalDataLabelsY = totalDataLabelsY - (w.globals.barGroups.length / 2) * (barHeight / 2) } } if (!w.config.chart.stacked) { if (dataLabelsConfig.textAnchor === 'start') { if (dataLabelsX - textRects.width < 0) { dataLabelsX = valIsNegative ? textRects.width + strokeWidth : strokeWidth } else if (dataLabelsX + textRects.width > w.globals.gridWidth) { dataLabelsX = valIsNegative ? w.globals.gridWidth - strokeWidth : w.globals.gridWidth - textRects.width - strokeWidth } } else if (dataLabelsConfig.textAnchor === 'middle') { if (dataLabelsX - textRects.width / 2 < 0) { dataLabelsX = textRects.width / 2 + strokeWidth } else if (dataLabelsX + textRects.width / 2 > w.globals.gridWidth) { dataLabelsX = w.globals.gridWidth - textRects.width / 2 - strokeWidth } } else if (dataLabelsConfig.textAnchor === 'end') { if (dataLabelsX < 1) { dataLabelsX = textRects.width + strokeWidth } else if (dataLabelsX + 1 > w.globals.gridWidth) { dataLabelsX = w.globals.gridWidth - textRects.width - strokeWidth } } } return { bcx: x, bcy, dataLabelsX, dataLabelsY, totalDataLabelsX, totalDataLabelsY, totalDataLabelsAnchor, } } drawCalculatedDataLabels({ x, y, val, i, // = realIndex j, textRects, barHeight, barWidth, dataLabelsConfig, }) { const w = this.w let rotate = 'rotate(0)' if (w.config.plotOptions.bar.dataLabels.orientation === 'vertical') rotate = `rotate(-90, ${x}, ${y})` const dataLabels = new DataLabels(this.barCtx.ctx) const graphics = new Graphics(this.barCtx.ctx) const formatter = dataLabelsConfig.formatter let elDataLabelsWrap = null const isSeriesNotCollapsed = w.globals.collapsedSeriesIndices.indexOf(i) > -1 if (dataLabelsConfig.enabled && !isSeriesNotCollapsed) { elDataLabelsWrap = graphics.group({ class: 'apexcharts-data-labels', transform: rotate, }) let text = '' if (typeof val !== 'undefined') { text = formatter(val, { ...w, seriesIndex: i, dataPointIndex: j, w, }) } if (!val && w.config.plotOptions.bar.hideZeroBarsWhenGrouped) { text = '' } let valIsNegative = w.globals.series[i][j] < 0 let position = w.config.plotOptions.bar.dataLabels.position if (w.config.plotOptions.bar.dataLabels.orientation === 'vertical') { if (position === 'top') { if (valIsNegative) dataLabelsConfig.textAnchor = 'end' else dataLabelsConfig.textAnchor = 'start' } if (position === 'center') { dataLabelsConfig.textAnchor = 'middle' } if (position === 'bottom') { if (valIsNegative) dataLabelsConfig.textAnchor = 'end' else dataLabelsConfig.textAnchor = 'start' } } if ( this.barCtx.isRangeBar && this.barCtx.barOptions.dataLabels.hideOverflowingLabels ) { // hide the datalabel if it cannot fit into the rect const txRect = graphics.getTextRects( text, parseFloat(dataLabelsConfig.style.fontSize) ) if (barWidth < txRect.width) { text = '' } } if ( w.config.chart.stacked && this.barCtx.barOptions.dataLabels.hideOverflowingLabels ) { // if there is not enough space to draw the label in the bar/column rect, check hideOverflowingLabels property to prevent overflowing on wrong rect // Note: This issue is only seen in stacked charts if (this.barCtx.isHorizontal) { if (textRects.width / 1.6 > Math.abs(barWidth)) { text = '' } } else { if (textRects.height / 1.6 > Math.abs(barHeight)) { text = '' } } } let modifiedDataLabelsConfig = { ...dataLabelsConfig, } if (this.barCtx.isHorizontal) { if (val < 0) { if (dataLabelsConfig.textAnchor === 'start') { modifiedDataLabelsConfig.textAnchor = 'end' } else if (dataLabelsConfig.textAnchor === 'end') { modifiedDataLabelsConfig.textAnchor = 'start' } } } dataLabels.plotDataLabelsText({ x, y, text, i, j, parent: elDataLabelsWrap, dataLabelsConfig: modifiedDataLabelsConfig, alwaysDrawDataLabel: true, offsetCorrection: true, }) } return elDataLabelsWrap } drawTotalDataLabels({ x, y, val, realIndex, textAnchor, barTotalDataLabelsConfig, }) { const w = this.w const graphics = new Graphics(this.barCtx.ctx) let totalDataLabelText if ( barTotalDataLabelsConfig.enabled && typeof x !== 'undefined' && typeof y !== 'undefined' && this.barCtx.lastActiveBarSerieIndex === realIndex ) { totalDataLabelText = graphics.drawText({ x: x, y: y, foreColor: barTotalDataLabelsConfig.style.color, text: val, textAnchor, fontFamily: barTotalDataLabelsConfig.style.fontFamily, fontSize: barTotalDataLabelsConfig.style.fontSize, fontWeight: barTotalDataLabelsConfig.style.fontWeight, }) } return totalDataLabelText } }