// Note: JavaScript computes angles in radians, while SVGs compute angles in degrees. /////////////////////////////////////////////////////////////////////////////// // Parameters /////////////////////////////////////////////////////////////////////////////// // Canvas dimensions var width = 500; var height = 500; // Animation parameters var startingAngle = 30; var angleCycle = 90; var animateLength = 1500; // Milliseconds // Shape parameters var count = 6; // Generate the angle shape based on parameters: var l = 60; // Length of triangle side. var angle = Math.PI / 6; // Angle in radians. var dx = -65; // Offset from origin. var dy = 0; // Offset from origin. /////////////////////////////////////////////////////////////////////////////// // SVG elements /////////////////////////////////////////////////////////////////////////////// var angle = function() { // For some reason, using direct ratios creates less accurate angles, // so I'll be using trigonometry. var point1 = [0, - Math.sin(angle) * l]; var point2 = [Math.cos(angle) * l, 0]; var point3 = [0, Math.sin(angle) * l]; var points = [point1, point2, point3]; points.forEach(function(e) { e[0] = e[0] + dx; e[1] = e[1] + dy; e = e.join(" "); }); var path = "M" + points.join(" L"); return path; }(); // Self-executing anonymous function. var pie = "m-34.4508,-20.3843l34.63679,22.07839l-34.60879,21.51281c9.4976,29.42 81.304,31.5881 81.2213,-21.59432c-0.0827,-53.18248 -70.8224,-56.46098 -81.2493,-21.99688l0,0z"; /* var circle = "m-29.11401,-20.384c-1.81,7.46101 -5.08398,9.982 -5.43097,22.384c-0.34702,12.40199 3.63898,14.138 5.45898,21.207c9.49701,29.42 81.30402,31.588 81.22101,-21.59399c-0.08301,-53.18291 -70.82202,-56.4614 -81.24902,-21.99701z"; */ /////////////////////////////////////////////////////////////////////////////// // Misc. derived values /////////////////////////////////////////////////////////////////////////////// var centerX = width / 2; var centerY = height / 2; var radius = Math.min(centerX, centerY) / 2; /////////////////////////////////////////////////////////////////////////////// // Helper functions /////////////////////////////////////////////////////////////////////////////// // Eschew needless annoying string concatenation. function translate(x, y) {return "translate(" + x + "," + y + ")";} function rotate(angle) {return "rotate(" + angle + ")";} function rotateAroundPoint(angle, x, y) {return "rotate(" + angle + "," + x + ", " + y + ")";} // Convert the index of an element to an angle on a circle. function toRadians(i) { var tau = 2 * Math.PI; var ratio = i / count; return tau * ratio; } function toDegrees(i) { var degrees = 360; var ratio = i / count; return degrees * ratio; } // Determine the output X and Y coordinates relative to (0, 0) of its parent. function getX(i) {return radius * Math.cos(toRadians(i));} // centerX function getY(i) {return radius * Math.sin(toRadians(i));} // centerY function getOppositeX(i) {return - radius * Math.cos(toRadians(i));} // centerX function getOppositeY(i) {return - radius * Math.sin(toRadians(i));} // centerY /////////////////////////////////////////////////////////////////////////////// // Graphics generation /////////////////////////////////////////////////////////////////////////////// // Create a dummy array to 'store' each shape. var data = new Array(count); // Create the canvas to place our images. var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height) ; // Create a group selection to center and rotate the shapes. var all = svg.append("g") .attr("transform", translate(centerX, centerY)); // Creates a data join selection (this is the confusing part). var join = all.selectAll("g").data(data); // For every element that was passed into data(), we do the following things: var gEnter = join.enter() // Create a sub-group and place it accordingly around the center. .append("g") .attr("transform", function(d, i) { var t = translate(getX(i), getY(i)); var r = rotateAroundPoint(toDegrees(i), 0, 0); return t + r; }) ; // Create the shape corresponding to the group. gEnter .append("path") .attr("d", function(d, i) {return (i % 2 === 0)? angle : pie;}) // Alternate between angles and pies. .attr("fill", function(d, i) {return (i % 2 === 0)? "none" : "black";}) // Alternate between lines and filled. .attr("stroke", "black") ; /////////////////////////////////////////////////////////////////////////////// // Animation /////////////////////////////////////////////////////////////////////////////// // Animate the shapes, rotating the canvas as an offset of the previous animation. function animateForward(prevAngle) { // newAngle will overflow after a long time, but I don't think we should worry about that. var newAngle = prevAngle + angleCycle; // Rotate the entire image. all.transition().duration(animateLength) .attr("transform", translate(centerX, centerY) + rotate(newAngle)) // Mutually recursive call to animateBackward .each("end", function(){animateBackward(newAngle);}) ; // Move each shape to the opposite side of the circle. join.transition().duration(animateLength) .attr("transform", function(d, i) { var transforms = d3.transform(d3.select(this).attr("transform")); var t = translate(getOppositeX(i), getOppositeY(i)); var r = rotateAroundPoint(transforms.rotate, 0, 0); return t + r; }) ; // Change shape when everything is in the center. /* join.selectAll("path").transition()//.duration(animateLength) .delay(animateLength / 2) .attr("d", circle) ; */ } function animateBackward(prevAngle) { var newAngle = prevAngle + angleCycle; // Rotate the entire image. all.transition().duration(animateLength) .attr("transform", translate(centerX, centerY) + rotate(newAngle)) // Mutually recursive call to animateForward .each("end", function(){animateForward(newAngle);}) ; // Move each shape to the original side of the circle. join.transition().duration(animateLength) .attr("transform", function(d, i) { var transforms = d3.transform(d3.select(this).attr("transform")); var t = translate(getX(i), getY(i)); var r = rotateAroundPoint(transforms.rotate, 0, 0); return t + r; }) ; // Change shape when everything is in the center. /* join.selectAll("path").transition()//.duration(animateLength) .delay(animateLength / 2) .attr("d", pie) ; */ } // Run the animation. animateForward(startingAngle);