@@ -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