Creating Dynamic Charts with Chart.JS – Part Two: Creating a Truly Dynamic Dashboard


Hello, and welcome to Another Salesforce Blog!  Here I will be posting solutions to problems that I couldn’t find an answer to in hopes of helping those who find themselves stuck when using the Salesforce platform.

User Story

We have some information on our objects that we want to view in dynamic charts. These charts will update based on the object that is created by inputting a sentence into a Lightning Web Component.

Background

In the previous post, we started on our Lightning Web Component. We are going to use Chart.js to create a truly dynamic dashboard for our existing LWC.

Charts!

Solution

The full code for Parts One and Two can be found at my GitHub repository here.

Step One – Download and Install Chart.js.

This one is pretty easy. We’re going to download Chart.js and install it into our Salesforce org. We will do this by uploading the file as a Static Resource.

Navigate to the Installation page on Chart.js and scroll to the GitHub section. Follow the link to navigate to the newest version of the source code on GitHub.

GitHub section on Chart.js Installation page.

Once on the GitHub page, download and unzip the Source Code zip.

There will be a number of folders and files, but the one we need is the dist folder. We are going to zip the files within folder and upload the .zip file as a Static Resource called chartJs. Be sure to create your .zip file with just the files contained within the dist folder! If you zip the whole dist folder, you’ll get stuck in a few minutes.

Static Resource chartJs.

This will allow us to use the files provided to us by Chart.js in Salesforce.

Step Two – Update Apex Class createSentence.cls.

Before we dive into the JavaScript, let’s update our Apex class. We need a new @AuraEnabled method to retrieve information from our Sentence__c object that is created based on information that is sent to us from our LWC. Add the following below your insertSentence method:

    @AuraEnabled(cacheable=true)
    public static Sentence__c getChartSentence(String recordId){
        Sentence__c returnSentence = [SELECT Id, Name, Sentence__c, IsPalindrome__c,
                                            A__c, B__c, C__c, D__c, E__c, F__c, G__c, H__c,
                                            I__c, J__c, K__c, L__c, M__c, N__c, O__c, P__c,
                                            Q__c, R__c, S__c, T__c, U__c, V__c, W__c, X__c,
                                            Y__c, Z__c, x0__c, x1__c, x2__c, x3__c, x4__c,
                                            x5__c, x6__c, x7__c, x8__c, x9__c
                                            FROM Sentence__c WHERE Id = :recordId LIMIT 1];
        
        return returnSentence;

    }

Step Three – Create Donut Chart Lightning Web Component.

Next, we’re going to create our second Lightning Web Component. This LWC will be nested inside of our palindromeChecker. We’ll call our new component sentenceDonutChart, and we will use this component to display the number of letters in our sentence.

We’ll start by creating our HTML file.

<template>
    <div class="chart slds-var-m-around_medium" lwc:dom="manual"></div>
</template>

This one is pretty simple, we’re just going to create a chart div, and we’ll place this inside our parent LWC.

We will do this by adding the following element below our first column element, which will send our sentenceId from Part One to the char element:

                <div class="slds-col slds-size_1-of-3">
                    <c-sentence-donut-chart sentence-id={sentenceId}></c-sentence-donut-chart>
                </div>

For now, we won’t see anything when we deploy and refresh our page, so let’s build our chart.

Navigate to the sentenceDonutchart.js file, and enter the following:

import { LightningElement, api, track, wire } from 'lwc';
import { loadScript, loadStyle } from 'lightning/platformResourceLoader';
import chartjs from '@salesforce/resourceUrl/chartJs';
import getChartSentence from '@salesforce/apex/createSentence.getChartSentence';

export default class LibsChartjs extends LightningElement {
    
    //receive parentId from parent LWC
    @api sentenceId;

    @track sentence;
    error;
    @track chart;
    chartjsInitialized = false;

    //configuration variable
    config = {
        type: 'doughnut',
        data: {
            datasets: [
                {
                    data: [],
                    backgroundColor: [
                        'rgb(192, 11, 92)', 
                        'rgb(191, 97, 215)',
                        'rgb(69, 15, 36)',
                        'rgb(197, 86, 186)',
                        'rgb(56, 7, 41)',
                        'rgb(124, 68, 225)',
                        'rgb(26, 243, 223)',
                        'rgb(159, 198, 55)',
                        'rgb(167, 69, 94)',
                        'rgb(173, 151, 102)',
                        'rgb(89, 106, 124)',
                        'rgb(69, 203, 69)',
                        'rgb(16, 72, 90)',
                        'rgb(176, 80, 1)',
                        'rgb(215, 6, 2)',
                        'rgb(137, 187, 127)',
                        'rgb(81, 113, 162)',
                        'rgb(230, 222, 86)',
                        'rgb(82, 239, 17)',
                        'rgb(226, 125, 8)',
                        'rgb(237, 241, 14)',
                        'rgb(40, 67, 192)',
                        'rgb(21, 85, 148)',
                        'rgb(162, 232, 153)',
                        'rgb(213, 45, 249)',
                        'rgb(186, 252, 119)'
                    ],
                    label: 'Sentence'
                }
            ],
            labels: ['A', 'B', 'C', 'D',
                     'E', 'F', 'G', 'H',
                     'I', 'J', 'K', 'L',
                     'M', 'N', 'O', 'P',
                     'Q', 'R', 'S', 'T',
                     'U', 'V', 'W', 'X',
                     'Y', 'Z']
        },
        options: {
            responsive: true,
            legend: {
                display: false
            },
            animation: {
                animateScale: true,
                animateRotate: true
            }
        }
    };

    //wired function
    @wire(getChartSentence, {recordId: '$sentenceId'})
    wiredSentence({data, error}) {
        if(data) {
            this.sentence = data;
            //clear previous data
            this.config.data.datasets[0].data.length = [];

            this.config.data.datasets[0].data.push(data.A__c);
            this.config.data.datasets[0].data.push(data.B__c);
            this.config.data.datasets[0].data.push(data.C__c);
            this.config.data.datasets[0].data.push(data.D__c);
            this.config.data.datasets[0].data.push(data.E__c);
            this.config.data.datasets[0].data.push(data.F__c);
            this.config.data.datasets[0].data.push(data.G__c);
            this.config.data.datasets[0].data.push(data.H__c);
            this.config.data.datasets[0].data.push(data.I__c);
            this.config.data.datasets[0].data.push(data.J__c);
            this.config.data.datasets[0].data.push(data.K__c);
            this.config.data.datasets[0].data.push(data.L__c);
            this.config.data.datasets[0].data.push(data.M__c);
            this.config.data.datasets[0].data.push(data.N__c);
            this.config.data.datasets[0].data.push(data.O__c);
            this.config.data.datasets[0].data.push(data.P__c);
            this.config.data.datasets[0].data.push(data.Q__c);
            this.config.data.datasets[0].data.push(data.R__c);
            this.config.data.datasets[0].data.push(data.S__c);
            this.config.data.datasets[0].data.push(data.T__c);
            this.config.data.datasets[0].data.push(data.U__c);
            this.config.data.datasets[0].data.push(data.V__c);
            this.config.data.datasets[0].data.push(data.W__c);
            this.config.data.datasets[0].data.push(data.X__c);
            this.config.data.datasets[0].data.push(data.Y__c);
            this.config.data.datasets[0].data.push(data.Z__c);

            this.chart.update();

        } else if(error) {
            this.error = error;
        }
    }

    updateChart() {
        
        this.chart.update();
        
    }

    renderedCallback() {
        if (this.chartjsInitialized) {
            return;
        }

        Promise.all([
            loadScript(this, chartjs + '/Chart.min.js'),
            loadStyle(this, chartjs + '/Chart.min.css')
        ])
            .then(() => {
                // disable Chart.js CSS injection
                window.Chart.platform.disableCSSInjection = true;

                const canvas = document.createElement('canvas');
                this.template.querySelector('div.chart').appendChild(canvas);
                const ctx = canvas.getContext('2d');
                this.chart = new window.Chart(ctx, this.config);
            })
            .catch((error) => {
                this.error = error;
                alert(this.error);
            });

            
        this.chartjsInitialized = true;
    }
}

Step Three – Create Bar Chart Lightning Web Component.

Lastly, we’re going to create our third Lightning Web Component. This LWC will be nested inside of our palindromeChecker along with our sentenceDonutChart. We’ll call our new component sentenceBarChart, and we will use this component to display the number of numerical characters in our sentence.

We’ll start with our HTML file, which should look familiar!

<template>
    <div class="chart slds-var-m-around_medium" lwc:dom="manual"></div>
</template>

Next, we’ll go to our sentenceBarChart.js file and use the following:

import { LightningElement, api, track, wire } from 'lwc';
import { loadScript, loadStyle } from 'lightning/platformResourceLoader';
import chartjs from '@salesforce/resourceUrl/chartJs';
import getChartSentence from '@salesforce/apex/createSentence.getChartSentence';

export default class LibsChartjs extends LightningElement {
    
    //receive parentId from parent LWC
    @api sentenceId;

    @track sentence;
    error;
    @track chart;
    chartjsInitialized = false;
    @track maxvalue;

    //configuration variable
    config = {
        type: 'bar',
        data: {
            datasets: [
                {
                    data: [],
                    backgroundColor: [
                        'rgb(192, 11, 92)', 
                        'rgb(191, 97, 215)',
                        'rgb(69, 15, 36)',
                        'rgb(197, 86, 186)',
                        'rgb(56, 7, 41)',
                        'rgb(124, 68, 225)',
                        'rgb(26, 243, 223)',
                        'rgb(159, 198, 55)',
                        'rgb(167, 69, 94)',
                        'rgb(173, 151, 102)'
                    ],
                    label: 'Numerical Characters'
                }
            ],
            labels: ['0', '1', '2', '3', '4',
                     '5', '6', '7', '8', '9']
        },
        options: {
            scales: {
                yAxes: [{
                    ticks: {
                        min: 0,
                        stepSize: 1
                    }
                }]
            },
            legend: {
                display: false
            }
        }
    };

    //wired function
    @wire(getChartSentence, {recordId: '$sentenceId'})
    wiredSentence({data, error}) {
        if(data) {
            this.sentence = data;
            //clear previous data
            this.config.data.datasets[0].data.length = [];

            this.config.data.datasets[0].data.push(data.X0__c);
            this.config.data.datasets[0].data.push(data.X1__c);
            this.config.data.datasets[0].data.push(data.X2__c);
            this.config.data.datasets[0].data.push(data.X3__c);
            this.config.data.datasets[0].data.push(data.X4__c);
            this.config.data.datasets[0].data.push(data.X5__c);
            this.config.data.datasets[0].data.push(data.X6__c);
            this.config.data.datasets[0].data.push(data.X7__c);
            this.config.data.datasets[0].data.push(data.X8__c);
            this.config.data.datasets[0].data.push(data.X9__c);

            this.chart.update();

        } else if(error) {
            this.error = error;
            alert('wired error')
        }
    }

    updateChart() {
        
        this.chart.update();
        
    }

    renderedCallback() {
        if (this.chartjsInitialized) {
            return;
        }

        Promise.all([
            loadScript(this, chartjs + '/Chart.min.js'),
            loadStyle(this, chartjs + '/Chart.min.css')
        ])
            .then(() => {
                // disable Chart.js CSS injection
                window.Chart.platform.disableCSSInjection = true;

                const canvas = document.createElement('canvas');
                this.template.querySelector('div.chart').appendChild(canvas);
                const ctx = canvas.getContext('2d');
                this.chart = new window.Chart(ctx, this.config);
            })
            .catch((error) => {
                this.error = error;
                alert(this.error);
            });

            
        this.chartjsInitialized = true;
    }
}

Now, when we deploy and refresh our page, we should get something like this:

GIF displaying dynamic charts.

So, why does this work?

In order to make the chart responsive to the input into our LWC, we need to use the .push() function to update our configuration object for our charts.

You’ll notice that in the JavaScript file for each chart, config.data.datasets[0].data starts as an empty array:

Configuration variable in VSCode.

We then .push() our dataset into that array, and update the chart. When we get a new set of data, we clear the array by setting it to an empty array once again.

Thanks for reading, let me know if you have any comments or questions!

Evelyn Grizzle

Another Salesforce Blog


Leave a Reply

%d bloggers like this: