Current File : /home/tradevaly/www/node_modules/apexcharts/src/modules/tooltip/Tooltip.js |
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
}
getElTooltip(ctx) {
if (!ctx) ctx = this
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.blxaxisTooltip = 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')
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.seriesHover.bind(self, extendedOpts),
{ capture: false, passive: true }
)
})
}
}
/*
** The actual series hover function
*/
seriesHover(opt, e) {
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()
// 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'
) {
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 (capj.hoverX < 0 || capj.hoverX > w.globals.gridWidth) {
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) {
this.create(e, this, 0, j, opt.ttItems)
}
}
}
handleStickyCapturedSeries(e, capturedSeries, opt, j) {
const w = this.w
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)) {
this.create(e, this, 0, 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.blxaxisTooltip) {
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()
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
}
}
}
if (shared) {
ttCtx.tooltipLabels.drawSeriesTexts({
ttItems,
i: capturedSeries,
j,
shared: this.showOnIntersect ? false : this.tConfig.shared
})
if (hasMarkers) {
if (w.globals.markers.largestSize > 0) {
ttCtx.marker.enlargePoints(j)
} else {
ttCtx.tooltipPosition.moveDynamicPointsOnHover(j)
}
}
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)
for (let b = 0; b < paths.length; b++) {
graphics.pathMouseEnter(paths[b])
}
}
}
} else {
ttCtx.tooltipLabels.drawSeriesTexts({
shared: false,
ttItems,
i: capturedSeries,
j
})
if (this.tooltipUtil.hasBars()) {
ttCtx.tooltipPosition.moveStickyTooltipOverBars(j)
}
if (hasMarkers) {
ttCtx.tooltipPosition.moveMarkers(capturedSeries, j)
}
}
}
}