Implement possibility to set and remove a steps range

This commit is contained in:
Nicklas Gummesson 2015-11-24 18:34:46 +01:00
parent 6f35d6399a
commit b6169fb31a
8 changed files with 201 additions and 18 deletions

View File

@ -7,7 +7,7 @@ JS_CLIENT_FILES= lib/torque/*.js \
lib/torque/gmaps/*.js \ lib/torque/gmaps/*.js \
lib/torque/leaflet/leaflet_tileloader_mixin.js \ lib/torque/leaflet/leaflet_tileloader_mixin.js \
lib/torque/leaflet/canvas_layer.js \ lib/torque/leaflet/canvas_layer.js \
lib/torque/leaflet/torque.js lib/torque/leaflet/torque.js
all: dist/torque.js dist/torque.full.js add-header all: dist/torque.js dist/torque.full.js add-header
@ -47,7 +47,7 @@ test-acceptance: clean-results
test-all: test test-acceptance test-all: test test-acceptance
clean: clean:
rm -rf dist rm -rf dist
.PHONY: clean dist_folder .PHONY: clean dist_folder

View File

@ -0,0 +1,24 @@
/**
* Abstract handler for animator steps
*/
var AnimatorStepsRange = function(start, end) {
if (start < 0) throw new Error('start must be a positive number');
if (start >= end) throw new Error('start must be smaller than end');
this.start = start;
this.end = end;
};
AnimatorStepsRange.prototype = {
diff: function() {
return this.end - this.start;
},
isLast: function(step) {
// round step into an integer, to be able to compare number as expected (also converts bad input to 0)
return (step | 0) === this.end;
}
};
module.exports = AnimatorStepsRange;

View File

@ -1,4 +1,5 @@
var torque = require('./'); var torque = require('./');
var StepsRange = require('./animator-steps-range');
var requestAnimationFrame = global.requestAnimationFrame var requestAnimationFrame = global.requestAnimationFrame
|| global.mozRequestAnimationFrame || global.mozRequestAnimationFrame
@ -35,18 +36,16 @@ var cancelAnimationFrame = global.cancelAnimationFrame
loop: options.loop === undefined ? true : options.loop loop: options.loop === undefined ? true : options.loop
}, this.options); }, this.options);
this.rescale(); this.steps(options.steps);
} }
Animator.prototype = { Animator.prototype = {
start: function() { start: function() {
this.running = true; this.running = true;
requestAnimationFrame(this._tick); requestAnimationFrame(this._tick);
this.options.onStart && this.options.onStart(); this.options.onStart && this.options.onStart();
if(this.options.steps === 1){ if (this.stepsRange().diff() === 1) {
this.running = false; this.running = false;
} }
}, },
@ -57,7 +56,7 @@ var cancelAnimationFrame = global.cancelAnimationFrame
stop: function() { stop: function() {
this.pause(); this.pause();
this.time(0); this.time(this.stepsRange().start);
this.options.onStop && this.options.onStop(); this.options.onStop && this.options.onStop();
}, },
@ -80,7 +79,7 @@ var cancelAnimationFrame = global.cancelAnimationFrame
rescale: function() { rescale: function() {
this.domainInv = torque.math.linear(this.options.animationDelay, this.options.animationDelay + this.options.animationDuration); this.domainInv = torque.math.linear(this.options.animationDelay, this.options.animationDelay + this.options.animationDuration);
this.domain = this.domainInv.invert(); this.domain = this.domainInv.invert();
this.range = torque.math.linear(0, this.options.steps); this.range = torque.math.linear(0, this._defaultStepsRange.end);
this.rangeInv = this.range.invert(); this.rangeInv = this.range.invert();
this.time(this._time); this.time(this._time);
this.running? this.start(): this.pause(); this.running? this.start(): this.pause();
@ -99,9 +98,34 @@ var cancelAnimationFrame = global.cancelAnimationFrame
steps: function(_) { steps: function(_) {
this.options.steps = _; this.options.steps = _;
this._defaultStepsRange = new StepsRange(0, _);
return this.rescale(); return this.rescale();
}, },
// Returns or sets a (custom) steps range
// Setting a steps range must be within the full range
stepsRange: function(start, end) {
if (arguments.length === 2) {
if (start < this._defaultStepsRange.start) throw new Error('start must be within default steps range');
if (end > this._defaultStepsRange.end) throw new Error('end must be within default steps range');
this._customStepsRange = new StepsRange(start, end);
this.options.onStepsRange && this.options.onStepsRange();
// Change current step if it's outside the new custom range
var step = this.step() | 0; // round to an integer
if (step < start || step > end) {
this.step(start);
}
}
return this._customStepsRange || this._defaultStepsRange;
},
removeCustomStepsRange: function() {
this._customStepsRange = undefined;
this.options.onStepsRange && this.options.onStepsRange();
},
step: function(s) { step: function(s) {
if(arguments.length === 0) return this.range(this.domain(this._time)); if(arguments.length === 0) return this.range(this.domain(this._time));
this._time = this.domainInv(this.rangeInv(s)); this._time = this.domainInv(this.rangeInv(s));
@ -121,13 +145,15 @@ var cancelAnimationFrame = global.cancelAnimationFrame
delta = Math.min(this.options.maxDelta, delta); delta = Math.min(this.options.maxDelta, delta);
this._t0 = t1; this._t0 = t1;
this._time += delta; this._time += delta;
if(this.step() >= this.options.steps) {
var stepsRange = this.stepsRange();
if (stepsRange.isLast(this.step())) {
if(!this.options.loop){ if(!this.options.loop){
// set time to max time // set time to max time
this.time(this.options.animationDuration); this.time(this.options.animationDuration);
this.pause(); this.pause();
} else { } else {
this._time = 0; this.step(stepsRange.start);
} }
} }
if(this.running) { if(this.running) {

View File

@ -43,6 +43,9 @@ function GMapsTorqueLayer(options) {
}, },
onStart: function() { onStart: function() {
self.fire('play'); self.fire('play');
},
onStepsRange: function() {
self.fire('change:stepsRange', self.animator.stepsRange());
} }
})); }));
@ -237,7 +240,7 @@ GMapsTorqueLayer.prototype = torque.extend({},
}, },
/** /**
* helper function, does the same than ``setKey`` but only * helper function, does the same than ``setKey`` but only
* accepts scalars. * accepts scalars.
*/ */
setStep: function(time) { setStep: function(time) {
@ -248,10 +251,10 @@ GMapsTorqueLayer.prototype = torque.extend({},
}, },
/** /**
* transform from animation step to Date object * transform from animation step to Date object
* that contains the animation time * that contains the animation time
* *
* ``step`` should be between 0 and ``steps - 1`` * ``step`` should be between 0 and ``steps - 1``
*/ */
stepToTime: function(step) { stepToTime: function(step) {
if (!this.provider) return 0; if (!this.provider) return 0;
@ -369,7 +372,7 @@ GMapsTorqueLayer.prototype = torque.extend({},
} }
return sum; return sum;
}, },
error: function (callback) { error: function (callback) {
this.options.errorCallback = callback; this.options.errorCallback = callback;
return this; return this;
@ -450,6 +453,18 @@ GMapsTiledTorqueLayer.prototype = torque.extend({}, CanvasTileLayer.prototype, {
setCartoCSS: function(cartocss) { setCartoCSS: function(cartocss) {
if (!this.renderer) throw new Error('renderer is not valid'); if (!this.renderer) throw new Error('renderer is not valid');
return this.renderer.setCartoCSS(cartocss); return this.renderer.setCartoCSS(cartocss);
},
setStepsRange: function(start, end) {
this.animator.stepsRange(start, end);
},
removeStepsRange: function() {
this.animator.removeCustomStepsRange();
},
getStepsRange: function() {
return this.animator.stepsRange();
} }
}); });

View File

@ -51,6 +51,9 @@ L.TorqueLayer = L.CanvasLayer.extend({
}, },
onStart: function() { onStart: function() {
self.fire('play'); self.fire('play');
},
onStepsRange: function() {
self.fire('change:stepsRange', self.animator.stepsRange());
} }
})); }));
@ -428,5 +431,17 @@ L.TorqueLayer = L.CanvasLayer.extend({
invalidate: function() { invalidate: function() {
this.provider.reload(); this.provider.reload();
},
setStepsRange: function(start, end) {
this.animator.stepsRange(start, end);
},
removeStepsRange: function() {
this.animator.removeCustomStepsRange();
},
getStepsRange: function() {
return this.animator.stepsRange();
} }
}); });

View File

@ -0,0 +1,34 @@
var AnimatorStepsRange = require('../lib/torque/animator-steps-range');
QUnit.module('animator-steps-range');
test('start and end props are available', function(assert) {
var stepsRange = validStepsRange();
assert.equal(stepsRange.start, 0);
assert.equal(stepsRange.end, 4);
});
test('.lastStep returns true if given last step', function(assert) {
var stepsRange = validStepsRange();
assert.ok(stepsRange.isLast(stepsRange.end));
assert.notOk(stepsRange.isLast(3));
assert.notOk(stepsRange.isLast(42));
assert.notOk(stepsRange.isLast(true));
assert.notOk(stepsRange.isLast());
assert.notOk(stepsRange.isLast('whatever'));
});
test('.diff returns the steps between start and end', function(assert) {
var stepsRange = validStepsRange();
assert.equal(stepsRange.diff(), 4);
});
test('throws error in inconsistent range', function(assert) {
assert.throws(function() { new AnimatorStepsRange(4, 3) });
assert.throws(function() { new AnimatorStepsRange(4, 4) });
});
function validStepsRange() {
return new AnimatorStepsRange(0, 4);
}

View File

@ -39,12 +39,66 @@ test("onStart runs properly", function(assert){
animator.stop(); animator.stop();
}); });
test("stop should take the pointer to position zero", function(assert){ test(".stepsRange sets a custom range with valid input", function(assert){
var animator = new torque.Animator(function(){}, {steps: 500, animationDuration: 2});
var customStepsRange = animator.stepsRange(101, 202);
assert.ok(customStepsRange);
assert.equal(customStepsRange.start, 101);
assert.equal(customStepsRange.end, 202);
var didCallOnStepsRange = false;
var animator = new torque.Animator(function(){}, {steps: 500, animationDuration: 2,
onStepsRange: function() {
didCallOnStepsRange = true;
}
});
animator.stepsRange(101, 202);
assert.ok(didCallOnStepsRange);
});
test(".stepsRange throws error if given range is outside default range", function(assert){
var animator = new torque.Animator(function(){}, {steps: 500, animationDuration: 2});
assert.throws(function() { animator.stepsRange(1, 501) });
assert.throws(function() { animator.stepsRange(-1, 500) });
assert.throws(function() { animator.stepsRange(-1, 9000) });
});
test(".removeCustomStepsRange should remove any custom steps range", function(assert){
var animator = new torque.Animator(function(){}, {steps: 500, animationDuration: 2});
animator.removeCustomStepsRange();
animator.stepsRange(101, 202);
var customStepsRange = animator.stepsRange();
animator.removeCustomStepsRange();
var defaultStepsRange = animator.stepsRange();
assert.ok(defaultStepsRange);
assert.notEqual(defaultStepsRange, customStepsRange);
assert.equal(defaultStepsRange.start, 0);
assert.equal(defaultStepsRange.end, 500);
var didCallOnStepsRange = false;
var animator = new torque.Animator(function(){}, {steps: 500, animationDuration: 2,
onStepsRange: function() {
didCallOnStepsRange = true;
}
});
animator.stepsRange(101, 202);
animator.removeCustomStepsRange();
assert.ok(didCallOnStepsRange);
});
test("stop should take the pointer to position zero by default", function(assert){
var animator = new torque.Animator(function(){}, {steps: 500, animationDuration: 2}); var animator = new torque.Animator(function(){}, {steps: 500, animationDuration: 2});
animator.stop() animator.stop()
assert.equal(animator._time, 0); assert.equal(animator._time, 0);
}); });
test("stop should take the pointer to start position for custom range", function(assert){
var animator = new torque.Animator(function(){}, {steps: 500, animationDuration: 2});
animator.stepsRange(42, 137);
animator.stop()
assert.equal(animator._time, 42);
});
test("stop should call onStop", function(assert){ test("stop should call onStop", function(assert){
var animator = new torque.Animator(function(){}, {steps: 500, animationDuration: 2}); var animator = new torque.Animator(function(){}, {steps: 500, animationDuration: 2});
animator.options.onStop = function(){ animator.options.onStop = function(){
@ -61,14 +115,29 @@ test("altering steps should rescale", function(assert){
assert.ok(animator.rescale.calledOnce); assert.ok(animator.rescale.calledOnce);
}); });
test("tick should set time to zero if steps are bigger than range", function(assert){ test("tick should set time to zero if steps are bigger than default range", function(assert){
var done = assert.async(); var done = assert.async();
var animatorb = new torque.Animator(function(){}, {steps: 500, animationDuration: 2}); var animatorb = new torque.Animator(function(){}, {steps: 500, animationDuration: 2});
animatorb.start(); animatorb.start();
animatorb.step(800); animatorb.step(800);
setTimeout(function(){ setTimeout(function(){
console.log(animatorb.step()); console.log(animatorb.step());
assert.ok(animatorb.step() < 800); assert.ok(animatorb.step() | 0 === 0);
done();
}, 20);
animatorb.pause();
});
test("tick should set time to start step if steps are bigger than custom range", function(assert){
var done = assert.async();
var animatorb = new torque.Animator(function(){}, {steps: 500, animationDuration: 2});
animatorb.start();
animatorb.step(800);
animatorb.stepsRange(42, 137);
setTimeout(function(){
console.log(animatorb.step());
// round step to an integer
assert.ok(animatorb.step() | 0 === 42);
done(); done();
}, 20); }, 20);
animatorb.pause(); animatorb.pause();
@ -80,5 +149,4 @@ test("tick should pause animation on end if loop is disabled", function(assert){
animator.toggle(); animator.toggle();
animator.step(600); animator.step(600);
assert.equal(animator._time,animator.options.animationDuration); assert.equal(animator._time,animator.options.animationDuration);
}); });

View File

@ -9,3 +9,4 @@ require('./provider.windshaft.test');
require('./provider.json'); require('./provider.json');
require('./request'); require('./request');
require('./animator'); require('./animator');
require('./animator-steps-range');