From 1663675a0368f6033b24706ab9bd10491dcab910 Mon Sep 17 00:00:00 2001 From: Andreas Gerstmayr Date: Sat, 19 Feb 2022 15:57:33 +0100 Subject: [PATCH 1/2] feat(chart): support negative values in logarithmic axes The logarithm of negative values is not defined. However, in the case of a logarithmic chart axis, it makes sense to define log(-x) as -log(x), to support logarithmic axes with negative values. Cf. 'symlog' in matplotlib Resolves: #15558 --- src/scale/Log.ts | 41 ++++++++++++++----- test/bar-log-negative.html | 83 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 11 deletions(-) create mode 100644 test/bar-log-negative.html diff --git a/src/scale/Log.ts b/src/scale/Log.ts index 41586e8775..1baa0a3101 100644 --- a/src/scale/Log.ts +++ b/src/scale/Log.ts @@ -36,9 +36,28 @@ const roundingErrorFix = numberUtil.round; const mathFloor = Math.floor; const mathCeil = Math.ceil; const mathPow = Math.pow; - const mathLog = Math.log; +/** + * symmetric log, allowing negative values for logarithmic scales + */ +function symMathLog(x: number): number { + if (x >= 0) { + return mathLog(x); + } + return -mathLog(-x); +} + +/** + * symmetric pow, allowing negative values for logarithmic scales + */ +function symMathPow(x: number, y: number): number { + if (y >= 0) { + return mathPow(x, y); + } + return -mathPow(x, -y); +} + class LogScale extends Scale { static type = 'log'; readonly type = 'log'; @@ -68,7 +87,7 @@ class LogScale extends Scale { return zrUtil.map(ticks, function (tick) { const val = tick.value; - let powVal = numberUtil.round(mathPow(this.base, val)); + let powVal = numberUtil.round(symMathPow(this.base, val)); // Fix #4158 powVal = (val === extent[0] && this._fixMin) @@ -86,8 +105,8 @@ class LogScale extends Scale { setExtent(start: number, end: number): void { const base = this.base; - start = mathLog(start) / mathLog(base); - end = mathLog(end) / mathLog(base); + start = symMathLog(start) / mathLog(base); + end = symMathLog(end) / mathLog(base); intervalScaleProto.setExtent.call(this, start, end); } @@ -97,8 +116,8 @@ class LogScale extends Scale { getExtent() { const base = this.base; const extent = scaleProto.getExtent.call(this); - extent[0] = mathPow(base, extent[0]); - extent[1] = mathPow(base, extent[1]); + extent[0] = symMathPow(base, extent[0]); + extent[1] = symMathPow(base, extent[1]); // Fix #4158 const originalScale = this._originalScale; @@ -113,8 +132,8 @@ class LogScale extends Scale { this._originalScale.unionExtent(extent); const base = this.base; - extent[0] = mathLog(extent[0]) / mathLog(base); - extent[1] = mathLog(extent[1]) / mathLog(base); + extent[0] = symMathLog(extent[0]) / mathLog(base); + extent[1] = symMathLog(extent[1]) / mathLog(base); scaleProto.unionExtent.call(this, extent); } @@ -176,18 +195,18 @@ class LogScale extends Scale { } contain(val: number): boolean { - val = mathLog(val) / mathLog(this.base); + val = symMathLog(val) / mathLog(this.base); return scaleHelper.contain(val, this._extent); } normalize(val: number): number { - val = mathLog(val) / mathLog(this.base); + val = symMathLog(val) / mathLog(this.base); return scaleHelper.normalize(val, this._extent); } scale(val: number): number { val = scaleHelper.scale(val, this._extent); - return mathPow(this.base, val); + return symMathPow(this.base, val); } getMinorTicks: IntervalScale['getMinorTicks']; diff --git a/test/bar-log-negative.html b/test/bar-log-negative.html new file mode 100644 index 0000000000..a9c6342bf0 --- /dev/null +++ b/test/bar-log-negative.html @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + +
+ + + + + From 1852d6a1a359fea46f3d6767bfdac49cafce6ecd Mon Sep 17 00:00:00 2001 From: Andreas Gerstmayr Date: Fri, 23 Feb 2024 21:22:40 +0100 Subject: [PATCH 2/2] fix symMathLog(0) case, update syntax --- src/scale/Log.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/scale/Log.ts b/src/scale/Log.ts index 1baa0a3101..3ef0a3697b 100644 --- a/src/scale/Log.ts +++ b/src/scale/Log.ts @@ -42,20 +42,17 @@ const mathLog = Math.log; * symmetric log, allowing negative values for logarithmic scales */ function symMathLog(x: number): number { - if (x >= 0) { - return mathLog(x); + if (x === 0) { + return 0; } - return -mathLog(-x); + return x > 0 ? mathLog(x) : -mathLog(-x); } /** * symmetric pow, allowing negative values for logarithmic scales */ function symMathPow(x: number, y: number): number { - if (y >= 0) { - return mathPow(x, y); - } - return -mathPow(x, -y); + return y >= 0 ? mathPow(x, y) : -mathPow(x, -y); } class LogScale extends Scale {