Skip to content

Commit ae23fbf

Browse files
jonrimmersimonbrunel
authored andcommitted
Add onLeave callback to legend (chartjs#6059)
1 parent c27619f commit ae23fbf

File tree

4 files changed

+224
-26
lines changed

4 files changed

+224
-26
lines changed

samples/legend/callbacks.html

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<title>Legend Callbacks</title>
5+
<script src="../../dist/Chart.min.js"></script>
6+
<script src="../utils.js"></script>
7+
<style>
8+
body, html {
9+
height: 100%;
10+
font-family: sans-serif;
11+
}
12+
canvas{
13+
-moz-user-select: none;
14+
-webkit-user-select: none;
15+
-ms-user-select: none;
16+
}
17+
18+
#log {
19+
position: absolute;
20+
right: 0;
21+
top: 0;
22+
bottom: 0;
23+
background-color: #EEE;
24+
float: right;
25+
width: 20%;
26+
padding: 8px;
27+
overflow-y: auto;
28+
white-space: pre;
29+
line-height: 1.5em;
30+
}
31+
</style>
32+
</head>
33+
34+
<body>
35+
<div id="log"></div>
36+
<div style="width:75%;">
37+
<canvas id="canvas"></canvas>
38+
</div>
39+
<script>
40+
var logEntry = 1;
41+
var logElement = document.getElementById('log');
42+
var utils = Samples.utils;
43+
var inputs = {
44+
min: 20,
45+
max: 80,
46+
count: 8,
47+
decimals: 2,
48+
continuity: 1
49+
};
50+
var config;
51+
52+
function log(text) {
53+
logElement.innerText += logEntry + '. ' + text + '\n';
54+
logElement.scrollTop = logElement.scrollHeight;
55+
logEntry++;
56+
}
57+
58+
function generateData() {
59+
return utils.numbers(inputs);
60+
}
61+
function generateLabels() {
62+
return utils.months({count: inputs.count});
63+
}
64+
65+
utils.srand(42);
66+
67+
config = {
68+
type: 'line',
69+
data: {
70+
labels: generateLabels(),
71+
datasets: [{
72+
label: 'My First dataset',
73+
backgroundColor: utils.color(0),
74+
borderColor: utils.color(0),
75+
data: generateData(),
76+
fill: false,
77+
}, {
78+
label: 'My Second dataset',
79+
fill: false,
80+
backgroundColor: utils.color(1),
81+
borderColor: utils.color(1),
82+
data: generateData(),
83+
}]
84+
},
85+
options: {
86+
legend: {
87+
onHover: function(event, legendItem) {
88+
log('onHover: ' + legendItem.text);
89+
},
90+
onLeave: function(event, legendItem) {
91+
log('onLeave: ' + legendItem.text);
92+
},
93+
onClick: function(event, legendItem) {
94+
log('onClick:' + legendItem.text);
95+
}
96+
},
97+
title: {
98+
display: true,
99+
text: 'Chart.js Line Chart'
100+
},
101+
scales: {
102+
xAxes: [{
103+
display: true,
104+
scaleLabel: {
105+
display: true,
106+
labelString: 'Month'
107+
}
108+
}],
109+
yAxes: [{
110+
display: true,
111+
scaleLabel: {
112+
display: true,
113+
labelString: 'Value'
114+
}
115+
}]
116+
}
117+
}
118+
};
119+
120+
new Chart(document.getElementById('canvas'), config);
121+
</script>
122+
</body>
123+
124+
</html>

samples/samples.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@
148148
}, {
149149
title: 'Point style',
150150
path: 'legend/point-style.html'
151+
}, {
152+
title: 'Callbacks',
153+
path: 'legend/callbacks.html'
151154
}]
152155
}, {
153156
title: 'Tooltip',

src/plugins/plugin.legend.js

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ defaults._set('global', {
3030
},
3131

3232
onHover: null,
33+
onLeave: null,
3334

3435
labels: {
3536
boxWidth: 40,
@@ -106,6 +107,11 @@ var Legend = Element.extend({
106107
// Contains hit boxes for each dataset (in dataset order)
107108
this.legendHitBoxes = [];
108109

110+
/**
111+
* @private
112+
*/
113+
this._hoveredItem = null;
114+
109115
// Are we in doughnut mode which has a different data type
110116
this.doughnutMode = false;
111117
},
@@ -458,20 +464,42 @@ var Legend = Element.extend({
458464
}
459465
},
460466

467+
/**
468+
* @private
469+
*/
470+
_getLegendItemAt: function(x, y) {
471+
var me = this;
472+
var i, hitBox, lh;
473+
474+
if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {
475+
// See if we are touching one of the dataset boxes
476+
lh = me.legendHitBoxes;
477+
for (i = 0; i < lh.length; ++i) {
478+
hitBox = lh[i];
479+
480+
if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
481+
// Touching an element
482+
return me.legendItems[i];
483+
}
484+
}
485+
}
486+
487+
return null;
488+
},
489+
461490
/**
462491
* Handle an event
463492
* @private
464493
* @param {IEvent} event - The event to handle
465-
* @return {boolean} true if a change occured
466494
*/
467495
handleEvent: function(e) {
468496
var me = this;
469497
var opts = me.options;
470498
var type = e.type === 'mouseup' ? 'click' : e.type;
471-
var changed = false;
499+
var hoveredItem;
472500

473501
if (type === 'mousemove') {
474-
if (!opts.onHover) {
502+
if (!opts.onHover && !opts.onLeave) {
475503
return;
476504
}
477505
} else if (type === 'click') {
@@ -483,33 +511,26 @@ var Legend = Element.extend({
483511
}
484512

485513
// Chart event already has relative position in it
486-
var x = e.x;
487-
var y = e.y;
514+
hoveredItem = me._getLegendItemAt(e.x, e.y);
488515

489-
if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {
490-
// See if we are touching one of the dataset boxes
491-
var lh = me.legendHitBoxes;
492-
for (var i = 0; i < lh.length; ++i) {
493-
var hitBox = lh[i];
494-
495-
if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
496-
// Touching an element
497-
if (type === 'click') {
498-
// use e.native for backwards compatibility
499-
opts.onClick.call(me, e.native, me.legendItems[i]);
500-
changed = true;
501-
break;
502-
} else if (type === 'mousemove') {
503-
// use e.native for backwards compatibility
504-
opts.onHover.call(me, e.native, me.legendItems[i]);
505-
changed = true;
506-
break;
507-
}
516+
if (type === 'click') {
517+
if (hoveredItem && opts.onClick) {
518+
// use e.native for backwards compatibility
519+
opts.onClick.call(me, e.native, hoveredItem);
520+
}
521+
} else {
522+
if (opts.onLeave && hoveredItem !== me._hoveredItem) {
523+
if (me._hoveredItem) {
524+
opts.onLeave.call(me, e.native, me._hoveredItem);
508525
}
526+
me._hoveredItem = hoveredItem;
509527
}
510-
}
511528

512-
return changed;
529+
if (opts.onHover && hoveredItem) {
530+
// use e.native for backwards compatibility
531+
opts.onHover.call(me, e.native, hoveredItem);
532+
}
533+
}
513534
}
514535
});
515536

test/specs/plugin.legend.tests.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ describe('Legend block tests', function() {
1111
// a callback that will handle
1212
onClick: jasmine.any(Function),
1313
onHover: null,
14+
onLeave: null,
1415

1516
labels: {
1617
boxWidth: 40,
@@ -653,4 +654,53 @@ describe('Legend block tests', function() {
653654
expect(chart.legend.options).toEqual(jasmine.objectContaining(Chart.defaults.global.legend));
654655
});
655656
});
657+
658+
describe('callbacks', function() {
659+
it('should call onClick, onHover and onLeave at the correct times', function() {
660+
var clickItem = null;
661+
var hoverItem = null;
662+
var leaveItem = null;
663+
664+
var chart = acquireChart({
665+
type: 'line',
666+
data: {
667+
labels: ['A', 'B', 'C', 'D'],
668+
datasets: [{
669+
data: [10, 20, 30, 100]
670+
}]
671+
},
672+
options: {
673+
legend: {
674+
onClick: function(_, item) {
675+
clickItem = item;
676+
},
677+
onHover: function(_, item) {
678+
hoverItem = item;
679+
},
680+
onLeave: function(_, item) {
681+
leaveItem = item;
682+
}
683+
}
684+
}
685+
});
686+
687+
var hb = chart.legend.legendHitBoxes[0];
688+
var el = {
689+
x: hb.left + (hb.width / 2),
690+
y: hb.top + (hb.height / 2)
691+
};
692+
693+
jasmine.triggerMouseEvent(chart, 'click', el);
694+
695+
expect(clickItem).toBe(chart.legend.legendItems[0]);
696+
697+
jasmine.triggerMouseEvent(chart, 'mousemove', el);
698+
699+
expect(hoverItem).toBe(chart.legend.legendItems[0]);
700+
701+
jasmine.triggerMouseEvent(chart, 'mousemove', chart.getDatasetMeta(0).data[0]);
702+
703+
expect(leaveItem).toBe(chart.legend.legendItems[0]);
704+
});
705+
});
656706
});

0 commit comments

Comments
 (0)