chartjs/chartjs-plugin-datalabels

[BUG] Weird behavior with labels

wolflu05 opened this issue · 1 comments

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:
working

Wrong:
not-working

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

  1. Create a folder and add the example code provided below
  2. Create a folder next to the index.ts file called a.
  3. Run the file with npx ts-node index.ts and try all 3 for heads. (Remember to remove all files in a/ first to have no conflicts)
  4. 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');

    [...]