Skip to content

Commit 479dcd4

Browse files
committed
fix&feat: Change and clarify the rounding error and auto-precision utils and solutions.
1 parent d013d5e commit 479dcd4

3 files changed

Lines changed: 332 additions & 63 deletions

File tree

‎src/scale/helper.ts‎

Lines changed: 120 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,32 +17,54 @@
1717
* under the License.
1818
*/
1919

20-
import{getPrecision,round,nice,quantityExponent}from'../util/number';
20+
import{getPrecision,round,nice,quantityExponent,mathPow,mathMax,mathRound}from'../util/number';
2121
importIntervalScalefrom'./Interval';
2222
importLogScalefrom'./Log';
2323
importtypeScalefrom'./Scale';
2424
import{bind}from'zrender/src/core/util';
2525
importtype{ScaleBreakContext}from'./break';
26+
importTimeScalefrom'./Time';
27+
import{NullUndefined}from'../util/types';
2628

2729
typeintervalScaleNiceTicksResult={
2830
interval: number,
2931
intervalPrecision: number,
3032
niceTickExtent: [number,number]
3133
};
3234

33-
exportfunctionisValueNice(val: number){
34-
constexp10=Math.pow(10,quantityExponent(Math.abs(val)));
35-
constf=Math.abs(val/exp10);
36-
returnf===0
37-
||f===1
38-
||f===2
39-
||f===3
40-
||f===5;
41-
}
35+
/**
36+
* See also method `nice` in `src/util/number.ts`.
37+
*/
38+
// export function isValueNice(val: number) {
39+
// const exp10 = Math.pow(10, quantityExponent(Math.abs(val)));
40+
// const f = Math.abs(round(val / exp10, 0));
41+
// return f === 0
42+
// || f === 1
43+
// || f === 2
44+
// || f === 3
45+
// || f === 5;
46+
// }
4247

4348
exportfunctionisIntervalOrLogScale(scale: Scale): scale is LogScale|IntervalScale{
44-
returnscale.type==='interval'||scale.type==='log';
49+
returnisIntervalScale(scale)||isLogScale(scale);
50+
}
51+
52+
exportfunctionisIntervalScale(scale: Scale): scale is IntervalScale{
53+
returnscale.type==='interval';
4554
}
55+
56+
exportfunctionisTimeScale(scale: Scale): scale is TimeScale{
57+
returnscale.type==='time';
58+
}
59+
60+
exportfunctionisLogScale(scale: Scale): scale is LogScale{
61+
returnscale.type==='log';
62+
}
63+
64+
exportfunctionisOrdinalScale(scale: Scale): boolean{
65+
returnscale.type==='ordinal';
66+
}
67+
4668
/**
4769
* @param extent Both extent[0] and extent[1] should be valid number.
4870
* Should be extent[0] < extent[1].
@@ -65,23 +87,29 @@ export function intervalScaleNiceTicks(
6587
if(maxInterval!=null&&interval>maxInterval){
6688
interval=result.interval=maxInterval;
6789
}
68-
// Tow more digital for tick.
6990
constprecision=result.intervalPrecision=getIntervalPrecision(interval);
7091
// Niced extent inside original extent
7192
constniceTickExtent=result.niceTickExtent=[
7293
round(Math.ceil(extent[0]/interval)*interval,precision),
7394
round(Math.floor(extent[1]/interval)*interval,precision)
7495
];
7596

76-
fixExtent(niceTickExtent,extent);
97+
fixNiceExtent(niceTickExtent,extent);
7798

7899
returnresult;
79100
}
80101

81-
exportfunctionincreaseInterval(interval: number){
82-
constexp10=Math.pow(10,quantityExponent(interval));
83-
// Increase interval
84-
letf=interval/exp10;
102+
/**
103+
* The input `niceInterval` should be generated
104+
* from `nice` method in `src/util/number.ts`, or
105+
* from `increaseInterval` itself.
106+
*/
107+
exportfunctionincreaseInterval(niceInterval: number){
108+
constexponent=quantityExponent(niceInterval);
109+
// No rounding error in Math.pow(10, xxx).
110+
constexp10=mathPow(10,exponent);
111+
// Fix IEEE 754 float rounding error
112+
letf=mathRound(niceInterval/exp10);
85113
if(!f){
86114
f=1;
87115
}
@@ -94,25 +122,26 @@ export function increaseInterval(interval: number) {
94122
else{// f is 1 or 5
95123
f*=2;
96124
}
97-
returnround(f*exp10);
125+
// Fix IEEE 754 float rounding error
126+
returnround(f*exp10,-exponent);
98127
}
99128

100-
/**
101-
* @return interval precision
102-
*/
103-
exportfunctiongetIntervalPrecision(interval: number): number{
129+
exportfunctiongetIntervalPrecision(niceInterval: number): number{
104130
// Tow more digital for tick.
105-
returngetPrecision(interval)+2;
131+
// NOTE: `2` was introduced in commit `af2a2a9f6303081d7c3b52f0a38add07b4c6e0c7`;
132+
// it works on "nice" interval, but seems not necessarily mathematically required.
133+
returngetPrecision(niceInterval)+2;
106134
}
107135

136+
108137
functionclamp(
109138
niceTickExtent: [number,number],idx: number,extent: [number,number]
110139
): void{
111140
niceTickExtent[idx]=Math.max(Math.min(niceTickExtent[idx],extent[1]),extent[0]);
112141
}
113142

114143
// In some cases (e.g., splitNumber is 1), niceTickExtent may be out of extent.
115-
exportfunctionfixExtent(
144+
exportfunctionfixNiceExtent(
116145
niceTickExtent: [number,number],extent: [number,number]
117146
): void{
118147
!isFinite(niceTickExtent[0])&&(niceTickExtent[0]=extent[0]);
@@ -173,3 +202,70 @@ export function logTransform(base: number, extent: number[], noClampNegative?: b
173202
Math.log(noClampNegative ? extent[1] : Math.max(0,extent[1]))/loggedBase
174203
];
175204
}
205+
206+
exportfunctionpowTransform(base: number,extent: number[]): [number,number]{
207+
return[
208+
mathPow(base,extent[0]),
209+
mathPow(base,extent[1])
210+
];
211+
}
212+
213+
/**
214+
* A valid extent is:
215+
* - No non-finite number.
216+
* - `extent[0] < extent[1]`.
217+
*
218+
* [NOTICE]: The input `rawExtent` can only be:
219+
* - All non-finite numbers or `NaN`; or
220+
* - `[Infinity, -Infinity]` (A typical initial extent with no data.)
221+
* (Improve it when needed.)
222+
*/
223+
exportfunctionintervalScaleEnsureValidExtent(
224+
rawExtent: number[],
225+
opt: {
226+
fixMax?: boolean
227+
}
228+
): number[]{
229+
constextent=rawExtent.slice();
230+
// If extent start and end are same, expand them
231+
if(extent[0]===extent[1]){
232+
if(extent[0]!==0){
233+
// Expand extent
234+
// Note that extents can be both negative. See #13154
235+
constexpandSize=Math.abs(extent[0]);
236+
// In the fowllowing case
237+
// Axis has been fixed max 100
238+
// Plus data are all 100 and axis extent are [100, 100].
239+
// Extend to the both side will cause expanded max is larger than fixed max.
240+
// So only expand to the smaller side.
241+
if(!opt.fixMax){
242+
extent[1]+=expandSize/2;
243+
extent[0]-=expandSize/2;
244+
}
245+
else{
246+
extent[0]-=expandSize/2;
247+
}
248+
}
249+
else{
250+
extent[1]=1;
251+
}
252+
}
253+
constspan=extent[1]-extent[0];
254+
// If there are no data and extent are [Infinity, -Infinity]
255+
if(!isFinite(span)){
256+
extent[0]=0;
257+
extent[1]=1;
258+
}
259+
elseif(span<0){
260+
extent.reverse();
261+
}
262+
263+
returnextent;
264+
}
265+
266+
exportfunctionensureValidSplitNumber(
267+
rawSplitNumber: number|NullUndefined,defaultSplitNumber: number
268+
): number{
269+
rawSplitNumber=rawSplitNumber||defaultSplitNumber;
270+
returnmathRound(mathMax(rawSplitNumber,1));
271+
}

0 commit comments

Comments
 (0)