echarts、antv图表类 y轴范围 计算方法

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);
}
相关推荐
OpenTiny社区14 分钟前
一文解读“Performance面板”前端性能优化工具基础用法!
前端·性能优化·opentiny
拾光拾趣录35 分钟前
🔥FormData+Ajax组合拳,居然现在还用这种原始方式?💥
前端·面试
不会笑的卡哇伊1 小时前
新手必看!帮你踩坑h5的微信生态~
前端·javascript
bysking1 小时前
【28 - 记住上一个页面tab】实现一个记住用户上次点击的tab,上次搜索过的数据 bysking
前端·javascript
Dream耀1 小时前
跨域问题解析:从同源策略到JSONP与CORS
前端·javascript
前端布鲁伊1 小时前
【前端高频面试题】面试官: localhost 和 127.0.0.1有什么区别
前端
HANK1 小时前
Electron + Vue3 桌面应用开发实战指南
前端·vue.js
極光未晚1 小时前
Vue 前端高效分包指南:从 “卡成 PPT” 到 “丝滑如德芙” 的蜕变
前端·vue.js·性能优化
郝亚军1 小时前
炫酷圆形按钮调色器
前端·javascript·css
Spider_Man1 小时前
别再用Express了!用Node.js原生HTTP模块装逼的正确姿势
前端·http·node.js