Skip to content

Commit

Permalink
Add onLeave callback to legend (chartjs#6059)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonrimmer authored and simonbrunel committed Feb 24, 2019
1 parent c27619f commit ae23fbf
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 26 deletions.
124 changes: 124 additions & 0 deletions samples/legend/callbacks.html
@@ -0,0 +1,124 @@
<!doctype html>
<html>
<head>
<title>Legend Callbacks</title>
<script src="../../dist/Chart.min.js"></script>
<script src="../utils.js"></script>
<style>
body, html {
height: 100%;
font-family: sans-serif;
}
canvas{
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}

#log {
position: absolute;
right: 0;
top: 0;
bottom: 0;
background-color: #EEE;
float: right;
width: 20%;
padding: 8px;
overflow-y: auto;
white-space: pre;
line-height: 1.5em;
}
</style>
</head>

<body>
<div id="log"></div>
<div style="width:75%;">
<canvas id="canvas"></canvas>
</div>
<script>
var logEntry = 1;
var logElement = document.getElementById('log');
var utils = Samples.utils;
var inputs = {
min: 20,
max: 80,
count: 8,
decimals: 2,
continuity: 1
};
var config;

function log(text) {
logElement.innerText += logEntry + '. ' + text + '\n';
logElement.scrollTop = logElement.scrollHeight;
logEntry++;
}

function generateData() {
return utils.numbers(inputs);
}
function generateLabels() {
return utils.months({count: inputs.count});
}

utils.srand(42);

config = {
type: 'line',
data: {
labels: generateLabels(),
datasets: [{
label: 'My First dataset',
backgroundColor: utils.color(0),
borderColor: utils.color(0),
data: generateData(),
fill: false,
}, {
label: 'My Second dataset',
fill: false,
backgroundColor: utils.color(1),
borderColor: utils.color(1),
data: generateData(),
}]
},
options: {
legend: {
onHover: function(event, legendItem) {
log('onHover: ' + legendItem.text);
},
onLeave: function(event, legendItem) {
log('onLeave: ' + legendItem.text);
},
onClick: function(event, legendItem) {
log('onClick:' + legendItem.text);
}
},
title: {
display: true,
text: 'Chart.js Line Chart'
},
scales: {
xAxes: [{
display: true,
scaleLabel: {
display: true,
labelString: 'Month'
}
}],
yAxes: [{
display: true,
scaleLabel: {
display: true,
labelString: 'Value'
}
}]
}
}
};

new Chart(document.getElementById('canvas'), config);
</script>
</body>

</html>
3 changes: 3 additions & 0 deletions samples/samples.js
Expand Up @@ -148,6 +148,9 @@
}, {
title: 'Point style',
path: 'legend/point-style.html'
}, {
title: 'Callbacks',
path: 'legend/callbacks.html'
}]
}, {
title: 'Tooltip',
Expand Down
73 changes: 47 additions & 26 deletions src/plugins/plugin.legend.js
Expand Up @@ -30,6 +30,7 @@ defaults._set('global', {
},

onHover: null,
onLeave: null,

labels: {
boxWidth: 40,
Expand Down Expand Up @@ -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;
},
Expand Down Expand Up @@ -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') {
Expand All @@ -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);
}
}
}
});

Expand Down
50 changes: 50 additions & 0 deletions test/specs/plugin.legend.tests.js
Expand Up @@ -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,
Expand Down Expand Up @@ -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]);
});
});
});

0 comments on commit ae23fbf

Please sign in to comment.