132

I am using the Chart.js library to draw a bar graph, it is working fine, but now I want to destroy the bar graph and make a line graph in the same canvas. I have tried these two ways to clear the canvas:

var grapharea = document.getElementById("barChart").getContext("2d");

grapharea.destroy();

var myNewChart = new Chart(grapharea, { type: 'radar', data: barData, options: barOptions });

second way:

var grapharea = document.getElementById("barChart").getContext("2d");

grapharea.clear();

var myNewChart = new Chart(grapharea, { type: 'radar', data: barData, options: barOptions });

Am I calling it right? OnButtonClick I call this function which uses the same canvas.

xnakos
  • 9,870
  • 6
  • 27
  • 33
Syed Uzair Uddin
  • 3,296
  • 7
  • 31
  • 47
  • Duplicate question: https://stackoverflow.com/questions/24815851/how-to-clear-a-chart-from-a-canvas-so-that-hover-events-cannot-be-triggered – ThePhi Nov 18 '17 at 09:04

23 Answers23

188

The correct method to use, in order to be able to draw another chart on the same canvas, is .destroy(). You must call it on the previously created chart object. You may also use the same variable for both charts.

var grapharea = document.getElementById("barChart").getContext("2d");

var myChart = new Chart(grapharea, { type: 'bar', data: barData, options: barOptions });

myChart.destroy();

myChart = new Chart(grapharea, { type: 'radar', data: barData, options: barOptions });

Straight from the docs (under Prototype Methods):

.destroy()

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.

// Example from the docs
var myLineChart = new Chart(ctx, config);
// Destroys a specific chart instance
myLineChart.destroy();

It explicitly states that this method must be called before the canvas can be reused for a new chart.

.clear() is also mentioned later in the same section as the function that "will clear the chart canvas. Used extensively internally between animation frames, but you might find it useful." The chart will be alive and well after calling this method, so this is not the method to call, if you want to reuse the canvas for a brand new chart.

To be honest, though, in cases like yours, I have often used a container div to wrap my canvas and, whenever I needed to create a new chart, I placed a new canvas element in this div. I then used this newly created canvas for the new chart. If you ever come across strange behavior, possibly related to charts occupying the canvas before the current chart, have this approach in mind too.

xnakos
  • 9,870
  • 6
  • 27
  • 33
44

Remove the canvas after every chart call, this worked for me

$("canvas#chartreport").remove();
$("div.chartreport").append('<canvas id="chartreport" class="animated fadeIn" height="150"></canvas>');
var ctx = document.getElementById("chartreport").getContext("2d");
chartreport= new Chart(ctx, { .... });
Miriam Farber
  • 18,986
  • 14
  • 61
  • 76
It0007
  • 503
  • 4
  • 11
  • 3
    This is great idea, in vanilla js `document.getElementById( "sector-chart" ).remove(); let canvas = document.createElement('canvas'); canvas.setAttribute('id','chart'); canvas.setAttribute('width','300'); canvas.setAttribute('height','100'); document.querySelector('#chart-container').appendChild(canvas); ` – Albert Hidalgo Feb 14 '19 at 15:39
  • 1
    This works perfectly, especially if you have your JS file to draw graph on a different file. – James Ikubi Jul 08 '20 at 07:55
39

ChartJs's getChart(key) - finds the chart instance from the given key if the chart is already created.

  • If the key is a string, it is interpreted as the ID of the Canvas element for the Chart.
  • The key can also be a CanvasRenderingContext2D or an HTMLDOMElement.

Note: This will return undefined if no Chart is found. If the instance of the chart is found, it signifies that the chart must have previously been created.

// JS - Destroy exiting Chart Instance to reuse <canvas> element
let chartStatus = Chart.getChart("myChart"); // <canvas> id
if (chartStatus != undefined) {
  chartStatus.destroy();
}
//-- End of chart destroy   

var chartCanvas = $('#myChart'); //<canvas> id
chartInstance = new Chart(chartCanvas, {
  type: 'line',
  data: data
});
<!-- HTML -Line Graph - Chart.js -->
<div class="container-fluid" id="chartContainer">
  <canvas id="myChart" width="400" height="150">   </canvas>
</div>

This approach would save you from remove - create - append a Canvas element into DIV from inside JS.

Nishi Bangar
  • 720
  • 9
  • 20
  • 1
    This does a fab check against undefined element. Works true to OOP! – Shaze Dec 09 '21 at 03:34
  • This worked great. Thanks, you are a lifesaver; I think I lost 2 days with this. I had trouble because I used a hyphen instead of an underscore in the second chart name (rookie mistake), but once that was resolved this works like a charm. Highly recommend!! FYI the only special formating needed for a second chart is to increment Canvas id="chartContainer2" and also increment chartStatus2,3, etc. – Edward Jul 28 '22 at 21:03
  • Great answer, this was the perfect and best solution for me. Thank you. – Kamishiraishi Apr 19 '23 at 15:31
16

Maybe there was a better way, but no answers were suited to me.

document.querySelector("#chartReport").innerHTML = '<canvas id="myChart"></canvas>';

my HTML part is

<div class="col-md-6 col-md-offset-3">
     <div id="chartReport">
         <canvas id="myChart"></canvas>
     </div>
</div>
sh6210
  • 4,190
  • 1
  • 37
  • 27
15

For ChartJS v2.x you can use update() to update the chart data without explicitly destroying and creating the canvas.

var chart_ctx = document.getElementById("chart").getContext("2d");

var chart = new Chart(chart_ctx, {
    type: "pie",
    data: {},
    options: {}
});

$.ajax({
    ...
}).done(function (response) {
    chart.data = response;
    chart.update();
});
RGriffiths
  • 5,722
  • 18
  • 72
  • 120
bmatovu
  • 3,756
  • 1
  • 35
  • 37
  • 1
    Is there any way to retrieve the Chart instance if the variable has been overridden or is not in use anymore? – Rahul Hindocha May 30 '21 at 13:33
  • 2
    @RahulHindocha If you don't have access to your variable, you can re-access the instances by looking at the `Chart.instances` object. From here you can do `Chart.instances[key].destroy()` or `Chart.instances[key].update()`. – kingliam Sep 14 '21 at 10:23
11

Simple edit for 2020:

This worked for me. Change the chart to global by making it window owned (Change the declaration from var myChart to window myChart)

Check whether the chart variable is already initialized as Chart, if so, destroy it and create a new one, even you can create another one on the same name. Below is the code:

if(window.myChart instanceof Chart)
{
    window.myChart.destroy();
}
var ctx = document.getElementById('myChart').getContext("2d");

Hope it works!

Surya
  • 554
  • 5
  • 10
  • 2
    This has to be the only correct option. You should not be calling `destroy()` without first checking there is something to destroy. Also, checking explicitly for an `instanceof Chart` is correct. I have previously been checking for "undefined" and it can cause issues in certain circumstances. – Andrew Hardiman Jun 24 '21 at 16:58
6

you can test this

 $('#canvas').replaceWith($('<canvas id="canvas" height="320px"></canvas>'));

;)

Willywes
  • 161
  • 1
  • 2
5

I always use only 1 graph/page. Destroy() solved the issues.

 if (
        window.myLine !== undefined
        &&
        window.myLine !== null
    ) {
        window.myLine.destroy();
    }

    window.myLine = new Chart(graphCanvasCtx, config);
sabiland
  • 2,526
  • 1
  • 25
  • 24
4

Here is my strategy for destroying a ChartJS chart before creating a new one, for a given canvas ID. It's a little brute force, but gets the job done.

I create an object, chartsByCanvasId, that keeps track of a mapping from canvas id's to the associated ChartJS objects, and I'll just check that object for any existing ChartJS object to destroy before creating a new one.

Take a look here:


// Helper object and functions

const chartsByCanvasId = {};

const destroyChartIfNecessary = (canvasId) => {
    if (chartsByCanvasId[canvasId]) {
        chartsByCanvasId[canvasId].destroy();
    }
}

const registerNewChart = (canvasId, chart) => {
    chartsByCanvasId[canvasId] = chart;
}

Then, this is how you can destroy a Chart if it exists

destroyChartIfNecessary(canvasId);
const myChart = new Chart(ctx, config);
registerNewChart(canvasId, myChart);

Note that after creating a chart, we immediately "register" it with registerNewChart(). This registration step is important, because that is how destroyChartIfNecessary() is going to know that a ChartJS object already exists for a given canvas id.

Nice thing about this strategy, is that it works even if there are many charts on your page, because it keeps track of ChartJS objects by canvas ID.

tedz2usa
  • 76
  • 3
2

I'm using Chart.js 2.7.2 as of right now. In my app, I'm creating multiple charts and needed a way to access them to properly "replace" their data and fix the "old chart" showing on hover. None of the answers I've tried worked right.

Here's a way to manage this with one or multiple charts:

Store charts in global

var charts=[]; // global

Function to create charts

function createChart(id, type, labels, data)
{
    // for multiple datasets
    var datasets=[];
    data.forEach(function(set) {
        datasets.push({
            label: set.label,
            data: set.data
        });
    });  

    var config = {
        type: type,
        data: {
            labels: labels,
            datasets: datasets
        }
    };

    if(typeof charts[id] == "undefined") // see if passed id exists
    {   
        // doesn't, so create it
        charts[id]= new (function(){
            this.ctx=$(id); // canvas el
            this.chart=new Chart(this.ctx, config);
        })();     
        console.log('created chart '+charts[id].chart.canvas.id);     
    }
    else
    {
        charts[id].chart.destroy(); // "destroy" the "old chart"
        charts[id].chart=new Chart(charts[id].ctx, config); // create the chart with same id and el
        console.log('replaced chart '+charts[id].chart.canvas.id);        
    }
    // just to see all instances
    Chart.helpers.each(Chart.instances, function(instance){
        console.log('found instance '+instance.chart.canvas.id)
    })

}

For each of your canvas elements like:

<canvas id="thiscanvasid"></canvas>

Use the function to create/replace the chart

createChart('#thiscanvasid', 'bar', json.labels, json.datasets);
Chris
  • 893
  • 10
  • 23
2

Change new chart variable from...

var yourChart= new Chart(ctx1).Line(barChartData1, {animation: false});

TO

window.yourChart= new Chart(ctx1).Line(barChartData1, {animation: false});

Then

if(window.yourChart!= null)
{
 window.yourChart.destroy();
}

var doc1 = document.getElementById("stockPrice");
var ctx1 = doc1.getContext("2d");
window.yourChart = new Chart(ctx1).Bar(barChartData1, {animation: false});
khurram
  • 946
  • 1
  • 13
  • 34
  • Tried this and got "TypeError: window.line_chart.destroy is not a function". (Yes I included brackets in my code but Chrome doesn't report them in console.) I am missing something very subtle. The call I make from HTML is and the chart is created with your "window.line_chart = new Chart(ctx, config );" suggestion – Edward Jul 28 '22 at 18:24
1

Change this:

var myChart = new Chart(ctx, {...

to

if(window.myChartAnything != undefined)

window.Anything.destroy();

window.Anything = new Chart(ctx, {...

This rocks on my webpage.

Saeid Amini
  • 1,313
  • 5
  • 16
  • 26
Zsolt
  • 11
  • 1
0

In order to solve this problem I have used jQuery's add() and remove() methods, to clear the canvas. I am removing the component and before drawing it again I am appending the canvas again with the same id using jQuery's append() method.

redraw(){

 $("#myChart").remove();// removing previous canvas element
 //change the data values or add new values for new graph
 $("#chart_box").after("<canvas id='myChart'></canvas>");
 // again adding a new canvas element with same id
 generateGraph();// calling the main graph generating function 

}
scopchanov
  • 7,966
  • 10
  • 40
  • 68
0

Create a global object:

window['chart-' + chartId] = new Chart(...);

Access and destroy to procced with redraw:

if ( window['chart-' + chartId] != undefined ) {
    window['chart-' + chartId].destroy();
}
Arun R
  • 1
0

This will resolve the issue where your chart becomes slower while updating it several times on multiple ajax calls:

Just add this code before initiating your chart:

    $('.chartjs-size-monitor').each(function(){
      $(this).remove();
    })
    var grapharea = document.getElementById("barChart").getContext("2d");
SuKu
  • 383
  • 3
  • 11
0

I got the same problem, I have removed the canvas element and recreated canvas element and then again rendered with some delay.

var element = document.getElementById("canvasId");

element.parentNode.removeChild(element);

var canv =  document.createElement("canvas");

canv.setAttribute("id","canvasId");

canv.style.height = "20vw"; // give height and width as per the requirement

canv.style.width = "20vw"; 

setTimeout(()=>{
 var grapharea = document.getElementById("canvasId").getContext("2d");
},500)
ssomu
  • 79
  • 7
0

I managed to find a solution that it works with destroy method and allow reuse of the canvas without deleting and recreating it, and at the same time it's the less resources consumer.

First, declare var chart as global and create a boolean to check if the js is loaded

var chart;
var graphScriptLoaded = false;

Next part is nice, because it load the js just when the graph is needed, saving time loading the page, and at the same time it allows you to understand if it's first execution or not.

//load graph just when needed and destry existing istances
if (!Boolean(graphScriptLoaded)) {
  loadScript('https://cdn.jsdelivr.net/npm/chart.js@2.8.0', function(){
    graphScriptLoaded = true;
    chart=graphs_init(i_arr, spent_arr);
  });
} else {
  chart.destroy();
  chart=graphs_init(i_arr, spent_arr);
}

Then, in the function that create the graph, simply return chart var

var chart = new Chart(ctx, {
[.....]
});
return chart;

Function "loadscript" is customized and based on this answer: How do I include a JavaScript file in another JavaScript file?

and here it is:

    function loadScript(url, callback){

    var script = document.createElement("script")
    script.type = "text/javascript";

    if (script.readyState){  //IE
        script.onreadystatechange = function(){
          if (script.readyState == "loaded" || script.readyState == "complete"){
                script.onreadystatechange = null;
                callback();
          }
        };
    } else {  //Others
        script.onload = function(){
          callback();
        };
    }

    script.src = url;
    document.getElementsByTagName("head")[0].appendChild(script);
}

It works like a charm.

0

for me it seemed to work like below with core javascript (assuming chart min js already loaded):

const data = {
    labels : graphDataLabels,
        datasets: [{
            label: 'Sentiment Intensity Distribution',
            data: dataValues, //[300, 50, 100],
            backgroundColor: [
                "#0D5265",
                "#32DAC8",
                "#FF8300"
            ],
            hoverOffset: 4
        }]
    };
    const config = {
        type: 'pie',
        data: data
    };
    var ctx = document.getElementById('pieChart').getContext('2d');
    if(ctx.pieChart){
        pieChart = null;
    }else{
        pieChart = new Chart(ctx, config);
    }
shaneel
  • 11
  • 1
0

If you have many charts on one page them building a data structure to save lists of existing charts is complicated. It's easier in chart.js 3.5.0 to test to see if the chart canvas is already used or not. No separate data structures required:

// Chart may previously have been shown or not, so the chart may need creating or updating. 
// Vue messes with the DOM, so you can't just "update" the chart as per the docs.
var canv = this.$refs['canvas'];
const oldChart = Chart.getChart(canv);
if (typeof oldChart !== 'undefined') {
    oldChart.destroy();
}
new Chart(canv.getContext('2d'), this.config);
philw
  • 661
  • 10
  • 20
0

I don't know how many hours I gave to handle this problem.

Let's assume your html contains that

<div id="soner" class="card-body customerDonutChart">
<canvas id="customerDonutChart" style="min-height: 250px; height: 250px; max-height: 250px; max-width: 100%;"></canvas> 
</div>

Watch out the <div id="soner" part which is necessary to overcome the problem.

function myChartJsCaller()
{
   document.getElementById("soner").innerHTML = '<canvas id="customerDonutChart" style="min-height: 250px; height: 250px; max-height: 250px; max-width: 100%;"></canvas>';

  // here is important, your ctx = blabla must be below after changing innerHTML
  let ctx = document.getElementById(selector);
  .
  .
}
0

I have a slightly different approach to this. first in my js i have defined one map like below.

var chartContext = new Map();

And i insert one key value pair when i populate this chart like this value being chart object it self and key being id of that canvas.

 chartContext.set(chartid, chartObject);

after this when i need to repopulate / rerender same canvas again, I do following.

if (chartContext.has(chartid)) {
        tempchartContext = chartContext.get(chartid);
        if (tempchartContext instanceof Chart) {
            tempchartContext.destroy();
        }
    }

By this way old chart context is destroyed.

and after making new chart i call following code to update same canvas again

Hemin
  • 712
  • 1
  • 14
  • 29
  • When I try your solution, I get the error "Cannot access 'myChart' before initialization" Where must this destroy() script be placed exactly? It errors when I place it before "new Chart" – Edward Jul 28 '22 at 18:01
0

I'm using Angular for this example and found it easy by doing the following

import Chart, { ChartConfiguration } from 'chart.js/auto';
export class ReportsComponent implements OnInit {
    chart1: Chart;

    ngOnInit(): void {
       this.initGraphs(); // logic for creating your charts
       this.drawGraphs(data) // populate the chart and update graph
    }
    
    initGraphs(){
      this.chart1= new Chart(...)
    }

    drawGraphs(data){ // just an example of how you might populate your chart
      for (const total of Object.values(data.gender)) {
        this.chart1.data.datasets[0].data.push(+total);
      }

      this.chart1.update(); // to override the default empty datasets on the chart config
    }

    onUpdate(){
       const filteredData = {...} // this would be the new data you want to show
       this.chart1.destroy();
       this.initGraphs();
       this.drawGraphs(filteredData);
    }
}

So yes you use the .destroy() to destroy any chart instances that are created. But you have to create the chart again for it to be rendered on your screen

-1

This works for me in angular

removeHTML() {

    let chart = <HTMLCanvasElement>document.getElementById("myChart"); 
    let chart2 = <HTMLCanvasElement>document.getElementById("myChart2"); 

    if(chart){
       chart.remove()
    }

    if(chart2){
      chart2.remove()
    }


}


ngOnDestroy(): void {

    this.removeHTML();
}