I'm trying to integrate the Chart.js datalabels plugin into a Lightning Web Component (LWC) for creating customizable bar charts in my Salesforce application. I have followed the provided code example, but I'm encountering some issues with getting the datalabels to display properly. Here's a summary of what I've done:
- Imported the Chart.js and Chart.js datalabels plugin as static resources in my Salesforce org.
- Initialized Chart.js and the datalabels plugin using
loadScript
in therenderedCallback
of my LWC. - Added the datalabels plugin to the list of plugins when initializing the chart:
plugins: [ChartDataLabels]
. - Customized datalabels appearance and behavior inside the
options
object underplugins.datalabels
.
Despite following these steps, the datalabels are not displaying as expected. Can anyone provide guidance on how to properly integrate the Chart.js datalabels plugin into an LWC for creating bar charts with labeled data points? Any insights, code examples, or alternative approaches would be greatly appreciated. Thank you!
import { LightningElement, api } from 'lwc';
import chartjs from '@salesforce/resourceUrl/Chartjs_last';
import { loadScript } from 'lightning/platformResourceLoader';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import ChartDataLabels from '@salesforce/resourceUrl/chartjs_datalabels';
export default class Gen_barchart extends LightningElement {
@api chartData;
@api type
@api maintainAspectRatio = false
isChartJsInitialized;
renderedCallback() {
if (this.isChartJsInitialized) {
return;
}
if (this.chartData) {
// load chartjs from the static resource
Promise.all([loadScript(this, chartjs),
loadScript(this, ChartDataLabels)])
.then(() => {
console.log('this.ChartDataLabels', ChartDataLabels)
let datasets
let chartLabel = []
let max
let tempMax
console.log('this.type', this.type)
if (this.type === 'number') {
let chartGoals = [];
let chartSuccess = [];
this.chartData.forEach(record => {
let goalNum = record.goal == null ? 0 : Math.round(Number(record.goal))
chartGoals.push(goalNum);
let successNum = record.success == null ? 0 : Math.round(Number(record.success))
chartSuccess.push(successNum);
chartLabel.push(record.missionDetail);
});
if (chartGoals.length > 0 && chartSuccess.length > 0) {
let goalMax = Math.max(...chartGoals)
let successMax = Math.max(...chartSuccess)
tempMax = Math.max(goalMax, successMax)
}
datasets = [{
label: 'Goal Count',
backgroundColor: "#79D1CF",
data: chartGoals,
},
{
label: 'Success Count',
backgroundColor: "#D1CF79",
data: chartSuccess,
},
]
}
max = tempMax == null ? 100 : Math.ceil(tempMax + tempMax * 0.3)
max = max === 0 ? 100 : max
this.isChartJsInitialized = true;
const ctx = this.template.querySelector('canvas.barChart').getContext('2d');
var moveChart = {
id: 'moveChart',
afterEvent: function (chartInstance, args) {
const ctx = chartInstance.ctx;
const canvas = chartInstance.canvas;
const chartArea = chartInstance.chartArea;
const left = chartArea.left,
right = chartArea.right,
top = chartArea.top,
bottom = chartArea.bottom,
width = right - left,
height = bottom - top;
canvas.addEventListener('mousemove', function (e) {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
if (x >= left - 15 && x <= left + 15 && y >= top && y <= bottom) {
canvas.style.cursor = 'pointer';
} else if (x >= right - 15 && x <= right + 15 && y >= top && y <= bottom) {
canvas.style.cursor = 'pointer';
} else {
canvas.style.cursor = 'default';
}
});
},
afterDraw: function (chartInstance) {
const ctx = chartInstance.ctx
const chartArea = chartInstance.chartArea
const left = chartArea.left,
right = chartArea.right,
top = chartArea.top,
bottom = chartArea.bottom,
width = right - left,
height = bottom - top;
class CircleChevron {
draw(ctx, x1, pixel) {
const angle = Math.PI / 180;
ctx.beginPath();
ctx.lineWidth = 3;
ctx.strokeStyle = '#79D1CF';
ctx.fillStyle = 'white';
ctx.arc(x1, height / 2 + top, 15, angle * 0, angle * 360, false);
ctx.stroke();
ctx.fill();
ctx.closePath();
//chevron Arrow Left
ctx.beginPath();
ctx.lineWidth = 3;
ctx.strokeStyle = '#79D1CF';
ctx.fillStyle = 'white';
ctx.moveTo(x1 + pixel, height / 2 + top - 7.5);
ctx.lineTo(x1 - pixel, height / 2 + top);
ctx.lineTo(x1 + pixel, height / 2 + top + 7.5);
ctx.stroke();
ctx.fill();
ctx.closePath();
}
}
const drawCircleLeft = new CircleChevron();
drawCircleLeft.draw(ctx, left, 5);
const drawCircleRight = new CircleChevron();
drawCircleRight.draw(ctx, right, -5);
}
}
this.chart = new window.Chart(ctx, {
type: "bar",
plugins: [ChartDataLabels, moveChart],
options: {
layout: {
padding: {
right: 18,
}
},
// onComplete: chart => {
// const ctx = chart.ctx;
// console.log(ctx, 'ctx')
// chart.data.datasets.forEach((dataset, i) => {
// const meta = chart.getDatasetMeta(i);
// meta.data.forEach((bar, index) => {
// const data = dataset.data[index];
// console.log('data', data)
// ctx.fillStyle = 'black';
// ctx.textAlign = 'center';
// ctx.textBaseline = 'bottom';
// ctx.fillText(data, bar.x, bar.y - 5);
// });
// });
// },
responsive: true,
maintainAspectRatio: this.maintainAspectRatio,
plugins: {
legend: {
position: "bottom",
},
datalabels: {
color: "orange",
labels: {
title: {
font: {
weight: "bold"
}
}
}
},
title: {
display: true,
},
},
tooltips: {
enabled: true
},
scales: {
x: {
min: 0,
max: 3,
grid: {
display: false,
},
ticks: {
display: true,
},
},
y: {
beginAtZero: true,
}
},
},
data: {
datasets: datasets,
labels: chartLabel,
},
});
function moveScroll(chartInstance) {
console.log('ok')
const ctx = chartInstance.ctx
const chartArea = chartInstance.chartArea
const canvas = chartInstance.canvas
const left = chartArea.left,
right = chartArea.right,
top = chartArea.top,
bottom = chartArea.bottom,
width = right - left,
height = bottom - top;
canvas.addEventListener('click', function (event) {
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
if (x >= left - 20 && x <= left + 20 && y >= height / 2 + top - 20 && y <= height / 2 + top + 20) {
chartInstance.options.scales.x.min = chartInstance.options.scales.x.min - 4 //max size +1
chartInstance.options.scales.x.max = chartInstance.options.scales.x.max - 4 //max size +1
if (chartInstance.options.scales.x.min <= 0) {
chartInstance.options.scales.x.min = 0
chartInstance.options.scales.x.max = 3 //max size
}
}
if (x >= right - 20 && x <= right + 20 && y >= height / 2 + top - 20 && y <= height / 2 + top + 20) {
chartInstance.options.scales.x.min = chartInstance.options.scales.x.min + 4 //max size +1
chartInstance.options.scales.x.max = chartInstance.options.scales.x.max + 4 //max size +1
if (chartInstance.options.scales.x.max >= chartInstance.data.labels.length) {
chartInstance.options.scales.x.min = chartInstance.data.labels.length - 4 //max size +1
chartInstance.options.scales.x.max = chartInstance.data.labels.length
}
}
chartInstance.update()
})
}
this.chart.ctx.onclick = moveScroll(this.chart)
})
.catch(error => {
console.log(error)
if (!error.message.includes('Canvas is already in use')) {
this.dispatchEvent(
new ShowToastEvent({
title: 'Error loading Chart',
message: error.message,
variant: 'error',
})
);
}
});
}
}
}