@@ -36,9 +36,14 @@ const roundingErrorFix = numberUtil.round;
36
36
const mathFloor = Math . floor ;
37
37
const mathCeil = Math . ceil ;
38
38
const 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
+ */
42
47
class LogScale extends Scale {
43
48
static type = 'log' ;
44
49
readonly type = 'log' ;
@@ -47,6 +52,14 @@ class LogScale extends Scale {
47
52
48
53
private _originalScale : IntervalScale = new IntervalScale ( ) ;
49
54
55
+ /**
56
+ * Whether the original input values are negative.
57
+ *
58
+ * @type {boolean }
59
+ * @private
60
+ */
61
+ private _isNegative : boolean = false ;
62
+
50
63
private _fixMin : boolean ;
51
64
private _fixMax : boolean ;
52
65
@@ -63,12 +76,13 @@ class LogScale extends Scale {
63
76
const originalScale = this . _originalScale ;
64
77
const extent = this . _extent ;
65
78
const originalExtent = originalScale . getExtent ( ) ;
79
+ const negativeMultiplier = this . _isNegative ? - 1 : 1 ;
66
80
67
81
const ticks = intervalScaleProto . getTicks . call ( this , expandToNicedExtent ) ;
68
82
69
83
return zrUtil . map ( ticks , function ( tick ) {
70
84
const val = tick . value ;
71
- let powVal = numberUtil . round ( mathPow ( this . base , val ) ) ;
85
+ let powVal = mathPow ( this . base , val ) ;
72
86
73
87
// Fix #4158
74
88
powVal = ( val === extent [ 0 ] && this . _fixMin )
@@ -79,27 +93,31 @@ class LogScale extends Scale {
79
93
: powVal ;
80
94
81
95
return {
82
- value : powVal
96
+ value : powVal * negativeMultiplier
83
97
} ;
84
98
} , this ) ;
85
99
}
86
100
87
101
setExtent ( start : number , end : number ) : void {
88
- const base = mathLog ( this . base ) ;
102
+ // Assume the start and end can be infinity
89
103
// 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
+
92
111
intervalScaleProto . setExtent . call ( this , start , end ) ;
93
112
}
94
113
95
114
/**
96
115
* @return {number } end
97
116
*/
98
117
getExtent ( ) {
99
- const base = this . base ;
100
118
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 ] ) ;
103
121
104
122
// Fix #4158
105
123
const originalScale = this . _originalScale ;
@@ -113,9 +131,17 @@ class LogScale extends Scale {
113
131
unionExtent ( extent : [ number , number ] ) : void {
114
132
this . _originalScale . unionExtent ( extent ) ;
115
133
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
+
119
145
scaleProto . unionExtent . call ( this , extent ) ;
120
146
}
121
147
@@ -131,13 +157,18 @@ class LogScale extends Scale {
131
157
*/
132
158
calcNiceTicks ( approxTickNum : number ) : void {
133
159
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
+
136
163
if ( span === Infinity || span <= 0 ) {
137
164
return ;
138
165
}
139
166
140
- let interval = numberUtil . quantity ( span ) ;
167
+ let interval = mathMax (
168
+ 1 ,
169
+ mathRound ( span / approxTickNum )
170
+ ) ;
171
+
141
172
const err = approxTickNum / span * interval ;
142
173
143
174
// Filter ticks to get closer to the desired count.
@@ -150,10 +181,10 @@ class LogScale extends Scale {
150
181
interval *= 10 ;
151
182
}
152
183
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
+ ] ;
157
188
158
189
this . _interval = interval ;
159
190
this . _niceExtent = niceExtent ;
@@ -177,13 +208,19 @@ class LogScale extends Scale {
177
208
}
178
209
179
210
contain ( val : number ) : boolean {
180
- val = mathLog ( val ) / mathLog ( this . base ) ;
211
+ val = scaleHelper . absMathLog ( val , this . base ) ;
181
212
return scaleHelper . contain ( val , this . _extent ) ;
182
213
}
183
214
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 ) ;
187
224
}
188
225
189
226
scale ( val : number ) : number {
@@ -193,6 +230,26 @@ class LogScale extends Scale {
193
230
194
231
getMinorTicks : IntervalScale [ 'getMinorTicks' ] ;
195
232
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
+ }
196
253
}
197
254
198
255
const proto = LogScale . prototype ;
0 commit comments