I am stuck with a problem on chart js while creating line chart. I want to create a chart with the specified data and also need to have horizontal and vertical line while I hover on intersection point. I am able to create vertical line on hover but can not find any solution where I can draw both the line. Here is my code to draw vertical line on hover.
window.lineOnHover = function(){
Chart.defaults.LineWithLine = Chart.defaults.line;
Chart.controllers.LineWithLine = Chart.controllers.line.extend({
draw: function(ease) {
Chart.controllers.line.prototype.draw.call(this, ease);
if (this.chart.tooltip._active && this.chart.tooltip._active.length) {
var activePoint = this.chart.tooltip._active[0],
ctx = this.chart.ctx,
x = activePoint.tooltipPosition().x,
topY = this.chart.legend.bottom,
bottomY = this.chart.chartArea.bottom;
// draw line
ctx.save();
ctx.beginPath();
ctx.moveTo(x, topY);
ctx.lineTo(x, bottomY);
ctx.lineWidth = 1;
ctx.setLineDash([3,3]);
ctx.strokeStyle = '#FF4949';
ctx.stroke();
ctx.restore();
}
}
});
}
//create chart
var backhaul_wan_mos_chart = new Chart(backhaul_wan_mos_chart, {
type: 'LineWithLine',
data: {
labels: ['Aug 1', 'Aug 2', 'Aug 3', 'Aug 4', 'Aug 5', 'Aug 6', 'Aug 7', 'Aug 8'],
datasets: [{
label: 'Series 1',
data: [15, 16, 17, 18, 16, 18, 17, 14, 19, 16, 15, 15, 17],
pointRadius: 0,
fill: false,
borderDash: [3, 3],
borderColor: '#0F1731',
// backgroundColor: '#FF9CE9',
// pointBackgroundColor: ['#FB7BDF'],
borderWidth: 1
}],
// lineAtIndex: 2,
},
options: {
tooltips: {
intersect: false
},
legend: {
display: false
},
scales: {
xAxes: [{
gridLines: {
offsetGridLines: true
},
ticks: {
fontColor: '#878B98',
fontStyle: "600",
fontSize: 10,
fontFamily: "Poppins"
}
}],
yAxes: [{
display: true,
stacked: true,
ticks: {
min: 0,
max: 50,
stepSize: 10,
fontColor: '#878B98',
fontStyle: "500",
fontSize: 10,
fontFamily: "Poppins"
}
}]
},
responsive: true,
}
});
my output of the code is as follow in WAN MoS Score graph --
So I want to have an horizontal line with the same vertical line together when I hover on the intersection (plotted) point..
Please help my guys..Thanks in advance.
You can just add a second draw block for the y coordinate that you get from the tooltip, first you move to the left of the chartArea that you can get the same way you got bottom and top and then you move to the right on the same Y
Chart.defaults.LineWithLine = Chart.defaults.line;
Chart.controllers.LineWithLine = Chart.controllers.line.extend({
draw: function(ease) {
Chart.controllers.line.prototype.draw.call(this, ease);
if (this.chart.tooltip._active && this.chart.tooltip._active.length) {
var activePoint = this.chart.tooltip._active[0],
ctx = this.chart.ctx,
x = activePoint.tooltipPosition().x,
y = activePoint.tooltipPosition().y,
topY = this.chart.legend.bottom,
bottomY = this.chart.chartArea.bottom,
left = this.chart.chartArea.left,
right = this.chart.chartArea.right;
// Set line opts
ctx.save();
ctx.lineWidth = 1;
ctx.setLineDash([3, 3]);
ctx.strokeStyle = '#FF4949';
// draw vertical line
ctx.beginPath();
ctx.moveTo(x, topY);
ctx.lineTo(x, bottomY);
ctx.stroke();
// Draw horizontal line
ctx.beginPath();
ctx.moveTo(left, y);
ctx.lineTo(right, y);
ctx.stroke();
ctx.restore();
}
}
});
var options = {
type: 'LineWithLine',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderWidth: 1
},
{
label: '# of Points',
data: [7, 11, 5, 8, 3, 7],
borderWidth: 1
}
]
},
options: {
}
}
var ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.js"></script>
</body>
Edit:
You should use a custom plugin for this since you dont draw everytime you move the cursor and you can enforce this by using a custom plugin:
const options = {
type: 'line',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderWidth: 1
},
{
label: '# of Points',
data: [7, 11, 5, 8, 3, 7],
borderWidth: 1
}
]
},
options: {
plugins: {
corsair: {
dash: [2, 2],
color: 'red',
width: 3
}
}
},
plugins: [{
id: 'corsair',
afterInit: (chart) => {
chart.corsair = {
x: 0,
y: 0
}
},
afterEvent: (chart, evt) => {
const {
chartArea: {
top,
bottom,
left,
right
}
} = chart;
const {
x,
y
} = evt;
if (x < left || x > right || y < top || y > bottom) {
chart.corsair = {
x,
y,
draw: false
}
chart.draw();
return;
}
chart.corsair = {
x,
y,
draw: true
}
chart.draw();
},
afterDatasetsDraw: (chart, _, opts) => {
const {
ctx,
chartArea: {
top,
bottom,
left,
right
}
} = chart;
const {
x,
y,
draw
} = chart.corsair;
if (!draw) {
return;
}
ctx.lineWidth = opts.width || 0;
ctx.setLineDash(opts.dash || []);
ctx.strokeStyle = opts.color || 'black'
ctx.save();
ctx.beginPath();
ctx.moveTo(x, bottom);
ctx.lineTo(x, top);
ctx.moveTo(left, y);
ctx.lineTo(right, y);
ctx.stroke();
ctx.restore();
}
}]
}
const ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.js"></script>
</body>
Edit:
Updated answer for v3
const options = {
type: 'line',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderWidth: 1
},
{
label: '# of Points',
data: [7, 11, 5, 8, 3, 7],
borderWidth: 1
}
]
},
options: {
plugins: {
corsair: {
dash: [2, 2],
color: 'red',
width: 3
}
}
},
plugins: [{
id: 'corsair',
afterInit: (chart) => {
chart.corsair = {
x: 0,
y: 0
}
},
afterEvent: (chart, evt) => {
const {
chartArea: {
top,
bottom,
left,
right
}
} = chart;
const {
event: {
x,
y
}
} = evt;
if (x < left || x > right || y < top || y > bottom) {
chart.corsair = {
x,
y,
draw: false
}
chart.draw();
return;
}
chart.corsair = {
x,
y,
draw: true
}
chart.draw();
},
afterDatasetsDraw: (chart, _, opts) => {
const {
ctx,
chartArea: {
top,
bottom,
left,
right
}
} = chart;
const {
x,
y,
draw
} = chart.corsair;
if (!draw) {
return;
}
ctx.lineWidth = opts.width || 0;
ctx.setLineDash(opts.dash || []);
ctx.strokeStyle = opts.color || 'black'
ctx.save();
ctx.beginPath();
ctx.moveTo(x, bottom);
ctx.lineTo(x, top);
ctx.moveTo(left, y);
ctx.lineTo(right, y);
ctx.stroke();
ctx.restore();
}
}]
}
const ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.8.0/chart.js"></script>
</body>
This is 2022, the current version of ChartJS is 4.0.1. So, I recommend to use this new implementation.
First, let's define a plugin. ChartJS's plugins has an id parameter, in this case less say corsair.
Then we define default variables for our plugin, like width, color and line dash. Additionally, our plugin will have three parameters: x, y, and draw. x and y are the values of the mousemove event and draw represents the inChartArea parameter, this parameter defines if the event occurred inside of the chart area or not.
Finally, we capture the afterDraw hook to draw a vertical and horizontal lines based on the x and y values if the event was triggered inside of the chart area.
ChartJS has various hooks to capture different parts of the chart render cycle.
const plugin = {
id: 'corsair',
defaults: {
width: 1,
color: '#FF4949',
dash: [3, 3],
},
afterInit: (chart, args, opts) => {
chart.corsair = {
x: 0,
y: 0,
}
},
afterEvent: (chart, args) => {
const {inChartArea} = args
const {type,x,y} = args.event
chart.corsair = {x, y, draw: inChartArea}
chart.draw()
},
beforeDatasetsDraw: (chart, args, opts) => {
const {ctx} = chart
const {top, bottom, left, right} = chart.chartArea
const {x, y, draw} = chart.corsair
if (!draw) return
ctx.save()
ctx.beginPath()
ctx.lineWidth = opts.width
ctx.strokeStyle = opts.color
ctx.setLineDash(opts.dash)
ctx.moveTo(x, bottom)
ctx.lineTo(x, top)
ctx.moveTo(left, y)
ctx.lineTo(right, y)
ctx.stroke()
ctx.restore()
}
}
const data = {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderWidth: 1
},
{
label: '# of Points',
data: [7, 11, 5, 8, 3, 7],
borderWidth: 1
}
]
}
const options = {
maintainAspectRatio: false,
hover: {
mode: 'index',
intersect: false,
},
plugins: {
corsair: {
color: 'black',
}
}
}
const config = {
type: 'line',
data,
options,
plugins: [plugin],
}
const $chart = document.getElementById('chart')
const chart = new Chart($chart, config)
<div class="wrapper" style="width: 98vw; height: 180px">
<canvas id="chart"></canvas>
</div>
<script src="https://unpkg.com/chart.js#4.0.1/dist/chart.umd.js"></script>
I have done exactly this (but vertical line only) in a previous version of one of my projects. Unfortunately this feature has been removed but the older source code file can still be accessed via my github.
The key is this section of the code:
Chart.defaults.LineWithLine = Chart.defaults.line;
Chart.controllers.LineWithLine = Chart.controllers.line.extend({
draw: function(ease) {
Chart.controllers.line.prototype.draw.call(this, ease);
if (this.chart.tooltip._active && this.chart.tooltip._active.length) {
var activePoint = this.chart.tooltip._active[0],
ctx = this.chart.ctx,
x = activePoint.tooltipPosition().x,
topY = this.chart.legend.bottom,
bottomY = this.chart.chartArea.bottom;
// draw line
ctx.save();
ctx.beginPath();
ctx.moveTo(x, topY);
ctx.lineTo(x, bottomY);
ctx.lineWidth = 0.5;
ctx.strokeStyle = '#A6A6A6';
ctx.stroke();
ctx.restore();
}
}
});
Another caveat is that the above code works with Chart.js 2.8 and I am aware that the current version of Chart.js is 3.1. I haven't read the official manual on the update but my personal experience is that this update is not 100% backward-compatible--so not sure if it still works if you need Chart.js 3. (But sure you may try 2.8 first and if it works you can then somehow tweak the code to make it work on 3.1)
I need to plot some information that the X-axis is the time and the Y-axis is the a numerical value. The structure is similar to the next table,
Name Date Result
N1 9:00 88
N1 9:10 84
N1 9:16 83
N1 9:23 83
N1 9:29 85
N2 9:03 87
N2 9:07 87
N2 9:18 87
N2 9:25 86
N2 9:29 86
Thus, what I need with this example, are two independent lines that each one work with it data.
To show this info I'm using chart.js. My first idea was to use the scatter type graph, but this is not possible because it only works with numerical values. Therefore, I thought about using the line type graph, but the problem with this is that by default you can only define a single label and how you can see I need each information to be independent since they don't have to match the hours.
Is it possible to add another label? I've tried to define two lists but it doesn't work and I don't know how to deal with it.
let ctx = document.getElementById("my_chart").getContext("2d");
my_chart = new Chart(ctx, {
type: 'line',
data: {
labels: [
['9:00','9:10','9:16','9:23','9:29'],
['9:03','9:07','9:18','9:25','9:29']
],
datasets: [{
data: [88,84,83,83,85],
label: "N1",
borderColor: "#8e5ea2",
fill: false
}, {
data: [87,87,87,86,86],
label: "N2",
borderColor: "#c45850",
fill: false
}]
},
options: {
title: {
display: true,
text: 'Info'
}
}
});
EDIT: My modifications with the help of #alonas and #HeadhunterKev
This is what I have now, is not working I suppose the format is wrong but I don't know what. Maybe I need seconds? Probably is a silly question but I'm lost with this...
var sData = {
datasets: [{
label: 'Dataset1',
data: [{
x: '09:00',
y: 88
}, {
x: '09:10',
y: 89
}, {
x: '09:13',
y: 86
}, {
x: '09:23',
y: 86
}, {
x: '09:26',
y: 85
}, {
x: '09:29',
y: 83
}]
}, {
label: 'Dataset2',
data: [{
x: '09:02',
y: 88
}, {
x: '09:13',
y: 89
}, {
x: '09:14',
y: 86
}, {
x: '09:20',
y: 86
}, {
x: '09:24',
y: 85
}, {
x: '09:29',
y: 83
}]
}]
}
sData.datasets[0].data = formatData(sData.datasets[0].data)
sData.datasets[1].data = formatData(sData.datasets[1].data)
function formatData(oldData) {
var newData = []
for (var i = 0; i < oldData.length; i++) {
var currentData = {}
currentData.x = moment(oldData[i].x, "hh:mm").format('HH:mm')
currentData.y = oldData[i].y
newData.push(currentData)
}
return newData
}
var data = sData
var options = {
responsive: true,
scales: {
xAxes: [{
type: 'time',
}]
},
tooltips: {
callbacks: {
title: function(tooltipItem, data){
return moment(tooltipItem[0].label).format('hh:mm')
}
}
}
}
var ctx = document.getElementById('bateria_graf');
let chart = new Chart(ctx, {
type: 'line',
data: data,
options: options
});
Can somebody help me? Thank you very much.
You can provide data as a list of x,y:
datasets: [{
data: [{y:88,x:`${timestamp of 9:00}` },...],
...
}, {
data: [{y:87, x:`${timestamp of 9:03}`},...],
...
}]
like here https://www.chartjs.org/docs/latest/charts/line.html#point
And then in options define how to display the x value (timestamp) as hour:minutes
{
scales: {
xAxes: [{
ticks: {
callback = (label, index, labels) => {
return moment.unix(label).format('HH:mm');
}
}
}]
}
}
There are multiple charts on one page.
Each chart line is common.
I want to display a legend that is common to multiple charts like the figure.It shows and hides all chart lines with OnClick like the default legend.
THIS PICT IS FAKE
Is that possible? how?
I had tried Chart.js sync legend toggle on multiple charts, One legend, multiple charts Chart JS and etc.
But, those solutions have one chart with legend, and that legend affects other charts.
Should I hide the chart and show only the legend?
Should I draw a chart with no data?
I would be grad if you could tell me
HTML
<script src="https://rawgit.com/nnnick/Chart.js/v1.0.2/Chart.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.0.0-beta/Chart.js"></script>
<div>
<canvas id="myChartA"></canvas>
</div>
<div>
<canvas id="myChartB"></canvas>
</div>
JS
var ctxA = document.getElementById("myChartA").getContext("2d");
var ctxB = document.getElementById("myChartB").getContext("2d");
let data_A1 = [{
x: "2019-01-01 00:01:38",
y: "13.0"
},
{
x: "2019-01-01 01:01:39",
y: "11.0"
},
{
x: "2019-01-01 02:01:40",
y: "16.0"
},
{
x: "2019-01-01 03:01:41",
y: "15.0"
},
{
x: "2019-01-01 04:01:42",
y: "14.0"
}
];
var data_A2 = [{
x: "2019-01-01 00:01:42",
y: 14.671
}, {
x: "2019-01-01 01:01:42",
y: 13.691
}, {
x: "2019-01-01 02:01:42",
y: 16.691
}, {
x: "2019-01-01 03:01:42",
y: 17.691
}, {
x: "2019-01-01 04:01:42",
y: 18.691
}];
let data_B1 = [{
x: "2019-01-02 00:01:38",
y: "12.0"
},
{
x: "2019-01-02 01:01:39",
y: "11.0"
},
{
x: "2019-01-02 02:01:40",
y: "13.0"
},
{
x: "2019-01-02 03:01:41",
y: "14.0"
},
{
x: "2019-01-02 04:01:42",
y: "16.0"
}
];
var data_B2 = [{
x: "2019-01-02 00:00:00",
y: 14.671
}, {
x: "2019-01-02 01:01:42",
y: 13.691
}, {
x: "2019-01-02 02:01:42",
y: 16.691
}, {
x: "2019-01-02 03:01:42",
y: 15.691
}, {
x: "2019-01-02 04:01:42",
y: 14.691
}];
var myChartA = new Chart(ctxA, {
type: 'line',
data: {
datasets: [{
label: '1st Data',
data: data_A1,
borderColor: '#0f0',
showLine: true
}, {
label: '2nd Data',
data: data_A2,
borderColor: '#f00',
showLine: true
}]
},
options: {
scales: {
xAxes: [{
type: 'time',
time: {
displayFormat: 'h:mm',
}
}]
}
}
});
var myChartB = new Chart(ctxB, {
type: 'line',
data: {
datasets: [{
label: '1st Data',
data: data_B1,
borderColor: '#0f0',
showLine: true
}, {
label: '2nd Data',
data: data_B2,
borderColor: '#f00',
showLine: true
}]
},
options: {
scales: {
xAxes: [{
type: 'time',
time: {
displayFormat: 'h:mm',
}
}]
}
}
});
You can create a common legend and through generateLegend api, if both the datasets are similar.
First disable the default legends though the options
legend: {
display: false
}
Then use generateLegend() api to get the data labels and set it to a common element.
<ul class="legend">
</ul>
Then add event listeners to the generated elements and target all the charts
document.querySelector('.legend').innerHTML = myChartA.generateLegend();
var legendItems = document.querySelector('.legend').getElementsByTagName('li');
for (var i = 0; i < legendItems.length; i++) {
legendItems[i].addEventListener("click", legendClickCallback.bind(this,i), false);
}
function legendClickCallback(legendItemIndex){
document.querySelectorAll('.myChart').forEach((chartItem,index)=>{
var chart = Chart.instances[index];
var dataItem = chart.data.datasets[legendItemIndex]
if(dataItem.hidden == true || dataItem.hidden == null){
dataItem.hidden = false;
} else {
dataItem.hidden = true;
}
chart.update();
})
}
A sample pen is present here
https://codepen.io/srajagop/pen/yLBJOOo
Note I am using chartjs 2.8
If anyone using React version of Chartjs particularly react-chartjs-2, I have done it using React Hooks with react-chartjs-2, see the sandbox demo
I'm trying since to days and really new to Chart.js. Everything seems to be clear but now i would like to put a label on top of every single bar.
Trying this i get an error: this.scale is undefined. I got the animation.onComplete Snippet out of the net but it seems i make a mistake. The Chart works fine .. i just don't get the labels on top of the bars. Maybe someone can please help me with this ?!
I also have a line chart with the same problem.
var ctx = document.getElementById("chartA").getContext("2d");
Chart.defaults.global.animation.duration = 2400;
Chart.defaults.global.animation.easing = "easeInOutQuad";
Chart.defaults.global.elements.point.radius = 4;
Chart.defaults.global.elements.point.hoverRadius = 5;
Chart.defaults.global.elements.point.hitRadius = 1;
var chart = new Chart(ctx, {
type: "bar",
data: {
labels: ["A","B","C"],
datasets: [{
label: "Test",
backgroundColor: "rgba(255, 99, 132, 0.2)",
borderColor: "#CF2748",
borderWidth: 1,
data: [10,20,30]
}]
},
options: {
tooltips: { mode: 'nearest', intersect: false },
layout: { padding: { left: 20, right: 0, top: 0, bottom: 0 } },
legend: { display: true, position: 'top' },
scales: {
yAxes: [{
ticks: { maxTicksLimit: 9, stepSize: 300, callback: function(value, index, values) { return value+" €"; } }
}]
},
animation: {
onComplete: function () {
var ctx = this.chart.ctx; // this part doesn't work
ctx.font = this.scale.font;
ctx.fillStyle = this.scale.textColor;
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
this.datasets.forEach(function (dataset) {
dataset.bars.forEach(function (bar) {
ctx.fillText(bar.value, bar.x, bar.y - 5);
});
});
}
}
}
});
Thank you so much
Oliver
Thanks #Jeff I was testing around and get closer.
chart.data.datasets.forEach(function (dataset) {
dataset.data.forEach(function (value) {
ctx.fillText(value, x, y);
});
});
Now i have in "value" the right value. But i need to refer X and Y. Where do i get them? If i change X and Y with static value it works but all values were logically printed on the same space.
There are several issues with your code.
You could rather use the following chart plugin to accomplish the same :
Chart.plugins.register({
afterDatasetsDraw: function(chart) {
var ctx = chart.ctx;
chart.data.datasets.forEach(function(dataset, datasetIndex) {
var datasetMeta = chart.getDatasetMeta(datasetIndex);
datasetMeta.data.forEach(function(meta) {
var posX = meta._model.x;
var posY = meta._model.y;
var value = chart.data.datasets[meta._datasetIndex].data[meta._index];
// draw values
ctx.save();
ctx.textBaseline = 'bottom';
ctx.textAlign = 'center';
ctx.font = '16px Arial';
ctx.fillStyle = 'black';
ctx.fillText(value, posX, posY);
ctx.restore();
});
});
}
});
add this plugin at the beginning of your script.
ᴡᴏʀᴋɪɴɢ ᴇxᴀᴍᴘʟᴇ ⧩
Chart.plugins.register({
afterDatasetsDraw: function(chart) {
var ctx = chart.ctx;
chart.data.datasets.forEach(function(dataset, datasetIndex) {
var datasetMeta = chart.getDatasetMeta(datasetIndex);
datasetMeta.data.forEach(function(meta) {
var posX = meta._model.x;
var posY = meta._model.y;
var value = chart.data.datasets[meta._datasetIndex].data[meta._index];
// draw values
ctx.save();
ctx.textBaseline = 'bottom';
ctx.textAlign = 'center';
ctx.font = '16px Arial';
ctx.fillStyle = 'black';
ctx.fillText(value, posX, posY);
ctx.restore();
});
});
}
});
var ctx = document.getElementById("chartA").getContext("2d");
Chart.defaults.global.animation.duration = 2400;
Chart.defaults.global.animation.easing = "easeInOutQuad";
Chart.defaults.global.elements.point.radius = 4;
Chart.defaults.global.elements.point.hoverRadius = 5;
Chart.defaults.global.elements.point.hitRadius = 1;
var chart = new Chart(ctx, {
type: "bar",
data: {
labels: ["A", "B", "C"],
datasets: [{
label: "Test",
backgroundColor: "rgba(255, 99, 132, 0.2)",
borderColor: "#CF2748",
borderWidth: 1,
data: [10, 20, 30]
}]
},
options: {
tooltips: {
mode: 'nearest',
intersect: false
},
layout: {
padding: {
left: 20,
right: 0,
top: 0,
bottom: 0
}
},
legend: {
display: true,
position: 'top'
},
scales: {
yAxes: [{
ticks: {
maxTicksLimit: 9,
stepSize: 300,
callback: function(value, index, values) {
return value + " €";
}
}
}]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.min.js"></script>
<canvas id="chartA"></canvas>