(function() { var width = window.innerWidth; var height = window.innerHeight; var padding = 2; var external_padding = 10; var yy_r = (Math.min(width, height) - (2 * padding) - external_padding) / 2; var movable_id = 0; var big = 0.5 * yy_r; var small = 0.125 * yy_r; var NORTH = 0, EAST = 1, SOUTH = 2, WEST = 3; // Time to complete one transition / iteration var timeparam = 10000; var svg = d3.select("#chart").append("svg") .attr("width", width) .attr("height", height) .append("g") .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")") .append("g"); // fixed enclosure svg.append("circle") .attr("cx", 0) .attr("cy", 0) .attr("r", yy_r + padding) .attr("class", "yin"); function makeArc(sx, sy, rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, ex, ey) { return "M " + sx + "," + sy + " a " + rx + "," + ry + " " + x_axis_rotation + " " + large_arc_flag + " " + sweep_flag + " " + ex + "," + ey + " z"; } function makeCircle(radius, location, class_name) { var cx = -1, cy = -1; switch (location) { case NORTH: cx = 0; cy = -yy_r / 2; break; case EAST: cx = yy_r / 2; cy = 0; break; case SOUTH: cx = 0; cy = yy_r / 2; break; case WEST: cx = -yy_r / 2; cy = 0; break; } return { "movable_id": ++movable_id, "cx": cx, "cy": cy, "r": radius, "class_name": class_name + " movable" }; } var circleData = [ makeCircle(big, NORTH, "yin"), makeCircle(small, NORTH, "yang"), makeCircle(big, SOUTH, "yang"), makeCircle(small, SOUTH, "yin") ]; var arcData = [ { "movable_id": ++movable_id, "path": makeArc(0, yy_r, yy_r, yy_r, 0, 0, 1, 0, -yy_r * 2), "class_name": "yang movable arc" } ]; var arc = svg.selectAll("path.movable") .data(arcData, function(d) { return d.movable_id; }) .enter() .append("path") .attr("d", function(d) { return d.path; }) .attr("class", function(d) { return d.class_name; }); var circles = svg.selectAll("circle.movable") .data(circleData, function(d) { return d.movable_id; }) .enter() .append("circle") .attr("cx", function(d) { return d.cx; }) .attr("cy", function(d) { return d.cy; }) .attr("r", function(d) { return d.r; }) .attr("class", function(d) { return d.class_name; }); var max_iter = 100; var iter = 0; var easeparam = "linear"; function repeat() { if (iter >= max_iter) { return; } iter++; var d_value = 0; circles .transition() .delay(d_value) .duration(timeparam) .ease(easeparam) .attrTween("transform", transformCircleFn()) .each("end", repeat); arc .transition() .delay(d_value) .duration(timeparam) .ease(easeparam) .attrTween("transform", transformArcFn()) .each("end", repeat); } var min_scale = 0.25; function transformCircleFn() { var circleScaleFn = d3.scale.linear() .domain([-1, 1]) .range([0, 1]); var ratioScaleFn = d3.scale.linear() .domain([-1, 1]) .range([min_scale, 2 - min_scale]); return function(d, i, a) { var icx = d.cx; var icy = d.cy; var radius = d.r; var start_angle = Math.atan2(icx, icy); var rotation_radius = yy_r / 2; // console.log("factory params: " + d.cx + ", " + d.cy + ", " + start_angle); return function(t) { var t_angle = (2 * Math.PI) * t + start_angle; // scale circles based on current angle // sum of radii of opposite inner large circles // stays constant and equal to grand radius var scale_factor = ratioScaleFn(Math.cos(t_angle)); var scaleStr = "scale("+ scale_factor + ")"; // add an offset to scaled inner circles // to keep them algined and inside grand circle var offset = (1 - scale_factor) * 2 * rotation_radius; // compensate for SVG coordinate system changes after scaling offset = offset / scale_factor; var bx = (offset * Math.cos(t_angle) ); var by = (offset * Math.sin(t_angle) ); var translateOffsetStr = "translate(" + bx + "," + by + ")"; // basic rotation: polar to cartesian transformation var ax = (rotation_radius * Math.cos(t_angle) ) - icx; var ay = (rotation_radius * Math.sin(t_angle) ) - icy; var translateRotationStr = "translate(" + ax + "," + ay + ")"; // putting it all together var transf = [scaleStr, translateOffsetStr, translateRotationStr]; return transf.join(" "); }; }; } function transformArcFn() { return function(d, i, a) { return function(t) { var r_degrees = 360 * t + 90; var rotateStr = "rotate(" + r_degrees + ")"; return rotateStr; }; }; } repeat(); })();