apexcharts
Version:
A JavaScript Chart Library
1,171 lines (1,053 loc) • 33.7 kB
JavaScript
import CoreUtils from '../modules/CoreUtils'
import Graphics from '../modules/Graphics'
import Fill from '../modules/Fill'
import DataLabels from '../modules/DataLabels'
import Markers from '../modules/Markers'
import Scatter from './Scatter'
import Utils from '../utils/Utils'
import Helpers from './common/line/Helpers'
import { svgPath, spline } from '../libs/monotone-cubic'
/**
* ApexCharts Line Class responsible for drawing Line / Area / RangeArea Charts.
* This class is also responsible for generating values for Bubble/Scatter charts, so need to rename it to Axis Charts to avoid confusions
* @module Line
**/
class Line {
constructor(ctx, xyRatios, isPointsChart) {
this.ctx = ctx
this.w = ctx.w
this.xyRatios = xyRatios
this.pointsChart =
!(
this.w.config.chart.type !== 'bubble' &&
this.w.config.chart.type !== 'scatter'
) || isPointsChart
this.scatter = new Scatter(this.ctx)
this.noNegatives = this.w.globals.minX === Number.MAX_VALUE
this.lineHelpers = new Helpers(this)
this.markers = new Markers(this.ctx)
this.prevSeriesY = []
this.categoryAxisCorrection = 0
this.yaxisIndex = 0
}
draw(series, ctype, seriesIndex, seriesRangeEnd) {
let w = this.w
let graphics = new Graphics(this.ctx)
let type = w.globals.comboCharts ? ctype : w.config.chart.type
let ret = graphics.group({
class: `apexcharts-${type}-series apexcharts-plot-series`,
})
const coreUtils = new CoreUtils(this.ctx, w)
this.yRatio = this.xyRatios.yRatio
this.zRatio = this.xyRatios.zRatio
this.xRatio = this.xyRatios.xRatio
this.baseLineY = this.xyRatios.baseLineY
series = coreUtils.getLogSeries(series)
this.yRatio = coreUtils.getLogYRatios(this.yRatio)
// We call draw() for each series group
this.prevSeriesY = []
// push all series in an array, so we can draw in reverse order
// (for stacked charts)
let allSeries = []
for (let i = 0; i < series.length; i++) {
series = this.lineHelpers.sameValueSeriesFix(i, series)
let realIndex = w.globals.comboCharts ? seriesIndex[i] : i
let translationsIndex = this.yRatio.length > 1 ? realIndex : 0
this._initSerieVariables(series, i, realIndex)
let yArrj = [] // hold y values of current iterating series
let y2Arrj = [] // holds y2 values in range-area charts
let xArrj = [] // hold x values of current iterating series
let x = w.globals.padHorizontal + this.categoryAxisCorrection
let y = 1
let linePaths = []
let areaPaths = []
this.ctx.series.addCollapsedClassToSeries(this.elSeries, realIndex)
if (w.globals.isXNumeric && w.globals.seriesX.length > 0) {
x = (w.globals.seriesX[realIndex][0] - w.globals.minX) / this.xRatio
}
xArrj.push(x)
let pX = x
let pY
let pY2
let prevX = pX
let prevY = this.zeroY
let prevY2 = this.zeroY
let lineYPosition = 0
// the first value in the current series is not null or undefined
let firstPrevY = this.lineHelpers.determineFirstPrevY({
i,
realIndex,
series,
prevY,
lineYPosition,
translationsIndex,
})
prevY = firstPrevY.prevY
if (w.config.stroke.curve === 'monotoneCubic' && series[i][0] === null) {
// we have to discard the y position if 1st dataPoint is null as it
// causes issues with monotoneCubic path creation
yArrj.push(null)
} else {
yArrj.push(prevY)
}
pY = prevY
// y2 are needed for range-area charts
let firstPrevY2
if (type === 'rangeArea') {
firstPrevY2 = this.lineHelpers.determineFirstPrevY({
i,
realIndex,
series: seriesRangeEnd,
prevY: prevY2,
lineYPosition,
translationsIndex,
})
prevY2 = firstPrevY2.prevY
pY2 = prevY2
y2Arrj.push(yArrj[0] !== null ? prevY2 : null)
}
let pathsFrom = this._calculatePathsFrom({
type,
series,
i,
realIndex,
translationsIndex,
prevX,
prevY,
prevY2,
})
// RangeArea will resume with these for the upper path creation
let rYArrj = [yArrj[0]]
let rY2Arrj = [y2Arrj[0]]
const iteratingOpts = {
type,
series,
realIndex,
translationsIndex,
i,
x,
y,
pX,
pY,
pathsFrom,
linePaths,
areaPaths,
seriesIndex,
lineYPosition,
xArrj,
yArrj,
y2Arrj,
seriesRangeEnd,
}
let paths = this._iterateOverDataPoints({
...iteratingOpts,
iterations: type === 'rangeArea' ? series[i].length - 1 : undefined,
isRangeStart: true,
})
if (type === 'rangeArea') {
let pathsFrom2 = this._calculatePathsFrom({
series: seriesRangeEnd,
i,
realIndex,
prevX,
prevY: prevY2,
})
let rangePaths = this._iterateOverDataPoints({
...iteratingOpts,
series: seriesRangeEnd,
xArrj: [x],
yArrj: rYArrj,
y2Arrj: rY2Arrj,
pY: pY2,
areaPaths: paths.areaPaths,
pathsFrom: pathsFrom2,
iterations: seriesRangeEnd[i].length - 1,
isRangeStart: false,
})
// Path may be segmented by nulls in data.
// paths.linePaths should hold (segments * 2) paths (upper and lower)
// the first n segments belong to the lower and the last n segments
// belong to the upper.
// paths.linePaths and rangePaths.linepaths are actually equivalent
// but we retain the distinction below for consistency with the
// unsegmented paths conditional branch.
let segments = paths.linePaths.length / 2
for (let s = 0; s < segments; s++) {
paths.linePaths[s] =
rangePaths.linePaths[s + segments] + paths.linePaths[s]
}
paths.linePaths.splice(segments)
paths.pathFromLine = rangePaths.pathFromLine + paths.pathFromLine
} else {
paths.pathFromArea += 'z'
}
this._handlePaths({ type, realIndex, i, paths })
this.elSeries.add(this.elPointsMain)
this.elSeries.add(this.elDataLabelsWrap)
allSeries.push(this.elSeries)
}
if (typeof w.config.series[0]?.zIndex !== 'undefined') {
allSeries.sort(
(a, b) =>
Number(a.node.getAttribute('zIndex')) -
Number(b.node.getAttribute('zIndex'))
)
}
if (w.config.chart.stacked) {
for (let s = allSeries.length - 1; s >= 0; s--) {
ret.add(allSeries[s])
}
} else {
for (let s = 0; s < allSeries.length; s++) {
ret.add(allSeries[s])
}
}
return ret
}
_initSerieVariables(series, i, realIndex) {
const w = this.w
const graphics = new Graphics(this.ctx)
// width divided into equal parts
this.xDivision =
w.globals.gridWidth /
(w.globals.dataPoints - (w.config.xaxis.tickPlacement === 'on' ? 1 : 0))
this.strokeWidth = Array.isArray(w.config.stroke.width)
? w.config.stroke.width[realIndex]
: w.config.stroke.width
let translationsIndex = 0
if (this.yRatio.length > 1) {
this.yaxisIndex = w.globals.seriesYAxisReverseMap[realIndex]
translationsIndex = realIndex
}
this.isReversed =
w.config.yaxis[this.yaxisIndex] &&
w.config.yaxis[this.yaxisIndex].reversed
// zeroY is the 0 value in y series which can be used in negative charts
this.zeroY =
w.globals.gridHeight -
this.baseLineY[translationsIndex] -
(this.isReversed ? w.globals.gridHeight : 0) +
(this.isReversed ? this.baseLineY[translationsIndex] * 2 : 0)
this.areaBottomY = this.zeroY
if (
this.zeroY > w.globals.gridHeight ||
w.config.plotOptions.area.fillTo === 'end'
) {
this.areaBottomY = w.globals.gridHeight
}
this.categoryAxisCorrection = this.xDivision / 2
// el to which series will be drawn
this.elSeries = graphics.group({
class: `apexcharts-series`,
zIndex:
typeof w.config.series[realIndex].zIndex !== 'undefined'
? w.config.series[realIndex].zIndex
: realIndex,
seriesName: Utils.escapeString(w.globals.seriesNames[realIndex]),
})
// points
this.elPointsMain = graphics.group({
class: 'apexcharts-series-markers-wrap',
'data:realIndex': realIndex,
})
if (w.globals.hasNullValues) {
// fixes https://github.com/apexcharts/apexcharts.js/issues/3641
const firstPoint = this.markers.plotChartMarkers({
pointsPos: {
x: [0],
y: [w.globals.gridHeight + w.globals.markers.largestSize],
},
seriesIndex: i,
j: 0,
pSize: 0.1,
alwaysDrawMarker: true,
isVirtualPoint: true,
})
if (firstPoint !== null) {
// firstPoint is rendered for cases where there are null values and when dynamic markers are required
this.elPointsMain.add(firstPoint)
}
}
// eldatalabels
this.elDataLabelsWrap = graphics.group({
class: 'apexcharts-datalabels',
'data:realIndex': realIndex,
})
let longestSeries = series[i].length === w.globals.dataPoints
this.elSeries.attr({
'data:longestSeries': longestSeries,
rel: i + 1,
'data:realIndex': realIndex,
})
this.appendPathFrom = true
}
_calculatePathsFrom({
type,
series,
i,
realIndex,
translationsIndex,
prevX,
prevY,
prevY2,
}) {
const w = this.w
const graphics = new Graphics(this.ctx)
let linePath, areaPath, pathFromLine, pathFromArea
if (series[i][0] === null) {
// when the first value itself is null, we need to move the pointer to a location where a null value is not found
for (let s = 0; s < series[i].length; s++) {
if (series[i][s] !== null) {
prevX = this.xDivision * s
prevY = this.zeroY - series[i][s] / this.yRatio[translationsIndex]
linePath = graphics.move(prevX, prevY)
areaPath = graphics.move(prevX, this.areaBottomY)
break
}
}
} else {
linePath = graphics.move(prevX, prevY)
if (type === 'rangeArea') {
linePath = graphics.move(prevX, prevY2) + graphics.line(prevX, prevY)
}
areaPath =
graphics.move(prevX, this.areaBottomY) + graphics.line(prevX, prevY)
}
pathFromLine =
graphics.move(0, this.areaBottomY) + graphics.line(0, this.areaBottomY)
pathFromArea =
graphics.move(0, this.areaBottomY) + graphics.line(0, this.areaBottomY)
if (w.globals.previousPaths.length > 0) {
const pathFrom = this.lineHelpers.checkPreviousPaths({
pathFromLine,
pathFromArea,
realIndex,
})
pathFromLine = pathFrom.pathFromLine
pathFromArea = pathFrom.pathFromArea
}
return {
prevX,
prevY,
linePath,
areaPath,
pathFromLine,
pathFromArea,
}
}
_handlePaths({ type, realIndex, i, paths }) {
const w = this.w
const graphics = new Graphics(this.ctx)
const fill = new Fill(this.ctx)
// push all current y values array to main PrevY Array
this.prevSeriesY.push(paths.yArrj)
// push all x val arrays into main xArr
w.globals.seriesXvalues[realIndex] = paths.xArrj
w.globals.seriesYvalues[realIndex] = paths.yArrj
const forecast = w.config.forecastDataPoints
if (forecast.count > 0 && type !== 'rangeArea') {
const forecastCutoff =
w.globals.seriesXvalues[realIndex][
w.globals.seriesXvalues[realIndex].length - forecast.count - 1
]
const elForecastMask = graphics.drawRect(
forecastCutoff,
0,
w.globals.gridWidth,
w.globals.gridHeight,
0
)
w.globals.dom.elForecastMask.appendChild(elForecastMask.node)
const elNonForecastMask = graphics.drawRect(
0,
0,
forecastCutoff,
w.globals.gridHeight,
0
)
w.globals.dom.elNonForecastMask.appendChild(elNonForecastMask.node)
}
// these elements will be shown after area path animation completes
if (!this.pointsChart) {
w.globals.delayedElements.push({
el: this.elPointsMain.node,
index: realIndex,
})
}
const defaultRenderedPathOptions = {
i,
realIndex,
animationDelay: i,
initialSpeed: w.config.chart.animations.speed,
dataChangeSpeed: w.config.chart.animations.dynamicAnimation.speed,
className: `apexcharts-${type}`,
}
if (type === 'area') {
let pathFill = fill.fillPath({
seriesNumber: realIndex,
})
for (let p = 0; p < paths.areaPaths.length; p++) {
let renderedPath = graphics.renderPaths({
...defaultRenderedPathOptions,
pathFrom: paths.pathFromArea,
pathTo: paths.areaPaths[p],
stroke: 'none',
strokeWidth: 0,
strokeLineCap: null,
fill: pathFill,
})
this.elSeries.add(renderedPath)
}
}
if (w.config.stroke.show && !this.pointsChart) {
let lineFill = null
if (type === 'line') {
lineFill = fill.fillPath({
seriesNumber: realIndex,
i,
})
} else {
if (w.config.stroke.fill.type === 'solid') {
lineFill = w.globals.stroke.colors[realIndex]
} else {
const prevFill = w.config.fill
w.config.fill = w.config.stroke.fill
lineFill = fill.fillPath({
seriesNumber: realIndex,
i,
})
w.config.fill = prevFill
}
}
// range-area paths are drawn using linePaths
for (let p = 0; p < paths.linePaths.length; p++) {
let pathFill = lineFill
if (type === 'rangeArea') {
pathFill = fill.fillPath({
seriesNumber: realIndex,
})
}
const linePathCommonOpts = {
...defaultRenderedPathOptions,
pathFrom: paths.pathFromLine,
pathTo: paths.linePaths[p],
stroke: lineFill,
strokeWidth: this.strokeWidth,
strokeLineCap: w.config.stroke.lineCap,
fill: type === 'rangeArea' ? pathFill : 'none',
}
let renderedPath = graphics.renderPaths(linePathCommonOpts)
this.elSeries.add(renderedPath)
renderedPath.attr('fill-rule', `evenodd`)
if (forecast.count > 0 && type !== 'rangeArea') {
let renderedForecastPath = graphics.renderPaths(linePathCommonOpts)
renderedForecastPath.node.setAttribute(
'stroke-dasharray',
forecast.dashArray
)
if (forecast.strokeWidth) {
renderedForecastPath.node.setAttribute(
'stroke-width',
forecast.strokeWidth
)
}
this.elSeries.add(renderedForecastPath)
renderedForecastPath.attr(
'clip-path',
`url(#forecastMask${w.globals.cuid})`
)
renderedPath.attr(
'clip-path',
`url(#nonForecastMask${w.globals.cuid})`
)
}
}
}
}
_iterateOverDataPoints({
type,
series,
iterations,
realIndex,
translationsIndex,
i,
x,
y,
pX,
pY,
pathsFrom,
linePaths,
areaPaths,
seriesIndex,
lineYPosition,
xArrj,
yArrj,
y2Arrj,
isRangeStart,
seriesRangeEnd,
}) {
const w = this.w
let graphics = new Graphics(this.ctx)
let yRatio = this.yRatio
let { prevY, linePath, areaPath, pathFromLine, pathFromArea } = pathsFrom
const minY = Utils.isNumber(w.globals.minYArr[realIndex])
? w.globals.minYArr[realIndex]
: w.globals.minY
if (!iterations) {
iterations =
w.globals.dataPoints > 1
? w.globals.dataPoints - 1
: w.globals.dataPoints
}
const getY = (_y, lineYPos) => {
return (
lineYPos -
_y / yRatio[translationsIndex] +
(this.isReversed ? _y / yRatio[translationsIndex] : 0) * 2
)
}
let y2 = y
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'))
let curve = w.config.stroke.curve
if (Array.isArray(curve)) {
if (Array.isArray(seriesIndex)) {
curve = curve[seriesIndex[i]]
} else {
curve = curve[i]
}
}
let pathState = 0
let segmentStartX
for (let j = 0; j < iterations; j++) {
if (series[i].length === 0) break
const isNull =
typeof series[i][j + 1] === 'undefined' || series[i][j + 1] === null
if (w.globals.isXNumeric) {
let sX = w.globals.seriesX[realIndex][j + 1]
if (typeof w.globals.seriesX[realIndex][j + 1] === 'undefined') {
/* fix #374 */
sX = w.globals.seriesX[realIndex][iterations - 1]
}
x = (sX - w.globals.minX) / this.xRatio
} else {
x = x + this.xDivision
}
if (stackSeries) {
if (
i > 0 &&
w.globals.collapsedSeries.length < w.config.series.length - 1
) {
// a collapsed series in a stacked chart may provide wrong result
// for the next series, hence find the prevIndex of prev series
// which is not collapsed - fixes apexcharts.js#1372
const prevIndex = (pi) => {
for (let pii = pi; pii > 0; pii--) {
if (
w.globals.collapsedSeriesIndices.indexOf(
seriesIndex?.[pii] || pii
) > -1
) {
pii--
} else {
return pii
}
}
return 0
}
lineYPosition = this.prevSeriesY[prevIndex(i - 1)][j + 1]
} else {
// the first series will not have prevY values
lineYPosition = this.zeroY
}
} else {
lineYPosition = this.zeroY
}
if (isNull) {
y = getY(minY, lineYPosition)
} else {
y = getY(series[i][j + 1], lineYPosition)
if (type === 'rangeArea') {
y2 = getY(seriesRangeEnd[i][j + 1], lineYPosition)
}
}
// push current X
xArrj.push(series[i][j + 1] === null ? null : x)
// push current Y that will be used as next series's bottom position
if (
isNull &&
(w.config.stroke.curve === 'smooth' ||
w.config.stroke.curve === 'monotoneCubic')
) {
yArrj.push(null)
y2Arrj.push(null)
} else {
yArrj.push(y)
y2Arrj.push(y2)
}
let pointsPos = this.lineHelpers.calculatePoints({
series,
x,
y,
realIndex,
i,
j,
prevY,
})
let calculatedPaths = this._createPaths({
type,
series,
i,
realIndex,
j,
x,
y,
y2,
xArrj,
yArrj,
y2Arrj,
pX,
pY,
pathState,
segmentStartX,
linePath,
areaPath,
linePaths,
areaPaths,
curve,
isRangeStart,
})
areaPaths = calculatedPaths.areaPaths
linePaths = calculatedPaths.linePaths
pX = calculatedPaths.pX
pY = calculatedPaths.pY
pathState = calculatedPaths.pathState
segmentStartX = calculatedPaths.segmentStartX
areaPath = calculatedPaths.areaPath
linePath = calculatedPaths.linePath
if (
this.appendPathFrom &&
!w.globals.hasNullValues &&
!(curve === 'monotoneCubic' && type === 'rangeArea')
) {
pathFromLine += graphics.line(x, this.areaBottomY)
pathFromArea += graphics.line(x, this.areaBottomY)
}
this.handleNullDataPoints(series, pointsPos, i, j, realIndex)
this._handleMarkersAndLabels({
type,
pointsPos,
i,
j,
realIndex,
isRangeStart,
})
}
return {
yArrj,
xArrj,
pathFromArea,
areaPaths,
pathFromLine,
linePaths,
linePath,
areaPath,
}
}
_handleMarkersAndLabels({ type, pointsPos, isRangeStart, i, j, realIndex }) {
const w = this.w
let dataLabels = new DataLabels(this.ctx)
if (!this.pointsChart) {
if (w.globals.series[i].length > 1) {
this.elPointsMain.node.classList.add('apexcharts-element-hidden')
}
let elPointsWrap = this.markers.plotChartMarkers({
pointsPos,
seriesIndex: realIndex,
j: j + 1,
})
if (elPointsWrap !== null) {
this.elPointsMain.add(elPointsWrap)
}
} else {
// scatter / bubble chart points creation
this.scatter.draw(this.elSeries, j, {
realIndex,
pointsPos,
zRatio: this.zRatio,
elParent: this.elPointsMain,
})
}
let drawnLabels = dataLabels.drawDataLabel({
type,
isRangeStart,
pos: pointsPos,
i: realIndex,
j: j + 1,
})
if (drawnLabels !== null) {
this.elDataLabelsWrap.add(drawnLabels)
}
}
_createPaths({
type,
series,
i,
realIndex,
j,
x,
y,
xArrj,
yArrj,
y2,
y2Arrj,
pX,
pY,
pathState,
segmentStartX,
linePath,
areaPath,
linePaths,
areaPaths,
curve,
isRangeStart,
}) {
let graphics = new Graphics(this.ctx)
const areaBottomY = this.areaBottomY
let rangeArea = type === 'rangeArea'
let isLowerRangeAreaPath = type === 'rangeArea' && isRangeStart
switch (curve) {
case 'monotoneCubic':
let yAj = isRangeStart ? yArrj : y2Arrj
let getSmoothInputs = (xArr, yArr) => {
return xArr
.map((_, i) => {
return [_, yArr[i]]
})
.filter((_) => _[1] !== null)
}
let getSegmentLengths = (yArr) => {
// Get the segment lengths so the segments can be extracted from
// the null-filtered smoothInputs array
let segLens = []
let count = 0
yArr.forEach((_) => {
if (_ !== null) {
count++
} else if (count > 0) {
segLens.push(count)
count = 0
}
})
if (count > 0) {
segLens.push(count)
}
return segLens
}
let getSegments = (yArr, points) => {
let segLens = getSegmentLengths(yArr)
let segments = []
for (let i = 0, len = 0; i < segLens.length; len += segLens[i++]) {
segments[i] = spline.slice(points, len, len + segLens[i])
}
return segments
}
switch (pathState) {
case 0:
// Find start of segment
if (yAj[j + 1] === null) {
break
}
pathState = 1
// continue through to pathState 1
case 1:
if (
!(rangeArea
? xArrj.length === series[i].length
: j === series[i].length - 2)
) {
break
}
// continue through to pathState 2
case 2:
// Interpolate the full series with nulls excluded then extract the
// null delimited segments with interpolated points included.
const _xAj = isRangeStart ? xArrj : xArrj.slice().reverse()
const _yAj = isRangeStart ? yAj : yAj.slice().reverse()
const smoothInputs = getSmoothInputs(_xAj, _yAj)
const points =
smoothInputs.length > 1
? spline.points(smoothInputs)
: smoothInputs
let smoothInputsLower = []
if (rangeArea) {
if (isLowerRangeAreaPath) {
// As we won't be needing it, borrow areaPaths to retain our
// rangeArea lower points.
areaPaths = smoothInputs
} else {
// Retrieve the corresponding lower raw interpolated points so we
// can join onto its end points. Note: the upper Y2 segments will
// be in the reverse order relative to the lower segments.
smoothInputsLower = areaPaths.reverse()
}
}
let segmentCount = 0
let smoothInputsIndex = 0
getSegments(_yAj, points).forEach((_) => {
segmentCount++
let svgPoints = svgPath(_)
let _start = smoothInputsIndex
smoothInputsIndex += _.length
let _end = smoothInputsIndex - 1
if (isLowerRangeAreaPath) {
linePath =
graphics.move(
smoothInputs[_start][0],
smoothInputs[_start][1]
) + svgPoints
} else if (rangeArea) {
linePath =
graphics.move(
smoothInputsLower[_start][0],
smoothInputsLower[_start][1]
) +
graphics.line(
smoothInputs[_start][0],
smoothInputs[_start][1]
) +
svgPoints +
graphics.line(
smoothInputsLower[_end][0],
smoothInputsLower[_end][1]
)
} else {
linePath =
graphics.move(
smoothInputs[_start][0],
smoothInputs[_start][1]
) + svgPoints
areaPath =
linePath +
graphics.line(smoothInputs[_end][0], areaBottomY) +
graphics.line(smoothInputs[_start][0], areaBottomY) +
'z'
areaPaths.push(areaPath)
}
linePaths.push(linePath)
})
if (rangeArea && segmentCount > 1 && !isLowerRangeAreaPath) {
// Reverse the order of the upper path segments
let upperLinePaths = linePaths.slice(segmentCount).reverse()
linePaths.splice(segmentCount)
upperLinePaths.forEach((u) => linePaths.push(u))
}
pathState = 0
break
}
break
case 'smooth':
let length = (x - pX) * 0.35
if (series[i][j] === null) {
pathState = 0
} else {
switch (pathState) {
case 0:
// Beginning of segment
segmentStartX = pX
if (isLowerRangeAreaPath) {
// Need to add path portion that will join to the upper path
linePath = graphics.move(pX, y2Arrj[j]) + graphics.line(pX, pY)
} else {
linePath = graphics.move(pX, pY)
}
areaPath = graphics.move(pX, pY)
// Check for single isolated point
if (
series[i][j + 1] === null ||
typeof series[i][j + 1] === 'undefined'
) {
linePaths.push(linePath)
areaPaths.push(areaPath)
// Stay in pathState = 0;
break
}
pathState = 1
if (j < series[i].length - 2) {
let p = graphics.curve(pX + length, pY, x - length, y, x, y)
linePath += p
areaPath += p
break
}
// Continue on with pathState 1 to finish the path and exit
case 1:
// Continuing with segment
if (series[i][j + 1] === null) {
// Segment ends here
if (isLowerRangeAreaPath) {
linePath += graphics.line(pX, y2)
} else {
linePath += graphics.move(pX, pY)
}
areaPath +=
graphics.line(pX, areaBottomY) +
graphics.line(segmentStartX, areaBottomY) +
'z'
linePaths.push(linePath)
areaPaths.push(areaPath)
pathState = -1
} else {
let p = graphics.curve(pX + length, pY, x - length, y, x, y)
linePath += p
areaPath += p
if (j >= series[i].length - 2) {
if (isLowerRangeAreaPath) {
// Need to add path portion that will join to the upper path
linePath +=
graphics.curve(x, y, x, y, x, y2) + graphics.move(x, y2)
}
areaPath +=
graphics.curve(x, y, x, y, x, areaBottomY) +
graphics.line(segmentStartX, areaBottomY) +
'z'
linePaths.push(linePath)
areaPaths.push(areaPath)
pathState = -1
}
}
break
}
}
pX = x
pY = y
break
default:
let pathToPoint = (curve, x, y) => {
let path = []
switch (curve) {
case 'stepline':
path = graphics.line(x, null, 'H') + graphics.line(null, y, 'V')
break
case 'linestep':
path = graphics.line(null, y, 'V') + graphics.line(x, null, 'H')
break
case 'straight':
path = graphics.line(x, y)
break
}
return path
}
if (series[i][j] === null) {
pathState = 0
} else {
switch (pathState) {
case 0:
// Beginning of segment
segmentStartX = pX
if (isLowerRangeAreaPath) {
// Need to add path portion that will join to the upper path
linePath = graphics.move(pX, y2Arrj[j]) + graphics.line(pX, pY)
} else {
linePath = graphics.move(pX, pY)
}
areaPath = graphics.move(pX, pY)
// Check for single isolated point
if (
series[i][j + 1] === null ||
typeof series[i][j + 1] === 'undefined'
) {
linePaths.push(linePath)
areaPaths.push(areaPath)
// Stay in pathState = 0
break
}
pathState = 1
if (j < series[i].length - 2) {
let p = pathToPoint(curve, x, y)
linePath += p
areaPath += p
break
}
// Continue on with pathState 1 to finish the path and exit
case 1:
// Continuing with segment
if (series[i][j + 1] === null) {
// Segment ends here
if (isLowerRangeAreaPath) {
linePath += graphics.line(pX, y2)
} else {
linePath += graphics.move(pX, pY)
}
areaPath +=
graphics.line(pX, areaBottomY) +
graphics.line(segmentStartX, areaBottomY) +
'z'
linePaths.push(linePath)
areaPaths.push(areaPath)
pathState = -1
} else {
let p = pathToPoint(curve, x, y)
linePath += p
areaPath += p
if (j >= series[i].length - 2) {
if (isLowerRangeAreaPath) {
// Need to add path portion that will join to the upper path
linePath += graphics.line(x, y2)
}
areaPath +=
graphics.line(x, areaBottomY) +
graphics.line(segmentStartX, areaBottomY) +
'z'
linePaths.push(linePath)
areaPaths.push(areaPath)
pathState = -1
}
}
break
}
}
pX = x
pY = y
break
}
return {
linePaths,
areaPaths,
pX,
pY,
pathState,
segmentStartX,
linePath,
areaPath,
}
}
handleNullDataPoints(series, pointsPos, i, j, realIndex) {
const w = this.w
if (
(series[i][j] === null && w.config.markers.showNullDataPoints) ||
series[i].length === 1
) {
let pSize = this.strokeWidth - w.config.markers.strokeWidth / 2
if (!(pSize > 0)) {
pSize = 0
}
// fixes apexcharts.js#1282, #1252
let elPointsWrap = this.markers.plotChartMarkers({
pointsPos,
seriesIndex: realIndex,
j: j + 1,
pSize,
alwaysDrawMarker: true,
})
if (elPointsWrap !== null) {
this.elPointsMain.add(elPointsWrap)
}
}
}
}
export default Line