问题

笔者最近在做图表需求,遇到一个这样的问题,图表的y轴数据处理得不够好看,如下:



图表的数据为1005,1988,2500,3902,5530,很显然这个处理数据的结果不尽人意。

方案

既然有这个问题,那么就需要一个新的处理方案,平时我们在使用Excel等工具制作的折线图,会发现上面的数据其实是处理过的了,如下:


对比可知,为了让图表的数据规整,需要保证间隔的规整,如50,500等数字的倍数,其次是确定起点,只要确定好起点,通过间隔的累加即可。所以,对于这个方案有两个需要处理的,那就是确定起点min和找间隔interval。

制定需求

对于笔者而言,需求不仅包含数据的规整,还有小数位数的格式化,其中涉及到单位的转换,如初始值给了10345,21200,29001,y轴格式化为1.0万,2.0万,3.0万,那么需要对小数位数做处理,使得最后结果为1万,2万,3万。所以对于笔者而言,找出间隔之后,还需要对最终结果的小数位数做去0操作。

步骤

1. 初始值

首先,需要找出数据集的最小值min,最大值max,这里笔者给出的数据为min = 47769543,max = 54961473,最多需要保留的小数位数decimal=1。还包含以下需要用到的方法:

方法名

作用

formatDataByRanges

对数据进行单位转换处理,如47769534做“万”处理则为 4776.9534

2. 计算过程

  1. 确定起点
    首先计算range = max - min = 7191930,接着对range进行formatDataByRanges处理为719.1930,然后originInterval = range/4 = 179.798,这时候的originInterval就是没有处理过的间距值,接下来就是通过range找出规整的间距interval,首先通过range产生一组规整间距值为1000 500 200 100, 产生的伪代码如下:
export function genIntervalList(originInterval: number) {
    let base = genPowNum(originInterval)
    return [10 * base, 5 * base, 2 * base, base]
}

// 获取数字的十的幂次方的位数
//  如 90 则为 1
 // 如 0.01则为 -2
function getPowBit(number) {
    return Math.floor(Math.log10(data))
}  

/**
 * 生成十的幂次方
 * @param data
 */
export function genPowNum(data: number) {
    let bit = getPowBit(data)
    return Math.pow(10, bit)
}

复制代码

接着从第二个数开始遍历1000 500 200 100, 当找到大于originInterval的数时则停止,获取当前点的上一个数为我们所需的interval,如179.798 > 100, 那么interval就为200。然后通过interval对min做处理,当然这里的min也经过处理为4776.9543,处理过程如下:

function handleMin(min, interval) {
    if(min < interval) {
        return 0
    } else {
         let baseNum = genPowNum(interval) * 10
       // 去掉最小值不需要比较的位数 
       // 如min为4776.9543 interval为200 那么formatMin = 4000
       // 如min为0.073 interval为0.005 那么formatMin = 0.07
         let formatMin = Math.floor(min / baseNum) * baseNum
         let minRange = min - formatMin
         
          // 找出适合的interval 这里为500
        // 所以最后的min = 4000 + 500 = 4500
         return formatMin + this.findMinInterval(minRange, interval)
    }
}

  function findMinInterval(minRange: number, interval: number) {
        let factorList = genIntervalList(interval)
        for (let i = 1; i < factorList.length; i++) {
            if (minRange >= factorList[i]) {
                return factorList[i]
            }
        }
        return 0
    }
复制代码

经过上述的处理,可得最小值为4500

  1. 找间距
    由于经过步骤1确定了min,min已经改变了,所以range需要重新计算,interval也需要重新计算。计算方式参考步骤1的找间距,计算可得interval为500。
  2. 最终计算
    经过步骤1,2可以得到min和interval,如果此时min< interval,那么需要令min = 0,然后重新执行步骤2。这样就可以得到最后的min,interval,接着需要计算最后需要保留的小数位数,如下:
function getDecimal(interval, min) {
   if(interval < 1) {
       return  Math.abs(getPowBit(interval))
   } else {
       // 如果间距的range和max相等 则不需要保留小数位数
       // 如interval是1500万 max是4000万 range都为万 不需要保留小数
       if(interval.range = max.range) {
           return 0
       } else {
             // 当range不相等的情况 可知min一定大于formatInterval
             // 如min为121.1万 interval为2000 这时候对2000做range的处理
             // 则可得到最大需要保留的位数
           return Math.abs(getPowBit(formatInterval / formatMinData.range))
       }
   }
}
复制代码
  1. 最后结果
    最后可得y轴的数据为4500万 5000万 5500万 6000万 6500万

总结

对于这个方案,在于确定min和interval,对于interval而言,则可以自己灵活定义规整的间距,如520取的是550,而不是1000,这样的好处是可以让图表的走势更加明显。