apexcharts
Version:
A JavaScript Chart Library
680 lines (599 loc) • 19.5 kB
JavaScript
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.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)
let yaxis = Utils.clone(w.config.yaxis)
if (
w.config.chart.brush.autoScaleYaxis &&
targetChart.w.globals.series.length === 1
) {
const scale = new Scales(targetChart)
yaxis = scale.autoScaleY(targetChart, yaxis, e)
}
const multipleYaxis = targetChart.w.config.yaxis.reduce(
(acc, curr, index) => {
return [
...acc,
{
...targetChart.w.config.yaxis[index],
min: yaxis[0].min,
max: yaxis[0].max,
},
]
},
[]
)
targetChart.ctx.updateHelpers._updateOptions(
{
xaxis: {
min: e.xaxis.min,
max: e.xaxis.max,
},
yaxis: multipleYaxis,
},
false,
false,
false,
false
)
})
}
}
}
}