Skip to content

Commit

Permalink
Refactoring to put browser specific code in a new class, BrowserPlatf…
Browse files Browse the repository at this point in the history
…orm.

BrowserPlatform implements IPlatform. Chart.Platform is the constructor for the platform object that is attached to the chart instance.

Plugins are notified about the event using the `onEvent` call. The legend plugin was converted to use onEvent instead of the older private `handleEvent` method.
Wrote test to check that plugins are notified about events
  • Loading branch information
etimberg committed Dec 19, 2016
1 parent 64b5def commit 7d8afe8
Show file tree
Hide file tree
Showing 9 changed files with 385 additions and 158 deletions.
8 changes: 8 additions & 0 deletions docs/09-Advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@ Plugins will be called at the following times
* After datasets draw
* Resize
* Before an animation is started
* When an event occurs on the canvas (mousemove, click, etc). This requires the `options.events` property handled

Plugins should derive from Chart.PluginBase and implement the following interface
```javascript
Expand Down Expand Up @@ -437,6 +438,13 @@ Plugins should derive from Chart.PluginBase and implement the following interfac
afterDatasetsDraw: function(chartInstance, easing) { },

destroy: function(chartInstance) { }

/**
* Called when an event occurs on the chart
* @param e {Core.Event} the Chart.js wrapper around the native event. e.native is the original event
* @return {Boolean} true if the chart is changed and needs to re-render
*/
onEvent: function(chartInstance, e) {}
}
```

Expand Down
4 changes: 4 additions & 0 deletions src/chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ require('./core/core.title')(Chart);
require('./core/core.legend')(Chart);
require('./core/core.interaction')(Chart);
require('./core/core.tooltip')(Chart);
require('./core/core.platform')(Chart);

// By default, we only load the browser platform.
Chart.Platform = require('./platforms/platform.browser')(Chart);

require('./elements/element.arc')(Chart);
require('./elements/element.line')(Chart);
Expand Down
158 changes: 15 additions & 143 deletions src/core/core.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,140 +14,6 @@ module.exports = function(Chart) {
// Controllers available for dataset visualization eg. bar, line, slice, etc.
Chart.controllers = {};

/**
* The "used" size is the final value of a dimension property after all calculations have
* been performed. This method uses the computed style of `element` but returns undefined
* if the computed style is not expressed in pixels. That can happen in some cases where
* `element` has a size relative to its parent and this last one is not yet displayed,
* for example because of `display: none` on a parent node.
* TODO(SB) Move this method in the upcoming core.platform class.
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
* @returns {Number} Size in pixels or undefined if unknown.
*/
function readUsedSize(element, property) {
var value = helpers.getStyle(element, property);
var matches = value && value.match(/(\d+)px/);
return matches? Number(matches[1]) : undefined;
}

/**
* Initializes the canvas style and render size without modifying the canvas display size,
* since responsiveness is handled by the controller.resize() method. The config is used
* to determine the aspect ratio to apply in case no explicit height has been specified.
* TODO(SB) Move this method in the upcoming core.platform class.
*/
function initCanvas(canvas, config) {
var style = canvas.style;

// NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it
// returns null or '' if no explicit value has been set to the canvas attribute.
var renderHeight = canvas.getAttribute('height');
var renderWidth = canvas.getAttribute('width');

// Chart.js modifies some canvas values that we want to restore on destroy
canvas._chartjs = {
initial: {
height: renderHeight,
width: renderWidth,
style: {
display: style.display,
height: style.height,
width: style.width
}
}
};

// Force canvas to display as block to avoid extra space caused by inline
// elements, which would interfere with the responsive resize process.
// https://github.com/chartjs/Chart.js/issues/2538
style.display = style.display || 'block';

if (renderWidth === null || renderWidth === '') {
var displayWidth = readUsedSize(canvas, 'width');
if (displayWidth !== undefined) {
canvas.width = displayWidth;
}
}

if (renderHeight === null || renderHeight === '') {
if (canvas.style.height === '') {
// If no explicit render height and style height, let's apply the aspect ratio,
// which one can be specified by the user but also by charts as default option
// (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2.
canvas.height = canvas.width / (config.options.aspectRatio || 2);
} else {
var displayHeight = readUsedSize(canvas, 'height');
if (displayWidth !== undefined) {
canvas.height = displayHeight;
}
}
}

return canvas;
}

/**
* Restores the canvas initial state, such as render/display sizes and style.
* TODO(SB) Move this method in the upcoming core.platform class.
*/
function releaseCanvas(canvas) {
if (!canvas._chartjs) {
return;
}

var initial = canvas._chartjs.initial;
['height', 'width'].forEach(function(prop) {
var value = initial[prop];
if (value === undefined || value === null) {
canvas.removeAttribute(prop);
} else {
canvas.setAttribute(prop, value);
}
});

helpers.each(initial.style || {}, function(value, key) {
canvas.style[key] = value;
});

// The canvas render size might have been changed (and thus the state stack discarded),
// we can't use save() and restore() to restore the initial state. So make sure that at
// least the canvas context is reset to the default state by setting the canvas width.
// https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html
canvas.width = canvas.width;

delete canvas._chartjs;
}

/**
* TODO(SB) Move this method in the upcoming core.platform class.
*/
function acquireContext(item, config) {
if (typeof item === 'string') {
item = document.getElementById(item);
} else if (item.length) {
// Support for array based queries (such as jQuery)
item = item[0];
}

if (item && item.canvas) {
// Support for any object associated to a canvas (including a context2d)
item = item.canvas;
}

if (item instanceof HTMLCanvasElement) {
// To prevent canvas fingerprinting, some add-ons undefine the getContext
// method, for example: https://github.com/kkapsner/CanvasBlocker
// https://github.com/chartjs/Chart.js/issues/2807
var context = item.getContext && item.getContext('2d');
if (context instanceof CanvasRenderingContext2D) {
initCanvas(item, config);
return context;
}
}

return null;
}

/**
* Initializes the given config with global and chart default values.
*/
Expand Down Expand Up @@ -197,7 +63,9 @@ module.exports = function(Chart) {

config = initConfig(config);

var context = acquireContext(item, config);
// Create the platform implementation
me.platform = new Chart.Platform(me);
var context = me.platform.acquireContext(item, config);
var canvas = context && context.canvas;
var height = canvas && canvas.height;
var width = canvas && canvas.width;
Expand Down Expand Up @@ -696,7 +564,7 @@ module.exports = function(Chart) {
helpers.unbindEvents(me, me.events);
helpers.removeResizeListener(canvas.parentNode);
helpers.clear(me.chart);
releaseCanvas(canvas);
me.platform.releaseCanvas(canvas);
me.chart.canvas = null;
me.chart.ctx = null;
}
Expand Down Expand Up @@ -742,17 +610,19 @@ module.exports = function(Chart) {

eventHandler: function(e) {
var me = this;
var legend = me.legend;
var tooltip = me.tooltip;
var hoverOptions = me.options.hover;

// Buffer any update calls so that renders do not occur
me._bufferedRender = true;
me._bufferedRequest = null;

var changed = me.handleEvent(e);
changed |= legend && legend.handleEvent(e);
changed |= tooltip && tooltip.handleEvent(e);
// Create platform agnostic chart event using platform specific code
var chartEvent = me.platform.createEvent(e);

var changed = me.handleEvent(chartEvent);
changed |= tooltip && tooltip.handleEvent(chartEvent);
changed |= Chart.plugins.notify(me, 'onEvent', [chartEvent]);

var bufferedRequest = me._bufferedRequest;
if (bufferedRequest) {
Expand All @@ -776,7 +646,7 @@ module.exports = function(Chart) {
/**
* Handle an event
* @private
* param e {Event} the event to handle
* param e {Core.Event} the event to handle
* @return {Boolean} true if the chart needs to re-render
*/
handleEvent: function(e) {
Expand All @@ -796,12 +666,14 @@ module.exports = function(Chart) {

// On Hover hook
if (hoverOptions.onHover) {
hoverOptions.onHover.call(me, e, me.active);
// Need to call with native event here to not break backwards compatibility
hoverOptions.onHover.call(me, e.native, me.active);
}

if (e.type === 'mouseup' || e.type === 'click') {
if (options.onClick) {
options.onClick.call(me, e, me.active);
// Use e.native here for backwards compatibility
options.onClick.call(me, e.native, me.active);
}
}

Expand Down
31 changes: 24 additions & 7 deletions src/core/core.interaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,23 @@
module.exports = function(Chart) {
var helpers = Chart.helpers;

/**
* Helper function to get relative position for an event
* @param e {Event|Core.Event} the event to get the position for
* @param chart {chart} the chart
* @returns {Point} the event position
*/
function getRelativePosition(e, chart) {
if (e.native) {
return {
x: e.x,
y: e.y
};
}

return helpers.getRelativePosition(e, chart);
}

/**
* Helper function to traverse all of the visible elements in the chart
* @param chart {chart} the chart
Expand Down Expand Up @@ -82,7 +99,7 @@ module.exports = function(Chart) {
}

function indexMode(chart, e, options) {
var position = helpers.getRelativePosition(e, chart.chart);
var position = getRelativePosition(e, chart.chart);
var distanceMetric = function(pt1, pt2) {
return Math.abs(pt1.x - pt2.x);
};
Expand Down Expand Up @@ -125,7 +142,7 @@ module.exports = function(Chart) {
// Helper function for different modes
modes: {
single: function(chart, e) {
var position = helpers.getRelativePosition(e, chart.chart);
var position = getRelativePosition(e, chart.chart);
var elements = [];

parseVisibleItems(chart, function(element) {
Expand Down Expand Up @@ -166,7 +183,7 @@ module.exports = function(Chart) {
* @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
*/
dataset: function(chart, e, options) {
var position = helpers.getRelativePosition(e, chart.chart);
var position = getRelativePosition(e, chart.chart);
var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false);

if (items.length > 0) {
Expand All @@ -193,7 +210,7 @@ module.exports = function(Chart) {
* @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
*/
point: function(chart, e) {
var position = helpers.getRelativePosition(e, chart.chart);
var position = getRelativePosition(e, chart.chart);
return getIntersectItems(chart, position);
},

Expand All @@ -206,7 +223,7 @@ module.exports = function(Chart) {
* @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
*/
nearest: function(chart, e, options) {
var position = helpers.getRelativePosition(e, chart.chart);
var position = getRelativePosition(e, chart.chart);
var nearestItems = getNearestItems(chart, position, options.intersect);

// We have multiple items at the same distance from the event. Now sort by smallest
Expand Down Expand Up @@ -238,7 +255,7 @@ module.exports = function(Chart) {
* @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
*/
x: function(chart, e, options) {
var position = helpers.getRelativePosition(e, chart.chart);
var position = getRelativePosition(e, chart.chart);
var items = [];
var intersectsItem = false;

Expand Down Expand Up @@ -269,7 +286,7 @@ module.exports = function(Chart) {
* @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
*/
y: function(chart, e, options) {
var position = helpers.getRelativePosition(e, chart.chart);
var position = getRelativePosition(e, chart.chart);
var items = [];
var intersectsItem = false;

Expand Down
22 changes: 16 additions & 6 deletions src/core/core.legend.js
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ module.exports = function(Chart) {
/**
* Handle an event
* @private
* @param e {Event} the event to handle
* @param e {Core.Event} the event to handle
* @return {Boolean} true if a change occured
*/
handleEvent: function(e) {
Expand All @@ -460,9 +460,9 @@ module.exports = function(Chart) {
return;
}

var position = helpers.getRelativePosition(e, me.chart.chart),
x = position.x,
y = position.y;
// Chart event already has relative position in it
var x = e.x,
y = e.y;

if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {
// See if we are touching one of the dataset boxes
Expand All @@ -473,11 +473,13 @@ module.exports = function(Chart) {
if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
// Touching an element
if (type === 'click') {
opts.onClick.call(me, e, me.legendItems[i]);
// use e.native for backwards compatibility
opts.onClick.call(me, e.native, me.legendItems[i]);
changed = true;
break;
} else if (type === 'mousemove') {
opts.onHover.call(me, e, me.legendItems[i]);
// use e.native for backwards compatibility
opts.onHover.call(me, e.native, me.legendItems[i]);
changed = true;
break;
}
Expand Down Expand Up @@ -523,6 +525,14 @@ module.exports = function(Chart) {
Chart.layoutService.removeBox(chartInstance, chartInstance.legend);
delete chartInstance.legend;
}
},
onEvent: function(chartInstance, e) {
var legend = chartInstance.legend;
if (legend) {
return legend.handleEvent(e);
}

return false;
}
});
};
Loading

0 comments on commit 7d8afe8

Please sign in to comment.