(function() { var config = { sides: 4, increase: 2, radius: 30, steps: 10, size: 15, opacity: .2, drawLines: true }; var app = d3.select('#app'); var bbox = app.node().getBoundingClientRect(); var w = bbox.width; var h = bbox.height; var stage = app.append('svg').append('g'); resize(w, h); gui(); update(); d3.select(window).on('resize', function() { bbox = app.node().getBoundingClientRect(); w = bbox.width; h = bbox.height; resize(w, h); }); function resize(width, height) { d3.select(stage.node().parentNode) .attr('width', width) .attr('height', height); stage.attr('transform', 'translate(' + (width / 2) + ' ' + (height / 2) + ')'); } function gui() { var delayUpdate = delayCallback(update, 20, 3); var ui = new dat.GUI(); ui.add(config, 'sides').min(3).max(10).step(1).onChange(delayUpdate); ui.add(config, 'increase').min(0).max(3).step(1).onChange(delayUpdate); ui.add(config, 'steps').min(1).max(30).step(1).onChange(delayUpdate); ui.add(config, 'radius').min(1).max(300).step(1).onChange(delayUpdate); ui.add(config, 'size').min(1).max(300).step(1).onChange(delayUpdate); ui.add(config, 'opacity').min(0).max(1).step(.001).onChange(delayUpdate); ui.add(config, 'drawLines').onChange(delayUpdate); } function delayCallback(callback, delay, skipMax) { function run() { skipped = toId = 0; callback(); } var toId = skipped = 0; return function() { if(!toId || skipped < skipMax) { skipped++; clearTimeout(toId); toId = setTimeout(run, delay); } }; } function getPoints(sides, segments, radius, angleOffset) { function segmentLine(p1, p2, segments) { var points = []; var dx = (p2[0] - p1[0]) / segments; var dy = (p2[1] - p1[1]) / segments; for(var i = 1; i < segments; i++) { points.push([ p1[0] + dx * i, p1[1] + dy * i ]); } return points; } var points = [], corners = []; var aSegment = Math.PI * 2 / sides; var i, a; for(i = 0; i < sides; i++) { a = aSegment * i + angleOffset; corners.push([ Math.cos(a) * radius, Math.sin(a) * radius ]); } var p1, p2; for(i = 0; i < corners.length; i++) { p1 = corners[i]; p2 = corners[(i + 1) % corners.length]; points.push(p1); points = points.concat(segmentLine(p1, p2, segments)); } return points; } function points(sides, steps, increase, radius) { var offset = Math.PI / 2 - Math.PI / sides; return d3.range(steps).map(function(v, i) { return getPoints(sides, (i + 1) * increase, (i + 1) * radius, offset); }); } function renderCenter(parent, size, opacity) { var center = parent.selectAll('.center').data([null]); center.enter() .append('circle') .attr('class', 'center') .attr('cx', 0) .attr('cy', 0) .merge(center) .attr('r', size / 2) .attr('fill', 'rgba(0,0,0,' + opacity + ')'); } function renderPoints(points, parent, size, dotOpacity, drawLines) { function renderLine(d) { var p = d3.select(this).selectAll('.line').data(drawLines ? [d] : []); p.exit().remove(); p.enter() .append('path') .attr('class', 'line') .merge(p) .attr('stroke', 'black') .attr('d', function(d) { return 'M' + d.map(function(d) { return d.join(','); }).join('L') + 'Z'; }); } var group = parent.selectAll('.step').data(points); group.exit().remove(); group = group.enter() .append('g') .attr('class', 'step') .merge(group) .each(renderLine); var dot = group.selectAll('.dot') .data(function(d) { return d; }); dot.exit().remove(); dot .enter() .append('circle') .attr('class', 'dot') .merge(dot) .attr('r', size / 2) .attr('cx', function(d) { return d[0]; }) .attr('cy', function(d) { return d[1]; }) .attr('fill', 'rgba(0,0,0,' + dotOpacity + ')'); } function update() { renderCenter(stage, config.size, config.opacity); var p = points(config.sides, config.steps, config.increase, config.radius); renderPoints(p, stage, config.size, config.opacity, config.drawLines); } }());