1

I am using chart.js to display Bar chart using LWC. I want to open report when we click on Bar chart. I am facing issue to while identifying onclick event.

enter image description here

Parent Component: HtML

<template>
    <lightning-card title="Opportunities Report" icon-name="utility:chart">
        <template if:true={chartConfiguration}>
            <c-childbarchart chart-config={chartConfiguration}></c-childbarchart>
        </template>
    </lightning-card>
</template>

JS file

import { LightningElement,wire } from 'lwc';
import getOpportunities from  '@salesforce/apex/summary2.runReport'; 
import getReporId from  '@salesforce/apex/summary2.runReport3'; 
import {NavigationMixin} from 'lightning/navigation';
export default class SummaryCount extends NavigationMixin(LightningElement) {

    chartConfiguration;
    chartLabel = [];
    reportId;
    @wire(getOpportunities)
    getOpportunities({ error, data })
     {
        if (error) {
            this.error = error;
            this.chartConfiguration = undefined;
                   } 
        else if (data) {
            //console.log(data)
            let chartdata = [];
            let chartLabel =['Year Count','Last Month Count'];
            var conts = data;
            
            data.forEach(opp => {
                    chartdata.push(opp);
             })
            console.log('chartdata',chartdata);
            this.chartConfiguration = {
                type: 'bar',
                data: {
                    datasets: [{
                        axis: 'x',
                        barPercentage: 1,
                        barThickness: 2,
                        label: 'Total Count',
                        backgroundColor: "green",
                        //backgroundColor: ["green","green"],
                        data: chartdata,
                    },],
                   labels: chartLabel,
                },
                options: {
                    scales: {
                        xAxes: [{
                                  barThickness: 20,  // number (pixels) or 'flex'
                                  // maxBarThickness: 12 // number (pixels)
                               }],
                        yAxes: [{
                            ticks: {
                                     beginAtZero: true
                                   }
                               }]
                            },
                    onClick:this.graphClickEvent,  
                  
                    }
                
               
            };
           
            console.log('data => ', data);
            this.error = undefined;
        }
    }

    @wire(getReporId)getIdd({data,error})
    {
        if(data){
                console.log('Report Id:'+data);
                this.reportId=data;
                }
    }
      graphClickEvent(){
        console.log('i got clicked');
    } 
}

Child Component: HTML

<template>
    <div class="slds-p-around_small slds-grid slds-grid--vertical-align-center slds-grid--align-center">
        <canvas class="barChart" lwc:dom="manual"></canvas>
        <div if:false={isChartJsInitialized} class="slds-col--padded slds-size--1-of-1">
            <lightning-spinner alternative-text="Loading" size="medium" variant="base"></lightning-spinner>
        </div>
    </div>
</template>

Js

import { LightningElement, api } from 'lwc';
import chartjs from '@salesforce/resourceUrl/ChartJS';
import { loadScript } from 'lightning/platformResourceLoader';
import { ShowToastEvent } from 'lightning/platformShowToastEvent'; 

export default class childbarchart extends NavigationMixin(LightningElement) {
    @api chartConfig; 
    isChartJsInitialized;
    renderedCallback() {
        if (this.isChartJsInitialized) {
            return;
        }
        // load chartjs from the static resource
        Promise.all([loadScript(this, chartjs)])
        .then(() => {
            this.isChartJsInitialized = true;
            const ctx = this.template.querySelector('canvas.barChart').getContext('2d');
            this.chart = new window.Chart(ctx, JSON.parse(JSON.stringify(this.chartConfig)));
            
          
        })
        .catch(error => {
            this.dispatchEvent(
                new ShowToastEvent({
                    title: 'Error loading Chart',
                    message: error.message,
                    variant: 'error',
                })
            );
        });
    }
  
  
}

1 Answer 1

1

JSON.parse(JSON.stringify(param)) can only handle certain primitives, such as strings, numbers, Plain Old JavaScript Objects (POJO), and Boolean values. Things like functions will automatically be stripped out using this process, and recursive structures will fail to copy correctly. So, by the time the library gets that data, onClick is null/undefined and won't be able to be used.

Instead, you need to use a structured clone algorithm. You can use many popular polyfills, but if you only need support for functions, non-recursive arrays, and non-recursive POJO, you can use a quick algorithm for that purpose.

export function clone(source) {
  // Stop here for some types
  if(source === undefined || source === null || source instanceof Function) {
    return source;
  }
  // Recursively copy every element in array
  if(Array.isArray(source)) {
    return source.map((item)=>clone(item));
  }
  // Recursively copy every property we can
  if(typeof source === 'object') {
    return Object.fromEntries(
      Object.entries(source).map(
        ([key, value]) => ([ key, clone(value)])
      )
    );
  }
  return source;
}

This method should work fine for any data type you receive from Apex, but be aware that it, like JSON.stringify, will fail on most data types, including recursive structures. This code is optimized to be terse, not overly functional.

Demo.

Some other notes:

To make sure that this is set correctly, don't do this:

onClick: this.graphClickEvent,

Instead, use either:

onClick: (...params) => this.graphClickEvent(...params),

Or:

onClick: this.graphClickEvent.bind(this),

I prefer the arrow function, but either way should make sure that your current context is set correctly.

You can get better performance if you don't wait to load ChartJS:

isChartLoaded = false;
isChartRendered = false;
async connectedCallback() {
  await loadScript(this, chartjs);
  this.isChartLoaded = true;
  // Just in case we get the data back before the script loads.
  this.renderedCallback(); 
}

renderedCallback() {
  // Missing data, or already rendered
  if(!this.chartConfig || !this.isChartLoaded || this.isChartRendered) {
    return;
  }
  const ctx = this.template.querySelector('canvas.barChart').getContext('2d');
  this.chart = new ChartAPI(ctx, clone(this.chartConfig));
  this.isChartRendered = true;
}

In the demo, I'm importing the script directly, something not possible in Salesforce, so there will be some minor differences to the code there.

One final note, the "scales" properties were messed up, so I took them out of the demo. I don't recall ever using ChartJS before, so you'll want to read the documentation on how they're supposed to work.

You must log in to answer this question.

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