Skip to content

Commit

Permalink
Make Chart.Animation/animations/Tooltip importable (chartjs#5382)
Browse files Browse the repository at this point in the history
  • Loading branch information
simonbrunel committed Apr 2, 2018
1 parent f5e173e commit 83df0e1
Show file tree
Hide file tree
Showing 8 changed files with 975 additions and 921 deletions.
1 change: 1 addition & 0 deletions .htmllintrc
@@ -1,5 +1,6 @@
{
"indent-style": "tabs",
"line-end-style": false,
"attr-quote-style": "double",
"spec-char-escape": false,
"attr-bans": [
Expand Down
5 changes: 3 additions & 2 deletions src/chart.js
Expand Up @@ -8,6 +8,8 @@ Chart.helpers = require('./helpers/index');
// @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests!
require('./core/core.helpers')(Chart);

Chart.Animation = require('./core/core.animation');
Chart.animationService = require('./core/core.animations');
Chart.defaults = require('./core/core.defaults');
Chart.Element = require('./core/core.element');
Chart.elements = require('./elements/index');
Expand All @@ -16,13 +18,12 @@ Chart.layouts = require('./core/core.layouts');
Chart.platform = require('./platforms/platform');
Chart.plugins = require('./core/core.plugins');
Chart.Ticks = require('./core/core.ticks');
Chart.Tooltip = require('./core/core.tooltip');

require('./core/core.animation')(Chart);
require('./core/core.controller')(Chart);
require('./core/core.datasetController')(Chart);
require('./core/core.scaleService')(Chart);
require('./core/core.scale')(Chart);
require('./core/core.tooltip')(Chart);

require('./scales/scale.linearbase')(Chart);
require('./scales/scale.category')(Chart);
Expand Down
201 changes: 36 additions & 165 deletions src/core/core.animation.js
@@ -1,172 +1,43 @@
/* global window: false */
'use strict';

var defaults = require('./core.defaults');
var Element = require('./core.element');
var helpers = require('../helpers/index');

defaults._set('global', {
animation: {
duration: 1000,
easing: 'easeOutQuart',
onProgress: helpers.noop,
onComplete: helpers.noop
}
});

module.exports = function(Chart) {

Chart.Animation = Element.extend({
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
});

Chart.animationService = {
frameDuration: 17,
animations: [],
dropFrames: 0,
request: null,

/**
* @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, animation, duration, lazy) {
var animations = this.animations;
var i, ilen;

animation.chart = chart;

if (!lazy) {
chart.animating = true;
}

for (i = 0, ilen = animations.length; i < ilen; ++i) {
if (animations[i].chart === chart) {
animations[i] = animation;
return;
}
}

animations.push(animation);

// If there are no animations queued, manually kickstart a digest, for lack of a better word
if (animations.length === 1) {
this.requestAnimationFrame();
}
},

cancelAnimation: function(chart) {
var index = helpers.findIndex(this.animations, function(animation) {
return animation.chart === chart;
});

if (index !== -1) {
this.animations.splice(index, 1);
chart.animating = false;
}
},

requestAnimationFrame: function() {
var me = this;
if (me.request === null) {
// Skip animation frame requests until the active one is executed.
// This can happen when processing mouse events, e.g. 'mousemove'
// and 'mouseout' events will trigger multiple renders.
me.request = helpers.requestAnimFrame.call(window, function() {
me.request = null;
me.startDigest();
});
}
},

/**
* @private
*/
startDigest: function() {
var me = this;
var startTime = Date.now();
var framesToDrop = 0;
var exports = module.exports = Element.extend({
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

if (me.dropFrames > 1) {
framesToDrop = Math.floor(me.dropFrames);
me.dropFrames = me.dropFrames % 1;
}

me.advance(1 + framesToDrop);

var endTime = Date.now();

me.dropFrames += (endTime - startTime) / me.frameDuration;

// Do we have more stuff to animate?
if (me.animations.length > 0) {
me.requestAnimationFrame();
}
},

/**
* @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);

helpers.callback(animation.render, [chart, animation], chart);
helpers.callback(animation.onAnimationProgress, [animation], chart);

if (animation.currentStep >= animation.numSteps) {
helpers.callback(animation.onAnimationComplete, [animation], chart);
chart.animating = false;
animations.splice(i, 1);
} else {
++i;
}
}
}
};

/**
* 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;
}
});
onAnimationProgress: null, // user specified callback to fire on each step of the animation
onAnimationComplete: null, // user specified callback to fire when the animation finishes
});

/**
* 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;
}
});
// DEPRECATIONS

/**
* 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(exports.prototype, 'animationObject', {
get: function() {
return this;
}
});

};
/**
* 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(exports.prototype, 'chartInstance', {
get: function() {
return this.chart;
},
set: function(value) {
this.chart = value;
}
});
129 changes: 129 additions & 0 deletions src/core/core.animations.js
@@ -0,0 +1,129 @@
/* global window: false */
'use strict';

var defaults = require('./core.defaults');
var helpers = require('../helpers/index');

defaults._set('global', {
animation: {
duration: 1000,
easing: 'easeOutQuart',
onProgress: helpers.noop,
onComplete: helpers.noop
}
});

module.exports = {
frameDuration: 17,
animations: [],
dropFrames: 0,
request: null,

/**
* @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, animation, duration, lazy) {
var animations = this.animations;
var i, ilen;

animation.chart = chart;

if (!lazy) {
chart.animating = true;
}

for (i = 0, ilen = animations.length; i < ilen; ++i) {
if (animations[i].chart === chart) {
animations[i] = animation;
return;
}
}

animations.push(animation);

// If there are no animations queued, manually kickstart a digest, for lack of a better word
if (animations.length === 1) {
this.requestAnimationFrame();
}
},

cancelAnimation: function(chart) {
var index = helpers.findIndex(this.animations, function(animation) {
return animation.chart === chart;
});

if (index !== -1) {
this.animations.splice(index, 1);
chart.animating = false;
}
},

requestAnimationFrame: function() {
var me = this;
if (me.request === null) {
// Skip animation frame requests until the active one is executed.
// This can happen when processing mouse events, e.g. 'mousemove'
// and 'mouseout' events will trigger multiple renders.
me.request = helpers.requestAnimFrame.call(window, function() {
me.request = null;
me.startDigest();
});
}
},

/**
* @private
*/
startDigest: function() {
var me = this;
var startTime = Date.now();
var framesToDrop = 0;

if (me.dropFrames > 1) {
framesToDrop = Math.floor(me.dropFrames);
me.dropFrames = me.dropFrames % 1;
}

me.advance(1 + framesToDrop);

var endTime = Date.now();

me.dropFrames += (endTime - startTime) / me.frameDuration;

// Do we have more stuff to animate?
if (me.animations.length > 0) {
me.requestAnimationFrame();
}
},

/**
* @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);

helpers.callback(animation.render, [chart, animation], chart);
helpers.callback(animation.onAnimationProgress, [animation], chart);

if (animation.currentStep >= animation.numSteps) {
helpers.callback(animation.onAnimationComplete, [animation], chart);
chart.animating = false;
animations.splice(i, 1);
} else {
++i;
}
}
}
};

0 comments on commit 83df0e1

Please sign in to comment.