Skip to content

Instantly share code, notes, and snippets.

@mbostock
Last active February 9, 2016 01:56
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save mbostock/10343037 to your computer and use it in GitHub Desktop.
Accelerate, Then Coast II
license: gpl-3.0

This diagram takes the Accelerate, Then Coast easing function and reflects it to apply slow-out. The reflection center r is configurable. With r = .5, the reflection is equivalent to the standard “in-out” symmetric easing; with r = 0, only slow-out is applied, and with r = 1, only slow-in is applied.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
width: 960px;
height: 500px;
position: relative;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.axis text {
font: 10px sans-serif;
}
.line {
fill: none;
stroke: #000;
stroke-linecap: round;
}
line {
stroke: #aaa;
shape-rendering: crispEdges;
}
#form {
position: absolute;
bottom: 27px;
right: 50px;
}
input {
width: 140px;
}
span {
position: relative;
top: -3px;
}
output {
display: inline-block;
width: 3.5em;
}
</style>
<body>
<div id="form">
<div id="acceleration">
<input type="range" min="0" max="1" step=".01" value=".55">
<span><i>a</i> = <output name="acceleration"></output></span>
</div>
<div id="reflection">
<input type="range" min="0" max="1" step=".01" value=".5">
<span><i>r</i> = <output name="reflection"></output></span>
</div>
</div>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 40, right: 340, bottom: 40, left: 200},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var color = d3.scale.category20c();
var formatNumber = d3.format(".4f");
var x = d3.scale.linear()
.domain([0, 1])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, 1])
.range([height, 0]);
var continuousLine = (function() {
var samples = d3.range(0, 1 + 1e-6, 2 / width);
var discreteLine = d3.svg.line()
.x(function(d, i) { return x(samples[i]); })
.y(function(d) { return y(d); });
return function(f) {
return discreteLine(samples.map(f));
};
})();
var ease;
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.svg.axis()
.scale(x)
.orient("bottom"))
.append("text")
.attr("x", width)
.attr("y", -3)
.attr("dy", "-.35em")
.style("font-weight", "bold")
.style("text-anchor", "middle")
.text("time");
svg.append("g")
.attr("class", "axis axis--y")
.call(d3.svg.axis()
.scale(y)
.orient("left"))
.append("text")
.attr("x", 6)
.attr("dy", ".35em")
.style("font-weight", "bold")
.text("ease(time)");
var lineEase = svg.append("path")
.attr("class", "line")
.style("stroke", "#000")
.style("stroke-width", "1.5px");
var timeReference = svg.append("line").attr("y1", height),
easeReference = svg.append("line");
var circle = svg.append("circle")
.attr("r", 4.5)
.attr("cx", 0)
.attr("cy", height);
var accelerationInput = d3.select("#acceleration input").on("input", reset),
accelerationOutput = d3.select("#acceleration output"),
reflectionInput = d3.select("#reflection input").on("input", reset),
reflectionOutput = d3.select("#reflection output");
var accelerationInputScale = d3.scale.pow()
.exponent(4)
.domain([0, 1])
.range([1, 10]);
reset();
!function loop() {
circle.transition()
.ease("linear")
.duration(5000)
.tween("transform", function() {
return function(t) {
circle.attr("cx", x(t)).attr("cy", y(ease(t)));
timeReference.attr("x1", x(t)).attr("x2", x(t)).attr("y2", y(ease(t)));
easeReference.attr("x2", x(t)).attr("y1", y(ease(t))).attr("y2", y(ease(t)));
};
})
.each("end", loop);
}();
function reset() {
var acceleration = accelerationInputScale(accelerationInput.property("value")),
reflection = reflectionInput.property("value");
accelerationOutput.text(formatNumber(acceleration));
reflectionOutput.text(formatNumber(reflection));
ease = easeReflect(easeAccelerateThenCoast(acceleration), reflection);
lineEase.attr("d", continuousLine(ease));
}
// Like in-out reflection, except around the specified center.
// If center = .5, this is the same as in-out reflection.
function easeReflect(ease, center) {
return function(t) {
return t < center ? center * ease(t / center) : 1 - ease((1 - t) / (1 - center)) * (1 - center);
};
}
// Constant acceleration followed by constant speed.
// If acceleration = 1, this is the same as quadratic easing.
// If acceleration = Infinity, this is the same as linear easing.
function easeAccelerateThenCoast(acceleration) {
if (acceleration < 1) throw new Error("unpossible");
if (!isFinite(acceleration)) return d3.ease("linear");
var speed = 2 * (acceleration - Math.sqrt((acceleration - 1) * acceleration)),
t0 = speed / 2 / acceleration;
return function(t) {
return t < t0 ? acceleration * t * t : speed * t - speed + 1;
};
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment