initial commit
This commit is contained in:
@@ -0,0 +1,492 @@
|
||||
import BarDataLabels from './common/bar/DataLabels'
|
||||
import BarHelpers from './common/bar/Helpers'
|
||||
import CoreUtils from '../modules/CoreUtils'
|
||||
import Utils from '../utils/Utils'
|
||||
import Filters from '../modules/Filters'
|
||||
import Graphics from '../modules/Graphics'
|
||||
|
||||
/**
|
||||
* ApexCharts Bar Class responsible for drawing both Columns and Bars.
|
||||
*
|
||||
* @module Bar
|
||||
**/
|
||||
|
||||
class Bar {
|
||||
constructor(ctx, xyRatios) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
const w = this.w
|
||||
this.barOptions = w.config.plotOptions.bar
|
||||
|
||||
this.isHorizontal = this.barOptions.horizontal
|
||||
this.strokeWidth = w.config.stroke.width
|
||||
this.isNullValue = false
|
||||
|
||||
this.isRangeBar = w.globals.seriesRangeBar.length && this.isHorizontal
|
||||
|
||||
this.xyRatios = xyRatios
|
||||
|
||||
if (this.xyRatios !== null) {
|
||||
this.xRatio = xyRatios.xRatio
|
||||
this.initialXRatio = xyRatios.initialXRatio
|
||||
this.yRatio = xyRatios.yRatio
|
||||
this.invertedXRatio = xyRatios.invertedXRatio
|
||||
this.invertedYRatio = xyRatios.invertedYRatio
|
||||
this.baseLineY = xyRatios.baseLineY
|
||||
this.baseLineInvertedY = xyRatios.baseLineInvertedY
|
||||
}
|
||||
this.yaxisIndex = 0
|
||||
this.seriesLen = 0
|
||||
|
||||
this.barHelpers = new BarHelpers(this)
|
||||
}
|
||||
|
||||
/** primary draw method which is called on bar object
|
||||
* @memberof Bar
|
||||
* @param {array} series - user supplied series values
|
||||
* @param {int} seriesIndex - the index by which series will be drawn on the svg
|
||||
* @return {node} element which is supplied to parent chart draw method for appending
|
||||
**/
|
||||
draw(series, seriesIndex) {
|
||||
let w = this.w
|
||||
let graphics = new Graphics(this.ctx)
|
||||
|
||||
const coreUtils = new CoreUtils(this.ctx, w)
|
||||
series = coreUtils.getLogSeries(series)
|
||||
this.series = series
|
||||
this.yRatio = coreUtils.getLogYRatios(this.yRatio)
|
||||
|
||||
this.barHelpers.initVariables(series)
|
||||
|
||||
let ret = graphics.group({
|
||||
class: 'apexcharts-bar-series apexcharts-plot-series'
|
||||
})
|
||||
|
||||
if (w.config.dataLabels.enabled) {
|
||||
if (this.totalItems > this.barOptions.dataLabels.maxItems) {
|
||||
console.warn(
|
||||
'WARNING: DataLabels are enabled but there are too many to display. This may cause performance issue when rendering.'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0, bc = 0; i < series.length; i++, bc++) {
|
||||
let x,
|
||||
y,
|
||||
xDivision, // xDivision is the GRIDWIDTH divided by number of datapoints (columns)
|
||||
yDivision, // yDivision is the GRIDHEIGHT divided by number of datapoints (bars)
|
||||
zeroH, // zeroH is the baseline where 0 meets y axis
|
||||
zeroW // zeroW is the baseline where 0 meets x axis
|
||||
|
||||
let yArrj = [] // hold y values of current iterating series
|
||||
let xArrj = [] // hold x values of current iterating series
|
||||
|
||||
let realIndex = w.globals.comboCharts ? seriesIndex[i] : i
|
||||
|
||||
// el to which series will be drawn
|
||||
let elSeries = graphics.group({
|
||||
class: `apexcharts-series`,
|
||||
rel: i + 1,
|
||||
seriesName: Utils.escapeString(w.globals.seriesNames[realIndex]),
|
||||
'data:realIndex': realIndex
|
||||
})
|
||||
|
||||
this.ctx.series.addCollapsedClassToSeries(elSeries, realIndex)
|
||||
|
||||
if (series[i].length > 0) {
|
||||
this.visibleI = this.visibleI + 1
|
||||
}
|
||||
|
||||
let barHeight = 0
|
||||
let barWidth = 0
|
||||
|
||||
if (this.yRatio.length > 1) {
|
||||
this.yaxisIndex = realIndex
|
||||
}
|
||||
|
||||
this.isReversed =
|
||||
w.config.yaxis[this.yaxisIndex] &&
|
||||
w.config.yaxis[this.yaxisIndex].reversed
|
||||
|
||||
let initPositions = this.barHelpers.initialPositions()
|
||||
|
||||
y = initPositions.y
|
||||
barHeight = initPositions.barHeight
|
||||
yDivision = initPositions.yDivision
|
||||
zeroW = initPositions.zeroW
|
||||
|
||||
x = initPositions.x
|
||||
barWidth = initPositions.barWidth
|
||||
xDivision = initPositions.xDivision
|
||||
zeroH = initPositions.zeroH
|
||||
|
||||
if (!this.horizontal) {
|
||||
xArrj.push(x + barWidth / 2)
|
||||
}
|
||||
|
||||
// eldatalabels
|
||||
let elDataLabelsWrap = graphics.group({
|
||||
class: 'apexcharts-datalabels',
|
||||
'data:realIndex': realIndex
|
||||
})
|
||||
|
||||
let elGoalsMarkers = graphics.group({
|
||||
class: 'apexcharts-bar-goals-markers',
|
||||
style: `pointer-events: none`
|
||||
})
|
||||
|
||||
for (let j = 0; j < w.globals.dataPoints; j++) {
|
||||
const strokeWidth = this.barHelpers.getStrokeWidth(i, j, realIndex)
|
||||
|
||||
let paths = null
|
||||
const pathsParams = {
|
||||
indexes: {
|
||||
i,
|
||||
j,
|
||||
realIndex,
|
||||
bc
|
||||
},
|
||||
x,
|
||||
y,
|
||||
strokeWidth,
|
||||
elSeries
|
||||
}
|
||||
if (this.isHorizontal) {
|
||||
paths = this.drawBarPaths({
|
||||
...pathsParams,
|
||||
barHeight,
|
||||
zeroW,
|
||||
yDivision
|
||||
})
|
||||
barWidth = this.series[i][j] / this.invertedYRatio
|
||||
} else {
|
||||
paths = this.drawColumnPaths({
|
||||
...pathsParams,
|
||||
xDivision,
|
||||
barWidth,
|
||||
zeroH
|
||||
})
|
||||
barHeight = this.series[i][j] / this.yRatio[this.yaxisIndex]
|
||||
}
|
||||
|
||||
const barGoalLine = this.barHelpers.drawGoalLine({
|
||||
barXPosition: paths.barXPosition,
|
||||
barYPosition: paths.barYPosition,
|
||||
goalX: paths.goalX,
|
||||
goalY: paths.goalY,
|
||||
barHeight,
|
||||
barWidth
|
||||
})
|
||||
|
||||
if (barGoalLine) {
|
||||
elGoalsMarkers.add(barGoalLine)
|
||||
}
|
||||
|
||||
y = paths.y
|
||||
x = paths.x
|
||||
|
||||
// push current X
|
||||
if (j > 0) {
|
||||
xArrj.push(x + barWidth / 2)
|
||||
}
|
||||
|
||||
yArrj.push(y)
|
||||
|
||||
let pathFill = this.barHelpers.getPathFillColor(series, i, j, realIndex)
|
||||
|
||||
this.renderSeries({
|
||||
realIndex,
|
||||
pathFill,
|
||||
j,
|
||||
i,
|
||||
pathFrom: paths.pathFrom,
|
||||
pathTo: paths.pathTo,
|
||||
strokeWidth,
|
||||
elSeries,
|
||||
x,
|
||||
y,
|
||||
series,
|
||||
barHeight,
|
||||
barWidth,
|
||||
elDataLabelsWrap,
|
||||
elGoalsMarkers,
|
||||
visibleSeries: this.visibleI,
|
||||
type: 'bar'
|
||||
})
|
||||
}
|
||||
|
||||
// push all x val arrays into main xArr
|
||||
w.globals.seriesXvalues[realIndex] = xArrj
|
||||
w.globals.seriesYvalues[realIndex] = yArrj
|
||||
|
||||
ret.add(elSeries)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
renderSeries({
|
||||
realIndex,
|
||||
pathFill,
|
||||
lineFill,
|
||||
j,
|
||||
i,
|
||||
pathFrom,
|
||||
pathTo,
|
||||
strokeWidth,
|
||||
elSeries,
|
||||
x,
|
||||
y,
|
||||
y1,
|
||||
y2,
|
||||
series,
|
||||
barHeight,
|
||||
barWidth,
|
||||
barYPosition,
|
||||
elDataLabelsWrap,
|
||||
elGoalsMarkers,
|
||||
visibleSeries,
|
||||
type
|
||||
}) {
|
||||
const w = this.w
|
||||
const graphics = new Graphics(this.ctx)
|
||||
|
||||
if (!lineFill) {
|
||||
/* fix apexcharts#341 */
|
||||
lineFill = this.barOptions.distributed
|
||||
? w.globals.stroke.colors[j]
|
||||
: w.globals.stroke.colors[realIndex]
|
||||
}
|
||||
|
||||
if (w.config.series[i].data[j] && w.config.series[i].data[j].strokeColor) {
|
||||
lineFill = w.config.series[i].data[j].strokeColor
|
||||
}
|
||||
|
||||
if (this.isNullValue) {
|
||||
pathFill = 'none'
|
||||
}
|
||||
|
||||
let delay =
|
||||
((j / w.config.chart.animations.animateGradually.delay) *
|
||||
(w.config.chart.animations.speed / w.globals.dataPoints)) /
|
||||
2.4
|
||||
|
||||
let renderedPath = graphics.renderPaths({
|
||||
i,
|
||||
j,
|
||||
realIndex,
|
||||
pathFrom,
|
||||
pathTo,
|
||||
stroke: lineFill,
|
||||
strokeWidth,
|
||||
strokeLineCap: w.config.stroke.lineCap,
|
||||
fill: pathFill,
|
||||
animationDelay: delay,
|
||||
initialSpeed: w.config.chart.animations.speed,
|
||||
dataChangeSpeed: w.config.chart.animations.dynamicAnimation.speed,
|
||||
className: `apexcharts-${type}-area`
|
||||
})
|
||||
|
||||
renderedPath.attr('clip-path', `url(#gridRectMask${w.globals.cuid})`)
|
||||
|
||||
const forecast = w.config.forecastDataPoints
|
||||
if (forecast.count > 0) {
|
||||
if (j >= w.globals.dataPoints - forecast.count) {
|
||||
renderedPath.node.setAttribute('stroke-dasharray', forecast.dashArray)
|
||||
renderedPath.node.setAttribute('stroke-width', forecast.strokeWidth)
|
||||
renderedPath.node.setAttribute('fill-opacity', forecast.fillOpacity)
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof y1 !== 'undefined' && typeof y2 !== 'undefined') {
|
||||
renderedPath.attr('data-range-y1', y1)
|
||||
renderedPath.attr('data-range-y2', y2)
|
||||
}
|
||||
|
||||
const filters = new Filters(this.ctx)
|
||||
filters.setSelectionFilter(renderedPath, realIndex, j)
|
||||
elSeries.add(renderedPath)
|
||||
|
||||
let barDataLabels = new BarDataLabels(this)
|
||||
let dataLabels = barDataLabels.handleBarDataLabels({
|
||||
x,
|
||||
y,
|
||||
y1,
|
||||
y2,
|
||||
i,
|
||||
j,
|
||||
series,
|
||||
realIndex,
|
||||
barHeight,
|
||||
barWidth,
|
||||
barYPosition,
|
||||
renderedPath,
|
||||
visibleSeries
|
||||
})
|
||||
if (dataLabels !== null) {
|
||||
elDataLabelsWrap.add(dataLabels)
|
||||
}
|
||||
|
||||
elSeries.add(elDataLabelsWrap)
|
||||
|
||||
if (elGoalsMarkers) {
|
||||
elSeries.add(elGoalsMarkers)
|
||||
}
|
||||
return elSeries
|
||||
}
|
||||
|
||||
drawBarPaths({
|
||||
indexes,
|
||||
barHeight,
|
||||
strokeWidth,
|
||||
zeroW,
|
||||
x,
|
||||
y,
|
||||
yDivision,
|
||||
elSeries
|
||||
}) {
|
||||
let w = this.w
|
||||
|
||||
let i = indexes.i
|
||||
let j = indexes.j
|
||||
|
||||
if (w.globals.isXNumeric) {
|
||||
y =
|
||||
(w.globals.seriesX[i][j] - w.globals.minX) / this.invertedXRatio -
|
||||
barHeight
|
||||
}
|
||||
|
||||
let barYPosition = y + barHeight * this.visibleI
|
||||
|
||||
x = this.barHelpers.getXForValue(this.series[i][j], zeroW)
|
||||
|
||||
const paths = this.barHelpers.getBarpaths({
|
||||
barYPosition,
|
||||
barHeight,
|
||||
x1: zeroW,
|
||||
x2: x,
|
||||
strokeWidth,
|
||||
series: this.series,
|
||||
realIndex: indexes.realIndex,
|
||||
i,
|
||||
j,
|
||||
w
|
||||
})
|
||||
|
||||
if (!w.globals.isXNumeric) {
|
||||
y = y + yDivision
|
||||
}
|
||||
|
||||
this.barHelpers.barBackground({
|
||||
j,
|
||||
i,
|
||||
y1: barYPosition - barHeight * this.visibleI,
|
||||
y2: barHeight * this.seriesLen,
|
||||
elSeries
|
||||
})
|
||||
|
||||
return {
|
||||
pathTo: paths.pathTo,
|
||||
pathFrom: paths.pathFrom,
|
||||
x,
|
||||
y,
|
||||
goalX: this.barHelpers.getGoalValues('x', zeroW, null, i, j),
|
||||
barYPosition
|
||||
}
|
||||
}
|
||||
|
||||
drawColumnPaths({
|
||||
indexes,
|
||||
x,
|
||||
y,
|
||||
xDivision,
|
||||
barWidth,
|
||||
zeroH,
|
||||
strokeWidth,
|
||||
elSeries
|
||||
}) {
|
||||
let w = this.w
|
||||
|
||||
let realIndex = indexes.realIndex
|
||||
let i = indexes.i
|
||||
let j = indexes.j
|
||||
let bc = indexes.bc
|
||||
|
||||
if (w.globals.isXNumeric) {
|
||||
let sxI = realIndex
|
||||
if (!w.globals.seriesX[realIndex].length) {
|
||||
sxI = w.globals.maxValsInArrayIndex
|
||||
}
|
||||
|
||||
x =
|
||||
(w.globals.seriesX[sxI][j] - w.globals.minX) / this.xRatio -
|
||||
(barWidth * this.seriesLen) / 2
|
||||
}
|
||||
|
||||
let barXPosition = x + barWidth * this.visibleI
|
||||
|
||||
y = this.barHelpers.getYForValue(this.series[i][j], zeroH)
|
||||
|
||||
const paths = this.barHelpers.getColumnPaths({
|
||||
barXPosition,
|
||||
barWidth,
|
||||
y1: zeroH,
|
||||
y2: y,
|
||||
strokeWidth,
|
||||
series: this.series,
|
||||
realIndex: indexes.realIndex,
|
||||
i,
|
||||
j,
|
||||
w
|
||||
})
|
||||
|
||||
if (!w.globals.isXNumeric) {
|
||||
x = x + xDivision
|
||||
}
|
||||
|
||||
this.barHelpers.barBackground({
|
||||
bc,
|
||||
j,
|
||||
i,
|
||||
x1: barXPosition - strokeWidth / 2 - barWidth * this.visibleI,
|
||||
x2: barWidth * this.seriesLen + strokeWidth / 2,
|
||||
elSeries
|
||||
})
|
||||
|
||||
return {
|
||||
pathTo: paths.pathTo,
|
||||
pathFrom: paths.pathFrom,
|
||||
x,
|
||||
y,
|
||||
goalY: this.barHelpers.getGoalValues('y', null, zeroH, i, j),
|
||||
barXPosition
|
||||
}
|
||||
}
|
||||
|
||||
/** getPreviousPath is a common function for bars/columns which is used to get previous paths when data changes.
|
||||
* @memberof Bar
|
||||
* @param {int} realIndex - current iterating i
|
||||
* @param {int} j - current iterating series's j index
|
||||
* @return {string} pathFrom is the string which will be appended in animations
|
||||
**/
|
||||
getPreviousPath(realIndex, j) {
|
||||
let w = this.w
|
||||
let pathFrom
|
||||
for (let pp = 0; pp < w.globals.previousPaths.length; pp++) {
|
||||
let gpp = w.globals.previousPaths[pp]
|
||||
|
||||
if (
|
||||
gpp.paths &&
|
||||
gpp.paths.length > 0 &&
|
||||
parseInt(gpp.realIndex, 10) === parseInt(realIndex, 10)
|
||||
) {
|
||||
if (typeof w.globals.previousPaths[pp].paths[j] !== 'undefined') {
|
||||
pathFrom = w.globals.previousPaths[pp].paths[j].d
|
||||
}
|
||||
}
|
||||
}
|
||||
return pathFrom
|
||||
}
|
||||
}
|
||||
|
||||
export default Bar
|
||||
@@ -0,0 +1,481 @@
|
||||
import CoreUtils from '../modules/CoreUtils'
|
||||
import Bar from './Bar'
|
||||
import Graphics from '../modules/Graphics'
|
||||
import Utils from '../utils/Utils'
|
||||
|
||||
/**
|
||||
* ApexCharts BarStacked Class responsible for drawing both Stacked Columns and Bars.
|
||||
*
|
||||
* @module BarStacked
|
||||
* The whole calculation for stacked bar/column is different from normal bar/column,
|
||||
* hence it makes sense to derive a new class for it extending most of the props of Parent Bar
|
||||
**/
|
||||
|
||||
class BarStacked extends Bar {
|
||||
draw(series, seriesIndex) {
|
||||
let w = this.w
|
||||
this.graphics = new Graphics(this.ctx)
|
||||
this.bar = new Bar(this.ctx, this.xyRatios)
|
||||
|
||||
const coreUtils = new CoreUtils(this.ctx, w)
|
||||
series = coreUtils.getLogSeries(series)
|
||||
this.yRatio = coreUtils.getLogYRatios(this.yRatio)
|
||||
|
||||
this.barHelpers.initVariables(series)
|
||||
|
||||
if (w.config.chart.stackType === '100%') {
|
||||
series = w.globals.seriesPercent.slice()
|
||||
}
|
||||
|
||||
this.series = series
|
||||
|
||||
this.totalItems = 0
|
||||
|
||||
this.prevY = [] // y position on chart
|
||||
this.prevX = [] // x position on chart
|
||||
this.prevYF = [] // y position including shapes on chart
|
||||
this.prevXF = [] // x position including shapes on chart
|
||||
this.prevYVal = [] // y values (series[i][j]) in columns
|
||||
this.prevXVal = [] // x values (series[i][j]) in bars
|
||||
|
||||
this.xArrj = [] // xj indicates x position on graph in bars
|
||||
this.xArrjF = [] // xjF indicates bar's x position + roundedShape's positions in bars
|
||||
this.xArrjVal = [] // x val means the actual series's y values in horizontal/bars
|
||||
this.yArrj = [] // yj indicates y position on graph in columns
|
||||
this.yArrjF = [] // yjF indicates bar's y position + roundedShape's positions in columns
|
||||
this.yArrjVal = [] // y val means the actual series's y values in columns
|
||||
|
||||
for (let sl = 0; sl < series.length; sl++) {
|
||||
if (series[sl].length > 0) {
|
||||
this.totalItems += series[sl].length
|
||||
}
|
||||
}
|
||||
|
||||
let ret = this.graphics.group({
|
||||
class: 'apexcharts-bar-series apexcharts-plot-series'
|
||||
})
|
||||
|
||||
let x = 0
|
||||
let y = 0
|
||||
|
||||
for (let i = 0, bc = 0; i < series.length; i++, bc++) {
|
||||
let xDivision // xDivision is the GRIDWIDTH divided by number of datapoints (columns)
|
||||
let yDivision // yDivision is the GRIDHEIGHT divided by number of datapoints (bars)
|
||||
let zeroH // zeroH is the baseline where 0 meets y axis
|
||||
let zeroW // zeroW is the baseline where 0 meets x axis
|
||||
|
||||
let xArrValues = []
|
||||
let yArrValues = []
|
||||
|
||||
let realIndex = w.globals.comboCharts ? seriesIndex[i] : i
|
||||
|
||||
if (this.yRatio.length > 1) {
|
||||
this.yaxisIndex = realIndex
|
||||
}
|
||||
|
||||
this.isReversed =
|
||||
w.config.yaxis[this.yaxisIndex] &&
|
||||
w.config.yaxis[this.yaxisIndex].reversed
|
||||
|
||||
// el to which series will be drawn
|
||||
let elSeries = this.graphics.group({
|
||||
class: `apexcharts-series`,
|
||||
seriesName: Utils.escapeString(w.globals.seriesNames[realIndex]),
|
||||
rel: i + 1,
|
||||
'data:realIndex': realIndex
|
||||
})
|
||||
this.ctx.series.addCollapsedClassToSeries(elSeries, realIndex)
|
||||
|
||||
// eldatalabels
|
||||
let elDataLabelsWrap = this.graphics.group({
|
||||
class: 'apexcharts-datalabels',
|
||||
'data:realIndex': realIndex
|
||||
})
|
||||
|
||||
let barHeight = 0
|
||||
let barWidth = 0
|
||||
|
||||
let initPositions = this.initialPositions(
|
||||
x,
|
||||
y,
|
||||
xDivision,
|
||||
yDivision,
|
||||
zeroH,
|
||||
zeroW
|
||||
)
|
||||
y = initPositions.y
|
||||
barHeight = initPositions.barHeight
|
||||
yDivision = initPositions.yDivision
|
||||
zeroW = initPositions.zeroW
|
||||
|
||||
x = initPositions.x
|
||||
barWidth = initPositions.barWidth
|
||||
xDivision = initPositions.xDivision
|
||||
zeroH = initPositions.zeroH
|
||||
|
||||
this.yArrj = []
|
||||
this.yArrjF = []
|
||||
this.yArrjVal = []
|
||||
this.xArrj = []
|
||||
this.xArrjF = []
|
||||
this.xArrjVal = []
|
||||
|
||||
// if (!this.horizontal) {
|
||||
// this.xArrj.push(x + barWidth / 2)
|
||||
// }
|
||||
|
||||
// fix issue #1215;
|
||||
// where all stack bar disappear after collapsing the first series
|
||||
// sol: if only 1 arr in this.prevY(this.prevY.length === 1) and all are NaN
|
||||
if (this.prevY.length === 1 && this.prevY[0].every((val) => isNaN(val))) {
|
||||
// make this.prevY[0] all zeroH
|
||||
this.prevY[0] = this.prevY[0].map((val) => zeroH)
|
||||
// make this.prevYF[0] all 0
|
||||
this.prevYF[0] = this.prevYF[0].map((val) => 0)
|
||||
}
|
||||
|
||||
for (let j = 0; j < w.globals.dataPoints; j++) {
|
||||
const strokeWidth = this.barHelpers.getStrokeWidth(i, j, realIndex)
|
||||
const commonPathOpts = {
|
||||
indexes: { i, j, realIndex, bc },
|
||||
strokeWidth,
|
||||
x,
|
||||
y,
|
||||
elSeries
|
||||
}
|
||||
let paths = null
|
||||
if (this.isHorizontal) {
|
||||
paths = this.drawStackedBarPaths({
|
||||
...commonPathOpts,
|
||||
zeroW,
|
||||
barHeight,
|
||||
yDivision
|
||||
})
|
||||
barWidth = this.series[i][j] / this.invertedYRatio
|
||||
} else {
|
||||
paths = this.drawStackedColumnPaths({
|
||||
...commonPathOpts,
|
||||
xDivision,
|
||||
barWidth,
|
||||
zeroH
|
||||
})
|
||||
barHeight = this.series[i][j] / this.yRatio[this.yaxisIndex]
|
||||
}
|
||||
|
||||
y = paths.y
|
||||
x = paths.x
|
||||
|
||||
xArrValues.push(x)
|
||||
yArrValues.push(y)
|
||||
|
||||
let pathFill = this.barHelpers.getPathFillColor(series, i, j, realIndex)
|
||||
|
||||
elSeries = this.renderSeries({
|
||||
realIndex,
|
||||
pathFill,
|
||||
j,
|
||||
i,
|
||||
pathFrom: paths.pathFrom,
|
||||
pathTo: paths.pathTo,
|
||||
strokeWidth,
|
||||
elSeries,
|
||||
x,
|
||||
y,
|
||||
series,
|
||||
barHeight,
|
||||
barWidth,
|
||||
elDataLabelsWrap,
|
||||
type: 'bar',
|
||||
visibleSeries: 0
|
||||
})
|
||||
}
|
||||
|
||||
// push all x val arrays into main xArr
|
||||
w.globals.seriesXvalues[realIndex] = xArrValues
|
||||
w.globals.seriesYvalues[realIndex] = yArrValues
|
||||
|
||||
// push all current y values array to main PrevY Array
|
||||
this.prevY.push(this.yArrj)
|
||||
this.prevYF.push(this.yArrjF)
|
||||
this.prevYVal.push(this.yArrjVal)
|
||||
this.prevX.push(this.xArrj)
|
||||
this.prevXF.push(this.xArrjF)
|
||||
this.prevXVal.push(this.xArrjVal)
|
||||
|
||||
ret.add(elSeries)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
initialPositions(x, y, xDivision, yDivision, zeroH, zeroW) {
|
||||
let w = this.w
|
||||
|
||||
let barHeight, barWidth
|
||||
if (this.isHorizontal) {
|
||||
// height divided into equal parts
|
||||
yDivision = w.globals.gridHeight / w.globals.dataPoints
|
||||
barHeight = yDivision
|
||||
|
||||
barHeight =
|
||||
(barHeight * parseInt(w.config.plotOptions.bar.barHeight, 10)) / 100
|
||||
|
||||
zeroW =
|
||||
this.baseLineInvertedY +
|
||||
w.globals.padHorizontal +
|
||||
(this.isReversed ? w.globals.gridWidth : 0) -
|
||||
(this.isReversed ? this.baseLineInvertedY * 2 : 0)
|
||||
|
||||
// initial y position is half of barHeight * half of number of Bars
|
||||
y = (yDivision - barHeight) / 2
|
||||
} else {
|
||||
// width divided into equal parts
|
||||
xDivision = w.globals.gridWidth / w.globals.dataPoints
|
||||
|
||||
barWidth = xDivision
|
||||
|
||||
if (w.globals.isXNumeric && w.globals.dataPoints > 1) {
|
||||
// the check (w.globals.dataPoints > 1) fixes apexcharts.js #1617
|
||||
xDivision = w.globals.minXDiff / this.xRatio
|
||||
barWidth = (xDivision * parseInt(this.barOptions.columnWidth, 10)) / 100
|
||||
} else {
|
||||
barWidth =
|
||||
(barWidth * parseInt(w.config.plotOptions.bar.columnWidth, 10)) / 100
|
||||
}
|
||||
|
||||
zeroH =
|
||||
this.baseLineY[this.yaxisIndex] +
|
||||
(this.isReversed ? w.globals.gridHeight : 0) -
|
||||
(this.isReversed ? this.baseLineY[this.yaxisIndex] * 2 : 0)
|
||||
|
||||
// initial x position is one third of barWidth
|
||||
x = w.globals.padHorizontal + (xDivision - barWidth) / 2
|
||||
}
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
yDivision,
|
||||
xDivision,
|
||||
barHeight,
|
||||
barWidth,
|
||||
zeroH,
|
||||
zeroW
|
||||
}
|
||||
}
|
||||
|
||||
drawStackedBarPaths({
|
||||
indexes,
|
||||
barHeight,
|
||||
strokeWidth,
|
||||
zeroW,
|
||||
x,
|
||||
y,
|
||||
yDivision,
|
||||
elSeries
|
||||
}) {
|
||||
let w = this.w
|
||||
let barYPosition = y
|
||||
let barXPosition
|
||||
let i = indexes.i
|
||||
let j = indexes.j
|
||||
|
||||
let prevBarW = 0
|
||||
for (let k = 0; k < this.prevXF.length; k++) {
|
||||
prevBarW = prevBarW + this.prevXF[k][j]
|
||||
}
|
||||
|
||||
if (i > 0) {
|
||||
let bXP = zeroW
|
||||
|
||||
if (this.prevXVal[i - 1][j] < 0) {
|
||||
bXP =
|
||||
this.series[i][j] >= 0
|
||||
? this.prevX[i - 1][j] +
|
||||
prevBarW -
|
||||
(this.isReversed ? prevBarW : 0) * 2
|
||||
: this.prevX[i - 1][j]
|
||||
} else if (this.prevXVal[i - 1][j] >= 0) {
|
||||
bXP =
|
||||
this.series[i][j] >= 0
|
||||
? this.prevX[i - 1][j]
|
||||
: this.prevX[i - 1][j] -
|
||||
prevBarW +
|
||||
(this.isReversed ? prevBarW : 0) * 2
|
||||
}
|
||||
|
||||
barXPosition = bXP
|
||||
} else {
|
||||
// the first series will not have prevX values
|
||||
barXPosition = zeroW
|
||||
}
|
||||
|
||||
if (this.series[i][j] === null) {
|
||||
x = barXPosition
|
||||
} else {
|
||||
x =
|
||||
barXPosition +
|
||||
this.series[i][j] / this.invertedYRatio -
|
||||
(this.isReversed ? this.series[i][j] / this.invertedYRatio : 0) * 2
|
||||
}
|
||||
|
||||
const paths = this.barHelpers.getBarpaths({
|
||||
barYPosition,
|
||||
barHeight,
|
||||
x1: barXPosition,
|
||||
x2: x,
|
||||
strokeWidth,
|
||||
series: this.series,
|
||||
realIndex: indexes.realIndex,
|
||||
i,
|
||||
j,
|
||||
w
|
||||
})
|
||||
|
||||
this.barHelpers.barBackground({
|
||||
j,
|
||||
i,
|
||||
y1: barYPosition,
|
||||
y2: barHeight,
|
||||
elSeries
|
||||
})
|
||||
|
||||
y = y + yDivision
|
||||
|
||||
return {
|
||||
pathTo: paths.pathTo,
|
||||
pathFrom: paths.pathFrom,
|
||||
x,
|
||||
y
|
||||
}
|
||||
}
|
||||
|
||||
drawStackedColumnPaths({
|
||||
indexes,
|
||||
x,
|
||||
y,
|
||||
xDivision,
|
||||
barWidth,
|
||||
zeroH,
|
||||
strokeWidth,
|
||||
elSeries
|
||||
}) {
|
||||
let w = this.w
|
||||
let i = indexes.i
|
||||
let j = indexes.j
|
||||
let bc = indexes.bc
|
||||
|
||||
if (w.globals.isXNumeric) {
|
||||
let seriesVal = w.globals.seriesX[i][j]
|
||||
if (!seriesVal) seriesVal = 0
|
||||
x = (seriesVal - w.globals.minX) / this.xRatio - barWidth / 2
|
||||
}
|
||||
|
||||
let barXPosition = x
|
||||
let barYPosition
|
||||
|
||||
let prevBarH = 0
|
||||
for (let k = 0; k < this.prevYF.length; k++) {
|
||||
// fix issue #1215
|
||||
// in case where this.prevYF[k][j] is NaN, use 0 instead
|
||||
prevBarH = prevBarH + (!isNaN(this.prevYF[k][j]) ? this.prevYF[k][j] : 0)
|
||||
}
|
||||
|
||||
if (
|
||||
(i > 0 && !w.globals.isXNumeric) ||
|
||||
(i > 0 &&
|
||||
w.globals.isXNumeric &&
|
||||
w.globals.seriesX[i - 1][j] === w.globals.seriesX[i][j])
|
||||
) {
|
||||
let bYP
|
||||
let prevYValue
|
||||
const p = Math.min(this.yRatio.length + 1, i + 1)
|
||||
if (this.prevY[i - 1] !== undefined) {
|
||||
for (let ii = 1; ii < p; ii++) {
|
||||
if (!isNaN(this.prevY[i - ii][j])) {
|
||||
// find the previous available value to give prevYValue
|
||||
prevYValue = this.prevY[i - ii][j]
|
||||
// if found it, break the loop
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let ii = 1; ii < p; ii++) {
|
||||
// find the previous available value(non-NaN) to give bYP
|
||||
if (this.prevYVal[i - ii][j] < 0) {
|
||||
bYP =
|
||||
this.series[i][j] >= 0
|
||||
? prevYValue - prevBarH + (this.isReversed ? prevBarH : 0) * 2
|
||||
: prevYValue
|
||||
// found it? break the loop
|
||||
break
|
||||
} else if (this.prevYVal[i - ii][j] >= 0) {
|
||||
bYP =
|
||||
this.series[i][j] >= 0
|
||||
? prevYValue
|
||||
: prevYValue + prevBarH - (this.isReversed ? prevBarH : 0) * 2
|
||||
// found it? break the loop
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof bYP === 'undefined') bYP = w.globals.gridHeight
|
||||
|
||||
// if this.prevYF[0] is all 0 resulted from line #486
|
||||
// AND every arr starting from the second only contains NaN
|
||||
if (
|
||||
this.prevYF[0].every((val) => val === 0) &&
|
||||
this.prevYF.slice(1, i).every((arr) => arr.every((val) => isNaN(val)))
|
||||
) {
|
||||
// Use the same calc way as line #485
|
||||
barYPosition = w.globals.gridHeight - zeroH
|
||||
} else {
|
||||
// Nothing special
|
||||
barYPosition = bYP
|
||||
}
|
||||
} else {
|
||||
// the first series will not have prevY values, also if the prev index's series X doesn't matches the current index's series X, then start from zero
|
||||
barYPosition = w.globals.gridHeight - zeroH
|
||||
}
|
||||
|
||||
y =
|
||||
barYPosition -
|
||||
this.series[i][j] / this.yRatio[this.yaxisIndex] +
|
||||
(this.isReversed ? this.series[i][j] / this.yRatio[this.yaxisIndex] : 0) *
|
||||
2
|
||||
|
||||
const paths = this.barHelpers.getColumnPaths({
|
||||
barXPosition,
|
||||
barWidth,
|
||||
y1: barYPosition,
|
||||
y2: y,
|
||||
yRatio: this.yRatio[this.yaxisIndex],
|
||||
strokeWidth: this.strokeWidth,
|
||||
series: this.series,
|
||||
realIndex: indexes.realIndex,
|
||||
i,
|
||||
j,
|
||||
w
|
||||
})
|
||||
|
||||
this.barHelpers.barBackground({
|
||||
bc,
|
||||
j,
|
||||
i,
|
||||
x1: barXPosition,
|
||||
x2: barWidth,
|
||||
elSeries
|
||||
})
|
||||
|
||||
x = x + xDivision
|
||||
|
||||
return {
|
||||
pathTo: paths.pathTo,
|
||||
pathFrom: paths.pathFrom,
|
||||
x: w.globals.isXNumeric ? x - xDivision : x,
|
||||
y
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default BarStacked
|
||||
@@ -0,0 +1,428 @@
|
||||
import CoreUtils from '../modules/CoreUtils'
|
||||
import Bar from './Bar'
|
||||
import Fill from '../modules/Fill'
|
||||
import Graphics from '../modules/Graphics'
|
||||
import Utils from '../utils/Utils'
|
||||
|
||||
/**
|
||||
* ApexCharts BoxCandleStick Class responsible for drawing both Stacked Columns and Bars.
|
||||
*
|
||||
* @module BoxCandleStick
|
||||
**/
|
||||
|
||||
class BoxCandleStick extends Bar {
|
||||
draw(series, seriesIndex) {
|
||||
let w = this.w
|
||||
let graphics = new Graphics(this.ctx)
|
||||
let fill = new Fill(this.ctx)
|
||||
|
||||
this.candlestickOptions = this.w.config.plotOptions.candlestick
|
||||
this.boxOptions = this.w.config.plotOptions.boxPlot
|
||||
this.isHorizontal = w.config.plotOptions.bar.horizontal
|
||||
|
||||
const coreUtils = new CoreUtils(this.ctx, w)
|
||||
series = coreUtils.getLogSeries(series)
|
||||
this.series = series
|
||||
this.yRatio = coreUtils.getLogYRatios(this.yRatio)
|
||||
|
||||
this.barHelpers.initVariables(series)
|
||||
|
||||
let ret = graphics.group({
|
||||
class: `apexcharts-${w.config.chart.type}-series apexcharts-plot-series`
|
||||
})
|
||||
|
||||
for (let i = 0; i < series.length; i++) {
|
||||
this.isBoxPlot =
|
||||
w.config.chart.type === 'boxPlot' ||
|
||||
w.config.series[i].type === 'boxPlot'
|
||||
|
||||
let x,
|
||||
y,
|
||||
xDivision, // xDivision is the GRIDWIDTH divided by number of datapoints (columns)
|
||||
yDivision, // yDivision is the GRIDHEIGHT divided by number of datapoints (bars)
|
||||
zeroH, // zeroH is the baseline where 0 meets y axis
|
||||
zeroW // zeroW is the baseline where 0 meets x axis
|
||||
|
||||
let yArrj = [] // hold y values of current iterating series
|
||||
let xArrj = [] // hold x values of current iterating series
|
||||
|
||||
let realIndex = w.globals.comboCharts ? seriesIndex[i] : i
|
||||
|
||||
// el to which series will be drawn
|
||||
let elSeries = graphics.group({
|
||||
class: `apexcharts-series`,
|
||||
seriesName: Utils.escapeString(w.globals.seriesNames[realIndex]),
|
||||
rel: i + 1,
|
||||
'data:realIndex': realIndex
|
||||
})
|
||||
|
||||
this.ctx.series.addCollapsedClassToSeries(elSeries, realIndex)
|
||||
|
||||
if (series[i].length > 0) {
|
||||
this.visibleI = this.visibleI + 1
|
||||
}
|
||||
|
||||
let barHeight = 0
|
||||
let barWidth = 0
|
||||
|
||||
if (this.yRatio.length > 1) {
|
||||
this.yaxisIndex = realIndex
|
||||
}
|
||||
|
||||
let initPositions = this.barHelpers.initialPositions()
|
||||
|
||||
y = initPositions.y
|
||||
barHeight = initPositions.barHeight
|
||||
yDivision = initPositions.yDivision
|
||||
zeroW = initPositions.zeroW
|
||||
|
||||
x = initPositions.x
|
||||
barWidth = initPositions.barWidth
|
||||
xDivision = initPositions.xDivision
|
||||
zeroH = initPositions.zeroH
|
||||
|
||||
xArrj.push(x + barWidth / 2)
|
||||
|
||||
// eldatalabels
|
||||
let elDataLabelsWrap = graphics.group({
|
||||
class: 'apexcharts-datalabels',
|
||||
'data:realIndex': realIndex
|
||||
})
|
||||
|
||||
for (let j = 0; j < w.globals.dataPoints; j++) {
|
||||
const strokeWidth = this.barHelpers.getStrokeWidth(i, j, realIndex)
|
||||
|
||||
let paths = null
|
||||
const pathsParams = {
|
||||
indexes: {
|
||||
i,
|
||||
j,
|
||||
realIndex
|
||||
},
|
||||
x,
|
||||
y,
|
||||
strokeWidth,
|
||||
elSeries
|
||||
}
|
||||
|
||||
if (this.isHorizontal) {
|
||||
paths = this.drawHorizontalBoxPaths({
|
||||
...pathsParams,
|
||||
yDivision,
|
||||
barHeight,
|
||||
zeroW
|
||||
})
|
||||
} else {
|
||||
paths = this.drawVerticalBoxPaths({
|
||||
...pathsParams,
|
||||
xDivision,
|
||||
barWidth,
|
||||
zeroH
|
||||
})
|
||||
}
|
||||
|
||||
y = paths.y
|
||||
x = paths.x
|
||||
|
||||
// push current X
|
||||
if (j > 0) {
|
||||
xArrj.push(x + barWidth / 2)
|
||||
}
|
||||
|
||||
yArrj.push(y)
|
||||
|
||||
paths.pathTo.forEach((pathTo, pi) => {
|
||||
let lineFill =
|
||||
!this.isBoxPlot && this.candlestickOptions.wick.useFillColor
|
||||
? paths.color[pi]
|
||||
: w.globals.stroke.colors[i]
|
||||
|
||||
let pathFill = fill.fillPath({
|
||||
seriesNumber: realIndex,
|
||||
dataPointIndex: j,
|
||||
color: paths.color[pi],
|
||||
value: series[i][j]
|
||||
})
|
||||
|
||||
this.renderSeries({
|
||||
realIndex,
|
||||
pathFill,
|
||||
lineFill,
|
||||
j,
|
||||
i,
|
||||
pathFrom: paths.pathFrom,
|
||||
pathTo,
|
||||
strokeWidth,
|
||||
elSeries,
|
||||
x,
|
||||
y,
|
||||
series,
|
||||
barHeight,
|
||||
barWidth,
|
||||
elDataLabelsWrap,
|
||||
visibleSeries: this.visibleI,
|
||||
type: w.config.chart.type
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// push all x val arrays into main xArr
|
||||
w.globals.seriesXvalues[realIndex] = xArrj
|
||||
w.globals.seriesYvalues[realIndex] = yArrj
|
||||
|
||||
ret.add(elSeries)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
drawVerticalBoxPaths({
|
||||
indexes,
|
||||
x,
|
||||
y,
|
||||
xDivision,
|
||||
barWidth,
|
||||
zeroH,
|
||||
strokeWidth
|
||||
}) {
|
||||
let w = this.w
|
||||
let graphics = new Graphics(this.ctx)
|
||||
|
||||
let i = indexes.i
|
||||
let j = indexes.j
|
||||
|
||||
let isPositive = true
|
||||
let colorPos = w.config.plotOptions.candlestick.colors.upward
|
||||
let colorNeg = w.config.plotOptions.candlestick.colors.downward
|
||||
let color = ''
|
||||
|
||||
if (this.isBoxPlot) {
|
||||
color = [this.boxOptions.colors.lower, this.boxOptions.colors.upper]
|
||||
}
|
||||
|
||||
const yRatio = this.yRatio[this.yaxisIndex]
|
||||
let realIndex = indexes.realIndex
|
||||
|
||||
const ohlc = this.getOHLCValue(realIndex, j)
|
||||
let l1 = zeroH
|
||||
let l2 = zeroH
|
||||
|
||||
if (ohlc.o > ohlc.c) {
|
||||
isPositive = false
|
||||
}
|
||||
|
||||
let y1 = Math.min(ohlc.o, ohlc.c)
|
||||
let y2 = Math.max(ohlc.o, ohlc.c)
|
||||
let m = ohlc.m
|
||||
|
||||
if (w.globals.isXNumeric) {
|
||||
x =
|
||||
(w.globals.seriesX[realIndex][j] - w.globals.minX) / this.xRatio -
|
||||
barWidth / 2
|
||||
}
|
||||
|
||||
let barXPosition = x + barWidth * this.visibleI
|
||||
|
||||
if (
|
||||
typeof this.series[i][j] === 'undefined' ||
|
||||
this.series[i][j] === null
|
||||
) {
|
||||
y1 = zeroH
|
||||
y2 = zeroH
|
||||
} else {
|
||||
y1 = zeroH - y1 / yRatio
|
||||
y2 = zeroH - y2 / yRatio
|
||||
l1 = zeroH - ohlc.h / yRatio
|
||||
l2 = zeroH - ohlc.l / yRatio
|
||||
m = zeroH - ohlc.m / yRatio
|
||||
}
|
||||
|
||||
let pathTo = graphics.move(barXPosition, zeroH)
|
||||
let pathFrom = graphics.move(barXPosition + barWidth / 2, y1)
|
||||
if (w.globals.previousPaths.length > 0) {
|
||||
pathFrom = this.getPreviousPath(realIndex, j, true)
|
||||
}
|
||||
|
||||
if (this.isBoxPlot) {
|
||||
pathTo = [
|
||||
graphics.move(barXPosition, y1) +
|
||||
graphics.line(barXPosition + barWidth / 2, y1) +
|
||||
graphics.line(barXPosition + barWidth / 2, l1) +
|
||||
graphics.line(barXPosition + barWidth / 4, l1) +
|
||||
graphics.line(barXPosition + barWidth - barWidth / 4, l1) +
|
||||
graphics.line(barXPosition + barWidth / 2, l1) +
|
||||
graphics.line(barXPosition + barWidth / 2, y1) +
|
||||
graphics.line(barXPosition + barWidth, y1) +
|
||||
graphics.line(barXPosition + barWidth, m) +
|
||||
graphics.line(barXPosition, m) +
|
||||
graphics.line(barXPosition, y1 + strokeWidth / 2),
|
||||
graphics.move(barXPosition, m) +
|
||||
graphics.line(barXPosition + barWidth, m) +
|
||||
graphics.line(barXPosition + barWidth, y2) +
|
||||
graphics.line(barXPosition + barWidth / 2, y2) +
|
||||
graphics.line(barXPosition + barWidth / 2, l2) +
|
||||
graphics.line(barXPosition + barWidth - barWidth / 4, l2) +
|
||||
graphics.line(barXPosition + barWidth / 4, l2) +
|
||||
graphics.line(barXPosition + barWidth / 2, l2) +
|
||||
graphics.line(barXPosition + barWidth / 2, y2) +
|
||||
graphics.line(barXPosition, y2) +
|
||||
graphics.line(barXPosition, m) +
|
||||
'z'
|
||||
]
|
||||
} else {
|
||||
// candlestick
|
||||
pathTo = [
|
||||
graphics.move(barXPosition, y2) +
|
||||
graphics.line(barXPosition + barWidth / 2, y2) +
|
||||
graphics.line(barXPosition + barWidth / 2, l1) +
|
||||
graphics.line(barXPosition + barWidth / 2, y2) +
|
||||
graphics.line(barXPosition + barWidth, y2) +
|
||||
graphics.line(barXPosition + barWidth, y1) +
|
||||
graphics.line(barXPosition + barWidth / 2, y1) +
|
||||
graphics.line(barXPosition + barWidth / 2, l2) +
|
||||
graphics.line(barXPosition + barWidth / 2, y1) +
|
||||
graphics.line(barXPosition, y1) +
|
||||
graphics.line(barXPosition, y2 - strokeWidth / 2)
|
||||
]
|
||||
}
|
||||
|
||||
pathFrom = pathFrom + graphics.move(barXPosition, y1)
|
||||
|
||||
if (!w.globals.isXNumeric) {
|
||||
x = x + xDivision
|
||||
}
|
||||
|
||||
return {
|
||||
pathTo,
|
||||
pathFrom,
|
||||
x,
|
||||
y: y2,
|
||||
barXPosition,
|
||||
color: this.isBoxPlot ? color : isPositive ? [colorPos] : [colorNeg]
|
||||
}
|
||||
}
|
||||
|
||||
drawHorizontalBoxPaths({
|
||||
indexes,
|
||||
x,
|
||||
y,
|
||||
yDivision,
|
||||
barHeight,
|
||||
zeroW,
|
||||
strokeWidth
|
||||
}) {
|
||||
let w = this.w
|
||||
let graphics = new Graphics(this.ctx)
|
||||
|
||||
let i = indexes.i
|
||||
let j = indexes.j
|
||||
|
||||
let color = this.boxOptions.colors.lower
|
||||
|
||||
if (this.isBoxPlot) {
|
||||
color = [this.boxOptions.colors.lower, this.boxOptions.colors.upper]
|
||||
}
|
||||
|
||||
const yRatio = this.invertedYRatio
|
||||
let realIndex = indexes.realIndex
|
||||
|
||||
const ohlc = this.getOHLCValue(realIndex, j)
|
||||
let l1 = zeroW
|
||||
let l2 = zeroW
|
||||
|
||||
let x1 = Math.min(ohlc.o, ohlc.c)
|
||||
let x2 = Math.max(ohlc.o, ohlc.c)
|
||||
let m = ohlc.m
|
||||
|
||||
if (w.globals.isXNumeric) {
|
||||
y =
|
||||
(w.globals.seriesX[realIndex][j] - w.globals.minX) /
|
||||
this.invertedXRatio -
|
||||
barHeight / 2
|
||||
}
|
||||
|
||||
let barYPosition = y + barHeight * this.visibleI
|
||||
|
||||
if (
|
||||
typeof this.series[i][j] === 'undefined' ||
|
||||
this.series[i][j] === null
|
||||
) {
|
||||
x1 = zeroW
|
||||
x2 = zeroW
|
||||
} else {
|
||||
x1 = zeroW + x1 / yRatio
|
||||
x2 = zeroW + x2 / yRatio
|
||||
l1 = zeroW + ohlc.h / yRatio
|
||||
l2 = zeroW + ohlc.l / yRatio
|
||||
m = zeroW + ohlc.m / yRatio
|
||||
}
|
||||
|
||||
let pathTo = graphics.move(zeroW, barYPosition)
|
||||
let pathFrom = graphics.move(x1, barYPosition + barHeight / 2)
|
||||
if (w.globals.previousPaths.length > 0) {
|
||||
pathFrom = this.getPreviousPath(realIndex, j, true)
|
||||
}
|
||||
|
||||
pathTo = [
|
||||
graphics.move(x1, barYPosition) +
|
||||
graphics.line(x1, barYPosition + barHeight / 2) +
|
||||
graphics.line(l1, barYPosition + barHeight / 2) +
|
||||
graphics.line(l1, barYPosition + barHeight / 2 - barHeight / 4) +
|
||||
graphics.line(l1, barYPosition + barHeight / 2 + barHeight / 4) +
|
||||
graphics.line(l1, barYPosition + barHeight / 2) +
|
||||
graphics.line(x1, barYPosition + barHeight / 2) +
|
||||
graphics.line(x1, barYPosition + barHeight) +
|
||||
graphics.line(m, barYPosition + barHeight) +
|
||||
graphics.line(m, barYPosition) +
|
||||
graphics.line(x1 + strokeWidth / 2, barYPosition),
|
||||
graphics.move(m, barYPosition) +
|
||||
graphics.line(m, barYPosition + barHeight) +
|
||||
graphics.line(x2, barYPosition + barHeight) +
|
||||
graphics.line(x2, barYPosition + barHeight / 2) +
|
||||
graphics.line(l2, barYPosition + barHeight / 2) +
|
||||
graphics.line(l2, barYPosition + barHeight - barHeight / 4) +
|
||||
graphics.line(l2, barYPosition + barHeight / 4) +
|
||||
graphics.line(l2, barYPosition + barHeight / 2) +
|
||||
graphics.line(x2, barYPosition + barHeight / 2) +
|
||||
graphics.line(x2, barYPosition) +
|
||||
graphics.line(m, barYPosition) +
|
||||
'z'
|
||||
]
|
||||
|
||||
pathFrom = pathFrom + graphics.move(x1, barYPosition)
|
||||
|
||||
if (!w.globals.isXNumeric) {
|
||||
y = y + yDivision
|
||||
}
|
||||
|
||||
return {
|
||||
pathTo,
|
||||
pathFrom,
|
||||
x: x2,
|
||||
y,
|
||||
barYPosition,
|
||||
color
|
||||
}
|
||||
}
|
||||
getOHLCValue(i, j) {
|
||||
const w = this.w
|
||||
|
||||
return {
|
||||
o: this.isBoxPlot
|
||||
? w.globals.seriesCandleH[i][j]
|
||||
: w.globals.seriesCandleO[i][j],
|
||||
h: this.isBoxPlot
|
||||
? w.globals.seriesCandleO[i][j]
|
||||
: w.globals.seriesCandleH[i][j],
|
||||
m: w.globals.seriesCandleM[i][j],
|
||||
l: this.isBoxPlot
|
||||
? w.globals.seriesCandleC[i][j]
|
||||
: w.globals.seriesCandleL[i][j],
|
||||
c: this.isBoxPlot
|
||||
? w.globals.seriesCandleL[i][j]
|
||||
: w.globals.seriesCandleC[i][j]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default BoxCandleStick
|
||||
@@ -0,0 +1,241 @@
|
||||
import Animations from '../modules/Animations'
|
||||
import Graphics from '../modules/Graphics'
|
||||
import Fill from '../modules/Fill'
|
||||
import Utils from '../utils/Utils'
|
||||
import Helpers from './common/treemap/Helpers'
|
||||
import Filters from '../modules/Filters'
|
||||
|
||||
/**
|
||||
* ApexCharts HeatMap Class.
|
||||
* @module HeatMap
|
||||
**/
|
||||
|
||||
export default class HeatMap {
|
||||
constructor(ctx, xyRatios) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
|
||||
this.xRatio = xyRatios.xRatio
|
||||
this.yRatio = xyRatios.yRatio
|
||||
|
||||
this.dynamicAnim = this.w.config.chart.animations.dynamicAnimation
|
||||
|
||||
this.helpers = new Helpers(ctx)
|
||||
this.rectRadius = this.w.config.plotOptions.heatmap.radius
|
||||
this.strokeWidth = this.w.config.stroke.show
|
||||
? this.w.config.stroke.width
|
||||
: 0
|
||||
}
|
||||
|
||||
draw(series) {
|
||||
let w = this.w
|
||||
const graphics = new Graphics(this.ctx)
|
||||
|
||||
let ret = graphics.group({
|
||||
class: 'apexcharts-heatmap'
|
||||
})
|
||||
|
||||
ret.attr('clip-path', `url(#gridRectMask${w.globals.cuid})`)
|
||||
|
||||
// width divided into equal parts
|
||||
let xDivision = w.globals.gridWidth / w.globals.dataPoints
|
||||
let yDivision = w.globals.gridHeight / w.globals.series.length
|
||||
|
||||
let y1 = 0
|
||||
let rev = false
|
||||
|
||||
this.negRange = this.helpers.checkColorRange()
|
||||
|
||||
let heatSeries = series.slice()
|
||||
if (w.config.yaxis[0].reversed) {
|
||||
rev = true
|
||||
heatSeries.reverse()
|
||||
}
|
||||
|
||||
for (
|
||||
let i = rev ? 0 : heatSeries.length - 1;
|
||||
rev ? i < heatSeries.length : i >= 0;
|
||||
rev ? i++ : i--
|
||||
) {
|
||||
// el to which series will be drawn
|
||||
let elSeries = graphics.group({
|
||||
class: `apexcharts-series apexcharts-heatmap-series`,
|
||||
seriesName: Utils.escapeString(w.globals.seriesNames[i]),
|
||||
rel: i + 1,
|
||||
'data:realIndex': i
|
||||
})
|
||||
this.ctx.series.addCollapsedClassToSeries(elSeries, i)
|
||||
|
||||
if (w.config.chart.dropShadow.enabled) {
|
||||
const shadow = w.config.chart.dropShadow
|
||||
const filters = new Filters(this.ctx)
|
||||
filters.dropShadow(elSeries, shadow, i)
|
||||
}
|
||||
|
||||
let x1 = 0
|
||||
let shadeIntensity = w.config.plotOptions.heatmap.shadeIntensity
|
||||
|
||||
for (let j = 0; j < heatSeries[i].length; j++) {
|
||||
let heatColor = this.helpers.getShadeColor(
|
||||
w.config.chart.type,
|
||||
i,
|
||||
j,
|
||||
this.negRange
|
||||
)
|
||||
let color = heatColor.color
|
||||
let heatColorProps = heatColor.colorProps
|
||||
|
||||
if (w.config.fill.type === 'image') {
|
||||
const fill = new Fill(this.ctx)
|
||||
|
||||
color = fill.fillPath({
|
||||
seriesNumber: i,
|
||||
dataPointIndex: j,
|
||||
opacity: w.globals.hasNegs
|
||||
? heatColorProps.percent < 0
|
||||
? 1 - (1 + heatColorProps.percent / 100)
|
||||
: shadeIntensity + heatColorProps.percent / 100
|
||||
: heatColorProps.percent / 100,
|
||||
patternID: Utils.randomId(),
|
||||
width: w.config.fill.image.width
|
||||
? w.config.fill.image.width
|
||||
: xDivision,
|
||||
height: w.config.fill.image.height
|
||||
? w.config.fill.image.height
|
||||
: yDivision
|
||||
})
|
||||
}
|
||||
|
||||
let radius = this.rectRadius
|
||||
|
||||
let rect = graphics.drawRect(x1, y1, xDivision, yDivision, radius)
|
||||
rect.attr({
|
||||
cx: x1,
|
||||
cy: y1
|
||||
})
|
||||
|
||||
rect.node.classList.add('apexcharts-heatmap-rect')
|
||||
elSeries.add(rect)
|
||||
|
||||
rect.attr({
|
||||
fill: color,
|
||||
i,
|
||||
index: i,
|
||||
j,
|
||||
val: heatSeries[i][j],
|
||||
'stroke-width': this.strokeWidth,
|
||||
stroke: w.config.plotOptions.heatmap.useFillColorAsStroke
|
||||
? color
|
||||
: w.globals.stroke.colors[0],
|
||||
color
|
||||
})
|
||||
|
||||
this.helpers.addListeners(rect)
|
||||
|
||||
if (w.config.chart.animations.enabled && !w.globals.dataChanged) {
|
||||
let speed = 1
|
||||
if (!w.globals.resized) {
|
||||
speed = w.config.chart.animations.speed
|
||||
}
|
||||
this.animateHeatMap(rect, x1, y1, xDivision, yDivision, speed)
|
||||
}
|
||||
|
||||
if (w.globals.dataChanged) {
|
||||
let speed = 1
|
||||
if (this.dynamicAnim.enabled && w.globals.shouldAnimate) {
|
||||
speed = this.dynamicAnim.speed
|
||||
|
||||
let colorFrom =
|
||||
w.globals.previousPaths[i] &&
|
||||
w.globals.previousPaths[i][j] &&
|
||||
w.globals.previousPaths[i][j].color
|
||||
|
||||
if (!colorFrom) colorFrom = 'rgba(255, 255, 255, 0)'
|
||||
|
||||
this.animateHeatColor(
|
||||
rect,
|
||||
Utils.isColorHex(colorFrom)
|
||||
? colorFrom
|
||||
: Utils.rgb2hex(colorFrom),
|
||||
Utils.isColorHex(color) ? color : Utils.rgb2hex(color),
|
||||
speed
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let formatter = w.config.dataLabels.formatter
|
||||
let formattedText = formatter(w.globals.series[i][j], {
|
||||
value: w.globals.series[i][j],
|
||||
seriesIndex: i,
|
||||
dataPointIndex: j,
|
||||
w
|
||||
})
|
||||
|
||||
let dataLabels = this.helpers.calculateDataLabels({
|
||||
text: formattedText,
|
||||
x: x1 + xDivision / 2,
|
||||
y: y1 + yDivision / 2,
|
||||
i,
|
||||
j,
|
||||
colorProps: heatColorProps,
|
||||
series: heatSeries
|
||||
})
|
||||
if (dataLabels !== null) {
|
||||
elSeries.add(dataLabels)
|
||||
}
|
||||
|
||||
x1 = x1 + xDivision
|
||||
}
|
||||
|
||||
y1 = y1 + yDivision
|
||||
|
||||
ret.add(elSeries)
|
||||
}
|
||||
|
||||
// adjust yaxis labels for heatmap
|
||||
let yAxisScale = w.globals.yAxisScale[0].result.slice()
|
||||
if (w.config.yaxis[0].reversed) {
|
||||
yAxisScale.unshift('')
|
||||
} else {
|
||||
yAxisScale.push('')
|
||||
}
|
||||
w.globals.yAxisScale[0].result = yAxisScale
|
||||
let divisor = w.globals.gridHeight / w.globals.series.length
|
||||
w.config.yaxis[0].labels.offsetY = -(divisor / 2)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
animateHeatMap(el, x, y, width, height, speed) {
|
||||
const animations = new Animations(this.ctx)
|
||||
animations.animateRect(
|
||||
el,
|
||||
{
|
||||
x: x + width / 2,
|
||||
y: y + height / 2,
|
||||
width: 0,
|
||||
height: 0
|
||||
},
|
||||
{
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height
|
||||
},
|
||||
speed,
|
||||
() => {
|
||||
animations.animationCompleted(el)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
animateHeatColor(el, colorFrom, colorTo, speed) {
|
||||
el.attr({
|
||||
fill: colorFrom
|
||||
})
|
||||
.animate(speed)
|
||||
.attr({
|
||||
fill: colorTo
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,728 @@
|
||||
import CoreUtils from '../modules/CoreUtils'
|
||||
import Graphics from '../modules/Graphics'
|
||||
import Fill from '../modules/Fill'
|
||||
import DataLabels from '../modules/DataLabels'
|
||||
import Markers from '../modules/Markers'
|
||||
import Scatter from './Scatter'
|
||||
import Utils from '../utils/Utils'
|
||||
import Helpers from './common/line/Helpers'
|
||||
|
||||
/**
|
||||
* ApexCharts Line Class responsible for drawing Line / Area Charts.
|
||||
* This class is also responsible for generating values for Bubble/Scatter charts, so need to rename it to Axis Charts to avoid confusions
|
||||
* @module Line
|
||||
**/
|
||||
|
||||
class Line {
|
||||
constructor(ctx, xyRatios, isPointsChart) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
|
||||
this.xyRatios = xyRatios
|
||||
|
||||
this.pointsChart =
|
||||
!(
|
||||
this.w.config.chart.type !== 'bubble' &&
|
||||
this.w.config.chart.type !== 'scatter'
|
||||
) || isPointsChart
|
||||
|
||||
this.scatter = new Scatter(this.ctx)
|
||||
|
||||
this.noNegatives = this.w.globals.minX === Number.MAX_VALUE
|
||||
|
||||
this.lineHelpers = new Helpers(this)
|
||||
this.markers = new Markers(this.ctx)
|
||||
|
||||
this.prevSeriesY = []
|
||||
this.categoryAxisCorrection = 0
|
||||
this.yaxisIndex = 0
|
||||
}
|
||||
|
||||
draw(series, ptype, seriesIndex) {
|
||||
let w = this.w
|
||||
let graphics = new Graphics(this.ctx)
|
||||
let type = w.globals.comboCharts ? ptype : w.config.chart.type
|
||||
let ret = graphics.group({
|
||||
class: `apexcharts-${type}-series apexcharts-plot-series`
|
||||
})
|
||||
|
||||
const coreUtils = new CoreUtils(this.ctx, w)
|
||||
this.yRatio = this.xyRatios.yRatio
|
||||
this.zRatio = this.xyRatios.zRatio
|
||||
this.xRatio = this.xyRatios.xRatio
|
||||
this.baseLineY = this.xyRatios.baseLineY
|
||||
|
||||
series = coreUtils.getLogSeries(series)
|
||||
this.yRatio = coreUtils.getLogYRatios(this.yRatio)
|
||||
|
||||
// push all series in an array, so we can draw in reverse order (for stacked charts)
|
||||
let allSeries = []
|
||||
|
||||
for (let i = 0; i < series.length; i++) {
|
||||
series = this.lineHelpers.sameValueSeriesFix(i, series)
|
||||
|
||||
let realIndex = w.globals.comboCharts ? seriesIndex[i] : i
|
||||
|
||||
this._initSerieVariables(series, i, realIndex)
|
||||
|
||||
let yArrj = [] // hold y values of current iterating series
|
||||
let xArrj = [] // hold x values of current iterating series
|
||||
|
||||
let x = w.globals.padHorizontal + this.categoryAxisCorrection
|
||||
let y = 1
|
||||
|
||||
let linePaths = []
|
||||
let areaPaths = []
|
||||
|
||||
this.ctx.series.addCollapsedClassToSeries(this.elSeries, realIndex)
|
||||
|
||||
if (w.globals.isXNumeric && w.globals.seriesX.length > 0) {
|
||||
x = (w.globals.seriesX[realIndex][0] - w.globals.minX) / this.xRatio
|
||||
}
|
||||
|
||||
xArrj.push(x)
|
||||
|
||||
let pX = x
|
||||
let pY
|
||||
let prevX = pX
|
||||
let prevY = this.zeroY
|
||||
let lineYPosition = 0
|
||||
|
||||
// the first value in the current series is not null or undefined
|
||||
let firstPrevY = this.lineHelpers.determineFirstPrevY({
|
||||
i,
|
||||
series,
|
||||
prevY,
|
||||
lineYPosition
|
||||
})
|
||||
prevY = firstPrevY.prevY
|
||||
|
||||
yArrj.push(prevY)
|
||||
pY = prevY
|
||||
|
||||
let pathsFrom = this._calculatePathsFrom({
|
||||
series,
|
||||
i,
|
||||
realIndex,
|
||||
prevX,
|
||||
prevY
|
||||
})
|
||||
|
||||
let paths = this._iterateOverDataPoints({
|
||||
series,
|
||||
realIndex,
|
||||
i,
|
||||
x,
|
||||
y,
|
||||
pX,
|
||||
pY,
|
||||
pathsFrom,
|
||||
linePaths,
|
||||
areaPaths,
|
||||
seriesIndex,
|
||||
lineYPosition,
|
||||
xArrj,
|
||||
yArrj
|
||||
})
|
||||
|
||||
this._handlePaths({ type, realIndex, i, paths })
|
||||
|
||||
this.elSeries.add(this.elPointsMain)
|
||||
this.elSeries.add(this.elDataLabelsWrap)
|
||||
|
||||
allSeries.push(this.elSeries)
|
||||
}
|
||||
|
||||
if (w.config.chart.stacked) {
|
||||
for (let s = allSeries.length; s > 0; s--) {
|
||||
ret.add(allSeries[s - 1])
|
||||
}
|
||||
} else {
|
||||
for (let s = 0; s < allSeries.length; s++) {
|
||||
ret.add(allSeries[s])
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
_initSerieVariables(series, i, realIndex) {
|
||||
const w = this.w
|
||||
const graphics = new Graphics(this.ctx)
|
||||
|
||||
// width divided into equal parts
|
||||
this.xDivision =
|
||||
w.globals.gridWidth /
|
||||
(w.globals.dataPoints - (w.config.xaxis.tickPlacement === 'on' ? 1 : 0))
|
||||
|
||||
this.strokeWidth = Array.isArray(w.config.stroke.width)
|
||||
? w.config.stroke.width[realIndex]
|
||||
: w.config.stroke.width
|
||||
|
||||
if (this.yRatio.length > 1) {
|
||||
this.yaxisIndex = realIndex
|
||||
}
|
||||
|
||||
this.isReversed =
|
||||
w.config.yaxis[this.yaxisIndex] &&
|
||||
w.config.yaxis[this.yaxisIndex].reversed
|
||||
|
||||
// zeroY is the 0 value in y series which can be used in negative charts
|
||||
this.zeroY =
|
||||
w.globals.gridHeight -
|
||||
this.baseLineY[this.yaxisIndex] -
|
||||
(this.isReversed ? w.globals.gridHeight : 0) +
|
||||
(this.isReversed ? this.baseLineY[this.yaxisIndex] * 2 : 0)
|
||||
|
||||
this.areaBottomY = this.zeroY
|
||||
if (
|
||||
this.zeroY > w.globals.gridHeight ||
|
||||
w.config.plotOptions.area.fillTo === 'end'
|
||||
) {
|
||||
this.areaBottomY = w.globals.gridHeight
|
||||
}
|
||||
|
||||
this.categoryAxisCorrection = this.xDivision / 2
|
||||
|
||||
// el to which series will be drawn
|
||||
this.elSeries = graphics.group({
|
||||
class: `apexcharts-series`,
|
||||
seriesName: Utils.escapeString(w.globals.seriesNames[realIndex])
|
||||
})
|
||||
|
||||
// points
|
||||
this.elPointsMain = graphics.group({
|
||||
class: 'apexcharts-series-markers-wrap',
|
||||
'data:realIndex': realIndex
|
||||
})
|
||||
|
||||
// eldatalabels
|
||||
this.elDataLabelsWrap = graphics.group({
|
||||
class: 'apexcharts-datalabels',
|
||||
'data:realIndex': realIndex
|
||||
})
|
||||
|
||||
let longestSeries = series[i].length === w.globals.dataPoints
|
||||
this.elSeries.attr({
|
||||
'data:longestSeries': longestSeries,
|
||||
rel: i + 1,
|
||||
'data:realIndex': realIndex
|
||||
})
|
||||
|
||||
this.appendPathFrom = true
|
||||
}
|
||||
|
||||
_calculatePathsFrom({ series, i, realIndex, prevX, prevY }) {
|
||||
const w = this.w
|
||||
const graphics = new Graphics(this.ctx)
|
||||
let linePath, areaPath, pathFromLine, pathFromArea
|
||||
|
||||
if (series[i][0] === null) {
|
||||
// when the first value itself is null, we need to move the pointer to a location where a null value is not found
|
||||
for (let s = 0; s < series[i].length; s++) {
|
||||
if (series[i][s] !== null) {
|
||||
prevX = this.xDivision * s
|
||||
prevY = this.zeroY - series[i][s] / this.yRatio[this.yaxisIndex]
|
||||
linePath = graphics.move(prevX, prevY)
|
||||
areaPath = graphics.move(prevX, this.areaBottomY)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
linePath = graphics.move(prevX, prevY)
|
||||
areaPath =
|
||||
graphics.move(prevX, this.areaBottomY) + graphics.line(prevX, prevY)
|
||||
}
|
||||
|
||||
pathFromLine = graphics.move(-1, this.zeroY) + graphics.line(-1, this.zeroY)
|
||||
pathFromArea = graphics.move(-1, this.zeroY) + graphics.line(-1, this.zeroY)
|
||||
|
||||
if (w.globals.previousPaths.length > 0) {
|
||||
const pathFrom = this.lineHelpers.checkPreviousPaths({
|
||||
pathFromLine,
|
||||
pathFromArea,
|
||||
realIndex
|
||||
})
|
||||
pathFromLine = pathFrom.pathFromLine
|
||||
pathFromArea = pathFrom.pathFromArea
|
||||
}
|
||||
|
||||
return {
|
||||
prevX,
|
||||
prevY,
|
||||
linePath,
|
||||
areaPath,
|
||||
pathFromLine,
|
||||
pathFromArea
|
||||
}
|
||||
}
|
||||
|
||||
_handlePaths({ type, realIndex, i, paths }) {
|
||||
const w = this.w
|
||||
const graphics = new Graphics(this.ctx)
|
||||
const fill = new Fill(this.ctx)
|
||||
|
||||
// push all current y values array to main PrevY Array
|
||||
this.prevSeriesY.push(paths.yArrj)
|
||||
|
||||
// push all x val arrays into main xArr
|
||||
w.globals.seriesXvalues[realIndex] = paths.xArrj
|
||||
w.globals.seriesYvalues[realIndex] = paths.yArrj
|
||||
|
||||
const forecast = w.config.forecastDataPoints
|
||||
if (forecast.count > 0) {
|
||||
const forecastCutoff =
|
||||
w.globals.seriesXvalues[realIndex][
|
||||
w.globals.seriesXvalues[realIndex].length - forecast.count - 1
|
||||
]
|
||||
const elForecastMask = graphics.drawRect(
|
||||
forecastCutoff,
|
||||
0,
|
||||
w.globals.gridWidth,
|
||||
w.globals.gridHeight,
|
||||
0
|
||||
)
|
||||
w.globals.dom.elForecastMask.appendChild(elForecastMask.node)
|
||||
|
||||
const elNonForecastMask = graphics.drawRect(
|
||||
0,
|
||||
0,
|
||||
forecastCutoff,
|
||||
w.globals.gridHeight,
|
||||
0
|
||||
)
|
||||
w.globals.dom.elNonForecastMask.appendChild(elNonForecastMask.node)
|
||||
}
|
||||
|
||||
// these elements will be shown after area path animation completes
|
||||
if (!this.pointsChart) {
|
||||
w.globals.delayedElements.push({
|
||||
el: this.elPointsMain.node,
|
||||
index: realIndex
|
||||
})
|
||||
}
|
||||
|
||||
const defaultRenderedPathOptions = {
|
||||
i,
|
||||
realIndex,
|
||||
animationDelay: i,
|
||||
initialSpeed: w.config.chart.animations.speed,
|
||||
dataChangeSpeed: w.config.chart.animations.dynamicAnimation.speed,
|
||||
className: `apexcharts-${type}`
|
||||
}
|
||||
|
||||
if (type === 'area') {
|
||||
let pathFill = fill.fillPath({
|
||||
seriesNumber: realIndex
|
||||
})
|
||||
|
||||
for (let p = 0; p < paths.areaPaths.length; p++) {
|
||||
let renderedPath = graphics.renderPaths({
|
||||
...defaultRenderedPathOptions,
|
||||
pathFrom: paths.pathFromArea,
|
||||
pathTo: paths.areaPaths[p],
|
||||
stroke: 'none',
|
||||
strokeWidth: 0,
|
||||
strokeLineCap: null,
|
||||
fill: pathFill
|
||||
})
|
||||
|
||||
this.elSeries.add(renderedPath)
|
||||
}
|
||||
}
|
||||
|
||||
if (w.config.stroke.show && !this.pointsChart) {
|
||||
let lineFill = null
|
||||
if (type === 'line') {
|
||||
lineFill = fill.fillPath({
|
||||
seriesNumber: realIndex,
|
||||
i
|
||||
})
|
||||
} else {
|
||||
if (w.config.stroke.fill.type === 'solid') {
|
||||
lineFill = w.globals.stroke.colors[realIndex]
|
||||
} else {
|
||||
const prevFill = w.config.fill
|
||||
w.config.fill = w.config.stroke.fill
|
||||
|
||||
lineFill = fill.fillPath({
|
||||
seriesNumber: realIndex,
|
||||
i
|
||||
})
|
||||
w.config.fill = prevFill
|
||||
}
|
||||
}
|
||||
|
||||
for (let p = 0; p < paths.linePaths.length; p++) {
|
||||
const linePathCommonOpts = {
|
||||
...defaultRenderedPathOptions,
|
||||
pathFrom: paths.pathFromLine,
|
||||
pathTo: paths.linePaths[p],
|
||||
stroke: lineFill,
|
||||
strokeWidth: this.strokeWidth,
|
||||
strokeLineCap: w.config.stroke.lineCap,
|
||||
fill: 'none'
|
||||
}
|
||||
let renderedPath = graphics.renderPaths(linePathCommonOpts)
|
||||
this.elSeries.add(renderedPath)
|
||||
|
||||
if (forecast.count > 0) {
|
||||
let renderedForecastPath = graphics.renderPaths(linePathCommonOpts)
|
||||
|
||||
renderedForecastPath.node.setAttribute(
|
||||
'stroke-dasharray',
|
||||
forecast.dashArray
|
||||
)
|
||||
|
||||
if (forecast.strokeWidth) {
|
||||
renderedForecastPath.node.setAttribute(
|
||||
'stroke-width',
|
||||
forecast.strokeWidth
|
||||
)
|
||||
}
|
||||
|
||||
this.elSeries.add(renderedForecastPath)
|
||||
renderedForecastPath.attr(
|
||||
'clip-path',
|
||||
`url(#forecastMask${w.globals.cuid})`
|
||||
)
|
||||
renderedPath.attr(
|
||||
'clip-path',
|
||||
`url(#nonForecastMask${w.globals.cuid})`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_iterateOverDataPoints({
|
||||
series,
|
||||
realIndex,
|
||||
i,
|
||||
x,
|
||||
y,
|
||||
pX,
|
||||
pY,
|
||||
pathsFrom,
|
||||
linePaths,
|
||||
areaPaths,
|
||||
seriesIndex,
|
||||
lineYPosition,
|
||||
xArrj,
|
||||
yArrj
|
||||
}) {
|
||||
const w = this.w
|
||||
let graphics = new Graphics(this.ctx)
|
||||
let yRatio = this.yRatio
|
||||
let { prevY, linePath, areaPath, pathFromLine, pathFromArea } = pathsFrom
|
||||
|
||||
const minY = Utils.isNumber(w.globals.minYArr[realIndex])
|
||||
? w.globals.minYArr[realIndex]
|
||||
: w.globals.minY
|
||||
|
||||
const iterations =
|
||||
w.globals.dataPoints > 1 ? w.globals.dataPoints - 1 : w.globals.dataPoints
|
||||
|
||||
for (let j = 0; j < iterations; j++) {
|
||||
const isNull =
|
||||
typeof series[i][j + 1] === 'undefined' || series[i][j + 1] === null
|
||||
|
||||
if (w.globals.isXNumeric) {
|
||||
let sX = w.globals.seriesX[realIndex][j + 1]
|
||||
if (typeof w.globals.seriesX[realIndex][j + 1] === 'undefined') {
|
||||
/* fix #374 */
|
||||
sX = w.globals.seriesX[realIndex][iterations - 1]
|
||||
}
|
||||
x = (sX - w.globals.minX) / this.xRatio
|
||||
} else {
|
||||
x = x + this.xDivision
|
||||
}
|
||||
|
||||
if (w.config.chart.stacked) {
|
||||
if (
|
||||
i > 0 &&
|
||||
w.globals.collapsedSeries.length < w.config.series.length - 1
|
||||
) {
|
||||
// a collapsed series in a stacked bar chart may provide wrong result for the next series, hence find the prevIndex of prev series which is not collapsed - fixes apexcharts.js#1372
|
||||
const prevIndex = (pi) => {
|
||||
let pii = pi
|
||||
for (let cpi = 0; cpi < w.globals.series.length; cpi++) {
|
||||
if (w.globals.collapsedSeriesIndices.indexOf(pi) > -1) {
|
||||
pii--
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return pii >= 0 ? pii : 0
|
||||
}
|
||||
lineYPosition = this.prevSeriesY[prevIndex(i - 1)][j + 1]
|
||||
} else {
|
||||
// the first series will not have prevY values
|
||||
lineYPosition = this.zeroY
|
||||
}
|
||||
} else {
|
||||
lineYPosition = this.zeroY
|
||||
}
|
||||
|
||||
if (isNull) {
|
||||
y =
|
||||
lineYPosition -
|
||||
minY / yRatio[this.yaxisIndex] +
|
||||
(this.isReversed ? minY / yRatio[this.yaxisIndex] : 0) * 2
|
||||
} else {
|
||||
y =
|
||||
lineYPosition -
|
||||
series[i][j + 1] / yRatio[this.yaxisIndex] +
|
||||
(this.isReversed ? series[i][j + 1] / yRatio[this.yaxisIndex] : 0) * 2
|
||||
}
|
||||
|
||||
// push current X
|
||||
xArrj.push(x)
|
||||
|
||||
// push current Y that will be used as next series's bottom position
|
||||
yArrj.push(y)
|
||||
|
||||
let pointsPos = this.lineHelpers.calculatePoints({
|
||||
series,
|
||||
x,
|
||||
y,
|
||||
realIndex,
|
||||
i,
|
||||
j,
|
||||
prevY
|
||||
})
|
||||
|
||||
let calculatedPaths = this._createPaths({
|
||||
series,
|
||||
i,
|
||||
realIndex,
|
||||
j,
|
||||
x,
|
||||
y,
|
||||
pX,
|
||||
pY,
|
||||
linePath,
|
||||
areaPath,
|
||||
linePaths,
|
||||
areaPaths,
|
||||
seriesIndex
|
||||
})
|
||||
|
||||
areaPaths = calculatedPaths.areaPaths
|
||||
linePaths = calculatedPaths.linePaths
|
||||
pX = calculatedPaths.pX
|
||||
pY = calculatedPaths.pY
|
||||
areaPath = calculatedPaths.areaPath
|
||||
linePath = calculatedPaths.linePath
|
||||
|
||||
if (this.appendPathFrom) {
|
||||
pathFromLine = pathFromLine + graphics.line(x, this.zeroY)
|
||||
pathFromArea = pathFromArea + graphics.line(x, this.zeroY)
|
||||
}
|
||||
|
||||
this.handleNullDataPoints(series, pointsPos, i, j, realIndex)
|
||||
|
||||
this._handleMarkersAndLabels({
|
||||
pointsPos,
|
||||
series,
|
||||
x,
|
||||
y,
|
||||
prevY,
|
||||
i,
|
||||
j,
|
||||
realIndex
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
yArrj,
|
||||
xArrj,
|
||||
pathFromArea,
|
||||
areaPaths,
|
||||
pathFromLine,
|
||||
linePaths
|
||||
}
|
||||
}
|
||||
|
||||
_handleMarkersAndLabels({ pointsPos, series, x, y, prevY, i, j, realIndex }) {
|
||||
const w = this.w
|
||||
let dataLabels = new DataLabels(this.ctx)
|
||||
|
||||
if (!this.pointsChart) {
|
||||
if (w.globals.series[i].length > 1) {
|
||||
this.elPointsMain.node.classList.add('apexcharts-element-hidden')
|
||||
}
|
||||
|
||||
let elPointsWrap = this.markers.plotChartMarkers(
|
||||
pointsPos,
|
||||
realIndex,
|
||||
j + 1
|
||||
)
|
||||
if (elPointsWrap !== null) {
|
||||
this.elPointsMain.add(elPointsWrap)
|
||||
}
|
||||
} else {
|
||||
// scatter / bubble chart points creation
|
||||
this.scatter.draw(this.elSeries, j, {
|
||||
realIndex,
|
||||
pointsPos,
|
||||
zRatio: this.zRatio,
|
||||
elParent: this.elPointsMain
|
||||
})
|
||||
}
|
||||
|
||||
let drawnLabels = dataLabels.drawDataLabel(
|
||||
pointsPos,
|
||||
realIndex,
|
||||
j + 1,
|
||||
null
|
||||
)
|
||||
if (drawnLabels !== null) {
|
||||
this.elDataLabelsWrap.add(drawnLabels)
|
||||
}
|
||||
}
|
||||
|
||||
_createPaths({
|
||||
series,
|
||||
i,
|
||||
realIndex,
|
||||
j,
|
||||
x,
|
||||
y,
|
||||
pX,
|
||||
pY,
|
||||
linePath,
|
||||
areaPath,
|
||||
linePaths,
|
||||
areaPaths,
|
||||
seriesIndex
|
||||
}) {
|
||||
let w = this.w
|
||||
let graphics = new Graphics(this.ctx)
|
||||
|
||||
let curve = w.config.stroke.curve
|
||||
const areaBottomY = this.areaBottomY
|
||||
|
||||
if (Array.isArray(w.config.stroke.curve)) {
|
||||
if (Array.isArray(seriesIndex)) {
|
||||
curve = w.config.stroke.curve[seriesIndex[i]]
|
||||
} else {
|
||||
curve = w.config.stroke.curve[i]
|
||||
}
|
||||
}
|
||||
|
||||
// logic of smooth curve derived from chartist
|
||||
// CREDITS: https://gionkunz.github.io/chartist-js/
|
||||
if (curve === 'smooth') {
|
||||
let length = (x - pX) * 0.35
|
||||
if (w.globals.hasNullValues) {
|
||||
if (series[i][j] !== null) {
|
||||
if (series[i][j + 1] !== null) {
|
||||
linePath =
|
||||
graphics.move(pX, pY) +
|
||||
graphics.curve(pX + length, pY, x - length, y, x + 1, y)
|
||||
areaPath =
|
||||
graphics.move(pX + 1, pY) +
|
||||
graphics.curve(pX + length, pY, x - length, y, x + 1, y) +
|
||||
graphics.line(x, areaBottomY) +
|
||||
graphics.line(pX, areaBottomY) +
|
||||
'z'
|
||||
} else {
|
||||
linePath = graphics.move(pX, pY)
|
||||
areaPath = graphics.move(pX, pY) + 'z'
|
||||
}
|
||||
}
|
||||
|
||||
linePaths.push(linePath)
|
||||
areaPaths.push(areaPath)
|
||||
} else {
|
||||
linePath =
|
||||
linePath + graphics.curve(pX + length, pY, x - length, y, x, y)
|
||||
areaPath =
|
||||
areaPath + graphics.curve(pX + length, pY, x - length, y, x, y)
|
||||
}
|
||||
|
||||
pX = x
|
||||
pY = y
|
||||
|
||||
if (j === series[i].length - 2) {
|
||||
// last loop, close path
|
||||
areaPath =
|
||||
areaPath +
|
||||
graphics.curve(pX, pY, x, y, x, areaBottomY) +
|
||||
graphics.move(x, y) +
|
||||
'z'
|
||||
if (!w.globals.hasNullValues) {
|
||||
linePaths.push(linePath)
|
||||
areaPaths.push(areaPath)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (series[i][j + 1] === null) {
|
||||
linePath = linePath + graphics.move(x, y)
|
||||
|
||||
const numericOrCatX = w.globals.isXNumeric
|
||||
? (w.globals.seriesX[realIndex][j] - w.globals.minX) / this.xRatio
|
||||
: x - this.xDivision
|
||||
areaPath =
|
||||
areaPath +
|
||||
graphics.line(numericOrCatX, areaBottomY) +
|
||||
graphics.move(x, y) +
|
||||
'z'
|
||||
}
|
||||
if (series[i][j] === null) {
|
||||
linePath = linePath + graphics.move(x, y)
|
||||
areaPath = areaPath + graphics.move(x, areaBottomY)
|
||||
}
|
||||
|
||||
if (curve === 'stepline') {
|
||||
linePath =
|
||||
linePath + graphics.line(x, null, 'H') + graphics.line(null, y, 'V')
|
||||
areaPath =
|
||||
areaPath + graphics.line(x, null, 'H') + graphics.line(null, y, 'V')
|
||||
} else if (curve === 'straight') {
|
||||
linePath = linePath + graphics.line(x, y)
|
||||
areaPath = areaPath + graphics.line(x, y)
|
||||
}
|
||||
|
||||
if (j === series[i].length - 2) {
|
||||
// last loop, close path
|
||||
areaPath =
|
||||
areaPath + graphics.line(x, areaBottomY) + graphics.move(x, y) + 'z'
|
||||
linePaths.push(linePath)
|
||||
areaPaths.push(areaPath)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
linePaths,
|
||||
areaPaths,
|
||||
pX,
|
||||
pY,
|
||||
linePath,
|
||||
areaPath
|
||||
}
|
||||
}
|
||||
|
||||
handleNullDataPoints(series, pointsPos, i, j, realIndex) {
|
||||
const w = this.w
|
||||
if (
|
||||
(series[i][j] === null && w.config.markers.showNullDataPoints) ||
|
||||
series[i].length === 1
|
||||
) {
|
||||
// fixes apexcharts.js#1282, #1252
|
||||
let elPointsWrap = this.markers.plotChartMarkers(
|
||||
pointsPos,
|
||||
realIndex,
|
||||
j + 1,
|
||||
this.strokeWidth - w.config.markers.strokeWidth / 2,
|
||||
true
|
||||
)
|
||||
if (elPointsWrap !== null) {
|
||||
this.elPointsMain.add(elPointsWrap)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Line
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,524 @@
|
||||
import Fill from '../modules/Fill'
|
||||
import Graphics from '../modules/Graphics'
|
||||
import Markers from '../modules/Markers'
|
||||
import DataLabels from '../modules/DataLabels'
|
||||
import Filters from '../modules/Filters'
|
||||
import Utils from '../utils/Utils'
|
||||
import Helpers from './common/circle/Helpers'
|
||||
import CoreUtils from '../modules/CoreUtils'
|
||||
|
||||
/**
|
||||
* ApexCharts Radar Class for Spider/Radar Charts.
|
||||
* @module Radar
|
||||
**/
|
||||
|
||||
class Radar {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
|
||||
this.chartType = this.w.config.chart.type
|
||||
|
||||
this.initialAnim = this.w.config.chart.animations.enabled
|
||||
this.dynamicAnim =
|
||||
this.initialAnim &&
|
||||
this.w.config.chart.animations.dynamicAnimation.enabled
|
||||
|
||||
this.animDur = 0
|
||||
|
||||
const w = this.w
|
||||
this.graphics = new Graphics(this.ctx)
|
||||
|
||||
this.lineColorArr =
|
||||
w.globals.stroke.colors !== undefined
|
||||
? w.globals.stroke.colors
|
||||
: w.globals.colors
|
||||
|
||||
this.defaultSize =
|
||||
w.globals.svgHeight < w.globals.svgWidth
|
||||
? w.globals.gridHeight + w.globals.goldenPadding * 1.5
|
||||
: w.globals.gridWidth
|
||||
|
||||
this.isLog = w.config.yaxis[0].logarithmic
|
||||
|
||||
this.coreUtils = new CoreUtils(this.ctx)
|
||||
this.maxValue = this.isLog
|
||||
? this.coreUtils.getLogVal(w.globals.maxY, 0)
|
||||
: w.globals.maxY
|
||||
this.minValue = this.isLog
|
||||
? this.coreUtils.getLogVal(this.w.globals.minY, 0)
|
||||
: w.globals.minY
|
||||
|
||||
this.polygons = w.config.plotOptions.radar.polygons
|
||||
|
||||
this.strokeWidth = w.config.stroke.show ? w.config.stroke.width : 0
|
||||
|
||||
this.size =
|
||||
this.defaultSize / 2.1 - this.strokeWidth - w.config.chart.dropShadow.blur
|
||||
|
||||
if (w.config.xaxis.labels.show) {
|
||||
this.size = this.size - w.globals.xAxisLabelsWidth / 1.75
|
||||
}
|
||||
|
||||
if (w.config.plotOptions.radar.size !== undefined) {
|
||||
this.size = w.config.plotOptions.radar.size
|
||||
}
|
||||
|
||||
this.dataRadiusOfPercent = []
|
||||
this.dataRadius = []
|
||||
this.angleArr = []
|
||||
|
||||
this.yaxisLabelsTextsPos = []
|
||||
}
|
||||
|
||||
draw(series) {
|
||||
let w = this.w
|
||||
const fill = new Fill(this.ctx)
|
||||
|
||||
const allSeries = []
|
||||
const dataLabels = new DataLabels(this.ctx)
|
||||
|
||||
if (series.length) {
|
||||
this.dataPointsLen = series[w.globals.maxValsInArrayIndex].length
|
||||
}
|
||||
this.disAngle = (Math.PI * 2) / this.dataPointsLen
|
||||
|
||||
let halfW = w.globals.gridWidth / 2
|
||||
let halfH = w.globals.gridHeight / 2
|
||||
let translateX = halfW + w.config.plotOptions.radar.offsetX
|
||||
let translateY = halfH + w.config.plotOptions.radar.offsetY
|
||||
|
||||
let ret = this.graphics.group({
|
||||
class: 'apexcharts-radar-series apexcharts-plot-series',
|
||||
transform: `translate(${translateX || 0}, ${translateY || 0})`
|
||||
})
|
||||
|
||||
let dataPointsPos = []
|
||||
let elPointsMain = null
|
||||
let elDataPointsMain = null
|
||||
|
||||
this.yaxisLabels = this.graphics.group({
|
||||
class: 'apexcharts-yaxis'
|
||||
})
|
||||
|
||||
series.forEach((s, i) => {
|
||||
let longestSeries = s.length === w.globals.dataPoints
|
||||
|
||||
// el to which series will be drawn
|
||||
let elSeries = this.graphics.group().attr({
|
||||
class: `apexcharts-series`,
|
||||
'data:longestSeries': longestSeries,
|
||||
seriesName: Utils.escapeString(w.globals.seriesNames[i]),
|
||||
rel: i + 1,
|
||||
'data:realIndex': i
|
||||
})
|
||||
|
||||
this.dataRadiusOfPercent[i] = []
|
||||
this.dataRadius[i] = []
|
||||
this.angleArr[i] = []
|
||||
|
||||
s.forEach((dv, j) => {
|
||||
const range = Math.abs(this.maxValue - this.minValue)
|
||||
dv = dv + Math.abs(this.minValue)
|
||||
|
||||
if (this.isLog) {
|
||||
dv = this.coreUtils.getLogVal(dv, 0)
|
||||
}
|
||||
|
||||
this.dataRadiusOfPercent[i][j] = dv / range
|
||||
|
||||
this.dataRadius[i][j] = this.dataRadiusOfPercent[i][j] * this.size
|
||||
this.angleArr[i][j] = j * this.disAngle
|
||||
})
|
||||
|
||||
dataPointsPos = this.getDataPointsPos(
|
||||
this.dataRadius[i],
|
||||
this.angleArr[i]
|
||||
)
|
||||
const paths = this.createPaths(dataPointsPos, {
|
||||
x: 0,
|
||||
y: 0
|
||||
})
|
||||
|
||||
// points
|
||||
elPointsMain = this.graphics.group({
|
||||
class: 'apexcharts-series-markers-wrap apexcharts-element-hidden'
|
||||
})
|
||||
|
||||
// datapoints
|
||||
elDataPointsMain = this.graphics.group({
|
||||
class: `apexcharts-datalabels`,
|
||||
'data:realIndex': i
|
||||
})
|
||||
|
||||
w.globals.delayedElements.push({
|
||||
el: elPointsMain.node,
|
||||
index: i
|
||||
})
|
||||
|
||||
const defaultRenderedPathOptions = {
|
||||
i,
|
||||
realIndex: i,
|
||||
animationDelay: i,
|
||||
initialSpeed: w.config.chart.animations.speed,
|
||||
dataChangeSpeed: w.config.chart.animations.dynamicAnimation.speed,
|
||||
className: `apexcharts-radar`,
|
||||
shouldClipToGrid: false,
|
||||
bindEventsOnPaths: false,
|
||||
stroke: w.globals.stroke.colors[i],
|
||||
strokeLineCap: w.config.stroke.lineCap
|
||||
}
|
||||
|
||||
let pathFrom = null
|
||||
|
||||
if (w.globals.previousPaths.length > 0) {
|
||||
pathFrom = this.getPreviousPath(i)
|
||||
}
|
||||
|
||||
for (let p = 0; p < paths.linePathsTo.length; p++) {
|
||||
let renderedLinePath = this.graphics.renderPaths({
|
||||
...defaultRenderedPathOptions,
|
||||
pathFrom: pathFrom === null ? paths.linePathsFrom[p] : pathFrom,
|
||||
pathTo: paths.linePathsTo[p],
|
||||
strokeWidth: Array.isArray(this.strokeWidth)
|
||||
? this.strokeWidth[i]
|
||||
: this.strokeWidth,
|
||||
fill: 'none',
|
||||
drawShadow: false
|
||||
})
|
||||
|
||||
elSeries.add(renderedLinePath)
|
||||
|
||||
let pathFill = fill.fillPath({
|
||||
seriesNumber: i
|
||||
})
|
||||
|
||||
let renderedAreaPath = this.graphics.renderPaths({
|
||||
...defaultRenderedPathOptions,
|
||||
pathFrom: pathFrom === null ? paths.areaPathsFrom[p] : pathFrom,
|
||||
pathTo: paths.areaPathsTo[p],
|
||||
strokeWidth: 0,
|
||||
fill: pathFill,
|
||||
drawShadow: false
|
||||
})
|
||||
|
||||
if (w.config.chart.dropShadow.enabled) {
|
||||
const filters = new Filters(this.ctx)
|
||||
|
||||
const shadow = w.config.chart.dropShadow
|
||||
filters.dropShadow(
|
||||
renderedAreaPath,
|
||||
Object.assign({}, shadow, { noUserSpaceOnUse: true }),
|
||||
i
|
||||
)
|
||||
}
|
||||
|
||||
elSeries.add(renderedAreaPath)
|
||||
}
|
||||
|
||||
s.forEach((sj, j) => {
|
||||
let markers = new Markers(this.ctx)
|
||||
|
||||
let opts = markers.getMarkerConfig({
|
||||
cssClass: 'apexcharts-marker',
|
||||
seriesIndex: i,
|
||||
dataPointIndex: j
|
||||
})
|
||||
|
||||
let point = this.graphics.drawMarker(
|
||||
dataPointsPos[j].x,
|
||||
dataPointsPos[j].y,
|
||||
opts
|
||||
)
|
||||
|
||||
point.attr('rel', j)
|
||||
point.attr('j', j)
|
||||
point.attr('index', i)
|
||||
point.node.setAttribute('default-marker-size', opts.pSize)
|
||||
|
||||
let elPointsWrap = this.graphics.group({
|
||||
class: 'apexcharts-series-markers'
|
||||
})
|
||||
|
||||
if (elPointsWrap) {
|
||||
elPointsWrap.add(point)
|
||||
}
|
||||
|
||||
elPointsMain.add(elPointsWrap)
|
||||
|
||||
elSeries.add(elPointsMain)
|
||||
|
||||
const dataLabelsConfig = w.config.dataLabels
|
||||
|
||||
if (dataLabelsConfig.enabled) {
|
||||
let text = dataLabelsConfig.formatter(w.globals.series[i][j], {
|
||||
seriesIndex: i,
|
||||
dataPointIndex: j,
|
||||
w
|
||||
})
|
||||
|
||||
dataLabels.plotDataLabelsText({
|
||||
x: dataPointsPos[j].x,
|
||||
y: dataPointsPos[j].y,
|
||||
text,
|
||||
textAnchor: 'middle',
|
||||
i,
|
||||
j: i,
|
||||
parent: elDataPointsMain,
|
||||
offsetCorrection: false,
|
||||
dataLabelsConfig: {
|
||||
...dataLabelsConfig
|
||||
}
|
||||
})
|
||||
}
|
||||
elSeries.add(elDataPointsMain)
|
||||
})
|
||||
|
||||
allSeries.push(elSeries)
|
||||
})
|
||||
|
||||
this.drawPolygons({
|
||||
parent: ret
|
||||
})
|
||||
|
||||
if (w.config.xaxis.labels.show) {
|
||||
const xaxisTexts = this.drawXAxisTexts()
|
||||
ret.add(xaxisTexts)
|
||||
}
|
||||
|
||||
allSeries.forEach((elS) => {
|
||||
ret.add(elS)
|
||||
})
|
||||
|
||||
ret.add(this.yaxisLabels)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
drawPolygons(opts) {
|
||||
const w = this.w
|
||||
const { parent } = opts
|
||||
const helpers = new Helpers(this.ctx)
|
||||
|
||||
const yaxisTexts = w.globals.yAxisScale[0].result.reverse()
|
||||
const layers = yaxisTexts.length
|
||||
|
||||
let radiusSizes = []
|
||||
let layerDis = this.size / (layers - 1)
|
||||
for (let i = 0; i < layers; i++) {
|
||||
radiusSizes[i] = layerDis * i
|
||||
}
|
||||
radiusSizes.reverse()
|
||||
|
||||
let polygonStrings = []
|
||||
let lines = []
|
||||
|
||||
radiusSizes.forEach((radiusSize, r) => {
|
||||
const polygon = Utils.getPolygonPos(radiusSize, this.dataPointsLen)
|
||||
let string = ''
|
||||
|
||||
polygon.forEach((p, i) => {
|
||||
if (r === 0) {
|
||||
const line = this.graphics.drawLine(
|
||||
p.x,
|
||||
p.y,
|
||||
0,
|
||||
0,
|
||||
Array.isArray(this.polygons.connectorColors)
|
||||
? this.polygons.connectorColors[i]
|
||||
: this.polygons.connectorColors
|
||||
)
|
||||
|
||||
lines.push(line)
|
||||
}
|
||||
|
||||
if (i === 0) {
|
||||
this.yaxisLabelsTextsPos.push({
|
||||
x: p.x,
|
||||
y: p.y
|
||||
})
|
||||
}
|
||||
|
||||
string += p.x + ',' + p.y + ' '
|
||||
})
|
||||
|
||||
polygonStrings.push(string)
|
||||
})
|
||||
|
||||
polygonStrings.forEach((p, i) => {
|
||||
const strokeColors = this.polygons.strokeColors
|
||||
const strokeWidth = this.polygons.strokeWidth
|
||||
const polygon = this.graphics.drawPolygon(
|
||||
p,
|
||||
Array.isArray(strokeColors) ? strokeColors[i] : strokeColors,
|
||||
Array.isArray(strokeWidth) ? strokeWidth[i] : strokeWidth,
|
||||
w.globals.radarPolygons.fill.colors[i]
|
||||
)
|
||||
parent.add(polygon)
|
||||
})
|
||||
|
||||
lines.forEach((l) => {
|
||||
parent.add(l)
|
||||
})
|
||||
|
||||
if (w.config.yaxis[0].show) {
|
||||
this.yaxisLabelsTextsPos.forEach((p, i) => {
|
||||
const yText = helpers.drawYAxisTexts(p.x, p.y, i, yaxisTexts[i])
|
||||
this.yaxisLabels.add(yText)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
drawXAxisTexts() {
|
||||
const w = this.w
|
||||
|
||||
const xaxisLabelsConfig = w.config.xaxis.labels
|
||||
let elXAxisWrap = this.graphics.group({
|
||||
class: 'apexcharts-xaxis'
|
||||
})
|
||||
|
||||
let polygonPos = Utils.getPolygonPos(this.size, this.dataPointsLen)
|
||||
|
||||
w.globals.labels.forEach((label, i) => {
|
||||
let formatter = w.config.xaxis.labels.formatter
|
||||
let dataLabels = new DataLabels(this.ctx)
|
||||
|
||||
if (polygonPos[i]) {
|
||||
let textPos = this.getTextPos(polygonPos[i], this.size)
|
||||
|
||||
let text = formatter(label, {
|
||||
seriesIndex: -1,
|
||||
dataPointIndex: i,
|
||||
w
|
||||
})
|
||||
|
||||
dataLabels.plotDataLabelsText({
|
||||
x: textPos.newX,
|
||||
y: textPos.newY,
|
||||
text,
|
||||
textAnchor: textPos.textAnchor,
|
||||
i,
|
||||
j: i,
|
||||
parent: elXAxisWrap,
|
||||
color:
|
||||
Array.isArray(xaxisLabelsConfig.style.colors) &&
|
||||
xaxisLabelsConfig.style.colors[i]
|
||||
? xaxisLabelsConfig.style.colors[i]
|
||||
: '#a8a8a8',
|
||||
dataLabelsConfig: {
|
||||
textAnchor: textPos.textAnchor,
|
||||
dropShadow: { enabled: false },
|
||||
...xaxisLabelsConfig
|
||||
},
|
||||
offsetCorrection: false
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return elXAxisWrap
|
||||
}
|
||||
|
||||
createPaths(pos, origin) {
|
||||
let linePathsTo = []
|
||||
let linePathsFrom = []
|
||||
let areaPathsTo = []
|
||||
let areaPathsFrom = []
|
||||
|
||||
if (pos.length) {
|
||||
linePathsFrom = [this.graphics.move(origin.x, origin.y)]
|
||||
areaPathsFrom = [this.graphics.move(origin.x, origin.y)]
|
||||
|
||||
let linePathTo = this.graphics.move(pos[0].x, pos[0].y)
|
||||
let areaPathTo = this.graphics.move(pos[0].x, pos[0].y)
|
||||
|
||||
pos.forEach((p, i) => {
|
||||
linePathTo += this.graphics.line(p.x, p.y)
|
||||
areaPathTo += this.graphics.line(p.x, p.y)
|
||||
if (i === pos.length - 1) {
|
||||
linePathTo += 'Z'
|
||||
areaPathTo += 'Z'
|
||||
}
|
||||
})
|
||||
|
||||
linePathsTo.push(linePathTo)
|
||||
areaPathsTo.push(areaPathTo)
|
||||
}
|
||||
|
||||
return {
|
||||
linePathsFrom,
|
||||
linePathsTo,
|
||||
areaPathsFrom,
|
||||
areaPathsTo
|
||||
}
|
||||
}
|
||||
|
||||
getTextPos(pos, polygonSize) {
|
||||
let limit = 10
|
||||
let textAnchor = 'middle'
|
||||
|
||||
let newX = pos.x
|
||||
let newY = pos.y
|
||||
|
||||
if (Math.abs(pos.x) >= limit) {
|
||||
if (pos.x > 0) {
|
||||
textAnchor = 'start'
|
||||
newX += 10
|
||||
} else if (pos.x < 0) {
|
||||
textAnchor = 'end'
|
||||
newX -= 10
|
||||
}
|
||||
} else {
|
||||
textAnchor = 'middle'
|
||||
}
|
||||
if (Math.abs(pos.y) >= polygonSize - limit) {
|
||||
if (pos.y < 0) {
|
||||
newY -= 10
|
||||
} else if (pos.y > 0) {
|
||||
newY += 10
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
textAnchor,
|
||||
newX,
|
||||
newY
|
||||
}
|
||||
}
|
||||
|
||||
getPreviousPath(realIndex) {
|
||||
let w = this.w
|
||||
let pathFrom = null
|
||||
for (let pp = 0; pp < w.globals.previousPaths.length; pp++) {
|
||||
let gpp = w.globals.previousPaths[pp]
|
||||
|
||||
if (
|
||||
gpp.paths.length > 0 &&
|
||||
parseInt(gpp.realIndex, 10) === parseInt(realIndex, 10)
|
||||
) {
|
||||
if (typeof w.globals.previousPaths[pp].paths[0] !== 'undefined') {
|
||||
pathFrom = w.globals.previousPaths[pp].paths[0].d
|
||||
}
|
||||
}
|
||||
}
|
||||
return pathFrom
|
||||
}
|
||||
|
||||
getDataPointsPos(
|
||||
dataRadiusArr,
|
||||
angleArr,
|
||||
dataPointsLen = this.dataPointsLen
|
||||
) {
|
||||
dataRadiusArr = dataRadiusArr || []
|
||||
angleArr = angleArr || []
|
||||
let dataPointsPosArray = []
|
||||
for (let j = 0; j < dataPointsLen; j++) {
|
||||
let curPointPos = {}
|
||||
curPointPos.x = dataRadiusArr[j] * Math.sin(angleArr[j])
|
||||
curPointPos.y = -dataRadiusArr[j] * Math.cos(angleArr[j])
|
||||
dataPointsPosArray.push(curPointPos)
|
||||
}
|
||||
return dataPointsPosArray
|
||||
}
|
||||
}
|
||||
|
||||
export default Radar
|
||||
@@ -0,0 +1,469 @@
|
||||
import Pie from './Pie'
|
||||
import Utils from '../utils/Utils'
|
||||
import Fill from '../modules/Fill'
|
||||
import Graphics from '../modules/Graphics'
|
||||
import Filters from '../modules/Filters'
|
||||
|
||||
/**
|
||||
* ApexCharts Radial Class for drawing Circle / Semi Circle Charts.
|
||||
* @module Radial
|
||||
**/
|
||||
|
||||
class Radial extends Pie {
|
||||
constructor(ctx) {
|
||||
super(ctx)
|
||||
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
this.animBeginArr = [0]
|
||||
this.animDur = 0
|
||||
|
||||
const w = this.w
|
||||
this.startAngle = w.config.plotOptions.radialBar.startAngle
|
||||
this.endAngle = w.config.plotOptions.radialBar.endAngle
|
||||
|
||||
this.totalAngle = Math.abs(
|
||||
w.config.plotOptions.radialBar.endAngle -
|
||||
w.config.plotOptions.radialBar.startAngle
|
||||
)
|
||||
|
||||
this.trackStartAngle = w.config.plotOptions.radialBar.track.startAngle
|
||||
this.trackEndAngle = w.config.plotOptions.radialBar.track.endAngle
|
||||
|
||||
this.donutDataLabels = this.w.config.plotOptions.radialBar.dataLabels
|
||||
this.radialDataLabels = this.donutDataLabels // make a copy for easy reference
|
||||
|
||||
if (!this.trackStartAngle) this.trackStartAngle = this.startAngle
|
||||
if (!this.trackEndAngle) this.trackEndAngle = this.endAngle
|
||||
|
||||
if (this.endAngle === 360) this.endAngle = 359.99
|
||||
|
||||
this.margin = parseInt(w.config.plotOptions.radialBar.track.margin, 10)
|
||||
}
|
||||
|
||||
draw(series) {
|
||||
let w = this.w
|
||||
const graphics = new Graphics(this.ctx)
|
||||
|
||||
let ret = graphics.group({
|
||||
class: 'apexcharts-radialbar'
|
||||
})
|
||||
|
||||
if (w.globals.noData) return ret
|
||||
|
||||
let elSeries = graphics.group()
|
||||
|
||||
let centerY = this.defaultSize / 2
|
||||
let centerX = w.globals.gridWidth / 2
|
||||
|
||||
let size = this.defaultSize / 2.05
|
||||
if (!w.config.chart.sparkline.enabled) {
|
||||
size = size - w.config.stroke.width - w.config.chart.dropShadow.blur
|
||||
}
|
||||
let colorArr = w.globals.fill.colors
|
||||
|
||||
if (w.config.plotOptions.radialBar.track.show) {
|
||||
let elTracks = this.drawTracks({
|
||||
size,
|
||||
centerX,
|
||||
centerY,
|
||||
colorArr,
|
||||
series
|
||||
})
|
||||
elSeries.add(elTracks)
|
||||
}
|
||||
|
||||
let elG = this.drawArcs({
|
||||
size,
|
||||
centerX,
|
||||
centerY,
|
||||
colorArr,
|
||||
series
|
||||
})
|
||||
|
||||
let totalAngle = 360
|
||||
|
||||
if (w.config.plotOptions.radialBar.startAngle < 0) {
|
||||
totalAngle = this.totalAngle
|
||||
}
|
||||
|
||||
let angleRatio = (360 - totalAngle) / 360
|
||||
w.globals.radialSize = size - size * angleRatio
|
||||
|
||||
if (this.radialDataLabels.value.show) {
|
||||
let offset = Math.max(
|
||||
this.radialDataLabels.value.offsetY,
|
||||
this.radialDataLabels.name.offsetY
|
||||
)
|
||||
w.globals.radialSize += offset * angleRatio
|
||||
}
|
||||
|
||||
elSeries.add(elG.g)
|
||||
|
||||
if (w.config.plotOptions.radialBar.hollow.position === 'front') {
|
||||
elG.g.add(elG.elHollow)
|
||||
if (elG.dataLabels) {
|
||||
elG.g.add(elG.dataLabels)
|
||||
}
|
||||
}
|
||||
|
||||
ret.add(elSeries)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
drawTracks(opts) {
|
||||
let w = this.w
|
||||
const graphics = new Graphics(this.ctx)
|
||||
|
||||
let g = graphics.group({
|
||||
class: 'apexcharts-tracks'
|
||||
})
|
||||
|
||||
let filters = new Filters(this.ctx)
|
||||
let fill = new Fill(this.ctx)
|
||||
|
||||
let strokeWidth = this.getStrokeWidth(opts)
|
||||
|
||||
opts.size = opts.size - strokeWidth / 2
|
||||
|
||||
for (let i = 0; i < opts.series.length; i++) {
|
||||
let elRadialBarTrack = graphics.group({
|
||||
class: 'apexcharts-radialbar-track apexcharts-track'
|
||||
})
|
||||
g.add(elRadialBarTrack)
|
||||
|
||||
elRadialBarTrack.attr({
|
||||
rel: i + 1
|
||||
})
|
||||
|
||||
opts.size = opts.size - strokeWidth - this.margin
|
||||
|
||||
const trackConfig = w.config.plotOptions.radialBar.track
|
||||
let pathFill = fill.fillPath({
|
||||
seriesNumber: 0,
|
||||
size: opts.size,
|
||||
fillColors: Array.isArray(trackConfig.background)
|
||||
? trackConfig.background[i]
|
||||
: trackConfig.background,
|
||||
solid: true
|
||||
})
|
||||
|
||||
let startAngle = this.trackStartAngle
|
||||
let endAngle = this.trackEndAngle
|
||||
|
||||
if (Math.abs(endAngle) + Math.abs(startAngle) >= 360)
|
||||
endAngle = 360 - Math.abs(this.startAngle) - 0.1
|
||||
|
||||
let elPath = graphics.drawPath({
|
||||
d: '',
|
||||
stroke: pathFill,
|
||||
strokeWidth:
|
||||
(strokeWidth * parseInt(trackConfig.strokeWidth, 10)) / 100,
|
||||
fill: 'none',
|
||||
strokeOpacity: trackConfig.opacity,
|
||||
classes: 'apexcharts-radialbar-area'
|
||||
})
|
||||
|
||||
if (trackConfig.dropShadow.enabled) {
|
||||
const shadow = trackConfig.dropShadow
|
||||
filters.dropShadow(elPath, shadow)
|
||||
}
|
||||
|
||||
elRadialBarTrack.add(elPath)
|
||||
|
||||
elPath.attr('id', 'apexcharts-radialbarTrack-' + i)
|
||||
|
||||
this.animatePaths(elPath, {
|
||||
centerX: opts.centerX,
|
||||
centerY: opts.centerY,
|
||||
endAngle,
|
||||
startAngle,
|
||||
size: opts.size,
|
||||
i,
|
||||
totalItems: 2,
|
||||
animBeginArr: 0,
|
||||
dur: 0,
|
||||
isTrack: true,
|
||||
easing: w.globals.easing
|
||||
})
|
||||
}
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
drawArcs(opts) {
|
||||
let w = this.w
|
||||
// size, donutSize, centerX, centerY, colorArr, lineColorArr, sectorAngleArr, series
|
||||
|
||||
let graphics = new Graphics(this.ctx)
|
||||
let fill = new Fill(this.ctx)
|
||||
let filters = new Filters(this.ctx)
|
||||
let g = graphics.group()
|
||||
|
||||
let strokeWidth = this.getStrokeWidth(opts)
|
||||
opts.size = opts.size - strokeWidth / 2
|
||||
|
||||
let hollowFillID = w.config.plotOptions.radialBar.hollow.background
|
||||
let hollowSize =
|
||||
opts.size -
|
||||
strokeWidth * opts.series.length -
|
||||
this.margin * opts.series.length -
|
||||
(strokeWidth *
|
||||
parseInt(w.config.plotOptions.radialBar.track.strokeWidth, 10)) /
|
||||
100 /
|
||||
2
|
||||
|
||||
let hollowRadius = hollowSize - w.config.plotOptions.radialBar.hollow.margin
|
||||
|
||||
if (w.config.plotOptions.radialBar.hollow.image !== undefined) {
|
||||
hollowFillID = this.drawHollowImage(opts, g, hollowSize, hollowFillID)
|
||||
}
|
||||
|
||||
let elHollow = this.drawHollow({
|
||||
size: hollowRadius,
|
||||
centerX: opts.centerX,
|
||||
centerY: opts.centerY,
|
||||
fill: hollowFillID ? hollowFillID : 'transparent'
|
||||
})
|
||||
|
||||
if (w.config.plotOptions.radialBar.hollow.dropShadow.enabled) {
|
||||
const shadow = w.config.plotOptions.radialBar.hollow.dropShadow
|
||||
filters.dropShadow(elHollow, shadow)
|
||||
}
|
||||
|
||||
let shown = 1
|
||||
if (!this.radialDataLabels.total.show && w.globals.series.length > 1) {
|
||||
shown = 0
|
||||
}
|
||||
|
||||
let dataLabels = null
|
||||
|
||||
if (this.radialDataLabels.show) {
|
||||
dataLabels = this.renderInnerDataLabels(this.radialDataLabels, {
|
||||
hollowSize,
|
||||
centerX: opts.centerX,
|
||||
centerY: opts.centerY,
|
||||
opacity: shown
|
||||
})
|
||||
}
|
||||
|
||||
if (w.config.plotOptions.radialBar.hollow.position === 'back') {
|
||||
g.add(elHollow)
|
||||
if (dataLabels) {
|
||||
g.add(dataLabels)
|
||||
}
|
||||
}
|
||||
|
||||
let reverseLoop = false
|
||||
if (w.config.plotOptions.radialBar.inverseOrder) {
|
||||
reverseLoop = true
|
||||
}
|
||||
|
||||
for (
|
||||
let i = reverseLoop ? opts.series.length - 1 : 0;
|
||||
reverseLoop ? i >= 0 : i < opts.series.length;
|
||||
reverseLoop ? i-- : i++
|
||||
) {
|
||||
let elRadialBarArc = graphics.group({
|
||||
class: `apexcharts-series apexcharts-radial-series`,
|
||||
seriesName: Utils.escapeString(w.globals.seriesNames[i])
|
||||
})
|
||||
g.add(elRadialBarArc)
|
||||
|
||||
elRadialBarArc.attr({
|
||||
rel: i + 1,
|
||||
'data:realIndex': i
|
||||
})
|
||||
|
||||
this.ctx.series.addCollapsedClassToSeries(elRadialBarArc, i)
|
||||
|
||||
opts.size = opts.size - strokeWidth - this.margin
|
||||
|
||||
let pathFill = fill.fillPath({
|
||||
seriesNumber: i,
|
||||
size: opts.size,
|
||||
value: opts.series[i]
|
||||
})
|
||||
|
||||
let startAngle = this.startAngle
|
||||
let prevStartAngle
|
||||
|
||||
// if data exceeds 100, make it 100
|
||||
const dataValue =
|
||||
Utils.negToZero(opts.series[i] > 100 ? 100 : opts.series[i]) / 100
|
||||
|
||||
let endAngle = Math.round(this.totalAngle * dataValue) + this.startAngle
|
||||
|
||||
let prevEndAngle
|
||||
if (w.globals.dataChanged) {
|
||||
prevStartAngle = this.startAngle
|
||||
prevEndAngle =
|
||||
Math.round(
|
||||
(this.totalAngle * Utils.negToZero(w.globals.previousPaths[i])) /
|
||||
100
|
||||
) + prevStartAngle
|
||||
}
|
||||
|
||||
const currFullAngle = Math.abs(endAngle) + Math.abs(startAngle)
|
||||
if (currFullAngle >= 360) {
|
||||
endAngle = endAngle - 0.01
|
||||
}
|
||||
|
||||
const prevFullAngle = Math.abs(prevEndAngle) + Math.abs(prevStartAngle)
|
||||
if (prevFullAngle >= 360) {
|
||||
prevEndAngle = prevEndAngle - 0.01
|
||||
}
|
||||
|
||||
let angle = endAngle - startAngle
|
||||
|
||||
const dashArray = Array.isArray(w.config.stroke.dashArray)
|
||||
? w.config.stroke.dashArray[i]
|
||||
: w.config.stroke.dashArray
|
||||
|
||||
let elPath = graphics.drawPath({
|
||||
d: '',
|
||||
stroke: pathFill,
|
||||
strokeWidth,
|
||||
fill: 'none',
|
||||
fillOpacity: w.config.fill.opacity,
|
||||
classes: 'apexcharts-radialbar-area apexcharts-radialbar-slice-' + i,
|
||||
strokeDashArray: dashArray
|
||||
})
|
||||
|
||||
Graphics.setAttrs(elPath.node, {
|
||||
'data:angle': angle,
|
||||
'data:value': opts.series[i]
|
||||
})
|
||||
|
||||
if (w.config.chart.dropShadow.enabled) {
|
||||
const shadow = w.config.chart.dropShadow
|
||||
filters.dropShadow(elPath, shadow, i)
|
||||
}
|
||||
filters.setSelectionFilter(elPath, 0, i)
|
||||
|
||||
this.addListeners(elPath, this.radialDataLabels)
|
||||
|
||||
elRadialBarArc.add(elPath)
|
||||
|
||||
elPath.attr({
|
||||
index: 0,
|
||||
j: i
|
||||
})
|
||||
|
||||
let dur = 0
|
||||
if (this.initialAnim && !w.globals.resized && !w.globals.dataChanged) {
|
||||
dur = w.config.chart.animations.speed
|
||||
}
|
||||
|
||||
if (w.globals.dataChanged) {
|
||||
dur = w.config.chart.animations.dynamicAnimation.speed
|
||||
}
|
||||
this.animDur = dur / (opts.series.length * 1.2) + this.animDur
|
||||
this.animBeginArr.push(this.animDur)
|
||||
|
||||
this.animatePaths(elPath, {
|
||||
centerX: opts.centerX,
|
||||
centerY: opts.centerY,
|
||||
endAngle,
|
||||
startAngle,
|
||||
prevEndAngle,
|
||||
prevStartAngle,
|
||||
size: opts.size,
|
||||
i,
|
||||
totalItems: 2,
|
||||
animBeginArr: this.animBeginArr,
|
||||
dur,
|
||||
shouldSetPrevPaths: true,
|
||||
easing: w.globals.easing
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
g,
|
||||
elHollow,
|
||||
dataLabels
|
||||
}
|
||||
}
|
||||
|
||||
drawHollow(opts) {
|
||||
const graphics = new Graphics(this.ctx)
|
||||
|
||||
let circle = graphics.drawCircle(opts.size * 2)
|
||||
|
||||
circle.attr({
|
||||
class: 'apexcharts-radialbar-hollow',
|
||||
cx: opts.centerX,
|
||||
cy: opts.centerY,
|
||||
r: opts.size,
|
||||
fill: opts.fill
|
||||
})
|
||||
|
||||
return circle
|
||||
}
|
||||
|
||||
drawHollowImage(opts, g, hollowSize, hollowFillID) {
|
||||
const w = this.w
|
||||
let fill = new Fill(this.ctx)
|
||||
|
||||
let randID = Utils.randomId()
|
||||
let hollowFillImg = w.config.plotOptions.radialBar.hollow.image
|
||||
|
||||
if (w.config.plotOptions.radialBar.hollow.imageClipped) {
|
||||
fill.clippedImgArea({
|
||||
width: hollowSize,
|
||||
height: hollowSize,
|
||||
image: hollowFillImg,
|
||||
patternID: `pattern${w.globals.cuid}${randID}`
|
||||
})
|
||||
hollowFillID = `url(#pattern${w.globals.cuid}${randID})`
|
||||
} else {
|
||||
const imgWidth = w.config.plotOptions.radialBar.hollow.imageWidth
|
||||
const imgHeight = w.config.plotOptions.radialBar.hollow.imageHeight
|
||||
if (imgWidth === undefined && imgHeight === undefined) {
|
||||
let image = w.globals.dom.Paper.image(hollowFillImg).loaded(function(
|
||||
loader
|
||||
) {
|
||||
this.move(
|
||||
opts.centerX -
|
||||
loader.width / 2 +
|
||||
w.config.plotOptions.radialBar.hollow.imageOffsetX,
|
||||
opts.centerY -
|
||||
loader.height / 2 +
|
||||
w.config.plotOptions.radialBar.hollow.imageOffsetY
|
||||
)
|
||||
})
|
||||
g.add(image)
|
||||
} else {
|
||||
let image = w.globals.dom.Paper.image(hollowFillImg).loaded(function(
|
||||
loader
|
||||
) {
|
||||
this.move(
|
||||
opts.centerX -
|
||||
imgWidth / 2 +
|
||||
w.config.plotOptions.radialBar.hollow.imageOffsetX,
|
||||
opts.centerY -
|
||||
imgHeight / 2 +
|
||||
w.config.plotOptions.radialBar.hollow.imageOffsetY
|
||||
)
|
||||
this.size(imgWidth, imgHeight)
|
||||
})
|
||||
g.add(image)
|
||||
}
|
||||
}
|
||||
return hollowFillID
|
||||
}
|
||||
|
||||
getStrokeWidth(opts) {
|
||||
const w = this.w
|
||||
return (
|
||||
(opts.size *
|
||||
(100 - parseInt(w.config.plotOptions.radialBar.hollow.size, 10))) /
|
||||
100 /
|
||||
(opts.series.length + 1) -
|
||||
this.margin
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Radial
|
||||
@@ -0,0 +1,454 @@
|
||||
import Bar from './Bar'
|
||||
import Graphics from '../modules/Graphics'
|
||||
import Utils from '../utils/Utils'
|
||||
import DateTime from '../utils/DateTime'
|
||||
|
||||
/**
|
||||
* ApexCharts RangeBar Class responsible for drawing Range/Timeline Bars.
|
||||
*
|
||||
* @module RangeBar
|
||||
**/
|
||||
|
||||
class RangeBar extends Bar {
|
||||
draw(series, seriesIndex) {
|
||||
let w = this.w
|
||||
let graphics = new Graphics(this.ctx)
|
||||
|
||||
this.rangeBarOptions = this.w.config.plotOptions.rangeBar
|
||||
|
||||
this.series = series
|
||||
this.seriesRangeStart = w.globals.seriesRangeStart
|
||||
this.seriesRangeEnd = w.globals.seriesRangeEnd
|
||||
|
||||
this.barHelpers.initVariables(series)
|
||||
|
||||
let ret = graphics.group({
|
||||
class: 'apexcharts-rangebar-series apexcharts-plot-series'
|
||||
})
|
||||
|
||||
for (let i = 0; i < series.length; i++) {
|
||||
let x,
|
||||
y,
|
||||
xDivision, // xDivision is the GRIDWIDTH divided by number of datapoints (columns)
|
||||
yDivision, // yDivision is the GRIDHEIGHT divided by number of datapoints (bars)
|
||||
zeroH, // zeroH is the baseline where 0 meets y axis
|
||||
zeroW // zeroW is the baseline where 0 meets x axis
|
||||
|
||||
let realIndex = w.globals.comboCharts ? seriesIndex[i] : i
|
||||
|
||||
// el to which series will be drawn
|
||||
let elSeries = graphics.group({
|
||||
class: `apexcharts-series`,
|
||||
seriesName: Utils.escapeString(w.globals.seriesNames[realIndex]),
|
||||
rel: i + 1,
|
||||
'data:realIndex': realIndex
|
||||
})
|
||||
|
||||
this.ctx.series.addCollapsedClassToSeries(elSeries, realIndex)
|
||||
|
||||
if (series[i].length > 0) {
|
||||
this.visibleI = this.visibleI + 1
|
||||
}
|
||||
|
||||
let barHeight = 0
|
||||
let barWidth = 0
|
||||
|
||||
if (this.yRatio.length > 1) {
|
||||
this.yaxisIndex = realIndex
|
||||
}
|
||||
|
||||
let initPositions = this.barHelpers.initialPositions()
|
||||
|
||||
y = initPositions.y
|
||||
zeroW = initPositions.zeroW
|
||||
|
||||
x = initPositions.x
|
||||
barWidth = initPositions.barWidth
|
||||
xDivision = initPositions.xDivision
|
||||
zeroH = initPositions.zeroH
|
||||
|
||||
// eldatalabels
|
||||
let elDataLabelsWrap = graphics.group({
|
||||
class: 'apexcharts-datalabels',
|
||||
'data:realIndex': realIndex
|
||||
})
|
||||
|
||||
let elGoalsMarkers = graphics.group({
|
||||
class: 'apexcharts-rangebar-goals-markers',
|
||||
style: `pointer-events: none`
|
||||
})
|
||||
|
||||
for (let j = 0; j < w.globals.dataPoints; j++) {
|
||||
const strokeWidth = this.barHelpers.getStrokeWidth(i, j, realIndex)
|
||||
|
||||
const y1 = this.seriesRangeStart[i][j]
|
||||
const y2 = this.seriesRangeEnd[i][j]
|
||||
|
||||
let paths = null
|
||||
let barYPosition = null
|
||||
const params = { x, y, strokeWidth, elSeries }
|
||||
|
||||
yDivision = initPositions.yDivision
|
||||
barHeight = initPositions.barHeight
|
||||
|
||||
if (this.isHorizontal) {
|
||||
barYPosition = y + barHeight * this.visibleI
|
||||
|
||||
let seriesLen = this.seriesLen
|
||||
if (w.config.plotOptions.bar.rangeBarGroupRows) {
|
||||
seriesLen = 1
|
||||
}
|
||||
|
||||
let srty = (yDivision - barHeight * seriesLen) / 2
|
||||
|
||||
if (typeof w.config.series[i].data[j] === 'undefined') {
|
||||
// no data exists for further indexes, hence we need to get out the innr loop.
|
||||
// As we are iterating over total datapoints, there is a possiblity the series might not have data for j index
|
||||
break
|
||||
}
|
||||
|
||||
if (w.config.series[i].data[j].x) {
|
||||
let positions = this.detectOverlappingBars({
|
||||
i,
|
||||
j,
|
||||
barYPosition,
|
||||
srty,
|
||||
barHeight,
|
||||
yDivision,
|
||||
initPositions
|
||||
})
|
||||
|
||||
barHeight = positions.barHeight
|
||||
barYPosition = positions.barYPosition
|
||||
}
|
||||
|
||||
paths = this.drawRangeBarPaths({
|
||||
indexes: { i, j, realIndex },
|
||||
barHeight,
|
||||
barYPosition,
|
||||
zeroW,
|
||||
yDivision,
|
||||
y1,
|
||||
y2,
|
||||
...params
|
||||
})
|
||||
|
||||
barWidth = paths.barWidth
|
||||
} else {
|
||||
paths = this.drawRangeColumnPaths({
|
||||
indexes: { i, j, realIndex },
|
||||
zeroH,
|
||||
barWidth,
|
||||
xDivision,
|
||||
...params
|
||||
})
|
||||
|
||||
barHeight = paths.barHeight
|
||||
}
|
||||
|
||||
const barGoalLine = this.barHelpers.drawGoalLine({
|
||||
barXPosition: paths.barXPosition,
|
||||
barYPosition,
|
||||
goalX: paths.goalX,
|
||||
goalY: paths.goalY,
|
||||
barHeight,
|
||||
barWidth
|
||||
})
|
||||
|
||||
if (barGoalLine) {
|
||||
elGoalsMarkers.add(barGoalLine)
|
||||
}
|
||||
|
||||
y = paths.y
|
||||
x = paths.x
|
||||
|
||||
let pathFill = this.barHelpers.getPathFillColor(series, i, j, realIndex)
|
||||
|
||||
let lineFill = w.globals.stroke.colors[realIndex]
|
||||
|
||||
this.renderSeries({
|
||||
realIndex,
|
||||
pathFill,
|
||||
lineFill,
|
||||
j,
|
||||
i,
|
||||
x,
|
||||
y,
|
||||
y1,
|
||||
y2,
|
||||
pathFrom: paths.pathFrom,
|
||||
pathTo: paths.pathTo,
|
||||
strokeWidth,
|
||||
elSeries,
|
||||
series,
|
||||
barHeight,
|
||||
barYPosition,
|
||||
barWidth,
|
||||
elDataLabelsWrap,
|
||||
elGoalsMarkers,
|
||||
visibleSeries: this.visibleI,
|
||||
type: 'rangebar'
|
||||
})
|
||||
}
|
||||
|
||||
ret.add(elSeries)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
detectOverlappingBars({
|
||||
i,
|
||||
j,
|
||||
barYPosition,
|
||||
srty,
|
||||
barHeight,
|
||||
yDivision,
|
||||
initPositions
|
||||
}) {
|
||||
const w = this.w
|
||||
let overlaps = []
|
||||
let rangeName = w.config.series[i].data[j].rangeName
|
||||
|
||||
const labelX = w.config.series[i].data[j].x
|
||||
const rowIndex = w.globals.labels.indexOf(labelX)
|
||||
const overlappedIndex = w.globals.seriesRangeBar[i].findIndex(
|
||||
(tx) => tx.x === labelX && tx.overlaps.length > 0
|
||||
)
|
||||
|
||||
if (w.config.plotOptions.bar.rangeBarGroupRows) {
|
||||
barYPosition = srty + yDivision * rowIndex
|
||||
} else {
|
||||
barYPosition = srty + barHeight * this.visibleI + yDivision * rowIndex
|
||||
}
|
||||
|
||||
if (overlappedIndex > -1 && !w.config.plotOptions.bar.rangeBarOverlap) {
|
||||
overlaps = w.globals.seriesRangeBar[i][overlappedIndex].overlaps
|
||||
|
||||
if (overlaps.indexOf(rangeName) > -1) {
|
||||
barHeight = initPositions.barHeight / overlaps.length
|
||||
|
||||
barYPosition =
|
||||
barHeight * this.visibleI +
|
||||
(yDivision * (100 - parseInt(this.barOptions.barHeight, 10))) /
|
||||
100 /
|
||||
2 +
|
||||
barHeight * (this.visibleI + overlaps.indexOf(rangeName)) +
|
||||
yDivision * rowIndex
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
barYPosition,
|
||||
barHeight
|
||||
}
|
||||
}
|
||||
|
||||
drawRangeColumnPaths({
|
||||
indexes,
|
||||
x,
|
||||
strokeWidth,
|
||||
xDivision,
|
||||
barWidth,
|
||||
zeroH
|
||||
}) {
|
||||
let w = this.w
|
||||
|
||||
let i = indexes.i
|
||||
let j = indexes.j
|
||||
|
||||
const yRatio = this.yRatio[this.yaxisIndex]
|
||||
let realIndex = indexes.realIndex
|
||||
|
||||
const range = this.getRangeValue(realIndex, j)
|
||||
|
||||
let y1 = Math.min(range.start, range.end)
|
||||
let y2 = Math.max(range.start, range.end)
|
||||
|
||||
if (w.globals.isXNumeric) {
|
||||
x =
|
||||
(w.globals.seriesX[i][j] - w.globals.minX) / this.xRatio - barWidth / 2
|
||||
}
|
||||
|
||||
let barXPosition = x + barWidth * this.visibleI
|
||||
|
||||
if (
|
||||
typeof this.series[i][j] === 'undefined' ||
|
||||
this.series[i][j] === null
|
||||
) {
|
||||
y1 = zeroH
|
||||
} else {
|
||||
y1 = zeroH - y1 / yRatio
|
||||
y2 = zeroH - y2 / yRatio
|
||||
}
|
||||
const barHeight = Math.abs(y2 - y1)
|
||||
|
||||
const paths = this.barHelpers.getColumnPaths({
|
||||
barXPosition,
|
||||
barWidth,
|
||||
y1,
|
||||
y2,
|
||||
strokeWidth: this.strokeWidth,
|
||||
series: this.seriesRangeEnd,
|
||||
realIndex: indexes.realIndex,
|
||||
i: realIndex,
|
||||
j,
|
||||
w
|
||||
})
|
||||
|
||||
if (!w.globals.isXNumeric) {
|
||||
x = x + xDivision
|
||||
}
|
||||
|
||||
return {
|
||||
pathTo: paths.pathTo,
|
||||
pathFrom: paths.pathFrom,
|
||||
barHeight,
|
||||
x,
|
||||
y: y2,
|
||||
goalY: this.barHelpers.getGoalValues('y', null, zeroH, i, j),
|
||||
barXPosition
|
||||
}
|
||||
}
|
||||
|
||||
drawRangeBarPaths({
|
||||
indexes,
|
||||
y,
|
||||
y1,
|
||||
y2,
|
||||
yDivision,
|
||||
barHeight,
|
||||
barYPosition,
|
||||
zeroW
|
||||
}) {
|
||||
let w = this.w
|
||||
|
||||
const x1 = zeroW + y1 / this.invertedYRatio
|
||||
const x2 = zeroW + y2 / this.invertedYRatio
|
||||
|
||||
const barWidth = Math.abs(x2 - x1)
|
||||
|
||||
const paths = this.barHelpers.getBarpaths({
|
||||
barYPosition,
|
||||
barHeight,
|
||||
x1,
|
||||
x2,
|
||||
strokeWidth: this.strokeWidth,
|
||||
series: this.seriesRangeEnd,
|
||||
i: indexes.realIndex,
|
||||
realIndex: indexes.realIndex,
|
||||
j: indexes.j,
|
||||
w
|
||||
})
|
||||
|
||||
if (!w.globals.isXNumeric) {
|
||||
y = y + yDivision
|
||||
}
|
||||
|
||||
return {
|
||||
pathTo: paths.pathTo,
|
||||
pathFrom: paths.pathFrom,
|
||||
barWidth,
|
||||
x: x2,
|
||||
goalX: this.barHelpers.getGoalValues(
|
||||
'x',
|
||||
zeroW,
|
||||
null,
|
||||
indexes.realIndex,
|
||||
indexes.j
|
||||
),
|
||||
y
|
||||
}
|
||||
}
|
||||
|
||||
getRangeValue(i, j) {
|
||||
const w = this.w
|
||||
return {
|
||||
start: w.globals.seriesRangeStart[i][j],
|
||||
end: w.globals.seriesRangeEnd[i][j]
|
||||
}
|
||||
}
|
||||
|
||||
getTooltipValues({ ctx, seriesIndex, dataPointIndex, y1, y2, w }) {
|
||||
let start = w.globals.seriesRangeStart[seriesIndex][dataPointIndex]
|
||||
let end = w.globals.seriesRangeEnd[seriesIndex][dataPointIndex]
|
||||
let ylabel = w.globals.labels[dataPointIndex]
|
||||
let seriesName = w.config.series[seriesIndex].name
|
||||
? w.config.series[seriesIndex].name
|
||||
: ''
|
||||
const yLbFormatter = w.config.tooltip.y.formatter
|
||||
const yLbTitleFormatter = w.config.tooltip.y.title.formatter
|
||||
|
||||
const opts = {
|
||||
w,
|
||||
seriesIndex,
|
||||
dataPointIndex,
|
||||
start,
|
||||
end
|
||||
}
|
||||
|
||||
if (typeof yLbTitleFormatter === 'function') {
|
||||
seriesName = yLbTitleFormatter(seriesName, opts)
|
||||
}
|
||||
|
||||
if (Number.isFinite(y1) && Number.isFinite(y2)) {
|
||||
start = y1
|
||||
end = y2
|
||||
|
||||
if (w.config.series[seriesIndex].data[dataPointIndex].x) {
|
||||
ylabel = w.config.series[seriesIndex].data[dataPointIndex].x + ':'
|
||||
}
|
||||
|
||||
if (typeof yLbFormatter === 'function') {
|
||||
ylabel = yLbFormatter(ylabel, opts)
|
||||
}
|
||||
}
|
||||
|
||||
let startVal = ''
|
||||
let endVal = ''
|
||||
|
||||
const color = w.globals.colors[seriesIndex]
|
||||
if (w.config.tooltip.x.formatter === undefined) {
|
||||
if (w.config.xaxis.type === 'datetime') {
|
||||
let datetimeObj = new DateTime(ctx)
|
||||
startVal = datetimeObj.formatDate(
|
||||
datetimeObj.getDate(start),
|
||||
w.config.tooltip.x.format
|
||||
)
|
||||
endVal = datetimeObj.formatDate(
|
||||
datetimeObj.getDate(end),
|
||||
w.config.tooltip.x.format
|
||||
)
|
||||
} else {
|
||||
startVal = start
|
||||
endVal = end
|
||||
}
|
||||
} else {
|
||||
startVal = w.config.tooltip.x.formatter(start)
|
||||
endVal = w.config.tooltip.x.formatter(end)
|
||||
}
|
||||
|
||||
return { start, end, startVal, endVal, ylabel, color, seriesName }
|
||||
}
|
||||
|
||||
buildCustomTooltipHTML({ color, seriesName, ylabel, start, end }) {
|
||||
return (
|
||||
'<div class="apexcharts-tooltip-rangebar">' +
|
||||
'<div> <span class="series-name" style="color: ' +
|
||||
color +
|
||||
'">' +
|
||||
(seriesName ? seriesName : '') +
|
||||
'</span></div>' +
|
||||
'<div> <span class="category">' +
|
||||
ylabel +
|
||||
' </span> <span class="value start-value">' +
|
||||
start +
|
||||
'</span> <span class="separator">-</span> <span class="value end-value">' +
|
||||
end +
|
||||
'</span></div>' +
|
||||
'</div>'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default RangeBar
|
||||
@@ -0,0 +1,267 @@
|
||||
import Animations from '../modules/Animations'
|
||||
import Fill from '../modules/Fill'
|
||||
import Filters from '../modules/Filters'
|
||||
import Graphics from '../modules/Graphics'
|
||||
import Markers from '../modules/Markers'
|
||||
|
||||
/**
|
||||
* ApexCharts Scatter Class.
|
||||
* This Class also handles bubbles chart as currently there is no major difference in drawing them,
|
||||
* @module Scatter
|
||||
**/
|
||||
export default class Scatter {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
|
||||
this.initialAnim = this.w.config.chart.animations.enabled
|
||||
this.dynamicAnim =
|
||||
this.initialAnim &&
|
||||
this.w.config.chart.animations.dynamicAnimation.enabled
|
||||
}
|
||||
|
||||
draw(elSeries, j, opts) {
|
||||
let w = this.w
|
||||
|
||||
let graphics = new Graphics(this.ctx)
|
||||
|
||||
let realIndex = opts.realIndex
|
||||
let pointsPos = opts.pointsPos
|
||||
let zRatio = opts.zRatio
|
||||
let elPointsMain = opts.elParent
|
||||
|
||||
let elPointsWrap = graphics.group({
|
||||
class: `apexcharts-series-markers apexcharts-series-${w.config.chart.type}`
|
||||
})
|
||||
|
||||
elPointsWrap.attr('clip-path', `url(#gridRectMarkerMask${w.globals.cuid})`)
|
||||
|
||||
if (Array.isArray(pointsPos.x)) {
|
||||
for (let q = 0; q < pointsPos.x.length; q++) {
|
||||
let dataPointIndex = j + 1
|
||||
let shouldDraw = true
|
||||
|
||||
// a small hack as we have 2 points for the first val to connect it
|
||||
if (j === 0 && q === 0) dataPointIndex = 0
|
||||
if (j === 0 && q === 1) dataPointIndex = 1
|
||||
|
||||
let radius = 0
|
||||
let finishRadius = w.globals.markers.size[realIndex]
|
||||
|
||||
if (zRatio !== Infinity) {
|
||||
// means we have a bubble
|
||||
finishRadius = w.globals.seriesZ[realIndex][dataPointIndex] / zRatio
|
||||
|
||||
const bubble = w.config.plotOptions.bubble
|
||||
if (bubble.minBubbleRadius && finishRadius < bubble.minBubbleRadius) {
|
||||
finishRadius = bubble.minBubbleRadius
|
||||
}
|
||||
|
||||
if (bubble.maxBubbleRadius && finishRadius > bubble.maxBubbleRadius) {
|
||||
finishRadius = bubble.maxBubbleRadius
|
||||
}
|
||||
}
|
||||
|
||||
if (!w.config.chart.animations.enabled) {
|
||||
radius = finishRadius
|
||||
}
|
||||
|
||||
let x = pointsPos.x[q]
|
||||
let y = pointsPos.y[q]
|
||||
|
||||
radius = radius || 0
|
||||
|
||||
if (
|
||||
y === null ||
|
||||
typeof w.globals.series[realIndex][dataPointIndex] === 'undefined'
|
||||
) {
|
||||
shouldDraw = false
|
||||
}
|
||||
|
||||
if (shouldDraw) {
|
||||
const point = this.drawPoint(
|
||||
x,
|
||||
y,
|
||||
radius,
|
||||
finishRadius,
|
||||
realIndex,
|
||||
dataPointIndex,
|
||||
j
|
||||
)
|
||||
elPointsWrap.add(point)
|
||||
}
|
||||
|
||||
elPointsMain.add(elPointsWrap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawPoint(x, y, radius, finishRadius, realIndex, dataPointIndex, j) {
|
||||
const w = this.w
|
||||
|
||||
let i = realIndex
|
||||
let anim = new Animations(this.ctx)
|
||||
let filters = new Filters(this.ctx)
|
||||
let fill = new Fill(this.ctx)
|
||||
let markers = new Markers(this.ctx)
|
||||
const graphics = new Graphics(this.ctx)
|
||||
|
||||
const markerConfig = markers.getMarkerConfig({
|
||||
cssClass: 'apexcharts-marker',
|
||||
seriesIndex: i,
|
||||
dataPointIndex,
|
||||
finishRadius:
|
||||
w.config.chart.type === 'bubble' ||
|
||||
(w.globals.comboCharts &&
|
||||
w.config.series[realIndex] &&
|
||||
w.config.series[realIndex].type === 'bubble')
|
||||
? finishRadius
|
||||
: null
|
||||
})
|
||||
|
||||
finishRadius = markerConfig.pSize
|
||||
|
||||
let pathFillCircle = fill.fillPath({
|
||||
seriesNumber: realIndex,
|
||||
dataPointIndex,
|
||||
color: markerConfig.pointFillColor,
|
||||
patternUnits: 'objectBoundingBox',
|
||||
value: w.globals.series[realIndex][j]
|
||||
})
|
||||
|
||||
let el
|
||||
if (markerConfig.shape === 'circle') {
|
||||
el = graphics.drawCircle(radius)
|
||||
} else if (
|
||||
markerConfig.shape === 'square' ||
|
||||
markerConfig.shape === 'rect'
|
||||
) {
|
||||
el = graphics.drawRect(
|
||||
0,
|
||||
0,
|
||||
markerConfig.width - markerConfig.pointStrokeWidth / 2,
|
||||
markerConfig.height - markerConfig.pointStrokeWidth / 2,
|
||||
markerConfig.pRadius
|
||||
)
|
||||
}
|
||||
|
||||
if (w.config.series[i].data[dataPointIndex]) {
|
||||
if (w.config.series[i].data[dataPointIndex].fillColor) {
|
||||
pathFillCircle = w.config.series[i].data[dataPointIndex].fillColor
|
||||
}
|
||||
}
|
||||
|
||||
el.attr({
|
||||
x: x - markerConfig.width / 2 - markerConfig.pointStrokeWidth / 2,
|
||||
y: y - markerConfig.height / 2 - markerConfig.pointStrokeWidth / 2,
|
||||
cx: x,
|
||||
cy: y,
|
||||
fill: pathFillCircle,
|
||||
'fill-opacity': markerConfig.pointFillOpacity,
|
||||
stroke: markerConfig.pointStrokeColor,
|
||||
r: finishRadius,
|
||||
'stroke-width': markerConfig.pointStrokeWidth,
|
||||
'stroke-dasharray': markerConfig.pointStrokeDashArray,
|
||||
'stroke-opacity': markerConfig.pointStrokeOpacity
|
||||
})
|
||||
|
||||
if (w.config.chart.dropShadow.enabled) {
|
||||
const dropShadow = w.config.chart.dropShadow
|
||||
filters.dropShadow(el, dropShadow, realIndex)
|
||||
}
|
||||
|
||||
if (this.initialAnim && !w.globals.dataChanged && !w.globals.resized) {
|
||||
let speed = w.config.chart.animations.speed
|
||||
|
||||
anim.animateMarker(
|
||||
el,
|
||||
0,
|
||||
markerConfig.shape === 'circle'
|
||||
? finishRadius
|
||||
: { width: markerConfig.width, height: markerConfig.height },
|
||||
speed,
|
||||
w.globals.easing,
|
||||
() => {
|
||||
window.setTimeout(() => {
|
||||
anim.animationCompleted(el)
|
||||
}, 100)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
w.globals.animationEnded = true
|
||||
}
|
||||
|
||||
if (w.globals.dataChanged && markerConfig.shape === 'circle') {
|
||||
if (this.dynamicAnim) {
|
||||
let speed = w.config.chart.animations.dynamicAnimation.speed
|
||||
let prevX, prevY, prevR
|
||||
|
||||
let prevPathJ = null
|
||||
|
||||
prevPathJ =
|
||||
w.globals.previousPaths[realIndex] &&
|
||||
w.globals.previousPaths[realIndex][j]
|
||||
|
||||
if (typeof prevPathJ !== 'undefined' && prevPathJ !== null) {
|
||||
// series containing less elements will ignore these values and revert to 0
|
||||
prevX = prevPathJ.x
|
||||
prevY = prevPathJ.y
|
||||
prevR =
|
||||
typeof prevPathJ.r !== 'undefined' ? prevPathJ.r : finishRadius
|
||||
}
|
||||
|
||||
for (let cs = 0; cs < w.globals.collapsedSeries.length; cs++) {
|
||||
if (w.globals.collapsedSeries[cs].index === realIndex) {
|
||||
speed = 1
|
||||
finishRadius = 0
|
||||
}
|
||||
}
|
||||
|
||||
if (x === 0 && y === 0) finishRadius = 0
|
||||
|
||||
anim.animateCircle(
|
||||
el,
|
||||
{
|
||||
cx: prevX,
|
||||
cy: prevY,
|
||||
r: prevR
|
||||
},
|
||||
{
|
||||
cx: x,
|
||||
cy: y,
|
||||
r: finishRadius
|
||||
},
|
||||
speed,
|
||||
w.globals.easing
|
||||
)
|
||||
} else {
|
||||
el.attr({
|
||||
r: finishRadius
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
el.attr({
|
||||
rel: dataPointIndex,
|
||||
j: dataPointIndex,
|
||||
index: realIndex,
|
||||
'default-marker-size': finishRadius
|
||||
})
|
||||
|
||||
filters.setSelectionFilter(el, realIndex, dataPointIndex)
|
||||
markers.addEvents(el)
|
||||
|
||||
el.node.classList.add('apexcharts-marker')
|
||||
|
||||
return el
|
||||
}
|
||||
|
||||
centerTextInBubble(y) {
|
||||
let w = this.w
|
||||
y = y + parseInt(w.config.dataLabels.style.fontSize, 10) / 4
|
||||
|
||||
return {
|
||||
y
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
import '../libs/Treemap-squared'
|
||||
import Graphics from '../modules/Graphics'
|
||||
import Animations from '../modules/Animations'
|
||||
import Fill from '../modules/Fill'
|
||||
import Helpers from './common/treemap/Helpers'
|
||||
import Filters from '../modules/Filters'
|
||||
|
||||
import Utils from '../utils/Utils'
|
||||
|
||||
/**
|
||||
* ApexCharts TreemapChart Class.
|
||||
* @module TreemapChart
|
||||
**/
|
||||
|
||||
export default class TreemapChart {
|
||||
constructor(ctx, xyRatios) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
|
||||
this.strokeWidth = this.w.config.stroke.width
|
||||
this.helpers = new Helpers(ctx)
|
||||
this.dynamicAnim = this.w.config.chart.animations.dynamicAnimation
|
||||
|
||||
this.labels = []
|
||||
}
|
||||
|
||||
draw(series) {
|
||||
let w = this.w
|
||||
const graphics = new Graphics(this.ctx)
|
||||
const fill = new Fill(this.ctx)
|
||||
|
||||
let ret = graphics.group({
|
||||
class: 'apexcharts-treemap'
|
||||
})
|
||||
|
||||
if (w.globals.noData) return ret
|
||||
|
||||
let ser = []
|
||||
series.forEach((s) => {
|
||||
let d = s.map((v) => {
|
||||
return Math.abs(v)
|
||||
})
|
||||
ser.push(d)
|
||||
})
|
||||
|
||||
this.negRange = this.helpers.checkColorRange()
|
||||
|
||||
w.config.series.forEach((s, i) => {
|
||||
s.data.forEach((l) => {
|
||||
if (!Array.isArray(this.labels[i])) this.labels[i] = []
|
||||
this.labels[i].push(l.x)
|
||||
})
|
||||
})
|
||||
|
||||
const nodes = window.TreemapSquared.generate(
|
||||
ser,
|
||||
w.globals.gridWidth,
|
||||
w.globals.gridHeight
|
||||
)
|
||||
|
||||
nodes.forEach((node, i) => {
|
||||
let elSeries = graphics.group({
|
||||
class: `apexcharts-series apexcharts-treemap-series`,
|
||||
seriesName: Utils.escapeString(w.globals.seriesNames[i]),
|
||||
rel: i + 1,
|
||||
'data:realIndex': i
|
||||
})
|
||||
|
||||
if (w.config.chart.dropShadow.enabled) {
|
||||
const shadow = w.config.chart.dropShadow
|
||||
const filters = new Filters(this.ctx)
|
||||
filters.dropShadow(ret, shadow, i)
|
||||
}
|
||||
|
||||
let elDataLabelWrap = graphics.group({
|
||||
class: 'apexcharts-data-labels'
|
||||
})
|
||||
|
||||
node.forEach((r, j) => {
|
||||
const x1 = r[0]
|
||||
const y1 = r[1]
|
||||
const x2 = r[2]
|
||||
const y2 = r[3]
|
||||
let elRect = graphics.drawRect(
|
||||
x1,
|
||||
y1,
|
||||
x2 - x1,
|
||||
y2 - y1,
|
||||
0,
|
||||
'#fff',
|
||||
1,
|
||||
this.strokeWidth,
|
||||
w.config.plotOptions.treemap.useFillColorAsStroke
|
||||
? color
|
||||
: w.globals.stroke.colors[i]
|
||||
)
|
||||
elRect.attr({
|
||||
cx: x1,
|
||||
cy: y1,
|
||||
index: i,
|
||||
i,
|
||||
j,
|
||||
width: x2 - x1,
|
||||
height: y2 - y1
|
||||
})
|
||||
|
||||
let colorProps = this.helpers.getShadeColor(
|
||||
w.config.chart.type,
|
||||
i,
|
||||
j,
|
||||
this.negRange
|
||||
)
|
||||
let color = colorProps.color
|
||||
|
||||
if (
|
||||
typeof w.config.series[i].data[j] !== 'undefined' &&
|
||||
w.config.series[i].data[j].fillColor
|
||||
) {
|
||||
color = w.config.series[i].data[j].fillColor
|
||||
}
|
||||
let pathFill = fill.fillPath({
|
||||
color,
|
||||
seriesNumber: i,
|
||||
dataPointIndex: j
|
||||
})
|
||||
|
||||
elRect.node.classList.add('apexcharts-treemap-rect')
|
||||
|
||||
elRect.attr({
|
||||
fill: pathFill
|
||||
})
|
||||
|
||||
this.helpers.addListeners(elRect)
|
||||
|
||||
let fromRect = {
|
||||
x: x1 + (x2 - x1) / 2,
|
||||
y: y1 + (y2 - y1) / 2,
|
||||
width: 0,
|
||||
height: 0
|
||||
}
|
||||
let toRect = {
|
||||
x: x1,
|
||||
y: y1,
|
||||
width: x2 - x1,
|
||||
height: y2 - y1
|
||||
}
|
||||
|
||||
if (w.config.chart.animations.enabled && !w.globals.dataChanged) {
|
||||
let speed = 1
|
||||
if (!w.globals.resized) {
|
||||
speed = w.config.chart.animations.speed
|
||||
}
|
||||
this.animateTreemap(elRect, fromRect, toRect, speed)
|
||||
}
|
||||
if (w.globals.dataChanged) {
|
||||
let speed = 1
|
||||
if (this.dynamicAnim.enabled && w.globals.shouldAnimate) {
|
||||
speed = this.dynamicAnim.speed
|
||||
|
||||
if (
|
||||
w.globals.previousPaths[i] &&
|
||||
w.globals.previousPaths[i][j] &&
|
||||
w.globals.previousPaths[i][j].rect
|
||||
) {
|
||||
fromRect = w.globals.previousPaths[i][j].rect
|
||||
}
|
||||
|
||||
this.animateTreemap(elRect, fromRect, toRect, speed)
|
||||
}
|
||||
}
|
||||
|
||||
const fontSize = this.getFontSize(r)
|
||||
|
||||
let formattedText = w.config.dataLabels.formatter(this.labels[i][j], {
|
||||
value: w.globals.series[i][j],
|
||||
seriesIndex: i,
|
||||
dataPointIndex: j,
|
||||
w
|
||||
})
|
||||
let dataLabels = this.helpers.calculateDataLabels({
|
||||
text: formattedText,
|
||||
x: (x1 + x2) / 2,
|
||||
y: (y1 + y2) / 2 + this.strokeWidth / 2 + fontSize / 3,
|
||||
i,
|
||||
j,
|
||||
colorProps,
|
||||
fontSize,
|
||||
series
|
||||
})
|
||||
if (w.config.dataLabels.enabled && dataLabels) {
|
||||
this.rotateToFitLabel(dataLabels, formattedText, x1, y1, x2, y2)
|
||||
}
|
||||
elSeries.add(elRect)
|
||||
|
||||
if (dataLabels !== null) {
|
||||
elSeries.add(dataLabels)
|
||||
}
|
||||
})
|
||||
elSeries.add(elDataLabelWrap)
|
||||
|
||||
ret.add(elSeries)
|
||||
})
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// This calculates a font-size based upon
|
||||
// average label length and the size of the box the label is
|
||||
// going into. The maximum font size is set in chart config.
|
||||
getFontSize(coordinates) {
|
||||
const w = this.w
|
||||
|
||||
// total length of labels (i.e [["Italy"],["Spain", "Greece"]] -> 16)
|
||||
function totalLabelLength(arr) {
|
||||
let i,
|
||||
total = 0
|
||||
if (Array.isArray(arr[0])) {
|
||||
for (i = 0; i < arr.length; i++) {
|
||||
total += totalLabelLength(arr[i])
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < arr.length; i++) {
|
||||
total += arr[i].length
|
||||
}
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// count of labels (i.e [["Italy"],["Spain", "Greece"]] -> 3)
|
||||
function countLabels(arr) {
|
||||
let i,
|
||||
total = 0
|
||||
if (Array.isArray(arr[0])) {
|
||||
for (i = 0; i < arr.length; i++) {
|
||||
total += countLabels(arr[i])
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < arr.length; i++) {
|
||||
total += 1
|
||||
}
|
||||
}
|
||||
return total
|
||||
}
|
||||
let averagelabelsize =
|
||||
totalLabelLength(this.labels) / countLabels(this.labels)
|
||||
|
||||
function fontSize(width, height) {
|
||||
// the font size should be proportional to the size of the box (and the value)
|
||||
// otherwise you can end up creating a visual distortion where two boxes of identical
|
||||
// size have different sized labels, and thus make it look as if the two boxes
|
||||
// represent different sizes
|
||||
let area = width * height
|
||||
let arearoot = Math.pow(area, 0.5)
|
||||
return Math.min(
|
||||
arearoot / averagelabelsize,
|
||||
parseInt(w.config.dataLabels.style.fontSize, 10)
|
||||
)
|
||||
}
|
||||
|
||||
return fontSize(
|
||||
coordinates[2] - coordinates[0],
|
||||
coordinates[3] - coordinates[1]
|
||||
)
|
||||
}
|
||||
|
||||
rotateToFitLabel(elText, text, x1, y1, x2, y2) {
|
||||
const graphics = new Graphics(this.ctx)
|
||||
const textRect = graphics.getTextRects(text)
|
||||
//if the label fits better sideways then rotate it
|
||||
if (textRect.width + 5 > x2 - x1 && textRect.width <= y2 - y1) {
|
||||
let labelRotatingCenter = graphics.rotateAroundCenter(elText.node)
|
||||
|
||||
elText.node.setAttribute(
|
||||
'transform',
|
||||
`rotate(-90 ${labelRotatingCenter.x} ${labelRotatingCenter.y})`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
animateTreemap(el, fromRect, toRect, speed) {
|
||||
const animations = new Animations(this.ctx)
|
||||
animations.animateRect(
|
||||
el,
|
||||
{
|
||||
x: fromRect.x,
|
||||
y: fromRect.y,
|
||||
width: fromRect.width,
|
||||
height: fromRect.height
|
||||
},
|
||||
{
|
||||
x: toRect.x,
|
||||
y: toRect.y,
|
||||
width: toRect.width,
|
||||
height: toRect.height
|
||||
},
|
||||
speed,
|
||||
() => {
|
||||
animations.animationCompleted(el)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,442 @@
|
||||
import Graphics from '../../../modules/Graphics'
|
||||
import DataLabels from '../../../modules/DataLabels'
|
||||
|
||||
export default class BarDataLabels {
|
||||
constructor(barCtx) {
|
||||
this.w = barCtx.w
|
||||
this.barCtx = barCtx
|
||||
}
|
||||
/** handleBarDataLabels is used to calculate the positions for the data-labels
|
||||
* It also sets the element's data attr for bars and calls drawCalculatedBarDataLabels()
|
||||
* After calculating, it also calls the function to draw data labels
|
||||
* @memberof Bar
|
||||
* @param {object} {barProps} most of the bar properties used throughout the bar
|
||||
* drawing function
|
||||
* @return {object} dataLabels node-element which you can append later
|
||||
**/
|
||||
handleBarDataLabels(opts) {
|
||||
let {
|
||||
x,
|
||||
y,
|
||||
y1,
|
||||
y2,
|
||||
i,
|
||||
j,
|
||||
realIndex,
|
||||
series,
|
||||
barHeight,
|
||||
barWidth,
|
||||
barYPosition,
|
||||
visibleSeries,
|
||||
renderedPath
|
||||
} = opts
|
||||
let w = this.w
|
||||
let graphics = new Graphics(this.barCtx.ctx)
|
||||
|
||||
let strokeWidth = Array.isArray(this.barCtx.strokeWidth)
|
||||
? this.barCtx.strokeWidth[realIndex]
|
||||
: this.barCtx.strokeWidth
|
||||
|
||||
let bcx = x + parseFloat(barWidth * visibleSeries)
|
||||
let bcy = y + parseFloat(barHeight * visibleSeries)
|
||||
|
||||
if (w.globals.isXNumeric && !w.globals.isBarHorizontal) {
|
||||
bcx = x + parseFloat(barWidth * (visibleSeries + 1))
|
||||
bcy = y + parseFloat(barHeight * (visibleSeries + 1)) - strokeWidth
|
||||
}
|
||||
|
||||
let dataLabels = null
|
||||
let dataLabelsX = x
|
||||
let dataLabelsY = y
|
||||
let dataLabelsPos = {}
|
||||
let dataLabelsConfig = w.config.dataLabels
|
||||
let barDataLabelsConfig = this.barCtx.barOptions.dataLabels
|
||||
|
||||
if (typeof barYPosition !== 'undefined' && this.barCtx.isRangeBar) {
|
||||
bcy = barYPosition
|
||||
dataLabelsY = barYPosition
|
||||
}
|
||||
|
||||
const offX = dataLabelsConfig.offsetX
|
||||
const offY = dataLabelsConfig.offsetY
|
||||
|
||||
let textRects = {
|
||||
width: 0,
|
||||
height: 0
|
||||
}
|
||||
if (w.config.dataLabels.enabled) {
|
||||
const yLabel = this.barCtx.series[i][j]
|
||||
|
||||
textRects = graphics.getTextRects(
|
||||
w.globals.yLabelFormatters[0](yLabel),
|
||||
parseFloat(dataLabelsConfig.style.fontSize)
|
||||
)
|
||||
}
|
||||
|
||||
const params = {
|
||||
x,
|
||||
y,
|
||||
i,
|
||||
j,
|
||||
renderedPath,
|
||||
bcx,
|
||||
bcy,
|
||||
barHeight,
|
||||
barWidth,
|
||||
textRects,
|
||||
strokeWidth,
|
||||
dataLabelsX,
|
||||
dataLabelsY,
|
||||
barDataLabelsConfig,
|
||||
offX,
|
||||
offY
|
||||
}
|
||||
|
||||
if (this.barCtx.isHorizontal) {
|
||||
dataLabelsPos = this.calculateBarsDataLabelsPosition(params)
|
||||
} else {
|
||||
dataLabelsPos = this.calculateColumnsDataLabelsPosition(params)
|
||||
}
|
||||
|
||||
renderedPath.attr({
|
||||
cy: dataLabelsPos.bcy,
|
||||
cx: dataLabelsPos.bcx,
|
||||
j,
|
||||
val: series[i][j],
|
||||
barHeight,
|
||||
barWidth
|
||||
})
|
||||
|
||||
dataLabels = this.drawCalculatedDataLabels({
|
||||
x: dataLabelsPos.dataLabelsX,
|
||||
y: dataLabelsPos.dataLabelsY,
|
||||
val: this.barCtx.isRangeBar ? [y1, y2] : series[i][j],
|
||||
i: realIndex,
|
||||
j,
|
||||
barWidth,
|
||||
barHeight,
|
||||
textRects,
|
||||
dataLabelsConfig
|
||||
})
|
||||
|
||||
return dataLabels
|
||||
}
|
||||
|
||||
calculateColumnsDataLabelsPosition(opts) {
|
||||
const w = this.w
|
||||
let {
|
||||
i,
|
||||
j,
|
||||
y,
|
||||
bcx,
|
||||
barWidth,
|
||||
barHeight,
|
||||
textRects,
|
||||
dataLabelsY,
|
||||
barDataLabelsConfig,
|
||||
strokeWidth,
|
||||
offX,
|
||||
offY
|
||||
} = opts
|
||||
|
||||
let dataLabelsX
|
||||
barHeight = Math.abs(barHeight)
|
||||
|
||||
let vertical =
|
||||
w.config.plotOptions.bar.dataLabels.orientation === 'vertical'
|
||||
|
||||
bcx = bcx - strokeWidth / 2
|
||||
|
||||
let dataPointsDividedWidth = w.globals.gridWidth / w.globals.dataPoints
|
||||
if (w.globals.isXNumeric) {
|
||||
dataLabelsX = bcx - barWidth / 2 + offX
|
||||
} else {
|
||||
dataLabelsX = bcx - dataPointsDividedWidth + barWidth / 2 + offX
|
||||
}
|
||||
|
||||
if (vertical) {
|
||||
const offsetDLX = 2
|
||||
dataLabelsX =
|
||||
dataLabelsX + textRects.height / 2 - strokeWidth / 2 - offsetDLX
|
||||
}
|
||||
|
||||
let valIsNegative = this.barCtx.series[i][j] < 0
|
||||
|
||||
let newY = y
|
||||
if (this.barCtx.isReversed) {
|
||||
newY = y - barHeight + (valIsNegative ? barHeight * 2 : 0)
|
||||
y = y - barHeight
|
||||
}
|
||||
|
||||
switch (barDataLabelsConfig.position) {
|
||||
case 'center':
|
||||
if (vertical) {
|
||||
if (valIsNegative) {
|
||||
dataLabelsY = newY + barHeight / 2 + offY
|
||||
} else {
|
||||
dataLabelsY = newY + barHeight / 2 - offY
|
||||
}
|
||||
} else {
|
||||
if (valIsNegative) {
|
||||
dataLabelsY = newY - barHeight / 2 + textRects.height / 2 + offY
|
||||
} else {
|
||||
dataLabelsY = newY + barHeight / 2 + textRects.height / 2 - offY
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'bottom':
|
||||
if (vertical) {
|
||||
if (valIsNegative) {
|
||||
dataLabelsY = newY + barHeight + offY
|
||||
} else {
|
||||
dataLabelsY = newY + barHeight - offY
|
||||
}
|
||||
} else {
|
||||
if (valIsNegative) {
|
||||
dataLabelsY =
|
||||
newY - barHeight + textRects.height + strokeWidth + offY
|
||||
} else {
|
||||
dataLabelsY =
|
||||
newY + barHeight - textRects.height / 2 + strokeWidth - offY
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'top':
|
||||
if (vertical) {
|
||||
if (valIsNegative) {
|
||||
dataLabelsY = newY + offY
|
||||
} else {
|
||||
dataLabelsY = newY - offY
|
||||
}
|
||||
} else {
|
||||
if (valIsNegative) {
|
||||
dataLabelsY = newY - textRects.height / 2 - offY
|
||||
} else {
|
||||
dataLabelsY = newY + textRects.height + offY
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if (!w.config.chart.stacked) {
|
||||
if (dataLabelsY < 0) {
|
||||
dataLabelsY = 0 + strokeWidth
|
||||
} else if (dataLabelsY + textRects.height / 3 > w.globals.gridHeight) {
|
||||
dataLabelsY = w.globals.gridHeight - strokeWidth
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
bcx,
|
||||
bcy: y,
|
||||
dataLabelsX,
|
||||
dataLabelsY
|
||||
}
|
||||
}
|
||||
|
||||
calculateBarsDataLabelsPosition(opts) {
|
||||
const w = this.w
|
||||
let {
|
||||
x,
|
||||
i,
|
||||
j,
|
||||
bcy,
|
||||
barHeight,
|
||||
barWidth,
|
||||
textRects,
|
||||
dataLabelsX,
|
||||
strokeWidth,
|
||||
barDataLabelsConfig,
|
||||
offX,
|
||||
offY
|
||||
} = opts
|
||||
|
||||
let dataPointsDividedHeight = w.globals.gridHeight / w.globals.dataPoints
|
||||
|
||||
barWidth = Math.abs(barWidth)
|
||||
|
||||
let dataLabelsY =
|
||||
bcy -
|
||||
(this.barCtx.isRangeBar ? 0 : dataPointsDividedHeight) +
|
||||
barHeight / 2 +
|
||||
textRects.height / 2 +
|
||||
offY -
|
||||
3
|
||||
|
||||
let valIsNegative = this.barCtx.series[i][j] < 0
|
||||
|
||||
let newX = x
|
||||
if (this.barCtx.isReversed) {
|
||||
newX = x + barWidth - (valIsNegative ? barWidth * 2 : 0)
|
||||
x = w.globals.gridWidth - barWidth
|
||||
}
|
||||
|
||||
switch (barDataLabelsConfig.position) {
|
||||
case 'center':
|
||||
if (valIsNegative) {
|
||||
dataLabelsX = newX + barWidth / 2 - offX
|
||||
} else {
|
||||
dataLabelsX =
|
||||
Math.max(textRects.width / 2, newX - barWidth / 2) + offX
|
||||
}
|
||||
break
|
||||
case 'bottom':
|
||||
if (valIsNegative) {
|
||||
dataLabelsX =
|
||||
newX +
|
||||
barWidth -
|
||||
strokeWidth -
|
||||
Math.round(textRects.width / 2) -
|
||||
offX
|
||||
} else {
|
||||
dataLabelsX =
|
||||
newX -
|
||||
barWidth +
|
||||
strokeWidth +
|
||||
Math.round(textRects.width / 2) +
|
||||
offX
|
||||
}
|
||||
break
|
||||
case 'top':
|
||||
if (valIsNegative) {
|
||||
dataLabelsX =
|
||||
newX - strokeWidth + Math.round(textRects.width / 2) - offX
|
||||
} else {
|
||||
dataLabelsX =
|
||||
newX - strokeWidth - Math.round(textRects.width / 2) + offX
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if (!w.config.chart.stacked) {
|
||||
if (dataLabelsX < 0) {
|
||||
dataLabelsX = dataLabelsX + textRects.width + strokeWidth
|
||||
} else if (dataLabelsX + textRects.width / 2 > w.globals.gridWidth) {
|
||||
dataLabelsX = w.globals.gridWidth - textRects.width - strokeWidth
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
bcx: x,
|
||||
bcy,
|
||||
dataLabelsX,
|
||||
dataLabelsY
|
||||
}
|
||||
}
|
||||
|
||||
drawCalculatedDataLabels({
|
||||
x,
|
||||
y,
|
||||
val,
|
||||
i,
|
||||
j,
|
||||
textRects,
|
||||
barHeight,
|
||||
barWidth,
|
||||
dataLabelsConfig
|
||||
}) {
|
||||
const w = this.w
|
||||
let rotate = 'rotate(0)'
|
||||
if (w.config.plotOptions.bar.dataLabels.orientation === 'vertical')
|
||||
rotate = `rotate(-90, ${x}, ${y})`
|
||||
|
||||
const dataLabels = new DataLabels(this.barCtx.ctx)
|
||||
const graphics = new Graphics(this.barCtx.ctx)
|
||||
const formatter = dataLabelsConfig.formatter
|
||||
|
||||
let elDataLabelsWrap = null
|
||||
|
||||
const isSeriesNotCollapsed =
|
||||
w.globals.collapsedSeriesIndices.indexOf(i) > -1
|
||||
|
||||
if (dataLabelsConfig.enabled && !isSeriesNotCollapsed) {
|
||||
elDataLabelsWrap = graphics.group({
|
||||
class: 'apexcharts-data-labels',
|
||||
transform: rotate
|
||||
})
|
||||
|
||||
let text = ''
|
||||
if (typeof val !== 'undefined') {
|
||||
text = formatter(val, {
|
||||
seriesIndex: i,
|
||||
dataPointIndex: j,
|
||||
w
|
||||
})
|
||||
}
|
||||
|
||||
let valIsNegative = w.globals.series[i][j] < 0
|
||||
let position = w.config.plotOptions.bar.dataLabels.position
|
||||
if (w.config.plotOptions.bar.dataLabels.orientation === 'vertical') {
|
||||
if (position === 'top') {
|
||||
if (valIsNegative) dataLabelsConfig.textAnchor = 'end'
|
||||
else dataLabelsConfig.textAnchor = 'start'
|
||||
}
|
||||
if (position === 'center') {
|
||||
dataLabelsConfig.textAnchor = 'middle'
|
||||
}
|
||||
if (position === 'bottom') {
|
||||
if (valIsNegative) dataLabelsConfig.textAnchor = 'end'
|
||||
else dataLabelsConfig.textAnchor = 'start'
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
this.barCtx.isRangeBar &&
|
||||
this.barCtx.barOptions.dataLabels.hideOverflowingLabels
|
||||
) {
|
||||
// hide the datalabel if it cannot fit into the rect
|
||||
const txRect = graphics.getTextRects(
|
||||
text,
|
||||
parseFloat(dataLabelsConfig.style.fontSize)
|
||||
)
|
||||
if (barWidth < txRect.width) {
|
||||
text = ''
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
w.config.chart.stacked &&
|
||||
this.barCtx.barOptions.dataLabels.hideOverflowingLabels
|
||||
) {
|
||||
// if there is not enough space to draw the label in the bar/column rect, check hideOverflowingLabels property to prevent overflowing on wrong rect
|
||||
// Note: This issue is only seen in stacked charts
|
||||
if (this.barCtx.isHorizontal) {
|
||||
if (textRects.width / 1.6 > Math.abs(barWidth)) {
|
||||
text = ''
|
||||
}
|
||||
} else {
|
||||
if (textRects.height / 1.6 > Math.abs(barHeight)) {
|
||||
text = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let modifiedDataLabelsConfig = {
|
||||
...dataLabelsConfig
|
||||
}
|
||||
if (this.barCtx.isHorizontal) {
|
||||
if (val < 0) {
|
||||
if (dataLabelsConfig.textAnchor === 'start') {
|
||||
modifiedDataLabelsConfig.textAnchor = 'end'
|
||||
} else if (dataLabelsConfig.textAnchor === 'end') {
|
||||
modifiedDataLabelsConfig.textAnchor = 'start'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dataLabels.plotDataLabelsText({
|
||||
x,
|
||||
y,
|
||||
text,
|
||||
i,
|
||||
j,
|
||||
parent: elDataLabelsWrap,
|
||||
dataLabelsConfig: modifiedDataLabelsConfig,
|
||||
alwaysDrawDataLabel: true,
|
||||
offsetCorrection: true
|
||||
})
|
||||
}
|
||||
|
||||
return elDataLabelsWrap
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,638 @@
|
||||
import Fill from '../../../modules/Fill'
|
||||
import Graphics from '../../../modules/Graphics'
|
||||
import Series from '../../../modules/Series'
|
||||
|
||||
export default class Helpers {
|
||||
constructor(barCtx) {
|
||||
this.w = barCtx.w
|
||||
this.barCtx = barCtx
|
||||
}
|
||||
|
||||
initVariables(series) {
|
||||
const w = this.w
|
||||
this.barCtx.series = series
|
||||
this.barCtx.totalItems = 0
|
||||
this.barCtx.seriesLen = 0
|
||||
this.barCtx.visibleI = -1 // visible Series
|
||||
this.barCtx.visibleItems = 1 // number of visible bars after user zoomed in/out
|
||||
|
||||
for (let sl = 0; sl < series.length; sl++) {
|
||||
if (series[sl].length > 0) {
|
||||
this.barCtx.seriesLen = this.barCtx.seriesLen + 1
|
||||
this.barCtx.totalItems += series[sl].length
|
||||
}
|
||||
if (w.globals.isXNumeric) {
|
||||
// get max visible items
|
||||
for (let j = 0; j < series[sl].length; j++) {
|
||||
if (
|
||||
w.globals.seriesX[sl][j] > w.globals.minX &&
|
||||
w.globals.seriesX[sl][j] < w.globals.maxX
|
||||
) {
|
||||
this.barCtx.visibleItems++
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.barCtx.visibleItems = w.globals.dataPoints
|
||||
}
|
||||
}
|
||||
|
||||
if (this.barCtx.seriesLen === 0) {
|
||||
// A small adjustment when combo charts are used
|
||||
this.barCtx.seriesLen = 1
|
||||
}
|
||||
this.barCtx.zeroSerieses = []
|
||||
this.barCtx.radiusOnSeriesNumber = series.length - 1 // which series to draw ending shape on
|
||||
|
||||
if (!w.globals.comboCharts) {
|
||||
this.checkZeroSeries({ series })
|
||||
}
|
||||
}
|
||||
|
||||
initialPositions() {
|
||||
let w = this.w
|
||||
let x, y, yDivision, xDivision, barHeight, barWidth, zeroH, zeroW
|
||||
|
||||
let dataPoints = w.globals.dataPoints
|
||||
if (this.barCtx.isRangeBar) {
|
||||
// timeline rangebar chart
|
||||
dataPoints = w.globals.labels.length
|
||||
}
|
||||
|
||||
let seriesLen = this.barCtx.seriesLen
|
||||
if (w.config.plotOptions.bar.rangeBarGroupRows) {
|
||||
seriesLen = 1
|
||||
}
|
||||
|
||||
if (this.barCtx.isHorizontal) {
|
||||
// height divided into equal parts
|
||||
yDivision = w.globals.gridHeight / dataPoints
|
||||
barHeight = yDivision / seriesLen
|
||||
|
||||
if (w.globals.isXNumeric) {
|
||||
yDivision = w.globals.gridHeight / this.barCtx.totalItems
|
||||
barHeight = yDivision / this.barCtx.seriesLen
|
||||
}
|
||||
|
||||
barHeight =
|
||||
(barHeight * parseInt(this.barCtx.barOptions.barHeight, 10)) / 100
|
||||
|
||||
zeroW =
|
||||
this.barCtx.baseLineInvertedY +
|
||||
w.globals.padHorizontal +
|
||||
(this.barCtx.isReversed ? w.globals.gridWidth : 0) -
|
||||
(this.barCtx.isReversed ? this.barCtx.baseLineInvertedY * 2 : 0)
|
||||
|
||||
y = (yDivision - barHeight * this.barCtx.seriesLen) / 2
|
||||
} else {
|
||||
// width divided into equal parts
|
||||
xDivision = w.globals.gridWidth / this.barCtx.visibleItems
|
||||
if (w.config.xaxis.convertedCatToNumeric) {
|
||||
xDivision = w.globals.gridWidth / w.globals.dataPoints
|
||||
}
|
||||
barWidth =
|
||||
((xDivision / this.barCtx.seriesLen) *
|
||||
parseInt(this.barCtx.barOptions.columnWidth, 10)) /
|
||||
100
|
||||
|
||||
if (w.globals.isXNumeric) {
|
||||
// max barwidth should be equal to minXDiff to avoid overlap
|
||||
let xRatio = this.barCtx.xRatio
|
||||
if (w.config.xaxis.convertedCatToNumeric) {
|
||||
xRatio = this.barCtx.initialXRatio
|
||||
}
|
||||
if (
|
||||
w.globals.minXDiff &&
|
||||
w.globals.minXDiff !== 0.5 &&
|
||||
w.globals.minXDiff / xRatio > 0
|
||||
) {
|
||||
xDivision = w.globals.minXDiff / xRatio
|
||||
}
|
||||
|
||||
barWidth =
|
||||
((xDivision / this.barCtx.seriesLen) *
|
||||
parseInt(this.barCtx.barOptions.columnWidth, 10)) /
|
||||
100
|
||||
|
||||
if (barWidth < 1) {
|
||||
barWidth = 1
|
||||
}
|
||||
}
|
||||
|
||||
zeroH =
|
||||
w.globals.gridHeight -
|
||||
this.barCtx.baseLineY[this.barCtx.yaxisIndex] -
|
||||
(this.barCtx.isReversed ? w.globals.gridHeight : 0) +
|
||||
(this.barCtx.isReversed
|
||||
? this.barCtx.baseLineY[this.barCtx.yaxisIndex] * 2
|
||||
: 0)
|
||||
|
||||
x =
|
||||
w.globals.padHorizontal +
|
||||
(xDivision - barWidth * this.barCtx.seriesLen) / 2
|
||||
}
|
||||
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
yDivision,
|
||||
xDivision,
|
||||
barHeight,
|
||||
barWidth,
|
||||
zeroH,
|
||||
zeroW
|
||||
}
|
||||
}
|
||||
|
||||
getPathFillColor(series, i, j, realIndex) {
|
||||
const w = this.w
|
||||
let fill = new Fill(this.barCtx.ctx)
|
||||
|
||||
let fillColor = null
|
||||
let seriesNumber = this.barCtx.barOptions.distributed ? j : i
|
||||
|
||||
if (this.barCtx.barOptions.colors.ranges.length > 0) {
|
||||
const colorRange = this.barCtx.barOptions.colors.ranges
|
||||
colorRange.map((range) => {
|
||||
if (series[i][j] >= range.from && series[i][j] <= range.to) {
|
||||
fillColor = range.color
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (w.config.series[i].data[j] && w.config.series[i].data[j].fillColor) {
|
||||
fillColor = w.config.series[i].data[j].fillColor
|
||||
}
|
||||
|
||||
let pathFill = fill.fillPath({
|
||||
seriesNumber: this.barCtx.barOptions.distributed
|
||||
? seriesNumber
|
||||
: realIndex,
|
||||
dataPointIndex: j,
|
||||
color: fillColor,
|
||||
value: series[i][j]
|
||||
})
|
||||
|
||||
return pathFill
|
||||
}
|
||||
|
||||
getStrokeWidth(i, j, realIndex) {
|
||||
let strokeWidth = 0
|
||||
const w = this.w
|
||||
|
||||
if (
|
||||
typeof this.barCtx.series[i][j] === 'undefined' ||
|
||||
this.barCtx.series[i][j] === null
|
||||
) {
|
||||
this.barCtx.isNullValue = true
|
||||
} else {
|
||||
this.barCtx.isNullValue = false
|
||||
}
|
||||
if (w.config.stroke.show) {
|
||||
if (!this.barCtx.isNullValue) {
|
||||
strokeWidth = Array.isArray(this.barCtx.strokeWidth)
|
||||
? this.barCtx.strokeWidth[realIndex]
|
||||
: this.barCtx.strokeWidth
|
||||
}
|
||||
}
|
||||
return strokeWidth
|
||||
}
|
||||
|
||||
barBackground({ j, i, x1, x2, y1, y2, elSeries }) {
|
||||
const w = this.w
|
||||
const graphics = new Graphics(this.barCtx.ctx)
|
||||
|
||||
const sr = new Series(this.barCtx.ctx)
|
||||
let activeSeriesIndex = sr.getActiveConfigSeriesIndex()
|
||||
|
||||
if (
|
||||
this.barCtx.barOptions.colors.backgroundBarColors.length > 0 &&
|
||||
activeSeriesIndex === i
|
||||
) {
|
||||
if (j >= this.barCtx.barOptions.colors.backgroundBarColors.length) {
|
||||
j %= this.barCtx.barOptions.colors.backgroundBarColors.length
|
||||
}
|
||||
|
||||
let bcolor = this.barCtx.barOptions.colors.backgroundBarColors[j]
|
||||
let rect = graphics.drawRect(
|
||||
typeof x1 !== 'undefined' ? x1 : 0,
|
||||
typeof y1 !== 'undefined' ? y1 : 0,
|
||||
typeof x2 !== 'undefined' ? x2 : w.globals.gridWidth,
|
||||
typeof y2 !== 'undefined' ? y2 : w.globals.gridHeight,
|
||||
this.barCtx.barOptions.colors.backgroundBarRadius,
|
||||
bcolor,
|
||||
this.barCtx.barOptions.colors.backgroundBarOpacity
|
||||
)
|
||||
elSeries.add(rect)
|
||||
rect.node.classList.add('apexcharts-backgroundBar')
|
||||
}
|
||||
}
|
||||
|
||||
getColumnPaths({
|
||||
barWidth,
|
||||
barXPosition,
|
||||
yRatio,
|
||||
y1,
|
||||
y2,
|
||||
strokeWidth,
|
||||
series,
|
||||
realIndex,
|
||||
i,
|
||||
j,
|
||||
w
|
||||
}) {
|
||||
const graphics = new Graphics(this.barCtx.ctx)
|
||||
strokeWidth = Array.isArray(strokeWidth)
|
||||
? strokeWidth[realIndex]
|
||||
: strokeWidth
|
||||
if (!strokeWidth) strokeWidth = 0
|
||||
|
||||
let shapeOpts = {
|
||||
barWidth,
|
||||
strokeWidth,
|
||||
yRatio,
|
||||
barXPosition,
|
||||
y1,
|
||||
y2
|
||||
}
|
||||
let newPath = this.getRoundedBars(w, shapeOpts, series, i, j)
|
||||
|
||||
const x1 = barXPosition
|
||||
const x2 = barXPosition + barWidth
|
||||
|
||||
let pathTo = graphics.move(x1, y1)
|
||||
let pathFrom = graphics.move(x1, y1)
|
||||
|
||||
const sl = graphics.line(x2 - strokeWidth, y1)
|
||||
if (w.globals.previousPaths.length > 0) {
|
||||
pathFrom = this.barCtx.getPreviousPath(realIndex, j, false)
|
||||
}
|
||||
|
||||
pathTo =
|
||||
pathTo +
|
||||
graphics.line(x1, newPath.y2) +
|
||||
newPath.pathWithRadius +
|
||||
graphics.line(x2 - strokeWidth, newPath.y2) +
|
||||
sl +
|
||||
sl +
|
||||
'z'
|
||||
|
||||
// the lines in pathFrom are repeated to equal it to the points of pathTo
|
||||
// this is to avoid weird animation (bug in svg.js)
|
||||
pathFrom =
|
||||
pathFrom +
|
||||
graphics.line(x1, y1) +
|
||||
sl +
|
||||
sl +
|
||||
sl +
|
||||
sl +
|
||||
sl +
|
||||
graphics.line(x1, y1)
|
||||
|
||||
if (w.config.chart.stacked) {
|
||||
this.barCtx.yArrj.push(newPath.y2)
|
||||
this.barCtx.yArrjF.push(Math.abs(y1 - newPath.y2))
|
||||
this.barCtx.yArrjVal.push(this.barCtx.series[i][j])
|
||||
}
|
||||
|
||||
return {
|
||||
pathTo,
|
||||
pathFrom
|
||||
}
|
||||
}
|
||||
|
||||
getBarpaths({
|
||||
barYPosition,
|
||||
barHeight,
|
||||
x1,
|
||||
x2,
|
||||
strokeWidth,
|
||||
series,
|
||||
realIndex,
|
||||
i,
|
||||
j,
|
||||
w
|
||||
}) {
|
||||
const graphics = new Graphics(this.barCtx.ctx)
|
||||
strokeWidth = Array.isArray(strokeWidth)
|
||||
? strokeWidth[realIndex]
|
||||
: strokeWidth
|
||||
if (!strokeWidth) strokeWidth = 0
|
||||
|
||||
let shapeOpts = {
|
||||
barHeight,
|
||||
strokeWidth,
|
||||
barYPosition,
|
||||
x2,
|
||||
x1
|
||||
}
|
||||
|
||||
let newPath = this.getRoundedBars(w, shapeOpts, series, i, j)
|
||||
|
||||
let pathTo = graphics.move(x1, barYPosition)
|
||||
let pathFrom = graphics.move(x1, barYPosition)
|
||||
|
||||
if (w.globals.previousPaths.length > 0) {
|
||||
pathFrom = this.barCtx.getPreviousPath(realIndex, j, false)
|
||||
}
|
||||
|
||||
const y1 = barYPosition
|
||||
const y2 = barYPosition + barHeight
|
||||
|
||||
const sl = graphics.line(x1, y2 - strokeWidth)
|
||||
pathTo =
|
||||
pathTo +
|
||||
graphics.line(newPath.x2, y1) +
|
||||
newPath.pathWithRadius +
|
||||
graphics.line(newPath.x2, y2 - strokeWidth) +
|
||||
sl +
|
||||
sl +
|
||||
'z'
|
||||
|
||||
pathFrom =
|
||||
pathFrom +
|
||||
graphics.line(x1, y1) +
|
||||
sl +
|
||||
sl +
|
||||
sl +
|
||||
sl +
|
||||
sl +
|
||||
graphics.line(x1, y1)
|
||||
|
||||
if (w.config.chart.stacked) {
|
||||
this.barCtx.xArrj.push(newPath.x2)
|
||||
this.barCtx.xArrjF.push(Math.abs(x1 - newPath.x2))
|
||||
this.barCtx.xArrjVal.push(this.barCtx.series[i][j])
|
||||
}
|
||||
return {
|
||||
pathTo,
|
||||
pathFrom
|
||||
}
|
||||
}
|
||||
|
||||
/** getRoundedBars draws border radius for bars/columns
|
||||
* @memberof Bar
|
||||
* @param {object} w - chart context
|
||||
* @param {object} opts - consists several properties like barHeight/barWidth
|
||||
* @param {array} series - global primary series
|
||||
* @param {int} i - current iterating series's index
|
||||
* @param {int} j - series's j of i
|
||||
* @return {object} pathWithRadius - ending shape path string
|
||||
* newY/newX - which is calculated from existing x/y based on rounded border
|
||||
**/
|
||||
getRoundedBars(w, opts, series, i, j) {
|
||||
let graphics = new Graphics(this.barCtx.ctx)
|
||||
let radius = 0
|
||||
|
||||
const borderRadius = w.config.plotOptions.bar.borderRadius
|
||||
const borderRadiusIsArray = Array.isArray(borderRadius)
|
||||
if (borderRadiusIsArray) {
|
||||
const radiusIndex =
|
||||
i > borderRadius.length - 1 ? borderRadius.length - 1 : i
|
||||
radius = borderRadius[radiusIndex]
|
||||
} else {
|
||||
radius = borderRadius
|
||||
}
|
||||
|
||||
if (
|
||||
w.config.chart.stacked &&
|
||||
series.length > 1 &&
|
||||
i !== this.barCtx.radiusOnSeriesNumber &&
|
||||
!borderRadiusIsArray
|
||||
) {
|
||||
radius = 0
|
||||
}
|
||||
|
||||
if (this.barCtx.isHorizontal) {
|
||||
let pathWithRadius = ''
|
||||
let x2 = opts.x2
|
||||
|
||||
if (Math.abs(opts.x1 - opts.x2) < radius) {
|
||||
radius = Math.abs(opts.x1 - opts.x2)
|
||||
}
|
||||
|
||||
if (typeof series[i][j] !== 'undefined' || series[i][j] !== null) {
|
||||
let inverse = this.barCtx.isReversed
|
||||
? series[i][j] > 0
|
||||
: series[i][j] < 0
|
||||
|
||||
if (inverse) radius = radius * -1
|
||||
|
||||
x2 = x2 - radius
|
||||
|
||||
pathWithRadius =
|
||||
graphics.quadraticCurve(
|
||||
x2 + radius,
|
||||
opts.barYPosition,
|
||||
x2 + radius,
|
||||
opts.barYPosition + (!inverse ? radius : radius * -1)
|
||||
) +
|
||||
graphics.line(
|
||||
x2 + radius,
|
||||
opts.barYPosition +
|
||||
opts.barHeight -
|
||||
opts.strokeWidth -
|
||||
(!inverse ? radius : radius * -1)
|
||||
) +
|
||||
graphics.quadraticCurve(
|
||||
x2 + radius,
|
||||
opts.barYPosition + opts.barHeight - opts.strokeWidth,
|
||||
x2,
|
||||
opts.barYPosition + opts.barHeight - opts.strokeWidth
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
pathWithRadius,
|
||||
x2
|
||||
}
|
||||
} else {
|
||||
let pathWithRadius = ''
|
||||
let y2 = opts.y2
|
||||
|
||||
if (Math.abs(opts.y1 - opts.y2) < radius) {
|
||||
radius = Math.abs(opts.y1 - opts.y2)
|
||||
}
|
||||
|
||||
if (typeof series[i][j] !== 'undefined' || series[i][j] !== null) {
|
||||
let inverse = series[i][j] < 0
|
||||
|
||||
if (inverse) radius = radius * -1
|
||||
|
||||
y2 = y2 + radius
|
||||
|
||||
pathWithRadius =
|
||||
graphics.quadraticCurve(
|
||||
opts.barXPosition,
|
||||
y2 - radius,
|
||||
opts.barXPosition + (!inverse ? radius : radius * -1),
|
||||
y2 - radius
|
||||
) +
|
||||
graphics.line(
|
||||
opts.barXPosition +
|
||||
opts.barWidth -
|
||||
opts.strokeWidth -
|
||||
(!inverse ? radius : radius * -1),
|
||||
y2 - radius
|
||||
) +
|
||||
graphics.quadraticCurve(
|
||||
opts.barXPosition + opts.barWidth - opts.strokeWidth,
|
||||
y2 - radius,
|
||||
opts.barXPosition + opts.barWidth - opts.strokeWidth,
|
||||
y2
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
pathWithRadius,
|
||||
y2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkZeroSeries({ series }) {
|
||||
let w = this.w
|
||||
for (let zs = 0; zs < series.length; zs++) {
|
||||
let total = 0
|
||||
for (
|
||||
let zsj = 0;
|
||||
zsj < series[w.globals.maxValsInArrayIndex].length;
|
||||
zsj++
|
||||
) {
|
||||
total += series[zs][zsj]
|
||||
}
|
||||
if (total === 0) {
|
||||
this.barCtx.zeroSerieses.push(zs)
|
||||
}
|
||||
}
|
||||
|
||||
// After getting all zeroserieses, we need to ensure whether radiusOnSeriesNumber is not in that zeroseries array
|
||||
for (let s = series.length - 1; s >= 0; s--) {
|
||||
if (
|
||||
this.barCtx.zeroSerieses.indexOf(s) > -1 &&
|
||||
s === this.radiusOnSeriesNumber
|
||||
) {
|
||||
this.barCtx.radiusOnSeriesNumber -= 1
|
||||
}
|
||||
}
|
||||
|
||||
for (let s = series.length - 1; s >= 0; s--) {
|
||||
if (
|
||||
w.globals.collapsedSeriesIndices.indexOf(
|
||||
this.barCtx.radiusOnSeriesNumber
|
||||
) > -1
|
||||
) {
|
||||
this.barCtx.radiusOnSeriesNumber -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getXForValue(value, zeroW, zeroPositionForNull = true) {
|
||||
let xForVal = zeroPositionForNull ? zeroW : null
|
||||
if (typeof value !== 'undefined' && value !== null) {
|
||||
xForVal =
|
||||
zeroW +
|
||||
value / this.barCtx.invertedYRatio -
|
||||
(this.barCtx.isReversed ? value / this.barCtx.invertedYRatio : 0) * 2
|
||||
}
|
||||
return xForVal
|
||||
}
|
||||
|
||||
getYForValue(value, zeroH, zeroPositionForNull = true) {
|
||||
let yForVal = zeroPositionForNull ? zeroH : null
|
||||
if (typeof value !== 'undefined' && value !== null) {
|
||||
yForVal =
|
||||
zeroH -
|
||||
value / this.barCtx.yRatio[this.barCtx.yaxisIndex] +
|
||||
(this.barCtx.isReversed
|
||||
? value / this.barCtx.yRatio[this.barCtx.yaxisIndex]
|
||||
: 0) *
|
||||
2
|
||||
}
|
||||
return yForVal
|
||||
}
|
||||
|
||||
getGoalValues(type, zeroW, zeroH, i, j) {
|
||||
const w = this.w
|
||||
|
||||
let goals = []
|
||||
if (
|
||||
w.globals.seriesGoals[i] &&
|
||||
w.globals.seriesGoals[i][j] &&
|
||||
Array.isArray(w.globals.seriesGoals[i][j])
|
||||
) {
|
||||
w.globals.seriesGoals[i][j].forEach((goal) => {
|
||||
goals.push({
|
||||
[type]:
|
||||
type === 'x'
|
||||
? this.getXForValue(goal.value, zeroW, false)
|
||||
: this.getYForValue(goal.value, zeroH, false),
|
||||
attrs: goal
|
||||
})
|
||||
})
|
||||
}
|
||||
return goals
|
||||
}
|
||||
|
||||
drawGoalLine({
|
||||
barXPosition,
|
||||
barYPosition,
|
||||
goalX,
|
||||
goalY,
|
||||
barWidth,
|
||||
barHeight
|
||||
}) {
|
||||
let graphics = new Graphics(this.barCtx.ctx)
|
||||
const lineGroup = graphics.group({
|
||||
className: 'apexcharts-bar-goals-groups'
|
||||
})
|
||||
|
||||
let line = null
|
||||
if (this.barCtx.isHorizontal) {
|
||||
if (Array.isArray(goalX)) {
|
||||
goalX.forEach((goal) => {
|
||||
let sHeight =
|
||||
typeof goal.attrs.strokeHeight !== 'undefined'
|
||||
? goal.attrs.strokeHeight
|
||||
: barHeight / 2
|
||||
let y = barYPosition + sHeight + barHeight / 2
|
||||
|
||||
line = graphics.drawLine(
|
||||
goal.x,
|
||||
y - sHeight * 2,
|
||||
goal.x,
|
||||
y,
|
||||
goal.attrs.strokeColor ? goal.attrs.strokeColor : undefined,
|
||||
goal.attrs.strokeDashArray,
|
||||
goal.attrs.strokeWidth ? goal.attrs.strokeWidth : 2,
|
||||
goal.attrs.strokeLineCap
|
||||
)
|
||||
lineGroup.add(line)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (Array.isArray(goalY)) {
|
||||
goalY.forEach((goal) => {
|
||||
let sWidth =
|
||||
typeof goal.attrs.strokeWidth !== 'undefined'
|
||||
? goal.attrs.strokeWidth
|
||||
: barWidth / 2
|
||||
let x = barXPosition + sWidth + barWidth / 2
|
||||
|
||||
line = graphics.drawLine(
|
||||
x - sWidth * 2,
|
||||
goal.y,
|
||||
x,
|
||||
goal.y,
|
||||
goal.attrs.strokeColor ? goal.attrs.strokeColor : undefined,
|
||||
goal.attrs.strokeDashArray,
|
||||
goal.attrs.strokeHeight ? goal.attrs.strokeHeight : 2,
|
||||
goal.attrs.strokeLineCap
|
||||
)
|
||||
lineGroup.add(line)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return lineGroup
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import Graphics from '../../../modules/Graphics'
|
||||
|
||||
export default class CircularChartsHelpers {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
}
|
||||
|
||||
drawYAxisTexts(x, y, i, text) {
|
||||
const w = this.w
|
||||
|
||||
const yaxisConfig = w.config.yaxis[0]
|
||||
const formatter = w.globals.yLabelFormatters[0]
|
||||
|
||||
const graphics = new Graphics(this.ctx)
|
||||
const yaxisLabel = graphics.drawText({
|
||||
x: x + yaxisConfig.labels.offsetX,
|
||||
y: y + yaxisConfig.labels.offsetY,
|
||||
text: formatter(text, i),
|
||||
textAnchor: 'middle',
|
||||
fontSize: yaxisConfig.labels.style.fontSize,
|
||||
fontFamily: yaxisConfig.labels.style.fontFamily,
|
||||
foreColor: Array.isArray(yaxisConfig.labels.style.colors)
|
||||
? yaxisConfig.labels.style.colors[i]
|
||||
: yaxisConfig.labels.style.colors
|
||||
})
|
||||
|
||||
return yaxisLabel
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
import CoreUtils from '../../../modules/CoreUtils'
|
||||
import Utils from '../../../utils/Utils'
|
||||
|
||||
export default class Helpers {
|
||||
constructor(lineCtx) {
|
||||
this.w = lineCtx.w
|
||||
this.lineCtx = lineCtx
|
||||
}
|
||||
|
||||
sameValueSeriesFix(i, series) {
|
||||
const w = this.w
|
||||
|
||||
if (
|
||||
w.config.chart.type === 'line' &&
|
||||
(w.config.fill.type === 'gradient' ||
|
||||
w.config.fill.type[i] === 'gradient')
|
||||
) {
|
||||
const coreUtils = new CoreUtils(this.lineCtx.ctx, w)
|
||||
|
||||
// a small adjustment to allow gradient line to draw correctly for all same values
|
||||
/* #fix https://github.com/apexcharts/apexcharts.js/issues/358 */
|
||||
if (coreUtils.seriesHaveSameValues(i)) {
|
||||
let gSeries = series[i].slice()
|
||||
gSeries[gSeries.length - 1] = gSeries[gSeries.length - 1] + 0.000001
|
||||
series[i] = gSeries
|
||||
}
|
||||
}
|
||||
return series
|
||||
}
|
||||
|
||||
calculatePoints({ series, realIndex, x, y, i, j, prevY }) {
|
||||
let w = this.w
|
||||
|
||||
let ptX = []
|
||||
let ptY = []
|
||||
|
||||
if (j === 0) {
|
||||
let xPT1st =
|
||||
this.lineCtx.categoryAxisCorrection + w.config.markers.offsetX
|
||||
// the first point for line series
|
||||
// we need to check whether it's not a time series, because a time series may
|
||||
// start from the middle of the x axis
|
||||
if (w.globals.isXNumeric) {
|
||||
xPT1st =
|
||||
(w.globals.seriesX[realIndex][0] - w.globals.minX) /
|
||||
this.lineCtx.xRatio +
|
||||
w.config.markers.offsetX
|
||||
}
|
||||
|
||||
// push 2 points for the first data values
|
||||
ptX.push(xPT1st)
|
||||
ptY.push(
|
||||
Utils.isNumber(series[i][0]) ? prevY + w.config.markers.offsetY : null
|
||||
)
|
||||
ptX.push(x + w.config.markers.offsetX)
|
||||
ptY.push(
|
||||
Utils.isNumber(series[i][j + 1]) ? y + w.config.markers.offsetY : null
|
||||
)
|
||||
} else {
|
||||
ptX.push(x + w.config.markers.offsetX)
|
||||
ptY.push(
|
||||
Utils.isNumber(series[i][j + 1]) ? y + w.config.markers.offsetY : null
|
||||
)
|
||||
}
|
||||
|
||||
let pointsPos = {
|
||||
x: ptX,
|
||||
y: ptY
|
||||
}
|
||||
|
||||
return pointsPos
|
||||
}
|
||||
|
||||
checkPreviousPaths({ pathFromLine, pathFromArea, realIndex }) {
|
||||
let w = this.w
|
||||
|
||||
for (let pp = 0; pp < w.globals.previousPaths.length; pp++) {
|
||||
let gpp = w.globals.previousPaths[pp]
|
||||
|
||||
if (
|
||||
(gpp.type === 'line' || gpp.type === 'area') &&
|
||||
gpp.paths.length > 0 &&
|
||||
parseInt(gpp.realIndex, 10) === parseInt(realIndex, 10)
|
||||
) {
|
||||
if (gpp.type === 'line') {
|
||||
this.lineCtx.appendPathFrom = false
|
||||
pathFromLine = w.globals.previousPaths[pp].paths[0].d
|
||||
} else if (gpp.type === 'area') {
|
||||
this.lineCtx.appendPathFrom = false
|
||||
pathFromArea = w.globals.previousPaths[pp].paths[0].d
|
||||
|
||||
if (w.config.stroke.show && w.globals.previousPaths[pp].paths[1]) {
|
||||
pathFromLine = w.globals.previousPaths[pp].paths[1].d
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
pathFromLine,
|
||||
pathFromArea
|
||||
}
|
||||
}
|
||||
|
||||
determineFirstPrevY({ i, series, prevY, lineYPosition }) {
|
||||
let w = this.w
|
||||
if (typeof series[i][0] !== 'undefined') {
|
||||
if (w.config.chart.stacked) {
|
||||
if (i > 0) {
|
||||
// 1st y value of previous series
|
||||
lineYPosition = this.lineCtx.prevSeriesY[i - 1][0]
|
||||
} else {
|
||||
// the first series will not have prevY values
|
||||
lineYPosition = this.lineCtx.zeroY
|
||||
}
|
||||
} else {
|
||||
lineYPosition = this.lineCtx.zeroY
|
||||
}
|
||||
prevY =
|
||||
lineYPosition -
|
||||
series[i][0] / this.lineCtx.yRatio[this.lineCtx.yaxisIndex] +
|
||||
(this.lineCtx.isReversed
|
||||
? series[i][0] / this.lineCtx.yRatio[this.lineCtx.yaxisIndex]
|
||||
: 0) *
|
||||
2
|
||||
} else {
|
||||
// the first value in the current series is null
|
||||
if (
|
||||
w.config.chart.stacked &&
|
||||
i > 0 &&
|
||||
typeof series[i][0] === 'undefined'
|
||||
) {
|
||||
// check for undefined value (undefined value will occur when we clear the series while user clicks on legend to hide serieses)
|
||||
for (let s = i - 1; s >= 0; s--) {
|
||||
// for loop to get to 1st previous value until we get it
|
||||
if (series[s][0] !== null && typeof series[s][0] !== 'undefined') {
|
||||
lineYPosition = this.lineCtx.prevSeriesY[s][0]
|
||||
prevY = lineYPosition
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
prevY,
|
||||
lineYPosition
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
import Utils from '../../../utils/Utils'
|
||||
import Graphics from '../../../modules/Graphics'
|
||||
import DataLabels from '../../../modules/DataLabels'
|
||||
|
||||
export default class TreemapHelpers {
|
||||
constructor(ctx) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
}
|
||||
|
||||
checkColorRange() {
|
||||
const w = this.w
|
||||
|
||||
let negRange = false
|
||||
let chartOpts = w.config.plotOptions[w.config.chart.type]
|
||||
|
||||
if (chartOpts.colorScale.ranges.length > 0) {
|
||||
chartOpts.colorScale.ranges.map((range, index) => {
|
||||
if (range.from <= 0) {
|
||||
negRange = true
|
||||
}
|
||||
})
|
||||
}
|
||||
return negRange
|
||||
}
|
||||
|
||||
getShadeColor(chartType, i, j, negRange) {
|
||||
const w = this.w
|
||||
|
||||
let colorShadePercent = 1
|
||||
let shadeIntensity = w.config.plotOptions[chartType].shadeIntensity
|
||||
|
||||
const colorProps = this.determineColor(chartType, i, j)
|
||||
|
||||
if (w.globals.hasNegs || negRange) {
|
||||
if (w.config.plotOptions[chartType].reverseNegativeShade) {
|
||||
if (colorProps.percent < 0) {
|
||||
colorShadePercent =
|
||||
(colorProps.percent / 100) * (shadeIntensity * 1.25)
|
||||
} else {
|
||||
colorShadePercent =
|
||||
(1 - colorProps.percent / 100) * (shadeIntensity * 1.25)
|
||||
}
|
||||
} else {
|
||||
if (colorProps.percent <= 0) {
|
||||
colorShadePercent =
|
||||
1 - (1 + colorProps.percent / 100) * shadeIntensity
|
||||
} else {
|
||||
colorShadePercent = (1 - colorProps.percent / 100) * shadeIntensity
|
||||
}
|
||||
}
|
||||
} else {
|
||||
colorShadePercent = 1 - colorProps.percent / 100
|
||||
if (chartType === 'treemap') {
|
||||
colorShadePercent =
|
||||
(1 - colorProps.percent / 100) * (shadeIntensity * 1.25)
|
||||
}
|
||||
}
|
||||
|
||||
let color = colorProps.color
|
||||
let utils = new Utils()
|
||||
|
||||
if (w.config.plotOptions[chartType].enableShades) {
|
||||
if (this.w.config.theme.mode === 'dark') {
|
||||
color = Utils.hexToRgba(
|
||||
utils.shadeColor(colorShadePercent * -1, colorProps.color),
|
||||
w.config.fill.opacity
|
||||
)
|
||||
} else {
|
||||
color = Utils.hexToRgba(
|
||||
utils.shadeColor(colorShadePercent, colorProps.color),
|
||||
w.config.fill.opacity
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return { color, colorProps }
|
||||
}
|
||||
|
||||
determineColor(chartType, i, j) {
|
||||
const w = this.w
|
||||
|
||||
let val = w.globals.series[i][j]
|
||||
|
||||
let chartOpts = w.config.plotOptions[chartType]
|
||||
|
||||
let seriesNumber = chartOpts.colorScale.inverse ? j : i
|
||||
|
||||
if (chartOpts.distributed && w.config.chart.type === 'treemap') {
|
||||
seriesNumber = j
|
||||
}
|
||||
|
||||
let color = w.globals.colors[seriesNumber]
|
||||
let foreColor = null
|
||||
let min = Math.min(...w.globals.series[i])
|
||||
let max = Math.max(...w.globals.series[i])
|
||||
|
||||
if (!chartOpts.distributed && chartType === 'heatmap') {
|
||||
min = w.globals.minY
|
||||
max = w.globals.maxY
|
||||
}
|
||||
|
||||
if (typeof chartOpts.colorScale.min !== 'undefined') {
|
||||
min =
|
||||
chartOpts.colorScale.min < w.globals.minY
|
||||
? chartOpts.colorScale.min
|
||||
: w.globals.minY
|
||||
max =
|
||||
chartOpts.colorScale.max > w.globals.maxY
|
||||
? chartOpts.colorScale.max
|
||||
: w.globals.maxY
|
||||
}
|
||||
|
||||
let total = Math.abs(max) + Math.abs(min)
|
||||
|
||||
let percent = (100 * val) / (total === 0 ? total - 0.000001 : total)
|
||||
|
||||
if (chartOpts.colorScale.ranges.length > 0) {
|
||||
const colorRange = chartOpts.colorScale.ranges
|
||||
colorRange.map((range, index) => {
|
||||
if (val >= range.from && val <= range.to) {
|
||||
color = range.color
|
||||
foreColor = range.foreColor ? range.foreColor : null
|
||||
min = range.from
|
||||
max = range.to
|
||||
let rTotal = Math.abs(max) + Math.abs(min)
|
||||
percent = (100 * val) / (rTotal === 0 ? rTotal - 0.000001 : rTotal)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
color,
|
||||
foreColor,
|
||||
percent
|
||||
}
|
||||
}
|
||||
|
||||
calculateDataLabels({ text, x, y, i, j, colorProps, fontSize }) {
|
||||
let w = this.w
|
||||
let dataLabelsConfig = w.config.dataLabels
|
||||
|
||||
const graphics = new Graphics(this.ctx)
|
||||
|
||||
let dataLabels = new DataLabels(this.ctx)
|
||||
|
||||
let elDataLabelsWrap = null
|
||||
|
||||
if (dataLabelsConfig.enabled) {
|
||||
elDataLabelsWrap = graphics.group({
|
||||
class: 'apexcharts-data-labels'
|
||||
})
|
||||
|
||||
const offX = dataLabelsConfig.offsetX
|
||||
const offY = dataLabelsConfig.offsetY
|
||||
|
||||
let dataLabelsX = x + offX
|
||||
let dataLabelsY =
|
||||
y + parseFloat(dataLabelsConfig.style.fontSize) / 3 + offY
|
||||
|
||||
dataLabels.plotDataLabelsText({
|
||||
x: dataLabelsX,
|
||||
y: dataLabelsY,
|
||||
text,
|
||||
i,
|
||||
j,
|
||||
color: colorProps.foreColor,
|
||||
parent: elDataLabelsWrap,
|
||||
fontSize,
|
||||
dataLabelsConfig
|
||||
})
|
||||
}
|
||||
|
||||
return elDataLabelsWrap
|
||||
}
|
||||
|
||||
addListeners(elRect) {
|
||||
const graphics = new Graphics(this.ctx)
|
||||
elRect.node.addEventListener(
|
||||
'mouseenter',
|
||||
graphics.pathMouseEnter.bind(this, elRect)
|
||||
)
|
||||
elRect.node.addEventListener(
|
||||
'mouseleave',
|
||||
graphics.pathMouseLeave.bind(this, elRect)
|
||||
)
|
||||
elRect.node.addEventListener(
|
||||
'mousedown',
|
||||
graphics.pathMouseDown.bind(this, elRect)
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user