Chart.js
Chart.js copied to clipboard
Axis onHover & onLeave
Feature Proposal
Support onHover
and onLeave
handlers per axis similar to the onHover
and onLeave
handler of the legend. Ideally triggered per tick? And ideally through arguments we can determine the tick index.
Feature Use Case
This in combination with tooltip.setActiveElements
would allow us to show tooltips for axis when hovering over ticks.
Like also mentioned in: https://github.com/chartjs/Chart.js/issues/3907
+1
You can write a custom plugin that will fire when hovering over labels (mind its not perfect, not fully optimized etc):
const findLabel = (labels, evt) => {
let found = false;
let res = null;
labels.forEach(l => {
l.labels.forEach(label => {
if (evt.x > label.x && evt.x < label.x2 && evt.y > label.y && evt.y < label.y2) {
res = label.label;
found = true;
}
});
});
return [found, res];
};
const getLabelHitboxes = (scales) => (Object.values(scales).map((s) => ({
scaleId: s.id,
labels: s._labelItems.map((e, i) => ({
x: e.translation[0] - s._labelSizes.widths[i] / 2,
x2: e.translation[0] + s._labelSizes.widths[i] / 2,
y: e.translation[1] - s._labelSizes.heights[i] / 2,
y2: e.translation[1] + s._labelSizes.heights[i] / 2,
label: e.label,
index: i
}))
})));
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'
},
{
label: '# of Points',
data: [7, 11, 5, 8, 3, 7],
borderColor: 'orange'
}
]
},
options: {},
plugins: [{
id: 'customHover',
afterEvent: (chart, event, opts) => {
const evt = event.event;
if (evt.type !== 'mousemove') {
return;
}
const [found, label] = findLabel(getLabelHitboxes(chart.scales), evt);
if (found) {
console.log(label);
}
}
}]
}
const ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
fiddle: https://jsfiddle.net/Leelenaleee/9zqxfn0y/33/
Update for 2023. translation
was moved into labelItem.options
. Also the example above doesn't show you how to actually show/hide a tooltip. So I added my impl below.
const getLabelHitboxes = (scales) => (Object.values(scales).map((s) => ({
scaleId: s.id,
labels: s._labelItems.map((labelItem, i) => ({
x: labelItem.options.translation[0] - s._labelSizes.widths[i] / 2,
x2: labelItem.options.translation[0] + s._labelSizes.widths[i] / 2,
y: labelItem.options.translation[1] - s._labelSizes.heights[i] / 2,
y2: labelItem.options.translation[1] + s._labelSizes.heights[i] / 2,
label: labelItem.label,
index: i
}))
})));
Then for showing the tooltip the only way I could get it to work was with a custom one.
// Showing the tooltip
function showTooltip(context, label, completed_at){
// Tooltip Element
let tooltipEl = document.getElementById('chartjs-tooltip');
// Create element on first render
if (!tooltipEl) {
tooltipEl = document.createElement('div');
tooltipEl.id = 'chartjs-tooltip';
tooltipEl.innerHTML = '<table></table>';
document.body.appendChild(tooltipEl);
}
tooltipEl.classList.remove("d-none")
const tooltipModel = context.tooltip;
// Set caret Position
tooltipEl.classList.remove('above', 'below', 'no-transform');
if (tooltipModel.yAlign) {
tooltipEl.classList.add(tooltipModel.yAlign);
} else {
tooltipEl.classList.add('no-transform');
}
// Set Text
const titleLines = [label]
const bodyLines = [completed_at];
let innerHtml = '<thead>';
titleLines.forEach(function(title) {
innerHtml += '<tr><th>' + title + '</th></tr>';
});
innerHtml += '</thead><tbody>';
bodyLines.forEach(function(body, i) {
innerHtml += '<tr><td><span>' + body + '</span></td></tr>';
});
innerHtml += '</tbody>';
let tableRoot = tooltipEl.querySelector('table');
tableRoot.innerHTML = innerHtml;
const position = context.canvas.getBoundingClientRect();
const bodyFont = Chart.helpers.toFont(tooltipModel.options.bodyFont);
// Display, position, and set styles for font
tooltipEl.style.opacity = .75;
tooltipEl.style.position = 'absolute';
tooltipEl.style.left = mouseX + 'px';
tooltipEl.style.top = mouseY + 'px';
tooltipEl.style.font = bodyFont.string;
tooltipEl.style.padding = '10px';
tooltipEl.style.backgroundColor = '#000';
tooltipEl.style.color = '#fff';
tooltipEl.style.borderRadius = '8px';
tooltipEl.style.pointerEvents = 'none';
}
Hiding the tooltip
function hideTooltip(context) {
let tooltipEl = document.getElementById('chartjs-tooltip');
// Create element on first render
if (!tooltipEl) {
tooltipEl = document.createElement('div');
tooltipEl.id = 'chartjs-tooltip';
tooltipEl.innerHTML = '<table></table>';
document.body.appendChild(tooltipEl);
}
tooltipEl.classList.add("d-none")
}
Then in the afterEvent
fn just called show or hide.
plugins: [{
id: 'customHover',
afterEvent: (chart, event, opts) => {
const evt = event.event;
if (evt.type !== 'mousemove') {
return;
}
const [found, label] = findLabel(getLabelHitboxes(tournamentPlayerResultsChart.scales), evt);
if (found)) {
showTooltip(chart, label, 'I added a date to the body here')
} else {
hideTooltip(chart)
}
},
}]
Mine looks like this:
@mitchtabian sorry but do you have a working example? i tried to follow what you wrote in a simple jsfiddle before trying in angular15 but could not get it to work. https://jsfiddle.net/4z6hr0qw/9/
not been able to get anything working so far.
would be really handy to have (eg) a tick: { onHover: (event, active, chart)=> .... }
type function that can be used to trigger an action when the Axis label is hovered over, similar to the onhover for each bar on a barchart etc