47
votes

I have a bar chart that's created using chart.js. Everything works fine on page load, but when I change the time frame using a daterangepicker, a glitch appears. The new data is brought in, but when I hover over it, the old data is shown. I'm new to javascript so I'm hoping to get some help. It looks like I need to incorporate .destroy(); somehow, but I don't know how. A snippet of my code is below:

function loadFTPRChart(startdate, enddate){
var BCData = {
labels: [],
datasets: [
 {
  label: "Pass %",
  backgroundColor: "#536A7F",
  data: [],
  stack: 1
},
{
  label: "Fail %",
  backgroundColor: "#e6e6e6",
  data: [],
  stack: 1
},
{
  label: "Auto %",
  backgroundColor: "#286090",
  data: [],
  stack: 2
},
{
  label: "Manual %",
  backgroundColor: "#f0f0f0",
  data: [],
  stack: 2
}
 ]
};
  $.getJSON( "content/FTPR_AM_Graph_ajax.php", {
    startdate: startdate,
    enddate: enddate,
    location: "M"
})
.done(function( data ) {
    console.log("data", data);
    $.each( data.aaData, function( key, val ) {
      if(val == ""){return true}
      BCData.labels.push("Coater " + val[0]);
      BCData.datasets[0].data.push(parseFloat(val[2]));
      BCData.datasets[1].data.push(parseFloat(100-val[2]));
      BCData.datasets[2].data.push(parseFloat(val[1]));
      BCData.datasets[3].data.push(parseFloat(100-val[1]));
    });

    var option = {   
     responsive:true,
};
console.log("BCData", BCData);


//console.log("PrevData", data);
var ctx = document.getElementById("mybarChart2").getContext("2d");
new Chart(ctx, {
  type: 'groupableBar',
  data: BCData,
  options: {
    scales: {
      yAxes: [{
        ticks: {
          max: 100,
        },
        stacked: true,
      }]
    }
  }
});
});

}


loadFTPRChart($('#reportrange').data().daterangepicker.startDate.format('MM/DD/YYYY'), $('#reportrange').data().daterangepicker.endDate.format('MM/DD/YYYY'));

What is the best way to destroy the original data so that when I change the date range and hover over the new chart, the old data no longer flickers?

Thanks

11
Had this problem. Solution to create/re-create stackoverflow.com/a/51882403/1181367Chris
Thank you so much for posting this question :) You saved my time /\Abhijeet Raj

11 Answers

67
votes

With the approach you are taking (e.g. creating a new Chart object each time the date range changes), then you must first destroy the previous chart and then create the new one.

You can use the .destroy() prototype method to do this. Here is exactly what the API states.

Use this to destroy any chart instances that are created. This will clean up any references stored to the chart object within Chart.js, along with any associated event listeners attached by Chart.js. This must be called before the canvas is reused for a new chart.

Therefore, your code would look something like this (notice that we destroy and re-create).

// define a variable to store the chart instance (this must be outside of your function)
var myChart;

function loadFTPRChart(startdate, enddate) {
  var BCData = {
    labels: [],
    datasets: [{
      label: "Pass %",
      backgroundColor: "#536A7F",
      data: [],
      stack: 1
    }, {
      label: "Fail %",
      backgroundColor: "#e6e6e6",
      data: [],
      stack: 1
    }, {
      label: "Auto %",
      backgroundColor: "#286090",
      data: [],
      stack: 2
    }, {
      label: "Manual %",
      backgroundColor: "#f0f0f0",
      data: [],
      stack: 2
    }]
  };

  $.getJSON("content/FTPR_AM_Graph_ajax.php", {
      startdate: startdate,
      enddate: enddate,
      location: "M"
    })
    .done(function(data) {
      console.log("data", data);
      $.each(data.aaData, function(key, val) {
        if (val == "") {
          return true
        }
        BCData.labels.push("Coater " + val[0]);
        BCData.datasets[0].data.push(parseFloat(val[2]));
        BCData.datasets[1].data.push(parseFloat(100 - val[2]));
        BCData.datasets[2].data.push(parseFloat(val[1]));
        BCData.datasets[3].data.push(parseFloat(100 - val[1]));
      });

      var option = {
        responsive: true,
      };
      console.log("BCData", BCData);

      // if the chart is not undefined (e.g. it has been created)
      // then destory the old one so we can create a new one later
      if (myChart) {
        myChart.destroy();
      }

      var ctx = document.getElementById("mybarChart2").getContext("2d");
      myChart = new Chart(ctx, {
        type: 'groupableBar',
        data: BCData,
        options: {
          scales: {
            yAxes: [{
              ticks: {
                max: 100,
              },
              stacked: true,
            }]
          }
        }
      });
    });
}

With that said, it's expensive the destroy/create over and over and actually it isn't even necessary. There is another prototype method called .update() that you can use to just re-render the chart if you have changed it's underlying data or label objects.

Here is a jsfiddle showing an example of changing the underlying data and labels and then re-rendering the chart. I would highly recommend you take this approach instead.

Here is how your code would look taking this better approach.

// define a variable to store the chart instance (this must be outside of your function)
var myChart;

function loadFTPRChart(startdate, enddate) {
  var BCData = {
    labels: [],
    datasets: [{
      label: "Pass %",
      backgroundColor: "#536A7F",
      data: [],
      stack: 1
    }, {
      label: "Fail %",
      backgroundColor: "#e6e6e6",
      data: [],
      stack: 1
    }, {
      label: "Auto %",
      backgroundColor: "#286090",
      data: [],
      stack: 2
    }, {
      label: "Manual %",
      backgroundColor: "#f0f0f0",
      data: [],
      stack: 2
    }]
  };

  $.getJSON("content/FTPR_AM_Graph_ajax.php", {
      startdate: startdate,
      enddate: enddate,
      location: "M"
    })
    .done(function(data) {
      console.log("data", data);
      $.each(data.aaData, function(key, val) {
        if (val == "") {
          return true
        }
        BCData.labels.push("Coater " + val[0]);
        BCData.datasets[0].data.push(parseFloat(val[2]));
        BCData.datasets[1].data.push(parseFloat(100 - val[2]));
        BCData.datasets[2].data.push(parseFloat(val[1]));
        BCData.datasets[3].data.push(parseFloat(100 - val[1]));
      });

      var option = {
        responsive: true,
      };
      console.log("BCData", BCData);

      // if the chart is not undefined (e.g. it has been created)
      // then just update the underlying labels and data for each
      // dataset and re-render the chart
      if (myChart) {
        myChart.data.labels = BCData.labels;
        myChart.data.datasets[0].data = BCData.datasets[0].data;
        myChart.data.datasets[1].data = BCData.datasets[1].data;
        myChart.data.datasets[2].data = BCData.datasets[2].data;
        myChart.data.datasets[3].data = BCData.datasets[3].data;
        myChart.update();
      } else {
        // otherwise, this is the first time we are loading so create the chart
        var ctx = document.getElementById("mybarChart2").getContext("2d");
        myChart = new Chart(ctx, {
          type: 'groupableBar',
          data: BCData,
          options: {
            scales: {
              yAxes: [{
                ticks: {
                  max: 100,
                },
                stacked: true,
              }]
            }
          }
        });
      }
    });
}
12
votes

This is chartjs trick i found

var ctxLine = document.getElementById("line-chart").getContext("2d");
if(window.bar != undefined) 
window.bar.destroy(); 
window.bar = new Chart(ctxLine, {});

https://therichpost.com/solved-hovering-chartjs-bar-chart-showing-old-data

10
votes

It will help you...

Check whether MyChart have already configurations or not, If it exist clear it with destroy(); method, And bind new configuration to canvas.

Sample Code..

if (window.MyChart != undefined)
{
    window.MyChart.destroy();
}
window.MyChart = new Chart(ctx, MyChartconfig);
5
votes

There is a simple solution for this, which can be done in JS itself.

let's say your html has something like below

<div id="chartContainer">
 <canvas id="mybarChart2"></canvas>
</div>

Then in your script, you can add below lines of code before updating the data.

document.getElementById("chartContainer").innerHTML = '&nbsp;';
document.getElementById("chartContainer").innerHTML = '<canvas id="mybarChart2"></canvas>';
var ctx = document.getElementById("mybarChart2").getContext("2d");

This solved my issue.

2
votes

after stackoverflowing for so many hours i found and easy solution to it no need to do so many changes ... just add this line in options section

events:[]

in the options section it is an quick solution to get rid from Chartjs Bar Chart showing old data when hovering

if You need Hovering event also then try to reinitialise or re-render the canvas adding if conditions this will fix for sure

1
votes

I was found the problem and my solution is remove them from html first and before load new chart then append canvas.

Html

 $('#chart').empty();
    
$('#chart').html('<canvas id="survey_result"  width="400" height="200"></canvas>'); // then load chart.
    
var ctx = document.getElementById("survey_result");
    <div id="chart" class="display>
    </div>
0
votes

The code of the provided samples by chartjs.org have shown that they don't destroy() the chart and create a new one. Instead, they pop() the existing data from the chart and push() the new dataset to the graph and then update() the chart.

This code is from the chartjs.org website which removes the dataset from the chart by POP().

    document.getElementById('removeDataset').addEventListener('click', function() {
        horizontalBarChartData.datasets.pop();
        window.myHorizontalBar.update();
    });

And this code is for adding the dataset to the chart by PUSH():

    document.getElementById('addDataset').addEventListener('click', function() {
        var colorName = colorNames[horizontalBarChartData.datasets.length % colorNames.length];
        var dsColor = window.chartColors[colorName];
        var newDataset = {
            label: 'Dataset ' + (horizontalBarChartData.datasets.length + 1),
            backgroundColor: color(dsColor).alpha(0.5).rgbString(),
            borderColor: dsColor,
            data: []
        };

        for (var index = 0; index < horizontalBarChartData.labels.length; ++index) {
            newDataset.data.push(randomScalingFactor());
        }

        horizontalBarChartData.datasets.push(newDataset);
        window.myHorizontalBar.update();
    });

The last step of these two code blocks is to update the chart.

Generally speaking, it is necessary to pop the data that you want to remove from the chart and then push the new data and finally update the chart. Therefore, the pervious data is not going to be shown when hovering.

0
votes

I know this is old, but most of these didn't work in my situation, so I'm posting what worked for me.

I had two charts exhibiting this behavior, a z-stacked bar chart and a pie chart. I tried these to no avail:

  • myChart.destroy(): This would work to change the values, but for some reason also affected the size and display values for my charts
  • options: { events: [] }: As said in that post, this removes all tooltips on hover, which I still wanted
  • innerHTML: I don't know if this is a newer feature of chartjs, but none of my charts ever had an innerHTML attribute apart from ""
  • myChart.datasets.pop(): This one is the spirit behind my solution, but in most cases I had more than one dataset, so I just removed them all:

  if (myChart !== undefined) {
    while (myChart.data.datasets.length > 0) {
      myChart.data.datasets.pop();
    }
  }

I was also previously creating var myChart with the function I was using to create the chart and organize the data. After moving that variable declaration outside the function and implementing the above within the function, they work like a charm!

0
votes

What worked for me (maybe it's ineffective but this isn't a bit issue to me as it would only be done a few times per visit) - destroying and recreating the whole canvas object (using JQuery).

$("#canvasID").remove();
$("<canvas>").attr({
    id: "canvasID"
}).appendTo("#canvasParent");
0
votes

I could not get the "destroy()" to work at all. I struggled. I ended up removing the element altogether, then recreating on each update of data.

        if(document.getElementById(chartID)) {
            document.getElementById(chartID).remove();
        }
        

        var canvasParent;
        // I had 2 charts, so a ternary to select the right element
        chartID == 'fleet-fuel-chartComp1' ? canvasParent = document.getElementById('canvas-node1') : canvasParent = document.getElementById('canvas-node2');
        
        var canvas = document.createElement('canvas');
        canvas.id = chartID;
        canvasParent.appendChild(canvas);

        var ffc = document.getElementById(chartID);
0
votes

Declare myChart as a global variable

let mychart;

Before graph creation just write

function pieChart(pdata) {
        // destroy previous created graph
        if (myChart) {
            myChart.destroy()
        }
        let ctx = document.getElementById("pie-chart").getContext('2d');
         myChart = new Chart(ctx, {

            type: 'doughnut',
            data: {
                labels: ['Count'],
                datasets: [{
                    backgroundColor: [
                        "#2ecc71",
                    ],
                    data: [pdata],

                }]
            },
            
        });
    }

why we adding this? Because, if we create any graph at first time the mychart create a object, in that case when we rander it, it won't be change. that's why we need to destroy previous object and create new object for newer visualization.