feat:Added piechart in Dashboard
This commit is contained in:
+260
@@ -0,0 +1,260 @@
|
||||
import Utils from '../utils/Utils'
|
||||
|
||||
/**
|
||||
* ApexCharts Animation Class.
|
||||
*
|
||||
* @module Animations
|
||||
**/
|
||||
|
||||
export default class Animations {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
|
||||
this.setEasingFunctions()
|
||||
}
|
||||
|
||||
setEasingFunctions() {
|
||||
let easing
|
||||
|
||||
if (this.w.globals.easing) return
|
||||
|
||||
const userDefinedEasing = this.w.config.chart.animations.easing
|
||||
|
||||
switch (userDefinedEasing) {
|
||||
case 'linear': {
|
||||
easing = '-'
|
||||
break
|
||||
}
|
||||
case 'easein': {
|
||||
easing = '<'
|
||||
break
|
||||
}
|
||||
case 'easeout': {
|
||||
easing = '>'
|
||||
break
|
||||
}
|
||||
case 'easeinout': {
|
||||
easing = '<>'
|
||||
break
|
||||
}
|
||||
case 'swing': {
|
||||
easing = (pos) => {
|
||||
let s = 1.70158
|
||||
let ret = (pos -= 1) * pos * ((s + 1) * pos + s) + 1
|
||||
return ret
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'bounce': {
|
||||
easing = (pos) => {
|
||||
let ret = ''
|
||||
if (pos < 1 / 2.75) {
|
||||
ret = 7.5625 * pos * pos
|
||||
} else if (pos < 2 / 2.75) {
|
||||
ret = 7.5625 * (pos -= 1.5 / 2.75) * pos + 0.75
|
||||
} else if (pos < 2.5 / 2.75) {
|
||||
ret = 7.5625 * (pos -= 2.25 / 2.75) * pos + 0.9375
|
||||
} else {
|
||||
ret = 7.5625 * (pos -= 2.625 / 2.75) * pos + 0.984375
|
||||
}
|
||||
return ret
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'elastic': {
|
||||
easing = (pos) => {
|
||||
if (pos === !!pos) return pos
|
||||
return (
|
||||
Math.pow(2, -10 * pos) *
|
||||
Math.sin(((pos - 0.075) * (2 * Math.PI)) / 0.3) +
|
||||
1
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
easing = '<>'
|
||||
}
|
||||
}
|
||||
|
||||
this.w.globals.easing = easing
|
||||
}
|
||||
|
||||
animateLine(el, from, to, speed) {
|
||||
el.attr(from).animate(speed).attr(to)
|
||||
}
|
||||
|
||||
/*
|
||||
** Animate radius of a circle element
|
||||
*/
|
||||
animateMarker(el, from, to, speed, easing, cb) {
|
||||
if (!from) from = 0
|
||||
|
||||
el.attr({
|
||||
r: from,
|
||||
width: from,
|
||||
height: from,
|
||||
})
|
||||
.animate(speed, easing)
|
||||
.attr({
|
||||
r: to,
|
||||
width: to.width,
|
||||
height: to.height,
|
||||
})
|
||||
.afterAll(() => {
|
||||
cb()
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
** Animate radius and position of a circle element
|
||||
*/
|
||||
animateCircle(el, from, to, speed, easing) {
|
||||
el.attr({
|
||||
r: from.r,
|
||||
cx: from.cx,
|
||||
cy: from.cy,
|
||||
})
|
||||
.animate(speed, easing)
|
||||
.attr({
|
||||
r: to.r,
|
||||
cx: to.cx,
|
||||
cy: to.cy,
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
** Animate rect properties
|
||||
*/
|
||||
animateRect(el, from, to, speed, fn) {
|
||||
el.attr(from)
|
||||
.animate(speed)
|
||||
.attr(to)
|
||||
.afterAll(() => fn())
|
||||
}
|
||||
|
||||
animatePathsGradually(params) {
|
||||
let { el, realIndex, j, fill, pathFrom, pathTo, speed, delay } = params
|
||||
|
||||
let me = this
|
||||
let w = this.w
|
||||
|
||||
let delayFactor = 0
|
||||
|
||||
if (w.config.chart.animations.animateGradually.enabled) {
|
||||
delayFactor = w.config.chart.animations.animateGradually.delay
|
||||
}
|
||||
|
||||
if (
|
||||
w.config.chart.animations.dynamicAnimation.enabled &&
|
||||
w.globals.dataChanged &&
|
||||
w.config.chart.type !== 'bar'
|
||||
) {
|
||||
// disabled due to this bug - https://github.com/apexcharts/vue-apexcharts/issues/75
|
||||
delayFactor = 0
|
||||
}
|
||||
me.morphSVG(
|
||||
el,
|
||||
realIndex,
|
||||
j,
|
||||
w.config.chart.type === 'line' && !w.globals.comboCharts
|
||||
? 'stroke'
|
||||
: fill,
|
||||
pathFrom,
|
||||
pathTo,
|
||||
speed,
|
||||
delay * delayFactor
|
||||
)
|
||||
}
|
||||
|
||||
showDelayedElements() {
|
||||
this.w.globals.delayedElements.forEach((d) => {
|
||||
const ele = d.el
|
||||
ele.classList.remove('apexcharts-element-hidden')
|
||||
ele.classList.add('apexcharts-hidden-element-shown')
|
||||
})
|
||||
}
|
||||
|
||||
animationCompleted(el) {
|
||||
const w = this.w
|
||||
if (w.globals.animationEnded) return
|
||||
|
||||
w.globals.animationEnded = true
|
||||
this.showDelayedElements()
|
||||
|
||||
if (typeof w.config.chart.events.animationEnd === 'function') {
|
||||
w.config.chart.events.animationEnd(this.ctx, { el, w })
|
||||
}
|
||||
}
|
||||
|
||||
// SVG.js animation for morphing one path to another
|
||||
morphSVG(el, realIndex, j, fill, pathFrom, pathTo, speed, delay) {
|
||||
let w = this.w
|
||||
|
||||
if (!pathFrom) {
|
||||
pathFrom = el.attr('pathFrom')
|
||||
}
|
||||
|
||||
if (!pathTo) {
|
||||
pathTo = el.attr('pathTo')
|
||||
}
|
||||
|
||||
const disableAnimationForCorrupPath = (path) => {
|
||||
if (w.config.chart.type === 'radar') {
|
||||
// radar chart drops the path to bottom and hence a corrup path looks ugly
|
||||
// therefore, disable animation for such a case
|
||||
speed = 1
|
||||
}
|
||||
return `M 0 ${w.globals.gridHeight}`
|
||||
}
|
||||
|
||||
if (
|
||||
!pathFrom ||
|
||||
pathFrom.indexOf('undefined') > -1 ||
|
||||
pathFrom.indexOf('NaN') > -1
|
||||
) {
|
||||
pathFrom = disableAnimationForCorrupPath()
|
||||
}
|
||||
|
||||
if (
|
||||
!pathTo ||
|
||||
pathTo.indexOf('undefined') > -1 ||
|
||||
pathTo.indexOf('NaN') > -1
|
||||
) {
|
||||
pathTo = disableAnimationForCorrupPath()
|
||||
}
|
||||
if (!w.globals.shouldAnimate) {
|
||||
speed = 1
|
||||
}
|
||||
|
||||
el.plot(pathFrom)
|
||||
.animate(1, w.globals.easing, delay)
|
||||
.plot(pathFrom)
|
||||
.animate(speed, w.globals.easing, delay)
|
||||
.plot(pathTo)
|
||||
.afterAll(() => {
|
||||
// a flag to indicate that the original mount function can return true now as animation finished here
|
||||
|
||||
if (Utils.isNumber(j)) {
|
||||
if (
|
||||
j === w.globals.series[w.globals.maxValsInArrayIndex].length - 2 &&
|
||||
w.globals.shouldAnimate
|
||||
) {
|
||||
this.animationCompleted(el)
|
||||
}
|
||||
} else if (fill !== 'none' && w.globals.shouldAnimate) {
|
||||
if (
|
||||
(!w.globals.comboCharts &&
|
||||
realIndex === w.globals.series.length - 1) ||
|
||||
w.globals.comboCharts
|
||||
) {
|
||||
this.animationCompleted(el)
|
||||
}
|
||||
}
|
||||
|
||||
this.showDelayedElements()
|
||||
})
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import Config from './settings/Config'
|
||||
import Globals from './settings/Globals'
|
||||
|
||||
/**
|
||||
* ApexCharts Base Class for extending user options with pre-defined ApexCharts config.
|
||||
*
|
||||
* @module Base
|
||||
**/
|
||||
export default class Base {
|
||||
constructor(opts) {
|
||||
this.opts = opts
|
||||
}
|
||||
|
||||
init() {
|
||||
const config = new Config(this.opts).init({ responsiveOverride: false })
|
||||
const globals = new Globals().init(config)
|
||||
|
||||
const w = {
|
||||
config,
|
||||
globals
|
||||
}
|
||||
|
||||
return w
|
||||
}
|
||||
}
|
||||
+655
@@ -0,0 +1,655 @@
|
||||
import Bar from '../charts/Bar'
|
||||
import BarStacked from '../charts/BarStacked'
|
||||
import BoxCandleStick from '../charts/BoxCandleStick'
|
||||
import CoreUtils from './CoreUtils'
|
||||
import Crosshairs from './Crosshairs'
|
||||
import HeatMap from '../charts/HeatMap'
|
||||
import Globals from '../modules/settings/Globals'
|
||||
import Pie from '../charts/Pie'
|
||||
import Radar from '../charts/Radar'
|
||||
import Radial from '../charts/Radial'
|
||||
import RangeBar from '../charts/RangeBar'
|
||||
import Legend from './legend/Legend'
|
||||
import Line from '../charts/Line'
|
||||
import Treemap from '../charts/Treemap'
|
||||
import Graphics from './Graphics'
|
||||
import Range from './Range'
|
||||
import Utils from '../utils/Utils'
|
||||
import Scales from './Scales'
|
||||
import TimeScale from './TimeScale'
|
||||
|
||||
/**
|
||||
* ApexCharts Core Class responsible for major calculations and creating elements.
|
||||
*
|
||||
* @module Core
|
||||
**/
|
||||
|
||||
export default class Core {
|
||||
constructor(el, ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
this.el = el
|
||||
}
|
||||
|
||||
// get data and store into appropriate vars
|
||||
|
||||
setupElements() {
|
||||
let gl = this.w.globals
|
||||
let cnf = this.w.config
|
||||
|
||||
// const graphics = new Graphics(this.ctx)
|
||||
|
||||
let ct = cnf.chart.type
|
||||
let axisChartsArrTypes = [
|
||||
'line',
|
||||
'area',
|
||||
'bar',
|
||||
'rangeBar',
|
||||
'rangeArea',
|
||||
'candlestick',
|
||||
'boxPlot',
|
||||
'scatter',
|
||||
'bubble',
|
||||
'radar',
|
||||
'heatmap',
|
||||
'treemap',
|
||||
]
|
||||
|
||||
let xyChartsArrTypes = [
|
||||
'line',
|
||||
'area',
|
||||
'bar',
|
||||
'rangeBar',
|
||||
'rangeArea',
|
||||
'candlestick',
|
||||
'boxPlot',
|
||||
'scatter',
|
||||
'bubble',
|
||||
]
|
||||
|
||||
gl.axisCharts = axisChartsArrTypes.indexOf(ct) > -1
|
||||
|
||||
gl.xyCharts = xyChartsArrTypes.indexOf(ct) > -1
|
||||
|
||||
gl.isBarHorizontal =
|
||||
(cnf.chart.type === 'bar' ||
|
||||
cnf.chart.type === 'rangeBar' ||
|
||||
cnf.chart.type === 'boxPlot') &&
|
||||
cnf.plotOptions.bar.horizontal
|
||||
|
||||
gl.chartClass = '.apexcharts' + gl.chartID
|
||||
|
||||
gl.dom.baseEl = this.el
|
||||
|
||||
gl.dom.elWrap = document.createElement('div')
|
||||
Graphics.setAttrs(gl.dom.elWrap, {
|
||||
id: gl.chartClass.substring(1),
|
||||
class: 'apexcharts-canvas ' + gl.chartClass.substring(1),
|
||||
})
|
||||
this.el.appendChild(gl.dom.elWrap)
|
||||
|
||||
gl.dom.Paper = new window.SVG.Doc(gl.dom.elWrap)
|
||||
gl.dom.Paper.attr({
|
||||
class: 'apexcharts-svg',
|
||||
'xmlns:data': 'ApexChartsNS',
|
||||
transform: `translate(${cnf.chart.offsetX}, ${cnf.chart.offsetY})`,
|
||||
})
|
||||
|
||||
gl.dom.Paper.node.style.background =
|
||||
cnf.theme.mode === 'dark' && !cnf.chart.background
|
||||
? 'rgba(0, 0, 0, 0.8)'
|
||||
: cnf.chart.background
|
||||
|
||||
this.setSVGDimensions()
|
||||
|
||||
// append foreignElement (legend's parent)
|
||||
// legend is kept in foreignElement to be included while exporting
|
||||
// removing foreignElement and creating legend through HTML will not render legend in export
|
||||
gl.dom.elLegendForeign = document.createElementNS(gl.SVGNS, 'foreignObject')
|
||||
Graphics.setAttrs(gl.dom.elLegendForeign, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: gl.svgWidth,
|
||||
height: gl.svgHeight,
|
||||
})
|
||||
gl.dom.elLegendWrap = document.createElement('div')
|
||||
gl.dom.elLegendWrap.classList.add('apexcharts-legend')
|
||||
gl.dom.elLegendWrap.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml')
|
||||
gl.dom.elLegendForeign.appendChild(gl.dom.elLegendWrap)
|
||||
gl.dom.Paper.node.appendChild(gl.dom.elLegendForeign)
|
||||
|
||||
// the elGraphical is the parent of all primary visuals
|
||||
gl.dom.elGraphical = gl.dom.Paper.group().attr({
|
||||
class: 'apexcharts-inner apexcharts-graphical',
|
||||
})
|
||||
|
||||
gl.dom.elDefs = gl.dom.Paper.defs()
|
||||
|
||||
gl.dom.Paper.add(gl.dom.elGraphical)
|
||||
gl.dom.elGraphical.add(gl.dom.elDefs)
|
||||
}
|
||||
|
||||
plotChartType(ser, xyRatios) {
|
||||
const w = this.w
|
||||
const cnf = w.config
|
||||
const gl = w.globals
|
||||
|
||||
let lineSeries = {
|
||||
series: [],
|
||||
i: [],
|
||||
}
|
||||
let areaSeries = {
|
||||
series: [],
|
||||
i: [],
|
||||
}
|
||||
let scatterSeries = {
|
||||
series: [],
|
||||
i: [],
|
||||
}
|
||||
|
||||
let bubbleSeries = {
|
||||
series: [],
|
||||
i: [],
|
||||
}
|
||||
|
||||
let columnSeries = {
|
||||
series: [],
|
||||
i: [],
|
||||
}
|
||||
|
||||
let candlestickSeries = {
|
||||
series: [],
|
||||
i: [],
|
||||
}
|
||||
|
||||
let boxplotSeries = {
|
||||
series: [],
|
||||
i: [],
|
||||
}
|
||||
|
||||
let rangeBarSeries = {
|
||||
series: [],
|
||||
i: [],
|
||||
}
|
||||
|
||||
let rangeAreaSeries = {
|
||||
series: [],
|
||||
seriesRangeEnd: [],
|
||||
i: [],
|
||||
}
|
||||
|
||||
gl.series.map((serie, st) => {
|
||||
let comboCount = 0
|
||||
// if user has specified a particular type for particular series
|
||||
if (typeof ser[st].type !== 'undefined') {
|
||||
if (ser[st].type === 'column' || ser[st].type === 'bar') {
|
||||
if (gl.series.length > 1 && cnf.plotOptions.bar.horizontal) {
|
||||
// horizontal bars not supported in mixed charts, hence show a warning
|
||||
console.warn(
|
||||
'Horizontal bars are not supported in a mixed/combo chart. Please turn off `plotOptions.bar.horizontal`'
|
||||
)
|
||||
}
|
||||
columnSeries.series.push(serie)
|
||||
columnSeries.i.push(st)
|
||||
comboCount++
|
||||
w.globals.columnSeries = columnSeries.series
|
||||
} else if (ser[st].type === 'area') {
|
||||
areaSeries.series.push(serie)
|
||||
areaSeries.i.push(st)
|
||||
comboCount++
|
||||
} else if (ser[st].type === 'line') {
|
||||
lineSeries.series.push(serie)
|
||||
lineSeries.i.push(st)
|
||||
comboCount++
|
||||
} else if (ser[st].type === 'scatter') {
|
||||
scatterSeries.series.push(serie)
|
||||
scatterSeries.i.push(st)
|
||||
} else if (ser[st].type === 'bubble') {
|
||||
bubbleSeries.series.push(serie)
|
||||
bubbleSeries.i.push(st)
|
||||
comboCount++
|
||||
} else if (ser[st].type === 'candlestick') {
|
||||
candlestickSeries.series.push(serie)
|
||||
candlestickSeries.i.push(st)
|
||||
comboCount++
|
||||
} else if (ser[st].type === 'boxPlot') {
|
||||
boxplotSeries.series.push(serie)
|
||||
boxplotSeries.i.push(st)
|
||||
comboCount++
|
||||
} else if (ser[st].type === 'rangeBar') {
|
||||
rangeBarSeries.series.push(serie)
|
||||
rangeBarSeries.i.push(st)
|
||||
comboCount++
|
||||
} else if (ser[st].type === 'rangeArea') {
|
||||
rangeAreaSeries.series.push(gl.seriesRangeStart[st])
|
||||
rangeAreaSeries.seriesRangeEnd.push(gl.seriesRangeEnd[st])
|
||||
rangeAreaSeries.i.push(st)
|
||||
comboCount++
|
||||
} else {
|
||||
// user has specified type, but it is not valid (other than line/area/column)
|
||||
console.warn(
|
||||
'You have specified an unrecognized chart type. Available types for this property are line/area/column/bar/scatter/bubble/candlestick/boxPlot/rangeBar/rangeArea'
|
||||
)
|
||||
}
|
||||
if (comboCount > 1) {
|
||||
gl.comboCharts = true
|
||||
}
|
||||
} else {
|
||||
lineSeries.series.push(serie)
|
||||
lineSeries.i.push(st)
|
||||
}
|
||||
})
|
||||
|
||||
let line = new Line(this.ctx, xyRatios)
|
||||
let boxCandlestick = new BoxCandleStick(this.ctx, xyRatios)
|
||||
this.ctx.pie = new Pie(this.ctx)
|
||||
let radialBar = new Radial(this.ctx)
|
||||
this.ctx.rangeBar = new RangeBar(this.ctx, xyRatios)
|
||||
let radar = new Radar(this.ctx)
|
||||
let elGraph = []
|
||||
|
||||
if (gl.comboCharts) {
|
||||
if (areaSeries.series.length > 0) {
|
||||
elGraph.push(line.draw(areaSeries.series, 'area', areaSeries.i))
|
||||
}
|
||||
if (columnSeries.series.length > 0) {
|
||||
if (w.config.chart.stacked) {
|
||||
let barStacked = new BarStacked(this.ctx, xyRatios)
|
||||
elGraph.push(barStacked.draw(columnSeries.series, columnSeries.i))
|
||||
} else {
|
||||
this.ctx.bar = new Bar(this.ctx, xyRatios)
|
||||
elGraph.push(this.ctx.bar.draw(columnSeries.series, columnSeries.i))
|
||||
}
|
||||
}
|
||||
if (rangeAreaSeries.series.length > 0) {
|
||||
elGraph.push(
|
||||
line.draw(
|
||||
rangeAreaSeries.series,
|
||||
'rangeArea',
|
||||
rangeAreaSeries.i,
|
||||
rangeAreaSeries.seriesRangeEnd
|
||||
)
|
||||
)
|
||||
}
|
||||
if (lineSeries.series.length > 0) {
|
||||
elGraph.push(line.draw(lineSeries.series, 'line', lineSeries.i))
|
||||
}
|
||||
if (candlestickSeries.series.length > 0) {
|
||||
elGraph.push(
|
||||
boxCandlestick.draw(
|
||||
candlestickSeries.series,
|
||||
'candlestick',
|
||||
candlestickSeries.i
|
||||
)
|
||||
)
|
||||
}
|
||||
if (boxplotSeries.series.length > 0) {
|
||||
elGraph.push(
|
||||
boxCandlestick.draw(boxplotSeries.series, 'boxPlot', boxplotSeries.i)
|
||||
)
|
||||
}
|
||||
if (rangeBarSeries.series.length > 0) {
|
||||
elGraph.push(
|
||||
this.ctx.rangeBar.draw(rangeBarSeries.series, rangeBarSeries.i)
|
||||
)
|
||||
}
|
||||
|
||||
if (scatterSeries.series.length > 0) {
|
||||
const scatterLine = new Line(this.ctx, xyRatios, true)
|
||||
elGraph.push(
|
||||
scatterLine.draw(scatterSeries.series, 'scatter', scatterSeries.i)
|
||||
)
|
||||
}
|
||||
if (bubbleSeries.series.length > 0) {
|
||||
const bubbleLine = new Line(this.ctx, xyRatios, true)
|
||||
elGraph.push(
|
||||
bubbleLine.draw(bubbleSeries.series, 'bubble', bubbleSeries.i)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
switch (cnf.chart.type) {
|
||||
case 'line':
|
||||
elGraph = line.draw(gl.series, 'line')
|
||||
break
|
||||
case 'area':
|
||||
elGraph = line.draw(gl.series, 'area')
|
||||
break
|
||||
case 'bar':
|
||||
if (cnf.chart.stacked) {
|
||||
let barStacked = new BarStacked(this.ctx, xyRatios)
|
||||
elGraph = barStacked.draw(gl.series)
|
||||
} else {
|
||||
this.ctx.bar = new Bar(this.ctx, xyRatios)
|
||||
elGraph = this.ctx.bar.draw(gl.series)
|
||||
}
|
||||
break
|
||||
case 'candlestick':
|
||||
let candleStick = new BoxCandleStick(this.ctx, xyRatios)
|
||||
elGraph = candleStick.draw(gl.series, 'candlestick')
|
||||
break
|
||||
case 'boxPlot':
|
||||
let boxPlot = new BoxCandleStick(this.ctx, xyRatios)
|
||||
elGraph = boxPlot.draw(gl.series, cnf.chart.type)
|
||||
break
|
||||
case 'rangeBar':
|
||||
elGraph = this.ctx.rangeBar.draw(gl.series)
|
||||
break
|
||||
case 'rangeArea':
|
||||
elGraph = line.draw(
|
||||
gl.seriesRangeStart,
|
||||
'rangeArea',
|
||||
undefined,
|
||||
gl.seriesRangeEnd
|
||||
)
|
||||
break
|
||||
case 'heatmap':
|
||||
let heatmap = new HeatMap(this.ctx, xyRatios)
|
||||
elGraph = heatmap.draw(gl.series)
|
||||
break
|
||||
case 'treemap':
|
||||
let treemap = new Treemap(this.ctx, xyRatios)
|
||||
elGraph = treemap.draw(gl.series)
|
||||
break
|
||||
case 'pie':
|
||||
case 'donut':
|
||||
case 'polarArea':
|
||||
elGraph = this.ctx.pie.draw(gl.series)
|
||||
break
|
||||
case 'radialBar':
|
||||
elGraph = radialBar.draw(gl.series)
|
||||
break
|
||||
case 'radar':
|
||||
elGraph = radar.draw(gl.series)
|
||||
break
|
||||
default:
|
||||
elGraph = line.draw(gl.series)
|
||||
}
|
||||
}
|
||||
|
||||
return elGraph
|
||||
}
|
||||
|
||||
setSVGDimensions() {
|
||||
let gl = this.w.globals
|
||||
let cnf = this.w.config
|
||||
|
||||
gl.svgWidth = cnf.chart.width
|
||||
gl.svgHeight = cnf.chart.height
|
||||
|
||||
let elDim = Utils.getDimensions(this.el)
|
||||
|
||||
let widthUnit = cnf.chart.width
|
||||
.toString()
|
||||
.split(/[0-9]+/g)
|
||||
.pop()
|
||||
|
||||
if (widthUnit === '%') {
|
||||
if (Utils.isNumber(elDim[0])) {
|
||||
if (elDim[0].width === 0) {
|
||||
elDim = Utils.getDimensions(this.el.parentNode)
|
||||
}
|
||||
|
||||
gl.svgWidth = (elDim[0] * parseInt(cnf.chart.width, 10)) / 100
|
||||
}
|
||||
} else if (widthUnit === 'px' || widthUnit === '') {
|
||||
gl.svgWidth = parseInt(cnf.chart.width, 10)
|
||||
}
|
||||
|
||||
let heightUnit = cnf.chart.height
|
||||
.toString()
|
||||
.split(/[0-9]+/g)
|
||||
.pop()
|
||||
if (gl.svgHeight !== 'auto' && gl.svgHeight !== '') {
|
||||
if (heightUnit === '%') {
|
||||
let elParentDim = Utils.getDimensions(this.el.parentNode)
|
||||
gl.svgHeight = (elParentDim[1] * parseInt(cnf.chart.height, 10)) / 100
|
||||
} else {
|
||||
gl.svgHeight = parseInt(cnf.chart.height, 10)
|
||||
}
|
||||
} else {
|
||||
if (gl.axisCharts) {
|
||||
gl.svgHeight = gl.svgWidth / 1.61
|
||||
} else {
|
||||
gl.svgHeight = gl.svgWidth / 1.2
|
||||
}
|
||||
}
|
||||
|
||||
if (gl.svgWidth < 0) gl.svgWidth = 0
|
||||
if (gl.svgHeight < 0) gl.svgHeight = 0
|
||||
|
||||
Graphics.setAttrs(gl.dom.Paper.node, {
|
||||
width: gl.svgWidth,
|
||||
height: gl.svgHeight,
|
||||
})
|
||||
|
||||
if (heightUnit !== '%') {
|
||||
// fixes https://github.com/apexcharts/apexcharts.js/issues/2059
|
||||
let offsetY = cnf.chart.sparkline.enabled
|
||||
? 0
|
||||
: gl.axisCharts
|
||||
? cnf.chart.parentHeightOffset
|
||||
: 0
|
||||
|
||||
gl.dom.Paper.node.parentNode.parentNode.style.minHeight =
|
||||
gl.svgHeight + offsetY + 'px'
|
||||
}
|
||||
|
||||
gl.dom.elWrap.style.width = gl.svgWidth + 'px'
|
||||
gl.dom.elWrap.style.height = gl.svgHeight + 'px'
|
||||
}
|
||||
|
||||
shiftGraphPosition() {
|
||||
let gl = this.w.globals
|
||||
|
||||
let tY = gl.translateY
|
||||
let tX = gl.translateX
|
||||
|
||||
let scalingAttrs = {
|
||||
transform: 'translate(' + tX + ', ' + tY + ')',
|
||||
}
|
||||
Graphics.setAttrs(gl.dom.elGraphical.node, scalingAttrs)
|
||||
}
|
||||
|
||||
// To prevent extra spacings in the bottom of the chart, we need to recalculate the height for pie/donut/radialbar charts
|
||||
resizeNonAxisCharts() {
|
||||
const w = this.w
|
||||
|
||||
const gl = w.globals
|
||||
|
||||
let legendHeight = 0
|
||||
let offY = w.config.chart.sparkline.enabled ? 1 : 15
|
||||
offY = offY + w.config.grid.padding.bottom
|
||||
|
||||
if (
|
||||
(w.config.legend.position === 'top' ||
|
||||
w.config.legend.position === 'bottom') &&
|
||||
w.config.legend.show &&
|
||||
!w.config.legend.floating
|
||||
) {
|
||||
legendHeight =
|
||||
new Legend(this.ctx).legendHelpers.getLegendBBox().clwh + 10
|
||||
}
|
||||
|
||||
let el = w.globals.dom.baseEl.querySelector(
|
||||
'.apexcharts-radialbar, .apexcharts-pie'
|
||||
)
|
||||
|
||||
let chartInnerDimensions = w.globals.radialSize * 2.05
|
||||
|
||||
if (
|
||||
el &&
|
||||
!w.config.chart.sparkline.enabled &&
|
||||
w.config.plotOptions.radialBar.startAngle !== 0
|
||||
) {
|
||||
let elRadialRect = Utils.getBoundingClientRect(el)
|
||||
chartInnerDimensions = elRadialRect.bottom
|
||||
|
||||
let maxHeight = elRadialRect.bottom - elRadialRect.top
|
||||
|
||||
chartInnerDimensions = Math.max(w.globals.radialSize * 2.05, maxHeight)
|
||||
}
|
||||
|
||||
let newHeight = chartInnerDimensions + gl.translateY + legendHeight + offY
|
||||
|
||||
if (gl.dom.elLegendForeign) {
|
||||
gl.dom.elLegendForeign.setAttribute('height', newHeight)
|
||||
}
|
||||
|
||||
// fix apexcharts/apexcharts.js/issues/3105 (when % is provided in height, it keeps increasing)
|
||||
if (w.config.chart.height && String(w.config.chart.height).indexOf('%') > 0)
|
||||
return
|
||||
|
||||
gl.dom.elWrap.style.height = newHeight + 'px'
|
||||
|
||||
Graphics.setAttrs(gl.dom.Paper.node, {
|
||||
height: newHeight,
|
||||
})
|
||||
|
||||
gl.dom.Paper.node.parentNode.parentNode.style.minHeight = newHeight + 'px'
|
||||
}
|
||||
|
||||
/*
|
||||
** All the calculations for setting range in charts will be done here
|
||||
*/
|
||||
coreCalculations() {
|
||||
const range = new Range(this.ctx)
|
||||
range.init()
|
||||
}
|
||||
|
||||
resetGlobals() {
|
||||
const resetxyValues = () => {
|
||||
return this.w.config.series.map((s) => [])
|
||||
}
|
||||
const globalObj = new Globals()
|
||||
|
||||
let gl = this.w.globals
|
||||
globalObj.initGlobalVars(gl)
|
||||
gl.seriesXvalues = resetxyValues()
|
||||
gl.seriesYvalues = resetxyValues()
|
||||
}
|
||||
|
||||
isMultipleY() {
|
||||
// user has supplied an array in yaxis property. So, turn on multipleYAxis flag
|
||||
if (
|
||||
this.w.config.yaxis.constructor === Array &&
|
||||
this.w.config.yaxis.length > 1
|
||||
) {
|
||||
this.w.globals.isMultipleYAxis = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
xySettings() {
|
||||
let xyRatios = null
|
||||
const w = this.w
|
||||
|
||||
if (w.globals.axisCharts) {
|
||||
if (w.config.xaxis.crosshairs.position === 'back') {
|
||||
const crosshairs = new Crosshairs(this.ctx)
|
||||
crosshairs.drawXCrosshairs()
|
||||
}
|
||||
if (w.config.yaxis[0].crosshairs.position === 'back') {
|
||||
const crosshairs = new Crosshairs(this.ctx)
|
||||
crosshairs.drawYCrosshairs()
|
||||
}
|
||||
|
||||
if (
|
||||
w.config.xaxis.type === 'datetime' &&
|
||||
w.config.xaxis.labels.formatter === undefined
|
||||
) {
|
||||
this.ctx.timeScale = new TimeScale(this.ctx)
|
||||
let formattedTimeScale = []
|
||||
if (
|
||||
isFinite(w.globals.minX) &&
|
||||
isFinite(w.globals.maxX) &&
|
||||
!w.globals.isBarHorizontal
|
||||
) {
|
||||
formattedTimeScale = this.ctx.timeScale.calculateTimeScaleTicks(
|
||||
w.globals.minX,
|
||||
w.globals.maxX
|
||||
)
|
||||
} else if (w.globals.isBarHorizontal) {
|
||||
formattedTimeScale = this.ctx.timeScale.calculateTimeScaleTicks(
|
||||
w.globals.minY,
|
||||
w.globals.maxY
|
||||
)
|
||||
}
|
||||
this.ctx.timeScale.recalcDimensionsBasedOnFormat(formattedTimeScale)
|
||||
}
|
||||
|
||||
const coreUtils = new CoreUtils(this.ctx)
|
||||
xyRatios = coreUtils.getCalculatedRatios()
|
||||
}
|
||||
return xyRatios
|
||||
}
|
||||
|
||||
updateSourceChart(targetChart) {
|
||||
this.ctx.w.globals.selection = undefined
|
||||
this.ctx.updateHelpers._updateOptions(
|
||||
{
|
||||
chart: {
|
||||
selection: {
|
||||
xaxis: {
|
||||
min: targetChart.w.globals.minX,
|
||||
max: targetChart.w.globals.maxX,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
setupBrushHandler() {
|
||||
const w = this.w
|
||||
|
||||
// only for brush charts
|
||||
if (!w.config.chart.brush.enabled) {
|
||||
return
|
||||
}
|
||||
|
||||
// if user has not defined a custom function for selection - we handle the brush chart
|
||||
// otherwise we leave it to the user to define the functionality for selection
|
||||
if (typeof w.config.chart.events.selection !== 'function') {
|
||||
let targets = Array.isArray(w.config.chart.brush.targets) ? w.config.chart.brush.targets : [
|
||||
w.config.chart.brush.target,
|
||||
]
|
||||
// retro compatibility with single target option
|
||||
targets.forEach((target) => {
|
||||
let targetChart = ApexCharts.getChartByID(target)
|
||||
targetChart.w.globals.brushSource = this.ctx
|
||||
|
||||
if (typeof targetChart.w.config.chart.events.zoomed !== 'function') {
|
||||
targetChart.w.config.chart.events.zoomed = () => {
|
||||
this.updateSourceChart(targetChart)
|
||||
}
|
||||
}
|
||||
if (typeof targetChart.w.config.chart.events.scrolled !== 'function') {
|
||||
targetChart.w.config.chart.events.scrolled = () => {
|
||||
this.updateSourceChart(targetChart)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
w.config.chart.events.selection = (chart, e) => {
|
||||
targets.forEach((target) => {
|
||||
let targetChart = ApexCharts.getChartByID(target)
|
||||
|
||||
targetChart.ctx.updateHelpers._updateOptions(
|
||||
{
|
||||
xaxis: {
|
||||
min: e.xaxis.min,
|
||||
max: e.xaxis.max,
|
||||
}
|
||||
},
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+412
@@ -0,0 +1,412 @@
|
||||
/*
|
||||
** Util functions which are dependent on ApexCharts instance
|
||||
*/
|
||||
|
||||
class CoreUtils {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
}
|
||||
|
||||
static checkComboSeries(series) {
|
||||
let comboCharts = false
|
||||
let comboBarCount = 0
|
||||
let comboCount = 0
|
||||
|
||||
// if user specified a type in series too, turn on comboCharts flag
|
||||
if (series.length && typeof series[0].type !== 'undefined') {
|
||||
series.forEach((s) => {
|
||||
if (
|
||||
s.type === 'bar' ||
|
||||
s.type === 'column' ||
|
||||
s.type === 'candlestick' ||
|
||||
s.type === 'boxPlot'
|
||||
) {
|
||||
comboBarCount++
|
||||
}
|
||||
if (typeof s.type !== 'undefined') {
|
||||
comboCount++
|
||||
}
|
||||
})
|
||||
}
|
||||
if (comboCount > 0) {
|
||||
comboCharts = true
|
||||
}
|
||||
|
||||
return {
|
||||
comboBarCount,
|
||||
comboCharts,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @memberof CoreUtils
|
||||
* returns the sum of all individual values in a multiple stacked series
|
||||
* Eg. w.globals.series = [[32,33,43,12], [2,3,5,1]]
|
||||
* @return [34,36,48,13]
|
||||
**/
|
||||
getStackedSeriesTotals(excludedSeriesIndices = []) {
|
||||
const w = this.w
|
||||
let total = []
|
||||
|
||||
if (w.globals.series.length === 0) return total
|
||||
|
||||
for (
|
||||
let i = 0;
|
||||
i < w.globals.series[w.globals.maxValsInArrayIndex].length;
|
||||
i++
|
||||
) {
|
||||
let t = 0
|
||||
for (let j = 0; j < w.globals.series.length; j++) {
|
||||
if (
|
||||
typeof w.globals.series[j][i] !== 'undefined' &&
|
||||
excludedSeriesIndices.indexOf(j) === -1
|
||||
) {
|
||||
t += w.globals.series[j][i]
|
||||
}
|
||||
}
|
||||
total.push(t)
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// get total of the all values inside all series
|
||||
getSeriesTotalByIndex(index = null) {
|
||||
if (index === null) {
|
||||
// non-plot chart types - pie / donut / circle
|
||||
return this.w.config.series.reduce((acc, cur) => acc + cur, 0)
|
||||
} else {
|
||||
// axis charts - supporting multiple series
|
||||
return this.w.globals.series[index].reduce((acc, cur) => acc + cur, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @memberof CoreUtils
|
||||
* returns the sum of values in a multiple stacked grouped charts
|
||||
* Eg. w.globals.series = [[32,33,43,12], [2,3,5,1], [43, 23, 34, 22]]
|
||||
* series 1 and 2 are in a group, while series 3 is in another group
|
||||
* @return [[34, 36, 48, 12], [43, 23, 34, 22]]
|
||||
**/
|
||||
getStackedSeriesTotalsByGroups() {
|
||||
const w = this.w
|
||||
let total = []
|
||||
|
||||
w.globals.seriesGroups.forEach((sg) => {
|
||||
let includedIndexes = []
|
||||
w.config.series.forEach((s, si) => {
|
||||
if (sg.indexOf(s.name) > -1) {
|
||||
includedIndexes.push(si)
|
||||
}
|
||||
})
|
||||
|
||||
const excludedIndices = w.globals.series
|
||||
.map((_, fi) => (includedIndexes.indexOf(fi) === -1 ? fi : -1))
|
||||
.filter((f) => f !== -1)
|
||||
|
||||
total.push(this.getStackedSeriesTotals(excludedIndices))
|
||||
})
|
||||
return total
|
||||
}
|
||||
|
||||
isSeriesNull(index = null) {
|
||||
let r = []
|
||||
if (index === null) {
|
||||
// non-plot chart types - pie / donut / circle
|
||||
r = this.w.config.series.filter((d) => d !== null)
|
||||
} else {
|
||||
// axis charts - supporting multiple series
|
||||
r = this.w.config.series[index].data.filter((d) => d !== null)
|
||||
}
|
||||
|
||||
return r.length === 0
|
||||
}
|
||||
|
||||
seriesHaveSameValues(index) {
|
||||
return this.w.globals.series[index].every((val, i, arr) => val === arr[0])
|
||||
}
|
||||
|
||||
getCategoryLabels(labels) {
|
||||
const w = this.w
|
||||
let catLabels = labels.slice()
|
||||
if (w.config.xaxis.convertedCatToNumeric) {
|
||||
catLabels = labels.map((i, li) => {
|
||||
return w.config.xaxis.labels.formatter(i - w.globals.minX + 1)
|
||||
})
|
||||
}
|
||||
return catLabels
|
||||
}
|
||||
// maxValsInArrayIndex is the index of series[] which has the largest number of items
|
||||
getLargestSeries() {
|
||||
const w = this.w
|
||||
w.globals.maxValsInArrayIndex = w.globals.series
|
||||
.map((a) => a.length)
|
||||
.indexOf(
|
||||
Math.max.apply(
|
||||
Math,
|
||||
w.globals.series.map((a) => a.length)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
getLargestMarkerSize() {
|
||||
const w = this.w
|
||||
let size = 0
|
||||
|
||||
w.globals.markers.size.forEach((m) => {
|
||||
size = Math.max(size, m)
|
||||
})
|
||||
|
||||
if (w.config.markers.discrete && w.config.markers.discrete.length) {
|
||||
w.config.markers.discrete.forEach((m) => {
|
||||
size = Math.max(size, m.size)
|
||||
})
|
||||
}
|
||||
|
||||
if (size > 0) {
|
||||
size += w.config.markers.hover.sizeOffset + 1
|
||||
}
|
||||
|
||||
w.globals.markers.largestSize = size
|
||||
|
||||
return size
|
||||
}
|
||||
|
||||
/**
|
||||
* @memberof Core
|
||||
* returns the sum of all values in a series
|
||||
* Eg. w.globals.series = [[32,33,43,12], [2,3,5,1]]
|
||||
* @return [120, 11]
|
||||
**/
|
||||
getSeriesTotals() {
|
||||
const w = this.w
|
||||
|
||||
w.globals.seriesTotals = w.globals.series.map((ser, index) => {
|
||||
let total = 0
|
||||
|
||||
if (Array.isArray(ser)) {
|
||||
for (let j = 0; j < ser.length; j++) {
|
||||
total += ser[j]
|
||||
}
|
||||
} else {
|
||||
// for pie/donuts/gauges
|
||||
total += ser
|
||||
}
|
||||
|
||||
return total
|
||||
})
|
||||
}
|
||||
|
||||
getSeriesTotalsXRange(minX, maxX) {
|
||||
const w = this.w
|
||||
|
||||
const seriesTotalsXRange = w.globals.series.map((ser, index) => {
|
||||
let total = 0
|
||||
|
||||
for (let j = 0; j < ser.length; j++) {
|
||||
if (
|
||||
w.globals.seriesX[index][j] > minX &&
|
||||
w.globals.seriesX[index][j] < maxX
|
||||
) {
|
||||
total += ser[j]
|
||||
}
|
||||
}
|
||||
|
||||
return total
|
||||
})
|
||||
|
||||
return seriesTotalsXRange
|
||||
}
|
||||
|
||||
/**
|
||||
* @memberof CoreUtils
|
||||
* returns the percentage value of all individual values which can be used in a 100% stacked series
|
||||
* Eg. w.globals.series = [[32, 33, 43, 12], [2, 3, 5, 1]]
|
||||
* @return [[94.11, 91.66, 89.58, 92.30], [5.88, 8.33, 10.41, 7.7]]
|
||||
**/
|
||||
getPercentSeries() {
|
||||
const w = this.w
|
||||
|
||||
w.globals.seriesPercent = w.globals.series.map((ser, index) => {
|
||||
let seriesPercent = []
|
||||
if (Array.isArray(ser)) {
|
||||
for (let j = 0; j < ser.length; j++) {
|
||||
let total = w.globals.stackedSeriesTotals[j]
|
||||
let percent = 0
|
||||
if (total) {
|
||||
percent = (100 * ser[j]) / total
|
||||
}
|
||||
seriesPercent.push(percent)
|
||||
}
|
||||
} else {
|
||||
const total = w.globals.seriesTotals.reduce((acc, val) => acc + val, 0)
|
||||
let percent = (100 * ser) / total
|
||||
seriesPercent.push(percent)
|
||||
}
|
||||
|
||||
return seriesPercent
|
||||
})
|
||||
}
|
||||
|
||||
getCalculatedRatios() {
|
||||
let gl = this.w.globals
|
||||
|
||||
let yRatio = []
|
||||
let invertedYRatio = 0
|
||||
let xRatio = 0
|
||||
let invertedXRatio = 0
|
||||
let zRatio = 0
|
||||
let baseLineY = []
|
||||
let baseLineInvertedY = 0.1
|
||||
let baseLineX = 0
|
||||
|
||||
gl.yRange = []
|
||||
if (gl.isMultipleYAxis) {
|
||||
for (let i = 0; i < gl.minYArr.length; i++) {
|
||||
gl.yRange.push(Math.abs(gl.minYArr[i] - gl.maxYArr[i]))
|
||||
baseLineY.push(0)
|
||||
}
|
||||
} else {
|
||||
gl.yRange.push(Math.abs(gl.minY - gl.maxY))
|
||||
}
|
||||
gl.xRange = Math.abs(gl.maxX - gl.minX)
|
||||
gl.zRange = Math.abs(gl.maxZ - gl.minZ)
|
||||
|
||||
// multiple y axis
|
||||
for (let i = 0; i < gl.yRange.length; i++) {
|
||||
yRatio.push(gl.yRange[i] / gl.gridHeight)
|
||||
}
|
||||
|
||||
xRatio = gl.xRange / gl.gridWidth
|
||||
|
||||
invertedYRatio = gl.yRange / gl.gridWidth
|
||||
invertedXRatio = gl.xRange / gl.gridHeight
|
||||
zRatio = (gl.zRange / gl.gridHeight) * 16
|
||||
|
||||
if (!zRatio) {
|
||||
zRatio = 1
|
||||
}
|
||||
|
||||
if (gl.minY !== Number.MIN_VALUE && Math.abs(gl.minY) !== 0) {
|
||||
// Negative numbers present in series
|
||||
gl.hasNegs = true
|
||||
}
|
||||
|
||||
if (gl.isMultipleYAxis) {
|
||||
baseLineY = []
|
||||
|
||||
// baseline variables is the 0 of the yaxis which will be needed when there are negatives
|
||||
for (let i = 0; i < yRatio.length; i++) {
|
||||
baseLineY.push(-gl.minYArr[i] / yRatio[i])
|
||||
}
|
||||
} else {
|
||||
baseLineY.push(-gl.minY / yRatio[0])
|
||||
|
||||
if (gl.minY !== Number.MIN_VALUE && Math.abs(gl.minY) !== 0) {
|
||||
baseLineInvertedY = -gl.minY / invertedYRatio // this is for bar chart
|
||||
baseLineX = gl.minX / xRatio
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
yRatio,
|
||||
invertedYRatio,
|
||||
zRatio,
|
||||
xRatio,
|
||||
invertedXRatio,
|
||||
baseLineInvertedY,
|
||||
baseLineY,
|
||||
baseLineX,
|
||||
}
|
||||
}
|
||||
|
||||
getLogSeries(series) {
|
||||
const w = this.w
|
||||
|
||||
w.globals.seriesLog = series.map((s, i) => {
|
||||
if (w.config.yaxis[i] && w.config.yaxis[i].logarithmic) {
|
||||
return s.map((d) => {
|
||||
if (d === null) return null
|
||||
return this.getLogVal(w.config.yaxis[i].logBase, d, i)
|
||||
})
|
||||
} else {
|
||||
return s
|
||||
}
|
||||
})
|
||||
|
||||
return w.globals.invalidLogScale ? series : w.globals.seriesLog
|
||||
}
|
||||
getBaseLog(base, value) {
|
||||
return Math.log(value) / Math.log(base)
|
||||
}
|
||||
getLogVal(b, d, yIndex) {
|
||||
if (d === 0) {
|
||||
return 0
|
||||
}
|
||||
const w = this.w
|
||||
const min_log_val =
|
||||
w.globals.minYArr[yIndex] === 0
|
||||
? -1 // make sure we dont calculate log of 0
|
||||
: this.getBaseLog(b, w.globals.minYArr[yIndex])
|
||||
const max_log_val =
|
||||
w.globals.maxYArr[yIndex] === 0
|
||||
? 0 // make sure we dont calculate log of 0
|
||||
: this.getBaseLog(b, w.globals.maxYArr[yIndex])
|
||||
const number_of_height_levels = max_log_val - min_log_val
|
||||
if (d < 1) return d / number_of_height_levels
|
||||
const log_height_value = this.getBaseLog(b, d) - min_log_val
|
||||
return log_height_value / number_of_height_levels
|
||||
}
|
||||
|
||||
getLogYRatios(yRatio) {
|
||||
const w = this.w
|
||||
const gl = this.w.globals
|
||||
|
||||
gl.yLogRatio = yRatio.slice()
|
||||
|
||||
gl.logYRange = gl.yRange.map((yRange, i) => {
|
||||
if (w.config.yaxis[i] && this.w.config.yaxis[i].logarithmic) {
|
||||
let maxY = -Number.MAX_VALUE
|
||||
let minY = Number.MIN_VALUE
|
||||
let range = 1
|
||||
gl.seriesLog.forEach((s, si) => {
|
||||
s.forEach((v) => {
|
||||
if (w.config.yaxis[si] && w.config.yaxis[si].logarithmic) {
|
||||
maxY = Math.max(v, maxY)
|
||||
minY = Math.min(v, minY)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
range = Math.pow(gl.yRange[i], Math.abs(minY - maxY) / gl.yRange[i])
|
||||
|
||||
gl.yLogRatio[i] = range / gl.gridHeight
|
||||
return range
|
||||
}
|
||||
})
|
||||
|
||||
return gl.invalidLogScale ? yRatio.slice() : gl.yLogRatio
|
||||
}
|
||||
|
||||
// Some config objects can be array - and we need to extend them correctly
|
||||
static extendArrayProps(configInstance, options, w) {
|
||||
if (options.yaxis) {
|
||||
options = configInstance.extendYAxis(options, w)
|
||||
}
|
||||
if (options.annotations) {
|
||||
if (options.annotations.yaxis) {
|
||||
options = configInstance.extendYAxisAnnotations(options)
|
||||
}
|
||||
if (options.annotations.xaxis) {
|
||||
options = configInstance.extendXAxisAnnotations(options)
|
||||
}
|
||||
if (options.annotations.points) {
|
||||
options = configInstance.extendPointAnnotations(options)
|
||||
}
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
}
|
||||
|
||||
export default CoreUtils
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
import Graphics from './Graphics'
|
||||
import Filters from './Filters'
|
||||
import Utils from '../utils/Utils'
|
||||
|
||||
class Crosshairs {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
}
|
||||
|
||||
drawXCrosshairs() {
|
||||
const w = this.w
|
||||
|
||||
let graphics = new Graphics(this.ctx)
|
||||
let filters = new Filters(this.ctx)
|
||||
|
||||
let crosshairGradient = w.config.xaxis.crosshairs.fill.gradient
|
||||
let crosshairShadow = w.config.xaxis.crosshairs.dropShadow
|
||||
|
||||
let fillType = w.config.xaxis.crosshairs.fill.type
|
||||
let gradientFrom = crosshairGradient.colorFrom
|
||||
let gradientTo = crosshairGradient.colorTo
|
||||
let opacityFrom = crosshairGradient.opacityFrom
|
||||
let opacityTo = crosshairGradient.opacityTo
|
||||
let stops = crosshairGradient.stops
|
||||
|
||||
let shadow = 'none'
|
||||
let dropShadow = crosshairShadow.enabled
|
||||
let shadowLeft = crosshairShadow.left
|
||||
let shadowTop = crosshairShadow.top
|
||||
let shadowBlur = crosshairShadow.blur
|
||||
let shadowColor = crosshairShadow.color
|
||||
let shadowOpacity = crosshairShadow.opacity
|
||||
|
||||
let xcrosshairsFill = w.config.xaxis.crosshairs.fill.color
|
||||
|
||||
if (w.config.xaxis.crosshairs.show) {
|
||||
if (fillType === 'gradient') {
|
||||
xcrosshairsFill = graphics.drawGradient(
|
||||
'vertical',
|
||||
gradientFrom,
|
||||
gradientTo,
|
||||
opacityFrom,
|
||||
opacityTo,
|
||||
null,
|
||||
stops,
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
let xcrosshairs = graphics.drawRect()
|
||||
if (w.config.xaxis.crosshairs.width === 1) {
|
||||
// to prevent drawing 2 lines, convert rect to line
|
||||
xcrosshairs = graphics.drawLine()
|
||||
}
|
||||
|
||||
let gridHeight = w.globals.gridHeight
|
||||
if (!Utils.isNumber(gridHeight) || gridHeight < 0) {
|
||||
gridHeight = 0
|
||||
}
|
||||
let crosshairsWidth = w.config.xaxis.crosshairs.width
|
||||
if (!Utils.isNumber(crosshairsWidth) || crosshairsWidth < 0) {
|
||||
crosshairsWidth = 0
|
||||
}
|
||||
|
||||
xcrosshairs.attr({
|
||||
class: 'apexcharts-xcrosshairs',
|
||||
x: 0,
|
||||
y: 0,
|
||||
y2: gridHeight,
|
||||
width: crosshairsWidth,
|
||||
height: gridHeight,
|
||||
fill: xcrosshairsFill,
|
||||
filter: shadow,
|
||||
'fill-opacity': w.config.xaxis.crosshairs.opacity,
|
||||
stroke: w.config.xaxis.crosshairs.stroke.color,
|
||||
'stroke-width': w.config.xaxis.crosshairs.stroke.width,
|
||||
'stroke-dasharray': w.config.xaxis.crosshairs.stroke.dashArray
|
||||
})
|
||||
|
||||
if (dropShadow) {
|
||||
xcrosshairs = filters.dropShadow(xcrosshairs, {
|
||||
left: shadowLeft,
|
||||
top: shadowTop,
|
||||
blur: shadowBlur,
|
||||
color: shadowColor,
|
||||
opacity: shadowOpacity
|
||||
})
|
||||
}
|
||||
|
||||
w.globals.dom.elGraphical.add(xcrosshairs)
|
||||
}
|
||||
}
|
||||
|
||||
drawYCrosshairs() {
|
||||
const w = this.w
|
||||
|
||||
let graphics = new Graphics(this.ctx)
|
||||
|
||||
let crosshair = w.config.yaxis[0].crosshairs
|
||||
const offX = w.globals.barPadForNumericAxis
|
||||
|
||||
if (w.config.yaxis[0].crosshairs.show) {
|
||||
let ycrosshairs = graphics.drawLine(
|
||||
-offX,
|
||||
0,
|
||||
w.globals.gridWidth + offX,
|
||||
0,
|
||||
crosshair.stroke.color,
|
||||
crosshair.stroke.dashArray,
|
||||
crosshair.stroke.width
|
||||
)
|
||||
ycrosshairs.attr({
|
||||
class: 'apexcharts-ycrosshairs'
|
||||
})
|
||||
|
||||
w.globals.dom.elGraphical.add(ycrosshairs)
|
||||
}
|
||||
|
||||
// draw an invisible crosshair to help in positioning the yaxis tooltip
|
||||
let ycrosshairsHidden = graphics.drawLine(
|
||||
-offX,
|
||||
0,
|
||||
w.globals.gridWidth + offX,
|
||||
0,
|
||||
crosshair.stroke.color,
|
||||
0,
|
||||
0
|
||||
)
|
||||
ycrosshairsHidden.attr({
|
||||
class: 'apexcharts-ycrosshairs-hidden'
|
||||
})
|
||||
|
||||
w.globals.dom.elGraphical.add(ycrosshairsHidden)
|
||||
}
|
||||
}
|
||||
|
||||
export default Crosshairs
|
||||
+727
@@ -0,0 +1,727 @@
|
||||
import CoreUtils from './CoreUtils'
|
||||
import DateTime from './../utils/DateTime'
|
||||
import Series from './Series'
|
||||
import Utils from '../utils/Utils'
|
||||
import Defaults from './settings/Defaults'
|
||||
|
||||
export default class Data {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
|
||||
this.twoDSeries = []
|
||||
this.threeDSeries = []
|
||||
this.twoDSeriesX = []
|
||||
this.seriesGoals = []
|
||||
this.coreUtils = new CoreUtils(this.ctx)
|
||||
}
|
||||
|
||||
isMultiFormat() {
|
||||
return this.isFormatXY() || this.isFormat2DArray()
|
||||
}
|
||||
|
||||
// given format is [{x, y}, {x, y}]
|
||||
isFormatXY() {
|
||||
const series = this.w.config.series.slice()
|
||||
|
||||
const sr = new Series(this.ctx)
|
||||
this.activeSeriesIndex = sr.getActiveConfigSeriesIndex()
|
||||
|
||||
if (
|
||||
typeof series[this.activeSeriesIndex].data !== 'undefined' &&
|
||||
series[this.activeSeriesIndex].data.length > 0 &&
|
||||
series[this.activeSeriesIndex].data[0] !== null &&
|
||||
typeof series[this.activeSeriesIndex].data[0].x !== 'undefined' &&
|
||||
series[this.activeSeriesIndex].data[0] !== null
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// given format is [[x, y], [x, y]]
|
||||
isFormat2DArray() {
|
||||
const series = this.w.config.series.slice()
|
||||
|
||||
const sr = new Series(this.ctx)
|
||||
this.activeSeriesIndex = sr.getActiveConfigSeriesIndex()
|
||||
|
||||
if (
|
||||
typeof series[this.activeSeriesIndex].data !== 'undefined' &&
|
||||
series[this.activeSeriesIndex].data.length > 0 &&
|
||||
typeof series[this.activeSeriesIndex].data[0] !== 'undefined' &&
|
||||
series[this.activeSeriesIndex].data[0] !== null &&
|
||||
series[this.activeSeriesIndex].data[0].constructor === Array
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
handleFormat2DArray(ser, i) {
|
||||
const cnf = this.w.config
|
||||
const gl = this.w.globals
|
||||
|
||||
const isBoxPlot =
|
||||
cnf.chart.type === 'boxPlot' || cnf.series[i].type === 'boxPlot'
|
||||
|
||||
for (let j = 0; j < ser[i].data.length; j++) {
|
||||
if (typeof ser[i].data[j][1] !== 'undefined') {
|
||||
if (
|
||||
Array.isArray(ser[i].data[j][1]) &&
|
||||
ser[i].data[j][1].length === 4 &&
|
||||
!isBoxPlot
|
||||
) {
|
||||
// candlestick nested ohlc format
|
||||
this.twoDSeries.push(Utils.parseNumber(ser[i].data[j][1][3]))
|
||||
} else if (ser[i].data[j].length >= 5) {
|
||||
// candlestick non-nested ohlc format
|
||||
this.twoDSeries.push(Utils.parseNumber(ser[i].data[j][4]))
|
||||
} else {
|
||||
this.twoDSeries.push(Utils.parseNumber(ser[i].data[j][1]))
|
||||
}
|
||||
gl.dataFormatXNumeric = true
|
||||
}
|
||||
if (cnf.xaxis.type === 'datetime') {
|
||||
// if timestamps are provided and xaxis type is datetime,
|
||||
|
||||
let ts = new Date(ser[i].data[j][0])
|
||||
ts = new Date(ts).getTime()
|
||||
this.twoDSeriesX.push(ts)
|
||||
} else {
|
||||
this.twoDSeriesX.push(ser[i].data[j][0])
|
||||
}
|
||||
}
|
||||
|
||||
for (let j = 0; j < ser[i].data.length; j++) {
|
||||
if (typeof ser[i].data[j][2] !== 'undefined') {
|
||||
this.threeDSeries.push(ser[i].data[j][2])
|
||||
gl.isDataXYZ = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleFormatXY(ser, i) {
|
||||
const cnf = this.w.config
|
||||
const gl = this.w.globals
|
||||
|
||||
const dt = new DateTime(this.ctx)
|
||||
|
||||
let activeI = i
|
||||
if (gl.collapsedSeriesIndices.indexOf(i) > -1) {
|
||||
// fix #368
|
||||
activeI = this.activeSeriesIndex
|
||||
}
|
||||
|
||||
// get series
|
||||
for (let j = 0; j < ser[i].data.length; j++) {
|
||||
if (typeof ser[i].data[j].y !== 'undefined') {
|
||||
if (Array.isArray(ser[i].data[j].y)) {
|
||||
this.twoDSeries.push(
|
||||
Utils.parseNumber(ser[i].data[j].y[ser[i].data[j].y.length - 1])
|
||||
)
|
||||
} else {
|
||||
this.twoDSeries.push(Utils.parseNumber(ser[i].data[j].y))
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
typeof ser[i].data[j].goals !== 'undefined' &&
|
||||
Array.isArray(ser[i].data[j].goals)
|
||||
) {
|
||||
if (typeof this.seriesGoals[i] === 'undefined') {
|
||||
this.seriesGoals[i] = []
|
||||
}
|
||||
this.seriesGoals[i].push(ser[i].data[j].goals)
|
||||
} else {
|
||||
if (typeof this.seriesGoals[i] === 'undefined') {
|
||||
this.seriesGoals[i] = []
|
||||
}
|
||||
this.seriesGoals[i].push(null)
|
||||
}
|
||||
}
|
||||
|
||||
// get seriesX
|
||||
for (let j = 0; j < ser[activeI].data.length; j++) {
|
||||
const isXString = typeof ser[activeI].data[j].x === 'string'
|
||||
const isXArr = Array.isArray(ser[activeI].data[j].x)
|
||||
const isXDate = !isXArr && !!dt.isValidDate(ser[activeI].data[j].x)
|
||||
|
||||
if (isXString || isXDate) {
|
||||
// user supplied '01/01/2017' or a date string (a JS date object is not supported)
|
||||
if (isXString || cnf.xaxis.convertedCatToNumeric) {
|
||||
const isRangeColumn = gl.isBarHorizontal && gl.isRangeData
|
||||
|
||||
if (cnf.xaxis.type === 'datetime' && !isRangeColumn) {
|
||||
this.twoDSeriesX.push(dt.parseDate(ser[activeI].data[j].x))
|
||||
} else {
|
||||
// a category and not a numeric x value
|
||||
this.fallbackToCategory = true
|
||||
this.twoDSeriesX.push(ser[activeI].data[j].x)
|
||||
|
||||
if (
|
||||
!isNaN(ser[activeI].data[j].x) &&
|
||||
this.w.config.xaxis.type !== 'category' &&
|
||||
typeof ser[activeI].data[j].x !== 'string'
|
||||
) {
|
||||
gl.isXNumeric = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (cnf.xaxis.type === 'datetime') {
|
||||
this.twoDSeriesX.push(
|
||||
dt.parseDate(ser[activeI].data[j].x.toString())
|
||||
)
|
||||
} else {
|
||||
gl.dataFormatXNumeric = true
|
||||
gl.isXNumeric = true
|
||||
this.twoDSeriesX.push(parseFloat(ser[activeI].data[j].x))
|
||||
}
|
||||
}
|
||||
} else if (isXArr) {
|
||||
// a multiline label described in array format
|
||||
this.fallbackToCategory = true
|
||||
this.twoDSeriesX.push(ser[activeI].data[j].x)
|
||||
} else {
|
||||
// a numeric value in x property
|
||||
gl.isXNumeric = true
|
||||
gl.dataFormatXNumeric = true
|
||||
this.twoDSeriesX.push(ser[activeI].data[j].x)
|
||||
}
|
||||
}
|
||||
|
||||
if (ser[i].data[0] && typeof ser[i].data[0].z !== 'undefined') {
|
||||
for (let t = 0; t < ser[i].data.length; t++) {
|
||||
this.threeDSeries.push(ser[i].data[t].z)
|
||||
}
|
||||
gl.isDataXYZ = true
|
||||
}
|
||||
}
|
||||
|
||||
handleRangeData(ser, i) {
|
||||
const gl = this.w.globals
|
||||
|
||||
let range = {}
|
||||
if (this.isFormat2DArray()) {
|
||||
range = this.handleRangeDataFormat('array', ser, i)
|
||||
} else if (this.isFormatXY()) {
|
||||
range = this.handleRangeDataFormat('xy', ser, i)
|
||||
}
|
||||
|
||||
gl.seriesRangeStart.push(range.start)
|
||||
gl.seriesRangeEnd.push(range.end)
|
||||
|
||||
gl.seriesRange.push(range.rangeUniques)
|
||||
|
||||
// check for overlaps to avoid clashes in a timeline chart
|
||||
gl.seriesRange.forEach((sr, si) => {
|
||||
if (sr) {
|
||||
sr.forEach((sarr, sarri) => {
|
||||
sarr.y.forEach((arr, arri) => {
|
||||
for (let sri = 0; sri < sarr.y.length; sri++) {
|
||||
if (arri !== sri) {
|
||||
const range1y1 = arr.y1
|
||||
const range1y2 = arr.y2
|
||||
const range2y1 = sarr.y[sri].y1
|
||||
const range2y2 = sarr.y[sri].y2
|
||||
if (range1y1 <= range2y2 && range2y1 <= range1y2) {
|
||||
if (sarr.overlaps.indexOf(arr.rangeName) < 0) {
|
||||
sarr.overlaps.push(arr.rangeName)
|
||||
}
|
||||
if (sarr.overlaps.indexOf(sarr.y[sri].rangeName) < 0) {
|
||||
sarr.overlaps.push(sarr.y[sri].rangeName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return range
|
||||
}
|
||||
|
||||
handleCandleStickBoxData(ser, i) {
|
||||
const gl = this.w.globals
|
||||
|
||||
let ohlc = {}
|
||||
if (this.isFormat2DArray()) {
|
||||
ohlc = this.handleCandleStickBoxDataFormat('array', ser, i)
|
||||
} else if (this.isFormatXY()) {
|
||||
ohlc = this.handleCandleStickBoxDataFormat('xy', ser, i)
|
||||
}
|
||||
|
||||
gl.seriesCandleO[i] = ohlc.o
|
||||
gl.seriesCandleH[i] = ohlc.h
|
||||
gl.seriesCandleM[i] = ohlc.m
|
||||
gl.seriesCandleL[i] = ohlc.l
|
||||
gl.seriesCandleC[i] = ohlc.c
|
||||
|
||||
return ohlc
|
||||
}
|
||||
|
||||
handleRangeDataFormat(format, ser, i) {
|
||||
const rangeStart = []
|
||||
const rangeEnd = []
|
||||
|
||||
const uniqueKeys = ser[i].data
|
||||
.filter(
|
||||
(thing, index, self) => index === self.findIndex((t) => t.x === thing.x)
|
||||
)
|
||||
.map((r, index) => {
|
||||
return {
|
||||
x: r.x,
|
||||
overlaps: [],
|
||||
y: [],
|
||||
}
|
||||
})
|
||||
|
||||
if (format === 'array') {
|
||||
for (let j = 0; j < ser[i].data.length; j++) {
|
||||
if (Array.isArray(ser[i].data[j])) {
|
||||
rangeStart.push(ser[i].data[j][1][0])
|
||||
rangeEnd.push(ser[i].data[j][1][1])
|
||||
} else {
|
||||
rangeStart.push(ser[i].data[j])
|
||||
rangeEnd.push(ser[i].data[j])
|
||||
}
|
||||
}
|
||||
} else if (format === 'xy') {
|
||||
for (let j = 0; j < ser[i].data.length; j++) {
|
||||
let isDataPoint2D = Array.isArray(ser[i].data[j].y)
|
||||
const id = Utils.randomId()
|
||||
const x = ser[i].data[j].x
|
||||
const y = {
|
||||
y1: isDataPoint2D ? ser[i].data[j].y[0] : ser[i].data[j].y,
|
||||
y2: isDataPoint2D ? ser[i].data[j].y[1] : ser[i].data[j].y,
|
||||
rangeName: id,
|
||||
}
|
||||
|
||||
// CAUTION: mutating config object by adding a new property
|
||||
// TODO: As this is specifically for timeline rangebar charts, update the docs mentioning the series only supports xy format
|
||||
ser[i].data[j].rangeName = id
|
||||
|
||||
const uI = uniqueKeys.findIndex((t) => t.x === x)
|
||||
uniqueKeys[uI].y.push(y)
|
||||
|
||||
rangeStart.push(y.y1)
|
||||
rangeEnd.push(y.y2)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
start: rangeStart,
|
||||
end: rangeEnd,
|
||||
rangeUniques: uniqueKeys,
|
||||
}
|
||||
}
|
||||
|
||||
handleCandleStickBoxDataFormat(format, ser, i) {
|
||||
const w = this.w
|
||||
const isBoxPlot =
|
||||
w.config.chart.type === 'boxPlot' || w.config.series[i].type === 'boxPlot'
|
||||
|
||||
const serO = []
|
||||
const serH = []
|
||||
const serM = []
|
||||
const serL = []
|
||||
const serC = []
|
||||
|
||||
if (format === 'array') {
|
||||
if (
|
||||
(isBoxPlot && ser[i].data[0].length === 6) ||
|
||||
(!isBoxPlot && ser[i].data[0].length === 5)
|
||||
) {
|
||||
for (let j = 0; j < ser[i].data.length; j++) {
|
||||
serO.push(ser[i].data[j][1])
|
||||
serH.push(ser[i].data[j][2])
|
||||
|
||||
if (isBoxPlot) {
|
||||
serM.push(ser[i].data[j][3])
|
||||
serL.push(ser[i].data[j][4])
|
||||
serC.push(ser[i].data[j][5])
|
||||
} else {
|
||||
serL.push(ser[i].data[j][3])
|
||||
serC.push(ser[i].data[j][4])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let j = 0; j < ser[i].data.length; j++) {
|
||||
if (Array.isArray(ser[i].data[j][1])) {
|
||||
serO.push(ser[i].data[j][1][0])
|
||||
serH.push(ser[i].data[j][1][1])
|
||||
if (isBoxPlot) {
|
||||
serM.push(ser[i].data[j][1][2])
|
||||
serL.push(ser[i].data[j][1][3])
|
||||
serC.push(ser[i].data[j][1][4])
|
||||
} else {
|
||||
serL.push(ser[i].data[j][1][2])
|
||||
serC.push(ser[i].data[j][1][3])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (format === 'xy') {
|
||||
for (let j = 0; j < ser[i].data.length; j++) {
|
||||
if (Array.isArray(ser[i].data[j].y)) {
|
||||
serO.push(ser[i].data[j].y[0])
|
||||
serH.push(ser[i].data[j].y[1])
|
||||
if (isBoxPlot) {
|
||||
serM.push(ser[i].data[j].y[2])
|
||||
serL.push(ser[i].data[j].y[3])
|
||||
serC.push(ser[i].data[j].y[4])
|
||||
} else {
|
||||
serL.push(ser[i].data[j].y[2])
|
||||
serC.push(ser[i].data[j].y[3])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
o: serO,
|
||||
h: serH,
|
||||
m: serM,
|
||||
l: serL,
|
||||
c: serC,
|
||||
}
|
||||
}
|
||||
|
||||
parseDataAxisCharts(ser, ctx = this.ctx) {
|
||||
const cnf = this.w.config
|
||||
const gl = this.w.globals
|
||||
|
||||
const dt = new DateTime(ctx)
|
||||
|
||||
const xlabels =
|
||||
cnf.labels.length > 0 ? cnf.labels.slice() : cnf.xaxis.categories.slice()
|
||||
|
||||
gl.isRangeBar = cnf.chart.type === 'rangeBar' && gl.isBarHorizontal
|
||||
|
||||
gl.hasXaxisGroups =
|
||||
cnf.xaxis.type === 'category' && cnf.xaxis.group.groups.length > 0
|
||||
if (gl.hasXaxisGroups) {
|
||||
gl.groups = cnf.xaxis.group.groups
|
||||
}
|
||||
|
||||
gl.hasSeriesGroups = ser[0]?.group
|
||||
if (gl.hasSeriesGroups) {
|
||||
let buckets = []
|
||||
let groups = [...new Set(ser.map((s) => s.group))]
|
||||
ser.forEach((s, i) => {
|
||||
let index = groups.indexOf(s.group)
|
||||
if (!buckets[index]) buckets[index] = []
|
||||
|
||||
buckets[index].push(s.name)
|
||||
})
|
||||
gl.seriesGroups = buckets
|
||||
}
|
||||
|
||||
const handleDates = () => {
|
||||
for (let j = 0; j < xlabels.length; j++) {
|
||||
if (typeof xlabels[j] === 'string') {
|
||||
// user provided date strings
|
||||
let isDate = dt.isValidDate(xlabels[j])
|
||||
if (isDate) {
|
||||
this.twoDSeriesX.push(dt.parseDate(xlabels[j]))
|
||||
} else {
|
||||
throw new Error(
|
||||
'You have provided invalid Date format. Please provide a valid JavaScript Date'
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// user provided timestamps
|
||||
this.twoDSeriesX.push(xlabels[j])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < ser.length; i++) {
|
||||
this.twoDSeries = []
|
||||
this.twoDSeriesX = []
|
||||
this.threeDSeries = []
|
||||
|
||||
if (typeof ser[i].data === 'undefined') {
|
||||
console.error(
|
||||
"It is a possibility that you may have not included 'data' property in series."
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
cnf.chart.type === 'rangeBar' ||
|
||||
cnf.chart.type === 'rangeArea' ||
|
||||
ser[i].type === 'rangeBar' ||
|
||||
ser[i].type === 'rangeArea'
|
||||
) {
|
||||
gl.isRangeData = true
|
||||
if (cnf.chart.type === 'rangeBar' || cnf.chart.type === 'rangeArea') {
|
||||
this.handleRangeData(ser, i)
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isMultiFormat()) {
|
||||
if (this.isFormat2DArray()) {
|
||||
this.handleFormat2DArray(ser, i)
|
||||
} else if (this.isFormatXY()) {
|
||||
this.handleFormatXY(ser, i)
|
||||
}
|
||||
|
||||
if (
|
||||
cnf.chart.type === 'candlestick' ||
|
||||
ser[i].type === 'candlestick' ||
|
||||
cnf.chart.type === 'boxPlot' ||
|
||||
ser[i].type === 'boxPlot'
|
||||
) {
|
||||
this.handleCandleStickBoxData(ser, i)
|
||||
}
|
||||
|
||||
gl.series.push(this.twoDSeries)
|
||||
gl.labels.push(this.twoDSeriesX)
|
||||
gl.seriesX.push(this.twoDSeriesX)
|
||||
gl.seriesGoals = this.seriesGoals
|
||||
|
||||
if (i === this.activeSeriesIndex && !this.fallbackToCategory) {
|
||||
gl.isXNumeric = true
|
||||
}
|
||||
} else {
|
||||
if (cnf.xaxis.type === 'datetime') {
|
||||
// user didn't supplied [{x,y}] or [[x,y]], but single array in data.
|
||||
// Also labels/categories were supplied differently
|
||||
gl.isXNumeric = true
|
||||
|
||||
handleDates()
|
||||
|
||||
gl.seriesX.push(this.twoDSeriesX)
|
||||
} else if (cnf.xaxis.type === 'numeric') {
|
||||
gl.isXNumeric = true
|
||||
|
||||
if (xlabels.length > 0) {
|
||||
this.twoDSeriesX = xlabels
|
||||
gl.seriesX.push(this.twoDSeriesX)
|
||||
}
|
||||
}
|
||||
gl.labels.push(this.twoDSeriesX)
|
||||
const singleArray = ser[i].data.map((d) => Utils.parseNumber(d))
|
||||
gl.series.push(singleArray)
|
||||
}
|
||||
|
||||
gl.seriesZ.push(this.threeDSeries)
|
||||
|
||||
if (ser[i].name !== undefined) {
|
||||
gl.seriesNames.push(ser[i].name)
|
||||
} else {
|
||||
gl.seriesNames.push('series-' + parseInt(i + 1, 10))
|
||||
}
|
||||
|
||||
// overrided default color if user inputs color with series data
|
||||
if (ser[i].color !== undefined) {
|
||||
gl.seriesColors.push(ser[i].color)
|
||||
} else {
|
||||
gl.seriesColors.push(undefined)
|
||||
}
|
||||
}
|
||||
|
||||
return this.w
|
||||
}
|
||||
|
||||
parseDataNonAxisCharts(ser) {
|
||||
const gl = this.w.globals
|
||||
const cnf = this.w.config
|
||||
|
||||
gl.series = ser.slice()
|
||||
gl.seriesNames = cnf.labels.slice()
|
||||
for (let i = 0; i < gl.series.length; i++) {
|
||||
if (gl.seriesNames[i] === undefined) {
|
||||
gl.seriesNames.push('series-' + (i + 1))
|
||||
}
|
||||
}
|
||||
|
||||
return this.w
|
||||
}
|
||||
|
||||
/** User possibly set string categories in xaxis.categories or labels prop
|
||||
* Or didn't set xaxis labels at all - in which case we manually do it.
|
||||
* If user passed series data as [[3, 2], [4, 5]] or [{ x: 3, y: 55 }],
|
||||
* this shouldn't be called
|
||||
* @param {array} ser - the series which user passed to the config
|
||||
*/
|
||||
handleExternalLabelsData(ser) {
|
||||
const cnf = this.w.config
|
||||
const gl = this.w.globals
|
||||
|
||||
if (cnf.xaxis.categories.length > 0) {
|
||||
// user provided labels in xaxis.category prop
|
||||
gl.labels = cnf.xaxis.categories
|
||||
} else if (cnf.labels.length > 0) {
|
||||
// user provided labels in labels props
|
||||
gl.labels = cnf.labels.slice()
|
||||
} else if (this.fallbackToCategory) {
|
||||
// user provided labels in x prop in [{ x: 3, y: 55 }] data, and those labels are already stored in gl.labels[0], so just re-arrange the gl.labels array
|
||||
gl.labels = gl.labels[0]
|
||||
|
||||
if (gl.seriesRange.length) {
|
||||
gl.seriesRange.map((srt) => {
|
||||
srt.forEach((sr) => {
|
||||
if (gl.labels.indexOf(sr.x) < 0 && sr.x) {
|
||||
gl.labels.push(sr.x)
|
||||
}
|
||||
})
|
||||
})
|
||||
// remove duplicate x-axis labels
|
||||
gl.labels = Array.from(
|
||||
new Set(gl.labels.map(JSON.stringify)),
|
||||
JSON.parse
|
||||
)
|
||||
}
|
||||
|
||||
if (cnf.xaxis.convertedCatToNumeric) {
|
||||
const defaults = new Defaults(cnf)
|
||||
defaults.convertCatToNumericXaxis(cnf, this.ctx, gl.seriesX[0])
|
||||
this._generateExternalLabels(ser)
|
||||
}
|
||||
} else {
|
||||
this._generateExternalLabels(ser)
|
||||
}
|
||||
}
|
||||
|
||||
_generateExternalLabels(ser) {
|
||||
const gl = this.w.globals
|
||||
const cnf = this.w.config
|
||||
// user didn't provided any labels, fallback to 1-2-3-4-5
|
||||
let labelArr = []
|
||||
|
||||
if (gl.axisCharts) {
|
||||
if (gl.series.length > 0) {
|
||||
if (this.isFormatXY()) {
|
||||
// in case there is a combo chart (boxplot/scatter)
|
||||
// and there are duplicated x values, we need to eliminate duplicates
|
||||
const seriesDataFiltered = cnf.series.map((serie, s) => {
|
||||
return serie.data.filter(
|
||||
(v, i, a) => a.findIndex((t) => t.x === v.x) === i
|
||||
)
|
||||
})
|
||||
|
||||
const len = seriesDataFiltered.reduce(
|
||||
(p, c, i, a) => (a[p].length > c.length ? p : i),
|
||||
0
|
||||
)
|
||||
|
||||
for (let i = 0; i < seriesDataFiltered[len].length; i++) {
|
||||
labelArr.push(i + 1)
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < gl.series[gl.maxValsInArrayIndex].length; i++) {
|
||||
labelArr.push(i + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gl.seriesX = []
|
||||
// create gl.seriesX as it will be used in calculations of x positions
|
||||
for (let i = 0; i < ser.length; i++) {
|
||||
gl.seriesX.push(labelArr)
|
||||
}
|
||||
|
||||
// turn on the isXNumeric flag to allow minX and maxX to function properly
|
||||
if (!this.w.globals.isBarHorizontal) {
|
||||
gl.isXNumeric = true
|
||||
}
|
||||
}
|
||||
|
||||
// no series to pull labels from, put a 0-10 series
|
||||
// possibly, user collapsed all series. Hence we can't work with above calc
|
||||
if (labelArr.length === 0) {
|
||||
labelArr = gl.axisCharts
|
||||
? []
|
||||
: gl.series.map((gls, glsi) => {
|
||||
return glsi + 1
|
||||
})
|
||||
for (let i = 0; i < ser.length; i++) {
|
||||
gl.seriesX.push(labelArr)
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, pass the labelArr in gl.labels which will be printed on x-axis
|
||||
gl.labels = labelArr
|
||||
|
||||
if (cnf.xaxis.convertedCatToNumeric) {
|
||||
gl.categoryLabels = labelArr.map((l) => {
|
||||
return cnf.xaxis.labels.formatter(l)
|
||||
})
|
||||
}
|
||||
|
||||
// Turn on this global flag to indicate no labels were provided by user
|
||||
gl.noLabelsProvided = true
|
||||
}
|
||||
|
||||
// Segregate user provided data into appropriate vars
|
||||
parseData(ser) {
|
||||
let w = this.w
|
||||
let cnf = w.config
|
||||
let gl = w.globals
|
||||
this.excludeCollapsedSeriesInYAxis()
|
||||
|
||||
// If we detected string in X prop of series, we fallback to category x-axis
|
||||
this.fallbackToCategory = false
|
||||
|
||||
this.ctx.core.resetGlobals()
|
||||
this.ctx.core.isMultipleY()
|
||||
|
||||
if (gl.axisCharts) {
|
||||
// axisCharts includes line / area / column / scatter
|
||||
this.parseDataAxisCharts(ser)
|
||||
this.coreUtils.getLargestSeries()
|
||||
} else {
|
||||
// non-axis charts are pie / donut
|
||||
this.parseDataNonAxisCharts(ser)
|
||||
}
|
||||
|
||||
// set Null values to 0 in all series when user hides/shows some series
|
||||
if (cnf.chart.stacked) {
|
||||
const series = new Series(this.ctx)
|
||||
gl.series = series.setNullSeriesToZeroValues(gl.series)
|
||||
}
|
||||
|
||||
this.coreUtils.getSeriesTotals()
|
||||
if (gl.axisCharts) {
|
||||
gl.stackedSeriesTotals = this.coreUtils.getStackedSeriesTotals()
|
||||
gl.stackedSeriesTotalsByGroups =
|
||||
this.coreUtils.getStackedSeriesTotalsByGroups()
|
||||
}
|
||||
|
||||
this.coreUtils.getPercentSeries()
|
||||
|
||||
if (
|
||||
!gl.dataFormatXNumeric &&
|
||||
(!gl.isXNumeric ||
|
||||
(cnf.xaxis.type === 'numeric' &&
|
||||
cnf.labels.length === 0 &&
|
||||
cnf.xaxis.categories.length === 0))
|
||||
) {
|
||||
// x-axis labels couldn't be detected; hence try searching every option in config
|
||||
this.handleExternalLabelsData(ser)
|
||||
}
|
||||
|
||||
// check for multiline xaxis
|
||||
const catLabels = this.coreUtils.getCategoryLabels(gl.labels)
|
||||
for (let l = 0; l < catLabels.length; l++) {
|
||||
if (Array.isArray(catLabels[l])) {
|
||||
gl.isMultiLineX = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
excludeCollapsedSeriesInYAxis() {
|
||||
const w = this.w
|
||||
w.globals.ignoreYAxisIndexes = w.globals.collapsedSeries.map(
|
||||
(collapsed, i) => {
|
||||
// fix issue #1215
|
||||
// if stacked, not returning collapsed.index to preserve yaxis
|
||||
if (this.w.globals.isMultipleYAxis && !w.config.chart.stacked) {
|
||||
return collapsed.index
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
+382
@@ -0,0 +1,382 @@
|
||||
import Scatter from './../charts/Scatter'
|
||||
import Graphics from './Graphics'
|
||||
import Filters from './Filters'
|
||||
|
||||
/**
|
||||
* ApexCharts DataLabels Class for drawing dataLabels on Axes based Charts.
|
||||
*
|
||||
* @module DataLabels
|
||||
**/
|
||||
|
||||
class DataLabels {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
}
|
||||
|
||||
// When there are many datalabels to be printed, and some of them overlaps each other in the same series, this method will take care of that
|
||||
// Also, when datalabels exceeds the drawable area and get clipped off, we need to adjust and move some pixels to make them visible again
|
||||
dataLabelsCorrection(
|
||||
x,
|
||||
y,
|
||||
val,
|
||||
i,
|
||||
dataPointIndex,
|
||||
alwaysDrawDataLabel,
|
||||
fontSize
|
||||
) {
|
||||
let w = this.w
|
||||
let graphics = new Graphics(this.ctx)
|
||||
let drawnextLabel = false //
|
||||
|
||||
let textRects = graphics.getTextRects(val, fontSize)
|
||||
let width = textRects.width
|
||||
let height = textRects.height
|
||||
|
||||
if (y < 0) y = 0
|
||||
if (y > w.globals.gridHeight + height) y = w.globals.gridHeight + height / 2
|
||||
|
||||
// first value in series, so push an empty array
|
||||
if (typeof w.globals.dataLabelsRects[i] === 'undefined')
|
||||
w.globals.dataLabelsRects[i] = []
|
||||
|
||||
// then start pushing actual rects in that sub-array
|
||||
w.globals.dataLabelsRects[i].push({ x, y, width, height })
|
||||
|
||||
let len = w.globals.dataLabelsRects[i].length - 2
|
||||
let lastDrawnIndex =
|
||||
typeof w.globals.lastDrawnDataLabelsIndexes[i] !== 'undefined'
|
||||
? w.globals.lastDrawnDataLabelsIndexes[i][
|
||||
w.globals.lastDrawnDataLabelsIndexes[i].length - 1
|
||||
]
|
||||
: 0
|
||||
|
||||
if (typeof w.globals.dataLabelsRects[i][len] !== 'undefined') {
|
||||
let lastDataLabelRect = w.globals.dataLabelsRects[i][lastDrawnIndex]
|
||||
if (
|
||||
// next label forward and x not intersecting
|
||||
x > lastDataLabelRect.x + lastDataLabelRect.width ||
|
||||
y > lastDataLabelRect.y + lastDataLabelRect.height ||
|
||||
y + height < lastDataLabelRect.y ||
|
||||
x + width < lastDataLabelRect.x // next label is going to be drawn backwards
|
||||
) {
|
||||
// the 2 indexes don't override, so OK to draw next label
|
||||
drawnextLabel = true
|
||||
}
|
||||
}
|
||||
|
||||
if (dataPointIndex === 0 || alwaysDrawDataLabel) {
|
||||
drawnextLabel = true
|
||||
}
|
||||
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
textRects,
|
||||
drawnextLabel,
|
||||
}
|
||||
}
|
||||
|
||||
drawDataLabel({ type, pos, i, j, isRangeStart, strokeWidth = 2 }) {
|
||||
// this method handles line, area, bubble, scatter charts as those charts contains markers/points which have pre-defined x/y positions
|
||||
// all other charts like radar / bars / heatmaps will define their own drawDataLabel routine
|
||||
let w = this.w
|
||||
const graphics = new Graphics(this.ctx)
|
||||
|
||||
let dataLabelsConfig = w.config.dataLabels
|
||||
|
||||
let x = 0
|
||||
let y = 0
|
||||
|
||||
let dataPointIndex = j
|
||||
|
||||
let elDataLabelsWrap = null
|
||||
|
||||
if (!dataLabelsConfig.enabled || !Array.isArray(pos.x)) {
|
||||
return elDataLabelsWrap
|
||||
}
|
||||
|
||||
elDataLabelsWrap = graphics.group({
|
||||
class: 'apexcharts-data-labels',
|
||||
})
|
||||
|
||||
for (let q = 0; q < pos.x.length; q++) {
|
||||
x = pos.x[q] + dataLabelsConfig.offsetX
|
||||
y = pos.y[q] + dataLabelsConfig.offsetY + strokeWidth
|
||||
|
||||
if (!isNaN(x)) {
|
||||
// a small hack as we have 2 points for the first val to connect it
|
||||
if (j === 1 && q === 0) dataPointIndex = 0
|
||||
if (j === 1 && q === 1) dataPointIndex = 1
|
||||
|
||||
let val = w.globals.series[i][dataPointIndex]
|
||||
|
||||
if (type === 'rangeArea') {
|
||||
if (isRangeStart) {
|
||||
val = w.globals.seriesRangeStart[i][dataPointIndex]
|
||||
} else {
|
||||
val = w.globals.seriesRangeEnd[i][dataPointIndex]
|
||||
}
|
||||
}
|
||||
|
||||
let text = ''
|
||||
|
||||
const getText = (v) => {
|
||||
return w.config.dataLabels.formatter(v, {
|
||||
ctx: this.ctx,
|
||||
seriesIndex: i,
|
||||
dataPointIndex,
|
||||
w,
|
||||
})
|
||||
}
|
||||
|
||||
if (w.config.chart.type === 'bubble') {
|
||||
val = w.globals.seriesZ[i][dataPointIndex]
|
||||
text = getText(val)
|
||||
|
||||
y = pos.y[q]
|
||||
const scatter = new Scatter(this.ctx)
|
||||
let centerTextInBubbleCoords = scatter.centerTextInBubble(
|
||||
y,
|
||||
i,
|
||||
dataPointIndex
|
||||
)
|
||||
y = centerTextInBubbleCoords.y
|
||||
} else {
|
||||
if (typeof val !== 'undefined') {
|
||||
text = getText(val)
|
||||
}
|
||||
}
|
||||
|
||||
this.plotDataLabelsText({
|
||||
x,
|
||||
y,
|
||||
text,
|
||||
i,
|
||||
j: dataPointIndex,
|
||||
parent: elDataLabelsWrap,
|
||||
offsetCorrection: true,
|
||||
dataLabelsConfig: w.config.dataLabels,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return elDataLabelsWrap
|
||||
}
|
||||
|
||||
plotDataLabelsText(opts) {
|
||||
let w = this.w
|
||||
let graphics = new Graphics(this.ctx)
|
||||
let {
|
||||
x,
|
||||
y,
|
||||
i,
|
||||
j,
|
||||
text,
|
||||
textAnchor,
|
||||
fontSize,
|
||||
parent,
|
||||
dataLabelsConfig,
|
||||
color,
|
||||
alwaysDrawDataLabel,
|
||||
offsetCorrection,
|
||||
} = opts
|
||||
|
||||
if (Array.isArray(w.config.dataLabels.enabledOnSeries)) {
|
||||
if (w.config.dataLabels.enabledOnSeries.indexOf(i) < 0) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let correctedLabels = {
|
||||
x,
|
||||
y,
|
||||
drawnextLabel: true,
|
||||
textRects: null,
|
||||
}
|
||||
|
||||
if (offsetCorrection) {
|
||||
correctedLabels = this.dataLabelsCorrection(
|
||||
x,
|
||||
y,
|
||||
text,
|
||||
i,
|
||||
j,
|
||||
alwaysDrawDataLabel,
|
||||
parseInt(dataLabelsConfig.style.fontSize, 10)
|
||||
)
|
||||
}
|
||||
|
||||
// when zoomed, we don't need to correct labels offsets,
|
||||
// but if normally, labels get cropped, correct them
|
||||
if (!w.globals.zoomed) {
|
||||
x = correctedLabels.x
|
||||
y = correctedLabels.y
|
||||
}
|
||||
|
||||
if (correctedLabels.textRects) {
|
||||
// fixes #2264
|
||||
if (
|
||||
x < -20 - correctedLabels.textRects.width ||
|
||||
x > w.globals.gridWidth + correctedLabels.textRects.width + 30
|
||||
) {
|
||||
// datalabels fall outside drawing area, so draw a blank label
|
||||
text = ''
|
||||
}
|
||||
}
|
||||
|
||||
let dataLabelColor = w.globals.dataLabels.style.colors[i]
|
||||
if (
|
||||
((w.config.chart.type === 'bar' || w.config.chart.type === 'rangeBar') &&
|
||||
w.config.plotOptions.bar.distributed) ||
|
||||
w.config.dataLabels.distributed
|
||||
) {
|
||||
dataLabelColor = w.globals.dataLabels.style.colors[j]
|
||||
}
|
||||
if (typeof dataLabelColor === 'function') {
|
||||
dataLabelColor = dataLabelColor({
|
||||
series: w.globals.series,
|
||||
seriesIndex: i,
|
||||
dataPointIndex: j,
|
||||
w,
|
||||
})
|
||||
}
|
||||
if (color) {
|
||||
dataLabelColor = color
|
||||
}
|
||||
|
||||
let offX = dataLabelsConfig.offsetX
|
||||
let offY = dataLabelsConfig.offsetY
|
||||
|
||||
if (w.config.chart.type === 'bar' || w.config.chart.type === 'rangeBar') {
|
||||
// for certain chart types, we handle offsets while calculating datalabels pos
|
||||
// why? because bars/column may have negative values and based on that
|
||||
// offsets becomes reversed
|
||||
offX = 0
|
||||
offY = 0
|
||||
}
|
||||
|
||||
if (correctedLabels.drawnextLabel) {
|
||||
let dataLabelText = graphics.drawText({
|
||||
width: 100,
|
||||
height: parseInt(dataLabelsConfig.style.fontSize, 10),
|
||||
x: x + offX,
|
||||
y: y + offY,
|
||||
foreColor: dataLabelColor,
|
||||
textAnchor: textAnchor || dataLabelsConfig.textAnchor,
|
||||
text,
|
||||
fontSize: fontSize || dataLabelsConfig.style.fontSize,
|
||||
fontFamily: dataLabelsConfig.style.fontFamily,
|
||||
fontWeight: dataLabelsConfig.style.fontWeight || 'normal',
|
||||
})
|
||||
|
||||
dataLabelText.attr({
|
||||
class: 'apexcharts-datalabel',
|
||||
cx: x,
|
||||
cy: y,
|
||||
})
|
||||
|
||||
if (dataLabelsConfig.dropShadow.enabled) {
|
||||
const textShadow = dataLabelsConfig.dropShadow
|
||||
const filters = new Filters(this.ctx)
|
||||
filters.dropShadow(dataLabelText, textShadow)
|
||||
}
|
||||
|
||||
parent.add(dataLabelText)
|
||||
|
||||
if (typeof w.globals.lastDrawnDataLabelsIndexes[i] === 'undefined') {
|
||||
w.globals.lastDrawnDataLabelsIndexes[i] = []
|
||||
}
|
||||
|
||||
w.globals.lastDrawnDataLabelsIndexes[i].push(j)
|
||||
}
|
||||
}
|
||||
|
||||
addBackgroundToDataLabel(el, coords) {
|
||||
const w = this.w
|
||||
|
||||
const bCnf = w.config.dataLabels.background
|
||||
|
||||
const paddingH = bCnf.padding
|
||||
const paddingV = bCnf.padding / 2
|
||||
|
||||
const width = coords.width
|
||||
const height = coords.height
|
||||
const graphics = new Graphics(this.ctx)
|
||||
const elRect = graphics.drawRect(
|
||||
coords.x - paddingH,
|
||||
coords.y - paddingV / 2,
|
||||
width + paddingH * 2,
|
||||
height + paddingV,
|
||||
bCnf.borderRadius,
|
||||
w.config.chart.background === 'transparent'
|
||||
? '#fff'
|
||||
: w.config.chart.background,
|
||||
bCnf.opacity,
|
||||
bCnf.borderWidth,
|
||||
bCnf.borderColor
|
||||
)
|
||||
|
||||
if (bCnf.dropShadow.enabled) {
|
||||
const filters = new Filters(this.ctx)
|
||||
filters.dropShadow(elRect, bCnf.dropShadow)
|
||||
}
|
||||
|
||||
return elRect
|
||||
}
|
||||
|
||||
dataLabelsBackground() {
|
||||
const w = this.w
|
||||
|
||||
if (w.config.chart.type === 'bubble') return
|
||||
|
||||
const elDataLabels = w.globals.dom.baseEl.querySelectorAll(
|
||||
'.apexcharts-datalabels text'
|
||||
)
|
||||
|
||||
for (let i = 0; i < elDataLabels.length; i++) {
|
||||
const el = elDataLabels[i]
|
||||
const coords = el.getBBox()
|
||||
let elRect = null
|
||||
|
||||
if (coords.width && coords.height) {
|
||||
elRect = this.addBackgroundToDataLabel(el, coords)
|
||||
}
|
||||
if (elRect) {
|
||||
el.parentNode.insertBefore(elRect.node, el)
|
||||
const background = el.getAttribute('fill')
|
||||
|
||||
const shouldAnim =
|
||||
w.config.chart.animations.enabled &&
|
||||
!w.globals.resized &&
|
||||
!w.globals.dataChanged
|
||||
|
||||
if (shouldAnim) {
|
||||
elRect.animate().attr({ fill: background })
|
||||
} else {
|
||||
elRect.attr({ fill: background })
|
||||
}
|
||||
el.setAttribute('fill', w.config.dataLabels.background.foreColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bringForward() {
|
||||
const w = this.w
|
||||
const elDataLabelsNodes = w.globals.dom.baseEl.querySelectorAll(
|
||||
'.apexcharts-datalabels'
|
||||
)
|
||||
|
||||
const elSeries = w.globals.dom.baseEl.querySelector(
|
||||
'.apexcharts-plot-series:last-child'
|
||||
)
|
||||
|
||||
for (let i = 0; i < elDataLabelsNodes.length; i++) {
|
||||
if (elSeries) {
|
||||
elSeries.insertBefore(elDataLabelsNodes[i], elSeries.nextSibling)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default DataLabels
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
import Utils from '../utils/Utils'
|
||||
|
||||
export default class Events {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
|
||||
this.documentEvent = Utils.bind(this.documentEvent, this)
|
||||
}
|
||||
|
||||
addEventListener(name, handler) {
|
||||
const w = this.w
|
||||
|
||||
if (w.globals.events.hasOwnProperty(name)) {
|
||||
w.globals.events[name].push(handler)
|
||||
} else {
|
||||
w.globals.events[name] = [handler]
|
||||
}
|
||||
}
|
||||
|
||||
removeEventListener(name, handler) {
|
||||
const w = this.w
|
||||
if (!w.globals.events.hasOwnProperty(name)) {
|
||||
return
|
||||
}
|
||||
|
||||
let index = w.globals.events[name].indexOf(handler)
|
||||
if (index !== -1) {
|
||||
w.globals.events[name].splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
fireEvent(name, args) {
|
||||
const w = this.w
|
||||
|
||||
if (!w.globals.events.hasOwnProperty(name)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!args || !args.length) {
|
||||
args = []
|
||||
}
|
||||
|
||||
let evs = w.globals.events[name]
|
||||
let l = evs.length
|
||||
|
||||
for (let i = 0; i < l; i++) {
|
||||
evs[i].apply(null, args)
|
||||
}
|
||||
}
|
||||
|
||||
setupEventHandlers() {
|
||||
const w = this.w
|
||||
const me = this.ctx
|
||||
|
||||
let clickableArea = w.globals.dom.baseEl.querySelector(w.globals.chartClass)
|
||||
|
||||
this.ctx.eventList.forEach((event) => {
|
||||
clickableArea.addEventListener(
|
||||
event,
|
||||
(e) => {
|
||||
const opts = Object.assign({}, w, {
|
||||
seriesIndex: w.globals.capturedSeriesIndex,
|
||||
dataPointIndex: w.globals.capturedDataPointIndex
|
||||
})
|
||||
|
||||
if (e.type === 'mousemove' || e.type === 'touchmove') {
|
||||
if (typeof w.config.chart.events.mouseMove === 'function') {
|
||||
w.config.chart.events.mouseMove(e, me, opts)
|
||||
}
|
||||
} else if (e.type === 'mouseleave' || e.type === 'touchleave') {
|
||||
if (typeof w.config.chart.events.mouseLeave === 'function') {
|
||||
w.config.chart.events.mouseLeave(e, me, opts)
|
||||
}
|
||||
} else if (
|
||||
(e.type === 'mouseup' && e.which === 1) ||
|
||||
e.type === 'touchend'
|
||||
) {
|
||||
if (typeof w.config.chart.events.click === 'function') {
|
||||
w.config.chart.events.click(e, me, opts)
|
||||
}
|
||||
me.ctx.events.fireEvent('click', [e, me, opts])
|
||||
}
|
||||
},
|
||||
{ capture: false, passive: true }
|
||||
)
|
||||
})
|
||||
|
||||
this.ctx.eventList.forEach((event) => {
|
||||
w.globals.dom.baseEl.addEventListener(event, this.documentEvent, {
|
||||
passive: true
|
||||
})
|
||||
})
|
||||
|
||||
this.ctx.core.setupBrushHandler()
|
||||
}
|
||||
|
||||
documentEvent(e) {
|
||||
const w = this.w
|
||||
const target = e.target.className
|
||||
|
||||
if (e.type === 'click') {
|
||||
let elMenu = w.globals.dom.baseEl.querySelector('.apexcharts-menu')
|
||||
if (
|
||||
elMenu &&
|
||||
elMenu.classList.contains('apexcharts-menu-open') &&
|
||||
target !== 'apexcharts-menu-icon'
|
||||
) {
|
||||
elMenu.classList.remove('apexcharts-menu-open')
|
||||
}
|
||||
}
|
||||
|
||||
w.globals.clientX =
|
||||
e.type === 'touchmove' ? e.touches[0].clientX : e.clientX
|
||||
w.globals.clientY =
|
||||
e.type === 'touchmove' ? e.touches[0].clientY : e.clientY
|
||||
}
|
||||
}
|
||||
+465
@@ -0,0 +1,465 @@
|
||||
import Data from '../modules/Data'
|
||||
import AxesUtils from '../modules/axes/AxesUtils'
|
||||
import Series from '../modules/Series'
|
||||
import Utils from '../utils/Utils'
|
||||
|
||||
class Exports {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
}
|
||||
|
||||
scaleSvgNode(svg, scale) {
|
||||
// get current both width and height of the svg
|
||||
let svgWidth = parseFloat(svg.getAttributeNS(null, 'width'))
|
||||
let svgHeight = parseFloat(svg.getAttributeNS(null, 'height'))
|
||||
// set new width and height based on the scale
|
||||
svg.setAttributeNS(null, 'width', svgWidth * scale)
|
||||
svg.setAttributeNS(null, 'height', svgHeight * scale)
|
||||
svg.setAttributeNS(null, 'viewBox', '0 0 ' + svgWidth + ' ' + svgHeight)
|
||||
}
|
||||
|
||||
fixSvgStringForIe11(svgData) {
|
||||
// IE11 generates broken SVG that we have to fix by using regex
|
||||
if (!Utils.isIE11()) {
|
||||
// not IE11 - noop
|
||||
return svgData.replace(/ /g, ' ')
|
||||
}
|
||||
|
||||
// replace second occurrence of "xmlns" attribute with "xmlns:xlink" with correct url + add xmlns:svgjs
|
||||
let nXmlnsSeen = 0
|
||||
let result = svgData.replace(
|
||||
/xmlns="http:\/\/www.w3.org\/2000\/svg"/g,
|
||||
(match) => {
|
||||
nXmlnsSeen++
|
||||
return nXmlnsSeen === 2
|
||||
? 'xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.dev"'
|
||||
: match
|
||||
}
|
||||
)
|
||||
|
||||
// remove the invalid empty namespace declarations
|
||||
result = result.replace(/xmlns:NS\d+=""/g, '')
|
||||
// remove these broken namespaces from attributes
|
||||
result = result.replace(/NS\d+:(\w+:\w+=")/g, '$1')
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
getSvgString(scale) {
|
||||
if (scale == undefined) {
|
||||
scale = 1 // if no scale is specified, don't scale...
|
||||
}
|
||||
let svgString = this.w.globals.dom.Paper.svg()
|
||||
// in case the scale is different than 1, the svg needs to be rescaled
|
||||
if (scale !== 1) {
|
||||
// clone the svg node so it remains intact in the UI
|
||||
const svgNode = this.w.globals.dom.Paper.node.cloneNode(true)
|
||||
// scale the image
|
||||
this.scaleSvgNode(svgNode, scale)
|
||||
// get the string representation of the svgNode
|
||||
svgString = new XMLSerializer().serializeToString(svgNode)
|
||||
}
|
||||
return this.fixSvgStringForIe11(svgString)
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
const w = this.w
|
||||
|
||||
// hide some elements to avoid printing them on exported svg
|
||||
const xcrosshairs = w.globals.dom.baseEl.getElementsByClassName(
|
||||
'apexcharts-xcrosshairs'
|
||||
)
|
||||
const ycrosshairs = w.globals.dom.baseEl.getElementsByClassName(
|
||||
'apexcharts-ycrosshairs'
|
||||
)
|
||||
const zoomSelectionRects = w.globals.dom.baseEl.querySelectorAll(
|
||||
'.apexcharts-zoom-rect, .apexcharts-selection-rect'
|
||||
)
|
||||
Array.prototype.forEach.call(zoomSelectionRects, (z) => {
|
||||
z.setAttribute('width', 0)
|
||||
})
|
||||
if (xcrosshairs && xcrosshairs[0]) {
|
||||
xcrosshairs[0].setAttribute('x', -500)
|
||||
xcrosshairs[0].setAttribute('x1', -500)
|
||||
xcrosshairs[0].setAttribute('x2', -500)
|
||||
}
|
||||
if (ycrosshairs && ycrosshairs[0]) {
|
||||
ycrosshairs[0].setAttribute('y', -100)
|
||||
ycrosshairs[0].setAttribute('y1', -100)
|
||||
ycrosshairs[0].setAttribute('y2', -100)
|
||||
}
|
||||
}
|
||||
|
||||
svgUrl() {
|
||||
this.cleanup()
|
||||
|
||||
const svgData = this.getSvgString()
|
||||
const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' })
|
||||
return URL.createObjectURL(svgBlob)
|
||||
}
|
||||
|
||||
dataURI(options) {
|
||||
return new Promise((resolve) => {
|
||||
const w = this.w
|
||||
|
||||
const scale = options
|
||||
? options.scale || options.width / w.globals.svgWidth
|
||||
: 1
|
||||
|
||||
this.cleanup()
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = w.globals.svgWidth * scale
|
||||
canvas.height = parseInt(w.globals.dom.elWrap.style.height, 10) * scale // because of resizeNonAxisCharts
|
||||
|
||||
const canvasBg =
|
||||
w.config.chart.background === 'transparent'
|
||||
? '#fff'
|
||||
: w.config.chart.background
|
||||
|
||||
let ctx = canvas.getContext('2d')
|
||||
ctx.fillStyle = canvasBg
|
||||
ctx.fillRect(0, 0, canvas.width * scale, canvas.height * scale)
|
||||
|
||||
const svgData = this.getSvgString(scale)
|
||||
|
||||
if (window.canvg && Utils.isIE11()) {
|
||||
// use canvg as a polyfill to workaround ie11 considering a canvas with loaded svg 'unsafe'
|
||||
// without ignoreClear we lose our background color; without ignoreDimensions some grid lines become invisible
|
||||
let v = window.canvg.Canvg.fromString(ctx, svgData, {
|
||||
ignoreClear: true,
|
||||
ignoreDimensions: true,
|
||||
})
|
||||
// render the svg to canvas
|
||||
v.start()
|
||||
|
||||
let blob = canvas.msToBlob()
|
||||
// dispose - missing this will cause a memory leak
|
||||
v.stop()
|
||||
|
||||
resolve({ blob })
|
||||
} else {
|
||||
const svgUrl = 'data:image/svg+xml,' + encodeURIComponent(svgData)
|
||||
let img = new Image()
|
||||
img.crossOrigin = 'anonymous'
|
||||
|
||||
img.onload = () => {
|
||||
ctx.drawImage(img, 0, 0)
|
||||
|
||||
if (canvas.msToBlob) {
|
||||
// IE and Edge can't navigate to data urls, so we return the blob instead
|
||||
let blob = canvas.msToBlob()
|
||||
resolve({ blob })
|
||||
} else {
|
||||
let imgURI = canvas.toDataURL('image/png')
|
||||
resolve({ imgURI })
|
||||
}
|
||||
}
|
||||
|
||||
img.src = svgUrl
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
exportToSVG() {
|
||||
this.triggerDownload(
|
||||
this.svgUrl(),
|
||||
this.w.config.chart.toolbar.export.svg.filename,
|
||||
'.svg'
|
||||
)
|
||||
}
|
||||
|
||||
exportToPng() {
|
||||
this.dataURI().then(({ imgURI, blob }) => {
|
||||
if (blob) {
|
||||
navigator.msSaveOrOpenBlob(blob, this.w.globals.chartID + '.png')
|
||||
} else {
|
||||
this.triggerDownload(
|
||||
imgURI,
|
||||
this.w.config.chart.toolbar.export.png.filename,
|
||||
'.png'
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
exportToCSV({
|
||||
series,
|
||||
fileName,
|
||||
columnDelimiter = ',',
|
||||
lineDelimiter = '\n',
|
||||
}) {
|
||||
const w = this.w
|
||||
|
||||
if (!series) series = w.config.series
|
||||
|
||||
let columns = []
|
||||
let rows = []
|
||||
let result = ''
|
||||
let universalBOM = '\uFEFF'
|
||||
let gSeries = w.globals.series.map((s, i) => {
|
||||
return w.globals.collapsedSeriesIndices.indexOf(i) === -1 ? s : []
|
||||
})
|
||||
|
||||
const isTimeStamp = (num) => {
|
||||
return w.config.xaxis.type === 'datetime' && String(num).length >= 10
|
||||
}
|
||||
const seriesMaxDataLength = Math.max(
|
||||
...series.map((s) => {
|
||||
return s.data ? s.data.length : 0
|
||||
})
|
||||
)
|
||||
const dataFormat = new Data(this.ctx)
|
||||
|
||||
const axesUtils = new AxesUtils(this.ctx)
|
||||
const getCat = (i) => {
|
||||
let cat = ''
|
||||
|
||||
// pie / donut/ radial
|
||||
if (!w.globals.axisCharts) {
|
||||
cat = w.config.labels[i]
|
||||
} else {
|
||||
// xy charts
|
||||
|
||||
// non datetime
|
||||
if (
|
||||
w.config.xaxis.type === 'category' ||
|
||||
w.config.xaxis.convertedCatToNumeric
|
||||
) {
|
||||
if (w.globals.isBarHorizontal) {
|
||||
let lbFormatter = w.globals.yLabelFormatters[0]
|
||||
let sr = new Series(this.ctx)
|
||||
let activeSeries = sr.getActiveConfigSeriesIndex()
|
||||
|
||||
cat = lbFormatter(w.globals.labels[i], {
|
||||
seriesIndex: activeSeries,
|
||||
dataPointIndex: i,
|
||||
w,
|
||||
})
|
||||
} else {
|
||||
cat = axesUtils.getLabel(
|
||||
w.globals.labels,
|
||||
w.globals.timescaleLabels,
|
||||
0,
|
||||
i
|
||||
).text
|
||||
}
|
||||
}
|
||||
|
||||
// datetime, but labels specified in categories or labels
|
||||
if (w.config.xaxis.type === 'datetime') {
|
||||
if (w.config.xaxis.categories.length) {
|
||||
cat = w.config.xaxis.categories[i]
|
||||
} else if (w.config.labels.length) {
|
||||
cat = w.config.labels[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(cat)) {
|
||||
cat = cat.join(' ')
|
||||
}
|
||||
|
||||
return Utils.isNumber(cat) ? cat : cat.split(columnDelimiter).join('')
|
||||
}
|
||||
|
||||
// Fix https://github.com/apexcharts/apexcharts.js/issues/3365
|
||||
const getEmptyDataForCsvColumn = () => {
|
||||
return [...Array(seriesMaxDataLength)].map(() => '')
|
||||
}
|
||||
|
||||
const handleAxisRowsColumns = (s, sI) => {
|
||||
if (columns.length && sI === 0) {
|
||||
// It's the first series. Go ahead and create the first row with header information.
|
||||
rows.push(columns.join(columnDelimiter))
|
||||
}
|
||||
|
||||
if (s.data) {
|
||||
// Use the data we have, or generate a properly sized empty array with empty data if some data is missing.
|
||||
s.data = (s.data.length && s.data) || getEmptyDataForCsvColumn()
|
||||
for (let i = 0; i < s.data.length; i++) {
|
||||
// Reset the columns array so that we can start building columns for this row.
|
||||
columns = []
|
||||
|
||||
let cat = getCat(i)
|
||||
if (!cat) {
|
||||
if (dataFormat.isFormatXY()) {
|
||||
cat = series[sI].data[i].x
|
||||
} else if (dataFormat.isFormat2DArray()) {
|
||||
cat = series[sI].data[i] ? series[sI].data[i][0] : ''
|
||||
}
|
||||
}
|
||||
|
||||
if (sI === 0) {
|
||||
// It's the first series. Also handle the category.
|
||||
columns.push(
|
||||
isTimeStamp(cat)
|
||||
? w.config.chart.toolbar.export.csv.dateFormatter(cat)
|
||||
: Utils.isNumber(cat)
|
||||
? cat
|
||||
: cat.split(columnDelimiter).join('')
|
||||
)
|
||||
|
||||
for (let ci = 0; ci < w.globals.series.length; ci++) {
|
||||
if (dataFormat.isFormatXY()) {
|
||||
columns.push(series[ci].data[i]?.y)
|
||||
} else {
|
||||
columns.push(gSeries[ci][i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
w.config.chart.type === 'candlestick' ||
|
||||
(s.type && s.type === 'candlestick')
|
||||
) {
|
||||
columns.pop()
|
||||
columns.push(w.globals.seriesCandleO[sI][i])
|
||||
columns.push(w.globals.seriesCandleH[sI][i])
|
||||
columns.push(w.globals.seriesCandleL[sI][i])
|
||||
columns.push(w.globals.seriesCandleC[sI][i])
|
||||
}
|
||||
|
||||
if (
|
||||
w.config.chart.type === 'boxPlot' ||
|
||||
(s.type && s.type === 'boxPlot')
|
||||
) {
|
||||
columns.pop()
|
||||
columns.push(w.globals.seriesCandleO[sI][i])
|
||||
columns.push(w.globals.seriesCandleH[sI][i])
|
||||
columns.push(w.globals.seriesCandleM[sI][i])
|
||||
columns.push(w.globals.seriesCandleL[sI][i])
|
||||
columns.push(w.globals.seriesCandleC[sI][i])
|
||||
}
|
||||
|
||||
if (w.config.chart.type === 'rangeBar') {
|
||||
columns.pop()
|
||||
columns.push(w.globals.seriesRangeStart[sI][i])
|
||||
columns.push(w.globals.seriesRangeEnd[sI][i])
|
||||
}
|
||||
|
||||
if (columns.length) {
|
||||
rows.push(columns.join(columnDelimiter))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleUnequalXValues = () => {
|
||||
const categories = new Set()
|
||||
const data = {}
|
||||
|
||||
series.forEach((s, sI) => {
|
||||
s?.data.forEach((dataItem) => {
|
||||
let cat, value
|
||||
if (dataFormat.isFormatXY()) {
|
||||
cat = dataItem.x
|
||||
value = dataItem.y
|
||||
} else if (dataFormat.isFormat2DArray()) {
|
||||
cat = dataItem[0]
|
||||
value = dataItem[1]
|
||||
} else {
|
||||
return
|
||||
}
|
||||
if (!data[cat]) {
|
||||
data[cat] = Array(series.length).fill('')
|
||||
}
|
||||
data[cat][sI] = value
|
||||
categories.add(cat)
|
||||
})
|
||||
})
|
||||
|
||||
if (columns.length) {
|
||||
rows.push(columns.join(columnDelimiter))
|
||||
}
|
||||
|
||||
Array.from(categories)
|
||||
.sort()
|
||||
.forEach((cat) => {
|
||||
rows.push([
|
||||
isTimeStamp(cat) && w.config.xaxis.type === 'datetime'
|
||||
? w.config.chart.toolbar.export.csv.dateFormatter(cat)
|
||||
: Utils.isNumber(cat)
|
||||
? cat
|
||||
: cat.split(columnDelimiter).join(''),
|
||||
data[cat].join(columnDelimiter),
|
||||
])
|
||||
})
|
||||
}
|
||||
|
||||
columns.push(w.config.chart.toolbar.export.csv.headerCategory)
|
||||
|
||||
if (w.config.chart.type === 'boxPlot') {
|
||||
columns.push('minimum')
|
||||
columns.push('q1')
|
||||
columns.push('median')
|
||||
columns.push('q3')
|
||||
columns.push('maximum')
|
||||
} else if (w.config.chart.type === 'candlestick') {
|
||||
columns.push('open')
|
||||
columns.push('high')
|
||||
columns.push('low')
|
||||
columns.push('close')
|
||||
} else if (w.config.chart.type === 'rangeBar') {
|
||||
columns.push('minimum')
|
||||
columns.push('maximum')
|
||||
} else {
|
||||
series.map((s, sI) => {
|
||||
const sname = (s.name ? s.name : `series-${sI}`) + ''
|
||||
if (w.globals.axisCharts) {
|
||||
columns.push(
|
||||
sname.split(columnDelimiter).join('')
|
||||
? sname.split(columnDelimiter).join('')
|
||||
: `series-${sI}`
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (!w.globals.axisCharts) {
|
||||
columns.push(w.config.chart.toolbar.export.csv.headerValue)
|
||||
rows.push(columns.join(columnDelimiter))
|
||||
}
|
||||
|
||||
if (
|
||||
!w.globals.allSeriesHasEqualX &&
|
||||
w.globals.axisCharts &&
|
||||
!w.config.xaxis.categories.length &&
|
||||
!w.config.labels.length
|
||||
) {
|
||||
handleUnequalXValues()
|
||||
} else {
|
||||
series.map((s, sI) => {
|
||||
if (w.globals.axisCharts) {
|
||||
handleAxisRowsColumns(s, sI)
|
||||
} else {
|
||||
columns = []
|
||||
|
||||
columns.push(w.globals.labels[sI].split(columnDelimiter).join(''))
|
||||
columns.push(gSeries[sI])
|
||||
rows.push(columns.join(columnDelimiter))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
result += rows.join(lineDelimiter)
|
||||
|
||||
this.triggerDownload(
|
||||
'data:text/csv; charset=utf-8,' +
|
||||
encodeURIComponent(universalBOM + result),
|
||||
fileName ? fileName : w.config.chart.toolbar.export.csv.filename,
|
||||
'.csv'
|
||||
)
|
||||
}
|
||||
|
||||
triggerDownload(href, filename, ext) {
|
||||
const downloadLink = document.createElement('a')
|
||||
downloadLink.href = href
|
||||
downloadLink.download = (filename ? filename : this.w.globals.chartID) + ext
|
||||
document.body.appendChild(downloadLink)
|
||||
downloadLink.click()
|
||||
document.body.removeChild(downloadLink)
|
||||
}
|
||||
}
|
||||
|
||||
export default Exports
|
||||
+414
@@ -0,0 +1,414 @@
|
||||
import Graphics from './Graphics'
|
||||
import Utils from '../utils/Utils'
|
||||
|
||||
/**
|
||||
* ApexCharts Fill Class for setting fill options of the paths.
|
||||
*
|
||||
* @module Fill
|
||||
**/
|
||||
|
||||
class Fill {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
|
||||
this.opts = null
|
||||
this.seriesIndex = 0
|
||||
}
|
||||
|
||||
clippedImgArea(params) {
|
||||
let w = this.w
|
||||
let cnf = w.config
|
||||
|
||||
let svgW = parseInt(w.globals.gridWidth, 10)
|
||||
let svgH = parseInt(w.globals.gridHeight, 10)
|
||||
|
||||
let size = svgW > svgH ? svgW : svgH
|
||||
|
||||
let fillImg = params.image
|
||||
|
||||
let imgWidth = 0
|
||||
let imgHeight = 0
|
||||
if (
|
||||
typeof params.width === 'undefined' &&
|
||||
typeof params.height === 'undefined'
|
||||
) {
|
||||
if (
|
||||
cnf.fill.image.width !== undefined &&
|
||||
cnf.fill.image.height !== undefined
|
||||
) {
|
||||
imgWidth = cnf.fill.image.width + 1
|
||||
imgHeight = cnf.fill.image.height
|
||||
} else {
|
||||
imgWidth = size + 1
|
||||
imgHeight = size
|
||||
}
|
||||
} else {
|
||||
imgWidth = params.width
|
||||
imgHeight = params.height
|
||||
}
|
||||
|
||||
let elPattern = document.createElementNS(w.globals.SVGNS, 'pattern')
|
||||
|
||||
Graphics.setAttrs(elPattern, {
|
||||
id: params.patternID,
|
||||
patternUnits: params.patternUnits
|
||||
? params.patternUnits
|
||||
: 'userSpaceOnUse',
|
||||
width: imgWidth + 'px',
|
||||
height: imgHeight + 'px',
|
||||
})
|
||||
|
||||
let elImage = document.createElementNS(w.globals.SVGNS, 'image')
|
||||
elPattern.appendChild(elImage)
|
||||
|
||||
elImage.setAttributeNS(window.SVG.xlink, 'href', fillImg)
|
||||
|
||||
Graphics.setAttrs(elImage, {
|
||||
x: 0,
|
||||
y: 0,
|
||||
preserveAspectRatio: 'none',
|
||||
width: imgWidth + 'px',
|
||||
height: imgHeight + 'px',
|
||||
})
|
||||
|
||||
elImage.style.opacity = params.opacity
|
||||
|
||||
w.globals.dom.elDefs.node.appendChild(elPattern)
|
||||
}
|
||||
|
||||
getSeriesIndex(opts) {
|
||||
const w = this.w
|
||||
const cType = w.config.chart.type
|
||||
|
||||
if (
|
||||
((cType === 'bar' || cType === 'rangeBar') &&
|
||||
w.config.plotOptions.bar.distributed) ||
|
||||
cType === 'heatmap' ||
|
||||
cType === 'treemap'
|
||||
) {
|
||||
this.seriesIndex = opts.seriesNumber
|
||||
} else {
|
||||
this.seriesIndex = opts.seriesNumber % w.globals.series.length
|
||||
}
|
||||
|
||||
return this.seriesIndex
|
||||
}
|
||||
|
||||
fillPath(opts) {
|
||||
let w = this.w
|
||||
this.opts = opts
|
||||
|
||||
let cnf = this.w.config
|
||||
let pathFill
|
||||
|
||||
let patternFill, gradientFill
|
||||
|
||||
this.seriesIndex = this.getSeriesIndex(opts)
|
||||
|
||||
let fillColors = this.getFillColors()
|
||||
let fillColor = fillColors[this.seriesIndex]
|
||||
|
||||
//override fillcolor if user inputted color with data
|
||||
if (w.globals.seriesColors[this.seriesIndex] !== undefined) {
|
||||
fillColor = w.globals.seriesColors[this.seriesIndex]
|
||||
}
|
||||
|
||||
if (typeof fillColor === 'function') {
|
||||
fillColor = fillColor({
|
||||
seriesIndex: this.seriesIndex,
|
||||
dataPointIndex: opts.dataPointIndex,
|
||||
value: opts.value,
|
||||
w,
|
||||
})
|
||||
}
|
||||
let fillType = opts.fillType
|
||||
? opts.fillType
|
||||
: this.getFillType(this.seriesIndex)
|
||||
let fillOpacity = Array.isArray(cnf.fill.opacity)
|
||||
? cnf.fill.opacity[this.seriesIndex]
|
||||
: cnf.fill.opacity
|
||||
|
||||
if (opts.color) {
|
||||
fillColor = opts.color
|
||||
}
|
||||
|
||||
// in case a color is undefined, fallback to white color to prevent runtime error
|
||||
if (!fillColor) {
|
||||
fillColor = '#fff'
|
||||
console.warn('undefined color - ApexCharts')
|
||||
}
|
||||
|
||||
let defaultColor = fillColor
|
||||
|
||||
if (fillColor.indexOf('rgb') === -1) {
|
||||
if (fillColor.length < 9) {
|
||||
// if the hex contains alpha and is of 9 digit, skip the opacity
|
||||
defaultColor = Utils.hexToRgba(fillColor, fillOpacity)
|
||||
}
|
||||
} else {
|
||||
if (fillColor.indexOf('rgba') > -1) {
|
||||
fillOpacity = Utils.getOpacityFromRGBA(fillColor)
|
||||
}
|
||||
}
|
||||
if (opts.opacity) fillOpacity = opts.opacity
|
||||
|
||||
if (fillType === 'pattern') {
|
||||
patternFill = this.handlePatternFill({
|
||||
fillConfig: opts.fillConfig,
|
||||
patternFill,
|
||||
fillColor,
|
||||
fillOpacity,
|
||||
defaultColor,
|
||||
})
|
||||
}
|
||||
|
||||
if (fillType === 'gradient') {
|
||||
gradientFill = this.handleGradientFill({
|
||||
fillConfig: opts.fillConfig,
|
||||
fillColor,
|
||||
fillOpacity,
|
||||
i: this.seriesIndex,
|
||||
})
|
||||
}
|
||||
|
||||
if (fillType === 'image') {
|
||||
let imgSrc = cnf.fill.image.src
|
||||
|
||||
let patternID = opts.patternID ? opts.patternID : ''
|
||||
this.clippedImgArea({
|
||||
opacity: fillOpacity,
|
||||
image: Array.isArray(imgSrc)
|
||||
? opts.seriesNumber < imgSrc.length
|
||||
? imgSrc[opts.seriesNumber]
|
||||
: imgSrc[0]
|
||||
: imgSrc,
|
||||
width: opts.width ? opts.width : undefined,
|
||||
height: opts.height ? opts.height : undefined,
|
||||
patternUnits: opts.patternUnits,
|
||||
patternID: `pattern${w.globals.cuid}${
|
||||
opts.seriesNumber + 1
|
||||
}${patternID}`,
|
||||
})
|
||||
pathFill = `url(#pattern${w.globals.cuid}${
|
||||
opts.seriesNumber + 1
|
||||
}${patternID})`
|
||||
} else if (fillType === 'gradient') {
|
||||
pathFill = gradientFill
|
||||
} else if (fillType === 'pattern') {
|
||||
pathFill = patternFill
|
||||
} else {
|
||||
pathFill = defaultColor
|
||||
}
|
||||
|
||||
// override pattern/gradient if opts.solid is true
|
||||
if (opts.solid) {
|
||||
pathFill = defaultColor
|
||||
}
|
||||
|
||||
return pathFill
|
||||
}
|
||||
|
||||
getFillType(seriesIndex) {
|
||||
const w = this.w
|
||||
|
||||
if (Array.isArray(w.config.fill.type)) {
|
||||
return w.config.fill.type[seriesIndex]
|
||||
} else {
|
||||
return w.config.fill.type
|
||||
}
|
||||
}
|
||||
|
||||
getFillColors() {
|
||||
const w = this.w
|
||||
const cnf = w.config
|
||||
const opts = this.opts
|
||||
|
||||
let fillColors = []
|
||||
|
||||
if (w.globals.comboCharts) {
|
||||
if (w.config.series[this.seriesIndex].type === 'line') {
|
||||
if (Array.isArray(w.globals.stroke.colors)) {
|
||||
fillColors = w.globals.stroke.colors
|
||||
} else {
|
||||
fillColors.push(w.globals.stroke.colors)
|
||||
}
|
||||
} else {
|
||||
if (Array.isArray(w.globals.fill.colors)) {
|
||||
fillColors = w.globals.fill.colors
|
||||
} else {
|
||||
fillColors.push(w.globals.fill.colors)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (cnf.chart.type === 'line') {
|
||||
if (Array.isArray(w.globals.stroke.colors)) {
|
||||
fillColors = w.globals.stroke.colors
|
||||
} else {
|
||||
fillColors.push(w.globals.stroke.colors)
|
||||
}
|
||||
} else {
|
||||
if (Array.isArray(w.globals.fill.colors)) {
|
||||
fillColors = w.globals.fill.colors
|
||||
} else {
|
||||
fillColors.push(w.globals.fill.colors)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// colors passed in arguments
|
||||
if (typeof opts.fillColors !== 'undefined') {
|
||||
fillColors = []
|
||||
if (Array.isArray(opts.fillColors)) {
|
||||
fillColors = opts.fillColors.slice()
|
||||
} else {
|
||||
fillColors.push(opts.fillColors)
|
||||
}
|
||||
}
|
||||
|
||||
return fillColors
|
||||
}
|
||||
|
||||
handlePatternFill({
|
||||
fillConfig,
|
||||
patternFill,
|
||||
fillColor,
|
||||
fillOpacity,
|
||||
defaultColor,
|
||||
}) {
|
||||
let fillCnf = this.w.config.fill
|
||||
|
||||
if (fillConfig) {
|
||||
fillCnf = fillConfig
|
||||
}
|
||||
|
||||
const opts = this.opts
|
||||
let graphics = new Graphics(this.ctx)
|
||||
|
||||
let patternStrokeWidth = Array.isArray(fillCnf.pattern.strokeWidth)
|
||||
? fillCnf.pattern.strokeWidth[this.seriesIndex]
|
||||
: fillCnf.pattern.strokeWidth
|
||||
let patternLineColor = fillColor
|
||||
|
||||
if (Array.isArray(fillCnf.pattern.style)) {
|
||||
if (typeof fillCnf.pattern.style[opts.seriesNumber] !== 'undefined') {
|
||||
let pf = graphics.drawPattern(
|
||||
fillCnf.pattern.style[opts.seriesNumber],
|
||||
fillCnf.pattern.width,
|
||||
fillCnf.pattern.height,
|
||||
patternLineColor,
|
||||
patternStrokeWidth,
|
||||
fillOpacity
|
||||
)
|
||||
patternFill = pf
|
||||
} else {
|
||||
patternFill = defaultColor
|
||||
}
|
||||
} else {
|
||||
patternFill = graphics.drawPattern(
|
||||
fillCnf.pattern.style,
|
||||
fillCnf.pattern.width,
|
||||
fillCnf.pattern.height,
|
||||
patternLineColor,
|
||||
patternStrokeWidth,
|
||||
fillOpacity
|
||||
)
|
||||
}
|
||||
return patternFill
|
||||
}
|
||||
|
||||
handleGradientFill({ fillColor, fillOpacity, fillConfig, i }) {
|
||||
let fillCnf = this.w.config.fill
|
||||
|
||||
if (fillConfig) {
|
||||
fillCnf = {
|
||||
...fillCnf,
|
||||
...fillConfig,
|
||||
}
|
||||
}
|
||||
const opts = this.opts
|
||||
let graphics = new Graphics(this.ctx)
|
||||
let utils = new Utils()
|
||||
|
||||
let type = fillCnf.gradient.type
|
||||
let gradientFrom = fillColor
|
||||
let gradientTo
|
||||
let opacityFrom =
|
||||
fillCnf.gradient.opacityFrom === undefined
|
||||
? fillOpacity
|
||||
: Array.isArray(fillCnf.gradient.opacityFrom)
|
||||
? fillCnf.gradient.opacityFrom[i]
|
||||
: fillCnf.gradient.opacityFrom
|
||||
|
||||
if (gradientFrom.indexOf('rgba') > -1) {
|
||||
opacityFrom = Utils.getOpacityFromRGBA(gradientFrom)
|
||||
}
|
||||
let opacityTo =
|
||||
fillCnf.gradient.opacityTo === undefined
|
||||
? fillOpacity
|
||||
: Array.isArray(fillCnf.gradient.opacityTo)
|
||||
? fillCnf.gradient.opacityTo[i]
|
||||
: fillCnf.gradient.opacityTo
|
||||
|
||||
if (
|
||||
fillCnf.gradient.gradientToColors === undefined ||
|
||||
fillCnf.gradient.gradientToColors.length === 0
|
||||
) {
|
||||
if (fillCnf.gradient.shade === 'dark') {
|
||||
gradientTo = utils.shadeColor(
|
||||
parseFloat(fillCnf.gradient.shadeIntensity) * -1,
|
||||
fillColor.indexOf('rgb') > -1 ? Utils.rgb2hex(fillColor) : fillColor
|
||||
)
|
||||
} else {
|
||||
gradientTo = utils.shadeColor(
|
||||
parseFloat(fillCnf.gradient.shadeIntensity),
|
||||
fillColor.indexOf('rgb') > -1 ? Utils.rgb2hex(fillColor) : fillColor
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (fillCnf.gradient.gradientToColors[opts.seriesNumber]) {
|
||||
const gToColor = fillCnf.gradient.gradientToColors[opts.seriesNumber]
|
||||
gradientTo = gToColor
|
||||
if (gToColor.indexOf('rgba') > -1) {
|
||||
opacityTo = Utils.getOpacityFromRGBA(gToColor)
|
||||
}
|
||||
} else {
|
||||
gradientTo = fillColor
|
||||
}
|
||||
}
|
||||
|
||||
if (fillCnf.gradient.gradientFrom) {
|
||||
gradientFrom = fillCnf.gradient.gradientFrom
|
||||
}
|
||||
if (fillCnf.gradient.gradientTo) {
|
||||
gradientTo = fillCnf.gradient.gradientTo
|
||||
}
|
||||
|
||||
if (fillCnf.gradient.inverseColors) {
|
||||
let t = gradientFrom
|
||||
gradientFrom = gradientTo
|
||||
gradientTo = t
|
||||
}
|
||||
|
||||
if (gradientFrom.indexOf('rgb') > -1) {
|
||||
gradientFrom = Utils.rgb2hex(gradientFrom)
|
||||
}
|
||||
if (gradientTo.indexOf('rgb') > -1) {
|
||||
gradientTo = Utils.rgb2hex(gradientTo)
|
||||
}
|
||||
|
||||
return graphics.drawGradient(
|
||||
type,
|
||||
gradientFrom,
|
||||
gradientTo,
|
||||
opacityFrom,
|
||||
opacityTo,
|
||||
opts.size,
|
||||
fillCnf.gradient.stops,
|
||||
fillCnf.gradient.colorStops,
|
||||
i
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Fill
|
||||
+225
@@ -0,0 +1,225 @@
|
||||
import Utils from './../utils/Utils'
|
||||
|
||||
/**
|
||||
* ApexCharts Filters Class for setting hover/active states on the paths.
|
||||
*
|
||||
* @module Formatters
|
||||
**/
|
||||
class Filters {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
}
|
||||
|
||||
// create a re-usable filter which can be appended other filter effects and applied to multiple elements
|
||||
getDefaultFilter(el, i) {
|
||||
const w = this.w
|
||||
el.unfilter(true)
|
||||
|
||||
let filter = new window.SVG.Filter()
|
||||
filter.size('120%', '180%', '-5%', '-40%')
|
||||
|
||||
if (w.config.states.normal.filter !== 'none') {
|
||||
this.applyFilter(
|
||||
el,
|
||||
i,
|
||||
w.config.states.normal.filter.type,
|
||||
w.config.states.normal.filter.value
|
||||
)
|
||||
} else {
|
||||
if (w.config.chart.dropShadow.enabled) {
|
||||
this.dropShadow(el, w.config.chart.dropShadow, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addNormalFilter(el, i) {
|
||||
const w = this.w
|
||||
|
||||
// revert shadow if it was there
|
||||
// but, ignore marker as marker don't have dropshadow yet
|
||||
if (
|
||||
w.config.chart.dropShadow.enabled &&
|
||||
!el.node.classList.contains('apexcharts-marker')
|
||||
) {
|
||||
this.dropShadow(el, w.config.chart.dropShadow, i)
|
||||
}
|
||||
}
|
||||
|
||||
// appends dropShadow to the filter object which can be chained with other filter effects
|
||||
addLightenFilter(el, i, attrs) {
|
||||
const w = this.w
|
||||
const { intensity } = attrs
|
||||
|
||||
el.unfilter(true)
|
||||
|
||||
let filter = new window.SVG.Filter()
|
||||
|
||||
el.filter((add) => {
|
||||
const shadowAttr = w.config.chart.dropShadow
|
||||
if (shadowAttr.enabled) {
|
||||
filter = this.addShadow(add, i, shadowAttr)
|
||||
} else {
|
||||
filter = add
|
||||
}
|
||||
filter.componentTransfer({
|
||||
rgb: { type: 'linear', slope: 1.5, intercept: intensity },
|
||||
})
|
||||
})
|
||||
el.filterer.node.setAttribute('filterUnits', 'userSpaceOnUse')
|
||||
|
||||
this._scaleFilterSize(el.filterer.node)
|
||||
}
|
||||
|
||||
// appends dropShadow to the filter object which can be chained with other filter effects
|
||||
addDarkenFilter(el, i, attrs) {
|
||||
const w = this.w
|
||||
const { intensity } = attrs
|
||||
|
||||
el.unfilter(true)
|
||||
|
||||
let filter = new window.SVG.Filter()
|
||||
|
||||
el.filter((add) => {
|
||||
const shadowAttr = w.config.chart.dropShadow
|
||||
if (shadowAttr.enabled) {
|
||||
filter = this.addShadow(add, i, shadowAttr)
|
||||
} else {
|
||||
filter = add
|
||||
}
|
||||
filter.componentTransfer({
|
||||
rgb: { type: 'linear', slope: intensity },
|
||||
})
|
||||
})
|
||||
el.filterer.node.setAttribute('filterUnits', 'userSpaceOnUse')
|
||||
this._scaleFilterSize(el.filterer.node)
|
||||
}
|
||||
|
||||
applyFilter(el, i, filter, intensity = 0.5) {
|
||||
switch (filter) {
|
||||
case 'none': {
|
||||
this.addNormalFilter(el, i)
|
||||
break
|
||||
}
|
||||
case 'lighten': {
|
||||
this.addLightenFilter(el, i, {
|
||||
intensity,
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'darken': {
|
||||
this.addDarkenFilter(el, i, {
|
||||
intensity,
|
||||
})
|
||||
break
|
||||
}
|
||||
default:
|
||||
// do nothing
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// appends dropShadow to the filter object which can be chained with other filter effects
|
||||
addShadow(add, i, attrs) {
|
||||
const w = this.w
|
||||
const { blur, top, left, color, opacity } = attrs
|
||||
|
||||
if (w.config.chart.dropShadow.enabledOnSeries?.length > 0) {
|
||||
if (w.config.chart.dropShadow.enabledOnSeries.indexOf(i) === -1) {
|
||||
return add
|
||||
}
|
||||
}
|
||||
|
||||
let shadowBlur = add
|
||||
.flood(Array.isArray(color) ? color[i] : color, opacity)
|
||||
.composite(add.sourceAlpha, 'in')
|
||||
.offset(left, top)
|
||||
.gaussianBlur(blur)
|
||||
.merge(add.source)
|
||||
return add.blend(add.source, shadowBlur)
|
||||
}
|
||||
|
||||
// directly adds dropShadow to the element and returns the same element.
|
||||
// the only way it is different from the addShadow() function is that addShadow is chainable to other filters, while this function discards all filters and add dropShadow
|
||||
dropShadow(el, attrs, i = 0) {
|
||||
let { top, left, blur, color, opacity, noUserSpaceOnUse } = attrs
|
||||
const w = this.w
|
||||
|
||||
el.unfilter(true)
|
||||
|
||||
if (Utils.isIE() && w.config.chart.type === 'radialBar') {
|
||||
// in radialbar charts, dropshadow is clipping actual drawing in IE
|
||||
return el
|
||||
}
|
||||
|
||||
if (w.config.chart.dropShadow.enabledOnSeries?.length > 0) {
|
||||
if (w.config.chart.dropShadow.enabledOnSeries?.indexOf(i) === -1) {
|
||||
return el
|
||||
}
|
||||
}
|
||||
|
||||
color = Array.isArray(color) ? color[i] : color
|
||||
|
||||
el.filter((add) => {
|
||||
let shadowBlur = null
|
||||
if (Utils.isSafari() || Utils.isFirefox() || Utils.isIE()) {
|
||||
// safari/firefox/IE have some alternative way to use this filter
|
||||
shadowBlur = add
|
||||
.flood(color, opacity)
|
||||
.composite(add.sourceAlpha, 'in')
|
||||
.offset(left, top)
|
||||
.gaussianBlur(blur)
|
||||
} else {
|
||||
shadowBlur = add
|
||||
.flood(color, opacity)
|
||||
.composite(add.sourceAlpha, 'in')
|
||||
.offset(left, top)
|
||||
.gaussianBlur(blur)
|
||||
.merge(add.source)
|
||||
}
|
||||
|
||||
add.blend(add.source, shadowBlur)
|
||||
})
|
||||
|
||||
if (!noUserSpaceOnUse) {
|
||||
el.filterer.node.setAttribute('filterUnits', 'userSpaceOnUse')
|
||||
}
|
||||
|
||||
this._scaleFilterSize(el.filterer.node)
|
||||
|
||||
return el
|
||||
}
|
||||
|
||||
setSelectionFilter(el, realIndex, dataPointIndex) {
|
||||
const w = this.w
|
||||
if (typeof w.globals.selectedDataPoints[realIndex] !== 'undefined') {
|
||||
if (
|
||||
w.globals.selectedDataPoints[realIndex].indexOf(dataPointIndex) > -1
|
||||
) {
|
||||
el.node.setAttribute('selected', true)
|
||||
let activeFilter = w.config.states.active.filter
|
||||
if (activeFilter !== 'none') {
|
||||
this.applyFilter(el, realIndex, activeFilter.type, activeFilter.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_scaleFilterSize(el) {
|
||||
const setAttributes = (attrs) => {
|
||||
for (let key in attrs) {
|
||||
if (attrs.hasOwnProperty(key)) {
|
||||
el.setAttribute(key, attrs[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
setAttributes({
|
||||
width: '200%',
|
||||
height: '200%',
|
||||
x: '-50%',
|
||||
y: '-50%',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default Filters
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
import DateTime from '../utils/DateTime'
|
||||
import Utils from '../utils/Utils'
|
||||
|
||||
/**
|
||||
* ApexCharts Formatter Class for setting value formatters for axes as well as tooltips.
|
||||
*
|
||||
* @module Formatters
|
||||
**/
|
||||
|
||||
class Formatters {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
this.tooltipKeyFormat = 'dd MMM'
|
||||
}
|
||||
|
||||
xLabelFormat(fn, val, timestamp, opts) {
|
||||
let w = this.w
|
||||
|
||||
if (w.config.xaxis.type === 'datetime') {
|
||||
if (w.config.xaxis.labels.formatter === undefined) {
|
||||
// if user has not specified a custom formatter, use the default tooltip.x.format
|
||||
if (w.config.tooltip.x.formatter === undefined) {
|
||||
let datetimeObj = new DateTime(this.ctx)
|
||||
return datetimeObj.formatDate(
|
||||
datetimeObj.getDate(val),
|
||||
w.config.tooltip.x.format
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fn(val, timestamp, opts)
|
||||
}
|
||||
|
||||
defaultGeneralFormatter(val) {
|
||||
if (Array.isArray(val)) {
|
||||
return val.map((v) => {
|
||||
return v
|
||||
})
|
||||
} else {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
defaultYFormatter(v, yaxe, i) {
|
||||
let w = this.w
|
||||
|
||||
if (Utils.isNumber(v)) {
|
||||
if (w.globals.yValueDecimal !== 0) {
|
||||
v = v.toFixed(
|
||||
yaxe.decimalsInFloat !== undefined
|
||||
? yaxe.decimalsInFloat
|
||||
: w.globals.yValueDecimal
|
||||
)
|
||||
} else if (w.globals.maxYArr[i] - w.globals.minYArr[i] < 5) {
|
||||
v = v.toFixed(1)
|
||||
} else {
|
||||
v = v.toFixed(0)
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
setLabelFormatters() {
|
||||
let w = this.w
|
||||
|
||||
w.globals.xaxisTooltipFormatter = (val) => {
|
||||
return this.defaultGeneralFormatter(val)
|
||||
}
|
||||
|
||||
w.globals.ttKeyFormatter = (val) => {
|
||||
return this.defaultGeneralFormatter(val)
|
||||
}
|
||||
|
||||
w.globals.ttZFormatter = (val) => {
|
||||
return val
|
||||
}
|
||||
|
||||
w.globals.legendFormatter = (val) => {
|
||||
return this.defaultGeneralFormatter(val)
|
||||
}
|
||||
|
||||
// formatter function will always overwrite format property
|
||||
if (w.config.xaxis.labels.formatter !== undefined) {
|
||||
w.globals.xLabelFormatter = w.config.xaxis.labels.formatter
|
||||
} else {
|
||||
w.globals.xLabelFormatter = (val) => {
|
||||
if (Utils.isNumber(val)) {
|
||||
if (
|
||||
!w.config.xaxis.convertedCatToNumeric &&
|
||||
w.config.xaxis.type === 'numeric'
|
||||
) {
|
||||
if (Utils.isNumber(w.config.xaxis.decimalsInFloat)) {
|
||||
return val.toFixed(w.config.xaxis.decimalsInFloat)
|
||||
} else {
|
||||
const diff = w.globals.maxX - w.globals.minX
|
||||
if (diff > 0 && diff < 100) {
|
||||
return val.toFixed(1)
|
||||
}
|
||||
return val.toFixed(0)
|
||||
}
|
||||
}
|
||||
|
||||
if (w.globals.isBarHorizontal) {
|
||||
const range = w.globals.maxY - w.globals.minYArr
|
||||
if (range < 4) {
|
||||
return val.toFixed(1)
|
||||
}
|
||||
}
|
||||
return val.toFixed(0)
|
||||
}
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof w.config.tooltip.x.formatter === 'function') {
|
||||
w.globals.ttKeyFormatter = w.config.tooltip.x.formatter
|
||||
} else {
|
||||
w.globals.ttKeyFormatter = w.globals.xLabelFormatter
|
||||
}
|
||||
|
||||
if (typeof w.config.xaxis.tooltip.formatter === 'function') {
|
||||
w.globals.xaxisTooltipFormatter = w.config.xaxis.tooltip.formatter
|
||||
}
|
||||
|
||||
if (Array.isArray(w.config.tooltip.y)) {
|
||||
w.globals.ttVal = w.config.tooltip.y
|
||||
} else {
|
||||
if (w.config.tooltip.y.formatter !== undefined) {
|
||||
w.globals.ttVal = w.config.tooltip.y
|
||||
}
|
||||
}
|
||||
|
||||
if (w.config.tooltip.z.formatter !== undefined) {
|
||||
w.globals.ttZFormatter = w.config.tooltip.z.formatter
|
||||
}
|
||||
|
||||
// legend formatter - if user wants to append any global values of series to legend text
|
||||
if (w.config.legend.formatter !== undefined) {
|
||||
w.globals.legendFormatter = w.config.legend.formatter
|
||||
}
|
||||
|
||||
// formatter function will always overwrite format property
|
||||
w.config.yaxis.forEach((yaxe, i) => {
|
||||
if (yaxe.labels.formatter !== undefined) {
|
||||
w.globals.yLabelFormatters[i] = yaxe.labels.formatter
|
||||
} else {
|
||||
w.globals.yLabelFormatters[i] = (val) => {
|
||||
if (!w.globals.xyCharts) return val
|
||||
|
||||
if (Array.isArray(val)) {
|
||||
return val.map((v) => {
|
||||
return this.defaultYFormatter(v, yaxe, i)
|
||||
})
|
||||
} else {
|
||||
return this.defaultYFormatter(val, yaxe, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return w.globals
|
||||
}
|
||||
|
||||
heatmapLabelFormatters() {
|
||||
const w = this.w
|
||||
if (w.config.chart.type === 'heatmap') {
|
||||
w.globals.yAxisScale[0].result = w.globals.seriesNames.slice()
|
||||
|
||||
// get the longest string from the labels array and also apply label formatter to it
|
||||
let longest = w.globals.seriesNames.reduce(
|
||||
(a, b) => (a.length > b.length ? a : b),
|
||||
0
|
||||
)
|
||||
w.globals.yAxisScale[0].niceMax = longest
|
||||
w.globals.yAxisScale[0].niceMin = longest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Formatters
|
||||
+1112
File diff suppressed because it is too large
Load Diff
+255
@@ -0,0 +1,255 @@
|
||||
import Filters from './Filters'
|
||||
import Graphics from './Graphics'
|
||||
import Utils from '../utils/Utils'
|
||||
|
||||
/**
|
||||
* ApexCharts Markers Class for drawing points on y values in axes charts.
|
||||
*
|
||||
* @module Markers
|
||||
**/
|
||||
|
||||
export default class Markers {
|
||||
constructor(ctx, opts) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
}
|
||||
|
||||
setGlobalMarkerSize() {
|
||||
const w = this.w
|
||||
|
||||
w.globals.markers.size = Array.isArray(w.config.markers.size)
|
||||
? w.config.markers.size
|
||||
: [w.config.markers.size]
|
||||
|
||||
if (w.globals.markers.size.length > 0) {
|
||||
if (w.globals.markers.size.length < w.globals.series.length + 1) {
|
||||
for (let i = 0; i <= w.globals.series.length; i++) {
|
||||
if (typeof w.globals.markers.size[i] === 'undefined') {
|
||||
w.globals.markers.size.push(w.globals.markers.size[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
w.globals.markers.size = w.config.series.map((s) => w.config.markers.size)
|
||||
}
|
||||
}
|
||||
|
||||
plotChartMarkers(pointsPos, seriesIndex, j, pSize, alwaysDrawMarker = false) {
|
||||
let w = this.w
|
||||
|
||||
let i = seriesIndex
|
||||
let p = pointsPos
|
||||
let elPointsWrap = null
|
||||
|
||||
let graphics = new Graphics(this.ctx)
|
||||
|
||||
let point
|
||||
|
||||
const hasDiscreteMarkers =
|
||||
w.config.markers.discrete && w.config.markers.discrete.length
|
||||
|
||||
if (
|
||||
w.globals.markers.size[seriesIndex] > 0 ||
|
||||
alwaysDrawMarker ||
|
||||
hasDiscreteMarkers
|
||||
) {
|
||||
elPointsWrap = graphics.group({
|
||||
class:
|
||||
alwaysDrawMarker || hasDiscreteMarkers
|
||||
? ''
|
||||
: 'apexcharts-series-markers',
|
||||
})
|
||||
|
||||
elPointsWrap.attr(
|
||||
'clip-path',
|
||||
`url(#gridRectMarkerMask${w.globals.cuid})`
|
||||
)
|
||||
}
|
||||
|
||||
if (Array.isArray(p.x)) {
|
||||
for (let q = 0; q < p.x.length; q++) {
|
||||
let dataPointIndex = j
|
||||
|
||||
// a small hack as we have 2 points for the first val to connect it
|
||||
if (j === 1 && q === 0) dataPointIndex = 0
|
||||
if (j === 1 && q === 1) dataPointIndex = 1
|
||||
|
||||
let PointClasses = 'apexcharts-marker'
|
||||
if (
|
||||
(w.config.chart.type === 'line' || w.config.chart.type === 'area') &&
|
||||
!w.globals.comboCharts &&
|
||||
!w.config.tooltip.intersect
|
||||
) {
|
||||
PointClasses += ' no-pointer-events'
|
||||
}
|
||||
|
||||
const shouldMarkerDraw = Array.isArray(w.config.markers.size)
|
||||
? w.globals.markers.size[seriesIndex] > 0
|
||||
: w.config.markers.size > 0
|
||||
|
||||
if (shouldMarkerDraw || alwaysDrawMarker || hasDiscreteMarkers) {
|
||||
if (Utils.isNumber(p.y[q])) {
|
||||
PointClasses += ` w${Utils.randomId()}`
|
||||
} else {
|
||||
PointClasses = 'apexcharts-nullpoint'
|
||||
}
|
||||
|
||||
let opts = this.getMarkerConfig({
|
||||
cssClass: PointClasses,
|
||||
seriesIndex,
|
||||
dataPointIndex,
|
||||
})
|
||||
|
||||
if (w.config.series[i].data[dataPointIndex]) {
|
||||
if (w.config.series[i].data[dataPointIndex].fillColor) {
|
||||
opts.pointFillColor =
|
||||
w.config.series[i].data[dataPointIndex].fillColor
|
||||
}
|
||||
|
||||
if (w.config.series[i].data[dataPointIndex].strokeColor) {
|
||||
opts.pointStrokeColor =
|
||||
w.config.series[i].data[dataPointIndex].strokeColor
|
||||
}
|
||||
}
|
||||
|
||||
if (pSize) {
|
||||
opts.pSize = pSize
|
||||
}
|
||||
|
||||
if (
|
||||
p.x[q] < 0 ||
|
||||
p.x[q] > w.globals.gridWidth ||
|
||||
p.y[q] < -w.globals.markers.largestSize ||
|
||||
p.y[q] > w.globals.gridHeight + w.globals.markers.largestSize
|
||||
) {
|
||||
opts.pSize = 0
|
||||
}
|
||||
|
||||
point = graphics.drawMarker(p.x[q], p.y[q], opts)
|
||||
|
||||
point.attr('rel', dataPointIndex)
|
||||
point.attr('j', dataPointIndex)
|
||||
point.attr('index', seriesIndex)
|
||||
point.node.setAttribute('default-marker-size', opts.pSize)
|
||||
|
||||
const filters = new Filters(this.ctx)
|
||||
filters.setSelectionFilter(point, seriesIndex, dataPointIndex)
|
||||
this.addEvents(point)
|
||||
|
||||
if (elPointsWrap) {
|
||||
elPointsWrap.add(point)
|
||||
}
|
||||
} else {
|
||||
// dynamic array creation - multidimensional
|
||||
if (typeof w.globals.pointsArray[seriesIndex] === 'undefined')
|
||||
w.globals.pointsArray[seriesIndex] = []
|
||||
|
||||
w.globals.pointsArray[seriesIndex].push([p.x[q], p.y[q]])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return elPointsWrap
|
||||
}
|
||||
|
||||
getMarkerConfig({
|
||||
cssClass,
|
||||
seriesIndex,
|
||||
dataPointIndex = null,
|
||||
finishRadius = null,
|
||||
}) {
|
||||
const w = this.w
|
||||
let pStyle = this.getMarkerStyle(seriesIndex)
|
||||
let pSize = w.globals.markers.size[seriesIndex]
|
||||
|
||||
const m = w.config.markers
|
||||
|
||||
// discrete markers is an option where user can specify a particular marker with different shape, size and color
|
||||
|
||||
if (dataPointIndex !== null && m.discrete.length) {
|
||||
m.discrete.map((marker) => {
|
||||
if (
|
||||
marker.seriesIndex === seriesIndex &&
|
||||
marker.dataPointIndex === dataPointIndex
|
||||
) {
|
||||
pStyle.pointStrokeColor = marker.strokeColor
|
||||
pStyle.pointFillColor = marker.fillColor
|
||||
pSize = marker.size
|
||||
pStyle.pointShape = marker.shape
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
pSize: finishRadius === null ? pSize : finishRadius,
|
||||
pRadius: m.radius,
|
||||
width: Array.isArray(m.width) ? m.width[seriesIndex] : m.width,
|
||||
height: Array.isArray(m.height) ? m.height[seriesIndex] : m.height,
|
||||
pointStrokeWidth: Array.isArray(m.strokeWidth)
|
||||
? m.strokeWidth[seriesIndex]
|
||||
: m.strokeWidth,
|
||||
pointStrokeColor: pStyle.pointStrokeColor,
|
||||
pointFillColor: pStyle.pointFillColor,
|
||||
shape:
|
||||
pStyle.pointShape ||
|
||||
(Array.isArray(m.shape) ? m.shape[seriesIndex] : m.shape),
|
||||
class: cssClass,
|
||||
pointStrokeOpacity: Array.isArray(m.strokeOpacity)
|
||||
? m.strokeOpacity[seriesIndex]
|
||||
: m.strokeOpacity,
|
||||
pointStrokeDashArray: Array.isArray(m.strokeDashArray)
|
||||
? m.strokeDashArray[seriesIndex]
|
||||
: m.strokeDashArray,
|
||||
pointFillOpacity: Array.isArray(m.fillOpacity)
|
||||
? m.fillOpacity[seriesIndex]
|
||||
: m.fillOpacity,
|
||||
seriesIndex,
|
||||
}
|
||||
}
|
||||
|
||||
addEvents(circle) {
|
||||
const w = this.w
|
||||
|
||||
const graphics = new Graphics(this.ctx)
|
||||
circle.node.addEventListener(
|
||||
'mouseenter',
|
||||
graphics.pathMouseEnter.bind(this.ctx, circle)
|
||||
)
|
||||
circle.node.addEventListener(
|
||||
'mouseleave',
|
||||
graphics.pathMouseLeave.bind(this.ctx, circle)
|
||||
)
|
||||
|
||||
circle.node.addEventListener(
|
||||
'mousedown',
|
||||
graphics.pathMouseDown.bind(this.ctx, circle)
|
||||
)
|
||||
|
||||
circle.node.addEventListener('click', w.config.markers.onClick)
|
||||
circle.node.addEventListener('dblclick', w.config.markers.onDblClick)
|
||||
|
||||
circle.node.addEventListener(
|
||||
'touchstart',
|
||||
graphics.pathMouseDown.bind(this.ctx, circle),
|
||||
{ passive: true }
|
||||
)
|
||||
}
|
||||
|
||||
getMarkerStyle(seriesIndex) {
|
||||
let w = this.w
|
||||
|
||||
let colors = w.globals.markers.colors
|
||||
let strokeColors =
|
||||
w.config.markers.strokeColor || w.config.markers.strokeColors
|
||||
|
||||
let pointStrokeColor = Array.isArray(strokeColors)
|
||||
? strokeColors[seriesIndex]
|
||||
: strokeColors
|
||||
let pointFillColor = Array.isArray(colors) ? colors[seriesIndex] : colors
|
||||
|
||||
return {
|
||||
pointStrokeColor,
|
||||
pointFillColor,
|
||||
}
|
||||
}
|
||||
}
|
||||
+605
@@ -0,0 +1,605 @@
|
||||
import Utils from '../utils/Utils'
|
||||
import DateTime from '../utils/DateTime'
|
||||
import Scales from './Scales'
|
||||
|
||||
/**
|
||||
* Range is used to generates values between min and max.
|
||||
*
|
||||
* @module Range
|
||||
**/
|
||||
|
||||
class Range {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
|
||||
this.scales = new Scales(ctx)
|
||||
}
|
||||
|
||||
init() {
|
||||
this.setYRange()
|
||||
this.setXRange()
|
||||
this.setZRange()
|
||||
}
|
||||
|
||||
getMinYMaxY(
|
||||
startingIndex,
|
||||
lowestY = Number.MAX_VALUE,
|
||||
highestY = -Number.MAX_VALUE,
|
||||
endingIndex = null
|
||||
) {
|
||||
const cnf = this.w.config
|
||||
const gl = this.w.globals
|
||||
let maxY = -Number.MAX_VALUE
|
||||
let minY = Number.MIN_VALUE
|
||||
|
||||
if (endingIndex === null) {
|
||||
endingIndex = startingIndex + 1
|
||||
}
|
||||
|
||||
let firstXIndex = 0
|
||||
let lastXIndex = 0
|
||||
let seriesX = undefined
|
||||
if (gl.seriesX.length >= endingIndex) {
|
||||
seriesX = [...new Set([].concat(...gl.seriesX.slice(startingIndex, endingIndex)))]
|
||||
firstXIndex = 0
|
||||
lastXIndex = seriesX.length - 1
|
||||
if (cnf.xaxis.min) {
|
||||
for (
|
||||
firstXIndex = 0;
|
||||
firstXIndex < lastXIndex && seriesX[firstXIndex] <= cnf.xaxis.min;
|
||||
firstXIndex++
|
||||
) {}
|
||||
}
|
||||
if (cnf.xaxis.max) {
|
||||
for (
|
||||
;
|
||||
lastXIndex > firstXIndex && seriesX[lastXIndex] >= cnf.xaxis.max;
|
||||
lastXIndex--
|
||||
) {}
|
||||
}
|
||||
}
|
||||
|
||||
let series = gl.series
|
||||
let seriesMin = series
|
||||
let seriesMax = series
|
||||
|
||||
if (cnf.chart.type === 'candlestick') {
|
||||
seriesMin = gl.seriesCandleL
|
||||
seriesMax = gl.seriesCandleH
|
||||
} else if (cnf.chart.type === 'boxPlot') {
|
||||
seriesMin = gl.seriesCandleO
|
||||
seriesMax = gl.seriesCandleC
|
||||
} else if (gl.isRangeData) {
|
||||
seriesMin = gl.seriesRangeStart
|
||||
seriesMax = gl.seriesRangeEnd
|
||||
}
|
||||
|
||||
for (let i = startingIndex; i < endingIndex; i++) {
|
||||
gl.dataPoints = Math.max(gl.dataPoints, series[i].length)
|
||||
|
||||
if (gl.categoryLabels.length) {
|
||||
gl.dataPoints = gl.categoryLabels.filter(
|
||||
(label) => typeof label !== 'undefined'
|
||||
).length
|
||||
}
|
||||
|
||||
if (
|
||||
gl.labels.length &&
|
||||
cnf.xaxis.type !== 'datetime' &&
|
||||
gl.series.reduce((a, c) => a + c.length, 0) !== 0
|
||||
) {
|
||||
// the condition cnf.xaxis.type !== 'datetime' fixes #3897 and #3905
|
||||
gl.dataPoints = Math.max(gl.dataPoints, gl.labels.length)
|
||||
}
|
||||
if (!seriesX) {
|
||||
firstXIndex = 0
|
||||
lastXIndex = gl.series[i].length
|
||||
}
|
||||
for (let j = firstXIndex; j <= lastXIndex; j++) {
|
||||
let val = series[i][j]
|
||||
if (val !== null && Utils.isNumber(val)) {
|
||||
if (typeof seriesMax[i][j] !== 'undefined') {
|
||||
maxY = Math.max(maxY, seriesMax[i][j])
|
||||
lowestY = Math.min(lowestY, seriesMax[i][j])
|
||||
}
|
||||
if (typeof seriesMin[i][j] !== 'undefined') {
|
||||
lowestY = Math.min(lowestY, seriesMin[i][j])
|
||||
highestY = Math.max(highestY, seriesMin[i][j])
|
||||
}
|
||||
|
||||
// These series arrays are dual purpose:
|
||||
// Array : CandleO, CandleH, CandleM, CandleL, CandleC
|
||||
// Candlestick: O H L C
|
||||
// Boxplot : Min Q1 Median Q3 Max
|
||||
switch (cnf.series[i].type) {
|
||||
case 'candlestick': {
|
||||
if (typeof gl.seriesCandleC[i][j] !== 'undefined') {
|
||||
maxY = Math.max(maxY, gl.seriesCandleH[i][j])
|
||||
lowestY = Math.min(lowestY, gl.seriesCandleL[i][j])
|
||||
}
|
||||
}
|
||||
case 'boxPlot': {
|
||||
if (typeof gl.seriesCandleC[i][j] !== 'undefined') {
|
||||
maxY = Math.max(maxY, gl.seriesCandleC[i][j])
|
||||
lowestY = Math.min(lowestY, gl.seriesCandleO[i][j])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// there is a combo chart and the specified series in not either candlestick, boxplot, or rangeArea/rangeBar; find the max there
|
||||
if (
|
||||
cnf.series[i].type &&
|
||||
(cnf.series[i].type !== 'candlestick' &&
|
||||
cnf.series[i].type !== 'boxPlot' &&
|
||||
cnf.series[i].type !== 'rangeArea' &&
|
||||
cnf.series[i].type !== 'rangeBar')
|
||||
) {
|
||||
maxY = Math.max(maxY, gl.series[i][j])
|
||||
lowestY = Math.min(lowestY, gl.series[i][j])
|
||||
}
|
||||
highestY = maxY
|
||||
|
||||
if (
|
||||
gl.seriesGoals[i] &&
|
||||
gl.seriesGoals[i][j] &&
|
||||
Array.isArray(gl.seriesGoals[i][j])
|
||||
) {
|
||||
gl.seriesGoals[i][j].forEach((g) => {
|
||||
if (minY !== Number.MIN_VALUE) {
|
||||
minY = Math.min(minY, g.value)
|
||||
lowestY = minY
|
||||
}
|
||||
maxY = Math.max(maxY, g.value)
|
||||
highestY = maxY
|
||||
})
|
||||
}
|
||||
|
||||
if (Utils.isFloat(val)) {
|
||||
val = Utils.noExponents(val)
|
||||
gl.yValueDecimal = Math.max(
|
||||
gl.yValueDecimal,
|
||||
val.toString().split('.')[1].length
|
||||
)
|
||||
}
|
||||
if (minY > seriesMin[i][j] && seriesMin[i][j] < 0) {
|
||||
minY = seriesMin[i][j]
|
||||
}
|
||||
} else {
|
||||
gl.hasNullValues = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
cnf.chart.type === 'rangeBar' &&
|
||||
gl.seriesRangeStart.length &&
|
||||
gl.isBarHorizontal
|
||||
) {
|
||||
minY = lowestY
|
||||
}
|
||||
|
||||
if (cnf.chart.type === 'bar') {
|
||||
if (minY < 0 && maxY < 0) {
|
||||
// all negative values in a bar chart, hence make the max to 0
|
||||
maxY = 0
|
||||
}
|
||||
if (minY === Number.MIN_VALUE) {
|
||||
minY = 0
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
minY,
|
||||
maxY,
|
||||
lowestY,
|
||||
highestY,
|
||||
}
|
||||
}
|
||||
|
||||
setYRange() {
|
||||
let gl = this.w.globals
|
||||
let cnf = this.w.config
|
||||
gl.maxY = -Number.MAX_VALUE
|
||||
gl.minY = Number.MIN_VALUE
|
||||
|
||||
let lowestYInAllSeries = Number.MAX_VALUE
|
||||
|
||||
if (gl.isMultipleYAxis) {
|
||||
// we need to get minY and maxY for multiple y axis
|
||||
lowestYInAllSeries = Number.MAX_VALUE
|
||||
for (let i = 0; i < gl.series.length; i++) {
|
||||
const minYMaxYArr = this.getMinYMaxY(i)
|
||||
gl.minYArr[i] = minYMaxYArr.lowestY
|
||||
gl.maxYArr[i] = minYMaxYArr.highestY
|
||||
lowestYInAllSeries = Math.min(lowestYInAllSeries, minYMaxYArr.lowestY)
|
||||
}
|
||||
}
|
||||
|
||||
// and then, get the minY and maxY from all series
|
||||
const minYMaxY = this.getMinYMaxY(
|
||||
0,
|
||||
lowestYInAllSeries,
|
||||
null,
|
||||
gl.series.length
|
||||
)
|
||||
gl.minY = minYMaxY.lowestY
|
||||
gl.maxY = minYMaxY.highestY
|
||||
lowestYInAllSeries = minYMaxY.lowestY
|
||||
|
||||
if (cnf.chart.stacked) {
|
||||
this._setStackedMinMax()
|
||||
}
|
||||
|
||||
// if the numbers are too big, reduce the range
|
||||
// for eg, if number is between 100000-110000, putting 0 as the lowest
|
||||
// value is not so good idea. So change the gl.minY for
|
||||
// line/area/scatter/candlesticks/boxPlot/vertical rangebar
|
||||
if (
|
||||
cnf.chart.type === 'line' ||
|
||||
cnf.chart.type === 'area' ||
|
||||
cnf.chart.type === 'scatter' ||
|
||||
cnf.chart.type === 'candlestick' ||
|
||||
cnf.chart.type === 'boxPlot' ||
|
||||
(cnf.chart.type === 'rangeBar' && !gl.isBarHorizontal)
|
||||
) {
|
||||
if (
|
||||
gl.minY === Number.MIN_VALUE &&
|
||||
lowestYInAllSeries !== -Number.MAX_VALUE &&
|
||||
lowestYInAllSeries !== gl.maxY // single value possibility
|
||||
) {
|
||||
gl.minY = lowestYInAllSeries
|
||||
}
|
||||
} else {
|
||||
gl.minY = minYMaxY.minY
|
||||
}
|
||||
|
||||
cnf.yaxis.forEach((yaxe, index) => {
|
||||
// override all min/max values by user defined values (y axis)
|
||||
if (yaxe.max !== undefined) {
|
||||
if (typeof yaxe.max === 'number') {
|
||||
gl.maxYArr[index] = yaxe.max
|
||||
} else if (typeof yaxe.max === 'function') {
|
||||
// fixes apexcharts.js/issues/2098
|
||||
gl.maxYArr[index] = yaxe.max(
|
||||
gl.isMultipleYAxis ? gl.maxYArr[index] : gl.maxY
|
||||
)
|
||||
}
|
||||
|
||||
// gl.maxY is for single y-axis chart, it will be ignored in multi-yaxis
|
||||
gl.maxY = gl.maxYArr[index]
|
||||
}
|
||||
if (yaxe.min !== undefined) {
|
||||
if (typeof yaxe.min === 'number') {
|
||||
gl.minYArr[index] = yaxe.min
|
||||
} else if (typeof yaxe.min === 'function') {
|
||||
// fixes apexcharts.js/issues/2098
|
||||
gl.minYArr[index] = yaxe.min(
|
||||
gl.isMultipleYAxis
|
||||
? gl.minYArr[index] === Number.MIN_VALUE
|
||||
? 0
|
||||
: gl.minYArr[index]
|
||||
: gl.minY
|
||||
)
|
||||
}
|
||||
// gl.minY is for single y-axis chart, it will be ignored in multi-yaxis
|
||||
gl.minY = gl.minYArr[index]
|
||||
}
|
||||
})
|
||||
|
||||
// for horizontal bar charts, we need to check xaxis min/max as user may have specified there
|
||||
if (gl.isBarHorizontal) {
|
||||
const minmax = ['min', 'max']
|
||||
minmax.forEach((m) => {
|
||||
if (cnf.xaxis[m] !== undefined && typeof cnf.xaxis[m] === 'number') {
|
||||
m === 'min' ? (gl.minY = cnf.xaxis[m]) : (gl.maxY = cnf.xaxis[m])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// for multi y-axis we need different scales for each
|
||||
if (gl.isMultipleYAxis) {
|
||||
this.scales.setMultipleYScales()
|
||||
gl.minY = lowestYInAllSeries
|
||||
gl.yAxisScale.forEach((scale, i) => {
|
||||
gl.minYArr[i] = scale.niceMin
|
||||
gl.maxYArr[i] = scale.niceMax
|
||||
})
|
||||
} else {
|
||||
this.scales.setYScaleForIndex(0, gl.minY, gl.maxY)
|
||||
gl.minY = gl.yAxisScale[0].niceMin
|
||||
gl.maxY = gl.yAxisScale[0].niceMax
|
||||
gl.minYArr[0] = gl.yAxisScale[0].niceMin
|
||||
gl.maxYArr[0] = gl.yAxisScale[0].niceMax
|
||||
}
|
||||
|
||||
return {
|
||||
minY: gl.minY,
|
||||
maxY: gl.maxY,
|
||||
minYArr: gl.minYArr,
|
||||
maxYArr: gl.maxYArr,
|
||||
yAxisScale: gl.yAxisScale,
|
||||
}
|
||||
}
|
||||
|
||||
setXRange() {
|
||||
let gl = this.w.globals
|
||||
let cnf = this.w.config
|
||||
|
||||
const isXNumeric =
|
||||
cnf.xaxis.type === 'numeric' ||
|
||||
cnf.xaxis.type === 'datetime' ||
|
||||
(cnf.xaxis.type === 'category' && !gl.noLabelsProvided) ||
|
||||
gl.noLabelsProvided ||
|
||||
gl.isXNumeric
|
||||
|
||||
const getInitialMinXMaxX = () => {
|
||||
for (let i = 0; i < gl.series.length; i++) {
|
||||
if (gl.labels[i]) {
|
||||
for (let j = 0; j < gl.labels[i].length; j++) {
|
||||
if (gl.labels[i][j] !== null && Utils.isNumber(gl.labels[i][j])) {
|
||||
gl.maxX = Math.max(gl.maxX, gl.labels[i][j])
|
||||
gl.initialMaxX = Math.max(gl.maxX, gl.labels[i][j])
|
||||
gl.minX = Math.min(gl.minX, gl.labels[i][j])
|
||||
gl.initialMinX = Math.min(gl.minX, gl.labels[i][j])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// minX maxX starts here
|
||||
if (gl.isXNumeric) {
|
||||
getInitialMinXMaxX()
|
||||
}
|
||||
|
||||
if (gl.noLabelsProvided) {
|
||||
if (cnf.xaxis.categories.length === 0) {
|
||||
gl.maxX = gl.labels[gl.labels.length - 1]
|
||||
gl.initialMaxX = gl.labels[gl.labels.length - 1]
|
||||
gl.minX = 1
|
||||
gl.initialMinX = 1
|
||||
}
|
||||
}
|
||||
|
||||
if (gl.isXNumeric || gl.noLabelsProvided || gl.dataFormatXNumeric) {
|
||||
let ticks
|
||||
|
||||
if (cnf.xaxis.tickAmount === undefined) {
|
||||
ticks = Math.round(gl.svgWidth / 150)
|
||||
|
||||
// no labels provided and total number of dataPoints is less than 30
|
||||
if (cnf.xaxis.type === 'numeric' && gl.dataPoints < 30) {
|
||||
ticks = gl.dataPoints - 1
|
||||
}
|
||||
|
||||
// this check is for when ticks exceeds total datapoints and that would result in duplicate labels
|
||||
if (ticks > gl.dataPoints && gl.dataPoints !== 0) {
|
||||
ticks = gl.dataPoints - 1
|
||||
}
|
||||
} else if (cnf.xaxis.tickAmount === 'dataPoints') {
|
||||
if (gl.series.length > 1) {
|
||||
ticks = gl.series[gl.maxValsInArrayIndex].length - 1
|
||||
}
|
||||
if (gl.isXNumeric) {
|
||||
ticks = gl.maxX - gl.minX - 1
|
||||
}
|
||||
} else {
|
||||
ticks = cnf.xaxis.tickAmount
|
||||
}
|
||||
gl.xTickAmount = ticks
|
||||
|
||||
// override all min/max values by user defined values (x axis)
|
||||
if (cnf.xaxis.max !== undefined && typeof cnf.xaxis.max === 'number') {
|
||||
gl.maxX = cnf.xaxis.max
|
||||
}
|
||||
if (cnf.xaxis.min !== undefined && typeof cnf.xaxis.min === 'number') {
|
||||
gl.minX = cnf.xaxis.min
|
||||
}
|
||||
|
||||
// if range is provided, adjust the new minX
|
||||
if (cnf.xaxis.range !== undefined) {
|
||||
gl.minX = gl.maxX - cnf.xaxis.range
|
||||
}
|
||||
|
||||
if (gl.minX !== Number.MAX_VALUE && gl.maxX !== -Number.MAX_VALUE) {
|
||||
if (cnf.xaxis.convertedCatToNumeric && !gl.dataFormatXNumeric) {
|
||||
let catScale = []
|
||||
for (let i = gl.minX - 1; i < gl.maxX; i++) {
|
||||
catScale.push(i + 1)
|
||||
}
|
||||
gl.xAxisScale = {
|
||||
result: catScale,
|
||||
niceMin: catScale[0],
|
||||
niceMax: catScale[catScale.length - 1]
|
||||
}
|
||||
} else {
|
||||
gl.xAxisScale = this.scales.setXScale(gl.minX, gl.maxX)
|
||||
}
|
||||
} else {
|
||||
gl.xAxisScale = this.scales.linearScale(
|
||||
0,
|
||||
ticks,
|
||||
ticks,
|
||||
0,
|
||||
cnf.xaxis.stepSize
|
||||
)
|
||||
if (gl.noLabelsProvided && gl.labels.length > 0) {
|
||||
gl.xAxisScale = this.scales.linearScale(
|
||||
1,
|
||||
gl.labels.length,
|
||||
ticks - 1,
|
||||
0,
|
||||
cnf.xaxis.stepSize
|
||||
)
|
||||
|
||||
// this is the only place seriesX is again mutated
|
||||
gl.seriesX = gl.labels.slice()
|
||||
}
|
||||
}
|
||||
// we will still store these labels as the count for this will be different (to draw grid and labels placement)
|
||||
if (isXNumeric) {
|
||||
gl.labels = gl.xAxisScale.result.slice()
|
||||
}
|
||||
}
|
||||
|
||||
if (gl.isBarHorizontal && gl.labels.length) {
|
||||
gl.xTickAmount = gl.labels.length
|
||||
}
|
||||
|
||||
// single dataPoint
|
||||
this._handleSingleDataPoint()
|
||||
|
||||
// minimum x difference to calculate bar width in numeric bars
|
||||
this._getMinXDiff()
|
||||
|
||||
return {
|
||||
minX: gl.minX,
|
||||
maxX: gl.maxX,
|
||||
}
|
||||
}
|
||||
|
||||
setZRange() {
|
||||
// minZ, maxZ starts here
|
||||
let gl = this.w.globals
|
||||
|
||||
if (!gl.isDataXYZ) return
|
||||
for (let i = 0; i < gl.series.length; i++) {
|
||||
if (typeof gl.seriesZ[i] !== 'undefined') {
|
||||
for (let j = 0; j < gl.seriesZ[i].length; j++) {
|
||||
if (gl.seriesZ[i][j] !== null && Utils.isNumber(gl.seriesZ[i][j])) {
|
||||
gl.maxZ = Math.max(gl.maxZ, gl.seriesZ[i][j])
|
||||
gl.minZ = Math.min(gl.minZ, gl.seriesZ[i][j])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_handleSingleDataPoint() {
|
||||
const gl = this.w.globals
|
||||
const cnf = this.w.config
|
||||
|
||||
if (gl.minX === gl.maxX) {
|
||||
let datetimeObj = new DateTime(this.ctx)
|
||||
|
||||
if (cnf.xaxis.type === 'datetime') {
|
||||
const newMinX = datetimeObj.getDate(gl.minX)
|
||||
if (cnf.xaxis.labels.datetimeUTC) {
|
||||
newMinX.setUTCDate(newMinX.getUTCDate() - 2)
|
||||
} else {
|
||||
newMinX.setDate(newMinX.getDate() - 2)
|
||||
}
|
||||
|
||||
gl.minX = new Date(newMinX).getTime()
|
||||
|
||||
const newMaxX = datetimeObj.getDate(gl.maxX)
|
||||
if (cnf.xaxis.labels.datetimeUTC) {
|
||||
newMaxX.setUTCDate(newMaxX.getUTCDate() + 2)
|
||||
} else {
|
||||
newMaxX.setDate(newMaxX.getDate() + 2)
|
||||
}
|
||||
gl.maxX = new Date(newMaxX).getTime()
|
||||
} else if (
|
||||
cnf.xaxis.type === 'numeric' ||
|
||||
(cnf.xaxis.type === 'category' && !gl.noLabelsProvided)
|
||||
) {
|
||||
gl.minX = gl.minX - 2
|
||||
gl.initialMinX = gl.minX
|
||||
gl.maxX = gl.maxX + 2
|
||||
gl.initialMaxX = gl.maxX
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_getMinXDiff() {
|
||||
const gl = this.w.globals
|
||||
|
||||
if (gl.isXNumeric) {
|
||||
// get the least x diff if numeric x axis is present
|
||||
gl.seriesX.forEach((sX, i) => {
|
||||
if (sX.length === 1) {
|
||||
// a small hack to prevent overlapping multiple bars when there is just 1 datapoint in bar series.
|
||||
// fix #811
|
||||
sX.push(
|
||||
gl.seriesX[gl.maxValsInArrayIndex][
|
||||
gl.seriesX[gl.maxValsInArrayIndex].length - 1
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
// fix #983 (clone the array to avoid side effects)
|
||||
const seriesX = sX.slice()
|
||||
seriesX.sort((a, b) => a - b)
|
||||
|
||||
seriesX.forEach((s, j) => {
|
||||
if (j > 0) {
|
||||
let xDiff = s - seriesX[j - 1]
|
||||
if (xDiff > 0) {
|
||||
gl.minXDiff = Math.min(xDiff, gl.minXDiff)
|
||||
}
|
||||
}
|
||||
})
|
||||
if (gl.dataPoints === 1 || gl.minXDiff === Number.MAX_VALUE) {
|
||||
// fixes apexcharts.js #1221
|
||||
gl.minXDiff = 0.5
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
_setStackedMinMax() {
|
||||
const gl = this.w.globals
|
||||
// for stacked charts, we calculate each series's parallel values. i.e, series[0][j] + series[1][j] .... [series[i.length][j]] and get the max out of it
|
||||
|
||||
if (!gl.series.length) return
|
||||
let seriesGroups = gl.seriesGroups
|
||||
|
||||
if (!seriesGroups.length) {
|
||||
seriesGroups = [this.w.config.series.map((serie) => serie.name)]
|
||||
}
|
||||
let stackedPoss = {}
|
||||
let stackedNegs = {}
|
||||
|
||||
seriesGroups.forEach((group) => {
|
||||
stackedPoss[group] = []
|
||||
stackedNegs[group] = []
|
||||
const indicesOfSeriesInGroup = this.w.config.series
|
||||
.map((serie, si) => (group.indexOf(serie.name) > -1 ? si : null))
|
||||
.filter((f) => f !== null)
|
||||
|
||||
indicesOfSeriesInGroup.forEach((i) => {
|
||||
for (let j = 0; j < gl.series[gl.maxValsInArrayIndex].length; j++) {
|
||||
if (typeof stackedPoss[group][j] === 'undefined') {
|
||||
stackedPoss[group][j] = 0
|
||||
stackedNegs[group][j] = 0
|
||||
}
|
||||
|
||||
let stackSeries =
|
||||
(this.w.config.chart.stacked && !gl.comboCharts) ||
|
||||
(this.w.config.chart.stacked &&
|
||||
gl.comboCharts &&
|
||||
(!this.w.config.chart.stackOnlyBar ||
|
||||
this.w.config.series?.[i]?.type === 'bar'))
|
||||
|
||||
if (stackSeries) {
|
||||
if (gl.series[i][j] !== null && Utils.isNumber(gl.series[i][j])) {
|
||||
gl.series[i][j] > 0
|
||||
? (stackedPoss[group][j] +=
|
||||
parseFloat(gl.series[i][j]) + 0.0001)
|
||||
: (stackedNegs[group][j] += parseFloat(gl.series[i][j]))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Object.entries(stackedPoss).forEach(([key]) => {
|
||||
stackedPoss[key].forEach((_, stgi) => {
|
||||
gl.maxY = Math.max(gl.maxY, stackedPoss[key][stgi])
|
||||
gl.minY = Math.min(gl.minY, stackedNegs[key][stgi])
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default Range
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
import Config from './settings/Config'
|
||||
import Utils from '../utils/Utils'
|
||||
import CoreUtils from './CoreUtils'
|
||||
|
||||
/**
|
||||
* ApexCharts Responsive Class to override options for different screen sizes.
|
||||
*
|
||||
* @module Responsive
|
||||
**/
|
||||
|
||||
export default class Responsive {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
}
|
||||
|
||||
// the opts parameter if not null has to be set overriding everything
|
||||
// as the opts is set by user externally
|
||||
checkResponsiveConfig(opts) {
|
||||
const w = this.w
|
||||
const cnf = w.config
|
||||
|
||||
// check if responsive config exists
|
||||
if (cnf.responsive.length === 0) return
|
||||
|
||||
let res = cnf.responsive.slice()
|
||||
res
|
||||
.sort((a, b) =>
|
||||
a.breakpoint > b.breakpoint ? 1 : b.breakpoint > a.breakpoint ? -1 : 0
|
||||
)
|
||||
.reverse()
|
||||
|
||||
let config = new Config({})
|
||||
|
||||
const iterateResponsiveOptions = (newOptions = {}) => {
|
||||
let largestBreakpoint = res[0].breakpoint
|
||||
const width = window.innerWidth > 0 ? window.innerWidth : screen.width
|
||||
|
||||
if (width > largestBreakpoint) {
|
||||
let options = CoreUtils.extendArrayProps(
|
||||
config,
|
||||
w.globals.initialConfig,
|
||||
w
|
||||
)
|
||||
newOptions = Utils.extend(options, newOptions)
|
||||
newOptions = Utils.extend(w.config, newOptions)
|
||||
this.overrideResponsiveOptions(newOptions)
|
||||
} else {
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
if (width < res[i].breakpoint) {
|
||||
newOptions = CoreUtils.extendArrayProps(config, res[i].options, w)
|
||||
newOptions = Utils.extend(w.config, newOptions)
|
||||
this.overrideResponsiveOptions(newOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (opts) {
|
||||
let options = CoreUtils.extendArrayProps(config, opts, w)
|
||||
options = Utils.extend(w.config, options)
|
||||
options = Utils.extend(options, opts)
|
||||
iterateResponsiveOptions(options)
|
||||
} else {
|
||||
iterateResponsiveOptions({})
|
||||
}
|
||||
}
|
||||
|
||||
overrideResponsiveOptions(newOptions) {
|
||||
let newConfig = new Config(newOptions).init({ responsiveOverride: true })
|
||||
this.w.config = newConfig
|
||||
}
|
||||
}
|
||||
+813
@@ -0,0 +1,813 @@
|
||||
import Utils from '../utils/Utils'
|
||||
|
||||
export default class Scales {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
}
|
||||
|
||||
// http://stackoverflow.com/questions/326679/choosing-an-attractive-linear-scale-for-a-graphs-y-axis
|
||||
// This routine creates the Y axis values for a graph.
|
||||
niceScale(yMin, yMax, index = 0) {
|
||||
const jsPrecision = 1e-11 // JS precision errors
|
||||
const w = this.w
|
||||
const gl = w.globals
|
||||
const xaxisCnf = w.config.xaxis
|
||||
const yaxisCnf = w.config.yaxis[index]
|
||||
let gotMin = yaxisCnf.min !== undefined && yaxisCnf.min !== null
|
||||
let gotMax = yaxisCnf.max !== undefined && yaxisCnf.min !== null
|
||||
let gotStepSize =
|
||||
yaxisCnf.stepSize !== undefined && yaxisCnf.stepSize !== null
|
||||
let gotTickAmount =
|
||||
yaxisCnf.tickAmount !== undefined && yaxisCnf.tickAmount !== null
|
||||
// The most ticks we can fit into the svg chart dimensions
|
||||
const maxTicks =
|
||||
((gl.isBarHorizontal ? gl.svgWidth : gl.svgHeight) - 100) / 15 // Guestimate
|
||||
let ticks = gotTickAmount ? yaxisCnf.tickAmount : 10
|
||||
|
||||
// In case we have a multi axis chart:
|
||||
// Ensure subsequent series start with the same tickAmount as series[0],
|
||||
// because the tick lines are drawn based on series[0]. This does not
|
||||
// override user defined options for any series.
|
||||
if (gl.isMultipleYAxis && !gotTickAmount && gl.multiAxisTickAmount > 0) {
|
||||
ticks = gl.multiAxisTickAmount
|
||||
gotTickAmount = true
|
||||
}
|
||||
|
||||
if (ticks === 'dataPoints') {
|
||||
ticks = gl.dataPoints - 1
|
||||
} else {
|
||||
// Ensure ticks is an integer
|
||||
ticks = Math.abs(Math.round(ticks))
|
||||
}
|
||||
|
||||
if (
|
||||
(yMin === Number.MIN_VALUE && yMax === 0) ||
|
||||
(!Utils.isNumber(yMin) && !Utils.isNumber(yMax)) ||
|
||||
(yMin === Number.MIN_VALUE && yMax === -Number.MAX_VALUE)
|
||||
) {
|
||||
// when all values are 0
|
||||
yMin = 0
|
||||
yMax = ticks
|
||||
gl.allSeriesCollapsed = false
|
||||
}
|
||||
|
||||
if (yMin > yMax) {
|
||||
// if somehow due to some wrong config, user sent max less than min,
|
||||
// adjust the min/max again
|
||||
console.warn(
|
||||
'axis.min cannot be greater than axis.max: swapping min and max'
|
||||
)
|
||||
let temp = yMax
|
||||
yMax = yMin
|
||||
yMin = temp
|
||||
} else if (yMin === yMax) {
|
||||
// If yMin and yMax are identical, then
|
||||
// adjust the yMin and yMax values to actually
|
||||
// make a graph. Also avoids division by zero errors.
|
||||
yMin = yMin === 0 ? 0 : yMin - 1 // choose an integer in case yValueDecimals=0
|
||||
yMax = yMax === 0 ? 2 : yMax + 1 // choose an integer in case yValueDecimals=0
|
||||
}
|
||||
|
||||
// Calculate Min amd Max graphical labels and graph
|
||||
// increments.
|
||||
//
|
||||
// Output will be an array of the Y axis values that
|
||||
// encompass the Y values.
|
||||
let result = []
|
||||
|
||||
if (ticks < 1) {
|
||||
ticks = 1
|
||||
}
|
||||
let tiks = ticks
|
||||
|
||||
// Determine Range
|
||||
let range = Math.abs(yMax - yMin)
|
||||
|
||||
if (yaxisCnf.forceNiceScale) {
|
||||
// Snap min or max to zero if close
|
||||
let proximityRatio = 0.15
|
||||
if (!gotMin && yMin > 0 && yMin / range < proximityRatio) {
|
||||
yMin = 0
|
||||
gotMin = true
|
||||
}
|
||||
if (!gotMax && yMax < 0 && -yMax / range < proximityRatio) {
|
||||
yMax = 0
|
||||
gotMax = true
|
||||
}
|
||||
range = Math.abs(yMax - yMin)
|
||||
}
|
||||
|
||||
// Calculate a pretty step value based on ticks
|
||||
|
||||
// Initial stepSize
|
||||
let stepSize = range / tiks
|
||||
let niceStep = stepSize
|
||||
let mag = Math.floor(Math.log10(niceStep))
|
||||
let magPow = Math.pow(10, mag)
|
||||
// ceil() is used below in conjunction with the values populating
|
||||
// niceScaleAllowedMagMsd[][] to ensure that (niceStep * tiks)
|
||||
// produces a range that doesn't clip data points after stretching
|
||||
// the raw range out a little to match the prospective new range.
|
||||
let magMsd = Math.ceil(niceStep / magPow)
|
||||
// See globals.js for info on what niceScaleAllowedMagMsd does
|
||||
magMsd = gl.niceScaleAllowedMagMsd[gl.yValueDecimal === 0 ? 0 : 1][magMsd]
|
||||
niceStep = magMsd * magPow
|
||||
|
||||
// Initial stepSize
|
||||
stepSize = niceStep
|
||||
|
||||
// Get step value
|
||||
if (
|
||||
gl.isBarHorizontal &&
|
||||
xaxisCnf.stepSize &&
|
||||
xaxisCnf.type !== 'datetime'
|
||||
) {
|
||||
stepSize = xaxisCnf.stepSize
|
||||
gotStepSize = true
|
||||
} else if (gotStepSize) {
|
||||
stepSize = yaxisCnf.stepSize
|
||||
}
|
||||
if (gotStepSize) {
|
||||
if (yaxisCnf.forceNiceScale) {
|
||||
// Check that given stepSize is sane with respect to the range.
|
||||
//
|
||||
// The user can, by setting forceNiceScale = true,
|
||||
// define a stepSize that will be scaled to useful value before
|
||||
// it's checked for consistency.
|
||||
//
|
||||
// If, for example, the range = 4 and the user defined stepSize = 8
|
||||
// (or 8000 or 0.0008, etc), then stepSize is inapplicable as
|
||||
// it is. Reducing it to 0.8 will fit with 5 ticks.
|
||||
//
|
||||
if (Math.round(Math.log10(stepSize)) != mag) {
|
||||
let ref = range / ticks
|
||||
while (stepSize < ref) {
|
||||
stepSize *= 10
|
||||
}
|
||||
while (stepSize > ref) {
|
||||
stepSize /= 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start applying some rules
|
||||
if (gotMin && gotMax) {
|
||||
let crudeStep = range / tiks
|
||||
// min and max (range) cannot be changed
|
||||
if (gotTickAmount) {
|
||||
if (gotStepSize) {
|
||||
if (Utils.mod(range, stepSize) != 0) {
|
||||
// stepSize conflicts with range
|
||||
let gcdStep = Utils.getGCD(stepSize, crudeStep)
|
||||
// gcdStep is a multiple of range because crudeStep is a multiple.
|
||||
// gcdStep is also a multiple of stepSize, so it partially honoured
|
||||
// All three could be equal, which would be very nice
|
||||
// if the computed stepSize generates too many ticks they will be
|
||||
// reduced later, unless the number is prime, in which case,
|
||||
// the chart will display all of them or just one (plus the X axis)
|
||||
// depending on svg dimensions. Setting forceNiceScale: true will force
|
||||
// the display of at least the default number of ticks.
|
||||
if (crudeStep / gcdStep < 10) {
|
||||
stepSize = gcdStep
|
||||
} else {
|
||||
// stepSize conflicts and no reasonable adjustment, but must
|
||||
// honour tickAmount
|
||||
stepSize = crudeStep
|
||||
}
|
||||
} else {
|
||||
// stepSize fits
|
||||
if (Utils.mod(stepSize, crudeStep) == 0) {
|
||||
// crudeStep is a multiple of stepSize, or vice versa
|
||||
// we know crudeStep will generate tickAmount ticks
|
||||
stepSize = crudeStep
|
||||
} else {
|
||||
// stepSize conflicts with tickAmount
|
||||
// if the user is setting up a multi-axis chart and wants
|
||||
// synced axis ticks then they should not define stepSize
|
||||
// or ensure there is no conflict between any of their options
|
||||
// on any axis.
|
||||
crudeStep = stepSize
|
||||
// De-prioritizing ticks from now on
|
||||
gotTickAmount = false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// no user stepSize, honour ticks
|
||||
stepSize = crudeStep
|
||||
}
|
||||
} else {
|
||||
// default ticks in use
|
||||
if (gotStepSize) {
|
||||
if (Utils.mod(range, stepSize) == 0) {
|
||||
// stepSize fits
|
||||
crudeStep = stepSize
|
||||
} else {
|
||||
stepSize = crudeStep
|
||||
}
|
||||
} else {
|
||||
// no user stepSize
|
||||
tiks = Math.round(range / niceStep)
|
||||
crudeStep = range / tiks
|
||||
if (Utils.mod(range, stepSize) != 0) {
|
||||
// stepSize doesn't fit
|
||||
let gcdStep = Utils.getGCD(range, niceStep)
|
||||
if (niceStep / gcdStep < 10) {
|
||||
crudeStep = gcdStep
|
||||
}
|
||||
stepSize = crudeStep
|
||||
} else {
|
||||
// stepSize fits
|
||||
crudeStep = stepSize
|
||||
}
|
||||
}
|
||||
}
|
||||
tiks = Math.round(range / stepSize)
|
||||
} else {
|
||||
// Snap range to ticks
|
||||
if (!gotMin && !gotMax) {
|
||||
if (gotTickAmount) {
|
||||
// Allow a half-stepSize shift if series doesn't cross the X axis
|
||||
// to ensure graph doesn't clip. Not if it does cross, in order
|
||||
// to keep the 0 aligned with a grid line in multi axis charts.
|
||||
let shift = stepSize / (yMax - yMin > yMax ? 1 : 2)
|
||||
yMin = shift * Math.floor(yMin / shift)
|
||||
yMax = yMin + stepSize * tiks
|
||||
} else {
|
||||
yMin = stepSize * Math.floor(yMin / stepSize)
|
||||
yMax = stepSize * Math.ceil(yMax / stepSize)
|
||||
}
|
||||
} else if (gotMax) {
|
||||
if (gotTickAmount) {
|
||||
yMin = yMax - stepSize * tiks
|
||||
} else {
|
||||
yMin = stepSize * Math.floor(yMin / stepSize)
|
||||
}
|
||||
} else if (gotMin) {
|
||||
if (gotTickAmount) {
|
||||
yMax = yMin + stepSize * tiks
|
||||
} else {
|
||||
yMax = stepSize * Math.ceil(yMax / stepSize)
|
||||
}
|
||||
}
|
||||
range = Math.abs(yMax - yMin)
|
||||
// Final check and possible adjustment of stepSize to prevent
|
||||
// overridding the user's min or max choice.
|
||||
stepSize = Utils.getGCD(range, stepSize)
|
||||
tiks = Math.round(range / stepSize)
|
||||
}
|
||||
|
||||
// Shrinkwrap ticks to the range
|
||||
if (!gotTickAmount && !(gotMin || gotMax)) {
|
||||
tiks = Math.ceil((range - jsPrecision) / (stepSize + jsPrecision))
|
||||
// No user tickAmount, or min or max, we are free to adjust to avoid a
|
||||
// prime number. This helps when reducing ticks for small svg dimensions.
|
||||
if (tiks > 16 && Utils.getPrimeFactors(tiks).length < 2) {
|
||||
tiks++
|
||||
}
|
||||
}
|
||||
|
||||
// Record final tiks for use by other series that call niceScale().
|
||||
// Note: some don't, like logarithmicScale(), etc.
|
||||
if (gl.isMultipleYAxis && gl.multiAxisTickAmount == 0) {
|
||||
gl.multiAxisTickAmount = tiks
|
||||
}
|
||||
|
||||
if (
|
||||
tiks > maxTicks &&
|
||||
(!(gotTickAmount || gotStepSize) || yaxisCnf.forceNiceScale)
|
||||
) {
|
||||
// Reduce the number of ticks nicely if chart svg dimensions shrink too far.
|
||||
// The reduced tick set should always be a subset of the full set.
|
||||
//
|
||||
// This following products of prime factors method works as follows:
|
||||
// We compute the prime factors of the full tick count (tiks), then all the
|
||||
// possible products of those factors in order from smallest to biggest,
|
||||
// until we find a product P such that: tiks/P < maxTicks.
|
||||
//
|
||||
// Example:
|
||||
// Computing products of the prime factors of 30.
|
||||
//
|
||||
// tiks | pf | 1 2 3 4 5 6 <-- compute order
|
||||
// --------------------------------------------------
|
||||
// 30 | 5 | 5 5 5 <-- Multiply all
|
||||
// | 3 | 3 3 3 3 <-- primes in each
|
||||
// | 2 | 2 2 2 <-- column = P
|
||||
// --------------------------------------------------
|
||||
// 15 10 6 5 2 1 <-- tiks/P
|
||||
//
|
||||
// tiks = 30 has prime factors [2, 3, 5]
|
||||
// The loop below computes the products [2,3,5,6,15,30].
|
||||
// The last product of P = 2*3*5 is skipped since 30/P = 1.
|
||||
// This yields tiks/P = [15,10,6,5,2,1], checked in order until
|
||||
// tiks/P < maxTicks.
|
||||
//
|
||||
// Pros:
|
||||
// 1) The ticks in the reduced set are always members of the
|
||||
// full set of ticks.
|
||||
// Cons:
|
||||
// 1) None: if tiks is prime, we get all or one, nothing between, so
|
||||
// the worst case is to display all, which is the status quo. Really
|
||||
// only a problem visually for larger tick numbers, say, > 7.
|
||||
//
|
||||
let pf = Utils.getPrimeFactors(tiks)
|
||||
let last = pf.length - 1
|
||||
let tt = tiks
|
||||
reduceLoop: for (var xFactors = 0; xFactors < last; xFactors++) {
|
||||
for (var lowest = 0; lowest <= last - xFactors; lowest++) {
|
||||
let stop = Math.min(lowest + xFactors, last)
|
||||
let t = tt
|
||||
let div = 1
|
||||
for (var next = lowest; next <= stop; next++) {
|
||||
div *= pf[next]
|
||||
}
|
||||
t /= div
|
||||
if (t < maxTicks) {
|
||||
tt = t
|
||||
break reduceLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
// Only reduce tiks all the way down to 1 (increase stepSize to range)
|
||||
// if forceNiceScale = true, to give the user the option if tiks is
|
||||
// prime and > maxTicks, which may result in premature removal of all but
|
||||
// the last tick. It will not be immediately obvious why that has occured.
|
||||
if (tt === tiks && yaxisCnf.forceNiceScale) {
|
||||
stepSize = range
|
||||
} else {
|
||||
stepSize = range / tt
|
||||
}
|
||||
}
|
||||
|
||||
// build Y label array.
|
||||
|
||||
let val = yMin - stepSize
|
||||
// Ensure we don't under/over shoot due to JS precision errors.
|
||||
// This also fixes (amongst others):
|
||||
// https://github.com/apexcharts/apexcharts.js/issues/430
|
||||
let err = stepSize * jsPrecision
|
||||
do {
|
||||
val += stepSize
|
||||
result.push(Utils.stripNumber(val, 7))
|
||||
} while (yMax - val > err)
|
||||
|
||||
return {
|
||||
result,
|
||||
niceMin: result[0],
|
||||
niceMax: result[result.length - 1],
|
||||
}
|
||||
}
|
||||
|
||||
linearScale(yMin, yMax, ticks = 10, index = 0, step = undefined) {
|
||||
let range = Math.abs(yMax - yMin)
|
||||
|
||||
ticks = this._adjustTicksForSmallRange(ticks, index, range)
|
||||
|
||||
if (ticks === 'dataPoints') {
|
||||
ticks = this.w.globals.dataPoints - 1
|
||||
}
|
||||
|
||||
if (!step) {
|
||||
step = range / ticks
|
||||
}
|
||||
|
||||
if (ticks === Number.MAX_VALUE) {
|
||||
ticks = 5
|
||||
step = 1
|
||||
}
|
||||
|
||||
let result = []
|
||||
let v = yMin
|
||||
|
||||
while (ticks >= 0) {
|
||||
result.push(v)
|
||||
v = v + step
|
||||
ticks -= 1
|
||||
}
|
||||
|
||||
return {
|
||||
result,
|
||||
niceMin: result[0],
|
||||
niceMax: result[result.length - 1],
|
||||
}
|
||||
}
|
||||
|
||||
logarithmicScaleNice(yMin, yMax, base) {
|
||||
// Basic validation to avoid for loop starting at -inf.
|
||||
if (yMax <= 0) yMax = Math.max(yMin, base)
|
||||
if (yMin <= 0) yMin = Math.min(yMax, base)
|
||||
|
||||
const logs = []
|
||||
|
||||
const logMax = Math.ceil(Math.log(yMax) / Math.log(base) + 1) // Get powers of base for our max and min
|
||||
const logMin = Math.floor(Math.log(yMin) / Math.log(base))
|
||||
|
||||
for (let i = logMin; i < logMax; i++) {
|
||||
logs.push(Math.pow(base, i))
|
||||
}
|
||||
|
||||
return {
|
||||
result: logs,
|
||||
niceMin: logs[0],
|
||||
niceMax: logs[logs.length - 1],
|
||||
}
|
||||
}
|
||||
|
||||
logarithmicScale(yMin, yMax, base) {
|
||||
// Basic validation to avoid for loop starting at -inf.
|
||||
if (yMax <= 0) yMax = Math.max(yMin, base)
|
||||
if (yMin <= 0) yMin = Math.min(yMax, base)
|
||||
|
||||
const logs = []
|
||||
|
||||
// Get the logarithmic range.
|
||||
const logMax = Math.log(yMax) / Math.log(base)
|
||||
const logMin = Math.log(yMin) / Math.log(base)
|
||||
|
||||
// Get the exact logarithmic range.
|
||||
// (This is the exact number of multiples of the base there are between yMin and yMax).
|
||||
const logRange = logMax - logMin
|
||||
|
||||
// Round the logarithmic range to get the number of ticks we will create.
|
||||
// If the chosen min/max values are multiples of each other WRT the base, this will be neat.
|
||||
// If the chosen min/max aren't, we will at least still provide USEFUL ticks.
|
||||
const ticks = Math.round(logRange)
|
||||
|
||||
// Get the logarithmic spacing between ticks.
|
||||
const logTickSpacing = logRange / ticks
|
||||
|
||||
// Create as many ticks as there is range in the logs.
|
||||
for (
|
||||
let i = 0, logTick = logMin;
|
||||
i < ticks;
|
||||
i++, logTick += logTickSpacing
|
||||
) {
|
||||
logs.push(Math.pow(base, logTick))
|
||||
}
|
||||
|
||||
// Add a final tick at the yMax.
|
||||
logs.push(Math.pow(base, logMax))
|
||||
|
||||
return {
|
||||
result: logs,
|
||||
niceMin: yMin,
|
||||
niceMax: yMax,
|
||||
}
|
||||
}
|
||||
|
||||
_adjustTicksForSmallRange(ticks, index, range) {
|
||||
let newTicks = ticks
|
||||
if (
|
||||
typeof index !== 'undefined' &&
|
||||
this.w.config.yaxis[index].labels.formatter &&
|
||||
this.w.config.yaxis[index].tickAmount === undefined
|
||||
) {
|
||||
const formattedVal = Number(
|
||||
this.w.config.yaxis[index].labels.formatter(1)
|
||||
)
|
||||
if (Utils.isNumber(formattedVal) && this.w.globals.yValueDecimal === 0) {
|
||||
newTicks = Math.ceil(range)
|
||||
}
|
||||
}
|
||||
return newTicks < ticks ? newTicks : ticks
|
||||
}
|
||||
|
||||
setYScaleForIndex(index, minY, maxY) {
|
||||
const gl = this.w.globals
|
||||
const cnf = this.w.config
|
||||
|
||||
let y = gl.isBarHorizontal ? cnf.xaxis : cnf.yaxis[index]
|
||||
|
||||
if (typeof gl.yAxisScale[index] === 'undefined') {
|
||||
gl.yAxisScale[index] = []
|
||||
}
|
||||
|
||||
let diff = Math.abs(maxY - minY)
|
||||
|
||||
if (y.logarithmic && diff <= 5) {
|
||||
gl.invalidLogScale = true
|
||||
}
|
||||
|
||||
if (y.logarithmic && diff > 5) {
|
||||
gl.allSeriesCollapsed = false
|
||||
gl.yAxisScale[index] = y.forceNiceScale
|
||||
? this.logarithmicScaleNice(minY, maxY, y.logBase)
|
||||
: this.logarithmicScale(minY, maxY, y.logBase)
|
||||
} else {
|
||||
if (maxY === -Number.MAX_VALUE || !Utils.isNumber(maxY)) {
|
||||
// no data in the chart. Either all series collapsed or user passed a blank array
|
||||
gl.yAxisScale[index] = this.linearScale(
|
||||
0,
|
||||
10,
|
||||
10,
|
||||
index,
|
||||
cnf.yaxis[index].stepSize
|
||||
)
|
||||
} else {
|
||||
// there is some data. Turn off the allSeriesCollapsed flag
|
||||
gl.allSeriesCollapsed = false
|
||||
gl.yAxisScale[index] = this.niceScale(minY, maxY, index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setXScale(minX, maxX) {
|
||||
const w = this.w
|
||||
const gl = w.globals
|
||||
let diff = Math.abs(maxX - minX)
|
||||
if (maxX === -Number.MAX_VALUE || !Utils.isNumber(maxX)) {
|
||||
// no data in the chart. Either all series collapsed or user passed a blank array
|
||||
gl.xAxisScale = this.linearScale(0, 10, 10)
|
||||
} else {
|
||||
gl.xAxisScale = this.linearScale(
|
||||
minX,
|
||||
maxX,
|
||||
w.config.xaxis.tickAmount
|
||||
? w.config.xaxis.tickAmount
|
||||
: diff < 10 && diff > 1
|
||||
? diff + 1
|
||||
: 10,
|
||||
0,
|
||||
w.config.xaxis.stepSize
|
||||
)
|
||||
}
|
||||
return gl.xAxisScale
|
||||
}
|
||||
|
||||
setMultipleYScales() {
|
||||
const gl = this.w.globals
|
||||
const cnf = this.w.config
|
||||
|
||||
const minYArr = gl.minYArr.concat([])
|
||||
const maxYArr = gl.maxYArr.concat([])
|
||||
|
||||
let scalesIndices = []
|
||||
// here, we loop through the yaxis array and find the item which has "seriesName" property
|
||||
cnf.yaxis.forEach((yaxe, i) => {
|
||||
let index = i
|
||||
cnf.series.forEach((s, si) => {
|
||||
// if seriesName matches and that series is not collapsed, we use that scale
|
||||
// fix issue #1215
|
||||
// proceed even if si is in gl.collapsedSeriesIndices
|
||||
if (s.name === yaxe.seriesName) {
|
||||
index = si
|
||||
|
||||
if (i !== si) {
|
||||
scalesIndices.push({
|
||||
index: si,
|
||||
similarIndex: i,
|
||||
alreadyExists: true,
|
||||
})
|
||||
} else {
|
||||
scalesIndices.push({
|
||||
index: si,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let minY = minYArr[index]
|
||||
let maxY = maxYArr[index]
|
||||
|
||||
this.setYScaleForIndex(i, minY, maxY)
|
||||
})
|
||||
|
||||
this.sameScaleInMultipleAxes(minYArr, maxYArr, scalesIndices)
|
||||
}
|
||||
|
||||
sameScaleInMultipleAxes(minYArr, maxYArr, scalesIndices) {
|
||||
const cnf = this.w.config
|
||||
const gl = this.w.globals
|
||||
|
||||
// we got the scalesIndices array in the above code, but we need to filter out the items which doesn't have same scales
|
||||
let similarIndices = []
|
||||
scalesIndices.forEach((scale) => {
|
||||
if (scale.alreadyExists) {
|
||||
if (typeof similarIndices[scale.index] === 'undefined') {
|
||||
similarIndices[scale.index] = []
|
||||
}
|
||||
similarIndices[scale.index].push(scale.index)
|
||||
similarIndices[scale.index].push(scale.similarIndex)
|
||||
}
|
||||
})
|
||||
|
||||
function intersect(a, b) {
|
||||
return a.filter((value) => b.indexOf(value) !== -1)
|
||||
}
|
||||
|
||||
gl.yAxisSameScaleIndices = similarIndices
|
||||
|
||||
similarIndices.forEach((si, i) => {
|
||||
similarIndices.forEach((sj, j) => {
|
||||
if (i !== j) {
|
||||
if (intersect(si, sj).length > 0) {
|
||||
similarIndices[i] = similarIndices[i].concat(similarIndices[j])
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// then, we remove duplicates from the similarScale array
|
||||
let uniqueSimilarIndices = similarIndices.map((item) => {
|
||||
return item.filter((i, pos) => item.indexOf(i) === pos)
|
||||
})
|
||||
|
||||
// sort further to remove whole duplicate arrays later
|
||||
let sortedIndices = uniqueSimilarIndices.map((s) => s.sort())
|
||||
|
||||
// remove undefined items
|
||||
similarIndices = similarIndices.filter((s) => !!s)
|
||||
|
||||
let indices = sortedIndices.slice()
|
||||
let stringIndices = indices.map((ind) => JSON.stringify(ind))
|
||||
indices = indices.filter(
|
||||
(ind, p) => stringIndices.indexOf(JSON.stringify(ind)) === p
|
||||
)
|
||||
|
||||
let sameScaleMinYArr = []
|
||||
let sameScaleMaxYArr = []
|
||||
minYArr.forEach((minYValue, yi) => {
|
||||
indices.forEach((scale, i) => {
|
||||
// we compare only the yIndex which exists in the indices array
|
||||
if (scale.indexOf(yi) > -1) {
|
||||
if (typeof sameScaleMinYArr[i] === 'undefined') {
|
||||
sameScaleMinYArr[i] = []
|
||||
sameScaleMaxYArr[i] = []
|
||||
}
|
||||
sameScaleMinYArr[i].push({
|
||||
key: yi,
|
||||
value: minYValue,
|
||||
})
|
||||
sameScaleMaxYArr[i].push({
|
||||
key: yi,
|
||||
value: maxYArr[yi],
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
let sameScaleMin = Array.apply(null, Array(indices.length)).map(
|
||||
Number.prototype.valueOf,
|
||||
Number.MIN_VALUE
|
||||
)
|
||||
let sameScaleMax = Array.apply(null, Array(indices.length)).map(
|
||||
Number.prototype.valueOf,
|
||||
-Number.MAX_VALUE
|
||||
)
|
||||
|
||||
sameScaleMinYArr.forEach((s, i) => {
|
||||
s.forEach((sc, j) => {
|
||||
sameScaleMin[i] = Math.min(sc.value, sameScaleMin[i])
|
||||
})
|
||||
})
|
||||
|
||||
sameScaleMaxYArr.forEach((s, i) => {
|
||||
s.forEach((sc, j) => {
|
||||
sameScaleMax[i] = Math.max(sc.value, sameScaleMax[i])
|
||||
})
|
||||
})
|
||||
|
||||
minYArr.forEach((min, i) => {
|
||||
sameScaleMaxYArr.forEach((s, si) => {
|
||||
let minY = sameScaleMin[si]
|
||||
let maxY = sameScaleMax[si]
|
||||
|
||||
if (cnf.chart.stacked) {
|
||||
// for stacked charts, we need to add the values
|
||||
maxY = 0
|
||||
|
||||
s.forEach((ind, k) => {
|
||||
// fix incorrectly adjust y scale issue #1215
|
||||
if (ind.value !== -Number.MAX_VALUE) {
|
||||
maxY += ind.value
|
||||
}
|
||||
if (minY !== Number.MIN_VALUE) {
|
||||
minY += sameScaleMinYArr[si][k].value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
s.forEach((ind, k) => {
|
||||
if (s[k].key === i) {
|
||||
if (cnf.yaxis[i].min !== undefined) {
|
||||
if (typeof cnf.yaxis[i].min === 'function') {
|
||||
minY = cnf.yaxis[i].min(gl.minY)
|
||||
} else {
|
||||
minY = cnf.yaxis[i].min
|
||||
}
|
||||
}
|
||||
if (cnf.yaxis[i].max !== undefined) {
|
||||
if (typeof cnf.yaxis[i].max === 'function') {
|
||||
maxY = cnf.yaxis[i].max(gl.maxY)
|
||||
} else {
|
||||
maxY = cnf.yaxis[i].max
|
||||
}
|
||||
}
|
||||
|
||||
this.setYScaleForIndex(i, minY, maxY)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// experimental feature which scales the y-axis to a min/max based on x-axis range
|
||||
autoScaleY(ctx, yaxis, e) {
|
||||
if (!ctx) {
|
||||
ctx = this
|
||||
}
|
||||
|
||||
const w = ctx.w
|
||||
|
||||
if (w.globals.isMultipleYAxis || w.globals.collapsedSeries.length) {
|
||||
// The autoScale option for multiple y-axis is turned off as it leads to buggy behavior.
|
||||
// Also, when a series is collapsed, it results in incorrect behavior. Hence turned it off for that too - fixes apexcharts.js#795
|
||||
console.warn('autoScaleYaxis not supported in a multi-yaxis chart.')
|
||||
return yaxis
|
||||
}
|
||||
|
||||
const seriesX = w.globals.seriesX[0]
|
||||
|
||||
let isStacked = w.config.chart.stacked
|
||||
|
||||
yaxis.forEach((yaxe, yi) => {
|
||||
let firstXIndex = 0
|
||||
|
||||
for (let xi = 0; xi < seriesX.length; xi++) {
|
||||
if (seriesX[xi] >= e.xaxis.min) {
|
||||
firstXIndex = xi
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let initialMin = w.globals.minYArr[yi]
|
||||
let initialMax = w.globals.maxYArr[yi]
|
||||
let min, max
|
||||
|
||||
let stackedSer = w.globals.stackedSeriesTotals
|
||||
|
||||
w.globals.series.forEach((serie, sI) => {
|
||||
let firstValue = serie[firstXIndex]
|
||||
|
||||
if (isStacked) {
|
||||
firstValue = stackedSer[firstXIndex]
|
||||
min = max = firstValue
|
||||
|
||||
stackedSer.forEach((y, yI) => {
|
||||
if (seriesX[yI] <= e.xaxis.max && seriesX[yI] >= e.xaxis.min) {
|
||||
if (y > max && y !== null) max = y
|
||||
if (serie[yI] < min && serie[yI] !== null) min = serie[yI]
|
||||
}
|
||||
})
|
||||
} else {
|
||||
min = max = firstValue
|
||||
|
||||
serie.forEach((y, yI) => {
|
||||
if (seriesX[yI] <= e.xaxis.max && seriesX[yI] >= e.xaxis.min) {
|
||||
let valMin = y
|
||||
let valMax = y
|
||||
w.globals.series.forEach((wS, wSI) => {
|
||||
if (y !== null) {
|
||||
valMin = Math.min(wS[yI], valMin)
|
||||
valMax = Math.max(wS[yI], valMax)
|
||||
}
|
||||
})
|
||||
if (valMax > max && valMax !== null) max = valMax
|
||||
if (valMin < min && valMin !== null) min = valMin
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (min === undefined && max === undefined) {
|
||||
min = initialMin
|
||||
max = initialMax
|
||||
}
|
||||
min *= min < 0 ? 1.1 : 0.9
|
||||
max *= max < 0 ? 0.9 : 1.1
|
||||
|
||||
if (min === 0 && max === 0) {
|
||||
min = -1
|
||||
max = 1
|
||||
}
|
||||
|
||||
if (max < 0 && max < initialMax) {
|
||||
max = initialMax
|
||||
}
|
||||
if (min < 0 && min > initialMin) {
|
||||
min = initialMin
|
||||
}
|
||||
|
||||
if (yaxis.length > 1) {
|
||||
yaxis[sI].min = yaxe.min === undefined ? min : yaxe.min
|
||||
yaxis[sI].max = yaxe.max === undefined ? max : yaxe.max
|
||||
} else {
|
||||
yaxis[0].min = yaxe.min === undefined ? min : yaxe.min
|
||||
yaxis[0].max = yaxe.max === undefined ? max : yaxe.max
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return yaxis
|
||||
}
|
||||
}
|
||||
+479
@@ -0,0 +1,479 @@
|
||||
import Graphics from './Graphics'
|
||||
import Utils from '../utils/Utils'
|
||||
|
||||
/**
|
||||
* ApexCharts Series Class for interaction with the Series of the chart.
|
||||
*
|
||||
* @module Series
|
||||
**/
|
||||
|
||||
export default class Series {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
|
||||
this.legendInactiveClass = 'legend-mouseover-inactive'
|
||||
}
|
||||
|
||||
getAllSeriesEls() {
|
||||
return this.w.globals.dom.baseEl.getElementsByClassName(`apexcharts-series`)
|
||||
}
|
||||
|
||||
getSeriesByName(seriesName) {
|
||||
return this.w.globals.dom.baseEl.querySelector(
|
||||
`.apexcharts-inner .apexcharts-series[seriesName='${Utils.escapeString(
|
||||
seriesName
|
||||
)}']`
|
||||
)
|
||||
}
|
||||
|
||||
isSeriesHidden(seriesName) {
|
||||
const targetElement = this.getSeriesByName(seriesName)
|
||||
let realIndex = parseInt(targetElement.getAttribute('data:realIndex'), 10)
|
||||
let isHidden = targetElement.classList.contains(
|
||||
'apexcharts-series-collapsed'
|
||||
)
|
||||
|
||||
return { isHidden, realIndex }
|
||||
}
|
||||
|
||||
addCollapsedClassToSeries(elSeries, index) {
|
||||
const w = this.w
|
||||
function iterateOnAllCollapsedSeries(series) {
|
||||
for (let cs = 0; cs < series.length; cs++) {
|
||||
if (series[cs].index === index) {
|
||||
elSeries.node.classList.add('apexcharts-series-collapsed')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
iterateOnAllCollapsedSeries(w.globals.collapsedSeries)
|
||||
iterateOnAllCollapsedSeries(w.globals.ancillaryCollapsedSeries)
|
||||
}
|
||||
|
||||
toggleSeries(seriesName) {
|
||||
let isSeriesHidden = this.isSeriesHidden(seriesName)
|
||||
|
||||
this.ctx.legend.legendHelpers.toggleDataSeries(
|
||||
isSeriesHidden.realIndex,
|
||||
isSeriesHidden.isHidden
|
||||
)
|
||||
|
||||
return isSeriesHidden.isHidden
|
||||
}
|
||||
|
||||
showSeries(seriesName) {
|
||||
let isSeriesHidden = this.isSeriesHidden(seriesName)
|
||||
|
||||
if (isSeriesHidden.isHidden) {
|
||||
this.ctx.legend.legendHelpers.toggleDataSeries(
|
||||
isSeriesHidden.realIndex,
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
hideSeries(seriesName) {
|
||||
let isSeriesHidden = this.isSeriesHidden(seriesName)
|
||||
|
||||
if (!isSeriesHidden.isHidden) {
|
||||
this.ctx.legend.legendHelpers.toggleDataSeries(
|
||||
isSeriesHidden.realIndex,
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
resetSeries(
|
||||
shouldUpdateChart = true,
|
||||
shouldResetZoom = true,
|
||||
shouldResetCollapsed = true
|
||||
) {
|
||||
const w = this.w
|
||||
|
||||
let series = Utils.clone(w.globals.initialSeries)
|
||||
|
||||
w.globals.previousPaths = []
|
||||
|
||||
if (shouldResetCollapsed) {
|
||||
w.globals.collapsedSeries = []
|
||||
w.globals.ancillaryCollapsedSeries = []
|
||||
w.globals.collapsedSeriesIndices = []
|
||||
w.globals.ancillaryCollapsedSeriesIndices = []
|
||||
} else {
|
||||
series = this.emptyCollapsedSeries(series)
|
||||
}
|
||||
|
||||
w.config.series = series
|
||||
|
||||
if (shouldUpdateChart) {
|
||||
if (shouldResetZoom) {
|
||||
w.globals.zoomed = false
|
||||
this.ctx.updateHelpers.revertDefaultAxisMinMax()
|
||||
}
|
||||
this.ctx.updateHelpers._updateSeries(
|
||||
series,
|
||||
w.config.chart.animations.dynamicAnimation.enabled
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
emptyCollapsedSeries(series) {
|
||||
const w = this.w
|
||||
for (let i = 0; i < series.length; i++) {
|
||||
if (w.globals.collapsedSeriesIndices.indexOf(i) > -1) {
|
||||
series[i].data = []
|
||||
}
|
||||
}
|
||||
return series
|
||||
}
|
||||
toggleSeriesOnHover(e, targetElement) {
|
||||
const w = this.w
|
||||
|
||||
if (!targetElement) targetElement = e.target
|
||||
|
||||
let allSeriesEls = w.globals.dom.baseEl.querySelectorAll(
|
||||
`.apexcharts-series, .apexcharts-datalabels`
|
||||
)
|
||||
|
||||
if (e.type === 'mousemove') {
|
||||
let seriesCnt = parseInt(targetElement.getAttribute('rel'), 10) - 1
|
||||
|
||||
let seriesEl = null
|
||||
let dataLabelEl = null
|
||||
if (w.globals.axisCharts || w.config.chart.type === 'radialBar') {
|
||||
if (w.globals.axisCharts) {
|
||||
seriesEl = w.globals.dom.baseEl.querySelector(
|
||||
`.apexcharts-series[data\\:realIndex='${seriesCnt}']`
|
||||
)
|
||||
dataLabelEl = w.globals.dom.baseEl.querySelector(
|
||||
`.apexcharts-datalabels[data\\:realIndex='${seriesCnt}']`
|
||||
)
|
||||
} else {
|
||||
seriesEl = w.globals.dom.baseEl.querySelector(
|
||||
`.apexcharts-series[rel='${seriesCnt + 1}']`
|
||||
)
|
||||
}
|
||||
} else {
|
||||
seriesEl = w.globals.dom.baseEl.querySelector(
|
||||
`.apexcharts-series[rel='${seriesCnt + 1}'] path`
|
||||
)
|
||||
}
|
||||
|
||||
for (let se = 0; se < allSeriesEls.length; se++) {
|
||||
allSeriesEls[se].classList.add(this.legendInactiveClass)
|
||||
}
|
||||
|
||||
if (seriesEl !== null) {
|
||||
if (!w.globals.axisCharts) {
|
||||
seriesEl.parentNode.classList.remove(this.legendInactiveClass)
|
||||
}
|
||||
seriesEl.classList.remove(this.legendInactiveClass)
|
||||
|
||||
if (dataLabelEl !== null) {
|
||||
dataLabelEl.classList.remove(this.legendInactiveClass)
|
||||
}
|
||||
}
|
||||
} else if (e.type === 'mouseout') {
|
||||
for (let se = 0; se < allSeriesEls.length; se++) {
|
||||
allSeriesEls[se].classList.remove(this.legendInactiveClass)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
highlightRangeInSeries(e, targetElement) {
|
||||
const w = this.w
|
||||
const allHeatMapElements = w.globals.dom.baseEl.getElementsByClassName(
|
||||
'apexcharts-heatmap-rect'
|
||||
)
|
||||
|
||||
const activeInactive = (action) => {
|
||||
for (let i = 0; i < allHeatMapElements.length; i++) {
|
||||
allHeatMapElements[i].classList[action](this.legendInactiveClass)
|
||||
}
|
||||
}
|
||||
|
||||
const removeInactiveClassFromHoveredRange = (range) => {
|
||||
for (let i = 0; i < allHeatMapElements.length; i++) {
|
||||
const val = parseInt(allHeatMapElements[i].getAttribute('val'), 10)
|
||||
if (val >= range.from && val <= range.to) {
|
||||
allHeatMapElements[i].classList.remove(this.legendInactiveClass)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (e.type === 'mousemove') {
|
||||
let seriesCnt = parseInt(targetElement.getAttribute('rel'), 10) - 1
|
||||
activeInactive('add')
|
||||
|
||||
const range = w.config.plotOptions.heatmap.colorScale.ranges[seriesCnt]
|
||||
|
||||
removeInactiveClassFromHoveredRange(range)
|
||||
} else if (e.type === 'mouseout') {
|
||||
activeInactive('remove')
|
||||
}
|
||||
}
|
||||
|
||||
getActiveConfigSeriesIndex(order = 'asc', chartTypes = []) {
|
||||
const w = this.w
|
||||
let activeIndex = 0
|
||||
|
||||
if (w.config.series.length > 1) {
|
||||
// active series flag is required to know if user has not deactivated via legend click
|
||||
let activeSeriesIndex = w.config.series.map((s, index) => {
|
||||
const checkChartType = () => {
|
||||
if (w.globals.comboCharts) {
|
||||
return (
|
||||
chartTypes.length === 0 ||
|
||||
(chartTypes.length &&
|
||||
chartTypes.indexOf(w.config.series[index].type) > -1)
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const hasData =
|
||||
s.data &&
|
||||
s.data.length > 0 &&
|
||||
w.globals.collapsedSeriesIndices.indexOf(index) === -1
|
||||
|
||||
return hasData && checkChartType() ? index : -1
|
||||
})
|
||||
for (
|
||||
let a = order === 'asc' ? 0 : activeSeriesIndex.length - 1;
|
||||
order === 'asc' ? a < activeSeriesIndex.length : a >= 0;
|
||||
order === 'asc' ? a++ : a--
|
||||
) {
|
||||
if (activeSeriesIndex[a] !== -1) {
|
||||
activeIndex = activeSeriesIndex[a]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return activeIndex
|
||||
}
|
||||
|
||||
getBarSeriesIndices() {
|
||||
const w = this.w
|
||||
if (w.globals.comboCharts) {
|
||||
return this.w.config.series
|
||||
.map((s, i) => {
|
||||
return s.type === 'bar' || s.type === 'column' ? i : -1
|
||||
})
|
||||
.filter((i) => {
|
||||
return i !== -1
|
||||
})
|
||||
}
|
||||
return this.w.config.series.map((s, i) => {
|
||||
return i
|
||||
})
|
||||
}
|
||||
|
||||
getPreviousPaths() {
|
||||
let w = this.w
|
||||
|
||||
w.globals.previousPaths = []
|
||||
|
||||
function pushPaths(seriesEls, i, type) {
|
||||
let paths = seriesEls[i].childNodes
|
||||
let dArr = {
|
||||
type,
|
||||
paths: [],
|
||||
realIndex: seriesEls[i].getAttribute('data:realIndex')
|
||||
}
|
||||
|
||||
for (let j = 0; j < paths.length; j++) {
|
||||
if (paths[j].hasAttribute('pathTo')) {
|
||||
let d = paths[j].getAttribute('pathTo')
|
||||
dArr.paths.push({
|
||||
d
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
w.globals.previousPaths.push(dArr)
|
||||
}
|
||||
|
||||
const getPaths = (chartType) => {
|
||||
return w.globals.dom.baseEl.querySelectorAll(
|
||||
`.apexcharts-${chartType}-series .apexcharts-series`
|
||||
)
|
||||
}
|
||||
|
||||
const chartTypes = [
|
||||
'line',
|
||||
'area',
|
||||
'bar',
|
||||
'rangebar',
|
||||
'rangeArea',
|
||||
'candlestick',
|
||||
'radar'
|
||||
]
|
||||
chartTypes.forEach((type) => {
|
||||
const paths = getPaths(type)
|
||||
for (let p = 0; p < paths.length; p++) {
|
||||
pushPaths(paths, p, type)
|
||||
}
|
||||
})
|
||||
|
||||
this.handlePrevBubbleScatterPaths('bubble')
|
||||
this.handlePrevBubbleScatterPaths('scatter')
|
||||
|
||||
let heatTreeSeries = w.globals.dom.baseEl.querySelectorAll(
|
||||
`.apexcharts-${w.config.chart.type} .apexcharts-series`
|
||||
)
|
||||
|
||||
if (heatTreeSeries.length > 0) {
|
||||
for (let h = 0; h < heatTreeSeries.length; h++) {
|
||||
let seriesEls = w.globals.dom.baseEl.querySelectorAll(
|
||||
`.apexcharts-${w.config.chart.type} .apexcharts-series[data\\:realIndex='${h}'] rect`
|
||||
)
|
||||
|
||||
let dArr = []
|
||||
|
||||
for (let i = 0; i < seriesEls.length; i++) {
|
||||
const getAttr = (x) => {
|
||||
return seriesEls[i].getAttribute(x)
|
||||
}
|
||||
const rect = {
|
||||
x: parseFloat(getAttr('x')),
|
||||
y: parseFloat(getAttr('y')),
|
||||
width: parseFloat(getAttr('width')),
|
||||
height: parseFloat(getAttr('height'))
|
||||
}
|
||||
dArr.push({
|
||||
rect,
|
||||
color: seriesEls[i].getAttribute('color')
|
||||
})
|
||||
}
|
||||
w.globals.previousPaths.push(dArr)
|
||||
}
|
||||
}
|
||||
|
||||
if (!w.globals.axisCharts) {
|
||||
// for non-axis charts (i.e., circular charts, pathFrom is not usable. We need whole series)
|
||||
w.globals.previousPaths = w.globals.series
|
||||
}
|
||||
}
|
||||
|
||||
handlePrevBubbleScatterPaths(type) {
|
||||
const w = this.w
|
||||
let paths = w.globals.dom.baseEl.querySelectorAll(
|
||||
`.apexcharts-${type}-series .apexcharts-series`
|
||||
)
|
||||
if (paths.length > 0) {
|
||||
for (let s = 0; s < paths.length; s++) {
|
||||
let seriesEls = w.globals.dom.baseEl.querySelectorAll(
|
||||
`.apexcharts-${type}-series .apexcharts-series[data\\:realIndex='${s}'] circle`
|
||||
)
|
||||
let dArr = []
|
||||
|
||||
for (let i = 0; i < seriesEls.length; i++) {
|
||||
dArr.push({
|
||||
x: seriesEls[i].getAttribute('cx'),
|
||||
y: seriesEls[i].getAttribute('cy'),
|
||||
r: seriesEls[i].getAttribute('r')
|
||||
})
|
||||
}
|
||||
w.globals.previousPaths.push(dArr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clearPreviousPaths() {
|
||||
const w = this.w
|
||||
w.globals.previousPaths = []
|
||||
w.globals.allSeriesCollapsed = false
|
||||
}
|
||||
|
||||
handleNoData() {
|
||||
const w = this.w
|
||||
const me = this
|
||||
|
||||
const noDataOpts = w.config.noData
|
||||
const graphics = new Graphics(me.ctx)
|
||||
|
||||
let x = w.globals.svgWidth / 2
|
||||
let y = w.globals.svgHeight / 2
|
||||
let textAnchor = 'middle'
|
||||
|
||||
w.globals.noData = true
|
||||
w.globals.animationEnded = true
|
||||
|
||||
if (noDataOpts.align === 'left') {
|
||||
x = 10
|
||||
textAnchor = 'start'
|
||||
} else if (noDataOpts.align === 'right') {
|
||||
x = w.globals.svgWidth - 10
|
||||
textAnchor = 'end'
|
||||
}
|
||||
|
||||
if (noDataOpts.verticalAlign === 'top') {
|
||||
y = 50
|
||||
} else if (noDataOpts.verticalAlign === 'bottom') {
|
||||
y = w.globals.svgHeight - 50
|
||||
}
|
||||
|
||||
x = x + noDataOpts.offsetX
|
||||
y = y + parseInt(noDataOpts.style.fontSize, 10) + 2 + noDataOpts.offsetY
|
||||
|
||||
if (noDataOpts.text !== undefined && noDataOpts.text !== '') {
|
||||
let titleText = graphics.drawText({
|
||||
x,
|
||||
y,
|
||||
text: noDataOpts.text,
|
||||
textAnchor,
|
||||
fontSize: noDataOpts.style.fontSize,
|
||||
fontFamily: noDataOpts.style.fontFamily,
|
||||
foreColor: noDataOpts.style.color,
|
||||
opacity: 1,
|
||||
class: 'apexcharts-text-nodata'
|
||||
})
|
||||
|
||||
w.globals.dom.Paper.add(titleText)
|
||||
}
|
||||
}
|
||||
|
||||
// When user clicks on legends, the collapsed series is filled with [0,0,0,...,0]
|
||||
// This is because we don't want to alter the series' length as it is used at many places
|
||||
setNullSeriesToZeroValues(series) {
|
||||
let w = this.w
|
||||
for (let sl = 0; sl < series.length; sl++) {
|
||||
if (series[sl].length === 0) {
|
||||
for (let j = 0; j < series[w.globals.maxValsInArrayIndex].length; j++) {
|
||||
series[sl].push(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
return series
|
||||
}
|
||||
|
||||
hasAllSeriesEqualX() {
|
||||
let equalLen = true
|
||||
const w = this.w
|
||||
|
||||
const filteredSerX = this.filteredSeriesX()
|
||||
|
||||
for (let i = 0; i < filteredSerX.length - 1; i++) {
|
||||
if (filteredSerX[i][0] !== filteredSerX[i + 1][0]) {
|
||||
equalLen = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
w.globals.allSeriesHasEqualX = equalLen
|
||||
|
||||
return equalLen
|
||||
}
|
||||
|
||||
filteredSeriesX() {
|
||||
const w = this.w
|
||||
|
||||
const filteredSeriesX = w.globals.seriesX.map((ser) =>
|
||||
ser.length > 0 ? ser : []
|
||||
)
|
||||
|
||||
return filteredSeriesX
|
||||
}
|
||||
}
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
import Utils from '../utils/Utils'
|
||||
|
||||
/**
|
||||
* ApexCharts Theme Class for setting the colors and palettes.
|
||||
*
|
||||
* @module Theme
|
||||
**/
|
||||
|
||||
export default class Theme {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.colors = []
|
||||
this.w = ctx.w
|
||||
const w = this.w
|
||||
|
||||
this.isColorFn = false
|
||||
this.isHeatmapDistributed =
|
||||
(w.config.chart.type === 'treemap' &&
|
||||
w.config.plotOptions.treemap.distributed) ||
|
||||
(w.config.chart.type === 'heatmap' &&
|
||||
w.config.plotOptions.heatmap.distributed)
|
||||
this.isBarDistributed =
|
||||
w.config.plotOptions.bar.distributed &&
|
||||
(w.config.chart.type === 'bar' || w.config.chart.type === 'rangeBar')
|
||||
}
|
||||
|
||||
init() {
|
||||
this.setDefaultColors()
|
||||
}
|
||||
|
||||
setDefaultColors() {
|
||||
let w = this.w
|
||||
let utils = new Utils()
|
||||
|
||||
w.globals.dom.elWrap.classList.add(
|
||||
`apexcharts-theme-${w.config.theme.mode}`
|
||||
)
|
||||
|
||||
if (w.config.colors === undefined || w.config.colors?.length === 0) {
|
||||
w.globals.colors = this.predefined()
|
||||
} else {
|
||||
w.globals.colors = w.config.colors
|
||||
|
||||
// if user provided a function in colors, we need to eval here
|
||||
if (
|
||||
Array.isArray(w.config.colors) &&
|
||||
w.config.colors.length > 0 &&
|
||||
typeof w.config.colors[0] === 'function'
|
||||
) {
|
||||
w.globals.colors = w.config.series.map((s, i) => {
|
||||
let c = w.config.colors[i]
|
||||
if (!c) c = w.config.colors[0]
|
||||
if (typeof c === 'function') {
|
||||
this.isColorFn = true
|
||||
return c({
|
||||
value: w.globals.axisCharts
|
||||
? w.globals.series[i][0]
|
||||
? w.globals.series[i][0]
|
||||
: 0
|
||||
: w.globals.series[i],
|
||||
seriesIndex: i,
|
||||
dataPointIndex: i,
|
||||
w,
|
||||
})
|
||||
}
|
||||
return c
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// user defined colors in series array
|
||||
w.globals.seriesColors.map((c, i) => {
|
||||
if (c) {
|
||||
w.globals.colors[i] = c
|
||||
}
|
||||
})
|
||||
|
||||
if (w.config.theme.monochrome.enabled) {
|
||||
let monoArr = []
|
||||
let glsCnt = w.globals.series.length
|
||||
if (this.isBarDistributed || this.isHeatmapDistributed) {
|
||||
glsCnt = w.globals.series[0].length * w.globals.series.length
|
||||
}
|
||||
|
||||
let mainColor = w.config.theme.monochrome.color
|
||||
let part = 1 / (glsCnt / w.config.theme.monochrome.shadeIntensity)
|
||||
let shade = w.config.theme.monochrome.shadeTo
|
||||
let percent = 0
|
||||
|
||||
for (let gsl = 0; gsl < glsCnt; gsl++) {
|
||||
let newColor
|
||||
|
||||
if (shade === 'dark') {
|
||||
newColor = utils.shadeColor(percent * -1, mainColor)
|
||||
percent = percent + part
|
||||
} else {
|
||||
newColor = utils.shadeColor(percent, mainColor)
|
||||
percent = percent + part
|
||||
}
|
||||
|
||||
monoArr.push(newColor)
|
||||
}
|
||||
w.globals.colors = monoArr.slice()
|
||||
}
|
||||
const defaultColors = w.globals.colors.slice()
|
||||
|
||||
// if user specified fewer colors than no. of series, push the same colors again
|
||||
this.pushExtraColors(w.globals.colors)
|
||||
|
||||
const colorTypes = ['fill', 'stroke']
|
||||
colorTypes.forEach((c) => {
|
||||
if (w.config[c].colors === undefined) {
|
||||
w.globals[c].colors = this.isColorFn ? w.config.colors : defaultColors
|
||||
} else {
|
||||
w.globals[c].colors = w.config[c].colors.slice()
|
||||
}
|
||||
this.pushExtraColors(w.globals[c].colors)
|
||||
})
|
||||
|
||||
if (w.config.dataLabels.style.colors === undefined) {
|
||||
w.globals.dataLabels.style.colors = defaultColors
|
||||
} else {
|
||||
w.globals.dataLabels.style.colors =
|
||||
w.config.dataLabels.style.colors.slice()
|
||||
}
|
||||
this.pushExtraColors(w.globals.dataLabels.style.colors, 50)
|
||||
|
||||
if (w.config.plotOptions.radar.polygons.fill.colors === undefined) {
|
||||
w.globals.radarPolygons.fill.colors = [
|
||||
w.config.theme.mode === 'dark' ? '#424242' : 'none',
|
||||
]
|
||||
} else {
|
||||
w.globals.radarPolygons.fill.colors =
|
||||
w.config.plotOptions.radar.polygons.fill.colors.slice()
|
||||
}
|
||||
this.pushExtraColors(w.globals.radarPolygons.fill.colors, 20)
|
||||
|
||||
// The point colors
|
||||
if (w.config.markers.colors === undefined) {
|
||||
w.globals.markers.colors = defaultColors
|
||||
} else {
|
||||
w.globals.markers.colors = w.config.markers.colors.slice()
|
||||
}
|
||||
this.pushExtraColors(w.globals.markers.colors)
|
||||
}
|
||||
|
||||
// When the number of colors provided is less than the number of series, this method
|
||||
// will push same colors to the list
|
||||
// params:
|
||||
// distributed is only valid for distributed column/bar charts
|
||||
pushExtraColors(colorSeries, length, distributed = null) {
|
||||
let w = this.w
|
||||
|
||||
let len = length || w.globals.series.length
|
||||
|
||||
if (distributed === null) {
|
||||
distributed =
|
||||
this.isBarDistributed ||
|
||||
this.isHeatmapDistributed ||
|
||||
(w.config.chart.type === 'heatmap' &&
|
||||
w.config.plotOptions.heatmap.colorScale.inverse)
|
||||
}
|
||||
|
||||
if (distributed && w.globals.series.length) {
|
||||
len =
|
||||
w.globals.series[w.globals.maxValsInArrayIndex].length *
|
||||
w.globals.series.length
|
||||
}
|
||||
|
||||
if (colorSeries.length < len) {
|
||||
let diff = len - colorSeries.length
|
||||
for (let i = 0; i < diff; i++) {
|
||||
colorSeries.push(colorSeries[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateThemeOptions(options) {
|
||||
options.chart = options.chart || {}
|
||||
options.tooltip = options.tooltip || {}
|
||||
const mode = options.theme.mode || 'light'
|
||||
const palette = options.theme.palette
|
||||
? options.theme.palette
|
||||
: mode === 'dark'
|
||||
? 'palette4'
|
||||
: 'palette1'
|
||||
const foreColor = options.chart.foreColor
|
||||
? options.chart.foreColor
|
||||
: mode === 'dark'
|
||||
? '#f6f7f8'
|
||||
: '#373d3f'
|
||||
|
||||
options.tooltip.theme = mode
|
||||
options.chart.foreColor = foreColor
|
||||
options.theme.palette = palette
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
predefined() {
|
||||
let palette = this.w.config.theme.palette
|
||||
|
||||
// D6E3F8, FCEFEF, DCE0D9, A5978B, EDDDD4, D6E3F8, FEF5EF
|
||||
switch (palette) {
|
||||
case 'palette1':
|
||||
this.colors = ['#008FFB', '#00E396', '#FEB019', '#FF4560', '#775DD0']
|
||||
break
|
||||
case 'palette2':
|
||||
this.colors = ['#3f51b5', '#03a9f4', '#4caf50', '#f9ce1d', '#FF9800']
|
||||
break
|
||||
case 'palette3':
|
||||
this.colors = ['#33b2df', '#546E7A', '#d4526e', '#13d8aa', '#A5978B']
|
||||
break
|
||||
case 'palette4':
|
||||
this.colors = ['#4ecdc4', '#c7f464', '#81D4FA', '#fd6a6a', '#546E7A']
|
||||
break
|
||||
case 'palette5':
|
||||
this.colors = ['#2b908f', '#f9a3a4', '#90ee7e', '#fa4443', '#69d2e7']
|
||||
break
|
||||
case 'palette6':
|
||||
this.colors = ['#449DD1', '#F86624', '#EA3546', '#662E9B', '#C5D86D']
|
||||
break
|
||||
case 'palette7':
|
||||
this.colors = ['#D7263D', '#1B998B', '#2E294E', '#F46036', '#E2C044']
|
||||
break
|
||||
case 'palette8':
|
||||
this.colors = ['#662E9B', '#F86624', '#F9C80E', '#EA3546', '#43BCCD']
|
||||
break
|
||||
case 'palette9':
|
||||
this.colors = ['#5C4742', '#A5978B', '#8D5B4C', '#5A2A27', '#C4BBAF']
|
||||
break
|
||||
case 'palette10':
|
||||
this.colors = ['#A300D6', '#7D02EB', '#5653FE', '#2983FF', '#00B1F2']
|
||||
break
|
||||
default:
|
||||
this.colors = ['#008FFB', '#00E396', '#FEB019', '#FF4560', '#775DD0']
|
||||
break
|
||||
}
|
||||
return this.colors
|
||||
}
|
||||
}
|
||||
+927
@@ -0,0 +1,927 @@
|
||||
import DateTime from '../utils/DateTime'
|
||||
import Dimensions from './dimensions/Dimensions'
|
||||
import Graphics from './Graphics'
|
||||
import Utils from '../utils/Utils'
|
||||
|
||||
const MINUTES_IN_DAY = 24 * 60
|
||||
const SECONDS_IN_DAY = MINUTES_IN_DAY * 60
|
||||
const MIN_ZOOM_DAYS = 10 / SECONDS_IN_DAY
|
||||
|
||||
/**
|
||||
* ApexCharts TimeScale Class for generating time ticks for x-axis.
|
||||
*
|
||||
* @module TimeScale
|
||||
**/
|
||||
|
||||
class TimeScale {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
this.timeScaleArray = []
|
||||
this.utc = this.w.config.xaxis.labels.datetimeUTC
|
||||
}
|
||||
|
||||
calculateTimeScaleTicks(minX, maxX) {
|
||||
let w = this.w
|
||||
|
||||
// null check when no series to show
|
||||
if (w.globals.allSeriesCollapsed) {
|
||||
w.globals.labels = []
|
||||
w.globals.timescaleLabels = []
|
||||
return []
|
||||
}
|
||||
|
||||
let dt = new DateTime(this.ctx)
|
||||
|
||||
const daysDiff = (maxX - minX) / (1000 * SECONDS_IN_DAY)
|
||||
this.determineInterval(daysDiff)
|
||||
|
||||
w.globals.disableZoomIn = false
|
||||
w.globals.disableZoomOut = false
|
||||
|
||||
if (daysDiff < MIN_ZOOM_DAYS) {
|
||||
w.globals.disableZoomIn = true
|
||||
} else if (daysDiff > 50000) {
|
||||
w.globals.disableZoomOut = true
|
||||
}
|
||||
|
||||
const timeIntervals = dt.getTimeUnitsfromTimestamp(minX, maxX, this.utc)
|
||||
|
||||
const daysWidthOnXAxis = w.globals.gridWidth / daysDiff
|
||||
const hoursWidthOnXAxis = daysWidthOnXAxis / 24
|
||||
const minutesWidthOnXAxis = hoursWidthOnXAxis / 60
|
||||
const secondsWidthOnXAxis = minutesWidthOnXAxis / 60
|
||||
|
||||
let numberOfHours = Math.floor(daysDiff * 24)
|
||||
let numberOfMinutes = Math.floor(daysDiff * MINUTES_IN_DAY)
|
||||
let numberOfSeconds = Math.floor(daysDiff * SECONDS_IN_DAY)
|
||||
let numberOfDays = Math.floor(daysDiff)
|
||||
let numberOfMonths = Math.floor(daysDiff / 30)
|
||||
let numberOfYears = Math.floor(daysDiff / 365)
|
||||
|
||||
const firstVal = {
|
||||
minMillisecond: timeIntervals.minMillisecond,
|
||||
minSecond: timeIntervals.minSecond,
|
||||
minMinute: timeIntervals.minMinute,
|
||||
minHour: timeIntervals.minHour,
|
||||
minDate: timeIntervals.minDate,
|
||||
minMonth: timeIntervals.minMonth,
|
||||
minYear: timeIntervals.minYear,
|
||||
}
|
||||
|
||||
let currentMillisecond = firstVal.minMillisecond
|
||||
let currentSecond = firstVal.minSecond
|
||||
let currentMinute = firstVal.minMinute
|
||||
let currentHour = firstVal.minHour
|
||||
let currentMonthDate = firstVal.minDate
|
||||
let currentDate = firstVal.minDate
|
||||
let currentMonth = firstVal.minMonth
|
||||
let currentYear = firstVal.minYear
|
||||
|
||||
const params = {
|
||||
firstVal,
|
||||
currentMillisecond,
|
||||
currentSecond,
|
||||
currentMinute,
|
||||
currentHour,
|
||||
currentMonthDate,
|
||||
currentDate,
|
||||
currentMonth,
|
||||
currentYear,
|
||||
daysWidthOnXAxis,
|
||||
hoursWidthOnXAxis,
|
||||
minutesWidthOnXAxis,
|
||||
secondsWidthOnXAxis,
|
||||
numberOfSeconds,
|
||||
numberOfMinutes,
|
||||
numberOfHours,
|
||||
numberOfDays,
|
||||
numberOfMonths,
|
||||
numberOfYears,
|
||||
}
|
||||
|
||||
switch (this.tickInterval) {
|
||||
case 'years': {
|
||||
this.generateYearScale(params)
|
||||
break
|
||||
}
|
||||
case 'months':
|
||||
case 'half_year': {
|
||||
this.generateMonthScale(params)
|
||||
break
|
||||
}
|
||||
case 'months_days':
|
||||
case 'months_fortnight':
|
||||
case 'days':
|
||||
case 'week_days': {
|
||||
this.generateDayScale(params)
|
||||
break
|
||||
}
|
||||
case 'hours': {
|
||||
this.generateHourScale(params)
|
||||
break
|
||||
}
|
||||
case 'minutes_fives':
|
||||
case 'minutes':
|
||||
this.generateMinuteScale(params)
|
||||
break
|
||||
case 'seconds_tens':
|
||||
case 'seconds_fives':
|
||||
case 'seconds':
|
||||
this.generateSecondScale(params)
|
||||
break
|
||||
}
|
||||
|
||||
// first, we will adjust the month values index
|
||||
// as in the upper function, it is starting from 0
|
||||
// we will start them from 1
|
||||
const adjustedMonthInTimeScaleArray = this.timeScaleArray.map((ts) => {
|
||||
let defaultReturn = {
|
||||
position: ts.position,
|
||||
unit: ts.unit,
|
||||
year: ts.year,
|
||||
day: ts.day ? ts.day : 1,
|
||||
hour: ts.hour ? ts.hour : 0,
|
||||
month: ts.month + 1,
|
||||
}
|
||||
if (ts.unit === 'month') {
|
||||
return {
|
||||
...defaultReturn,
|
||||
day: 1,
|
||||
value: ts.value + 1,
|
||||
}
|
||||
} else if (ts.unit === 'day' || ts.unit === 'hour') {
|
||||
return {
|
||||
...defaultReturn,
|
||||
value: ts.value,
|
||||
}
|
||||
} else if (ts.unit === 'minute') {
|
||||
return {
|
||||
...defaultReturn,
|
||||
value: ts.value,
|
||||
minute: ts.value,
|
||||
}
|
||||
} else if (ts.unit === 'second') {
|
||||
return {
|
||||
...defaultReturn,
|
||||
value: ts.value,
|
||||
minute: ts.minute,
|
||||
second: ts.second,
|
||||
}
|
||||
}
|
||||
|
||||
return ts
|
||||
})
|
||||
|
||||
const filteredTimeScale = adjustedMonthInTimeScaleArray.filter((ts) => {
|
||||
let modulo = 1
|
||||
let ticks = Math.ceil(w.globals.gridWidth / 120)
|
||||
let value = ts.value
|
||||
if (w.config.xaxis.tickAmount !== undefined) {
|
||||
ticks = w.config.xaxis.tickAmount
|
||||
}
|
||||
if (adjustedMonthInTimeScaleArray.length > ticks) {
|
||||
modulo = Math.floor(adjustedMonthInTimeScaleArray.length / ticks)
|
||||
}
|
||||
|
||||
let shouldNotSkipUnit = false // there is a big change in unit i.e days to months
|
||||
let shouldNotPrint = false // should skip these values
|
||||
|
||||
switch (this.tickInterval) {
|
||||
case 'years':
|
||||
// make years label denser
|
||||
if (ts.unit === 'year') {
|
||||
shouldNotSkipUnit = true
|
||||
}
|
||||
break
|
||||
case 'half_year':
|
||||
modulo = 7
|
||||
if (ts.unit === 'year') {
|
||||
shouldNotSkipUnit = true
|
||||
}
|
||||
break
|
||||
case 'months':
|
||||
modulo = 1
|
||||
if (ts.unit === 'year') {
|
||||
shouldNotSkipUnit = true
|
||||
}
|
||||
break
|
||||
case 'months_fortnight':
|
||||
modulo = 15
|
||||
if (ts.unit === 'year' || ts.unit === 'month') {
|
||||
shouldNotSkipUnit = true
|
||||
}
|
||||
if (value === 30) {
|
||||
shouldNotPrint = true
|
||||
}
|
||||
break
|
||||
case 'months_days':
|
||||
modulo = 10
|
||||
if (ts.unit === 'month') {
|
||||
shouldNotSkipUnit = true
|
||||
}
|
||||
if (value === 30) {
|
||||
shouldNotPrint = true
|
||||
}
|
||||
break
|
||||
case 'week_days':
|
||||
modulo = 8
|
||||
if (ts.unit === 'month') {
|
||||
shouldNotSkipUnit = true
|
||||
}
|
||||
break
|
||||
case 'days':
|
||||
modulo = 1
|
||||
if (ts.unit === 'month') {
|
||||
shouldNotSkipUnit = true
|
||||
}
|
||||
break
|
||||
case 'hours':
|
||||
if (ts.unit === 'day') {
|
||||
shouldNotSkipUnit = true
|
||||
}
|
||||
break
|
||||
case 'minutes_fives':
|
||||
if (value % 5 !== 0) {
|
||||
shouldNotPrint = true
|
||||
}
|
||||
break
|
||||
case 'seconds_tens':
|
||||
if (value % 10 !== 0) {
|
||||
shouldNotPrint = true
|
||||
}
|
||||
break
|
||||
case 'seconds_fives':
|
||||
if (value % 5 !== 0) {
|
||||
shouldNotPrint = true
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if (
|
||||
this.tickInterval === 'hours' ||
|
||||
this.tickInterval === 'minutes_fives' ||
|
||||
this.tickInterval === 'seconds_tens' ||
|
||||
this.tickInterval === 'seconds_fives'
|
||||
) {
|
||||
if (!shouldNotPrint) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if ((value % modulo === 0 || shouldNotSkipUnit) && !shouldNotPrint) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return filteredTimeScale
|
||||
}
|
||||
|
||||
recalcDimensionsBasedOnFormat(filteredTimeScale, inverted) {
|
||||
const w = this.w
|
||||
const reformattedTimescaleArray = this.formatDates(filteredTimeScale)
|
||||
|
||||
const removedOverlappingTS = this.removeOverlappingTS(
|
||||
reformattedTimescaleArray
|
||||
)
|
||||
|
||||
w.globals.timescaleLabels = removedOverlappingTS.slice()
|
||||
|
||||
// at this stage, we need to re-calculate coords of the grid as timeline labels may have altered the xaxis labels coords
|
||||
// The reason we can't do this prior to this stage is because timeline labels depends on gridWidth, and as the ticks are calculated based on available gridWidth, there can be unknown number of ticks generated for different minX and maxX
|
||||
// Dependency on Dimensions(), need to refactor correctly
|
||||
// TODO - find an alternate way to avoid calling this Heavy method twice
|
||||
let dimensions = new Dimensions(this.ctx)
|
||||
dimensions.plotCoords()
|
||||
}
|
||||
|
||||
determineInterval(daysDiff) {
|
||||
const yearsDiff = daysDiff / 365
|
||||
const hoursDiff = daysDiff * 24
|
||||
const minutesDiff = hoursDiff * 60
|
||||
const secondsDiff = minutesDiff * 60
|
||||
switch (true) {
|
||||
case yearsDiff > 5:
|
||||
this.tickInterval = 'years'
|
||||
break
|
||||
case daysDiff > 800:
|
||||
this.tickInterval = 'half_year'
|
||||
break
|
||||
case daysDiff > 180:
|
||||
this.tickInterval = 'months'
|
||||
break
|
||||
case daysDiff > 90:
|
||||
this.tickInterval = 'months_fortnight'
|
||||
break
|
||||
case daysDiff > 60:
|
||||
this.tickInterval = 'months_days'
|
||||
break
|
||||
case daysDiff > 30:
|
||||
this.tickInterval = 'week_days'
|
||||
break
|
||||
case daysDiff > 2:
|
||||
this.tickInterval = 'days'
|
||||
break
|
||||
case hoursDiff > 2.4:
|
||||
this.tickInterval = 'hours'
|
||||
break
|
||||
case minutesDiff > 15:
|
||||
this.tickInterval = 'minutes_fives'
|
||||
break
|
||||
case minutesDiff > 5:
|
||||
this.tickInterval = 'minutes'
|
||||
break
|
||||
case minutesDiff > 1:
|
||||
this.tickInterval = 'seconds_tens'
|
||||
break
|
||||
case secondsDiff > 20:
|
||||
this.tickInterval = 'seconds_fives'
|
||||
break
|
||||
default:
|
||||
this.tickInterval = 'seconds'
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
generateYearScale({
|
||||
firstVal,
|
||||
currentMonth,
|
||||
currentYear,
|
||||
daysWidthOnXAxis,
|
||||
numberOfYears,
|
||||
}) {
|
||||
let firstTickValue = firstVal.minYear
|
||||
let firstTickPosition = 0
|
||||
const dt = new DateTime(this.ctx)
|
||||
|
||||
let unit = 'year'
|
||||
|
||||
if (firstVal.minDate > 1 || firstVal.minMonth > 0) {
|
||||
let remainingDays = dt.determineRemainingDaysOfYear(
|
||||
firstVal.minYear,
|
||||
firstVal.minMonth,
|
||||
firstVal.minDate
|
||||
)
|
||||
|
||||
// remainingDaysofFirstMonth is used to reacht the 2nd tick position
|
||||
let remainingDaysOfFirstYear =
|
||||
dt.determineDaysOfYear(firstVal.minYear) - remainingDays + 1
|
||||
|
||||
// calculate the first tick position
|
||||
firstTickPosition = remainingDaysOfFirstYear * daysWidthOnXAxis
|
||||
firstTickValue = firstVal.minYear + 1
|
||||
// push the first tick in the array
|
||||
this.timeScaleArray.push({
|
||||
position: firstTickPosition,
|
||||
value: firstTickValue,
|
||||
unit,
|
||||
year: firstTickValue,
|
||||
month: Utils.monthMod(currentMonth + 1),
|
||||
})
|
||||
} else if (firstVal.minDate === 1 && firstVal.minMonth === 0) {
|
||||
// push the first tick in the array
|
||||
this.timeScaleArray.push({
|
||||
position: firstTickPosition,
|
||||
value: firstTickValue,
|
||||
unit,
|
||||
year: currentYear,
|
||||
month: Utils.monthMod(currentMonth + 1),
|
||||
})
|
||||
}
|
||||
|
||||
let year = firstTickValue
|
||||
let pos = firstTickPosition
|
||||
|
||||
// keep drawing rest of the ticks
|
||||
for (let i = 0; i < numberOfYears; i++) {
|
||||
year++
|
||||
pos = dt.determineDaysOfYear(year - 1) * daysWidthOnXAxis + pos
|
||||
this.timeScaleArray.push({
|
||||
position: pos,
|
||||
value: year,
|
||||
unit,
|
||||
year,
|
||||
month: 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
generateMonthScale({
|
||||
firstVal,
|
||||
currentMonthDate,
|
||||
currentMonth,
|
||||
currentYear,
|
||||
daysWidthOnXAxis,
|
||||
numberOfMonths,
|
||||
}) {
|
||||
let firstTickValue = currentMonth
|
||||
let firstTickPosition = 0
|
||||
const dt = new DateTime(this.ctx)
|
||||
let unit = 'month'
|
||||
let yrCounter = 0
|
||||
|
||||
if (firstVal.minDate > 1) {
|
||||
// remainingDaysofFirstMonth is used to reacht the 2nd tick position
|
||||
let remainingDaysOfFirstMonth =
|
||||
dt.determineDaysOfMonths(currentMonth + 1, firstVal.minYear) -
|
||||
currentMonthDate +
|
||||
1
|
||||
|
||||
// calculate the first tick position
|
||||
firstTickPosition = remainingDaysOfFirstMonth * daysWidthOnXAxis
|
||||
firstTickValue = Utils.monthMod(currentMonth + 1)
|
||||
|
||||
let year = currentYear + yrCounter
|
||||
let month = Utils.monthMod(firstTickValue)
|
||||
let value = firstTickValue
|
||||
// it's Jan, so update the year
|
||||
if (firstTickValue === 0) {
|
||||
unit = 'year'
|
||||
value = year
|
||||
month = 1
|
||||
yrCounter += 1
|
||||
year = year + yrCounter
|
||||
}
|
||||
|
||||
// push the first tick in the array
|
||||
this.timeScaleArray.push({
|
||||
position: firstTickPosition,
|
||||
value,
|
||||
unit,
|
||||
year,
|
||||
month,
|
||||
})
|
||||
} else {
|
||||
// push the first tick in the array
|
||||
this.timeScaleArray.push({
|
||||
position: firstTickPosition,
|
||||
value: firstTickValue,
|
||||
unit,
|
||||
year: currentYear,
|
||||
month: Utils.monthMod(currentMonth),
|
||||
})
|
||||
}
|
||||
|
||||
let month = firstTickValue + 1
|
||||
let pos = firstTickPosition
|
||||
|
||||
// keep drawing rest of the ticks
|
||||
for (let i = 0, j = 1; i < numberOfMonths; i++, j++) {
|
||||
month = Utils.monthMod(month)
|
||||
|
||||
if (month === 0) {
|
||||
unit = 'year'
|
||||
yrCounter += 1
|
||||
} else {
|
||||
unit = 'month'
|
||||
}
|
||||
let year = this._getYear(currentYear, month, yrCounter)
|
||||
|
||||
pos = dt.determineDaysOfMonths(month, year) * daysWidthOnXAxis + pos
|
||||
let monthVal = month === 0 ? year : month
|
||||
this.timeScaleArray.push({
|
||||
position: pos,
|
||||
value: monthVal,
|
||||
unit,
|
||||
year,
|
||||
month: month === 0 ? 1 : month,
|
||||
})
|
||||
month++
|
||||
}
|
||||
}
|
||||
|
||||
generateDayScale({
|
||||
firstVal,
|
||||
currentMonth,
|
||||
currentYear,
|
||||
hoursWidthOnXAxis,
|
||||
numberOfDays,
|
||||
}) {
|
||||
const dt = new DateTime(this.ctx)
|
||||
let unit = 'day'
|
||||
let firstTickValue = firstVal.minDate + 1
|
||||
let date = firstTickValue
|
||||
|
||||
const changeMonth = (dateVal, month, year) => {
|
||||
let monthdays = dt.determineDaysOfMonths(month + 1, year)
|
||||
|
||||
if (dateVal > monthdays) {
|
||||
month = month + 1
|
||||
date = 1
|
||||
unit = 'month'
|
||||
val = month
|
||||
return month
|
||||
}
|
||||
|
||||
return month
|
||||
}
|
||||
|
||||
let remainingHours = 24 - firstVal.minHour
|
||||
let yrCounter = 0
|
||||
|
||||
// calculate the first tick position
|
||||
let firstTickPosition = remainingHours * hoursWidthOnXAxis
|
||||
|
||||
let val = firstTickValue
|
||||
let month = changeMonth(date, currentMonth, currentYear)
|
||||
|
||||
if (firstVal.minHour === 0 && firstVal.minDate === 1) {
|
||||
// the first value is the first day of month
|
||||
firstTickPosition = 0
|
||||
val = Utils.monthMod(firstVal.minMonth)
|
||||
unit = 'month'
|
||||
date = firstVal.minDate
|
||||
// numberOfDays++
|
||||
// removed the above line to fix https://github.com/apexcharts/apexcharts.js/issues/305#issuecomment-1019520513
|
||||
} else if (
|
||||
firstVal.minDate !== 1 &&
|
||||
firstVal.minHour === 0 &&
|
||||
firstVal.minMinute === 0
|
||||
) {
|
||||
// fixes apexcharts/apexcharts.js/issues/1730
|
||||
firstTickPosition = 0
|
||||
firstTickValue = firstVal.minDate
|
||||
date = firstTickValue
|
||||
val = firstTickValue
|
||||
// in case it's the last date of month, we need to check it
|
||||
month = changeMonth(date, currentMonth, currentYear)
|
||||
}
|
||||
|
||||
// push the first tick in the array
|
||||
this.timeScaleArray.push({
|
||||
position: firstTickPosition,
|
||||
value: val,
|
||||
unit,
|
||||
year: this._getYear(currentYear, month, yrCounter),
|
||||
month: Utils.monthMod(month),
|
||||
day: date,
|
||||
})
|
||||
|
||||
let pos = firstTickPosition
|
||||
// keep drawing rest of the ticks
|
||||
for (let i = 0; i < numberOfDays; i++) {
|
||||
date += 1
|
||||
unit = 'day'
|
||||
month = changeMonth(
|
||||
date,
|
||||
month,
|
||||
this._getYear(currentYear, month, yrCounter)
|
||||
)
|
||||
|
||||
let year = this._getYear(currentYear, month, yrCounter)
|
||||
|
||||
pos = 24 * hoursWidthOnXAxis + pos
|
||||
let value = date === 1 ? Utils.monthMod(month) : date
|
||||
this.timeScaleArray.push({
|
||||
position: pos,
|
||||
value,
|
||||
unit,
|
||||
year,
|
||||
month: Utils.monthMod(month),
|
||||
day: value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
generateHourScale({
|
||||
firstVal,
|
||||
currentDate,
|
||||
currentMonth,
|
||||
currentYear,
|
||||
minutesWidthOnXAxis,
|
||||
numberOfHours,
|
||||
}) {
|
||||
const dt = new DateTime(this.ctx)
|
||||
|
||||
let yrCounter = 0
|
||||
let unit = 'hour'
|
||||
|
||||
const changeDate = (dateVal, month) => {
|
||||
let monthdays = dt.determineDaysOfMonths(month + 1, currentYear)
|
||||
if (dateVal > monthdays) {
|
||||
date = 1
|
||||
month = month + 1
|
||||
}
|
||||
return { month, date }
|
||||
}
|
||||
|
||||
const changeMonth = (dateVal, month) => {
|
||||
let monthdays = dt.determineDaysOfMonths(month + 1, currentYear)
|
||||
if (dateVal > monthdays) {
|
||||
month = month + 1
|
||||
return month
|
||||
}
|
||||
|
||||
return month
|
||||
}
|
||||
|
||||
// factor in minSeconds as well
|
||||
let remainingMins = 60 - (firstVal.minMinute + firstVal.minSecond / 60.0)
|
||||
|
||||
let firstTickPosition = remainingMins * minutesWidthOnXAxis
|
||||
let firstTickValue = firstVal.minHour + 1
|
||||
let hour = firstTickValue
|
||||
|
||||
if (remainingMins === 60) {
|
||||
firstTickPosition = 0
|
||||
firstTickValue = firstVal.minHour
|
||||
hour = firstTickValue
|
||||
}
|
||||
|
||||
let date = currentDate
|
||||
|
||||
// we need to apply date switching logic here as well, to avoid duplicated labels
|
||||
if (hour >= 24) {
|
||||
hour = 0
|
||||
date += 1
|
||||
unit = 'day'
|
||||
}
|
||||
|
||||
const checkNextMonth = changeDate(date, currentMonth)
|
||||
|
||||
let month = checkNextMonth.month
|
||||
month = changeMonth(date, month)
|
||||
|
||||
// push the first tick in the array
|
||||
this.timeScaleArray.push({
|
||||
position: firstTickPosition,
|
||||
value: firstTickValue,
|
||||
unit,
|
||||
day: date,
|
||||
hour,
|
||||
year: currentYear,
|
||||
month: Utils.monthMod(month),
|
||||
})
|
||||
|
||||
hour++
|
||||
|
||||
let pos = firstTickPosition
|
||||
// keep drawing rest of the ticks
|
||||
for (let i = 0; i < numberOfHours; i++) {
|
||||
unit = 'hour'
|
||||
|
||||
if (hour >= 24) {
|
||||
hour = 0
|
||||
date += 1
|
||||
unit = 'day'
|
||||
|
||||
const checkNextMonth = changeDate(date, month)
|
||||
|
||||
month = checkNextMonth.month
|
||||
month = changeMonth(date, month)
|
||||
}
|
||||
|
||||
let year = this._getYear(currentYear, month, yrCounter)
|
||||
pos = 60 * minutesWidthOnXAxis + pos
|
||||
let val = hour === 0 ? date : hour
|
||||
this.timeScaleArray.push({
|
||||
position: pos,
|
||||
value: val,
|
||||
unit,
|
||||
hour,
|
||||
day: date,
|
||||
year,
|
||||
month: Utils.monthMod(month),
|
||||
})
|
||||
|
||||
hour++
|
||||
}
|
||||
}
|
||||
|
||||
generateMinuteScale({
|
||||
currentMillisecond,
|
||||
currentSecond,
|
||||
currentMinute,
|
||||
currentHour,
|
||||
currentDate,
|
||||
currentMonth,
|
||||
currentYear,
|
||||
minutesWidthOnXAxis,
|
||||
secondsWidthOnXAxis,
|
||||
numberOfMinutes,
|
||||
}) {
|
||||
let yrCounter = 0
|
||||
let unit = 'minute'
|
||||
|
||||
let remainingSecs = 60 - currentSecond
|
||||
let firstTickPosition =
|
||||
(remainingSecs - currentMillisecond / 1000) * secondsWidthOnXAxis
|
||||
let minute = currentMinute + 1
|
||||
|
||||
let date = currentDate
|
||||
let month = currentMonth
|
||||
let year = currentYear
|
||||
let hour = currentHour
|
||||
|
||||
let pos = firstTickPosition
|
||||
for (let i = 0; i < numberOfMinutes; i++) {
|
||||
if (minute >= 60) {
|
||||
minute = 0
|
||||
hour += 1
|
||||
if (hour === 24) {
|
||||
hour = 0
|
||||
}
|
||||
}
|
||||
|
||||
this.timeScaleArray.push({
|
||||
position: pos,
|
||||
value: minute,
|
||||
unit,
|
||||
hour,
|
||||
minute,
|
||||
day: date,
|
||||
year: this._getYear(year, month, yrCounter),
|
||||
month: Utils.monthMod(month),
|
||||
})
|
||||
|
||||
pos += minutesWidthOnXAxis
|
||||
minute++
|
||||
}
|
||||
}
|
||||
|
||||
generateSecondScale({
|
||||
currentMillisecond,
|
||||
currentSecond,
|
||||
currentMinute,
|
||||
currentHour,
|
||||
currentDate,
|
||||
currentMonth,
|
||||
currentYear,
|
||||
secondsWidthOnXAxis,
|
||||
numberOfSeconds,
|
||||
}) {
|
||||
let yrCounter = 0
|
||||
let unit = 'second'
|
||||
|
||||
const remainingMillisecs = 1000 - currentMillisecond
|
||||
let firstTickPosition = (remainingMillisecs / 1000) * secondsWidthOnXAxis
|
||||
|
||||
let second = currentSecond + 1
|
||||
let minute = currentMinute
|
||||
let date = currentDate
|
||||
let month = currentMonth
|
||||
let year = currentYear
|
||||
let hour = currentHour
|
||||
|
||||
let pos = firstTickPosition
|
||||
for (let i = 0; i < numberOfSeconds; i++) {
|
||||
if (second >= 60) {
|
||||
minute++
|
||||
second = 0
|
||||
if (minute >= 60) {
|
||||
hour++
|
||||
minute = 0
|
||||
if (hour === 24) {
|
||||
hour = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.timeScaleArray.push({
|
||||
position: pos,
|
||||
value: second,
|
||||
unit,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
day: date,
|
||||
year: this._getYear(year, month, yrCounter),
|
||||
month: Utils.monthMod(month),
|
||||
})
|
||||
|
||||
pos += secondsWidthOnXAxis
|
||||
second++
|
||||
}
|
||||
}
|
||||
|
||||
createRawDateString(ts, value) {
|
||||
let raw = ts.year
|
||||
|
||||
if (ts.month === 0) {
|
||||
// invalid month, correct it
|
||||
ts.month = 1
|
||||
}
|
||||
raw += '-' + ('0' + ts.month.toString()).slice(-2)
|
||||
|
||||
// unit is day
|
||||
if (ts.unit === 'day') {
|
||||
raw += ts.unit === 'day' ? '-' + ('0' + value).slice(-2) : '-01'
|
||||
} else {
|
||||
raw += '-' + ('0' + (ts.day ? ts.day : '1')).slice(-2)
|
||||
}
|
||||
|
||||
// unit is hour
|
||||
if (ts.unit === 'hour') {
|
||||
raw += ts.unit === 'hour' ? 'T' + ('0' + value).slice(-2) : 'T00'
|
||||
} else {
|
||||
raw += 'T' + ('0' + (ts.hour ? ts.hour : '0')).slice(-2)
|
||||
}
|
||||
|
||||
if (ts.unit === 'minute') {
|
||||
raw += ':' + ('0' + value).slice(-2)
|
||||
} else {
|
||||
raw += ':' + (ts.minute ? ('0' + ts.minute).slice(-2) : '00')
|
||||
}
|
||||
|
||||
if (ts.unit === 'second') {
|
||||
raw += ':' + ('0' + value).slice(-2)
|
||||
} else {
|
||||
raw += ':00'
|
||||
}
|
||||
|
||||
if (this.utc) {
|
||||
raw += '.000Z'
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
formatDates(filteredTimeScale) {
|
||||
const w = this.w
|
||||
|
||||
const reformattedTimescaleArray = filteredTimeScale.map((ts) => {
|
||||
let value = ts.value.toString()
|
||||
|
||||
let dt = new DateTime(this.ctx)
|
||||
|
||||
const raw = this.createRawDateString(ts, value)
|
||||
|
||||
let dateToFormat = dt.getDate(dt.parseDate(raw))
|
||||
if (!this.utc) {
|
||||
// Fixes #1726, #1544, #1485, #1255
|
||||
dateToFormat = dt.getDate(dt.parseDateWithTimezone(raw))
|
||||
}
|
||||
|
||||
if (w.config.xaxis.labels.format === undefined) {
|
||||
let customFormat = 'dd MMM'
|
||||
const dtFormatter = w.config.xaxis.labels.datetimeFormatter
|
||||
if (ts.unit === 'year') customFormat = dtFormatter.year
|
||||
if (ts.unit === 'month') customFormat = dtFormatter.month
|
||||
if (ts.unit === 'day') customFormat = dtFormatter.day
|
||||
if (ts.unit === 'hour') customFormat = dtFormatter.hour
|
||||
if (ts.unit === 'minute') customFormat = dtFormatter.minute
|
||||
if (ts.unit === 'second') customFormat = dtFormatter.second
|
||||
|
||||
value = dt.formatDate(dateToFormat, customFormat)
|
||||
} else {
|
||||
value = dt.formatDate(dateToFormat, w.config.xaxis.labels.format)
|
||||
}
|
||||
|
||||
return {
|
||||
dateString: raw,
|
||||
position: ts.position,
|
||||
value,
|
||||
unit: ts.unit,
|
||||
year: ts.year,
|
||||
month: ts.month,
|
||||
}
|
||||
})
|
||||
|
||||
return reformattedTimescaleArray
|
||||
}
|
||||
|
||||
removeOverlappingTS(arr) {
|
||||
const graphics = new Graphics(this.ctx)
|
||||
|
||||
let equalLabelLengthFlag = false // These labels got same length?
|
||||
let constantLabelWidth // If true, what is the constant length to use
|
||||
if (
|
||||
arr.length > 0 && // check arr length
|
||||
arr[0].value && // check arr[0] contains value
|
||||
arr.every((lb) => lb.value.length === arr[0].value.length) // check every arr label value is the same as the first one
|
||||
) {
|
||||
equalLabelLengthFlag = true // These labels got same length
|
||||
constantLabelWidth = graphics.getTextRects(arr[0].value).width // The constant label width to use
|
||||
}
|
||||
|
||||
let lastDrawnIndex = 0
|
||||
|
||||
let filteredArray = arr.map((item, index) => {
|
||||
if (index > 0 && this.w.config.xaxis.labels.hideOverlappingLabels) {
|
||||
const prevLabelWidth = !equalLabelLengthFlag // if vary in label length
|
||||
? graphics.getTextRects(arr[lastDrawnIndex].value).width // get individual length
|
||||
: constantLabelWidth // else: use constant length
|
||||
const prevPos = arr[lastDrawnIndex].position
|
||||
const pos = item.position
|
||||
|
||||
if (pos > prevPos + prevLabelWidth + 10) {
|
||||
lastDrawnIndex = index
|
||||
return item
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
} else {
|
||||
return item
|
||||
}
|
||||
})
|
||||
|
||||
filteredArray = filteredArray.filter((f) => f !== null)
|
||||
|
||||
return filteredArray
|
||||
}
|
||||
|
||||
_getYear(currentYear, month, yrCounter) {
|
||||
return currentYear + Math.floor(month / 12) + yrCounter
|
||||
}
|
||||
}
|
||||
|
||||
export default TimeScale
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
import Graphics from './Graphics'
|
||||
|
||||
export default class TitleSubtitle {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
}
|
||||
|
||||
draw() {
|
||||
this.drawTitleSubtitle('title')
|
||||
this.drawTitleSubtitle('subtitle')
|
||||
}
|
||||
|
||||
drawTitleSubtitle(type) {
|
||||
let w = this.w
|
||||
const tsConfig = type === 'title' ? w.config.title : w.config.subtitle
|
||||
|
||||
let x = w.globals.svgWidth / 2
|
||||
let y = tsConfig.offsetY
|
||||
let textAnchor = 'middle'
|
||||
|
||||
if (tsConfig.align === 'left') {
|
||||
x = 10
|
||||
textAnchor = 'start'
|
||||
} else if (tsConfig.align === 'right') {
|
||||
x = w.globals.svgWidth - 10
|
||||
textAnchor = 'end'
|
||||
}
|
||||
|
||||
x = x + tsConfig.offsetX
|
||||
y = y + parseInt(tsConfig.style.fontSize, 10) + tsConfig.margin / 2
|
||||
|
||||
if (tsConfig.text !== undefined) {
|
||||
let graphics = new Graphics(this.ctx)
|
||||
let titleText = graphics.drawText({
|
||||
x,
|
||||
y,
|
||||
text: tsConfig.text,
|
||||
textAnchor,
|
||||
fontSize: tsConfig.style.fontSize,
|
||||
fontFamily: tsConfig.style.fontFamily,
|
||||
fontWeight: tsConfig.style.fontWeight,
|
||||
foreColor: tsConfig.style.color,
|
||||
opacity: 1
|
||||
})
|
||||
|
||||
titleText.node.setAttribute('class', `apexcharts-${type}-text`)
|
||||
|
||||
w.globals.dom.Paper.add(titleText)
|
||||
}
|
||||
}
|
||||
}
|
||||
+527
@@ -0,0 +1,527 @@
|
||||
import Graphics from './Graphics'
|
||||
import Exports from './Exports'
|
||||
import Scales from './Scales'
|
||||
import Utils from './../utils/Utils'
|
||||
import icoPan from './../assets/ico-pan-hand.svg'
|
||||
import icoZoom from './../assets/ico-zoom-in.svg'
|
||||
import icoReset from './../assets/ico-home.svg'
|
||||
import icoZoomIn from './../assets/ico-plus.svg'
|
||||
import icoZoomOut from './../assets/ico-minus.svg'
|
||||
import icoSelect from './../assets/ico-select.svg'
|
||||
import icoMenu from './../assets/ico-menu.svg'
|
||||
|
||||
/**
|
||||
* ApexCharts Toolbar Class for creating toolbar in axis based charts.
|
||||
*
|
||||
* @module Toolbar
|
||||
**/
|
||||
|
||||
export default class Toolbar {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
const w = this.w
|
||||
|
||||
this.ev = this.w.config.chart.events
|
||||
this.selectedClass = 'apexcharts-selected'
|
||||
|
||||
this.localeValues = this.w.globals.locale.toolbar
|
||||
|
||||
this.minX = w.globals.minX
|
||||
this.maxX = w.globals.maxX
|
||||
}
|
||||
|
||||
createToolbar() {
|
||||
let w = this.w
|
||||
|
||||
const createDiv = () => {
|
||||
return document.createElement('div')
|
||||
}
|
||||
const elToolbarWrap = createDiv()
|
||||
elToolbarWrap.setAttribute('class', 'apexcharts-toolbar')
|
||||
elToolbarWrap.style.top = w.config.chart.toolbar.offsetY + 'px'
|
||||
elToolbarWrap.style.right = -w.config.chart.toolbar.offsetX + 3 + 'px'
|
||||
w.globals.dom.elWrap.appendChild(elToolbarWrap)
|
||||
|
||||
this.elZoom = createDiv()
|
||||
this.elZoomIn = createDiv()
|
||||
this.elZoomOut = createDiv()
|
||||
this.elPan = createDiv()
|
||||
this.elSelection = createDiv()
|
||||
this.elZoomReset = createDiv()
|
||||
this.elMenuIcon = createDiv()
|
||||
this.elMenu = createDiv()
|
||||
this.elCustomIcons = []
|
||||
|
||||
this.t = w.config.chart.toolbar.tools
|
||||
|
||||
if (Array.isArray(this.t.customIcons)) {
|
||||
for (let i = 0; i < this.t.customIcons.length; i++) {
|
||||
this.elCustomIcons.push(createDiv())
|
||||
}
|
||||
}
|
||||
|
||||
let toolbarControls = []
|
||||
|
||||
const appendZoomControl = (type, el, ico) => {
|
||||
const tool = type.toLowerCase()
|
||||
if (this.t[tool] && w.config.chart.zoom.enabled) {
|
||||
toolbarControls.push({
|
||||
el,
|
||||
icon: typeof this.t[tool] === 'string' ? this.t[tool] : ico,
|
||||
title: this.localeValues[type],
|
||||
class: `apexcharts-${tool}-icon`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
appendZoomControl('zoomIn', this.elZoomIn, icoZoomIn)
|
||||
appendZoomControl('zoomOut', this.elZoomOut, icoZoomOut)
|
||||
|
||||
const zoomSelectionCtrls = (z) => {
|
||||
if (this.t[z] && w.config.chart[z].enabled) {
|
||||
toolbarControls.push({
|
||||
el: z === 'zoom' ? this.elZoom : this.elSelection,
|
||||
icon:
|
||||
typeof this.t[z] === 'string'
|
||||
? this.t[z]
|
||||
: z === 'zoom'
|
||||
? icoZoom
|
||||
: icoSelect,
|
||||
title:
|
||||
this.localeValues[z === 'zoom' ? 'selectionZoom' : 'selection'],
|
||||
class: w.globals.isTouchDevice
|
||||
? 'apexcharts-element-hidden'
|
||||
: `apexcharts-${z}-icon`,
|
||||
})
|
||||
}
|
||||
}
|
||||
zoomSelectionCtrls('zoom')
|
||||
zoomSelectionCtrls('selection')
|
||||
|
||||
if (this.t.pan && w.config.chart.zoom.enabled) {
|
||||
toolbarControls.push({
|
||||
el: this.elPan,
|
||||
icon: typeof this.t.pan === 'string' ? this.t.pan : icoPan,
|
||||
title: this.localeValues.pan,
|
||||
class: w.globals.isTouchDevice
|
||||
? 'apexcharts-element-hidden'
|
||||
: 'apexcharts-pan-icon',
|
||||
})
|
||||
}
|
||||
|
||||
appendZoomControl('reset', this.elZoomReset, icoReset)
|
||||
|
||||
if (this.t.download) {
|
||||
toolbarControls.push({
|
||||
el: this.elMenuIcon,
|
||||
icon: typeof this.t.download === 'string' ? this.t.download : icoMenu,
|
||||
title: this.localeValues.menu,
|
||||
class: 'apexcharts-menu-icon',
|
||||
})
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.elCustomIcons.length; i++) {
|
||||
toolbarControls.push({
|
||||
el: this.elCustomIcons[i],
|
||||
icon: this.t.customIcons[i].icon,
|
||||
title: this.t.customIcons[i].title,
|
||||
index: this.t.customIcons[i].index,
|
||||
class: 'apexcharts-toolbar-custom-icon ' + this.t.customIcons[i].class,
|
||||
})
|
||||
}
|
||||
|
||||
toolbarControls.forEach((t, index) => {
|
||||
if (t.index) {
|
||||
Utils.moveIndexInArray(toolbarControls, index, t.index)
|
||||
}
|
||||
})
|
||||
|
||||
for (let i = 0; i < toolbarControls.length; i++) {
|
||||
Graphics.setAttrs(toolbarControls[i].el, {
|
||||
class: toolbarControls[i].class,
|
||||
title: toolbarControls[i].title,
|
||||
})
|
||||
|
||||
toolbarControls[i].el.innerHTML = toolbarControls[i].icon
|
||||
elToolbarWrap.appendChild(toolbarControls[i].el)
|
||||
}
|
||||
|
||||
this._createHamburgerMenu(elToolbarWrap)
|
||||
|
||||
if (w.globals.zoomEnabled) {
|
||||
this.elZoom.classList.add(this.selectedClass)
|
||||
} else if (w.globals.panEnabled) {
|
||||
this.elPan.classList.add(this.selectedClass)
|
||||
} else if (w.globals.selectionEnabled) {
|
||||
this.elSelection.classList.add(this.selectedClass)
|
||||
}
|
||||
|
||||
this.addToolbarEventListeners()
|
||||
}
|
||||
|
||||
_createHamburgerMenu(parent) {
|
||||
this.elMenuItems = []
|
||||
parent.appendChild(this.elMenu)
|
||||
|
||||
Graphics.setAttrs(this.elMenu, {
|
||||
class: 'apexcharts-menu',
|
||||
})
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
name: 'exportSVG',
|
||||
title: this.localeValues.exportToSVG,
|
||||
},
|
||||
{
|
||||
name: 'exportPNG',
|
||||
title: this.localeValues.exportToPNG,
|
||||
},
|
||||
{
|
||||
name: 'exportCSV',
|
||||
title: this.localeValues.exportToCSV,
|
||||
},
|
||||
]
|
||||
|
||||
for (let i = 0; i < menuItems.length; i++) {
|
||||
this.elMenuItems.push(document.createElement('div'))
|
||||
this.elMenuItems[i].innerHTML = menuItems[i].title
|
||||
Graphics.setAttrs(this.elMenuItems[i], {
|
||||
class: `apexcharts-menu-item ${menuItems[i].name}`,
|
||||
title: menuItems[i].title,
|
||||
})
|
||||
this.elMenu.appendChild(this.elMenuItems[i])
|
||||
}
|
||||
}
|
||||
|
||||
addToolbarEventListeners() {
|
||||
this.elZoomReset.addEventListener('click', this.handleZoomReset.bind(this))
|
||||
this.elSelection.addEventListener(
|
||||
'click',
|
||||
this.toggleZoomSelection.bind(this, 'selection')
|
||||
)
|
||||
this.elZoom.addEventListener(
|
||||
'click',
|
||||
this.toggleZoomSelection.bind(this, 'zoom')
|
||||
)
|
||||
this.elZoomIn.addEventListener('click', this.handleZoomIn.bind(this))
|
||||
this.elZoomOut.addEventListener('click', this.handleZoomOut.bind(this))
|
||||
this.elPan.addEventListener('click', this.togglePanning.bind(this))
|
||||
this.elMenuIcon.addEventListener('click', this.toggleMenu.bind(this))
|
||||
this.elMenuItems.forEach((m) => {
|
||||
if (m.classList.contains('exportSVG')) {
|
||||
m.addEventListener('click', this.handleDownload.bind(this, 'svg'))
|
||||
} else if (m.classList.contains('exportPNG')) {
|
||||
m.addEventListener('click', this.handleDownload.bind(this, 'png'))
|
||||
} else if (m.classList.contains('exportCSV')) {
|
||||
m.addEventListener('click', this.handleDownload.bind(this, 'csv'))
|
||||
}
|
||||
})
|
||||
for (let i = 0; i < this.t.customIcons.length; i++) {
|
||||
this.elCustomIcons[i].addEventListener(
|
||||
'click',
|
||||
this.t.customIcons[i].click.bind(this, this.ctx, this.ctx.w)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
toggleZoomSelection(type) {
|
||||
const charts = this.ctx.getSyncedCharts()
|
||||
|
||||
charts.forEach((ch) => {
|
||||
ch.ctx.toolbar.toggleOtherControls()
|
||||
|
||||
let el =
|
||||
type === 'selection'
|
||||
? ch.ctx.toolbar.elSelection
|
||||
: ch.ctx.toolbar.elZoom
|
||||
let enabledType =
|
||||
type === 'selection' ? 'selectionEnabled' : 'zoomEnabled'
|
||||
|
||||
ch.w.globals[enabledType] = !ch.w.globals[enabledType]
|
||||
|
||||
if (!el.classList.contains(ch.ctx.toolbar.selectedClass)) {
|
||||
el.classList.add(ch.ctx.toolbar.selectedClass)
|
||||
} else {
|
||||
el.classList.remove(ch.ctx.toolbar.selectedClass)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getToolbarIconsReference() {
|
||||
const w = this.w
|
||||
if (!this.elZoom) {
|
||||
this.elZoom = w.globals.dom.baseEl.querySelector('.apexcharts-zoom-icon')
|
||||
}
|
||||
if (!this.elPan) {
|
||||
this.elPan = w.globals.dom.baseEl.querySelector('.apexcharts-pan-icon')
|
||||
}
|
||||
if (!this.elSelection) {
|
||||
this.elSelection = w.globals.dom.baseEl.querySelector(
|
||||
'.apexcharts-selection-icon'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
enableZoomPanFromToolbar(type) {
|
||||
this.toggleOtherControls()
|
||||
|
||||
type === 'pan'
|
||||
? (this.w.globals.panEnabled = true)
|
||||
: (this.w.globals.zoomEnabled = true)
|
||||
|
||||
const el = type === 'pan' ? this.elPan : this.elZoom
|
||||
const el2 = type === 'pan' ? this.elZoom : this.elPan
|
||||
if (el) {
|
||||
el.classList.add(this.selectedClass)
|
||||
}
|
||||
if (el2) {
|
||||
el2.classList.remove(this.selectedClass)
|
||||
}
|
||||
}
|
||||
|
||||
togglePanning() {
|
||||
const charts = this.ctx.getSyncedCharts()
|
||||
|
||||
charts.forEach((ch) => {
|
||||
ch.ctx.toolbar.toggleOtherControls()
|
||||
ch.w.globals.panEnabled = !ch.w.globals.panEnabled
|
||||
|
||||
if (
|
||||
!ch.ctx.toolbar.elPan.classList.contains(ch.ctx.toolbar.selectedClass)
|
||||
) {
|
||||
ch.ctx.toolbar.elPan.classList.add(ch.ctx.toolbar.selectedClass)
|
||||
} else {
|
||||
ch.ctx.toolbar.elPan.classList.remove(ch.ctx.toolbar.selectedClass)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
toggleOtherControls() {
|
||||
const w = this.w
|
||||
w.globals.panEnabled = false
|
||||
w.globals.zoomEnabled = false
|
||||
w.globals.selectionEnabled = false
|
||||
|
||||
this.getToolbarIconsReference()
|
||||
|
||||
const toggleEls = [this.elPan, this.elSelection, this.elZoom]
|
||||
toggleEls.forEach((el) => {
|
||||
if (el) {
|
||||
el.classList.remove(this.selectedClass)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
handleZoomIn() {
|
||||
const w = this.w
|
||||
|
||||
if (w.globals.isRangeBar) {
|
||||
this.minX = w.globals.minY
|
||||
this.maxX = w.globals.maxY
|
||||
}
|
||||
|
||||
const centerX = (this.minX + this.maxX) / 2
|
||||
let newMinX = (this.minX + centerX) / 2
|
||||
let newMaxX = (this.maxX + centerX) / 2
|
||||
|
||||
const newMinXMaxX = this._getNewMinXMaxX(newMinX, newMaxX)
|
||||
|
||||
if (!w.globals.disableZoomIn) {
|
||||
this.zoomUpdateOptions(newMinXMaxX.minX, newMinXMaxX.maxX)
|
||||
}
|
||||
}
|
||||
|
||||
handleZoomOut() {
|
||||
const w = this.w
|
||||
|
||||
if (w.globals.isRangeBar) {
|
||||
this.minX = w.globals.minY
|
||||
this.maxX = w.globals.maxY
|
||||
}
|
||||
|
||||
// avoid zooming out beyond 1000 which may result in NaN values being printed on x-axis
|
||||
if (
|
||||
w.config.xaxis.type === 'datetime' &&
|
||||
new Date(this.minX).getUTCFullYear() < 1000
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const centerX = (this.minX + this.maxX) / 2
|
||||
let newMinX = this.minX - (centerX - this.minX)
|
||||
let newMaxX = this.maxX - (centerX - this.maxX)
|
||||
|
||||
const newMinXMaxX = this._getNewMinXMaxX(newMinX, newMaxX)
|
||||
|
||||
if (!w.globals.disableZoomOut) {
|
||||
this.zoomUpdateOptions(newMinXMaxX.minX, newMinXMaxX.maxX)
|
||||
}
|
||||
}
|
||||
|
||||
_getNewMinXMaxX(newMinX, newMaxX) {
|
||||
const shouldFloor = this.w.config.xaxis.convertedCatToNumeric
|
||||
return {
|
||||
minX: shouldFloor ? Math.floor(newMinX) : newMinX,
|
||||
maxX: shouldFloor ? Math.floor(newMaxX) : newMaxX,
|
||||
}
|
||||
}
|
||||
|
||||
zoomUpdateOptions(newMinX, newMaxX) {
|
||||
const w = this.w
|
||||
|
||||
if (newMinX === undefined && newMaxX === undefined) {
|
||||
this.handleZoomReset()
|
||||
return
|
||||
}
|
||||
|
||||
if (w.config.xaxis.convertedCatToNumeric) {
|
||||
// in category charts, avoid zooming out beyond min and max
|
||||
if (newMinX < 1) {
|
||||
newMinX = 1
|
||||
newMaxX = w.globals.dataPoints
|
||||
}
|
||||
|
||||
if (newMaxX - newMinX < 2) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let xaxis = {
|
||||
min: newMinX,
|
||||
max: newMaxX,
|
||||
}
|
||||
|
||||
const beforeZoomRange = this.getBeforeZoomRange(xaxis)
|
||||
if (beforeZoomRange) {
|
||||
xaxis = beforeZoomRange.xaxis
|
||||
}
|
||||
|
||||
let options = {
|
||||
xaxis,
|
||||
}
|
||||
|
||||
let yaxis = Utils.clone(w.globals.initialConfig.yaxis)
|
||||
if (w.config.chart.zoom.autoScaleYaxis) {
|
||||
const scale = new Scales(this.ctx)
|
||||
yaxis = scale.autoScaleY(this.ctx, yaxis, {
|
||||
xaxis,
|
||||
})
|
||||
}
|
||||
|
||||
if (!w.config.chart.group) {
|
||||
// if chart in a group, prevent yaxis update here
|
||||
// fix issue #650
|
||||
options.yaxis = yaxis
|
||||
}
|
||||
|
||||
this.w.globals.zoomed = true
|
||||
|
||||
this.ctx.updateHelpers._updateOptions(
|
||||
options,
|
||||
false,
|
||||
this.w.config.chart.animations.dynamicAnimation.enabled
|
||||
)
|
||||
|
||||
this.zoomCallback(xaxis, yaxis)
|
||||
}
|
||||
|
||||
zoomCallback(xaxis, yaxis) {
|
||||
if (typeof this.ev.zoomed === 'function') {
|
||||
this.ev.zoomed(this.ctx, { xaxis, yaxis })
|
||||
}
|
||||
}
|
||||
|
||||
getBeforeZoomRange(xaxis, yaxis) {
|
||||
let newRange = null
|
||||
if (typeof this.ev.beforeZoom === 'function') {
|
||||
newRange = this.ev.beforeZoom(this, { xaxis, yaxis })
|
||||
}
|
||||
|
||||
return newRange
|
||||
}
|
||||
|
||||
toggleMenu() {
|
||||
window.setTimeout(() => {
|
||||
if (this.elMenu.classList.contains('apexcharts-menu-open')) {
|
||||
this.elMenu.classList.remove('apexcharts-menu-open')
|
||||
} else {
|
||||
this.elMenu.classList.add('apexcharts-menu-open')
|
||||
}
|
||||
}, 0)
|
||||
}
|
||||
|
||||
handleDownload(type) {
|
||||
const w = this.w
|
||||
const exprt = new Exports(this.ctx)
|
||||
switch (type) {
|
||||
case 'svg':
|
||||
exprt.exportToSVG(this.ctx)
|
||||
break
|
||||
case 'png':
|
||||
exprt.exportToPng(this.ctx)
|
||||
break
|
||||
case 'csv':
|
||||
exprt.exportToCSV({
|
||||
series: w.config.series,
|
||||
columnDelimiter: w.config.chart.toolbar.export.csv.columnDelimiter,
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
handleZoomReset(e) {
|
||||
const charts = this.ctx.getSyncedCharts()
|
||||
|
||||
charts.forEach((ch) => {
|
||||
let w = ch.w
|
||||
|
||||
// forget lastXAxis min/max as reset button isn't resetting the x-axis completely if zoomX is called before
|
||||
w.globals.lastXAxis.min = w.globals.initialConfig.xaxis.min
|
||||
w.globals.lastXAxis.max = w.globals.initialConfig.xaxis.max
|
||||
|
||||
ch.updateHelpers.revertDefaultAxisMinMax()
|
||||
|
||||
if (typeof w.config.chart.events.beforeResetZoom === 'function') {
|
||||
// here, user get an option to control xaxis and yaxis when resetZoom is called
|
||||
// at this point, whatever is returned from w.config.chart.events.beforeResetZoom
|
||||
// is set as the new xaxis/yaxis min/max
|
||||
const resetZoomRange = w.config.chart.events.beforeResetZoom(ch, w)
|
||||
|
||||
if (resetZoomRange) {
|
||||
ch.updateHelpers.revertDefaultAxisMinMax(resetZoomRange)
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof w.config.chart.events.zoomed === 'function') {
|
||||
ch.ctx.toolbar.zoomCallback({
|
||||
min: w.config.xaxis.min,
|
||||
max: w.config.xaxis.max,
|
||||
})
|
||||
}
|
||||
|
||||
w.globals.zoomed = false
|
||||
|
||||
// if user has some series collapsed before hitting zoom reset button,
|
||||
// those series should stay collapsed
|
||||
let series = ch.ctx.series.emptyCollapsedSeries(
|
||||
Utils.clone(w.globals.initialSeries)
|
||||
)
|
||||
|
||||
ch.updateHelpers._updateSeries(
|
||||
series,
|
||||
w.config.chart.animations.dynamicAnimation.enabled
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.elZoom = null
|
||||
this.elZoomIn = null
|
||||
this.elZoomOut = null
|
||||
this.elPan = null
|
||||
this.elSelection = null
|
||||
this.elZoomReset = null
|
||||
this.elMenuIcon = null
|
||||
}
|
||||
}
|
||||
+804
@@ -0,0 +1,804 @@
|
||||
import Graphics from './Graphics'
|
||||
import Utils from './../utils/Utils'
|
||||
import Toolbar from './Toolbar'
|
||||
import Scales from './Scales'
|
||||
|
||||
/**
|
||||
* ApexCharts Zoom Class for handling zooming and panning on axes based charts.
|
||||
*
|
||||
* @module ZoomPanSelection
|
||||
**/
|
||||
|
||||
export default class ZoomPanSelection extends Toolbar {
|
||||
constructor(ctx) {
|
||||
super(ctx)
|
||||
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
|
||||
this.dragged = false
|
||||
this.graphics = new Graphics(this.ctx)
|
||||
|
||||
this.eventList = [
|
||||
'mousedown',
|
||||
'mouseleave',
|
||||
'mousemove',
|
||||
'touchstart',
|
||||
'touchmove',
|
||||
'mouseup',
|
||||
'touchend',
|
||||
]
|
||||
|
||||
this.clientX = 0
|
||||
this.clientY = 0
|
||||
this.startX = 0
|
||||
this.endX = 0
|
||||
this.dragX = 0
|
||||
this.startY = 0
|
||||
this.endY = 0
|
||||
this.dragY = 0
|
||||
this.moveDirection = 'none'
|
||||
}
|
||||
|
||||
init({ xyRatios }) {
|
||||
let w = this.w
|
||||
let me = this
|
||||
|
||||
this.xyRatios = xyRatios
|
||||
|
||||
this.zoomRect = this.graphics.drawRect(0, 0, 0, 0)
|
||||
this.selectionRect = this.graphics.drawRect(0, 0, 0, 0)
|
||||
this.gridRect = w.globals.dom.baseEl.querySelector('.apexcharts-grid')
|
||||
|
||||
this.zoomRect.node.classList.add('apexcharts-zoom-rect')
|
||||
this.selectionRect.node.classList.add('apexcharts-selection-rect')
|
||||
w.globals.dom.elGraphical.add(this.zoomRect)
|
||||
w.globals.dom.elGraphical.add(this.selectionRect)
|
||||
|
||||
if (w.config.chart.selection.type === 'x') {
|
||||
this.slDraggableRect = this.selectionRect
|
||||
.draggable({
|
||||
minX: 0,
|
||||
minY: 0,
|
||||
maxX: w.globals.gridWidth,
|
||||
maxY: w.globals.gridHeight,
|
||||
})
|
||||
.on('dragmove', this.selectionDragging.bind(this, 'dragging'))
|
||||
} else if (w.config.chart.selection.type === 'y') {
|
||||
this.slDraggableRect = this.selectionRect
|
||||
.draggable({
|
||||
minX: 0,
|
||||
maxX: w.globals.gridWidth,
|
||||
})
|
||||
.on('dragmove', this.selectionDragging.bind(this, 'dragging'))
|
||||
} else {
|
||||
this.slDraggableRect = this.selectionRect
|
||||
.draggable()
|
||||
.on('dragmove', this.selectionDragging.bind(this, 'dragging'))
|
||||
}
|
||||
this.preselectedSelection()
|
||||
|
||||
this.hoverArea = w.globals.dom.baseEl.querySelector(
|
||||
`${w.globals.chartClass} .apexcharts-svg`
|
||||
)
|
||||
this.hoverArea.classList.add('apexcharts-zoomable')
|
||||
|
||||
this.eventList.forEach((event) => {
|
||||
this.hoverArea.addEventListener(
|
||||
event,
|
||||
me.svgMouseEvents.bind(me, xyRatios),
|
||||
{
|
||||
capture: false,
|
||||
passive: true,
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// remove the event listeners which were previously added on hover area
|
||||
destroy() {
|
||||
if (this.slDraggableRect) {
|
||||
this.slDraggableRect.draggable(false)
|
||||
this.slDraggableRect.off()
|
||||
this.selectionRect.off()
|
||||
}
|
||||
|
||||
this.selectionRect = null
|
||||
this.zoomRect = null
|
||||
this.gridRect = null
|
||||
}
|
||||
|
||||
svgMouseEvents(xyRatios, e) {
|
||||
let w = this.w
|
||||
let me = this
|
||||
const toolbar = this.ctx.toolbar
|
||||
|
||||
let zoomtype = w.globals.zoomEnabled
|
||||
? w.config.chart.zoom.type
|
||||
: w.config.chart.selection.type
|
||||
|
||||
const autoSelected = w.config.chart.toolbar.autoSelected
|
||||
|
||||
if (e.shiftKey) {
|
||||
this.shiftWasPressed = true
|
||||
toolbar.enableZoomPanFromToolbar(autoSelected === 'pan' ? 'zoom' : 'pan')
|
||||
} else {
|
||||
if (this.shiftWasPressed) {
|
||||
toolbar.enableZoomPanFromToolbar(autoSelected)
|
||||
this.shiftWasPressed = false
|
||||
}
|
||||
}
|
||||
|
||||
if (!e.target) return
|
||||
|
||||
const tc = e.target.classList
|
||||
let pc
|
||||
if (e.target.parentNode && e.target.parentNode !== null) {
|
||||
pc = e.target.parentNode.classList
|
||||
}
|
||||
const falsePositives =
|
||||
tc.contains('apexcharts-selection-rect') ||
|
||||
tc.contains('apexcharts-legend-marker') ||
|
||||
tc.contains('apexcharts-legend-text') ||
|
||||
(pc && pc.contains('apexcharts-toolbar'))
|
||||
|
||||
if (falsePositives) return
|
||||
|
||||
me.clientX =
|
||||
e.type === 'touchmove' || e.type === 'touchstart'
|
||||
? e.touches[0].clientX
|
||||
: e.type === 'touchend'
|
||||
? e.changedTouches[0].clientX
|
||||
: e.clientX
|
||||
me.clientY =
|
||||
e.type === 'touchmove' || e.type === 'touchstart'
|
||||
? e.touches[0].clientY
|
||||
: e.type === 'touchend'
|
||||
? e.changedTouches[0].clientY
|
||||
: e.clientY
|
||||
|
||||
if (e.type === 'mousedown' && e.which === 1) {
|
||||
let gridRectDim = me.gridRect.getBoundingClientRect()
|
||||
|
||||
me.startX = me.clientX - gridRectDim.left
|
||||
me.startY = me.clientY - gridRectDim.top
|
||||
|
||||
me.dragged = false
|
||||
me.w.globals.mousedown = true
|
||||
}
|
||||
|
||||
if ((e.type === 'mousemove' && e.which === 1) || e.type === 'touchmove') {
|
||||
me.dragged = true
|
||||
|
||||
if (w.globals.panEnabled) {
|
||||
w.globals.selection = null
|
||||
if (me.w.globals.mousedown) {
|
||||
me.panDragging({
|
||||
context: me,
|
||||
zoomtype,
|
||||
xyRatios,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
(me.w.globals.mousedown && w.globals.zoomEnabled) ||
|
||||
(me.w.globals.mousedown && w.globals.selectionEnabled)
|
||||
) {
|
||||
me.selection = me.selectionDrawing({
|
||||
context: me,
|
||||
zoomtype,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
e.type === 'mouseup' ||
|
||||
e.type === 'touchend' ||
|
||||
e.type === 'mouseleave'
|
||||
) {
|
||||
// we will be calling getBoundingClientRect on each mousedown/mousemove/mouseup
|
||||
let gridRectDim = me.gridRect.getBoundingClientRect()
|
||||
|
||||
if (me.w.globals.mousedown) {
|
||||
// user released the drag, now do all the calculations
|
||||
me.endX = me.clientX - gridRectDim.left
|
||||
me.endY = me.clientY - gridRectDim.top
|
||||
me.dragX = Math.abs(me.endX - me.startX)
|
||||
me.dragY = Math.abs(me.endY - me.startY)
|
||||
|
||||
if (w.globals.zoomEnabled || w.globals.selectionEnabled) {
|
||||
me.selectionDrawn({
|
||||
context: me,
|
||||
zoomtype,
|
||||
})
|
||||
}
|
||||
|
||||
if (w.globals.panEnabled && w.config.xaxis.convertedCatToNumeric) {
|
||||
me.delayedPanScrolled()
|
||||
}
|
||||
}
|
||||
|
||||
if (w.globals.zoomEnabled) {
|
||||
me.hideSelectionRect(this.selectionRect)
|
||||
}
|
||||
|
||||
me.dragged = false
|
||||
me.w.globals.mousedown = false
|
||||
}
|
||||
|
||||
this.makeSelectionRectDraggable()
|
||||
}
|
||||
|
||||
makeSelectionRectDraggable() {
|
||||
const w = this.w
|
||||
|
||||
if (!this.selectionRect) return
|
||||
|
||||
const rectDim = this.selectionRect.node.getBoundingClientRect()
|
||||
if (rectDim.width > 0 && rectDim.height > 0) {
|
||||
this.slDraggableRect
|
||||
.selectize({
|
||||
points: 'l, r',
|
||||
pointSize: 8,
|
||||
pointType: 'rect',
|
||||
})
|
||||
.resize({
|
||||
constraint: {
|
||||
minX: 0,
|
||||
minY: 0,
|
||||
maxX: w.globals.gridWidth,
|
||||
maxY: w.globals.gridHeight,
|
||||
},
|
||||
})
|
||||
.on('resizing', this.selectionDragging.bind(this, 'resizing'))
|
||||
}
|
||||
}
|
||||
|
||||
preselectedSelection() {
|
||||
const w = this.w
|
||||
const xyRatios = this.xyRatios
|
||||
|
||||
if (!w.globals.zoomEnabled) {
|
||||
if (
|
||||
typeof w.globals.selection !== 'undefined' &&
|
||||
w.globals.selection !== null
|
||||
) {
|
||||
this.drawSelectionRect(w.globals.selection)
|
||||
} else {
|
||||
if (
|
||||
w.config.chart.selection.xaxis.min !== undefined &&
|
||||
w.config.chart.selection.xaxis.max !== undefined
|
||||
) {
|
||||
let x =
|
||||
(w.config.chart.selection.xaxis.min - w.globals.minX) /
|
||||
xyRatios.xRatio
|
||||
let width =
|
||||
w.globals.gridWidth -
|
||||
(w.globals.maxX - w.config.chart.selection.xaxis.max) /
|
||||
xyRatios.xRatio -
|
||||
x
|
||||
if (w.globals.isRangeBar) {
|
||||
// rangebars put datetime data in y axis
|
||||
x = // calculation: (selection left time - chart left time) / milliseconds per pixel = selection X value in pixels
|
||||
(w.config.chart.selection.xaxis.min -
|
||||
w.globals.yAxisScale[0].niceMin) /
|
||||
xyRatios.invertedYRatio
|
||||
width =
|
||||
(w.config.chart.selection.xaxis.max -
|
||||
w.config.chart.selection.xaxis.min) /
|
||||
xyRatios.invertedYRatio
|
||||
}
|
||||
let selectionRect = {
|
||||
x,
|
||||
y: 0,
|
||||
width,
|
||||
height: w.globals.gridHeight,
|
||||
translateX: 0,
|
||||
translateY: 0,
|
||||
selectionEnabled: true,
|
||||
}
|
||||
this.drawSelectionRect(selectionRect)
|
||||
this.makeSelectionRectDraggable()
|
||||
if (typeof w.config.chart.events.selection === 'function') {
|
||||
w.config.chart.events.selection(this.ctx, {
|
||||
xaxis: {
|
||||
min: w.config.chart.selection.xaxis.min,
|
||||
max: w.config.chart.selection.xaxis.max,
|
||||
},
|
||||
yaxis: {},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawSelectionRect({ x, y, width, height, translateX = 0, translateY = 0 }) {
|
||||
const w = this.w
|
||||
const zoomRect = this.zoomRect
|
||||
const selectionRect = this.selectionRect
|
||||
if (this.dragged || w.globals.selection !== null) {
|
||||
let scalingAttrs = {
|
||||
transform: 'translate(' + translateX + ', ' + translateY + ')',
|
||||
}
|
||||
|
||||
// change styles based on zoom or selection
|
||||
// zoom is Enabled and user has dragged, so draw blue rect
|
||||
if (w.globals.zoomEnabled && this.dragged) {
|
||||
if (width < 0) width = 1 // fixes apexcharts.js#1168
|
||||
zoomRect.attr({
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
fill: w.config.chart.zoom.zoomedArea.fill.color,
|
||||
'fill-opacity': w.config.chart.zoom.zoomedArea.fill.opacity,
|
||||
stroke: w.config.chart.zoom.zoomedArea.stroke.color,
|
||||
'stroke-width': w.config.chart.zoom.zoomedArea.stroke.width,
|
||||
'stroke-opacity': w.config.chart.zoom.zoomedArea.stroke.opacity,
|
||||
})
|
||||
Graphics.setAttrs(zoomRect.node, scalingAttrs)
|
||||
}
|
||||
|
||||
// selection is enabled
|
||||
if (w.globals.selectionEnabled) {
|
||||
selectionRect.attr({
|
||||
x,
|
||||
y,
|
||||
width: width > 0 ? width : 0,
|
||||
height: height > 0 ? height : 0,
|
||||
fill: w.config.chart.selection.fill.color,
|
||||
'fill-opacity': w.config.chart.selection.fill.opacity,
|
||||
stroke: w.config.chart.selection.stroke.color,
|
||||
'stroke-width': w.config.chart.selection.stroke.width,
|
||||
'stroke-dasharray': w.config.chart.selection.stroke.dashArray,
|
||||
'stroke-opacity': w.config.chart.selection.stroke.opacity,
|
||||
})
|
||||
|
||||
Graphics.setAttrs(selectionRect.node, scalingAttrs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hideSelectionRect(rect) {
|
||||
if (rect) {
|
||||
rect.attr({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
selectionDrawing({ context, zoomtype }) {
|
||||
const w = this.w
|
||||
let me = context
|
||||
|
||||
let gridRectDim = this.gridRect.getBoundingClientRect()
|
||||
|
||||
let startX = me.startX - 1
|
||||
let startY = me.startY
|
||||
let inversedX = false
|
||||
let inversedY = false
|
||||
|
||||
let selectionWidth = me.clientX - gridRectDim.left - startX
|
||||
let selectionHeight = me.clientY - gridRectDim.top - startY
|
||||
|
||||
let selectionRect = {}
|
||||
|
||||
if (Math.abs(selectionWidth + startX) > w.globals.gridWidth) {
|
||||
// user dragged the mouse outside drawing area to the right
|
||||
selectionWidth = w.globals.gridWidth - startX
|
||||
} else if (me.clientX - gridRectDim.left < 0) {
|
||||
// user dragged the mouse outside drawing area to the left
|
||||
selectionWidth = startX
|
||||
}
|
||||
|
||||
// inverse selection X
|
||||
if (startX > me.clientX - gridRectDim.left) {
|
||||
inversedX = true
|
||||
selectionWidth = Math.abs(selectionWidth)
|
||||
}
|
||||
|
||||
// inverse selection Y
|
||||
if (startY > me.clientY - gridRectDim.top) {
|
||||
inversedY = true
|
||||
selectionHeight = Math.abs(selectionHeight)
|
||||
}
|
||||
|
||||
if (zoomtype === 'x') {
|
||||
selectionRect = {
|
||||
x: inversedX ? startX - selectionWidth : startX,
|
||||
y: 0,
|
||||
width: selectionWidth,
|
||||
height: w.globals.gridHeight,
|
||||
}
|
||||
} else if (zoomtype === 'y') {
|
||||
selectionRect = {
|
||||
x: 0,
|
||||
y: inversedY ? startY - selectionHeight : startY,
|
||||
width: w.globals.gridWidth,
|
||||
height: selectionHeight,
|
||||
}
|
||||
} else {
|
||||
selectionRect = {
|
||||
x: inversedX ? startX - selectionWidth : startX,
|
||||
y: inversedY ? startY - selectionHeight : startY,
|
||||
width: selectionWidth,
|
||||
height: selectionHeight,
|
||||
}
|
||||
}
|
||||
|
||||
me.drawSelectionRect(selectionRect)
|
||||
me.selectionDragging('resizing')
|
||||
return selectionRect
|
||||
}
|
||||
|
||||
selectionDragging(type, e) {
|
||||
const w = this.w
|
||||
const xyRatios = this.xyRatios
|
||||
|
||||
const selRect = this.selectionRect
|
||||
|
||||
let timerInterval = 0
|
||||
|
||||
if (type === 'resizing') {
|
||||
timerInterval = 30
|
||||
}
|
||||
|
||||
// update selection when selection rect is dragged
|
||||
const getSelAttr = (attr) => {
|
||||
return parseFloat(selRect.node.getAttribute(attr))
|
||||
}
|
||||
const draggedProps = {
|
||||
x: getSelAttr('x'),
|
||||
y: getSelAttr('y'),
|
||||
width: getSelAttr('width'),
|
||||
height: getSelAttr('height'),
|
||||
}
|
||||
w.globals.selection = draggedProps
|
||||
// update selection ends
|
||||
|
||||
if (
|
||||
typeof w.config.chart.events.selection === 'function' &&
|
||||
w.globals.selectionEnabled
|
||||
) {
|
||||
// a small debouncer is required when resizing to avoid freezing the chart
|
||||
clearTimeout(this.w.globals.selectionResizeTimer)
|
||||
this.w.globals.selectionResizeTimer = window.setTimeout(() => {
|
||||
const gridRectDim = this.gridRect.getBoundingClientRect()
|
||||
const selectionRect = selRect.node.getBoundingClientRect()
|
||||
|
||||
let minX, maxX, minY, maxY
|
||||
|
||||
if (!w.globals.isRangeBar) {
|
||||
// original code is in the IF. rangeBar exception is in the ELSE.
|
||||
minX =
|
||||
w.globals.xAxisScale.niceMin +
|
||||
(selectionRect.left - gridRectDim.left) * xyRatios.xRatio
|
||||
maxX =
|
||||
w.globals.xAxisScale.niceMin +
|
||||
(selectionRect.right - gridRectDim.left) * xyRatios.xRatio
|
||||
|
||||
minY =
|
||||
w.globals.yAxisScale[0].niceMin +
|
||||
(gridRectDim.bottom - selectionRect.bottom) * xyRatios.yRatio[0]
|
||||
maxY =
|
||||
w.globals.yAxisScale[0].niceMax -
|
||||
(selectionRect.top - gridRectDim.top) * xyRatios.yRatio[0]
|
||||
} else {
|
||||
// rangeBars use x as the category, and y as the datetime data. // find data in y axis and use Y ratio
|
||||
minX =
|
||||
w.globals.yAxisScale[0].niceMin +
|
||||
(selectionRect.left - gridRectDim.left) * xyRatios.invertedYRatio
|
||||
maxX =
|
||||
w.globals.yAxisScale[0].niceMin +
|
||||
(selectionRect.right - gridRectDim.left) * xyRatios.invertedYRatio
|
||||
|
||||
minY = 0 // there is no y min/max with rangebars (it uses categories, not numeric data), so use dummy values
|
||||
maxY = 1
|
||||
}
|
||||
|
||||
const xyAxis = {
|
||||
xaxis: {
|
||||
min: minX,
|
||||
max: maxX,
|
||||
},
|
||||
yaxis: {
|
||||
min: minY,
|
||||
max: maxY,
|
||||
},
|
||||
}
|
||||
w.config.chart.events.selection(this.ctx, xyAxis)
|
||||
|
||||
if (
|
||||
w.config.chart.brush.enabled &&
|
||||
w.config.chart.events.brushScrolled !== undefined
|
||||
) {
|
||||
w.config.chart.events.brushScrolled(this.ctx, xyAxis)
|
||||
}
|
||||
}, timerInterval)
|
||||
}
|
||||
}
|
||||
|
||||
selectionDrawn({ context, zoomtype }) {
|
||||
const w = this.w
|
||||
const me = context
|
||||
const xyRatios = this.xyRatios
|
||||
const toolbar = this.ctx.toolbar
|
||||
|
||||
if (me.startX > me.endX) {
|
||||
let tempX = me.startX
|
||||
me.startX = me.endX
|
||||
me.endX = tempX
|
||||
}
|
||||
if (me.startY > me.endY) {
|
||||
let tempY = me.startY
|
||||
me.startY = me.endY
|
||||
me.endY = tempY
|
||||
}
|
||||
|
||||
let xLowestValue = undefined
|
||||
let xHighestValue = undefined
|
||||
|
||||
if (!w.globals.isRangeBar) {
|
||||
xLowestValue = w.globals.xAxisScale.niceMin + me.startX * xyRatios.xRatio
|
||||
xHighestValue = w.globals.xAxisScale.niceMin + me.endX * xyRatios.xRatio
|
||||
} else {
|
||||
xLowestValue =
|
||||
w.globals.yAxisScale[0].niceMin + me.startX * xyRatios.invertedYRatio
|
||||
xHighestValue =
|
||||
w.globals.yAxisScale[0].niceMin + me.endX * xyRatios.invertedYRatio
|
||||
}
|
||||
|
||||
// TODO: we will consider the 1st y axis values here for getting highest and lowest y
|
||||
let yHighestValue = []
|
||||
let yLowestValue = []
|
||||
|
||||
w.config.yaxis.forEach((yaxe, index) => {
|
||||
yHighestValue.push(
|
||||
w.globals.yAxisScale[index].niceMax - xyRatios.yRatio[index] * me.startY
|
||||
)
|
||||
yLowestValue.push(
|
||||
w.globals.yAxisScale[index].niceMax - xyRatios.yRatio[index] * me.endY
|
||||
)
|
||||
})
|
||||
|
||||
if (
|
||||
me.dragged &&
|
||||
(me.dragX > 10 || me.dragY > 10) &&
|
||||
xLowestValue !== xHighestValue
|
||||
) {
|
||||
if (w.globals.zoomEnabled) {
|
||||
let yaxis = Utils.clone(w.globals.initialConfig.yaxis)
|
||||
let xaxis = Utils.clone(w.globals.initialConfig.xaxis)
|
||||
|
||||
w.globals.zoomed = true
|
||||
|
||||
if (w.config.xaxis.convertedCatToNumeric) {
|
||||
xLowestValue = Math.floor(xLowestValue)
|
||||
xHighestValue = Math.floor(xHighestValue)
|
||||
|
||||
if (xLowestValue < 1) {
|
||||
xLowestValue = 1
|
||||
xHighestValue = w.globals.dataPoints
|
||||
}
|
||||
|
||||
if (xHighestValue - xLowestValue < 2) {
|
||||
xHighestValue = xLowestValue + 1
|
||||
}
|
||||
}
|
||||
|
||||
if (zoomtype === 'xy' || zoomtype === 'x') {
|
||||
xaxis = {
|
||||
min: xLowestValue,
|
||||
max: xHighestValue,
|
||||
}
|
||||
}
|
||||
|
||||
if (zoomtype === 'xy' || zoomtype === 'y') {
|
||||
yaxis.forEach((yaxe, index) => {
|
||||
yaxis[index].min = yLowestValue[index]
|
||||
yaxis[index].max = yHighestValue[index]
|
||||
})
|
||||
}
|
||||
|
||||
if (w.config.chart.zoom.autoScaleYaxis) {
|
||||
const scale = new Scales(me.ctx)
|
||||
yaxis = scale.autoScaleY(me.ctx, yaxis, {
|
||||
xaxis,
|
||||
})
|
||||
}
|
||||
|
||||
if (toolbar) {
|
||||
let beforeZoomRange = toolbar.getBeforeZoomRange(xaxis, yaxis)
|
||||
if (beforeZoomRange) {
|
||||
xaxis = beforeZoomRange.xaxis ? beforeZoomRange.xaxis : xaxis
|
||||
yaxis = beforeZoomRange.yaxis ? beforeZoomRange.yaxis : yaxis
|
||||
}
|
||||
}
|
||||
|
||||
let options = {
|
||||
xaxis,
|
||||
}
|
||||
|
||||
if (!w.config.chart.group) {
|
||||
// if chart in a group, prevent yaxis update here
|
||||
// fix issue #650
|
||||
options.yaxis = yaxis
|
||||
}
|
||||
me.ctx.updateHelpers._updateOptions(
|
||||
options,
|
||||
false,
|
||||
me.w.config.chart.animations.dynamicAnimation.enabled
|
||||
)
|
||||
|
||||
if (typeof w.config.chart.events.zoomed === 'function') {
|
||||
toolbar.zoomCallback(xaxis, yaxis)
|
||||
}
|
||||
} else if (w.globals.selectionEnabled) {
|
||||
let yaxis = null
|
||||
let xaxis = null
|
||||
xaxis = {
|
||||
min: xLowestValue,
|
||||
max: xHighestValue,
|
||||
}
|
||||
if (zoomtype === 'xy' || zoomtype === 'y') {
|
||||
yaxis = Utils.clone(w.config.yaxis)
|
||||
yaxis.forEach((yaxe, index) => {
|
||||
yaxis[index].min = yLowestValue[index]
|
||||
yaxis[index].max = yHighestValue[index]
|
||||
})
|
||||
}
|
||||
|
||||
w.globals.selection = me.selection
|
||||
if (typeof w.config.chart.events.selection === 'function') {
|
||||
w.config.chart.events.selection(me.ctx, {
|
||||
xaxis,
|
||||
yaxis,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panDragging({ context }) {
|
||||
const w = this.w
|
||||
let me = context
|
||||
|
||||
// check to make sure there is data to compare against
|
||||
if (typeof w.globals.lastClientPosition.x !== 'undefined') {
|
||||
// get the change from last position to this position
|
||||
const deltaX = w.globals.lastClientPosition.x - me.clientX
|
||||
const deltaY = w.globals.lastClientPosition.y - me.clientY
|
||||
|
||||
// check which direction had the highest amplitude and then figure out direction by checking if the value is greater or less than zero
|
||||
if (Math.abs(deltaX) > Math.abs(deltaY) && deltaX > 0) {
|
||||
this.moveDirection = 'left'
|
||||
} else if (Math.abs(deltaX) > Math.abs(deltaY) && deltaX < 0) {
|
||||
this.moveDirection = 'right'
|
||||
} else if (Math.abs(deltaY) > Math.abs(deltaX) && deltaY > 0) {
|
||||
this.moveDirection = 'up'
|
||||
} else if (Math.abs(deltaY) > Math.abs(deltaX) && deltaY < 0) {
|
||||
this.moveDirection = 'down'
|
||||
}
|
||||
}
|
||||
|
||||
// set the new last position to the current for next time (to get the position of drag)
|
||||
w.globals.lastClientPosition = {
|
||||
x: me.clientX,
|
||||
y: me.clientY,
|
||||
}
|
||||
|
||||
let xLowestValue = w.globals.isRangeBar ? w.globals.minY : w.globals.minX
|
||||
|
||||
let xHighestValue = w.globals.isRangeBar ? w.globals.maxY : w.globals.maxX
|
||||
|
||||
// on a category, we don't pan continuosly as it causes bugs
|
||||
if (!w.config.xaxis.convertedCatToNumeric) {
|
||||
me.panScrolled(xLowestValue, xHighestValue)
|
||||
}
|
||||
}
|
||||
|
||||
delayedPanScrolled() {
|
||||
const w = this.w
|
||||
|
||||
let newMinX = w.globals.minX
|
||||
let newMaxX = w.globals.maxX
|
||||
const centerX = (w.globals.maxX - w.globals.minX) / 2
|
||||
|
||||
if (this.moveDirection === 'left') {
|
||||
newMinX = w.globals.minX + centerX
|
||||
newMaxX = w.globals.maxX + centerX
|
||||
} else if (this.moveDirection === 'right') {
|
||||
newMinX = w.globals.minX - centerX
|
||||
newMaxX = w.globals.maxX - centerX
|
||||
}
|
||||
|
||||
newMinX = Math.floor(newMinX)
|
||||
newMaxX = Math.floor(newMaxX)
|
||||
this.updateScrolledChart(
|
||||
{ xaxis: { min: newMinX, max: newMaxX } },
|
||||
newMinX,
|
||||
newMaxX
|
||||
)
|
||||
}
|
||||
|
||||
panScrolled(xLowestValue, xHighestValue) {
|
||||
const w = this.w
|
||||
|
||||
const xyRatios = this.xyRatios
|
||||
let yaxis = Utils.clone(w.globals.initialConfig.yaxis)
|
||||
|
||||
let xRatio = xyRatios.xRatio
|
||||
let minX = w.globals.minX
|
||||
let maxX = w.globals.maxX
|
||||
if (w.globals.isRangeBar) {
|
||||
xRatio = xyRatios.invertedYRatio
|
||||
minX = w.globals.minY
|
||||
maxX = w.globals.maxY
|
||||
}
|
||||
|
||||
if (this.moveDirection === 'left') {
|
||||
xLowestValue = minX + (w.globals.gridWidth / 15) * xRatio
|
||||
xHighestValue = maxX + (w.globals.gridWidth / 15) * xRatio
|
||||
} else if (this.moveDirection === 'right') {
|
||||
xLowestValue = minX - (w.globals.gridWidth / 15) * xRatio
|
||||
xHighestValue = maxX - (w.globals.gridWidth / 15) * xRatio
|
||||
}
|
||||
|
||||
if (!w.globals.isRangeBar) {
|
||||
if (
|
||||
xLowestValue < w.globals.initialMinX ||
|
||||
xHighestValue > w.globals.initialMaxX
|
||||
) {
|
||||
xLowestValue = minX
|
||||
xHighestValue = maxX
|
||||
}
|
||||
}
|
||||
|
||||
let xaxis = {
|
||||
min: xLowestValue,
|
||||
max: xHighestValue,
|
||||
}
|
||||
|
||||
if (w.config.chart.zoom.autoScaleYaxis) {
|
||||
const scale = new Scales(this.ctx)
|
||||
yaxis = scale.autoScaleY(this.ctx, yaxis, {
|
||||
xaxis,
|
||||
})
|
||||
}
|
||||
|
||||
let options = {
|
||||
xaxis: {
|
||||
min: xLowestValue,
|
||||
max: xHighestValue,
|
||||
},
|
||||
}
|
||||
|
||||
if (!w.config.chart.group) {
|
||||
// if chart in a group, prevent yaxis update here
|
||||
// fix issue #650
|
||||
options.yaxis = yaxis
|
||||
}
|
||||
|
||||
this.updateScrolledChart(options, xLowestValue, xHighestValue)
|
||||
}
|
||||
|
||||
updateScrolledChart(options, xLowestValue, xHighestValue) {
|
||||
const w = this.w
|
||||
|
||||
this.ctx.updateHelpers._updateOptions(options, false, false)
|
||||
|
||||
if (typeof w.config.chart.events.scrolled === 'function') {
|
||||
w.config.chart.events.scrolled(this.ctx, {
|
||||
xaxis: {
|
||||
min: xLowestValue,
|
||||
max: xHighestValue,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
+321
@@ -0,0 +1,321 @@
|
||||
import Graphics from '../../modules/Graphics'
|
||||
import Utils from '../../utils/Utils'
|
||||
import Helpers from './Helpers'
|
||||
import XAxisAnnotations from './XAxisAnnotations'
|
||||
import YAxisAnnotations from './YAxisAnnotations'
|
||||
import PointsAnnotations from './PointsAnnotations'
|
||||
import Options from './../settings/Options'
|
||||
|
||||
/**
|
||||
* ApexCharts Annotations Class for drawing lines/rects on both xaxis and yaxis.
|
||||
*
|
||||
* @module Annotations
|
||||
**/
|
||||
export default class Annotations {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
this.graphics = new Graphics(this.ctx)
|
||||
|
||||
if (this.w.globals.isBarHorizontal) {
|
||||
this.invertAxis = true
|
||||
}
|
||||
|
||||
this.helpers = new Helpers(this)
|
||||
this.xAxisAnnotations = new XAxisAnnotations(this)
|
||||
this.yAxisAnnotations = new YAxisAnnotations(this)
|
||||
this.pointsAnnotations = new PointsAnnotations(this)
|
||||
|
||||
if (this.w.globals.isBarHorizontal && this.w.config.yaxis[0].reversed) {
|
||||
this.inversedReversedAxis = true
|
||||
}
|
||||
|
||||
this.xDivision = this.w.globals.gridWidth / this.w.globals.dataPoints
|
||||
}
|
||||
|
||||
drawAxesAnnotations() {
|
||||
const w = this.w
|
||||
if (w.globals.axisCharts) {
|
||||
let yAnnotations = this.yAxisAnnotations.drawYAxisAnnotations()
|
||||
let xAnnotations = this.xAxisAnnotations.drawXAxisAnnotations()
|
||||
let pointAnnotations = this.pointsAnnotations.drawPointAnnotations()
|
||||
|
||||
const initialAnim = w.config.chart.animations.enabled
|
||||
|
||||
const annoArray = [yAnnotations, xAnnotations, pointAnnotations]
|
||||
const annoElArray = [
|
||||
xAnnotations.node,
|
||||
yAnnotations.node,
|
||||
pointAnnotations.node,
|
||||
]
|
||||
for (let i = 0; i < 3; i++) {
|
||||
w.globals.dom.elGraphical.add(annoArray[i])
|
||||
if (initialAnim && !w.globals.resized && !w.globals.dataChanged) {
|
||||
// fixes apexcharts/apexcharts.js#685
|
||||
if (
|
||||
w.config.chart.type !== 'scatter' &&
|
||||
w.config.chart.type !== 'bubble' &&
|
||||
w.globals.dataPoints > 1
|
||||
) {
|
||||
annoElArray[i].classList.add('apexcharts-element-hidden')
|
||||
}
|
||||
}
|
||||
w.globals.delayedElements.push({ el: annoElArray[i], index: 0 })
|
||||
}
|
||||
|
||||
// background sizes needs to be calculated after text is drawn, so calling them last
|
||||
this.helpers.annotationsBackground()
|
||||
}
|
||||
}
|
||||
|
||||
drawImageAnnos() {
|
||||
const w = this.w
|
||||
|
||||
w.config.annotations.images.map((s, index) => {
|
||||
this.addImage(s, index)
|
||||
})
|
||||
}
|
||||
|
||||
drawTextAnnos() {
|
||||
const w = this.w
|
||||
|
||||
w.config.annotations.texts.map((t, index) => {
|
||||
this.addText(t, index)
|
||||
})
|
||||
}
|
||||
|
||||
addXaxisAnnotation(anno, parent, index) {
|
||||
this.xAxisAnnotations.addXaxisAnnotation(anno, parent, index)
|
||||
}
|
||||
|
||||
addYaxisAnnotation(anno, parent, index) {
|
||||
this.yAxisAnnotations.addYaxisAnnotation(anno, parent, index)
|
||||
}
|
||||
|
||||
addPointAnnotation(anno, parent, index) {
|
||||
this.pointsAnnotations.addPointAnnotation(anno, parent, index)
|
||||
}
|
||||
|
||||
addText(params, index) {
|
||||
const {
|
||||
x,
|
||||
y,
|
||||
text,
|
||||
textAnchor,
|
||||
foreColor,
|
||||
fontSize,
|
||||
fontFamily,
|
||||
fontWeight,
|
||||
cssClass,
|
||||
backgroundColor,
|
||||
borderWidth,
|
||||
strokeDashArray,
|
||||
borderRadius,
|
||||
borderColor,
|
||||
appendTo = '.apexcharts-svg',
|
||||
paddingLeft = 4,
|
||||
paddingRight = 4,
|
||||
paddingBottom = 2,
|
||||
paddingTop = 2,
|
||||
} = params
|
||||
|
||||
const w = this.w
|
||||
|
||||
let elText = this.graphics.drawText({
|
||||
x,
|
||||
y,
|
||||
text,
|
||||
textAnchor: textAnchor || 'start',
|
||||
fontSize: fontSize || '12px',
|
||||
fontWeight: fontWeight || 'regular',
|
||||
fontFamily: fontFamily || w.config.chart.fontFamily,
|
||||
foreColor: foreColor || w.config.chart.foreColor,
|
||||
cssClass: 'apexcharts-text ' + cssClass ? cssClass : '',
|
||||
})
|
||||
|
||||
const parent = w.globals.dom.baseEl.querySelector(appendTo)
|
||||
if (parent) {
|
||||
parent.appendChild(elText.node)
|
||||
}
|
||||
|
||||
const textRect = elText.bbox()
|
||||
|
||||
if (text) {
|
||||
const elRect = this.graphics.drawRect(
|
||||
textRect.x - paddingLeft,
|
||||
textRect.y - paddingTop,
|
||||
textRect.width + paddingLeft + paddingRight,
|
||||
textRect.height + paddingBottom + paddingTop,
|
||||
borderRadius,
|
||||
backgroundColor ? backgroundColor : 'transparent',
|
||||
1,
|
||||
borderWidth,
|
||||
borderColor,
|
||||
strokeDashArray
|
||||
)
|
||||
|
||||
parent.insertBefore(elRect.node, elText.node)
|
||||
}
|
||||
}
|
||||
|
||||
addImage(params, index) {
|
||||
const w = this.w
|
||||
|
||||
const {
|
||||
path,
|
||||
x = 0,
|
||||
y = 0,
|
||||
width = 20,
|
||||
height = 20,
|
||||
appendTo = '.apexcharts-svg',
|
||||
} = params
|
||||
|
||||
let img = w.globals.dom.Paper.image(path)
|
||||
img.size(width, height).move(x, y)
|
||||
|
||||
const parent = w.globals.dom.baseEl.querySelector(appendTo)
|
||||
if (parent) {
|
||||
parent.appendChild(img.node)
|
||||
}
|
||||
|
||||
return img
|
||||
}
|
||||
|
||||
// The addXaxisAnnotation method requires a parent class, and user calling this method externally on the chart instance may not specify parent, hence a different method
|
||||
addXaxisAnnotationExternal(params, pushToMemory, context) {
|
||||
this.addAnnotationExternal({
|
||||
params,
|
||||
pushToMemory,
|
||||
context,
|
||||
type: 'xaxis',
|
||||
contextMethod: context.addXaxisAnnotation,
|
||||
})
|
||||
return context
|
||||
}
|
||||
|
||||
addYaxisAnnotationExternal(params, pushToMemory, context) {
|
||||
this.addAnnotationExternal({
|
||||
params,
|
||||
pushToMemory,
|
||||
context,
|
||||
type: 'yaxis',
|
||||
contextMethod: context.addYaxisAnnotation,
|
||||
})
|
||||
return context
|
||||
}
|
||||
|
||||
addPointAnnotationExternal(params, pushToMemory, context) {
|
||||
if (typeof this.invertAxis === 'undefined') {
|
||||
this.invertAxis = context.w.globals.isBarHorizontal
|
||||
}
|
||||
|
||||
this.addAnnotationExternal({
|
||||
params,
|
||||
pushToMemory,
|
||||
context,
|
||||
type: 'point',
|
||||
contextMethod: context.addPointAnnotation,
|
||||
})
|
||||
return context
|
||||
}
|
||||
|
||||
addAnnotationExternal({
|
||||
params,
|
||||
pushToMemory,
|
||||
context,
|
||||
type,
|
||||
contextMethod,
|
||||
}) {
|
||||
const me = context
|
||||
const w = me.w
|
||||
const parent = w.globals.dom.baseEl.querySelector(
|
||||
`.apexcharts-${type}-annotations`
|
||||
)
|
||||
const index = parent.childNodes.length + 1
|
||||
|
||||
const options = new Options()
|
||||
const axesAnno = Object.assign(
|
||||
{},
|
||||
type === 'xaxis'
|
||||
? options.xAxisAnnotation
|
||||
: type === 'yaxis'
|
||||
? options.yAxisAnnotation
|
||||
: options.pointAnnotation
|
||||
)
|
||||
|
||||
const anno = Utils.extend(axesAnno, params)
|
||||
|
||||
switch (type) {
|
||||
case 'xaxis':
|
||||
this.addXaxisAnnotation(anno, parent, index)
|
||||
break
|
||||
case 'yaxis':
|
||||
this.addYaxisAnnotation(anno, parent, index)
|
||||
break
|
||||
case 'point':
|
||||
this.addPointAnnotation(anno, parent, index)
|
||||
break
|
||||
}
|
||||
|
||||
// add background
|
||||
let axesAnnoLabel = w.globals.dom.baseEl.querySelector(
|
||||
`.apexcharts-${type}-annotations .apexcharts-${type}-annotation-label[rel='${index}']`
|
||||
)
|
||||
const elRect = this.helpers.addBackgroundToAnno(axesAnnoLabel, anno)
|
||||
if (elRect) {
|
||||
parent.insertBefore(elRect.node, axesAnnoLabel)
|
||||
}
|
||||
|
||||
if (pushToMemory) {
|
||||
w.globals.memory.methodsToExec.push({
|
||||
context: me,
|
||||
id: anno.id ? anno.id : Utils.randomId(),
|
||||
method: contextMethod,
|
||||
label: 'addAnnotation',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
clearAnnotations(ctx) {
|
||||
const w = ctx.w
|
||||
let annos = w.globals.dom.baseEl.querySelectorAll(
|
||||
'.apexcharts-yaxis-annotations, .apexcharts-xaxis-annotations, .apexcharts-point-annotations'
|
||||
)
|
||||
|
||||
// annotations added externally should be cleared out too
|
||||
w.globals.memory.methodsToExec.map((m, i) => {
|
||||
if (m.label === 'addText' || m.label === 'addAnnotation') {
|
||||
w.globals.memory.methodsToExec.splice(i, 1)
|
||||
}
|
||||
})
|
||||
|
||||
annos = Utils.listToArray(annos)
|
||||
|
||||
// delete the DOM elements
|
||||
Array.prototype.forEach.call(annos, (a) => {
|
||||
while (a.firstChild) {
|
||||
a.removeChild(a.firstChild)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
removeAnnotation(ctx, id) {
|
||||
const w = ctx.w
|
||||
let annos = w.globals.dom.baseEl.querySelectorAll(`.${id}`)
|
||||
|
||||
if (annos) {
|
||||
w.globals.memory.methodsToExec.map((m, i) => {
|
||||
if (m.id === id) {
|
||||
w.globals.memory.methodsToExec.splice(i, 1)
|
||||
}
|
||||
})
|
||||
|
||||
Array.prototype.forEach.call(annos, (a) => {
|
||||
a.parentElement.removeChild(a)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
+299
@@ -0,0 +1,299 @@
|
||||
import CoreUtils from '../CoreUtils'
|
||||
|
||||
export default class Helpers {
|
||||
constructor(annoCtx) {
|
||||
this.w = annoCtx.w
|
||||
this.annoCtx = annoCtx
|
||||
}
|
||||
|
||||
setOrientations(anno, annoIndex = null) {
|
||||
let w = this.w
|
||||
|
||||
if (anno.label.orientation === 'vertical') {
|
||||
const i = annoIndex !== null ? annoIndex : 0
|
||||
let xAnno = w.globals.dom.baseEl.querySelector(
|
||||
`.apexcharts-xaxis-annotations .apexcharts-xaxis-annotation-label[rel='${i}']`
|
||||
)
|
||||
|
||||
if (xAnno !== null) {
|
||||
const xAnnoCoord = xAnno.getBoundingClientRect()
|
||||
xAnno.setAttribute(
|
||||
'x',
|
||||
parseFloat(xAnno.getAttribute('x')) - xAnnoCoord.height + 4
|
||||
)
|
||||
|
||||
if (anno.label.position === 'top') {
|
||||
xAnno.setAttribute(
|
||||
'y',
|
||||
parseFloat(xAnno.getAttribute('y')) + xAnnoCoord.width
|
||||
)
|
||||
} else {
|
||||
xAnno.setAttribute(
|
||||
'y',
|
||||
parseFloat(xAnno.getAttribute('y')) - xAnnoCoord.width
|
||||
)
|
||||
}
|
||||
|
||||
let annoRotatingCenter = this.annoCtx.graphics.rotateAroundCenter(xAnno)
|
||||
const x = annoRotatingCenter.x
|
||||
const y = annoRotatingCenter.y
|
||||
|
||||
xAnno.setAttribute('transform', `rotate(-90 ${x} ${y})`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addBackgroundToAnno(annoEl, anno) {
|
||||
const w = this.w
|
||||
|
||||
if (
|
||||
!annoEl ||
|
||||
typeof anno.label.text === 'undefined' ||
|
||||
(typeof anno.label.text !== 'undefined' &&
|
||||
!String(anno.label.text).trim())
|
||||
)
|
||||
return null
|
||||
|
||||
const elGridRect = w.globals.dom.baseEl
|
||||
.querySelector('.apexcharts-grid')
|
||||
.getBoundingClientRect()
|
||||
|
||||
const coords = annoEl.getBoundingClientRect()
|
||||
|
||||
let pleft = anno.label.style.padding.left
|
||||
let pright = anno.label.style.padding.right
|
||||
let ptop = anno.label.style.padding.top
|
||||
let pbottom = anno.label.style.padding.bottom
|
||||
|
||||
if (anno.label.orientation === 'vertical') {
|
||||
ptop = anno.label.style.padding.left
|
||||
pbottom = anno.label.style.padding.right
|
||||
pleft = anno.label.style.padding.top
|
||||
pright = anno.label.style.padding.bottom
|
||||
}
|
||||
|
||||
const x1 = coords.left - elGridRect.left - pleft
|
||||
const y1 = coords.top - elGridRect.top - ptop
|
||||
const elRect = this.annoCtx.graphics.drawRect(
|
||||
x1 - w.globals.barPadForNumericAxis,
|
||||
y1,
|
||||
coords.width + pleft + pright,
|
||||
coords.height + ptop + pbottom,
|
||||
anno.label.borderRadius,
|
||||
anno.label.style.background,
|
||||
1,
|
||||
anno.label.borderWidth,
|
||||
anno.label.borderColor,
|
||||
0
|
||||
)
|
||||
|
||||
if (anno.id) {
|
||||
// don't escapeString for this ID as it causes duplicate rects
|
||||
elRect.node.classList.add(anno.id)
|
||||
}
|
||||
|
||||
return elRect
|
||||
}
|
||||
|
||||
annotationsBackground() {
|
||||
const w = this.w
|
||||
|
||||
const add = (anno, i, type) => {
|
||||
let annoLabel = w.globals.dom.baseEl.querySelector(
|
||||
`.apexcharts-${type}-annotations .apexcharts-${type}-annotation-label[rel='${i}']`
|
||||
)
|
||||
|
||||
if (annoLabel) {
|
||||
const parent = annoLabel.parentNode
|
||||
const elRect = this.addBackgroundToAnno(annoLabel, anno)
|
||||
|
||||
if (elRect) {
|
||||
parent.insertBefore(elRect.node, annoLabel)
|
||||
|
||||
if (anno.label.mouseEnter) {
|
||||
elRect.node.addEventListener(
|
||||
'mouseenter',
|
||||
anno.label.mouseEnter.bind(this, anno)
|
||||
)
|
||||
}
|
||||
if (anno.label.mouseLeave) {
|
||||
elRect.node.addEventListener(
|
||||
'mouseleave',
|
||||
anno.label.mouseLeave.bind(this, anno)
|
||||
)
|
||||
}
|
||||
if (anno.label.click) {
|
||||
elRect.node.addEventListener(
|
||||
'click',
|
||||
anno.label.click.bind(this, anno)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
w.config.annotations.xaxis.map((anno, i) => {
|
||||
add(anno, i, 'xaxis')
|
||||
})
|
||||
|
||||
w.config.annotations.yaxis.map((anno, i) => {
|
||||
add(anno, i, 'yaxis')
|
||||
})
|
||||
|
||||
w.config.annotations.points.map((anno, i) => {
|
||||
add(anno, i, 'point')
|
||||
})
|
||||
}
|
||||
|
||||
getY1Y2(type, anno) {
|
||||
let y = type === 'y1' ? anno.y : anno.y2
|
||||
let yP
|
||||
|
||||
const w = this.w
|
||||
if (this.annoCtx.invertAxis) {
|
||||
let catIndex = w.globals.labels.indexOf(y)
|
||||
if (w.config.xaxis.convertedCatToNumeric) {
|
||||
catIndex = w.globals.categoryLabels.indexOf(y)
|
||||
}
|
||||
const xLabel = w.globals.dom.baseEl.querySelector(
|
||||
'.apexcharts-yaxis-texts-g text:nth-child(' + (catIndex + 1) + ')'
|
||||
)
|
||||
if (xLabel) {
|
||||
yP = parseFloat(xLabel.getAttribute('y'))
|
||||
}
|
||||
|
||||
if (typeof anno.seriesIndex !== 'undefined') {
|
||||
if (w.globals.barHeight) {
|
||||
yP =
|
||||
yP -
|
||||
(w.globals.barHeight / 2) * (w.globals.series.length - 1) +
|
||||
w.globals.barHeight * anno.seriesIndex
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let yPos
|
||||
if (w.config.yaxis[anno.yAxisIndex].logarithmic) {
|
||||
const coreUtils = new CoreUtils(this.annoCtx.ctx)
|
||||
y = coreUtils.getLogVal(y, anno.yAxisIndex)
|
||||
yPos = y / w.globals.yLogRatio[anno.yAxisIndex]
|
||||
} else {
|
||||
yPos =
|
||||
(y - w.globals.minYArr[anno.yAxisIndex]) /
|
||||
(w.globals.yRange[anno.yAxisIndex] / w.globals.gridHeight)
|
||||
}
|
||||
yP = w.globals.gridHeight - yPos
|
||||
|
||||
if (anno.marker && (anno.y === undefined || anno.y === null)) {
|
||||
// point annotation
|
||||
yP = 0
|
||||
}
|
||||
|
||||
if (
|
||||
w.config.yaxis[anno.yAxisIndex] &&
|
||||
w.config.yaxis[anno.yAxisIndex].reversed
|
||||
) {
|
||||
yP = yPos
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof y === 'string' && y.indexOf('px') > -1) {
|
||||
yP = parseFloat(y)
|
||||
}
|
||||
|
||||
return yP
|
||||
}
|
||||
|
||||
getX1X2(type, anno) {
|
||||
const w = this.w
|
||||
let min = this.annoCtx.invertAxis ? w.globals.minY : w.globals.minX
|
||||
let max = this.annoCtx.invertAxis ? w.globals.maxY : w.globals.maxX
|
||||
const range = this.annoCtx.invertAxis
|
||||
? w.globals.yRange[0]
|
||||
: w.globals.xRange
|
||||
|
||||
let x1 = (anno.x - min) / (range / w.globals.gridWidth)
|
||||
|
||||
if (this.annoCtx.inversedReversedAxis) {
|
||||
x1 = (max - anno.x) / (range / w.globals.gridWidth)
|
||||
}
|
||||
|
||||
if (
|
||||
(w.config.xaxis.type === 'category' ||
|
||||
w.config.xaxis.convertedCatToNumeric) &&
|
||||
!this.annoCtx.invertAxis &&
|
||||
!w.globals.dataFormatXNumeric
|
||||
) {
|
||||
x1 = this.getStringX(anno.x)
|
||||
}
|
||||
|
||||
let x2 = (anno.x2 - min) / (range / w.globals.gridWidth)
|
||||
|
||||
if (this.annoCtx.inversedReversedAxis) {
|
||||
x2 = (max - anno.x2) / (range / w.globals.gridWidth)
|
||||
}
|
||||
if (
|
||||
(w.config.xaxis.type === 'category' ||
|
||||
w.config.xaxis.convertedCatToNumeric) &&
|
||||
!this.annoCtx.invertAxis &&
|
||||
!w.globals.dataFormatXNumeric
|
||||
) {
|
||||
x2 = this.getStringX(anno.x2)
|
||||
}
|
||||
|
||||
if ((anno.x === undefined || anno.x === null) && anno.marker) {
|
||||
// point annotation in a horizontal chart
|
||||
x1 = w.globals.gridWidth
|
||||
}
|
||||
|
||||
if (
|
||||
type === 'x1' &&
|
||||
typeof anno.x === 'string' &&
|
||||
anno.x.indexOf('px') > -1
|
||||
) {
|
||||
x1 = parseFloat(anno.x)
|
||||
}
|
||||
|
||||
if (
|
||||
type === 'x2' &&
|
||||
typeof anno.x2 === 'string' &&
|
||||
anno.x2.indexOf('px') > -1
|
||||
) {
|
||||
x2 = parseFloat(anno.x2)
|
||||
}
|
||||
|
||||
if (typeof anno.seriesIndex !== 'undefined') {
|
||||
if (w.globals.barWidth && !this.annoCtx.invertAxis) {
|
||||
x1 =
|
||||
x1 -
|
||||
(w.globals.barWidth / 2) * (w.globals.series.length - 1) +
|
||||
w.globals.barWidth * anno.seriesIndex
|
||||
}
|
||||
}
|
||||
|
||||
return type === 'x1' ? x1 : x2
|
||||
}
|
||||
|
||||
getStringX(x) {
|
||||
const w = this.w
|
||||
let rX = x
|
||||
|
||||
if (
|
||||
w.config.xaxis.convertedCatToNumeric &&
|
||||
w.globals.categoryLabels.length
|
||||
) {
|
||||
x = w.globals.categoryLabels.indexOf(x) + 1
|
||||
}
|
||||
|
||||
let catIndex = w.globals.labels.indexOf(x)
|
||||
|
||||
const xLabel = w.globals.dom.baseEl.querySelector(
|
||||
'.apexcharts-xaxis-texts-g text:nth-child(' + (catIndex + 1) + ')'
|
||||
)
|
||||
|
||||
if (xLabel) {
|
||||
rX = parseFloat(xLabel.getAttribute('x'))
|
||||
}
|
||||
|
||||
return rX
|
||||
}
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
import Utils from '../../utils/Utils'
|
||||
import Helpers from './Helpers'
|
||||
|
||||
export default class PointAnnotations {
|
||||
constructor(annoCtx) {
|
||||
this.w = annoCtx.w
|
||||
this.annoCtx = annoCtx
|
||||
this.helpers = new Helpers(this.annoCtx)
|
||||
}
|
||||
|
||||
addPointAnnotation(anno, parent, index) {
|
||||
const w = this.w
|
||||
|
||||
let x = this.helpers.getX1X2('x1', anno)
|
||||
let y = this.helpers.getY1Y2('y1', anno)
|
||||
|
||||
if (!Utils.isNumber(x)) return
|
||||
|
||||
let optsPoints = {
|
||||
pSize: anno.marker.size,
|
||||
pointStrokeWidth: anno.marker.strokeWidth,
|
||||
pointFillColor: anno.marker.fillColor,
|
||||
pointStrokeColor: anno.marker.strokeColor,
|
||||
shape: anno.marker.shape,
|
||||
pRadius: anno.marker.radius,
|
||||
class: `apexcharts-point-annotation-marker ${anno.marker.cssClass} ${
|
||||
anno.id ? anno.id : ''
|
||||
}`,
|
||||
}
|
||||
|
||||
let point = this.annoCtx.graphics.drawMarker(
|
||||
x + anno.marker.offsetX,
|
||||
y + anno.marker.offsetY,
|
||||
optsPoints
|
||||
)
|
||||
|
||||
parent.appendChild(point.node)
|
||||
|
||||
const text = anno.label.text ? anno.label.text : ''
|
||||
|
||||
let elText = this.annoCtx.graphics.drawText({
|
||||
x: x + anno.label.offsetX,
|
||||
y:
|
||||
y +
|
||||
anno.label.offsetY -
|
||||
anno.marker.size -
|
||||
parseFloat(anno.label.style.fontSize) / 1.6,
|
||||
text,
|
||||
textAnchor: anno.label.textAnchor,
|
||||
fontSize: anno.label.style.fontSize,
|
||||
fontFamily: anno.label.style.fontFamily,
|
||||
fontWeight: anno.label.style.fontWeight,
|
||||
foreColor: anno.label.style.color,
|
||||
cssClass: `apexcharts-point-annotation-label ${
|
||||
anno.label.style.cssClass
|
||||
} ${anno.id ? anno.id : ''}`,
|
||||
})
|
||||
|
||||
elText.attr({
|
||||
rel: index,
|
||||
})
|
||||
|
||||
parent.appendChild(elText.node)
|
||||
|
||||
// TODO: deprecate this as we will use custom
|
||||
if (anno.customSVG.SVG) {
|
||||
let g = this.annoCtx.graphics.group({
|
||||
class:
|
||||
'apexcharts-point-annotations-custom-svg ' + anno.customSVG.cssClass,
|
||||
})
|
||||
|
||||
g.attr({
|
||||
transform: `translate(${x + anno.customSVG.offsetX}, ${
|
||||
y + anno.customSVG.offsetY
|
||||
})`,
|
||||
})
|
||||
|
||||
g.node.innerHTML = anno.customSVG.SVG
|
||||
parent.appendChild(g.node)
|
||||
}
|
||||
|
||||
if (anno.image.path) {
|
||||
let imgWidth = anno.image.width ? anno.image.width : 20
|
||||
let imgHeight = anno.image.height ? anno.image.height : 20
|
||||
|
||||
point = this.annoCtx.addImage({
|
||||
x: x + anno.image.offsetX - imgWidth / 2,
|
||||
y: y + anno.image.offsetY - imgHeight / 2,
|
||||
width: imgWidth,
|
||||
height: imgHeight,
|
||||
path: anno.image.path,
|
||||
appendTo: '.apexcharts-point-annotations',
|
||||
})
|
||||
}
|
||||
|
||||
if (anno.mouseEnter) {
|
||||
point.node.addEventListener(
|
||||
'mouseenter',
|
||||
anno.mouseEnter.bind(this, anno)
|
||||
)
|
||||
}
|
||||
if (anno.mouseLeave) {
|
||||
point.node.addEventListener(
|
||||
'mouseleave',
|
||||
anno.mouseLeave.bind(this, anno)
|
||||
)
|
||||
}
|
||||
if (anno.click) {
|
||||
point.node.addEventListener('click', anno.click.bind(this, anno))
|
||||
}
|
||||
}
|
||||
|
||||
drawPointAnnotations() {
|
||||
let w = this.w
|
||||
|
||||
let elg = this.annoCtx.graphics.group({
|
||||
class: 'apexcharts-point-annotations',
|
||||
})
|
||||
|
||||
w.config.annotations.points.map((anno, index) => {
|
||||
this.addPointAnnotation(anno, elg.node, index)
|
||||
})
|
||||
|
||||
return elg
|
||||
}
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
import Utils from '../../utils/Utils'
|
||||
import Helpers from './Helpers'
|
||||
|
||||
export default class XAnnotations {
|
||||
constructor(annoCtx) {
|
||||
this.w = annoCtx.w
|
||||
this.annoCtx = annoCtx
|
||||
|
||||
this.invertAxis = this.annoCtx.invertAxis
|
||||
|
||||
this.helpers = new Helpers(this.annoCtx)
|
||||
}
|
||||
|
||||
addXaxisAnnotation(anno, parent, index) {
|
||||
let w = this.w
|
||||
|
||||
let x1 = this.helpers.getX1X2('x1', anno)
|
||||
let x2
|
||||
|
||||
const text = anno.label.text
|
||||
|
||||
let strokeDashArray = anno.strokeDashArray
|
||||
|
||||
if (!Utils.isNumber(x1)) return
|
||||
|
||||
if (anno.x2 === null || typeof anno.x2 === 'undefined') {
|
||||
let line = this.annoCtx.graphics.drawLine(
|
||||
x1 + anno.offsetX, // x1
|
||||
0 + anno.offsetY, // y1
|
||||
x1 + anno.offsetX, // x2
|
||||
w.globals.gridHeight + anno.offsetY, // y2
|
||||
anno.borderColor, // lineColor
|
||||
strokeDashArray, //dashArray
|
||||
anno.borderWidth
|
||||
)
|
||||
parent.appendChild(line.node)
|
||||
if (anno.id) {
|
||||
line.node.classList.add(anno.id)
|
||||
}
|
||||
} else {
|
||||
x2 = this.helpers.getX1X2('x2', anno)
|
||||
|
||||
if (x2 < x1) {
|
||||
let temp = x1
|
||||
x1 = x2
|
||||
x2 = temp
|
||||
}
|
||||
|
||||
let rect = this.annoCtx.graphics.drawRect(
|
||||
x1 + anno.offsetX, // x1
|
||||
0 + anno.offsetY, // y1
|
||||
x2 - x1, // x2
|
||||
w.globals.gridHeight + anno.offsetY, // y2
|
||||
0, // radius
|
||||
anno.fillColor, // color
|
||||
anno.opacity, // opacity,
|
||||
1, // strokeWidth
|
||||
anno.borderColor, // strokeColor
|
||||
strokeDashArray // stokeDashArray
|
||||
)
|
||||
rect.node.classList.add('apexcharts-annotation-rect')
|
||||
rect.attr('clip-path', `url(#gridRectMask${w.globals.cuid})`)
|
||||
parent.appendChild(rect.node)
|
||||
if (anno.id) {
|
||||
rect.node.classList.add(anno.id)
|
||||
}
|
||||
}
|
||||
|
||||
let textRects = this.annoCtx.graphics.getTextRects(
|
||||
text,
|
||||
parseFloat(anno.label.style.fontSize)
|
||||
)
|
||||
let textY =
|
||||
anno.label.position === 'top'
|
||||
? 4
|
||||
: anno.label.position === 'center'
|
||||
? w.globals.gridHeight / 2 +
|
||||
(anno.label.orientation === 'vertical' ? textRects.width / 2 : 0)
|
||||
: w.globals.gridHeight
|
||||
|
||||
let elText = this.annoCtx.graphics.drawText({
|
||||
x: x1 + anno.label.offsetX,
|
||||
y:
|
||||
textY +
|
||||
anno.label.offsetY -
|
||||
(anno.label.orientation === 'vertical'
|
||||
? anno.label.position === 'top'
|
||||
? textRects.width / 2 - 12
|
||||
: -textRects.width / 2
|
||||
: 0),
|
||||
text,
|
||||
textAnchor: anno.label.textAnchor,
|
||||
fontSize: anno.label.style.fontSize,
|
||||
fontFamily: anno.label.style.fontFamily,
|
||||
fontWeight: anno.label.style.fontWeight,
|
||||
foreColor: anno.label.style.color,
|
||||
cssClass: `apexcharts-xaxis-annotation-label ${
|
||||
anno.label.style.cssClass
|
||||
} ${anno.id ? anno.id : ''}`
|
||||
})
|
||||
|
||||
elText.attr({
|
||||
rel: index
|
||||
})
|
||||
|
||||
parent.appendChild(elText.node)
|
||||
|
||||
// after placing the annotations on svg, set any vertically placed annotations
|
||||
this.annoCtx.helpers.setOrientations(anno, index)
|
||||
}
|
||||
drawXAxisAnnotations() {
|
||||
let w = this.w
|
||||
|
||||
let elg = this.annoCtx.graphics.group({
|
||||
class: 'apexcharts-xaxis-annotations'
|
||||
})
|
||||
|
||||
w.config.annotations.xaxis.map((anno, index) => {
|
||||
this.addXaxisAnnotation(anno, elg.node, index)
|
||||
})
|
||||
|
||||
return elg
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
import Helpers from './Helpers'
|
||||
|
||||
export default class YAnnotations {
|
||||
constructor(annoCtx) {
|
||||
this.w = annoCtx.w
|
||||
this.annoCtx = annoCtx
|
||||
|
||||
this.helpers = new Helpers(this.annoCtx)
|
||||
}
|
||||
|
||||
addYaxisAnnotation(anno, parent, index) {
|
||||
let w = this.w
|
||||
|
||||
let strokeDashArray = anno.strokeDashArray
|
||||
|
||||
let y1 = this.helpers.getY1Y2('y1', anno)
|
||||
let y2
|
||||
|
||||
const text = anno.label.text
|
||||
|
||||
if (anno.y2 === null || typeof anno.y2 === 'undefined') {
|
||||
let line = this.annoCtx.graphics.drawLine(
|
||||
0 + anno.offsetX, // x1
|
||||
y1 + anno.offsetY, // y1
|
||||
this._getYAxisAnnotationWidth(anno), // x2
|
||||
y1 + anno.offsetY, // y2
|
||||
anno.borderColor, // lineColor
|
||||
strokeDashArray, // dashArray
|
||||
anno.borderWidth
|
||||
)
|
||||
parent.appendChild(line.node)
|
||||
if (anno.id) {
|
||||
line.node.classList.add(anno.id)
|
||||
}
|
||||
} else {
|
||||
y2 = this.helpers.getY1Y2('y2', anno)
|
||||
|
||||
if (y2 > y1) {
|
||||
let temp = y1
|
||||
y1 = y2
|
||||
y2 = temp
|
||||
}
|
||||
|
||||
let rect = this.annoCtx.graphics.drawRect(
|
||||
0 + anno.offsetX, // x1
|
||||
y2 + anno.offsetY, // y1
|
||||
this._getYAxisAnnotationWidth(anno), // x2
|
||||
y1 - y2, // y2
|
||||
0, // radius
|
||||
anno.fillColor, // color
|
||||
anno.opacity, // opacity,
|
||||
1, // strokeWidth
|
||||
anno.borderColor, // strokeColor
|
||||
strokeDashArray // stokeDashArray
|
||||
)
|
||||
rect.node.classList.add('apexcharts-annotation-rect')
|
||||
rect.attr('clip-path', `url(#gridRectMask${w.globals.cuid})`)
|
||||
|
||||
parent.appendChild(rect.node)
|
||||
if (anno.id) {
|
||||
rect.node.classList.add(anno.id)
|
||||
}
|
||||
}
|
||||
let textX =
|
||||
anno.label.position === 'right'
|
||||
? w.globals.gridWidth
|
||||
: anno.label.position === 'center'
|
||||
? w.globals.gridWidth / 2
|
||||
: 0
|
||||
|
||||
let elText = this.annoCtx.graphics.drawText({
|
||||
x: textX + anno.label.offsetX,
|
||||
y: (y2 != null ? y2 : y1) + anno.label.offsetY - 3,
|
||||
text,
|
||||
textAnchor: anno.label.textAnchor,
|
||||
fontSize: anno.label.style.fontSize,
|
||||
fontFamily: anno.label.style.fontFamily,
|
||||
fontWeight: anno.label.style.fontWeight,
|
||||
foreColor: anno.label.style.color,
|
||||
cssClass: `apexcharts-yaxis-annotation-label ${
|
||||
anno.label.style.cssClass
|
||||
} ${anno.id ? anno.id : ''}`
|
||||
})
|
||||
|
||||
elText.attr({
|
||||
rel: index
|
||||
})
|
||||
|
||||
parent.appendChild(elText.node)
|
||||
}
|
||||
|
||||
_getYAxisAnnotationWidth(anno) {
|
||||
// issue apexcharts.js#2009
|
||||
const w = this.w
|
||||
let width = w.globals.gridWidth
|
||||
if (anno.width.indexOf('%') > -1) {
|
||||
width = (w.globals.gridWidth * parseInt(anno.width, 10)) / 100
|
||||
} else {
|
||||
width = parseInt(anno.width, 10)
|
||||
}
|
||||
return width + anno.offsetX
|
||||
}
|
||||
|
||||
drawYAxisAnnotations() {
|
||||
let w = this.w
|
||||
|
||||
let elg = this.annoCtx.graphics.group({
|
||||
class: 'apexcharts-yaxis-annotations'
|
||||
})
|
||||
|
||||
w.config.annotations.yaxis.map((anno, index) => {
|
||||
this.addYaxisAnnotation(anno, elg.node, index)
|
||||
})
|
||||
|
||||
return elg
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import XAxis from './XAxis'
|
||||
import YAxis from './YAxis'
|
||||
|
||||
export default class Axes {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
}
|
||||
|
||||
drawAxis(type, elgrid) {
|
||||
let gl = this.w.globals
|
||||
let cnf = this.w.config
|
||||
|
||||
let xAxis = new XAxis(this.ctx, elgrid)
|
||||
let yAxis = new YAxis(this.ctx, elgrid)
|
||||
|
||||
if (gl.axisCharts && type !== 'radar') {
|
||||
let elXaxis, elYaxis
|
||||
|
||||
if (gl.isBarHorizontal) {
|
||||
elYaxis = yAxis.drawYaxisInversed(0)
|
||||
elXaxis = xAxis.drawXaxisInversed(0)
|
||||
|
||||
gl.dom.elGraphical.add(elXaxis)
|
||||
gl.dom.elGraphical.add(elYaxis)
|
||||
} else {
|
||||
elXaxis = xAxis.drawXaxis()
|
||||
gl.dom.elGraphical.add(elXaxis)
|
||||
|
||||
cnf.yaxis.map((yaxe, index) => {
|
||||
if (gl.ignoreYAxisIndexes.indexOf(index) === -1) {
|
||||
elYaxis = yAxis.drawYaxis(index)
|
||||
gl.dom.Paper.add(elYaxis)
|
||||
|
||||
if (this.w.config.grid.position === 'back') {
|
||||
const inner = gl.dom.Paper.children()[1]
|
||||
inner.remove()
|
||||
gl.dom.Paper.add(inner)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
import Formatters from '../Formatters'
|
||||
import Graphics from '../Graphics'
|
||||
import CoreUtils from '../CoreUtils'
|
||||
import DateTime from '../../utils/DateTime'
|
||||
|
||||
export default class AxesUtils {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
}
|
||||
|
||||
// Based on the formatter function, get the label text and position
|
||||
getLabel(
|
||||
labels,
|
||||
timescaleLabels,
|
||||
x,
|
||||
i,
|
||||
drawnLabels = [],
|
||||
fontSize = '12px',
|
||||
isLeafGroup = true
|
||||
) {
|
||||
const w = this.w
|
||||
let rawLabel = typeof labels[i] === 'undefined' ? '' : labels[i]
|
||||
let label = rawLabel
|
||||
|
||||
let xlbFormatter = w.globals.xLabelFormatter
|
||||
let customFormatter = w.config.xaxis.labels.formatter
|
||||
|
||||
let isBold = false
|
||||
|
||||
let xFormat = new Formatters(this.ctx)
|
||||
let timestamp = rawLabel
|
||||
|
||||
if (isLeafGroup) {
|
||||
label = xFormat.xLabelFormat(xlbFormatter, rawLabel, timestamp, {
|
||||
i,
|
||||
dateFormatter: new DateTime(this.ctx).formatDate,
|
||||
w,
|
||||
})
|
||||
|
||||
if (customFormatter !== undefined) {
|
||||
label = customFormatter(rawLabel, labels[i], {
|
||||
i,
|
||||
dateFormatter: new DateTime(this.ctx).formatDate,
|
||||
w,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const determineHighestUnit = (unit) => {
|
||||
let highestUnit = null
|
||||
timescaleLabels.forEach((t) => {
|
||||
if (t.unit === 'month') {
|
||||
highestUnit = 'year'
|
||||
} else if (t.unit === 'day') {
|
||||
highestUnit = 'month'
|
||||
} else if (t.unit === 'hour') {
|
||||
highestUnit = 'day'
|
||||
} else if (t.unit === 'minute') {
|
||||
highestUnit = 'hour'
|
||||
}
|
||||
})
|
||||
|
||||
return highestUnit === unit
|
||||
}
|
||||
if (timescaleLabels.length > 0) {
|
||||
isBold = determineHighestUnit(timescaleLabels[i].unit)
|
||||
x = timescaleLabels[i].position
|
||||
label = timescaleLabels[i].value
|
||||
} else {
|
||||
if (w.config.xaxis.type === 'datetime' && customFormatter === undefined) {
|
||||
label = ''
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof label === 'undefined') label = ''
|
||||
|
||||
label = Array.isArray(label) ? label : label.toString()
|
||||
|
||||
let graphics = new Graphics(this.ctx)
|
||||
let textRect = {}
|
||||
if (w.globals.rotateXLabels && isLeafGroup) {
|
||||
textRect = graphics.getTextRects(
|
||||
label,
|
||||
parseInt(fontSize, 10),
|
||||
null,
|
||||
`rotate(${w.config.xaxis.labels.rotate} 0 0)`,
|
||||
false
|
||||
)
|
||||
} else {
|
||||
textRect = graphics.getTextRects(label, parseInt(fontSize, 10))
|
||||
}
|
||||
|
||||
const allowDuplicatesInTimeScale =
|
||||
!w.config.xaxis.labels.showDuplicates && this.ctx.timeScale
|
||||
|
||||
if (
|
||||
!Array.isArray(label) &&
|
||||
(String(label) === 'NaN' ||
|
||||
(drawnLabels.indexOf(label) >= 0 && allowDuplicatesInTimeScale))
|
||||
) {
|
||||
label = ''
|
||||
}
|
||||
|
||||
return {
|
||||
x,
|
||||
text: label,
|
||||
textRect,
|
||||
isBold,
|
||||
}
|
||||
}
|
||||
|
||||
checkLabelBasedOnTickamount(i, label, labelsLen) {
|
||||
const w = this.w
|
||||
|
||||
let ticks = w.config.xaxis.tickAmount
|
||||
if (ticks === 'dataPoints') ticks = Math.round(w.globals.gridWidth / 120)
|
||||
|
||||
if (ticks > labelsLen) return label
|
||||
let tickMultiple = Math.round(labelsLen / (ticks + 1))
|
||||
|
||||
if (i % tickMultiple === 0) {
|
||||
return label
|
||||
} else {
|
||||
label.text = ''
|
||||
}
|
||||
|
||||
return label
|
||||
}
|
||||
|
||||
checkForOverflowingLabels(
|
||||
i,
|
||||
label,
|
||||
labelsLen,
|
||||
drawnLabels,
|
||||
drawnLabelsRects
|
||||
) {
|
||||
const w = this.w
|
||||
|
||||
if (i === 0) {
|
||||
// check if first label is being truncated
|
||||
if (w.globals.skipFirstTimelinelabel) {
|
||||
label.text = ''
|
||||
}
|
||||
}
|
||||
|
||||
if (i === labelsLen - 1) {
|
||||
// check if last label is being truncated
|
||||
if (w.globals.skipLastTimelinelabel) {
|
||||
label.text = ''
|
||||
}
|
||||
}
|
||||
|
||||
if (w.config.xaxis.labels.hideOverlappingLabels && drawnLabels.length > 0) {
|
||||
const prev = drawnLabelsRects[drawnLabelsRects.length - 1]
|
||||
if (
|
||||
label.x <
|
||||
prev.textRect.width /
|
||||
(w.globals.rotateXLabels
|
||||
? Math.abs(w.config.xaxis.labels.rotate) / 12
|
||||
: 1.01) +
|
||||
prev.x
|
||||
) {
|
||||
label.text = ''
|
||||
}
|
||||
}
|
||||
|
||||
return label
|
||||
}
|
||||
|
||||
checkForReversedLabels(i, labels) {
|
||||
const w = this.w
|
||||
if (w.config.yaxis[i] && w.config.yaxis[i].reversed) {
|
||||
labels.reverse()
|
||||
}
|
||||
return labels
|
||||
}
|
||||
|
||||
isYAxisHidden(index) {
|
||||
const w = this.w
|
||||
const coreUtils = new CoreUtils(this.ctx)
|
||||
|
||||
return (
|
||||
!w.config.yaxis[index].show ||
|
||||
(!w.config.yaxis[index].showForNullSeries &&
|
||||
coreUtils.isSeriesNull(index) &&
|
||||
w.globals.collapsedSeriesIndices.indexOf(index) === -1)
|
||||
)
|
||||
}
|
||||
|
||||
// get the label color for y-axis
|
||||
// realIndex is the actual series index, while i is the tick Index
|
||||
getYAxisForeColor(yColors, realIndex) {
|
||||
const w = this.w
|
||||
if (Array.isArray(yColors) && w.globals.yAxisScale[realIndex]) {
|
||||
this.ctx.theme.pushExtraColors(
|
||||
yColors,
|
||||
w.globals.yAxisScale[realIndex].result.length,
|
||||
false
|
||||
)
|
||||
}
|
||||
return yColors
|
||||
}
|
||||
|
||||
drawYAxisTicks(
|
||||
x,
|
||||
tickAmount,
|
||||
axisBorder,
|
||||
axisTicks,
|
||||
realIndex,
|
||||
labelsDivider,
|
||||
elYaxis
|
||||
) {
|
||||
let w = this.w
|
||||
let graphics = new Graphics(this.ctx)
|
||||
|
||||
// initial label position = 0;
|
||||
let t = w.globals.translateY
|
||||
|
||||
if (axisTicks.show && tickAmount > 0) {
|
||||
if (w.config.yaxis[realIndex].opposite === true) x = x + axisTicks.width
|
||||
|
||||
for (let i = tickAmount; i >= 0; i--) {
|
||||
let tY =
|
||||
t + tickAmount / 10 + w.config.yaxis[realIndex].labels.offsetY - 1
|
||||
if (w.globals.isBarHorizontal) {
|
||||
tY = labelsDivider * i
|
||||
}
|
||||
|
||||
if (w.config.chart.type === 'heatmap') {
|
||||
tY = tY + labelsDivider / 2
|
||||
}
|
||||
let elTick = graphics.drawLine(
|
||||
x + axisBorder.offsetX - axisTicks.width + axisTicks.offsetX,
|
||||
tY + axisTicks.offsetY,
|
||||
x + axisBorder.offsetX + axisTicks.offsetX,
|
||||
tY + axisTicks.offsetY,
|
||||
axisTicks.color
|
||||
)
|
||||
elYaxis.add(elTick)
|
||||
t = t + labelsDivider
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+550
@@ -0,0 +1,550 @@
|
||||
import Graphics from '../Graphics'
|
||||
import XAxis from './XAxis'
|
||||
import AxesUtils from './AxesUtils'
|
||||
|
||||
/**
|
||||
* ApexCharts Grid Class for drawing Cartesian Grid.
|
||||
*
|
||||
* @module Grid
|
||||
**/
|
||||
|
||||
class Grid {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
|
||||
const w = this.w
|
||||
this.xaxisLabels = w.globals.labels.slice()
|
||||
this.axesUtils = new AxesUtils(ctx)
|
||||
|
||||
this.isRangeBar = w.globals.seriesRange.length && w.globals.isBarHorizontal
|
||||
|
||||
if (w.globals.timescaleLabels.length > 0) {
|
||||
// timescaleLabels labels are there
|
||||
this.xaxisLabels = w.globals.timescaleLabels.slice()
|
||||
}
|
||||
}
|
||||
|
||||
// when using sparklines or when showing no grid, we need to have a grid area which is reused at many places for other calculations as well
|
||||
drawGridArea(elGrid = null) {
|
||||
let w = this.w
|
||||
|
||||
let graphics = new Graphics(this.ctx)
|
||||
|
||||
if (elGrid === null) {
|
||||
elGrid = graphics.group({
|
||||
class: 'apexcharts-grid',
|
||||
})
|
||||
}
|
||||
|
||||
let elVerticalLine = graphics.drawLine(
|
||||
w.globals.padHorizontal,
|
||||
1,
|
||||
w.globals.padHorizontal,
|
||||
w.globals.gridHeight,
|
||||
'transparent'
|
||||
)
|
||||
|
||||
let elHorzLine = graphics.drawLine(
|
||||
w.globals.padHorizontal,
|
||||
w.globals.gridHeight,
|
||||
w.globals.gridWidth,
|
||||
w.globals.gridHeight,
|
||||
'transparent'
|
||||
)
|
||||
|
||||
elGrid.add(elHorzLine)
|
||||
elGrid.add(elVerticalLine)
|
||||
|
||||
return elGrid
|
||||
}
|
||||
|
||||
drawGrid() {
|
||||
let gl = this.w.globals
|
||||
|
||||
let elgrid = null
|
||||
|
||||
if (gl.axisCharts) {
|
||||
// grid is drawn after xaxis and yaxis are drawn
|
||||
elgrid = this.renderGrid()
|
||||
|
||||
this.drawGridArea(elgrid.el)
|
||||
}
|
||||
return elgrid
|
||||
}
|
||||
|
||||
// This mask will clip off overflowing graphics from the drawable area
|
||||
createGridMask() {
|
||||
let w = this.w
|
||||
let gl = w.globals
|
||||
const graphics = new Graphics(this.ctx)
|
||||
|
||||
let strokeSize = Array.isArray(w.config.stroke.width)
|
||||
? 0
|
||||
: w.config.stroke.width
|
||||
|
||||
if (Array.isArray(w.config.stroke.width)) {
|
||||
let strokeMaxSize = 0
|
||||
w.config.stroke.width.forEach((m) => {
|
||||
strokeMaxSize = Math.max(strokeMaxSize, m)
|
||||
})
|
||||
strokeSize = strokeMaxSize
|
||||
}
|
||||
|
||||
gl.dom.elGridRectMask = document.createElementNS(gl.SVGNS, 'clipPath')
|
||||
gl.dom.elGridRectMask.setAttribute('id', `gridRectMask${gl.cuid}`)
|
||||
|
||||
gl.dom.elGridRectMarkerMask = document.createElementNS(gl.SVGNS, 'clipPath')
|
||||
gl.dom.elGridRectMarkerMask.setAttribute(
|
||||
'id',
|
||||
`gridRectMarkerMask${gl.cuid}`
|
||||
)
|
||||
|
||||
gl.dom.elForecastMask = document.createElementNS(gl.SVGNS, 'clipPath')
|
||||
gl.dom.elForecastMask.setAttribute('id', `forecastMask${gl.cuid}`)
|
||||
|
||||
gl.dom.elNonForecastMask = document.createElementNS(gl.SVGNS, 'clipPath')
|
||||
gl.dom.elNonForecastMask.setAttribute('id', `nonForecastMask${gl.cuid}`)
|
||||
|
||||
// let barHalfWidth = 0
|
||||
|
||||
const type = w.config.chart.type
|
||||
const hasBar =
|
||||
type === 'bar' ||
|
||||
type === 'rangeBar' ||
|
||||
type === 'candlestick' ||
|
||||
type === 'boxPlot' ||
|
||||
w.globals.comboBarCount > 0
|
||||
|
||||
let barWidthLeft = 0
|
||||
let barWidthRight = 0
|
||||
if (hasBar && w.globals.isXNumeric && !w.globals.isBarHorizontal) {
|
||||
barWidthLeft = w.config.grid.padding.left
|
||||
barWidthRight = w.config.grid.padding.right
|
||||
|
||||
if (gl.barPadForNumericAxis > barWidthLeft) {
|
||||
barWidthLeft = gl.barPadForNumericAxis
|
||||
barWidthRight = gl.barPadForNumericAxis
|
||||
}
|
||||
}
|
||||
gl.dom.elGridRect = graphics.drawRect(
|
||||
-strokeSize - barWidthLeft - 2,
|
||||
-strokeSize * 2 - 2,
|
||||
gl.gridWidth + strokeSize + barWidthRight + barWidthLeft + 4,
|
||||
gl.gridHeight + strokeSize * 4 + 4,
|
||||
0,
|
||||
'#fff'
|
||||
)
|
||||
|
||||
let markerSize = w.globals.markers.largestSize + 1
|
||||
|
||||
gl.dom.elGridRectMarker = graphics.drawRect(
|
||||
-markerSize * 2,
|
||||
-markerSize * 2,
|
||||
gl.gridWidth + markerSize * 4,
|
||||
gl.gridHeight + markerSize * 4,
|
||||
0,
|
||||
'#fff'
|
||||
)
|
||||
gl.dom.elGridRectMask.appendChild(gl.dom.elGridRect.node)
|
||||
gl.dom.elGridRectMarkerMask.appendChild(gl.dom.elGridRectMarker.node)
|
||||
|
||||
let defs = gl.dom.baseEl.querySelector('defs')
|
||||
defs.appendChild(gl.dom.elGridRectMask)
|
||||
defs.appendChild(gl.dom.elForecastMask)
|
||||
defs.appendChild(gl.dom.elNonForecastMask)
|
||||
defs.appendChild(gl.dom.elGridRectMarkerMask)
|
||||
}
|
||||
|
||||
_drawGridLines({ i, x1, y1, x2, y2, xCount, parent }) {
|
||||
const w = this.w
|
||||
|
||||
const shouldDraw = () => {
|
||||
if (i === 0 && w.globals.skipFirstTimelinelabel) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (
|
||||
i === xCount - 1 &&
|
||||
w.globals.skipLastTimelinelabel &&
|
||||
!w.config.xaxis.labels.formatter
|
||||
) {
|
||||
return false
|
||||
}
|
||||
if (w.config.chart.type === 'radar') {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if (shouldDraw()) {
|
||||
if (w.config.grid.xaxis.lines.show) {
|
||||
this._drawGridLine({ i, x1, y1, x2, y2, xCount, parent })
|
||||
}
|
||||
let y_2 = 0
|
||||
if (
|
||||
w.globals.hasXaxisGroups &&
|
||||
w.config.xaxis.tickPlacement === 'between'
|
||||
) {
|
||||
const groups = w.globals.groups
|
||||
if (groups) {
|
||||
let gacc = 0
|
||||
for (let gi = 0; gacc < i && gi < groups.length; gi++) {
|
||||
gacc += groups[gi].cols
|
||||
}
|
||||
if (gacc === i) {
|
||||
y_2 = w.globals.xAxisLabelsHeight * 0.6
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let xAxis = new XAxis(this.ctx)
|
||||
xAxis.drawXaxisTicks(x1, y_2, w.globals.dom.elGraphical)
|
||||
}
|
||||
}
|
||||
|
||||
_drawGridLine({ i, x1, y1, x2, y2, xCount, parent }) {
|
||||
const w = this.w
|
||||
let excludeBorders = false
|
||||
|
||||
const isHorzLine = parent.node.classList.contains(
|
||||
'apexcharts-gridlines-horizontal'
|
||||
)
|
||||
|
||||
let strokeDashArray = w.config.grid.strokeDashArray
|
||||
const offX = w.globals.barPadForNumericAxis
|
||||
if ((y1 === 0 && y2 === 0) || (x1 === 0 && x2 === 0)) {
|
||||
excludeBorders = true
|
||||
}
|
||||
if (y1 === w.globals.gridHeight && y2 === w.globals.gridHeight) {
|
||||
excludeBorders = true
|
||||
}
|
||||
if (w.globals.isBarHorizontal && (i === 0 || i === xCount - 1)) {
|
||||
excludeBorders = true
|
||||
}
|
||||
|
||||
const graphics = new Graphics(this)
|
||||
let line = graphics.drawLine(
|
||||
x1 - (isHorzLine ? offX : 0),
|
||||
y1,
|
||||
x2 + (isHorzLine ? offX : 0),
|
||||
y2,
|
||||
w.config.grid.borderColor,
|
||||
strokeDashArray
|
||||
)
|
||||
line.node.classList.add('apexcharts-gridline')
|
||||
|
||||
if (excludeBorders && w.config.grid.show) {
|
||||
this.elGridBorders.add(line)
|
||||
} else {
|
||||
parent.add(line)
|
||||
}
|
||||
}
|
||||
|
||||
_drawGridBandRect({ c, x1, y1, x2, y2, type }) {
|
||||
const w = this.w
|
||||
const graphics = new Graphics(this.ctx)
|
||||
const offX = w.globals.barPadForNumericAxis
|
||||
|
||||
if (type === 'column' && w.config.xaxis.type === 'datetime') return
|
||||
|
||||
const color = w.config.grid[type].colors[c]
|
||||
|
||||
let rect = graphics.drawRect(
|
||||
x1 - (type === 'row' ? offX : 0),
|
||||
y1,
|
||||
x2 + (type === 'row' ? offX * 2 : 0),
|
||||
y2,
|
||||
0,
|
||||
color,
|
||||
w.config.grid[type].opacity
|
||||
)
|
||||
this.elg.add(rect)
|
||||
rect.attr('clip-path', `url(#gridRectMask${w.globals.cuid})`)
|
||||
rect.node.classList.add(`apexcharts-grid-${type}`)
|
||||
}
|
||||
|
||||
_drawXYLines({ xCount, tickAmount }) {
|
||||
const w = this.w
|
||||
|
||||
const datetimeLines = ({ xC, x1, y1, x2, y2 }) => {
|
||||
for (let i = 0; i < xC; i++) {
|
||||
x1 = this.xaxisLabels[i].position
|
||||
x2 = this.xaxisLabels[i].position
|
||||
|
||||
this._drawGridLines({
|
||||
i,
|
||||
x1,
|
||||
y1,
|
||||
x2,
|
||||
y2,
|
||||
xCount,
|
||||
parent: this.elgridLinesV,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const categoryLines = ({ xC, x1, y1, x2, y2 }) => {
|
||||
for (let i = 0; i < xC + (w.globals.isXNumeric ? 0 : 1); i++) {
|
||||
if (i === 0 && xC === 1 && w.globals.dataPoints === 1) {
|
||||
// single datapoint
|
||||
x1 = w.globals.gridWidth / 2
|
||||
x2 = x1
|
||||
}
|
||||
this._drawGridLines({
|
||||
i,
|
||||
x1,
|
||||
y1,
|
||||
x2,
|
||||
y2,
|
||||
xCount,
|
||||
parent: this.elgridLinesV,
|
||||
})
|
||||
|
||||
x1 = x1 + w.globals.gridWidth / (w.globals.isXNumeric ? xC - 1 : xC)
|
||||
x2 = x1
|
||||
}
|
||||
}
|
||||
|
||||
// draw vertical lines
|
||||
if (w.config.grid.xaxis.lines.show || w.config.xaxis.axisTicks.show) {
|
||||
let x1 = w.globals.padHorizontal
|
||||
let y1 = 0
|
||||
let x2
|
||||
let y2 = w.globals.gridHeight
|
||||
|
||||
if (w.globals.timescaleLabels.length) {
|
||||
datetimeLines({ xC: xCount, x1, y1, x2, y2 })
|
||||
} else {
|
||||
if (w.globals.isXNumeric) {
|
||||
xCount = w.globals.xAxisScale.result.length
|
||||
}
|
||||
categoryLines({ xC: xCount, x1, y1, x2, y2 })
|
||||
}
|
||||
}
|
||||
|
||||
// draw horizontal lines
|
||||
if (w.config.grid.yaxis.lines.show) {
|
||||
let x1 = 0
|
||||
let y1 = 0
|
||||
let y2 = 0
|
||||
let x2 = w.globals.gridWidth
|
||||
let tA = tickAmount + 1
|
||||
|
||||
if (this.isRangeBar) {
|
||||
tA = w.globals.labels.length
|
||||
}
|
||||
|
||||
for (let i = 0; i < tA + (this.isRangeBar ? 1 : 0); i++) {
|
||||
this._drawGridLine({
|
||||
i,
|
||||
xCount: tA + (this.isRangeBar ? 1 : 0),
|
||||
x1,
|
||||
y1,
|
||||
x2,
|
||||
y2,
|
||||
parent: this.elgridLinesH,
|
||||
})
|
||||
|
||||
y1 = y1 + w.globals.gridHeight / (this.isRangeBar ? tA : tickAmount)
|
||||
|
||||
y2 = y1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_drawInvertedXYLines({ xCount }) {
|
||||
const w = this.w
|
||||
|
||||
// draw vertical lines
|
||||
if (w.config.grid.xaxis.lines.show || w.config.xaxis.axisTicks.show) {
|
||||
let x1 = w.globals.padHorizontal
|
||||
let y1 = 0
|
||||
let x2
|
||||
let y2 = w.globals.gridHeight
|
||||
for (let i = 0; i < xCount + 1; i++) {
|
||||
if (w.config.grid.xaxis.lines.show) {
|
||||
this._drawGridLine({
|
||||
i,
|
||||
xCount: xCount + 1,
|
||||
x1,
|
||||
y1,
|
||||
x2,
|
||||
y2,
|
||||
parent: this.elgridLinesV,
|
||||
})
|
||||
}
|
||||
|
||||
let xAxis = new XAxis(this.ctx)
|
||||
xAxis.drawXaxisTicks(x1, 0, w.globals.dom.elGraphical)
|
||||
x1 = x1 + w.globals.gridWidth / xCount + 0.3
|
||||
x2 = x1
|
||||
}
|
||||
}
|
||||
|
||||
// draw horizontal lines
|
||||
if (w.config.grid.yaxis.lines.show) {
|
||||
let x1 = 0
|
||||
let y1 = 0
|
||||
let y2 = 0
|
||||
let x2 = w.globals.gridWidth
|
||||
|
||||
for (let i = 0; i < w.globals.dataPoints + 1; i++) {
|
||||
this._drawGridLine({
|
||||
i,
|
||||
xCount: w.globals.dataPoints + 1,
|
||||
x1,
|
||||
y1,
|
||||
x2,
|
||||
y2,
|
||||
parent: this.elgridLinesH,
|
||||
})
|
||||
|
||||
y1 = y1 + w.globals.gridHeight / w.globals.dataPoints
|
||||
y2 = y1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// actual grid rendering
|
||||
renderGrid() {
|
||||
let w = this.w
|
||||
let graphics = new Graphics(this.ctx)
|
||||
|
||||
this.elg = graphics.group({
|
||||
class: 'apexcharts-grid',
|
||||
})
|
||||
this.elgridLinesH = graphics.group({
|
||||
class: 'apexcharts-gridlines-horizontal',
|
||||
})
|
||||
this.elgridLinesV = graphics.group({
|
||||
class: 'apexcharts-gridlines-vertical',
|
||||
})
|
||||
this.elGridBorders = graphics.group({
|
||||
class: 'apexcharts-grid-borders',
|
||||
})
|
||||
|
||||
this.elg.add(this.elgridLinesH)
|
||||
this.elg.add(this.elgridLinesV)
|
||||
|
||||
if (!w.config.grid.show) {
|
||||
this.elgridLinesV.hide()
|
||||
this.elgridLinesH.hide()
|
||||
this.elGridBorders.hide()
|
||||
}
|
||||
|
||||
let yTickAmount = w.globals.yAxisScale.length
|
||||
? w.globals.yAxisScale[0].result.length - 1
|
||||
: 5
|
||||
for (let i = 0; i < w.globals.series.length; i++) {
|
||||
if (typeof w.globals.yAxisScale[i] !== 'undefined') {
|
||||
yTickAmount = w.globals.yAxisScale[i].result.length - 1
|
||||
}
|
||||
if (yTickAmount > 2) break
|
||||
}
|
||||
|
||||
let xCount
|
||||
|
||||
if (!w.globals.isBarHorizontal || this.isRangeBar) {
|
||||
xCount = this.xaxisLabels.length
|
||||
|
||||
if (this.isRangeBar) {
|
||||
xCount--
|
||||
yTickAmount = w.globals.labels.length
|
||||
if (w.config.xaxis.tickAmount && w.config.xaxis.labels.formatter) {
|
||||
xCount = w.config.xaxis.tickAmount
|
||||
}
|
||||
if (
|
||||
w.globals.yAxisScale?.[0]?.result?.length > 0 &&
|
||||
w.config.xaxis.type !== 'datetime'
|
||||
) {
|
||||
xCount = w.globals.yAxisScale[0].result.length - 1
|
||||
}
|
||||
}
|
||||
|
||||
this._drawXYLines({
|
||||
xCount,
|
||||
tickAmount: yTickAmount,
|
||||
})
|
||||
} else {
|
||||
xCount = yTickAmount
|
||||
|
||||
// for horizontal bar chart, get the xaxis tickamount
|
||||
yTickAmount = w.globals.xTickAmount
|
||||
this._drawInvertedXYLines({ xCount, tickAmount: yTickAmount })
|
||||
}
|
||||
|
||||
this.drawGridBands(xCount, yTickAmount)
|
||||
return {
|
||||
el: this.elg,
|
||||
elGridBorders: this.elGridBorders,
|
||||
xAxisTickWidth: w.globals.gridWidth / xCount,
|
||||
}
|
||||
}
|
||||
|
||||
drawGridBands(xCount, tickAmount) {
|
||||
const w = this.w
|
||||
|
||||
// rows background bands
|
||||
if (
|
||||
w.config.grid.row.colors !== undefined &&
|
||||
w.config.grid.row.colors.length > 0
|
||||
) {
|
||||
let x1 = 0
|
||||
let y1 = 0
|
||||
let y2 = w.globals.gridHeight / tickAmount
|
||||
let x2 = w.globals.gridWidth
|
||||
|
||||
for (let i = 0, c = 0; i < tickAmount; i++, c++) {
|
||||
if (c >= w.config.grid.row.colors.length) {
|
||||
c = 0
|
||||
}
|
||||
this._drawGridBandRect({
|
||||
c,
|
||||
x1,
|
||||
y1,
|
||||
x2,
|
||||
y2,
|
||||
type: 'row',
|
||||
})
|
||||
|
||||
y1 = y1 + w.globals.gridHeight / tickAmount
|
||||
}
|
||||
}
|
||||
|
||||
// columns background bands
|
||||
if (
|
||||
w.config.grid.column.colors !== undefined &&
|
||||
w.config.grid.column.colors.length > 0
|
||||
) {
|
||||
const xc =
|
||||
!w.globals.isBarHorizontal &&
|
||||
w.config.xaxis.tickPlacement === 'on' &&
|
||||
(w.config.xaxis.type === 'category' ||
|
||||
w.config.xaxis.convertedCatToNumeric)
|
||||
? xCount - 1
|
||||
: xCount
|
||||
let x1 = w.globals.padHorizontal
|
||||
let y1 = 0
|
||||
let x2 = w.globals.padHorizontal + w.globals.gridWidth / xc
|
||||
let y2 = w.globals.gridHeight
|
||||
for (let i = 0, c = 0; i < xCount; i++, c++) {
|
||||
if (c >= w.config.grid.column.colors.length) {
|
||||
c = 0
|
||||
}
|
||||
this._drawGridBandRect({
|
||||
c,
|
||||
x1,
|
||||
y1,
|
||||
x2,
|
||||
y2,
|
||||
type: 'column',
|
||||
})
|
||||
|
||||
x1 = x1 + w.globals.gridWidth / xc
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Grid
|
||||
+683
@@ -0,0 +1,683 @@
|
||||
import Graphics from '../Graphics'
|
||||
import AxesUtils from './AxesUtils'
|
||||
|
||||
/**
|
||||
* ApexCharts XAxis Class for drawing X-Axis.
|
||||
*
|
||||
* @module XAxis
|
||||
**/
|
||||
|
||||
export default class XAxis {
|
||||
constructor(ctx, elgrid) {
|
||||
this.ctx = ctx
|
||||
this.elgrid = elgrid
|
||||
this.w = ctx.w
|
||||
|
||||
const w = this.w
|
||||
this.axesUtils = new AxesUtils(ctx)
|
||||
|
||||
this.xaxisLabels = w.globals.labels.slice()
|
||||
if (w.globals.timescaleLabels.length > 0 && !w.globals.isBarHorizontal) {
|
||||
// timeline labels are there and chart is not rangeabr timeline
|
||||
this.xaxisLabels = w.globals.timescaleLabels.slice()
|
||||
}
|
||||
|
||||
if (w.config.xaxis.overwriteCategories) {
|
||||
this.xaxisLabels = w.config.xaxis.overwriteCategories
|
||||
}
|
||||
this.drawnLabels = []
|
||||
this.drawnLabelsRects = []
|
||||
|
||||
if (w.config.xaxis.position === 'top') {
|
||||
this.offY = 0
|
||||
} else {
|
||||
this.offY = w.globals.gridHeight + 1
|
||||
}
|
||||
this.offY = this.offY + w.config.xaxis.axisBorder.offsetY
|
||||
this.isCategoryBarHorizontal =
|
||||
w.config.chart.type === 'bar' && w.config.plotOptions.bar.horizontal
|
||||
|
||||
this.xaxisFontSize = w.config.xaxis.labels.style.fontSize
|
||||
this.xaxisFontFamily = w.config.xaxis.labels.style.fontFamily
|
||||
this.xaxisForeColors = w.config.xaxis.labels.style.colors
|
||||
this.xaxisBorderWidth = w.config.xaxis.axisBorder.width
|
||||
if (this.isCategoryBarHorizontal) {
|
||||
this.xaxisBorderWidth = w.config.yaxis[0].axisBorder.width.toString()
|
||||
}
|
||||
|
||||
if (this.xaxisBorderWidth.indexOf('%') > -1) {
|
||||
this.xaxisBorderWidth =
|
||||
(w.globals.gridWidth * parseInt(this.xaxisBorderWidth, 10)) / 100
|
||||
} else {
|
||||
this.xaxisBorderWidth = parseInt(this.xaxisBorderWidth, 10)
|
||||
}
|
||||
this.xaxisBorderHeight = w.config.xaxis.axisBorder.height
|
||||
|
||||
// For bars, we will only consider single y xais,
|
||||
// as we are not providing multiple yaxis for bar charts
|
||||
this.yaxis = w.config.yaxis[0]
|
||||
}
|
||||
|
||||
drawXaxis() {
|
||||
let w = this.w
|
||||
let graphics = new Graphics(this.ctx)
|
||||
|
||||
let elXaxis = graphics.group({
|
||||
class: 'apexcharts-xaxis',
|
||||
transform: `translate(${w.config.xaxis.offsetX}, ${w.config.xaxis.offsetY})`,
|
||||
})
|
||||
|
||||
let elXaxisTexts = graphics.group({
|
||||
class: 'apexcharts-xaxis-texts-g',
|
||||
transform: `translate(${w.globals.translateXAxisX}, ${w.globals.translateXAxisY})`,
|
||||
})
|
||||
|
||||
elXaxis.add(elXaxisTexts)
|
||||
|
||||
let labels = []
|
||||
|
||||
for (let i = 0; i < this.xaxisLabels.length; i++) {
|
||||
labels.push(this.xaxisLabels[i])
|
||||
}
|
||||
|
||||
this.drawXAxisLabelAndGroup(
|
||||
true,
|
||||
graphics,
|
||||
elXaxisTexts,
|
||||
labels,
|
||||
w.globals.isXNumeric,
|
||||
(i, colWidth) => colWidth
|
||||
)
|
||||
|
||||
if (w.globals.hasXaxisGroups) {
|
||||
let labelsGroup = w.globals.groups
|
||||
|
||||
labels = []
|
||||
for (let i = 0; i < labelsGroup.length; i++) {
|
||||
labels.push(labelsGroup[i].title)
|
||||
}
|
||||
|
||||
let overwriteStyles = {}
|
||||
if (w.config.xaxis.group.style) {
|
||||
overwriteStyles.xaxisFontSize = w.config.xaxis.group.style.fontSize
|
||||
overwriteStyles.xaxisFontFamily = w.config.xaxis.group.style.fontFamily
|
||||
overwriteStyles.xaxisForeColors = w.config.xaxis.group.style.colors
|
||||
overwriteStyles.fontWeight = w.config.xaxis.group.style.fontWeight
|
||||
overwriteStyles.cssClass = w.config.xaxis.group.style.cssClass
|
||||
}
|
||||
|
||||
this.drawXAxisLabelAndGroup(
|
||||
false,
|
||||
graphics,
|
||||
elXaxisTexts,
|
||||
labels,
|
||||
false,
|
||||
(i, colWidth) => labelsGroup[i].cols * colWidth,
|
||||
overwriteStyles
|
||||
)
|
||||
}
|
||||
|
||||
if (w.config.xaxis.title.text !== undefined) {
|
||||
let elXaxisTitle = graphics.group({
|
||||
class: 'apexcharts-xaxis-title',
|
||||
})
|
||||
|
||||
let elXAxisTitleText = graphics.drawText({
|
||||
x: w.globals.gridWidth / 2 + w.config.xaxis.title.offsetX,
|
||||
y:
|
||||
this.offY +
|
||||
parseFloat(this.xaxisFontSize) +
|
||||
(w.config.xaxis.position === 'bottom'
|
||||
? w.globals.xAxisLabelsHeight
|
||||
: -w.globals.xAxisLabelsHeight - 10) +
|
||||
w.config.xaxis.title.offsetY,
|
||||
text: w.config.xaxis.title.text,
|
||||
textAnchor: 'middle',
|
||||
fontSize: w.config.xaxis.title.style.fontSize,
|
||||
fontFamily: w.config.xaxis.title.style.fontFamily,
|
||||
fontWeight: w.config.xaxis.title.style.fontWeight,
|
||||
foreColor: w.config.xaxis.title.style.color,
|
||||
cssClass:
|
||||
'apexcharts-xaxis-title-text ' + w.config.xaxis.title.style.cssClass,
|
||||
})
|
||||
|
||||
elXaxisTitle.add(elXAxisTitleText)
|
||||
|
||||
elXaxis.add(elXaxisTitle)
|
||||
}
|
||||
|
||||
if (w.config.xaxis.axisBorder.show) {
|
||||
const offX = w.globals.barPadForNumericAxis
|
||||
let elHorzLine = graphics.drawLine(
|
||||
w.globals.padHorizontal + w.config.xaxis.axisBorder.offsetX - offX,
|
||||
this.offY,
|
||||
this.xaxisBorderWidth + offX,
|
||||
this.offY,
|
||||
w.config.xaxis.axisBorder.color,
|
||||
0,
|
||||
this.xaxisBorderHeight
|
||||
)
|
||||
if (this.elgrid && this.elgrid.elGridBorders && w.config.grid.show) {
|
||||
this.elgrid.elGridBorders.add(elHorzLine)
|
||||
} else {
|
||||
elXaxis.add(elHorzLine)
|
||||
}
|
||||
}
|
||||
|
||||
return elXaxis
|
||||
}
|
||||
|
||||
drawXAxisLabelAndGroup(
|
||||
isLeafGroup,
|
||||
graphics,
|
||||
elXaxisTexts,
|
||||
labels,
|
||||
isXNumeric,
|
||||
colWidthCb,
|
||||
overwriteStyles = {}
|
||||
) {
|
||||
let drawnLabels = []
|
||||
let drawnLabelsRects = []
|
||||
let w = this.w
|
||||
|
||||
const xaxisFontSize = overwriteStyles.xaxisFontSize || this.xaxisFontSize
|
||||
const xaxisFontFamily =
|
||||
overwriteStyles.xaxisFontFamily || this.xaxisFontFamily
|
||||
const xaxisForeColors =
|
||||
overwriteStyles.xaxisForeColors || this.xaxisForeColors
|
||||
const fontWeight =
|
||||
overwriteStyles.fontWeight || w.config.xaxis.labels.style.fontWeight
|
||||
const cssClass =
|
||||
overwriteStyles.cssClass || w.config.xaxis.labels.style.cssClass
|
||||
|
||||
let colWidth
|
||||
|
||||
// initial x Position (keep adding column width in the loop)
|
||||
let xPos = w.globals.padHorizontal
|
||||
|
||||
let labelsLen = labels.length
|
||||
|
||||
/**
|
||||
* labelsLen can be different (whether you are drawing x-axis labels or x-axis group labels)
|
||||
* hence, we introduce dataPoints to be consistent.
|
||||
* Also, in datetime/numeric xaxis, dataPoints can be misleading, so we resort to labelsLen for such xaxis type
|
||||
*/
|
||||
let dataPoints =
|
||||
w.config.xaxis.type === 'category' ? w.globals.dataPoints : labelsLen
|
||||
|
||||
// when all series are collapsed, fixes #3381
|
||||
if (dataPoints === 0 && labelsLen > dataPoints) dataPoints = labelsLen
|
||||
|
||||
if (isXNumeric) {
|
||||
let len = dataPoints > 1 ? dataPoints - 1 : dataPoints
|
||||
colWidth = w.globals.gridWidth / Math.min(len, labelsLen - 1)
|
||||
|
||||
xPos = xPos + colWidthCb(0, colWidth) / 2 + w.config.xaxis.labels.offsetX
|
||||
} else {
|
||||
colWidth = w.globals.gridWidth / dataPoints
|
||||
xPos = xPos + colWidthCb(0, colWidth) + w.config.xaxis.labels.offsetX
|
||||
}
|
||||
|
||||
for (let i = 0; i <= labelsLen - 1; i++) {
|
||||
let x = xPos - colWidthCb(i, colWidth) / 2 + w.config.xaxis.labels.offsetX
|
||||
|
||||
if (
|
||||
i === 0 &&
|
||||
labelsLen === 1 &&
|
||||
colWidth / 2 === xPos &&
|
||||
dataPoints === 1
|
||||
) {
|
||||
// single datapoint
|
||||
x = w.globals.gridWidth / 2
|
||||
}
|
||||
let label = this.axesUtils.getLabel(
|
||||
labels,
|
||||
w.globals.timescaleLabels,
|
||||
x,
|
||||
i,
|
||||
drawnLabels,
|
||||
xaxisFontSize,
|
||||
isLeafGroup
|
||||
)
|
||||
|
||||
let offsetYCorrection = 28
|
||||
if (w.globals.rotateXLabels && isLeafGroup) {
|
||||
offsetYCorrection = 22
|
||||
}
|
||||
|
||||
if (w.config.xaxis.title.text && w.config.xaxis.position === 'top') {
|
||||
offsetYCorrection += parseFloat(w.config.xaxis.title.style.fontSize) + 2
|
||||
}
|
||||
|
||||
if (!isLeafGroup) {
|
||||
offsetYCorrection =
|
||||
offsetYCorrection +
|
||||
parseFloat(xaxisFontSize) +
|
||||
(w.globals.xAxisLabelsHeight - w.globals.xAxisGroupLabelsHeight) +
|
||||
(w.globals.rotateXLabels ? 10 : 0)
|
||||
}
|
||||
|
||||
const isCategoryTickAmounts =
|
||||
typeof w.config.xaxis.tickAmount !== 'undefined' &&
|
||||
w.config.xaxis.tickAmount !== 'dataPoints' &&
|
||||
w.config.xaxis.type !== 'datetime'
|
||||
|
||||
if (isCategoryTickAmounts) {
|
||||
label = this.axesUtils.checkLabelBasedOnTickamount(i, label, labelsLen)
|
||||
} else {
|
||||
label = this.axesUtils.checkForOverflowingLabels(
|
||||
i,
|
||||
label,
|
||||
labelsLen,
|
||||
drawnLabels,
|
||||
drawnLabelsRects
|
||||
)
|
||||
}
|
||||
|
||||
const getCatForeColor = () => {
|
||||
return isLeafGroup && w.config.xaxis.convertedCatToNumeric
|
||||
? xaxisForeColors[w.globals.minX + i - 1]
|
||||
: xaxisForeColors[i]
|
||||
}
|
||||
|
||||
if (w.config.xaxis.labels.show) {
|
||||
let elText = graphics.drawText({
|
||||
x: label.x,
|
||||
y:
|
||||
this.offY +
|
||||
w.config.xaxis.labels.offsetY +
|
||||
offsetYCorrection -
|
||||
(w.config.xaxis.position === 'top'
|
||||
? w.globals.xAxisHeight + w.config.xaxis.axisTicks.height - 2
|
||||
: 0),
|
||||
text: label.text,
|
||||
textAnchor: 'middle',
|
||||
fontWeight: label.isBold ? 600 : fontWeight,
|
||||
fontSize: xaxisFontSize,
|
||||
fontFamily: xaxisFontFamily,
|
||||
foreColor: Array.isArray(xaxisForeColors)
|
||||
? getCatForeColor()
|
||||
: xaxisForeColors,
|
||||
isPlainText: false,
|
||||
cssClass:
|
||||
(isLeafGroup
|
||||
? 'apexcharts-xaxis-label '
|
||||
: 'apexcharts-xaxis-group-label ') + cssClass,
|
||||
})
|
||||
elXaxisTexts.add(elText)
|
||||
|
||||
elText.on('click', (e) => {
|
||||
if (typeof w.config.chart.events.xAxisLabelClick === 'function') {
|
||||
const opts = Object.assign({}, w, {
|
||||
labelIndex: i,
|
||||
})
|
||||
|
||||
w.config.chart.events.xAxisLabelClick(e, this.ctx, opts)
|
||||
}
|
||||
})
|
||||
|
||||
if (isLeafGroup) {
|
||||
let elTooltipTitle = document.createElementNS(
|
||||
w.globals.SVGNS,
|
||||
'title'
|
||||
)
|
||||
elTooltipTitle.textContent = Array.isArray(label.text)
|
||||
? label.text.join(' ')
|
||||
: label.text
|
||||
elText.node.appendChild(elTooltipTitle)
|
||||
if (label.text !== '') {
|
||||
drawnLabels.push(label.text)
|
||||
drawnLabelsRects.push(label)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i < labelsLen - 1) {
|
||||
xPos = xPos + colWidthCb(i + 1, colWidth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this actually becomes the vertical axis (for bar charts)
|
||||
drawXaxisInversed(realIndex) {
|
||||
let w = this.w
|
||||
let graphics = new Graphics(this.ctx)
|
||||
|
||||
let translateYAxisX = w.config.yaxis[0].opposite
|
||||
? w.globals.translateYAxisX[realIndex]
|
||||
: 0
|
||||
|
||||
let elYaxis = graphics.group({
|
||||
class: 'apexcharts-yaxis apexcharts-xaxis-inversed',
|
||||
rel: realIndex,
|
||||
})
|
||||
|
||||
let elYaxisTexts = graphics.group({
|
||||
class: 'apexcharts-yaxis-texts-g apexcharts-xaxis-inversed-texts-g',
|
||||
transform: 'translate(' + translateYAxisX + ', 0)',
|
||||
})
|
||||
|
||||
elYaxis.add(elYaxisTexts)
|
||||
|
||||
let colHeight
|
||||
|
||||
// initial x Position (keep adding column width in the loop)
|
||||
let yPos
|
||||
let labels = []
|
||||
|
||||
if (w.config.yaxis[realIndex].show) {
|
||||
for (let i = 0; i < this.xaxisLabels.length; i++) {
|
||||
labels.push(this.xaxisLabels[i])
|
||||
}
|
||||
}
|
||||
|
||||
colHeight = w.globals.gridHeight / labels.length
|
||||
yPos = -(colHeight / 2.2)
|
||||
|
||||
let lbFormatter = w.globals.yLabelFormatters[0]
|
||||
|
||||
const ylabels = w.config.yaxis[0].labels
|
||||
|
||||
if (ylabels.show) {
|
||||
for (let i = 0; i <= labels.length - 1; i++) {
|
||||
let label = typeof labels[i] === 'undefined' ? '' : labels[i]
|
||||
|
||||
label = lbFormatter(label, {
|
||||
seriesIndex: realIndex,
|
||||
dataPointIndex: i,
|
||||
w,
|
||||
})
|
||||
|
||||
const yColors = this.axesUtils.getYAxisForeColor(
|
||||
ylabels.style.colors,
|
||||
realIndex
|
||||
)
|
||||
const getForeColor = () => {
|
||||
return Array.isArray(yColors) ? yColors[i] : yColors
|
||||
}
|
||||
|
||||
let multiY = 0
|
||||
if (Array.isArray(label)) {
|
||||
multiY = (label.length / 2) * parseInt(ylabels.style.fontSize, 10)
|
||||
}
|
||||
|
||||
let offsetX = ylabels.offsetX - 15
|
||||
let textAnchor = 'end'
|
||||
if (this.yaxis.opposite) {
|
||||
textAnchor = 'start'
|
||||
}
|
||||
if (w.config.yaxis[0].labels.align === 'left') {
|
||||
offsetX = ylabels.offsetX
|
||||
textAnchor = 'start'
|
||||
} else if (w.config.yaxis[0].labels.align === 'center') {
|
||||
offsetX = ylabels.offsetX
|
||||
textAnchor = 'middle'
|
||||
} else if (w.config.yaxis[0].labels.align === 'right') {
|
||||
textAnchor = 'end'
|
||||
}
|
||||
|
||||
let elLabel = graphics.drawText({
|
||||
x: offsetX,
|
||||
y: yPos + colHeight + ylabels.offsetY - multiY,
|
||||
text: label,
|
||||
textAnchor,
|
||||
foreColor: getForeColor(),
|
||||
fontSize: ylabels.style.fontSize,
|
||||
fontFamily: ylabels.style.fontFamily,
|
||||
fontWeight: ylabels.style.fontWeight,
|
||||
isPlainText: false,
|
||||
cssClass: 'apexcharts-yaxis-label ' + ylabels.style.cssClass,
|
||||
maxWidth: ylabels.maxWidth,
|
||||
})
|
||||
|
||||
elYaxisTexts.add(elLabel)
|
||||
|
||||
elLabel.on('click', (e) => {
|
||||
if (typeof w.config.chart.events.xAxisLabelClick === 'function') {
|
||||
const opts = Object.assign({}, w, {
|
||||
labelIndex: i,
|
||||
})
|
||||
|
||||
w.config.chart.events.xAxisLabelClick(e, this.ctx, opts)
|
||||
}
|
||||
})
|
||||
|
||||
let elTooltipTitle = document.createElementNS(w.globals.SVGNS, 'title')
|
||||
elTooltipTitle.textContent = Array.isArray(label)
|
||||
? label.join(' ')
|
||||
: label
|
||||
elLabel.node.appendChild(elTooltipTitle)
|
||||
|
||||
if (w.config.yaxis[realIndex].labels.rotate !== 0) {
|
||||
let labelRotatingCenter = graphics.rotateAroundCenter(elLabel.node)
|
||||
elLabel.node.setAttribute(
|
||||
'transform',
|
||||
`rotate(${w.config.yaxis[realIndex].labels.rotate} 0 ${labelRotatingCenter.y})`
|
||||
)
|
||||
}
|
||||
yPos = yPos + colHeight
|
||||
}
|
||||
}
|
||||
|
||||
if (w.config.yaxis[0].title.text !== undefined) {
|
||||
let elXaxisTitle = graphics.group({
|
||||
class: 'apexcharts-yaxis-title apexcharts-xaxis-title-inversed',
|
||||
transform: 'translate(' + translateYAxisX + ', 0)',
|
||||
})
|
||||
|
||||
let elXAxisTitleText = graphics.drawText({
|
||||
x: w.config.yaxis[0].title.offsetX,
|
||||
y: w.globals.gridHeight / 2 + w.config.yaxis[0].title.offsetY,
|
||||
text: w.config.yaxis[0].title.text,
|
||||
textAnchor: 'middle',
|
||||
foreColor: w.config.yaxis[0].title.style.color,
|
||||
fontSize: w.config.yaxis[0].title.style.fontSize,
|
||||
fontWeight: w.config.yaxis[0].title.style.fontWeight,
|
||||
fontFamily: w.config.yaxis[0].title.style.fontFamily,
|
||||
cssClass:
|
||||
'apexcharts-yaxis-title-text ' +
|
||||
w.config.yaxis[0].title.style.cssClass,
|
||||
})
|
||||
|
||||
elXaxisTitle.add(elXAxisTitleText)
|
||||
|
||||
elYaxis.add(elXaxisTitle)
|
||||
}
|
||||
|
||||
let offX = 0
|
||||
if (this.isCategoryBarHorizontal && w.config.yaxis[0].opposite) {
|
||||
offX = w.globals.gridWidth
|
||||
}
|
||||
const axisBorder = w.config.xaxis.axisBorder
|
||||
if (axisBorder.show) {
|
||||
let elVerticalLine = graphics.drawLine(
|
||||
w.globals.padHorizontal + axisBorder.offsetX + offX,
|
||||
1 + axisBorder.offsetY,
|
||||
w.globals.padHorizontal + axisBorder.offsetX + offX,
|
||||
w.globals.gridHeight + axisBorder.offsetY,
|
||||
axisBorder.color,
|
||||
0
|
||||
)
|
||||
|
||||
if (this.elgrid && this.elgrid.elGridBorders && w.config.grid.show) {
|
||||
this.elgrid.elGridBorders.add(elVerticalLine)
|
||||
} else {
|
||||
elYaxis.add(elVerticalLine)
|
||||
}
|
||||
}
|
||||
|
||||
if (w.config.yaxis[0].axisTicks.show) {
|
||||
this.axesUtils.drawYAxisTicks(
|
||||
offX,
|
||||
labels.length,
|
||||
w.config.yaxis[0].axisBorder,
|
||||
w.config.yaxis[0].axisTicks,
|
||||
0,
|
||||
colHeight,
|
||||
elYaxis
|
||||
)
|
||||
}
|
||||
|
||||
return elYaxis
|
||||
}
|
||||
|
||||
drawXaxisTicks(x1, y2, appendToElement) {
|
||||
let w = this.w
|
||||
let x2 = x1
|
||||
|
||||
if (x1 < 0 || x1 - 2 > w.globals.gridWidth) return
|
||||
|
||||
let y1 = this.offY + w.config.xaxis.axisTicks.offsetY
|
||||
y2 = y2 + y1 + w.config.xaxis.axisTicks.height
|
||||
if (w.config.xaxis.position === 'top') {
|
||||
y2 = y1 - w.config.xaxis.axisTicks.height
|
||||
}
|
||||
|
||||
if (w.config.xaxis.axisTicks.show) {
|
||||
let graphics = new Graphics(this.ctx)
|
||||
|
||||
let line = graphics.drawLine(
|
||||
x1 + w.config.xaxis.axisTicks.offsetX,
|
||||
y1 + w.config.xaxis.offsetY,
|
||||
x2 + w.config.xaxis.axisTicks.offsetX,
|
||||
y2 + w.config.xaxis.offsetY,
|
||||
w.config.xaxis.axisTicks.color
|
||||
)
|
||||
|
||||
// we are not returning anything, but appending directly to the element passed in param
|
||||
appendToElement.add(line)
|
||||
line.node.classList.add('apexcharts-xaxis-tick')
|
||||
}
|
||||
}
|
||||
|
||||
getXAxisTicksPositions() {
|
||||
const w = this.w
|
||||
let xAxisTicksPositions = []
|
||||
|
||||
const xCount = this.xaxisLabels.length
|
||||
let x1 = w.globals.padHorizontal
|
||||
|
||||
if (w.globals.timescaleLabels.length > 0) {
|
||||
for (let i = 0; i < xCount; i++) {
|
||||
x1 = this.xaxisLabels[i].position
|
||||
xAxisTicksPositions.push(x1)
|
||||
}
|
||||
} else {
|
||||
let xCountForCategoryCharts = xCount
|
||||
for (let i = 0; i < xCountForCategoryCharts; i++) {
|
||||
let x1Count = xCountForCategoryCharts
|
||||
if (w.globals.isXNumeric && w.config.chart.type !== 'bar') {
|
||||
x1Count -= 1
|
||||
}
|
||||
x1 = x1 + w.globals.gridWidth / x1Count
|
||||
xAxisTicksPositions.push(x1)
|
||||
}
|
||||
}
|
||||
|
||||
return xAxisTicksPositions
|
||||
}
|
||||
|
||||
// to rotate x-axis labels or to put ... for longer text in xaxis
|
||||
xAxisLabelCorrections() {
|
||||
let w = this.w
|
||||
|
||||
let graphics = new Graphics(this.ctx)
|
||||
|
||||
let xAxis = w.globals.dom.baseEl.querySelector('.apexcharts-xaxis-texts-g')
|
||||
|
||||
let xAxisTexts = w.globals.dom.baseEl.querySelectorAll(
|
||||
'.apexcharts-xaxis-texts-g text:not(.apexcharts-xaxis-group-label)'
|
||||
)
|
||||
let yAxisTextsInversed = w.globals.dom.baseEl.querySelectorAll(
|
||||
'.apexcharts-yaxis-inversed text'
|
||||
)
|
||||
let xAxisTextsInversed = w.globals.dom.baseEl.querySelectorAll(
|
||||
'.apexcharts-xaxis-inversed-texts-g text tspan'
|
||||
)
|
||||
|
||||
if (w.globals.rotateXLabels || w.config.xaxis.labels.rotateAlways) {
|
||||
for (let xat = 0; xat < xAxisTexts.length; xat++) {
|
||||
let textRotatingCenter = graphics.rotateAroundCenter(xAxisTexts[xat])
|
||||
textRotatingCenter.y = textRotatingCenter.y - 1 // + tickWidth/4;
|
||||
textRotatingCenter.x = textRotatingCenter.x + 1
|
||||
|
||||
xAxisTexts[xat].setAttribute(
|
||||
'transform',
|
||||
`rotate(${w.config.xaxis.labels.rotate} ${textRotatingCenter.x} ${textRotatingCenter.y})`
|
||||
)
|
||||
|
||||
xAxisTexts[xat].setAttribute('text-anchor', `end`)
|
||||
|
||||
let offsetHeight = 10
|
||||
|
||||
xAxis.setAttribute('transform', `translate(0, ${-offsetHeight})`)
|
||||
|
||||
let tSpan = xAxisTexts[xat].childNodes
|
||||
|
||||
if (w.config.xaxis.labels.trim) {
|
||||
Array.prototype.forEach.call(tSpan, (ts) => {
|
||||
graphics.placeTextWithEllipsis(
|
||||
ts,
|
||||
ts.textContent,
|
||||
w.globals.xAxisLabelsHeight -
|
||||
(w.config.legend.position === 'bottom' ? 20 : 10)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let width = w.globals.gridWidth / (w.globals.labels.length + 1)
|
||||
|
||||
for (let xat = 0; xat < xAxisTexts.length; xat++) {
|
||||
let tSpan = xAxisTexts[xat].childNodes
|
||||
|
||||
if (w.config.xaxis.labels.trim && w.config.xaxis.type !== 'datetime') {
|
||||
Array.prototype.forEach.call(tSpan, (ts) => {
|
||||
graphics.placeTextWithEllipsis(ts, ts.textContent, width)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (yAxisTextsInversed.length > 0) {
|
||||
// truncate rotated y axis in bar chart (x axis)
|
||||
let firstLabelPosX =
|
||||
yAxisTextsInversed[yAxisTextsInversed.length - 1].getBBox()
|
||||
let lastLabelPosX = yAxisTextsInversed[0].getBBox()
|
||||
|
||||
if (firstLabelPosX.x < -20) {
|
||||
yAxisTextsInversed[
|
||||
yAxisTextsInversed.length - 1
|
||||
].parentNode.removeChild(
|
||||
yAxisTextsInversed[yAxisTextsInversed.length - 1]
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
lastLabelPosX.x + lastLabelPosX.width > w.globals.gridWidth &&
|
||||
!w.globals.isBarHorizontal
|
||||
) {
|
||||
yAxisTextsInversed[0].parentNode.removeChild(yAxisTextsInversed[0])
|
||||
}
|
||||
|
||||
// truncate rotated x axis in bar chart (y axis)
|
||||
for (let xat = 0; xat < xAxisTextsInversed.length; xat++) {
|
||||
graphics.placeTextWithEllipsis(
|
||||
xAxisTextsInversed[xat],
|
||||
xAxisTextsInversed[xat].textContent,
|
||||
w.config.yaxis[0].labels.maxWidth -
|
||||
(w.config.yaxis[0].title.text
|
||||
? parseFloat(w.config.yaxis[0].title.style.fontSize) * 2
|
||||
: 0) -
|
||||
15
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// renderXAxisBands() {
|
||||
// let w = this.w;
|
||||
|
||||
// let plotBand = document.createElementNS(w.globals.SVGNS, 'rect')
|
||||
// w.globals.dom.elGraphical.add(plotBand)
|
||||
// }
|
||||
}
|
||||
+583
@@ -0,0 +1,583 @@
|
||||
import Graphics from '../Graphics'
|
||||
import Utils from '../../utils/Utils'
|
||||
import AxesUtils from './AxesUtils'
|
||||
|
||||
/**
|
||||
* ApexCharts YAxis Class for drawing Y-Axis.
|
||||
*
|
||||
* @module YAxis
|
||||
**/
|
||||
|
||||
export default class YAxis {
|
||||
constructor(ctx, elgrid) {
|
||||
this.ctx = ctx
|
||||
this.elgrid = elgrid
|
||||
this.w = ctx.w
|
||||
const w = this.w
|
||||
|
||||
this.xaxisFontSize = w.config.xaxis.labels.style.fontSize
|
||||
this.axisFontFamily = w.config.xaxis.labels.style.fontFamily
|
||||
|
||||
this.xaxisForeColors = w.config.xaxis.labels.style.colors
|
||||
this.isCategoryBarHorizontal =
|
||||
w.config.chart.type === 'bar' && w.config.plotOptions.bar.horizontal
|
||||
|
||||
this.xAxisoffX = 0
|
||||
if (w.config.xaxis.position === 'bottom') {
|
||||
this.xAxisoffX = w.globals.gridHeight
|
||||
}
|
||||
this.drawnLabels = []
|
||||
this.axesUtils = new AxesUtils(ctx)
|
||||
}
|
||||
|
||||
drawYaxis(realIndex) {
|
||||
let w = this.w
|
||||
let graphics = new Graphics(this.ctx)
|
||||
|
||||
const yaxisStyle = w.config.yaxis[realIndex].labels.style
|
||||
let yaxisFontSize = yaxisStyle.fontSize
|
||||
let yaxisFontFamily = yaxisStyle.fontFamily
|
||||
let yaxisFontWeight = yaxisStyle.fontWeight
|
||||
|
||||
let elYaxis = graphics.group({
|
||||
class: 'apexcharts-yaxis',
|
||||
rel: realIndex,
|
||||
transform: 'translate(' + w.globals.translateYAxisX[realIndex] + ', 0)',
|
||||
})
|
||||
|
||||
if (this.axesUtils.isYAxisHidden(realIndex)) {
|
||||
return elYaxis
|
||||
}
|
||||
|
||||
let elYaxisTexts = graphics.group({
|
||||
class: 'apexcharts-yaxis-texts-g',
|
||||
})
|
||||
|
||||
elYaxis.add(elYaxisTexts)
|
||||
|
||||
let tickAmount = w.globals.yAxisScale[realIndex].result.length - 1
|
||||
|
||||
// labelsDivider is simply svg height/number of ticks
|
||||
let labelsDivider = w.globals.gridHeight / tickAmount
|
||||
|
||||
// initial label position = 0;
|
||||
let l = w.globals.translateY
|
||||
let lbFormatter = w.globals.yLabelFormatters[realIndex]
|
||||
|
||||
let labels = w.globals.yAxisScale[realIndex].result.slice()
|
||||
|
||||
labels = this.axesUtils.checkForReversedLabels(realIndex, labels)
|
||||
|
||||
let firstLabel = ''
|
||||
if (w.config.yaxis[realIndex].labels.show) {
|
||||
for (let i = tickAmount; i >= 0; i--) {
|
||||
let val = labels[i]
|
||||
|
||||
val = lbFormatter(val, i, w)
|
||||
|
||||
let xPad = w.config.yaxis[realIndex].labels.padding
|
||||
if (w.config.yaxis[realIndex].opposite && w.config.yaxis.length !== 0) {
|
||||
xPad = xPad * -1
|
||||
}
|
||||
|
||||
let textAnchor = 'end'
|
||||
if (w.config.yaxis[realIndex].opposite) {
|
||||
textAnchor = 'start'
|
||||
}
|
||||
if (w.config.yaxis[realIndex].labels.align === 'left') {
|
||||
textAnchor = 'start'
|
||||
} else if (w.config.yaxis[realIndex].labels.align === 'center') {
|
||||
textAnchor = 'middle'
|
||||
} else if (w.config.yaxis[realIndex].labels.align === 'right') {
|
||||
textAnchor = 'end'
|
||||
}
|
||||
|
||||
const yColors = this.axesUtils.getYAxisForeColor(
|
||||
yaxisStyle.colors,
|
||||
realIndex
|
||||
)
|
||||
const getForeColor = () => {
|
||||
return Array.isArray(yColors) ? yColors[i] : yColors
|
||||
}
|
||||
|
||||
let offsetY = w.config.yaxis[realIndex].labels.offsetY
|
||||
|
||||
if (w.config.chart.type === 'heatmap') {
|
||||
const divisor = w.globals.gridHeight / w.globals.series.length - 1
|
||||
offsetY = offsetY - divisor / 2
|
||||
}
|
||||
|
||||
let label = graphics.drawText({
|
||||
x: xPad,
|
||||
y: l + tickAmount / 10 + offsetY + 1,
|
||||
text: val,
|
||||
textAnchor,
|
||||
fontSize: yaxisFontSize,
|
||||
fontFamily: yaxisFontFamily,
|
||||
fontWeight: yaxisFontWeight,
|
||||
maxWidth: w.config.yaxis[realIndex].labels.maxWidth,
|
||||
foreColor: getForeColor(),
|
||||
isPlainText: false,
|
||||
cssClass: 'apexcharts-yaxis-label ' + yaxisStyle.cssClass,
|
||||
})
|
||||
if (i === tickAmount) {
|
||||
firstLabel = label
|
||||
}
|
||||
elYaxisTexts.add(label)
|
||||
|
||||
let elTooltipTitle = document.createElementNS(w.globals.SVGNS, 'title')
|
||||
elTooltipTitle.textContent = Array.isArray(val) ? val.join(' ') : val
|
||||
label.node.appendChild(elTooltipTitle)
|
||||
|
||||
if (w.config.yaxis[realIndex].labels.rotate !== 0) {
|
||||
let firstabelRotatingCenter = graphics.rotateAroundCenter(
|
||||
firstLabel.node
|
||||
)
|
||||
let labelRotatingCenter = graphics.rotateAroundCenter(label.node)
|
||||
label.node.setAttribute(
|
||||
'transform',
|
||||
`rotate(${w.config.yaxis[realIndex].labels.rotate} ${firstabelRotatingCenter.x} ${labelRotatingCenter.y})`
|
||||
)
|
||||
}
|
||||
l = l + labelsDivider
|
||||
}
|
||||
}
|
||||
|
||||
if (w.config.yaxis[realIndex].title.text !== undefined) {
|
||||
let elYaxisTitle = graphics.group({
|
||||
class: 'apexcharts-yaxis-title',
|
||||
})
|
||||
|
||||
let x = 0
|
||||
if (w.config.yaxis[realIndex].opposite) {
|
||||
x = w.globals.translateYAxisX[realIndex]
|
||||
}
|
||||
let elYAxisTitleText = graphics.drawText({
|
||||
x,
|
||||
y:
|
||||
w.globals.gridHeight / 2 +
|
||||
w.globals.translateY +
|
||||
w.config.yaxis[realIndex].title.offsetY,
|
||||
text: w.config.yaxis[realIndex].title.text,
|
||||
textAnchor: 'end',
|
||||
foreColor: w.config.yaxis[realIndex].title.style.color,
|
||||
fontSize: w.config.yaxis[realIndex].title.style.fontSize,
|
||||
fontWeight: w.config.yaxis[realIndex].title.style.fontWeight,
|
||||
fontFamily: w.config.yaxis[realIndex].title.style.fontFamily,
|
||||
cssClass:
|
||||
'apexcharts-yaxis-title-text ' +
|
||||
w.config.yaxis[realIndex].title.style.cssClass,
|
||||
})
|
||||
|
||||
elYaxisTitle.add(elYAxisTitleText)
|
||||
|
||||
elYaxis.add(elYaxisTitle)
|
||||
}
|
||||
|
||||
let axisBorder = w.config.yaxis[realIndex].axisBorder
|
||||
|
||||
let x = 31 + axisBorder.offsetX
|
||||
if (w.config.yaxis[realIndex].opposite) {
|
||||
x = -31 - axisBorder.offsetX
|
||||
}
|
||||
|
||||
if (axisBorder.show) {
|
||||
let elVerticalLine = graphics.drawLine(
|
||||
x,
|
||||
w.globals.translateY + axisBorder.offsetY - 2,
|
||||
x,
|
||||
w.globals.gridHeight + w.globals.translateY + axisBorder.offsetY + 2,
|
||||
axisBorder.color,
|
||||
0,
|
||||
axisBorder.width
|
||||
)
|
||||
|
||||
elYaxis.add(elVerticalLine)
|
||||
}
|
||||
if (w.config.yaxis[realIndex].axisTicks.show) {
|
||||
this.axesUtils.drawYAxisTicks(
|
||||
x,
|
||||
tickAmount,
|
||||
axisBorder,
|
||||
w.config.yaxis[realIndex].axisTicks,
|
||||
realIndex,
|
||||
labelsDivider,
|
||||
elYaxis
|
||||
)
|
||||
}
|
||||
|
||||
return elYaxis
|
||||
}
|
||||
|
||||
// This actually becomes horizontal axis (for bar charts)
|
||||
drawYaxisInversed(realIndex) {
|
||||
let w = this.w
|
||||
let graphics = new Graphics(this.ctx)
|
||||
|
||||
let elXaxis = graphics.group({
|
||||
class: 'apexcharts-xaxis apexcharts-yaxis-inversed',
|
||||
})
|
||||
|
||||
let elXaxisTexts = graphics.group({
|
||||
class: 'apexcharts-xaxis-texts-g',
|
||||
transform: `translate(${w.globals.translateXAxisX}, ${w.globals.translateXAxisY})`,
|
||||
})
|
||||
|
||||
elXaxis.add(elXaxisTexts)
|
||||
|
||||
let tickAmount = w.globals.yAxisScale[realIndex].result.length - 1
|
||||
|
||||
// labelsDivider is simply svg width/number of ticks
|
||||
let labelsDivider = w.globals.gridWidth / tickAmount + 0.1
|
||||
|
||||
// initial label position;
|
||||
let l = labelsDivider + w.config.xaxis.labels.offsetX
|
||||
|
||||
let lbFormatter = w.globals.xLabelFormatter
|
||||
|
||||
let labels = w.globals.yAxisScale[realIndex].result.slice()
|
||||
|
||||
let timescaleLabels = w.globals.timescaleLabels
|
||||
if (timescaleLabels.length > 0) {
|
||||
this.xaxisLabels = timescaleLabels.slice()
|
||||
labels = timescaleLabels.slice()
|
||||
tickAmount = labels.length
|
||||
}
|
||||
|
||||
labels = this.axesUtils.checkForReversedLabels(realIndex, labels)
|
||||
|
||||
const tl = timescaleLabels.length
|
||||
|
||||
if (w.config.xaxis.labels.show) {
|
||||
for (let i = tl ? 0 : tickAmount; tl ? i < tl : i >= 0; tl ? i++ : i--) {
|
||||
let val = labels[i]
|
||||
val = lbFormatter(val, i, w)
|
||||
|
||||
let x =
|
||||
w.globals.gridWidth +
|
||||
w.globals.padHorizontal -
|
||||
(l - labelsDivider + w.config.xaxis.labels.offsetX)
|
||||
|
||||
if (timescaleLabels.length) {
|
||||
let label = this.axesUtils.getLabel(
|
||||
labels,
|
||||
timescaleLabels,
|
||||
x,
|
||||
i,
|
||||
this.drawnLabels,
|
||||
this.xaxisFontSize
|
||||
)
|
||||
x = label.x
|
||||
val = label.text
|
||||
this.drawnLabels.push(label.text)
|
||||
|
||||
if (i === 0 && w.globals.skipFirstTimelinelabel) {
|
||||
val = ''
|
||||
}
|
||||
if (i === labels.length - 1 && w.globals.skipLastTimelinelabel) {
|
||||
val = ''
|
||||
}
|
||||
}
|
||||
let elTick = graphics.drawText({
|
||||
x,
|
||||
y:
|
||||
this.xAxisoffX +
|
||||
w.config.xaxis.labels.offsetY +
|
||||
30 -
|
||||
(w.config.xaxis.position === 'top'
|
||||
? w.globals.xAxisHeight + w.config.xaxis.axisTicks.height - 2
|
||||
: 0),
|
||||
text: val,
|
||||
textAnchor: 'middle',
|
||||
foreColor: Array.isArray(this.xaxisForeColors)
|
||||
? this.xaxisForeColors[realIndex]
|
||||
: this.xaxisForeColors,
|
||||
fontSize: this.xaxisFontSize,
|
||||
fontFamily: this.xaxisFontFamily,
|
||||
fontWeight: w.config.xaxis.labels.style.fontWeight,
|
||||
isPlainText: false,
|
||||
cssClass:
|
||||
'apexcharts-xaxis-label ' + w.config.xaxis.labels.style.cssClass,
|
||||
})
|
||||
|
||||
elXaxisTexts.add(elTick)
|
||||
|
||||
elTick.tspan(val)
|
||||
|
||||
let elTooltipTitle = document.createElementNS(w.globals.SVGNS, 'title')
|
||||
elTooltipTitle.textContent = val
|
||||
elTick.node.appendChild(elTooltipTitle)
|
||||
|
||||
l = l + labelsDivider
|
||||
}
|
||||
}
|
||||
|
||||
this.inversedYAxisTitleText(elXaxis)
|
||||
this.inversedYAxisBorder(elXaxis)
|
||||
|
||||
return elXaxis
|
||||
}
|
||||
|
||||
inversedYAxisBorder(parent) {
|
||||
const w = this.w
|
||||
const graphics = new Graphics(this.ctx)
|
||||
|
||||
let axisBorder = w.config.xaxis.axisBorder
|
||||
if (axisBorder.show) {
|
||||
let lineCorrection = 0
|
||||
if (w.config.chart.type === 'bar' && w.globals.isXNumeric) {
|
||||
lineCorrection = lineCorrection - 15
|
||||
}
|
||||
let elHorzLine = graphics.drawLine(
|
||||
w.globals.padHorizontal + lineCorrection + axisBorder.offsetX,
|
||||
this.xAxisoffX,
|
||||
w.globals.gridWidth,
|
||||
this.xAxisoffX,
|
||||
axisBorder.color,
|
||||
0,
|
||||
axisBorder.height
|
||||
)
|
||||
|
||||
// in horizontal bars, we append axisBorder to elGridBorders element to avoid z-index issues
|
||||
if (this.elgrid && this.elgrid.elGridBorders && w.config.grid.show) {
|
||||
this.elgrid.elGridBorders.add(elHorzLine)
|
||||
} else {
|
||||
parent.add(elHorzLine)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inversedYAxisTitleText(parent) {
|
||||
const w = this.w
|
||||
const graphics = new Graphics(this.ctx)
|
||||
if (w.config.xaxis.title.text !== undefined) {
|
||||
let elYaxisTitle = graphics.group({
|
||||
class: 'apexcharts-xaxis-title apexcharts-yaxis-title-inversed',
|
||||
})
|
||||
|
||||
let elYAxisTitleText = graphics.drawText({
|
||||
x: w.globals.gridWidth / 2 + w.config.xaxis.title.offsetX,
|
||||
y:
|
||||
this.xAxisoffX +
|
||||
parseFloat(this.xaxisFontSize) +
|
||||
parseFloat(w.config.xaxis.title.style.fontSize) +
|
||||
w.config.xaxis.title.offsetY +
|
||||
20,
|
||||
text: w.config.xaxis.title.text,
|
||||
textAnchor: 'middle',
|
||||
fontSize: w.config.xaxis.title.style.fontSize,
|
||||
fontFamily: w.config.xaxis.title.style.fontFamily,
|
||||
fontWeight: w.config.xaxis.title.style.fontWeight,
|
||||
foreColor: w.config.xaxis.title.style.color,
|
||||
cssClass:
|
||||
'apexcharts-xaxis-title-text ' + w.config.xaxis.title.style.cssClass,
|
||||
})
|
||||
|
||||
elYaxisTitle.add(elYAxisTitleText)
|
||||
|
||||
parent.add(elYaxisTitle)
|
||||
}
|
||||
}
|
||||
|
||||
yAxisTitleRotate(realIndex, yAxisOpposite) {
|
||||
let w = this.w
|
||||
|
||||
let graphics = new Graphics(this.ctx)
|
||||
|
||||
let yAxisLabelsCoord = {
|
||||
width: 0,
|
||||
height: 0,
|
||||
}
|
||||
let yAxisTitleCoord = {
|
||||
width: 0,
|
||||
height: 0,
|
||||
}
|
||||
|
||||
let elYAxisLabelsWrap = w.globals.dom.baseEl.querySelector(
|
||||
` .apexcharts-yaxis[rel='${realIndex}'] .apexcharts-yaxis-texts-g`
|
||||
)
|
||||
|
||||
if (elYAxisLabelsWrap !== null) {
|
||||
yAxisLabelsCoord = elYAxisLabelsWrap.getBoundingClientRect()
|
||||
}
|
||||
|
||||
let yAxisTitle = w.globals.dom.baseEl.querySelector(
|
||||
`.apexcharts-yaxis[rel='${realIndex}'] .apexcharts-yaxis-title text`
|
||||
)
|
||||
|
||||
if (yAxisTitle !== null) {
|
||||
yAxisTitleCoord = yAxisTitle.getBoundingClientRect()
|
||||
}
|
||||
|
||||
if (yAxisTitle !== null) {
|
||||
let x = this.xPaddingForYAxisTitle(
|
||||
realIndex,
|
||||
yAxisLabelsCoord,
|
||||
yAxisTitleCoord,
|
||||
yAxisOpposite
|
||||
)
|
||||
|
||||
yAxisTitle.setAttribute('x', x.xPos - (yAxisOpposite ? 10 : 0))
|
||||
}
|
||||
|
||||
if (yAxisTitle !== null) {
|
||||
let titleRotatingCenter = graphics.rotateAroundCenter(yAxisTitle)
|
||||
yAxisTitle.setAttribute(
|
||||
'transform',
|
||||
`rotate(${
|
||||
yAxisOpposite
|
||||
? w.config.yaxis[realIndex].title.rotate * -1
|
||||
: w.config.yaxis[realIndex].title.rotate
|
||||
} ${titleRotatingCenter.x} ${titleRotatingCenter.y})`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
xPaddingForYAxisTitle(
|
||||
realIndex,
|
||||
yAxisLabelsCoord,
|
||||
yAxisTitleCoord,
|
||||
yAxisOpposite
|
||||
) {
|
||||
let w = this.w
|
||||
let oppositeAxisCount = 0
|
||||
|
||||
let x = 0
|
||||
let padd = 10
|
||||
|
||||
if (w.config.yaxis[realIndex].title.text === undefined || realIndex < 0) {
|
||||
return {
|
||||
xPos: x,
|
||||
padd: 0,
|
||||
}
|
||||
}
|
||||
|
||||
if (yAxisOpposite) {
|
||||
x =
|
||||
yAxisLabelsCoord.width +
|
||||
w.config.yaxis[realIndex].title.offsetX +
|
||||
yAxisTitleCoord.width / 2 +
|
||||
padd / 2
|
||||
|
||||
oppositeAxisCount += 1
|
||||
|
||||
if (oppositeAxisCount === 0) {
|
||||
x = x - padd / 2
|
||||
}
|
||||
} else {
|
||||
x =
|
||||
yAxisLabelsCoord.width * -1 +
|
||||
w.config.yaxis[realIndex].title.offsetX +
|
||||
padd / 2 +
|
||||
yAxisTitleCoord.width / 2
|
||||
|
||||
if (w.globals.isBarHorizontal) {
|
||||
padd = 25
|
||||
x =
|
||||
yAxisLabelsCoord.width * -1 -
|
||||
w.config.yaxis[realIndex].title.offsetX -
|
||||
padd
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
xPos: x,
|
||||
padd,
|
||||
}
|
||||
}
|
||||
|
||||
// sets the x position of the y-axis by counting the labels width, title width and any offset
|
||||
setYAxisXPosition(yaxisLabelCoords, yTitleCoords) {
|
||||
let w = this.w
|
||||
|
||||
let xLeft = 0
|
||||
let xRight = 0
|
||||
let leftOffsetX = 18
|
||||
let rightOffsetX = 1
|
||||
|
||||
if (w.config.yaxis.length > 1) {
|
||||
this.multipleYs = true
|
||||
}
|
||||
|
||||
w.config.yaxis.map((yaxe, index) => {
|
||||
let shouldNotDrawAxis =
|
||||
w.globals.ignoreYAxisIndexes.indexOf(index) > -1 ||
|
||||
!yaxe.show ||
|
||||
yaxe.floating ||
|
||||
yaxisLabelCoords[index].width === 0
|
||||
|
||||
let axisWidth = yaxisLabelCoords[index].width + yTitleCoords[index].width
|
||||
|
||||
if (!yaxe.opposite) {
|
||||
xLeft = w.globals.translateX - leftOffsetX
|
||||
|
||||
if (!shouldNotDrawAxis) {
|
||||
leftOffsetX = leftOffsetX + axisWidth + 20
|
||||
}
|
||||
|
||||
w.globals.translateYAxisX[index] = xLeft + yaxe.labels.offsetX
|
||||
} else {
|
||||
if (w.globals.isBarHorizontal) {
|
||||
xRight = w.globals.gridWidth + w.globals.translateX - 1
|
||||
|
||||
w.globals.translateYAxisX[index] = xRight - yaxe.labels.offsetX
|
||||
} else {
|
||||
xRight = w.globals.gridWidth + w.globals.translateX + rightOffsetX
|
||||
|
||||
if (!shouldNotDrawAxis) {
|
||||
rightOffsetX = rightOffsetX + axisWidth + 20
|
||||
}
|
||||
|
||||
w.globals.translateYAxisX[index] = xRight - yaxe.labels.offsetX + 20
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setYAxisTextAlignments() {
|
||||
const w = this.w
|
||||
|
||||
let yaxis = w.globals.dom.baseEl.getElementsByClassName(`apexcharts-yaxis`)
|
||||
yaxis = Utils.listToArray(yaxis)
|
||||
yaxis.forEach((y, index) => {
|
||||
const yaxe = w.config.yaxis[index]
|
||||
// proceed only if user has specified alignment
|
||||
if (yaxe && !yaxe.floating && yaxe.labels.align !== undefined) {
|
||||
const yAxisInner = w.globals.dom.baseEl.querySelector(
|
||||
`.apexcharts-yaxis[rel='${index}'] .apexcharts-yaxis-texts-g`
|
||||
)
|
||||
let yAxisTexts = w.globals.dom.baseEl.querySelectorAll(
|
||||
`.apexcharts-yaxis[rel='${index}'] .apexcharts-yaxis-label`
|
||||
)
|
||||
|
||||
yAxisTexts = Utils.listToArray(yAxisTexts)
|
||||
|
||||
const rect = yAxisInner.getBoundingClientRect()
|
||||
|
||||
if (yaxe.labels.align === 'left') {
|
||||
yAxisTexts.forEach((label, lI) => {
|
||||
label.setAttribute('text-anchor', 'start')
|
||||
})
|
||||
if (!yaxe.opposite) {
|
||||
yAxisInner.setAttribute('transform', `translate(-${rect.width}, 0)`)
|
||||
}
|
||||
} else if (yaxe.labels.align === 'center') {
|
||||
yAxisTexts.forEach((label, lI) => {
|
||||
label.setAttribute('text-anchor', 'middle')
|
||||
})
|
||||
yAxisInner.setAttribute(
|
||||
'transform',
|
||||
`translate(${(rect.width / 2) * (!yaxe.opposite ? -1 : 1)}, 0)`
|
||||
)
|
||||
} else if (yaxe.labels.align === 'right') {
|
||||
yAxisTexts.forEach((label, lI) => {
|
||||
label.setAttribute('text-anchor', 'end')
|
||||
})
|
||||
if (yaxe.opposite) {
|
||||
yAxisInner.setAttribute('transform', `translate(${rect.width}, 0)`)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+344
@@ -0,0 +1,344 @@
|
||||
import YAxis from '../axes/YAxis'
|
||||
import Helpers from './Helpers'
|
||||
import DimXAxis from './XAxis'
|
||||
import DimYAxis from './YAxis'
|
||||
import Grid from './Grid'
|
||||
|
||||
/**
|
||||
* ApexCharts Dimensions Class for calculating rects of all elements that are drawn and will be drawn.
|
||||
*
|
||||
* @module Dimensions
|
||||
**/
|
||||
|
||||
export default class Dimensions {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
this.lgRect = {}
|
||||
this.yAxisWidth = 0
|
||||
this.yAxisWidthLeft = 0
|
||||
this.yAxisWidthRight = 0
|
||||
this.xAxisHeight = 0
|
||||
this.isSparkline = this.w.config.chart.sparkline.enabled
|
||||
|
||||
this.dimHelpers = new Helpers(this)
|
||||
this.dimYAxis = new DimYAxis(this)
|
||||
this.dimXAxis = new DimXAxis(this)
|
||||
this.dimGrid = new Grid(this)
|
||||
this.lgWidthForSideLegends = 0
|
||||
this.gridPad = this.w.config.grid.padding
|
||||
this.xPadRight = 0
|
||||
this.xPadLeft = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* @memberof Dimensions
|
||||
* @param {object} w - chart context
|
||||
**/
|
||||
plotCoords() {
|
||||
let w = this.w
|
||||
let gl = w.globals
|
||||
|
||||
this.lgRect = this.dimHelpers.getLegendsRect()
|
||||
|
||||
if (this.isSparkline) {
|
||||
if (w.config.markers.discrete.length > 0 || w.config.markers.size > 0) {
|
||||
Object.entries(this.gridPad).forEach(([k, v]) => {
|
||||
this.gridPad[k] = Math.max(
|
||||
v,
|
||||
this.w.globals.markers.largestSize / 1.5
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
this.gridPad.top = Math.max(w.config.stroke.width / 2, this.gridPad.top)
|
||||
this.gridPad.bottom = Math.max(
|
||||
w.config.stroke.width / 2,
|
||||
this.gridPad.bottom
|
||||
)
|
||||
}
|
||||
|
||||
if (gl.axisCharts) {
|
||||
// for line / area / scatter / column
|
||||
this.setDimensionsForAxisCharts()
|
||||
} else {
|
||||
// for pie / donuts / circle
|
||||
this.setDimensionsForNonAxisCharts()
|
||||
}
|
||||
|
||||
this.dimGrid.gridPadFortitleSubtitle()
|
||||
|
||||
// after calculating everything, apply padding set by user
|
||||
gl.gridHeight = gl.gridHeight - this.gridPad.top - this.gridPad.bottom
|
||||
|
||||
gl.gridWidth =
|
||||
gl.gridWidth -
|
||||
this.gridPad.left -
|
||||
this.gridPad.right -
|
||||
this.xPadRight -
|
||||
this.xPadLeft
|
||||
|
||||
let barWidth = this.dimGrid.gridPadForColumnsInNumericAxis(gl.gridWidth)
|
||||
|
||||
gl.gridWidth = gl.gridWidth - barWidth * 2
|
||||
|
||||
gl.translateX =
|
||||
gl.translateX +
|
||||
this.gridPad.left +
|
||||
this.xPadLeft +
|
||||
(barWidth > 0 ? barWidth + 4 : 0)
|
||||
gl.translateY = gl.translateY + this.gridPad.top
|
||||
}
|
||||
|
||||
setDimensionsForAxisCharts() {
|
||||
let w = this.w
|
||||
let gl = w.globals
|
||||
|
||||
let yaxisLabelCoords = this.dimYAxis.getyAxisLabelsCoords()
|
||||
let yTitleCoords = this.dimYAxis.getyAxisTitleCoords()
|
||||
|
||||
w.globals.yLabelsCoords = []
|
||||
w.globals.yTitleCoords = []
|
||||
w.config.yaxis.map((yaxe, index) => {
|
||||
// store the labels and titles coords in global vars
|
||||
w.globals.yLabelsCoords.push({
|
||||
width: yaxisLabelCoords[index].width,
|
||||
index,
|
||||
})
|
||||
w.globals.yTitleCoords.push({
|
||||
width: yTitleCoords[index].width,
|
||||
index,
|
||||
})
|
||||
})
|
||||
|
||||
this.yAxisWidth = this.dimYAxis.getTotalYAxisWidth()
|
||||
|
||||
let xaxisLabelCoords = this.dimXAxis.getxAxisLabelsCoords()
|
||||
let xaxisGroupLabelCoords = this.dimXAxis.getxAxisGroupLabelsCoords()
|
||||
let xtitleCoords = this.dimXAxis.getxAxisTitleCoords()
|
||||
|
||||
this.conditionalChecksForAxisCoords(
|
||||
xaxisLabelCoords,
|
||||
xtitleCoords,
|
||||
xaxisGroupLabelCoords
|
||||
)
|
||||
|
||||
gl.translateXAxisY = w.globals.rotateXLabels ? this.xAxisHeight / 8 : -4
|
||||
gl.translateXAxisX =
|
||||
w.globals.rotateXLabels &&
|
||||
w.globals.isXNumeric &&
|
||||
w.config.xaxis.labels.rotate <= -45
|
||||
? -this.xAxisWidth / 4
|
||||
: 0
|
||||
|
||||
if (w.globals.isBarHorizontal) {
|
||||
gl.rotateXLabels = false
|
||||
gl.translateXAxisY =
|
||||
-1 * (parseInt(w.config.xaxis.labels.style.fontSize, 10) / 1.5)
|
||||
}
|
||||
|
||||
gl.translateXAxisY = gl.translateXAxisY + w.config.xaxis.labels.offsetY
|
||||
gl.translateXAxisX = gl.translateXAxisX + w.config.xaxis.labels.offsetX
|
||||
|
||||
let yAxisWidth = this.yAxisWidth
|
||||
let xAxisHeight = this.xAxisHeight
|
||||
gl.xAxisLabelsHeight = this.xAxisHeight - xtitleCoords.height
|
||||
gl.xAxisGroupLabelsHeight = gl.xAxisLabelsHeight - xaxisLabelCoords.height
|
||||
gl.xAxisLabelsWidth = this.xAxisWidth
|
||||
gl.xAxisHeight = this.xAxisHeight
|
||||
let translateY = 10
|
||||
|
||||
if (w.config.chart.type === 'radar' || this.isSparkline) {
|
||||
yAxisWidth = 0
|
||||
xAxisHeight = gl.goldenPadding
|
||||
}
|
||||
|
||||
if (this.isSparkline) {
|
||||
this.lgRect = {
|
||||
height: 0,
|
||||
width: 0,
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isSparkline || w.config.chart.type === 'treemap') {
|
||||
yAxisWidth = 0
|
||||
xAxisHeight = 0
|
||||
translateY = 0
|
||||
}
|
||||
|
||||
if (!this.isSparkline) {
|
||||
this.dimXAxis.additionalPaddingXLabels(xaxisLabelCoords)
|
||||
}
|
||||
|
||||
const legendTopBottom = () => {
|
||||
gl.translateX = yAxisWidth
|
||||
gl.gridHeight =
|
||||
gl.svgHeight -
|
||||
this.lgRect.height -
|
||||
xAxisHeight -
|
||||
(!this.isSparkline && w.config.chart.type !== 'treemap'
|
||||
? w.globals.rotateXLabels
|
||||
? 10
|
||||
: 15
|
||||
: 0)
|
||||
gl.gridWidth = gl.svgWidth - yAxisWidth
|
||||
}
|
||||
|
||||
if (w.config.xaxis.position === 'top')
|
||||
translateY = gl.xAxisHeight - w.config.xaxis.axisTicks.height - 5
|
||||
|
||||
switch (w.config.legend.position) {
|
||||
case 'bottom':
|
||||
gl.translateY = translateY
|
||||
legendTopBottom()
|
||||
break
|
||||
case 'top':
|
||||
gl.translateY = this.lgRect.height + translateY
|
||||
legendTopBottom()
|
||||
break
|
||||
case 'left':
|
||||
gl.translateY = translateY
|
||||
gl.translateX = this.lgRect.width + yAxisWidth
|
||||
gl.gridHeight = gl.svgHeight - xAxisHeight - 12
|
||||
gl.gridWidth = gl.svgWidth - this.lgRect.width - yAxisWidth
|
||||
break
|
||||
case 'right':
|
||||
gl.translateY = translateY
|
||||
gl.translateX = yAxisWidth
|
||||
gl.gridHeight = gl.svgHeight - xAxisHeight - 12
|
||||
gl.gridWidth = gl.svgWidth - this.lgRect.width - yAxisWidth - 5
|
||||
break
|
||||
default:
|
||||
throw new Error('Legend position not supported')
|
||||
}
|
||||
|
||||
this.dimGrid.setGridXPosForDualYAxis(yTitleCoords, yaxisLabelCoords)
|
||||
|
||||
// after drawing everything, set the Y axis positions
|
||||
let objyAxis = new YAxis(this.ctx)
|
||||
objyAxis.setYAxisXPosition(yaxisLabelCoords, yTitleCoords)
|
||||
}
|
||||
|
||||
setDimensionsForNonAxisCharts() {
|
||||
let w = this.w
|
||||
let gl = w.globals
|
||||
let cnf = w.config
|
||||
let xPad = 0
|
||||
|
||||
if (w.config.legend.show && !w.config.legend.floating) {
|
||||
xPad = 20
|
||||
}
|
||||
|
||||
const type =
|
||||
cnf.chart.type === 'pie' ||
|
||||
cnf.chart.type === 'polarArea' ||
|
||||
cnf.chart.type === 'donut'
|
||||
? 'pie'
|
||||
: 'radialBar'
|
||||
|
||||
let offY = cnf.plotOptions[type].offsetY
|
||||
let offX = cnf.plotOptions[type].offsetX
|
||||
|
||||
if (!cnf.legend.show || cnf.legend.floating) {
|
||||
gl.gridHeight =
|
||||
gl.svgHeight - cnf.grid.padding.left + cnf.grid.padding.right
|
||||
gl.gridWidth = gl.gridHeight
|
||||
|
||||
gl.translateY = offY
|
||||
gl.translateX = offX + (gl.svgWidth - gl.gridWidth) / 2
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
switch (cnf.legend.position) {
|
||||
case 'bottom':
|
||||
gl.gridHeight = gl.svgHeight - this.lgRect.height - gl.goldenPadding
|
||||
gl.gridWidth = gl.svgWidth
|
||||
gl.translateY = offY - 10
|
||||
gl.translateX = offX + (gl.svgWidth - gl.gridWidth) / 2
|
||||
break
|
||||
case 'top':
|
||||
gl.gridHeight = gl.svgHeight - this.lgRect.height - gl.goldenPadding
|
||||
gl.gridWidth = gl.svgWidth
|
||||
gl.translateY = this.lgRect.height + offY + 10
|
||||
gl.translateX = offX + (gl.svgWidth - gl.gridWidth) / 2
|
||||
break
|
||||
case 'left':
|
||||
gl.gridWidth = gl.svgWidth - this.lgRect.width - xPad
|
||||
gl.gridHeight =
|
||||
cnf.chart.height !== 'auto' ? gl.svgHeight : gl.gridWidth
|
||||
gl.translateY = offY
|
||||
gl.translateX = offX + this.lgRect.width + xPad
|
||||
break
|
||||
case 'right':
|
||||
gl.gridWidth = gl.svgWidth - this.lgRect.width - xPad - 5
|
||||
gl.gridHeight =
|
||||
cnf.chart.height !== 'auto' ? gl.svgHeight : gl.gridWidth
|
||||
gl.translateY = offY
|
||||
gl.translateX = offX + 10
|
||||
break
|
||||
default:
|
||||
throw new Error('Legend position not supported')
|
||||
}
|
||||
}
|
||||
|
||||
conditionalChecksForAxisCoords(
|
||||
xaxisLabelCoords,
|
||||
xtitleCoords,
|
||||
xaxisGroupLabelCoords
|
||||
) {
|
||||
const w = this.w
|
||||
|
||||
const xAxisNum = w.globals.hasXaxisGroups ? 2 : 1
|
||||
|
||||
const baseXAxisHeight =
|
||||
xaxisGroupLabelCoords.height +
|
||||
xaxisLabelCoords.height +
|
||||
xtitleCoords.height
|
||||
const xAxisHeightMultiplicate = w.globals.isMultiLineX
|
||||
? 1.2
|
||||
: w.globals.LINE_HEIGHT_RATIO
|
||||
const rotatedXAxisOffset = w.globals.rotateXLabels ? 22 : 10
|
||||
const rotatedXAxisLegendOffset =
|
||||
w.globals.rotateXLabels && w.config.legend.position === 'bottom'
|
||||
const additionalOffset = rotatedXAxisLegendOffset ? 10 : 0
|
||||
|
||||
this.xAxisHeight =
|
||||
baseXAxisHeight * xAxisHeightMultiplicate +
|
||||
xAxisNum * rotatedXAxisOffset +
|
||||
additionalOffset
|
||||
|
||||
this.xAxisWidth = xaxisLabelCoords.width
|
||||
|
||||
if (
|
||||
this.xAxisHeight - xtitleCoords.height >
|
||||
w.config.xaxis.labels.maxHeight
|
||||
) {
|
||||
this.xAxisHeight = w.config.xaxis.labels.maxHeight
|
||||
}
|
||||
|
||||
if (
|
||||
w.config.xaxis.labels.minHeight &&
|
||||
this.xAxisHeight < w.config.xaxis.labels.minHeight
|
||||
) {
|
||||
this.xAxisHeight = w.config.xaxis.labels.minHeight
|
||||
}
|
||||
|
||||
if (w.config.xaxis.floating) {
|
||||
this.xAxisHeight = 0
|
||||
}
|
||||
|
||||
let minYAxisWidth = 0
|
||||
let maxYAxisWidth = 0
|
||||
w.config.yaxis.forEach((y) => {
|
||||
minYAxisWidth += y.labels.minWidth
|
||||
maxYAxisWidth += y.labels.maxWidth
|
||||
})
|
||||
if (this.yAxisWidth < minYAxisWidth) {
|
||||
this.yAxisWidth = minYAxisWidth
|
||||
}
|
||||
if (this.yAxisWidth > maxYAxisWidth) {
|
||||
this.yAxisWidth = maxYAxisWidth
|
||||
}
|
||||
}
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
import AxesUtils from '../axes/AxesUtils'
|
||||
|
||||
export default class DimGrid {
|
||||
constructor(dCtx) {
|
||||
this.w = dCtx.w
|
||||
this.dCtx = dCtx
|
||||
}
|
||||
|
||||
gridPadForColumnsInNumericAxis(gridWidth) {
|
||||
const w = this.w
|
||||
|
||||
if (w.globals.noData || w.globals.allSeriesCollapsed) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const hasBar = (type) => {
|
||||
return (
|
||||
type === 'bar' ||
|
||||
type === 'rangeBar' ||
|
||||
type === 'candlestick' ||
|
||||
type === 'boxPlot'
|
||||
)
|
||||
}
|
||||
|
||||
const type = w.config.chart.type
|
||||
|
||||
let barWidth = 0
|
||||
let seriesLen = hasBar(type) ? w.config.series.length : 1
|
||||
|
||||
if (w.globals.comboBarCount > 0) {
|
||||
seriesLen = w.globals.comboBarCount
|
||||
}
|
||||
w.globals.collapsedSeries.forEach((c) => {
|
||||
if (hasBar(c.type)) {
|
||||
seriesLen = seriesLen - 1
|
||||
}
|
||||
})
|
||||
if (w.config.chart.stacked) {
|
||||
seriesLen = 1
|
||||
}
|
||||
|
||||
const barsPresent = hasBar(type) || w.globals.comboBarCount > 0
|
||||
|
||||
if (
|
||||
barsPresent &&
|
||||
w.globals.isXNumeric &&
|
||||
!w.globals.isBarHorizontal &&
|
||||
seriesLen > 0
|
||||
) {
|
||||
let xRatio = 0
|
||||
let xRange = Math.abs(w.globals.initialMaxX - w.globals.initialMinX)
|
||||
|
||||
if (xRange <= 3) {
|
||||
xRange = w.globals.dataPoints
|
||||
}
|
||||
|
||||
xRatio = xRange / gridWidth
|
||||
|
||||
let xDivision
|
||||
// max barwidth should be equal to minXDiff to avoid overlap
|
||||
if (w.globals.minXDiff && w.globals.minXDiff / xRatio > 0) {
|
||||
xDivision = w.globals.minXDiff / xRatio
|
||||
}
|
||||
|
||||
if (xDivision > gridWidth / 2) {
|
||||
xDivision = xDivision / 2
|
||||
}
|
||||
// Here, barWidth is assumed to be the width occupied by a group of bars.
|
||||
// There will be one bar in the group for each series plotted.
|
||||
// Note: This version of the following math is different to that over in
|
||||
// Helpers.js. Don't assume they should be the same. Over there,
|
||||
// xDivision is computed differently and it's used on different charts.
|
||||
// They were the same, but the solution to
|
||||
// https://github.com/apexcharts/apexcharts.js/issues/4178
|
||||
// was to remove the division by seriesLen.
|
||||
barWidth =
|
||||
(xDivision * parseInt(w.config.plotOptions.bar.columnWidth, 10)) / 100
|
||||
|
||||
if (barWidth < 1) {
|
||||
barWidth = 1
|
||||
}
|
||||
|
||||
w.globals.barPadForNumericAxis = barWidth
|
||||
}
|
||||
return barWidth
|
||||
}
|
||||
|
||||
gridPadFortitleSubtitle() {
|
||||
const w = this.w
|
||||
const gl = w.globals
|
||||
let gridShrinkOffset =
|
||||
this.dCtx.isSparkline || !w.globals.axisCharts ? 0 : 10
|
||||
|
||||
const titleSubtitle = ['title', 'subtitle']
|
||||
|
||||
titleSubtitle.forEach((t) => {
|
||||
if (w.config[t].text !== undefined) {
|
||||
gridShrinkOffset += w.config[t].margin
|
||||
} else {
|
||||
gridShrinkOffset +=
|
||||
this.dCtx.isSparkline || !w.globals.axisCharts ? 0 : 5
|
||||
}
|
||||
})
|
||||
|
||||
if (
|
||||
w.config.legend.show &&
|
||||
w.config.legend.position === 'bottom' &&
|
||||
!w.config.legend.floating &&
|
||||
!w.globals.axisCharts
|
||||
) {
|
||||
gridShrinkOffset += 10
|
||||
}
|
||||
|
||||
let titleCoords = this.dCtx.dimHelpers.getTitleSubtitleCoords('title')
|
||||
let subtitleCoords = this.dCtx.dimHelpers.getTitleSubtitleCoords('subtitle')
|
||||
|
||||
gl.gridHeight =
|
||||
gl.gridHeight -
|
||||
titleCoords.height -
|
||||
subtitleCoords.height -
|
||||
gridShrinkOffset
|
||||
|
||||
gl.translateY =
|
||||
gl.translateY +
|
||||
titleCoords.height +
|
||||
subtitleCoords.height +
|
||||
gridShrinkOffset
|
||||
}
|
||||
|
||||
setGridXPosForDualYAxis(yTitleCoords, yaxisLabelCoords) {
|
||||
let w = this.w
|
||||
const axesUtils = new AxesUtils(this.dCtx.ctx)
|
||||
|
||||
w.config.yaxis.map((yaxe, index) => {
|
||||
if (
|
||||
w.globals.ignoreYAxisIndexes.indexOf(index) === -1 &&
|
||||
!yaxe.floating &&
|
||||
!axesUtils.isYAxisHidden(index)
|
||||
) {
|
||||
if (yaxe.opposite) {
|
||||
w.globals.translateX =
|
||||
w.globals.translateX -
|
||||
(yaxisLabelCoords[index].width + yTitleCoords[index].width) -
|
||||
parseInt(w.config.yaxis[index].labels.style.fontSize, 10) / 1.2 -
|
||||
12
|
||||
}
|
||||
|
||||
// fixes apexcharts.js#1599
|
||||
if (w.globals.translateX < 2) {
|
||||
w.globals.translateX = 2
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
import Utils from '../../utils/Utils'
|
||||
|
||||
export default class Helpers {
|
||||
constructor(dCtx) {
|
||||
this.w = dCtx.w
|
||||
this.dCtx = dCtx
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Chart Title/Subtitle Dimensions
|
||||
* @memberof Dimensions
|
||||
* @return {{width, height}}
|
||||
**/
|
||||
getTitleSubtitleCoords(type) {
|
||||
let w = this.w
|
||||
let width = 0
|
||||
let height = 0
|
||||
|
||||
const floating =
|
||||
type === 'title' ? w.config.title.floating : w.config.subtitle.floating
|
||||
|
||||
let el = w.globals.dom.baseEl.querySelector(`.apexcharts-${type}-text`)
|
||||
|
||||
if (el !== null && !floating) {
|
||||
let coord = el.getBoundingClientRect()
|
||||
width = coord.width
|
||||
height = w.globals.axisCharts ? coord.height + 5 : coord.height
|
||||
}
|
||||
|
||||
return {
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
|
||||
getLegendsRect() {
|
||||
let w = this.w
|
||||
|
||||
let elLegendWrap = w.globals.dom.elLegendWrap
|
||||
|
||||
if (
|
||||
!w.config.legend.height &&
|
||||
(w.config.legend.position === 'top' ||
|
||||
w.config.legend.position === 'bottom')
|
||||
) {
|
||||
// avoid legend to take up all the space
|
||||
elLegendWrap.style.maxHeight = w.globals.svgHeight / 2 + 'px'
|
||||
}
|
||||
|
||||
let lgRect = Object.assign({}, Utils.getBoundingClientRect(elLegendWrap))
|
||||
|
||||
if (
|
||||
elLegendWrap !== null &&
|
||||
!w.config.legend.floating &&
|
||||
w.config.legend.show
|
||||
) {
|
||||
this.dCtx.lgRect = {
|
||||
x: lgRect.x,
|
||||
y: lgRect.y,
|
||||
height: lgRect.height,
|
||||
width: lgRect.height === 0 ? 0 : lgRect.width
|
||||
}
|
||||
} else {
|
||||
this.dCtx.lgRect = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
height: 0,
|
||||
width: 0
|
||||
}
|
||||
}
|
||||
|
||||
// if legend takes up all of the chart space, we need to restrict it.
|
||||
if (
|
||||
w.config.legend.position === 'left' ||
|
||||
w.config.legend.position === 'right'
|
||||
) {
|
||||
if (this.dCtx.lgRect.width * 1.5 > w.globals.svgWidth) {
|
||||
this.dCtx.lgRect.width = w.globals.svgWidth / 1.5
|
||||
}
|
||||
}
|
||||
|
||||
return this.dCtx.lgRect
|
||||
}
|
||||
|
||||
getLargestStringFromMultiArr(val, arr) {
|
||||
const w = this.w
|
||||
let valArr = val
|
||||
if (w.globals.isMultiLineX) {
|
||||
// if the xaxis labels has multiline texts (array)
|
||||
let maxArrs = arr.map((xl, idx) => {
|
||||
return Array.isArray(xl) ? xl.length : 1
|
||||
})
|
||||
let maxArrLen = Math.max(...maxArrs)
|
||||
let maxArrIndex = maxArrs.indexOf(maxArrLen)
|
||||
valArr = arr[maxArrIndex]
|
||||
}
|
||||
|
||||
return valArr
|
||||
}
|
||||
}
|
||||
+372
@@ -0,0 +1,372 @@
|
||||
import Formatters from '../Formatters'
|
||||
import Graphics from '../Graphics'
|
||||
import Utils from '../../utils/Utils'
|
||||
import DateTime from '../../utils/DateTime'
|
||||
|
||||
export default class DimXAxis {
|
||||
constructor(dCtx) {
|
||||
this.w = dCtx.w
|
||||
this.dCtx = dCtx
|
||||
}
|
||||
|
||||
/**
|
||||
* Get X Axis Dimensions
|
||||
* @memberof Dimensions
|
||||
* @return {{width, height}}
|
||||
**/
|
||||
getxAxisLabelsCoords() {
|
||||
let w = this.w
|
||||
|
||||
let xaxisLabels = w.globals.labels.slice()
|
||||
if (w.config.xaxis.convertedCatToNumeric && xaxisLabels.length === 0) {
|
||||
xaxisLabels = w.globals.categoryLabels
|
||||
}
|
||||
|
||||
let rect
|
||||
|
||||
if (w.globals.timescaleLabels.length > 0) {
|
||||
const coords = this.getxAxisTimeScaleLabelsCoords()
|
||||
rect = {
|
||||
width: coords.width,
|
||||
height: coords.height,
|
||||
}
|
||||
w.globals.rotateXLabels = false
|
||||
} else {
|
||||
this.dCtx.lgWidthForSideLegends =
|
||||
(w.config.legend.position === 'left' ||
|
||||
w.config.legend.position === 'right') &&
|
||||
!w.config.legend.floating
|
||||
? this.dCtx.lgRect.width
|
||||
: 0
|
||||
|
||||
// get the longest string from the labels array and also apply label formatter
|
||||
let xlbFormatter = w.globals.xLabelFormatter
|
||||
// prevent changing xaxisLabels to avoid issues in multi-yaxes - fix #522
|
||||
let val = Utils.getLargestStringFromArr(xaxisLabels)
|
||||
let valArr = this.dCtx.dimHelpers.getLargestStringFromMultiArr(
|
||||
val,
|
||||
xaxisLabels
|
||||
)
|
||||
|
||||
// the labels gets changed for bar charts
|
||||
if (w.globals.isBarHorizontal) {
|
||||
val = w.globals.yAxisScale[0].result.reduce(
|
||||
(a, b) => (a.length > b.length ? a : b),
|
||||
0
|
||||
)
|
||||
valArr = val
|
||||
}
|
||||
|
||||
let xFormat = new Formatters(this.dCtx.ctx)
|
||||
let timestamp = val
|
||||
val = xFormat.xLabelFormat(xlbFormatter, val, timestamp, {
|
||||
i: undefined,
|
||||
dateFormatter: new DateTime(this.dCtx.ctx).formatDate,
|
||||
w,
|
||||
})
|
||||
valArr = xFormat.xLabelFormat(xlbFormatter, valArr, timestamp, {
|
||||
i: undefined,
|
||||
dateFormatter: new DateTime(this.dCtx.ctx).formatDate,
|
||||
w,
|
||||
})
|
||||
|
||||
if (
|
||||
(w.config.xaxis.convertedCatToNumeric && typeof val === 'undefined') ||
|
||||
String(val).trim() === ''
|
||||
) {
|
||||
val = '1'
|
||||
valArr = val
|
||||
}
|
||||
|
||||
let graphics = new Graphics(this.dCtx.ctx)
|
||||
let xLabelrect = graphics.getTextRects(
|
||||
val,
|
||||
w.config.xaxis.labels.style.fontSize
|
||||
)
|
||||
let xArrLabelrect = xLabelrect
|
||||
if (val !== valArr) {
|
||||
xArrLabelrect = graphics.getTextRects(
|
||||
valArr,
|
||||
w.config.xaxis.labels.style.fontSize
|
||||
)
|
||||
}
|
||||
|
||||
rect = {
|
||||
width:
|
||||
xLabelrect.width >= xArrLabelrect.width
|
||||
? xLabelrect.width
|
||||
: xArrLabelrect.width,
|
||||
height:
|
||||
xLabelrect.height >= xArrLabelrect.height
|
||||
? xLabelrect.height
|
||||
: xArrLabelrect.height,
|
||||
}
|
||||
|
||||
if (
|
||||
(rect.width * xaxisLabels.length >
|
||||
w.globals.svgWidth -
|
||||
this.dCtx.lgWidthForSideLegends -
|
||||
this.dCtx.yAxisWidth -
|
||||
this.dCtx.gridPad.left -
|
||||
this.dCtx.gridPad.right &&
|
||||
w.config.xaxis.labels.rotate !== 0) ||
|
||||
w.config.xaxis.labels.rotateAlways
|
||||
) {
|
||||
if (!w.globals.isBarHorizontal) {
|
||||
w.globals.rotateXLabels = true
|
||||
const getRotatedTextRects = (text) => {
|
||||
return graphics.getTextRects(
|
||||
text,
|
||||
w.config.xaxis.labels.style.fontSize,
|
||||
w.config.xaxis.labels.style.fontFamily,
|
||||
`rotate(${w.config.xaxis.labels.rotate} 0 0)`,
|
||||
false
|
||||
)
|
||||
}
|
||||
xLabelrect = getRotatedTextRects(val)
|
||||
if (val !== valArr) {
|
||||
xArrLabelrect = getRotatedTextRects(valArr)
|
||||
}
|
||||
|
||||
rect.height =
|
||||
(xLabelrect.height > xArrLabelrect.height
|
||||
? xLabelrect.height
|
||||
: xArrLabelrect.height) / 1.5
|
||||
rect.width =
|
||||
xLabelrect.width > xArrLabelrect.width
|
||||
? xLabelrect.width
|
||||
: xArrLabelrect.width
|
||||
}
|
||||
} else {
|
||||
w.globals.rotateXLabels = false
|
||||
}
|
||||
}
|
||||
|
||||
if (!w.config.xaxis.labels.show) {
|
||||
rect = {
|
||||
width: 0,
|
||||
height: 0,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get X Axis Label Group height
|
||||
* @memberof Dimensions
|
||||
* @return {{width, height}}
|
||||
*/
|
||||
getxAxisGroupLabelsCoords() {
|
||||
let w = this.w
|
||||
|
||||
if (!w.globals.hasXaxisGroups) {
|
||||
return { width: 0, height: 0 }
|
||||
}
|
||||
|
||||
const fontSize =
|
||||
w.config.xaxis.group.style?.fontSize ||
|
||||
w.config.xaxis.labels.style.fontSize
|
||||
|
||||
let xaxisLabels = w.globals.groups.map((g) => g.title)
|
||||
|
||||
let rect
|
||||
|
||||
// prevent changing xaxisLabels to avoid issues in multi-yaxes - fix #522
|
||||
let val = Utils.getLargestStringFromArr(xaxisLabels)
|
||||
let valArr = this.dCtx.dimHelpers.getLargestStringFromMultiArr(
|
||||
val,
|
||||
xaxisLabels
|
||||
)
|
||||
|
||||
let graphics = new Graphics(this.dCtx.ctx)
|
||||
let xLabelrect = graphics.getTextRects(val, fontSize)
|
||||
let xArrLabelrect = xLabelrect
|
||||
if (val !== valArr) {
|
||||
xArrLabelrect = graphics.getTextRects(valArr, fontSize)
|
||||
}
|
||||
|
||||
rect = {
|
||||
width:
|
||||
xLabelrect.width >= xArrLabelrect.width
|
||||
? xLabelrect.width
|
||||
: xArrLabelrect.width,
|
||||
height:
|
||||
xLabelrect.height >= xArrLabelrect.height
|
||||
? xLabelrect.height
|
||||
: xArrLabelrect.height,
|
||||
}
|
||||
|
||||
if (!w.config.xaxis.labels.show) {
|
||||
rect = {
|
||||
width: 0,
|
||||
height: 0,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get X Axis Title Dimensions
|
||||
* @memberof Dimensions
|
||||
* @return {{width, height}}
|
||||
**/
|
||||
getxAxisTitleCoords() {
|
||||
let w = this.w
|
||||
let width = 0
|
||||
let height = 0
|
||||
|
||||
if (w.config.xaxis.title.text !== undefined) {
|
||||
let graphics = new Graphics(this.dCtx.ctx)
|
||||
|
||||
let rect = graphics.getTextRects(
|
||||
w.config.xaxis.title.text,
|
||||
w.config.xaxis.title.style.fontSize
|
||||
)
|
||||
|
||||
width = rect.width
|
||||
height = rect.height
|
||||
}
|
||||
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
getxAxisTimeScaleLabelsCoords() {
|
||||
let w = this.w
|
||||
let rect
|
||||
|
||||
this.dCtx.timescaleLabels = w.globals.timescaleLabels.slice()
|
||||
|
||||
let labels = this.dCtx.timescaleLabels.map((label) => label.value)
|
||||
|
||||
// get the longest string from the labels array and also apply label formatter to it
|
||||
let val = labels.reduce((a, b) => {
|
||||
// if undefined, maybe user didn't pass the datetime(x) values
|
||||
if (typeof a === 'undefined') {
|
||||
console.error(
|
||||
'You have possibly supplied invalid Date format. Please supply a valid JavaScript Date'
|
||||
)
|
||||
return 0
|
||||
} else {
|
||||
return a.length > b.length ? a : b
|
||||
}
|
||||
}, 0)
|
||||
|
||||
let graphics = new Graphics(this.dCtx.ctx)
|
||||
rect = graphics.getTextRects(val, w.config.xaxis.labels.style.fontSize)
|
||||
|
||||
let totalWidthRotated = rect.width * 1.05 * labels.length
|
||||
|
||||
if (
|
||||
totalWidthRotated > w.globals.gridWidth &&
|
||||
w.config.xaxis.labels.rotate !== 0
|
||||
) {
|
||||
w.globals.overlappingXLabels = true
|
||||
}
|
||||
|
||||
return rect
|
||||
}
|
||||
|
||||
// In certain cases, the last labels gets cropped in xaxis.
|
||||
// Hence, we add some additional padding based on the label length to avoid the last label being cropped or we don't draw it at all
|
||||
additionalPaddingXLabels(xaxisLabelCoords) {
|
||||
const w = this.w
|
||||
const gl = w.globals
|
||||
const cnf = w.config
|
||||
const xtype = cnf.xaxis.type
|
||||
|
||||
let lbWidth = xaxisLabelCoords.width
|
||||
|
||||
gl.skipLastTimelinelabel = false
|
||||
gl.skipFirstTimelinelabel = false
|
||||
const isBarOpposite =
|
||||
w.config.yaxis[0].opposite && w.globals.isBarHorizontal
|
||||
|
||||
const isCollapsed = (i) => gl.collapsedSeriesIndices.indexOf(i) !== -1
|
||||
|
||||
const rightPad = (yaxe) => {
|
||||
if (this.dCtx.timescaleLabels && this.dCtx.timescaleLabels.length) {
|
||||
// for timeline labels, we take the last label and check if it exceeds gridWidth
|
||||
const firstimescaleLabel = this.dCtx.timescaleLabels[0]
|
||||
const lastTimescaleLabel =
|
||||
this.dCtx.timescaleLabels[this.dCtx.timescaleLabels.length - 1]
|
||||
|
||||
const lastLabelPosition =
|
||||
lastTimescaleLabel.position +
|
||||
lbWidth / 1.75 -
|
||||
this.dCtx.yAxisWidthRight
|
||||
|
||||
const firstLabelPosition =
|
||||
firstimescaleLabel.position -
|
||||
lbWidth / 1.75 +
|
||||
this.dCtx.yAxisWidthLeft
|
||||
|
||||
let lgRightRectWidth =
|
||||
w.config.legend.position === 'right' && this.dCtx.lgRect.width > 0
|
||||
? this.dCtx.lgRect.width
|
||||
: 0
|
||||
if (
|
||||
lastLabelPosition >
|
||||
gl.svgWidth - gl.translateX - lgRightRectWidth
|
||||
) {
|
||||
gl.skipLastTimelinelabel = true
|
||||
}
|
||||
|
||||
if (
|
||||
firstLabelPosition <
|
||||
-((!yaxe.show || yaxe.floating) &&
|
||||
(cnf.chart.type === 'bar' ||
|
||||
cnf.chart.type === 'candlestick' ||
|
||||
cnf.chart.type === 'rangeBar' ||
|
||||
cnf.chart.type === 'boxPlot')
|
||||
? lbWidth / 1.75
|
||||
: 10)
|
||||
) {
|
||||
gl.skipFirstTimelinelabel = true
|
||||
}
|
||||
} else if (xtype === 'datetime') {
|
||||
// If user has enabled DateTime, but uses own's formatter
|
||||
if (this.dCtx.gridPad.right < lbWidth && !gl.rotateXLabels) {
|
||||
gl.skipLastTimelinelabel = true
|
||||
}
|
||||
} else if (xtype !== 'datetime') {
|
||||
if (
|
||||
this.dCtx.gridPad.right < lbWidth / 2 - this.dCtx.yAxisWidthRight &&
|
||||
!gl.rotateXLabels &&
|
||||
!w.config.xaxis.labels.trim &&
|
||||
(w.config.xaxis.tickPlacement !== 'between' ||
|
||||
w.globals.isBarHorizontal)
|
||||
) {
|
||||
this.dCtx.xPadRight = lbWidth / 2 + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const padYAxe = (yaxe, i) => {
|
||||
if (cnf.yaxis.length > 1 && isCollapsed(i)) return
|
||||
|
||||
rightPad(yaxe)
|
||||
}
|
||||
|
||||
cnf.yaxis.forEach((yaxe, i) => {
|
||||
if (isBarOpposite) {
|
||||
if (this.dCtx.gridPad.left < lbWidth) {
|
||||
this.dCtx.xPadLeft = lbWidth / 2 + 1
|
||||
}
|
||||
this.dCtx.xPadRight = lbWidth / 2 + 1
|
||||
} else {
|
||||
padYAxe(yaxe, i)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
import Graphics from '../Graphics'
|
||||
import Utils from '../../utils/Utils'
|
||||
import AxesUtils from '../axes/AxesUtils'
|
||||
|
||||
export default class DimYAxis {
|
||||
constructor(dCtx) {
|
||||
this.w = dCtx.w
|
||||
this.dCtx = dCtx
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Y Axis Dimensions
|
||||
* @memberof Dimensions
|
||||
* @return {{width, height}}
|
||||
**/
|
||||
getyAxisLabelsCoords() {
|
||||
let w = this.w
|
||||
|
||||
let width = 0
|
||||
let height = 0
|
||||
let ret = []
|
||||
let labelPad = 10
|
||||
const axesUtils = new AxesUtils(this.dCtx.ctx)
|
||||
|
||||
w.config.yaxis.map((yaxe, index) => {
|
||||
const formatterArgs = {
|
||||
seriesIndex: index,
|
||||
dataPointIndex: -1,
|
||||
w,
|
||||
}
|
||||
const yS = w.globals.yAxisScale[index]
|
||||
let yAxisMinWidth = 0
|
||||
if (
|
||||
!axesUtils.isYAxisHidden(index) &&
|
||||
yaxe.labels.show &&
|
||||
yaxe.labels.minWidth !== undefined
|
||||
)
|
||||
yAxisMinWidth = yaxe.labels.minWidth
|
||||
|
||||
if (
|
||||
!axesUtils.isYAxisHidden(index) &&
|
||||
yaxe.labels.show &&
|
||||
yS.result.length
|
||||
) {
|
||||
let lbFormatter = w.globals.yLabelFormatters[index]
|
||||
let minV = yS.niceMin === Number.MIN_VALUE ? 0 : yS.niceMin
|
||||
let val = yS.result.reduce((acc, curr) => {
|
||||
return String(lbFormatter(acc, formatterArgs))?.length >
|
||||
String(lbFormatter(curr, formatterArgs))?.length
|
||||
? acc
|
||||
: curr
|
||||
}, minV)
|
||||
|
||||
val = lbFormatter(val, formatterArgs)
|
||||
|
||||
// the second parameter -1 is the index of tick which user can use in the formatter
|
||||
let valArr = val
|
||||
|
||||
// if user has specified a custom formatter, and the result is null or empty, we need to discard the formatter and take the value as it is.
|
||||
if (typeof val === 'undefined' || val.length === 0) {
|
||||
val = yS.niceMax
|
||||
}
|
||||
|
||||
if (w.globals.isBarHorizontal) {
|
||||
labelPad = 0
|
||||
|
||||
let barYaxisLabels = w.globals.labels.slice()
|
||||
|
||||
// get the longest string from the labels array and also apply label formatter to it
|
||||
val = Utils.getLargestStringFromArr(barYaxisLabels)
|
||||
|
||||
val = lbFormatter(val, { seriesIndex: index, dataPointIndex: -1, w })
|
||||
valArr = this.dCtx.dimHelpers.getLargestStringFromMultiArr(
|
||||
val,
|
||||
barYaxisLabels
|
||||
)
|
||||
}
|
||||
|
||||
let graphics = new Graphics(this.dCtx.ctx)
|
||||
|
||||
let rotateStr = 'rotate('.concat(yaxe.labels.rotate, ' 0 0)')
|
||||
let rect = graphics.getTextRects(
|
||||
val,
|
||||
yaxe.labels.style.fontSize,
|
||||
yaxe.labels.style.fontFamily,
|
||||
rotateStr,
|
||||
false
|
||||
)
|
||||
|
||||
let arrLabelrect = rect
|
||||
|
||||
if (val !== valArr) {
|
||||
arrLabelrect = graphics.getTextRects(
|
||||
valArr,
|
||||
yaxe.labels.style.fontSize,
|
||||
yaxe.labels.style.fontFamily,
|
||||
rotateStr,
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
ret.push({
|
||||
width:
|
||||
(yAxisMinWidth > arrLabelrect.width || yAxisMinWidth > rect.width
|
||||
? yAxisMinWidth
|
||||
: arrLabelrect.width > rect.width
|
||||
? arrLabelrect.width
|
||||
: rect.width) + labelPad,
|
||||
height:
|
||||
arrLabelrect.height > rect.height
|
||||
? arrLabelrect.height
|
||||
: rect.height,
|
||||
})
|
||||
} else {
|
||||
ret.push({
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Y Axis Dimensions
|
||||
* @memberof Dimensions
|
||||
* @return {{width, height}}
|
||||
**/
|
||||
getyAxisTitleCoords() {
|
||||
let w = this.w
|
||||
let ret = []
|
||||
|
||||
w.config.yaxis.map((yaxe, index) => {
|
||||
if (yaxe.show && yaxe.title.text !== undefined) {
|
||||
let graphics = new Graphics(this.dCtx.ctx)
|
||||
let rotateStr = 'rotate('.concat(yaxe.title.rotate, ' 0 0)')
|
||||
let rect = graphics.getTextRects(
|
||||
yaxe.title.text,
|
||||
yaxe.title.style.fontSize,
|
||||
yaxe.title.style.fontFamily,
|
||||
rotateStr,
|
||||
false
|
||||
)
|
||||
|
||||
ret.push({
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
})
|
||||
} else {
|
||||
ret.push({
|
||||
width: 0,
|
||||
height: 0,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
getTotalYAxisWidth() {
|
||||
let w = this.w
|
||||
let yAxisWidth = 0
|
||||
let yAxisWidthLeft = 0
|
||||
let yAxisWidthRight = 0
|
||||
let padding = w.globals.yAxisScale.length > 1 ? 10 : 0
|
||||
const axesUtils = new AxesUtils(this.dCtx.ctx)
|
||||
|
||||
const isHiddenYAxis = function (index) {
|
||||
return w.globals.ignoreYAxisIndexes.indexOf(index) > -1
|
||||
}
|
||||
|
||||
const padForLabelTitle = (coord, index) => {
|
||||
let floating = w.config.yaxis[index].floating
|
||||
let width = 0
|
||||
|
||||
if (coord.width > 0 && !floating) {
|
||||
width = coord.width + padding
|
||||
if (isHiddenYAxis(index)) {
|
||||
width = width - coord.width - padding
|
||||
}
|
||||
} else {
|
||||
width = floating || axesUtils.isYAxisHidden(index) ? 0 : 5
|
||||
}
|
||||
|
||||
w.config.yaxis[index].opposite
|
||||
? (yAxisWidthRight = yAxisWidthRight + width)
|
||||
: (yAxisWidthLeft = yAxisWidthLeft + width)
|
||||
|
||||
yAxisWidth = yAxisWidth + width
|
||||
}
|
||||
|
||||
w.globals.yLabelsCoords.map((yLabelCoord, index) => {
|
||||
padForLabelTitle(yLabelCoord, index)
|
||||
})
|
||||
|
||||
w.globals.yTitleCoords.map((yTitleCoord, index) => {
|
||||
padForLabelTitle(yTitleCoord, index)
|
||||
})
|
||||
|
||||
if (w.globals.isBarHorizontal && !w.config.yaxis[0].floating) {
|
||||
yAxisWidth =
|
||||
w.globals.yLabelsCoords[0].width + w.globals.yTitleCoords[0].width + 15
|
||||
}
|
||||
|
||||
this.dCtx.yAxisWidthLeft = yAxisWidthLeft
|
||||
this.dCtx.yAxisWidthRight = yAxisWidthRight
|
||||
|
||||
return yAxisWidth
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
export default class Destroy {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
}
|
||||
|
||||
clear({ isUpdating }) {
|
||||
if (this.ctx.zoomPanSelection) {
|
||||
this.ctx.zoomPanSelection.destroy()
|
||||
}
|
||||
if (this.ctx.toolbar) {
|
||||
this.ctx.toolbar.destroy()
|
||||
}
|
||||
|
||||
this.ctx.animations = null
|
||||
this.ctx.axes = null
|
||||
this.ctx.annotations = null
|
||||
this.ctx.core = null
|
||||
this.ctx.data = null
|
||||
this.ctx.grid = null
|
||||
this.ctx.series = null
|
||||
this.ctx.responsive = null
|
||||
this.ctx.theme = null
|
||||
this.ctx.formatters = null
|
||||
this.ctx.titleSubtitle = null
|
||||
this.ctx.legend = null
|
||||
this.ctx.dimensions = null
|
||||
this.ctx.options = null
|
||||
this.ctx.crosshairs = null
|
||||
this.ctx.zoomPanSelection = null
|
||||
this.ctx.updateHelpers = null
|
||||
this.ctx.toolbar = null
|
||||
this.ctx.localization = null
|
||||
this.ctx.w.globals.tooltip = null
|
||||
this.clearDomElements({ isUpdating })
|
||||
}
|
||||
|
||||
killSVG(draw) {
|
||||
draw.each(function(i, children) {
|
||||
this.removeClass('*')
|
||||
this.off()
|
||||
this.stop()
|
||||
}, true)
|
||||
draw.ungroup()
|
||||
draw.clear()
|
||||
}
|
||||
|
||||
clearDomElements({ isUpdating }) {
|
||||
const elSVG = this.w.globals.dom.Paper.node
|
||||
// fixes apexcharts.js#1654 & vue-apexcharts#256
|
||||
if (elSVG.parentNode && elSVG.parentNode.parentNode && !isUpdating) {
|
||||
elSVG.parentNode.parentNode.style.minHeight = 'unset'
|
||||
}
|
||||
|
||||
// detach root event
|
||||
const baseEl = this.w.globals.dom.baseEl
|
||||
if (baseEl) {
|
||||
// see https://github.com/apexcharts/vue-apexcharts/issues/275
|
||||
this.ctx.eventList.forEach((event) => {
|
||||
baseEl.removeEventListener(event, this.ctx.events.documentEvent)
|
||||
})
|
||||
}
|
||||
|
||||
const domEls = this.w.globals.dom
|
||||
|
||||
if (this.ctx.el !== null) {
|
||||
// remove all child elements - resetting the whole chart
|
||||
while (this.ctx.el.firstChild) {
|
||||
this.ctx.el.removeChild(this.ctx.el.firstChild)
|
||||
}
|
||||
}
|
||||
|
||||
this.killSVG(domEls.Paper)
|
||||
domEls.Paper.remove()
|
||||
|
||||
domEls.elWrap = null
|
||||
domEls.elGraphical = null
|
||||
domEls.elLegendWrap = null
|
||||
domEls.elLegendForeign = null
|
||||
domEls.baseEl = null
|
||||
domEls.elGridRect = null
|
||||
domEls.elGridRectMask = null
|
||||
domEls.elGridRectMarkerMask = null
|
||||
domEls.elForecastMask = null
|
||||
domEls.elNonForecastMask = null
|
||||
domEls.elDefs = null
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
import Events from '../Events'
|
||||
import Localization from './Localization'
|
||||
import Animations from '../Animations'
|
||||
import Axes from '../axes/Axes'
|
||||
import Config from '../settings/Config'
|
||||
import CoreUtils from '../CoreUtils'
|
||||
import Crosshairs from '../Crosshairs'
|
||||
import Grid from '../axes/Grid'
|
||||
import Graphics from '../Graphics'
|
||||
import Exports from '../Exports'
|
||||
import Options from '../settings/Options'
|
||||
import Responsive from '../Responsive'
|
||||
import Series from '../Series'
|
||||
import Theme from '../Theme'
|
||||
import Formatters from '../Formatters'
|
||||
import TitleSubtitle from '../TitleSubtitle'
|
||||
import Legend from '../legend/Legend'
|
||||
import Toolbar from '../Toolbar'
|
||||
import Dimensions from '../dimensions/Dimensions'
|
||||
import ZoomPanSelection from '../ZoomPanSelection'
|
||||
import Tooltip from '../tooltip/Tooltip'
|
||||
import Core from '../Core'
|
||||
import Data from '../Data'
|
||||
import UpdateHelpers from './UpdateHelpers'
|
||||
|
||||
import '../../svgjs/svg.js'
|
||||
import 'svg.filter.js'
|
||||
import 'svg.pathmorphing.js'
|
||||
import 'svg.draggable.js'
|
||||
import 'svg.select.js'
|
||||
import 'svg.resize.js'
|
||||
|
||||
// global Apex object which user can use to override chart's defaults globally
|
||||
if (typeof window.Apex === 'undefined') {
|
||||
window.Apex = {}
|
||||
}
|
||||
|
||||
export default class InitCtxVariables {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
}
|
||||
|
||||
initModules() {
|
||||
this.ctx.publicMethods = [
|
||||
'updateOptions',
|
||||
'updateSeries',
|
||||
'appendData',
|
||||
'appendSeries',
|
||||
'isSeriesHidden',
|
||||
'toggleSeries',
|
||||
'showSeries',
|
||||
'hideSeries',
|
||||
'setLocale',
|
||||
'resetSeries',
|
||||
'zoomX',
|
||||
'toggleDataPointSelection',
|
||||
'dataURI',
|
||||
'exportToCSV',
|
||||
'addXaxisAnnotation',
|
||||
'addYaxisAnnotation',
|
||||
'addPointAnnotation',
|
||||
'clearAnnotations',
|
||||
'removeAnnotation',
|
||||
'paper',
|
||||
'destroy'
|
||||
]
|
||||
|
||||
this.ctx.eventList = [
|
||||
'click',
|
||||
'mousedown',
|
||||
'mousemove',
|
||||
'mouseleave',
|
||||
'touchstart',
|
||||
'touchmove',
|
||||
'touchleave',
|
||||
'mouseup',
|
||||
'touchend'
|
||||
]
|
||||
|
||||
this.ctx.animations = new Animations(this.ctx)
|
||||
this.ctx.axes = new Axes(this.ctx)
|
||||
this.ctx.core = new Core(this.ctx.el, this.ctx)
|
||||
this.ctx.config = new Config({})
|
||||
this.ctx.data = new Data(this.ctx)
|
||||
this.ctx.grid = new Grid(this.ctx)
|
||||
this.ctx.graphics = new Graphics(this.ctx)
|
||||
this.ctx.coreUtils = new CoreUtils(this.ctx)
|
||||
this.ctx.crosshairs = new Crosshairs(this.ctx)
|
||||
this.ctx.events = new Events(this.ctx)
|
||||
this.ctx.exports = new Exports(this.ctx)
|
||||
this.ctx.localization = new Localization(this.ctx)
|
||||
this.ctx.options = new Options()
|
||||
this.ctx.responsive = new Responsive(this.ctx)
|
||||
this.ctx.series = new Series(this.ctx)
|
||||
this.ctx.theme = new Theme(this.ctx)
|
||||
this.ctx.formatters = new Formatters(this.ctx)
|
||||
this.ctx.titleSubtitle = new TitleSubtitle(this.ctx)
|
||||
this.ctx.legend = new Legend(this.ctx)
|
||||
this.ctx.toolbar = new Toolbar(this.ctx)
|
||||
this.ctx.tooltip = new Tooltip(this.ctx)
|
||||
this.ctx.dimensions = new Dimensions(this.ctx)
|
||||
this.ctx.updateHelpers = new UpdateHelpers(this.ctx)
|
||||
this.ctx.zoomPanSelection = new ZoomPanSelection(this.ctx)
|
||||
this.ctx.w.globals.tooltip = new Tooltip(this.ctx)
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import Utils from '../../utils/Utils'
|
||||
|
||||
import en from '../../locales/en.json'
|
||||
|
||||
export default class Localization {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
}
|
||||
|
||||
setCurrentLocaleValues(localeName) {
|
||||
let locales = this.w.config.chart.locales
|
||||
|
||||
// check if user has specified locales in global Apex variable
|
||||
// if yes - then extend those with local chart's locale
|
||||
if (
|
||||
window.Apex.chart &&
|
||||
window.Apex.chart.locales &&
|
||||
window.Apex.chart.locales.length > 0
|
||||
) {
|
||||
locales = this.w.config.chart.locales.concat(window.Apex.chart.locales)
|
||||
}
|
||||
|
||||
// find the locale from the array of locales which user has set (either by chart.defaultLocale or by calling setLocale() method.)
|
||||
const selectedLocale = locales.filter((c) => c.name === localeName)[0]
|
||||
|
||||
if (selectedLocale) {
|
||||
// create a complete locale object by extending defaults so you don't get undefined errors.
|
||||
let ret = Utils.extend(en, selectedLocale)
|
||||
|
||||
// store these locale options in global var for ease access
|
||||
this.w.globals.locale = ret.options
|
||||
} else {
|
||||
throw new Error(
|
||||
'Wrong locale name provided. Please make sure you set the correct locale name in options'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+303
@@ -0,0 +1,303 @@
|
||||
import Defaults from '../settings/Defaults'
|
||||
import Config from '../settings/Config'
|
||||
import CoreUtils from '../CoreUtils'
|
||||
import Graphics from '../Graphics'
|
||||
import Utils from '../../utils/Utils'
|
||||
|
||||
export default class UpdateHelpers {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
}
|
||||
|
||||
/**
|
||||
* private method to update Options.
|
||||
*
|
||||
* @param {object} options - A new config object can be passed which will be merged with the existing config object
|
||||
* @param {boolean} redraw - should redraw from beginning or should use existing paths and redraw from there
|
||||
* @param {boolean} animate - should animate or not on updating Options
|
||||
* @param {boolean} overwriteInitialConfig - should update the initial config or not
|
||||
*/
|
||||
_updateOptions(
|
||||
options,
|
||||
redraw = false,
|
||||
animate = true,
|
||||
updateSyncedCharts = true,
|
||||
overwriteInitialConfig = false
|
||||
) {
|
||||
return new Promise((resolve) => {
|
||||
let charts = [this.ctx]
|
||||
if (updateSyncedCharts) {
|
||||
charts = this.ctx.getSyncedCharts()
|
||||
}
|
||||
|
||||
if (this.ctx.w.globals.isExecCalled) {
|
||||
// If the user called exec method, we don't want to get grouped charts as user specifically provided a chartID to update
|
||||
charts = [this.ctx]
|
||||
this.ctx.w.globals.isExecCalled = false
|
||||
}
|
||||
|
||||
charts.forEach((ch, chartIndex) => {
|
||||
let w = ch.w
|
||||
|
||||
w.globals.shouldAnimate = animate
|
||||
|
||||
if (!redraw) {
|
||||
w.globals.resized = true
|
||||
w.globals.dataChanged = true
|
||||
|
||||
if (animate) {
|
||||
ch.series.getPreviousPaths()
|
||||
}
|
||||
}
|
||||
|
||||
if (options && typeof options === 'object') {
|
||||
ch.config = new Config(options)
|
||||
options = CoreUtils.extendArrayProps(ch.config, options, w)
|
||||
|
||||
// fixes #914, #623
|
||||
if (ch.w.globals.chartID !== this.ctx.w.globals.chartID) {
|
||||
// don't overwrite series of synchronized charts
|
||||
delete options.series
|
||||
}
|
||||
|
||||
w.config = Utils.extend(w.config, options)
|
||||
|
||||
if (overwriteInitialConfig) {
|
||||
// we need to forget the lastXAxis and lastYAxis as user forcefully overwriteInitialConfig. If we do not do this, and next time when user zooms the chart after setting yaxis.min/max or xaxis.min/max - the stored lastXAxis will never allow the chart to use the updated min/max by user.
|
||||
w.globals.lastXAxis = options.xaxis
|
||||
? Utils.clone(options.xaxis)
|
||||
: []
|
||||
w.globals.lastYAxis = options.yaxis
|
||||
? Utils.clone(options.yaxis)
|
||||
: []
|
||||
|
||||
// After forgetting lastAxes, we need to restore the new config in initialConfig/initialSeries
|
||||
w.globals.initialConfig = Utils.extend({}, w.config)
|
||||
w.globals.initialSeries = Utils.clone(w.config.series)
|
||||
|
||||
if (options.series) {
|
||||
// Replace the collapsed series data
|
||||
for (
|
||||
let i = 0;
|
||||
i < w.globals.collapsedSeriesIndices.length;
|
||||
i++
|
||||
) {
|
||||
let series =
|
||||
w.config.series[w.globals.collapsedSeriesIndices[i]]
|
||||
w.globals.collapsedSeries[i].data = w.globals.axisCharts
|
||||
? series.data.slice()
|
||||
: series
|
||||
}
|
||||
for (
|
||||
let i = 0;
|
||||
i < w.globals.ancillaryCollapsedSeriesIndices.length;
|
||||
i++
|
||||
) {
|
||||
let series =
|
||||
w.config.series[w.globals.ancillaryCollapsedSeriesIndices[i]]
|
||||
w.globals.ancillaryCollapsedSeries[i].data = w.globals
|
||||
.axisCharts
|
||||
? series.data.slice()
|
||||
: series
|
||||
}
|
||||
|
||||
// Ensure that auto-generated axes are scaled to the visible data
|
||||
ch.series.emptyCollapsedSeries(w.config.series)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ch.update(options).then(() => {
|
||||
if (chartIndex === charts.length - 1) {
|
||||
resolve(ch)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Private method to update Series.
|
||||
*
|
||||
* @param {array} series - New series which will override the existing
|
||||
*/
|
||||
_updateSeries(newSeries, animate, overwriteInitialSeries = false) {
|
||||
return new Promise((resolve) => {
|
||||
const w = this.w
|
||||
|
||||
w.globals.shouldAnimate = animate
|
||||
|
||||
w.globals.dataChanged = true
|
||||
|
||||
if (animate) {
|
||||
this.ctx.series.getPreviousPaths()
|
||||
}
|
||||
|
||||
let existingSeries
|
||||
|
||||
// axis charts
|
||||
if (w.globals.axisCharts) {
|
||||
existingSeries = newSeries.map((s, i) => {
|
||||
return this._extendSeries(s, i)
|
||||
})
|
||||
|
||||
if (existingSeries.length === 0) {
|
||||
existingSeries = [{ data: [] }]
|
||||
}
|
||||
w.config.series = existingSeries
|
||||
} else {
|
||||
// non-axis chart (pie/radialbar)
|
||||
w.config.series = newSeries.slice()
|
||||
}
|
||||
|
||||
if (overwriteInitialSeries) {
|
||||
w.globals.initialConfig.series = Utils.clone(w.config.series)
|
||||
w.globals.initialSeries = Utils.clone(w.config.series)
|
||||
}
|
||||
return this.ctx.update().then(() => {
|
||||
resolve(this.ctx)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
_extendSeries(s, i) {
|
||||
const w = this.w
|
||||
const ser = w.config.series[i]
|
||||
|
||||
return {
|
||||
...w.config.series[i],
|
||||
name: s.name ? s.name : ser?.name,
|
||||
color: s.color ? s.color : ser?.color,
|
||||
type: s.type ? s.type : ser?.type,
|
||||
group: s.group ? s.group : ser?.group,
|
||||
data: s.data ? s.data : ser?.data,
|
||||
zIndex: typeof s.zIndex !== 'undefined' ? s.zIndex : i,
|
||||
}
|
||||
}
|
||||
|
||||
toggleDataPointSelection(seriesIndex, dataPointIndex) {
|
||||
const w = this.w
|
||||
let elPath = null
|
||||
const parent = `.apexcharts-series[data\\:realIndex='${seriesIndex}']`
|
||||
|
||||
if (w.globals.axisCharts) {
|
||||
elPath = w.globals.dom.Paper.select(
|
||||
`${parent} path[j='${dataPointIndex}'], ${parent} circle[j='${dataPointIndex}'], ${parent} rect[j='${dataPointIndex}']`
|
||||
).members[0]
|
||||
} else {
|
||||
// dataPointIndex will be undefined here, hence using seriesIndex
|
||||
if (typeof dataPointIndex === 'undefined') {
|
||||
elPath = w.globals.dom.Paper.select(
|
||||
`${parent} path[j='${seriesIndex}']`
|
||||
).members[0]
|
||||
|
||||
if (
|
||||
w.config.chart.type === 'pie' ||
|
||||
w.config.chart.type === 'polarArea' ||
|
||||
w.config.chart.type === 'donut'
|
||||
) {
|
||||
this.ctx.pie.pieClicked(seriesIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (elPath) {
|
||||
const graphics = new Graphics(this.ctx)
|
||||
graphics.pathMouseDown(elPath, null)
|
||||
} else {
|
||||
console.warn('toggleDataPointSelection: Element not found')
|
||||
return null
|
||||
}
|
||||
|
||||
return elPath.node ? elPath.node : null
|
||||
}
|
||||
|
||||
forceXAxisUpdate(options) {
|
||||
const w = this.w
|
||||
const minmax = ['min', 'max']
|
||||
|
||||
minmax.forEach((a) => {
|
||||
if (typeof options.xaxis[a] !== 'undefined') {
|
||||
w.config.xaxis[a] = options.xaxis[a]
|
||||
w.globals.lastXAxis[a] = options.xaxis[a]
|
||||
}
|
||||
})
|
||||
|
||||
if (options.xaxis.categories && options.xaxis.categories.length) {
|
||||
w.config.xaxis.categories = options.xaxis.categories
|
||||
}
|
||||
|
||||
if (w.config.xaxis.convertedCatToNumeric) {
|
||||
const defaults = new Defaults(options)
|
||||
options = defaults.convertCatToNumericXaxis(options, this.ctx)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
forceYAxisUpdate(options) {
|
||||
if (
|
||||
options.chart &&
|
||||
options.chart.stacked &&
|
||||
options.chart.stackType === '100%'
|
||||
) {
|
||||
if (Array.isArray(options.yaxis)) {
|
||||
options.yaxis.forEach((yaxe, index) => {
|
||||
options.yaxis[index].min = 0
|
||||
options.yaxis[index].max = 100
|
||||
})
|
||||
} else {
|
||||
options.yaxis.min = 0
|
||||
options.yaxis.max = 100
|
||||
}
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
/**
|
||||
* This function reverts the yaxis and xaxis min/max values to what it was when the chart was defined.
|
||||
* This function fixes an important bug where a user might load a new series after zooming in/out of previous series which resulted in wrong min/max
|
||||
* Also, this should never be called internally on zoom/pan - the reset should only happen when user calls the updateSeries() function externally
|
||||
* The function also accepts an object {xaxis, yaxis} which when present is set as the new xaxis/yaxis
|
||||
*/
|
||||
revertDefaultAxisMinMax(opts) {
|
||||
const w = this.w
|
||||
|
||||
let xaxis = w.globals.lastXAxis
|
||||
let yaxis = w.globals.lastYAxis
|
||||
|
||||
if (opts && opts.xaxis) {
|
||||
xaxis = opts.xaxis
|
||||
}
|
||||
if (opts && opts.yaxis) {
|
||||
yaxis = opts.yaxis
|
||||
}
|
||||
w.config.xaxis.min = xaxis.min
|
||||
w.config.xaxis.max = xaxis.max
|
||||
|
||||
const getLastYAxis = (index) => {
|
||||
if (typeof yaxis[index] !== 'undefined') {
|
||||
w.config.yaxis[index].min = yaxis[index].min
|
||||
w.config.yaxis[index].max = yaxis[index].max
|
||||
}
|
||||
}
|
||||
|
||||
w.config.yaxis.map((yaxe, index) => {
|
||||
if (w.globals.zoomed) {
|
||||
// user has zoomed, check the last yaxis
|
||||
getLastYAxis(index)
|
||||
} else {
|
||||
// user hasn't zoomed, check the last yaxis first
|
||||
if (typeof yaxis[index] !== 'undefined') {
|
||||
getLastYAxis(index)
|
||||
} else {
|
||||
// if last y-axis don't exist, check the original yaxis
|
||||
if (typeof this.ctx.opts.yaxis[index] !== 'undefined') {
|
||||
yaxe.min = this.ctx.opts.yaxis[index].min
|
||||
yaxe.max = this.ctx.opts.yaxis[index].max
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+284
@@ -0,0 +1,284 @@
|
||||
import Graphics from '../Graphics'
|
||||
import Utils from '../../utils/Utils'
|
||||
|
||||
export default class Helpers {
|
||||
constructor(lgCtx) {
|
||||
this.w = lgCtx.w
|
||||
this.lgCtx = lgCtx
|
||||
}
|
||||
|
||||
getLegendStyles() {
|
||||
let stylesheet = document.createElement('style')
|
||||
stylesheet.setAttribute('type', 'text/css')
|
||||
const nonce = this.lgCtx.ctx?.opts?.chart?.nonce || this.w.config.chart.nonce;
|
||||
if (nonce) {
|
||||
stylesheet.setAttribute('nonce', nonce);
|
||||
}
|
||||
|
||||
const text = `
|
||||
.apexcharts-legend {
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.apexcharts-legend.apx-legend-position-bottom, .apexcharts-legend.apx-legend-position-top {
|
||||
flex-wrap: wrap
|
||||
}
|
||||
.apexcharts-legend.apx-legend-position-right, .apexcharts-legend.apx-legend-position-left {
|
||||
flex-direction: column;
|
||||
bottom: 0;
|
||||
}
|
||||
.apexcharts-legend.apx-legend-position-bottom.apexcharts-align-left, .apexcharts-legend.apx-legend-position-top.apexcharts-align-left, .apexcharts-legend.apx-legend-position-right, .apexcharts-legend.apx-legend-position-left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.apexcharts-legend.apx-legend-position-bottom.apexcharts-align-center, .apexcharts-legend.apx-legend-position-top.apexcharts-align-center {
|
||||
justify-content: center;
|
||||
}
|
||||
.apexcharts-legend.apx-legend-position-bottom.apexcharts-align-right, .apexcharts-legend.apx-legend-position-top.apexcharts-align-right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.apexcharts-legend-series {
|
||||
cursor: pointer;
|
||||
line-height: normal;
|
||||
}
|
||||
.apexcharts-legend.apx-legend-position-bottom .apexcharts-legend-series, .apexcharts-legend.apx-legend-position-top .apexcharts-legend-series{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.apexcharts-legend-text {
|
||||
position: relative;
|
||||
font-size: 14px;
|
||||
}
|
||||
.apexcharts-legend-text *, .apexcharts-legend-marker * {
|
||||
pointer-events: none;
|
||||
}
|
||||
.apexcharts-legend-marker {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
margin-right: 3px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.apexcharts-legend.apexcharts-align-right .apexcharts-legend-series, .apexcharts-legend.apexcharts-align-left .apexcharts-legend-series{
|
||||
display: inline-block;
|
||||
}
|
||||
.apexcharts-legend-series.apexcharts-no-click {
|
||||
cursor: auto;
|
||||
}
|
||||
.apexcharts-legend .apexcharts-hidden-zero-series, .apexcharts-legend .apexcharts-hidden-null-series {
|
||||
display: none !important;
|
||||
}
|
||||
.apexcharts-inactive-legend {
|
||||
opacity: 0.45;
|
||||
}`
|
||||
|
||||
let rules = document.createTextNode(text)
|
||||
|
||||
stylesheet.appendChild(rules)
|
||||
|
||||
return stylesheet
|
||||
}
|
||||
|
||||
getLegendBBox() {
|
||||
const w = this.w
|
||||
let currLegendsWrap = w.globals.dom.baseEl.querySelector(
|
||||
'.apexcharts-legend'
|
||||
)
|
||||
let currLegendsWrapRect = currLegendsWrap.getBoundingClientRect()
|
||||
|
||||
let currLegendsWrapWidth = currLegendsWrapRect.width
|
||||
let currLegendsWrapHeight = currLegendsWrapRect.height
|
||||
|
||||
return {
|
||||
clwh: currLegendsWrapHeight,
|
||||
clww: currLegendsWrapWidth
|
||||
}
|
||||
}
|
||||
|
||||
appendToForeignObject() {
|
||||
const gl = this.w.globals
|
||||
|
||||
gl.dom.elLegendForeign.appendChild(this.getLegendStyles())
|
||||
}
|
||||
|
||||
toggleDataSeries(seriesCnt, isHidden) {
|
||||
const w = this.w
|
||||
if (w.globals.axisCharts || w.config.chart.type === 'radialBar') {
|
||||
w.globals.resized = true // we don't want initial animations again
|
||||
|
||||
let seriesEl = null
|
||||
|
||||
let realIndex = null
|
||||
|
||||
// yes, make it null. 1 series will rise at a time
|
||||
w.globals.risingSeries = []
|
||||
|
||||
if (w.globals.axisCharts) {
|
||||
seriesEl = w.globals.dom.baseEl.querySelector(
|
||||
`.apexcharts-series[data\\:realIndex='${seriesCnt}']`
|
||||
)
|
||||
realIndex = parseInt(seriesEl.getAttribute('data:realIndex'), 10)
|
||||
} else {
|
||||
seriesEl = w.globals.dom.baseEl.querySelector(
|
||||
`.apexcharts-series[rel='${seriesCnt + 1}']`
|
||||
)
|
||||
realIndex = parseInt(seriesEl.getAttribute('rel'), 10) - 1
|
||||
}
|
||||
|
||||
if (isHidden) {
|
||||
const seriesToMakeVisible = [
|
||||
{
|
||||
cs: w.globals.collapsedSeries,
|
||||
csi: w.globals.collapsedSeriesIndices
|
||||
},
|
||||
{
|
||||
cs: w.globals.ancillaryCollapsedSeries,
|
||||
csi: w.globals.ancillaryCollapsedSeriesIndices
|
||||
}
|
||||
]
|
||||
seriesToMakeVisible.forEach((r) => {
|
||||
this.riseCollapsedSeries(r.cs, r.csi, realIndex)
|
||||
})
|
||||
} else {
|
||||
this.hideSeries({ seriesEl, realIndex })
|
||||
}
|
||||
} else {
|
||||
// for non-axis charts i.e pie / donuts
|
||||
let seriesEl = w.globals.dom.Paper.select(
|
||||
` .apexcharts-series[rel='${seriesCnt + 1}'] path`
|
||||
)
|
||||
|
||||
const type = w.config.chart.type
|
||||
if (type === 'pie' || type === 'polarArea' || type === 'donut') {
|
||||
let dataLabels = w.config.plotOptions.pie.donut.labels
|
||||
|
||||
const graphics = new Graphics(this.lgCtx.ctx)
|
||||
graphics.pathMouseDown(seriesEl.members[0], null)
|
||||
this.lgCtx.ctx.pie.printDataLabelsInner(
|
||||
seriesEl.members[0].node,
|
||||
dataLabels
|
||||
)
|
||||
}
|
||||
|
||||
seriesEl.fire('click')
|
||||
}
|
||||
}
|
||||
|
||||
hideSeries({ seriesEl, realIndex }) {
|
||||
const w = this.w
|
||||
|
||||
let series = Utils.clone(w.config.series)
|
||||
|
||||
if (w.globals.axisCharts) {
|
||||
let shouldNotHideYAxis = false
|
||||
|
||||
if (
|
||||
w.config.yaxis[realIndex] &&
|
||||
w.config.yaxis[realIndex].show &&
|
||||
w.config.yaxis[realIndex].showAlways
|
||||
) {
|
||||
shouldNotHideYAxis = true
|
||||
if (w.globals.ancillaryCollapsedSeriesIndices.indexOf(realIndex) < 0) {
|
||||
w.globals.ancillaryCollapsedSeries.push({
|
||||
index: realIndex,
|
||||
data: series[realIndex].data.slice(),
|
||||
type: seriesEl.parentNode.className.baseVal.split('-')[1]
|
||||
})
|
||||
w.globals.ancillaryCollapsedSeriesIndices.push(realIndex)
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldNotHideYAxis) {
|
||||
w.globals.collapsedSeries.push({
|
||||
index: realIndex,
|
||||
data: series[realIndex].data.slice(),
|
||||
type: seriesEl.parentNode.className.baseVal.split('-')[1]
|
||||
})
|
||||
w.globals.collapsedSeriesIndices.push(realIndex)
|
||||
|
||||
let removeIndexOfRising = w.globals.risingSeries.indexOf(realIndex)
|
||||
|
||||
w.globals.risingSeries.splice(removeIndexOfRising, 1)
|
||||
}
|
||||
} else {
|
||||
w.globals.collapsedSeries.push({
|
||||
index: realIndex,
|
||||
data: series[realIndex]
|
||||
})
|
||||
w.globals.collapsedSeriesIndices.push(realIndex)
|
||||
}
|
||||
|
||||
let seriesChildren = seriesEl.childNodes
|
||||
for (let sc = 0; sc < seriesChildren.length; sc++) {
|
||||
if (
|
||||
seriesChildren[sc].classList.contains('apexcharts-series-markers-wrap')
|
||||
) {
|
||||
if (seriesChildren[sc].classList.contains('apexcharts-hide')) {
|
||||
seriesChildren[sc].classList.remove('apexcharts-hide')
|
||||
} else {
|
||||
seriesChildren[sc].classList.add('apexcharts-hide')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
w.globals.allSeriesCollapsed =
|
||||
w.globals.collapsedSeries.length === w.config.series.length
|
||||
|
||||
series = this._getSeriesBasedOnCollapsedState(series)
|
||||
this.lgCtx.ctx.updateHelpers._updateSeries(
|
||||
series,
|
||||
w.config.chart.animations.dynamicAnimation.enabled
|
||||
)
|
||||
}
|
||||
|
||||
riseCollapsedSeries(collapsedSeries, seriesIndices, realIndex) {
|
||||
const w = this.w
|
||||
let series = Utils.clone(w.config.series)
|
||||
|
||||
if (collapsedSeries.length > 0) {
|
||||
for (let c = 0; c < collapsedSeries.length; c++) {
|
||||
if (collapsedSeries[c].index === realIndex) {
|
||||
if (w.globals.axisCharts) {
|
||||
series[realIndex].data = collapsedSeries[c].data.slice()
|
||||
collapsedSeries.splice(c, 1)
|
||||
seriesIndices.splice(c, 1)
|
||||
w.globals.risingSeries.push(realIndex)
|
||||
} else {
|
||||
series[realIndex] = collapsedSeries[c].data
|
||||
collapsedSeries.splice(c, 1)
|
||||
seriesIndices.splice(c, 1)
|
||||
w.globals.risingSeries.push(realIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
series = this._getSeriesBasedOnCollapsedState(series)
|
||||
|
||||
this.lgCtx.ctx.updateHelpers._updateSeries(
|
||||
series,
|
||||
w.config.chart.animations.dynamicAnimation.enabled
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
_getSeriesBasedOnCollapsedState(series) {
|
||||
const w = this.w
|
||||
|
||||
if (w.globals.axisCharts) {
|
||||
series.forEach((s, sI) => {
|
||||
if (w.globals.collapsedSeriesIndices.indexOf(sI) > -1) {
|
||||
series[sI].data = []
|
||||
}
|
||||
})
|
||||
} else {
|
||||
series.forEach((s, sI) => {
|
||||
if (w.globals.collapsedSeriesIndices.indexOf(sI) > -1) {
|
||||
series[sI] = 0
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return series
|
||||
}
|
||||
}
|
||||
+478
@@ -0,0 +1,478 @@
|
||||
import CoreUtils from '../CoreUtils'
|
||||
import Dimensions from '../dimensions/Dimensions'
|
||||
import Graphics from '../Graphics'
|
||||
import Series from '../Series'
|
||||
import Utils from '../../utils/Utils'
|
||||
import Helpers from './Helpers'
|
||||
|
||||
/**
|
||||
* ApexCharts Legend Class to draw legend.
|
||||
*
|
||||
* @module Legend
|
||||
**/
|
||||
|
||||
class Legend {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
|
||||
this.onLegendClick = this.onLegendClick.bind(this)
|
||||
this.onLegendHovered = this.onLegendHovered.bind(this)
|
||||
|
||||
this.isBarsDistributed =
|
||||
this.w.config.chart.type === 'bar' &&
|
||||
this.w.config.plotOptions.bar.distributed &&
|
||||
this.w.config.series.length === 1
|
||||
|
||||
this.legendHelpers = new Helpers(this)
|
||||
}
|
||||
|
||||
init() {
|
||||
const w = this.w
|
||||
|
||||
const gl = w.globals
|
||||
const cnf = w.config
|
||||
|
||||
const showLegendAlways =
|
||||
(cnf.legend.showForSingleSeries && gl.series.length === 1) ||
|
||||
this.isBarsDistributed ||
|
||||
gl.series.length > 1
|
||||
|
||||
if ((showLegendAlways || !gl.axisCharts) && cnf.legend.show) {
|
||||
while (gl.dom.elLegendWrap.firstChild) {
|
||||
gl.dom.elLegendWrap.removeChild(gl.dom.elLegendWrap.firstChild)
|
||||
}
|
||||
|
||||
this.drawLegends()
|
||||
|
||||
if (!Utils.isIE11()) {
|
||||
this.legendHelpers.appendToForeignObject()
|
||||
} else {
|
||||
// IE11 doesn't supports foreignObject, hence append it to <head>
|
||||
document
|
||||
.getElementsByTagName('head')[0]
|
||||
.appendChild(this.legendHelpers.getLegendStyles())
|
||||
}
|
||||
|
||||
if (cnf.legend.position === 'bottom' || cnf.legend.position === 'top') {
|
||||
this.legendAlignHorizontal()
|
||||
} else if (
|
||||
cnf.legend.position === 'right' ||
|
||||
cnf.legend.position === 'left'
|
||||
) {
|
||||
this.legendAlignVertical()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawLegends() {
|
||||
let me = this
|
||||
let w = this.w
|
||||
|
||||
let fontFamily = w.config.legend.fontFamily
|
||||
|
||||
let legendNames = w.globals.seriesNames
|
||||
let fillcolor = w.globals.colors.slice()
|
||||
|
||||
if (w.config.chart.type === 'heatmap') {
|
||||
const ranges = w.config.plotOptions.heatmap.colorScale.ranges
|
||||
legendNames = ranges.map((colorScale) => {
|
||||
return colorScale.name
|
||||
? colorScale.name
|
||||
: colorScale.from + ' - ' + colorScale.to
|
||||
})
|
||||
fillcolor = ranges.map((color) => color.color)
|
||||
} else if (this.isBarsDistributed) {
|
||||
legendNames = w.globals.labels.slice()
|
||||
}
|
||||
|
||||
if (w.config.legend.customLegendItems.length) {
|
||||
legendNames = w.config.legend.customLegendItems
|
||||
}
|
||||
let legendFormatter = w.globals.legendFormatter
|
||||
|
||||
let isLegendInversed = w.config.legend.inverseOrder
|
||||
|
||||
for (
|
||||
let i = isLegendInversed ? legendNames.length - 1 : 0;
|
||||
isLegendInversed ? i >= 0 : i <= legendNames.length - 1;
|
||||
isLegendInversed ? i-- : i++
|
||||
) {
|
||||
let text = legendFormatter(legendNames[i], { seriesIndex: i, w })
|
||||
|
||||
let collapsedSeries = false
|
||||
let ancillaryCollapsedSeries = false
|
||||
if (w.globals.collapsedSeries.length > 0) {
|
||||
for (let c = 0; c < w.globals.collapsedSeries.length; c++) {
|
||||
if (w.globals.collapsedSeries[c].index === i) {
|
||||
collapsedSeries = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (w.globals.ancillaryCollapsedSeriesIndices.length > 0) {
|
||||
for (
|
||||
let c = 0;
|
||||
c < w.globals.ancillaryCollapsedSeriesIndices.length;
|
||||
c++
|
||||
) {
|
||||
if (w.globals.ancillaryCollapsedSeriesIndices[c] === i) {
|
||||
ancillaryCollapsedSeries = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let elMarker = document.createElement('span')
|
||||
elMarker.classList.add('apexcharts-legend-marker')
|
||||
|
||||
let mOffsetX = w.config.legend.markers.offsetX
|
||||
let mOffsetY = w.config.legend.markers.offsetY
|
||||
let mHeight = w.config.legend.markers.height
|
||||
let mWidth = w.config.legend.markers.width
|
||||
let mBorderWidth = w.config.legend.markers.strokeWidth
|
||||
let mBorderColor = w.config.legend.markers.strokeColor
|
||||
let mBorderRadius = w.config.legend.markers.radius
|
||||
|
||||
let mStyle = elMarker.style
|
||||
|
||||
mStyle.background = fillcolor[i]
|
||||
mStyle.color = fillcolor[i]
|
||||
mStyle.setProperty('background', fillcolor[i], 'important')
|
||||
|
||||
// override fill color with custom legend.markers.fillColors
|
||||
if (
|
||||
w.config.legend.markers.fillColors &&
|
||||
w.config.legend.markers.fillColors[i]
|
||||
) {
|
||||
mStyle.background = w.config.legend.markers.fillColors[i]
|
||||
}
|
||||
|
||||
// override with data color
|
||||
if (w.globals.seriesColors[i] !== undefined) {
|
||||
mStyle.background = w.globals.seriesColors[i]
|
||||
mStyle.color = w.globals.seriesColors[i]
|
||||
}
|
||||
|
||||
mStyle.height = Array.isArray(mHeight)
|
||||
? parseFloat(mHeight[i]) + 'px'
|
||||
: parseFloat(mHeight) + 'px'
|
||||
mStyle.width = Array.isArray(mWidth)
|
||||
? parseFloat(mWidth[i]) + 'px'
|
||||
: parseFloat(mWidth) + 'px'
|
||||
mStyle.left =
|
||||
(Array.isArray(mOffsetX)
|
||||
? parseFloat(mOffsetX[i])
|
||||
: parseFloat(mOffsetX)) + 'px'
|
||||
mStyle.top =
|
||||
(Array.isArray(mOffsetY)
|
||||
? parseFloat(mOffsetY[i])
|
||||
: parseFloat(mOffsetY)) + 'px'
|
||||
mStyle.borderWidth = Array.isArray(mBorderWidth)
|
||||
? mBorderWidth[i]
|
||||
: mBorderWidth
|
||||
mStyle.borderColor = Array.isArray(mBorderColor)
|
||||
? mBorderColor[i]
|
||||
: mBorderColor
|
||||
mStyle.borderRadius = Array.isArray(mBorderRadius)
|
||||
? parseFloat(mBorderRadius[i]) + 'px'
|
||||
: parseFloat(mBorderRadius) + 'px'
|
||||
|
||||
if (w.config.legend.markers.customHTML) {
|
||||
if (Array.isArray(w.config.legend.markers.customHTML)) {
|
||||
if (w.config.legend.markers.customHTML[i]) {
|
||||
elMarker.innerHTML = w.config.legend.markers.customHTML[i]()
|
||||
}
|
||||
} else {
|
||||
elMarker.innerHTML = w.config.legend.markers.customHTML()
|
||||
}
|
||||
}
|
||||
|
||||
Graphics.setAttrs(elMarker, {
|
||||
rel: i + 1,
|
||||
'data:collapsed': collapsedSeries || ancillaryCollapsedSeries,
|
||||
})
|
||||
|
||||
if (collapsedSeries || ancillaryCollapsedSeries) {
|
||||
elMarker.classList.add('apexcharts-inactive-legend')
|
||||
}
|
||||
|
||||
let elLegend = document.createElement('div')
|
||||
|
||||
let elLegendText = document.createElement('span')
|
||||
elLegendText.classList.add('apexcharts-legend-text')
|
||||
elLegendText.innerHTML = Array.isArray(text) ? text.join(' ') : text
|
||||
|
||||
let textColor = w.config.legend.labels.useSeriesColors
|
||||
? w.globals.colors[i]
|
||||
: Array.isArray(w.config.legend.labels.colors)
|
||||
? w.config.legend.labels.colors?.[i]
|
||||
: w.config.legend.labels.colors
|
||||
|
||||
if (!textColor) {
|
||||
textColor = w.config.chart.foreColor
|
||||
}
|
||||
|
||||
elLegendText.style.color = textColor
|
||||
|
||||
elLegendText.style.fontSize = parseFloat(w.config.legend.fontSize) + 'px'
|
||||
elLegendText.style.fontWeight = w.config.legend.fontWeight
|
||||
elLegendText.style.fontFamily = fontFamily || w.config.chart.fontFamily
|
||||
|
||||
Graphics.setAttrs(elLegendText, {
|
||||
rel: i + 1,
|
||||
i,
|
||||
'data:default-text': encodeURIComponent(text),
|
||||
'data:collapsed': collapsedSeries || ancillaryCollapsedSeries,
|
||||
})
|
||||
|
||||
elLegend.appendChild(elMarker)
|
||||
elLegend.appendChild(elLegendText)
|
||||
|
||||
const coreUtils = new CoreUtils(this.ctx)
|
||||
if (!w.config.legend.showForZeroSeries) {
|
||||
const total = coreUtils.getSeriesTotalByIndex(i)
|
||||
|
||||
if (
|
||||
total === 0 &&
|
||||
coreUtils.seriesHaveSameValues(i) &&
|
||||
!coreUtils.isSeriesNull(i) &&
|
||||
w.globals.collapsedSeriesIndices.indexOf(i) === -1 &&
|
||||
w.globals.ancillaryCollapsedSeriesIndices.indexOf(i) === -1
|
||||
) {
|
||||
elLegend.classList.add('apexcharts-hidden-zero-series')
|
||||
}
|
||||
}
|
||||
|
||||
if (!w.config.legend.showForNullSeries) {
|
||||
if (
|
||||
coreUtils.isSeriesNull(i) &&
|
||||
w.globals.collapsedSeriesIndices.indexOf(i) === -1 &&
|
||||
w.globals.ancillaryCollapsedSeriesIndices.indexOf(i) === -1
|
||||
) {
|
||||
elLegend.classList.add('apexcharts-hidden-null-series')
|
||||
}
|
||||
}
|
||||
|
||||
w.globals.dom.elLegendWrap.appendChild(elLegend)
|
||||
w.globals.dom.elLegendWrap.classList.add(
|
||||
`apexcharts-align-${w.config.legend.horizontalAlign}`
|
||||
)
|
||||
w.globals.dom.elLegendWrap.classList.add(
|
||||
'apx-legend-position-' + w.config.legend.position
|
||||
)
|
||||
|
||||
elLegend.classList.add('apexcharts-legend-series')
|
||||
elLegend.style.margin = `${w.config.legend.itemMargin.vertical}px ${w.config.legend.itemMargin.horizontal}px`
|
||||
w.globals.dom.elLegendWrap.style.width = w.config.legend.width
|
||||
? w.config.legend.width + 'px'
|
||||
: ''
|
||||
w.globals.dom.elLegendWrap.style.height = w.config.legend.height
|
||||
? w.config.legend.height + 'px'
|
||||
: ''
|
||||
|
||||
Graphics.setAttrs(elLegend, {
|
||||
rel: i + 1,
|
||||
seriesName: Utils.escapeString(legendNames[i]),
|
||||
'data:collapsed': collapsedSeries || ancillaryCollapsedSeries,
|
||||
})
|
||||
|
||||
if (collapsedSeries || ancillaryCollapsedSeries) {
|
||||
elLegend.classList.add('apexcharts-inactive-legend')
|
||||
}
|
||||
|
||||
if (!w.config.legend.onItemClick.toggleDataSeries) {
|
||||
elLegend.classList.add('apexcharts-no-click')
|
||||
}
|
||||
}
|
||||
|
||||
w.globals.dom.elWrap.addEventListener('click', me.onLegendClick, true)
|
||||
|
||||
if (
|
||||
w.config.legend.onItemHover.highlightDataSeries &&
|
||||
w.config.legend.customLegendItems.length === 0
|
||||
) {
|
||||
w.globals.dom.elWrap.addEventListener(
|
||||
'mousemove',
|
||||
me.onLegendHovered,
|
||||
true
|
||||
)
|
||||
w.globals.dom.elWrap.addEventListener(
|
||||
'mouseout',
|
||||
me.onLegendHovered,
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
setLegendWrapXY(offsetX, offsetY) {
|
||||
let w = this.w
|
||||
|
||||
let elLegendWrap = w.globals.dom.elLegendWrap
|
||||
|
||||
const legendRect = elLegendWrap.getBoundingClientRect()
|
||||
|
||||
let x = 0
|
||||
let y = 0
|
||||
|
||||
if (w.config.legend.position === 'bottom') {
|
||||
y = y + (w.globals.svgHeight - legendRect.height / 2)
|
||||
} else if (w.config.legend.position === 'top') {
|
||||
const dim = new Dimensions(this.ctx)
|
||||
const titleH = dim.dimHelpers.getTitleSubtitleCoords('title').height
|
||||
const subtitleH = dim.dimHelpers.getTitleSubtitleCoords('subtitle').height
|
||||
|
||||
y =
|
||||
y +
|
||||
(titleH > 0 ? titleH - 10 : 0) +
|
||||
(subtitleH > 0 ? subtitleH - 10 : 0)
|
||||
}
|
||||
|
||||
elLegendWrap.style.position = 'absolute'
|
||||
|
||||
x = x + offsetX + w.config.legend.offsetX
|
||||
y = y + offsetY + w.config.legend.offsetY
|
||||
|
||||
elLegendWrap.style.left = x + 'px'
|
||||
elLegendWrap.style.top = y + 'px'
|
||||
|
||||
if (w.config.legend.position === 'bottom') {
|
||||
elLegendWrap.style.top = 'auto'
|
||||
elLegendWrap.style.bottom = 5 - w.config.legend.offsetY + 'px'
|
||||
} else if (w.config.legend.position === 'right') {
|
||||
elLegendWrap.style.left = 'auto'
|
||||
elLegendWrap.style.right = 25 + w.config.legend.offsetX + 'px'
|
||||
}
|
||||
|
||||
const fixedHeigthWidth = ['width', 'height']
|
||||
fixedHeigthWidth.forEach((hw) => {
|
||||
if (elLegendWrap.style[hw]) {
|
||||
elLegendWrap.style[hw] = parseInt(w.config.legend[hw], 10) + 'px'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
legendAlignHorizontal() {
|
||||
let w = this.w
|
||||
|
||||
let elLegendWrap = w.globals.dom.elLegendWrap
|
||||
|
||||
elLegendWrap.style.right = 0
|
||||
|
||||
let lRect = this.legendHelpers.getLegendBBox()
|
||||
|
||||
let dimensions = new Dimensions(this.ctx)
|
||||
let titleRect = dimensions.dimHelpers.getTitleSubtitleCoords('title')
|
||||
let subtitleRect = dimensions.dimHelpers.getTitleSubtitleCoords('subtitle')
|
||||
|
||||
let offsetX = 20
|
||||
let offsetY = 0
|
||||
|
||||
// the whole legend box is set to bottom
|
||||
if (w.config.legend.position === 'bottom') {
|
||||
offsetY = -lRect.clwh / 1.8
|
||||
} else if (w.config.legend.position === 'top') {
|
||||
offsetY =
|
||||
titleRect.height +
|
||||
subtitleRect.height +
|
||||
w.config.title.margin +
|
||||
w.config.subtitle.margin -
|
||||
10
|
||||
}
|
||||
|
||||
this.setLegendWrapXY(offsetX, offsetY)
|
||||
}
|
||||
|
||||
legendAlignVertical() {
|
||||
let w = this.w
|
||||
|
||||
let lRect = this.legendHelpers.getLegendBBox()
|
||||
|
||||
let offsetY = 20
|
||||
let offsetX = 0
|
||||
|
||||
if (w.config.legend.position === 'left') {
|
||||
offsetX = 20
|
||||
}
|
||||
|
||||
if (w.config.legend.position === 'right') {
|
||||
offsetX = w.globals.svgWidth - lRect.clww - 10
|
||||
}
|
||||
|
||||
this.setLegendWrapXY(offsetX, offsetY)
|
||||
}
|
||||
|
||||
onLegendHovered(e) {
|
||||
const w = this.w
|
||||
|
||||
const hoverOverLegend =
|
||||
e.target.classList.contains('apexcharts-legend-series') ||
|
||||
e.target.classList.contains('apexcharts-legend-text') ||
|
||||
e.target.classList.contains('apexcharts-legend-marker')
|
||||
|
||||
if (w.config.chart.type !== 'heatmap' && !this.isBarsDistributed) {
|
||||
if (
|
||||
!e.target.classList.contains('apexcharts-inactive-legend') &&
|
||||
hoverOverLegend
|
||||
) {
|
||||
let series = new Series(this.ctx)
|
||||
series.toggleSeriesOnHover(e, e.target)
|
||||
}
|
||||
} else {
|
||||
// for heatmap handling
|
||||
if (hoverOverLegend) {
|
||||
let seriesCnt = parseInt(e.target.getAttribute('rel'), 10) - 1
|
||||
this.ctx.events.fireEvent('legendHover', [this.ctx, seriesCnt, this.w])
|
||||
|
||||
let series = new Series(this.ctx)
|
||||
series.highlightRangeInSeries(e, e.target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onLegendClick(e) {
|
||||
const w = this.w
|
||||
|
||||
if (w.config.legend.customLegendItems.length) return
|
||||
|
||||
if (
|
||||
e.target.classList.contains('apexcharts-legend-series') ||
|
||||
e.target.classList.contains('apexcharts-legend-text') ||
|
||||
e.target.classList.contains('apexcharts-legend-marker')
|
||||
) {
|
||||
let seriesCnt = parseInt(e.target.getAttribute('rel'), 10) - 1
|
||||
let isHidden = e.target.getAttribute('data:collapsed') === 'true'
|
||||
|
||||
const legendClick = this.w.config.chart.events.legendClick
|
||||
if (typeof legendClick === 'function') {
|
||||
legendClick(this.ctx, seriesCnt, this.w)
|
||||
}
|
||||
|
||||
this.ctx.events.fireEvent('legendClick', [this.ctx, seriesCnt, this.w])
|
||||
|
||||
const markerClick = this.w.config.legend.markers.onClick
|
||||
if (
|
||||
typeof markerClick === 'function' &&
|
||||
e.target.classList.contains('apexcharts-legend-marker')
|
||||
) {
|
||||
markerClick(this.ctx, seriesCnt, this.w)
|
||||
this.ctx.events.fireEvent('legendMarkerClick', [
|
||||
this.ctx,
|
||||
seriesCnt,
|
||||
this.w,
|
||||
])
|
||||
}
|
||||
|
||||
// for now - just prevent click on heatmap legend - and allow hover only
|
||||
const clickAllowed =
|
||||
w.config.chart.type !== 'treemap' &&
|
||||
w.config.chart.type !== 'heatmap' &&
|
||||
!this.isBarsDistributed
|
||||
|
||||
if (clickAllowed && w.config.legend.onItemClick.toggleDataSeries) {
|
||||
this.legendHelpers.toggleDataSeries(seriesCnt, isHidden)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Legend
|
||||
+338
@@ -0,0 +1,338 @@
|
||||
import Defaults from './Defaults'
|
||||
import Utils from './../../utils/Utils'
|
||||
import Options from './Options'
|
||||
|
||||
/**
|
||||
* ApexCharts Config Class for extending user options with pre-defined ApexCharts config.
|
||||
*
|
||||
* @module Config
|
||||
**/
|
||||
export default class Config {
|
||||
constructor(opts) {
|
||||
this.opts = opts
|
||||
}
|
||||
|
||||
init({ responsiveOverride }) {
|
||||
let opts = this.opts
|
||||
let options = new Options()
|
||||
let defaults = new Defaults(opts)
|
||||
|
||||
this.chartType = opts.chart.type
|
||||
|
||||
opts = this.extendYAxis(opts)
|
||||
opts = this.extendAnnotations(opts)
|
||||
|
||||
let config = options.init()
|
||||
let newDefaults = {}
|
||||
if (opts && typeof opts === 'object') {
|
||||
let chartDefaults = {}
|
||||
const chartTypes = [
|
||||
'line',
|
||||
'area',
|
||||
'bar',
|
||||
'candlestick',
|
||||
'boxPlot',
|
||||
'rangeBar',
|
||||
'rangeArea',
|
||||
'bubble',
|
||||
'scatter',
|
||||
'heatmap',
|
||||
'treemap',
|
||||
'pie',
|
||||
'polarArea',
|
||||
'donut',
|
||||
'radar',
|
||||
'radialBar',
|
||||
]
|
||||
|
||||
if (chartTypes.indexOf(opts.chart.type) !== -1) {
|
||||
chartDefaults = defaults[opts.chart.type]()
|
||||
} else {
|
||||
chartDefaults = defaults.line()
|
||||
}
|
||||
|
||||
if (opts.plotOptions?.bar?.isFunnel) {
|
||||
chartDefaults = defaults.funnel()
|
||||
}
|
||||
|
||||
if (opts.chart.stacked && opts.chart.type === 'bar') {
|
||||
chartDefaults = defaults.stackedBars()
|
||||
}
|
||||
|
||||
if (opts.chart.brush?.enabled) {
|
||||
chartDefaults = defaults.brush(chartDefaults)
|
||||
}
|
||||
|
||||
if (opts.chart.stacked && opts.chart.stackType === '100%') {
|
||||
opts = defaults.stacked100(opts)
|
||||
}
|
||||
|
||||
if (opts.plotOptions?.bar?.isDumbbell) {
|
||||
opts = defaults.dumbbell(opts)
|
||||
}
|
||||
|
||||
// If user has specified a dark theme, make the tooltip dark too
|
||||
this.checkForDarkTheme(window.Apex) // check global window Apex options
|
||||
this.checkForDarkTheme(opts) // check locally passed options
|
||||
|
||||
opts.xaxis = opts.xaxis || window.Apex.xaxis || {}
|
||||
|
||||
// an important boolean needs to be set here
|
||||
// otherwise all the charts will have this flag set to true window.Apex.xaxis is set globally
|
||||
if (!responsiveOverride) {
|
||||
opts.xaxis.convertedCatToNumeric = false
|
||||
}
|
||||
|
||||
opts = this.checkForCatToNumericXAxis(this.chartType, chartDefaults, opts)
|
||||
|
||||
if (
|
||||
opts.chart.sparkline?.enabled ||
|
||||
window.Apex.chart?.sparkline?.enabled
|
||||
) {
|
||||
chartDefaults = defaults.sparkline(chartDefaults)
|
||||
}
|
||||
newDefaults = Utils.extend(config, chartDefaults)
|
||||
}
|
||||
|
||||
// config should cascade in this fashion
|
||||
// default-config < global-apex-variable-config < user-defined-config
|
||||
|
||||
// get GLOBALLY defined options and merge with the default config
|
||||
let mergedWithDefaultConfig = Utils.extend(newDefaults, window.Apex)
|
||||
|
||||
// get the merged config and extend with user defined config
|
||||
config = Utils.extend(mergedWithDefaultConfig, opts)
|
||||
|
||||
// some features are not supported. those mismatches should be handled
|
||||
config = this.handleUserInputErrors(config)
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
checkForCatToNumericXAxis(chartType, chartDefaults, opts) {
|
||||
let defaults = new Defaults(opts)
|
||||
|
||||
const isBarHorizontal =
|
||||
(chartType === 'bar' || chartType === 'boxPlot') &&
|
||||
opts.plotOptions?.bar?.horizontal
|
||||
|
||||
const unsupportedZoom =
|
||||
chartType === 'pie' ||
|
||||
chartType === 'polarArea' ||
|
||||
chartType === 'donut' ||
|
||||
chartType === 'radar' ||
|
||||
chartType === 'radialBar' ||
|
||||
chartType === 'heatmap'
|
||||
|
||||
const notNumericXAxis =
|
||||
opts.xaxis.type !== 'datetime' && opts.xaxis.type !== 'numeric'
|
||||
|
||||
let tickPlacement = opts.xaxis.tickPlacement
|
||||
? opts.xaxis.tickPlacement
|
||||
: chartDefaults.xaxis && chartDefaults.xaxis.tickPlacement
|
||||
if (
|
||||
!isBarHorizontal &&
|
||||
!unsupportedZoom &&
|
||||
notNumericXAxis &&
|
||||
tickPlacement !== 'between'
|
||||
) {
|
||||
opts = defaults.convertCatToNumeric(opts)
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
extendYAxis(opts, w) {
|
||||
let options = new Options()
|
||||
|
||||
if (
|
||||
typeof opts.yaxis === 'undefined' ||
|
||||
!opts.yaxis ||
|
||||
(Array.isArray(opts.yaxis) && opts.yaxis.length === 0)
|
||||
) {
|
||||
opts.yaxis = {}
|
||||
}
|
||||
|
||||
// extend global yaxis config (only if object is provided / not an array)
|
||||
if (
|
||||
opts.yaxis.constructor !== Array &&
|
||||
window.Apex.yaxis &&
|
||||
window.Apex.yaxis.constructor !== Array
|
||||
) {
|
||||
opts.yaxis = Utils.extend(opts.yaxis, window.Apex.yaxis)
|
||||
}
|
||||
|
||||
// as we can't extend nested object's array with extend, we need to do it first
|
||||
// user can provide either an array or object in yaxis config
|
||||
if (opts.yaxis.constructor !== Array) {
|
||||
// convert the yaxis to array if user supplied object
|
||||
opts.yaxis = [Utils.extend(options.yAxis, opts.yaxis)]
|
||||
} else {
|
||||
opts.yaxis = Utils.extendArray(opts.yaxis, options.yAxis)
|
||||
}
|
||||
|
||||
let isLogY = false
|
||||
opts.yaxis.forEach((y) => {
|
||||
if (y.logarithmic) {
|
||||
isLogY = true
|
||||
}
|
||||
})
|
||||
|
||||
let series = opts.series
|
||||
if (w && !series) {
|
||||
series = w.config.series
|
||||
}
|
||||
|
||||
// A logarithmic chart works correctly when each series has a corresponding y-axis
|
||||
// If this is not the case, we manually create yaxis for multi-series log chart
|
||||
if (isLogY && series.length !== opts.yaxis.length && series.length) {
|
||||
opts.yaxis = series.map((s, i) => {
|
||||
if (!s.name) {
|
||||
series[i].name = `series-${i + 1}`
|
||||
}
|
||||
if (opts.yaxis[i]) {
|
||||
opts.yaxis[i].seriesName = series[i].name
|
||||
return opts.yaxis[i]
|
||||
} else {
|
||||
const newYaxis = Utils.extend(options.yAxis, opts.yaxis[0])
|
||||
newYaxis.show = false
|
||||
return newYaxis
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (isLogY && series.length > 1 && series.length !== opts.yaxis.length) {
|
||||
console.warn(
|
||||
'A multi-series logarithmic chart should have equal number of series and y-axes'
|
||||
)
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// annotations also accepts array, so we need to extend them manually
|
||||
extendAnnotations(opts) {
|
||||
if (typeof opts.annotations === 'undefined') {
|
||||
opts.annotations = {}
|
||||
opts.annotations.yaxis = []
|
||||
opts.annotations.xaxis = []
|
||||
opts.annotations.points = []
|
||||
}
|
||||
|
||||
opts = this.extendYAxisAnnotations(opts)
|
||||
opts = this.extendXAxisAnnotations(opts)
|
||||
opts = this.extendPointAnnotations(opts)
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
extendYAxisAnnotations(opts) {
|
||||
let options = new Options()
|
||||
|
||||
opts.annotations.yaxis = Utils.extendArray(
|
||||
typeof opts.annotations.yaxis !== 'undefined'
|
||||
? opts.annotations.yaxis
|
||||
: [],
|
||||
options.yAxisAnnotation
|
||||
)
|
||||
return opts
|
||||
}
|
||||
|
||||
extendXAxisAnnotations(opts) {
|
||||
let options = new Options()
|
||||
|
||||
opts.annotations.xaxis = Utils.extendArray(
|
||||
typeof opts.annotations.xaxis !== 'undefined'
|
||||
? opts.annotations.xaxis
|
||||
: [],
|
||||
options.xAxisAnnotation
|
||||
)
|
||||
return opts
|
||||
}
|
||||
extendPointAnnotations(opts) {
|
||||
let options = new Options()
|
||||
|
||||
opts.annotations.points = Utils.extendArray(
|
||||
typeof opts.annotations.points !== 'undefined'
|
||||
? opts.annotations.points
|
||||
: [],
|
||||
options.pointAnnotation
|
||||
)
|
||||
return opts
|
||||
}
|
||||
|
||||
checkForDarkTheme(opts) {
|
||||
if (opts.theme && opts.theme.mode === 'dark') {
|
||||
if (!opts.tooltip) {
|
||||
opts.tooltip = {}
|
||||
}
|
||||
if (opts.tooltip.theme !== 'light') {
|
||||
opts.tooltip.theme = 'dark'
|
||||
}
|
||||
|
||||
if (!opts.chart.foreColor) {
|
||||
opts.chart.foreColor = '#f6f7f8'
|
||||
}
|
||||
|
||||
if (!opts.chart.background) {
|
||||
opts.chart.background = '#424242'
|
||||
}
|
||||
|
||||
if (!opts.theme.palette) {
|
||||
opts.theme.palette = 'palette4'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleUserInputErrors(opts) {
|
||||
let config = opts
|
||||
// conflicting tooltip option. intersect makes sure to focus on 1 point at a time. Shared cannot be used along with it
|
||||
if (config.tooltip.shared && config.tooltip.intersect) {
|
||||
throw new Error(
|
||||
'tooltip.shared cannot be enabled when tooltip.intersect is true. Turn off any other option by setting it to false.'
|
||||
)
|
||||
}
|
||||
|
||||
if (config.chart.type === 'bar' && config.plotOptions.bar.horizontal) {
|
||||
// No multiple yaxis for bars
|
||||
if (config.yaxis.length > 1) {
|
||||
throw new Error(
|
||||
'Multiple Y Axis for bars are not supported. Switch to column chart by setting plotOptions.bar.horizontal=false'
|
||||
)
|
||||
}
|
||||
|
||||
// if yaxis is reversed in horizontal bar chart, you should draw the y-axis on right side
|
||||
if (config.yaxis[0].reversed) {
|
||||
config.yaxis[0].opposite = true
|
||||
}
|
||||
|
||||
config.xaxis.tooltip.enabled = false // no xaxis tooltip for horizontal bar
|
||||
config.yaxis[0].tooltip.enabled = false // no xaxis tooltip for horizontal bar
|
||||
config.chart.zoom.enabled = false // no zooming for horz bars
|
||||
}
|
||||
|
||||
if (config.chart.type === 'bar' || config.chart.type === 'rangeBar') {
|
||||
if (config.tooltip.shared) {
|
||||
if (
|
||||
config.xaxis.crosshairs.width === 'barWidth' &&
|
||||
config.series.length > 1
|
||||
) {
|
||||
config.xaxis.crosshairs.width = 'tickWidth'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
config.chart.type === 'candlestick' ||
|
||||
config.chart.type === 'boxPlot'
|
||||
) {
|
||||
if (config.yaxis[0].reversed) {
|
||||
console.warn(
|
||||
`Reversed y-axis in ${config.chart.type} chart is not supported.`
|
||||
)
|
||||
config.yaxis[0].reversed = false
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
}
|
||||
+1114
File diff suppressed because it is too large
Load Diff
+247
@@ -0,0 +1,247 @@
|
||||
import Utils from './../../utils/Utils'
|
||||
|
||||
export default class Globals {
|
||||
initGlobalVars(gl) {
|
||||
gl.series = [] // the MAIN series array (y values)
|
||||
gl.seriesCandleO = []
|
||||
gl.seriesCandleH = []
|
||||
gl.seriesCandleM = []
|
||||
gl.seriesCandleL = []
|
||||
gl.seriesCandleC = []
|
||||
gl.seriesRangeStart = []
|
||||
gl.seriesRangeEnd = []
|
||||
gl.seriesRange = []
|
||||
gl.seriesPercent = []
|
||||
gl.seriesGoals = []
|
||||
gl.seriesX = []
|
||||
gl.seriesZ = []
|
||||
gl.seriesNames = []
|
||||
gl.seriesTotals = []
|
||||
gl.seriesLog = []
|
||||
gl.seriesColors = []
|
||||
gl.stackedSeriesTotals = []
|
||||
gl.seriesXvalues = [] // we will need this in tooltip (it's x position)
|
||||
// when we will have unequal x values, we will need
|
||||
// some way to get x value depending on mouse pointer
|
||||
gl.seriesYvalues = [] // we will need this when deciding which series
|
||||
// user hovered on
|
||||
gl.labels = []
|
||||
gl.hasXaxisGroups = false
|
||||
gl.groups = []
|
||||
gl.hasSeriesGroups = false
|
||||
gl.seriesGroups = []
|
||||
gl.categoryLabels = []
|
||||
gl.timescaleLabels = []
|
||||
gl.noLabelsProvided = false
|
||||
gl.resizeTimer = null
|
||||
gl.selectionResizeTimer = null
|
||||
gl.delayedElements = []
|
||||
gl.pointsArray = []
|
||||
gl.dataLabelsRects = []
|
||||
gl.isXNumeric = false
|
||||
gl.skipLastTimelinelabel = false
|
||||
gl.skipFirstTimelinelabel = false
|
||||
gl.isDataXYZ = false
|
||||
gl.isMultiLineX = false
|
||||
gl.isMultipleYAxis = false
|
||||
gl.maxY = -Number.MAX_VALUE
|
||||
gl.minY = Number.MIN_VALUE
|
||||
gl.minYArr = []
|
||||
gl.maxYArr = []
|
||||
gl.maxX = -Number.MAX_VALUE
|
||||
gl.minX = Number.MAX_VALUE
|
||||
gl.initialMaxX = -Number.MAX_VALUE
|
||||
gl.initialMinX = Number.MAX_VALUE
|
||||
gl.maxDate = 0
|
||||
gl.minDate = Number.MAX_VALUE
|
||||
gl.minZ = Number.MAX_VALUE
|
||||
gl.maxZ = -Number.MAX_VALUE
|
||||
gl.minXDiff = Number.MAX_VALUE
|
||||
gl.yAxisScale = []
|
||||
gl.xAxisScale = null
|
||||
gl.xAxisTicksPositions = []
|
||||
gl.yLabelsCoords = []
|
||||
gl.yTitleCoords = []
|
||||
gl.barPadForNumericAxis = 0
|
||||
gl.padHorizontal = 0
|
||||
gl.xRange = 0
|
||||
gl.yRange = []
|
||||
gl.zRange = 0
|
||||
gl.dataPoints = 0
|
||||
gl.xTickAmount = 0
|
||||
gl.multiAxisTickAmount = 0
|
||||
}
|
||||
|
||||
globalVars(config) {
|
||||
return {
|
||||
chartID: null, // chart ID - apexcharts-cuid
|
||||
cuid: null, // chart ID - random numbers excluding "apexcharts" part
|
||||
events: {
|
||||
beforeMount: [],
|
||||
mounted: [],
|
||||
updated: [],
|
||||
clicked: [],
|
||||
selection: [],
|
||||
dataPointSelection: [],
|
||||
zoomed: [],
|
||||
scrolled: []
|
||||
},
|
||||
colors: [],
|
||||
clientX: null,
|
||||
clientY: null,
|
||||
fill: {
|
||||
colors: []
|
||||
},
|
||||
stroke: {
|
||||
colors: []
|
||||
},
|
||||
dataLabels: {
|
||||
style: {
|
||||
colors: []
|
||||
}
|
||||
},
|
||||
radarPolygons: {
|
||||
fill: {
|
||||
colors: []
|
||||
}
|
||||
},
|
||||
markers: {
|
||||
colors: [],
|
||||
size: config.markers.size,
|
||||
largestSize: 0
|
||||
},
|
||||
animationEnded: false,
|
||||
isTouchDevice: 'ontouchstart' in window || navigator.msMaxTouchPoints,
|
||||
isDirty: false, // chart has been updated after the initial render. This is different than dataChanged property. isDirty means user manually called some method to update
|
||||
isExecCalled: false, // whether user updated the chart through the exec method
|
||||
initialConfig: null, // we will store the first config user has set to go back when user finishes interactions like zooming and come out of it
|
||||
initialSeries: [],
|
||||
lastXAxis: [],
|
||||
lastYAxis: [],
|
||||
columnSeries: null,
|
||||
labels: [], // store the text to draw on x axis
|
||||
// Don't mutate the labels, many things including tooltips depends on it!
|
||||
timescaleLabels: [], // store the timescaleLabels Labels in another variable
|
||||
noLabelsProvided: false, // if user didn't provide any categories/labels or x values, fallback to 1,2,3,4...
|
||||
allSeriesCollapsed: false,
|
||||
collapsedSeries: [], // when user collapses a series, it goes into this array
|
||||
collapsedSeriesIndices: [], // this stores the index of the collapsedSeries instead of whole object for quick access
|
||||
ancillaryCollapsedSeries: [], // when user collapses an "alwaysVisible" series, it goes into this array
|
||||
ancillaryCollapsedSeriesIndices: [], // this stores the index of the ancillaryCollapsedSeries whose y-axis is always visible
|
||||
risingSeries: [], // when user re-opens a collapsed series, it goes here
|
||||
dataFormatXNumeric: false, // boolean value to indicate user has passed numeric x values
|
||||
capturedSeriesIndex: -1,
|
||||
capturedDataPointIndex: -1,
|
||||
selectedDataPoints: [],
|
||||
goldenPadding: 35, // this value is used at a lot of places for spacing purpose
|
||||
invalidLogScale: false, // if a user enabled log scale but the data provided is not valid to generate a log scale, turn on this flag
|
||||
ignoreYAxisIndexes: [], // when series are being collapsed in multiple y axes, ignore certain index
|
||||
yAxisSameScaleIndices: [],
|
||||
maxValsInArrayIndex: 0,
|
||||
radialSize: 0,
|
||||
selection: undefined,
|
||||
zoomEnabled:
|
||||
config.chart.toolbar.autoSelected === 'zoom' &&
|
||||
config.chart.toolbar.tools.zoom &&
|
||||
config.chart.zoom.enabled,
|
||||
panEnabled:
|
||||
config.chart.toolbar.autoSelected === 'pan' &&
|
||||
config.chart.toolbar.tools.pan,
|
||||
selectionEnabled:
|
||||
config.chart.toolbar.autoSelected === 'selection' &&
|
||||
config.chart.toolbar.tools.selection,
|
||||
yaxis: null,
|
||||
mousedown: false,
|
||||
lastClientPosition: {}, // don't reset this variable this the chart is destroyed. It is used to detect right or left mousemove in panning
|
||||
visibleXRange: undefined,
|
||||
yValueDecimal: 0, // are there floating numbers in the series. If yes, this represent the len of the decimals
|
||||
total: 0,
|
||||
SVGNS: 'http://www.w3.org/2000/svg', // svg namespace
|
||||
svgWidth: 0, // the whole svg width
|
||||
svgHeight: 0, // the whole svg height
|
||||
noData: false, // whether there is any data to display or not
|
||||
locale: {}, // the current locale values will be preserved here for global access
|
||||
dom: {}, // for storing all dom nodes in this particular property
|
||||
memory: {
|
||||
methodsToExec: []
|
||||
},
|
||||
shouldAnimate: true,
|
||||
skipLastTimelinelabel: false, // when last label is cropped, skip drawing it
|
||||
skipFirstTimelinelabel: false, // when first label is cropped, skip drawing it
|
||||
delayedElements: [], // element which appear after animation has finished
|
||||
axisCharts: true, // chart type = line or area or bar
|
||||
// (refer them also as plot charts in the code)
|
||||
isDataXYZ: false, // bool: data was provided in a {[x,y,z]} pattern
|
||||
resized: false, // bool: user has resized
|
||||
resizeTimer: null, // timeout function to make a small delay before
|
||||
// drawing when user resized
|
||||
comboCharts: false, // bool: whether it's a combination of line/column
|
||||
dataChanged: false, // bool: has data changed dynamically
|
||||
previousPaths: [], // array: when data is changed, it will animate from
|
||||
// previous paths
|
||||
allSeriesHasEqualX: true,
|
||||
pointsArray: [], // store the points positions here to draw later on hover
|
||||
// format is - [[x,y],[x,y]... [x,y]]
|
||||
dataLabelsRects: [], // store the positions of datalabels to prevent collision
|
||||
lastDrawnDataLabelsIndexes: [],
|
||||
hasNullValues: false, // bool: whether series contains null values
|
||||
easing: null, // function: animation effect to apply
|
||||
zoomed: false, // whether user has zoomed or not
|
||||
gridWidth: 0, // drawable width of actual graphs (series paths)
|
||||
gridHeight: 0, // drawable height of actual graphs (series paths)
|
||||
rotateXLabels: false,
|
||||
defaultLabels: false,
|
||||
xLabelFormatter: undefined, // formatter for x axis labels
|
||||
yLabelFormatters: [],
|
||||
xaxisTooltipFormatter: undefined, // formatter for x axis tooltip
|
||||
ttKeyFormatter: undefined,
|
||||
ttVal: undefined,
|
||||
ttZFormatter: undefined,
|
||||
LINE_HEIGHT_RATIO: 1.618,
|
||||
xAxisLabelsHeight: 0,
|
||||
xAxisGroupLabelsHeight: 0,
|
||||
xAxisLabelsWidth: 0,
|
||||
yAxisLabelsWidth: 0,
|
||||
scaleX: 1,
|
||||
scaleY: 1,
|
||||
translateX: 0,
|
||||
translateY: 0,
|
||||
translateYAxisX: [],
|
||||
yAxisWidths: [],
|
||||
translateXAxisY: 0,
|
||||
translateXAxisX: 0,
|
||||
tooltip: null,
|
||||
// Rules for niceScaleAllowedMagMsd:
|
||||
// 1) An array of two arrays only ([[],[]]):
|
||||
// * array[0][]: influences labelling of data series that contain only integers
|
||||
// - must contain only integers (or expect ugly ticks)
|
||||
// * array[1][]: influences labelling of data series that contain at least one float
|
||||
// - may contain floats
|
||||
// * both arrays:
|
||||
// - each array[][i] ideally satisfy: 10 mod array[][i] == 0 (or expect ugly ticks)
|
||||
// - to avoid clipping data point keep each array[][i] >= i
|
||||
// 2) each array[i][] contains 11 values, for all possible index values 0..10.
|
||||
// array[][0] should not be needed (not proven) but ensures non-zero is returned.
|
||||
//
|
||||
// Users can effectively force their preferred "magMsd" through stepSize and
|
||||
// forceNiceScale. With forceNiceScale: true, stepSize becomes normalizable to the
|
||||
// axis's min..max range, which allows users to set stepSize to an integer 1..10, for
|
||||
// example, stepSize: 3. This value will be preferred to the value determined through
|
||||
// this array. The range-normalized value is checked for consistency with other
|
||||
// user defined options and will be ignored if inconsistent.
|
||||
niceScaleAllowedMagMsd: [[1,1,2,5,5,5,10,10,10,10,10],[1,1,2,5,5,5,10,10,10,10,10]]
|
||||
}
|
||||
}
|
||||
|
||||
init(config) {
|
||||
let globals = this.globalVars(config)
|
||||
this.initGlobalVars(globals)
|
||||
|
||||
globals.initialConfig = Utils.extend({}, config)
|
||||
globals.initialSeries = Utils.clone(config.series)
|
||||
|
||||
globals.lastXAxis = Utils.clone(globals.initialConfig.xaxis)
|
||||
globals.lastYAxis = Utils.clone(globals.initialConfig.yaxis)
|
||||
return globals
|
||||
}
|
||||
}
|
||||
+1127
File diff suppressed because it is too large
Load Diff
+193
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user