<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);
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.