feat(openclaw): add shopping, supermarket deals, geizhals, and ebay MCP tools
This commit is contained in:
+674
@@ -0,0 +1,674 @@
|
||||
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()
|
||||
|
||||
y = initPositions.y
|
||||
barHeight = initPositions.barHeight
|
||||
yDivision = initPositions.yDivision
|
||||
zeroW = initPositions.zeroW
|
||||
|
||||
x = initPositions.x
|
||||
barWidth = initPositions.barWidth
|
||||
xDivision = initPositions.xDivision
|
||||
zeroH = initPositions.zeroH
|
||||
|
||||
if (!this.horizontal) {
|
||||
xArrj.push(x + barWidth / 2)
|
||||
}
|
||||
|
||||
// eldatalabels
|
||||
let elDataLabelsWrap = graphics.group({
|
||||
class: 'apexcharts-datalabels',
|
||||
'data:realIndex': realIndex,
|
||||
})
|
||||
|
||||
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 === 'string' && pathFill?.indexOf('url') === -1
|
||||
? pathFill
|
||||
: Utils.hexToRgba(w.globals.colors[i]),
|
||||
prevPaths: this.pathArr[this.pathArr.length - 1],
|
||||
currPaths: paths,
|
||||
})
|
||||
|
||||
if (barShadow) {
|
||||
elBarShadows.add(barShadow)
|
||||
}
|
||||
}
|
||||
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,
|
||||
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 (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
|
||||
+570
@@ -0,0 +1,570 @@
|
||||
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 = ''
|
||||
|
||||
if (w.globals.isBarHorizontal) {
|
||||
if (
|
||||
this.barHelpers.arrBorderRadius[realIndex][j] === 'bottom' &&
|
||||
w.globals.series[realIndex][j] > 0
|
||||
) {
|
||||
classes = 'apexcharts-flip-x'
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
this.barHelpers.arrBorderRadius[realIndex][j] === 'bottom' &&
|
||||
w.globals.series[realIndex][j] > 0
|
||||
) {
|
||||
classes = 'apexcharts-flip-y'
|
||||
}
|
||||
}
|
||||
elSeries = this.renderSeries({
|
||||
realIndex,
|
||||
pathFill,
|
||||
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
|
||||
Generated
Vendored
+435
@@ -0,0 +1,435 @@
|
||||
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()
|
||||
|
||||
y = initPositions.y
|
||||
barHeight = initPositions.barHeight
|
||||
yDivision = initPositions.yDivision
|
||||
zeroW = initPositions.zeroW
|
||||
|
||||
x = initPositions.x
|
||||
barWidth = initPositions.barWidth
|
||||
xDivision = initPositions.xDivision
|
||||
zeroH = initPositions.zeroH
|
||||
|
||||
xArrj.push(x + barWidth / 2)
|
||||
|
||||
// eldatalabels
|
||||
let elDataLabelsWrap = graphics.group({
|
||||
class: 'apexcharts-datalabels',
|
||||
'data:realIndex': realIndex,
|
||||
})
|
||||
|
||||
for (let j = 0; j < w.globals.dataPoints; j++) {
|
||||
const strokeWidth = this.barHelpers.getStrokeWidth(i, j, realIndex)
|
||||
|
||||
let paths = null
|
||||
const pathsParams = {
|
||||
indexes: {
|
||||
i,
|
||||
j,
|
||||
realIndex,
|
||||
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
|
||||
|
||||
// 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,
|
||||
visibleSeries: this.visibleI,
|
||||
type: w.config.chart.type,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// push all x val arrays into main xArr
|
||||
w.globals.seriesXvalues[realIndex] = xArrj
|
||||
w.globals.seriesYvalues[realIndex] = yArrj
|
||||
|
||||
ret.add(elSeries)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
drawVerticalBoxPaths({
|
||||
indexes,
|
||||
x,
|
||||
y,
|
||||
xDivision,
|
||||
barWidth,
|
||||
zeroH,
|
||||
strokeWidth,
|
||||
}) {
|
||||
let w = this.w
|
||||
let graphics = new Graphics(this.ctx)
|
||||
|
||||
let i = indexes.i
|
||||
let j = indexes.j
|
||||
|
||||
let isPositive = true
|
||||
let colorPos = w.config.plotOptions.candlestick.colors.upward
|
||||
let colorNeg = w.config.plotOptions.candlestick.colors.downward
|
||||
let color = ''
|
||||
|
||||
if (this.isBoxPlot) {
|
||||
color = [this.boxOptions.colors.lower, this.boxOptions.colors.upper]
|
||||
}
|
||||
|
||||
const yRatio = this.yRatio[indexes.translationsIndex]
|
||||
let realIndex = indexes.realIndex
|
||||
|
||||
const ohlc = this.getOHLCValue(realIndex, j)
|
||||
let l1 = zeroH
|
||||
let l2 = zeroH
|
||||
|
||||
if (ohlc.o > ohlc.c) {
|
||||
isPositive = false
|
||||
}
|
||||
|
||||
let y1 = Math.min(ohlc.o, ohlc.c)
|
||||
let y2 = Math.max(ohlc.o, ohlc.c)
|
||||
let m = ohlc.m
|
||||
|
||||
if (w.globals.isXNumeric) {
|
||||
x =
|
||||
(w.globals.seriesX[realIndex][j] - w.globals.minX) / this.xRatio -
|
||||
barWidth / 2
|
||||
}
|
||||
|
||||
let barXPosition = x + barWidth * this.visibleI
|
||||
|
||||
if (
|
||||
typeof this.series[i][j] === 'undefined' ||
|
||||
this.series[i][j] === null
|
||||
) {
|
||||
y1 = zeroH
|
||||
y2 = zeroH
|
||||
} else {
|
||||
y1 = zeroH - y1 / yRatio
|
||||
y2 = zeroH - y2 / yRatio
|
||||
l1 = zeroH - ohlc.h / yRatio
|
||||
l2 = zeroH - ohlc.l / yRatio
|
||||
m = zeroH - ohlc.m / yRatio
|
||||
}
|
||||
|
||||
let pathTo = graphics.move(barXPosition, zeroH)
|
||||
let pathFrom = graphics.move(barXPosition + barWidth / 2, y1)
|
||||
if (w.globals.previousPaths.length > 0) {
|
||||
pathFrom = this.getPreviousPath(realIndex, j, true)
|
||||
}
|
||||
|
||||
if (this.isBoxPlot) {
|
||||
pathTo = [
|
||||
graphics.move(barXPosition, y1) +
|
||||
graphics.line(barXPosition + barWidth / 2, y1) +
|
||||
graphics.line(barXPosition + barWidth / 2, l1) +
|
||||
graphics.line(barXPosition + barWidth / 4, l1) +
|
||||
graphics.line(barXPosition + barWidth - barWidth / 4, l1) +
|
||||
graphics.line(barXPosition + barWidth / 2, l1) +
|
||||
graphics.line(barXPosition + barWidth / 2, y1) +
|
||||
graphics.line(barXPosition + barWidth, y1) +
|
||||
graphics.line(barXPosition + barWidth, m) +
|
||||
graphics.line(barXPosition, m) +
|
||||
graphics.line(barXPosition, y1 + strokeWidth / 2),
|
||||
graphics.move(barXPosition, m) +
|
||||
graphics.line(barXPosition + barWidth, m) +
|
||||
graphics.line(barXPosition + barWidth, y2) +
|
||||
graphics.line(barXPosition + barWidth / 2, y2) +
|
||||
graphics.line(barXPosition + barWidth / 2, l2) +
|
||||
graphics.line(barXPosition + barWidth - barWidth / 4, l2) +
|
||||
graphics.line(barXPosition + barWidth / 4, l2) +
|
||||
graphics.line(barXPosition + barWidth / 2, l2) +
|
||||
graphics.line(barXPosition + barWidth / 2, y2) +
|
||||
graphics.line(barXPosition, y2) +
|
||||
graphics.line(barXPosition, m) +
|
||||
'z',
|
||||
]
|
||||
} else {
|
||||
// candlestick
|
||||
pathTo = [
|
||||
graphics.move(barXPosition, y2) +
|
||||
graphics.line(barXPosition + barWidth / 2, y2) +
|
||||
graphics.line(barXPosition + barWidth / 2, l1) +
|
||||
graphics.line(barXPosition + barWidth / 2, y2) +
|
||||
graphics.line(barXPosition + barWidth, y2) +
|
||||
graphics.line(barXPosition + barWidth, y1) +
|
||||
graphics.line(barXPosition + barWidth / 2, y1) +
|
||||
graphics.line(barXPosition + barWidth / 2, l2) +
|
||||
graphics.line(barXPosition + barWidth / 2, y1) +
|
||||
graphics.line(barXPosition, y1) +
|
||||
graphics.line(barXPosition, y2 - strokeWidth / 2),
|
||||
]
|
||||
}
|
||||
|
||||
pathFrom = pathFrom + graphics.move(barXPosition, y1)
|
||||
|
||||
if (!w.globals.isXNumeric) {
|
||||
x = x + xDivision
|
||||
}
|
||||
|
||||
return {
|
||||
pathTo,
|
||||
pathFrom,
|
||||
x,
|
||||
y: y2,
|
||||
barXPosition,
|
||||
color: this.isBoxPlot ? color : isPositive ? [colorPos] : [colorNeg],
|
||||
}
|
||||
}
|
||||
|
||||
drawHorizontalBoxPaths({
|
||||
indexes,
|
||||
x,
|
||||
y,
|
||||
yDivision,
|
||||
barHeight,
|
||||
zeroW,
|
||||
strokeWidth,
|
||||
}) {
|
||||
let w = this.w
|
||||
let graphics = new Graphics(this.ctx)
|
||||
|
||||
let i = indexes.i
|
||||
let j = indexes.j
|
||||
|
||||
let color = this.boxOptions.colors.lower
|
||||
|
||||
if (this.isBoxPlot) {
|
||||
color = [this.boxOptions.colors.lower, this.boxOptions.colors.upper]
|
||||
}
|
||||
|
||||
const yRatio = this.invertedYRatio
|
||||
let realIndex = indexes.realIndex
|
||||
|
||||
const ohlc = this.getOHLCValue(realIndex, j)
|
||||
let l1 = zeroW
|
||||
let l2 = zeroW
|
||||
|
||||
let x1 = Math.min(ohlc.o, ohlc.c)
|
||||
let x2 = Math.max(ohlc.o, ohlc.c)
|
||||
let m = ohlc.m
|
||||
|
||||
if (w.globals.isXNumeric) {
|
||||
y =
|
||||
(w.globals.seriesX[realIndex][j] - w.globals.minX) /
|
||||
this.invertedXRatio -
|
||||
barHeight / 2
|
||||
}
|
||||
|
||||
let barYPosition = y + barHeight * this.visibleI
|
||||
|
||||
if (
|
||||
typeof this.series[i][j] === 'undefined' ||
|
||||
this.series[i][j] === null
|
||||
) {
|
||||
x1 = zeroW
|
||||
x2 = zeroW
|
||||
} else {
|
||||
x1 = zeroW + x1 / yRatio
|
||||
x2 = zeroW + x2 / yRatio
|
||||
l1 = zeroW + ohlc.h / yRatio
|
||||
l2 = zeroW + ohlc.l / yRatio
|
||||
m = zeroW + ohlc.m / yRatio
|
||||
}
|
||||
|
||||
let pathTo = graphics.move(zeroW, barYPosition)
|
||||
let pathFrom = graphics.move(x1, barYPosition + barHeight / 2)
|
||||
if (w.globals.previousPaths.length > 0) {
|
||||
pathFrom = this.getPreviousPath(realIndex, j, true)
|
||||
}
|
||||
|
||||
pathTo = [
|
||||
graphics.move(x1, barYPosition) +
|
||||
graphics.line(x1, barYPosition + barHeight / 2) +
|
||||
graphics.line(l1, barYPosition + barHeight / 2) +
|
||||
graphics.line(l1, barYPosition + barHeight / 2 - barHeight / 4) +
|
||||
graphics.line(l1, barYPosition + barHeight / 2 + barHeight / 4) +
|
||||
graphics.line(l1, barYPosition + barHeight / 2) +
|
||||
graphics.line(x1, barYPosition + barHeight / 2) +
|
||||
graphics.line(x1, barYPosition + barHeight) +
|
||||
graphics.line(m, barYPosition + barHeight) +
|
||||
graphics.line(m, barYPosition) +
|
||||
graphics.line(x1 + strokeWidth / 2, barYPosition),
|
||||
graphics.move(m, barYPosition) +
|
||||
graphics.line(m, barYPosition + barHeight) +
|
||||
graphics.line(x2, barYPosition + barHeight) +
|
||||
graphics.line(x2, barYPosition + barHeight / 2) +
|
||||
graphics.line(l2, barYPosition + barHeight / 2) +
|
||||
graphics.line(l2, barYPosition + barHeight - barHeight / 4) +
|
||||
graphics.line(l2, barYPosition + barHeight / 4) +
|
||||
graphics.line(l2, barYPosition + barHeight / 2) +
|
||||
graphics.line(x2, barYPosition + barHeight / 2) +
|
||||
graphics.line(x2, barYPosition) +
|
||||
graphics.line(m, barYPosition) +
|
||||
'z',
|
||||
]
|
||||
|
||||
pathFrom = pathFrom + graphics.move(x1, barYPosition)
|
||||
|
||||
if (!w.globals.isXNumeric) {
|
||||
y = y + yDivision
|
||||
}
|
||||
|
||||
return {
|
||||
pathTo,
|
||||
pathFrom,
|
||||
x: x2,
|
||||
y,
|
||||
barYPosition,
|
||||
color,
|
||||
}
|
||||
}
|
||||
getOHLCValue(i, j) {
|
||||
const w = this.w
|
||||
|
||||
return {
|
||||
o: this.isBoxPlot
|
||||
? w.globals.seriesCandleH[i][j]
|
||||
: w.globals.seriesCandleO[i][j],
|
||||
h: this.isBoxPlot
|
||||
? w.globals.seriesCandleO[i][j]
|
||||
: w.globals.seriesCandleH[i][j],
|
||||
m: w.globals.seriesCandleM[i][j],
|
||||
l: this.isBoxPlot
|
||||
? w.globals.seriesCandleC[i][j]
|
||||
: w.globals.seriesCandleL[i][j],
|
||||
c: this.isBoxPlot
|
||||
? w.globals.seriesCandleL[i][j]
|
||||
: w.globals.seriesCandleC[i][j],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default BoxCandleStick
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
import Animations from '../modules/Animations'
|
||||
import Graphics from '../modules/Graphics'
|
||||
import Fill from '../modules/Fill'
|
||||
import Utils from '../utils/Utils'
|
||||
import Helpers from './common/treemap/Helpers'
|
||||
import Filters from '../modules/Filters'
|
||||
|
||||
/**
|
||||
* ApexCharts HeatMap Class.
|
||||
* @module HeatMap
|
||||
**/
|
||||
|
||||
export default class HeatMap {
|
||||
constructor(ctx, xyRatios) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
|
||||
this.xRatio = xyRatios.xRatio
|
||||
this.yRatio = xyRatios.yRatio
|
||||
|
||||
this.dynamicAnim = this.w.config.chart.animations.dynamicAnimation
|
||||
|
||||
this.helpers = new Helpers(ctx)
|
||||
this.rectRadius = this.w.config.plotOptions.heatmap.radius
|
||||
this.strokeWidth = this.w.config.stroke.show
|
||||
? this.w.config.stroke.width
|
||||
: 0
|
||||
}
|
||||
|
||||
draw(series) {
|
||||
let w = this.w
|
||||
const graphics = new Graphics(this.ctx)
|
||||
|
||||
let ret = graphics.group({
|
||||
class: 'apexcharts-heatmap',
|
||||
})
|
||||
|
||||
ret.attr('clip-path', `url(#gridRectMask${w.globals.cuid})`)
|
||||
|
||||
// width divided into equal parts
|
||||
let xDivision = w.globals.gridWidth / w.globals.dataPoints
|
||||
let yDivision = w.globals.gridHeight / w.globals.series.length
|
||||
|
||||
let y1 = 0
|
||||
let rev = false
|
||||
|
||||
this.negRange = this.helpers.checkColorRange()
|
||||
|
||||
let heatSeries = series.slice()
|
||||
|
||||
if (w.config.yaxis[0].reversed) {
|
||||
rev = true
|
||||
heatSeries.reverse()
|
||||
}
|
||||
|
||||
for (
|
||||
let i = rev ? 0 : heatSeries.length - 1;
|
||||
rev ? i < heatSeries.length : i >= 0;
|
||||
rev ? i++ : i--
|
||||
) {
|
||||
// el to which series will be drawn
|
||||
let elSeries = graphics.group({
|
||||
class: `apexcharts-series apexcharts-heatmap-series`,
|
||||
seriesName: Utils.escapeString(w.globals.seriesNames[i]),
|
||||
rel: i + 1,
|
||||
'data:realIndex': i,
|
||||
})
|
||||
this.ctx.series.addCollapsedClassToSeries(elSeries, i)
|
||||
|
||||
if (w.config.chart.dropShadow.enabled) {
|
||||
const shadow = w.config.chart.dropShadow
|
||||
const filters = new Filters(this.ctx)
|
||||
filters.dropShadow(elSeries, shadow, i)
|
||||
}
|
||||
|
||||
let x1 = 0
|
||||
let shadeIntensity = w.config.plotOptions.heatmap.shadeIntensity
|
||||
|
||||
for (let j = 0; j < heatSeries[i].length; j++) {
|
||||
let heatColor = this.helpers.getShadeColor(
|
||||
w.config.chart.type,
|
||||
i,
|
||||
j,
|
||||
this.negRange
|
||||
)
|
||||
let color = heatColor.color
|
||||
let heatColorProps = heatColor.colorProps
|
||||
|
||||
if (w.config.fill.type === 'image') {
|
||||
const fill = new Fill(this.ctx)
|
||||
|
||||
color = fill.fillPath({
|
||||
seriesNumber: i,
|
||||
dataPointIndex: j,
|
||||
opacity: w.globals.hasNegs
|
||||
? heatColorProps.percent < 0
|
||||
? 1 - (1 + heatColorProps.percent / 100)
|
||||
: shadeIntensity + heatColorProps.percent / 100
|
||||
: heatColorProps.percent / 100,
|
||||
patternID: Utils.randomId(),
|
||||
width: w.config.fill.image.width
|
||||
? w.config.fill.image.width
|
||||
: xDivision,
|
||||
height: w.config.fill.image.height
|
||||
? w.config.fill.image.height
|
||||
: yDivision,
|
||||
})
|
||||
}
|
||||
|
||||
let radius = this.rectRadius
|
||||
|
||||
let rect = graphics.drawRect(x1, y1, xDivision, yDivision, radius)
|
||||
rect.attr({
|
||||
cx: x1,
|
||||
cy: y1,
|
||||
})
|
||||
|
||||
rect.node.classList.add('apexcharts-heatmap-rect')
|
||||
elSeries.add(rect)
|
||||
|
||||
rect.attr({
|
||||
fill: color,
|
||||
i,
|
||||
index: i,
|
||||
j,
|
||||
val: 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
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
+1139
File diff suppressed because it is too large
Load Diff
+1021
File diff suppressed because it is too large
Load Diff
+536
@@ -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
|
||||
+545
@@ -0,0 +1,545 @@
|
||||
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,
|
||||
easing: w.globals.easing,
|
||||
})
|
||||
}
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
drawArcs(opts) {
|
||||
let w = this.w
|
||||
// size, donutSize, centerX, centerY, colorArr, lineColorArr, sectorAngleArr, series
|
||||
|
||||
let graphics = new Graphics(this.ctx)
|
||||
let fill = new Fill(this.ctx)
|
||||
let filters = new Filters(this.ctx)
|
||||
let g = graphics.group()
|
||||
|
||||
let strokeWidth = this.getStrokeWidth(opts)
|
||||
opts.size = opts.size - strokeWidth / 2
|
||||
|
||||
let hollowFillID = w.config.plotOptions.radialBar.hollow.background
|
||||
let hollowSize =
|
||||
opts.size -
|
||||
strokeWidth * opts.series.length -
|
||||
this.margin * opts.series.length -
|
||||
(strokeWidth *
|
||||
parseInt(w.config.plotOptions.radialBar.track.strokeWidth, 10)) /
|
||||
100 /
|
||||
2
|
||||
|
||||
let hollowRadius = hollowSize - w.config.plotOptions.radialBar.hollow.margin
|
||||
|
||||
if (w.config.plotOptions.radialBar.hollow.image !== undefined) {
|
||||
hollowFillID = this.drawHollowImage(opts, g, hollowSize, hollowFillID)
|
||||
}
|
||||
|
||||
let elHollow = this.drawHollow({
|
||||
size: hollowRadius,
|
||||
centerX: opts.centerX,
|
||||
centerY: opts.centerY,
|
||||
fill: hollowFillID ? hollowFillID : 'transparent',
|
||||
})
|
||||
|
||||
if (w.config.plotOptions.radialBar.hollow.dropShadow.enabled) {
|
||||
const shadow = w.config.plotOptions.radialBar.hollow.dropShadow
|
||||
filters.dropShadow(elHollow, shadow)
|
||||
}
|
||||
|
||||
let shown = 1
|
||||
if (!this.radialDataLabels.total.show && w.globals.series.length > 1) {
|
||||
shown = 0
|
||||
}
|
||||
|
||||
let dataLabels = null
|
||||
|
||||
if (this.radialDataLabels.show) {
|
||||
let dataLabelsGroup = w.globals.dom.Paper.select(
|
||||
`.apexcharts-datalabels-group`
|
||||
).members[0]
|
||||
|
||||
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,
|
||||
easing: w.globals.easing,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
g,
|
||||
elHollow,
|
||||
dataLabels,
|
||||
}
|
||||
}
|
||||
|
||||
drawHollow(opts) {
|
||||
const graphics = new Graphics(this.ctx)
|
||||
|
||||
let circle = graphics.drawCircle(opts.size * 2)
|
||||
|
||||
circle.attr({
|
||||
class: 'apexcharts-radialbar-hollow',
|
||||
cx: opts.centerX,
|
||||
cy: opts.centerY,
|
||||
r: opts.size,
|
||||
fill: opts.fill,
|
||||
})
|
||||
|
||||
return circle
|
||||
}
|
||||
|
||||
drawHollowImage(opts, g, hollowSize, hollowFillID) {
|
||||
const w = this.w
|
||||
let fill = new Fill(this.ctx)
|
||||
|
||||
let randID = Utils.randomId()
|
||||
let hollowFillImg = w.config.plotOptions.radialBar.hollow.image
|
||||
|
||||
if (w.config.plotOptions.radialBar.hollow.imageClipped) {
|
||||
fill.clippedImgArea({
|
||||
width: hollowSize,
|
||||
height: hollowSize,
|
||||
image: hollowFillImg,
|
||||
patternID: `pattern${w.globals.cuid}${randID}`,
|
||||
})
|
||||
hollowFillID = `url(#pattern${w.globals.cuid}${randID})`
|
||||
} else {
|
||||
const imgWidth = w.config.plotOptions.radialBar.hollow.imageWidth
|
||||
const imgHeight = w.config.plotOptions.radialBar.hollow.imageHeight
|
||||
if (imgWidth === undefined && imgHeight === undefined) {
|
||||
let image = w.globals.dom.Paper.image(hollowFillImg).loaded(function (
|
||||
loader
|
||||
) {
|
||||
this.move(
|
||||
opts.centerX -
|
||||
loader.width / 2 +
|
||||
w.config.plotOptions.radialBar.hollow.imageOffsetX,
|
||||
opts.centerY -
|
||||
loader.height / 2 +
|
||||
w.config.plotOptions.radialBar.hollow.imageOffsetY
|
||||
)
|
||||
})
|
||||
g.add(image)
|
||||
} else {
|
||||
let image = w.globals.dom.Paper.image(hollowFillImg).loaded(function (
|
||||
loader
|
||||
) {
|
||||
this.move(
|
||||
opts.centerX -
|
||||
imgWidth / 2 +
|
||||
w.config.plotOptions.radialBar.hollow.imageOffsetX,
|
||||
opts.centerY -
|
||||
imgHeight / 2 +
|
||||
w.config.plotOptions.radialBar.hollow.imageOffsetY
|
||||
)
|
||||
this.size(imgWidth, imgHeight)
|
||||
})
|
||||
g.add(image)
|
||||
}
|
||||
}
|
||||
return hollowFillID
|
||||
}
|
||||
|
||||
getStrokeWidth(opts) {
|
||||
const w = this.w
|
||||
return (
|
||||
(opts.size *
|
||||
(100 - parseInt(w.config.plotOptions.radialBar.hollow.size, 10))) /
|
||||
100 /
|
||||
(opts.series.length + 1) -
|
||||
this.margin
|
||||
)
|
||||
}
|
||||
|
||||
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
@@ -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()
|
||||
|
||||
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)
|
||||
|
||||
let lineFill = w.globals.stroke.colors[realIndex]
|
||||
|
||||
this.renderSeries({
|
||||
realIndex,
|
||||
pathFill,
|
||||
lineFill,
|
||||
j,
|
||||
i,
|
||||
x,
|
||||
y,
|
||||
y1,
|
||||
y2,
|
||||
pathFrom: paths.pathFrom,
|
||||
pathTo: paths.pathTo,
|
||||
strokeWidth,
|
||||
elSeries,
|
||||
series,
|
||||
barHeight,
|
||||
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
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
+357
@@ -0,0 +1,357 @@
|
||||
import '../libs/Treemap-squared'
|
||||
import Graphics from '../modules/Graphics'
|
||||
import Animations from '../modules/Animations'
|
||||
import Fill from '../modules/Fill'
|
||||
import Helpers from './common/treemap/Helpers'
|
||||
import Filters from '../modules/Filters'
|
||||
|
||||
import Utils from '../utils/Utils'
|
||||
|
||||
/**
|
||||
* ApexCharts TreemapChart Class.
|
||||
* @module TreemapChart
|
||||
**/
|
||||
|
||||
export default class TreemapChart {
|
||||
constructor(ctx, xyRatios) {
|
||||
this.ctx = ctx
|
||||
this.w = ctx.w
|
||||
|
||||
this.strokeWidth = this.w.config.stroke.width
|
||||
this.helpers = new Helpers(ctx)
|
||||
this.dynamicAnim = this.w.config.chart.animations.dynamicAnimation
|
||||
|
||||
this.labels = []
|
||||
}
|
||||
|
||||
draw(series) {
|
||||
let w = this.w
|
||||
const graphics = new Graphics(this.ctx)
|
||||
const fill = new Fill(this.ctx)
|
||||
|
||||
let ret = graphics.group({
|
||||
class: 'apexcharts-treemap',
|
||||
})
|
||||
|
||||
if (w.globals.noData) return ret
|
||||
|
||||
let ser = []
|
||||
series.forEach((s) => {
|
||||
let d = s.map((v) => {
|
||||
return Math.abs(v)
|
||||
})
|
||||
ser.push(d)
|
||||
})
|
||||
|
||||
this.negRange = this.helpers.checkColorRange()
|
||||
|
||||
w.config.series.forEach((s, i) => {
|
||||
s.data.forEach((l) => {
|
||||
if (!Array.isArray(this.labels[i])) this.labels[i] = []
|
||||
this.labels[i].push(l.x)
|
||||
})
|
||||
})
|
||||
|
||||
const nodes = window.TreemapSquared.generate(
|
||||
ser,
|
||||
w.globals.gridWidth,
|
||||
w.globals.gridHeight
|
||||
)
|
||||
|
||||
nodes.forEach((node, i) => {
|
||||
let elSeries = graphics.group({
|
||||
class: `apexcharts-series apexcharts-treemap-series`,
|
||||
seriesName: Utils.escapeString(w.globals.seriesNames[i]),
|
||||
rel: i + 1,
|
||||
'data:realIndex': i,
|
||||
})
|
||||
|
||||
if (w.config.chart.dropShadow.enabled) {
|
||||
const shadow = w.config.chart.dropShadow
|
||||
const filters = new Filters(this.ctx)
|
||||
filters.dropShadow(ret, shadow, i)
|
||||
}
|
||||
|
||||
let elDataLabelWrap = graphics.group({
|
||||
class: 'apexcharts-data-labels',
|
||||
})
|
||||
|
||||
node.forEach((r, j) => {
|
||||
const x1 = r[0]
|
||||
const y1 = r[1]
|
||||
const x2 = r[2]
|
||||
const y2 = r[3]
|
||||
let elRect = graphics.drawRect(
|
||||
x1,
|
||||
y1,
|
||||
x2 - x1,
|
||||
y2 - y1,
|
||||
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,
|
||||
})
|
||||
|
||||
let colorProps = this.helpers.getShadeColor(
|
||||
w.config.chart.type,
|
||||
i,
|
||||
j,
|
||||
this.negRange
|
||||
)
|
||||
let color = colorProps.color
|
||||
|
||||
if (
|
||||
typeof w.config.series[i].data[j] !== 'undefined' &&
|
||||
w.config.series[i].data[j].fillColor
|
||||
) {
|
||||
color = w.config.series[i].data[j].fillColor
|
||||
}
|
||||
let pathFill = fill.fillPath({
|
||||
color,
|
||||
seriesNumber: i,
|
||||
dataPointIndex: j,
|
||||
})
|
||||
|
||||
elRect.node.classList.add('apexcharts-treemap-rect')
|
||||
|
||||
elRect.attr({
|
||||
fill: pathFill,
|
||||
})
|
||||
|
||||
this.helpers.addListeners(elRect)
|
||||
|
||||
let fromRect = {
|
||||
x: x1 + (x2 - x1) / 2,
|
||||
y: y1 + (y2 - y1) / 2,
|
||||
width: 0,
|
||||
height: 0,
|
||||
}
|
||||
let toRect = {
|
||||
x: x1,
|
||||
y: y1,
|
||||
width: x2 - x1,
|
||||
height: y2 - y1,
|
||||
}
|
||||
|
||||
if (w.config.chart.animations.enabled && !w.globals.dataChanged) {
|
||||
let speed = 1
|
||||
if (!w.globals.resized) {
|
||||
speed = w.config.chart.animations.speed
|
||||
}
|
||||
this.animateTreemap(elRect, fromRect, toRect, speed)
|
||||
}
|
||||
if (w.globals.dataChanged) {
|
||||
let speed = 1
|
||||
if (this.dynamicAnim.enabled && w.globals.shouldAnimate) {
|
||||
speed = this.dynamicAnim.speed
|
||||
|
||||
if (
|
||||
w.globals.previousPaths[i] &&
|
||||
w.globals.previousPaths[i][j] &&
|
||||
w.globals.previousPaths[i][j].rect
|
||||
) {
|
||||
fromRect = w.globals.previousPaths[i][j].rect
|
||||
}
|
||||
|
||||
this.animateTreemap(elRect, fromRect, toRect, speed)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
elSeries.add(elDataLabelWrap)
|
||||
|
||||
ret.add(elSeries)
|
||||
})
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// This calculates a font-size based upon
|
||||
// average label length and the size of the box the label is
|
||||
// going into. The maximum font size is set in chart config.
|
||||
getFontSize(coordinates) {
|
||||
const w = this.w
|
||||
|
||||
// total length of labels (i.e [["Italy"],["Spain", "Greece"]] -> 16)
|
||||
function totalLabelLength(arr) {
|
||||
let i,
|
||||
total = 0
|
||||
if (Array.isArray(arr[0])) {
|
||||
for (i = 0; i < arr.length; i++) {
|
||||
total += totalLabelLength(arr[i])
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < arr.length; i++) {
|
||||
total += arr[i].length
|
||||
}
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// count of labels (i.e [["Italy"],["Spain", "Greece"]] -> 3)
|
||||
function countLabels(arr) {
|
||||
let i,
|
||||
total = 0
|
||||
if (Array.isArray(arr[0])) {
|
||||
for (i = 0; i < arr.length; i++) {
|
||||
total += countLabels(arr[i])
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < arr.length; i++) {
|
||||
total += 1
|
||||
}
|
||||
}
|
||||
return total
|
||||
}
|
||||
let averagelabelsize =
|
||||
totalLabelLength(this.labels) / countLabels(this.labels)
|
||||
|
||||
function fontSize(width, height) {
|
||||
// the font size should be proportional to the size of the box (and the value)
|
||||
// otherwise you can end up creating a visual distortion where two boxes of identical
|
||||
// size have different sized labels, and thus make it look as if the two boxes
|
||||
// represent different sizes
|
||||
let area = width * height
|
||||
let arearoot = Math.pow(area, 0.5)
|
||||
return Math.min(
|
||||
arearoot / averagelabelsize,
|
||||
parseInt(w.config.dataLabels.style.fontSize, 10)
|
||||
)
|
||||
}
|
||||
|
||||
return fontSize(
|
||||
coordinates[2] - coordinates[0],
|
||||
coordinates[3] - coordinates[1]
|
||||
)
|
||||
}
|
||||
|
||||
rotateToFitLabel(elText, 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,
|
||||
{
|
||||
x: fromRect.x,
|
||||
y: fromRect.y,
|
||||
width: fromRect.width,
|
||||
height: fromRect.height,
|
||||
},
|
||||
{
|
||||
x: toRect.x,
|
||||
y: toRect.y,
|
||||
width: toRect.width,
|
||||
height: toRect.height,
|
||||
},
|
||||
speed,
|
||||
() => {
|
||||
animations.animationCompleted(el)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+662
@@ -0,0 +1,662 @@
|
||||
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
|
||||
}
|
||||
|
||||
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 =
|
||||
newY -
|
||||
totalLabeltextRects.height / 2 -
|
||||
offY -
|
||||
barTotalDataLabelsConfig.offsetY +
|
||||
ADDITIONAL_OFFY
|
||||
} else {
|
||||
totalDataLabelsY =
|
||||
newY +
|
||||
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
|
||||
}
|
||||
|
||||
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 =
|
||||
newX - strokeWidth - offX - barTotalDataLabelsConfig.offsetX
|
||||
|
||||
totalDataLabelsAnchor = 'end'
|
||||
} else {
|
||||
totalDataLabelsX =
|
||||
newX +
|
||||
offX +
|
||||
barTotalDataLabelsConfig.offsetX +
|
||||
(this.barCtx.isReversed ? -(barWidth + strokeWidth) : strokeWidth)
|
||||
}
|
||||
totalDataLabelsY =
|
||||
dataLabelsY -
|
||||
textRects.height / 2 +
|
||||
totalLabeltextRects.height / 2 +
|
||||
barTotalDataLabelsConfig.offsetY +
|
||||
strokeWidth
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+806
@@ -0,0 +1,806 @@
|
||||
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() {
|
||||
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)
|
||||
|
||||
x =
|
||||
w.globals.padHorizontal +
|
||||
(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
|
||||
|
||||
if (this.barCtx.barOptions.colors.ranges.length > 0) {
|
||||
const colorRange = this.barCtx.barOptions.colors.ranges
|
||||
colorRange.map((range) => {
|
||||
if (series[i][j] >= range.from && series[i][j] <= range.to) {
|
||||
fillColor = range.color
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (w.config.series[i].data[j]?.fillColor) {
|
||||
fillColor = w.config.series[i].data[j].fillColor
|
||||
}
|
||||
|
||||
let pathFill = fill.fillPath({
|
||||
seriesNumber: this.barCtx.barOptions.distributed
|
||||
? seriesNumber
|
||||
: realIndex,
|
||||
dataPointIndex: j,
|
||||
color: fillColor,
|
||||
value: series[i][j],
|
||||
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 pathFill
|
||||
}
|
||||
|
||||
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.borderRadiusWhenStacked !== 'last' ||
|
||||
w.config.plotOptions.bar.borderRadius <= 0
|
||||
|
||||
const numSeries = series.length
|
||||
const numColumns = series[0].length
|
||||
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 firstNegativeIndex = negativeIndices[0]
|
||||
const lastNegativeIndex = negativeIndices[negativeIndices.length - 1]
|
||||
for (let i of negativeIndices) {
|
||||
if (i === firstNegativeIndex) {
|
||||
output[i][j] = 'bottom'
|
||||
} else if (i === lastNegativeIndex) {
|
||||
output[i][j] = 'top'
|
||||
} 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 last negative bar (closest to axis)
|
||||
const lastNegativeIndex = negativeIndices[negativeIndices.length - 1]
|
||||
for (let i of negativeIndices) {
|
||||
if (i === lastNegativeIndex) {
|
||||
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-shadows',
|
||||
})
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+30
@@ -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
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+151
@@ -0,0 +1,151 @@
|
||||
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 = []
|
||||
|
||||
if (j === 0) {
|
||||
let xPT1st =
|
||||
this.lineCtx.categoryAxisCorrection + w.config.markers.offsetX
|
||||
// the first point for line series
|
||||
// we need to check whether it's not a time series, because a time series may
|
||||
// start from the middle of the x axis
|
||||
if (w.globals.isXNumeric) {
|
||||
xPT1st =
|
||||
(w.globals.seriesX[realIndex][0] - w.globals.minX) /
|
||||
this.lineCtx.xRatio +
|
||||
w.config.markers.offsetX
|
||||
}
|
||||
|
||||
// push 2 points for the first data values
|
||||
ptX.push(xPT1st)
|
||||
ptY.push(
|
||||
Utils.isNumber(series[i][0]) ? prevY + w.config.markers.offsetY : null
|
||||
)
|
||||
ptX.push(x + w.config.markers.offsetX)
|
||||
ptY.push(
|
||||
Utils.isNumber(series[i][j + 1]) ? y + w.config.markers.offsetY : null
|
||||
)
|
||||
} else {
|
||||
ptX.push(x + w.config.markers.offsetX)
|
||||
ptY.push(
|
||||
Utils.isNumber(series[i][j + 1]) ? y + w.config.markers.offsetY : null
|
||||
)
|
||||
}
|
||||
|
||||
let pointsPos = {
|
||||
x: ptX,
|
||||
y: ptY,
|
||||
}
|
||||
|
||||
return pointsPos
|
||||
}
|
||||
|
||||
checkPreviousPaths({ pathFromLine, pathFromArea, realIndex }) {
|
||||
let w = this.w
|
||||
|
||||
for (let pp = 0; pp < w.globals.previousPaths.length; pp++) {
|
||||
let gpp = w.globals.previousPaths[pp]
|
||||
|
||||
if (
|
||||
(gpp.type === 'line' || gpp.type === 'area') &&
|
||||
gpp.paths.length > 0 &&
|
||||
parseInt(gpp.realIndex, 10) === parseInt(realIndex, 10)
|
||||
) {
|
||||
if (gpp.type === 'line') {
|
||||
this.lineCtx.appendPathFrom = false
|
||||
pathFromLine = w.globals.previousPaths[pp].paths[0].d
|
||||
} else if (gpp.type === 'area') {
|
||||
this.lineCtx.appendPathFrom = false
|
||||
pathFromArea = w.globals.previousPaths[pp].paths[0].d
|
||||
|
||||
if (w.config.stroke.show && w.globals.previousPaths[pp].paths[1]) {
|
||||
pathFromLine = w.globals.previousPaths[pp].paths[1].d
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
pathFromLine,
|
||||
pathFromArea,
|
||||
}
|
||||
}
|
||||
|
||||
determineFirstPrevY({ i, 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+200
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user