Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions docs/09-Advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,8 @@ Plugins will be called at the following times
* End of initialization
* Start of update
* After the chart scales have calculated
* Start of datasets update
* End of datasets update
* End of update (before render occurs)
* Start of draw
* End of draw
Expand All @@ -396,18 +398,20 @@ Plugins should derive from Chart.PluginBase and implement the following interfac

beforeUpdate: function(chartInstance) { },
afterScaleUpdate: function(chartInstance) { }
beforeDatasetsUpdate: function(chartInstance) { }
afterDatasetsUpdate: function(chartInstance) { }
afterUpdate: function(chartInstance) { },

// This is called at the start of a render. It is only called once, even if the animation will run for a number of frames. Use beforeDraw or afterDraw
// This is called at the start of a render. It is only called once, even if the animation will run for a number of frames. Use beforeDraw or afterDraw
// to do something on each animation frame
beforeRender: function(chartInstance) { },

// Easing is for animation
beforeDraw: function(chartInstance, easing) { },
afterDraw: function(chartInstance, easing) { },
// Before the datasets are drawn but after scales are drawn
beforeDatasetDraw: function(chartInstance, easing) { },
afterDatasetDraw: function(chartInstance, easing) { },
beforeDatasetsDraw: function(chartInstance, easing) { },
afterDatasetsDraw: function(chartInstance, easing) { },

destroy: function(chartInstance) { }
}
Expand Down
3 changes: 3 additions & 0 deletions src/chart.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* @namespace Chart
*/
var Chart = require('./core/core.js')();

require('./core/core.helpers')(Chart);
Expand Down
79 changes: 60 additions & 19 deletions src/core/core.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ module.exports = function(Chart) {
// Controllers available for dataset visualization eg. bar, line, slice, etc.
Chart.controllers = {};

// The main controller of a chart
/**
* @class Chart.Controller
* The main controller of a chart.
*/
Chart.Controller = function(instance) {

this.chart = instance;
Expand All @@ -40,12 +43,12 @@ module.exports = function(Chart) {
return this;
};

helpers.extend(Chart.Controller.prototype, {
helpers.extend(Chart.Controller.prototype, /** @lends Chart.Controller */ {

initialize: function initialize() {
var me = this;
// Before init plugin notification
Chart.pluginService.notifyPlugins('beforeInit', [me]);
Chart.plugins.notify('beforeInit', [me]);

me.bindEvents();

Expand All @@ -60,7 +63,7 @@ module.exports = function(Chart) {
me.update();

// After init plugin notification
Chart.pluginService.notifyPlugins('afterInit', [me]);
Chart.plugins.notify('afterInit', [me]);

return me;
},
Expand All @@ -83,7 +86,7 @@ module.exports = function(Chart) {
var newWidth = helpers.getMaximumWidth(canvas);
var aspectRatio = chart.aspectRatio;
var newHeight = (me.options.maintainAspectRatio && isNaN(aspectRatio) === false && isFinite(aspectRatio) && aspectRatio !== 0) ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas);

var sizeChanged = chart.width !== newWidth || chart.height !== newHeight;

if (!sizeChanged) {
Expand All @@ -97,7 +100,7 @@ module.exports = function(Chart) {

// Notify any plugins about the resize
var newSize = { width: newWidth, height: newHeight };
Chart.pluginService.notifyPlugins('resize', [me, newSize]);
Chart.plugins.notify('resize', [me, newSize]);

// Notify of resize
if (me.options.onResize) {
Expand Down Expand Up @@ -225,7 +228,7 @@ module.exports = function(Chart) {

update: function update(animationDuration, lazy) {
var me = this;
Chart.pluginService.notifyPlugins('beforeUpdate', [me]);
Chart.plugins.notify('beforeUpdate', [me]);

// In case the entire data object changed
me.tooltip._data = me.data;
Expand All @@ -241,27 +244,65 @@ module.exports = function(Chart) {
Chart.layoutService.update(me, me.chart.width, me.chart.height);

// Apply changes to the dataets that require the scales to have been calculated i.e BorderColor chages
Chart.pluginService.notifyPlugins('afterScaleUpdate', [me]);
Chart.plugins.notify('afterScaleUpdate', [me]);

// Can only reset the new controllers after the scales have been updated
helpers.each(newControllers, function(controller) {
controller.reset();
});

// This will loop through any data and do the appropriate element update for the type
helpers.each(me.data.datasets, function(dataset, datasetIndex) {
me.getDatasetMeta(datasetIndex).controller.update();
}, me);
me.updateDatasets();

// Do this before render so that any plugins that need final scale updates can use it
Chart.pluginService.notifyPlugins('afterUpdate', [me]);
Chart.plugins.notify('afterUpdate', [me]);

me.render(animationDuration, lazy);
},

/**
* @method beforeDatasetsUpdate
* @description Called before all datasets are updated. If a plugin returns false,
* the datasets update will be cancelled until another chart update is triggered.
* @param {Object} instance the chart instance being updated.
* @returns {Boolean} false to cancel the datasets update.
* @memberof Chart.PluginBase
* @since version 2.1.5
* @instance
*/

/**
* @method afterDatasetsUpdate
* @description Called after all datasets have been updated. Note that this
* extension will not be called if the datasets update has been cancelled.
* @param {Object} instance the chart instance being updated.
* @memberof Chart.PluginBase
* @since version 2.1.5
* @instance
*/

/**
* Updates all datasets unless a plugin returns false to the beforeDatasetsUpdate
* extension, in which case no datasets will be updated and the afterDatasetsUpdate
* notification will be skipped.
* @protected
* @instance
*/
updateDatasets: function() {
var me = this;
var i, ilen;

if (Chart.plugins.notify('beforeDatasetsUpdate', [ me ])) {
for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
me.getDatasetMeta(i).controller.update();
}

Chart.plugins.notify('afterDatasetsUpdate', [ me ]);
}
},

render: function render(duration, lazy) {
var me = this;
Chart.pluginService.notifyPlugins('beforeRender', [me]);
Chart.plugins.notify('beforeRender', [me]);

var animationOptions = me.options.animation;
if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) {
Expand Down Expand Up @@ -297,7 +338,7 @@ module.exports = function(Chart) {
var easingDecimal = ease || 1;
me.clear();

Chart.pluginService.notifyPlugins('beforeDraw', [me, easingDecimal]);
Chart.plugins.notify('beforeDraw', [me, easingDecimal]);

// Draw all the scales
helpers.each(me.boxes, function(box) {
Expand All @@ -307,7 +348,7 @@ module.exports = function(Chart) {
me.scale.draw();
}

Chart.pluginService.notifyPlugins('beforeDatasetDraw', [me, easingDecimal]);
Chart.plugins.notify('beforeDatasetsDraw', [me, easingDecimal]);

// Draw each dataset via its respective controller (reversed to support proper line stacking)
helpers.each(me.data.datasets, function(dataset, datasetIndex) {
Expand All @@ -316,12 +357,12 @@ module.exports = function(Chart) {
}
}, me, true);

Chart.pluginService.notifyPlugins('afterDatasetDraw', [me, easingDecimal]);
Chart.plugins.notify('afterDatasetsDraw', [me, easingDecimal]);

// Finally draw the tooltip
me.tooltip.transition(easingDecimal).draw();

Chart.pluginService.notifyPlugins('afterDraw', [me, easingDecimal]);
Chart.plugins.notify('afterDraw', [me, easingDecimal]);
},

// Get the single element that was clicked on
Expand Down Expand Up @@ -470,7 +511,7 @@ module.exports = function(Chart) {
canvas.style.width = me.chart.originalCanvasStyleWidth;
canvas.style.height = me.chart.originalCanvasStyleHeight;

Chart.pluginService.notifyPlugins('destroy', [me]);
Chart.plugins.notify('destroy', [me]);

delete Chart.instances[me.id];
},
Expand Down
2 changes: 1 addition & 1 deletion src/core/core.legend.js
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ module.exports = function(Chart) {
});

// Register the legend plugin
Chart.pluginService.register({
Chart.plugins.register({
beforeInit: function(chartInstance) {
var opts = chartInstance.options;
var legendOpts = opts.legend;
Expand Down
122 changes: 95 additions & 27 deletions src/core/core.plugin.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,102 @@
"use strict";

module.exports = function(Chart) {
var helpers = Chart.helpers;

// Plugins are stored here
Chart.plugins = [];
Chart.pluginService = {
// Register a new plugin
register: function(plugin) {
var p = Chart.plugins;
if (p.indexOf(plugin) === -1) {
p.push(plugin);
}

var noop = Chart.helpers.noop;

/**
* The plugin service singleton
* @namespace Chart.plugins
* @since 2.1.0
*/
Chart.plugins = {
_plugins: [],

/**
* Registers the given plugin(s) if not already registered.
* @param {Array|Object} plugins plugin instance(s).
*/
register: function(plugins) {
var p = this._plugins;
([]).concat(plugins).forEach(function(plugin) {
if (p.indexOf(plugin) === -1) {
p.push(plugin);
}
});
},

// Remove a registered plugin
remove: function(plugin) {
var p = Chart.plugins;
var idx = p.indexOf(plugin);
if (idx !== -1) {
p.splice(idx, 1);
}
/**
* Unregisters the given plugin(s) only if registered.
* @param {Array|Object} plugins plugin instance(s).
*/
unregister: function(plugins) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for completeness we should implement remove to alias to unregister

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the same first but then realized that this method has never been used in code or plugins (correct me if I'm wrong), and I can't see any case where it should be used (except in unit tests). So was thinking that we may not keep compatibility and save a few bits. What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I don't think anyone has used it.

var p = this._plugins;
([]).concat(plugins).forEach(function(plugin) {
var idx = p.indexOf(plugin);
if (idx !== -1) {
p.splice(idx, 1);
}
});
},

/**
* Remove all registered p^lugins.
* @since 2.1.5
*/
clear: function() {
this._plugins = [];
},

// Iterate over all plugins
notifyPlugins: function(method, args, scope) {
helpers.each(Chart.plugins, function(plugin) {
if (plugin[method] && typeof plugin[method] === 'function') {
plugin[method].apply(scope, args);
/**
* Returns the number of registered plugins?
* @returns {Number}
* @since 2.1.5
*/
count: function() {
return this._plugins.length;
},

/**
* Returns all registered plugin intances.
* @returns {Array} array of plugin objects.
* @since 2.1.5
*/
getAll: function() {
return this._plugins;
},

/**
* Calls registered plugins on the specified extension, with the given args. This
* method immediately returns as soon as a plugin explicitly returns false. The
* returned value can be used, for instance, to interrupt the current action.
* @param {String} extension the name of the plugin method to call (e.g. 'beforeUpdate').
* @param {Array} [args] extra arguments to apply to the extension call.
* @returns {Boolean} false if any of the plugins return false, else returns true.
*/
notify: function(extension, args) {
var plugins = this._plugins;
var ilen = plugins.length;
var i, plugin;

for (i=0; i<ilen; ++i) {
plugin = plugins[i];
if (typeof plugin[extension] === 'function') {
if (plugin[extension].apply(plugin, args || []) === false) {
return false;
}
}
}, scope);
}

return true;
}
};

var noop = helpers.noop;
/**
* Plugin extension methods.
* @interface Chart.PluginBase
* @since 2.1.0
*/
Chart.PluginBase = Chart.Element.extend({
// Plugin methods. All functions are passed the chart instance

// Called at start of chart init
beforeInit: noop,

Expand All @@ -58,4 +118,12 @@ module.exports = function(Chart) {
// Called during destroy
destroy: noop
});

/**
* Provided for backward compatibility, use Chart.plugins instead
* @namespace Chart.pluginService
* @deprecated since version 2.1.5
* @todo remove me at version 3
*/
Chart.pluginService = Chart.plugins;
};
6 changes: 3 additions & 3 deletions src/core/core.title.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ module.exports = function(Chart) {
},
update: function(maxWidth, maxHeight, margins) {
var me = this;

// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
me.beforeUpdate();

Expand Down Expand Up @@ -155,7 +155,7 @@ module.exports = function(Chart) {
fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily),
titleFont = helpers.fontString(fontSize, fontStyle, fontFamily),
rotation = 0,
titleX,
titleX,
titleY,
top = me.top,
left = me.left,
Expand Down Expand Up @@ -187,7 +187,7 @@ module.exports = function(Chart) {
});

// Register the title plugin
Chart.pluginService.register({
Chart.plugins.register({
beforeInit: function(chartInstance) {
var opts = chartInstance.options;
var titleOpts = opts.title;
Expand Down
Loading