apexcharts
Version:
A JavaScript Chart Library
345 lines (292 loc) • 9 kB
JavaScript
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,
})
if (barXY.j === null && barXY.barHeight === 0 && barXY.barWidth === 0) {
return // bar was not hovered and didn't receive correct coords
}
i = barXY.i
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
)
if (x + ttCtx.tooltipRect.ttWidth > w.globals.gridWidth) {
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()))
) {
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)
}
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