Animation callbacks now receives `animationObject` directly with a reference on the associated chart (`animation.chart`), which deprecates `animation.animationObject` and `animation.chartInstance`. Also fix missing `onComplete` animation argument and make sure that an animation object is passed even when animations are disabled.
--- |:---:| --- | ---
duration | Number | 1000 | The number of milliseconds an animation takes.
easing | String | "easeOutQuart" | Easing function to use. Available options are: `'linear'`, `'easeInQuad'`, `'easeOutQuad'`, `'easeInOutQuad'`, `'easeInCubic'`, `'easeOutCubic'`, `'easeInOutCubic'`, `'easeInQuart'`, `'easeOutQuart'`, `'easeInOutQuart'`, `'easeInQuint'`, `'easeOutQuint'`, `'easeInOutQuint'`, `'easeInSine'`, `'easeOutSine'`, `'easeInOutSine'`, `'easeInExpo'`, `'easeOutExpo'`, `'easeInOutExpo'`, `'easeInCirc'`, `'easeOutCirc'`, `'easeInOutCirc'`, `'easeInElastic'`, `'easeOutElastic'`, `'easeInOutElastic'`, `'easeInBack'`, `'easeOutBack'`, `'easeInOutBack'`, `'easeInBounce'`, `'easeOutBounce'`, `'easeInOutBounce'`. See [Robert Penner's easing equations](http://robertpenner.com/easing/).
-onProgress | Function | none | Callback called on each step of an animation. Passed a single argument, an object, containing the chart instance and an object with details of the animation.
-onComplete | Function | none | Callback called at the end of an animation. Passed the same arguments as `onProgress`
+onProgress | Function | none | Callback called on each step of an animation. Passed a single argument, a `Chart.Animation` instance, see below.
+onComplete | Function | none | Callback called at the end of an animation. Passed a single argument, a `Chart.Animation` instance, see below.
#### Animation Callbacks
-The `onProgress` and `onComplete` callbacks are useful for synchronizing an external draw to the chart animation. The callback is passed an object that implements the following interface. An example usage of these callbacks can be found on [Github](https://github.com/chartjs/Chart.js/blob/master/samples/animation/progress-bar.html). This sample displays a progress bar showing how far along the animation is.
+The `onProgress` and `onComplete` callbacks are useful for synchronizing an external draw to the chart animation. The callback is passed a `Chart.Animation` instance:
```javascript
{
// Chart instance
chart,
- // Contains details of the on-going animation
- animationObject,
-}
-```
-
-#### Animation Object
-
-The animation object passed to the callbacks is of type `Chart.Animation`. The object has the following parameters.
-
-```javascript
-{
// Current Animation frame number
currentStep: Number,
}
```
+An example usage of these callbacks can be found on [Github](https://github.com/chartjs/Chart.js/blob/master/samples/animation/progress-bar.html): this sample displays a progress bar showing how far along the animation is.
+
### Element Configuration
The global options for elements are defined in `Chart.defaults.global.elements`.
borderColor: window.chartColors.red,
backgroundColor: window.chartColors.red,
data: [
- randomScalingFactor(),
- randomScalingFactor(),
- randomScalingFactor(),
- randomScalingFactor(),
- randomScalingFactor(),
- randomScalingFactor(),
+ randomScalingFactor(),
+ randomScalingFactor(),
+ randomScalingFactor(),
+ randomScalingFactor(),
+ randomScalingFactor(),
+ randomScalingFactor(),
randomScalingFactor()
]
}, {
borderColor: window.chartColors.blue,
backgroundColor: window.chartColors.blue,
data: [
- randomScalingFactor(),
- randomScalingFactor(),
- randomScalingFactor(),
- randomScalingFactor(),
- randomScalingFactor(),
- randomScalingFactor(),
+ randomScalingFactor(),
+ randomScalingFactor(),
+ randomScalingFactor(),
+ randomScalingFactor(),
+ randomScalingFactor(),
+ randomScalingFactor(),
randomScalingFactor()
]
}]
animation: {
duration: 2000,
onProgress: function(animation) {
- progress.value = animation.animationObject.currentStep / animation.animationObject.numSteps;
+ progress.value = animation.currentStep / animation.numSteps;
},
onComplete: function(animation) {
window.setTimeout(function() {
};
Chart.Animation = Chart.Element.extend({
- currentStep: null, // the current animation step
+ chart: null, // the animation associated chart instance
+ currentStep: 0, // the current animation step
numSteps: 60, // default number of steps
easing: '', // the easing to use for this animation
render: null, // render function used by the animation service
onAnimationProgress: null, // user specified callback to fire on each step of the animation
- onAnimationComplete: null // user specified callback to fire when the animation finishes
+ onAnimationComplete: null, // user specified callback to fire when the animation finishes
});
Chart.animationService = {
request: null,
/**
- * @function Chart.animationService.addAnimation
- * @param chart {ChartController} the chart to animate
- * @param animationObject {IAnimation} the animation that we will animate
- * @param duration {Number} length of animation in ms
- * @param lazy {Boolean} if true, the chart is not marked as animating to enable more responsive interactions
+ * @param {Chart} chart - The chart to animate.
+ * @param {Chart.Animation} animation - The animation that we will animate.
+ * @param {Number} duration - The animation duration in ms.
+ * @param {Boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions
*/
- addAnimation: function(chart, animationObject, duration, lazy) {
- var me = this;
+ addAnimation: function(chart, animation, duration, lazy) {
+ var animations = this.animations;
+ var i, ilen;
+
+ animation.chart = chart;
if (!lazy) {
chart.animating = true;
}
- for (var index = 0; index < me.animations.length; ++index) {
- if (me.animations[index].chart === chart) {
- // replacing an in progress animation
- me.animations[index].animationObject = animationObject;
+ for (i=0, ilen=animations.length; i < ilen; ++i) {
+ if (animations[i].chart === chart) {
+ animations[i] = animation;
return;
}
}
- me.animations.push({
- chart: chart,
- chartInstance: chart, // deprecated, backward compatibility
- animationObject: animationObject
- });
+ animations.push(animation);
// If there are no animations queued, manually kickstart a digest, for lack of a better word
- if (me.animations.length === 1) {
- me.requestAnimationFrame();
+ if (animations.length === 1) {
+ this.requestAnimationFrame();
}
},
- // Cancel the animation for a given chart instance
+
cancelAnimation: function(chart) {
- var index = helpers.findIndex(this.animations, function(animationWrapper) {
- return animationWrapper.chart === chart;
+ var index = helpers.findIndex(this.animations, function(animation) {
+ return animation.chart === chart;
});
if (index !== -1) {
chart.animating = false;
}
},
+
requestAnimationFrame: function() {
var me = this;
if (me.request === null) {
});
}
},
+
+ /**
+ * @private
+ */
startDigest: function() {
var me = this;
-
var startTime = Date.now();
var framesToDrop = 0;
me.dropFrames = me.dropFrames % 1;
}
- var i = 0;
- while (i < me.animations.length) {
- if (me.animations[i].animationObject.currentStep === null) {
- me.animations[i].animationObject.currentStep = 0;
- }
+ me.advance(1 + framesToDrop);
- me.animations[i].animationObject.currentStep += 1 + framesToDrop;
+ var endTime = Date.now();
- if (me.animations[i].animationObject.currentStep > me.animations[i].animationObject.numSteps) {
- me.animations[i].animationObject.currentStep = me.animations[i].animationObject.numSteps;
- }
+ me.dropFrames += (endTime - startTime) / me.frameDuration;
- me.animations[i].animationObject.render(me.animations[i].chart, me.animations[i].animationObject);
- if (me.animations[i].animationObject.onAnimationProgress && me.animations[i].animationObject.onAnimationProgress.call) {
- me.animations[i].animationObject.onAnimationProgress.call(me.animations[i].chart, me.animations[i]);
- }
+ // Do we have more stuff to animate?
+ if (me.animations.length > 0) {
+ me.requestAnimationFrame();
+ }
+ },
- if (me.animations[i].animationObject.currentStep === me.animations[i].animationObject.numSteps) {
- if (me.animations[i].animationObject.onAnimationComplete && me.animations[i].animationObject.onAnimationComplete.call) {
- me.animations[i].animationObject.onAnimationComplete.call(me.animations[i].chart, me.animations[i]);
- }
+ /**
+ * @private
+ */
+ advance: function(count) {
+ var animations = this.animations;
+ var animation, chart;
+ var i = 0;
+
+ while (i < animations.length) {
+ animation = animations[i];
+ chart = animation.chart;
+
+ animation.currentStep = (animation.currentStep || 0) + count;
+ animation.currentStep = Math.min(animation.currentStep, animation.numSteps);
- // executed the last frame. Remove the animation.
- me.animations[i].chart.animating = false;
+ helpers.callback(animation.render, [chart, animation], chart);
+ helpers.callback(animation.onAnimationProgress, [animation], chart);
- me.animations.splice(i, 1);
+ if (animation.currentStep >= animation.numSteps) {
+ helpers.callback(animation.onAnimationComplete, [animation], chart);
+ chart.animating = false;
+ animations.splice(i, 1);
} else {
++i;
}
}
+ }
+ };
- var endTime = Date.now();
- var dropFrames = (endTime - startTime) / me.frameDuration;
-
- me.dropFrames += dropFrames;
+ /**
+ * Provided for backward compatibility, use Chart.Animation instead
+ * @prop Chart.Animation#animationObject
+ * @deprecated since version 2.6.0
+ * @todo remove at version 3
+ */
+ Object.defineProperty(Chart.Animation.prototype, 'animationObject', {
+ get: function() {
+ return this;
+ }
+ });
- // Do we have more stuff to animate?
- if (me.animations.length > 0) {
- me.requestAnimationFrame();
- }
+ /**
+ * Provided for backward compatibility, use Chart.Animation#chart instead
+ * @prop Chart.Animation#chartInstance
+ * @deprecated since version 2.6.0
+ * @todo remove at version 3
+ */
+ Object.defineProperty(Chart.Animation.prototype, 'chartInstance', {
+ get: function() {
+ return this.chart;
+ },
+ set: function(value) {
+ this.chart = value;
}
- };
+ });
+
};
}
var animationOptions = me.options.animation;
- var onComplete = function() {
+ var onComplete = function(animation) {
plugins.notify(me, 'afterRender');
- var callback = animationOptions && animationOptions.onComplete;
- if (callback && callback.call) {
- callback.call(me);
- }
+ helpers.callback(animationOptions && animationOptions.onComplete, [animation], me);
};
if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) {
- var animation = new Chart.Animation();
- animation.numSteps = (duration || animationOptions.duration) / 16.66; // 60 fps
- animation.easing = animationOptions.easing;
+ var animation = new Chart.Animation({
+ numSteps: (duration || animationOptions.duration) / 16.66, // 60 fps
+ easing: animationOptions.easing,
- // render function
- animation.render = function(chart, animationObject) {
- var easingFunction = helpers.easingEffects[animationObject.easing];
- var stepDecimal = animationObject.currentStep / animationObject.numSteps;
- var easeDecimal = easingFunction(stepDecimal);
+ render: function(chart, animationObject) {
+ var easingFunction = helpers.easingEffects[animationObject.easing];
+ var currentStep = animationObject.currentStep;
+ var stepDecimal = currentStep / animationObject.numSteps;
- chart.draw(easeDecimal, stepDecimal, animationObject.currentStep);
- };
+ chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep);
+ },
- // user events
- animation.onAnimationProgress = animationOptions.onProgress;
- animation.onAnimationComplete = onComplete;
+ onAnimationProgress: animationOptions.onProgress,
+ onAnimationComplete: onComplete
+ });
Chart.animationService.addAnimation(me, animation, duration, lazy);
} else {
me.draw();
- onComplete();
+
+ // See https://github.com/chartjs/Chart.js/issues/3781
+ onComplete(new Chart.Animation({numSteps: 0, chart: me}));
}
return me;
return true;
};
- helpers.callCallback = function(fn, args, _tArg) {
+ helpers.callback = function(fn, args, thisArg) {
if (fn && typeof fn.call === 'function') {
- fn.apply(_tArg, args);
+ fn.apply(thisArg, args);
}
};
helpers.getHoverColor = function(colorValue) {
colorValue :
helpers.color(colorValue).saturate(0.5).darken(0.1).rgbString();
};
+
+ /**
+ * Provided for backward compatibility, use Chart.helpers#callback instead.
+ * @function Chart.helpers#callCallback
+ * @deprecated since version 2.6.0
+ * @todo remove at version 3
+ */
+ helpers.callCallback = helpers.callback;
};
// Any function can be extended by the scale type
beforeUpdate: function() {
- helpers.callCallback(this.options.beforeUpdate, [this]);
+ helpers.callback(this.options.beforeUpdate, [this]);
},
update: function(maxWidth, maxHeight, margins) {
var me = this;
},
afterUpdate: function() {
- helpers.callCallback(this.options.afterUpdate, [this]);
+ helpers.callback(this.options.afterUpdate, [this]);
},
//
beforeSetDimensions: function() {
- helpers.callCallback(this.options.beforeSetDimensions, [this]);
+ helpers.callback(this.options.beforeSetDimensions, [this]);
},
setDimensions: function() {
var me = this;
me.paddingBottom = 0;
},
afterSetDimensions: function() {
- helpers.callCallback(this.options.afterSetDimensions, [this]);
+ helpers.callback(this.options.afterSetDimensions, [this]);
},
// Data limits
beforeDataLimits: function() {
- helpers.callCallback(this.options.beforeDataLimits, [this]);
+ helpers.callback(this.options.beforeDataLimits, [this]);
},
determineDataLimits: helpers.noop,
afterDataLimits: function() {
- helpers.callCallback(this.options.afterDataLimits, [this]);
+ helpers.callback(this.options.afterDataLimits, [this]);
},
//
beforeBuildTicks: function() {
- helpers.callCallback(this.options.beforeBuildTicks, [this]);
+ helpers.callback(this.options.beforeBuildTicks, [this]);
},
buildTicks: helpers.noop,
afterBuildTicks: function() {
- helpers.callCallback(this.options.afterBuildTicks, [this]);
+ helpers.callback(this.options.afterBuildTicks, [this]);
},
beforeTickToLabelConversion: function() {
- helpers.callCallback(this.options.beforeTickToLabelConversion, [this]);
+ helpers.callback(this.options.beforeTickToLabelConversion, [this]);
},
convertTicksToLabels: function() {
var me = this;
me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback);
},
afterTickToLabelConversion: function() {
- helpers.callCallback(this.options.afterTickToLabelConversion, [this]);
+ helpers.callback(this.options.afterTickToLabelConversion, [this]);
},
//
beforeCalculateTickRotation: function() {
- helpers.callCallback(this.options.beforeCalculateTickRotation, [this]);
+ helpers.callback(this.options.beforeCalculateTickRotation, [this]);
},
calculateTickRotation: function() {
var me = this;
me.labelRotation = labelRotation;
},
afterCalculateTickRotation: function() {
- helpers.callCallback(this.options.afterCalculateTickRotation, [this]);
+ helpers.callback(this.options.afterCalculateTickRotation, [this]);
},
//
beforeFit: function() {
- helpers.callCallback(this.options.beforeFit, [this]);
+ helpers.callback(this.options.beforeFit, [this]);
},
fit: function() {
var me = this;
},
afterFit: function() {
- helpers.callCallback(this.options.afterFit, [this]);
+ helpers.callback(this.options.afterFit, [this]);
},
// Shared Methods
expect(proxy.width).toBe(140);
});
});
+
+ describe('Chart.Animation.animationObject', function() {
+ it('should be defined and an alias of Chart.Animation', function(done) {
+ var animation = null;
+
+ acquireChart({
+ options: {
+ animation: {
+ duration: 50,
+ onComplete: function(arg) {
+ animation = arg;
+ }
+ }
+ }
+ });
+
+ setTimeout(function() {
+ expect(animation).not.toBeNull();
+ expect(animation.animationObject).toBeDefined();
+ expect(animation.animationObject).toBe(animation);
+ done();
+ }, 200);
+ });
+ });
+
+ describe('Chart.Animation.chartInstance', function() {
+ it('should be defined and an alias of Chart.Animation.chart', function(done) {
+ var animation = null;
+ var chart = acquireChart({
+ options: {
+ animation: {
+ duration: 50,
+ onComplete: function(arg) {
+ animation = arg;
+ }
+ }
+ }
+ });
+
+ setTimeout(function() {
+ expect(animation).not.toBeNull();
+ expect(animation.chartInstance).toBeDefined();
+ expect(animation.chartInstance).toBe(chart);
+ done();
+ }, 200);
+ });
+ });
});
describe('Version 2.5.0', function() {