Chart.js is a lovely library for interactive charts. It’s probably the best free and open source library for JavaScript based charts available at the moment.
For a project that creates custom charts based on data collected with our quiz and survey WP plugin we had to create custom tooltips that appear when the mouse hovers any of a linear chart datapoints.
The Problem
The default tooltips in chart.js unfortunately allow only text. For our project we needed to include fully featured HTML code – a pop-up with an image and text had to appear when the mouse is hovered on a data point.
The Solution In Action
Here is what you will achieve after following this kind-of a tutorial:
You see how the tooltips are fully featured HTML boxes with text, image(s), and a close button.
The Solution in Code
For this post we just created a hardcoded chart with 5 data points and 5 divs with information about them. It’s of course more likely that you will use some dynamic data from a database, etc. So you will need to loop through the data and generate parts of the HTML and JS code. This however does not change the logic that we will describe here.
First, let’s create some basic HTML. You need a div with information (tooltip contents) for each data point. The important thing here is to place these divs AND your chart canvas in a wrapper with position:relative. Each of the info divs has position:absolute. This allows you to properly display the divs pretty close to each data point.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<styletype="text/css">
.pointinfo-container {
display:none;
padding: 7px;
background: white;
width: 200px;
border: 1pt solid black;
position: absolute;
}
</style>
<divstyle="position: relative;"><!-- This is the wrapper -->
<divid="pointInfo1"class="pointinfo-container">
<h3class="pointinfo-event-title">Info about One</h3>
// Remember, we are hovering the mouse. Don't expect the user to be exact and give some allowance which will display the info when the user is close to any
// of the data points. Here we give allowance of 10 pixels around each point.
onHover: (e) => {
// this gets the meta data of the chart to figure out the coordinates of each data point
// This part is very important and specific to Chart.js version 3. In version 2 the syntax will not work.
var meta = myChart.getDatasetMeta(0);
datapoints = [
{'x' : Math.round(meta.data[0].x), 'y': Math.round(meta.data[0].y)},
{'x' : Math.round(meta.data[1].x), 'y': Math.round(meta.data[1].y)},
{'x' : Math.round(meta.data[2].x), 'y': Math.round(meta.data[2].y)},
{'x' : Math.round(meta.data[3].x), 'y': Math.round(meta.data[3].y)},
{'x' : Math.round(meta.data[4].x), 'y': Math.round(meta.data[4].y)},
];
if(typeof(datapoints) === 'undefined') return false;
// Once we know the datapoints position, we also need to get the x/y of the canvas
const canvasPosition = Chart.helpers.getRelativePosition(e, myChart);
jQuery(datapoints).each(function(i, elt){
let j = i+1;
// Remember, we are hovering the mouse. Don't expect the user to be exact and give some allowance which will display the info when the user is close to any
// of the data points. Here we give allowance of 10 pixels around each point.
if(elt.x >= canvasPosition.x - 10 && elt.x <= canvasPosition.x + 10 && elt.y >= canvasPosition.y - 10 && elt.y <= canvasPosition.y + 10) {
// and the stuff here is rather straightforward: set the element position around the datapoint position
jQuery('#pointInfo' + j).css( {position:"absolute", top: canvasPosition.y - 50, left: canvasPosition.x - 50});
// then show
jQuery('#pointInfo' + j).show();
}
});
},
onHover: (e) => {
// this gets the meta data of the chart to figure out the coordinates of each data point
// This part is very important and specific to Chart.js version 3. In version 2 the syntax will not work.
var meta = myChart.getDatasetMeta(0);
datapoints = [
{'x' : Math.round(meta.data[0].x), 'y': Math.round(meta.data[0].y)},
{'x' : Math.round(meta.data[1].x), 'y': Math.round(meta.data[1].y)},
{'x' : Math.round(meta.data[2].x), 'y': Math.round(meta.data[2].y)},
{'x' : Math.round(meta.data[3].x), 'y': Math.round(meta.data[3].y)},
{'x' : Math.round(meta.data[4].x), 'y': Math.round(meta.data[4].y)},
];
if(typeof(datapoints) === 'undefined') return false;
// Once we know the datapoints position, we also need to get the x/y of the canvas
const canvasPosition = Chart.helpers.getRelativePosition(e, myChart);
jQuery(datapoints).each(function(i, elt){
let j = i+1;
// Remember, we are hovering the mouse. Don't expect the user to be exact and give some allowance which will display the info when the user is close to any
// of the data points. Here we give allowance of 10 pixels around each point.
if(elt.x >= canvasPosition.x - 10 && elt.x <= canvasPosition.x + 10 && elt.y >= canvasPosition.y - 10 && elt.y <= canvasPosition.y + 10) {
// and the stuff here is rather straightforward: set the element position around the datapoint position
jQuery('#pointInfo' + j).css( {position:"absolute", top: canvasPosition.y - 50, left: canvasPosition.x - 50});
// then show
jQuery('#pointInfo' + j).show();
}
});
},
The most important part above is getting the data points coordinates. You need to do this inside the onHover function. In Chart.js version 2 you could do it in a document.ready handler, but this no longer works correctly.
The data points information is retrieved through the getDatasetMeta() method of the library. The argument is the dataset number, staring at 0. (Our chart has only one dataset).
Each data point is in the resulting object property meta.data. It is an array of datapoint objects and you need the properties x and y of each of them.
Finally, you need a function to close the pop-up when the user clicks on the close()
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
functionclosePointInfo(cnt){
jQuery('#pointInfo' + cnt).hide();
// if all point infos are hidden, make sure the show all mode is show
function closePointInfo(cnt) {
jQuery('#pointInfo' + cnt).hide();
// if all point infos are hidden, make sure the show all mode is show
let anyOpen = false;
jQuery('.pointinfo-container').each(function(i, elt){
if(jQuery(elt).is(":visible")) {
anyOpen = true;
return;
}
});
}
function closePointInfo(cnt) {
jQuery('#pointInfo' + cnt).hide();
// if all point infos are hidden, make sure the show all mode is show
let anyOpen = false;
jQuery('.pointinfo-container').each(function(i, elt){
if(jQuery(elt).is(":visible")) {
anyOpen = true;
return;
}
});
}
That’s it. If you have used this somewhere, let us know in the comments!