How to create a simple Gauge with ChartJS v3?

Viewed 6649

I was using ChartJS v2 on a former project to create gauges looking like this:

enter image description here

During a React integration, I need to do the same thing but the freshly installed v3 version of this library, starting by the Doughnut modification itself.

Here is the current configuration:

export const GaugeChart: VFC<GaugeChartProps> = ({
  value,
  max = 100,
})  => {
  const percent = value / max;

  return <ChartJS
    type="doughnut"
    data={{
      datasets: [
        {
          backgroundColor: [
            'rgb(255, 99, 132)',
            '#ccc',
          ],
          data: [
            percent * 100,
            100 - (percent * 100),
          ],
        },
      ],
    }}
    options={{
      rotation: -1.0 * Math.PI, // start angle in radians
      circumference: Math.PI, // sweep angle in radians
    }}
  />
};

Note: type, data and options props are part of the ChartJS configuration itself, the component is just a wrapper.

Here is what I get:

enter image description here

What I am missing?

2 Answers

As per the migration guide in v3 the rotation and circumference options are in degrees instead of in radians, if you place them as degrees it works just fine:

var options = {
  type: 'doughnut',
  data: {
    labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
    datasets: [{
      label: '# of Votes',
      data: [12, 19, 3, 5, 2, 3],
      backgroundColor: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"]
    }]
  },
  options: {
    rotation: 270, // start angle in degrees
    circumference: 180, // sweep angle in degrees
  }
}

var ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
  <canvas id="chartJSContainer" width="600" height="400"></canvas>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.1/chart.js"></script>
</body>

Here is an example that I use and it works fine with version 3.5.1

HTML

<div class="chartBox">
  <canvas id="gauge"></canvas>
</div>

CSS

.chartBox {
  width: 400px;
  padding: 20px;
  border-radius: 20px;
}

JavaScript

// setup
const data = {
  labels: ["Safe", "Risky", "High Risk"],
  datasets: [
    {
      label: "Gauge",
      data: [100, 65, 35],
      backgroundColor: [
        "rgba(75, 192, 192, 0.8)",
        "rgba(255, 206, 86, 0.8)",
        "rgba(255, 26, 104, 0.8)",
      ],
      needleValue: 50,
      borderColor: "white",
      borderWidth: 2,
      cutout: "95%",
      circumference: 180,
      rotation: 270,
      borderRadius: 5,
    },
  ],
};

//gaugeNeedle
const gaugeNeedle = {
  id: "gaugeNeedle",
  afterDatasetDraw(chart, args, options) {
    const {
      ctx,
      config,
      data,
      chartArea: { top, right, bottom, left, width, height },
    } = chart;
    ctx.save();
    const needleValue = data.datasets[0].needleValue;
    const dataTotal = data.datasets[0].data.reduce((a, b) => a + b, 0);

    if (needleValue <= 100) {
      var angle = Math.PI + (1 / 200) * needleValue * Math.PI;
      console.log(angle);
    } else if (needleValue <= 10000) {
      var angle =
        Math.PI +
        (1 / 200) * 100 * Math.PI +
        ((1 / 200) * needleValue * Math.PI * 65) / 10000;
    } else if (needleValue <= 1000000) {
      var angle =
        Math.PI +
        (1 / 200) * 100 * Math.PI +
        ((1 / 200) * 10000 * Math.PI * 65) / 10000 +
        ((1 / 200) * needleValue * Math.PI * 35) / 1000000;
    } else {
      var angle = 0;
    }

    const cx = width / 2;
    const cy = chart._metasets[0].data[0].y;

    //needle
    ctx.translate(cx, cy);
    ctx.rotate(angle);
    ctx.beginPath();
    ctx.moveTo(0, -2);
    ctx.lineTo(height - ctx.canvas.offsetTop - 160, 0); // change 160 value if the needle size gets changed
    ctx.lineTo(0, 2);
    ctx.fillStyle = "#444";
    ctx.fill();
    //needle dot
    ctx.translate(-cx, -cy);
    ctx.beginPath();
    ctx.arc(cx, cy, 5, 0, 10);
    ctx.fill();
    ctx.restore();

    //text
    ctx.font = "20px Ubuntu";
    ctx.fillStyle = "#444";
    ctx.fillText(needleValue + " CPM", cx, cy + 50);
    ctx.font = "10px Ubuntu";
    ctx.fillText(0, 5, cy + 20);
    ctx.fillText(100, cx, 90);
    ctx.fillText("10k", cx + 185, 200); // change values if the position gets changed
    ctx.fillText("1M", cx + 193, 320); // change values if the position gets changed
    ctx.textAlign = "center";
    ctx.restore();
  },
};
// config
const config = {
  type: "doughnut",
  data,
  options: {
    plugins: {
      legend: {
        display: false,
      },
      tooltip: {
        yAlign: "bottom",
        displayColors: false,
        callbacks: {
          label: function (tooltipItem, data, value) {
            return tooltipItem.label;
          },
        },
      },
    },
  },
  plugins: [gaugeNeedle],
};

// render init block
const myChart = new Chart(document.getElementById("gauge"), config);

Note: This works fine when the width is set 400px. If you change this value other parameters need to be changed accordingly. However, The affected locations are commented in the JavaScript.

Related