diff --git a/docs/07-Advanced.md b/docs/07-Advanced.md index e272de089..ffa9fc5c3 100644 --- a/docs/07-Advanced.md +++ b/docs/07-Advanced.md @@ -366,6 +366,32 @@ The built in controller types are: #### Bar Controller The bar controller has a special property that you should be aware of. To correctly calculate the width of a bar, the controller must determine the number of datasets that map to bars. To do this, the bar controller attaches a property `bar` to the dataset during initialization. If you are creating a replacement or updated bar controller, you should do the same. This will ensure that charts with regular bars and your new derived bars will work seamlessly. +### Creating Plugins + +Starting with v2.1.0, you can create plugins for chart.js. To register your plugin, simply call `Chart.pluginService.register` and pass your plugin in. +Plugins will be called at the following times +* Start of initialization +* End of initialization +* Start of update +* End of update +* Start of draw +* End of draw + +Plugins should derive from Chart.PluginBase and implement the following interface +```javascript +{ + beforeInit: function(chartInstance) { }, + afterInit: function(chartInstance) { }, + + beforeUpdate: function(chartInstance) { }, + afterUpdate: function(chartInstance) { }, + + // Easing is for animation + beforeDraw: function(chartInstance, easing) { }, + afterDraw: function(chartInstance, easing) { } +} +``` + ### Building Chart.js Chart.js uses gulp to build the library into a single JavaScript file. diff --git a/src/chart.js b/src/chart.js index d0c4fd067..8f1b42855 100644 --- a/src/chart.js +++ b/src/chart.js @@ -18,6 +18,7 @@ require('./core/core.controller')(Chart); require('./core/core.datasetController')(Chart); require('./core/core.layoutService')(Chart); require('./core/core.legend')(Chart); +require('./core/core.plugin.js')(Chart); require('./core/core.scale')(Chart); require('./core/core.scaleService')(Chart); require('./core/core.title')(Chart); diff --git a/src/core/core.controller.js b/src/core/core.controller.js index 64af936de..dfb62a331 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -43,9 +43,8 @@ module.exports = function(Chart) { helpers.extend(Chart.Controller.prototype, { initialize: function initialize() { - - // TODO - // If BeforeInit(this) doesn't return false, proceed + // Before init plugin notification + Chart.pluginService.notifyPlugins('beforeInit', [this]); this.bindEvents(); @@ -60,8 +59,8 @@ module.exports = function(Chart) { this.initToolTip(); this.update(); - // TODO - // If AfterInit(this) doesn't return false, proceed + // After init plugin notification + Chart.pluginService.notifyPlugins('afterInit', [this]); return this; }, @@ -243,6 +242,8 @@ module.exports = function(Chart) { }, update: function update(animationDuration, lazy) { + Chart.pluginService.notifyPlugins('beforeUpdate', [this]); + // In case the entire data object changed this.tooltip._data = this.data; @@ -266,6 +267,8 @@ module.exports = function(Chart) { dataset.controller.update(); }); this.render(animationDuration, lazy); + + Chart.pluginService.notifyPlugins('afterUpdate', [this]); }, render: function render(duration, lazy) { @@ -302,6 +305,8 @@ module.exports = function(Chart) { var easingDecimal = ease || 1; this.clear(); + Chart.pluginService.notifyPlugins('beforeDraw', [this, easingDecimal]); + // Draw all the scales helpers.each(this.boxes, function(box) { box.draw(this.chartArea); @@ -328,6 +333,8 @@ module.exports = function(Chart) { // Finally draw the tooltip this.tooltip.transition(easingDecimal).draw(); + + Chart.pluginService.notifyPlugins('afterDraw', [this, easingDecimal]); }, // Get the single element that was clicked on diff --git a/src/core/core.plugin.js b/src/core/core.plugin.js new file mode 100644 index 000000000..78f5fe988 --- /dev/null +++ b/src/core/core.plugin.js @@ -0,0 +1,55 @@ +"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) { + if (Chart.plugins.indexOf(plugin) === -1) { + Chart.plugins.push(plugin); + } + }, + + // Remove a registered plugin + remove: function(plugin) { + var idx = Chart.plugins.indexOf(plugin); + if (idx !== -1) { + Chart.plugins.splice(idx, 1); + } + }, + + // 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); + } + }, scope); + } + }; + + Chart.PluginBase = Chart.Element.extend({ + // Plugin methods. All functions are passed the chart instance + + // Called at start of chart init + beforeInit: helpers.noop, + + // Called at end of chart init + afterInit: helpers.noop, + + // Called at start of update + beforeUpdate: helpers.noop, + + // Called at end of update + afterUpdate: helpers.noop, + + // Called at start of draw + beforeDraw: helpers.noop, + + // Called at end of draw + afterDraw: helpers.noop, + }); +}; \ No newline at end of file diff --git a/test/core.plugin.tests.js b/test/core.plugin.tests.js new file mode 100644 index 000000000..5a6891071 --- /dev/null +++ b/test/core.plugin.tests.js @@ -0,0 +1,43 @@ +// Plugin tests +describe('Test the plugin system', function() { + beforeEach(function() { + Chart.plugins = []; + }); + + it ('Should register plugins', function() { + var myplugin = {}; + Chart.pluginService.register(myplugin); + expect(Chart.plugins.length).toBe(1); + + // Should only add plugin once + Chart.pluginService.register(myplugin); + expect(Chart.plugins.length).toBe(1); + }); + + it ('Should allow unregistering plugins', function() { + var myplugin = {}; + Chart.pluginService.register(myplugin); + expect(Chart.plugins.length).toBe(1); + + // Should only add plugin once + Chart.pluginService.remove(myplugin); + expect(Chart.plugins.length).toBe(0); + + // Removing a plugin that doesn't exist should not error + Chart.pluginService.remove(myplugin); + }); + + it ('Should allow notifying plugins', function() { + var myplugin = { + count: 0, + trigger: function(chart) { + myplugin.count = chart.count; + } + }; + Chart.pluginService.register(myplugin); + + Chart.pluginService.notifyPlugins('trigger', [{ count: 10 }]); + + expect(myplugin.count).toBe(10); + }); +});