Initial commit: igny8 project

This commit is contained in:
igny8
2025-11-09 10:27:02 +00:00
commit 60b8188111
27265 changed files with 4360521 additions and 0 deletions

679
frontend/node_modules/apexcharts/src/charts/Bar.js generated vendored Normal file
View File

@@ -0,0 +1,679 @@
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'
import Series from '../modules/Series'
/**
* 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.seriesRange.length && this.isHorizontal
this.isVerticalGroupedRangeBar =
!w.globals.isBarHorizontal &&
w.globals.seriesRange.length &&
w.config.plotOptions.bar.rangeBarGroupRows
this.isFunnel = this.barOptions.isFunnel
this.xyRatios = xyRatios
if (this.xyRatios !== null) {
this.xRatio = xyRatios.xRatio
this.yRatio = xyRatios.yRatio
this.invertedXRatio = xyRatios.invertedXRatio
this.invertedYRatio = xyRatios.invertedYRatio
this.baseLineY = xyRatios.baseLineY
this.baseLineInvertedY = xyRatios.baseLineInvertedY
}
this.yaxisIndex = 0
this.translationsIndex = 0
this.seriesLen = 0
this.pathArr = []
const ser = new Series(this.ctx)
this.lastActiveBarSerieIndex = ser.getActiveConfigSeriesIndex('desc', [
'bar',
'column',
])
this.columnGroupIndices = []
const barSeriesIndices = ser.getBarSeriesIndices()
const coreUtils = new CoreUtils(this.ctx)
this.stackedSeriesTotals = coreUtils.getStackedSeriesTotals(
this.w.config.series
.map((s, i) => {
return barSeriesIndices.indexOf(i) === -1 ? i : -1
})
.filter((s) => {
return s !== -1
})
)
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 - ApexCharts'
)
}
}
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
let { columnGroupIndex } = this.barHelpers.getGroupIndex(realIndex)
// 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 = w.globals.seriesYAxisReverseMap[realIndex]
this.translationsIndex = realIndex
}
let translationsIndex = this.translationsIndex
this.isReversed =
w.config.yaxis[this.yaxisIndex] &&
w.config.yaxis[this.yaxisIndex].reversed
let initPositions = this.barHelpers.initialPositions(realIndex)
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.isHorizontal) {
xArrj.push(x + barWidth / 2)
}
// eldatalabels
let elDataLabelsWrap = graphics.group({
class: 'apexcharts-datalabels',
'data:realIndex': realIndex,
})
w.globals.delayedElements.push({
el: elDataLabelsWrap.node,
})
elDataLabelsWrap.node.classList.add('apexcharts-element-hidden')
let elGoalsMarkers = graphics.group({
class: 'apexcharts-bar-goals-markers',
})
let elBarShadows = graphics.group({
class: 'apexcharts-bar-shadows',
})
w.globals.delayedElements.push({
el: elBarShadows.node,
})
elBarShadows.node.classList.add('apexcharts-element-hidden')
for (let j = 0; j < series[i].length; j++) {
const strokeWidth = this.barHelpers.getStrokeWidth(i, j, realIndex)
let paths = null
const pathsParams = {
indexes: {
i,
j,
realIndex,
translationsIndex,
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[translationsIndex]
}
let pathFill = this.barHelpers.getPathFillColor(series, i, j, realIndex)
if (
this.isFunnel &&
this.barOptions.isFunnel3d &&
this.pathArr.length &&
j > 0
) {
const barShadow = this.barHelpers.drawBarShadow({
color:
typeof pathFill.color === 'string' &&
pathFill.color?.indexOf('url') === -1
? pathFill.color
: Utils.hexToRgba(w.globals.colors[i]),
prevPaths: this.pathArr[this.pathArr.length - 1],
currPaths: paths,
})
elBarShadows.add(barShadow)
if (w.config.chart.dropShadow.enabled) {
const filters = new Filters(this.ctx)
filters.dropShadow(barShadow, w.config.chart.dropShadow, realIndex)
}
}
this.pathArr.push(paths)
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)
this.renderSeries({
realIndex,
pathFill: pathFill.color,
...(pathFill.useRangeColor ? { lineFill: pathFill.color } : {}),
j,
i,
columnGroupIndex,
pathFrom: paths.pathFrom,
pathTo: paths.pathTo,
strokeWidth,
elSeries,
x,
y,
series,
barHeight: Math.abs(paths.barHeight ? paths.barHeight : barHeight),
barWidth: Math.abs(paths.barWidth ? paths.barWidth : barWidth),
elDataLabelsWrap,
elGoalsMarkers,
elBarShadows,
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,
columnGroupIndex,
pathFrom,
pathTo,
strokeWidth,
elSeries,
x, // x pos
y, // y pos
y1, // absolute value
y2, // absolute value
series,
barHeight,
barWidth,
barXPosition,
barYPosition,
elDataLabelsWrap,
elGoalsMarkers,
elBarShadows,
visibleSeries,
type,
classes,
}) {
const w = this.w
const graphics = new Graphics(this.ctx)
if (!lineFill) {
// if user provided a function in colors, we need to eval here
// Note: the position of this function logic (ex. stroke: { colors: ["",function(){}] }) i.e array index 1 depicts the realIndex/seriesIndex.
function fetchColor(i) {
const exp = w.config.stroke.colors
let c
if (Array.isArray(exp) && exp.length > 0) {
c = exp[i]
if (!c) c = ''
if (typeof c === 'function') {
return c({
value: w.globals.series[i][j],
dataPointIndex: j,
w,
})
}
}
return c
}
const checkAvailableColor =
typeof w.globals.stroke.colors[realIndex] === 'function'
? fetchColor(realIndex)
: w.globals.stroke.colors[realIndex]
/* fix apexcharts#341 */
lineFill = this.barOptions.distributed
? w.globals.stroke.colors[j]
: checkAvailableColor
}
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 ${classes}`,
chartType: type,
})
renderedPath.attr('clip-path', `url(#gridRectBarMask${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 dataLabelsObj = barDataLabels.handleBarDataLabels({
x,
y,
y1,
y2,
i,
j,
series,
realIndex,
columnGroupIndex,
barHeight,
barWidth,
barXPosition,
barYPosition,
renderedPath,
visibleSeries,
})
if (dataLabelsObj.dataLabels !== null) {
elDataLabelsWrap.add(dataLabelsObj.dataLabels)
}
if (dataLabelsObj.totalDataLabels) {
elDataLabelsWrap.add(dataLabelsObj.totalDataLabels)
}
elSeries.add(elDataLabelsWrap)
if (elGoalsMarkers) {
elSeries.add(elGoalsMarkers)
}
if (elBarShadows) {
elSeries.add(elBarShadows)
}
return elSeries
}
drawBarPaths({
indexes,
barHeight,
strokeWidth,
zeroW,
x,
y,
yDivision,
elSeries,
}) {
let w = this.w
let i = indexes.i
let j = indexes.j
let barYPosition
if (w.globals.isXNumeric) {
y =
(w.globals.seriesX[i][j] - w.globals.minX) / this.invertedXRatio -
barHeight
barYPosition = y + barHeight * this.visibleI
} else {
if (w.config.plotOptions.bar.hideZeroBarsWhenGrouped) {
let nonZeroColumns = 0
let zeroEncounters = 0
w.globals.seriesPercent.forEach((_s, _si) => {
if (_s[j]) {
nonZeroColumns++
}
if (_si < i && _s[j] === 0) {
zeroEncounters++
}
})
if (nonZeroColumns > 0) {
barHeight = (this.seriesLen * barHeight) / nonZeroColumns
}
barYPosition = y + barHeight * this.visibleI
barYPosition -= barHeight * zeroEncounters
} else {
barYPosition = y + barHeight * this.visibleI
}
}
if (this.isFunnel) {
zeroW =
zeroW -
(this.barHelpers.getXForValue(this.series[i][j], zeroW) - zeroW) / 2
}
x = this.barHelpers.getXForValue(this.series[i][j], zeroW)
const paths = this.barHelpers.getBarpaths({
barYPosition,
barHeight,
x1: zeroW,
x2: x,
strokeWidth,
isReversed: this.isReversed,
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,
x1: zeroW,
x,
y,
goalX: this.barHelpers.getGoalValues('x', zeroW, null, i, j),
barYPosition,
barHeight,
}
}
drawColumnPaths({
indexes,
x,
y,
xDivision,
barWidth,
zeroH,
strokeWidth,
elSeries,
}) {
let w = this.w
let realIndex = indexes.realIndex
let translationsIndex = indexes.translationsIndex
let i = indexes.i
let j = indexes.j
let bc = indexes.bc
let barXPosition
if (w.globals.isXNumeric) {
const xForNumericX = this.getBarXForNumericXAxis({
x,
j,
realIndex,
barWidth,
})
x = xForNumericX.x
barXPosition = xForNumericX.barXPosition
} else {
if (w.config.plotOptions.bar.hideZeroBarsWhenGrouped) {
const { nonZeroColumns, zeroEncounters } =
this.barHelpers.getZeroValueEncounters({ i, j })
if (nonZeroColumns > 0) {
barWidth = (this.seriesLen * barWidth) / nonZeroColumns
}
barXPosition = x + barWidth * this.visibleI
barXPosition -= barWidth * zeroEncounters
} else {
barXPosition = x + barWidth * this.visibleI
}
}
y = this.barHelpers.getYForValue(
this.series[i][j],
zeroH,
translationsIndex
)
const paths = this.barHelpers.getColumnPaths({
barXPosition,
barWidth,
y1: zeroH,
y2: y,
strokeWidth,
isReversed: this.isReversed,
series: this.series,
realIndex: 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,
translationsIndex
),
barXPosition,
barWidth,
}
}
getBarXForNumericXAxis({ x, barWidth, realIndex, j }) {
const w = this.w
let sxI = realIndex
if (!w.globals.seriesX[realIndex].length) {
sxI = w.globals.maxValsInArrayIndex
}
if (Utils.isNumber(w.globals.seriesX[sxI][j])) {
x =
(w.globals.seriesX[sxI][j] - w.globals.minX) / this.xRatio -
(barWidth * this.seriesLen) / 2
}
return {
barXPosition: x + barWidth * this.visibleI,
x,
}
}
/** 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

View File

@@ -0,0 +1,567 @@
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.comboCharts
? seriesIndex.map((_) => w.globals.seriesPercent[_])
: w.globals.seriesPercent.slice()
}
this.series = series
this.barHelpers.initializeStackedPrevVars(this)
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 realIndex = w.globals.comboCharts ? seriesIndex[i] : i
let { groupIndex, columnGroupIndex } =
this.barHelpers.getGroupIndex(realIndex)
this.groupCtx = this[w.globals.seriesGroups[groupIndex]]
let xArrValues = []
let yArrValues = []
let translationsIndex = 0
if (this.yRatio.length > 1) {
this.yaxisIndex = w.globals.seriesYAxisReverseMap[realIndex][0]
translationsIndex = 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 elGoalsMarkers = this.graphics.group({
class: 'apexcharts-bar-goals-markers',
})
let barHeight = 0
let barWidth = 0
let initPositions = this.initialPositions(
x,
y,
xDivision,
yDivision,
zeroH,
zeroW,
translationsIndex
)
y = initPositions.y
barHeight = initPositions.barHeight
yDivision = initPositions.yDivision
zeroW = initPositions.zeroW
x = initPositions.x
barWidth = initPositions.barWidth
xDivision = initPositions.xDivision
zeroH = initPositions.zeroH
w.globals.barHeight = barHeight
w.globals.barWidth = barWidth
this.barHelpers.initializeStackedXYVars(this)
// where all stack bar disappear after collapsing the first series
if (
this.groupCtx.prevY.length === 1 &&
this.groupCtx.prevY[0].every((val) => isNaN(val))
) {
this.groupCtx.prevY[0] = this.groupCtx.prevY[0].map(() => zeroH)
this.groupCtx.prevYF[0] = this.groupCtx.prevYF[0].map(() => 0)
}
for (let j = 0; j < w.globals.dataPoints; j++) {
const strokeWidth = this.barHelpers.getStrokeWidth(i, j, realIndex)
const commonPathOpts = {
indexes: { i, j, realIndex, translationsIndex, bc },
strokeWidth,
x,
y,
elSeries,
columnGroupIndex,
seriesGroup: w.globals.seriesGroups[groupIndex],
}
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[translationsIndex]
}
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
xArrValues.push(x)
yArrValues.push(y)
let pathFill = this.barHelpers.getPathFillColor(series, i, j, realIndex)
let classes = ''
const flipClass = w.globals.isBarHorizontal
? 'apexcharts-flip-x'
: 'apexcharts-flip-y'
if (
(this.barHelpers.arrBorderRadius[realIndex][j] === 'bottom' &&
w.globals.series[realIndex][j] > 0) ||
(this.barHelpers.arrBorderRadius[realIndex][j] === 'top' &&
w.globals.series[realIndex][j] < 0)
) {
classes = flipClass
}
elSeries = this.renderSeries({
realIndex,
pathFill: pathFill.color,
...(pathFill.useRangeColor ? { lineFill: pathFill.color } : {}),
j,
i,
columnGroupIndex,
pathFrom: paths.pathFrom,
pathTo: paths.pathTo,
strokeWidth,
elSeries,
x,
y,
series,
barHeight,
barWidth,
elDataLabelsWrap,
elGoalsMarkers,
type: 'bar',
visibleSeries: columnGroupIndex,
classes,
})
}
// 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.groupCtx.prevY.push(this.groupCtx.yArrj)
this.groupCtx.prevYF.push(this.groupCtx.yArrjF)
this.groupCtx.prevYVal.push(this.groupCtx.yArrjVal)
this.groupCtx.prevX.push(this.groupCtx.xArrj)
this.groupCtx.prevXF.push(this.groupCtx.xArrjF)
this.groupCtx.prevXVal.push(this.groupCtx.xArrjVal)
ret.add(elSeries)
}
return ret
}
initialPositions(
x,
y,
xDivision,
yDivision,
zeroH,
zeroW,
translationsIndex
) {
let w = this.w
let barHeight, barWidth
if (this.isHorizontal) {
// height divided into equal parts
yDivision = w.globals.gridHeight / w.globals.dataPoints
let userBarHeight = w.config.plotOptions.bar.barHeight
if (String(userBarHeight).indexOf('%') === -1) {
barHeight = parseInt(userBarHeight, 10)
} else {
barHeight = (yDivision * parseInt(userBarHeight, 10)) / 100
}
zeroW =
w.globals.padHorizontal +
(this.isReversed
? w.globals.gridWidth - this.baseLineInvertedY
: this.baseLineInvertedY)
// 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
let userColumnWidth = w.config.plotOptions.bar.columnWidth
if (w.globals.isXNumeric && w.globals.dataPoints > 1) {
xDivision = w.globals.minXDiff / this.xRatio
barWidth = (xDivision * parseInt(this.barOptions.columnWidth, 10)) / 100
} else if (String(userColumnWidth).indexOf('%') === -1) {
barWidth = parseInt(userColumnWidth, 10)
} else {
barWidth *= parseInt(userColumnWidth, 10) / 100
}
if (this.isReversed) {
zeroH = this.baseLineY[translationsIndex]
} else {
zeroH = w.globals.gridHeight - this.baseLineY[translationsIndex]
}
// initial x position is the left-most edge of the first bar relative to
// the left-most side of the grid area.
x = w.globals.padHorizontal + (xDivision - barWidth) / 2
}
// Up to this point, barWidth is the width that will accommodate all bars
// at each datapoint or category.
// The crude subdivision here assumes the series within each group are
// stacked. If there is no stacking then the barWidth/barHeight is
// further divided later by the number of series in the group. So, eg, two
// groups of three series would become six bars side-by-side unstacked,
// or two bars stacked.
let subDivisions = w.globals.barGroups.length || 1
return {
x,
y,
yDivision,
xDivision,
barHeight: barHeight / subDivisions,
barWidth: barWidth / subDivisions,
zeroH,
zeroW,
}
}
drawStackedBarPaths({
indexes,
barHeight,
strokeWidth,
zeroW,
x,
y,
columnGroupIndex,
seriesGroup,
yDivision,
elSeries,
}) {
let w = this.w
let barYPosition = y + columnGroupIndex * barHeight
let barXPosition
let i = indexes.i
let j = indexes.j
let realIndex = indexes.realIndex
let translationsIndex = indexes.translationsIndex
let prevBarW = 0
for (let k = 0; k < this.groupCtx.prevXF.length; k++) {
prevBarW = prevBarW + this.groupCtx.prevXF[k][j]
}
let gsi = i // an index to keep track of the series inside a group
gsi = seriesGroup.indexOf(w.config.series[realIndex].name)
if (gsi > 0) {
let bXP = zeroW
if (this.groupCtx.prevXVal[gsi - 1][j] < 0) {
bXP =
this.series[i][j] >= 0
? this.groupCtx.prevX[gsi - 1][j] +
prevBarW -
(this.isReversed ? prevBarW : 0) * 2
: this.groupCtx.prevX[gsi - 1][j]
} else if (this.groupCtx.prevXVal[gsi - 1][j] >= 0) {
bXP =
this.series[i][j] >= 0
? this.groupCtx.prevX[gsi - 1][j]
: this.groupCtx.prevX[gsi - 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,
isReversed: this.isReversed,
series: this.series,
realIndex: indexes.realIndex,
seriesGroup,
i,
j,
w,
})
this.barHelpers.barBackground({
j,
i,
y1: barYPosition,
y2: barHeight,
elSeries,
})
y = y + yDivision
return {
pathTo: paths.pathTo,
pathFrom: paths.pathFrom,
goalX: this.barHelpers.getGoalValues(
'x',
zeroW,
null,
i,
j,
translationsIndex
),
barXPosition,
barYPosition,
x,
y,
}
}
drawStackedColumnPaths({
indexes,
x,
y,
xDivision,
barWidth,
zeroH,
columnGroupIndex,
seriesGroup,
elSeries,
}) {
let w = this.w
let i = indexes.i
let j = indexes.j
let bc = indexes.bc
let realIndex = indexes.realIndex
let translationsIndex = indexes.translationsIndex
if (w.globals.isXNumeric) {
let seriesVal = w.globals.seriesX[realIndex][j]
if (!seriesVal) seriesVal = 0
// TODO: move the barWidth factor to barXPosition
x =
(seriesVal - w.globals.minX) / this.xRatio -
(barWidth / 2) * w.globals.barGroups.length
}
let barXPosition = x + columnGroupIndex * barWidth
let barYPosition
let prevBarH = 0
for (let k = 0; k < this.groupCtx.prevYF.length; k++) {
// fix issue #1215
// in case where this.groupCtx.prevYF[k][j] is NaN, use 0 instead
prevBarH =
prevBarH +
(!isNaN(this.groupCtx.prevYF[k][j]) ? this.groupCtx.prevYF[k][j] : 0)
}
let gsi = i // an index to keep track of the series inside a group
if (seriesGroup) {
gsi = seriesGroup.indexOf(w.globals.seriesNames[realIndex])
}
if (
(gsi > 0 && !w.globals.isXNumeric) ||
(gsi > 0 &&
w.globals.isXNumeric &&
w.globals.seriesX[realIndex - 1][j] === w.globals.seriesX[realIndex][j])
) {
let bYP
let prevYValue
const p = Math.min(this.yRatio.length + 1, realIndex + 1)
if (
this.groupCtx.prevY[gsi - 1] !== undefined &&
this.groupCtx.prevY[gsi - 1].length
) {
for (let ii = 1; ii < p; ii++) {
if (!isNaN(this.groupCtx.prevY[gsi - ii]?.[j])) {
// find the previous available value to give prevYValue
prevYValue = this.groupCtx.prevY[gsi - 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.groupCtx.prevYVal[gsi - 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.groupCtx.prevYVal[gsi - 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.groupCtx.prevYF[0]?.every((val) => val === 0) &&
this.groupCtx.prevYF
.slice(1, gsi)
.every((arr) => arr.every((val) => isNaN(val)))
) {
barYPosition = 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 = zeroH
}
if (this.series[i][j]) {
y =
barYPosition -
this.series[i][j] / this.yRatio[translationsIndex] +
(this.isReversed
? this.series[i][j] / this.yRatio[translationsIndex]
: 0) *
2
} else {
// fixes #3610
y = barYPosition
}
const paths = this.barHelpers.getColumnPaths({
barXPosition,
barWidth,
y1: barYPosition,
y2: y,
yRatio: this.yRatio[translationsIndex],
strokeWidth: this.strokeWidth,
isReversed: this.isReversed,
series: this.series,
seriesGroup,
realIndex: indexes.realIndex,
i,
j,
w,
})
this.barHelpers.barBackground({
bc,
j,
i,
x1: barXPosition,
x2: barWidth,
elSeries,
})
return {
pathTo: paths.pathTo,
pathFrom: paths.pathFrom,
goalY: this.barHelpers.getGoalValues('y', null, zeroH, i, j),
barXPosition,
x: w.globals.isXNumeric ? x : x + xDivision,
y,
}
}
}
export default BarStacked

View File

@@ -0,0 +1,461 @@
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, ctype, seriesIndex) {
let w = this.w
let graphics = new Graphics(this.ctx)
let type = w.globals.comboCharts ? ctype : w.config.chart.type
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-${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
// As BoxCandleStick derives from Bar, we need this to render.
let { columnGroupIndex } = this.barHelpers.getGroupIndex(realIndex)
// 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
let translationsIndex = 0
if (this.yRatio.length > 1) {
this.yaxisIndex = w.globals.seriesYAxisReverseMap[realIndex][0]
translationsIndex = realIndex
}
let initPositions = this.barHelpers.initialPositions(realIndex)
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,
})
let elGoalsMarkers = graphics.group({
class: 'apexcharts-bar-goals-markers',
})
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,
translationsIndex,
},
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
const barGoalLine = this.barHelpers.drawGoalLine({
barXPosition: paths.barXPosition,
barYPosition: paths.barYPosition,
goalX: paths.goalX,
goalY: paths.goalY,
barHeight,
barWidth,
})
if (barGoalLine) {
elGoalsMarkers.add(barGoalLine)
}
// 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,
columnGroupIndex,
barHeight,
barWidth,
elDataLabelsWrap,
elGoalsMarkers,
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
const { colors: candleColors } = w.config.plotOptions.candlestick
const { colors: boxColors } = this.boxOptions
const realIndex = indexes.realIndex
const getColor = (color) =>
Array.isArray(color) ? color[realIndex] : color
const colorPos = getColor(candleColors.upward)
const colorNeg = getColor(candleColors.downward)
const yRatio = this.yRatio[indexes.translationsIndex]
const ohlc = this.getOHLCValue(realIndex, j)
let l1 = zeroH
let l2 = zeroH
let color = ohlc.o < ohlc.c ? [colorPos] : [colorNeg]
if (this.isBoxPlot) {
color = [getColor(boxColors.lower), getColor(boxColors.upper)]
}
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,
goalY: this.barHelpers.getGoalValues(
'y',
null,
zeroH,
i,
j,
indexes.translationsIndex
),
barXPosition,
color,
}
}
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,
goalX: this.barHelpers.getGoalValues('x', zeroW, null, i, j),
barYPosition,
color,
}
}
getOHLCValue(i, j) {
const w = this.w
const coreUtils = new CoreUtils(this.ctx, w)
const h = coreUtils.getLogValAtSeriesIndex(w.globals.seriesCandleH[i][j], i)
const o = coreUtils.getLogValAtSeriesIndex(w.globals.seriesCandleO[i][j], i)
const m = coreUtils.getLogValAtSeriesIndex(w.globals.seriesCandleM[i][j], i)
const c = coreUtils.getLogValAtSeriesIndex(w.globals.seriesCandleC[i][j], i)
const l = coreUtils.getLogValAtSeriesIndex(w.globals.seriesCandleL[i][j], i)
return {
o: this.isBoxPlot ? h : o,
h: this.isBoxPlot ? o : h,
m: m,
l: this.isBoxPlot ? c : l,
c: this.isBoxPlot ? l : c,
}
}
}
export default BoxCandleStick

256
frontend/node_modules/apexcharts/src/charts/HeatMap.js generated vendored Normal file
View File

@@ -0,0 +1,256 @@
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
let j = 0
for (let dIndex = 0; dIndex < w.globals.dataPoints; dIndex++) {
// Recognize gaps and align values based on x axis
if (w.globals.seriesX.length && !w.globals.allSeriesHasEqualX) {
if (
w.globals.minX + w.globals.minXDiff * dIndex <
w.globals.seriesX[i][j]
) {
x1 = x1 + xDivision
continue
}
}
// Stop loop if index is out of array length
if (j >= heatSeries[i].length) break
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: series[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
j++
}
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
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,
})
}
}

1170
frontend/node_modules/apexcharts/src/charts/Line.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

1021
frontend/node_modules/apexcharts/src/charts/Pie.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

536
frontend/node_modules/apexcharts/src/charts/Radar.js generated vendored Normal file
View File

@@ -0,0 +1,536 @@
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.gridWidth
this.isLog = w.config.yaxis[0].logarithmic
this.logBase = w.config.yaxis[0].logBase
this.coreUtils = new CoreUtils(this.ctx)
this.maxValue = this.isLog
? this.coreUtils.getLogVal(this.logBase, w.globals.maxY, 0)
: w.globals.maxY
this.minValue = this.isLog
? this.coreUtils.getLogVal(this.logBase, 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 - this.minValue
if (this.isLog) {
dv = this.coreUtils.getLogVal(this.logBase, 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,
})
const dataLabelText = dataLabels.plotDataLabelsText({
x: textPos.newX,
y: textPos.newY,
text,
textAnchor: textPos.textAnchor,
i,
j: i,
parent: elXAxisWrap,
className: 'apexcharts-xaxis-label',
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,
})
dataLabelText.on('click', (e) => {
if (typeof w.config.chart.events.xAxisLabelClick === 'function') {
const opts = Object.assign({}, w, {
labelIndex: i,
})
w.config.chart.events.xAxisLabelClick(e, this.ctx, opts)
}
})
}
})
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

539
frontend/node_modules/apexcharts/src/charts/Radial.js generated vendored Normal file
View File

@@ -0,0 +1,539 @@
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.barLabels = this.w.config.plotOptions.radialBar.barLabels
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)
this.onBarLabelClick = this.onBarLabelClick.bind(this)
}
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,
})
}
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) {
let dataLabelsGroup = w.globals.dom.Paper.findOne(
`.apexcharts-datalabels-group`
)
dataLabels = this.renderInnerDataLabels(
dataLabelsGroup,
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,
})
if (this.barLabels.enabled) {
let barStartCords = Utils.polarToCartesian(
opts.centerX,
opts.centerY,
opts.size,
startAngle
)
let text = this.barLabels.formatter(w.globals.seriesNames[i], {
seriesIndex: i,
w,
})
let classes = ['apexcharts-radialbar-label']
if (!this.barLabels.onClick) {
classes.push('apexcharts-no-click')
}
let textColor = this.barLabels.useSeriesColors
? w.globals.colors[i]
: w.config.chart.foreColor
if (!textColor) {
textColor = w.config.chart.foreColor
}
const x = barStartCords.x + this.barLabels.offsetX
const y = barStartCords.y + this.barLabels.offsetY
let elText = graphics.drawText({
x,
y,
text,
textAnchor: 'end',
dominantBaseline: 'middle',
fontFamily: this.barLabels.fontFamily,
fontWeight: this.barLabels.fontWeight,
fontSize: this.barLabels.fontSize,
foreColor: textColor,
cssClass: classes.join(' '),
})
elText.on('click', this.onBarLabelClick)
elText.attr({
rel: i + 1,
})
if (startAngle !== 0) {
elText.attr({
'transform-origin': `${x} ${y}`,
transform: `rotate(${startAngle} 0 0)`,
})
}
elRadialBarArc.add(elText)
}
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,
})
}
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, 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, 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
)
}
onBarLabelClick(e) {
let seriesIndex = parseInt(e.target.getAttribute('rel'), 10) - 1
const legendClick = this.barLabels.onClick
const w = this.w
if (legendClick) {
legendClick(w.globals.seriesNames[seriesIndex], { w, seriesIndex })
}
}
}
export default Radial

456
frontend/node_modules/apexcharts/src/charts/RangeBar.js generated vendored Normal file
View File

@@ -0,0 +1,456 @@
import Bar from './Bar'
import Graphics from '../modules/Graphics'
import Utils from '../utils/Utils'
/**
* 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
let { columnGroupIndex } = this.barHelpers.getGroupIndex(realIndex)
// 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
let translationsIndex = 0
if (this.yRatio.length > 1) {
this.yaxisIndex = w.globals.seriesYAxisReverseMap[realIndex][0]
translationsIndex = realIndex
}
let initPositions = this.barHelpers.initialPositions(realIndex)
y = initPositions.y
zeroW = initPositions.zeroW
x = initPositions.x
barWidth = initPositions.barWidth
barHeight = initPositions.barHeight
xDivision = initPositions.xDivision
yDivision = initPositions.yDivision
zeroH = initPositions.zeroH
// eldatalabels
let elDataLabelsWrap = graphics.group({
class: 'apexcharts-datalabels',
'data:realIndex': realIndex,
})
let elGoalsMarkers = graphics.group({
class: 'apexcharts-rangebar-goals-markers',
})
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 barXPosition = null
let barYPosition = null
const params = { x, y, strokeWidth, elSeries }
let seriesLen = this.seriesLen
if (w.config.plotOptions.bar.rangeBarGroupRows) {
seriesLen = 1
}
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 (this.isHorizontal) {
barYPosition = y + barHeight * this.visibleI
let srty = (yDivision - barHeight * seriesLen) / 2
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 {
if (w.globals.isXNumeric) {
x =
(w.globals.seriesX[i][j] - w.globals.minX) / this.xRatio -
barWidth / 2
}
barXPosition = x + barWidth * this.visibleI
let srtx = (xDivision - barWidth * seriesLen) / 2
if (w.config.series[i].data[j].x) {
let positions = this.detectOverlappingBars({
i,
j,
barXPosition,
srtx,
barWidth,
xDivision,
initPositions,
})
barWidth = positions.barWidth
barXPosition = positions.barXPosition
}
paths = this.drawRangeColumnPaths({
indexes: { i, j, realIndex, translationsIndex },
barWidth,
barXPosition,
zeroH,
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)
this.renderSeries({
realIndex,
pathFill: pathFill.color,
lineFill: pathFill.useRangeColor
? pathFill.color
: w.globals.stroke.colors[realIndex],
j,
i,
x,
y,
y1,
y2,
pathFrom: paths.pathFrom,
pathTo: paths.pathTo,
strokeWidth,
elSeries,
series,
barHeight,
barWidth,
barXPosition,
barYPosition,
columnGroupIndex,
elDataLabelsWrap,
elGoalsMarkers,
visibleSeries: this.visibleI,
type: 'rangebar',
})
}
ret.add(elSeries)
}
return ret
}
detectOverlappingBars({
i,
j,
barYPosition,
barXPosition,
srty,
srtx,
barHeight,
barWidth,
yDivision,
xDivision,
initPositions,
}) {
const w = this.w
let overlaps = []
let rangeName = w.config.series[i].data[j].rangeName
const x = w.config.series[i].data[j].x
const labelX = Array.isArray(x) ? x.join(' ') : x
const rowIndex = w.globals.labels
.map((_) => (Array.isArray(_) ? _.join(' ') : _))
.indexOf(labelX)
const overlappedIndex = w.globals.seriesRange[i].findIndex(
(tx) => tx.x === labelX && tx.overlaps.length > 0
)
if (this.isHorizontal) {
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.seriesRange[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
}
}
} else {
if (rowIndex > -1 && !w.globals.timescaleLabels.length) {
if (w.config.plotOptions.bar.rangeBarGroupRows) {
barXPosition = srtx + xDivision * rowIndex
} else {
barXPosition = srtx + barWidth * this.visibleI + xDivision * rowIndex
}
}
if (overlappedIndex > -1 && !w.config.plotOptions.bar.rangeBarOverlap) {
overlaps = w.globals.seriesRange[i][overlappedIndex].overlaps
if (overlaps.indexOf(rangeName) > -1) {
barWidth = initPositions.barWidth / overlaps.length
barXPosition =
barWidth * this.visibleI +
(xDivision * (100 - parseInt(this.barOptions.barWidth, 10))) /
100 /
2 +
barWidth * (this.visibleI + overlaps.indexOf(rangeName)) +
xDivision * rowIndex
}
}
}
return {
barYPosition,
barXPosition,
barHeight,
barWidth,
}
}
drawRangeColumnPaths({
indexes,
x,
xDivision,
barWidth,
barXPosition,
zeroH,
}) {
let w = this.w
const { i, j, realIndex, translationsIndex } = indexes
const yRatio = this.yRatio[translationsIndex]
const range = this.getRangeValue(realIndex, j)
let y1 = Math.min(range.start, range.end)
let y2 = Math.max(range.start, range.end)
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: realIndex,
i: realIndex,
j,
w,
})
if (!w.globals.isXNumeric) {
x = x + xDivision
} else {
const xForNumericXAxis = this.getBarXForNumericXAxis({
x,
j,
realIndex,
barWidth,
})
x = xForNumericXAxis.x
barXPosition = xForNumericXAxis.barXPosition
}
return {
pathTo: paths.pathTo,
pathFrom: paths.pathFrom,
barHeight,
x,
y: range.start < 0 && range.end < 0 ? y1 : y2,
goalY: this.barHelpers.getGoalValues(
'y',
null,
zeroH,
i,
j,
translationsIndex
),
barXPosition,
}
}
preventBarOverflow(val) {
const w = this.w
if (val < 0) {
val = 0
}
if (val > w.globals.gridWidth) {
val = w.globals.gridWidth
}
return val
}
drawRangeBarPaths({
indexes,
y,
y1,
y2,
yDivision,
barHeight,
barYPosition,
zeroW,
}) {
let w = this.w
const { realIndex, j } = indexes
let x1 = this.preventBarOverflow(zeroW + y1 / this.invertedYRatio)
let x2 = this.preventBarOverflow(zeroW + y2 / this.invertedYRatio)
const range = this.getRangeValue(realIndex, j)
const barWidth = Math.abs(x2 - x1)
const paths = this.barHelpers.getBarpaths({
barYPosition,
barHeight,
x1,
x2,
strokeWidth: this.strokeWidth,
series: this.seriesRangeEnd,
i: realIndex,
realIndex,
j,
w,
})
if (!w.globals.isXNumeric) {
y = y + yDivision
}
return {
pathTo: paths.pathTo,
pathFrom: paths.pathFrom,
barWidth,
x: range.start < 0 && range.end < 0 ? x1 : x2,
goalX: this.barHelpers.getGoalValues('x', zeroW, null, realIndex, j),
y,
}
}
getRangeValue(i, j) {
const w = this.w
return {
start: w.globals.seriesRangeStart[i][j],
end: w.globals.seriesRangeEnd[i][j],
}
}
}
export default RangeBar

177
frontend/node_modules/apexcharts/src/charts/Scatter.js generated vendored Normal file
View File

@@ -0,0 +1,177 @@
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
}
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 = w.globals.markers.size[realIndex]
if (zRatio !== Infinity) {
// means we have a bubble
const bubble = w.config.plotOptions.bubble
radius = w.globals.seriesZ[realIndex][dataPointIndex]
if (bubble.zScaling) {
radius /= zRatio
}
if (bubble.minBubbleRadius && radius < bubble.minBubbleRadius) {
radius = bubble.minBubbleRadius
}
if (bubble.maxBubbleRadius && radius > bubble.maxBubbleRadius) {
radius = bubble.maxBubbleRadius
}
}
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,
realIndex,
dataPointIndex,
j
)
elPointsWrap.add(point)
}
elPointsMain.add(elPointsWrap)
}
}
}
drawPoint(x, y, radius, 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,
radius:
w.config.chart.type === 'bubble' ||
(w.globals.comboCharts &&
w.config.series[realIndex] &&
w.config.series[realIndex].type === 'bubble')
? radius
: null,
})
let pathFillCircle = fill.fillPath({
seriesNumber: realIndex,
dataPointIndex,
color: markerConfig.pointFillColor,
patternUnits: 'objectBoundingBox',
value: w.globals.series[realIndex][j],
})
let el = graphics.drawMarker(x, y, markerConfig)
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({
fill: pathFillCircle,
})
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, speed, w.globals.easing, () => {
window.setTimeout(() => {
anim.animationCompleted(el)
}, 100)
})
} else {
w.globals.animationEnded = true
}
el.attr({
rel: dataPointIndex,
j: dataPointIndex,
index: realIndex,
'default-marker-size': markerConfig.pSize,
})
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,
}
}
}

403
frontend/node_modules/apexcharts/src/charts/Treemap.js generated vendored Normal file
View File

@@ -0,0 +1,403 @@
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',
})
let bounds = {
xMin: Infinity,
yMin: Infinity,
xMax: -Infinity,
yMax: -Infinity,
}
node.forEach((r, j) => {
const x1 = r[0]
const y1 = r[1]
const x2 = r[2]
const y2 = r[3]
bounds.xMin = Math.min(bounds.xMin, x1)
bounds.yMin = Math.min(bounds.yMin, y1)
bounds.xMax = Math.max(bounds.xMax, x2)
bounds.yMax = Math.max(bounds.yMax, y2)
let colorProps = this.helpers.getShadeColor(
w.config.chart.type,
i,
j,
this.negRange
)
let color = colorProps.color
let pathFill = fill.fillPath({
color,
seriesNumber: i,
dataPointIndex: j,
})
let elRect = graphics.drawRect(
x1,
y1,
x2 - x1,
y2 - y1,
w.config.plotOptions.treemap.borderRadius,
'#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,
fill: pathFill,
})
elRect.node.classList.add('apexcharts-treemap-rect')
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)
}
}
let 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,
})
if (w.config.plotOptions.treemap.dataLabels.format === 'truncate') {
fontSize = parseInt(w.config.dataLabels.style.fontSize, 10)
formattedText = this.truncateLabels(
formattedText,
fontSize,
x1,
y1,
x2,
y2
)
}
let dataLabels = null
if (w.globals.series[i][j]) {
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,
fontSize,
formattedText,
x1,
y1,
x2,
y2
)
}
elSeries.add(elRect)
if (dataLabels !== null) {
elSeries.add(dataLabels)
}
})
const seriesTitle = w.config.plotOptions.treemap.seriesTitle
if (w.config.series.length > 1 && seriesTitle && seriesTitle.show) {
const sName = w.config.series[i].name || ''
if (sName && bounds.xMin < Infinity && bounds.yMin < Infinity) {
const {
offsetX,
offsetY,
borderColor,
borderWidth,
borderRadius,
style,
} = seriesTitle
const textColor = style.color || w.config.chart.foreColor
const padding = {
left: style.padding.left,
right: style.padding.right,
top: style.padding.top,
bottom: style.padding.bottom,
}
const textSize = graphics.getTextRects(
sName,
style.fontSize,
style.fontFamily
)
const labelRectWidth = textSize.width + padding.left + padding.right
const labelRectHeight = textSize.height + padding.top + padding.bottom
// Position
const labelX = bounds.xMin + (offsetX || 0)
const labelY = bounds.yMin + (offsetY || 0)
// Draw background rect
const elLabelRect = graphics.drawRect(
labelX,
labelY,
labelRectWidth,
labelRectHeight,
borderRadius,
style.background,
1,
borderWidth,
borderColor
)
const elLabelText = graphics.drawText({
x: labelX + padding.left,
y: labelY + padding.top + textSize.height * 0.75,
text: sName,
fontSize: style.fontSize,
fontFamily: style.fontFamily,
fontWeight: style.fontWeight,
foreColor: textColor,
cssClass: style.cssClass || '',
})
elSeries.add(elLabelRect)
elSeries.add(elLabelText)
}
}
elSeries.add(elDataLabelWrap)
ret.add(elSeries)
})
return ret
}
// This calculates a font-size based upon
// average label length and the size of the box
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) {
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, fontSize, text, x1, y1, x2, y2) {
const graphics = new Graphics(this.ctx)
const textRect = graphics.getTextRects(text, fontSize)
// if the label fits better sideways then rotate it
if (
textRect.width + this.w.config.stroke.width + 5 > x2 - x1 &&
textRect.width <= y2 - y1
) {
let labelRotatingCenter = graphics.rotateAroundCenter(elText.node)
elText.node.setAttribute(
'transform',
`rotate(-90 ${labelRotatingCenter.x} ${
labelRotatingCenter.y
}) translate(${textRect.height / 3})`
)
}
}
// This is an alternative label formatting method that uses a
// consistent font size, and trims the edge of long labels
truncateLabels(text, fontSize, x1, y1, x2, y2) {
const graphics = new Graphics(this.ctx)
const textRect = graphics.getTextRects(text, fontSize)
// Determine max width based on ideal orientation of text
const labelMaxWidth =
textRect.width + this.w.config.stroke.width + 5 > x2 - x1 &&
y2 - y1 > x2 - x1
? y2 - y1
: x2 - x1
const truncatedText = graphics.getTextBasedOnMaxWidth({
text: text,
maxWidth: labelMaxWidth,
fontSize: fontSize,
})
// Return empty label when text has been trimmed for very small rects
if (text.length !== truncatedText.length && labelMaxWidth / fontSize < 5) {
return ''
} else {
return truncatedText
}
}
animateTreemap(el, fromRect, toRect, speed) {
const animations = new Animations(this.ctx)
animations.animateRect(el, fromRect, toRect, speed, () => {
animations.animationCompleted(el)
})
}
}

View File

@@ -0,0 +1,689 @@
import Graphics from '../../../modules/Graphics'
import DataLabels from '../../../modules/DataLabels'
export default class BarDataLabels {
constructor(barCtx) {
this.w = barCtx.w
this.barCtx = barCtx
this.totalFormatter =
this.w.config.plotOptions.bar.dataLabels.total.formatter
if (!this.totalFormatter) {
this.totalFormatter = this.w.config.dataLabels.formatter
}
}
/** 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,
columnGroupIndex,
series,
barHeight,
barWidth,
barXPosition,
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
let bcy
if (w.globals.isXNumeric && !w.globals.isBarHorizontal) {
bcx = x + parseFloat(barWidth * (visibleSeries + 1))
bcy = y + parseFloat(barHeight * (visibleSeries + 1)) - strokeWidth
} else {
bcx = x + parseFloat(barWidth * visibleSeries)
bcy = y + parseFloat(barHeight * visibleSeries)
}
let dataLabels = null
let totalDataLabels = null
let dataLabelsX = x
let dataLabelsY = y
let dataLabelsPos = {}
let dataLabelsConfig = w.config.dataLabels
let barDataLabelsConfig = this.barCtx.barOptions.dataLabels
let barTotalDataLabelsConfig = this.barCtx.barOptions.dataLabels.total
if (typeof barYPosition !== 'undefined' && this.barCtx.isRangeBar) {
bcy = barYPosition
dataLabelsY = barYPosition
}
if (
typeof barXPosition !== 'undefined' &&
this.barCtx.isVerticalGroupedRangeBar
) {
bcx = barXPosition
dataLabelsX = barXPosition
}
const offX = dataLabelsConfig.offsetX
const offY = dataLabelsConfig.offsetY
let textRects = {
width: 0,
height: 0,
}
if (w.config.dataLabels.enabled) {
const yLabel = w.globals.series[i][j]
textRects = graphics.getTextRects(
w.config.dataLabels.formatter
? w.config.dataLabels.formatter(yLabel, {
...w,
seriesIndex: i,
dataPointIndex: j,
w,
})
: w.globals.yLabelFormatters[0](yLabel),
parseFloat(dataLabelsConfig.style.fontSize)
)
}
const params = {
x,
y,
i,
j,
realIndex,
columnGroupIndex,
renderedPath,
bcx,
bcy,
barHeight,
barWidth,
textRects,
strokeWidth,
dataLabelsX,
dataLabelsY,
dataLabelsConfig,
barDataLabelsConfig,
barTotalDataLabelsConfig,
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: w.globals.series[i][j],
barHeight,
barWidth,
})
dataLabels = this.drawCalculatedDataLabels({
x: dataLabelsPos.dataLabelsX,
y: dataLabelsPos.dataLabelsY,
val: this.barCtx.isRangeBar
? [y1, y2]
: w.config.chart.stackType === '100%'
? series[realIndex][j]
: w.globals.series[realIndex][j],
i: realIndex,
j,
barWidth,
barHeight,
textRects,
dataLabelsConfig,
})
if (w.config.chart.stacked && barTotalDataLabelsConfig.enabled) {
totalDataLabels = this.drawTotalDataLabels({
x: dataLabelsPos.totalDataLabelsX,
y: dataLabelsPos.totalDataLabelsY,
barWidth,
barHeight,
realIndex,
textAnchor: dataLabelsPos.totalDataLabelsAnchor,
val: this.getStackedTotalDataLabel({ realIndex, j }),
dataLabelsConfig,
barTotalDataLabelsConfig,
})
}
return {
dataLabels,
totalDataLabels,
}
}
getStackedTotalDataLabel({ realIndex, j }) {
const w = this.w
let val = this.barCtx.stackedSeriesTotals[j]
if (this.totalFormatter) {
val = this.totalFormatter(val, {
...w,
seriesIndex: realIndex,
dataPointIndex: j,
w,
})
}
return val
}
calculateColumnsDataLabelsPosition(opts) {
const w = this.w
let {
i,
j,
realIndex,
columnGroupIndex,
y,
bcx,
barWidth,
barHeight,
textRects,
dataLabelsX,
dataLabelsY,
dataLabelsConfig,
barDataLabelsConfig,
barTotalDataLabelsConfig,
strokeWidth,
offX,
offY,
} = opts
let totalDataLabelsY
let totalDataLabelsX
let totalDataLabelsAnchor = 'middle'
let totalDataLabelsBcx = bcx
barHeight = Math.abs(barHeight)
let vertical =
w.config.plotOptions.bar.dataLabels.orientation === 'vertical'
const { zeroEncounters } = this.barCtx.barHelpers.getZeroValueEncounters({
i,
j,
})
bcx = bcx - strokeWidth / 2
let dataPointsDividedWidth = w.globals.gridWidth / w.globals.dataPoints
if (this.barCtx.isVerticalGroupedRangeBar) {
dataLabelsX += barWidth / 2
} else {
if (w.globals.isXNumeric) {
dataLabelsX = bcx - barWidth / 2 + offX
} else {
dataLabelsX = bcx - dataPointsDividedWidth + barWidth / 2 + offX
}
if (
zeroEncounters > 0 &&
w.config.plotOptions.bar.hideZeroBarsWhenGrouped
) {
dataLabelsX -= barWidth * zeroEncounters
}
}
if (vertical) {
const offsetDLX = 2
dataLabelsX =
dataLabelsX + textRects.height / 2 - strokeWidth / 2 - offsetDLX
}
let valIsNegative = w.globals.series[i][j] < 0
let newY = y
if (this.barCtx.isReversed) {
newY = y + (valIsNegative ? barHeight : -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
}
let lowestPrevY = newY
w.globals.seriesGroups.forEach((sg) => {
this.barCtx[sg.join(',')]?.prevY.forEach((arr) => {
if (valIsNegative) {
lowestPrevY = Math.max(arr[j], lowestPrevY)
} else {
lowestPrevY = Math.min(arr[j], lowestPrevY)
}
})
})
if (
this.barCtx.lastActiveBarSerieIndex === realIndex &&
barTotalDataLabelsConfig.enabled
) {
const ADDITIONAL_OFFY = 18
const graphics = new Graphics(this.barCtx.ctx)
const totalLabeltextRects = graphics.getTextRects(
this.getStackedTotalDataLabel({ realIndex, j }),
dataLabelsConfig.fontSize
)
if (valIsNegative) {
totalDataLabelsY =
lowestPrevY -
totalLabeltextRects.height / 2 -
offY -
barTotalDataLabelsConfig.offsetY +
ADDITIONAL_OFFY
} else {
totalDataLabelsY =
lowestPrevY +
totalLabeltextRects.height +
offY +
barTotalDataLabelsConfig.offsetY -
ADDITIONAL_OFFY
}
// width divided into equal parts
let xDivision = dataPointsDividedWidth
totalDataLabelsX =
totalDataLabelsBcx +
(w.globals.isXNumeric
? (-barWidth * w.globals.barGroups.length) / 2
: (w.globals.barGroups.length * barWidth) / 2 -
(w.globals.barGroups.length - 1) * barWidth -
xDivision) +
barTotalDataLabelsConfig.offsetX
}
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,
totalDataLabelsX,
totalDataLabelsY,
totalDataLabelsAnchor,
}
}
calculateBarsDataLabelsPosition(opts) {
const w = this.w
let {
x,
i,
j,
realIndex,
bcy,
barHeight,
barWidth,
textRects,
dataLabelsX,
strokeWidth,
dataLabelsConfig,
barDataLabelsConfig,
barTotalDataLabelsConfig,
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 totalDataLabelsX
let totalDataLabelsY
let totalDataLabelsAnchor = 'start'
let valIsNegative = w.globals.series[i][j] < 0
let newX = x
if (this.barCtx.isReversed) {
newX = x + (valIsNegative ? -barWidth : barWidth)
totalDataLabelsAnchor = valIsNegative ? 'start' : 'end'
}
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 - offX
} else {
dataLabelsX = newX - barWidth + strokeWidth + offX
}
break
case 'top':
if (valIsNegative) {
dataLabelsX = newX - strokeWidth - offX
} else {
dataLabelsX = newX - strokeWidth + offX
}
break
}
let lowestPrevX = newX
w.globals.seriesGroups.forEach((sg) => {
this.barCtx[sg.join(',')]?.prevX.forEach((arr) => {
if (valIsNegative) {
lowestPrevX = Math.min(arr[j], lowestPrevX)
} else {
lowestPrevX = Math.max(arr[j], lowestPrevX)
}
})
})
if (
this.barCtx.lastActiveBarSerieIndex === realIndex &&
barTotalDataLabelsConfig.enabled
) {
const graphics = new Graphics(this.barCtx.ctx)
const totalLabeltextRects = graphics.getTextRects(
this.getStackedTotalDataLabel({ realIndex, j }),
dataLabelsConfig.fontSize
)
if (valIsNegative) {
totalDataLabelsX =
lowestPrevX - strokeWidth - offX - barTotalDataLabelsConfig.offsetX
totalDataLabelsAnchor = 'end'
} else {
totalDataLabelsX =
lowestPrevX +
offX +
barTotalDataLabelsConfig.offsetX +
(this.barCtx.isReversed ? -(barWidth + strokeWidth) : strokeWidth)
}
totalDataLabelsY =
dataLabelsY -
textRects.height / 2 +
totalLabeltextRects.height / 2 +
barTotalDataLabelsConfig.offsetY +
strokeWidth
if (w.globals.barGroups.length > 1) {
totalDataLabelsY =
totalDataLabelsY - (w.globals.barGroups.length / 2) * (barHeight / 2)
}
}
if (!w.config.chart.stacked) {
if (dataLabelsConfig.textAnchor === 'start') {
if (dataLabelsX - textRects.width < 0) {
dataLabelsX = valIsNegative
? textRects.width + strokeWidth
: strokeWidth
} else if (dataLabelsX + textRects.width > w.globals.gridWidth) {
dataLabelsX = valIsNegative
? w.globals.gridWidth - strokeWidth
: w.globals.gridWidth - textRects.width - strokeWidth
}
} else if (dataLabelsConfig.textAnchor === 'middle') {
if (dataLabelsX - textRects.width / 2 < 0) {
dataLabelsX = textRects.width / 2 + strokeWidth
} else if (dataLabelsX + textRects.width / 2 > w.globals.gridWidth) {
dataLabelsX = w.globals.gridWidth - textRects.width / 2 - strokeWidth
}
} else if (dataLabelsConfig.textAnchor === 'end') {
if (dataLabelsX < 1) {
dataLabelsX = textRects.width + strokeWidth
} else if (dataLabelsX + 1 > w.globals.gridWidth) {
dataLabelsX = w.globals.gridWidth - textRects.width - strokeWidth
}
}
}
return {
bcx: x,
bcy,
dataLabelsX,
dataLabelsY,
totalDataLabelsX,
totalDataLabelsY,
totalDataLabelsAnchor,
}
}
drawCalculatedDataLabels({
x,
y,
val,
i, // = realIndex
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, {
...w,
seriesIndex: i,
dataPointIndex: j,
w,
})
}
if (!val && w.config.plotOptions.bar.hideZeroBarsWhenGrouped) {
text = ''
}
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
}
drawTotalDataLabels({
x,
y,
val,
realIndex,
textAnchor,
barTotalDataLabelsConfig,
}) {
const w = this.w
const graphics = new Graphics(this.barCtx.ctx)
let totalDataLabelText
if (
barTotalDataLabelsConfig.enabled &&
typeof x !== 'undefined' &&
typeof y !== 'undefined' &&
this.barCtx.lastActiveBarSerieIndex === realIndex
) {
totalDataLabelText = graphics.drawText({
x: x,
y: y,
foreColor: barTotalDataLabelsConfig.style.color,
text: val,
textAnchor,
fontFamily: barTotalDataLabelsConfig.style.fontFamily,
fontSize: barTotalDataLabelsConfig.style.fontSize,
fontWeight: barTotalDataLabelsConfig.style.fontWeight,
})
}
return totalDataLabelText
}
}

View File

@@ -0,0 +1,815 @@
import Fill from '../../../modules/Fill'
import Graphics from '../../../modules/Graphics'
import Series from '../../../modules/Series'
import Utils from '../../../utils/Utils'
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
}
}
this.arrBorderRadius = this.createBorderRadiusArr(w.globals.series)
if (this.barCtx.seriesLen === 0) {
// A small adjustment when combo charts are used
this.barCtx.seriesLen = 1
}
this.barCtx.zeroSerieses = []
if (!w.globals.comboCharts) {
this.checkZeroSeries({ series })
}
}
initialPositions(realIndex) {
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
if (String(this.barCtx.barOptions.barHeight).indexOf('%') === -1) {
barHeight = parseInt(this.barCtx.barOptions.barHeight, 10)
}
zeroW =
this.barCtx.baseLineInvertedY +
w.globals.padHorizontal +
(this.barCtx.isReversed ? w.globals.gridWidth : 0) -
(this.barCtx.isReversed ? this.barCtx.baseLineInvertedY * 2 : 0)
if (this.barCtx.isFunnel) {
zeroW = w.globals.gridWidth / 2
}
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 / 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.globals.minXDiff &&
w.globals.minXDiff !== 0.5 &&
w.globals.minXDiff / xRatio > 0
) {
xDivision = w.globals.minXDiff / xRatio
}
barWidth =
((xDivision / seriesLen) *
parseInt(this.barCtx.barOptions.columnWidth, 10)) /
100
if (barWidth < 1) {
barWidth = 1
}
}
if (String(this.barCtx.barOptions.columnWidth).indexOf('%') === -1) {
barWidth = parseInt(this.barCtx.barOptions.columnWidth, 10)
}
zeroH =
w.globals.gridHeight -
this.barCtx.baseLineY[this.barCtx.translationsIndex] -
(this.barCtx.isReversed ? w.globals.gridHeight : 0) +
(this.barCtx.isReversed
? this.barCtx.baseLineY[this.barCtx.translationsIndex] * 2
: 0)
if (w.globals.isXNumeric) {
const xForNumericX = this.barCtx.getBarXForNumericXAxis({
x,
j: 0,
realIndex,
barWidth,
})
x = xForNumericX.x
} else {
x =
w.globals.padHorizontal +
Utils.noExponents(xDivision - barWidth * this.barCtx.seriesLen) / 2
}
}
w.globals.barHeight = barHeight
w.globals.barWidth = barWidth
return {
x,
y,
yDivision,
xDivision,
barHeight,
barWidth,
zeroH,
zeroW,
}
}
initializeStackedPrevVars(ctx) {
const w = ctx.w
w.globals.seriesGroups.forEach((group) => {
if (!ctx[group]) ctx[group] = {}
ctx[group].prevY = []
ctx[group].prevX = []
ctx[group].prevYF = []
ctx[group].prevXF = []
ctx[group].prevYVal = []
ctx[group].prevXVal = []
})
}
initializeStackedXYVars(ctx) {
const w = ctx.w
w.globals.seriesGroups.forEach((group) => {
if (!ctx[group]) ctx[group] = {}
ctx[group].xArrj = []
ctx[group].xArrjF = []
ctx[group].xArrjVal = []
ctx[group].yArrj = []
ctx[group].yArrjF = []
ctx[group].yArrjVal = []
})
}
getPathFillColor(series, i, j, realIndex) {
const w = this.w
let fill = this.barCtx.ctx.fill
let fillColor = null
let seriesNumber = this.barCtx.barOptions.distributed ? j : i
let useRangeColor = false
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
useRangeColor = true
}
})
}
let pathFill = fill.fillPath({
seriesNumber: this.barCtx.barOptions.distributed
? seriesNumber
: realIndex,
dataPointIndex: j,
color: fillColor,
value: series[i][j],
fillConfig: w.config.series[i].data[j]?.fill,
fillType: w.config.series[i].data[j]?.fill?.type
? w.config.series[i].data[j]?.fill.type
: Array.isArray(w.config.fill.type)
? w.config.fill.type[realIndex]
: w.config.fill.type,
})
return {
color: pathFill,
useRangeColor,
}
}
getStrokeWidth(i, j, realIndex) {
let strokeWidth = 0
const w = this.w
if (!this.barCtx.series[i][j]) {
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
}
createBorderRadiusArr(series) {
const w = this.w
const alwaysApplyRadius =
!this.w.config.chart.stacked || w.config.plotOptions.bar.borderRadius <= 0
const numSeries = series.length
const numColumns = series[0]?.length | 0
const output = Array.from({ length: numSeries }, () =>
Array(numColumns).fill(alwaysApplyRadius ? 'top' : 'none')
)
if (alwaysApplyRadius) return output
for (let j = 0; j < numColumns; j++) {
let positiveIndices = []
let negativeIndices = []
let nonZeroCount = 0
// Collect positive and negative indices
for (let i = 0; i < numSeries; i++) {
const value = series[i][j]
if (value > 0) {
positiveIndices.push(i)
nonZeroCount++
} else if (value < 0) {
negativeIndices.push(i)
nonZeroCount++
}
}
if (positiveIndices.length > 0 && negativeIndices.length === 0) {
// Only positive values in this column
if (positiveIndices.length === 1) {
// Single positive value
output[positiveIndices[0]][j] = 'both'
} else {
// Multiple positive values
const firstPositiveIndex = positiveIndices[0]
const lastPositiveIndex = positiveIndices[positiveIndices.length - 1]
for (let i of positiveIndices) {
if (i === firstPositiveIndex) {
output[i][j] = 'bottom'
} else if (i === lastPositiveIndex) {
output[i][j] = 'top'
} else {
output[i][j] = 'none'
}
}
}
} else if (negativeIndices.length > 0 && positiveIndices.length === 0) {
// Only negative values in this column
if (negativeIndices.length === 1) {
// Single negative value
output[negativeIndices[0]][j] = 'both'
} else {
// Multiple negative values
const highestNegativeIndex = Math.max(...negativeIndices)
const lowestNegativeIndex = Math.min(...negativeIndices)
for (let i of negativeIndices) {
if (i === highestNegativeIndex) {
output[i][j] = 'bottom' // Closest to axis
} else if (i === lowestNegativeIndex) {
output[i][j] = 'top' // Farthest from axis
} else {
output[i][j] = 'none'
}
}
}
} else if (positiveIndices.length > 0 && negativeIndices.length > 0) {
// Mixed positive and negative values
// Assign 'top' to the last positive bar
const lastPositiveIndex = positiveIndices[positiveIndices.length - 1]
for (let i of positiveIndices) {
if (i === lastPositiveIndex) {
output[i][j] = 'top'
} else {
output[i][j] = 'none'
}
}
// Assign 'bottom' to the highest negative index (closest to axis)
const highestNegativeIndex = Math.max(...negativeIndices)
for (let i of negativeIndices) {
if (i === highestNegativeIndex) {
output[i][j] = 'bottom'
} else {
output[i][j] = 'none'
}
}
} else if (nonZeroCount === 1) {
// Only one non-zero value (either positive or negative)
const index = positiveIndices[0] || negativeIndices[0]
output[index][j] = 'both'
}
}
return output
}
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,
y1,
y2,
strokeWidth,
isReversed,
series,
seriesGroup,
realIndex,
i,
j,
w,
}) {
const graphics = new Graphics(this.barCtx.ctx)
strokeWidth = Array.isArray(strokeWidth)
? strokeWidth[realIndex]
: strokeWidth
if (!strokeWidth) strokeWidth = 0
let bW = barWidth
let bXP = barXPosition
if (w.config.series[realIndex].data[j]?.columnWidthOffset) {
bXP =
barXPosition - w.config.series[realIndex].data[j].columnWidthOffset / 2
bW = barWidth + w.config.series[realIndex].data[j].columnWidthOffset
}
// Center the stroke on the coordinates
let strokeCenter = strokeWidth / 2
const x1 = bXP + strokeCenter
const x2 = bXP + bW - strokeCenter
let direction = (series[i][j] >= 0 ? 1 : -1) * (isReversed ? -1 : 1)
// append tiny pixels to avoid exponentials (which cause issues in border-radius)
y1 += 0.001 - strokeCenter * direction
y2 += 0.001 + strokeCenter * direction
let pathTo = graphics.move(x1, y1)
let pathFrom = graphics.move(x1, y1)
const sl = graphics.line(x2, y1)
if (w.globals.previousPaths.length > 0) {
pathFrom = this.barCtx.getPreviousPath(realIndex, j, false)
}
pathTo =
pathTo +
graphics.line(x1, y2) +
graphics.line(x2, y2) +
sl +
(w.config.plotOptions.bar.borderRadiusApplication === 'around' ||
this.arrBorderRadius[realIndex][j] === 'both'
? ' Z'
: ' 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) +
(w.config.plotOptions.bar.borderRadiusApplication === 'around' ||
this.arrBorderRadius[realIndex][j] === 'both'
? ' Z'
: ' z')
if (this.arrBorderRadius[realIndex][j] !== 'none') {
pathTo = graphics.roundPathCorners(
pathTo,
w.config.plotOptions.bar.borderRadius
)
}
if (w.config.chart.stacked) {
let _ctx = this.barCtx
_ctx = this.barCtx[seriesGroup]
_ctx.yArrj.push(y2 - strokeCenter * direction)
_ctx.yArrjF.push(Math.abs(y1 - y2 + strokeWidth * direction))
_ctx.yArrjVal.push(this.barCtx.series[i][j])
}
return {
pathTo,
pathFrom,
}
}
getBarpaths({
barYPosition,
barHeight,
x1,
x2,
strokeWidth,
isReversed,
series,
seriesGroup,
realIndex,
i,
j,
w,
}) {
const graphics = new Graphics(this.barCtx.ctx)
strokeWidth = Array.isArray(strokeWidth)
? strokeWidth[realIndex]
: strokeWidth
if (!strokeWidth) strokeWidth = 0
let bYP = barYPosition
let bH = barHeight
if (w.config.series[realIndex].data[j]?.barHeightOffset) {
bYP =
barYPosition - w.config.series[realIndex].data[j].barHeightOffset / 2
bH = barHeight + w.config.series[realIndex].data[j].barHeightOffset
}
// Center the stroke on the coordinates
let strokeCenter = strokeWidth / 2
const y1 = bYP + strokeCenter
const y2 = bYP + bH - strokeCenter
let direction = (series[i][j] >= 0 ? 1 : -1) * (isReversed ? -1 : 1)
// append tiny pixels to avoid exponentials (which cause issues in border-radius)
x1 += 0.001 + strokeCenter * direction
x2 += 0.001 - strokeCenter * direction
let pathTo = graphics.move(x1, y1)
let pathFrom = graphics.move(x1, y1)
if (w.globals.previousPaths.length > 0) {
pathFrom = this.barCtx.getPreviousPath(realIndex, j, false)
}
const sl = graphics.line(x1, y2)
pathTo =
pathTo +
graphics.line(x2, y1) +
graphics.line(x2, y2) +
sl +
(w.config.plotOptions.bar.borderRadiusApplication === 'around' ||
this.arrBorderRadius[realIndex][j] === 'both'
? ' Z'
: ' z')
pathFrom =
pathFrom +
graphics.line(x1, y1) +
sl +
sl +
sl +
sl +
sl +
graphics.line(x1, y1) +
(w.config.plotOptions.bar.borderRadiusApplication === 'around' ||
this.arrBorderRadius[realIndex][j] === 'both'
? ' Z'
: ' z')
if (this.arrBorderRadius[realIndex][j] !== 'none') {
pathTo = graphics.roundPathCorners(
pathTo,
w.config.plotOptions.bar.borderRadius
)
}
if (w.config.chart.stacked) {
let _ctx = this.barCtx
_ctx = this.barCtx[seriesGroup]
_ctx.xArrj.push(x2 + strokeCenter * direction)
_ctx.xArrjF.push(Math.abs(x1 - x2 - strokeWidth * direction))
_ctx.xArrjVal.push(this.barCtx.series[i][j])
}
return {
pathTo,
pathFrom,
}
}
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)
}
}
}
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, translationsIndex, zeroPositionForNull = true) {
let yForVal = zeroPositionForNull ? zeroH : null
if (typeof value !== 'undefined' && value !== null) {
yForVal =
zeroH -
value / this.barCtx.yRatio[translationsIndex] +
(this.barCtx.isReversed
? value / this.barCtx.yRatio[translationsIndex]
: 0) *
2
}
return yForVal
}
getGoalValues(type, zeroW, zeroH, i, j, translationsIndex) {
const w = this.w
let goals = []
const pushGoal = (value, attrs) => {
goals.push({
[type]:
type === 'x'
? this.getXForValue(value, zeroW, false)
: this.getYForValue(value, zeroH, translationsIndex, false),
attrs,
})
}
if (
w.globals.seriesGoals[i] &&
w.globals.seriesGoals[i][j] &&
Array.isArray(w.globals.seriesGoals[i][j])
) {
w.globals.seriesGoals[i][j].forEach((goal) => {
pushGoal(goal.value, goal)
})
}
if (this.barCtx.barOptions.isDumbbell && w.globals.seriesRange.length) {
let colors = this.barCtx.barOptions.dumbbellColors
? this.barCtx.barOptions.dumbbellColors
: w.globals.colors
const commonAttrs = {
strokeHeight: type === 'x' ? 0 : w.globals.markers.size[i],
strokeWidth: type === 'x' ? w.globals.markers.size[i] : 0,
strokeDashArray: 0,
strokeLineCap: 'round',
strokeColor: Array.isArray(colors[i]) ? colors[i][0] : colors[i],
}
pushGoal(w.globals.seriesRangeStart[i][j], commonAttrs)
pushGoal(w.globals.seriesRangeEnd[i][j], {
...commonAttrs,
strokeColor: Array.isArray(colors[i]) ? colors[i][1] : colors[i],
})
}
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',
})
lineGroup.node.classList.add('apexcharts-element-hidden')
this.barCtx.w.globals.delayedElements.push({
el: lineGroup.node,
})
lineGroup.attr(
'clip-path',
`url(#gridRectMarkerMask${this.barCtx.w.globals.cuid})`
)
let line = null
if (this.barCtx.isHorizontal) {
if (Array.isArray(goalX)) {
goalX.forEach((goal) => {
// Need a tiny margin of 1 each side so goals don't disappear at extremeties
if (goal.x >= -1 && goal.x <= graphics.w.globals.gridWidth + 1) {
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) => {
// Need a tiny margin of 1 each side so goals don't disappear at extremeties
if (goal.y >= -1 && goal.y <= graphics.w.globals.gridHeight + 1) {
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
}
drawBarShadow({ prevPaths, currPaths, color }) {
const w = this.w
const { x: prevX2, x1: prevX1, barYPosition: prevY1 } = prevPaths
const { x: currX2, x1: currX1, barYPosition: currY1 } = currPaths
const prevY2 = prevY1 + currPaths.barHeight
const graphics = new Graphics(this.barCtx.ctx)
const utils = new Utils()
const shadowPath =
graphics.move(prevX1, prevY2) +
graphics.line(prevX2, prevY2) +
graphics.line(currX2, currY1) +
graphics.line(currX1, currY1) +
graphics.line(prevX1, prevY2) +
(w.config.plotOptions.bar.borderRadiusApplication === 'around' ||
this.arrBorderRadius[realIndex][j] === 'both'
? ' Z'
: ' z')
return graphics.drawPath({
d: shadowPath,
fill: utils.shadeColor(0.5, Utils.rgb2hex(color)),
stroke: 'none',
strokeWidth: 0,
fillOpacity: 1,
classes: 'apexcharts-bar-shadow apexcharts-decoration-element',
})
}
getZeroValueEncounters({ i, j }) {
const w = this.w
let nonZeroColumns = 0
let zeroEncounters = 0
let seriesIndices = w.config.plotOptions.bar.horizontal
? w.globals.series.map((_, _i) => _i)
: w.globals.columnSeries?.i.map((_i) => _i) || []
seriesIndices.forEach((_si) => {
let val = w.globals.seriesPercent[_si][j]
if (val) {
nonZeroColumns++
}
if (_si < i && val === 0) {
zeroEncounters++
}
})
return {
nonZeroColumns,
zeroEncounters,
}
}
getGroupIndex(seriesIndex) {
const w = this.w
// groupIndex is the index of group buckets (group1, group2, ...)
let groupIndex = w.globals.seriesGroups.findIndex(
(group) =>
// w.config.series[i].name may be undefined, so use
// w.globals.seriesNames[i], which has default names for those
// series. w.globals.seriesGroups[] uses the same default naming.
group.indexOf(w.globals.seriesNames[seriesIndex]) > -1
)
// We need the column groups to be indexable as 0,1,2,... for their
// positioning relative to each other.
let cGI = this.barCtx.columnGroupIndices
let columnGroupIndex = cGI.indexOf(groupIndex)
if (columnGroupIndex < 0) {
cGI.push(groupIndex)
columnGroupIndex = cGI.length - 1
}
return { groupIndex, columnGroupIndex }
}
}

View File

@@ -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
}
}

View File

@@ -0,0 +1,154 @@
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.fill.type === 'gradient' ||
w.config.fill.type[i] === 'gradient'
) {
const coreUtils = new CoreUtils(this.lineCtx.ctx, w)
// applied only to LINE chart
// 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 = []
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
if (j === 0) {
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
)
return {
x: ptX,
y: ptY,
}
}
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,
realIndex,
series,
prevY,
lineYPosition,
translationsIndex,
}) {
let w = this.w
let stackSeries =
(w.config.chart.stacked && !w.globals.comboCharts) ||
(w.config.chart.stacked &&
w.globals.comboCharts &&
(!this.w.config.chart.stackOnlyBar ||
this.w.config.series[realIndex]?.type === 'bar' ||
this.w.config.series[realIndex]?.type === 'column'))
if (typeof series[i]?.[0] !== 'undefined') {
if (stackSeries) {
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[translationsIndex] +
(this.lineCtx.isReversed
? series[i][0] / this.lineCtx.yRatio[translationsIndex]
: 0) *
2
} else {
// the first value in the current series is null
if (stackSeries && 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,
}
}
}

View File

@@ -0,0 +1,200 @@
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) {
// The shadeColor function may return either an RGB or a hex color value
// However, hexToRgba requires the input to be in hex format
// The ternary operator checks if the color is in RGB format, and if so, converts it to hex
if (this.w.config.theme.mode === 'dark') {
const shadeColor = utils.shadeColor(
colorShadePercent * -1,
colorProps.color
)
color = Utils.hexToRgba(
Utils.isColorHex(shadeColor) ? shadeColor : Utils.rgb2hex(shadeColor),
w.config.fill.opacity
)
} else {
const shadeColor = utils.shadeColor(colorShadePercent, colorProps.color)
color = Utils.hexToRgba(
Utils.isColorHex(shadeColor) ? shadeColor : Utils.rgb2hex(shadeColor),
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)
)
}
}