4๐
Chart.js provides for โExternal (Custom) Tooltipsโ that can be built using whatever HTML you choose:
options: {
tooltips: {
// Disable the on-canvas tooltip
enabled: false,
custom: function(tooltipModel) {
// your custom tooltip code ...
Multiple examples are provided on the Chart.js samples page:
Based on your comment, hereโs a quick example of loading an image in a tooltip using the code in the documentation:
const tooltip = document.getElementById("tooltip");
new Chart(document.getElementById("chart"), {
type: "bar",
data: {
labels: ["A", "B", "C"],
datasets: [{
label: "Series 1",
data: [1, 4, 2]
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
},
tooltips: {
enabled: false,
custom: function(tooltipModel) {
// Hide if no tooltip
if (tooltipModel.opacity === 0) {
tooltip.style.opacity = 0;
return;
}
// show the tooltip.
tooltip.style.opacity = 1;
// create the img element and append it to the tooltip element.
const img = document.createElement('img');
img.src = "https://www.gravatar.com/avatar/6fcc51ca5e7029116a383e7aeb0bbaa0?s=32&d=identicon&r=PG&f=1";
tooltip.innerHTML = "";
tooltip.appendChild(img);
// move the tooltip to the 'correct' position.
const position = this._chart.canvas.getBoundingClientRect();
tooltip.style.left = position.left + window.pageXOffset + tooltipModel.caretX + 'px';
tooltip.style.top = position.top + window.pageYOffset + tooltipModel.caretY + 'px';
}
}
}
});
#tooltip {
opacity: 0;
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="chart"></canvas>
<div id="tooltip"></div>
2๐
This is not a complete answer, it is something to get you started.
You can create a custom tooltip using the renderer. You can also use the document like they did at the bottom here https://www.chartjs.org/docs/latest/configuration/tooltip.html but it may not work with server side generated applications.
The thisAsThat
is a good utility function => it allows you to refer to the Chart.js object as that
and allows you to refer to the class as this
.
At the bottom of this page of the link I provided, it shows how to make a custom tooltip. Take your time in going through it. Basically everywhere they use the document
, you can use the renderer
. To position and style the tooltip it is up to you and you may have to do calculations.
constructor(private renderer: Renderer2, @Inject(DOCUMENT) private document: Document) {}
private thisAsThat(callBack: Function) {
const self = this;
return function () {
return callBack.apply(self, [this].concat(Array.prototype.slice.call(arguments)));
};
}
options: {
tooltips: {
enabled: false,
custom: this.thisAsThat((that, tooltipModel: any) => {
// maybe you need chartPosition
const chartPosition = that._chart.canvas.getBoundingClientRect();
const tooltipEl = this.renderer.createElement('div');
const image = this.renderer.createElement('img');
// Pretty sure it is setProperty, can also give setAttribute a try as well
this.renderer.setProperty(image, 'src', 'src of image here');
this.renderer.setProperty(image, 'alt', 'Your alt');
this.renderer.appendChild(tooltipEl, image);
// Can also add a class as well.
this.renderer.setStyle(tooltipEl, 'background', 'black');
// this should add your tooltip at the end of the DOM right before the body tag
this.renderer.appendChild(this.document.body, tooltipEl);
})
}
}
1๐
In V3 the tooltip config has been moved to the options.plugins.tooltip
namespace, also the custom
option has been renamed to external
. Since it renders just html you can pass anything to it you want like so:
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.6.0/chart.js"></script>
</body>
const getOrCreateTooltip = (chart) => {
let tooltipEl = chart.canvas.parentNode.querySelector('div');
if (!tooltipEl) {
tooltipEl = document.createElement('div');
tooltipEl.style.background = 'rgba(0, 0, 0, 0.7)';
tooltipEl.style.borderRadius = '3px';
tooltipEl.style.color = 'white';
tooltipEl.style.opacity = 1;
tooltipEl.style.pointerEvents = 'none';
tooltipEl.style.position = 'absolute';
tooltipEl.style.transform = 'translate(-50%, 0)';
tooltipEl.style.transition = 'all .1s ease';
const table = document.createElement('table');
table.style.margin = '0px';
tooltipEl.appendChild(table);
chart.canvas.parentNode.appendChild(tooltipEl);
}
return tooltipEl;
};
const externalTooltipHandler = (context) => {
// Tooltip Element
const {
chart,
tooltip
} = context;
const tooltipEl = getOrCreateTooltip(chart);
// Hide if no tooltip
if (tooltip.opacity === 0) {
tooltipEl.style.opacity = 0;
return;
}
// Set Text
if (tooltip.body) {
const titleLines = tooltip.title || [];
const bodyLines = tooltip.body.map(b => b.lines);
const tableHead = document.createElement('thead');
titleLines.forEach(title => {
const tr = document.createElement('tr');
tr.style.borderWidth = 0;
const th = document.createElement('th');
th.style.borderWidth = 0;
const text = document.createTextNode(title);
th.appendChild(text);
// THIS BLOCK ADDED
const imageTh = document.createElement('th');
th.style.borderWidth = 0;
const image = document.createElement('img');
image.style = 'width:20px'
image.src = context.tooltip.dataPoints[0].dataset.url;
imageTh.appendChild(image);
tr.appendChild(th);
tr.appendChild(imageTh);
tableHead.appendChild(tr);
});
const tableBody = document.createElement('tbody');
bodyLines.forEach((body, i) => {
const colors = tooltip.labelColors[i];
const span = document.createElement('span');
span.style.background = colors.backgroundColor;
span.style.borderColor = colors.borderColor;
span.style.borderWidth = '2px';
span.style.marginRight = '10px';
span.style.height = '10px';
span.style.width = '10px';
span.style.display = 'inline-block';
const tr = document.createElement('tr');
tr.style.backgroundColor = 'inherit';
tr.style.borderWidth = 0;
const td = document.createElement('td');
td.style.borderWidth = 0;
const text = document.createTextNode(body);
td.appendChild(span);
td.appendChild(text);
tr.appendChild(td);
tableBody.appendChild(tr);
});
const tableRoot = tooltipEl.querySelector('table');
// Remove old children
if(tableRoot && tableRoot.firstChild) {
while (tableRoot.firstChild) {
tableRoot.firstChild.remove();
}
}
// Add new children
tableRoot.appendChild(tableHead);
tableRoot.appendChild(tableBody);
}
const {
offsetLeft: positionX,
offsetTop: positionY
} = chart.canvas;
// Display, position, and set styles for font
tooltipEl.style.opacity = 1;
tooltipEl.style.left = positionX + tooltip.caretX + 'px';
tooltipEl.style.top = positionY + tooltip.caretY + 'px';
tooltipEl.style.font = tooltip.options.bodyFont.string;
tooltipEl.style.padding = tooltip.options.padding + 'px ' + tooltip.options.padding + 'px';
};
const options = {
type: 'line',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderColor: 'pink',
backgroundColor: 'pink',
url: 'https://www.chartjs.org/img/chartjs-logo.svg'
},
{
label: '# of Points',
data: [7, 11, 5, 8, 3, 7],
borderColor: 'orange',
backgroundColor: 'orange',
url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/ef/Stack_Overflow_icon.svg/512px-Stack_Overflow_icon.svg.png'
}
]
},
options: {
plugins: {
tooltip: {
enabled: false,
position: 'nearest',
external: externalTooltipHandler
}
}
}
}
const ctx = document.getElementById('chartJSContainer').getContext('2d');
const chart = new Chart(ctx, options);
Fiddle link since stack sandbox didnt like the table: https://jsfiddle.net/Leelenaleee/xhrs2wvc/19/