|
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; |
|
} |