[BUG] Weird behavior with labels
Hello,
I experienced a really weird behavior with my labels and don't know why. Im using this package in combination with chartjs-node-canvas.
Description
The provided example code has three different for loop heads which work/don't work.
// run only for top=60 => perfect result
for (let top = 60; top <= 60; top += 5) {
// run a few values for top more => only a few bad
for (let top = 40; top < 70; top += 5) {
// run from 0 to 100 in 5er steps => everything bad
for (let top = 0; top < 100; top += 5) {
The weird part is, that it works if the for loop only runs one iteration. If it runs only a few iterations, it's sometimes correct and if its runs 20 iterations nothing is correct.
Screenshots
Correct:

Wrong:

Look at the placement of the labels. In the correct one, the labels are placed ontop of the bars, in the wrong one they are wrapped somehow.
Steps to reproduce
- Create a folder and add the example code provided below
- Create a folder next to the
index.tsfile calleda. - Run the file with
npx ts-node index.tsand try all 3 for heads. (Remember to remove all files ina/first to have no conflicts) - Compare the results
Example code
package.json:
{
"name": "typescript-node",
"version": "1.0.0",
"scripts": {},
"dependencies": {
"@types/node": "14.14.29",
"ts-node": "9.1.1",
"typescript": "4.1.5",
"chart.js": "^3.9.1",
"chartjs-node-canvas": "^4.1.6",
"chartjs-plugin-datalabels": "^2.2.0"
}
}
import { ChartJSNodeCanvas } from "chartjs-node-canvas";
import { ChartConfiguration } from "chart.js";
import "chartjs-plugin-datalabels"; // to fix types
import { writeFileSync } from "fs";
const abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const srcData = new Array(100)
.fill(0)
.map((_, i) => ({ x: abc[i % (abc.length - 1)], y: i }));
(async () => {
// run only for top=60 => perfect result
// for (let top = 60; top <= 60; top += 5) {
// run a few values for top more => only a few bad
for (let top = 40; top < 70; top += 5) {
// run from 0 to 100 in 5er steps => everything bad
// for (let top = 0; top < 100; top += 5) {
const sliced = srcData.slice(0, top);
// import via require as it doesnt work otherwise
// eslint-disable-next-line @typescript-eslint/no-var-requires
const ChartDataLabels = require("chartjs-plugin-datalabels");
const scale = 2;
const chartRenderer = new ChartJSNodeCanvas({
width: scale * Math.max(400, 40 * sliced.length),
height: scale * 400,
chartCallback: async (ChartJS) => {
ChartJS.register(ChartDataLabels);
},
});
const chart: ChartConfiguration = {
type: "bar",
data: {
labels: sliced.map((x) => x.x),
datasets: [
{
label: "ABC",
data: sliced.map((x) => x.y * 2),
backgroundColor: "#f43543",
datalabels: {
labels: {
relative: {
align: "end",
anchor: "end",
color: "#ffffff",
offset: scale * 12,
font: {
size: scale * 10,
weight: "bold",
},
formatter: (_value, ctx) =>
`${~~(sliced[ctx.dataIndex].y * 10 ** 3) / 10}%`,
},
},
},
},
],
},
};
const pictureBuffer = await chartRenderer.renderToBuffer(chart);
writeFileSync(`./a/chart${top}.png`, pictureBuffer);
}
})();
This must be related to this library, because doing the require in the for loop with some tweeks to fresh require it, everything is working fine.
// https://github.com/hughsk/fresh-require
export const freshRequire: /*NodeJS.Require*/ (id: string) => any = (file) => {
const resolvedFile = require.resolve(file);
const temp = require.cache[resolvedFile];
delete require.cache[resolvedFile];
// eslint-disable-next-line @typescript-eslint/no-var-requires
const modified = require(resolvedFile);
require.cache[resolvedFile] = temp;
return modified;
};
// run only for top=60 => perfect result
//for (let top = 45; top <= 45; top += 5) {
// run a few values for top more => only a few bad
for (let top = 40; top < 70; top += 5) {
// run from 0 to 100 in 5er steps => everything bad
// for (let top = 0; top < 100; top += 5) {
// import via require as it doesnt work otherwise
// eslint-disable-next-line @typescript-eslint/no-var-requires
const ChartDataLabels = freshRequire('chartjs-plugin-datalabels');
[...]