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);
}
相关推荐
荒诞英雄1 分钟前
菠萝滞销,帮帮我们(多个APP实例间pinia混乱)
前端·架构
llq_3508 分钟前
pnpm / Yarn / npm 覆盖依赖用法对比
前端
麦当_11 分钟前
ReAct 模式在 Neovate 中的应用
前端·javascript·架构
折七11 分钟前
告别传统开发痛点:AI 驱动的现代化企业级模板 Clhoria
前端·后端·node.js
程序00712 分钟前
纯html实现商品首页
前端
coderlin_12 分钟前
BI磁吸布局 (2) 基于react-grid-layout扩展的布局方式
前端·react.js·前端框架
Ankkaya14 分钟前
vue3 实现自定义模板表单打印
前端
itslife15 分钟前
vite源码 - 开始
前端·javascript
Achieve - 前端实验室15 分钟前
【每日一面】React Hooks闭包陷阱
前端·javascript·react.js
张愚歌16 分钟前
Leaflet行政区划边界开发全攻略
前端