diff --git a/docs/01-Chart-Configuration.md b/docs/01-Chart-Configuration.md index b01af7fb9c3..104c6953ad8 100644 --- a/docs/01-Chart-Configuration.md +++ b/docs/01-Chart-Configuration.md @@ -339,29 +339,18 @@ Name | Type | Default | Description --- |:---:| --- | --- 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, @@ -382,6 +371,8 @@ The animation object passed to the callbacks is of type `Chart.Animation`. The o } ``` +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`. diff --git a/samples/animation/progress-bar.html b/samples/animation/progress-bar.html index b0c495a02db..d460bc88e2a 100644 --- a/samples/animation/progress-bar.html +++ b/samples/animation/progress-bar.html @@ -33,12 +33,12 @@ borderColor: window.chartColors.red, backgroundColor: window.chartColors.red, data: [ - randomScalingFactor(), - randomScalingFactor(), - randomScalingFactor(), - randomScalingFactor(), - randomScalingFactor(), - randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), randomScalingFactor() ] }, { @@ -47,12 +47,12 @@ borderColor: window.chartColors.blue, backgroundColor: window.chartColors.blue, data: [ - randomScalingFactor(), - randomScalingFactor(), - randomScalingFactor(), - randomScalingFactor(), - randomScalingFactor(), - randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), + randomScalingFactor(), randomScalingFactor() ] }] @@ -65,7 +65,7 @@ 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() { diff --git a/src/core/core.animation.js b/src/core/core.animation.js index c0f4a35f707..ad450df8e2a 100644 --- a/src/core/core.animation.js +++ b/src/core/core.animation.js @@ -13,13 +13,14 @@ module.exports = function(Chart) { }; 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 = { @@ -29,42 +30,39 @@ module.exports = function(Chart) { 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) { @@ -72,6 +70,7 @@ module.exports = function(Chart) { chart.animating = false; } }, + requestAnimationFrame: function() { var me = this; if (me.request === null) { @@ -84,9 +83,12 @@ module.exports = function(Chart) { }); } }, + + /** + * @private + */ startDigest: function() { var me = this; - var startTime = Date.now(); var framesToDrop = 0; @@ -95,46 +97,72 @@ module.exports = function(Chart) { 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; } - }; + }); + }; diff --git a/src/core/core.controller.js b/src/core/core.controller.js index ddd02e7afde..1090aa9b80d 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -445,36 +445,34 @@ module.exports = function(Chart) { } 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; diff --git a/src/core/core.helpers.js b/src/core/core.helpers.js index f95af269f6b..d5780ed2f36 100644 --- a/src/core/core.helpers.js +++ b/src/core/core.helpers.js @@ -955,9 +955,9 @@ module.exports = function(Chart) { 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) { @@ -966,4 +966,12 @@ module.exports = function(Chart) { 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; }; diff --git a/src/core/core.scale.js b/src/core/core.scale.js index b7d04354755..7c9b27fa075 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -93,7 +93,7 @@ module.exports = function(Chart) { // 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; @@ -146,13 +146,13 @@ module.exports = function(Chart) { }, 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; @@ -177,29 +177,29 @@ module.exports = function(Chart) { 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; @@ -208,13 +208,13 @@ module.exports = function(Chart) { 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; @@ -257,13 +257,13 @@ module.exports = function(Chart) { 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; @@ -381,7 +381,7 @@ module.exports = function(Chart) { }, afterFit: function() { - helpers.callCallback(this.options.afterFit, [this]); + helpers.callback(this.options.afterFit, [this]); }, // Shared Methods diff --git a/test/global.deprecations.tests.js b/test/global.deprecations.tests.js index a6b4f70f55c..63f383ed6cd 100644 --- a/test/global.deprecations.tests.js +++ b/test/global.deprecations.tests.js @@ -39,6 +39,53 @@ describe('Deprecations', function() { 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() {