ChartJS: Custom tooltip always displaying - charts

Im using ChartJS to create a graph on my website.
Im trying to create a custom tooltip. According to the documentation, this should be easy:
var myPieChart = new Chart(ctx, {
type: 'pie',
data: data,
options: {
tooltips: {
custom: function(tooltip) {
// tooltip will be false if tooltip is not visible or should be hidden
if (!tooltip) {
return;
}
}
}
}
});
My problem is that the tooptip is never false and because of this my custom tooltip is always displayed.
Please see this JSFiddle (line 42 is never executed)
Question: Is it a bug that tooltip is never false, or am I missing something?

The custom tooltip option is used for when you want to create/style your own tooltip using HTLM/CSS outside of the scope of the canvas (and not use the built in tooltips at all).
In order to do this, you must define a place outside of your canvas to contain your tooltip (e.g. a div) and then use that container within your tooltips.custom function.
Here is an example where I used a custom tooltip to display the hovered pie chart section percentage in the middle of the chart. In this example I'm generating my tooltip inside a div with id "chartjs-tooltip". Notice how I interact with this div in my tooltips.custom function to position and change the value.
Also, the correct way to check if the tooltip should be hidden is to check it's opacity. The tooltip object will always exist, but when it should not be visible, the opacity is set to 0.
Chart.defaults.global.tooltips.custom = function(tooltip) {
// Tooltip Element
var tooltipEl = document.getElementById('chartjs-tooltip');
// Hide if no tooltip
if (tooltip.opacity === 0) {
tooltipEl.style.opacity = 0;
return;
}
// Set Text
if (tooltip.body) {
var total = 0;
// get the value of the datapoint
var value = this._data.datasets[tooltip.dataPoints[0].datasetIndex].data[tooltip.dataPoints[0].index].toLocaleString();
// calculate value of all datapoints
this._data.datasets[tooltip.dataPoints[0].datasetIndex].data.forEach(function(e) {
total += e;
});
// calculate percentage and set tooltip value
tooltipEl.innerHTML = '<h1>' + (value / total * 100) + '%</h1>';
}
// calculate position of tooltip
var centerX = (this._chartInstance.chartArea.left + this._chartInstance.chartArea.right) / 2;
var centerY = ((this._chartInstance.chartArea.top + this._chartInstance.chartArea.bottom) / 2);
// Display, position, and set styles for font
tooltipEl.style.opacity = 1;
tooltipEl.style.left = centerX + 'px';
tooltipEl.style.top = centerY + 'px';
tooltipEl.style.fontFamily = tooltip._fontFamily;
tooltipEl.style.fontSize = tooltip.fontSize;
tooltipEl.style.fontStyle = tooltip._fontStyle;
tooltipEl.style.padding = tooltip.yPadding + 'px ' + tooltip.xPadding + 'px';
};
Here is the full codepen example.
I hope that helps clear things up!

Related

How to set reset zoom button and title to exactly center of the high charts

I am trying to set the reset zoom button and title to exactly the center of the high chart. Here the title is the selected time range of the chart portion. I need to set this as dynamically centered based on the screen resolution.
chart:{
zoomType:'x',
resetZoomButton:{
position:{
verticalAlign:'top'
},
relativeTo:'chart'
},
events:{
selection:function(event){
this.setTitle({{text:selectedChartPortion,useHtml:true}})
}
}
}
here text:selectedChartPortion is a span tag that has some style properties and the selected value.
Expected Chart outlook:
Can anyone help me to resolve it?
I think that this config should help you to achieve a wanted effect:
xAxis: {
events: {
afterSetExtremes() {
const chart = this.chart;
const resetZoomButton = chart.resetZoomButton;
const padding = 5;
if (resetZoomButton) {
chart.title.translate(-chart.title.getBBox().width / 2 - padding, 0)
resetZoomButton.translate(chart.plotWidth / 2 + resetZoomButton.getBBox().width / 2 + padding, 0)
} else {
chart.title.translate(chart.title.getBBox().width / 2 + chart.title.translateX, 0)
}
}
}
}
Demo: https://jsfiddle.net/BlackLabel/8yjd9orx/
API: https://api.highcharts.com/highcharts/chart.resetZoomButton.position.align
API: https://api.highcharts.com/highcharts/xAxis.events.afterSetExtremes

Chartjs custom tooltip is not removing from UI

I have included chart.js custom tooltip using example in official docs.
Problem:
If someone have mouse on chart then this custom tooltip is displayed. And while the mouse is in chart, if someone uses tabs/enter to navigate to another page, this custom tooltip function is not called. As a consequence, tooltip is not removed from UI in rest of the interactions with app. I have inspected the core library and I think it was only detecting mouseout event which in this case, didn't call this custom tooltip hook.
Below attached is my code for tooltip configuration, and I am using react-chartjs-2 wrapper to consume chart.js
Chart.js version: 2.9.1
react-chartjs-2: 2.8.0
tooltips: {
enabled: false,
placement: 'left',
caretSize: 5,
cornerRadius: 6,
custom: function(tooltipModel) {
let tooltipEl = document.getElementById('custom-tooltip');
// Create element on first render
if (!tooltipEl) {
tooltipEl = document.createElement('div');
tooltipEl.id = 'custom-tooltip';
document.body.appendChild(tooltipEl);
}
// Hide if no tooltip
if (tooltipModel.opacity === 0) {
tooltipEl.style.opacity = 0;
return;
}
let position = this._chart.canvas.getBoundingClientRect();
let clickPointPosition = position.left + window.pageXOffset + tooltipModel.caretX;
// Display, position, and set styles for font
tooltipEl.style.opacity = 0.9;
tooltipEl.style.position = 'absolute';
tooltipEl.style.left = (clickPointPosition < tooltipEl.offsetWidth ? clickPointPosition : clickPointPosition - tooltipEl.offsetWidth) + 'px';
tooltipEl.style.top = position.top + window.pageYOffset + tooltipModel.caretY + 'px';
tooltipEl.style.fontFamily = tooltipModel._bodyFontFamily;
tooltipEl.style.fontSize = tooltipModel.bodyFontSize + 'px';
tooltipEl.style.fontStyle = tooltipModel._bodyFontStyle;
tooltipEl.style.padding = tooltipModel.yPadding + 'px ' + tooltipModel.xPadding + 'px';
tooltipEl.style.pointerEvents = 'none';
}
},
Just for reference, This issue was resolved by adding animation: false and var tooltipModel = this.
Luckily, this issue was resolved by chartjs. Otherwise an alternate solution is to hide the tooltip in componentWillUnmount hook of your component or react-chartjs-2 wrapper component.
Here is link to actual github issue which includes fiddles as well for demonstration.
https://github.com/chartjs/Chart.js/issues/7493

References in axis using chart.js (or another library)

Im trying to make a graph like this:
https://www.google.com/finance?q=BCBA:PAMP
I have a line chart in chart.js, now I want to add labels (like the letters A, B, C) for certain dates.
Can't find a doc/example to start from. Any idea?
If its more simple to do with another library a recommendation is more than welcome.
Thanks!
Unfortunately, there is no native support in chart.js for what you are wanting. However, you can certainly add this capability using the plugin interface. This requires that you implement your own logic to draw the canvas pixels at the locations that you want them. It might sound challenging, but its easier than it sounds.
Here is an example plugin that will add a value above specific points in the chart (based upon configuration).
Chart.plugins.register({
afterDraw: function(chartInstance) {
if (chartInstance.config.options.showDatapoints || chartInstance.config.options.showDatapoints.display) {
var showOnly = chartInstance.config.options.showDatapoints.showOnly || [];
var helpers = Chart.helpers;
var ctx = chartInstance.chart.ctx;
var fontColor = helpers.getValueOrDefault(chartInstance.config.options.showDatapoints.fontColor, chartInstance.config.options.defaultFontColor);
// render the value of the chart above the bar
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize + 5, 'normal', Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
ctx.fillStyle = fontColor;
chartInstance.data.datasets.forEach(function (dataset) {
for (var i = 0; i < dataset.data.length; i++) {
if (showOnly.includes(dataset.data[i])) {
var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model;
var scaleMax = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._yScale.maxHeight;
var yPos = (scaleMax - model.y) / scaleMax >= 0.93 ? model.y + 20 : model.y - 5;
ctx.fillText(dataset.data[i], model.x, yPos);
}
}
});
}
}
});
It allows you to configure which points you want to annotate using this new configuration. The showOnly option contains the points that you want to label.
options: {
showDatapoints: {
display: true,
showOnly: [3, 10, 9]
},
}
Obviously, this only adds the datapoint value at the specified points, but you can just change the plugin to paint whatever you want to show instead. Simply replace ctx.fillText(dataset.data[i], model.x, yPos) with different code to render something different on the canvas.
Here is a codepen example to show you want it looks like.

Display values outside of pie chart in chartjs

When I hover on pie chart, the values are displayed in tooltip. However, I want to display values outside of pie chart. I want to make chart like this image:
How to do this?
I was able to get something similar working using chart.js v2.3.0 using both the plugin API and extending chart types API. You should be able to take this as a starting point and tweak it to your needs.
Here is how it looks after being rendered.
Note, this requires digging deep into chart.js internals and could break if they change the way tooltips are positioned or rendered in the future. I also added a new configuration option called showAllTooltips to enable selectively using the plugin on certain charts. This should work for all chart types, but I am currently only using it for pie, doughnut, bar, and line charts so far.
With that said, here is a working solution for the image above.
Chart.plugins.register({
beforeRender: function (chart) {
if (chart.config.options.showAllTooltips) {
// create a namespace to persist plugin state (which unfortunately we have to do)
if (!chart.showAllTooltipsPlugin) {
chart.showAllTooltipsPlugin = {};
}
// turn off normal tooltips in case it was also enabled (which is the global default)
chart.options.tooltips.enabled = false;
// we can't use the chart tooltip because there is only one tooltip per chart which gets
// re-positioned via animation steps.....so let's create a place to hold our tooltips
chart.showAllTooltipsPlugin.tooltipsCollection = [];
// create a tooltip for each plot on the chart
chart.config.data.datasets.forEach(function (dataset, i) {
chart.getDatasetMeta(i).data.forEach(function (sector, j) {
// but only create one for pie and doughnut charts if the plot is large enough to even see
if (!_.contains(['doughnut', 'pie'], sector._chart.config.type) || sector._model.circumference > 0.1) {
var tooltip;
// create a new tooltip based upon configuration
if (chart.config.options.showAllTooltips.extendOut) {
// this tooltip reverses the location of the carets from the default
tooltip = new Chart.TooltipReversed({
_chart: chart.chart,
_chartInstance: chart,
_data: chart.data,
_options: chart.options.tooltips,
_active: [sector]
}, chart);
} else {
tooltip = new Chart.Tooltip({
_chart: chart.chart,
_chartInstance: chart,
_data: chart.data,
_options: chart.options.tooltips,
_active: [sector]
}, chart);
}
// might as well initialize this now...it would be a waste to do it once we are looping over our tooltips
tooltip.initialize();
// save the tooltips so they can be rendered later
chart.showAllTooltipsPlugin.tooltipsCollection.push(tooltip);
}
});
});
}
},
afterDraw: function (chart, easing) {
if (chart.config.options.showAllTooltips) {
// we want to wait until everything on the chart has been rendered before showing the
// tooltips for the first time...otherwise it looks weird
if (!chart.showAllTooltipsPlugin.initialRenderComplete) {
// still animating until easing === 1
if (easing !== 1) {
return;
}
// animation is complete, let's remember that fact
chart.showAllTooltipsPlugin.initialRenderComplete = true;
}
// at this point the chart has been fully rendered for the first time so start rendering tooltips
Chart.helpers.each(chart.showAllTooltipsPlugin.tooltipsCollection, function (tooltip) {
// create a namespace to persist plugin state within this tooltip (which unfortunately we have to do)
if (!tooltip.showAllTooltipsPlugin) {
tooltip.showAllTooltipsPlugin = {};
}
// re-enable this tooltip otherise it won't be drawn (remember we disabled all tooltips in beforeRender)
tooltip._options.enabled = true;
// perform standard tooltip setup (which determines it's alignment and x, y coordinates)
tooltip.update(); // determines alignment/position and stores in _view
tooltip.pivot(); // we don't actually need this since we are not animating tooltips, but let's be consistent
tooltip.transition(easing).draw(); // render and animate the tooltip
// disable this tooltip in case something else tries to do something with it later
tooltip._options.enabled = false;
});
}
},
});
// A 'reversed' tooltip places the caret on the opposite side from the current default.
// In order to do this we just need to change the 'alignment' logic
Chart.TooltipReversed = Chart.Tooltip.extend({
// Note: tooltipSize is the size of the box (not including the caret)
determineAlignment: function(tooltipSize) {
var me = this;
var model = me._model;
var chart = me._chart;
var chartArea = me._chartInstance.chartArea;
// set caret position to top or bottom if tooltip y position will extend outsite the chart top/bottom
if (model.y < tooltipSize.height) {
model.yAlign = 'top';
} else if (model.y > (chart.height - tooltipSize.height)) {
model.yAlign = 'bottom';
}
var leftAlign, rightAlign; // functions to determine left, right alignment
var overflowLeft, overflowRight; // functions to determine if left/right alignment causes tooltip to go outside chart
var yAlign; // function to get the y alignment if the tooltip goes outside of the left or right edges
var midX = (chartArea.left + chartArea.right) / 2;
var midY = (chartArea.top + chartArea.bottom) / 2;
if (model.yAlign === 'center') {
leftAlign = function(x) {
return x >= midX;
};
rightAlign = function(x) {
return x < midX;
};
} else {
leftAlign = function(x) {
return x <= (tooltipSize.width / 2);
};
rightAlign = function(x) {
return x >= (chart.width - (tooltipSize.width / 2));
};
}
overflowLeft = function(x) {
return x - tooltipSize.width < 0;
};
overflowRight = function(x) {
return x + tooltipSize.width > chart.width;
};
yAlign = function(y) {
return y <= midY ? 'bottom' : 'top';
};
if (leftAlign(model.x)) {
model.xAlign = 'left';
// Is tooltip too wide and goes over the right side of the chart.?
if (overflowLeft(model.x)) {
model.xAlign = 'center';
model.yAlign = yAlign(model.y);
}
} else if (rightAlign(model.x)) {
model.xAlign = 'right';
// Is tooltip too wide and goes outside left edge of canvas?
if (overflowRight(model.x)) {
model.xAlign = 'center';
model.yAlign = yAlign(model.y);
}
}
}
});

Add a vertical line marker to Google Visualization's LineChart that moves when mouse move?

Is it possible to display a vertical Line marker showing the current x-axis value on LineChart, and moving when mouse moves ?
Thanks in advance.
While this was difficult before, a recent update to the API makes it much easier! You need to use a mouseover event handler to get the mouse coordinates and the new ChartLayoutInterface to translate the coordinates into chart values. Here's some example code:
[edit - fixed cross-browser compatibility issue]
*[edit - updated to get the value of points near the annotation line]*
function drawChart() {
// Create and populate the data table.
var data = new google.visualization.DataTable();
data.addColumn('number', 'x');
// add an "annotation" role column to the domain column
data.addColumn({type: 'string', role: 'annotation'});
data.addColumn('number', 'y');
// add 100 rows of pseudorandom data
var y = 50;
for (var i = 0; i < 100; i++) {
y += Math.floor(Math.random() * 5) * Math.pow(-1, Math.floor(Math.random() * 2));
data.addRow([i, null, y]);
}
// add a blank row to the end of the DataTable to hold the annotations
data.addRow([null, null, null]);
// get the index of the row used for the annotations
var annotationRowIndex = data.getNumberOfRows() - 1;
var options = {
annotation: {
1: {
// set the style of the domain column annotations to "line"
style: 'line'
}
},
height: 400,
width: 600
};
// create the chart
var chart = new google.visualization.LineChart(document.getElementById('chart_div'));
// create 'ready' event listener to add mousemove event listener to the chart
var runOnce = google.visualization.events.addListener(chart, 'ready', function () {
google.visualization.events.removeListener(runOnce);
// create mousemove event listener in the chart's container
// I use jQuery, but you can use whatever works best for you
$('#chart_div').mousemove(function (e) {
var xPos = e.pageX - container.offsetLeft;
var yPos = e.pageY - container.offsetTop;
var cli = chart.getChartLayoutInterface();
var xBounds = cli.getBoundingBox('hAxis#0#gridline');
var yBounds = cli.getBoundingBox('vAxis#0#gridline');
// is the mouse inside the chart area?
if (
(xPos >= xBounds.left || xPos <= xBounds.left + xBounds.width) &&
(yPos >= yBounds.top || yPos <= yBounds.top + yBounds.height)
) {
// if so, draw the vertical line here
// get the x-axis value at these coordinates
var xVal = cli.getHAxisValue(xPos);
// set the x-axis value of the annotation
data.setValue(annotationRowIndex, 0, xVal);
// set the value to display on the line, this could be any value you want
data.setValue(annotationRowIndex, 1, xVal.toFixed(2));
// get the data value (if any) at the line
// truncating xVal to one decimal place,
// since it is unlikely to find an annotation like that aligns precisely with the data
var rows = data.getFilteredRows([{column: 0, value: parseFloat(xVal.toFixed(1))}]);
if (rows.length) {
var value = data.getValue(rows[0], 2);
// do something with value
}
// draw the chart with the new annotation
chart.draw(data, options);
}
});
});
// draw the chart
chart.draw(data, options);
}
See it working here: http://jsfiddle.net/asgallant/tVCv9/12