0

I have a chart showing a trend. I need to add custom vertical lines to represent some events as they happened during the reported period. This may help me see the connection between those events and changes (if any) in the trend.

Example: please see the message bubbles and scattered dashed lines in the centre (click for higher resolution).

Screenshot

Checked the latest docs and samples but couldn't find anything suitable.

Few ideas from the top of my head:

  • Place very narrow bar charts, separately from the main data, on the same canvas
  • Use Javascript to calculate canvas size and manually place custom vertical lines over it

Both sound like workarounds, and I was wondering if there's a better way. Thank you.

ᴍᴇʜᴏᴠ
  • 4,804
  • 4
  • 44
  • 57

1 Answers1

1

You can draw you own lines directly on to the canvas using the Plugin Core API. It offers different hooks that may be used for executing custom code. In below code snippet, I use the afterDraw hook to draw dashed lines for points from the dataset that represent an event each.

The message bubbles can be drawn on the canvas using CanvasRenderingContext2D.drawImage().

const labels = ['2020-02-06', '2020-02-07', '2020-02-08', '2020-02-09', '2020-02-10', '2020-02-11', '2020-02-12'];
const events = ['2020-02-08', '2020-02-10'];
const values = [75, 56, 33, 44, 71, 62, 24];

var icon = new Image(20, 20);
icon.src = 'https://i.stack.imgur.com/EXAgg.png';

new Chart(document.getElementById("myChart"), {
  type: "line",
  plugins: [{
    afterDraw: chart => {      
      var ctx = chart.chart.ctx; 
      var xAxis = chart.scales['x-axis-0'];
      var yAxis = chart.scales['y-axis-0'];
      xAxis.ticks.forEach((value, index) => {         
         if (events.includes(labels[index])) {
           var x = xAxis.getPixelForTick(index);           
           ctx.save();
           ctx.setLineDash([10, 5]);
           ctx.strokeStyle = '#888888';
           ctx.beginPath();
           ctx.moveTo(x, yAxis.bottom);             
           ctx.lineTo(x, yAxis.top);
           ctx.stroke();
           ctx.restore();
           ctx.drawImage(icon, x - 12, yAxis.bottom - 15);
         }
      });      
    }
  }],
  data: {
    labels: labels,
    datasets: [{
      label: 'My Dataset',
      data: values,
      fill: false,
      backgroundColor: 'green',
      borderColor: 'green'
    }]
  },
  options: {
    responsive: true,
    title: {
      display: false
    },
    legend: {
      display: false
    },
    scales: {
      yAxes: [{        
        ticks: {
          beginAtZero: true,
          stepSize: 20
        }
      }],
      xAxes: [{
        type: 'time',
        time: {
          unit: 'day',
          tooltipFormat: 'MMM DD'
        },
        gridLines: {
          display: false
        }        
      }],
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.min.js"></script>
<canvas id="myChart" height="90"></canvas>
uminder
  • 23,831
  • 5
  • 37
  • 72
  • Thank you so much! Few (not obvious) comments for those finding this: **1.** [`afterDraw`](https://www.chartjs.org/docs/latest/developers/plugins.html) keeps firing as you move your mouse and the chart re-renders tooltips. **2.** Tooltip style affects all text on the main canvas, so instead put your text on [a separate canvas](https://stackoverflow.com/q/10652513) and [`drawImage()`](https://stackoverflow.com/a/53164807) it back onto the main canvas. **3.** Canvas texts will be blurry on Retina screens, unless you [account for `devicePixelRatio`](https://stackoverflow.com/q/15661339) – ᴍᴇʜᴏᴠ Oct 28 '20 at 07:30