2

We've used the open source chart.js to implement our charts in lightning components but we've noticed that if the chart's data set changes, e.g. a picklist that the user can select a different value from which generates a different data set for the chart, the chart flickers between the old & new values when the mouse moves over it.

Looking around the web the solution from Chart.js is to keep a global variable in the javascript which holds the chart object when it is created & then destroy that chart object before re-creating a new one (see https://stackoverflow.com/questions/28609932/chartjs-resizing-very-quickly-flickering-on-mouseover). However I don't think that mechanism is possible from within a lightning component, but I may be wrong. Any suggestions?

[Edit -Here's a working example of the problem. After the chart is displayed if you check the checkbox & then move the mouse over the chart it will toggle between the old & new chart at certain positions. Obviously in real component I'd be calling an apex controller etc. but I thought a simplified version would be easier to understand. For it to work you'll need to create a static resource called Chart that contains the Chart.js from [http://www.chartjs.org/]

(AndeeChart.cmp)

<aura:component implements="flexipage:availableForAllPageTypes" access="global">
<ltng:require scripts="{!$Resource.Chart}" afterScriptsLoaded="{!c.init}"/>

<aura:attribute name="dataset" type="String" default="1"  description="Which set of data to display in the chart.  Will be either 1 or 2"/>

<div class="slds-grid slds-wrap">
    <div class="slds-col slds-size--1-of-1 slds-small-size--1-of-2 slds-medium-size--1-of-4">
        <ui:inputCheckbox label="Toggle Data?" click="{!c.updateDataset}"/>

    </div>
    <div class="slds-col slds-size--1-of-1 slds-small-size--1-of-2 slds-medium-size--3-of-4">
        Chart1<br></br>
        <canvas aura:id="andeeChart" id="andeeChart123"/>
    </div>

</div>

(AndeeChartController.js)

({
    init : function(component, event, helper) {
        helper.setupChart(component);
    },
    updateDataset : function(component, event, helper) {
        var dataset = component.get('v.dataset');
        if (dataset == '1'){
            dataset = '2';
        } else {
            dataset = '1';
        }
        component.set('v.dataset', dataset)
        helper.setupChart(component);
    } 
})

(AndeeChartHelper.js)

({
    setupChart  : function(component) {


        // Normally call apex controller to get data but hardcoded for demonstration purposes
        var dataset = component.get('v.dataset');
        var data;
        var jsonRetVal
        if (dataset == '1'){
            jsonRetVal = {"chartLabels":["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],"chartData":[1.00,3.00,6.00,10.00,15.00,21.00]}
        } else {
           jsonRetVal = {"chartLabels":["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],"chartData":[21.00,3.00,16.00,19.00,17.00,12.00]} 
        }


        var el = component.find('andeeChart').getElement();
        var ctx = el.getContext('2d'); 

        // Need something here to destroy any chart that is currently being displayed to stop the 'flicker'

        new Chart(ctx, {
            type: 'bar',
            data: {
                labels: jsonRetVal.chartLabels,
                datasets: [
                    {
                        label: "Data",
                        fillColor: "rgba(220,220,220,1)",
                        strokeColor: "rgba(220,220,220,1)",                
                        data: jsonRetVal.chartData
                    }
                ]
            },
            options: {
                hover: {
                    mode: "none"
                },
                scales: {
                    yAxes: [{
                        ticks: {
                            beginAtZero:true
                        }
                    }]
                }
            }
        });



    }
})
2
  • 1
    Please post your code.So that we can help you out.
    – Praveen
    Sep 19, 2016 at 18:12
  • Hi Praveen & thanks for trying to help out. I've added code to my original post which should demonstrate the problem. Thanks again. Sep 20, 2016 at 8:11

2 Answers 2

2

you cannot have global var chartobj in lightning component.The proper way to solve this, is by having an aura:attribute to store the chart object; and later on access the same object and destroy the chart instance.

Something like this:

component:

<aura:component implements="flexipage:availableForAllPageTypes" access="global">
    <ltng:require scripts="{!$Resource.chartjs}" afterScriptsLoaded="{!c.init}"/>

    <aura:attribute name="dataset" type="String" default="1"  description="Which set of data to display in the chart.  Will be either 1 or 2"/>
    <aura:attribute name="chartobj" type="Object" access="public"/>

    <div class="slds-grid slds-wrap">
        <div class="slds-col slds-size--1-of-1 slds-small-size--1-of-2 slds-medium-size--1-of-4">
            <ui:inputCheckbox label="Toggle Data?" click="{!c.updateDataset}"/>

        </div>
        <div class="slds-col slds-size--1-of-1 slds-small-size--1-of-2 slds-medium-size--3-of-4">
            Chart1<br></br>
            <canvas aura:id="andeeChart" id="andeeChart123"/>
        </div>

    </div>
</aura:component>

helper:

({
    setupChart  : function(component) {
        // Normally call apex controller to get data but hardcoded for demonstration purposes
        var dataset = component.get('v.dataset');
        var data;
        var jsonRetVal;
        var chartobj = component.get("v.chartobj");
        if (dataset == '1'){
            jsonRetVal = {"chartLabels":["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],"chartData":[1.00,3.00,6.00,10.00,15.00,21.00]}
        } else {
           jsonRetVal = {"chartLabels":["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],"chartData":[21.00,3.00,16.00,19.00,17.00,12.00]} 
        }


        var el = component.find('andeeChart').getElement();
        var ctx = el.getContext('2d'); 

        //if chartobj is not empty, then destory the chart in the view
        if(chartobj){
            chartobj.destroy();
        }

        chartobj = new Chart(ctx, {
            type: 'bar',
            data: {
                labels: jsonRetVal.chartLabels,
                datasets: [
                    {
                        label: "Data",
                        fillColor: "rgba(220,220,220,1)",
                        strokeColor: "rgba(220,220,220,1)",                
                        data: jsonRetVal.chartData
                    }
                ]
            },
            options: {
                hover: {
                    mode: "none"
                },
                scales: {
                    yAxes: [{
                        ticks: {
                            beginAtZero:true
                        }
                    }]
                }
            }
        });

        //store the chart in the attribute
        component.set("v.chartobj",chartobj);   
    }
})

NOTE : chartjs works only if locker service is disabled in the org.

4
  • Thanks Praveen, that makes perfect sense. However it is failing for me when it tried to destroy the old chart with 'TypeError: Cannot read property 'removeChild''. I suspect this is due to Locker Service which is not something I'd come across before & from reading around I was automatically enabled for us as our org was created post-Summer 2016. Does this error sound like Locker Service getting in the way? In which case are they any other charting tools out there that you can recommend? Sep 21, 2016 at 7:59
  • @Stickleback I would recommend to raise a case with salesforce support because locker service creates a layer where you cannot interact with direct DOM Api's available in the browser instead you interact with SecureDocument,SecureWindow which only has limited DOM Api's exposed and others standard Api's are slowly opened up later on.
    – Praveen
    Sep 21, 2016 at 9:25
  • Thanks. I'll mark this as answered & go down the SFDC Support route. Thanks again. Sep 21, 2016 at 9:52
  • Was there any feedback from Salesforce on this? I'm currently facing issues with ChartJS not working with LockerService enabled. ChartJS can't find the canvas element when I try to generate the chart. Jan 12, 2017 at 1:31
0

Dynamically create the component in the js controller & pop() then push() to v.body.

so in Component :-

<aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    <table  style="width:100%;">
        <tr>
            <td style="width:100%;">
                <div class="slds-grid slds-wrap">
                    <div class="slds-col slds-size--1-of-1 slds-small-size--1-of-2 slds-medium-size--1-of-4">
                        <ui:inputCheckbox label="Toggle Data?" click="{!c.updateDataset}"/>            
                    </div>            
                </div>
                {!v.body}
            </td>
        </tr>
     </table>

Then in the component's controller do something like :-

updateDataset : function(component, event, helper) {

    dataset = getTheData; // Need some real code here
    $A.createComponent(
        "c:ChartTestClear",  // The name of the component you want to display
        {
            "dataset": dataset  // With the dataset to show
        },
        function(newChartComp, status, errorMessage){
            if(status == "SUCCESS"){
                var body = component.get("v.body");
                body.pop(); // Remove the old body
                body.push(newChartComp);  // Add the new version of the component to the body
                component.set("v.body", body); 
            } 
        }
    );
}

Not a fully working example but hopefully you get the gist

1
  • I have 4 charts on the same page, and currently, I am injecting each chart in different sections, then how I can push a particular chart into the desired section? Aug 17, 2018 at 4:11

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .