diff --git a/samples/legend/callbacks.html b/samples/legend/callbacks.html new file mode 100644 index 00000000000..bfb7f831005 --- /dev/null +++ b/samples/legend/callbacks.html @@ -0,0 +1,131 @@ + + + + + Line Chart + + + + + + +
+
+ +
+ + + + diff --git a/samples/samples.js b/samples/samples.js index b6ffe762682..b0f8005908c 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 7d13ea13ed4..81080d26d17 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,9 @@ var Legend = Element.extend({ // Contains hit boxes for each dataset (in dataset order) this.legendHitBoxes = []; + // Contains the currently hovered legend item + this.hoveredItem = null; + // Are we in doughnut mode which has a different data type this.doughnutMode = false; }, @@ -472,7 +476,7 @@ var Legend = Element.extend({ var changed = false; if (type === 'mousemove') { - if (!opts.onHover) { + if (!opts.onHover && !opts.onLeave) { return; } } else if (type === 'click') { @@ -486,6 +490,7 @@ var Legend = Element.extend({ // Chart event already has relative position in it var x = e.x; var y = e.y; + var hoveredItem = null; if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) { // See if we are touching one of the dataset boxes @@ -495,21 +500,33 @@ var Legend = Element.extend({ if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) { // Touching an element + hoveredItem = me.legendItems[i]; + 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; + if (opts.onHover) { + // use e.native for backwards compatibility + opts.onHover.call(me, e.native, me.legendItems[i]); + changed = true; + } break; } } } } + if (type === 'mousemove' && me.hoveredItem !== hoveredItem) { + if (me.hoveredItem) { + opts.onLeave.call(me, e.native, me.hoveredItem); + changed = true; + } + me.hoveredItem = hoveredItem; + } + return changed; } }); 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]); + }); + }); });