<svg viewbox='-1000 -1000 2000 2000' class='frame frame--poly-distribution'></svg>
<div class='controls'>
  <label for='n-edges-text'>number of polygon vertices/ edges</label>
  <input type='range' id='n-edges-slider' min='3' value='3' max='10'/>
  <input type='text' id='n-edges-text' value='3'/>
  <label for='m-layers-text'>number of layers</label>
  <input type='range' id='m-layers-slider' min='1' value='3' max='8'/>
  <input type='text' id='m-layers-text' value='3'/>
</div>
@import "compass/css3";

$base-dark: #000;
$base-light: #f1f1f1;
$medium-dark: #312c24;
$medium-light: #f9de4e;
$highlight-dark: #bb1654;
$highlight-light: #fadf1d;

$a: 1;
$b: 1;

* { box-sizing: inherit; }

html {
  box-sizing: border-box;
  background: $base-dark;
  font: 1em trebuchet ms, verdana, sans serif;
}

body { margin: 0; }

svg {
  position: absolute;
  left: calc(50vw - #{$a/$b/2*100vh});
  height: 100vh; width: $a/$b*100vh;
}

.assembly {
  
  &--zebra {
    
  }
}

.layer {
  cursor: pointer;
  
  &__polygon {
    fill: darken($medium-dark, 14%);
  }
  
  &:nth-child(odd) &__polygon {
    fill: darken($medium-dark, 12%);
  }
  
  &:hover &__polygon {
    fill: mix($medium-dark, $medium-light, 50%);
  }
  
  &__highlight {
    fill: $base-light;
    fill-opacity: .05;
  }
  
  &:hover &__highlight {
    fill-opacity: .5;
    
    &:hover {
      fill-opacity: .99;
    }
  }
  
  &__edge {
    stroke: $highlight-dark;
    stroke-width: 2;
    pointer-events: none;
  }
  
  &__point {
    fill: $highlight-light;
    stroke-width: 10;
    
    &--vertex {
      fill: $highlight-dark;
      stroke: $base-light;
    }
  }
  
  &:hover &__point {
    stroke: $base-dark;
  }
}

.controls {
  position: fixed;
  top: .5em;
  padding: .5em;
  //background: $medium-dark;
  color: $base-light;
  text-align: center;
  
  label {
    display: block;
    padding: .75em 0;
    text-align: left;
    
    & ~ label { padding-top: 2em; }
  }
  
  input {
    padding: .25em .5em;
    display: inline-block;
    vertical-align: middle;
    
    &[type='range'] {
      cursor: pointer;
      
      &:focus {
        outline: none;
      }
    }
    
    &[type='text'] {
      max-width: 3em;
      border: none;
      border-radius: .25em;
      box-shadow: 0 0 .25em $highlight-dark;
      background: transparent;
      color: $base-light;
      text-align: right;
      
      &:focus {
        box-shadow: 0 0 .25em $highlight-light;
      }
    }
  }
}

@media (max-aspect-ratio: #{$a}/#{$b}) {
  svg {
    top: calc(50vh - #{$b/$a/2*100vw}); left: auto;
    width: 100vw; height: $b/$a*100vw;
  }
}
View Compiled
var NS_URI = 'http://www.w3.org/2000/svg', 
    POINT_RADIUS = 14, 
    
    frame = document.querySelector('.frame--poly-distribution'), 
    n_edges_slider_el = document.getElementById('n-edges-slider'), 
    n_edges_text_el = document.getElementById('n-edges-text'), 
    m_layers_slider_el = document.getElementById('m-layers-slider'), 
    m_layers_text_el = document.getElementById('m-layers-text'), 
    
    min = Math.min, 
    round = Math.round, 
    PI = Math.PI, sin = Math.sin, cos = Math.cos, 
    
    distribution = null;

/*
 * returns a random number between the min and max values
 * if min is omitted, it defaults to 0
 * if max is omitted, it defaults to 1
 */
var rand = function(max, min) {
  var b = max || 1, a = min || 0;

  return a + (b - a)*Math.random();
};

var setAttrs = function(el, o) {
  for(var a in o) { el.setAttribute(a, o[a]); }
}

var Point = function(x, y, r) {
  this.x = (x === 0 || x)?~~x:round(rand());
  this.y = (y === 0 || y)?~~y:round(rand());
  this.el = document.createElementNS(NS_URI, 'circle');
  
  setAttrs(this.el, {
    'cx': this.x, 
    'cy': this.y, 
    'r': (r)?r:POINT_RADIUS
  });
};

var Segment = function(start, end) {
  this.start = start || new Point();
  this.end = end || new Point();
  this.el = document.createElementNS(NS_URI, 'line');
  
  setAttrs(this.el, {
    'x1': this.start.x, 
    'y1': this.start.y, 
    'x2': this.end.x, 
    'y2': this.end.y
  });
};

var Polygon = function(n, cradius) {
  var central_angle = 2*PI/n, 
      angle, x, y, 
      points = '';
  
  this.el = document.createElementNS(NS_URI, 'polygon');
  this.el.classList.add('layer__polygon');
  
  for(var i = 0; i < n; i++) {
    angle = i*central_angle;
    x = round(cradius*cos(angle));
    y = round(cradius*sin(angle));
    
    points += x + ',' + y + ' ';
  }
  
  this.el.setAttribute('points', points.trim())
};

var EdgeGroup = function(layer, idx, edge) {
  var s = edge/layer, 
      highlight_h = 3*POINT_RADIUS, 
      highlight_w = round((layer - 1)*s + highlight_h), 
      highlight_x = round(-(highlight_w - s)/2), 
      highlight_y = round(-highlight_h/2), 
      highlight = document.createElementNS(NS_URI, 'rect'), 
      points_frag = document.createDocumentFragment();
  
  this.el = document.createElementNS(NS_URI, 'g');
  this.edge = new Segment(new Point((edge - highlight_h)/2, 0), 
                          new Point(-(edge - highlight_h)/2, 0));
  this.points = [];
  
  highlight.classList.add('layer__highlight');
  setAttrs(highlight, {
    'x': highlight_x, 
    'y': highlight_y, 
    'width': highlight_w, 
    'height': highlight_h, 
    'rx': highlight_h/2
  });
  
  this.edge.el.classList.add('layer__edge')
  
  for(var i = layer; i > 0; i--) {
    this.points.push(new Point(i*s - edge/2, 0));
    this.points[layer - i].el.classList.add('layer__point');
    points_frag.appendChild(this.points[layer - i].el);
    
    if(i === layer) {
      this.points[layer - i].el.classList.add('layer__point--vertex');
    }
  }
  
  this.el.appendChild(highlight);
  this.el.appendChild(this.edge.el);
  this.el.appendChild(points_frag);
};

var Layer = function(n_edges, idx, base_cradius) {
  var cradius = idx*base_cradius, 
      edge = 2*cradius*sin(PI/n_edges), 
      group_frag = document.createDocumentFragment(), 
      central_angle = 2*PI/n_edges, angle, 
      iradius = round(cradius*cos(central_angle/2));

  this.poly = new Polygon(n_edges, cradius);
  this.edge_groups = [];
  this.el = document.createElementNS(NS_URI, 'g');
  
  this.el.classList.add('layer');

  if(idx > 0) {
    for(var i = 0; i < n_edges; i++) {
      angle = (i + .5)*central_angle;

      this.edge_groups.push(new EdgeGroup(idx, i, edge));
      group_frag.appendChild(this.edge_groups[i].el);
      setAttrs(this.edge_groups[i].el, {
        'transform': 'rotate(' + round(angle*180/PI - 90) + ') ' + 
                     'translate(0 ' + iradius + ')'
      });
    }

    this.el.appendChild(this.poly.el);
    this.el.appendChild(group_frag);
  }
};

var Distribution = function(n_edges, m_layers) {
  var vb = frame.getAttribute('viewBox').split(' '), 
      w = ~~vb[2], h = ~~vb[3], 
      base_cradius = round(.95*min(w, h)/2/(m_layers - 1));
  
  this.n = n_edges;
  this.m = m_layers;
  this.layers = [];
  this.el = document.createElementNS(NS_URI, 'g');
  
  for(var i = this.m - 1; i >= 0; i--) {
    this.layers.push(new Layer(n_edges, i, base_cradius));
    this.el.appendChild(this.layers[this.m - i - 1].el);
  }
  
  this.el.classList.add('assembly');
  this.el.setAttribute('transform', 'rotate(' + round(rand(360/n_edges)) + ')')
  frame.appendChild(this.el);
};

var init = function() {
  if(distribution) {
    frame.removeChild(distribution.el);
  }
  
  distribution = new Distribution(n_edges_text_el.value, ~~m_layers_text_el.value + 1);
};

init();

n_edges_slider_el.addEventListener('change', function() {
  if(this.value != distribution.n || this.value != n_edges_text_el.value) {
    n_edges_text_el.value = this.value;
    init();
  }
}, false);

m_layers_slider_el.addEventListener('change', function() {
  if(this.value != distribution.m || this.value != m_layers_text_el.value) {
    m_layers_text_el.value = this.value;
    init();
  }
}, false);

n_edges_text_el.addEventListener('blur', function() {
  if(this.value != distribution.n || this.value != n_edges_slider_el.value) {
    n_edges_slider_el.value = this.value;
    init();
  }
}, false);

m_layers_text_el.addEventListener('blur', function() {
  if(this.value != distribution.m || this.value != m_layers_slider_el.value) {
    m_layers_slider_el.value = this.value;
    init();
  }
}, false);

frame.addEventListener('click', function(e) {
  var t = e.target;
  
  if(t.classList.contains('layer__polygon')) {
    
    return;
  }
  
  if(t.classList.contains('layer__highlight')) {
    
    return;
  }
}, false);

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.