最终结果呈现
怎么保证所有占比之和等于100%,什么是最大余额法?
echarts的饼图的百度通过传入数据自动算出每个项在总数当中所占的比例大小,且保证所有的比率相加等于100%,那他是如何计算百分比的呢。通过阅读源码我们可以得知他使用最大余额法,那什么是最大余额法呢?
概念说明
那什么是最大余额方法呢?以下概念摘自维基百科,有一点晦涩难懂,下一节通过具体例子分析一下就好懂多了最大余额方法(英文:Largest Remainder Method)又称数额制,是比例代表制投票制度下,一种议席分配的方法,相对于最高均数方法。
透过最大余额方法,候选人须以名单参选,每份名单的人数最多可达至相关选区内的议席数目。候选人在名单内按优先次序排列。选民投票给一份名单,而不是个别候选人。投票结束后,把有效选票除以数额(quota,见下)。一份名单每取得数额1倍的票数,便能获分配一个议席。每份名单的候选人按原先订立的顺序当选。
如此类推、将议席分配至每份名单的余额,均比数额为低的时候,则从最大余额者顺序分配余下议席;最大余额方法因而得名。
具体例子
假设选举投票人次100,000,分配10个议席。选举结果:
黑尔数额为黑尔数额为 100000/10 = 10000张选票,即每张名单每获得10,000张选票,便能首先得到1个议席:
因此,名单丙、丁、戊各得1席,名单己得4席。余下3席,则对比各个余额。其中名单乙、戊、己的余额最大,因此分别获选其余3席。
换言之,在最大余额方法之下,名单乙、丙、丁各得1席,名单戊得2席,名单己得5席。
源码解析
看了以上的图应该对最大余额法有一点的了解了。那我们来看看源码是如何通过最大余额法来进行百分比分配的。
以下通过一组例子数据来
数据值列表:[2, 4, 3]
精度:2(代表百分数的值最多保留2位小数)
期望结果: [ 22.22, 44.45, 33.33 ]
/**
*
* 给定一个精度值,计算某一项在一串数据中占据的百分比,确保百分比总和是1(100%)
* 使用最大余额法
* @param {Array.} valueList a list of all data 一列数据
* @param {number} idx index of the data to be processed in valueList 索引值(数组下标)
* @param {number} precision integer number showing digits of precision 精度值
* @return {number} percent ranging from 0 to 100 返回百分比从0到100
*/
function getPercentWithPrecision (valueList, idx, precision) {
if (!valueList[idx]) {
return 0
}
var sum = valueList.reduce(function (acc, val) {
return acc + (isNaN(val) ?0 : val)
}, 0)
if (sum ===0) {
return 0
}
console.log('sum', sum)
// sum 9
var digits = Math.pow(10, precision)// digits 100
console.log('digits', digits)
var votesPerQuota = valueList.map(function (val) {
return (isNaN(val) ?0 : val) / sum * digits *100 // 扩大比例,这样可以确保整数部分是已经确定的议席配额,小数部分是余额
})
console.log('votesPerQuota', votesPerQuota)
// votesPerQuota [ 2222.222222222222, 4444.444444444444, 3333.333333333333 ] 每一个项获得的议席配额,整数部分是已经确定的议席配额,小数部分是余额
var targetSeats = digits *100 // targetSeats 10000 全部的议席
console.log('targetSeats', targetSeats)
var seats = votesPerQuota.map(function (votes) {
// Assign automatic seats.
return Math.floor(votes)
})
console.log('seats', seats)
// seats [ 2222, 4444, 3333 ] 获取配额的整数部分
var currentSum = seats.reduce(function (acc, val) {
return acc + val
}, 0)
console.log('currentSum', currentSum)
// 9999 表示已经配额了9999个议席,还剩下一个议席
var remainder = votesPerQuota.map(function (votes, idx) {
return votes - seats[idx]
})
console.log('remainder', remainder)
// [ 0.2222222222221717, 0.4444444444443434, 0.33333333333303017 ]得到每一项的余额
// Has remainding votes. 如果还有剩余的坐席就继续分配
while (currentSum < targetSeats) {
// Find next largest remainder. 找到下一个最大的余额
var max = Number.NEGATIVE_INFINITY
var maxId =null
for (var i =0, len = remainder.length; i < len; ++i) {
if (remainder[i] > max) {
max = remainder[i]
maxId = i
}
}
// max: 0.4444444444443434, maxId 1
// Add a vote to max remainder.
++seats[maxId]// 第二项,即4的占比的坐席增加1
remainder[maxId] =0
++currentSum// 总的已分配的坐席数也加1
}
return seats[idx] / digits
}