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);
}
相关推荐
fruge1 天前
2025前端工程化与性能优化实战指南:从构建到监控的全链路方案
前端·性能优化
lijun_xiao20091 天前
前端最新Vue2+Vue3基础入门到实战项目全套教程
前端
90后的晨仔1 天前
Pinia 状态管理原理与实战全解析
前端·vue.js
杰克尼1 天前
JavaWeb_p165部门管理
java·开发语言·前端
90后的晨仔1 天前
Vue3 状态管理完全指南:从响应式 API 到 Pinia
前端·vue.js
90后的晨仔1 天前
Vue 内置组件全解析:提升开发效率的五大神器
前端·vue.js
我胡为喜呀1 天前
Vue3 中的 watch 和 watchEffect:如何优雅地监听数据变化
前端·javascript·vue.js
我登哥MVP1 天前
Ajax 详解
java·前端·ajax·javaweb
非凡ghost1 天前
Typora(跨平台MarkDown编辑器) v1.12.2 中文绿色版
前端·windows·智能手机·编辑器·软件需求
馨谙1 天前
/dev/null 是什么,有什么用途?
前端·chrome