Redistribute a range of percentages according to a minimum value, while keeping relative proportions

FIDDLE HERE: http://jsfiddle.net/jakelauer/5S8t8/

What I need to do

  • Looking for good physics/maths/algorithm learning resources for JavaScript
  • Multiple sequential string replacement between two indices
  • Algorihtm / asymptotic performance of setTimeout and clearTimeout
  • javascript slider weighted values
  • Best algorithm to highlight a list of given words in an HTML file
  • Reference material for calculating response times
  • Loop through a range of percentages, which add up to 100%, and set any values that fall below a minimum threshold to a certain percentage, then redistribute the remaining percentages so that they are still the same relative to each other, so that the whole range still equals 100%.

    Why?

    I’m making a pie chart, and I need each value to show on the pie chart, even if it’s a zero-value, so that it’s still clickable. That means I need to set values below a certain threshold to a higher value, but I don’t want to lose the relative proportions of the higher values, and they can’t go over 100%

    Details

    Say I have a range of values:

    var range = [190, 2453, 234, 14, 1244, 532, 0, 3, 1999];
    

    and I convert these to percentage values, such that each value in the range becomes its percentage of the sum of the whole range, resulting in:

    [0.02849002849002849, 0.36782126255810466, 0.03508771929824561, 0.002099265257159994, 0.18653471285050233, 0.07977207977207977, 0, 0.000449842555105713, 0.2997450892187734]
    

    I want to loop through these percentages and set them to a minimum of 0.02, or 2%. Here is the code I have so far:

    var range = [190, 2453, 234, 14, 1244, 532, 0, 3, 1999];
    var minimumPercent = 0.02;
    
    function distributeRangeGivenMinimum(range, min) {
        var sum = 0;
        for (var i = 0; i < range.length; i++) {
            sum += range[i];
        }
    
        var percents = [];
        for (var i = 0; i < range.length; i++) {
            percents.push(range[i] / sum);
        }
    
        var amtToSubtract = 0;
        var timesUnderMin = 0;
        for (var i = 0; i < range.length; i++) {
            if (percents[i] < min) {
                amtToSubtract += (min - percents[i]);
                timesUnderMin++;
                percents[i] = min;
            }
        }
    
        var timesOverMin = percents.length - timesUnderMin;
        if (timesOverMin > 0) {
            for (var i = 0; i < range.length; i++) {
                if (percents[i] > min) {
                    percents[i] = percents[i] - (amtToSubtract / timesOverMin);
                }
            }
        }
    
        return percents;
    }
    

    The problem with the above function is that, while it does accomplish the redistribution, it fails if any of the values are just above the minimum (e.g. if one of the values is 0.0201 and the minimum is 0.02, or something), they end up below the minimum after the redistribution, and I’m not sure how to solve that.

  • Algorithm for finding overlapping events/times
  • Color Logic Algorithm
  • Find the number in an array that is closest to a given number
  • Javascript Algorithm Practice Removing Negatives From Array
  • Implementing copied and pasted code/text detection?
  • Math/ Algorithm/ JS: How to determine if 2+ rectangles intersect, given the TopLeft(x0, y0) and Bottom-Right(x1, y1) of each rectangle
  • One Solution collect form web for “Redistribute a range of percentages according to a minimum value, while keeping relative proportions”

    Try the following (new section is marked by comments):

    var range = [190, 2453, 234, 14, 1244, 532, 0, 3, 1999];
    var minimumPercent = 0.02;
    
    function distributeRangeGivenMinimum(range, min) {
        var sum = 0;
        for (var i = 0; i < range.length; i++) {
            sum += range[i];
        }
    
        var percents = [];
        for (var i = 0; i < range.length; i++) {
            percents.push(range[i] / sum);
        }
    
        var amtToSubtract = 0;
        var timesUnderMin = 0;
        for (var i = 0; i < range.length; i++) {
            if (percents[i] < min) {
                amtToSubtract += (min - percents[i]);
                timesUnderMin++;
                percents[i] = min;
            }
        }
    
        var timesOverMin = percents.length - timesUnderMin;
        // begin new section
        do {
            var prevTimesOverMin = timesOverMin;
            var distSubAmt = amtToSubtract / timesOverMin;
            for (var i = 0; i < range.length; i++) {
                if (percents[i] > min && percents[i] - distSubAmt < min) {
                    amtToSubtract -= (percents[i] - min);
                    timesOverMin--;
                    percents[i] = min;
                }
            }
        } while (timesOverMin != prevTimesOverMin);
        // end new section
    
        if (timesOverMin > 0) {
            for (var i = 0; i < range.length; i++) {
                if (percents[i] > min) {
                    percents[i] = percents[i] - (amtToSubtract / timesOverMin);
                }
            }
        }
    
        return percents;
    }