diff --git a/samples/legend/callbacks.html b/samples/legend/callbacks.html
new file mode 100644
index 00000000000..aa71857489e
--- /dev/null
+++ b/samples/legend/callbacks.html
@@ -0,0 +1,124 @@
+
+
+
+ Legend Callbacks
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/samples.js b/samples/samples.js
index 29ed1ff8190..bb0463f7e6e 100644
--- a/samples/samples.js
+++ b/samples/samples.js
@@ -148,6 +148,9 @@
}, {
title: 'Point style',
path: 'legend/point-style.html'
+ }, {
+ title: 'Callbacks',
+ path: 'legend/callbacks.html'
}]
}, {
title: 'Tooltip',
diff --git a/src/plugins/plugin.legend.js b/src/plugins/plugin.legend.js
index 2e832f98afd..2c6870b279d 100644
--- a/src/plugins/plugin.legend.js
+++ b/src/plugins/plugin.legend.js
@@ -30,6 +30,7 @@ defaults._set('global', {
},
onHover: null,
+ onLeave: null,
labels: {
boxWidth: 40,
@@ -106,6 +107,11 @@ var Legend = Element.extend({
// Contains hit boxes for each dataset (in dataset order)
this.legendHitBoxes = [];
+ /**
+ * @private
+ */
+ this._hoveredItem = null;
+
// Are we in doughnut mode which has a different data type
this.doughnutMode = false;
},
@@ -458,20 +464,42 @@ var Legend = Element.extend({
}
},
+ /**
+ * @private
+ */
+ _getLegendItemAt: function(x, y) {
+ var me = this;
+ var i, hitBox, lh;
+
+ if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {
+ // See if we are touching one of the dataset boxes
+ lh = me.legendHitBoxes;
+ for (i = 0; i < lh.length; ++i) {
+ hitBox = lh[i];
+
+ if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
+ // Touching an element
+ return me.legendItems[i];
+ }
+ }
+ }
+
+ return null;
+ },
+
/**
* Handle an event
* @private
* @param {IEvent} event - The event to handle
- * @return {boolean} true if a change occured
*/
handleEvent: function(e) {
var me = this;
var opts = me.options;
var type = e.type === 'mouseup' ? 'click' : e.type;
- var changed = false;
+ var hoveredItem;
if (type === 'mousemove') {
- if (!opts.onHover) {
+ if (!opts.onHover && !opts.onLeave) {
return;
}
} else if (type === 'click') {
@@ -483,33 +511,26 @@ var Legend = Element.extend({
}
// Chart event already has relative position in it
- var x = e.x;
- var y = e.y;
+ hoveredItem = me._getLegendItemAt(e.x, 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
- var lh = me.legendHitBoxes;
- for (var i = 0; i < lh.length; ++i) {
- var hitBox = lh[i];
-
- if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
- // Touching an element
- if (type === 'click') {
- // use e.native for backwards compatibility
- opts.onClick.call(me, e.native, me.legendItems[i]);
- changed = true;
- break;
- } else if (type === 'mousemove') {
- // use e.native for backwards compatibility
- opts.onHover.call(me, e.native, me.legendItems[i]);
- changed = true;
- break;
- }
+ if (type === 'click') {
+ if (hoveredItem && opts.onClick) {
+ // use e.native for backwards compatibility
+ opts.onClick.call(me, e.native, hoveredItem);
+ }
+ } else {
+ if (opts.onLeave && hoveredItem !== me._hoveredItem) {
+ if (me._hoveredItem) {
+ opts.onLeave.call(me, e.native, me._hoveredItem);
}
+ me._hoveredItem = hoveredItem;
}
- }
- return changed;
+ if (opts.onHover && hoveredItem) {
+ // use e.native for backwards compatibility
+ opts.onHover.call(me, e.native, hoveredItem);
+ }
+ }
}
});
diff --git a/test/specs/plugin.legend.tests.js b/test/specs/plugin.legend.tests.js
index fee715bdb13..e970c596a82 100644
--- a/test/specs/plugin.legend.tests.js
+++ b/test/specs/plugin.legend.tests.js
@@ -11,6 +11,7 @@ describe('Legend block tests', function() {
// a callback that will handle
onClick: jasmine.any(Function),
onHover: null,
+ onLeave: null,
labels: {
boxWidth: 40,
@@ -653,4 +654,53 @@ describe('Legend block tests', function() {
expect(chart.legend.options).toEqual(jasmine.objectContaining(Chart.defaults.global.legend));
});
});
+
+ describe('callbacks', function() {
+ it('should call onClick, onHover and onLeave at the correct times', function() {
+ var clickItem = null;
+ var hoverItem = null;
+ var leaveItem = null;
+
+ var chart = acquireChart({
+ type: 'line',
+ data: {
+ labels: ['A', 'B', 'C', 'D'],
+ datasets: [{
+ data: [10, 20, 30, 100]
+ }]
+ },
+ options: {
+ legend: {
+ onClick: function(_, item) {
+ clickItem = item;
+ },
+ onHover: function(_, item) {
+ hoverItem = item;
+ },
+ onLeave: function(_, item) {
+ leaveItem = item;
+ }
+ }
+ }
+ });
+
+ var hb = chart.legend.legendHitBoxes[0];
+ var el = {
+ x: hb.left + (hb.width / 2),
+ y: hb.top + (hb.height / 2)
+ };
+
+ jasmine.triggerMouseEvent(chart, 'click', el);
+
+ expect(clickItem).toBe(chart.legend.legendItems[0]);
+
+ jasmine.triggerMouseEvent(chart, 'mousemove', el);
+
+ expect(hoverItem).toBe(chart.legend.legendItems[0]);
+
+ jasmine.triggerMouseEvent(chart, 'mousemove', chart.getDatasetMeta(0).data[0]);
+
+ expect(leaveItem).toBe(chart.legend.legendItems[0]);
+ });
+ });
});