diff --git a/src/scale/Log.ts b/src/scale/Log.ts index b1f4d08b16..8918c68370 100644 --- a/src/scale/Log.ts +++ b/src/scale/Log.ts @@ -36,9 +36,25 @@ 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 0; + } + return x > 0 ? mathLog(x) : -mathLog(-x); +} + +/** + * symmetric pow, allowing negative values for logarithmic scales + */ +function symMathPow(x: number, y: number): number { + return y >= 0 ? mathPow(x, y) : -mathPow(x, -y); +} + class LogScale extends Scale { static type = 'log'; readonly type = 'log'; @@ -68,7 +84,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,9 +102,8 @@ class LogScale extends Scale { setExtent(start: number, end: number): void { const base = mathLog(this.base); - // log(-Infinity) is NaN, so safe guard here - start = mathLog(Math.max(0, start)) / base; - end = mathLog(Math.max(0, end)) / base; + start = symMathLog(start) / base; + end = symMathLog(end) / base; intervalScaleProto.setExtent.call(this, start, end); } @@ -98,8 +113,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; @@ -114,8 +129,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); } @@ -177,18 +192,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 @@ + + + + + + + + + + + + + + + + + + + +
+ + + + +