Skip to content

Instantly share code, notes, and snippets.

@cloudshapes

cloudshapes/README.md

Last active Dec 17, 2015
Embed
What would you like to do?
attrTween in a Transition to Move an Element Using a Function

Using attrTween in a Transition to Move an Element Using a Function

Kind of an evolution from Mike Bostock's ball-following-a-path example.

Mike Bostock's code uses attrTween and transform to move a ball in a transition along a pre-determined set of points. So Mike's code creates a route using a 'path' element, then uses the attrTween and transform to navigate along those sets of points.

This code uses attrTween and transform, but the code that actually does the moving uses a function to generate the points for the balls to move to.

The code also has the data specify which pattern the balls should follow: a circular or elliptical path?

Why do this? It opens up all kinds of possibilities to controlling and manipulating movement from within transitions. What's also quite cool is that you can still use all the various "ease parameters".

<!doctype html>
<html lang="en">
<head lang=en>
<meta charset="utf-8">
<title>D3: Using a Fn in attrTween to Control Movement</title>
</head>
<body>
<style>
svg {
background: #eee;
}
circle {
fill: steelblue;
stroke: #fff;
stroke-width: 3px;
}
</style>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var width = 640;
var height = 480;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g");
var circleData = [];
// Define first circle and type of movement - circle
var t_circle = d3.map();
t_circle.set("id", 1);
t_circle.set("cr", 10);
t_circle.set("rotr", 100);
t_circle.set("rtype", "circle");
t_circle.set("offset", 20);
circleData.push(t_circle);
// Define first circle and type of movement - ellipse
t_circle = d3.map();
t_circle.set("id", 2);
t_circle.set("cr", 20);
t_circle.set("rotrx", 200);
t_circle.set("rotry", 100);
t_circle.set("rtype", "ellipse");
t_circle.set("offset", -100);
circleData.push(t_circle);
var easeparam = "linear";
// Time to complete one transition / iteration of the circular or elliptical path
var timeparam = 2000;
// Add each of the two circles:
var circle = svg.selectAll("circle")
.data(circleData, function(d) { return d.get('id');})
.enter()
.append("circle")
.attr("r", function(d) { return d.get('cr'); });
// Setup each circle with a transition, each transition working on transform attribute,
// and using the translateFn
circle
.transition()
.duration(timeparam)
.ease(easeparam)
.attrTween("transform", translateFn());
// Setup a couple of text elements to follow the circles:
var text = svg.selectAll("text")
.data(circleData, function(d) { return d.get('id');})
.enter()
.append("text")
.text(function(d){ return d.get('id');})
text
.transition()
.duration(timeparam)
.ease(easeparam)
.attrTween("transform", translateFn());
// The function that actually does the moving:
function translateFn() {
// We only use 'd', but list d,i,a as params just to show can have them as params.
// Code only really uses d and t.
return function(d, i, a) {
return function(t) {
// 't': what's t? T is the fraction of time (between 0 and 1) since the
// transition began. Handy.
var t_offset = d.get('offset');
var t_x, t_y;
// If the data says the element should follow a circular path, do that.
if (d.get('rtype') == 'circle') {
var rotation_radius = d.get('rotr');
var t_angle = (2 * Math.PI) * t;
var t_x = rotation_radius * Math.cos(t_angle);
var t_y = rotation_radius * Math.sin(t_angle);
}
// Likewise for an ellipse:
if (d.get('rtype') == 'ellipse') {
var rotation_radius_x = d.get('rotrx');
var rotation_radius_y = d.get('rotry');
var t_angle = (2 * Math.PI) * t;
var t_x = rotation_radius_x * Math.cos(t_angle);
var t_y = rotation_radius_y * Math.sin(t_angle);
}
return "translate(" + ((width/2) + t_offset + t_x) + "," + (height/2 + t_offset + t_y) + ")";
};
};
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment