44

I've just started working with Chart.js, and I am getting very frustrated very quickly. I have my stacked bar chart working, but I can't get the click "events" to work.

I have found a comment on GitHub by nnnick from Chart.js stating to use the function getBarsAtEvent, even though this function cannot be found in the Chart.js documentation at all (go ahead, do a search for it). The documentation does mention the getElementsAtEvent function of the chart reference, but that is for Line Charts only.

I set an event listener (the right way) on my canvas element:

canv.addEventListener('click', handleClick, false);

...yet in my handleClick function, chart.getBarsAtEvent is undefined!

Now, in the Chart.js document, there is a statement about a different way to register the click event for the bar chart. It is much different than nnnick's comment on GitHub from 2 years ago.

In the Global Chart Defaults you can set an onClick function for your chart. I added an onClick function to my chart configuration, and it did nothing...

So, how the heck do I get the on-click-callback to work for my bar chart?!

Any help would be greatly appreciated. Thanks!

P.S.: I am not using the master build from GitHub. I tried, but it kept screaming that require is undefined and I was not ready to include CommonJS just so that I could use this chart library. I would rather write my own dang charts. Instead, I downloaded and am using the Standard Build version that I downloaded straight from the link at the top of the documentation page.

EXAMPLE: Here is an example of the configuration I am using:

var chart_config = {
    type: 'bar',
    data: {
        labels: ['One', 'Two', 'Three'],
        datasets: [
            {
                label: 'Dataset 1',
                backgroundColor: '#848484',
                data: [4, 2, 6]
            },
            {
                label: 'Dataset 2',
                backgroundColor: '#848484',
                data: [1, 6, 3]
            },
            {
                label: 'Dataset 3',
                backgroundColor: '#848484',
                data: [7, 5, 2]
            }
        ]
    },
    options: {
        title: {
            display: false,
            text: 'Stacked Bars'
        },
        tooltips: {
            mode: 'label'
        },
        responsive: true,
        maintainAspectRatio: false,
        scales: {
            xAxes: [
                {
                    stacked: true
                }
            ],
            yAxes: [
                {
                    stacked: true
                }
            ]
        },
        onClick: handleClick
    }
};
WebWanderer
  • 10,380
  • 3
  • 32
  • 51
  • 2
    I found that I was able to have my `onClick` function called by setting `chart.config.options.onClick = handleClick` yet this does not help me find what part of my chart was clicked on. This basically is the same as setting my event listener on the `canvas` node myself. – WebWanderer May 09 '16 at 18:15
  • 1
    It mentions `activeElements` being passed as the second argument to `onClick` in the `options` parameter to `new Chart()`. – tscizzle May 16 '18 at 18:28
  • 1
    @tscizzle Your link is broken – Alex Apr 09 '21 at 05:34
  • The link is actually this one: https://www.chartjs.org/docs/2.8.0/general/interactions/events.html – stoneshishang Jul 14 '21 at 19:53

13 Answers13

74

I managed to find the answer to my question by looking through the Chart.js source code.

Provided at line 3727 of Chart.js, Standard Build, is the method .getElementAtEvent. This method returns me the "chart element" that was clicked on. There is sufficent data here to determine what data to show in a drill-down view of the dataset clicked on.

On the first index of the array returned by chart.getElementAtEvent is a value _datasetIndex. This value shows the index of the dataset that was clicked on.

The specific bar that was clicked on, I believe, is noted by the value _index. In my example in my question, _index would point to One in chart_config.data.labels.

My handleClick function now looks like this:

function handleClick(evt)
{
    var activeElement = chart.getElementAtEvent(evt);

..where chart is the reference of the chart created by chart.js when doing:

chart = new Chart(canv, chart_config);

The specific set of data that was selected by the click can therefore be found as:

chart_config.data.datasets[activeElement[0]._datasetIndex].data[activeElement[0]._index];

And there you have it. I now have a datapoint that I can build a query from to display the data of the bar that was clicked on.

AUGUST 7TH, 2021. UPDATE

There is now a method for what we are looking for. Take a look at here

idunno
  • 105
  • 5
WebWanderer
  • 10,380
  • 3
  • 32
  • 51
  • 5
    This is also what I've found after hours of (hopeless) searching in documentation. But my problem is, that it returns me all the datasets, so `activeElement[0]._datasetIndex` is still the same - zero, no matter on which bar I click! `_index` is working. Haven't you had the same problem? – Ján Janočko Aug 12 '16 at 09:57
  • I have not encountered this problem @JánJanočko , but I haven't worked with Chart.js since this post. I need to take a look at it again, as I can see that there are some issues with the chart widget I created. I will keep you posted on my findings. – WebWanderer Sep 20 '16 at 17:03
  • 1
    Thanks @WebWanderer. Getting that activeElement was the key. In my case I could just use the chart's `label` and then did a `switch()` on it. Then each `case "":` would do a certain thing, for me just go to a different web page: `window.location.href = "/myLabelPage";`. – Phil Ryan Oct 18 '17 at 03:42
  • 1
    Hi @JánJanočko , I'm also getting zero for activeElement[0]._datasetIndex , no matter on which bar I click. Did you find a way to get the exact clicked bar value? – zlas Mar 14 '18 at 12:10
  • Sorry for late response, @zlas, I had to look into my old scripts. I'm not sure how I did it, but this is my code: `var points = myChart.getElementsAtEvent(event); var dataset = myChart.getDatasetAtEvent(event); if (points[0]) { pt = points[0]; dt = dataset[0]; showDetailsFor(dt._datasetIndex, pt._index); }` I think I somehow combined data from `getElementsAtEvent()` and `getDatasetAtEvent()` to get unique pair that clearly determines clicked bar and pass it to my own function `showDetailsFor()`. Sorry, I am totally out of context now. – Ján Janočko Mar 19 '18 at 08:26
  • Thanks @JánJanočko. I was able to find the issue with my code. Thanks again. – zlas Mar 20 '18 at 11:01
  • 2
    This is now documented in the API page! http://www.chartjs.org/docs/latest/developers/api.html#getelementatevente – Sebj Aug 02 '18 at 12:36
  • @Sebj Awesome! I'm glad to see that Chart.js has active support again. It has been a while since I checked it out. I will need to take a look again. – WebWanderer Aug 02 '18 at 14:20
  • Setting the `onClick()` event handler blocks the ability to hide datasets by clicking their legends. Is there a way to call the default handler inside the custom `onClick()` handler? – karliwson Aug 15 '19 at 13:00
  • Could you post the entire chart.js code with the event handler? Trying to figure out where to place all this. – mikelowry Jan 28 '21 at 21:02
  • @mikelowry This is really old, and likely very outdated for the most recent versions of Chart.js. I haven't looked at any of this in years, and I'm not even sure where this code is anymore. – WebWanderer Feb 22 '21 at 01:18
  • If user clicks on the x axis label, then how to get the label value – Sathish Kumar k k Apr 12 '21 at 16:20
  • 1
    With v3.9.1 there is a new function `getElementsAtEventForMode(...)` see [Dev API Docu v3.9.1](https://www.chartjs.org/docs/3.9.1/developers/api.html#getelementsateventformode-e-mode-options-usefinalposition) or use the [latest](https://www.chartjs.org/docs/3.9.1/developers/api.html) – KargWare Oct 07 '22 at 06:12
  • Could any of you tell me where exactly I would attach the handleClick function? What line of code attaches the function to the chart? – Hugobop Jan 24 '23 at 21:39
  • 1
    @Hugobop in this example, the `handleClick` method is attached to the Chart.js canvas element, such as: canv.addEventListener('click', handleClick, false); This is mentioned in my original post. This question is very outdated. Chart.js has since been re-adopted and developed on. At the time of writing, Chart.js was managed by a single developer that eventually abandoned the projects. I highly suggest checking the source of the latest Chart.js for your answers. – WebWanderer Feb 07 '23 at 15:39
56

Hi this is the click event under options which is getting values from x and y-axis

onClick: function(c,i) {
    e = i[0];
    console.log(e._index)
    var x_value = this.data.labels[e._index];
    var y_value = this.data.datasets[0].data[e._index];
    console.log(x_value);
    console.log(y_value);
}
Max
  • 915
  • 10
  • 28
Vaimeo
  • 1,078
  • 9
  • 14
  • 1
    I am getting an error as Cannot read property '0' of undefined for i – Arti Berde Dec 02 '17 at 22:09
  • 1
    please post plunker – Vaimeo Dec 03 '17 at 09:19
  • Simple and concise. Worked for me! – Brad May 07 '18 at 20:25
  • 3
    This should be the accepted answer! So you should just add this to `options`: `events: ['click'], onClick: function(c, i) {...`, and that function will be called *automagically*. Just add a validation for `e` not being undefined, otherwise if the user clicks too far from the bars it will throw an exception. – Andrew Mar 23 '19 at 00:28
  • Maybe also `var y_value = this.data.datasets[e._datasetIndex].data[e._index];` just to not hardcode that dataset index – 40pro Apr 06 '20 at 11:47
  • What is c in this function – mikelowry Jan 29 '21 at 23:44
  • I'm using React, this answer works for me. I guess I just need to log the e for the onClick property. e has the information of which bar that I'm clicking. – stoneshishang Jul 14 '21 at 20:42
  • 1
    @mikelowry, I have tried many times. I have no idea what c is, and c isn't used in that function. but that function won't work without the c. So I just left it there. – stoneshishang Jul 15 '21 at 18:52
  • c is the click event, i is an array of active elements. – richplane Jul 29 '21 at 15:28
13

I found this solution at https://github.com/valor-software/ng2-charts/issues/489

public chartClicked(e: any): void {
  if (e.active.length > 0) {
  const chart = e.active[0]._chart;
  const activePoints = chart.getElementAtEvent(e.event);
  if ( activePoints.length > 0) {
   // get the internal index of slice in pie chart
   const clickedElementIndex = activePoints[0]._index;
   const label = chart.data.labels[clickedElementIndex];
   // get value by index
   const value = chart.data.datasets[0].data[clickedElementIndex];
   console.log(clickedElementIndex, label, value)
  }
 }
}
Asif Karim Bherani
  • 1,023
  • 1
  • 13
  • 25
7

You can use onClick like this.

var worstCells3GBoxChart = new Chart(ctx, {
                type: 'bar',
                data: {
                    labels: lbls,
                    datasets: [{
                        label: 'Worst Cells by 3G',
                        data: datas,
                        backgroundColor: getColorsUptoArray('bg', datas.length),
                        borderColor: getColorsUptoArray('br', datas.length),
                        borderWidth: 1
                    }]
                },
                options: {
                    legend: {
                        display: false
                    },
                    scales: {
                        yAxes: [{
                            ticks: {
                                beginAtZero: true
                            }
                        }]
                    },
                    onClick: function (e) {
                        debugger;
                        var activePointLabel = this.getElementsAtEvent(e)[0]._model.label;
                        alert(activePointLabel);
                    }
                }
            });
  • 1
    Please explain the code inside the onClick function, as its not clear what you suggest to be the solution to the question. – henrik Feb 12 '20 at 19:55
5

Chartjs V3.4.1

This is what worked for me in v3, after looking at solutions for older versions:

const onClick = (event, clickedElements) => {
  if (clickedElements.length === 0) return

  const { dataIndex, raw } = clickedElements[0].element.$context
  const barLabel = event.chart.data.labels[dataIndex]
  ...
}
  • raw is the value of the clicked bar.
  • barLabel is the label of the clicked bar.

You need to pass the onClick to the bar chart config:

const barConfig = {
  ...
  options: {
    responsive: true,
    onClick,
    ...
  }
}
Mahsa2
  • 412
  • 6
  • 15
3

Well done! This seems to return the data value being charted though, which in many cases might be possible to appear more than once, thus making it unclear what was clicked on.

This will return the actual data label of the bar being clicked on. I found this more useful when drilling down into a category.

chart_config.data.labels[activeElement[0]._index]
AiDev
  • 1,214
  • 8
  • 11
  • This will not work if you do not have labels. You can have data specified in format `data: [{t:"2020-01-04 08:06Z", y:0},{t:"2020-01-04 08:07Z", y:45},{t:"2020-01-04 08:08Z", y:47}]`, and in this case, labels will not exists. – Wojciech Jakubas Jan 04 '20 at 17:23
3

This is clearly an old question, but it rated highly in my 2023 search for "chart.js click events". So a post seemed worthwhile.

Hardly any of the posted answers work in version 4 of chart.js. For version 4 use getElementsAtEventForMode

var chartStr = '{ "type": "bar","title": "hello title", "data": {"labels": ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],"datasets": [{ "label": "# of Votes", "data": [12, 19, 3, 5, 2, 3] , "backgroundColor":["Orange","255","#333333","#666666","#999999"] }] }, "options": {"indexAxis": "y","scales": {"y": {"beginAtZero": true, "ticks":{} } } } }'

const ctx1 = document.getElementById('myChart');
var chartObject = JSON.parse(chartStr);
var myChart = new Chart(ctx1, chartObject);
ctx1.onclick = function(evt) {
  var points = myChart.getElementsAtEventForMode(evt, 'nearest', {
    intersect: true
  }, true);
  if (points.length) {
    const firstPoint = points[0];
    const label = myChart.data.labels[firstPoint.index];
    const value = myChart.data.datasets[firstPoint.datasetIndex].data[firstPoint.index];
    alert(label);
  }
};
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<canvas id="myChart"></canvas>

The way the chart.js team change/break the API between major versions is certainly bold, but it can unintentionally can make historical stackoverflow posts (that don't specify a chart.js version) misleading and of negative help value. This seems to be the case for virtually all chart.js community help I come across.

mel
  • 88
  • 6
  • Thank you for providing an updated answer here. I am surprised by how often I still get questions on this very outdated post. – WebWanderer Apr 19 '23 at 20:29
  • It's more flexible to use `evt.chart` instead `myChart` inside function. – dlnsk May 04 '23 at 10:08
  • I agree and I will try and never use ChartJS again after this project. It's highly likely some of the newer libraries that support composition are way better and not such an utter pain to work with. – ThaJay Jun 06 '23 at 09:51
2

I was able to make this work in another way. Might not be supported, but sometimes, I find that neither the label nor the value is adequate to get me the necessary information to populate a drill-through.

So what I did was add a custom set of attributes to the data:

var ctx = document.getElementById("cnvMyChart").getContext("2d");
if(theChart != null) {theChart.destroy();}
theChart = new Chart(ctx, {
type: typ,
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datakeys: ["thefirstone","thesecondone","thethirdone","thefourthone","thefifthone","thesixthone"],
datasets: [{
    label: '# of Votes',
    data: [12, 19, 3, 5, 2, 3],

...etc

Then when I need to push the drillthrough key into another ajax call, I was able to get it with this:

var theDrillThroughKey = theChart.config.data.datakeys[activePoints[0]._index];

So I'm really not sure that it's appropriate to be adding custom elements into the data for the Chart, but it's working so far in Chrome, IE and Firefox. I needed to be able to put more information into the drillthrough than I really wanted displayed.

Example of the full thing: https://wa.rrdsb.com/chartExamples

Thoughts?

  • remove the getContext. `var ctx = document.getElementById("cnvMyChart").getContext("2d");` try `var ctx = document.getElementById("cnvMyChart");` – ferreirabraga May 16 '18 at 23:54
2

I had the same problem with multiple datasets, and used this workaround:

var clickOnChart = function(dataIndex){
    ...
}
var lastHoveredIndex = null;
var chart_options = {
    ...
    tooltips: {
        ...
        callbacks: {
            label: function(tooltipItem, chart) {
                var index = tooltipItem.datasetIndex;
                var value  = chart.datasets[index].data[0];
                var label  = chart.datasets[index].label;

                lastHoveredIndex = index;

                return value + "€";
            }
        }
    },
    onClick:function(e, items){
        if ( items.length == 0 ) return; //Clicked outside any bar.

        clickOnChart(lastHoveredIndex);
    }
}
sergio0983
  • 1,232
  • 8
  • 15
0

Let's say that you declared a chart using a method like so:

  window.myBar = new Chart({chart_name}, {
       type: xxx,
       data: xxx,
       events: ["click"],
       options: {
           ...
       }
  });

A good way of declaring onclick events would involve listening for the canvas click, like so:

({chart_name}.canvas).onclick = function(evt) {
     var activePoints = myBar.getElementsAtEvent(evt);
     // let's say you wanted to perform different actions based on label selected
     if (activePoints[0]._model.label == "label you are looking for") { ... }
}
Marquistador
  • 1,841
  • 19
  • 26
0

In the chart options for Chart.js v3.5.1 which is latest

Check below sample code

let enterpriseChartOptions = {
    responsive:true,
    maintainAspectRatio: false,
    onClick: (c,i) => {
      console.log('Get the underlying label for click,', c.chart.config._config.data.labels[i[0].index]);
    },
    plugins: { 
      title:{
        text:'Enterprise Dashboard (Health Status of 10 stores) updated every 30 minutes',
        fontSize:20
      },
    },
    scales: {
      x: {
        display: true,
        type: 'category',
        position: 'right',
        ticks: {
          padding: 8,
        },
      },
      y: {
        display: true,
        ticks: {
          callback: function(val, index) {
            // Show the label
            return val < 1 ? "All good" : (val < 2 && val >=1) ? "Warning": val === 2 ? "Critical" : ""; 
          },
          //color: 'red',
          stepSize: 1,
          padding: 8
        }
      }
    },
    layout: {
      padding: {
          left: 20,
          right: 20,
          top: 25,
          bottom: 0
      }
    },

  };
Jayanth Kumar T
  • 308
  • 4
  • 9
0
var employeeDetailsCtx = document.getElementById("employee-details").getContext("2d");

var employee_details_data = {
    labels: ["Late Present", "On Leave", "Training", "Tour"],
    datasets: [{
        label: "Officer",
        backgroundColor: "#5A8DEE",
        data: [
            ...
        ]
    }, {
        label: "Staff",
        backgroundColor: "#4BC0C0",
        data: [
            ...
        ]
    }]
};

var myoption = {
    tooltips: {
        enabled: true
    },
    hover: {
        animationDuration: 1
    },
    onClick: function (evt, i) {
        var activePoint = employeeDetailsBarChart.getElementAtEvent(evt)[0];
        var data = activePoint._chart.data;
        var datasetIndex = activePoint._datasetIndex;
        var label = data.datasets[datasetIndex].label;
        var value = data.datasets[datasetIndex].data[activePoint._index];
        e = i[0];
        var x_value = this.data.labels[e._index];

        console.log(x_value)
        console.log(label)
        console.log(value)
    },
    animation: {
        duration: 1,
        onComplete: function () {
            var chartInstance = this.chart,
                ctx = chartInstance.ctx;
            ctx.textAlign = 'center';
            ctx.fillStyle = "rgba(0, 0, 0, 1)";
            ctx.textBaseline = 'bottom';
            this.data.datasets.forEach(function (dataset, i) {
                var meta = chartInstance.controller.getDatasetMeta(i);
                meta.data.forEach(function (bar, index) {
                    var data = dataset.data[index];
                    ctx.fillText(data, bar._model.x, bar._model.y - 5);
                });
            });
        }
    }
};

var employeeDetailsBarChart = new Chart(employeeDetailsCtx, {
    type: 'bar',
    data: employee_details_data,
    options: myoption
});
TOPKAT
  • 6,667
  • 2
  • 44
  • 72
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jan 04 '22 at 10:56
  • While this code snippet may solve the problem, it doesn't explain why or how it answers the question. Please [include an explanation for your code](//meta.stackexchange.com/q/114762/269535), as that really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion. – Luca Kiebel Jan 07 '22 at 12:34
0

check below working code

<div class="nss_main_chart_box">
    <canvas id="myChartSite"></canvas>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-datalabels/2.2.0/chartjs-plugin-datalabels.min.js" integrity="sha512-JPcRR8yFa8mmCsfrw4TNte1ZvF1e3+1SdGMslZvmrzDYxS69J7J49vkFL8u6u8PlPJK+H3voElBtUCzaXj+6ig==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
$(document).ready(function() {
    window.chartColors = {
        lightpink: 'rgb(255, 136, 136)',
        yellow: 'rgb(255, 222, 121)',
        green: 'rgb(157, 223, 118)',
        orange: 'rgb(255, 159, 64)',
        blue: 'rgb(54, 162, 235)',
        purple: 'rgb(153, 102, 255)',
        grey: 'rgb(231,233,237)'
    };
    var myChartSite = document.getElementById("myChartSite").getContext('2d');
    Chart.register(ChartDataLabels); 
    var myChartOnSi = new Chart(myChartSite, {
        type: "bar",
        data: {
            labels: ["Last month", "This month", "Next month"],  //here to set x-axis option
            datasets: [
                {
                    label: 'January',  //here to set top heading label name
                    backgroundColor: window.chartColors.lightpink, //here to set top heading label Color
                    data: ['150', '500', '300'], //here to set Y-axis dynamic data for chart

                },
                {
                    label: 'February',
                    backgroundColor: window.chartColors.yellow,
                    data: ["70", "20", "130"],
                },
                {
                    label: 'March',
                    backgroundColor: window.chartColors.green,
                    data: [200, 90, 70],
                },
            ]
        },
        options: {
            responsive: true,
            plugins: {
                datalabels: {
                    labels: {
                        value: {
                            color: 'blue'
                        }
                    },
                    font: {
                        weight: 'bold',
                        size: 16
                    }
                },
                legend: {
                    display: true,
                },
                title: {
                    display: true,
                    text:"Month Data",
                    padding:{
                        left:0,
                        right:0,
                        bottom:0,
                        top:20
                    },
                },
            },
            onHover: (event, chartElement) => {
                event.native.target.style.cursor = chartElement[0] ? 'pointer' : 'default';
            },
            onClick: (e, activeEls) =>{
                let datasetIndex = activeEls[0].datasetIndex;
                let dataIndex = activeEls[0].index;
                let datasetLabel = e.chart.data.datasets[datasetIndex].label;
                let value = e.chart.data.datasets[datasetIndex].data[dataIndex];
                let label = e.chart.data.labels[dataIndex];
                var chartTitleText = e.chart.titleBlock.options.text;
                console.log('chartTitleText is ',chartTitleText,'Clicked:', datasetLabel, 'Value:', value,'label',label);

                },
            scales: {
                x: {
                    display: true,
                    ticks: {
                        font: {
                            size: 16
                        }
                    },
                },
                y: {
                    display: true,
                    ticks: {
                        beginAtZero: true,
                        stepSize : 5,
                        font: {
                            size: 16
                        }
                    },
                }
            }

        }
    }); 
});



</script>
  • 2
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jun 02 '23 at 11:21