// Parameters. // Canvas dimensions var width = 500; var height = 500; // Animation parameters var angleCycle = 90; var animateLength = 1000; // Milliseconds // Shape parameters var count = 5; var shape = "-50 0, 50 -50, 50 50"; // SVG coordinates /////////////////////////////////////////////////////////////////////////////// var centerX = width / 2; var centerY = height / 2; var radius = Math.min(width / 2, height / 2) / 2; // 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 /////////////////////////////////////////////////////////////////////////////// // Generate the graphics: // 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. var pEnter = gEnter .append("polygon") .attr("points", shape) ; // 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; }) ; } 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; }) ; } // Run the animation. animateForward(0);