Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add a simple paused and progress with event to control transition animation. #102

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,27 @@ If a *value* is not specified, returns the current easing function for the first

### Control Flow

The [paused](#transition_paused) and [progress](#transition_progress) of a transition is configurable in runtime.

<a name="transition_paused" href="#transition_paused">#</a> <i>transition</i>.<b>paused</b>([<i>value</i>]) [<>](https://github.com/d3/d3-transition/blob/master/src/transition/paused.js "Source")

To pause the transition animation, set the transition paused to `true`, or `false` to resume. he *value* may be specified either as a constant or a function.
snowyu marked this conversation as resolved.
Show resolved Hide resolved

```js
transition.paused(true);
```

If a *value* is not specified, returns the current value of the paused for the first (non-null) element in the transition. This is generally useful only if you know that the transition contains exactly one element.


<a name="transition_progress" href="#transition_progress">#</a> <i>transition</i>.<b>progress</b>([<i>value</i>]) [<>](https://github.com/d3/d3-transition/blob/master/src/transition/progress.js "Source")

The progress is a value between 0(begin) to 1(end). You can set or get the progress of the transition at any time.
snowyu marked this conversation as resolved.
Show resolved Hide resolved

```js
transition.progress(0.5);
```

For advanced usage, transitions provide methods for custom control flow.

<a name="transition_end" href="#transition_end">#</a> <i>transition</i>.<b>end</b>() [<>](https://github.com/d3/d3-transition/blob/master/src/transition/end.js "Source")
Expand All @@ -373,6 +394,7 @@ Returns a promise that resolves when every selected element finishes transitioni
Adds or removes a *listener* to each selected element for the specified event *typenames*. The *typenames* is one of the following string event types:

* `start` - when the transition starts.
* `progress` - notify the the transition progress.
snowyu marked this conversation as resolved.
Show resolved Hide resolved
* `end` - when the transition ends.
* `interrupt` - when the transition is interrupted.
* `cancel` - when the transition is cancelled.
Expand Down
4 changes: 4 additions & 0 deletions src/transition/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import transition_textTween from "./textTween.js";
import transition_transition from "./transition.js";
import transition_tween from "./tween.js";
import transition_end from "./end.js";
import transition_paused from "./paused.js";
import transition_progress from "./progress.js";

var id = 0;

Expand Down Expand Up @@ -63,6 +65,8 @@ Transition.prototype = transition.prototype = {
tween: transition_tween,
delay: transition_delay,
duration: transition_duration,
paused: transition_paused,
progress: transition_progress,
ease: transition_ease,
end: transition_end
};
23 changes: 23 additions & 0 deletions src/transition/paused.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {get} from "./schedule.js";

function pausedFunction(id, value) {
return function() {
get(this, id).paused = Boolean(value.apply(this, arguments));
};
}

function pausedConstant(id, value) {
return value = Boolean(value), function() {
get(this, id).paused = value;
};
}

export default function(value) {
var id = this._id;

return arguments.length
? this.each((typeof value === "function"
? pausedFunction
: pausedConstant)(id, value))
: get(this.node(), id).paused;
}
28 changes: 28 additions & 0 deletions src/transition/progress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {get} from "./schedule.js";

function progressFunction(id, value) {
return function() {
get(this, id).progress = +value.apply(this, arguments);
};
}

function progressConstant(id, value) {
return value = +value, function() {
get(this, id).progress = value;
};
}

function abs(value) {
snowyu marked this conversation as resolved.
Show resolved Hide resolved
if (value < 0) value = -value;
return value;
}

export default function(value) {
var id = this._id;

return arguments.length
? this.each((typeof value === "function"
? progressFunction
: progressConstant)(id, value))
: abs(get(this.node(), id).progress);
}
29 changes: 28 additions & 1 deletion src/transition/schedule.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {dispatch} from "d3-dispatch";
import {timer, timeout} from "d3-timer";

var emptyOn = dispatch("start", "end", "cancel", "interrupt");
var emptyOn = dispatch("start", "end", "cancel", "interrupt", "progress");
var emptyTween = [];

export var CREATED = 0;
Expand Down Expand Up @@ -127,7 +127,34 @@ function create(node, id, self) {
tween.length = j + 1;
}

function getProgress(elapsed) {
if (self.paused) {
if (self.progress >= 0) {
elapsed = self._lastprogress !== self.progress ? self.progress * self.duration : -1;
} else {
self.progress = elapsed / self.duration;
}
if (self._lastprogress !== self.progress) {
self.on.call("progress", node, node.__data__, self.index, self.group, self.progress);
self._lastprogress = self.progress;
}
} else if (self.progress >= 0) {
self.timer.restart(tick, 0, self.time + self.progress * self.duration);
elapsed = self.progress = - (self.progress + 1e-10);
} else {
if (elapsed >= self.duration) {
self.progress = -1;
} else {
self.progress = - (elapsed / self.duration);
}
self.on.call("progress", node, node.__data__, self.index, self.group, -self.progress);
}
return elapsed;
}

function tick(elapsed) {
elapsed = getProgress(elapsed);
if (elapsed < 0) return;
var t = elapsed < self.duration ? self.ease.call(null, elapsed / self.duration) : (self.timer.restart(stop), self.state = ENDING, 1),
i = -1,
n = tween.length;
Expand Down
161 changes: 161 additions & 0 deletions test/transition/paused-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
var tape = require("tape"),
jsdom = require("../jsdom"),
d3_ease = require("d3-ease"),
d3_timer = require("d3-timer"),
d3_interpolate = require("d3-interpolate"),
d3_selection = require("d3-selection");

require("../../");

tape("transition.paused(true) allows pause the transition animation", function(test) {
var root = jsdom().documentElement,
ease = d3_ease.easeCubic,
duration = 100,
interpolate = d3_interpolate.interpolateNumber(0, 100),
selection = d3_selection.select(root).attr("t", 0),
transition = selection.transition().duration(duration).attr("t", 100).on("end", ended);
var beginTime = d3_timer.now();

d3_timer.timeout(function(elapsed) {
transition.paused(true);
test.strictEqual(root.__transition[transition._id].paused, true);
test.strictEqual(transition.paused(), true);
test.strictEqual(Number(root.getAttribute("t")), interpolate(ease(elapsed / duration)));
}, 50);

d3_timer.timeout(function(elapsed) {
var progress = root.__transition[transition._id].progress;
test.strictEqual(transition.progress(), progress);
test.ok(progress >= 0.5);
test.strictEqual(Number(root.getAttribute("t")), interpolate(ease(progress)));
transition.paused(false);
test.strictEqual(root.__transition[transition._id].paused, false);
test.strictEqual(transition.paused(), false);
}, 150);

function ended() {
var t = d3_timer.now() - beginTime;
test.ok(t > 150);
test.end();
}
});

tape("transition.progress() allows get the progrss of the transition animation", function(test) {
snowyu marked this conversation as resolved.
Show resolved Hide resolved
var root = jsdom().documentElement,
ease = d3_ease.easeCubic,
duration = 100,
interpolate = d3_interpolate.interpolateNumber(0, 100),
selection = d3_selection.select(root).attr("t", 0),
transition = selection.transition().duration(duration).attr("t", 100).on("end", ended);
var beginTime = d3_timer.now();
var oldProgress;

d3_timer.timeout(function(elapsed) {
// get the progress on runtime
var progress = -root.__transition[transition._id].progress;
test.strictEqual(transition.progress(), progress);
test.ok(progress >= 0.5);
test.strictEqual(Number(root.getAttribute("t")), interpolate(ease(progress)));
transition.paused(true);
oldProgress = progress;
transition.progress(progress);
}, 50);

d3_timer.timeout(function(elapsed) {
var progress = root.__transition[transition._id].progress;
test.strictEqual(transition.progress(), progress);
test.strictEqual(oldProgress, progress);
test.ok(progress >= 0.5);
test.strictEqual(Number(root.getAttribute("t")), interpolate(ease(progress)));
transition.paused(false);
}, 150);

function ended() {
var t = d3_timer.now() - beginTime;
test.ok(t > 150);
test.strictEqual(transition.progress(), 1);
test.end();
}
});

tape("transition.on(\"progress\", listener) event to notify animation progress", function(test) {
var root = jsdom().documentElement,
duration = 100,
selection = d3_selection.select(root).attr("t", 0),
transition = selection.transition().duration(duration).attr("t", 100)
.on('progress', onProgress)
snowyu marked this conversation as resolved.
Show resolved Hide resolved
.on("end", ended);
var beginTime = d3_timer.now();
var oldProgress;
var lastProgress = 0;

d3_timer.timeout(function(elapsed) {
// get the progress on runtime
var progress = -root.__transition[transition._id].progress;
test.strictEqual(transition.progress(), progress);
test.strictEqual(lastProgress, progress);
test.ok(progress >= 0.5);
transition.paused(true);
oldProgress = progress;
transition.progress(progress);
}, 50);

d3_timer.timeout(function(elapsed) {
var progress = root.__transition[transition._id].progress;
test.strictEqual(transition.progress(), progress);
test.strictEqual(oldProgress, progress);
test.strictEqual(lastProgress, progress);
test.ok(progress >= 0.5);
transition.paused(false);
}, 150);

function onProgress(data, index, grp, progress) {
test.ok(progress >= lastProgress, `${progress} >= ${lastProgress}`);
lastProgress = progress;
}

function ended() {
var t = d3_timer.now() - beginTime;
test.ok(t > 150);
test.strictEqual(transition.progress(), 1);
test.strictEqual(lastProgress, 1);
test.end();
}
});

tape("transition.on(\"progress\", listener) event should work on paused status", function(test) {
var root = jsdom().documentElement,
duration = 100,
selection = d3_selection.select(root).attr("t", 0),
transition = selection.transition().duration(duration).attr("t", 100).paused(true)
.on('progress', onProgress)
snowyu marked this conversation as resolved.
Show resolved Hide resolved
.on("end", ended);
var beginTime = d3_timer.now();
var progresses = [];

d3_timer.timeout(function(elapsed) {
test.ok(progresses.length);
test.strictEqual(transition.progress(), progresses[0]);
transition.progress(0.2);
}, 50);

d3_timer.timeout(function(elapsed) {
test.ok(progresses.length === 2, `progresses.length(${progresses.length}) === 2`);
test.strictEqual(transition.progress(), progresses[1]);
transition.progress(1);
}, 100);

function onProgress(data, index, grp, progress) {
progresses.push(progress);
}

function ended() {
var t = d3_timer.now() - beginTime;
test.ok(t >= 100);
test.ok(t < 150);
test.ok(progresses.length === 3, `progresses.length(${progresses.length}) === 3`);
test.strictEqual(progresses[2], 1);
test.strictEqual(transition.progress(), 1);
test.end();
}
});