Chart.js OnClick event with a mixed chart, which chart did I click?
11,333
HTML
<div id="test" style="height:600px; width:600px;">
<canvas id="myCanvas" style="border: 1px solid black; margin: 25px 25px, display: none;" height="300" >Canvas</canvas>
</div>
JS
var ctx = document.getElementById("myCanvas");
var newArr;
var config = new Chart(ctx,{
type: 'bar',
data: {
labels: ["Test","Test","Test"],
datasets: [{
label: 'Dataset1',
yAxisID: 'Dataset1',
type: "line",
borderColor: "red",
backgroundColor: "red",
data: [70,60,50],
fill: false
},
{
label: 'Dataset0',
type: "bar",
backgroundColor: "blue",
data: [100,90,80]
}]
},
options: {
scales: {
xAxes: [{ barPercentage: 1.0 }],
yAxes: [{ id: 'Dataset1', position: 'left', type: 'linear',
ticks: { display: false, min: 0, beginAtZero: true, max: 120 },
scaleLabel: { display: true, labelString: "TestScale" } }]
},
responsive: true,
maintainAspectRatio: false,
legend : { display: true, position: 'bottom' },
onClick: chartClickEvent
}
}); // end of var config
function chartClickEvent(event, array){
if(typeof newArr === 'undefined'){
newArr = array;
}
if (window.config === 'undefined' || window.config == null)
{
return;
}
if (event === 'undefined' || event == null)
{
return;
}
if (newArr === 'undefined' || newArr == null)
{
return;
}
if (newArr.length <= 0)
{
return;
}
var active = window.config.getElementAtEvent(event);
if (active === 'undefined' || active == null || active.length === 0)
{
return;
}
var elementIndex = active[0]._datasetIndex;
console.log("elementIndex: " + elementIndex + "; array length: " + newArr.length);
if (newArr[elementIndex] === 'undefined' || newArr[elementIndex] == null){
return;
}
var chartData = newArr[elementIndex]['_chart'].config.data;
var idx = newArr[elementIndex]['_index'];
var label = chartData.labels[idx];
var value = chartData.datasets[elementIndex].data[idx];
var series = chartData.datasets[elementIndex].label;
alert(series + ':' + label + ':' + value);
}
Author by
JustLooking
Updated on June 19, 2022Comments
-
JustLooking almost 2 years
EDIT: Modified to add options, and a suggested (from the answer) chartClickEvent, here is a jsfiddle: http://jsfiddle.net/jmpxgufu/174/
Imagine if you will a Chart.js mixed chart with the following config:
var config = { type: 'bar', data: { labels: ["Test","Test","Test"], datasets: [{ label: 'Dataset1', yAxisID: 'Dataset1', type: "line", borderColor: "red", backgroundColor: "red", data: [70,60,50], fill: false }, { label: 'Dataset0', type: "bar", backgroundColor: "blue", data: [100,90,80] }] }, options: { scales: { xAxes: [{ barPercentage: 1.0 }], yAxes: [{ id: 'Dataset1', position: 'left', type: 'linear', ticks: { display: false, min: 0, beginAtZero: true, max: 120 }, scaleLabel: { display: true, labelString: "TestScale" } }] }, responsive: true, maintainAspectRatio: false, legend : { display: true, position: 'bottom' }, onClick: chartClickEvent } }; // end of var config function chartClickEvent(event, array) { if (window.myChart === undefined || window.myChart == null) { return; } if (event === undefined || event == null) { return; } if (array === undefined || array == null) { return; } if (array.length <= 0) { return; } var active = window.myChart.getElementAtEvent(event); if (active === undefined || active == null) { return; } var elementIndex = active[0]._datasetIndex; console.log("elementIndex: " + elementIndex + "; array length: " + array.length); if (array[elementIndex] === undefined || array[elementIndex] == null) { return; } var chartData = array[elementIndex]['_chart'].config.data; var idx = array[elementIndex]['_index']; var label = chartData.labels[idx]; var value = chartData.datasets[elementIndex].data[idx]; var series = chartData.datasets[elementIndex].label; alert(series + ':' + label + ':' + value); }
As my chartClickEvent says, my array is length 2, because I have two charts. That's great and all, but I have no idea how to figure out whether to use array[0] or array[1]. If they click specifically the line data point, I want to do something with that data (array[0]), if they click the big blue bar, I want to do something with that data (array[1]). How do I tell whether they clicked on the line or the bar?
Thank you.
-
JustLooking over 6 yearsBring it on in for a virtual hug! Thanks!
-
JustLooking over 6 yearsUh-oh. Discovered a bug when using datasetIndex. Let's say you have a line and a bar (like my example). The line will have a datasetIndex of 0, and the bar will have a datasetIndex of 1. The array will be of length 2. So far, so good. Here's the issue: if by using the chart.js legend, I hide the lines, this is what I get (since there's only a bar now): datasetIndex of 1, and array size of 1!! So, now I have exceeded the length of the array. How does one find the true "index", based on what is visible?
-
JustLooking over 6 yearsObviously, in this instance, I have just a bar and a line. So, of course, if the array length was 1, I could just grab the 0th index out of that array. But that's hard-coding this event to that data. I'm looking for a generic solution. I mean, for example, imagine if there were 6 bars and lines, and you hid three items from the legend, and the dataIndex you got was 5, and the array length was 3. Now what do you do? That's why I was looking for something generic.
-
Matt over 6 yearsWhat are you doing to get this error? I put another line or bar dataset and hide the middle one I will get a
Unable to get property '_datasetIndex' of undefined or null reference
error but I can still click on the other 2 and get the index of them. If you can let me know what you are trying to do with it now then I can recreate the issue and better assist -
JustLooking over 6 yearsI modified my original question to contain the options I am using, and the chartClickEvent I have put together (using the config.getElementAtEvent). I think what you are describing is the error. I shouldn't get undefined if something is hidden. For example, with just a simple bar and line (I didn't change that), on first load, if I click the big blue bar (any of them, I chose the first), I get an alert that says: Dataset0:Test:100 (all of the pieces of info I want from that data element). The console.log tells me that the bar was elementIndex 1, for an array length of 2.
-
JustLooking over 6 yearsNow, go and check Dataset1 (the red one) in the legend, so that it hides it. Now, when I click the big blue bar, I get no alert. It doesn't get that far because of an if guard I put in place. That's because console.log is reporting an elementIndex of 1, but this time with an array of 1. See? Before it was an array of 2, so I could do array[1] and retrieve the data. Now, if I do array[1] I exceed the bounds of the array, because the array changes size upon hiding things in the ledger. Yet, the datasetIndex always remains the same.
-
Matt over 6 yearsIf you go and replace your
myChart
entries withconfig
do you still get the error? -
JustLooking over 6 yearsHrmm, not sure what you mean. But I'm thinking it boils down to the fact that the array changes length, but the dataSetIndex doesn't adjust.
-
JustLooking over 6 yearsI mean, I can solve all of this by doing this (instead of using the array):
-
JustLooking over 6 yearsvar elementIndex = active[0]._datasetIndex; var idx = active[0]['_index']; var chartData = active[0]['_chart'].config.data;
-
JustLooking over 6 yearsSo then I'm like, what's the point of passing that array to the chartClickEvent???
-
JustLooking over 6 yearsWhen I override the tooltip callback for labels, it's defined as: function(tooltipItem, data) { ... }, so I can do something like: data.datasets[tooltipItem.datasetIndex].label; .... that's what I would expect from the chartClickEvent! That the event would get me event.dataSetIndex, and then I can peek into the array using that index.
-
Matt over 6 yearsI believe the error is just because of clicking on the label and nothing more. But what I did to help this is created a global
var newArr;
and right inside the function chartClickEvent didif(typeof newArr === 'undefined'){ newArr = array; }
Changed your checks to point to newArr instead of array and in the if statement after yourvar active = ..
did an or check to see ifactive.length === 0
. Always get alerts, no console errors. -
JustLooking over 6 yearsIt's definitely not because of clicking on the labels. That's what the array.length <= 0 guard finds and returns. And that behavior changes based on whether you define a scale (options) or not - which is odd. Have you seen the jsfiddle? You will see that clicking the labels/legends does not error.
-
JustLooking over 6 yearsYou can make those changes in the jsfiddle, and then save/update it. And post the link here. I'm really not seeing how that will help. The issue is that the array length changes and the dataIndex remains the same. Also, it seems odd that I would have to do all these things (global variables). What is the point in having an event and an array object passed to the click event, then? It seems to me I should be able to use both of those. Yet your solution uses a global variable (unconfirmed) and my solution doesn't use the passed in array variable.
-
Matt over 6 yearsI can't access it from where I currently am.
-
Matt over 6 yearsI updated my answer to show how I am doing everything. Maybe it's something that simple.
-
JustLooking over 6 yearsYeah, in my haste to get you something, I did have a check for active.length that got omitted. But that wasn't the issue. What you are effectively doing is getting a capture of the array, for your first click, that you never modify/replace. That's why it "works" for you. So you are saving the state of the array so that the length of that array never changes. That seems odd that one would have to jump through hoops and save the state. What happens if I turn off a legend item first, before clicking? Am I now saving a different state of that array? This is odd.
-
JustLooking over 6 yearsTake your same code, click the legend first (turn off dataset1, the red one), and then click the blue bar. Your solution would fail.
-
JustLooking over 6 yearsThat's because you are now saving the array in the state of length 1 instead of length 2.
-
JustLooking over 6 yearsYour getElementAtEvent function helped me greatly. But again, what is odd is that the array length changes (the array passed to the click event), but working backwards from the active element, the dataSetIndex does not. So, you can run into situations where the dataSetIndex is 5, and the length of the array is 1. So, array[5] on a length of 1 array would be undefined. I really think I'm running into a bug. Or something just isn't right. We should be able to extract a better index from the passed in event parameter, one that works with the passed in array parameter.
-
Matt over 6 yearsYeah. I'm sorry there isn't someone smarter to help out. I'm also new to the whole chart stuff too so I am going as is and testing. I apologize for it.
-
JustLooking over 6 yearsLOL. No, you were awesome! Like I said, you got me the getElementAtEvent function which saved me a ton of time. Very much appreciated. I'm just picking nits in regards to what I'm seeing from the chart.js people. Something just doesn't seem right. But dude, thanks again for your help. Just bouncing something off someone is great.
-
Wojciech Jakubas over 4 yearsI do not have config defined which means this code will not work for me. But you could use
this
instead. This is what I would suggest:var active = this.getElementAtEvent(event);