From 7b797780df61bad47aa208ad0c666be3ae8ac86c Mon Sep 17 00:00:00 2001 From: Ross Johnson <159597299+rosco54@users.noreply.github.com> Date: Fri, 15 Mar 2024 18:15:37 +1100 Subject: [PATCH] Fix #4323 Multiple-yaxis-scales, 3 series with 2 scales demo was broken by 3.47.0 after the introduction of the new yaxis:seriesName as an array feature. Refactored some of that code. Fixes relating to yaxis.seriesName array feature added to 3.47.0. Anything that indexes into minYArr[], maxYArr[], baseLineY[], xyRatios.yRatio[] that doesn't derive from realIndex needed to go through w.globals.seriesYAxisMap. Primarily affected y-axis annotations and possibly some other positioned features in multi-axis charts. Miscellaneous: Remove yaxis.min: 0 from the bar axes in sample as not required. --- samples/react/mixed/multiple-yaxes.html | 2 - samples/source/mixed/multiple-yaxes.xml | 2 - samples/vanilla-js/mixed/multiple-yaxes.html | 2 - samples/vue/mixed/multiple-yaxes.html | 2 - src/modules/Scales.js | 79 ++++++++++++++------ src/modules/ZoomPanSelection.js | 7 +- src/modules/annotations/Helpers.js | 7 +- src/modules/tooltip/AxesTooltip.js | 10 ++- 8 files changed, 73 insertions(+), 38 deletions(-) diff --git a/samples/react/mixed/multiple-yaxes.html b/samples/react/mixed/multiple-yaxes.html index 1899489d8..7354658f4 100644 --- a/samples/react/mixed/multiple-yaxes.html +++ b/samples/react/mixed/multiple-yaxes.html @@ -114,7 +114,6 @@ }, yaxis: [ { - min: 0, seriesName: 'Income', axisTicks: { show: true, @@ -139,7 +138,6 @@ } }, { - min: 0, seriesName: 'Cashflow', opposite: true, axisTicks: { diff --git a/samples/source/mixed/multiple-yaxes.xml b/samples/source/mixed/multiple-yaxes.xml index 1e03176b0..8e15cae1f 100644 --- a/samples/source/mixed/multiple-yaxes.xml +++ b/samples/source/mixed/multiple-yaxes.xml @@ -38,7 +38,6 @@ xaxis: { }, yaxis: [ { - min: 0, seriesName: 'Income', axisTicks: { show: true, @@ -63,7 +62,6 @@ yaxis: [ } }, { - min: 0, seriesName: 'Cashflow', opposite: true, axisTicks: { diff --git a/samples/vanilla-js/mixed/multiple-yaxes.html b/samples/vanilla-js/mixed/multiple-yaxes.html index c52c21e12..4948e4f4c 100644 --- a/samples/vanilla-js/mixed/multiple-yaxes.html +++ b/samples/vanilla-js/mixed/multiple-yaxes.html @@ -97,7 +97,6 @@ }, yaxis: [ { - min: 0, seriesName: 'Income', axisTicks: { show: true, @@ -122,7 +121,6 @@ } }, { - min: 0, seriesName: 'Cashflow', opposite: true, axisTicks: { diff --git a/samples/vue/mixed/multiple-yaxes.html b/samples/vue/mixed/multiple-yaxes.html index 986f56c56..9c228ff4c 100644 --- a/samples/vue/mixed/multiple-yaxes.html +++ b/samples/vue/mixed/multiple-yaxes.html @@ -117,7 +117,6 @@ }, yaxis: [ { - min: 0, seriesName: 'Income', axisTicks: { show: true, @@ -142,7 +141,6 @@ } }, { - min: 0, seriesName: 'Cashflow', opposite: true, axisTicks: { diff --git a/src/modules/Scales.js b/src/modules/Scales.js index 85c030251..f131988a2 100644 --- a/src/modules/Scales.js +++ b/src/modules/Scales.js @@ -578,9 +578,14 @@ export default class Scales { unassignedSeriesIndices.push(i) seriesYAxisReverseMap.push(null) }) + cnf.yaxis.forEach((yaxe, yi) => { + axisSeriesMap[yi] = [] + }) + let unassignedYAxisIndices = [] // here, we loop through the yaxis array and find the item which has "seriesName" property cnf.yaxis.forEach((yaxe, yi) => { + let assigned = false // Allow seriesName to be either a string (for backward compatibility), // in which case, handle multiple yaxes referencing the same series. // or an array of strings so that a yaxis can reference multiple series. @@ -592,19 +597,27 @@ export default class Scales { } else { seriesNames.push(yaxe.seriesName) } - axisSeriesMap[yi] = [] seriesNames.forEach((name) => { cnf.series.forEach((s, si) => { - // if seriesName matches we use that scale. if (s.name === name) { + assigned = true axisSeriesMap[yi].push(si) - seriesYAxisReverseMap[si] = yi - let remove = unassignedSeriesIndices.indexOf(si) - unassignedSeriesIndices.splice(remove, 1) + let remove + if (yi === si) { + seriesYAxisReverseMap[si] = yi + remove = unassignedSeriesIndices.indexOf(si) + } else { + seriesYAxisReverseMap[yi] = yi + remove = unassignedSeriesIndices.indexOf(yi) + } + if (remove !== -1) { + unassignedSeriesIndices.splice(remove, 1) + } } }) }) - } else { + } + if (!assigned) { unassignedYAxisIndices.push(yi) } }) @@ -667,26 +680,48 @@ export default class Scales { // 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. - let emptyAxes = [] - axisSeriesMap.forEach((axisSeries, ai) => { - for (let si = ai + 1; si < axisSeriesMap.length; si++) { - let iter = axisSeries.values() - for (const val of iter) { - let i = axisSeriesMap[si].indexOf(val) - if (i !== -1) { - axisSeries.push(si) // add series index to current yaxis - axisSeriesMap[si].splice(i, 1) // remove it from its old yaxis - } + + // 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) } - if (axisSeriesMap[si].length < 1 && emptyAxes.indexOf(si) === -1) { - emptyAxes.push(si) + }) + // 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) + } + }) } - } - }) - for (let i = emptyAxes.length - 1; i >= 0; i--) { - axisSeriesMap.splice(emptyAxes[i], 1) + }) } // Compute min..max for each yaxis diff --git a/src/modules/ZoomPanSelection.js b/src/modules/ZoomPanSelection.js index e372264a9..b47579d40 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 first series index referenced by the Yaxis + // because any others will 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..847f8cbb7 100644 --- a/src/modules/annotations/Helpers.js +++ b/src/modules/annotations/Helpers.js @@ -185,9 +185,12 @@ export default class Helpers { y = coreUtils.getLogVal(y, anno.yAxisIndex) yPos = y / w.globals.yLogRatio[anno.yAxisIndex] } else { + // We can use the first series index referenced by the Yaxis + // because any others will return the same value. + let seriesIndex = w.globals.seriesYAxisMap[anno.yAxisIndex][0] 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) } yP = w.globals.gridHeight - yPos diff --git a/src/modules/tooltip/AxesTooltip.js b/src/modules/tooltip/AxesTooltip.js index eb5a88397..314a5c595 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 first series index referenced by the Yaxis + // because any others will 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)