From 16570b0c0c75e985f252c7f2e538410f8274446d Mon Sep 17 00:00:00 2001 From: Evert Timberg Date: Sun, 17 Apr 2016 12:02:33 -0400 Subject: [PATCH 1/5] Plugin system + tests --- src/chart.js | 1 + src/core/core.plugin.js | 55 +++++++++++++++++++++++++++++++++++++++ test/core.plugin.tests.js | 43 ++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 src/core/core.plugin.js create mode 100644 test/core.plugin.tests.js 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.plugin.js b/src/core/core.plugin.js new file mode 100644 index 000000000..c98cef15a --- /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, chartInstance, scope) { + helpers.each(Chart.plugins, function(plugin) { + if (plugin[method] && typeof plugin[method] === 'function') { + plugin[method].call(scope, chartInstance); + } + }, 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..4f59c63cd --- /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); + }); +}); From 7fa4c4c1b8ae6b9e62bc9e72eb4e1cb816cc125b Mon Sep 17 00:00:00 2001 From: Evert Timberg Date: Sun, 17 Apr 2016 12:02:42 -0400 Subject: [PATCH 2/5] Initial plugin calls --- src/core/core.controller.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/core/core.controller.js b/src/core/core.controller.js index 64af936de..b6dad13b9 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); + // 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); }, // Get the single element that was clicked on From d131e7d07a3969d644fe3f2f36eac28e14a3aa7a Mon Sep 17 00:00:00 2001 From: Evert Timberg Date: Sun, 17 Apr 2016 12:25:47 -0400 Subject: [PATCH 3/5] Use apply instead of call so that the animation easing can be passed to the draw callbacks --- src/core/core.controller.js | 12 ++++++------ src/core/core.plugin.js | 4 ++-- test/core.plugin.tests.js | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/core/core.controller.js b/src/core/core.controller.js index b6dad13b9..dfb62a331 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -44,7 +44,7 @@ module.exports = function(Chart) { initialize: function initialize() { // Before init plugin notification - Chart.pluginService.notifyPlugins('beforeInit', this); + Chart.pluginService.notifyPlugins('beforeInit', [this]); this.bindEvents(); @@ -60,7 +60,7 @@ module.exports = function(Chart) { this.update(); // After init plugin notification - Chart.pluginService.notifyPlugins('afterInit', this); + Chart.pluginService.notifyPlugins('afterInit', [this]); return this; }, @@ -242,7 +242,7 @@ module.exports = function(Chart) { }, update: function update(animationDuration, lazy) { - Chart.pluginService.notifyPlugins('beforeUpdate', this); + Chart.pluginService.notifyPlugins('beforeUpdate', [this]); // In case the entire data object changed this.tooltip._data = this.data; @@ -268,7 +268,7 @@ module.exports = function(Chart) { }); this.render(animationDuration, lazy); - Chart.pluginService.notifyPlugins('afterUpdate', this); + Chart.pluginService.notifyPlugins('afterUpdate', [this]); }, render: function render(duration, lazy) { @@ -305,7 +305,7 @@ module.exports = function(Chart) { var easingDecimal = ease || 1; this.clear(); - Chart.pluginService.notifyPlugins('beforeDraw', this); + Chart.pluginService.notifyPlugins('beforeDraw', [this, easingDecimal]); // Draw all the scales helpers.each(this.boxes, function(box) { @@ -334,7 +334,7 @@ module.exports = function(Chart) { // Finally draw the tooltip this.tooltip.transition(easingDecimal).draw(); - Chart.pluginService.notifyPlugins('afterDraw', this); + 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 index c98cef15a..78f5fe988 100644 --- a/src/core/core.plugin.js +++ b/src/core/core.plugin.js @@ -22,10 +22,10 @@ module.exports = function(Chart) { }, // Iterate over all plugins - notifyPlugins: function(method, chartInstance, scope) { + notifyPlugins: function(method, args, scope) { helpers.each(Chart.plugins, function(plugin) { if (plugin[method] && typeof plugin[method] === 'function') { - plugin[method].call(scope, chartInstance); + plugin[method].apply(scope, args); } }, scope); } diff --git a/test/core.plugin.tests.js b/test/core.plugin.tests.js index 4f59c63cd..5a6891071 100644 --- a/test/core.plugin.tests.js +++ b/test/core.plugin.tests.js @@ -36,7 +36,7 @@ describe('Test the plugin system', function() { }; Chart.pluginService.register(myplugin); - Chart.pluginService.notifyPlugins('trigger', { count: 10 }); + Chart.pluginService.notifyPlugins('trigger', [{ count: 10 }]); expect(myplugin.count).toBe(10); }); From 3f8681a1e4f754235ecd45e4ccb0d3a5533e78a0 Mon Sep 17 00:00:00 2001 From: Evert Timberg Date: Sun, 17 Apr 2016 12:25:58 -0400 Subject: [PATCH 4/5] Documentation for creating plugins --- docs/07-Advanced.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/07-Advanced.md b/docs/07-Advanced.md index e272de089..034fee581 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.0.3, 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. From 43438d630308fb81662d6549ec74ddf49c33cd99 Mon Sep 17 00:00:00 2001 From: Evert Timberg Date: Sun, 17 Apr 2016 12:39:09 -0400 Subject: [PATCH 5/5] Update doc version # --- docs/07-Advanced.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/07-Advanced.md b/docs/07-Advanced.md index 034fee581..ffa9fc5c3 100644 --- a/docs/07-Advanced.md +++ b/docs/07-Advanced.md @@ -368,7 +368,7 @@ The bar controller has a special property that you should be aware of. To correc ### Creating Plugins -Starting with v2.0.3, you can create plugins for chart.js. To register your plugin, simply call `Chart.pluginService.register` and pass your plugin in. +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