From: Simon Brunel Date: Sat, 25 Feb 2017 10:51:47 +0000 (+0100) Subject: Flatten animation object and fix callbacks X-Git-Tag: v2.6.0~2^2~51 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=b92b256872e9d01ef56c9b8d4440cda784662fe0;p=thirdparty%2FChart.js.git Flatten animation object and fix callbacks 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. --- diff --git a/docs/01-Chart-Configuration.md b/docs/01-Chart-Configuration.md index b01af7fb9..104c6953a 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 b0c495a02..d460bc88e 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 c0f4a35f7..ad450df8e 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 ddd02e7af..1090aa9b8 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 c32e9a76c..2d96ac14b 100644 --- a/src/core/core.helpers.js +++ b/src/core/core.helpers.js @@ -957,9 +957,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) { @@ -968,4 +968,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 4584f0427..de453e64a 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 a6b4f70f5..63f383ed6 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() {