diff --git a/samples/react/column/stacked-column-with-line-new.html b/samples/react/column/stacked-column-with-line-new.html index 2ab0c0ae8..933697c2e 100644 --- a/samples/react/column/stacked-column-with-line-new.html +++ b/samples/react/column/stacked-column-with-line-new.html @@ -290,7 +290,8 @@ labels: { showDuplicates: false, formatter: function(value) { - return value?.toFixed(0) // Value is undefined if all series are collapsed + // Value is undefined if all series are collapsed + return value !== undefined ? value.toFixed(0) : '' } } }, diff --git a/samples/react/column/stacked-column-with-line.html b/samples/react/column/stacked-column-with-line.html index c9a48596c..86fc2d6fc 100644 --- a/samples/react/column/stacked-column-with-line.html +++ b/samples/react/column/stacked-column-with-line.html @@ -290,7 +290,8 @@ labels: { showDuplicates: false, formatter: function(value) { - return value?.toFixed(0) // Value is undefined if all series are collapsed + // Value is undefined if all series are collapsed + return value !== undefined ? value.toFixed(0) : '' } } }, diff --git a/samples/source/column/stacked-column-with-line-new.xml b/samples/source/column/stacked-column-with-line-new.xml index 00e1c8ec4..9a91aa143 100644 --- a/samples/source/column/stacked-column-with-line-new.xml +++ b/samples/source/column/stacked-column-with-line-new.xml @@ -71,7 +71,8 @@ xaxis: { labels: { showDuplicates: false, formatter: function(value) { - return value?.toFixed(0) // Value is undefined if all series are collapsed + // Value is undefined if all series are collapsed + return value !== undefined ? value.toFixed(0) : '' } } }, diff --git a/samples/source/column/stacked-column-with-line.xml b/samples/source/column/stacked-column-with-line.xml index 4e4dfae8e..27c116508 100644 --- a/samples/source/column/stacked-column-with-line.xml +++ b/samples/source/column/stacked-column-with-line.xml @@ -71,7 +71,8 @@ xaxis: { labels: { showDuplicates: false, formatter: function(value) { - return value?.toFixed(0) // Value is undefined if all series are collapsed + // Value is undefined if all series are collapsed + return value !== undefined ? value.toFixed(0) : '' } } }, diff --git a/samples/vanilla-js/column/stacked-column-with-line-new.html b/samples/vanilla-js/column/stacked-column-with-line-new.html index 4d955821c..1907f5d91 100644 --- a/samples/vanilla-js/column/stacked-column-with-line-new.html +++ b/samples/vanilla-js/column/stacked-column-with-line-new.html @@ -273,7 +273,8 @@ labels: { showDuplicates: false, formatter: function(value) { - return value?.toFixed(0) // Value is undefined if all series are collapsed + // Value is undefined if all series are collapsed + return value !== undefined ? value.toFixed(0) : '' } } }, diff --git a/samples/vanilla-js/column/stacked-column-with-line.html b/samples/vanilla-js/column/stacked-column-with-line.html index acb8fc588..063c7cf32 100644 --- a/samples/vanilla-js/column/stacked-column-with-line.html +++ b/samples/vanilla-js/column/stacked-column-with-line.html @@ -273,7 +273,8 @@ labels: { showDuplicates: false, formatter: function(value) { - return value?.toFixed(0) // Value is undefined if all series are collapsed + // Value is undefined if all series are collapsed + return value !== undefined ? value.toFixed(0) : '' } } }, diff --git a/samples/vue/column/stacked-column-with-line-new.html b/samples/vue/column/stacked-column-with-line-new.html index 8a12877df..7d48f9f17 100644 --- a/samples/vue/column/stacked-column-with-line-new.html +++ b/samples/vue/column/stacked-column-with-line-new.html @@ -293,7 +293,8 @@ labels: { showDuplicates: false, formatter: function(value) { - return value?.toFixed(0) // Value is undefined if all series are collapsed + // Value is undefined if all series are collapsed + return value !== undefined ? value.toFixed(0) : '' } } }, diff --git a/samples/vue/column/stacked-column-with-line.html b/samples/vue/column/stacked-column-with-line.html index 577f6e4fe..52a81d110 100644 --- a/samples/vue/column/stacked-column-with-line.html +++ b/samples/vue/column/stacked-column-with-line.html @@ -293,7 +293,8 @@ labels: { showDuplicates: false, formatter: function(value) { - return value?.toFixed(0) // Value is undefined if all series are collapsed + // Value is undefined if all series are collapsed + return value !== undefined ? value.toFixed(0) : '' } } }, diff --git a/src/charts/Bar.js b/src/charts/Bar.js index 2d4a76e00..300ac3f4c 100644 --- a/src/charts/Bar.js +++ b/src/charts/Bar.js @@ -42,6 +42,7 @@ class Bar { this.baseLineInvertedY = xyRatios.baseLineInvertedY } this.yaxisIndex = 0 + this.translationsIndex = 0 this.seriesLen = 0 this.pathArr = [] @@ -126,8 +127,10 @@ class Bar { let barWidth = 0 if (this.yRatio.length > 1) { - this.yaxisIndex = realIndex + this.yaxisIndex = w.globals.seriesYAxisReverseMap[realIndex] + this.translationsIndex = realIndex } + let translationsIndex = this.translationsIndex this.isReversed = w.config.yaxis[this.yaxisIndex] && @@ -182,6 +185,7 @@ class Bar { i, j, realIndex, + translationsIndex, bc, }, x, @@ -204,7 +208,7 @@ class Bar { barWidth, zeroH, }) - barHeight = this.series[i][j] / this.yRatio[this.yaxisIndex] + barHeight = this.series[i][j] / this.yRatio[translationsIndex] } let pathFill = this.barHelpers.getPathFillColor(series, i, j, realIndex) @@ -511,6 +515,7 @@ class Bar { let w = this.w let realIndex = indexes.realIndex + let translationsIndex = indexes.translationsIndex let i = indexes.i let j = indexes.j let bc = indexes.bc @@ -540,7 +545,7 @@ class Bar { } } - y = this.barHelpers.getYForValue(this.series[i][j], zeroH) + y = this.barHelpers.getYForValue(this.series[i][j], zeroH, translationsIndex) const paths = this.barHelpers.getColumnPaths({ barXPosition, @@ -549,7 +554,7 @@ class Bar { y2: y, strokeWidth, series: this.series, - realIndex: indexes.realIndex, + realIndex: realIndex, i, j, w, @@ -573,7 +578,7 @@ class Bar { pathFrom: paths.pathFrom, x, y, - goalY: this.barHelpers.getGoalValues('y', null, zeroH, i, j), + goalY: this.barHelpers.getGoalValues('y', null, zeroH, i, j, translationsIndex), barXPosition, barWidth, } diff --git a/src/charts/BarStacked.js b/src/charts/BarStacked.js index 9c49c0eb2..b529330f9 100644 --- a/src/charts/BarStacked.js +++ b/src/charts/BarStacked.js @@ -61,8 +61,10 @@ class BarStacked extends Bar { let realIndex = w.globals.comboCharts ? seriesIndex[i] : i + let translationsIndex = 0 if (this.yRatio.length > 1) { - this.yaxisIndex = realIndex + this.yaxisIndex = w.globals.seriesYAxisReverseMap[realIndex][0] + translationsIndex = realIndex } this.isReversed = @@ -97,7 +99,8 @@ class BarStacked extends Bar { xDivision, yDivision, zeroH, - zeroW + zeroW, + translationsIndex ) y = initPositions.y barHeight = initPositions.barHeight @@ -126,7 +129,7 @@ class BarStacked extends Bar { for (let j = 0; j < w.globals.dataPoints; j++) { const strokeWidth = this.barHelpers.getStrokeWidth(i, j, realIndex) const commonPathOpts = { - indexes: { i, j, realIndex, bc }, + indexes: { i, j, realIndex, translationsIndex, bc }, strokeWidth, x, y, @@ -150,7 +153,7 @@ class BarStacked extends Bar { barWidth, zeroH, }) - barHeight = this.series[i][j] / this.yRatio[this.yaxisIndex] + barHeight = this.series[i][j] / this.yRatio[translationsIndex] } const barGoalLine = this.barHelpers.drawGoalLine({ @@ -214,7 +217,7 @@ class BarStacked extends Bar { return ret } - initialPositions(x, y, xDivision, yDivision, zeroH, zeroW) { + initialPositions(x, y, xDivision, yDivision, zeroH, zeroW, translationsIndex) { let w = this.w let barHeight, barWidth @@ -257,9 +260,9 @@ class BarStacked extends Bar { } zeroH = w.globals.gridHeight - - this.baseLineY[this.yaxisIndex] - + this.baseLineY[translationsIndex] - (this.isReversed ? w.globals.gridHeight : 0) + - (this.isReversed ? this.baseLineY[this.yaxisIndex] * 2 : 0) + (this.isReversed ? this.baseLineY[translationsIndex] * 2 : 0) // initial x position is one third of barWidth x = w.globals.padHorizontal + (xDivision - barWidth) / 2 @@ -297,6 +300,7 @@ class BarStacked extends Bar { let barXPosition let i = indexes.i let j = indexes.j + let translationsIndex = indexes.translationsIndex let prevBarW = 0 for (let k = 0; k < this.groupCtx.prevXF.length; k++) { @@ -369,7 +373,7 @@ class BarStacked extends Bar { return { pathTo: paths.pathTo, pathFrom: paths.pathFrom, - goalX: this.barHelpers.getGoalValues('x', zeroW, null, i, j), + goalX: this.barHelpers.getGoalValues('x', zeroW, null, i, j, translationsIndex), barYPosition, x, y, @@ -391,6 +395,7 @@ class BarStacked extends Bar { let i = indexes.i let j = indexes.j let bc = indexes.bc + let translationsIndex = indexes.translationsIndex if (w.globals.isXNumeric) { let seriesVal = w.globals.seriesX[i][j] @@ -485,9 +490,9 @@ class BarStacked extends Bar { if (this.series[i][j]) { y = barYPosition - - this.series[i][j] / this.yRatio[this.yaxisIndex] + + this.series[i][j] / this.yRatio[translationsIndex] + (this.isReversed - ? this.series[i][j] / this.yRatio[this.yaxisIndex] + ? this.series[i][j] / this.yRatio[translationsIndex] : 0) * 2 } else { @@ -500,7 +505,7 @@ class BarStacked extends Bar { barWidth, y1: barYPosition, y2: y, - yRatio: this.yRatio[this.yaxisIndex], + yRatio: this.yRatio[translationsIndex], strokeWidth: this.strokeWidth, series: this.series, seriesGroup, diff --git a/src/charts/BoxCandleStick.js b/src/charts/BoxCandleStick.js index b0e2a0145..5a06a0e6e 100644 --- a/src/charts/BoxCandleStick.js +++ b/src/charts/BoxCandleStick.js @@ -66,8 +66,10 @@ class BoxCandleStick extends Bar { let barHeight = 0 let barWidth = 0 + let translationsIndex = 0 if (this.yRatio.length > 1) { - this.yaxisIndex = realIndex + this.yaxisIndex = w.globals.seriesYAxisReverseMap[realIndex][0] + translationsIndex = realIndex } let initPositions = this.barHelpers.initialPositions() @@ -98,7 +100,8 @@ class BoxCandleStick extends Bar { indexes: { i, j, - realIndex + realIndex, + translationsIndex }, x, y, @@ -201,7 +204,7 @@ class BoxCandleStick extends Bar { color = [this.boxOptions.colors.lower, this.boxOptions.colors.upper] } - const yRatio = this.yRatio[this.yaxisIndex] + const yRatio = this.yRatio[indexes.translationsIndex] let realIndex = indexes.realIndex const ohlc = this.getOHLCValue(realIndex, j) diff --git a/src/charts/Line.js b/src/charts/Line.js index bed2a31c5..25d966bf4 100644 --- a/src/charts/Line.js +++ b/src/charts/Line.js @@ -62,6 +62,8 @@ class Line { 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) @@ -97,6 +99,7 @@ class Line { series, prevY, lineYPosition, + translationsIndex }) prevY = firstPrevY.prevY if (w.config.stroke.curve === 'monotonCubic' && series[i][0] === null) { @@ -116,6 +119,7 @@ class Line { series: seriesRangeEnd, prevY: prevY2, lineYPosition, + translationsIndex }) prevY2 = firstPrevY2.prevY pY2 = prevY2 @@ -136,6 +140,7 @@ class Line { type, series, realIndex, + translationsIndex, i, x, y, @@ -221,8 +226,10 @@ class Line { ? w.config.stroke.width[realIndex] : w.config.stroke.width + let translationsIndex = 0 if (this.yRatio.length > 1) { - this.yaxisIndex = realIndex + this.yaxisIndex = w.globals.seriesYAxisReverseMap[realIndex] + translationsIndex = realIndex } this.isReversed = @@ -232,9 +239,9 @@ class Line { // zeroY is the 0 value in y series which can be used in negative charts this.zeroY = w.globals.gridHeight - - this.baseLineY[this.yaxisIndex] - + this.baseLineY[translationsIndex] - (this.isReversed ? w.globals.gridHeight : 0) + - (this.isReversed ? this.baseLineY[this.yaxisIndex] * 2 : 0) + (this.isReversed ? this.baseLineY[translationsIndex] * 2 : 0) this.areaBottomY = this.zeroY if ( @@ -288,7 +295,7 @@ class Line { for (let s = 0; s < series[i].length; s++) { if (series[i][s] !== null) { prevX = this.xDivision * s - prevY = this.zeroY - series[i][s] / this.yRatio[this.yaxisIndex] + prevY = this.zeroY - series[i][s] / this.yRatio[realIndex] linePath = graphics.move(prevX, prevY) areaPath = graphics.move(prevX, this.areaBottomY) break @@ -478,6 +485,7 @@ class Line { series, iterations, realIndex, + translationsIndex, i, x, y, @@ -513,8 +521,8 @@ class Line { const getY = (_y, lineYPos) => { return ( lineYPos - - _y / yRatio[this.yaxisIndex] + - (this.isReversed ? _y / yRatio[this.yaxisIndex] : 0) * 2 + _y / yRatio[translationsIndex] + + (this.isReversed ? _y / yRatio[translationsIndex] : 0) * 2 ) } diff --git a/src/charts/Radar.js b/src/charts/Radar.js index 21b93a60b..2791b82be 100644 --- a/src/charts/Radar.js +++ b/src/charts/Radar.js @@ -40,13 +40,14 @@ class Radar { : 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(w.globals.maxY, 0) + ? this.coreUtils.getLogVal(this.logBase, w.globals.maxY, 0) : w.globals.maxY this.minValue = this.isLog - ? this.coreUtils.getLogVal(this.w.globals.minY, 0) + ? this.coreUtils.getLogVal(this.logBase, this.w.globals.minY, 0) : w.globals.minY this.polygons = w.config.plotOptions.radar.polygons @@ -122,7 +123,7 @@ class Radar { dv = dv + Math.abs(this.minValue) if (this.isLog) { - dv = this.coreUtils.getLogVal(dv, 0) + dv = this.coreUtils.getLogVal(this.logBase, dv, 0) } this.dataRadiusOfPercent[i][j] = dv / range diff --git a/src/charts/RangeBar.js b/src/charts/RangeBar.js index 5513528bd..8e12f0c5d 100644 --- a/src/charts/RangeBar.js +++ b/src/charts/RangeBar.js @@ -52,8 +52,10 @@ class RangeBar extends Bar { let barHeight = 0 let barWidth = 0 + let translationsIndex = 0 if (this.yRatio.length > 1) { - this.yaxisIndex = realIndex + this.yaxisIndex = w.globals.seriesYAxisReverseMap[realIndex][0] + translationsIndex = realIndex } let initPositions = this.barHelpers.initialPositions() @@ -159,7 +161,7 @@ class RangeBar extends Bar { } paths = this.drawRangeColumnPaths({ - indexes: { i, j, realIndex }, + indexes: { i, j, realIndex, translationsIndex }, barWidth, barXPosition, zeroH, @@ -319,7 +321,7 @@ class RangeBar extends Bar { let i = indexes.i let j = indexes.j - const yRatio = this.yRatio[this.yaxisIndex] + const yRatio = this.yRatio[indexes.translationsIndex] let realIndex = indexes.realIndex const range = this.getRangeValue(realIndex, j) @@ -370,7 +372,13 @@ class RangeBar extends Bar { barHeight, x, y: y2, - goalY: this.barHelpers.getGoalValues('y', null, zeroH, i, j), + goalY: this.barHelpers.getGoalValues( + 'y', + null, + zeroH, + i, + j, + indexes.translationsIndex), barXPosition, } } diff --git a/src/charts/common/bar/Helpers.js b/src/charts/common/bar/Helpers.js index 34d2fc1de..2396d4e56 100644 --- a/src/charts/common/bar/Helpers.js +++ b/src/charts/common/bar/Helpers.js @@ -128,14 +128,13 @@ export default class Helpers { zeroH = w.globals.gridHeight - - this.barCtx.baseLineY[this.barCtx.yaxisIndex] - + this.barCtx.baseLineY[this.barCtx.translationsIndex] - (this.barCtx.isReversed ? w.globals.gridHeight : 0) + (this.barCtx.isReversed - ? this.barCtx.baseLineY[this.barCtx.yaxisIndex] * 2 + ? this.barCtx.baseLineY[this.barCtx.translationsIndex] * 2 : 0) - x = - w.globals.padHorizontal + + x = w.globals.padHorizontal + (xDivision - barWidth * this.barCtx.seriesLen) / 2 } @@ -515,21 +514,21 @@ export default class Helpers { return xForVal } - getYForValue(value, zeroH, zeroPositionForNull = true) { + getYForValue(value, zeroH, translationsIndex, zeroPositionForNull = true) { let yForVal = zeroPositionForNull ? zeroH : null if (typeof value !== 'undefined' && value !== null) { yForVal = zeroH - - value / this.barCtx.yRatio[this.barCtx.yaxisIndex] + + value / this.barCtx.yRatio[translationsIndex] + (this.barCtx.isReversed - ? value / this.barCtx.yRatio[this.barCtx.yaxisIndex] + ? value / this.barCtx.yRatio[translationsIndex] : 0) * 2 } return yForVal } - getGoalValues(type, zeroW, zeroH, i, j) { + getGoalValues(type, zeroW, zeroH, i, j, translationsIndex) { const w = this.w let goals = [] @@ -539,7 +538,7 @@ export default class Helpers { [type]: type === 'x' ? this.getXForValue(value, zeroW, false) - : this.getYForValue(value, zeroH, false), + : this.getYForValue(value, zeroH, translationsIndex, false), attrs, }) } @@ -600,45 +599,51 @@ export default class Helpers { if (this.barCtx.isHorizontal) { if (Array.isArray(goalX)) { goalX.forEach((goal) => { - let sHeight = - typeof goal.attrs.strokeHeight !== 'undefined' - ? goal.attrs.strokeHeight - : barHeight / 2 - let y = barYPosition + sHeight + barHeight / 2 - - line = graphics.drawLine( - goal.x, - y - sHeight * 2, - goal.x, - y, - goal.attrs.strokeColor ? goal.attrs.strokeColor : undefined, - goal.attrs.strokeDashArray, - goal.attrs.strokeWidth ? goal.attrs.strokeWidth : 2, - goal.attrs.strokeLineCap - ) - lineGroup.add(line) + // 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) => { - 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) + // 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) + } }) } } diff --git a/src/charts/common/line/Helpers.js b/src/charts/common/line/Helpers.js index 9021513f1..563a9709c 100644 --- a/src/charts/common/line/Helpers.js +++ b/src/charts/common/line/Helpers.js @@ -102,7 +102,7 @@ export default class Helpers { } } - determineFirstPrevY({ i, series, prevY, lineYPosition }) { + determineFirstPrevY({ i, series, prevY, lineYPosition, translationsIndex }) { let w = this.w let stackSeries = (w.config.chart.stacked && !w.globals.comboCharts) || @@ -125,11 +125,9 @@ export default class Helpers { } prevY = lineYPosition - - series[i][0] / this.lineCtx.yRatio[this.lineCtx.yaxisIndex] + - (this.lineCtx.isReversed - ? series[i][0] / this.lineCtx.yRatio[this.lineCtx.yaxisIndex] - : 0) * - 2 + 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') { diff --git a/src/modules/CoreUtils.js b/src/modules/CoreUtils.js index bd89aa474..ecf4e12ca 100644 --- a/src/modules/CoreUtils.js +++ b/src/modules/CoreUtils.js @@ -249,7 +249,8 @@ class CoreUtils { } getCalculatedRatios() { - let gl = this.w.globals + let w = this.w + let gl = w.globals let yRatio = [] let invertedYRatio = 0 @@ -292,20 +293,37 @@ class CoreUtils { gl.hasNegs = true } - if (gl.isMultipleYAxis) { - baseLineY = [] - - // baseline variables is the 0 of the yaxis which will be needed when there are negatives - for (let i = 0; i < yRatio.length; i++) { - baseLineY.push(-gl.minYArr[i] / yRatio[i]) + // Check we have a map as series may still to be added/updated. + if (w.globals.seriesYAxisReverseMap.length > 0) { + let scaleBaseLineYScale = (y, i) => { + let yAxis = w.config.yaxis[w.globals.seriesYAxisReverseMap[i]] + let sign = y < 0 ? -1 : 1 + y = Math.abs(y) + if (yAxis.logarithmic) { + y = this.getBaseLog(yAxis.logBase, y) + } + return -sign * y / yRatio[i] } - } else { - baseLineY.push(-gl.minY / yRatio[0]) + if (gl.isMultipleYAxis) { + baseLineY = [] + // baseline variables is the 0 of the yaxis which will be needed when there are negatives + for (let i = 0; i < yRatio.length; i++) { + baseLineY.push(scaleBaseLineYScale(gl.minYArr[i], i)) + } + } else { + baseLineY = [] + baseLineY.push(scaleBaseLineYScale(gl.minY, 0)) - if (gl.minY !== Number.MIN_VALUE && Math.abs(gl.minY) !== 0) { - baseLineInvertedY = -gl.minY / invertedYRatio // this is for bar chart - baseLineX = gl.minX / xRatio + if (gl.minY !== Number.MIN_VALUE && Math.abs(gl.minY) !== 0) { + baseLineInvertedY = -gl.minY / invertedYRatio // this is for bar chart + baseLineX = gl.minX / xRatio + } } + } else { + baseLineY = [] + baseLineY.push(0) + baseLineInvertedY = 0 + baseLineX = 0 } return { @@ -324,10 +342,11 @@ class CoreUtils { const w = this.w w.globals.seriesLog = series.map((s, i) => { - if (w.config.yaxis[i] && w.config.yaxis[i].logarithmic) { + let yAxisIndex = w.globals.seriesYAxisReverseMap[i] + if (w.config.yaxis[yAxisIndex] && w.config.yaxis[yAxisIndex].logarithmic) { return s.map((d) => { if (d === null) return null - return this.getLogVal(w.config.yaxis[i].logBase, d, i) + return this.getLogVal(w.config.yaxis[yAxisIndex].logBase, d, i) }) } else { return s @@ -339,19 +358,19 @@ class CoreUtils { getBaseLog(base, value) { return Math.log(value) / Math.log(base) } - getLogVal(b, d, yIndex) { - if (d === 0) { - return 0 + getLogVal(b, d, seriesIndex) { + if (d <= 0) { + return 0 // Should be Number.NEGATIVE_INFINITY } const w = this.w const min_log_val = - w.globals.minYArr[yIndex] === 0 + w.globals.minYArr[seriesIndex] === 0 ? -1 // make sure we dont calculate log of 0 - : this.getBaseLog(b, w.globals.minYArr[yIndex]) + : this.getBaseLog(b, w.globals.minYArr[seriesIndex]) const max_log_val = - w.globals.maxYArr[yIndex] === 0 + w.globals.maxYArr[seriesIndex] === 0 ? 0 // make sure we dont calculate log of 0 - : this.getBaseLog(b, w.globals.maxYArr[yIndex]) + : this.getBaseLog(b, w.globals.maxYArr[seriesIndex]) const number_of_height_levels = max_log_val - min_log_val if (d < 1) return d / number_of_height_levels const log_height_value = this.getBaseLog(b, d) - min_log_val @@ -365,7 +384,8 @@ class CoreUtils { gl.yLogRatio = yRatio.slice() gl.logYRange = gl.yRange.map((yRange, i) => { - if (w.config.yaxis[i] && this.w.config.yaxis[i].logarithmic) { + let yAxisIndex = w.globals.seriesYAxisReverseMap[i] + if (w.config.yaxis[yAxisIndex] && this.w.config.yaxis[yAxisIndex].logarithmic) { let maxY = -Number.MAX_VALUE let minY = Number.MIN_VALUE let range = 1 diff --git a/src/modules/Scales.js b/src/modules/Scales.js index f131988a2..e9d3f6704 100644 --- a/src/modules/Scales.js +++ b/src/modules/Scales.js @@ -429,7 +429,8 @@ export default class Scales { const logs = [] - const logMax = Math.ceil(Math.log(yMax) / Math.log(base) + 1) // Get powers of base for our max and min + // Get powers of base for our max and min + const logMax = Math.ceil(Math.log(yMax) / Math.log(base) + 1) const logMin = Math.floor(Math.log(yMin) / Math.log(base)) for (let i = logMin; i < logMax; i++) { @@ -571,9 +572,55 @@ export default class Scales { const minYArr = gl.minYArr const maxYArr = gl.maxYArr + // The current config method to map multiple series to a y axis is to + // include one yaxis config per series but set each yaxis seriesName to the + // same series name. This relies on indexing equivalence to map series to + // an axis: series[n] => yaxis[n]. This needs to be retained for compatibility. + // But we introduce an alternative that explicitly configures yaxis elements + // with the series that will be referenced to them (seriesName: []). This + // only requires including the yaxis elements that will be seen on the chart. + // Old way: + // ya: s + // 0: 0 + // 1: 1 + // 2: 1 + // 3: 1 + // 4: 1 + // Axes 0..4 are all scaled and all will be rendered unless the axes are + // show: false. If the chart is stacked, it's assumed that series 1..4 are + // the contributing series. This is not particularly intuitive. + // New way: + // ya: s + // 0: [0] + // 1: [1,2,3,4] + // If the chart is stacked, it can be assumed that any axis with multiple + // series is stacked. + // + // If this is an old chart and we are being backward compatible, it will be + // expected that each series is associated with it's corresponding yaxis + // through their indices, one-to-one. + // If yaxis.seriesName matches series.name, we have indices yi and si. + // A name match where yi != si is interpretted as yaxis[yi] and yaxis[si] + // will both be scaled to fit the combined series[si] and series[yi]. + // Consider series named: S0,S1,S2 and yaxes A0,A1,A2. + // + // Example 1: A0 and A1 scaled the same. + // A0.seriesName: S0 + // A1.seriesName: S0 + // A2.seriesName: S2 + // Then A1 <-> A0 + // + // Example 2: A0, A1 and A2 all scaled the same. + // A0.seriesName: S2 + // A1.seriesName: S0 + // A2.seriesName: S1 + // A0 <-> A2, A1 <-> A0, A2 <-> A1 --->>> A0 <-> A1 <-> A2 + let axisSeriesMap = [] let seriesYAxisReverseMap = [] let unassignedSeriesIndices = [] + let assumeSeriesNameArrays = cnf.yaxis.length !== cnf.series.length + cnf.series.forEach((s, i) => { unassignedSeriesIndices.push(i) seriesYAxisReverseMap.push(null) @@ -600,16 +647,13 @@ export default class Scales { seriesNames.forEach((name) => { cnf.series.forEach((s, si) => { if (s.name === name) { - assigned = true - axisSeriesMap[yi].push(si) - let remove - if (yi === si) { - seriesYAxisReverseMap[si] = yi - remove = unassignedSeriesIndices.indexOf(si) + if (yi === si || assumeSeriesNameArrays) { + axisSeriesMap[yi].push(si) } else { - seriesYAxisReverseMap[yi] = yi - remove = unassignedSeriesIndices.indexOf(yi) + axisSeriesMap[si].push(yi) } + assigned = true + let remove = unassignedSeriesIndices.indexOf(si) if (remove !== -1) { unassignedSeriesIndices.splice(remove, 1) } @@ -621,6 +665,11 @@ export default class Scales { unassignedYAxisIndices.push(yi) } }) + axisSeriesMap.forEach((yaxe, yi) => { + yaxe.forEach((si) => { + seriesYAxisReverseMap[si] = yi + }) + }) // All series referenced directly by yaxes have been assigned to those axes. // Any series so far unassigned will be assigned to any yaxes that have yet // to reference series directly, one-for-one in order of appearance, with @@ -648,6 +697,8 @@ export default class Scales { }) } + // We deliberately leave the zero-length yaxis array elements in for + // compatibility with the old equivalence in number between sereis and yaxes. gl.seriesYAxisMap = axisSeriesMap.map((x) => x) gl.seriesYAxisReverseMap = seriesYAxisReverseMap.map((x) => x) this.sameScaleInMultipleAxes(minYArr, maxYArr, axisSeriesMap) @@ -657,137 +708,72 @@ export default class Scales { const cnf = this.w.config const gl = this.w.globals - // The current config method to map multiple series to a y axis is to - // include one yaxis config per series but set each yaxis seriesName to the - // same series name. This relies on indexing equivalence to map series to - // an axis: series[n] => yaxis[n]. This needs to be retained for compatibility. - // But we introduce an alternative that explicitly configures yaxis elements - // with the series that will be referenced to them (seriesName: []). This - // only requires including the yaxis elements that will be seen on the chart. - // Old way: - // ya: s - // 0: 0 - // 1: 1 - // 2: 1 - // 3: 1 - // 4: 1 - // Axes 0..4 are all scaled and all will be rendered unless the axes are - // show: false. If the chart is stacked, it's assumed that series 1..4 are - // the contributing series. This is not particularly intuitive. - // New way: - // ya: s - // 0: [0] - // 1: [1,2,3,4] - // If the chart is stacked, it can be assumed that any axis with multiple - // series is stacked. - // - // If this is an old chart and we are being backward compatible, it will be - // expected that each series is associated with it's corresponding yaxis - // through their indices, one-to-one. - // If yaxis.seriesName matches series.name, we have indices yi and si. - // A name match where yi != si is interpretted as yaxis[yi] and yaxis[si] - // will both be scaled to fit the combined series[si] and series[yi]. - // Consider series named: S0,S1,S2 and yaxes A0,A1,A2. - // - // Example 1: A0 and A1 scaled the same. - // A0.seriesName: S0 - // A1.seriesName: S0 - // A2.seriesName: S2 - // Then A1 <-> A0 - // - // Example 2: A0, A1 and A2 all scaled the same. - // A0.seriesName: S2 - // A1.seriesName: S0 - // A2.seriesName: S1 - // A0 <-> A2, A1 <-> A0, A2 <-> A1 --->>> A0 <-> A1 <-> A2 - - // First things first, convert the old to the new. - - // Consolidate series indices into axes - if (axisSeriesMap.length === cnf.series.length) { - axisSeriesMap.forEach((axisSeries, ai) => { - let si = axisSeries[0] - if (si !== ai) { - axisSeriesMap[si].push(ai) - axisSeriesMap[ai].splice(0,1) - } - }) - // Now duplicate axisSeriesMap elements that need to be the same. - axisSeriesMap.forEach((axisSeries, ai) => { - if (!axisSeries.length) { - axisSeriesMap.forEach((as, i) => { - if (as.indexOf(ai) > -1) { - axisSeriesMap[ai] = axisSeriesMap[i].map((x) => x) - } - }) - } - }) - } - // Compute min..max for each yaxis // axisSeriesMap.forEach((axisSeries, ai) => { - let minY = Number.MAX_VALUE - let maxY = -Number.MAX_VALUE - if (cnf.chart.stacked) { - let sumSeries = gl.seriesX[axisSeries[0]].map((x) => Number.MIN_VALUE) - let posSeries = gl.seriesX[axisSeries[0]].map((x) => Number.MIN_VALUE) - let negSeries = gl.seriesX[axisSeries[0]].map((x) => Number.MIN_VALUE) - // The first series bound to the axis sets the type for stacked series - let seriesType = cnf.series[axisSeries[0]].type - for (let i = 0; i < axisSeries.length; i++) { - // Sum all series for this yaxis at each corresponding datapoint - // For bar and column charts we need to keep positive and negative - // values separate. - let si = axisSeries[i] - if (gl.collapsedSeriesIndices.indexOf(si) === -1) { - for (let j = 0; j < gl.series[si].length; j++) { - let val = gl.series[si][j] - if (val >= 0) { - posSeries[j] += val - } else { - negSeries[j] += val + if (axisSeries.length > 0) { + let minY = Number.MAX_VALUE + let maxY = -Number.MAX_VALUE + if (cnf.chart.stacked) { + let sumSeries = gl.seriesX[axisSeries[0]].map((x) => Number.MIN_VALUE) + let posSeries = gl.seriesX[axisSeries[0]].map((x) => Number.MIN_VALUE) + let negSeries = gl.seriesX[axisSeries[0]].map((x) => Number.MIN_VALUE) + // The first series bound to the axis sets the type for stacked series + let seriesType = cnf.series[axisSeries[0]].type + for (let i = 0; i < axisSeries.length; i++) { + // Sum all series for this yaxis at each corresponding datapoint + // For bar and column charts we need to keep positive and negative + // values separate. + let si = axisSeries[i] + if (gl.collapsedSeriesIndices.indexOf(si) === -1) { + for (let j = 0; j < gl.series[si].length; j++) { + let val = gl.series[si][j] + if (val >= 0) { + posSeries[j] += val + } else { + negSeries[j] += val + } + sumSeries[j] += val } - sumSeries[j] += val } } - } - if (seriesType === 'bar') { - minY = Math.min.apply(null, negSeries) - maxY = Math.max.apply(null, posSeries) + if (seriesType === 'bar') { + minY = Math.min.apply(null, negSeries) + maxY = Math.max.apply(null, posSeries) + } else { + minY = Math.min.apply(null, sumSeries) + maxY = Math.max.apply(null, sumSeries) + } } else { - minY = Math.min.apply(null, sumSeries) - maxY = Math.max.apply(null, sumSeries) - } - } else { - for (let i = 0; i < axisSeries.length; i++) { - minY = Math.min(minY, minYArr[axisSeries[i]]) - } - for (let i = 0; i < axisSeries.length; i++) { - maxY = Math.max(maxY, maxYArr[axisSeries[i]]) + for (let i = 0; i < axisSeries.length; i++) { + minY = Math.min(minY, minYArr[axisSeries[i]]) + } + for (let i = 0; i < axisSeries.length; i++) { + maxY = Math.max(maxY, maxYArr[axisSeries[i]]) + } } - } - if (cnf.yaxis[ai].min !== undefined) { - if (typeof cnf.yaxis[ai].min === 'function') { - minY = cnf.yaxis[ai].min(minY) - } else { - minY = cnf.yaxis[ai].min + if (cnf.yaxis[ai].min !== undefined) { + if (typeof cnf.yaxis[ai].min === 'function') { + minY = cnf.yaxis[ai].min(minY) + } else { + minY = cnf.yaxis[ai].min + } } - } - if (cnf.yaxis[ai].max !== undefined) { - if (typeof cnf.yaxis[ai].max === 'function') { - maxY = cnf.yaxis[ai].max(maxY) - } else { - maxY = cnf.yaxis[ai].max + if (cnf.yaxis[ai].max !== undefined) { + if (typeof cnf.yaxis[ai].max === 'function') { + maxY = cnf.yaxis[ai].max(maxY) + } else { + maxY = cnf.yaxis[ai].max + } } + // Set the scale for this yaxis + this.setYScaleForIndex(ai, minY, maxY) + // Set individual series min and max to nice values + axisSeries.forEach((si) => { + minYArr[si] = gl.yAxisScale[ai].niceMin + maxYArr[si] = gl.yAxisScale[ai].niceMax + }) } - // Set the scale for this yaxis - this.setYScaleForIndex(ai, minY, maxY) - // Set individual series min and max to nice values - axisSeries.forEach((si) => { - minYArr[si] = gl.yAxisScale[ai].niceMin - maxYArr[si] = gl.yAxisScale[ai].niceMax - }) }) } } diff --git a/src/modules/ZoomPanSelection.js b/src/modules/ZoomPanSelection.js index e372264a9..10ded9e44 100644 --- a/src/modules/ZoomPanSelection.js +++ b/src/modules/ZoomPanSelection.js @@ -572,11 +572,14 @@ export default class ZoomPanSelection extends Toolbar { let yLowestValue = [] w.config.yaxis.forEach((yaxe, index) => { + // We can use the index of any series referenced by the Yaxis + // because they will all return the same value. + let seriesIndex = w.globals.seriesYAxisMap[index][0] yHighestValue.push( - w.globals.yAxisScale[index].niceMax - xyRatios.yRatio[index] * me.startY + w.globals.yAxisScale[index].niceMax - xyRatios.yRatio[seriesIndex] * me.startY ) yLowestValue.push( - w.globals.yAxisScale[index].niceMax - xyRatios.yRatio[index] * me.endY + w.globals.yAxisScale[index].niceMax - xyRatios.yRatio[seriesIndex] * me.endY ) }) diff --git a/src/modules/annotations/Helpers.js b/src/modules/annotations/Helpers.js index c6999ec05..3c8b3f1fc 100644 --- a/src/modules/annotations/Helpers.js +++ b/src/modules/annotations/Helpers.js @@ -148,6 +148,7 @@ export default class Helpers { getY1Y2(type, anno) { let y = type === 'y1' ? anno.y : anno.y2 let yP + let clipped = false const w = this.w if (this.annoCtx.invertAxis) { @@ -180,14 +181,27 @@ export default class Helpers { } } else { let yPos + // We can use the index of any series referenced by the Yaxis + // because they will all return the same value. + let seriesIndex = w.globals.seriesYAxisMap[anno.yAxisIndex][0] if (w.config.yaxis[anno.yAxisIndex].logarithmic) { const coreUtils = new CoreUtils(this.annoCtx.ctx) - y = coreUtils.getLogVal(y, anno.yAxisIndex) - yPos = y / w.globals.yLogRatio[anno.yAxisIndex] + y = coreUtils.getLogVal( + w.config.yaxis[anno.yAxisIndex].logBase, + y, + seriesIndex) + yPos = y / w.globals.yLogRatio[seriesIndex] } else { yPos = - (y - w.globals.minYArr[anno.yAxisIndex]) / - (w.globals.yRange[anno.yAxisIndex] / w.globals.gridHeight) + (y - w.globals.minYArr[seriesIndex]) / + (w.globals.yRange[seriesIndex] / w.globals.gridHeight) + } + if (yPos > w.globals.gridHeight) { + yPos = w.globals.gridHeight + clipped = true + } else if (yPos < 0) { + yPos = 0 + clipped = true } yP = w.globals.gridHeight - yPos @@ -208,21 +222,24 @@ export default class Helpers { yP = parseFloat(y) } - return yP + return {'yP': yP, 'clipped': clipped} } getX1X2(type, anno) { + let x = type === 'x1' ? anno.x : anno.x2 const w = this.w let min = this.annoCtx.invertAxis ? w.globals.minY : w.globals.minX let max = this.annoCtx.invertAxis ? w.globals.maxY : w.globals.maxX const range = this.annoCtx.invertAxis ? w.globals.yRange[0] : w.globals.xRange + let clipped = false - let x1 = (anno.x - min) / (range / w.globals.gridWidth) - + let xP if (this.annoCtx.inversedReversedAxis) { - x1 = (max - anno.x) / (range / w.globals.gridWidth) + xP = (max - x) / (range / w.globals.gridWidth) + } else { + xP = (x - min) / (range / w.globals.gridWidth) } if ( @@ -231,54 +248,40 @@ export default class Helpers { !this.annoCtx.invertAxis && !w.globals.dataFormatXNumeric ) { - x1 = this.getStringX(anno.x) + if (!w.config.chart.sparkline.enabled) { + xP = this.getStringX(x) + } } - let x2 = (anno.x2 - min) / (range / w.globals.gridWidth) - - if (this.annoCtx.inversedReversedAxis) { - x2 = (max - anno.x2) / (range / w.globals.gridWidth) - } if ( - (w.config.xaxis.type === 'category' || - w.config.xaxis.convertedCatToNumeric) && - !this.annoCtx.invertAxis && - !w.globals.dataFormatXNumeric + typeof x === 'string' && + x.indexOf('px') > -1 ) { - x2 = this.getStringX(anno.x2) + xP = parseFloat(x) } - if ((anno.x === undefined || anno.x === null) && anno.marker) { + if ((x === undefined || x === null) && anno.marker) { // point annotation in a horizontal chart - x1 = w.globals.gridWidth - } - - if ( - type === 'x1' && - typeof anno.x === 'string' && - anno.x.indexOf('px') > -1 - ) { - x1 = parseFloat(anno.x) - } - - if ( - type === 'x2' && - typeof anno.x2 === 'string' && - anno.x2.indexOf('px') > -1 - ) { - x2 = parseFloat(anno.x2) + xP = w.globals.gridWidth } if (typeof anno.seriesIndex !== 'undefined') { if (w.globals.barWidth && !this.annoCtx.invertAxis) { - x1 = - x1 - - (w.globals.barWidth / 2) * (w.globals.series.length - 1) + - w.globals.barWidth * anno.seriesIndex + xP = xP - + (w.globals.barWidth / 2) * (w.globals.series.length - 1) + + w.globals.barWidth * anno.seriesIndex } } - return type === 'x1' ? x1 : x2 + if (xP > w.globals.gridWidth) { + xP = w.globals.gridWidth + clipped = true + } else if (xP < 0) { + xP = 0 + clipped = true + } + + return {'x': xP, 'clipped': clipped} } getStringX(x) { diff --git a/src/modules/annotations/PointsAnnotations.js b/src/modules/annotations/PointsAnnotations.js index 632db6fe1..222509fd3 100644 --- a/src/modules/annotations/PointsAnnotations.js +++ b/src/modules/annotations/PointsAnnotations.js @@ -11,102 +11,108 @@ export default class PointAnnotations { addPointAnnotation(anno, parent, index) { const w = this.w - let x = this.helpers.getX1X2('x1', anno) - let y = this.helpers.getY1Y2('y1', anno) + let result = this.helpers.getX1X2('x1', anno) + let x = result.x + let clipX = result.clipped + result = this.helpers.getY1Y2('y1', anno) + let y = result.yP + let clipY = result.clipped if (!Utils.isNumber(x)) return - let optsPoints = { - pSize: anno.marker.size, - pointStrokeWidth: anno.marker.strokeWidth, - pointFillColor: anno.marker.fillColor, - pointStrokeColor: anno.marker.strokeColor, - shape: anno.marker.shape, - pRadius: anno.marker.radius, - class: `apexcharts-point-annotation-marker ${anno.marker.cssClass} ${ - anno.id ? anno.id : '' - }`, - } - - let point = this.annoCtx.graphics.drawMarker( - x + anno.marker.offsetX, - y + anno.marker.offsetY, - optsPoints - ) - - parent.appendChild(point.node) - - const text = anno.label.text ? anno.label.text : '' - - let elText = this.annoCtx.graphics.drawText({ - x: x + anno.label.offsetX, - y: - y + - anno.label.offsetY - - anno.marker.size - - parseFloat(anno.label.style.fontSize) / 1.6, - text, - textAnchor: anno.label.textAnchor, - fontSize: anno.label.style.fontSize, - fontFamily: anno.label.style.fontFamily, - fontWeight: anno.label.style.fontWeight, - foreColor: anno.label.style.color, - cssClass: `apexcharts-point-annotation-label ${ - anno.label.style.cssClass - } ${anno.id ? anno.id : ''}`, - }) - - elText.attr({ - rel: index, - }) - - parent.appendChild(elText.node) - - // TODO: deprecate this as we will use custom - if (anno.customSVG.SVG) { - let g = this.annoCtx.graphics.group({ - class: - 'apexcharts-point-annotations-custom-svg ' + anno.customSVG.cssClass, - }) + if (!(clipY || clipX)) { + let optsPoints = { + pSize: anno.marker.size, + pointStrokeWidth: anno.marker.strokeWidth, + pointFillColor: anno.marker.fillColor, + pointStrokeColor: anno.marker.strokeColor, + shape: anno.marker.shape, + pRadius: anno.marker.radius, + class: `apexcharts-point-annotation-marker ${anno.marker.cssClass} ${ + anno.id ? anno.id : '' + }`, + } + + let point = this.annoCtx.graphics.drawMarker( + x + anno.marker.offsetX, + y + anno.marker.offsetY, + optsPoints + ) - g.attr({ - transform: `translate(${x + anno.customSVG.offsetX}, ${ - y + anno.customSVG.offsetY - })`, + parent.appendChild(point.node) + + const text = anno.label.text ? anno.label.text : '' + + let elText = this.annoCtx.graphics.drawText({ + x: x + anno.label.offsetX, + y: + y + + anno.label.offsetY - + anno.marker.size - + parseFloat(anno.label.style.fontSize) / 1.6, + text, + textAnchor: anno.label.textAnchor, + fontSize: anno.label.style.fontSize, + fontFamily: anno.label.style.fontFamily, + fontWeight: anno.label.style.fontWeight, + foreColor: anno.label.style.color, + cssClass: `apexcharts-point-annotation-label ${ + anno.label.style.cssClass + } ${anno.id ? anno.id : ''}`, }) - g.node.innerHTML = anno.customSVG.SVG - parent.appendChild(g.node) - } - - if (anno.image.path) { - let imgWidth = anno.image.width ? anno.image.width : 20 - let imgHeight = anno.image.height ? anno.image.height : 20 - - point = this.annoCtx.addImage({ - x: x + anno.image.offsetX - imgWidth / 2, - y: y + anno.image.offsetY - imgHeight / 2, - width: imgWidth, - height: imgHeight, - path: anno.image.path, - appendTo: '.apexcharts-point-annotations', + elText.attr({ + rel: index, }) - } - if (anno.mouseEnter) { - point.node.addEventListener( - 'mouseenter', - anno.mouseEnter.bind(this, anno) - ) - } - if (anno.mouseLeave) { - point.node.addEventListener( - 'mouseleave', - anno.mouseLeave.bind(this, anno) - ) - } - if (anno.click) { - point.node.addEventListener('click', anno.click.bind(this, anno)) + parent.appendChild(elText.node) + + // TODO: deprecate this as we will use custom + if (anno.customSVG.SVG) { + let g = this.annoCtx.graphics.group({ + class: + 'apexcharts-point-annotations-custom-svg ' + anno.customSVG.cssClass, + }) + + g.attr({ + transform: `translate(${x + anno.customSVG.offsetX}, ${ + y + anno.customSVG.offsetY + })`, + }) + + g.node.innerHTML = anno.customSVG.SVG + parent.appendChild(g.node) + } + + if (anno.image.path) { + let imgWidth = anno.image.width ? anno.image.width : 20 + let imgHeight = anno.image.height ? anno.image.height : 20 + + point = this.annoCtx.addImage({ + x: x + anno.image.offsetX - imgWidth / 2, + y: y + anno.image.offsetY - imgHeight / 2, + width: imgWidth, + height: imgHeight, + path: anno.image.path, + appendTo: '.apexcharts-point-annotations', + }) + } + + if (anno.mouseEnter) { + point.node.addEventListener( + 'mouseenter', + anno.mouseEnter.bind(this, anno) + ) + } + if (anno.mouseLeave) { + point.node.addEventListener( + 'mouseleave', + anno.mouseLeave.bind(this, anno) + ) + } + if (anno.click) { + point.node.addEventListener('click', anno.click.bind(this, anno)) + } } } diff --git a/src/modules/annotations/XAxisAnnotations.js b/src/modules/annotations/XAxisAnnotations.js index f90335c93..839036060 100644 --- a/src/modules/annotations/XAxisAnnotations.js +++ b/src/modules/annotations/XAxisAnnotations.js @@ -14,7 +14,10 @@ export default class XAnnotations { addXaxisAnnotation(anno, parent, index) { let w = this.w - let x1 = this.helpers.getX1X2('x1', anno) + let result = this.helpers.getX1X2('x1', anno) + let x1 = result.x + let clipX1 = result.clipped + let clipX2 = true let x2 const text = anno.label.text @@ -24,89 +27,97 @@ export default class XAnnotations { if (!Utils.isNumber(x1)) return if (anno.x2 === null || typeof anno.x2 === 'undefined') { - let line = this.annoCtx.graphics.drawLine( - x1 + anno.offsetX, // x1 - 0 + anno.offsetY, // y1 - x1 + anno.offsetX, // x2 - w.globals.gridHeight + anno.offsetY, // y2 - anno.borderColor, // lineColor - strokeDashArray, //dashArray - anno.borderWidth - ) - parent.appendChild(line.node) - if (anno.id) { - line.node.classList.add(anno.id) + if (!clipX1) { + let line = this.annoCtx.graphics.drawLine( + x1 + anno.offsetX, // x1 + 0 + anno.offsetY, // y1 + x1 + anno.offsetX, // x2 + w.globals.gridHeight + anno.offsetY, // y2 + anno.borderColor, // lineColor + strokeDashArray, //dashArray + anno.borderWidth + ) + parent.appendChild(line.node) + if (anno.id) { + line.node.classList.add(anno.id) + } } } else { - x2 = this.helpers.getX1X2('x2', anno) - - if (x2 < x1) { - let temp = x1 - x1 = x2 - x2 = temp + let result = this.helpers.getX1X2('x2', anno) + x2 = result.x + clipX2 = result.clipped + + if (!(clipX1 && clipX2)) { + if (x2 < x1) { + let temp = x1 + x1 = x2 + x2 = temp + } + + let rect = this.annoCtx.graphics.drawRect( + x1 + anno.offsetX, // x1 + 0 + anno.offsetY, // y1 + x2 - x1, // x2 + w.globals.gridHeight + anno.offsetY, // y2 + 0, // radius + anno.fillColor, // color + anno.opacity, // opacity, + 1, // strokeWidth + anno.borderColor, // strokeColor + strokeDashArray // stokeDashArray + ) + rect.node.classList.add('apexcharts-annotation-rect') + rect.attr('clip-path', `url(#gridRectMask${w.globals.cuid})`) + parent.appendChild(rect.node) + if (anno.id) { + rect.node.classList.add(anno.id) + } } + } - let rect = this.annoCtx.graphics.drawRect( - x1 + anno.offsetX, // x1 - 0 + anno.offsetY, // y1 - x2 - x1, // x2 - w.globals.gridHeight + anno.offsetY, // y2 - 0, // radius - anno.fillColor, // color - anno.opacity, // opacity, - 1, // strokeWidth - anno.borderColor, // strokeColor - strokeDashArray // stokeDashArray + if (!(clipX1 && clipX2)) { + let textRects = this.annoCtx.graphics.getTextRects( + text, + parseFloat(anno.label.style.fontSize) ) - rect.node.classList.add('apexcharts-annotation-rect') - rect.attr('clip-path', `url(#gridRectMask${w.globals.cuid})`) - parent.appendChild(rect.node) - if (anno.id) { - rect.node.classList.add(anno.id) - } + let textY = + anno.label.position === 'top' + ? 4 + : anno.label.position === 'center' + ? w.globals.gridHeight / 2 + + (anno.label.orientation === 'vertical' ? textRects.width / 2 : 0) + : w.globals.gridHeight + + let elText = this.annoCtx.graphics.drawText({ + x: x1 + anno.label.offsetX, + y: + textY + + anno.label.offsetY - + (anno.label.orientation === 'vertical' + ? anno.label.position === 'top' + ? textRects.width / 2 - 12 + : -textRects.width / 2 + : 0), + text, + textAnchor: anno.label.textAnchor, + fontSize: anno.label.style.fontSize, + fontFamily: anno.label.style.fontFamily, + fontWeight: anno.label.style.fontWeight, + foreColor: anno.label.style.color, + cssClass: `apexcharts-xaxis-annotation-label ${ + anno.label.style.cssClass + } ${anno.id ? anno.id : ''}` + }) + + elText.attr({ + rel: index + }) + + parent.appendChild(elText.node) + + // after placing the annotations on svg, set any vertically placed annotations + this.annoCtx.helpers.setOrientations(anno, index) } - - let textRects = this.annoCtx.graphics.getTextRects( - text, - parseFloat(anno.label.style.fontSize) - ) - let textY = - anno.label.position === 'top' - ? 4 - : anno.label.position === 'center' - ? w.globals.gridHeight / 2 + - (anno.label.orientation === 'vertical' ? textRects.width / 2 : 0) - : w.globals.gridHeight - - let elText = this.annoCtx.graphics.drawText({ - x: x1 + anno.label.offsetX, - y: - textY + - anno.label.offsetY - - (anno.label.orientation === 'vertical' - ? anno.label.position === 'top' - ? textRects.width / 2 - 12 - : -textRects.width / 2 - : 0), - text, - textAnchor: anno.label.textAnchor, - fontSize: anno.label.style.fontSize, - fontFamily: anno.label.style.fontFamily, - fontWeight: anno.label.style.fontWeight, - foreColor: anno.label.style.color, - cssClass: `apexcharts-xaxis-annotation-label ${ - anno.label.style.cssClass - } ${anno.id ? anno.id : ''}` - }) - - elText.attr({ - rel: index - }) - - parent.appendChild(elText.node) - - // after placing the annotations on svg, set any vertically placed annotations - this.annoCtx.helpers.setOrientations(anno, index) } drawXAxisAnnotations() { let w = this.w diff --git a/src/modules/annotations/YAxisAnnotations.js b/src/modules/annotations/YAxisAnnotations.js index c9be54206..913c1d5f9 100644 --- a/src/modules/annotations/YAxisAnnotations.js +++ b/src/modules/annotations/YAxisAnnotations.js @@ -1,4 +1,5 @@ import Helpers from './Helpers' +import AxesUtils from '../axes/AxesUtils' export default class YAnnotations { constructor(annoCtx) { @@ -6,6 +7,8 @@ export default class YAnnotations { this.annoCtx = annoCtx this.helpers = new Helpers(this.annoCtx) + this.axesUtils = new AxesUtils(this.annoCtx) + } addYaxisAnnotation(anno, parent, index) { @@ -13,27 +16,36 @@ export default class YAnnotations { let strokeDashArray = anno.strokeDashArray - let y1 = this.helpers.getY1Y2('y1', anno) + let result = this.helpers.getY1Y2('y1', anno) + let y1 = result.yP + let clipY1 = result.clipped let y2 + let clipY2 = true + let drawn = false const text = anno.label.text if (anno.y2 === null || typeof anno.y2 === 'undefined') { - let line = this.annoCtx.graphics.drawLine( - 0 + anno.offsetX, // x1 - y1 + anno.offsetY, // y1 - this._getYAxisAnnotationWidth(anno), // x2 - y1 + anno.offsetY, // y2 - anno.borderColor, // lineColor - strokeDashArray, // dashArray - anno.borderWidth - ) - parent.appendChild(line.node) - if (anno.id) { - line.node.classList.add(anno.id) + if (!clipY1) { + drawn = true + let line = this.annoCtx.graphics.drawLine( + 0 + anno.offsetX, // x1 + y1 + anno.offsetY, // y1 + this._getYAxisAnnotationWidth(anno), // x2 + y1 + anno.offsetY, // y2 + anno.borderColor, // lineColor + strokeDashArray, // dashArray + anno.borderWidth + ) + parent.appendChild(line.node) + if (anno.id) { + line.node.classList.add(anno.id) + } } } else { - y2 = this.helpers.getY1Y2('y2', anno) + result = this.helpers.getY1Y2('y2', anno) + y2 = result.yP + clipY2 = result.clipped if (y2 > y1) { let temp = y1 @@ -41,52 +53,57 @@ export default class YAnnotations { y2 = temp } - let rect = this.annoCtx.graphics.drawRect( - 0 + anno.offsetX, // x1 - y2 + anno.offsetY, // y1 - this._getYAxisAnnotationWidth(anno), // x2 - y1 - y2, // y2 - 0, // radius - anno.fillColor, // color - anno.opacity, // opacity, - 1, // strokeWidth - anno.borderColor, // strokeColor - strokeDashArray // stokeDashArray - ) - rect.node.classList.add('apexcharts-annotation-rect') - rect.attr('clip-path', `url(#gridRectMask${w.globals.cuid})`) - - parent.appendChild(rect.node) - if (anno.id) { - rect.node.classList.add(anno.id) + if (!(clipY1 && clipY2)) { + drawn = true + let rect = this.annoCtx.graphics.drawRect( + 0 + anno.offsetX, // x1 + y2 + anno.offsetY, // y1 + this._getYAxisAnnotationWidth(anno), // x2 + y1 - y2, // y2 + 0, // radius + anno.fillColor, // color + anno.opacity, // opacity, + 1, // strokeWidth + anno.borderColor, // strokeColor + strokeDashArray // stokeDashArray + ) + rect.node.classList.add('apexcharts-annotation-rect') + rect.attr('clip-path', `url(#gridRectMask${w.globals.cuid})`) + + parent.appendChild(rect.node) + if (anno.id) { + rect.node.classList.add(anno.id) + } } } - let textX = - anno.label.position === 'right' - ? w.globals.gridWidth - : anno.label.position === 'center' - ? w.globals.gridWidth / 2 - : 0 - - let elText = this.annoCtx.graphics.drawText({ - x: textX + anno.label.offsetX, - y: (y2 != null ? y2 : y1) + anno.label.offsetY - 3, - text, - textAnchor: anno.label.textAnchor, - fontSize: anno.label.style.fontSize, - fontFamily: anno.label.style.fontFamily, - fontWeight: anno.label.style.fontWeight, - foreColor: anno.label.style.color, - cssClass: `apexcharts-yaxis-annotation-label ${ - anno.label.style.cssClass - } ${anno.id ? anno.id : ''}` - }) - - elText.attr({ - rel: index - }) - - parent.appendChild(elText.node) + if (drawn) { + let textX = + anno.label.position === 'right' + ? w.globals.gridWidth + : anno.label.position === 'center' + ? w.globals.gridWidth / 2 + : 0 + + let elText = this.annoCtx.graphics.drawText({ + x: textX + anno.label.offsetX, + y: (y2 != null ? y2 : y1) + anno.label.offsetY - 3, + text, + textAnchor: anno.label.textAnchor, + fontSize: anno.label.style.fontSize, + fontFamily: anno.label.style.fontFamily, + fontWeight: anno.label.style.fontWeight, + foreColor: anno.label.style.color, + cssClass: `apexcharts-yaxis-annotation-label ${ + anno.label.style.cssClass + } ${anno.id ? anno.id : ''}` + }) + + elText.attr({ + rel: index + }) + + parent.appendChild(elText.node) + } } _getYAxisAnnotationWidth(anno) { @@ -108,8 +125,11 @@ export default class YAnnotations { class: 'apexcharts-yaxis-annotations' }) - w.config.annotations.yaxis.map((anno, index) => { - this.addYaxisAnnotation(anno, elg.node, index) + w.config.annotations.yaxis.forEach((anno, index) => { + let seriesIndex = w.globals.seriesYAxisMap[anno.yAxisIndex][0] + if (!this.axesUtils.isYAxisHidden(anno.yAxisIndex)) { + this.addYaxisAnnotation(anno, elg.node, index) + } }) return elg diff --git a/src/modules/axes/AxesUtils.js b/src/modules/axes/AxesUtils.js index 7dbed05ac..ff7ba8a01 100644 --- a/src/modules/axes/AxesUtils.js +++ b/src/modules/axes/AxesUtils.js @@ -185,10 +185,10 @@ export default class AxesUtils { }) return ( + allCollapsed || !w.config.yaxis[index].show || (!w.config.yaxis[index].showForNullSeries && - coreUtils.isSeriesNull(index) && - allCollapsed) + coreUtils.isSeriesNull(index)) ) } diff --git a/src/modules/tooltip/AxesTooltip.js b/src/modules/tooltip/AxesTooltip.js index eb5a88397..eac570ad3 100644 --- a/src/modules/tooltip/AxesTooltip.js +++ b/src/modules/tooltip/AxesTooltip.js @@ -168,10 +168,12 @@ class AxesTooltip { const elGrid = ttCtx.getElGrid() const seriesBound = elGrid.getBoundingClientRect() - const hoverY = (clientY - seriesBound.top) * xyRatios.yRatio[index] - const height = w.globals.maxYArr[index] - w.globals.minYArr[index] - - const val = w.globals.minYArr[index] + (height - hoverY) + // We can use the index of any series referenced by the Yaxis + // because they will all return the same value. + let seriesIndex = w.globals.seriesYAxisMap[anno.yAxisIndex][0] + const hoverY = (clientY - seriesBound.top) * xyRatios.yRatio[seriesIndex] + const height = w.globals.maxYArr[seriesIndex] - w.globals.minYArr[seriesIndex] + const val = w.globals.minYArr[seriesIndex] + (height - hoverY) ttCtx.tooltipPosition.moveYCrosshairs(clientY - seriesBound.top) ttCtx.yaxisTooltipText[index].innerHTML = lbFormatter(val)