Current File : /home/tradevaly/www/node_modules/apexcharts/src/modules/DataLabels.js |
import Scatter from './../charts/Scatter'
import Graphics from './Graphics'
import Filters from './Filters'
/**
* ApexCharts DataLabels Class for drawing dataLabels on Axes based Charts.
*
* @module DataLabels
**/
class DataLabels {
constructor(ctx) {
this.ctx = ctx
this.w = ctx.w
}
// When there are many datalabels to be printed, and some of them overlaps each other in the same series, this method will take care of that
// Also, when datalabels exceeds the drawable area and get clipped off, we need to adjust and move some pixels to make them visible again
dataLabelsCorrection(
x,
y,
val,
i,
dataPointIndex,
alwaysDrawDataLabel,
fontSize
) {
let w = this.w
let graphics = new Graphics(this.ctx)
let drawnextLabel = false //
let textRects = graphics.getTextRects(val, fontSize)
let width = textRects.width
let height = textRects.height
if (y < 0) y = 0
if (y > w.globals.gridHeight + height) y = w.globals.gridHeight + height / 2
// first value in series, so push an empty array
if (typeof w.globals.dataLabelsRects[i] === 'undefined')
w.globals.dataLabelsRects[i] = []
// then start pushing actual rects in that sub-array
w.globals.dataLabelsRects[i].push({ x, y, width, height })
let len = w.globals.dataLabelsRects[i].length - 2
let lastDrawnIndex =
typeof w.globals.lastDrawnDataLabelsIndexes[i] !== 'undefined'
? w.globals.lastDrawnDataLabelsIndexes[i][
w.globals.lastDrawnDataLabelsIndexes[i].length - 1
]
: 0
if (typeof w.globals.dataLabelsRects[i][len] !== 'undefined') {
let lastDataLabelRect = w.globals.dataLabelsRects[i][lastDrawnIndex]
if (
// next label forward and x not intersecting
x > lastDataLabelRect.x + lastDataLabelRect.width + 2 ||
y > lastDataLabelRect.y + lastDataLabelRect.height + 2 ||
x + width < lastDataLabelRect.x // next label is going to be drawn backwards
) {
// the 2 indexes don't override, so OK to draw next label
drawnextLabel = true
}
}
if (dataPointIndex === 0 || alwaysDrawDataLabel) {
drawnextLabel = true
}
return {
x,
y,
textRects,
drawnextLabel
}
}
drawDataLabel(pos, i, j, z = null, strokeWidth = 2) {
// this method handles line, area, bubble, scatter charts as those charts contains markers/points which have pre-defined x/y positions
// all other charts like radar / bars / heatmaps will define their own drawDataLabel routine
let w = this.w
const graphics = new Graphics(this.ctx)
let dataLabelsConfig = w.config.dataLabels
let x = 0
let y = 0
let dataPointIndex = j
let elDataLabelsWrap = null
if (!dataLabelsConfig.enabled || !Array.isArray(pos.x)) {
return elDataLabelsWrap
}
elDataLabelsWrap = graphics.group({
class: 'apexcharts-data-labels'
})
for (let q = 0; q < pos.x.length; q++) {
x = pos.x[q] + dataLabelsConfig.offsetX
y = pos.y[q] + dataLabelsConfig.offsetY + strokeWidth
if (!isNaN(x)) {
// a small hack as we have 2 points for the first val to connect it
if (j === 1 && q === 0) dataPointIndex = 0
if (j === 1 && q === 1) dataPointIndex = 1
let val = w.globals.series[i][dataPointIndex]
let text = ''
const getText = (v) => {
return w.config.dataLabels.formatter(v, {
ctx: this.ctx,
seriesIndex: i,
dataPointIndex,
w
})
}
if (w.config.chart.type === 'bubble') {
val = w.globals.seriesZ[i][dataPointIndex]
text = getText(val)
y = pos.y[q]
const scatter = new Scatter(this.ctx)
let centerTextInBubbleCoords = scatter.centerTextInBubble(
y,
i,
dataPointIndex
)
y = centerTextInBubbleCoords.y
} else {
if (typeof val !== 'undefined') {
text = getText(val)
}
}
this.plotDataLabelsText({
x,
y,
text,
i,
j: dataPointIndex,
parent: elDataLabelsWrap,
offsetCorrection: true,
dataLabelsConfig: w.config.dataLabels
})
}
}
return elDataLabelsWrap
}
plotDataLabelsText(opts) {
let w = this.w
let graphics = new Graphics(this.ctx)
let {
x,
y,
i,
j,
text,
textAnchor,
fontSize,
parent,
dataLabelsConfig,
color,
alwaysDrawDataLabel,
offsetCorrection
} = opts
if (Array.isArray(w.config.dataLabels.enabledOnSeries)) {
if (w.config.dataLabels.enabledOnSeries.indexOf(i) < 0) {
return
}
}
let correctedLabels = {
x,
y,
drawnextLabel: true
}
if (offsetCorrection) {
correctedLabels = this.dataLabelsCorrection(
x,
y,
text,
i,
j,
alwaysDrawDataLabel,
parseInt(dataLabelsConfig.style.fontSize, 10)
)
}
// when zoomed, we don't need to correct labels offsets,
// but if normally, labels get cropped, correct them
if (!w.globals.zoomed) {
x = correctedLabels.x
y = correctedLabels.y
}
if (correctedLabels.textRects) {
// fixes #2264
if (
x < -10 - correctedLabels.textRects.width ||
x > w.globals.gridWidth + correctedLabels.textRects.width + 10
) {
// datalabels fall outside drawing area, so draw a blank label
text = ''
}
}
let dataLabelColor = w.globals.dataLabels.style.colors[i]
if (
((w.config.chart.type === 'bar' || w.config.chart.type === 'rangeBar') &&
w.config.plotOptions.bar.distributed) ||
w.config.dataLabels.distributed
) {
dataLabelColor = w.globals.dataLabels.style.colors[j]
}
if (typeof dataLabelColor === 'function') {
dataLabelColor = dataLabelColor({
series: w.globals.series,
seriesIndex: i,
dataPointIndex: j,
w
})
}
if (color) {
dataLabelColor = color
}
let offX = dataLabelsConfig.offsetX
let offY = dataLabelsConfig.offsetY
if (w.config.chart.type === 'bar' || w.config.chart.type === 'rangeBar') {
// for certain chart types, we handle offsets while calculating datalabels pos
// why? because bars/column may have negative values and based on that
// offsets becomes reversed
offX = 0
offY = 0
}
if (correctedLabels.drawnextLabel) {
let dataLabelText = graphics.drawText({
width: 100,
height: parseInt(dataLabelsConfig.style.fontSize, 10),
x: x + offX,
y: y + offY,
foreColor: dataLabelColor,
textAnchor: textAnchor || dataLabelsConfig.textAnchor,
text,
fontSize: fontSize || dataLabelsConfig.style.fontSize,
fontFamily: dataLabelsConfig.style.fontFamily,
fontWeight: dataLabelsConfig.style.fontWeight || 'normal'
})
dataLabelText.attr({
class: 'apexcharts-datalabel',
cx: x,
cy: y
})
if (dataLabelsConfig.dropShadow.enabled) {
const textShadow = dataLabelsConfig.dropShadow
const filters = new Filters(this.ctx)
filters.dropShadow(dataLabelText, textShadow)
}
parent.add(dataLabelText)
if (typeof w.globals.lastDrawnDataLabelsIndexes[i] === 'undefined') {
w.globals.lastDrawnDataLabelsIndexes[i] = []
}
w.globals.lastDrawnDataLabelsIndexes[i].push(j)
}
}
addBackgroundToDataLabel(el, coords) {
const w = this.w
const bCnf = w.config.dataLabels.background
const paddingH = bCnf.padding
const paddingV = bCnf.padding / 2
const width = coords.width
const height = coords.height
const graphics = new Graphics(this.ctx)
const elRect = graphics.drawRect(
coords.x - paddingH,
coords.y - paddingV / 2,
width + paddingH * 2,
height + paddingV,
bCnf.borderRadius,
w.config.chart.background === 'transparent'
? '#fff'
: w.config.chart.background,
bCnf.opacity,
bCnf.borderWidth,
bCnf.borderColor
)
if (bCnf.dropShadow.enabled) {
const filters = new Filters(this.ctx)
filters.dropShadow(elRect, bCnf.dropShadow)
}
return elRect
}
dataLabelsBackground() {
const w = this.w
if (w.config.chart.type === 'bubble') return
const elDataLabels = w.globals.dom.baseEl.querySelectorAll(
'.apexcharts-datalabels text'
)
for (let i = 0; i < elDataLabels.length; i++) {
const el = elDataLabels[i]
const coords = el.getBBox()
let elRect = null
if (coords.width && coords.height) {
elRect = this.addBackgroundToDataLabel(el, coords)
}
if (elRect) {
el.parentNode.insertBefore(elRect.node, el)
const background = el.getAttribute('fill')
const shouldAnim =
w.config.chart.animations.enabled &&
!w.globals.resized &&
!w.globals.dataChanged
if (shouldAnim) {
elRect.animate().attr({ fill: background })
} else {
elRect.attr({ fill: background })
}
el.setAttribute('fill', w.config.dataLabels.background.foreColor)
}
}
}
bringForward() {
const w = this.w
const elDataLabelsNodes = w.globals.dom.baseEl.querySelectorAll(
'.apexcharts-datalabels'
)
const elSeries = w.globals.dom.baseEl.querySelector(
'.apexcharts-plot-series:last-child'
)
for (let i = 0; i < elDataLabelsNodes.length; i++) {
if (elSeries) {
elSeries.insertBefore(elDataLabelsNodes[i], elSeries.nextSibling)
}
}
}
}
export default DataLabels