@@ -36,9 +36,14 @@ 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 a linear values to a logarithmic range.
44+ *
45+ * Support for negative values is implemented by inverting the extents and mapping the values as they were positive.
46+ */
4247class LogScale extends Scale {
4348 static type = 'log' ;
4449 readonly type = 'log' ;
@@ -47,6 +52,14 @@ class LogScale extends Scale {
4752
4853 private _originalScale : IntervalScale = new IntervalScale ( ) ;
4954
55+ /**
56+ * Whether the original input values are negative.
57+ *
58+ * @type {boolean }
59+ * @private
60+ */
61+ private _isNegative : boolean = false ;
62+
5063 private _fixMin : boolean ;
5164 private _fixMax : boolean ;
5265
@@ -63,12 +76,13 @@ class LogScale extends Scale {
6376 const originalScale = this . _originalScale ;
6477 const extent = this . _extent ;
6578 const originalExtent = originalScale . getExtent ( ) ;
79+ const negativeMultiplier = this . _isNegative ? - 1 : 1 ;
6680
6781 const ticks = intervalScaleProto . getTicks . call ( this , expandToNicedExtent ) ;
6882
6983 return zrUtil . map ( ticks , function ( tick ) {
7084 const val = tick . value ;
71- let powVal = numberUtil . round ( mathPow ( this . base , val ) ) ;
85+ let powVal = mathPow ( this . base , val ) ;
7286
7387 // Fix #4158
7488 powVal = ( val === extent [ 0 ] && this . _fixMin )
@@ -79,27 +93,31 @@ class LogScale extends Scale {
7993 : powVal ;
8094
8195 return {
82- value : powVal
96+ value : powVal * negativeMultiplier
8397 } ;
8498 } , this ) ;
8599 }
86100
87101 setExtent ( start : number , end : number ) : void {
88- const base = mathLog ( this . base ) ;
102+ // Assume the start and end can be infinity
89103 // log(-Infinity) is NaN, so safe guard here
90- start = mathLog ( Math . max ( 0 , start ) ) / base ;
91- end = mathLog ( Math . max ( 0 , end ) ) / base ;
104+ if ( start < Infinity ) {
105+ start = scaleHelper . absMathLog ( start , this . base ) ;
106+ }
107+ if ( end > - Infinity ) {
108+ end = scaleHelper . absMathLog ( end , this . base ) ;
109+ }
110+
92111 intervalScaleProto . setExtent . call ( this , start , end ) ;
93112 }
94113
95114 /**
96115 * @return {number } end
97116 */
98117 getExtent ( ) {
99- const base = this . base ;
100118 const extent = scaleProto . getExtent . call ( this ) ;
101- extent [ 0 ] = mathPow ( base , extent [ 0 ] ) ;
102- extent [ 1 ] = mathPow ( base , extent [ 1 ] ) ;
119+ extent [ 0 ] = mathPow ( this . base , extent [ 0 ] ) ;
120+ extent [ 1 ] = mathPow ( this . base , extent [ 1 ] ) ;
103121
104122 // Fix #4158
105123 const originalScale = this . _originalScale ;
@@ -113,9 +131,17 @@ class LogScale extends Scale {
113131 unionExtent ( extent : [ number , number ] ) : void {
114132 this . _originalScale . unionExtent ( extent ) ;
115133
116- const base = this . base ;
117- extent [ 0 ] = mathLog ( extent [ 0 ] ) / mathLog ( base ) ;
118- extent [ 1 ] = mathLog ( extent [ 1 ] ) / mathLog ( base ) ;
134+ if ( extent [ 0 ] < 0 && extent [ 1 ] < 0 ) {
135+ // If both extent are negative, switch to plotting negative values.
136+ // If there are only some negative values, they will be plotted incorrectly as positive values.
137+ this . _isNegative = true ;
138+ }
139+
140+ const [ logStart , logEnd ] = this . getLogExtent ( extent [ 0 ] , extent [ 1 ] ) ;
141+
142+ extent [ 0 ] = logStart ;
143+ extent [ 1 ] = logEnd ;
144+
119145 scaleProto . unionExtent . call ( this , extent ) ;
120146 }
121147
@@ -131,13 +157,18 @@ class LogScale extends Scale {
131157 */
132158 calcNiceTicks ( approxTickNum : number ) : void {
133159 approxTickNum = approxTickNum || 10 ;
134- const extent = this . _extent ;
135- const span = extent [ 1 ] - extent [ 0 ] ;
160+
161+ const span = this . _extent [ 1 ] - this . _extent [ 0 ] ;
162+
136163 if ( span === Infinity || span <= 0 ) {
137164 return ;
138165 }
139166
140- let interval = numberUtil . quantity ( span ) ;
167+ let interval = mathMax (
168+ 1 ,
169+ mathRound ( span / approxTickNum )
170+ ) ;
171+
141172 const err = approxTickNum / span * interval ;
142173
143174 // Filter ticks to get closer to the desired count.
@@ -150,10 +181,10 @@ class LogScale extends Scale {
150181 interval *= 10 ;
151182 }
152183
153- const niceExtent = [
154- numberUtil . round ( mathCeil ( extent [ 0 ] / interval ) * interval ) ,
155- numberUtil . round ( mathFloor ( extent [ 1 ] / interval ) * interval )
156- ] as [ number , number ] ;
184+ const niceExtent : [ number , number ] = [
185+ mathFloor ( this . _extent [ 0 ] / interval ) * interval ,
186+ mathCeil ( this . _extent [ 1 ] / interval ) * interval
187+ ] ;
157188
158189 this . _interval = interval ;
159190 this . _niceExtent = niceExtent ;
@@ -177,13 +208,19 @@ class LogScale extends Scale {
177208 }
178209
179210 contain ( val : number ) : boolean {
180- val = mathLog ( val ) / mathLog ( this . base ) ;
211+ val = scaleHelper . absMathLog ( val , this . base ) ;
181212 return scaleHelper . contain ( val , this . _extent ) ;
182213 }
183214
184- normalize ( val : number ) : number {
185- val = mathLog ( val ) / mathLog ( this . base ) ;
186- return scaleHelper . normalize ( val , this . _extent ) ;
215+ normalize ( inputVal : number ) : number {
216+ const val = scaleHelper . absMathLog ( inputVal , this . base ) ;
217+ let ex : [ number , number ] = [ this . _extent [ 0 ] , this . _extent [ 1 ] ] ;
218+
219+ if ( this . _isNegative ) {
220+ // Invert the extent for normalize calculations as the extent is inverted for negative values.
221+ ex = [ this . _extent [ 1 ] , this . _extent [ 0 ] ] ;
222+ }
223+ return scaleHelper . normalize ( val , ex ) ;
187224 }
188225
189226 scale ( val : number ) : number {
@@ -193,6 +230,26 @@ class LogScale extends Scale {
193230
194231 getMinorTicks : IntervalScale [ 'getMinorTicks' ] ;
195232 getLabel : IntervalScale [ 'getLabel' ] ;
233+
234+ /**
235+ * Get the extent of the log scale.
236+ * @param start - The start value of the extent.
237+ * @param end - The end value of the extent.
238+ * @returns The extent of the log scale. The extent is reversed for negative values.
239+ */
240+ getLogExtent ( start : number , end : number ) : [ number , number ] {
241+ // Invert the extent but use absolute values
242+ if ( this . _isNegative ) {
243+ const logStart = scaleHelper . absMathLog ( Math . abs ( end ) , this . base ) ;
244+ const logEnd = scaleHelper . absMathLog ( Math . abs ( start ) , this . base ) ;
245+ return [ logStart , logEnd ] ;
246+ }
247+ else {
248+ const logStart = scaleHelper . absMathLog ( start , this . base ) ;
249+ const logEnd = scaleHelper . absMathLog ( end , this . base ) ;
250+ return [ logStart , logEnd ] ;
251+ }
252+ }
196253}
197254
198255const proto = LogScale . prototype ;
0 commit comments