Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <div id="piechart-controls">
    <canvas id="piechart" width="400" height="400">Your browser is too old!</canvas>
    <br>
    <table id="proportions-table"></table>
    <br>


</div>
              
            
!

CSS

              
                
* {
    font-family: Arial, sans-serif;
}

#piechart-controls {
    text-align: center;

}

#piechart-instructions {
    display: inline-block;
    width: 600px;
}


table {
    border-collapse: collapse;

    -webkit-touch-callout: none; /* iOS Safari */
    -webkit-user-select: none; /* Safari */
    -khtml-user-select: none; /* Konqueror HTML */
    -moz-user-select: none; /* Firefox */
    -ms-user-select: none; /* Internet Explorer/Edge */
    user-select: none;

    display: inline-block;

}

td, th {
    border: 1px solid lightgray;
    padding: 10px;
    margin: 0;

}

.adjust-button {
    width: 20px;
    height: 20px;
    font-size: 20px;
    line-height: 20px;
    text-align: center;
    display: inline-block;
    border: 1px solid grey;
    border-radius: 5px;
    margin: 1px;
}

var {
    width: 40px;
    display: inline-block;
}
              
            
!

JS

              
                /**
 * Created by james on 23/02/2017.
 */

(function() {
  var extend = function(out) {
    out = out || {};

    for (var i = 1; i < arguments.length; i++) {
      if (!arguments[i]) continue;

      for (var key in arguments[i]) {
        if (arguments[i].hasOwnProperty(key)) out[key] = arguments[i][key];
      }
    }

    return out;
  };

  var DraggablePiechart = function(setup) {
    var piechart = this;

    setup = extend({}, this.defaults, setup);

    this.canvas = setup.canvas;
    this.context = setup.canvas.getContext("2d");

    if (!this.context) {
      console.log("Error: DraggablePiechart needs an html5 canvas.");
      return;
    }

    if (setup.proportions) {
      this.data = generateDataFromProportions(setup.proportions);
    } else if (setup.data) {
      this.data = setup.data;
    }

    this.draggedPie = null;
    this.hoveredIndex = -1;
    this.radius = setup.radius;
    this.collapsing = setup.collapsing;
    this.minAngle = setup.minAngle;
    this.drawSegment = setup.drawSegment;
    this.drawNode = setup.drawNode;
    this.onchange = setup.onchange;

    // Bind appropriate events
    if (is_touch_device()) {
      this.canvas.addEventListener("touchstart", touchStart);
      this.canvas.addEventListener("touchmove", touchMove);
      document.addEventListener("touchend", touchEnd);
    } else {
      this.canvas.addEventListener("mousedown", touchStart);
      this.canvas.addEventListener("mousemove", touchMove);
      document.addEventListener("mouseup", touchEnd);
    }

    this.draw();

    function touchStart(event) {
      piechart.draggedPie = piechart.getTarget(getMouseLocation(event));
      if (piechart.draggedPie) {
        piechart.hoveredIndex = piechart.draggedPie.index;
      }
    }

    function touchEnd() {
      if (piechart.draggedPie) {
        piechart.draggedPie = null;
        piechart.draw();
      }
    }

    function touchMove(event) {
      var dragLocation = getMouseLocation(event);

      if (!piechart.draggedPie) {
        var hoveredTarget = piechart.getTarget(dragLocation);
        if (hoveredTarget) {
          piechart.hoveredIndex = hoveredTarget.index;
          piechart.draw();
        } else if (piechart.hoveredIndex != -1) {
          piechart.hoveredIndex = -1;
          piechart.draw();
        }
        return;
      }

      var draggedPie = piechart.draggedPie;

      var dx = dragLocation.x - draggedPie.centerX;
      var dy = dragLocation.y - draggedPie.centerY;

      // Get angle of grabbed target from centre of pie
      var newAngle = Math.atan2(dy, dx) - draggedPie.angleOffset;

      piechart.shiftSelectedAngle(newAngle);
      piechart.draw();
    }

    function getMouseLocation(evt) {
      var rect = piechart.canvas.getBoundingClientRect();

      if (evt.clientX) {
        return {
          x: evt.clientX - rect.left,
          y: evt.clientY - rect.top
        };
      } else {
        return {
          x: evt.targetTouches[0].clientX - rect.left,
          y: evt.targetTouches[0].clientY - rect.top
        };
      }
    }

    /*
     * Generates angle data from proportions (array of objects with proportion, format
     */
    function generateDataFromProportions(proportions) {
      // sum of proportions
      var total = proportions.reduce(function(a, v) {
        return a + v.proportion;
      }, 0);

      // begin at 0
      var currentAngle = 0;

      // use the proportions to reconstruct angles
      return proportions.map(function(v, i) {
        var arcSize = (TAU * v.proportion) / total;
        var data = {
          angle: currentAngle,
          format: v.format,
          collapsed: arcSize <= 0
        };
        currentAngle = normaliseAngle(currentAngle + arcSize);
        return data;
      });
    }
  };

  /*
   * Move angle specified by index: i, by amount: angle in rads
   */
  DraggablePiechart.prototype.moveAngle = function(i, amount) {
    if (this.data[i].collapsed && amount < 0) {
      this.setCollapsed(i, false);
      return;
    }

    var geometry = this.getGeometry();
    this.draggedPie = {
      index: i,
      angleOffset: 0,
      centerX: geometry.centerX,
      centerY: geometry.centerY,
      startingAngles: this.data.map(function(v) {
        return v.angle;
      }),
      collapsed: this.data.map(function(v) {
        return v.collapsed;
      }),
      angleDragDistance: 0
    };

    this.shiftSelectedAngle(this.data[i].angle + amount);
    this.draggedPie = null;
    this.draw();
  };

  /*
   * Gets percentage of indexed slice
   */
  DraggablePiechart.prototype.getSliceSizePercentage = function(index) {
    var visibleSegments = this.getVisibleSegments();

    for (var i = 0; i < visibleSegments.length; i += 1) {
      if (visibleSegments[i].index == index) {
        return (100 * visibleSegments[i].arcSize) / TAU;
      }
    }
    return 0;
  };

  /*
   * Gets all percentages for each slice
   */
  DraggablePiechart.prototype.getAllSliceSizePercentages = function() {
    var visibleSegments = this.getVisibleSegments();
    var percentages = [];
    for (var i = 0; i < this.data.length; i += 1) {
      if (this.data[i].collapsed) {
        percentages[i] = 0;
      } else {
        for (var j = 0; j < visibleSegments.length; j += 1) {
          if (visibleSegments[j].index == i) {
            percentages[i] = (100 * visibleSegments[j].arcSize) / TAU;
          }
        }
      }
    }

    return percentages;
  };

  /*
   * Gets the geometry of the pie chart in the canvas
   */
  DraggablePiechart.prototype.getGeometry = function() {
    var centerX = Math.floor(this.canvas.width / 2);
    var centerY = Math.floor(this.canvas.height / 2);
    return {
      centerX: centerX,
      centerY: centerY,
      radius: Math.min(centerX, centerY) * this.radius
    };
  };

  /*
   * Returns a segment to drag if given a close enough location
   */
  DraggablePiechart.prototype.getTarget = function(targetLocation) {
    var geometry = this.getGeometry();
    var startingAngles = [];
    var collapsed = [];

    var closest = {
      index: -1,
      distance: 9999999,
      angle: null
    };

    for (var i = 0; i < this.data.length; i += 1) {
      startingAngles.push(this.data[i].angle);
      collapsed.push(this.data[i].collapsed);

      if (this.data[i].collapsed) {
        continue;
      }

      var dx = targetLocation.x - geometry.centerX;
      var dy = targetLocation.y - geometry.centerY;
      var trueGrabbedAngle = Math.atan2(dy, dx);

      var distance = Math.abs(
        smallestSignedAngleBetween(trueGrabbedAngle, this.data[i].angle)
      );

      if (distance < closest.distance) {
        closest.index = i;
        closest.distance = distance;
        closest.angle = trueGrabbedAngle;
      }
    }

    if (closest.distance < 0.1) {
      return {
        index: closest.index,
        angleOffset: smallestSignedAngleBetween(
          closest.angle,
          startingAngles[closest.index]
        ),
        centerX: geometry.centerX,
        centerY: geometry.centerY,
        startingAngles: startingAngles,
        collapsed: collapsed,
        angleDragDistance: 0
      };
    } else {
      return null;
    }
  };

  /*
   * Sets segments collapsed or uncollapsed
   */
  DraggablePiechart.prototype.setCollapsed = function(index, collapsed) {
    // Flag to set position of previously collapsed to new location
    var setNewPos = this.data[index].collapsed && !collapsed;

    this.data[index].collapsed = collapsed;

    var visibleSegments = this.getVisibleSegments();

    // Shift other segments along to make space if necessary
    for (var i = 0; i < visibleSegments.length; i += 1) {
      // Start at this segment
      if (visibleSegments[i].index == index) {
        //Set new position
        if (setNewPos) {
          var nextSegment = visibleSegments[mod(i + 1, visibleSegments.length)];
          this.data[index].angle = nextSegment.angle - this.minAngle;
        }

        for (var j = 0; j < visibleSegments.length - 1; j += 1) {
          var currentSegment =
            visibleSegments[mod(1 + i - j, visibleSegments.length)];
          var nextAlongSegment =
            visibleSegments[mod(i - j, visibleSegments.length)];

          var angleBetween = Math.abs(
            smallestSignedAngleBetween(
              this.data[currentSegment.index].angle,
              this.data[nextAlongSegment.index].angle
            )
          );

          if (angleBetween < this.minAngle) {
            this.data[nextAlongSegment.index].angle = normaliseAngle(
              this.data[currentSegment.index].angle - this.minAngle
            );
          }
        }
        break;
      }
    }

    this.draw();
  };

  /*
   * Returns visible segments
   */
  DraggablePiechart.prototype.getVisibleSegments = function() {
    var piechart = this;
    // Collect data for visible segments
    var visibleSegments = [];
    for (var i = 0; i < piechart.data.length; i += 1) {
      if (!piechart.data[i].collapsed) {
        var startingAngle = piechart.data[i].angle;

        // Get arcSize
        var foundNextAngle = false;
        for (var j = 1; j < piechart.data.length; j += 1) {
          var nextAngleIndex = (i + j) % piechart.data.length;

          if (!piechart.data[nextAngleIndex].collapsed) {
            var arcSize = piechart.data[nextAngleIndex].angle - startingAngle;
            if (arcSize <= 0) {
              arcSize += TAU;
            }

            visibleSegments.push({
              arcSize: arcSize,
              angle: startingAngle,
              format: piechart.data[i].format,
              index: i
            });

            foundNextAngle = true;
            break;
          }
        }

        // Only one segment
        if (!foundNextAngle) {
          visibleSegments.push({
            arcSize: TAU,
            angle: startingAngle,
            format: piechart.data[i].format,
            index: i
          });
          break;
        }
      }
    }
    return visibleSegments;
  };

  /*
   * Returns invisible segments
   */
  DraggablePiechart.prototype.getInvisibleSegments = function() {
    var piechart = this;
    // Collect data for visible segments
    var invisibleSegments = [];
    for (var i = 0; i < piechart.data.length; i += 1) {
      if (piechart.data[i].collapsed) {
        invisibleSegments.push({
          index: i,
          format: piechart.data[i].format
        });
      }
    }

    return invisibleSegments;
  };

  /*
   * Draws the piechart
   */
  DraggablePiechart.prototype.draw = function() {
    var piechart = this;
    var context = piechart.context;
    var canvas = piechart.canvas;
    context.clearRect(0, 0, canvas.width, canvas.height);

    var geometry = this.getGeometry();

    var visibleSegments = this.getVisibleSegments();

    // Flags to get arc sizes and index of largest arc, for drawing order
    var largestArcSize = 0;
    var indexLargestArcSize = -1;

    // Get the largeset arcsize
    for (var i = 0; i < visibleSegments.length; i += 1) {
      if (visibleSegments[i].arcSize > largestArcSize) {
        largestArcSize = visibleSegments[i].arcSize;
        indexLargestArcSize = i;
      }
    }

    // Need to draw in correct order
    for (i = 0; i < visibleSegments.length; i += 1) {
      // Start with one *after* largest
      var index = mod(i + indexLargestArcSize + 1, visibleSegments.length);
      piechart.drawSegment(
        context,
        piechart,
        geometry.centerX,
        geometry.centerY,
        geometry.radius,
        visibleSegments[index].angle,
        visibleSegments[index].arcSize,
        visibleSegments[index].format,
        false
      );
    }

    // Now draw invisible segments
    var invisibleSegments = this.getInvisibleSegments();
    for (i = 0; i < invisibleSegments.length; i += 1) {
      piechart.drawSegment(
        context,
        piechart,
        geometry.centerX,
        geometry.centerY,
        geometry.radius,
        0,
        0,
        invisibleSegments[i].format,
        true
      );
    }

    // Finally draw drag nodes on top (order not important)
    for (i = 0; i < visibleSegments.length; i += 1) {
      var location = polarToCartesian(
        visibleSegments[i].angle,
        geometry.radius
      );
      piechart.drawNode(
        context,
        piechart,
        location.x,
        location.y,
        geometry.centerX,
        geometry.centerY,
        i == piechart.hoveredIndex
      );
    }

    piechart.onchange(piechart);
  };

  /*
   * *INTERNAL USE ONLY*
   * Moves the selected angle to a new angle
   */
  DraggablePiechart.prototype.shiftSelectedAngle = function(newAngle) {
    var piechart = this;
    if (!piechart.draggedPie) {
      return;
    }
    var draggedPie = piechart.draggedPie;

    // Get starting angle of the target
    var startingAngle = draggedPie.startingAngles[draggedPie.index];

    // Get previous angle of the target
    var previousAngle = piechart.data[draggedPie.index].angle;

    // Get diff from grabbed target start (as -pi to +pi)
    var angleDragDistance = smallestSignedAngleBetween(newAngle, startingAngle);

    // Get previous diff
    var previousDragDistance = draggedPie.angleDragDistance;

    // Determines whether we go clockwise or anticlockwise
    var rotationDirection = previousDragDistance > 0 ? 1 : -1;

    // Reverse the direction if we have done over 180 in either direction
    var sameDirection = previousDragDistance > 0 == angleDragDistance > 0;
    var greaterThanHalf =
      Math.abs(previousDragDistance - angleDragDistance) > Math.PI;

    if (greaterThanHalf && !sameDirection) {
      // Reverse the angle
      angleDragDistance =
        (TAU - Math.abs(angleDragDistance)) * rotationDirection;
    } else {
      rotationDirection = angleDragDistance > 0 ? 1 : -1;
    }

    draggedPie.angleDragDistance = angleDragDistance;

    // Set the new angle:
    piechart.data[draggedPie.index].angle = normaliseAngle(
      startingAngle + angleDragDistance
    );

    // Reset Collapse
    piechart.data[draggedPie.index].collapsed =
      draggedPie.collapsed[draggedPie.index];

    // Search other angles
    var shifting = true;
    var collapsed = false;
    var minAngle = piechart.minAngle;
    var numberOfAnglesShifted = 0;

    for (var i = 1; i < piechart.data.length; i += 1) {
      // Index to test each slice in order
      var index = mod(
        parseInt(draggedPie.index) + i * rotationDirection,
        piechart.data.length
      );

      // Get angle from target start to this angle
      var startingAngleToNonDragged = smallestSignedAngleBetween(
        draggedPie.startingAngles[index],
        startingAngle
      );

      // If angle is in the wrong direction then it should actually be OVER 180
      if (startingAngleToNonDragged * rotationDirection < 0) {
        startingAngleToNonDragged =
          (startingAngleToNonDragged * rotationDirection + TAU) *
          rotationDirection;
      }

      if (piechart.collapsing) {
        // *Collapsing behaviour* when smallest angle encountered

        // Reset collapse
        piechart.data[index].collapsed = draggedPie.collapsed[index];

        var checkForSnap = !collapsed && !piechart.data[index].collapsed;

        // Snap node to collapse, and prevent going any further
        if (
          checkForSnap &&
          startingAngleToNonDragged > 0 &&
          angleDragDistance > startingAngleToNonDragged - minAngle
        ) {
          piechart.data[draggedPie.index].angle = piechart.data[index].angle;
          piechart.data[draggedPie.index].collapsed = true;
          collapsed = true;
        } else if (
          checkForSnap &&
          startingAngleToNonDragged < 0 &&
          angleDragDistance < startingAngleToNonDragged + minAngle
        ) {
          piechart.data[draggedPie.index].angle = piechart.data[index].angle;
          piechart.data[index].collapsed = true;
          collapsed = true;
        } else {
          piechart.data[index].angle = draggedPie.startingAngles[index];
        }
      } else {
        // *Shifting behaviour* when smallest angle encountered

        // Shift all other angles along
        var shift = (numberOfAnglesShifted + 1) * minAngle;

        if (
          shifting &&
          startingAngleToNonDragged > 0 &&
          angleDragDistance > startingAngleToNonDragged - shift
        ) {
          piechart.data[index].angle = normaliseAngle(
            draggedPie.startingAngles[index] +
              (angleDragDistance - startingAngleToNonDragged) +
              shift
          );
          numberOfAnglesShifted += 1;
        } else if (
          shifting &&
          startingAngleToNonDragged < 0 &&
          angleDragDistance < startingAngleToNonDragged + shift
        ) {
          piechart.data[index].angle = normaliseAngle(
            draggedPie.startingAngles[index] -
              (startingAngleToNonDragged - angleDragDistance) -
              shift
          );
          numberOfAnglesShifted += 1;
        } else {
          shifting = false;
          piechart.data[index].angle = draggedPie.startingAngles[index];
        }
      }

      //console.log(JSON.stringify(piechart.data));
    }
  };

  DraggablePiechart.prototype.defaults = {
    onchange: function(piechart) {},
    radius: 0.9,
    data: [
      {
        angle: -2,
        format: { color: "#2665da", label: "Walking" },
        collapsed: false
      },
      {
        angle: -1,
        format: { color: "#6dd020", label: "Programming" },
        collapsed: false
      },
      {
        angle: 0,
        format: { color: "#f9df18", label: "Chess" },
        collapsed: false
      },
      {
        angle: 1,
        format: { color: "#d42a00", label: "Eating" },
        collapsed: false
      },
      {
        angle: 2,
        format: { color: "#e96400", label: "Sleeping" },
        collapsed: false
      }
    ],
    collapsing: false,
    minAngle: 0.1,

    drawSegment: function(
      context,
      piechart,
      centerX,
      centerY,
      radius,
      startingAngle,
      arcSize,
      format,
      collapsed
    ) {
      if (collapsed) {
        return;
      }

      // Draw coloured segment
      context.save();
      var endingAngle = startingAngle + arcSize;
      context.beginPath();
      context.moveTo(centerX, centerY);
      context.arc(centerX, centerY, radius, startingAngle, endingAngle, false);
      context.closePath();
      context.strokeStyle = "white";
      context.lineWidth = 2;
      context.stroke();

      context.fillStyle = format.color;
      context.fill();
      context.restore();

      // Draw label on top
      context.save();
      context.translate(centerX, centerY);
      context.rotate(startingAngle);

      var fontSize = Math.floor(context.canvas.height / 38);
      var dx = radius - fontSize;
      var dy = centerY / 10;

      context.textAlign = "right";
      context.fillStyle = "white";
      context.font = fontSize + "pt Helvetica";
      context.fillText(format.label, dx, dy);
      context.restore();
    },

    drawNode: function(context, piechart, x, y, centerX, centerY, hover) {
      context.save();
      context.translate(centerX, centerY);
      context.fillStyle = "#ffffff";

      var rad = hover ? 7 : 5;
      context.beginPath();
      context.arc(x, y, rad, 0, TAU, true);
      context.fill();
      context.stroke();
      context.restore();
    }
  };

  window.DraggablePiechart = DraggablePiechart;

  /*
   * Utilities + Constants
   */

  var TAU = Math.PI * 2;

  function degreesToRadians(degrees) {
    return (degrees * Math.PI) / 180;
  }

  function smallestSignedAngleBetween(target, source) {
    return Math.atan2(Math.sin(target - source), Math.cos(target - source));
  }

  function mod(n, m) {
    return ((n % m) + m) % m;
  }

  function is_touch_device() {
    return (
      "ontouchstart" in window || navigator.maxTouchPoints // works on most browsers
    ); // works on IE10/11 and Surface
  }

  function normaliseAngle(angle) {
    return mod(angle + Math.PI, TAU) - Math.PI;
  }

  function polarToCartesian(angle, radius) {
    return {
      x: radius * Math.cos(angle),
      y: radius * Math.sin(angle)
    };
  }
})();






(function(){

    //IE9+ http://youmightnotneedjquery.com/
    function ready(fn) {
        if (document.attachEvent ? document.readyState === "complete" : document.readyState !== "loading"){
            fn();
        } else {
            document.addEventListener('DOMContentLoaded', fn);
        }
    }

    ready(setupPieChart);


    function setupPieChart() {


        var dimensions = knuthfisheryates2(['Phone', 'Email', 'Web chat', 'Social media']);
        var randomProportions = generateRandomProportions(dimensions.length, 0.05);
        var proportions = dimensions.map(function(d,i) { return {
            label: d,
            proportion: randomProportions[i],
            collapsed: false,
            format: {
                label: d.charAt(0).toUpperCase() + d.slice(1) // capitalise first letter
            }
        }});


        var setup = {
            canvas: document.getElementById('piechart'),
            radius: 0.9,
            collapsing: false,
            proportions: proportions,
            drawSegment: drawSegmentOutlineOnly,
            onchange: onPieChartChange
        };

        var newPie = new DraggablePiechart(setup);

        function drawSegmentOutlineOnly(context, piechart, centerX, centerY, radius, startingAngle, arcSize, format, collapsed) {

            if (collapsed) { return; }

            // Draw segment
            context.save();
            var endingAngle = startingAngle + arcSize;
            context.beginPath();
            context.moveTo(centerX, centerY);
            context.arc(centerX, centerY, radius, startingAngle, endingAngle, false);
            context.closePath();

            context.fillStyle = '#f5f5f5';
            context.fill();
            context.stroke();
            context.restore();

            // Draw label on top
            context.save();
            context.translate(centerX, centerY);
            context.rotate(startingAngle);

            var fontSize = Math.floor(context.canvas.height / 38);
            var dx = radius - fontSize;
            var dy = centerY / 10;

            context.textAlign = "right";
            context.font = fontSize + "pt Helvetica";
            context.fillText(format.label, dx, dy);
            context.restore();
        }

        function onPieChartChange(piechart) {

            var table = document.getElementById('proportions-table');
            var percentages = piechart.getAllSliceSizePercentages();

            var labelsRow = '<tr>';
            var propsRow = '<tr>';
            for(var i = 0; i < proportions.length; i += 1) {
                labelsRow += '<th>' + proportions[i].format.label + '</th>';

                var v = '<var>' + percentages[i].toFixed(0) + '%</var>';
                var plus = '<div id="plu-' + dimensions[i] + '" class="adjust-button" data-i="' + i + '" data-d="-1">&#43;</div>';
                var minus = '<div id="min-' + dimensions[i] + '" class="adjust-button" data-i="' + i + '" data-d="1">&#8722;</div>';
                propsRow += '<td>' + v + plus + minus + '</td>';
            }
            labelsRow += '</tr>';
            propsRow += '</tr>';

            table.innerHTML = labelsRow + propsRow;

            var adjust = document.getElementsByClassName("adjust-button");

            function adjustClick(e) {
                var i = this.getAttribute('data-i');
                var d = this.getAttribute('data-d');

                piechart.moveAngle(i, (d * 0.1));
            }

            for (i = 0; i < adjust.length; i++) {
                adjust[i].addEventListener('click', adjustClick);
            }

        }

        /*
         * Generates n proportions with a minimum percentage gap between them
         */
        function generateRandomProportions(n, min) {

            // n random numbers 0 - 1
            var rnd = Array.apply(null, {length: n}).map(function(){ return Math.random(); });

            // sum of numbers
            var rndTotal = rnd.reduce(function(a, v) { return a + v; }, 0);

            // get proportions, then make sure each propoertion is above min
            return validateAndCorrectProportions(rnd.map(function(v) { return v / rndTotal; }), min);


            function validateAndCorrectProportions(proportions, min) {
                var sortedProportions = proportions.sort(function(a,b){return a - b});

                for (var i = 0; i < sortedProportions.length; i += 1) {
                    if (sortedProportions[i] < min) {
                        var diff = min - sortedProportions[i];
                        sortedProportions[i] += diff;
                        sortedProportions[sortedProportions.length - 1] -= diff;
                        return validateAndCorrectProportions(sortedProportions, min);
                    }
                }

                return sortedProportions;
            }
        }

        /*
         * Array sorting algorithm
         */
        function knuthfisheryates2(arr) {
            var temp, j, i = arr.length;
            while (--i) {
                j = ~~(Math.random() * (i + 1));
                temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }

            return arr;
        }
    }

})();

              
            
!
999px

Console