Refactored interaction modes to use lookup functions in Chart.Interaction.modes and added new modes for 'point', 'index', 'nearest', 'x', and 'y'
Chart.js merges the options object passed to the chart with the global configuration using chart type defaults and scales defaults appropriately. This way you can be as specific as you would like in your individual chart configuration, while still changing the defaults for all chart types where applicable. The global general options are defined in `Chart.defaults.global`. The defaults for each chart type are discussed in the documentation for that chart type.
-The following example would set the hover mode to 'single' for all charts where this was not overridden by the chart type defaults or the options passed to the constructor on creation.
+The following example would set the hover mode to 'nearest' for all charts where this was not overridden by the chart type defaults or the options passed to the constructor on creation.
```javascript
-Chart.defaults.global.hover.mode = 'single';
+Chart.defaults.global.hover.mode = 'nearest';
-// Hover mode is set to single because it was not overridden here
-var chartInstanceHoverModeSingle = new Chart(ctx, {
+// Hover mode is set to nearest because it was not overridden here
+var chartInstanceHoverModeNearest = new Chart(ctx, {
type: 'line',
data: data,
});
options: {
hover: {
// Overrides the global setting
- mode: 'label'
+ mode: 'index'
}
}
})
fontColor: 'rgb(255, 99, 132)'
}
}
- }
+}
});
```
--- | --- | --- | ---
enabled | Boolean | true | Are tooltips enabled
custom | Function | null | See [section](#advanced-usage-external-tooltips) below
-mode | String | 'single' | Sets which elements appear in the tooltip. Acceptable options are `'single'`, `'label'` or `'x-axis'`. <br> <br>`single` highlights the closest element. <br> <br>`label` highlights elements in all datasets at the same `X` value. <br> <br>`'x-axis'` also highlights elements in all datasets at the same `X` value, but activates when hovering anywhere within the vertical slice of the x-axis representing that `X` value.
+mode | String | 'nearest' | Sets which elements appear in the tooltip. See [Interaction Modes](#interaction-modes) for details
+intersect | Boolean | true | if true, the tooltip mode applies only when the mouse position intersects with an element. If false, the mode will be applied at all times.
itemSort | Function | undefined | Allows sorting of [tooltip items](#chart-configuration-tooltip-item-interface). Must implement at minimum a function that can be passed to [Array.prototype.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort). This function can also accept a third parameter that is the data object passed to the chart.
backgroundColor | Color | 'rgba(0,0,0,0.8)' | Background color of the tooltip
titleFontFamily | String | "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif" | Font family for tooltip title inherited from global font family
Name | Type | Default | Description
--- | --- | --- | ---
-mode | String | 'single' | Sets which elements hover. Acceptable options are `'single'`, `'label'`, `'x-axis'`, or `'dataset'`. <br> <br>`single` highlights the closest element. <br> <br>`label` highlights elements in all datasets at the same `X` value. <br> <br>`'x-axis'` also highlights elements in all datasets at the same `X` value, but activates when hovering anywhere within the vertical slice of the x-axis representing that `X` value. <br> <br>`dataset` highlights the closest dataset.
+mode | String | 'naerest' | Sets which elements appear in the tooltip. See [Interaction Modes](#interaction-modes) for details
+intersect | Boolean | true | if true, the hover mode only applies when the mouse position intersects an item on the chart
animationDuration | Number | 400 | Duration in milliseconds it takes to animate hover style changes
onHover | Function | null | Called when any of the events fire. Called in the context of the chart and passed an array of active elements (bars, points, etc)
+### Interaction Modes
+When configuring interaction with the graph via hover or tooltips, a number of different modes are available.
+
+The following table details the modes and how they behave in conjunction with the `intersect` setting
+
+Mode | Behaviour
+--- | ---
+point | Finds all of the items that intersect the point
+nearest | Gets the item that is nearest to the point. The nearest item is determined based on the distance to the center of the chart item (point, bar). If 2 or more items are at the same distance, the one with the smallest area is used. If `intersect` is true, this is only triggered when the mouse position intersects an item in the graph. This is very useful for combo charts where points are hidden behind bars.
+single (deprecated) | Finds the first item that intersects the point and returns it. Behaves like 'nearest' mode with intersect = true.
+label (deprecated) | See `'index'` mode
+index | Finds item at the same index. If the `intersect` setting is true, the first intersecting item is used to determine the index in the data. If `intersect` false the nearest item is used to determine the index.
+x-axis (deprecated) | Behaves like `'index'` mode with `intersect = true`
+dataset | Finds items in the same dataset. If the `intersect` setting is true, the first intersecting item is used to determine the index in the data. If `intersect` false the nearest item is used to determine the index.
+x | Returns all items that would intersect based on the `X` coordinate of the position only. Would be useful for a vertical cursor implementation. Note that this only applies to cartesian charts
+y | Returns all items that would intersect based on the `Y` coordinate of the position. This would be useful for a horizontal cursor implementation. Note that this only applies to cartesian charts.
+
### Animation Configuration
The following animation options are available. The global options for are defined in `Chart.defaults.global.animation`.
}
},
tooltips: {
- mode: 'label',
+ mode: 'index',
},
scales: {
xAxes: [{
data: barChartData,
options: {
responsive: true,
- hoverMode: 'label',
+ hoverMode: 'index',
hoverAnimationDuration: 400,
stacked: false,
title:{
text:"Chart.js Bar Chart - Stacked"
},
tooltips: {
- mode: 'label'
+ mode: 'index'
},
responsive: true,
scales: {
position: 'bottom',
},
hover: {
- mode: 'label'
+ mode: 'index'
},
scales: {
xAxes: [{
text:'Chart.js Line Chart - Cubic interpolation mode'
},
tooltips: {
- mode: 'label'
+ mode: 'index'
},
hover: {
mode: 'dataset'
position: 'bottom',
},
hover: {
- mode: 'label'
+ mode: 'index'
},
scales: {
xAxes: [{
data: lineChartData,
options: {
responsive: true,
- hoverMode: 'label',
+ hoverMode: 'index',
stacked: false,
title:{
display:true,
text:'Chart.js Line Chart'
},
tooltips: {
- mode: 'label',
+ mode: 'index',
callbacks: {
// beforeTitle: function() {
// return '...beforeTitle';
text:'Chart.js Line Chart - Skip Points'
},
tooltips: {
- mode: 'label',
+ mode: 'index',
},
hover: {
- mode: 'label'
+ mode: 'index'
},
scales: {
xAxes: [{
text:"Chart.js Line Chart - Stacked Area"
},
tooltips: {
- mode: 'label',
+ mode: 'index',
},
hover: {
- mode: 'label'
+ mode: 'index'
},
scales: {
xAxes: [{
text:'Chart.js Line Chart'
},
tooltips: {
- mode: 'label',
+ mode: 'index',
callbacks: {
// beforeTitle: function() {
// return '...beforeTitle';
text:'Chart.js Line Chart'
},
tooltips: {
- mode: 'label',
+ mode: 'index',
+ intersect: false,
callbacks: {
// beforeTitle: function() {
// return '...beforeTitle';
}
},
hover: {
- mode: 'dataset'
+ mode: 'nearest',
+ intersect: true
},
scales: {
xAxes: [{
data: scatterChartData,
options: {
responsive: true,
- hoverMode: 'single',
+ hoverMode: 'nearest',
+ intersect: true,
title: {
display: true,
text: 'Chart.js Scatter Chart - Multi Axis'
text:"Chart.js Line Chart - Tooltip Hooks"
},
tooltips: {
- mode: 'label',
+ mode: 'index',
callbacks: {
beforeTitle: function() {
return '...beforeTitle';
}
},
hover: {
- mode: 'label'
+ mode: 'index'
},
scales: {
xAxes: [{
require('./core/core.scale')(Chart);
require('./core/core.title')(Chart);
require('./core/core.legend')(Chart);
+require('./core/core.interaction')(Chart);
require('./core/core.tooltip')(Chart);
require('./elements/element.arc')(Chart);
if (vm.borderWidth) {
ctx.stroke();
}
- },
-
- inRange: function(mouseX, mouseY) {
- var vm = this._view;
- var inRange = false;
-
- if (vm) {
- if (vm.x < vm.base) {
- inRange = (mouseY >= vm.y - vm.height / 2 && mouseY <= vm.y + vm.height / 2) && (mouseX >= vm.x && mouseX <= vm.base);
- } else {
- inRange = (mouseY >= vm.y - vm.height / 2 && mouseY <= vm.y + vm.height / 2) && (mouseX >= vm.base && mouseX <= vm.x);
- }
- }
-
- return inRange;
}
});
// Get the single element that was clicked on
// @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw
getElementAtEvent: function(e) {
- var me = this;
- var eventPosition = helpers.getRelativePosition(e, me.chart);
- var elementsArray = [];
-
- helpers.each(me.data.datasets, function(dataset, datasetIndex) {
- if (me.isDatasetVisible(datasetIndex)) {
- var meta = me.getDatasetMeta(datasetIndex);
- helpers.each(meta.data, function(element) {
- if (element.inRange(eventPosition.x, eventPosition.y)) {
- elementsArray.push(element);
- return elementsArray;
- }
- });
- }
- });
-
- return elementsArray.slice(0, 1);
+ return Chart.Interaction.modes.single(this, e);
},
getElementsAtEvent: function(e) {
- var me = this;
- var eventPosition = helpers.getRelativePosition(e, me.chart);
- var elementsArray = [];
-
- var found = function() {
- if (me.data.datasets) {
- for (var i = 0; i < me.data.datasets.length; i++) {
- var meta = me.getDatasetMeta(i);
- if (me.isDatasetVisible(i)) {
- for (var j = 0; j < meta.data.length; j++) {
- if (meta.data[j].inRange(eventPosition.x, eventPosition.y)) {
- return meta.data[j];
- }
- }
- }
- }
- }
- }.call(me);
-
- if (!found) {
- return elementsArray;
- }
-
- helpers.each(me.data.datasets, function(dataset, datasetIndex) {
- if (me.isDatasetVisible(datasetIndex)) {
- var meta = me.getDatasetMeta(datasetIndex),
- element = meta.data[found._index];
- if (element && !element._view.skip) {
- elementsArray.push(element);
- }
- }
- }, me);
-
- return elementsArray;
+ return Chart.Interaction.modes.label(this, e, {intersect: true});
},
getElementsAtXAxis: function(e) {
- var me = this;
- var eventPosition = helpers.getRelativePosition(e, me.chart);
- var elementsArray = [];
-
- var found = function() {
- if (me.data.datasets) {
- for (var i = 0; i < me.data.datasets.length; i++) {
- var meta = me.getDatasetMeta(i);
- if (me.isDatasetVisible(i)) {
- for (var j = 0; j < meta.data.length; j++) {
- if (meta.data[j].inLabelRange(eventPosition.x, eventPosition.y)) {
- return meta.data[j];
- }
- }
- }
- }
- }
- }.call(me);
-
- if (!found) {
- return elementsArray;
- }
-
- helpers.each(me.data.datasets, function(dataset, datasetIndex) {
- if (me.isDatasetVisible(datasetIndex)) {
- var meta = me.getDatasetMeta(datasetIndex);
- var index = helpers.findIndex(meta.data, function(it) {
- return found._model.x === it._model.x;
- });
- if (index !== -1 && !meta.data[index]._view.skip) {
- elementsArray.push(meta.data[index]);
- }
- }
- }, me);
-
- return elementsArray;
+ return Chart.Interaction.modes['x-axis'](this, e, {intersect: true});
},
- getElementsAtEventForMode: function(e, mode) {
- var me = this;
- switch (mode) {
- case 'single':
- return me.getElementAtEvent(e);
- case 'label':
- return me.getElementsAtEvent(e);
- case 'dataset':
- return me.getDatasetAtEvent(e);
- case 'x-axis':
- return me.getElementsAtXAxis(e);
- default:
- return e;
+ getElementsAtEventForMode: function(e, mode, options) {
+ var method = Chart.Interaction.modes[mode];
+ if (typeof method === 'function') {
+ return method(this, e, options);
}
+
+ return [];
},
getDatasetAtEvent: function(e) {
- var elementsArray = this.getElementAtEvent(e);
-
- if (elementsArray.length > 0) {
- elementsArray = this.getDatasetMeta(elementsArray[0]._datasetIndex).data;
- }
-
- return elementsArray;
+ return Chart.Interaction.modes.dataset(this, e);
},
getDatasetMeta: function(datasetIndex) {
me.active = [];
me.tooltipActive = [];
} else {
- me.active = me.getElementsAtEventForMode(e, hoverOptions.mode);
- me.tooltipActive = me.getElementsAtEventForMode(e, tooltipsOptions.mode);
+ me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions);
+ me.tooltipActive = me.getElementsAtEventForMode(e, tooltipsOptions.mode, tooltipsOptions);
}
// On Hover hook
distance: radialDistanceFromCenter
};
};
+ helpers.distanceBetweenPoints = function(pt1, pt2) {
+ return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
+ };
helpers.aliasPixel = function(pixelWidth) {
return (pixelWidth % 2 === 0) ? 0 : 0.5;
};
--- /dev/null
+'use strict';
+
+module.exports = function(Chart) {
+ var helpers = Chart.helpers;
+
+ /**
+ * Helper function to traverse all of the visible elements in the chart
+ * @param chart {chart} the chart
+ * @param handler {Function} the callback to execute for each visible item
+ */
+ function parseVisibleItems(chart, handler) {
+ var datasets = chart.data.datasets;
+ var meta, i, j, ilen, jlen;
+
+ for (i = 0, ilen = datasets.length; i < ilen; ++i) {
+ if (!chart.isDatasetVisible(i)) {
+ continue;
+ }
+
+ meta = chart.getDatasetMeta(i);
+ for (j = 0, jlen = meta.data.length; j < jlen; ++j) {
+ var element = meta.data[j];
+ if (!element._view.skip) {
+ handler(element);
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper function to get the items that intersect the event position
+ * @param items {ChartElement[]} elements to filter
+ * @param position {Point} the point to be nearest to
+ * @return {ChartElement[]} the nearest items
+ */
+ function getIntersectItems(chart, position) {
+ var elements = [];
+
+ parseVisibleItems(chart, function(element) {
+ if (element.inRange(position.x, position.y)) {
+ elements.push(element);
+ }
+ });
+
+ return elements;
+ }
+
+ /**
+ * Helper function to get the items nearest to the event position considering all visible items in teh chart
+ * @param chart {Chart} the chart to look at elements from
+ * @param position {Point} the point to be nearest to
+ * @param intersect {Boolean} if true, only consider items that intersect the position
+ * @return {ChartElement[]} the nearest items
+ */
+ function getNearestItems(chart, position, intersect) {
+ var minDistance = Number.POSITIVE_INFINITY;
+ var nearestItems = [];
+
+ parseVisibleItems(chart, function(element) {
+ if (intersect && !element.inRange(position.x, position.y)) {
+ return;
+ }
+
+ var center = element.getCenterPoint();
+ var distance = Math.round(helpers.distanceBetweenPoints(position, center));
+
+ if (distance < minDistance) {
+ nearestItems = [element];
+ minDistance = distance;
+ } else if (distance === minDistance) {
+ // Can have multiple items at the same distance in which case we sort by size
+ nearestItems.push(element);
+ }
+ });
+
+ return nearestItems;
+ }
+
+ function indexMode(chart, e, options) {
+ var position = helpers.getRelativePosition(e, chart.chart);
+ var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false);
+ var elements = [];
+
+ if (!items.length) {
+ return [];
+ }
+
+ chart.data.datasets.forEach(function(dataset, datasetIndex) {
+ if (chart.isDatasetVisible(datasetIndex)) {
+ var meta = chart.getDatasetMeta(datasetIndex),
+ element = meta.data[items[0]._index];
+
+ // don't count items that are skipped (null data)
+ if (element && !element._view.skip) {
+ elements.push(element);
+ }
+ }
+ });
+
+ return elements;
+ }
+
+ /**
+ * @interface IInteractionOptions
+ */
+ /**
+ * If true, only consider items that intersect the point
+ * @name IInterfaceOptions#boolean
+ * @type Boolean
+ */
+
+ /**
+ * @namespace Chart.Interaction
+ * Contains interaction related functions
+ */
+ Chart.Interaction = {
+ // Helper function for different modes
+ modes: {
+ single: function(chart, e) {
+ var position = helpers.getRelativePosition(e, chart.chart);
+ var elements = [];
+
+ parseVisibleItems(chart, function(element) {
+ if (element.inRange(position.x, position.y)) {
+ elements.push(element);
+ return elements;
+ }
+ });
+
+ return elements.slice(0, 1);
+ },
+
+ /**
+ * @function Chart.Interaction.modes.label
+ * @deprecated since version 2.4.0
+ */
+ label: indexMode,
+
+ /**
+ * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something
+ * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item
+ * @function Chart.Interaction.modes.index
+ * @since v2.4.0
+ * @param chart {chart} the chart we are returning items from
+ * @param e {Event} the event we are find things at
+ * @param options {IInteractionOptions} options to use during interaction
+ * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
+ */
+ index: indexMode,
+
+ /**
+ * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something
+ * If the options.intersect is false, we find the nearest item and return the items in that dataset
+ * @function Chart.Interaction.modes.dataset
+ * @param chart {chart} the chart we are returning items from
+ * @param e {Event} the event we are find things at
+ * @param options {IInteractionOptions} options to use during interaction
+ * @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 items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false);
+
+ if (items.length > 0) {
+ items = chart.getDatasetMeta(items[0]._datasetIndex).data;
+ }
+
+ return items;
+ },
+
+ /**
+ * @function Chart.Interaction.modes.x-axis
+ * @deprecated since version 2.4.0. Use index mode and intersect == true
+ */
+ 'x-axis': function(chart, e) {
+ return indexMode(chart, e, true);
+ },
+
+ /**
+ * Point mode returns all elements that hit test based on the event position
+ * of the event
+ * @function Chart.Interaction.modes.intersect
+ * @param chart {chart} the chart we are returning items from
+ * @param e {Event} the event we are find things at
+ * @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);
+ return getIntersectItems(chart, position);
+ },
+
+ /**
+ * nearest mode returns the element closest to the point
+ * @function Chart.Interaction.modes.intersect
+ * @param chart {chart} the chart we are returning items from
+ * @param e {Event} the event we are find things at
+ * @param options {IInteractionOptions} options to use
+ * @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 nearestItems = getNearestItems(chart, position, options.intersect);
+
+ // We have multiple items at the same distance from the event. Now sort by smallest
+ if (nearestItems.length > 1) {
+ nearestItems.sort(function(a, b) {
+ var sizeA = a.getArea();
+ var sizeB = b.getArea();
+ var ret = sizeA - sizeB;
+
+ if (ret === 0) {
+ // if equal sort by dataset index
+ ret = a._datasetIndex - b._datasetIndex;
+ }
+
+ return ret;
+ });
+ }
+
+ // Return only 1 item
+ return nearestItems.slice(0, 1);
+ },
+
+ /**
+ * x mode returns the elements that hit-test at the current x coordinate
+ * @function Chart.Interaction.modes.x
+ * @param chart {chart} the chart we are returning items from
+ * @param e {Event} the event we are find things at
+ * @param options {IInteractionOptions} options to use
+ * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
+ */
+ x: function(chart, e) {
+ var position = helpers.getRelativePosition(e, chart.chart);
+ var items = [];
+ parseVisibleItems(chart, function(element) {
+ if (element.inXRange(position.x)) {
+ items.push(element);
+ }
+ });
+ return items;
+ },
+
+ /**
+ * y mode returns the elements that hit-test at the current y coordinate
+ * @function Chart.Interaction.modes.y
+ * @param chart {chart} the chart we are returning items from
+ * @param e {Event} the event we are find things at
+ * @param options {IInteractionOptions} options to use
+ * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
+ */
+ y: function(chart, e) {
+ var position = helpers.getRelativePosition(e, chart.chart);
+ var items = [];
+ parseVisibleItems(chart, function(element) {
+ if (element.inYRange(position.x)) {
+ items.push(element);
+ }
+ });
+ return items;
+ }
+ }
+ };
+};
events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],
hover: {
onHover: null,
- mode: 'single',
+ mode: 'nearest',
+ intersect: true,
animationDuration: 400
},
onClick: null,
Chart.defaults.global.tooltips = {
enabled: true,
custom: null,
- mode: 'single',
+ mode: 'nearest',
+ intersect: true,
backgroundColor: 'rgba(0,0,0,0.8)',
titleFontStyle: 'bold',
titleSpacing: 2,
}
return false;
},
+ getCenterPoint: function() {
+ var vm = this._view;
+ var halfAngle = (vm.startAngle + vm.endAngle) / 2;
+ var halfRadius = (vm.innerRadius + vm.outerRadius) / 2;
+ return {
+ x: vm.x + Math.cos(halfAngle) * halfRadius,
+ y: vm.y + Math.sin(halfAngle) * halfRadius
+ };
+ },
+ getArea: function() {
+ var vm = this._view;
+ return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2));
+ },
tooltipPosition: function() {
var vm = this._view;
hoverBorderWidth: 1
};
+ function xRange(mouseX) {
+ var vm = this._view;
+ return vm ? (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false;
+ }
+
+ function yRange(mouseY) {
+ var vm = this._view;
+ return vm ? (Math.pow(mouseY - vm.y, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false;
+ }
+
Chart.elements.Point = Chart.Element.extend({
inRange: function(mouseX, mouseY) {
var vm = this._view;
return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false;
},
- inLabelRange: function(mouseX) {
+
+ inLabelRange: xRange,
+ inXRange: xRange,
+ inYRange: yRange,
+
+ getCenterPoint: function() {
var vm = this._view;
- return vm ? (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)) : false;
+ return {
+ x: vm.x,
+ y: vm.y
+ };
+ },
+ getArea: function() {
+ return Math.PI * Math.pow(this._view.radius, 2);
},
tooltipPosition: function() {
var vm = this._view;
borderSkipped: 'bottom'
};
+ function isVertical(bar) {
+ return bar._view.width !== undefined;
+ }
+
+ /**
+ * Helper function to get the bounds of the bar regardless of the orientation
+ * @private
+ * @param bar {Chart.Element.Rectangle} the bar
+ * @return {Bounds} bounds of the bar
+ */
+ function getBarBounds(bar) {
+ var vm = bar._view;
+ var x1, x2, y1, y2;
+
+ if (isVertical(bar)) {
+ // vertical
+ var halfWidth = vm.width / 2;
+ x1 = vm.x - halfWidth;
+ x2 = vm.x + halfWidth;
+ y1 = Math.min(vm.y, vm.base);
+ y2 = Math.max(vm.y, vm.base);
+ } else {
+ // horizontal bar
+ var halfHeight = vm.height / 2;
+ x1 = Math.min(vm.x, vm.base);
+ x2 = Math.max(vm.x, vm.base);
+ y1 = vm.y - halfHeight;
+ y2 = vm.y + halfHeight;
+ }
+
+ return {
+ left: x1,
+ top: y1,
+ right: x2,
+ bottom: y2
+ };
+ }
+
Chart.elements.Rectangle = Chart.Element.extend({
draw: function() {
var ctx = this._chart.ctx;
return vm.base - vm.y;
},
inRange: function(mouseX, mouseY) {
+ var inRange = false;
+
+ if (this._view) {
+ var bounds = getBarBounds(this);
+ inRange = mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom;
+ }
+
+ return inRange;
+ },
+ inLabelRange: function(mouseX, mouseY) {
+ var me = this;
+ if (!me._view) {
+ return false;
+ }
+
+ var inRange = false;
+ var bounds = getBarBounds(me);
+
+ if (isVertical(me)) {
+ inRange = mouseX >= bounds.left && mouseX <= bounds.right;
+ } else {
+ inRange = mouseY >= bounds.top && mouseY <= bounds.bottom;
+ }
+
+ return inRange;
+ },
+ inXRange: function(mouseX) {
+ var bounds = getBarBounds(this);
+ return mouseX >= bounds.left && mouseX <= bounds.right;
+ },
+ inYRange: function(mouseY) {
+ var bounds = getBarBounds(this);
+ return mouseY >= bounds.top && mouseY <= bounds.bottom;
+ },
+ getCenterPoint: function() {
var vm = this._view;
- return vm ?
- (vm.y < vm.base ?
- (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.y && mouseY <= vm.base) :
- (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.base && mouseY <= vm.y)) :
- false;
+ var x, y;
+ if (isVertical(this)) {
+ x = vm.x;
+ y = (vm.y + vm.base) / 2;
+ } else {
+ x = (vm.x + vm.base) / 2;
+ y = vm.y;
+ }
+
+ return {x: x, y: y};
},
- inLabelRange: function(mouseX) {
+ getArea: function() {
var vm = this._view;
- return vm ? (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) : false;
+ return vm.width * Math.abs(vm.y - vm.base);
},
tooltipPosition: function() {
var vm = this._view;
--- /dev/null
+// Tests of the interaction handlers in Core.Interaction
+
+// Test the rectangle element
+describe('Core.Interaction', function() {
+ describe('point mode', function() {
+ it ('should return all items under the point', function() {
+ var chartInstance = window.acquireChart({
+ type: 'line',
+ data: {
+ datasets: [{
+ label: 'Dataset 1',
+ data: [10, 20, 30],
+ pointHoverBorderColor: 'rgb(255, 0, 0)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 0)'
+ }, {
+ label: 'Dataset 2',
+ data: [40, 20, 40],
+ pointHoverBorderColor: 'rgb(0, 0, 255)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 255)'
+ }],
+ labels: ['Point 1', 'Point 2', 'Point 3']
+ }
+ });
+
+ // Trigger an event over top of the
+ var meta0 = chartInstance.getDatasetMeta(0);
+ var meta1 = chartInstance.getDatasetMeta(1);
+ var point = meta0.data[1];
+
+ var node = chartInstance.chart.canvas;
+ var rect = node.getBoundingClientRect();
+
+ var evt = {
+ view: window,
+ bubbles: true,
+ cancelable: true,
+ clientX: rect.left + point._model.x,
+ clientY: rect.top + point._model.y,
+ currentTarget: node
+ };
+
+ var elements = Chart.Interaction.modes.point(chartInstance, evt);
+ expect(elements).toEqual([point, meta1.data[1]]);
+ });
+
+ it ('should return an empty array when no items are found', function() {
+ var chartInstance = window.acquireChart({
+ type: 'line',
+ data: {
+ datasets: [{
+ label: 'Dataset 1',
+ data: [10, 20, 30],
+ pointHoverBorderColor: 'rgb(255, 0, 0)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 0)'
+ }, {
+ label: 'Dataset 2',
+ data: [40, 20, 40],
+ pointHoverBorderColor: 'rgb(0, 0, 255)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 255)'
+ }],
+ labels: ['Point 1', 'Point 2', 'Point 3']
+ }
+ });
+
+ // Trigger an event at (0, 0)
+ var node = chartInstance.chart.canvas;
+ var evt = {
+ view: window,
+ bubbles: true,
+ cancelable: true,
+ clientX: 0,
+ clientY: 0,
+ currentTarget: node
+ };
+
+ var elements = Chart.Interaction.modes.point(chartInstance, evt);
+ expect(elements).toEqual([]);
+ });
+ });
+
+ describe('index mode', function() {
+ it ('should return all items at the same index', function() {
+ var chartInstance = window.acquireChart({
+ type: 'line',
+ data: {
+ datasets: [{
+ label: 'Dataset 1',
+ data: [10, 20, 30],
+ pointHoverBorderColor: 'rgb(255, 0, 0)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 0)'
+ }, {
+ label: 'Dataset 2',
+ data: [40, 40, 40],
+ pointHoverBorderColor: 'rgb(0, 0, 255)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 255)'
+ }],
+ labels: ['Point 1', 'Point 2', 'Point 3']
+ }
+ });
+
+ // Trigger an event over top of the
+ var meta0 = chartInstance.getDatasetMeta(0);
+ var meta1 = chartInstance.getDatasetMeta(1);
+ var point = meta0.data[1];
+
+ var node = chartInstance.chart.canvas;
+ var rect = node.getBoundingClientRect();
+
+ var evt = {
+ view: window,
+ bubbles: true,
+ cancelable: true,
+ clientX: rect.left + point._model.x,
+ clientY: rect.top + point._model.y,
+ currentTarget: node
+ };
+
+ var elements = Chart.Interaction.modes.index(chartInstance, evt, { intersect: true });
+ expect(elements).toEqual([point, meta1.data[1]]);
+ });
+
+ it ('should return all items at the same index when intersect is false', function() {
+ var chartInstance = window.acquireChart({
+ type: 'line',
+ data: {
+ datasets: [{
+ label: 'Dataset 1',
+ data: [10, 20, 30],
+ pointHoverBorderColor: 'rgb(255, 0, 0)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 0)'
+ }, {
+ label: 'Dataset 2',
+ data: [40, 40, 40],
+ pointHoverBorderColor: 'rgb(0, 0, 255)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 255)'
+ }],
+ labels: ['Point 1', 'Point 2', 'Point 3']
+ }
+ });
+
+ // Trigger an event over top of the
+ var meta0 = chartInstance.getDatasetMeta(0);
+ var meta1 = chartInstance.getDatasetMeta(1);
+
+ var node = chartInstance.chart.canvas;
+ var rect = node.getBoundingClientRect();
+
+ var evt = {
+ view: window,
+ bubbles: true,
+ cancelable: true,
+ clientX: rect.left,
+ clientY: rect.top,
+ currentTarget: node
+ };
+
+ var elements = Chart.Interaction.modes.index(chartInstance, evt, { intersect: false });
+ expect(elements).toEqual([meta0.data[0], meta1.data[0]]);
+ });
+ });
+
+ describe('dataset mode', function() {
+ it ('should return all items in the dataset of the first item found', function() {
+ var chartInstance = window.acquireChart({
+ type: 'line',
+ data: {
+ datasets: [{
+ label: 'Dataset 1',
+ data: [10, 20, 30],
+ pointHoverBorderColor: 'rgb(255, 0, 0)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 0)'
+ }, {
+ label: 'Dataset 2',
+ data: [40, 40, 40],
+ pointHoverBorderColor: 'rgb(0, 0, 255)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 255)'
+ }],
+ labels: ['Point 1', 'Point 2', 'Point 3']
+ }
+ });
+
+ // Trigger an event over top of the
+ var meta = chartInstance.getDatasetMeta(0);
+ var point = meta.data[1];
+
+ var node = chartInstance.chart.canvas;
+ var rect = node.getBoundingClientRect();
+
+ var evt = {
+ view: window,
+ bubbles: true,
+ cancelable: true,
+ clientX: rect.left + point._model.x,
+ clientY: rect.top + point._model.y,
+ currentTarget: node
+ };
+
+ var elements = Chart.Interaction.modes.dataset(chartInstance, evt, { intersect: true });
+ expect(elements).toEqual(meta.data);
+ });
+
+ it ('should return all items in the dataset of the first item found when intersect is false', function() {
+ var chartInstance = window.acquireChart({
+ type: 'line',
+ data: {
+ datasets: [{
+ label: 'Dataset 1',
+ data: [10, 20, 30],
+ pointHoverBorderColor: 'rgb(255, 0, 0)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 0)'
+ }, {
+ label: 'Dataset 2',
+ data: [40, 40, 40],
+ pointHoverBorderColor: 'rgb(0, 0, 255)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 255)'
+ }],
+ labels: ['Point 1', 'Point 2', 'Point 3']
+ }
+ });
+
+ var node = chartInstance.chart.canvas;
+ var rect = node.getBoundingClientRect();
+
+ var evt = {
+ view: window,
+ bubbles: true,
+ cancelable: true,
+ clientX: rect.left,
+ clientY: rect.top,
+ currentTarget: node
+ };
+
+ var elements = Chart.Interaction.modes.dataset(chartInstance, evt, { intersect: false });
+
+ var meta = chartInstance.getDatasetMeta(1);
+ expect(elements).toEqual(meta.data);
+ });
+ });
+
+ describe('nearest mode', function() {
+ it ('should return the nearest item', function() {
+ var chartInstance = window.acquireChart({
+ type: 'line',
+ data: {
+ datasets: [{
+ label: 'Dataset 1',
+ data: [10, 20, 30],
+ pointHoverBorderColor: 'rgb(255, 0, 0)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 0)'
+ }, {
+ label: 'Dataset 2',
+ data: [40, 40, 40],
+ pointHoverBorderColor: 'rgb(0, 0, 255)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 255)'
+ }],
+ labels: ['Point 1', 'Point 2', 'Point 3']
+ }
+ });
+
+ // Trigger an event over top of the
+ var meta = chartInstance.getDatasetMeta(1);
+ var node = chartInstance.chart.canvas;
+ var rect = node.getBoundingClientRect();
+ var evt = {
+ view: window,
+ bubbles: true,
+ cancelable: true,
+ clientX: 0,
+ clientY: 0,
+ currentTarget: node
+ };
+
+ // Nearest to 0,0 (top left) will be first point of dataset 2
+ var elements = Chart.Interaction.modes.nearest(chartInstance, evt, { intersect: false });
+ expect(elements).toEqual([meta.data[0]]);
+ });
+
+ it ('should return the smallest item if more than 1 are at the same distance', function() {
+ var chartInstance = window.acquireChart({
+ type: 'line',
+ data: {
+ datasets: [{
+ label: 'Dataset 1',
+ data: [10, 40, 30],
+ pointRadius: [5, 5, 5],
+ pointHoverBorderColor: 'rgb(255, 0, 0)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 0)'
+ }, {
+ label: 'Dataset 2',
+ data: [40, 40, 40],
+ pointRadius: [10, 10, 10],
+ pointHoverBorderColor: 'rgb(0, 0, 255)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 255)'
+ }],
+ labels: ['Point 1', 'Point 2', 'Point 3']
+ }
+ });
+
+ // Trigger an event over top of the
+ var meta0 = chartInstance.getDatasetMeta(0);
+ var meta1 = chartInstance.getDatasetMeta(1);
+
+ // Halfway between 2 mid points
+ var pt = {
+ x: meta0.data[1]._view.x,
+ y: (meta0.data[1]._view.y + meta1.data[1]._view.y) / 2
+ };
+
+ var node = chartInstance.chart.canvas;
+ var rect = node.getBoundingClientRect();
+ var evt = {
+ view: window,
+ bubbles: true,
+ cancelable: true,
+ clientX: rect.left + pt.x,
+ clientY: rect.top + pt.y,
+ currentTarget: node
+ };
+
+ // Nearest to 0,0 (top left) will be first point of dataset 2
+ var elements = Chart.Interaction.modes.nearest(chartInstance, evt, { intersect: false });
+ expect(elements).toEqual([meta0.data[1]]);
+ });
+
+ it ('should return the lowest dataset index if size and area are the same', function() {
+ var chartInstance = window.acquireChart({
+ type: 'line',
+ data: {
+ datasets: [{
+ label: 'Dataset 1',
+ data: [10, 40, 30],
+ pointRadius: [5, 10, 5],
+ pointHoverBorderColor: 'rgb(255, 0, 0)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 0)'
+ }, {
+ label: 'Dataset 2',
+ data: [40, 40, 40],
+ pointRadius: [10, 10, 10],
+ pointHoverBorderColor: 'rgb(0, 0, 255)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 255)'
+ }],
+ labels: ['Point 1', 'Point 2', 'Point 3']
+ }
+ });
+
+ // Trigger an event over top of the
+ var meta0 = chartInstance.getDatasetMeta(0);
+ var meta1 = chartInstance.getDatasetMeta(1);
+
+ // Halfway between 2 mid points
+ var pt = {
+ x: meta0.data[1]._view.x,
+ y: (meta0.data[1]._view.y + meta1.data[1]._view.y) / 2
+ };
+
+ var node = chartInstance.chart.canvas;
+ var rect = node.getBoundingClientRect();
+ var evt = {
+ view: window,
+ bubbles: true,
+ cancelable: true,
+ clientX: rect.left + pt.x,
+ clientY: rect.top + pt.y,
+ currentTarget: node
+ };
+
+ // Nearest to 0,0 (top left) will be first point of dataset 2
+ var elements = Chart.Interaction.modes.nearest(chartInstance, evt, { intersect: false });
+ expect(elements).toEqual([meta0.data[1]]);
+ });
+ });
+
+ describe('nearest intersect mode', function() {
+ it ('should return the nearest item', function() {
+ var chartInstance = window.acquireChart({
+ type: 'line',
+ data: {
+ datasets: [{
+ label: 'Dataset 1',
+ data: [10, 20, 30],
+ pointHoverBorderColor: 'rgb(255, 0, 0)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 0)'
+ }, {
+ label: 'Dataset 2',
+ data: [40, 40, 40],
+ pointHoverBorderColor: 'rgb(0, 0, 255)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 255)'
+ }],
+ labels: ['Point 1', 'Point 2', 'Point 3']
+ }
+ });
+
+ // Trigger an event over top of the
+ var meta = chartInstance.getDatasetMeta(1);
+ var point = meta.data[1];
+
+ var node = chartInstance.chart.canvas;
+ var rect = node.getBoundingClientRect();
+ var evt = {
+ view: window,
+ bubbles: true,
+ cancelable: true,
+ clientX: rect.left + point._view.x + 15,
+ clientY: rect.top + point._view.y,
+ currentTarget: node
+ };
+
+ // Nothing intersects so find nothing
+ var elements = Chart.Interaction.modes.nearest(chartInstance, evt, { intersect: true });
+ expect(elements).toEqual([]);
+
+ evt = {
+ view: window,
+ bubbles: true,
+ cancelable: true,
+ clientX: rect.left + point._view.x,
+ clientY: rect.top + point._view.y,
+ currentTarget: node
+ };
+ elements = Chart.Interaction.modes.nearest(chartInstance, evt, { intersect: true });
+ expect(elements).toEqual([point]);
+ });
+
+ it ('should return the nearest item even if 2 intersect', function() {
+ var chartInstance = window.acquireChart({
+ type: 'line',
+ data: {
+ datasets: [{
+ label: 'Dataset 1',
+ data: [10, 39, 30],
+ pointRadius: [5, 30, 5],
+ pointHoverBorderColor: 'rgb(255, 0, 0)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 0)'
+ }, {
+ label: 'Dataset 2',
+ data: [40, 40, 40],
+ pointRadius: [10, 10, 10],
+ pointHoverBorderColor: 'rgb(0, 0, 255)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 255)'
+ }],
+ labels: ['Point 1', 'Point 2', 'Point 3']
+ }
+ });
+
+ // Trigger an event over top of the
+ var meta0 = chartInstance.getDatasetMeta(0);
+
+ // Halfway between 2 mid points
+ var pt = {
+ x: meta0.data[1]._view.x,
+ y: meta0.data[1]._view.y
+ };
+
+ var node = chartInstance.chart.canvas;
+ var rect = node.getBoundingClientRect();
+ var evt = {
+ view: window,
+ bubbles: true,
+ cancelable: true,
+ clientX: rect.left + pt.x,
+ clientY: rect.top + pt.y,
+ currentTarget: node
+ };
+
+ // Nearest to 0,0 (top left) will be first point of dataset 2
+ var elements = Chart.Interaction.modes.nearest(chartInstance, evt, { intersect: true });
+ expect(elements).toEqual([meta0.data[1]]);
+ });
+
+ it ('should return the smallest item if more than 1 are at the same distance', function() {
+ var chartInstance = window.acquireChart({
+ type: 'line',
+ data: {
+ datasets: [{
+ label: 'Dataset 1',
+ data: [10, 40, 30],
+ pointRadius: [5, 5, 5],
+ pointHoverBorderColor: 'rgb(255, 0, 0)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 0)'
+ }, {
+ label: 'Dataset 2',
+ data: [40, 40, 40],
+ pointRadius: [10, 10, 10],
+ pointHoverBorderColor: 'rgb(0, 0, 255)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 255)'
+ }],
+ labels: ['Point 1', 'Point 2', 'Point 3']
+ }
+ });
+
+ // Trigger an event over top of the
+ var meta0 = chartInstance.getDatasetMeta(0);
+
+ // Halfway between 2 mid points
+ var pt = {
+ x: meta0.data[1]._view.x,
+ y: meta0.data[1]._view.y
+ };
+
+ var node = chartInstance.chart.canvas;
+ var rect = node.getBoundingClientRect();
+ var evt = {
+ view: window,
+ bubbles: true,
+ cancelable: true,
+ clientX: rect.left + pt.x,
+ clientY: rect.top + pt.y,
+ currentTarget: node
+ };
+
+ // Nearest to 0,0 (top left) will be first point of dataset 2
+ var elements = Chart.Interaction.modes.nearest(chartInstance, evt, { intersect: true });
+ expect(elements).toEqual([meta0.data[1]]);
+ });
+
+ it ('should return the item at the lowest dataset index if distance and area are the same', function() {
+ var chartInstance = window.acquireChart({
+ type: 'line',
+ data: {
+ datasets: [{
+ label: 'Dataset 1',
+ data: [10, 40, 30],
+ pointRadius: [5, 10, 5],
+ pointHoverBorderColor: 'rgb(255, 0, 0)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 0)'
+ }, {
+ label: 'Dataset 2',
+ data: [40, 40, 40],
+ pointRadius: [10, 10, 10],
+ pointHoverBorderColor: 'rgb(0, 0, 255)',
+ pointHoverBackgroundColor: 'rgb(0, 255, 255)'
+ }],
+ labels: ['Point 1', 'Point 2', 'Point 3']
+ }
+ });
+
+ // Trigger an event over top of the
+ var meta0 = chartInstance.getDatasetMeta(0);
+
+ // Halfway between 2 mid points
+ var pt = {
+ x: meta0.data[1]._view.x,
+ y: meta0.data[1]._view.y
+ };
+
+ var node = chartInstance.chart.canvas;
+ var rect = node.getBoundingClientRect();
+ var evt = {
+ view: window,
+ bubbles: true,
+ cancelable: true,
+ clientX: rect.left + pt.x,
+ clientY: rect.top + pt.y,
+ currentTarget: node
+ };
+
+ // Nearest to 0,0 (top left) will be first point of dataset 2
+ var elements = Chart.Interaction.modes.nearest(chartInstance, evt, { intersect: true });
+ expect(elements).toEqual([meta0.data[1]]);
+ });
+ });
+});
expect(pos.y).toBeCloseTo(0.5);
});
+ it ('should get the area', function() {
+ var arc = new Chart.elements.Arc({
+ _datasetIndex: 2,
+ _index: 1
+ });
+
+ // Mock out the view as if the controller put it there
+ arc._view = {
+ startAngle: 0,
+ endAngle: Math.PI / 2,
+ x: 0,
+ y: 0,
+ innerRadius: 0,
+ outerRadius: Math.sqrt(2),
+ };
+
+ expect(arc.getArea()).toBeCloseTo(0.5 * Math.PI, 6);
+ });
+
+ it ('should get the center', function() {
+ var arc = new Chart.elements.Arc({
+ _datasetIndex: 2,
+ _index: 1
+ });
+
+ // Mock out the view as if the controller put it there
+ arc._view = {
+ startAngle: 0,
+ endAngle: Math.PI / 2,
+ x: 0,
+ y: 0,
+ innerRadius: 0,
+ outerRadius: Math.sqrt(2),
+ };
+
+ var center = arc.getCenterPoint();
+ expect(center.x).toBeCloseTo(0.5, 6);
+ expect(center.y).toBeCloseTo(0.5, 6);
+ });
+
it ('should draw correctly with no border', function() {
var mockContext = window.createMockContext();
var arc = new Chart.elements.Arc({
});
});
+ it('should get the correct area', function() {
+ var point = new Chart.elements.Point({
+ _datasetIndex: 2,
+ _index: 1
+ });
+
+ // Attach a view object as if we were the controller
+ point._view = {
+ radius: 2,
+ };
+
+ expect(point.getArea()).toEqual(Math.PI * 4);
+ });
+
+ it('should get the correct center point', function() {
+ var point = new Chart.elements.Point({
+ _datasetIndex: 2,
+ _index: 1
+ });
+
+ // Attach a view object as if we were the controller
+ point._view = {
+ radius: 2,
+ x: 10,
+ y: 10
+ };
+
+ expect(point.getCenterPoint()).toEqual({ x: 10, y: 10 });
+ });
+
it ('should draw correctly', function() {
var mockContext = window.createMockContext();
var point = new Chart.elements.Point({
});
});
+ it ('should get the correct area', function() {
+ var rectangle = new Chart.elements.Rectangle({
+ _datasetIndex: 2,
+ _index: 1
+ });
+
+ // Attach a view object as if we were the controller
+ rectangle._view = {
+ base: 0,
+ width: 4,
+ x: 10,
+ y: 15
+ };
+
+ expect(rectangle.getArea()).toEqual(60);
+ });
+
+ it ('should get the center', function() {
+ var rectangle = new Chart.elements.Rectangle({
+ _datasetIndex: 2,
+ _index: 1
+ });
+
+ // Attach a view object as if we were the controller
+ rectangle._view = {
+ base: 0,
+ width: 4,
+ x: 10,
+ y: 15
+ };
+
+ expect(rectangle.getCenterPoint()).toEqual({ x: 10, y: 7.5 });
+ });
+
it ('should draw correctly', function() {
var mockContext = window.createMockContext();
var rectangle = new Chart.elements.Rectangle({