echarts 中 markLine 的值超过 series 的最大范围后不显示的解决办法

先说说背景. 公司的出库零件数趋势以折线图展示. 然后有这样一个需求, 就是在折线图上以横线的方式标出当前时间点的出库预期值. 既然是预期值, 那么即有可能会小于或等于实际值, 也有可能会大于实际值. 因此该需求可以用 markLine 简单实现一下:

// goal 为目标值, extent 中能够拿到所有折线图 series 的最大值和最小值
{
    yAxis: {
        max: extent => extent.max > goal ? extent.max : goal
    },
    series: {
        markLine: {
            data: [{
                yAxis: goal
            }]
        }
    }
}

效果 🔽

这样已经实现了功能!  然而这就可以交差了吗? 当然不是, 我们注意看上面截图的效果,纵轴最上面的一个坐标值是 13330, 而不是 15000。也就是说markLine是显示了,但是纵坐标的分割区间看起来就像是缺了最上面的一绺。按照预期13330这条标记线应该被包含在12000-15000这个分割区间范围内才对, 这好像跟我们以为的结果不太符合啊,看着十分别扭,这是一个追求完美的前端开发工程师绝对不能容忍的.  然后又仔细研究开发文档, 并没有找到有用的信息, 看来 echarts.yAxis.max 的唯一作用就是强制修改图表能够展示的最大值, 也同时影响坐标刻度的最后一个值. 这不是我们想要的.

 

来我们先看看原本不添加 markLine 时候的效果⏬,数据应该分布在由几条分割线组成的完整的分割区间内。

 

来我们再看看我们真正想要的结果⏬,同样的,我们希望包括markLine在内的数据也应该分布在几条分割线组成的完整的分割区间内

 

所以这是怎么做到的呢? 根据以上需求, 我们只需要得到相邻两条分割线之间的差值 (如上图, 在我的需求中应该是 3000) 和实际需要的分割段数, 然后据此计算出纵坐标最大的坐标轴刻度值, 最后通过 echarts.yAxis.max 返回这个结果即可. 很遗憾, 这两个值均无法直接获得 (也可能是我不知道正确的获取方法, 请知道的大佬告知). 

经过我的一番调试, 在 echarts 的源码中找到了纵坐标自动计算坐标轴分割段数相关的算法函数, 下面直接贴源码:

// echarts/lib/scale/helper.js line42 - line74 
/**
 * @param {Array.<number>} extent Both extent[0] and extent[1] should be valid number.
 *                                Should be extent[0] < extent[1].
 * @param {number} splitNumber splitNumber should be >= 1.
 * @param {number} [minInterval]
 * @param {number} [maxInterval]
 * @return {Object} {interval, intervalPrecision, niceTickExtent}
 */

function intervalScaleNiceTicks(extent, splitNumber, minInterval, maxInterval) {
  var result = {};
  var span = extent[1] - extent[0];
  var interval = result.interval = numberUtil.nice(span / splitNumber, true);

  if (minInterval != null && interval < minInterval) {
    interval = result.interval = minInterval;
  }

  if (maxInterval != null && interval > maxInterval) {
    interval = result.interval = maxInterval;
  } // Tow more digital for tick.


  var precision = result.intervalPrecision = getIntervalPrecision(interval); // Niced extent inside original extent

  var niceTickExtent = result.niceTickExtent = [roundNumber(Math.ceil(extent[0] / interval) * interval, precision), roundNumber(Math.floor(extent[1] / interval) * interval, precision)];
  fixExtent(niceTickExtent, extent);
  return result;
}

看到在第 14 行(源码58行)调用的 numberUtil.nice() 方法返回纵坐标分割线的刻度间距, 看看 numberUtil.nice() 方法的源码:

// echarts/lib/util/number.js line408 - line458
/**
 * find a “nice” number approximately equal to x. Round the number if round = true,
 * take ceiling if round = false. The primary observation is that the “nicest”
 * numbers in decimal are 1, 2, and 5, and all power-of-ten multiples of these numbers.
 *
 * See "Nice Numbers for Graph Labels" of Graphic Gems.
 *
 * @param  {number} val Non-negative value.
 * @param  {boolean} round
 * @return {number}
 */


function nice(val, round) {
  var exponent = Math.floor(Math.log(val) / Math.LN10);
  var exp10 = Math.pow(10, exponent);
  var f = val / exp10; // 1 <= f < 10

  var nf;

  if (round) {
    if (f < 1.5) {
      nf = 1;
    } else if (f < 2.5) {
      nf = 2;
    } else if (f < 4) {
      nf = 3;
    } else if (f < 7) {
      nf = 5;
    } else {
      nf = 10;
    }
  } else {
    if (f < 1) {
      nf = 1;
    } else if (f < 2) {
      nf = 2;
    } else if (f < 3) {
      nf = 3;
    } else if (f < 5) {
      nf = 5;
    } else {
      nf = 10;
    }
  }

  val = nf * exp10; // Fix 3 * 0.1 === 0.30000000000000004 issue (see IEEE 754).
  // 20 is the uppper bound of toFixed.

  return exponent >= -20 ? +val.toFixed(exponent < 0 ? -exponent : 0) : val;
}

相关参数源码里已经非常详细地注释了, 此处不做过多解释 (在此赞一下 echarts 的开发团队, 给我们这些菜鸟码农做了一个好榜样👍, 代码注释该写还是要写的)

经过改动, 写了以下函数对 max 值做进一步处理:

/**
 * @param span series 中最大值与最小值的差值
 * @param splitNumber 坐标轴分割段数
 * @param round 折线图中需要传 true
 * @returns {number} 处理后 max 的最后结果
 */
function nice(span, splitNumber, round) {
  let val = span / splitNumber
  var exponent = Math.floor(Math.log(val) / Math.LN10);
  var exp10 = Math.pow(10, exponent);
  var f = val / exp10; // 1 <= f < 10

  var nf;

  if (round) {
    if (f < 1.5) {
      nf = 1;
    } else if (f < 2.5) {
      nf = 2;
    } else if (f < 4) {
      nf = 3;
    } else if (f < 7) {
      nf = 5;
    } else {
      nf = 10;
    }
  } else {
    if (f < 1) {
      nf = 1;
    } else if (f < 2) {
      nf = 2;
    } else if (f < 3) {
      nf = 3;
    } else if (f < 5) {
      nf = 5;
    } else {
      nf = 10;
    }
  }

  val = nf * exp10; // Fix 3 * 0.1 === 0.30000000000000004 issue (see IEEE 754).
  // 20 is the uppper bound of toFixed.

  const step = exponent >= -20 ? +val.toFixed(exponent < 0 ? -exponent : 0) : val;

  let result
  for(let i = splitNumber - 3; i < splitNumber + 3; i++) {
    result = step * i
    if (result > span) break
  }

  return result
}

因工作繁忙, 未对以上代码做过优化. 可以根据自己的实际业务需求, 结合 echarts 的 api 对以上代码进行优化, 以适应自己的项目

使用时直接在项目里调一下修改后的方法就可以了:

{
    yAxis: {
        max: extent => {
            const max = extent.max > goal ? extent.max : goal
            return nice(max, 5, true)
        }
    },
    series: {
        markLine: {
            data: [{
                yAxis: goal
            }]
        }
    }
}

完整代码就不贴了, 没什么难度。如果还有其他问题,欢迎大家在评论区留言、讨论。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值