feat:Added piechart in Dashboard

This commit is contained in:
2025-02-22 15:35:48 +05:30
parent 357071b967
commit f7cb1af2c4
384 changed files with 112765 additions and 8 deletions
+193
View File
@@ -0,0 +1,193 @@
/**
* ApexCharts Tooltip.AxesTooltip Class.
* This file deals with the x-axis and y-axis tooltips.
*
* @module Tooltip.AxesTooltip
**/
class AxesTooltip {
constructor(tooltipContext) {
this.w = tooltipContext.w
this.ttCtx = tooltipContext
}
/**
* This method adds the secondary tooltip which appears below x axis
* @memberof Tooltip
**/
drawXaxisTooltip() {
let w = this.w
const ttCtx = this.ttCtx
const isBottom = w.config.xaxis.position === 'bottom'
ttCtx.xaxisOffY = isBottom
? w.globals.gridHeight + 1
: -w.globals.xAxisHeight - w.config.xaxis.axisTicks.height + 3
const tooltipCssClass = isBottom
? 'apexcharts-xaxistooltip apexcharts-xaxistooltip-bottom'
: 'apexcharts-xaxistooltip apexcharts-xaxistooltip-top'
let renderTo = w.globals.dom.elWrap
if (ttCtx.isXAxisTooltipEnabled) {
let xaxisTooltip = w.globals.dom.baseEl.querySelector(
'.apexcharts-xaxistooltip'
)
if (xaxisTooltip === null) {
ttCtx.xaxisTooltip = document.createElement('div')
ttCtx.xaxisTooltip.setAttribute(
'class',
tooltipCssClass + ' apexcharts-theme-' + w.config.tooltip.theme
)
renderTo.appendChild(ttCtx.xaxisTooltip)
ttCtx.xaxisTooltipText = document.createElement('div')
ttCtx.xaxisTooltipText.classList.add('apexcharts-xaxistooltip-text')
ttCtx.xaxisTooltipText.style.fontFamily =
w.config.xaxis.tooltip.style.fontFamily || w.config.chart.fontFamily
ttCtx.xaxisTooltipText.style.fontSize =
w.config.xaxis.tooltip.style.fontSize
ttCtx.xaxisTooltip.appendChild(ttCtx.xaxisTooltipText)
}
}
}
/**
* This method adds the secondary tooltip which appears below x axis
* @memberof Tooltip
**/
drawYaxisTooltip() {
let w = this.w
const ttCtx = this.ttCtx
for (let i = 0; i < w.config.yaxis.length; i++) {
const isRight =
w.config.yaxis[i].opposite || w.config.yaxis[i].crosshairs.opposite
ttCtx.yaxisOffX = isRight ? w.globals.gridWidth + 1 : 1
let tooltipCssClass = isRight
? `apexcharts-yaxistooltip apexcharts-yaxistooltip-${i} apexcharts-yaxistooltip-right`
: `apexcharts-yaxistooltip apexcharts-yaxistooltip-${i} apexcharts-yaxistooltip-left`
w.globals.yAxisSameScaleIndices.map((samescales, ssi) => {
samescales.map((s, si) => {
if (si === i) {
tooltipCssClass += w.config.yaxis[si].show
? ` `
: ` apexcharts-yaxistooltip-hidden`
}
})
})
let renderTo = w.globals.dom.elWrap
let yaxisTooltip = w.globals.dom.baseEl.querySelector(
`.apexcharts-yaxistooltip apexcharts-yaxistooltip-${i}`
)
if (yaxisTooltip === null) {
ttCtx.yaxisTooltip = document.createElement('div')
ttCtx.yaxisTooltip.setAttribute(
'class',
tooltipCssClass + ' apexcharts-theme-' + w.config.tooltip.theme
)
renderTo.appendChild(ttCtx.yaxisTooltip)
if (i === 0) ttCtx.yaxisTooltipText = []
ttCtx.yaxisTooltipText[i] = document.createElement('div')
ttCtx.yaxisTooltipText[i].classList.add('apexcharts-yaxistooltip-text')
ttCtx.yaxisTooltip.appendChild(ttCtx.yaxisTooltipText[i])
}
}
}
/**
* @memberof Tooltip
**/
setXCrosshairWidth() {
let w = this.w
const ttCtx = this.ttCtx
// set xcrosshairs width
const xcrosshairs = ttCtx.getElXCrosshairs()
ttCtx.xcrosshairsWidth = parseInt(w.config.xaxis.crosshairs.width, 10)
if (!w.globals.comboCharts) {
if (w.config.xaxis.crosshairs.width === 'tickWidth') {
let count = w.globals.labels.length
ttCtx.xcrosshairsWidth = w.globals.gridWidth / count
} else if (w.config.xaxis.crosshairs.width === 'barWidth') {
let bar = w.globals.dom.baseEl.querySelector('.apexcharts-bar-area')
if (bar !== null) {
let barWidth = parseFloat(bar.getAttribute('barWidth'))
ttCtx.xcrosshairsWidth = barWidth
} else {
ttCtx.xcrosshairsWidth = 1
}
}
} else {
let bar = w.globals.dom.baseEl.querySelector('.apexcharts-bar-area')
if (bar !== null && w.config.xaxis.crosshairs.width === 'barWidth') {
let barWidth = parseFloat(bar.getAttribute('barWidth'))
ttCtx.xcrosshairsWidth = barWidth
} else {
if (w.config.xaxis.crosshairs.width === 'tickWidth') {
let count = w.globals.labels.length
ttCtx.xcrosshairsWidth = w.globals.gridWidth / count
}
}
}
if (w.globals.isBarHorizontal) {
ttCtx.xcrosshairsWidth = 0
}
if (xcrosshairs !== null && ttCtx.xcrosshairsWidth > 0) {
xcrosshairs.setAttribute('width', ttCtx.xcrosshairsWidth)
}
}
handleYCrosshair() {
let w = this.w
const ttCtx = this.ttCtx
// set ycrosshairs height
ttCtx.ycrosshairs = w.globals.dom.baseEl.querySelector(
'.apexcharts-ycrosshairs'
)
ttCtx.ycrosshairsHidden = w.globals.dom.baseEl.querySelector(
'.apexcharts-ycrosshairs-hidden'
)
}
drawYaxisTooltipText(index, clientY, xyRatios) {
const ttCtx = this.ttCtx
const w = this.w
let lbFormatter = w.globals.yLabelFormatters[index]
if (ttCtx.yaxisTooltips[index]) {
const elGrid = ttCtx.getElGrid()
const seriesBound = elGrid.getBoundingClientRect()
const hoverY = (clientY - seriesBound.top) * xyRatios.yRatio[index]
const height = w.globals.maxYArr[index] - w.globals.minYArr[index]
const val = w.globals.minYArr[index] + (height - hoverY)
ttCtx.tooltipPosition.moveYCrosshairs(clientY - seriesBound.top)
ttCtx.yaxisTooltipText[index].innerHTML = lbFormatter(val)
ttCtx.tooltipPosition.moveYAxisTooltip(index)
}
}
}
export default AxesTooltip
+366
View File
@@ -0,0 +1,366 @@
import Utils from '../../utils/Utils'
/**
* ApexCharts Tooltip.Intersect Class.
* This file deals with functions related to intersecting tooltips
* (tooltips that appear when user hovers directly over a data-point whether)
*
* @module Tooltip.Intersect
**/
class Intersect {
constructor(tooltipContext) {
this.w = tooltipContext.w
const w = this.w
this.ttCtx = tooltipContext
this.isVerticalGroupedRangeBar =
!w.globals.isBarHorizontal &&
w.config.chart.type === 'rangeBar' &&
w.config.plotOptions.bar.rangeBarGroupRows
}
// a helper function to get an element's attribute value
getAttr(e, attr) {
return parseFloat(e.target.getAttribute(attr))
}
// handle tooltip for heatmaps and treemaps
handleHeatTreeTooltip({ e, opt, x, y, type }) {
const ttCtx = this.ttCtx
const w = this.w
if (e.target.classList.contains(`apexcharts-${type}-rect`)) {
let i = this.getAttr(e, 'i')
let j = this.getAttr(e, 'j')
let cx = this.getAttr(e, 'cx')
let cy = this.getAttr(e, 'cy')
let width = this.getAttr(e, 'width')
let height = this.getAttr(e, 'height')
ttCtx.tooltipLabels.drawSeriesTexts({
ttItems: opt.ttItems,
i,
j,
shared: false,
e,
})
w.globals.capturedSeriesIndex = i
w.globals.capturedDataPointIndex = j
x = cx + ttCtx.tooltipRect.ttWidth / 2 + width
y = cy + ttCtx.tooltipRect.ttHeight / 2 - height / 2
ttCtx.tooltipPosition.moveXCrosshairs(cx + width / 2)
if (x > w.globals.gridWidth / 2) {
x = cx - ttCtx.tooltipRect.ttWidth / 2 + width
}
if (ttCtx.w.config.tooltip.followCursor) {
let seriesBound = w.globals.dom.elWrap.getBoundingClientRect()
x =
w.globals.clientX -
seriesBound.left -
(x > w.globals.gridWidth / 2 ? ttCtx.tooltipRect.ttWidth : 0)
y =
w.globals.clientY -
seriesBound.top -
(y > w.globals.gridHeight / 2 ? ttCtx.tooltipRect.ttHeight : 0)
}
}
return {
x,
y,
}
}
/**
* handle tooltips for line/area/scatter charts where tooltip.intersect is true
* when user hovers over the marker directly, this function is executed
*/
handleMarkerTooltip({ e, opt, x, y }) {
let w = this.w
const ttCtx = this.ttCtx
let i
let j
if (e.target.classList.contains('apexcharts-marker')) {
let cx = parseInt(opt.paths.getAttribute('cx'), 10)
let cy = parseInt(opt.paths.getAttribute('cy'), 10)
let val = parseFloat(opt.paths.getAttribute('val'))
j = parseInt(opt.paths.getAttribute('rel'), 10)
i =
parseInt(
opt.paths.parentNode.parentNode.parentNode.getAttribute('rel'),
10
) - 1
if (ttCtx.intersect) {
const el = Utils.findAncestor(opt.paths, 'apexcharts-series')
if (el) {
i = parseInt(el.getAttribute('data:realIndex'), 10)
}
}
ttCtx.tooltipLabels.drawSeriesTexts({
ttItems: opt.ttItems,
i,
j,
shared: ttCtx.showOnIntersect ? false : w.config.tooltip.shared,
e,
})
if (e.type === 'mouseup') {
ttCtx.markerClick(e, i, j)
}
w.globals.capturedSeriesIndex = i
w.globals.capturedDataPointIndex = j
x = cx
y = cy + w.globals.translateY - ttCtx.tooltipRect.ttHeight * 1.4
if (ttCtx.w.config.tooltip.followCursor) {
const elGrid = ttCtx.getElGrid()
const seriesBound = elGrid.getBoundingClientRect()
y = ttCtx.e.clientY + w.globals.translateY - seriesBound.top
}
if (val < 0) {
y = cy
}
ttCtx.marker.enlargeCurrentPoint(j, opt.paths, x, y)
}
return {
x,
y,
}
}
/**
* handle tooltips for bar/column charts
*/
handleBarTooltip({ e, opt }) {
const w = this.w
const ttCtx = this.ttCtx
const tooltipEl = ttCtx.getElTooltip()
let bx = 0
let x = 0
let y = 0
let i = 0
let strokeWidth
let barXY = this.getBarTooltipXY({
e,
opt,
})
i = barXY.i
let barHeight = barXY.barHeight
let j = barXY.j
w.globals.capturedSeriesIndex = i
w.globals.capturedDataPointIndex = j
if (
(w.globals.isBarHorizontal && ttCtx.tooltipUtil.hasBars()) ||
!w.config.tooltip.shared
) {
x = barXY.x
y = barXY.y
strokeWidth = Array.isArray(w.config.stroke.width)
? w.config.stroke.width[i]
: w.config.stroke.width
bx = x
} else {
if (!w.globals.comboCharts && !w.config.tooltip.shared) {
// todo: re-check this condition as it's always 0
bx = bx / 2
}
}
// y is NaN, make it touch the bottom of grid area
if (isNaN(y)) {
y = w.globals.svgHeight - ttCtx.tooltipRect.ttHeight
}
const seriesIndex = parseInt(
opt.paths.parentNode.getAttribute('data:realIndex'),
10
)
const isReversed = w.globals.isMultipleYAxis
? w.config.yaxis[seriesIndex] && w.config.yaxis[seriesIndex].reversed
: w.config.yaxis[0].reversed
if (x + ttCtx.tooltipRect.ttWidth > w.globals.gridWidth && !isReversed) {
x = x - ttCtx.tooltipRect.ttWidth
} else if (x < 0) {
x = 0
}
if (ttCtx.w.config.tooltip.followCursor) {
const elGrid = ttCtx.getElGrid()
const seriesBound = elGrid.getBoundingClientRect()
y = ttCtx.e.clientY - seriesBound.top
}
// if tooltip is still null, querySelector
if (ttCtx.tooltip === null) {
ttCtx.tooltip = w.globals.dom.baseEl.querySelector('.apexcharts-tooltip')
}
if (!w.config.tooltip.shared) {
if (w.globals.comboBarCount > 0) {
ttCtx.tooltipPosition.moveXCrosshairs(bx + strokeWidth / 2)
} else {
ttCtx.tooltipPosition.moveXCrosshairs(bx)
}
}
// move tooltip here
if (
!ttCtx.fixedTooltip &&
(!w.config.tooltip.shared ||
(w.globals.isBarHorizontal && ttCtx.tooltipUtil.hasBars()))
) {
if (isReversed) {
x = x - ttCtx.tooltipRect.ttWidth
if (x < 0) {
x = 0
}
}
if (
isReversed &&
!(w.globals.isBarHorizontal && ttCtx.tooltipUtil.hasBars())
) {
y = y + barHeight - (w.globals.series[i][j] < 0 ? barHeight : 0) * 2
}
y = y + w.globals.translateY - ttCtx.tooltipRect.ttHeight / 2
tooltipEl.style.left = x + w.globals.translateX + 'px'
tooltipEl.style.top = y + 'px'
}
}
getBarTooltipXY({ e, opt }) {
let w = this.w
let j = null
const ttCtx = this.ttCtx
let i = 0
let x = 0
let y = 0
let barWidth = 0
let barHeight = 0
const cl = e.target.classList
if (
cl.contains('apexcharts-bar-area') ||
cl.contains('apexcharts-candlestick-area') ||
cl.contains('apexcharts-boxPlot-area') ||
cl.contains('apexcharts-rangebar-area')
) {
let bar = e.target
let barRect = bar.getBoundingClientRect()
let seriesBound = opt.elGrid.getBoundingClientRect()
let bh = barRect.height
barHeight = barRect.height
let bw = barRect.width
let cx = parseInt(bar.getAttribute('cx'), 10)
let cy = parseInt(bar.getAttribute('cy'), 10)
barWidth = parseFloat(bar.getAttribute('barWidth'))
const clientX = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX
j = parseInt(bar.getAttribute('j'), 10)
i = parseInt(bar.parentNode.getAttribute('rel'), 10) - 1
let y1 = bar.getAttribute('data-range-y1')
let y2 = bar.getAttribute('data-range-y2')
if (w.globals.comboCharts) {
i = parseInt(bar.parentNode.getAttribute('data:realIndex'), 10)
}
// if (w.config.tooltip.shared) {
// this check not needed at the moment
// const yDivisor = w.globals.gridHeight / (w.globals.series.length)
// const hoverY = ttCtx.clientY - ttCtx.seriesBound.top
// j = Math.ceil(hoverY / yDivisor)
// }
const handleXForColumns = (x) => {
if (w.globals.isXNumeric) {
x = cx - bw / 2
} else {
if (this.isVerticalGroupedRangeBar) {
x = cx + bw / 2
} else {
x = cx - ttCtx.dataPointsDividedWidth + bw / 2
}
}
return x
}
const handleYForBars = () => {
return (
cy -
ttCtx.dataPointsDividedHeight +
bh / 2 -
ttCtx.tooltipRect.ttHeight / 2
)
}
ttCtx.tooltipLabels.drawSeriesTexts({
ttItems: opt.ttItems,
i,
j,
y1: y1 ? parseInt(y1, 10) : null,
y2: y2 ? parseInt(y2, 10) : null,
shared: ttCtx.showOnIntersect ? false : w.config.tooltip.shared,
e,
})
if (w.config.tooltip.followCursor) {
if (w.globals.isBarHorizontal) {
x = clientX - seriesBound.left + 15
y = handleYForBars()
} else {
x = handleXForColumns(x)
y = e.clientY - seriesBound.top - ttCtx.tooltipRect.ttHeight / 2 - 15
}
} else {
if (w.globals.isBarHorizontal) {
x = cx
if (x < ttCtx.xyRatios.baseLineInvertedY) {
x = cx - ttCtx.tooltipRect.ttWidth
}
y = handleYForBars()
} else {
x = handleXForColumns(x)
y = cy // - ttCtx.tooltipRect.ttHeight / 2 + 10
}
}
}
return {
x,
y,
barHeight,
barWidth,
i,
j,
}
}
}
export default Intersect
+512
View File
@@ -0,0 +1,512 @@
import Formatters from '../Formatters'
import DateTime from '../../utils/DateTime'
import Utils from './Utils'
/**
* ApexCharts Tooltip.Labels Class to draw texts on the tooltip.
* This file deals with printing actual text on the tooltip.
*
* @module Tooltip.Labels
**/
export default class Labels {
constructor(tooltipContext) {
this.w = tooltipContext.w
this.ctx = tooltipContext.ctx
this.ttCtx = tooltipContext
this.tooltipUtil = new Utils(tooltipContext)
}
drawSeriesTexts({ shared = true, ttItems, i = 0, j = null, y1, y2, e }) {
let w = this.w
if (w.config.tooltip.custom !== undefined) {
this.handleCustomTooltip({ i, j, y1, y2, w })
} else {
this.toggleActiveInactiveSeries(shared)
}
let values = this.getValuesToPrint({
i,
j,
})
this.printLabels({
i,
j,
values,
ttItems,
shared,
e,
})
// Re-calculate tooltip dimensions now that we have drawn the text
const tooltipEl = this.ttCtx.getElTooltip()
this.ttCtx.tooltipRect.ttWidth = tooltipEl.getBoundingClientRect().width
this.ttCtx.tooltipRect.ttHeight = tooltipEl.getBoundingClientRect().height
}
printLabels({ i, j, values, ttItems, shared, e }) {
const w = this.w
let val
let goalVals = []
const hasGoalValues = (gi) => {
return (
w.globals.seriesGoals[gi] &&
w.globals.seriesGoals[gi][j] &&
Array.isArray(w.globals.seriesGoals[gi][j])
)
}
const { xVal, zVal, xAxisTTVal } = values
let seriesName = ''
let pColor = w.globals.colors[i] // The pColor here is for the markers inside tooltip
if (j !== null && w.config.plotOptions.bar.distributed) {
pColor = w.globals.colors[j]
}
for (
let t = 0, inverset = w.globals.series.length - 1;
t < w.globals.series.length;
t++, inverset--
) {
let f = this.getFormatters(i)
seriesName = this.getSeriesName({
fn: f.yLbTitleFormatter,
index: i,
seriesIndex: i,
j,
})
if (w.config.chart.type === 'treemap') {
seriesName = f.yLbTitleFormatter(String(w.config.series[i].data[j].x), {
series: w.globals.series,
seriesIndex: i,
dataPointIndex: j,
w,
})
}
const tIndex = w.config.tooltip.inverseOrder ? inverset : t
if (w.globals.axisCharts) {
const getValBySeriesIndex = (index) => {
if (w.globals.isRangeData) {
return (
f.yLbFormatter(w.globals.seriesRangeStart?.[index]?.[j], {
series: w.globals.seriesRangeStart,
seriesIndex: index,
dataPointIndex: j,
w,
}) +
' - ' +
f.yLbFormatter(w.globals.seriesRangeEnd?.[index]?.[j], {
series: w.globals.seriesRangeEnd,
seriesIndex: index,
dataPointIndex: j,
w,
})
)
}
return f.yLbFormatter(w.globals.series[index][j], {
series: w.globals.series,
seriesIndex: index,
dataPointIndex: j,
w,
})
}
if (shared) {
f = this.getFormatters(tIndex)
seriesName = this.getSeriesName({
fn: f.yLbTitleFormatter,
index: tIndex,
seriesIndex: i,
j,
})
pColor = w.globals.colors[tIndex]
val = getValBySeriesIndex(tIndex)
if (hasGoalValues(tIndex)) {
goalVals = w.globals.seriesGoals[tIndex][j].map((goal) => {
return {
attrs: goal,
val: f.yLbFormatter(goal.value, {
seriesIndex: tIndex,
dataPointIndex: j,
w,
}),
}
})
}
} else {
// get a color from a hover area (if it's a line pattern then get from a first line)
const targetFill = e?.target?.getAttribute('fill')
if (targetFill) {
pColor =
targetFill.indexOf('url') !== -1
? document
.querySelector(targetFill.substr(4).slice(0, -1))
.childNodes[0].getAttribute('stroke')
: targetFill
}
val = getValBySeriesIndex(i)
if (hasGoalValues(i) && Array.isArray(w.globals.seriesGoals[i][j])) {
goalVals = w.globals.seriesGoals[i][j].map((goal) => {
return {
attrs: goal,
val: f.yLbFormatter(goal.value, {
seriesIndex: i,
dataPointIndex: j,
w,
}),
}
})
}
}
}
// for pie / donuts
if (j === null) {
val = f.yLbFormatter(w.globals.series[i], {
...w,
seriesIndex: i,
dataPointIndex: i,
})
}
this.DOMHandling({
i,
t: tIndex,
j,
ttItems,
values: {
val,
goalVals,
xVal,
xAxisTTVal,
zVal,
},
seriesName,
shared,
pColor,
})
}
}
getFormatters(i) {
const w = this.w
let yLbFormatter = w.globals.yLabelFormatters[i]
let yLbTitleFormatter
if (w.globals.ttVal !== undefined) {
if (Array.isArray(w.globals.ttVal)) {
yLbFormatter = w.globals.ttVal[i] && w.globals.ttVal[i].formatter
yLbTitleFormatter =
w.globals.ttVal[i] &&
w.globals.ttVal[i].title &&
w.globals.ttVal[i].title.formatter
} else {
yLbFormatter = w.globals.ttVal.formatter
if (typeof w.globals.ttVal.title.formatter === 'function') {
yLbTitleFormatter = w.globals.ttVal.title.formatter
}
}
} else {
yLbTitleFormatter = w.config.tooltip.y.title.formatter
}
if (typeof yLbFormatter !== 'function') {
if (w.globals.yLabelFormatters[0]) {
yLbFormatter = w.globals.yLabelFormatters[0]
} else {
yLbFormatter = function (label) {
return label
}
}
}
if (typeof yLbTitleFormatter !== 'function') {
yLbTitleFormatter = function (label) {
return label
}
}
return {
yLbFormatter,
yLbTitleFormatter,
}
}
getSeriesName({ fn, index, seriesIndex, j }) {
const w = this.w
return fn(String(w.globals.seriesNames[index]), {
series: w.globals.series,
seriesIndex,
dataPointIndex: j,
w,
})
}
DOMHandling({ i, t, j, ttItems, values, seriesName, shared, pColor }) {
const w = this.w
const ttCtx = this.ttCtx
const { val, goalVals, xVal, xAxisTTVal, zVal } = values
let ttItemsChildren = null
ttItemsChildren = ttItems[t].children
if (w.config.tooltip.fillSeriesColor) {
ttItems[t].style.backgroundColor = pColor
ttItemsChildren[0].style.display = 'none'
}
if (ttCtx.showTooltipTitle) {
if (ttCtx.tooltipTitle === null) {
// get it once if null, and store it in class property
ttCtx.tooltipTitle = w.globals.dom.baseEl.querySelector(
'.apexcharts-tooltip-title'
)
}
ttCtx.tooltipTitle.innerHTML = xVal
}
// if xaxis tooltip is constructed, we need to replace the innerHTML
if (ttCtx.isXAxisTooltipEnabled) {
ttCtx.xaxisTooltipText.innerHTML = xAxisTTVal !== '' ? xAxisTTVal : xVal
}
const ttYLabel = ttItems[t].querySelector(
'.apexcharts-tooltip-text-y-label'
)
if (ttYLabel) {
ttYLabel.innerHTML = seriesName ? seriesName : ''
}
const ttYVal = ttItems[t].querySelector('.apexcharts-tooltip-text-y-value')
if (ttYVal) {
ttYVal.innerHTML = typeof val !== 'undefined' ? val : ''
}
if (
ttItemsChildren[0] &&
ttItemsChildren[0].classList.contains('apexcharts-tooltip-marker')
) {
if (
w.config.tooltip.marker.fillColors &&
Array.isArray(w.config.tooltip.marker.fillColors)
) {
pColor = w.config.tooltip.marker.fillColors[t]
}
ttItemsChildren[0].style.backgroundColor = pColor
}
if (!w.config.tooltip.marker.show) {
ttItemsChildren[0].style.display = 'none'
}
const ttGLabel = ttItems[t].querySelector(
'.apexcharts-tooltip-text-goals-label'
)
const ttGVal = ttItems[t].querySelector(
'.apexcharts-tooltip-text-goals-value'
)
if (goalVals.length && w.globals.seriesGoals[t]) {
const createGoalsHtml = () => {
let gLabels = '<div >'
let gVals = '<div>'
goalVals.forEach((goal, gi) => {
gLabels += ` <div style="display: flex"><span class="apexcharts-tooltip-marker" style="background-color: ${goal.attrs.strokeColor}; height: 3px; border-radius: 0; top: 5px;"></span> ${goal.attrs.name}</div>`
gVals += `<div>${goal.val}</div>`
})
ttGLabel.innerHTML = gLabels + `</div>`
ttGVal.innerHTML = gVals + `</div>`
}
if (shared) {
if (
w.globals.seriesGoals[t][j] &&
Array.isArray(w.globals.seriesGoals[t][j])
) {
createGoalsHtml()
} else {
ttGLabel.innerHTML = ''
ttGVal.innerHTML = ''
}
} else {
createGoalsHtml()
}
} else {
ttGLabel.innerHTML = ''
ttGVal.innerHTML = ''
}
if (zVal !== null) {
const ttZLabel = ttItems[t].querySelector(
'.apexcharts-tooltip-text-z-label'
)
ttZLabel.innerHTML = w.config.tooltip.z.title
const ttZVal = ttItems[t].querySelector(
'.apexcharts-tooltip-text-z-value'
)
ttZVal.innerHTML = typeof zVal !== 'undefined' ? zVal : ''
}
if (shared && ttItemsChildren[0]) {
// hide when no Val or series collapsed
if (w.config.tooltip.hideEmptySeries) {
let ttItemMarker = ttItems[t].querySelector(
'.apexcharts-tooltip-marker'
)
let ttItemText = ttItems[t].querySelector('.apexcharts-tooltip-text')
if (parseFloat(val) == 0) {
ttItemMarker.style.display = 'none'
ttItemText.style.display = 'none'
} else {
ttItemMarker.style.display = 'block'
ttItemText.style.display = 'block'
}
}
if (
typeof val === 'undefined' ||
val === null ||
w.globals.ancillaryCollapsedSeriesIndices.indexOf(t) > -1 ||
w.globals.collapsedSeriesIndices.indexOf(t) > -1
) {
ttItemsChildren[0].parentNode.style.display = 'none'
} else {
ttItemsChildren[0].parentNode.style.display =
w.config.tooltip.items.display
}
}
}
toggleActiveInactiveSeries(shared) {
const w = this.w
if (shared) {
// make all tooltips active
this.tooltipUtil.toggleAllTooltipSeriesGroups('enable')
} else {
// disable all tooltip text groups
this.tooltipUtil.toggleAllTooltipSeriesGroups('disable')
// enable the first tooltip text group
let firstTooltipSeriesGroup = w.globals.dom.baseEl.querySelector(
'.apexcharts-tooltip-series-group'
)
if (firstTooltipSeriesGroup) {
firstTooltipSeriesGroup.classList.add('apexcharts-active')
firstTooltipSeriesGroup.style.display = w.config.tooltip.items.display
}
}
}
getValuesToPrint({ i, j }) {
const w = this.w
const filteredSeriesX = this.ctx.series.filteredSeriesX()
let xVal = ''
let xAxisTTVal = ''
let zVal = null
let val = null
const customFormatterOpts = {
series: w.globals.series,
seriesIndex: i,
dataPointIndex: j,
w,
}
let zFormatter = w.globals.ttZFormatter
if (j === null) {
val = w.globals.series[i]
} else {
if (w.globals.isXNumeric && w.config.chart.type !== 'treemap') {
xVal = filteredSeriesX[i][j]
if (filteredSeriesX[i].length === 0) {
// a series (possibly the first one) might be collapsed, so get the next active index
const firstActiveSeriesIndex =
this.tooltipUtil.getFirstActiveXArray(filteredSeriesX)
xVal = filteredSeriesX[firstActiveSeriesIndex][j]
}
} else {
xVal =
typeof w.globals.labels[j] !== 'undefined' ? w.globals.labels[j] : ''
}
}
let bufferXVal = xVal
if (w.globals.isXNumeric && w.config.xaxis.type === 'datetime') {
let xFormat = new Formatters(this.ctx)
xVal = xFormat.xLabelFormat(
w.globals.ttKeyFormatter,
bufferXVal,
bufferXVal,
{
i: undefined,
dateFormatter: new DateTime(this.ctx).formatDate,
w: this.w,
}
)
} else {
if (w.globals.isBarHorizontal) {
xVal = w.globals.yLabelFormatters[0](bufferXVal, customFormatterOpts)
} else {
xVal = w.globals.xLabelFormatter(bufferXVal, customFormatterOpts)
}
}
// override default x-axis formatter with tooltip formatter
if (w.config.tooltip.x.formatter !== undefined) {
xVal = w.globals.ttKeyFormatter(bufferXVal, customFormatterOpts)
}
if (w.globals.seriesZ.length > 0 && w.globals.seriesZ[i].length > 0) {
zVal = zFormatter(w.globals.seriesZ[i][j], w)
}
if (typeof w.config.xaxis.tooltip.formatter === 'function') {
xAxisTTVal = w.globals.xaxisTooltipFormatter(
bufferXVal,
customFormatterOpts
)
} else {
xAxisTTVal = xVal
}
return {
val: Array.isArray(val) ? val.join(' ') : val,
xVal: Array.isArray(xVal) ? xVal.join(' ') : xVal,
xAxisTTVal: Array.isArray(xAxisTTVal) ? xAxisTTVal.join(' ') : xAxisTTVal,
zVal,
}
}
handleCustomTooltip({ i, j, y1, y2, w }) {
const tooltipEl = this.ttCtx.getElTooltip()
let fn = w.config.tooltip.custom
if (Array.isArray(fn) && fn[i]) {
fn = fn[i]
}
// override everything with a custom html tooltip and replace it
tooltipEl.innerHTML = fn({
ctx: this.ctx,
series: w.globals.series,
seriesIndex: i,
dataPointIndex: j,
y1,
y2,
w,
})
}
}
+188
View File
@@ -0,0 +1,188 @@
import Graphics from '../Graphics'
import Position from './Position'
import Markers from '../../modules/Markers'
import Utils from '../../utils/Utils'
/**
* ApexCharts Tooltip.Marker Class to draw texts on the tooltip.
* This file deals with the markers that appear near tooltip in line/area charts.
* These markers helps the user to associate the data-points and the values
* that are shown in the tooltip
*
* @module Tooltip.Marker
**/
export default class Marker {
constructor(tooltipContext) {
this.w = tooltipContext.w
this.ttCtx = tooltipContext
this.ctx = tooltipContext.ctx
this.tooltipPosition = new Position(tooltipContext)
}
drawDynamicPoints() {
let w = this.w
let graphics = new Graphics(this.ctx)
let marker = new Markers(this.ctx)
let elsSeries = w.globals.dom.baseEl.querySelectorAll('.apexcharts-series')
elsSeries = [...elsSeries]
if (w.config.chart.stacked) {
elsSeries.sort((a, b) => {
return (
parseFloat(a.getAttribute('data:realIndex')) -
parseFloat(b.getAttribute('data:realIndex'))
)
})
}
for (let i = 0; i < elsSeries.length; i++) {
let pointsMain = elsSeries[i].querySelector(
`.apexcharts-series-markers-wrap`
)
if (pointsMain !== null) {
// it can be null as we have tooltips in donut/bar charts
let point
let PointClasses = `apexcharts-marker w${(Math.random() + 1)
.toString(36)
.substring(4)}`
if (
(w.config.chart.type === 'line' || w.config.chart.type === 'area') &&
!w.globals.comboCharts &&
!w.config.tooltip.intersect
) {
PointClasses += ' no-pointer-events'
}
let elPointOptions = marker.getMarkerConfig({
cssClass: PointClasses,
seriesIndex: Number(pointsMain.getAttribute('data:realIndex')) // fixes apexcharts/apexcharts.js #1427
})
point = graphics.drawMarker(0, 0, elPointOptions)
point.node.setAttribute('default-marker-size', 0)
let elPointsG = document.createElementNS(w.globals.SVGNS, 'g')
elPointsG.classList.add('apexcharts-series-markers')
elPointsG.appendChild(point.node)
pointsMain.appendChild(elPointsG)
}
}
}
enlargeCurrentPoint(rel, point, x = null, y = null) {
let w = this.w
if (w.config.chart.type !== 'bubble') {
this.newPointSize(rel, point)
}
let cx = point.getAttribute('cx')
let cy = point.getAttribute('cy')
if (x !== null && y !== null) {
cx = x
cy = y
}
this.tooltipPosition.moveXCrosshairs(cx)
if (!this.fixedTooltip) {
if (w.config.chart.type === 'radar') {
const elGrid = this.ttCtx.getElGrid()
const seriesBound = elGrid.getBoundingClientRect()
cx = this.ttCtx.e.clientX - seriesBound.left
}
this.tooltipPosition.moveTooltip(cx, cy, w.config.markers.hover.size)
}
}
enlargePoints(j) {
let w = this.w
let me = this
const ttCtx = this.ttCtx
let col = j
let points = w.globals.dom.baseEl.querySelectorAll(
'.apexcharts-series:not(.apexcharts-series-collapsed) .apexcharts-marker'
)
let newSize = w.config.markers.hover.size
for (let p = 0; p < points.length; p++) {
let rel = points[p].getAttribute('rel')
let index = points[p].getAttribute('index')
if (newSize === undefined) {
newSize =
w.globals.markers.size[index] + w.config.markers.hover.sizeOffset
}
if (col === parseInt(rel, 10)) {
me.newPointSize(col, points[p])
let cx = points[p].getAttribute('cx')
let cy = points[p].getAttribute('cy')
me.tooltipPosition.moveXCrosshairs(cx)
if (!ttCtx.fixedTooltip) {
me.tooltipPosition.moveTooltip(cx, cy, newSize)
}
} else {
me.oldPointSize(points[p])
}
}
}
newPointSize(rel, point) {
let w = this.w
let newSize = w.config.markers.hover.size
let elPoint =
rel === 0 ? point.parentNode.firstChild : point.parentNode.lastChild
if (elPoint.getAttribute('default-marker-size') !== '0') {
const index = parseInt(elPoint.getAttribute('index'), 10)
if (newSize === undefined) {
newSize =
w.globals.markers.size[index] + w.config.markers.hover.sizeOffset
}
if (newSize < 0) newSize = 0
elPoint.setAttribute('r', newSize)
}
}
oldPointSize(point) {
const size = parseFloat(point.getAttribute('default-marker-size'))
point.setAttribute('r', size)
}
resetPointsSize() {
let w = this.w
let points = w.globals.dom.baseEl.querySelectorAll(
'.apexcharts-series:not(.apexcharts-series-collapsed) .apexcharts-marker'
)
for (let p = 0; p < points.length; p++) {
const size = parseFloat(points[p].getAttribute('default-marker-size'))
if (Utils.isNumber(size) && size >= 0) {
points[p].setAttribute('r', size)
} else {
points[p].setAttribute('r', 0)
}
}
}
}
+442
View File
@@ -0,0 +1,442 @@
import Graphics from '../Graphics'
import Series from '../Series'
/**
* ApexCharts Tooltip.Position Class to move the tooltip based on x and y position.
*
* @module Tooltip.Position
**/
export default class Position {
constructor(tooltipContext) {
this.ttCtx = tooltipContext
this.ctx = tooltipContext.ctx
this.w = tooltipContext.w
}
/**
* This will move the crosshair (the vertical/horz line that moves along with mouse)
* Along with this, this function also calls the xaxisMove function
* @memberof Position
* @param {int} - cx = point's x position, wherever point's x is, you need to move crosshair
*/
moveXCrosshairs(cx, j = null) {
const ttCtx = this.ttCtx
let w = this.w
const xcrosshairs = ttCtx.getElXCrosshairs()
let x = cx - ttCtx.xcrosshairsWidth / 2
let tickAmount = w.globals.labels.slice().length
if (j !== null) {
x = (w.globals.gridWidth / tickAmount) * j
}
if (xcrosshairs !== null && !w.globals.isBarHorizontal) {
xcrosshairs.setAttribute('x', x)
xcrosshairs.setAttribute('x1', x)
xcrosshairs.setAttribute('x2', x)
xcrosshairs.setAttribute('y2', w.globals.gridHeight)
xcrosshairs.classList.add('apexcharts-active')
}
if (x < 0) {
x = 0
}
if (x > w.globals.gridWidth) {
x = w.globals.gridWidth
}
if (ttCtx.isXAxisTooltipEnabled) {
let tx = x
if (
w.config.xaxis.crosshairs.width === 'tickWidth' ||
w.config.xaxis.crosshairs.width === 'barWidth'
) {
tx = x + ttCtx.xcrosshairsWidth / 2
}
this.moveXAxisTooltip(tx)
}
}
/**
* This will move the crosshair (the vertical/horz line that moves along with mouse)
* Along with this, this function also calls the xaxisMove function
* @memberof Position
* @param {int} - cx = point's x position, wherever point's x is, you need to move crosshair
*/
moveYCrosshairs(cy) {
const ttCtx = this.ttCtx
if (ttCtx.ycrosshairs !== null) {
Graphics.setAttrs(ttCtx.ycrosshairs, {
y1: cy,
y2: cy,
})
}
if (ttCtx.ycrosshairsHidden !== null) {
Graphics.setAttrs(ttCtx.ycrosshairsHidden, {
y1: cy,
y2: cy,
})
}
}
/**
** AxisTooltip is the small rectangle which appears on x axis with x value, when user moves
* @memberof Position
* @param {int} - cx = point's x position, wherever point's x is, you need to move
*/
moveXAxisTooltip(cx) {
let w = this.w
const ttCtx = this.ttCtx
if (ttCtx.xaxisTooltip !== null && ttCtx.xcrosshairsWidth !== 0) {
ttCtx.xaxisTooltip.classList.add('apexcharts-active')
let cy =
ttCtx.xaxisOffY +
w.config.xaxis.tooltip.offsetY +
w.globals.translateY +
1 +
w.config.xaxis.offsetY
let xaxisTTText = ttCtx.xaxisTooltip.getBoundingClientRect()
let xaxisTTTextWidth = xaxisTTText.width
cx = cx - xaxisTTTextWidth / 2
if (!isNaN(cx)) {
cx = cx + w.globals.translateX
let textRect = 0
const graphics = new Graphics(this.ctx)
textRect = graphics.getTextRects(ttCtx.xaxisTooltipText.innerHTML)
ttCtx.xaxisTooltipText.style.minWidth = textRect.width + 'px'
ttCtx.xaxisTooltip.style.left = cx + 'px'
ttCtx.xaxisTooltip.style.top = cy + 'px'
}
}
}
moveYAxisTooltip(index) {
const w = this.w
const ttCtx = this.ttCtx
if (ttCtx.yaxisTTEls === null) {
ttCtx.yaxisTTEls = w.globals.dom.baseEl.querySelectorAll(
'.apexcharts-yaxistooltip'
)
}
const ycrosshairsHiddenRectY1 = parseInt(
ttCtx.ycrosshairsHidden.getAttribute('y1'),
10
)
let cy = w.globals.translateY + ycrosshairsHiddenRectY1
const yAxisTTRect = ttCtx.yaxisTTEls[index].getBoundingClientRect()
const yAxisTTHeight = yAxisTTRect.height
let cx = w.globals.translateYAxisX[index] - 2
if (w.config.yaxis[index].opposite) {
cx = cx - 26
}
cy = cy - yAxisTTHeight / 2
if (w.globals.ignoreYAxisIndexes.indexOf(index) === -1) {
ttCtx.yaxisTTEls[index].classList.add('apexcharts-active')
ttCtx.yaxisTTEls[index].style.top = cy + 'px'
ttCtx.yaxisTTEls[index].style.left =
cx + w.config.yaxis[index].tooltip.offsetX + 'px'
} else {
ttCtx.yaxisTTEls[index].classList.remove('apexcharts-active')
}
}
/**
** moves the whole tooltip by changing x, y attrs
* @memberof Position
* @param {int} - cx = point's x position, wherever point's x is, you need to move tooltip
* @param {int} - cy = point's y position, wherever point's y is, you need to move tooltip
* @param {int} - r = point's radius
*/
moveTooltip(cx, cy, r = null) {
let w = this.w
let ttCtx = this.ttCtx
const tooltipEl = ttCtx.getElTooltip()
let tooltipRect = ttCtx.tooltipRect
let pointR = r !== null ? parseFloat(r) : 1
let x = parseFloat(cx) + pointR + 5
let y = parseFloat(cy) + pointR / 2 // - tooltipRect.ttHeight / 2
if (x > w.globals.gridWidth / 2) {
x = x - tooltipRect.ttWidth - pointR - 10
}
if (x > w.globals.gridWidth - tooltipRect.ttWidth - 10) {
x = w.globals.gridWidth - tooltipRect.ttWidth
}
if (x < -20) {
x = -20
}
if (w.config.tooltip.followCursor) {
const elGrid = ttCtx.getElGrid()
const seriesBound = elGrid.getBoundingClientRect()
x = ttCtx.e.clientX - seriesBound.left
if (x > w.globals.gridWidth / 2) {
x = x - ttCtx.tooltipRect.ttWidth
}
y = ttCtx.e.clientY + w.globals.translateY - seriesBound.top
if (y > w.globals.gridHeight / 2) {
y = y - ttCtx.tooltipRect.ttHeight
}
} else {
if (!w.globals.isBarHorizontal) {
if (tooltipRect.ttHeight / 2 + y > w.globals.gridHeight) {
y = w.globals.gridHeight - tooltipRect.ttHeight + w.globals.translateY
}
}
}
if (!isNaN(x)) {
x = x + w.globals.translateX
tooltipEl.style.left = x + 'px'
tooltipEl.style.top = y + 'px'
}
}
moveMarkers(i, j) {
let w = this.w
let ttCtx = this.ttCtx
if (w.globals.markers.size[i] > 0) {
let allPoints = w.globals.dom.baseEl.querySelectorAll(
` .apexcharts-series[data\\:realIndex='${i}'] .apexcharts-marker`
)
for (let p = 0; p < allPoints.length; p++) {
if (parseInt(allPoints[p].getAttribute('rel'), 10) === j) {
ttCtx.marker.resetPointsSize()
ttCtx.marker.enlargeCurrentPoint(j, allPoints[p])
}
}
} else {
ttCtx.marker.resetPointsSize()
this.moveDynamicPointOnHover(j, i)
}
}
// This function is used when you need to show markers/points only on hover -
// DIFFERENT X VALUES in multiple series
moveDynamicPointOnHover(j, capturedSeries) {
let w = this.w
let ttCtx = this.ttCtx
let cx = 0
let cy = 0
let pointsArr = w.globals.pointsArray
let hoverSize = ttCtx.tooltipUtil.getHoverMarkerSize(capturedSeries)
const serType = w.config.series[capturedSeries].type
if (
serType &&
(serType === 'column' ||
serType === 'candlestick' ||
serType === 'boxPlot')
) {
// fix error mentioned in #811
return
}
cx = pointsArr[capturedSeries][j][0]
cy = pointsArr[capturedSeries][j][1] ? pointsArr[capturedSeries][j][1] : 0
let point = w.globals.dom.baseEl.querySelector(
`.apexcharts-series[data\\:realIndex='${capturedSeries}'] .apexcharts-series-markers circle`
)
if (point && cy < w.globals.gridHeight && cy > 0) {
point.setAttribute('r', hoverSize)
point.setAttribute('cx', cx)
point.setAttribute('cy', cy)
}
// point.style.opacity = w.config.markers.hover.opacity
this.moveXCrosshairs(cx)
if (!ttCtx.fixedTooltip) {
this.moveTooltip(cx, cy, hoverSize)
}
}
// This function is used when you need to show markers/points only on hover -
// SAME X VALUES in multiple series
moveDynamicPointsOnHover(j) {
const ttCtx = this.ttCtx
let w = ttCtx.w
let cx = 0
let cy = 0
let activeSeries = 0
let pointsArr = w.globals.pointsArray
let series = new Series(this.ctx)
activeSeries = series.getActiveConfigSeriesIndex('asc', [
'line',
'area',
'scatter',
'bubble',
])
let hoverSize = ttCtx.tooltipUtil.getHoverMarkerSize(activeSeries)
if (pointsArr[activeSeries]) {
cx = pointsArr[activeSeries][j][0]
cy = pointsArr[activeSeries][j][1]
}
let points = ttCtx.tooltipUtil.getAllMarkers()
if (points !== null) {
for (let p = 0; p < w.globals.series.length; p++) {
let pointArr = pointsArr[p]
if (w.globals.comboCharts) {
// in a combo chart, if column charts are present, markers will not match with the number of series, hence this patch to push a null value in points array
if (typeof pointArr === 'undefined') {
// nodelist to array
points.splice(p, 0, null)
}
}
if (pointArr && pointArr.length) {
let pcy = pointsArr[p][j][1]
let pcy2
points[p].setAttribute('cx', cx)
if (w.config.chart.type === 'rangeArea' && !w.globals.comboCharts) {
const rangeStartIndex = j + w.globals.series[p].length
pcy2 = pointsArr[p][rangeStartIndex][1]
const pcyDiff = Math.abs(pcy - pcy2) / 2
pcy = pcy - pcyDiff
}
if (
pcy !== null &&
!isNaN(pcy) &&
pcy < w.globals.gridHeight + hoverSize &&
pcy + hoverSize > 0
) {
points[p] && points[p].setAttribute('r', hoverSize)
points[p] && points[p].setAttribute('cy', pcy)
} else {
points[p] && points[p].setAttribute('r', 0)
}
}
}
}
this.moveXCrosshairs(cx)
if (!ttCtx.fixedTooltip) {
this.moveTooltip(cx, cy || w.globals.gridHeight, hoverSize)
}
}
moveStickyTooltipOverBars(j, capturedSeries) {
const w = this.w
const ttCtx = this.ttCtx
let barLen = w.globals.columnSeries
? w.globals.columnSeries.length
: w.globals.series.length
let i =
barLen >= 2 && barLen % 2 === 0
? Math.floor(barLen / 2)
: Math.floor(barLen / 2) + 1
if (w.globals.isBarHorizontal) {
let series = new Series(this.ctx)
i = series.getActiveConfigSeriesIndex('desc') + 1
}
let jBar = w.globals.dom.baseEl.querySelector(
`.apexcharts-bar-series .apexcharts-series[rel='${i}'] path[j='${j}'], .apexcharts-candlestick-series .apexcharts-series[rel='${i}'] path[j='${j}'], .apexcharts-boxPlot-series .apexcharts-series[rel='${i}'] path[j='${j}'], .apexcharts-rangebar-series .apexcharts-series[rel='${i}'] path[j='${j}']`
)
if (!jBar && typeof capturedSeries === 'number') {
// Try with captured series index
jBar = w.globals.dom.baseEl.querySelector(
`.apexcharts-bar-series .apexcharts-series[data\\:realIndex='${capturedSeries}'] path[j='${j}'],
.apexcharts-candlestick-series .apexcharts-series[data\\:realIndex='${capturedSeries}'] path[j='${j}'],
.apexcharts-boxPlot-series .apexcharts-series[data\\:realIndex='${capturedSeries}'] path[j='${j}'],
.apexcharts-rangebar-series .apexcharts-series[data\\:realIndex='${capturedSeries}'] path[j='${j}']`
)
}
let bcx = jBar ? parseFloat(jBar.getAttribute('cx')) : 0
let bcy = jBar ? parseFloat(jBar.getAttribute('cy')) : 0
let bw = jBar ? parseFloat(jBar.getAttribute('barWidth')) : 0
const elGrid = ttCtx.getElGrid()
let seriesBound = elGrid.getBoundingClientRect()
const isBoxOrCandle =
jBar &&
(jBar.classList.contains('apexcharts-candlestick-area') ||
jBar.classList.contains('apexcharts-boxPlot-area'))
if (w.globals.isXNumeric) {
if (jBar && !isBoxOrCandle) {
bcx = bcx - (barLen % 2 !== 0 ? bw / 2 : 0)
}
if (
jBar && // fixes apexcharts.js#2354
isBoxOrCandle &&
w.globals.comboCharts
) {
bcx = bcx - bw / 2
}
} else {
if (!w.globals.isBarHorizontal) {
bcx =
ttCtx.xAxisTicksPositions[j - 1] + ttCtx.dataPointsDividedWidth / 2
if (isNaN(bcx)) {
bcx = ttCtx.xAxisTicksPositions[j] - ttCtx.dataPointsDividedWidth / 2
}
}
}
if (!w.globals.isBarHorizontal) {
if (w.config.tooltip.followCursor) {
bcy = ttCtx.e.clientY - seriesBound.top - ttCtx.tooltipRect.ttHeight / 2
} else {
if (bcy + ttCtx.tooltipRect.ttHeight + 15 > w.globals.gridHeight) {
bcy = w.globals.gridHeight
}
}
} else {
bcy = bcy - ttCtx.tooltipRect.ttHeight
}
if (!w.globals.isBarHorizontal) {
this.moveXCrosshairs(bcx)
}
if (!ttCtx.fixedTooltip) {
this.moveTooltip(bcx, bcy || w.globals.gridHeight)
}
}
}
+20
View File
@@ -0,0 +1,20 @@
### AxesTooltip.js
This file deals with the x-axis and y-axis tooltips.
### Intersect.js
This file deals with functions related to intersecting tooltips (tooltips that appear when user hovers directly over a data-point whether).
### Labels.js
This file deals with printing actual text on the tooltip.
### Marker.js
This file deals with the markers that appear near tooltip in line/area charts. These markers helps the user to associate the data-points and the values that are shown in the tooltip
### Position.js
This file deals with positioning of the tooltip.
### Tooltip.js
This is the primary file which is an entry point for all tooltip related functionality.
### Utils.js
Helper functions related to tooltips.
+900
View File
@@ -0,0 +1,900 @@
import Labels from './Labels'
import Position from './Position'
import Marker from './Marker'
import Intersect from './Intersect'
import AxesTooltip from './AxesTooltip'
import Graphics from '../Graphics'
import Series from '../Series'
import XAxis from './../axes/XAxis'
import Utils from './Utils'
/**
* ApexCharts Core Tooltip Class to handle the tooltip generation.
*
* @module Tooltip
**/
export default class Tooltip {
constructor(ctx) {
this.ctx = ctx
this.w = ctx.w
const w = this.w
this.tConfig = w.config.tooltip
this.tooltipUtil = new Utils(this)
this.tooltipLabels = new Labels(this)
this.tooltipPosition = new Position(this)
this.marker = new Marker(this)
this.intersect = new Intersect(this)
this.axesTooltip = new AxesTooltip(this)
this.showOnIntersect = this.tConfig.intersect
this.showTooltipTitle = this.tConfig.x.show
this.fixedTooltip = this.tConfig.fixed.enabled
this.xaxisTooltip = null
this.yaxisTTEls = null
this.isBarShared = !w.globals.isBarHorizontal && this.tConfig.shared
this.lastHoverTime = Date.now()
}
getElTooltip(ctx) {
if (!ctx) ctx = this
if (!ctx.w.globals.dom.baseEl) return null
return ctx.w.globals.dom.baseEl.querySelector('.apexcharts-tooltip')
}
getElXCrosshairs() {
return this.w.globals.dom.baseEl.querySelector('.apexcharts-xcrosshairs')
}
getElGrid() {
return this.w.globals.dom.baseEl.querySelector('.apexcharts-grid')
}
drawTooltip(xyRatios) {
let w = this.w
this.xyRatios = xyRatios
this.isXAxisTooltipEnabled =
w.config.xaxis.tooltip.enabled && w.globals.axisCharts
this.yaxisTooltips = w.config.yaxis.map((y, i) => {
return y.show && y.tooltip.enabled && w.globals.axisCharts ? true : false
})
this.allTooltipSeriesGroups = []
if (!w.globals.axisCharts) {
this.showTooltipTitle = false
}
const tooltipEl = document.createElement('div')
tooltipEl.classList.add('apexcharts-tooltip')
if (w.config.tooltip.cssClass) {
tooltipEl.classList.add(w.config.tooltip.cssClass)
}
tooltipEl.classList.add(`apexcharts-theme-${this.tConfig.theme}`)
w.globals.dom.elWrap.appendChild(tooltipEl)
if (w.globals.axisCharts) {
this.axesTooltip.drawXaxisTooltip()
this.axesTooltip.drawYaxisTooltip()
this.axesTooltip.setXCrosshairWidth()
this.axesTooltip.handleYCrosshair()
let xAxis = new XAxis(this.ctx)
this.xAxisTicksPositions = xAxis.getXAxisTicksPositions()
}
// we forcefully set intersect true for these conditions
if (
(w.globals.comboCharts ||
this.tConfig.intersect ||
w.config.chart.type === 'rangeBar') &&
!this.tConfig.shared
) {
this.showOnIntersect = true
}
if (w.config.markers.size === 0 || w.globals.markers.largestSize === 0) {
// when user don't want to show points all the time, but only on when hovering on series
this.marker.drawDynamicPoints(this)
}
// no visible series, exit
if (w.globals.collapsedSeries.length === w.globals.series.length) return
this.dataPointsDividedHeight = w.globals.gridHeight / w.globals.dataPoints
this.dataPointsDividedWidth = w.globals.gridWidth / w.globals.dataPoints
if (this.showTooltipTitle) {
this.tooltipTitle = document.createElement('div')
this.tooltipTitle.classList.add('apexcharts-tooltip-title')
this.tooltipTitle.style.fontFamily =
this.tConfig.style.fontFamily || w.config.chart.fontFamily
this.tooltipTitle.style.fontSize = this.tConfig.style.fontSize
tooltipEl.appendChild(this.tooltipTitle)
}
let ttItemsCnt = w.globals.series.length // whether shared or not, default is shared
if ((w.globals.xyCharts || w.globals.comboCharts) && this.tConfig.shared) {
if (!this.showOnIntersect) {
ttItemsCnt = w.globals.series.length
} else {
ttItemsCnt = 1
}
}
this.legendLabels = w.globals.dom.baseEl.querySelectorAll(
'.apexcharts-legend-text'
)
this.ttItems = this.createTTElements(ttItemsCnt)
this.addSVGEvents()
}
createTTElements(ttItemsCnt) {
const w = this.w
let ttItems = []
const tooltipEl = this.getElTooltip()
for (let i = 0; i < ttItemsCnt; i++) {
let gTxt = document.createElement('div')
gTxt.classList.add('apexcharts-tooltip-series-group')
gTxt.style.order = w.config.tooltip.inverseOrder ? ttItemsCnt - i : i + 1
if (
this.tConfig.shared &&
this.tConfig.enabledOnSeries &&
Array.isArray(this.tConfig.enabledOnSeries)
) {
if (this.tConfig.enabledOnSeries.indexOf(i) < 0) {
gTxt.classList.add('apexcharts-tooltip-series-group-hidden')
}
}
let point = document.createElement('span')
point.classList.add('apexcharts-tooltip-marker')
point.style.backgroundColor = w.globals.colors[i]
gTxt.appendChild(point)
const gYZ = document.createElement('div')
gYZ.classList.add('apexcharts-tooltip-text')
gYZ.style.fontFamily =
this.tConfig.style.fontFamily || w.config.chart.fontFamily
gYZ.style.fontSize = this.tConfig.style.fontSize
;['y', 'goals', 'z'].forEach((g) => {
const gValText = document.createElement('div')
gValText.classList.add(`apexcharts-tooltip-${g}-group`)
let txtLabel = document.createElement('span')
txtLabel.classList.add(`apexcharts-tooltip-text-${g}-label`)
gValText.appendChild(txtLabel)
let txtValue = document.createElement('span')
txtValue.classList.add(`apexcharts-tooltip-text-${g}-value`)
gValText.appendChild(txtValue)
gYZ.appendChild(gValText)
})
gTxt.appendChild(gYZ)
tooltipEl.appendChild(gTxt)
ttItems.push(gTxt)
}
return ttItems
}
addSVGEvents() {
const w = this.w
let type = w.config.chart.type
const tooltipEl = this.getElTooltip()
const commonBar = !!(
type === 'bar' ||
type === 'candlestick' ||
type === 'boxPlot' ||
type === 'rangeBar'
)
const chartWithmarkers =
type === 'area' ||
type === 'line' ||
type === 'scatter' ||
type === 'bubble' ||
type === 'radar'
let hoverArea = w.globals.dom.Paper.node
const elGrid = this.getElGrid()
if (elGrid) {
this.seriesBound = elGrid.getBoundingClientRect()
}
let tooltipY = []
let tooltipX = []
let seriesHoverParams = {
hoverArea,
elGrid,
tooltipEl,
tooltipY,
tooltipX,
ttItems: this.ttItems,
}
let points
if (w.globals.axisCharts) {
if (chartWithmarkers) {
points = w.globals.dom.baseEl.querySelectorAll(
".apexcharts-series[data\\:longestSeries='true'] .apexcharts-marker"
)
} else if (commonBar) {
points = w.globals.dom.baseEl.querySelectorAll(
'.apexcharts-series .apexcharts-bar-area, .apexcharts-series .apexcharts-candlestick-area, .apexcharts-series .apexcharts-boxPlot-area, .apexcharts-series .apexcharts-rangebar-area'
)
} else if (type === 'heatmap' || type === 'treemap') {
points = w.globals.dom.baseEl.querySelectorAll(
'.apexcharts-series .apexcharts-heatmap, .apexcharts-series .apexcharts-treemap'
)
}
if (points && points.length) {
for (let p = 0; p < points.length; p++) {
tooltipY.push(points[p].getAttribute('cy'))
tooltipX.push(points[p].getAttribute('cx'))
}
}
}
const validSharedChartTypes =
(w.globals.xyCharts && !this.showOnIntersect) ||
(w.globals.comboCharts && !this.showOnIntersect) ||
(commonBar && this.tooltipUtil.hasBars() && this.tConfig.shared)
if (validSharedChartTypes) {
this.addPathsEventListeners([hoverArea], seriesHoverParams)
} else if (
(commonBar && !w.globals.comboCharts) ||
(chartWithmarkers && this.showOnIntersect)
) {
this.addDatapointEventsListeners(seriesHoverParams)
} else if (
!w.globals.axisCharts ||
type === 'heatmap' ||
type === 'treemap'
) {
let seriesAll =
w.globals.dom.baseEl.querySelectorAll('.apexcharts-series')
this.addPathsEventListeners(seriesAll, seriesHoverParams)
}
if (this.showOnIntersect) {
let lineAreaPoints = w.globals.dom.baseEl.querySelectorAll(
'.apexcharts-line-series .apexcharts-marker, .apexcharts-area-series .apexcharts-marker'
)
if (lineAreaPoints.length > 0) {
// if we find any lineSeries, addEventListeners for them
this.addPathsEventListeners(lineAreaPoints, seriesHoverParams)
}
// combo charts may have bars, so add event listeners here too
if (this.tooltipUtil.hasBars() && !this.tConfig.shared) {
this.addDatapointEventsListeners(seriesHoverParams)
}
}
}
drawFixedTooltipRect() {
let w = this.w
const tooltipEl = this.getElTooltip()
let tooltipRect = tooltipEl.getBoundingClientRect()
let ttWidth = tooltipRect.width + 10
let ttHeight = tooltipRect.height + 10
let x = this.tConfig.fixed.offsetX
let y = this.tConfig.fixed.offsetY
const fixed = this.tConfig.fixed.position.toLowerCase()
if (fixed.indexOf('right') > -1) {
x = x + w.globals.svgWidth - ttWidth + 10
}
if (fixed.indexOf('bottom') > -1) {
y = y + w.globals.svgHeight - ttHeight - 10
}
tooltipEl.style.left = x + 'px'
tooltipEl.style.top = y + 'px'
return {
x,
y,
ttWidth,
ttHeight,
}
}
addDatapointEventsListeners(seriesHoverParams) {
let w = this.w
let points = w.globals.dom.baseEl.querySelectorAll(
'.apexcharts-series-markers .apexcharts-marker, .apexcharts-bar-area, .apexcharts-candlestick-area, .apexcharts-boxPlot-area, .apexcharts-rangebar-area'
)
this.addPathsEventListeners(points, seriesHoverParams)
}
addPathsEventListeners(paths, opts) {
let self = this
for (let p = 0; p < paths.length; p++) {
let extendedOpts = {
paths: paths[p],
tooltipEl: opts.tooltipEl,
tooltipY: opts.tooltipY,
tooltipX: opts.tooltipX,
elGrid: opts.elGrid,
hoverArea: opts.hoverArea,
ttItems: opts.ttItems,
}
let events = ['mousemove', 'mouseup', 'touchmove', 'mouseout', 'touchend']
events.map((ev) => {
return paths[p].addEventListener(
ev,
self.onSeriesHover.bind(self, extendedOpts),
{ capture: false, passive: true }
)
})
}
}
/*
** Check to see if the tooltips should be updated based on a mouse / touch event
*/
onSeriesHover(opt, e) {
// If a user is moving their mouse quickly, don't bother updating the tooltip every single frame
const targetDelay = 100
const timeSinceLastUpdate = Date.now() - this.lastHoverTime
if (timeSinceLastUpdate >= targetDelay) {
// The tooltip was last updated over 100ms ago - redraw it even if the user is still moving their
// mouse so they get some feedback that their moves are being registered
this.seriesHover(opt, e)
} else {
// The tooltip was last updated less than 100ms ago
// Cancel any other delayed draw, so we don't show stale data
clearTimeout(this.seriesHoverTimeout)
// Schedule the next draw so that it happens about 100ms after the last update
this.seriesHoverTimeout = setTimeout(() => {
this.seriesHover(opt, e)
}, targetDelay - timeSinceLastUpdate)
}
}
/*
** The actual series hover function
*/
seriesHover(opt, e) {
this.lastHoverTime = Date.now()
let chartGroups = []
const w = this.w
// if user has more than one charts in group, we need to sync
if (w.config.chart.group) {
chartGroups = this.ctx.getGroupedCharts()
}
if (
w.globals.axisCharts &&
((w.globals.minX === -Infinity && w.globals.maxX === Infinity) ||
w.globals.dataPoints === 0)
) {
return
}
if (chartGroups.length) {
chartGroups.forEach((ch) => {
const tooltipEl = this.getElTooltip(ch)
const newOpts = {
paths: opt.paths,
tooltipEl,
tooltipY: opt.tooltipY,
tooltipX: opt.tooltipX,
elGrid: opt.elGrid,
hoverArea: opt.hoverArea,
ttItems: ch.w.globals.tooltip.ttItems,
}
// all the charts should have the same minX and maxX (same xaxis) for multiple tooltips to work correctly
if (
ch.w.globals.minX === this.w.globals.minX &&
ch.w.globals.maxX === this.w.globals.maxX
) {
ch.w.globals.tooltip.seriesHoverByContext({
chartCtx: ch,
ttCtx: ch.w.globals.tooltip,
opt: newOpts,
e,
})
}
})
} else {
this.seriesHoverByContext({
chartCtx: this.ctx,
ttCtx: this.w.globals.tooltip,
opt,
e,
})
}
}
seriesHoverByContext({ chartCtx, ttCtx, opt, e }) {
let w = chartCtx.w
const tooltipEl = this.getElTooltip()
if (!tooltipEl) return
// tooltipRect is calculated on every mousemove, because the text is dynamic
ttCtx.tooltipRect = {
x: 0,
y: 0,
ttWidth: tooltipEl.getBoundingClientRect().width,
ttHeight: tooltipEl.getBoundingClientRect().height,
}
ttCtx.e = e
// highlight the current hovered bars
if (
ttCtx.tooltipUtil.hasBars() &&
!w.globals.comboCharts &&
!ttCtx.isBarShared
) {
if (this.tConfig.onDatasetHover.highlightDataSeries) {
let series = new Series(chartCtx)
series.toggleSeriesOnHover(e, e.target.parentNode)
}
}
if (ttCtx.fixedTooltip) {
ttCtx.drawFixedTooltipRect()
}
if (w.globals.axisCharts) {
ttCtx.axisChartsTooltips({
e,
opt,
tooltipRect: ttCtx.tooltipRect,
})
} else {
// non-plot charts i.e pie/donut/circle
ttCtx.nonAxisChartsTooltips({
e,
opt,
tooltipRect: ttCtx.tooltipRect,
})
}
}
// tooltip handling for line/area/bar/columns/scatter
axisChartsTooltips({ e, opt }) {
let w = this.w
let x, y
let seriesBound = opt.elGrid.getBoundingClientRect()
const clientX = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX
const clientY = e.type === 'touchmove' ? e.touches[0].clientY : e.clientY
this.clientY = clientY
this.clientX = clientX
w.globals.capturedSeriesIndex = -1
w.globals.capturedDataPointIndex = -1
if (
clientY < seriesBound.top ||
clientY > seriesBound.top + seriesBound.height
) {
this.handleMouseOut(opt)
return
}
if (
Array.isArray(this.tConfig.enabledOnSeries) &&
!w.config.tooltip.shared
) {
const index = parseInt(opt.paths.getAttribute('index'), 10)
if (this.tConfig.enabledOnSeries.indexOf(index) < 0) {
this.handleMouseOut(opt)
return
}
}
const tooltipEl = this.getElTooltip()
const xcrosshairs = this.getElXCrosshairs()
let isStickyTooltip =
w.globals.xyCharts ||
(w.config.chart.type === 'bar' &&
!w.globals.isBarHorizontal &&
this.tooltipUtil.hasBars() &&
this.tConfig.shared) ||
(w.globals.comboCharts && this.tooltipUtil.hasBars())
if (
e.type === 'mousemove' ||
e.type === 'touchmove' ||
e.type === 'mouseup'
) {
// there is no series to hover over
if (
w.globals.collapsedSeries.length +
w.globals.ancillaryCollapsedSeries.length ===
w.globals.series.length
) {
return
}
if (xcrosshairs !== null) {
xcrosshairs.classList.add('apexcharts-active')
}
const hasYAxisTooltip = this.yaxisTooltips.filter((b) => {
return b === true
})
if (this.ycrosshairs !== null && hasYAxisTooltip.length) {
this.ycrosshairs.classList.add('apexcharts-active')
}
if (isStickyTooltip && !this.showOnIntersect) {
this.handleStickyTooltip(e, clientX, clientY, opt)
} else {
if (
w.config.chart.type === 'heatmap' ||
w.config.chart.type === 'treemap'
) {
let markerXY = this.intersect.handleHeatTreeTooltip({
e,
opt,
x,
y,
type: w.config.chart.type,
})
x = markerXY.x
y = markerXY.y
tooltipEl.style.left = x + 'px'
tooltipEl.style.top = y + 'px'
} else {
if (this.tooltipUtil.hasBars()) {
this.intersect.handleBarTooltip({
e,
opt,
})
}
if (this.tooltipUtil.hasMarkers()) {
// intersect - line/area/scatter/bubble
this.intersect.handleMarkerTooltip({
e,
opt,
x,
y,
})
}
}
}
if (this.yaxisTooltips.length) {
for (let yt = 0; yt < w.config.yaxis.length; yt++) {
this.axesTooltip.drawYaxisTooltipText(yt, clientY, this.xyRatios)
}
}
opt.tooltipEl.classList.add('apexcharts-active')
} else if (e.type === 'mouseout' || e.type === 'touchend') {
this.handleMouseOut(opt)
}
}
// tooltip handling for pie/donuts
nonAxisChartsTooltips({ e, opt, tooltipRect }) {
let w = this.w
let rel = opt.paths.getAttribute('rel')
const tooltipEl = this.getElTooltip()
let seriesBound = w.globals.dom.elWrap.getBoundingClientRect()
if (e.type === 'mousemove' || e.type === 'touchmove') {
tooltipEl.classList.add('apexcharts-active')
this.tooltipLabels.drawSeriesTexts({
ttItems: opt.ttItems,
i: parseInt(rel, 10) - 1,
shared: false,
})
let x = w.globals.clientX - seriesBound.left - tooltipRect.ttWidth / 2
let y = w.globals.clientY - seriesBound.top - tooltipRect.ttHeight - 10
tooltipEl.style.left = x + 'px'
tooltipEl.style.top = y + 'px'
if (w.config.legend.tooltipHoverFormatter) {
let legendFormatter = w.config.legend.tooltipHoverFormatter
const i = rel - 1
const legendName =
this.legendLabels[i].getAttribute('data:default-text')
let text = legendFormatter(legendName, {
seriesIndex: i,
dataPointIndex: i,
w,
})
this.legendLabels[i].innerHTML = text
}
} else if (e.type === 'mouseout' || e.type === 'touchend') {
tooltipEl.classList.remove('apexcharts-active')
if (w.config.legend.tooltipHoverFormatter) {
this.legendLabels.forEach((l) => {
const defaultText = l.getAttribute('data:default-text')
l.innerHTML = decodeURIComponent(defaultText)
})
}
}
}
handleStickyTooltip(e, clientX, clientY, opt) {
const w = this.w
let capj = this.tooltipUtil.getNearestValues({
context: this,
hoverArea: opt.hoverArea,
elGrid: opt.elGrid,
clientX,
clientY,
})
let j = capj.j
let capturedSeries = capj.capturedSeries
if (w.globals.collapsedSeriesIndices.includes(capturedSeries))
capturedSeries = null
const bounds = opt.elGrid.getBoundingClientRect()
if (capj.hoverX < 0 || capj.hoverX > bounds.width) {
this.handleMouseOut(opt)
return
}
if (capturedSeries !== null) {
this.handleStickyCapturedSeries(e, capturedSeries, opt, j)
} else {
// couldn't capture any series. check if shared X is same,
// if yes, draw a grouped tooltip
if (this.tooltipUtil.isXoverlap(j) || w.globals.isBarHorizontal) {
const firstVisibleSeries = w.globals.series.findIndex(
(s, i) => !w.globals.collapsedSeriesIndices.includes(i)
)
this.create(e, this, firstVisibleSeries, j, opt.ttItems)
}
}
}
handleStickyCapturedSeries(e, capturedSeries, opt, j) {
const w = this.w
if (!this.tConfig.shared) {
let ignoreNull = w.globals.series[capturedSeries][j] === null
if (ignoreNull) {
this.handleMouseOut(opt)
return
}
}
if (typeof w.globals.series[capturedSeries][j] !== 'undefined') {
if (
this.tConfig.shared &&
this.tooltipUtil.isXoverlap(j) &&
this.tooltipUtil.isInitialSeriesSameLen()
) {
this.create(e, this, capturedSeries, j, opt.ttItems)
} else {
this.create(e, this, capturedSeries, j, opt.ttItems, false)
}
} else {
if (this.tooltipUtil.isXoverlap(j)) {
const firstVisibleSeries = w.globals.series.findIndex(
(s, i) => !w.globals.collapsedSeriesIndices.includes(i)
)
this.create(e, this, firstVisibleSeries, j, opt.ttItems)
}
}
}
deactivateHoverFilter() {
let w = this.w
let graphics = new Graphics(this.ctx)
let allPaths = w.globals.dom.Paper.select(`.apexcharts-bar-area`)
for (let b = 0; b < allPaths.length; b++) {
graphics.pathMouseLeave(allPaths[b])
}
}
handleMouseOut(opt) {
const w = this.w
const xcrosshairs = this.getElXCrosshairs()
opt.tooltipEl.classList.remove('apexcharts-active')
this.deactivateHoverFilter()
if (w.config.chart.type !== 'bubble') {
this.marker.resetPointsSize()
}
if (xcrosshairs !== null) {
xcrosshairs.classList.remove('apexcharts-active')
}
if (this.ycrosshairs !== null) {
this.ycrosshairs.classList.remove('apexcharts-active')
}
if (this.isXAxisTooltipEnabled) {
this.xaxisTooltip.classList.remove('apexcharts-active')
}
if (this.yaxisTooltips.length) {
if (this.yaxisTTEls === null) {
this.yaxisTTEls = w.globals.dom.baseEl.querySelectorAll(
'.apexcharts-yaxistooltip'
)
}
for (let i = 0; i < this.yaxisTTEls.length; i++) {
this.yaxisTTEls[i].classList.remove('apexcharts-active')
}
}
if (w.config.legend.tooltipHoverFormatter) {
this.legendLabels.forEach((l) => {
const defaultText = l.getAttribute('data:default-text')
l.innerHTML = decodeURIComponent(defaultText)
})
}
}
markerClick(e, seriesIndex, dataPointIndex) {
const w = this.w
if (typeof w.config.chart.events.markerClick === 'function') {
w.config.chart.events.markerClick(e, this.ctx, {
seriesIndex,
dataPointIndex,
w,
})
}
this.ctx.events.fireEvent('markerClick', [
e,
this.ctx,
{ seriesIndex, dataPointIndex, w },
])
}
create(e, context, capturedSeries, j, ttItems, shared = null) {
let w = this.w
let ttCtx = context
if (e.type === 'mouseup') {
this.markerClick(e, capturedSeries, j)
}
if (shared === null) shared = this.tConfig.shared
const hasMarkers = this.tooltipUtil.hasMarkers(capturedSeries)
const bars = this.tooltipUtil.getElBars()
if (w.config.legend.tooltipHoverFormatter) {
let legendFormatter = w.config.legend.tooltipHoverFormatter
let els = Array.from(this.legendLabels)
// reset all legend values first
els.forEach((l) => {
const legendName = l.getAttribute('data:default-text')
l.innerHTML = decodeURIComponent(legendName)
})
// for irregular time series
for (let i = 0; i < els.length; i++) {
const l = els[i]
const lsIndex = parseInt(l.getAttribute('i'), 10)
const legendName = decodeURIComponent(
l.getAttribute('data:default-text')
)
let text = legendFormatter(legendName, {
seriesIndex: shared ? lsIndex : capturedSeries,
dataPointIndex: j,
w,
})
if (!shared) {
l.innerHTML = lsIndex === capturedSeries ? text : legendName
if (capturedSeries === lsIndex) {
break
}
} else {
l.innerHTML =
w.globals.collapsedSeriesIndices.indexOf(lsIndex) < 0
? text
: legendName
}
}
}
const commonSeriesTextsParams = {
ttItems,
i: capturedSeries,
j,
...(typeof w.globals.seriesRange?.[capturedSeries]?.[j]?.y[0]?.y1 !==
'undefined' && {
y1: w.globals.seriesRange?.[capturedSeries]?.[j]?.y[0]?.y1,
}),
...(typeof w.globals.seriesRange?.[capturedSeries]?.[j]?.y[0]?.y2 !==
'undefined' && {
y2: w.globals.seriesRange?.[capturedSeries]?.[j]?.y[0]?.y2,
}),
}
if (shared) {
ttCtx.tooltipLabels.drawSeriesTexts({
...commonSeriesTextsParams,
shared: this.showOnIntersect ? false : this.tConfig.shared,
})
if (hasMarkers) {
if (w.globals.markers.largestSize > 0) {
ttCtx.marker.enlargePoints(j)
} else {
ttCtx.tooltipPosition.moveDynamicPointsOnHover(j)
}
} else if (this.tooltipUtil.hasBars()) {
this.barSeriesHeight = this.tooltipUtil.getBarsHeight(bars)
if (this.barSeriesHeight > 0) {
// hover state, activate snap filter
let graphics = new Graphics(this.ctx)
let paths = w.globals.dom.Paper.select(
`.apexcharts-bar-area[j='${j}']`
)
// de-activate first
this.deactivateHoverFilter()
this.tooltipPosition.moveStickyTooltipOverBars(j, capturedSeries)
for (let b = 0; b < paths.length; b++) {
graphics.pathMouseEnter(paths[b])
}
}
}
} else {
ttCtx.tooltipLabels.drawSeriesTexts({
shared: false,
...commonSeriesTextsParams,
})
if (this.tooltipUtil.hasBars()) {
ttCtx.tooltipPosition.moveStickyTooltipOverBars(j, capturedSeries)
}
if (hasMarkers) {
ttCtx.tooltipPosition.moveMarkers(capturedSeries, j)
}
}
}
}
+356
View File
@@ -0,0 +1,356 @@
import Utilities from '../../utils/Utils'
/**
* ApexCharts Tooltip.Utils Class to support Tooltip functionality.
*
* @module Tooltip.Utils
**/
export default class Utils {
constructor(tooltipContext) {
this.w = tooltipContext.w
this.ttCtx = tooltipContext
this.ctx = tooltipContext.ctx
}
/**
** When hovering over series, you need to capture which series is being hovered on.
** This function will return both capturedseries index as well as inner index of that series
* @memberof Utils
* @param {object}
* - hoverArea = the rect on which user hovers
* - elGrid = dimensions of the hover rect (it can be different than hoverarea)
*/
getNearestValues({ hoverArea, elGrid, clientX, clientY }) {
let w = this.w
const seriesBound = elGrid.getBoundingClientRect()
const hoverWidth = seriesBound.width
const hoverHeight = seriesBound.height
let xDivisor = hoverWidth / (w.globals.dataPoints - 1)
let yDivisor = hoverHeight / w.globals.dataPoints
const hasBars = this.hasBars()
if (
(w.globals.comboCharts || hasBars) &&
!w.config.xaxis.convertedCatToNumeric
) {
xDivisor = hoverWidth / w.globals.dataPoints
}
let hoverX = clientX - seriesBound.left - w.globals.barPadForNumericAxis
let hoverY = clientY - seriesBound.top
const notInRect =
hoverX < 0 || hoverY < 0 || hoverX > hoverWidth || hoverY > hoverHeight
if (notInRect) {
hoverArea.classList.remove('hovering-zoom')
hoverArea.classList.remove('hovering-pan')
} else {
if (w.globals.zoomEnabled) {
hoverArea.classList.remove('hovering-pan')
hoverArea.classList.add('hovering-zoom')
} else if (w.globals.panEnabled) {
hoverArea.classList.remove('hovering-zoom')
hoverArea.classList.add('hovering-pan')
}
}
let j = Math.round(hoverX / xDivisor)
let jHorz = Math.floor(hoverY / yDivisor)
if (hasBars && !w.config.xaxis.convertedCatToNumeric) {
j = Math.ceil(hoverX / xDivisor)
j = j - 1
}
let capturedSeries = null
let closest = null
let seriesXValArr = w.globals.seriesXvalues.map((seriesXVal) => {
return seriesXVal.filter((s) => Utilities.isNumber(s))
})
let seriesYValArr = w.globals.seriesYvalues.map((seriesYVal) => {
return seriesYVal.filter((s) => Utilities.isNumber(s))
})
// if X axis type is not category and tooltip is not shared, then we need to find the cursor position and get the nearest value
if (w.globals.isXNumeric) {
// Change origin of cursor position so that we can compute the relative nearest point to the cursor on our chart
// we only need to scale because all points are relative to the bounds.left and bounds.top => origin is virtually (0, 0)
const chartGridEl = this.ttCtx.getElGrid()
const chartGridElBoundingRect = chartGridEl.getBoundingClientRect()
const transformedHoverX =
hoverX * (chartGridElBoundingRect.width / hoverWidth)
const transformedHoverY =
hoverY * (chartGridElBoundingRect.height / hoverHeight)
closest = this.closestInMultiArray(
transformedHoverX,
transformedHoverY,
seriesXValArr,
seriesYValArr
)
capturedSeries = closest.index
j = closest.j
if (capturedSeries !== null) {
// initial push, it should be a little smaller than the 1st val
seriesXValArr = w.globals.seriesXvalues[capturedSeries]
closest = this.closestInArray(transformedHoverX, seriesXValArr)
j = closest.index
}
}
w.globals.capturedSeriesIndex =
capturedSeries === null ? -1 : capturedSeries
if (!j || j < 1) j = 0
if (w.globals.isBarHorizontal) {
w.globals.capturedDataPointIndex = jHorz
} else {
w.globals.capturedDataPointIndex = j
}
return {
capturedSeries,
j: w.globals.isBarHorizontal ? jHorz : j,
hoverX,
hoverY,
}
}
closestInMultiArray(hoverX, hoverY, Xarrays, Yarrays) {
let w = this.w
let activeIndex = 0
let currIndex = null
let j = -1
if (w.globals.series.length > 1) {
activeIndex = this.getFirstActiveXArray(Xarrays)
} else {
currIndex = 0
}
let currX = Xarrays[activeIndex][0]
let diffX = Math.abs(hoverX - currX)
// find nearest point on x-axis
Xarrays.forEach((arrX) => {
arrX.forEach((x, iX) => {
const newDiff = Math.abs(hoverX - x)
if (newDiff <= diffX) {
diffX = newDiff
j = iX
}
})
})
if (j !== -1) {
// find nearest graph on y-axis relevanted to nearest point on x-axis
let currY = Yarrays[activeIndex][j]
let diffY = Math.abs(hoverY - currY)
currIndex = activeIndex
Yarrays.forEach((arrY, iAY) => {
const newDiff = Math.abs(hoverY - arrY[j])
if (newDiff <= diffY) {
diffY = newDiff
currIndex = iAY
}
})
}
return {
index: currIndex,
j,
}
}
getFirstActiveXArray(Xarrays) {
const w = this.w
let activeIndex = 0
let firstActiveSeriesIndex = Xarrays.map((xarr, index) => {
return xarr.length > 0 ? index : -1
})
for (let a = 0; a < firstActiveSeriesIndex.length; a++) {
if (
firstActiveSeriesIndex[a] !== -1 &&
w.globals.collapsedSeriesIndices.indexOf(a) === -1 &&
w.globals.ancillaryCollapsedSeriesIndices.indexOf(a) === -1
) {
activeIndex = firstActiveSeriesIndex[a]
break
}
}
return activeIndex
}
closestInArray(val, arr) {
let curr = arr[0]
let currIndex = null
let diff = Math.abs(val - curr)
for (let i = 0; i < arr.length; i++) {
let newdiff = Math.abs(val - arr[i])
if (newdiff < diff) {
diff = newdiff
currIndex = i
}
}
return {
index: currIndex,
}
}
/**
* When there are multiple series, it is possible to have different x values for each series.
* But it may be possible in those multiple series, that there is same x value for 2 or more
* series.
* @memberof Utils
* @param {int}
* - j = is the inner index of series -> (series[i][j])
* @return {bool}
*/
isXoverlap(j) {
let w = this.w
let xSameForAllSeriesJArr = []
const seriesX = w.globals.seriesX.filter((s) => typeof s[0] !== 'undefined')
if (seriesX.length > 0) {
for (let i = 0; i < seriesX.length - 1; i++) {
if (
typeof seriesX[i][j] !== 'undefined' &&
typeof seriesX[i + 1][j] !== 'undefined'
) {
if (seriesX[i][j] !== seriesX[i + 1][j]) {
xSameForAllSeriesJArr.push('unEqual')
}
}
}
}
if (xSameForAllSeriesJArr.length === 0) {
return true
}
return false
}
isInitialSeriesSameLen() {
let sameLen = true
const initialSeries = this.w.globals.initialSeries
for (let i = 0; i < initialSeries.length - 1; i++) {
if (initialSeries[i].data.length !== initialSeries[i + 1].data.length) {
sameLen = false
break
}
}
return sameLen
}
getBarsHeight(allbars) {
let bars = [...allbars]
const totalHeight = bars.reduce((acc, bar) => acc + bar.getBBox().height, 0)
return totalHeight
}
getElMarkers(capturedSeries) {
// The selector .apexcharts-series-markers-wrap > * includes marker groups for which the
// .apexcharts-series-markers class is not added due to null values or discrete markers
if (typeof capturedSeries == 'number') {
return this.w.globals.dom.baseEl.querySelectorAll(
`.apexcharts-series[data\\:realIndex='${capturedSeries}'] .apexcharts-series-markers-wrap > *`
)
}
return this.w.globals.dom.baseEl.querySelectorAll(
'.apexcharts-series-markers-wrap > *'
)
}
getAllMarkers() {
// first get all marker parents. This parent class contains series-index
// which helps to sort the markers as they are dynamic
let markersWraps = this.w.globals.dom.baseEl.querySelectorAll(
'.apexcharts-series-markers-wrap'
)
markersWraps = [...markersWraps]
markersWraps.sort((a, b) => {
var indexA = Number(a.getAttribute('data:realIndex'))
var indexB = Number(b.getAttribute('data:realIndex'))
return indexB < indexA ? 1 : indexB > indexA ? -1 : 0
})
let markers = []
markersWraps.forEach((m) => {
markers.push(m.querySelector('.apexcharts-marker'))
})
return markers
}
hasMarkers(capturedSeries) {
const markers = this.getElMarkers(capturedSeries)
return markers.length > 0
}
getElBars() {
return this.w.globals.dom.baseEl.querySelectorAll(
'.apexcharts-bar-series, .apexcharts-candlestick-series, .apexcharts-boxPlot-series, .apexcharts-rangebar-series'
)
}
hasBars() {
const bars = this.getElBars()
return bars.length > 0
}
getHoverMarkerSize(index) {
const w = this.w
let hoverSize = w.config.markers.hover.size
if (hoverSize === undefined) {
hoverSize =
w.globals.markers.size[index] + w.config.markers.hover.sizeOffset
}
return hoverSize
}
toggleAllTooltipSeriesGroups(state) {
let w = this.w
const ttCtx = this.ttCtx
if (ttCtx.allTooltipSeriesGroups.length === 0) {
ttCtx.allTooltipSeriesGroups = w.globals.dom.baseEl.querySelectorAll(
'.apexcharts-tooltip-series-group'
)
}
let allTooltipSeriesGroups = ttCtx.allTooltipSeriesGroups
for (let i = 0; i < allTooltipSeriesGroups.length; i++) {
if (state === 'enable') {
allTooltipSeriesGroups[i].classList.add('apexcharts-active')
allTooltipSeriesGroups[i].style.display = w.config.tooltip.items.display
} else {
allTooltipSeriesGroups[i].classList.remove('apexcharts-active')
allTooltipSeriesGroups[i].style.display = 'none'
}
}
}
}