饼图各项百分比和不为100%的问题(最大余额法)

1,312 阅读2分钟

场景

在使用echarts的时候,将接口拿到的数据自定义去做一些按比例显示,通常是通过将各数相加然后除每一个数再四舍五入再乘以100%而得到百分比, 但是这样得出的数据想加可能会出现不等于100%的情况,如下

比如接口拿到的数据为[1, 1, 5] => [14.29%, 14.29%, 71.43%]
14.29 + 14.29 + 71.43 === 100.01 !== 100

解决方案

echarts本身没有该问题是使用了一个叫做最大余额的算法,所以可以参考该算法,自定义去处理百分比数据

/**
 * 最大余额法(用于和echarts数据百分比一致,加上所有百分比应为100)
 * @param valueList 数组数据
 * @param idx 数组下标
 * @param precision 精度
 * @returns {number}
 */
export function getPercentValue(valueList, idx, precision) {
  // 判断是否为空
  if (!valueList[idx]) {
    return 0
  }

  // 数组数据求和,若和为0则返回0
  const sum = valueList.reduce((a, c) => {
    return (a += isNaN(c) ? 0 : c)
  }, 0)
  if (sum === 0) {
    return 0
  }

  // 10的二次幂是100, 用于计算精度
  const digits = Math.pow(10, precision)
  // 扩大比例100
  const votesPerQuota = valueList.map(val => {
    return ((isNaN(val) ? 0 : val) / sum) * digits * 100
  })

  // 总数,扩大比例意味总数要扩大
  const targetSeats = digits * 100
  // 再向下取值,组成数组
  const seats = votesPerQuota.map(votes => {
    return Math.floor(votes)
  })

  // 在新计算合计,用于判断与总数量是否相同,相同则占比会100%
  let currentSum = seats.reduce((a, c) => {
    return a + c
  }, 0)

  // 余数部分的数组:原先数组减去向下取值的数组,得到余数部分的数组
  const remainder = votesPerQuota.map((votes, idx) => {
    return votes - seats[idx]
  })

  while (currentSum < targetSeats) {
    let max = Number.NEGATIVE_INFINITY
    let maxId = null
    for (let i = 0; i < remainder.length; i++) {
      if (remainder[i] > max) {
        max = remainder[i]
        maxId = i
      }
    }

    // 对最大余额项加1
    ++seats[maxId]
    // 已经对最大余数加1,则下次判断就跳过该余额数
    remainder[maxId] = 0
    // 总的也要加1,用来结束循环
    ++currentSum
  }

  return seats[idx] / digits
}

const arr = [1, 1, 5]
console.log(getPercentValue(arr, 0, 2)) // 14.29
console.log(getPercentValue(arr, 1, 2)) // 14.28
console.log(getPercentValue(arr, 2, 2)) // 71.43
// 14.29 + 14.28 + 71.43 = 100