var DEFAULT_OPTIONS = { margin: {top: 20, right: 20, bottom: 20, left: 20} }; // create sierpinski var sierpinski = new Sierpinski(6); var chart = new d3Kit.Skeleton('.chart', DEFAULT_OPTIONS) .autoResize('both') .on('resize', onResize); var options = chart.options(); // add sierpinski to chart.getRootG() .call(sierpinski.enter); // handle resize function onResize() { var width = chart.getInnerWidth (); var height = chart.getInnerHeight(); var center = [options.margin.left + width / 2, options.margin.top + height / 2]; sierpinski.property('edgeLength', d3.min([width, height])); chart.getRootG() .attr('transform', 'translate(' + center + ')') .call(sierpinski.update); } // sierpinski chartlet function Sierpinski(depth, color) { // constants var ANGLES = d3.range(0, 360, 120).map(function(d) {return degreesToRadians(d + 30);}); var TRIANGLE = 'M P0 L P1 L P2 z'; var EDGE_RADIUS_RATIO = Math.sqrt(3) / 3; color = color || [255, 128, 128]; var events = []; var children = depth > 0 ? new Sierpinski(depth - 1) : null; var charlet = d3Kit.Chartlet(enter, update, exit, events); function enter(selection, onEnd) { // if bottom out on depth, actually add path to plot triangles if (depth == 0) { selection .append('path') .classed('triangle', true) .attr('d', function() { return createTrianglePath(0); }) .style('fill', triangleColor); } // otherwise add children else { // create 3 groups one for each child sierpinski triangle var groups = selection.selectAll('g.child') .data(ANGLES) .enter() .append('g') .classed('child', true) .attr('transform', 'translate(0, 0)'); groups.call(children.enter); } onEnd(); } function update(selection, onEnd) { var radius = charlet.getPropertyValue('edgeLength') * EDGE_RADIUS_RATIO; // if children, update those if (children) { // update done when children update is done children.on('updateDone', onEnd); selection.selectAll('g.child') .transition() .attr('transform', function(d) { var x = Math.cos(d) * radius / 2; var y = Math.sin(d) * radius / 2 + radius / 8; return 'translate(' + x + ', ' + y + ')'; }); children.property('edgeLength', charlet.getPropertyValue('edgeLength') / 2); selection.selectAll('g.child') .call(children.update); } // otherwise update the triangle else { selection.selectAll('path.triangle') .transition() .attr('d', function() { return createTrianglePath(radius); }) .each('end', onEnd); } } function exit(selection, onEnd) { onEnd(); } function triangleColor(d, i) { var localColor = color.map(function(_d, _i) {return i != _i ? _d * .7 : _d;}); return d3.rgb(localColor[0], localColor[1], localColor[2]); }; // join a pattern and a set of x,y points function pathPoints(pattern, points) { return points.reduce(function(acc, point, i) { return acc.replace('P' + i, point.x + ' ' + point.y); }, pattern); } // convert degrees to radians function degreesToRadians(degrees) { return degrees / 180 * Math.PI; } // given a radius, create a triangel around the origin function createTrianglePath(radius) { var points = ANGLES.map(function(angle) { return { x: Math.cos(angle) * radius, y: Math.sin(angle) * radius + radius / 4, }; }); return pathPoints(TRIANGLE, points); } return charlet; }