@@ -36,9 +36,16 @@ const roundingErrorFix = numberUtil.round;
3636const mathFloor = Math . floor ;
3737const mathCeil = Math . ceil ;
3838const mathPow = Math . pow ;
39-
40- const mathLog = Math . log ;
41-
39+ const mathMax = Math . max ;
40+ const mathRound = Math . round ;
41+
42+ /**
43+ * LogScale is a scale that maps values to a logarithmic range.
44+ *
45+ * Support for negative values is implemented by inverting the extents and first handling values as absolute values.
46+ * Then in tick generation, the tick values are multiplied by -1 back to the original values and the normalize function
47+ * uses a reverse extent to get the correct negative values in plot with smaller values at the top of Y axis.
48+ */
4249class LogScale extends Scale {
4350 static type = 'log' ;
4451 readonly type = 'log' ;
@@ -47,6 +54,14 @@ class LogScale extends Scale {
4754
4855 private _originalScale : IntervalScale = new IntervalScale ( ) ;
4956
57+ /**
58+ * Whether the original input values are negative.
59+ *
60+ * @type {boolean }
61+ * @private
62+ */
63+ private _isNegative : boolean = false ;
64+
5065 private _fixMin : boolean ;
5166 private _fixMax : boolean ;
5267
@@ -63,12 +78,13 @@ class LogScale extends Scale {
6378 const originalScale = this . _originalScale ;
6479 const extent = this . _extent ;
6580 const originalExtent = originalScale . getExtent ( ) ;
81+ const negativeMultiplier = this . _isNegative ? - 1 : 1 ;
6682
6783 const ticks = intervalScaleProto . getTicks . call ( this , expandToNicedExtent ) ;
6884
6985 return zrUtil . map ( ticks , function ( tick ) {
7086 const val = tick . value ;
71- let powVal = numberUtil . round ( mathPow ( this . base , val ) ) ;
87+ let powVal = mathPow ( this . base , val ) ;
7288
7389 // Fix #4158
7490 powVal = ( val === extent [ 0 ] && this . _fixMin )
@@ -79,27 +95,31 @@ class LogScale extends Scale {
7995 : powVal ;
8096
8197 return {
82- value : powVal
98+ value : powVal * negativeMultiplier
8399 } ;
84100 } , this ) ;
85101 }
86102
87103 setExtent ( start : number , end : number ) : void {
88- const base = mathLog ( this . base ) ;
104+ // Assume the start and end can be infinity
89105 // log(-Infinity) is NaN, so safe guard here
90- start = mathLog ( Math . max ( 0 , start ) ) / base ;
91- end = mathLog ( Math . max ( 0 , end ) ) / base ;
106+ if ( start < Infinity ) {
107+ start = scaleHelper . absMathLog ( start , this . base ) ;
108+ }
109+ if ( end > - Infinity ) {
110+ end = scaleHelper . absMathLog ( end , this . base ) ;
111+ }
112+
92113 intervalScaleProto . setExtent . call ( this , start , end ) ;
93114 }
94115
95116 /**
96117 * @return {number } end
97118 */
98119 getExtent ( ) {
99- const base = this . base ;
100120 const extent = scaleProto . getExtent . call ( this ) ;
101- extent [ 0 ] = mathPow ( base , extent [ 0 ] ) ;
102- extent [ 1 ] = mathPow ( base , extent [ 1 ] ) ;
121+ extent [ 0 ] = mathPow ( this . base , extent [ 0 ] ) ;
122+ extent [ 1 ] = mathPow ( this . base , extent [ 1 ] ) ;
103123
104124 // Fix #4158
105125 const originalScale = this . _originalScale ;
@@ -113,9 +133,17 @@ class LogScale extends Scale {
113133 unionExtent ( extent : [ number , number ] ) : void {
114134 this . _originalScale . unionExtent ( extent ) ;
115135
116- const base = this . base ;
117- extent [ 0 ] = mathLog ( extent [ 0 ] ) / mathLog ( base ) ;
118- extent [ 1 ] = mathLog ( extent [ 1 ] ) / mathLog ( base ) ;
136+ if ( extent [ 0 ] < 0 && extent [ 1 ] < 0 ) {
137+ // If both extent are negative, switch to plotting negative values.
138+ // If there are only some negative values, they will be plotted incorrectly as positive values.
139+ this . _isNegative = true ;
140+ }
141+
142+ const [ logStart , logEnd ] = this . getLogExtent ( extent [ 0 ] , extent [ 1 ] ) ;
143+
144+ extent [ 0 ] = logStart ;
145+ extent [ 1 ] = logEnd ;
146+
119147 scaleProto . unionExtent . call ( this , extent ) ;
120148 }
121149
@@ -131,13 +159,18 @@ class LogScale extends Scale {
131159 */
132160 calcNiceTicks ( approxTickNum : number ) : void {
133161 approxTickNum = approxTickNum || 10 ;
134- const extent = this . _extent ;
135- const span = extent [ 1 ] - extent [ 0 ] ;
162+
163+ const span = this . _extent [ 1 ] - this . _extent [ 0 ] ;
164+
136165 if ( span === Infinity || span <= 0 ) {
137166 return ;
138167 }
139168
140- let interval = numberUtil . quantity ( span ) ;
169+ let interval = mathMax (
170+ 1 ,
171+ mathRound ( span / approxTickNum )
172+ ) ;
173+
141174 const err = approxTickNum / span * interval ;
142175
143176 // Filter ticks to get closer to the desired count.
@@ -150,10 +183,10 @@ class LogScale extends Scale {
150183 interval *= 10 ;
151184 }
152185
153- const niceExtent = [
154- numberUtil . round ( mathCeil ( extent [ 0 ] / interval ) * interval ) ,
155- numberUtil . round ( mathFloor ( extent [ 1 ] / interval ) * interval )
156- ] as [ number , number ] ;
186+ const niceExtent : [ number , number ] = [
187+ mathFloor ( this . _extent [ 0 ] / interval ) * interval ,
188+ mathCeil ( this . _extent [ 1 ] / interval ) * interval
189+ ] ;
157190
158191 this . _interval = interval ;
159192 this . _niceExtent = niceExtent ;
@@ -177,13 +210,19 @@ class LogScale extends Scale {
177210 }
178211
179212 contain ( val : number ) : boolean {
180- val = mathLog ( val ) / mathLog ( this . base ) ;
213+ val = scaleHelper . absMathLog ( val , this . base ) ;
181214 return scaleHelper . contain ( val , this . _extent ) ;
182215 }
183216
184- normalize ( val : number ) : number {
185- val = mathLog ( val ) / mathLog ( this . base ) ;
186- return scaleHelper . normalize ( val , this . _extent ) ;
217+ normalize ( inputVal : number ) : number {
218+ const val = scaleHelper . absMathLog ( inputVal , this . base ) ;
219+ let ex : [ number , number ] = [ this . _extent [ 0 ] , this . _extent [ 1 ] ] ;
220+
221+ if ( this . _isNegative ) {
222+ // Invert the extent for normalize calculations as the extent is inverted for negative values.
223+ ex = [ this . _extent [ 1 ] , this . _extent [ 0 ] ] ;
224+ }
225+ return scaleHelper . normalize ( val , ex ) ;
187226 }
188227
189228 scale ( val : number ) : number {
@@ -193,6 +232,26 @@ class LogScale extends Scale {
193232
194233 getMinorTicks : IntervalScale [ 'getMinorTicks' ] ;
195234 getLabel : IntervalScale [ 'getLabel' ] ;
235+
236+ /**
237+ * Get the extent of the log scale.
238+ * @param start - The start value of the extent.
239+ * @param end - The end value of the extent.
240+ * @returns The extent of the log scale. The extent is reversed for negative values.
241+ */
242+ getLogExtent ( start : number , end : number ) : [ number , number ] {
243+ // Invert the extent but use absolute values
244+ if ( this . _isNegative ) {
245+ const logStart = scaleHelper . absMathLog ( Math . abs ( end ) , this . base ) ;
246+ const logEnd = scaleHelper . absMathLog ( Math . abs ( start ) , this . base ) ;
247+ return [ logStart , logEnd ] ;
248+ }
249+ else {
250+ const logStart = scaleHelper . absMathLog ( start , this . base ) ;
251+ const logEnd = scaleHelper . absMathLog ( end , this . base ) ;
252+ return [ logStart , logEnd ] ;
253+ }
254+ }
196255}
197256
198257const proto = LogScale . prototype ;
0 commit comments