[Chartjs]-Chart.js — drawing an arbitrary vertical line

97👍

Update – this answer is for Chart.js 1.x, if you are looking for a 2.x answer check the comments and other answers.

You extend the line chart and include logic for drawing the line in the draw function.


Preview

enter image description here


HTML

<div>
    <canvas id="LineWithLine" width="600" height="400"></canvas>
</div>

Script

var data = {
    labels: ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"],
    datasets: [{
        data: [12, 3, 2, 1, 8, 8, 2, 2, 3, 5, 7, 1]
    }]
};

var ctx = document.getElementById("LineWithLine").getContext("2d");

Chart.types.Line.extend({
    name: "LineWithLine",
    draw: function () {
        Chart.types.Line.prototype.draw.apply(this, arguments);

        var point = this.datasets[0].points[this.options.lineAtIndex]
        var scale = this.scale

        // draw line
        this.chart.ctx.beginPath();
        this.chart.ctx.moveTo(point.x, scale.startPoint + 24);
        this.chart.ctx.strokeStyle = '#ff0000';
        this.chart.ctx.lineTo(point.x, scale.endPoint);
        this.chart.ctx.stroke();

        // write TODAY
        this.chart.ctx.textAlign = 'center';
        this.chart.ctx.fillText("TODAY", point.x, scale.startPoint + 12);
    }
});

new Chart(ctx).LineWithLine(data, {
    datasetFill : false,
    lineAtIndex: 2
});

The option property lineAtIndex controls which point to draw the line at.

Fiddle – http://jsfiddle.net/dbyze2ga/14/

73👍

Sharing my solution for chartjs.org version 2.5. I wanted to use a plugin, to make the implementation reusable.

const verticalLinePlugin = {
  getLinePosition: function (chart, pointIndex) {
      const meta = chart.getDatasetMeta(0); // first dataset is used to discover X coordinate of a point
      const data = meta.data;
      return data[pointIndex]._model.x;
  },
  renderVerticalLine: function (chartInstance, pointIndex) {
      const lineLeftOffset = this.getLinePosition(chartInstance, pointIndex);
      const scale = chartInstance.scales['y-axis-0'];
      const context = chartInstance.chart.ctx;

      // render vertical line
      context.beginPath();
      context.strokeStyle = '#ff0000';
      context.moveTo(lineLeftOffset, scale.top);
      context.lineTo(lineLeftOffset, scale.bottom);
      context.stroke();

      // write label
      context.fillStyle = "#ff0000";
      context.textAlign = 'center';
      context.fillText('MY TEXT', lineLeftOffset, (scale.bottom - scale.top) / 2 + scale.top);
  },

  afterDatasetsDraw: function (chart, easing) {
      if (chart.config.lineAtIndex) {
          chart.config.lineAtIndex.forEach(pointIndex => this.renderVerticalLine(chart, pointIndex));
      }
  }
  };

  Chart.plugins.register(verticalLinePlugin);

Usage is simple then:

 new Chart(ctx, {
     type: 'line',
     data: data,
     label: 'Progress',
     options: options,
     lineAtIndex: [2,4,8],
 })

The code above inserts red vertical lines at positions 2,4 and 8, running through points of first dataset at those positions.

8👍

I’d highly recommend to use the Chartjs-Plugin-Annotation.

An example can be found at CodePen

var chartData = {
  labels: ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"],
  datasets: [
    {
      data: [12, 3, 2, 1, 8, 8, 2, 2, 3, 5, 7, 1]
    }
  ]
};

window.onload = function() {
  var ctx = document.getElementById("canvas").getContext("2d");
  new Chart(ctx, {
    type: "line",
    data: chartData,
    options: {
      annotation: {
        annotations: [
          {
            type: "line",
            mode: "vertical",
            scaleID: "x-axis-0",
            value: "MAR",
            borderColor: "red",
            label: {
              content: "TODAY",
              enabled: true,
              position: "top"
            }
          }
        ]
      }
    }
  });
};

Have a look here for more Details: https://stackoverflow.com/a/36431041

7👍

I had to go through the trouble of figuring out how to do something similar with ChartJS 2.0 so I thought I would share.

This is based on the new way of overriding a chart prototype as explained here: https://github.com/chartjs/Chart.js/issues/2321

var ctx = document.getElementById('income-chart');

var originalDraw = Chart.controllers.line.prototype.draw;
Chart.controllers.line.prototype.draw = function (ease) {
    originalDraw.call(this, ease);

    var point = dataValues[vm.incomeCentile];
    var scale = this.chart.scales['x-axis-0'];

    // calculate the portion of the axis and multiply by total axis width
    var left = (point.x / scale.end * (scale.right - scale.left));
                
    // draw line
    this.chart.chart.ctx.beginPath();
    this.chart.chart.ctx.strokeStyle = '#ff0000';
    this.chart.chart.ctx.moveTo(scale.left + left, 0);
    this.chart.chart.ctx.lineTo(scale.left + left, 1000000);
    this.chart.chart.ctx.stroke();

    // write label
    this.chart.chart.ctx.textAlign = 'center';
    this.chart.chart.ctx.fillText('YOU', scale.left + left, 200);
};

6👍

With chart.js 3.8.0 I’ve used a combo between line/bar chart with timeline (xAxis) and percentage (yAxis). See docs

The dataset configuration provides a option to set the maxBarThickness (I’ve applied 2) and then apply the max value of the y-axis on each data entry of the bar chart.

Example of dataset configuration:

datasets: [
  {
    type: 'line'
    data: [
      {x: "2022-07-18", y: 10},
      {x: "2022-07-19", y: 60},
      {x: "2022-07-20", y: 30}
    ],
    ....
  },
  {
    type: 'bar',
    data: [
      {x: "2022-07-19", y: 100}
    ],
    maxBarThickness: 2,
    ...
  }
]

Example of the output:

enter image description here

5👍

Here’s a pen that achieves a similar effect without the chartjs-plugin-annotation, or hacking how Chart.js renders, or any other plugins: https://codepen.io/gkemmey/pen/qBWZbYM

Approach

  1. Use a combo bar / line chart, and use the bar chart to draw the vertical lines.
  2. Use two y-axes: one for the bar chart (which we don’t display), and one for all your other line chart datasets.
  3. Force the bar chart y-axes to min: 0 and max: 1. Anytime you want to draw a vertical line, add a data object like { x: where_the_line_goes, y: 1 } to your bar chart dataset.
  4. The pen also adds some custom data to the bar chart dataset and a legend filter and label callback to exclude the bar chart dataset from the legend, and control the label on the vertical line.

Pros

  1. No other dependencies. No custom monkey patching / extending.
  2. The annotations plugin doesn’t seem to be actively maintained. For instance, atm, their event handlers throw an error about “preventing default on passive events”
  3. Maybe a pro: The annotations plugin always shows the labels of lines drawn, and you have to use their event callbacks to get a show-on-hover effect. Chart.js tooltips show on hover by default.

Cons

  1. We’re adding custom data in the dataset config, and hoping it doesn’t conflict with anything Chart.js is doing. It’s data Chart.js doesn’t expect to be there, but as of 2.8, also doesn’t break it.

1👍

Typescript version of the vertical line plugin by Tomáš Dvořák for chart.js 2.9.4 and above:

import Chart from 'chart.js'
import {ChartOptions} from 'chart.js'

export interface ChartVerticalLine {
  pointIndex: number
  label: string
  color: string
  nPoints: number
}

export const verticalLinePlugin = {
  id: 'verticalLines',
  getLinePosition: function(chart: Chart, pointIndex: number) {
    const meta = chart.getDatasetMeta(0) // first dataset is used to discover X coordinate of a point
    const data = meta.data
    const returnVal = data[pointIndex]._model.x
    return returnVal
  },
  renderVerticalLine: function(chartInstance: Chart, pointIndex: number, text: string, color: string, nPoints: number) {
    const lineLeftOffset = this.getLinePosition(chartInstance, pointIndex)
    const area = chartInstance.chartArea
    const context = chartInstance.ctx
    // render vertical line
    if (context !== null) {
      context.beginPath()
      context.strokeStyle = color || '#ff0000'
      context.moveTo(lineLeftOffset, area ? .top)
      context.lineTo(lineLeftOffset, area ? .bottom)
      context.stroke()
      // write label
      context.fillStyle = color || '#ff0000'
      const leftQuartile = 0.25 * nPoints
      const rightQuartile = 0.75 * nPoints
      context.textAlign = pointIndex < leftQuartile ? 'left' : pointIndex > rightQuartile ? 'right' : 'center'
      const offsetText = pointIndex < leftQuartile ? `    ${text}` : pointIndex > rightQuartile ? `${text}    ` : `  ${text}  `
      context.fillText(offsetText || '', lineLeftOffset, area.top + 6)
    }
  },
  afterDatasetsDraw: function(chart: Chart, args: any, options: any) {
    if (options.lines) {
      options.lines.forEach((line: ChartVerticalLine) =>
        this.renderVerticalLine(chart, line.pointIndex, line.label, line.color, line.nPoints)
      )
    }
  },
}

Then register the plugin:

  Chart.plugins.register(verticalLinePlugin)

To use the plugin, just include it as one of the plugin attributes set in the chart options:

    const options: ChartOptions = {
      ...otherChartOptions, 
      plugins: {
        verticalLines: {
          lines: [
            {
              pointIndex: 100,
              label: 'First Line',
              color: '#ff0000',
              nPoints: 1000
            },
            {
              pointIndex: 200,
              label: 'Second Line',
              color: '#ff0000',
              nPoints: 1000
            },
            {
              pointIndex: 300,
              label: 'Third Line',
              color: '#ff0000',
              nPoints: 1000
            },
          ],
        },
      }
    }

0👍

Enhanced version of @Tomáš Dvořák answer

enter image description here

Supports:

  • custom text for labels
  • custom color for line+label
  • custom alignment for labels
  • custom X/Y offset for labels
    const verticalLinePlugin = {
      getLinePosition: function (chart, pointIndex) {
          const meta = chart.getDatasetMeta(0); // first dataset is used to discover X coordinate of a point
          const data = meta.data;
          return data[pointIndex]._model.x;
      },
      renderVerticalLine: function (chartInstance, pointIndex, label, color, alignment, xOffset, yOffset) {
          const lineLeftOffset = this.getLinePosition(chartInstance, pointIndex);
          const scale = chartInstance.scales['y-axis-0'];
          const context = chartInstance.chart.ctx;
          if (xOffset == undefined) xOffset = 0;
          if (yOffset == undefined) yOffset = 0;

          // render vertical line
          context.beginPath();
          context.strokeStyle = color;
          context.moveTo(lineLeftOffset, scale.top);
          context.lineTo(lineLeftOffset, scale.bottom);
          context.stroke();

          // write label
          context.fillStyle = color;
          context.textAlign = alignment;
          context.fillText(label, lineLeftOffset + xOffset, (scale.bottom - scale.top) / 2 + scale.top + yOffset);
      },

      afterDatasetsDraw: function (chart, easing) {
          if (chart.config.lineAtIndex) {
                labelIndex = 0;
                chart.config.lineAtIndex.forEach((pointIndex) => {
                    if (chart.config.verticalLinesLabels != undefined) { // if array of labels exists...
                        label = chart.config.verticalLinesLabels[labelIndex]; // chart.config.verticalLinesLabels must contain all elements; use  elements ="" for lines not requiring labels
                        color = chart.config.verticalLinesColors[labelIndex]; // chart.config.verticalLinesColors must contain all elements
                        alignment =  chart.config.verticalLinesAlignments[labelIndex]; // chart.config.verticalLinesAlignments must contain all elements
                        xOff =  chart.config.verticalLinesX[labelIndex]; // chart.config.verticalLinesX must contain all elements
                        yOff =  chart.config.verticalLinesY[labelIndex]; // chart.config.verticalLinesY must contain all elements
                    } else {
                        label = "";
                    }
                    this.renderVerticalLine(chart, pointIndex, label, color, alignment, xOff, yOff)
                    labelIndex++;
              });
          }
      }
      };

      Chart.plugins.register(verticalLinePlugin);

Usage:

myChart.config.verticalLinesLabels = ["aaa", "bbb", "ddd"]; 
myChart.config.verticalLinesColors = ["#FF0000", 'rgb(0,255,0)', 'rgba(0,0,255,0.5)']; 
myChart.config.verticalLinesAlignments = ["left", "center", "right"]; // Set label aligment (note: it is inverted because referred to line, not to label)
myChart.config.verticalLinesX = [10,5,0]; // Set label X offset
myChart.config.verticalLinesY = [10,5,0]; // Set label Y offset
myChart.config.lineAtIndex = [10,30,50]; // Mandatory to enable all previous ones
myChart.update()

Leave a comment