echarts、antv图表类的工具 一般从0开始
有时我们可能想展示一些变化浮动不算大的数据 从0开始的基本就是一条直线
此时需要自定义y轴两端的范围
typeScript
/**
* 计算Y轴的自适应范围,智能处理大数、小数及微小变化
* @param {number[]} data - 数据数组
* @returns {number[]} [min, max] 最小值和最大值
*/
export function calculateYRange(data: number[]) {
if (data.length === 0) return [0, 1];
// 1. 计算核心指标并确定数量级
const minVal = Math.min(...data);
const maxVal = Math.max(...data);
const range = maxVal - minVal;
const dataMax = Math.max(Math.abs(minVal), Math.abs(maxVal));
// 处理全等数据
if (range === 0) return handleEqualValues(minVal);
// 2. 智能检测数量级和微小变化
const magnitude = determineMagnitude(dataMax, range);
const isIntegerData = data.every(Number.isInteger);
// 3. 处理微小变化(范围小于整体量级的1%)
if (isSmallVariation(range, magnitude)) {
return handleSmallVariation(
minVal,
maxVal,
range,
magnitude,
isIntegerData,
);
}
// 4. 通用数据处理流程
return handleGeneralCase(minVal, maxVal, range, magnitude, isIntegerData);
}
// 辅助函数 =================================================================
/**
* 处理全等数据情况
*/
function handleEqualValues(minVal: number) {
if (minVal === 0) return [-1, 1];
const pad = minVal === 0 ? 1 : Math.abs(minVal) * 0.05;
return [minVal >= 0 ? 0 : minVal - pad, minVal + pad];
}
/**
* 智能确定数据的数量级
*/
function determineMagnitude(dataMax, range) {
if (dataMax === 0) return 1;
// 计算主数量级
const exponent = Math.floor(Math.log10(dataMax));
const magnitude = Math.pow(10, exponent);
// 当范围极小时,使用范围的数量级
const rangeExponent = Math.floor(Math.log10(range));
return Math.abs(rangeExponent) > Math.abs(exponent)
? Math.pow(10, rangeExponent)
: magnitude;
}
/**
* 检测是否为微小变化(范围 < 量级的1%)
*/
function isSmallVariation(range, magnitude) {
return range < magnitude * 0.01;
}
/**
* 处理微小变化数据(如[10001,10002]或[0.0001,0.0002])
*/
function handleSmallVariation(
minVal: number,
maxVal: number,
range: number,
magnitude: number,
isInteger: boolean,
) {
// 1. 计算合适步长
const step = calcSmallStep(range, magnitude, isInteger);
// 2. 只扩展半个步长,避免过度扩展
const padding = step / 2;
// 3. 计算边界(确保最小值不为负)
let targetMin = minVal - padding;
const targetMax = maxVal + padding;
if (minVal >= 0) targetMin = Math.max(0, targetMin);
// 4. 对齐到步长的整数倍
const min = Math.floor(targetMin / step) * step;
const max = Math.ceil(targetMax / step) * step;
// 5. 精度处理
return adjustPrecision([min, max], step);
}
/**
* 计算微小变化的合适步长
*/
function calcSmallStep(range: number, magnitude: number, isInteger: boolean) {
if (range < 1e-10) return magnitude < 1 ? magnitude / 10 : 1;
// 根据范围和量级确定步长
const rangeDigits = Math.floor(Math.log10(range));
const baseStep = Math.pow(10, rangeDigits);
// 候选步长序列
const candidates = [0.1, 0.2, 0.5, 1, 2, 5, 10].map((x) => x * baseStep);
// 选择覆盖5-10个刻度的步长
const idealSteps = range / 7; // 7个刻度理想值
return (
candidates.find((s) => s >= idealSteps) || candidates[candidates.length - 1]
);
}
/**
* 通用数据处理
*/
function handleGeneralCase(minVal, maxVal, range, magnitude, isInteger) {
// 1. 智能扩展边距
const ratio = minVal >= 0 ? [0.05, 0.15] : [0.1, 0.1];
const lowerPadding = range * ratio[0];
const upperPadding = range * ratio[1];
// 2. 计算目标范围
let targetMin = minVal - lowerPadding;
const targetMax = maxVal + upperPadding;
if (minVal >= 0) targetMin = Math.max(0, targetMin);
// 3. 计算步长
const step = calcNiceStep(targetMax - targetMin, magnitude, isInteger);
// 4. 对齐刻度
const min = Math.floor(targetMin / step) * step;
const max = Math.ceil(targetMax / step) * step;
// 5. 大数值处理
if (Math.max(Math.abs(min), Math.abs(max)) >= 1000) {
return handleLargeValues(min, max, minVal, step);
}
// 6. 精度处理
return adjustPrecision([min, max], step);
}
/**
* 大数值特殊处理
*/
function handleLargeValues(min, max, minVal, step) {
const unit = Math.max(Math.ceil(step / 100) * 100, 100);
const newMin = Math.floor(min / unit) * unit;
const newMax = Math.ceil(max / unit) * unit;
return adjustPrecision(
[minVal >= 0 ? Math.max(0, newMin) : newMin, newMax],
unit,
);
}
/**
* 优化步长计算
*/
function calcNiceStep(range, magnitude, isInteger) {
if (range < 1e-10) return magnitude < 1 ? magnitude / 10 : 1;
// 1. 计算基础步长
const power = Math.floor(Math.log10(range));
const stepSize = Math.pow(10, power);
// 2. 标准化步长序列
const relativeSize = range / stepSize;
let niceStep = stepSize;
if (relativeSize > 7) {
niceStep = stepSize * 10;
} else if (relativeSize > 3.5) {
niceStep = stepSize * 5;
} else if (relativeSize > 1.5) {
niceStep = stepSize * 2;
}
// 3. 整数数据处理
if (isInteger && niceStep < 1) niceStep = 1;
return niceStep;
}
/**
* 精度处理(防止浮点误差)
*/
function adjustPrecision(range: number[], step: number) {
// 计算需要保留的小数位数
let precision = 0;
if (step < 1 && step > 1e-10) {
precision = Math.max(0, -Math.floor(Math.log10(step)) + 2);
}
// 四舍五入处理
const round: (val: number) => number = (val: number) =>
precision > 0 ? Number(val.toFixed(precision)) : Math.round(val);
return range.map(round);
}