5

Using Chart.js at latest version (3.3.2).

<script src="https://cdn.jsdelivr.net/npm/chart.js@3.3.2/dist/chart.min.js"></script>

I'd like to draw a vertical line on chart when mouse is hovering on each x-axis. I have tried many of the answers on the Stackoverflow, but the problem couldn't be solved.

This is some of the errors I've got when trying out the solutions from others.

Chart.controllers.line.extend is not a function

And this is my code. I write it as a simple website HTML+JS. Can someone help me please?

PS. In fact, I would like to share the minimum code, but I'm afraid I couldn't solve it when I get back to the original one. So, I think I'd better show all the code I have done so far. Please don't mind me.

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .chart-container {
      position: relative;
      margin: auto;
      width: 90vw;
    }
  </style>
</head>

<body>
  <div class="chart-container">
    <canvas id="myChart"></canvas>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/chart.js@3.3.2/dist/chart.min.js"></script>

  <script>
    var ctx = document.getElementById('myChart');
    BuildChart();

    function BuildChart() {

      var myChart = new Chart(ctx, {
        data: {
          labels: ['1', '2', '2562-3', '4', '5', '2562-6', '7', '8', '2562-9', '10', '11', '2562-12',
            '1', '2', '2563-3', '4', '5', '2563-6', '7', '8', '2563-9', '10', '11', '2563-12',
            '1', '2', '2564-3', '4', '5', '2564-6', '7', '8', '2564-9', '10', '11', '2564-12'],
          datasets: [{
            type: 'line',
            label: 'Price(THB)',
            data: [4.20, 4.30, 4.10, 3.90, 3.30, 3.40, 3.30, 3, 2.8, 2.2, 2.2, 2.2,
              2.0, 1.7, 1.4, 2.2, 3, 3.1, 3.6, 4.3, 5.1, 5, 4.8, 4.9,
              6, 6.2, 6.3, 7, 9.5, 10.3, NaN, NaN, NaN, NaN, NaN, NaN],
            backgroundColor: 'rgba(71, 198, 241, 1)',
            borderColor: 'rgba(71, 198, 241, 1)',
            borderWidth: 2,
            tension: 0,
            pointRadius: 0,
          },
          {
            type: 'line',
            label: 'FV Price(THB)',
            data: [NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, 1.9,
              NaN, NaN, 2.47, NaN, NaN, 3.87, NaN, NaN, 4.75, NaN, NaN, 6.16,
              NaN, NaN, 10.07, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN],
            backgroundColor: 'rgb(108, 113, 114, 0.7)',
            borderColor: 'rgb(108, 113, 114, 0.7)',
            borderWidth: 2,
            borderDash: [10, 5],
            tension: 0,
            pointRadius: 0,
          },
          {
            label: 'Revenue(MillionTHB)',
            data: [NaN, NaN, 482.97, NaN, NaN, 546.19, NaN, NaN, 590.11, NaN, NaN, 611.61,
              NaN, NaN, 656.77, NaN, NaN, 1033.46, NaN, NaN, 916.43, NaN, NaN, 1399.30,
              NaN, NaN, 1287.47, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN],
            backgroundColor: 'rgb(255, 153, 51, 0.2)',
            borderColor: 'rgb(255, 153, 51, 0.2)',
            borderWidth: 1,
            borderDash: [10, 5],
            type: 'bar',
            yAxisID: 'y1',
          },
          {
            label: 'Profit(MillionTHB)',
            data: [NaN, NaN, 11.65, NaN, NaN, 9.43, NaN, NaN, 27.54, NaN, NaN, 13.36,
              NaN, NaN, 30.27, NaN, NaN, 55.26, NaN, NaN, 56.16, NaN, NaN, 59.40,
              NaN, NaN, 81.59, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN],
            backgroundColor: 'rgb(255, 153, 51, 0.5)',
            borderColor: 'rgb(255, 153, 51, 0.5)',
            borderWidth: 1,
            borderDash: [10, 5],
            type: 'bar',
            yAxisID: 'y1',
          },
          {
            type: 'line',
            label: 'Forecast FV Price(THB)',
            data: [NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN,
              NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN,
              NaN, NaN, 10.07, NaN, NaN, 10.18, NaN, NaN, 11.05, NaN, NaN, 11.81],
            backgroundColor: 'rgb(108, 113, 114, 0.3)',
            borderColor: 'rgb(108, 113, 114, 0.3)',
            borderWidth: 2,
            borderDash: [10, 5],
            tension: 0,
            pointRadius: 0,
          },
          {
            label: 'Forecast Revenue(MillionTHB)',
            data: [NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN,
              NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN,
              NaN, NaN, NaN, NaN, NaN, 1300, NaN, NaN, 1500, NaN, NaN, 2000],
            backgroundColor: 'rgb(108, 113, 114, 0.1)',
            borderColor: 'rgb(108, 113, 114, 0.1)',
            borderWidth: 1,
            borderDash: [10, 5],
            type: 'bar',
            yAxisID: 'y1',
          },
          {
            label: 'Forecast Profit(MillionTHB)',
            data: [NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN,
              NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN,
              NaN, NaN, NaN, NaN, NaN, 80, NaN, NaN, 80, NaN, NaN, 80],
            backgroundColor: 'rgb(108, 113, 114, 0.3)',
            borderColor: 'rgb(108, 113, 114, 0.3)',
            borderWidth: 1,
            borderDash: [10, 5],
            type: 'bar',
            yAxisID: 'y1',
          },
          ]
        },
        options: {
          scales: {
            y: {
              beginAtZero: true,
              title: {
                display: true,
                text: 'Price Per Share(THB)',
                //color: '#191',
                font: {
                  family: 'Kanit',
                  //size: 20,
                  //style: 'normal',
                  lineHeight: 1.2
                },
                padding: { top: 30, left: 0, right: 0, bottom: 0 }
              },
              position: 'right',
              //ticks: {
              //  // Include a dollar sign in the ticks
              //  callback: function (value, index, values) {
              //    return '$' + value;
              //  }
              //}
            },
            y1: {
              beginAtZero: true,
              title: {
                display: true,
                text: 'Revenue/Profit (Million THB)',
                //color: '#191',
                font: {
                  family: 'Kanit',
                  //size: 20,
                  //style: 'normal',
                  lineHeight: 1.2
                },
                padding: { top: 30, left: 0, right: 0, bottom: 0 }
              },
              type: 'linear',
              display: true,
              position: 'left',
              // grid line settings
              grid: {
                drawOnChartArea: false, // only want the grid lines for one axis to show up
              },
            },
            x: {
              grid: {
                drawOnChartArea: false, // only want the grid lines for one axis to show up
              },
            },
          },
          plugins: {
            title: {
              display: true,
              text: 'SET: WICE'
            }
          },
          interaction: {
            intersect: false,
            mode: 'index',
          },
          spanGaps: true,
        }
      });
    }

  </script>

</body>

</html>

Thanks you.

Warit Taveekarn
  • 339
  • 3
  • 7

1 Answers1

14

You can use the Plugin Core API that offers different hooks to perform custom code. The simplest way would be to define an inline plugin and draw the line directly on the canvas through the CanvasRenderingContext2D. I would use the afterDraw hook as follows:

plugins: [{
  afterDraw: chart => {
    if (chart.tooltip?._active?.length) {
      let x = chart.tooltip._active[0].element.x;
      let yAxis = chart.scales.y;
      let ctx = chart.ctx;
      ctx.save();
      ctx.beginPath();
      ctx.moveTo(x, yAxis.top);
      ctx.lineTo(x, yAxis.bottom);
      ctx.lineWidth = 1;
      ctx.strokeStyle = 'rgba(0, 0, 255, 0.4)';
      ctx.stroke();
      ctx.restore();
    }
  }
}]

Please take a look at your amended code and see hot it works.

<body>
  <div class="chart-container">
    <canvas id="myChart"></canvas>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/chart.js@3.3.2/dist/chart.min.js"></script>

  <script>  
    
    var ctx = document.getElementById('myChart');
    BuildChart();

    function BuildChart() {

      var myChart = new Chart(ctx, {
        plugins: [{
          afterDraw: chart => {
            if (chart.tooltip?._active?.length) {               
               let x = chart.tooltip._active[0].element.x;             
               let yAxis = chart.scales.y;
               let ctx = chart.ctx;
               ctx.save();
               ctx.beginPath();
               ctx.moveTo(x, yAxis.top);
               ctx.lineTo(x, yAxis.bottom);
               ctx.lineWidth = 1;
               ctx.strokeStyle = 'rgba(0, 0, 255, 0.4)';
               ctx.stroke();
               ctx.restore();
            }
          }
        }],
        data: {
          labels: ['1', '2', '2562-3', '4', '5', '2562-6', '7', '8', '2562-9', '10', '11', '2562-12',
            '1', '2', '2563-3', '4', '5', '2563-6', '7', '8', '2563-9', '10', '11', '2563-12',
            '1', '2', '2564-3', '4', '5', '2564-6', '7', '8', '2564-9', '10', '11', '2564-12'],
          datasets: [{
            type: 'line',
            label: 'Price(THB)',
            data: [4.20, 4.30, 4.10, 3.90, 3.30, 3.40, 3.30, 3, 2.8, 2.2, 2.2, 2.2,
              2.0, 1.7, 1.4, 2.2, 3, 3.1, 3.6, 4.3, 5.1, 5, 4.8, 4.9,
              6, 6.2, 6.3, 7, 9.5, 10.3, NaN, NaN, NaN, NaN, NaN, NaN],
            backgroundColor: 'rgba(71, 198, 241, 1)',
            borderColor: 'rgba(71, 198, 241, 1)',
            borderWidth: 2,
            tension: 0,
            pointRadius: 0,
          },
          {
            type: 'line',
            label: 'FV Price(THB)',
            data: [NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, 1.9,
              NaN, NaN, 2.47, NaN, NaN, 3.87, NaN, NaN, 4.75, NaN, NaN, 6.16,
              NaN, NaN, 10.07, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN],
            backgroundColor: 'rgb(108, 113, 114, 0.7)',
            borderColor: 'rgb(108, 113, 114, 0.7)',
            borderWidth: 2,
            borderDash: [10, 5],
            tension: 0,
            pointRadius: 0,
          },
          {
            label: 'Revenue(MillionTHB)',
            data: [NaN, NaN, 482.97, NaN, NaN, 546.19, NaN, NaN, 590.11, NaN, NaN, 611.61,
              NaN, NaN, 656.77, NaN, NaN, 1033.46, NaN, NaN, 916.43, NaN, NaN, 1399.30,
              NaN, NaN, 1287.47, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN],
            backgroundColor: 'rgb(255, 153, 51, 0.2)',
            borderColor: 'rgb(255, 153, 51, 0.2)',
            borderWidth: 1,
            borderDash: [10, 5],
            type: 'bar',
            yAxisID: 'y1',
          },
          {
            label: 'Profit(MillionTHB)',
            data: [NaN, NaN, 11.65, NaN, NaN, 9.43, NaN, NaN, 27.54, NaN, NaN, 13.36,
              NaN, NaN, 30.27, NaN, NaN, 55.26, NaN, NaN, 56.16, NaN, NaN, 59.40,
              NaN, NaN, 81.59, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN],
            backgroundColor: 'rgb(255, 153, 51, 0.5)',
            borderColor: 'rgb(255, 153, 51, 0.5)',
            borderWidth: 1,
            borderDash: [10, 5],
            type: 'bar',
            yAxisID: 'y1',
          },
          {
            type: 'line',
            label: 'Forecast FV Price(THB)',
            data: [NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN,
              NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN,
              NaN, NaN, 10.07, NaN, NaN, 10.18, NaN, NaN, 11.05, NaN, NaN, 11.81],
            backgroundColor: 'rgb(108, 113, 114, 0.3)',
            borderColor: 'rgb(108, 113, 114, 0.3)',
            borderWidth: 2,
            borderDash: [10, 5],
            tension: 0,
            pointRadius: 0,
          },
          {
            label: 'Forecast Revenue(MillionTHB)',
            data: [NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN,
              NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN,
              NaN, NaN, NaN, NaN, NaN, 1300, NaN, NaN, 1500, NaN, NaN, 2000],
            backgroundColor: 'rgb(108, 113, 114, 0.1)',
            borderColor: 'rgb(108, 113, 114, 0.1)',
            borderWidth: 1,
            borderDash: [10, 5],
            type: 'bar',
            yAxisID: 'y1',
          },
          {
            label: 'Forecast Profit(MillionTHB)',
            data: [NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN,
              NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN,
              NaN, NaN, NaN, NaN, NaN, 80, NaN, NaN, 80, NaN, NaN, 80],
            backgroundColor: 'rgb(108, 113, 114, 0.3)',
            borderColor: 'rgb(108, 113, 114, 0.3)',
            borderWidth: 1,
            borderDash: [10, 5],
            type: 'bar',
            yAxisID: 'y1',
          },
          ]
        },
        options: {
          scales: {
            y: {
              beginAtZero: true,
              title: {
                display: true,
                text: 'Price Per Share(THB)',
                //color: '#191',
                font: {
                  family: 'Kanit',
                  //size: 20,
                  //style: 'normal',
                  lineHeight: 1.2
                },
                padding: { top: 30, left: 0, right: 0, bottom: 0 }
              },
              position: 'right',
              //ticks: {
              //  // Include a dollar sign in the ticks
              //  callback: function (value, index, values) {
              //    return '$' + value;
              //  }
              //}
            },
            y1: {
              beginAtZero: true,
              title: {
                display: true,
                text: 'Revenue/Profit (Million THB)',
                //color: '#191',
                font: {
                  family: 'Kanit',
                  //size: 20,
                  //style: 'normal',
                  lineHeight: 1.2
                },
                padding: { top: 30, left: 0, right: 0, bottom: 0 }
              },
              type: 'linear',
              display: true,
              position: 'left',
              // grid line settings
              grid: {
                drawOnChartArea: false, // only want the grid lines for one axis to show up
              },
            },
            x: {
              grid: {
                drawOnChartArea: false, // only want the grid lines for one axis to show up
              },
            },
          },
          plugins: {
            title: {
              display: true,
              text: 'SET: WICE'
            }
          },
          interaction: {
            intersect: false,
            mode: 'index',
          },
          spanGaps: true,
        }
      });
    }

  </script>

</body>

</html>
uminder
  • 23,831
  • 5
  • 37
  • 72