-
Notifications
You must be signed in to change notification settings - Fork 649
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
320 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
|
||
import { fixedBase } from '@antv/util'; | ||
import { isNil } from '../../util/common'; | ||
import AutoUtil from './scale-util'; | ||
const { snapFactorTo, snapMultiple } = AutoUtil; | ||
|
||
// 默认经验刻度 | ||
const SNAP_COUNT_ARRAY = [ 1, 1.2, 1.5, 1.6, 2, 2.2, 2.4, 2.5, 3, 4, 5, 6, 7.5, 8, 10 ]; | ||
const DEFAULT_TICK_COUNT = 5; | ||
const EPS = 1e-12; | ||
|
||
// linear连续数值轴刻度nice算法 | ||
export default info => { | ||
|
||
let { min, max, interval, tickCount = DEFAULT_TICK_COUNT } = info; | ||
// 用户传入的经验刻度 | ||
const snapArray = info.snapArray || SNAP_COUNT_ARRAY; | ||
|
||
if (Math.abs(max - min) < EPS) { | ||
if (min === 0) { | ||
max = 1; | ||
} else { | ||
if (min > 0) { | ||
min = 0; | ||
} else { | ||
max = 0; | ||
} | ||
} | ||
if (max - min < 5 && !interval && max - min >= 1) { | ||
interval = 1; | ||
} | ||
} | ||
|
||
if (isNil(interval)) { | ||
// 计算间距 | ||
const temp = (max - min) / (tickCount - 1); | ||
interval = snapFactorTo(temp, snapArray, 'ceil'); | ||
} | ||
|
||
// 如果有自定义间隔 | ||
if (info.interval) { | ||
// 校正 max 和 min | ||
max = snapMultiple(max, interval, 'ceil'); // 向上逼近 | ||
min = snapMultiple(min, interval, 'floor'); // 向下逼近 | ||
|
||
tickCount = Math.round((max - min) / interval); | ||
min = fixedBase(min, interval); // 当min为负数的时候,fixedBase后,min可能会大于minLimit,导致最终产出的tick是大于minLimit的,所以必须进行修正 | ||
max = fixedBase(max, interval); | ||
} else { | ||
const avg = (max + min) / 2; | ||
const avgTick = snapMultiple(avg, interval, 'ceil'); | ||
const sideCount = Math.floor((tickCount - 2) / 2); | ||
let maxTick = fixedBase(avgTick + sideCount * interval, interval); | ||
let minTick; | ||
if (tickCount % 2 === 0) { | ||
minTick = fixedBase(avgTick - sideCount * interval, interval); | ||
} else { | ||
minTick = fixedBase(avgTick - (sideCount + 1) * interval, interval); | ||
} | ||
|
||
let prevMaxTick = null; | ||
// 顶部tick矫正 | ||
// 如果减去intervl, fixBase后,新的minTick没有大于之前的值,就退出,防止死循环 | ||
while (maxTick < max && (prevMaxTick === null || maxTick > prevMaxTick)) { // 保证计算出来的刻度最大值 maxTick 不小于数据最大值 max | ||
prevMaxTick = maxTick; | ||
maxTick = fixedBase(maxTick + interval, interval); | ||
} | ||
|
||
let prevMinTick = null; | ||
// 底部tick矫正 | ||
// 如果减去intervl, fixBase后,新的minTick没有小于之前的值,就退出,防止死循环 | ||
while (minTick > min && (prevMinTick === null || minTick < prevMinTick)) { // 保证计算出来的刻度最小值 minTick 不大于数据最小值 min | ||
prevMinTick = minTick; | ||
minTick = fixedBase(minTick - interval, interval); // 防止超常浮点数计算问题 | ||
} | ||
max = maxTick; | ||
min = minTick; | ||
} | ||
|
||
const ticks = []; | ||
|
||
ticks.push(min); | ||
for (let i = 1; i < tickCount; i++) { | ||
const tickValue = fixedBase(interval * i + min, interval); | ||
if (tickValue < max) { | ||
ticks.push(tickValue); | ||
} | ||
} | ||
if (ticks[ticks.length - 1] < max) { | ||
ticks.push(max); | ||
} | ||
|
||
return ticks; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
const DECIMAL_LENGTH = 12; | ||
// 获取系数 | ||
function getFactor(v) { | ||
let factor = 1; | ||
if (v === Infinity || v === -Infinity) { | ||
throw new Error('Not support Infinity!'); | ||
} | ||
if (v < 1) { | ||
let count = 0; | ||
while (v < 1) { | ||
factor = factor / 10; | ||
v = v * 10; | ||
count++; | ||
} | ||
// 浮点数计算出现问题 | ||
if (factor.toString().length > DECIMAL_LENGTH) { | ||
factor = parseFloat(factor.toFixed(count)); | ||
} | ||
} else { | ||
while (v > 10) { | ||
factor = factor * 10; | ||
v = v / 10; | ||
} | ||
} | ||
|
||
return factor; | ||
} | ||
|
||
// 取小于当前值的 | ||
function arrayFloor(values, value) { | ||
const length = values.length; | ||
if (length === 0) { | ||
return NaN; | ||
} | ||
|
||
let pre = values[0]; | ||
|
||
if (value < values[0]) { | ||
return NaN; | ||
} | ||
|
||
if (value >= values[length - 1]) { | ||
return values[length - 1]; | ||
} | ||
for (let i = 1; i < values.length; i++) { | ||
if (value < values[i]) { | ||
break; | ||
} | ||
pre = values[i]; | ||
} | ||
|
||
return pre; | ||
} | ||
|
||
// 大于当前值的第一个 | ||
function arrayCeiling(values, value) { | ||
const length = values.length; | ||
if (length === 0) { | ||
return NaN; | ||
} | ||
// var pre = values[0]; | ||
let rst; | ||
if (value > values[length - 1]) { | ||
return NaN; | ||
} | ||
if (value < values[0]) { | ||
return values[0]; | ||
} | ||
|
||
for (let i = 1; i < values.length; i++) { | ||
if (value <= values[i]) { | ||
rst = values[i]; | ||
break; | ||
} | ||
} | ||
|
||
return rst; | ||
} | ||
|
||
|
||
const Util = { | ||
// 获取逼近的数值 | ||
snapFactorTo(v, arr, snapType) { // 假设 v = -512,isFloor = true | ||
if (isNaN(v)) { | ||
return NaN; | ||
} | ||
let factor = 1; // 计算系数 | ||
if (v !== 0) { | ||
if (v < 0) { | ||
factor = -1; | ||
} | ||
v = v * factor; // v = 512 | ||
const tmpFactor = getFactor(v); | ||
factor = factor * tmpFactor; // factor = -100 | ||
|
||
v = v / tmpFactor; // v = 5.12 | ||
} | ||
if (snapType === 'floor') { | ||
v = Util.snapFloor(arr, v); // v = 5 | ||
} else if (snapType === 'ceil') { | ||
v = Util.snapCeiling(arr, v); // v = 6 | ||
} else { | ||
v = Util.snapTo(arr, v); // 四舍五入 5 | ||
} | ||
|
||
let rst = parseFloat((v * factor).toPrecision(DECIMAL_LENGTH)); // 如果出现浮点数计算问题,需要处理一下 | ||
|
||
// 如果出现浮点数计算问题,需要处理一下 | ||
if (Math.abs(factor) < 1 && rst.toString().length > DECIMAL_LENGTH) { | ||
const decimalVal = parseInt(1 / factor); | ||
const symbol = factor > 0 ? 1 : -1; | ||
rst = v / decimalVal * symbol; | ||
} | ||
return rst; | ||
}, | ||
// 获取逼近的倍数 | ||
snapMultiple(v, base, snapType) { | ||
let div; | ||
if (snapType === 'ceil') { | ||
div = Math.ceil(v / base); | ||
} else if (snapType === 'floor') { | ||
div = Math.floor(v / base); | ||
} else { | ||
div = Math.round(v / base); | ||
} | ||
return div * base; | ||
}, | ||
/** | ||
* 获取逼近的值,用于对齐数据 | ||
* @param {Array} values 数据集合 | ||
* @param {Number} value 数值 | ||
* @return {Number} 逼近的值 | ||
*/ | ||
snapTo(values, value) { | ||
// 这里假定values是升序排列 | ||
const floorVal = arrayFloor(values, value); | ||
const ceilingVal = arrayCeiling(values, value); | ||
if (isNaN(floorVal) || isNaN(ceilingVal)) { | ||
if (values[0] >= value) { | ||
return values[0]; | ||
} | ||
const last = values[values.length - 1]; | ||
if (last <= value) { | ||
return last; | ||
} | ||
} | ||
if (Math.abs(value - floorVal) < Math.abs(ceilingVal - value)) { | ||
return floorVal; | ||
} | ||
return ceilingVal; | ||
}, | ||
/** | ||
* 获取逼近的最小值,用于对齐数据 | ||
* @param {Array} values 数据集合 | ||
* @param {Number} value 数值 | ||
* @return {Number} 逼近的最小值 | ||
*/ | ||
snapFloor(values, value) { | ||
// 这里假定values是升序排列 | ||
return arrayFloor(values, value); | ||
}, | ||
/** | ||
* 获取逼近的最大值,用于对齐数据 | ||
* @param {Array} values 数据集合 | ||
* @param {Number} value 数值 | ||
* @return {Number} 逼近的最大值 | ||
*/ | ||
snapCeiling(values, value) { | ||
// 这里假定values是升序排列 | ||
return arrayCeiling(values, value); | ||
} | ||
}; | ||
|
||
export default Util; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import * as F2 from '../../../../src/index-all'; | ||
|
||
const canvas = document.createElement('canvas'); | ||
canvas.width = 500; | ||
canvas.height = 500; | ||
canvas.id = 'ctx'; | ||
document.body.appendChild(canvas); | ||
|
||
describe('刻度轴算法', function() { | ||
|
||
it('刻度轴', function() { | ||
|
||
const data = [ | ||
{ genre: 'Sports', sold: 275 }, | ||
{ genre: 'Strategy', sold: 115 }, | ||
{ genre: 'Action', sold: 120 }, | ||
{ genre: 'Shooter', sold: 350 }, | ||
{ genre: 'Other', sold: 150 } | ||
]; | ||
|
||
const chart = new F2.Chart({ | ||
id: 'ctx', | ||
pixelRatio: window.devicePixelRatio // 指定分辨率 | ||
}); | ||
|
||
chart.source(data); | ||
|
||
chart.scale('date', { | ||
type: 'timeCat', | ||
range: [ 0, 1 ], | ||
tickCount: 3, | ||
mask: 'MM-DD' | ||
// formatter: value => value.substr(5) // 去掉 yyyy-mm-dd 的年份 | ||
}); | ||
|
||
|
||
// chart.scale('sold', { | ||
// tickCount: 5, | ||
// }); | ||
|
||
chart.interval() | ||
.position('genre*sold') | ||
.color('genre'); | ||
|
||
chart.render(); | ||
|
||
}); | ||
|
||
}); |