提交 6aa93d7b 编写于 作者: P pah100

tweak tick precision, see #5041.

上级 a8c52cc1
......@@ -8,12 +8,10 @@ define(function (require) {
var numberUtil = require('../util/number');
var formatUtil = require('../util/format');
var Scale = require('./Scale');
var helper = require('./helper');
var mathFloor = Math.floor;
var mathCeil = Math.ceil;
var getPrecisionSafe = numberUtil.getPrecisionSafe;
var roundNumber = numberUtil.round;
/**
* @alias module:echarts/coord/scale/Interval
* @constructor
......@@ -72,40 +70,9 @@ define(function (require) {
if (!this._interval) {
this.niceTicks();
}
var interval = this._interval;
var extent = this._extent;
var ticks = [];
// Consider this case: using dataZoom toolbox, zoom and zoom.
var safeLimit = 10000;
if (interval) {
var niceExtent = this._niceExtent;
var precision = this._intervalPrecision = getPrecisionSafe(interval);
// FIXME
precision += 2;
if (extent[0] < niceExtent[0]) {
ticks.push(extent[0]);
}
var tick = niceExtent[0];
while (tick <= niceExtent[1]) {
ticks.push(tick);
// Avoid rounding error
tick = roundNumber(tick + interval, precision);
if (ticks.length > safeLimit) {
return [];
}
}
// Consider this case: the last item of ticks is smaller
// than niceExtent[1] and niceExtent[1] === extent[1].
if (extent[1] > (ticks.length ? ticks[ticks.length - 1] : niceExtent[1])) {
ticks.push(extent[1]);
}
}
return ticks;
return helper.intervalScaleGetTicks(
this._interval, this._extent, this._niceExtent, this._intervalPrecision
);
},
/**
......@@ -139,7 +106,7 @@ define(function (require) {
}
else if (precision === 'auto') {
// Should be more precise then tick.
precision = this._intervalPrecision + 2;
precision = this._intervalPrecision;
}
// (1) If `precision` is set, 12.005 should be display as '12.00500'.
......@@ -168,27 +135,11 @@ define(function (require) {
extent.reverse();
}
// From "Nice Numbers for Graph Labels" of Graphic Gems
// var niceSpan = numberUtil.nice(span, false);
var step = roundNumber(
numberUtil.nice(span / splitNumber, true),
Math.max(
getPrecisionSafe(extent[0]),
getPrecisionSafe(extent[1])
// extent may be [0, 1], and step should have 1 more digits.
// To make it safe we add 2 more digits
) + 2
);
var precision = getPrecisionSafe(step) + 2;
// Niced extent inside original extent
var niceExtent = [
roundNumber(mathCeil(extent[0] / step) * step, precision),
roundNumber(mathFloor(extent[1] / step) * step, precision)
];
var result = helper.intervalScaleNiceTicks(extent, splitNumber);
this._interval = step;
this._niceExtent = niceExtent;
this._intervalPrecision = result.intervalPrecision;
this._interval = result.interval;
this._niceExtent = result.niceTickExtent;
},
/**
......@@ -234,10 +185,10 @@ define(function (require) {
var interval = this._interval;
if (!fixMin) {
extent[0] = roundNumber(mathFloor(extent[0] / interval) * interval);
extent[0] = roundNumber(Math.floor(extent[0] / interval) * interval);
}
if (!fixMax) {
extent[1] = roundNumber(mathCeil(extent[1] / interval) * interval);
extent[1] = roundNumber(Math.ceil(extent[1] / interval) * interval);
}
}
});
......
/**
* For testable.
*/
define(function (require) {
var numberUtil = require('../util/number');
var roundNumber = numberUtil.round;
var helper = {};
/**
* @param {Array.<number>} extent Both extent[0] and extent[1] should be valid number.
* Should be extent[0] < extent[1].
* @param {number} splitNumber splitNumber should be >= 1.
* @return {Object} {interval, intervalPrecision, niceTickExtent}
*/
helper.intervalScaleNiceTicks = function (extent, splitNumber) {
var result = {};
var span = extent[1] - extent[0];
var interval = result.interval = numberUtil.nice(span / splitNumber, true);
// Tow more digital for tick.
var precision = result.intervalPrecision = numberUtil.getPrecisionSafe(interval) + 2;
// Niced extent inside original extent
var niceTickExtent = result.niceTickExtent = [
roundNumber(Math.ceil(extent[0] / interval) * interval, precision),
roundNumber(Math.floor(extent[1] / interval) * interval, precision)
];
// In some cases (e.g., splitNumber is 1), niceTickExtent may be out of extent.
!isFinite(niceTickExtent[0]) && (niceTickExtent[0] = extent[0]);
!isFinite(niceTickExtent[1]) && (niceTickExtent[1] = extent[1]);
clamp(niceTickExtent, 0, extent);
clamp(niceTickExtent, 1, extent);
if (niceTickExtent[0] > niceTickExtent[1]) {
niceTickExtent[0] = niceTickExtent[1];
}
return result;
};
function clamp(niceTickExtent, idx, extent) {
niceTickExtent[idx] = Math.max(Math.min(niceTickExtent[idx], extent[1]), extent[0]);
}
helper.intervalScaleGetTicks = function (interval, extent, niceTickExtent, intervalPrecision) {
var ticks = [];
// If interval is 0, return [];
if (!interval) {
return ticks;
}
// Consider this case: using dataZoom toolbox, zoom and zoom.
var safeLimit = 10000;
if (extent[0] < niceTickExtent[0]) {
ticks.push(extent[0]);
}
var tick = niceTickExtent[0];
while (tick <= niceTickExtent[1]) {
ticks.push(tick);
// Avoid rounding error
tick = roundNumber(tick + interval, intervalPrecision);
if (tick === ticks[ticks.length - 1]) {
// Consider out of safe float point, e.g.,
// -3711126.9907707 + 2e-10 === -3711126.9907707
break;
}
if (ticks.length > safeLimit) {
return [];
}
}
// Consider this case: the last item of ticks is smaller
// than niceTickExtent[1] and niceTickExtent[1] === extent[1].
if (extent[1] > (ticks.length ? ticks[ticks.length - 1] : niceTickExtent[1])) {
ticks.push(extent[1]);
}
return ticks;
};
return helper;
});
\ No newline at end of file
......@@ -170,6 +170,7 @@ define(function (require) {
/**
* Minimal dicernible data precisioin according to a single pixel.
*
* @param {Array.<number>} dataExtent
* @param {Array.<number>} pixelExtent
* @return {number} precision
......@@ -261,24 +262,33 @@ define(function (require) {
/**
* Quantity of a number. e.g. 0.1, 1, 10, 100
*
* @param {number} val
* @return {number}
*/
number.quantity = function (val) {
return Math.pow(10, Math.floor(Math.log(val) / Math.LN10));
return Math.pow(10, quantityExponent(val));
};
// "Nice Numbers for Graph Labels" of Graphic Gems
function quantityExponent(val) {
return Math.floor(Math.log(val) / Math.LN10);
}
/**
* find a “nice” number approximately equal to x. Round the number if round = true, take ceiling if round = false
* The primary observation is that the “nicest” numbers in decimal are 1, 2, and 5, and all power-of-ten multiples of these numbers.
* @param {number} val
* find a “nice” number approximately equal to x. Round the number if round = true,
* take ceiling if round = false. The primary observation is that the “nicest”
* numbers in decimal are 1, 2, and 5, and all power-of-ten multiples of these numbers.
*
* See "Nice Numbers for Graph Labels" of Graphic Gems.
*
* @param {number} val Non-negative value.
* @param {boolean} round
* @return {number}
*/
number.nice = function (val, round) {
var exp10 = number.quantity(val);
var f = val / exp10; // between 1 and 10
var exponent = quantityExponent(val);
var exp10 = Math.pow(10, exponent);
var f = val / exp10; // 1 <= f < 10
var nf;
if (round) {
if (f < 1.5) { nf = 1; }
......@@ -294,7 +304,11 @@ define(function (require) {
else if (f < 5) { nf = 5; }
else { nf = 10; }
}
return nf * exp10;
val = nf * exp10;
// Fix 3 * 0.1 === 0.30000000000000004 issue (see IEEE 754).
// 20 is the uppper bound of toFixed.
return exponent >= -20 ? +val.toFixed(exponent < 0 ? -exponent : 0) : val;
};
/**
......@@ -365,6 +379,7 @@ define(function (require) {
* parseFloat NaNs numeric-cast false positives (null|true|false|"")
* ...but misinterprets leading-number strings, particularly hex literals ("0x...")
* subtraction forces infinities to NaN
*
* @param {*} v
* @return {boolean}
*/
......
......@@ -294,6 +294,13 @@
});
};
/**
* @public
*/
helper.isValueFinite = function (val) {
return val != null && val !== '' && isFinite(val);
};
/**
* @public
* @param {Array.<string>} deps
......@@ -332,5 +339,13 @@
return window.JSON.stringify(result, null, 4);
};
/**
* @public
*/
helper.print = function (str) {
if (typeof console !== 'undefined') {
console.log(str);
}
};
})(window);
\ No newline at end of file
......@@ -3,6 +3,8 @@ describe('scale_interval', function() {
var utHelper = window.utHelper;
var testCase = utHelper.prepare([
'echarts/scale/helper',
'echarts/util/number',
'echarts/component/grid',
'echarts/chart/line',
'echarts/chart/bar'
......@@ -75,7 +77,90 @@ describe('scale_interval', function() {
);
expect(labelPrecisioned).toEqual('0.0000005000');
});
});
describe('ticks', function () {
// testCase.createChart()('randomCover', function (scaleHelper) {
// doRandomTest(scaleHelper, 10, 5);
// });
function randomNumber(quantity) {
return (Math.random() - 0.5) * Math.pow(10, (Math.random() - 0.5) * quantity);
}
function check(cond) {
expect(cond).toEqual(true);
return +cond;
}
function doSingleTest(scaleHelper, numberUtil, extent, splitNumber) {
var result = scaleHelper.intervalScaleNiceTicks(extent, splitNumber);
var intervalPrecision = result.intervalPrecision;
var interval = result.interval;
var niceTickExtent = result.niceTickExtent;
var fails = [];
!check(utHelper.isValueFinite(interval)) && fails.push(0);
!check(utHelper.isValueFinite(intervalPrecision)) && fails.push(1);
!check(utHelper.isValueFinite(niceTickExtent[0])) && fails.push(2);
!check(utHelper.isValueFinite(niceTickExtent[1])) && fails.push(3);
!check(niceTickExtent[0] >= extent[0]) && fails.push(4);
!check(niceTickExtent[1] <= extent[1]) && fails.push(5);
!check(niceTickExtent[1] >= niceTickExtent[0]) && fails.push(6);
var ticks = scaleHelper.intervalScaleGetTicks(interval, extent, niceTickExtent, intervalPrecision);
!check(ticks.length > 0) && fails.push(7);
!check(ticks[0] === extent[0] && ticks[ticks.length - 1] === extent[1]) && fails.push(8);
var ticksOK = 1;
for (var i = 1; i < ticks.length; i++) {
ticksOK &= check(ticks[i - 1] < ticks[i]);
if (ticks[i] !== extent[0] && ticks[i] !== extent[1]) {
var tickPrecision = numberUtil.getPrecisionSafe(ticks[i]);
ticksOK &= check(tickPrecision <= intervalPrecision);
}
}
!ticksOK && fails.push(9);
// check precision rounding error ????????????
if (fails.length) {
utHelper.print(
'FAIL:[' + fails
+ '] extent:[' + extent + '] niceTickExtent:[' + niceTickExtent + '] ticks:['
+ ticks + '] '
);
}
}
function doRandomTest(scaleHelper, numberUtil, count, splitNumber, quantity) {
for (var i = 0; i < count; i++) {
var extent = [];
extent[0] = randomNumber(quantity);
extent[1] = extent[0] + randomNumber(quantity);
if (extent[1] === extent[0]) {
extent[1] = extent[0] + 1;
}
if (extent[0] > extent[1]) {
extent.reverse();
}
doSingleTest(scaleHelper, numberUtil, extent, splitNumber);
}
}
testCase.createChart()('cases', function (scaleHelper, numberUtil) {
doSingleTest(scaleHelper, numberUtil, [3.7210923755786733e-8,176.4352516752083], 1);
doSingleTest(scaleHelper, numberUtil, [1550932.3941785, 1550932.3941786], 5);
doSingleTest(scaleHelper, numberUtil, [-3711126.9907707,-3711126.990770699], 5);
});
testCase.createChart()('randomCover', function (scaleHelper, numberUtil) {
doRandomTest(scaleHelper, numberUtil, 500, 5, 20);
doRandomTest(scaleHelper, numberUtil, 200, 1, 20);
});
});
});
\ No newline at end of file
......@@ -310,7 +310,6 @@ describe('util/number', function () {
describe('getPrecisionSafe', function () {
testCase('basic', function (numberUtil) {
expect(numberUtil.getPrecisionSafe(10)).toEqual(0);
expect(numberUtil.getPrecisionSafe(1)).toEqual(0);
......@@ -325,4 +324,24 @@ describe('util/number', function () {
});
});
describe('nice', function () {
testCase('extreme', function (numberUtil) {
// Should not be 0.30000000000000004
expect(numberUtil.nice(0.3869394696651766, true)).toEqual(0.3);
expect(numberUtil.nice(0.3869394696651766)).toEqual(0.5);
expect(numberUtil.nice(0.00003869394696651766, true)).toEqual(0.00003);
expect(numberUtil.nice(0.00003869394696651766, false)).toEqual(0.00005);
expect(numberUtil.nice(0, true)).toEqual(0);
expect(numberUtil.nice(0)).toEqual(0);
expect(numberUtil.nice(13, true)).toEqual(10);
expect(numberUtil.nice(13)).toEqual(20);
expect(numberUtil.nice(3900000000000000000021, true)).toEqual(3000000000000000000000);
expect(numberUtil.nice(3900000000000000000021)).toEqual(5000000000000000000000);
expect(numberUtil.nice(0.00000000000000000656939, true)).toEqual(0.000000000000000005);
expect(numberUtil.nice(0.00000000000000000656939)).toEqual(0.00000000000000001);
expect(numberUtil.nice(0.10000000000000000656939, true)).toEqual(0.1);
expect(numberUtil.nice(0.10000000000000000656939)).toEqual(0.2);
});
});
});
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册