javascriptreactjstypescriptchart.jsreact-chartjs

Text inside a chartjs doughnut has value but becomes undefined in runtime


I have a chartjs doughnut that I want to have text inside the ring/middle of it. I have created/registered a plugin for it.

If I hardcode the value it will work. If I pass a value from the doughnut chart to the plugin, I can see the plugin has that value, but becomes undefined during later stage at runtime. I added a conditional, but does not give any effect.

My plugin;

ChartJS.register({
  id: "doughnutInnerText",
  beforeDraw: (chart, args, options) => {
    const width = chart.width,
      height = chart.height,
      ctx = chart.ctx;
    ctx.restore();
    const fontSize = (height / 160).toFixed(2);
    ctx.font = fontSize + "em sans-serif";
    ctx.textBaseline = "top";
    const espnVal = chart.options.plugins.doughnutInnerText.myVal; //works if its hardcoded, otherwise will have a value but will become undefined later on

    if (espnVal) {
      const textX = Math.round((width - ctx.measureText(espnVal).width) / 2),
        textY = height / 2.2;
      ctx.fillText(espnVal, textX, textY);
    }
    ctx.save();
  },
});

Inside the chartJS chart itself, under plugin I have:

  doughnutInnerText: {
            espnVal: "My Text",
          },

if I would log it inside the plugin it would look something like this

console.log(chart.options.plugins.doughnutInnerText.myVal) //"My Text"
console.log(chart.options.plugins.doughnutInnerText.myVal) //"My Text"
console.log(chart.options.plugins.doughnutInnerText.myVal) //"My Text"
console.log(chart.options.plugins.doughnutInnerText.myVal) //"undefined"

My full chartjs code (yes, have tried to remove the destroy() without noticing any improvements


    const config = {
      type: "doughnut",
      data: data,
      options: {
        plugins: {
          doughnutInnerText: {
            espnVal: "My Text",
          },
          legend: {
            position: "top",
            labels: {
              font: {
                size: 66,
              },
            },
          },
        },
        animation: {
          onComplete: () => {
            const canvas = chart.canvas;
            const img = canvas.toDataURL("image/png", 1);
            this.modifyCardURL(this.state.adaptiveCard, img);
            //delete chart from DOM after we get the Base64 value
            chart.destroy();
          },
        },
      },
    };

    const ctx = document.createElement("canvas");
    ctx.style.display = "none";
    //since we are not rendering the chart, we have to manually add it to the DOM
    document.documentElement.appendChild(ctx);
    const chart = new Chart(ctx, config as any);
    chart.render();
  }

Solution

  • 2 thins, you configure your plugin to listen to the value myVal while you configure it in your options as espnVal so you need to read that. Also it is better practice to use the options object since it is the object of your plugin options so you dont need to drill through the entire chart path.

    With that you will get this:

    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.2.1"></script>
    <canvas id="myChart"></canvas>
    <style>
      #myChart {
        width: 400px;
        height: 400px;
      }
    </style>
    <script>
      Chart.register({
        id: "doughnutInnerText",
        beforeDraw: (chart, args, options) => {
          const width = chart.width,
            height = chart.height,
            ctx = chart.ctx;
          ctx.restore();
          const fontSize = (height / 160).toFixed(2);
          ctx.font = fontSize + "em sans-serif";
          ctx.textBaseline = "top";
          const espnVal = options.espnVal; //works if its hardcoded, otherwise will have a value but will become undefined later on
    
          if (espnVal) {
            const textX = Math.round((width - ctx.measureText(espnVal).width) / 2),
              textY = height / 2.2;
            ctx.fillText(espnVal, textX, textY);
          }
          ctx.save();
        },
      });
    
      const data = {
        labels: ["hello", "world", "!"],
        datasets: [{
          data: [2, 5, 6]
        }]
      };
    
      // Chart options
      const options = {
        plugins: {
          doughnutInnerText: {
            espnVal: "My Text",
          }
        }
      };
    
      // Create the chart
      var ctx = document.getElementById('myChart').getContext('2d');
      var myChart = new Chart(ctx, {
        type: 'doughnut',
        data: data,
        options: options
      });
    </script>