103

How can I draw an vertical line at a particular point on the x-axis using Chart.js?

In particular, I want to draw a line to indicate the current day on a LineChart. Here's a mockup of the chart: https://i.stack.imgur.com/VQDWR.png

enter image description here

ΦXocę 웃 Пepeúpa ツ
  • 47,427
  • 17
  • 69
  • 97
Fredrik
  • 1,033
  • 2
  • 8
  • 6

8 Answers8

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/

PW Kad
  • 14,953
  • 7
  • 49
  • 82
potatopeelings
  • 40,709
  • 7
  • 95
  • 119
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.

  • 3
    This is awesome and highly reusable! Great solution! +1 – EoghanTadhg Jun 15 '17 at 00:09
  • 1
    Reviving this old post, but is it possible to configure different text for each vertical line? – Matt Jun 22 '17 at 18:03
  • Sure, you just have to adapt it slightly. Instead of lineAtIndex array you should provide a map with index=>text, iterate over it in the afterDatasetsDraw method and use the text from the map in the renderVerticalLine method where 'MY TEXT' is currently rendered. – Tomáš Dvořák Jun 24 '17 at 06:02
  • 1
    Chart.js may have some bug now that is causing a ReferenceError "Chart is not defined" – Silverfin Oct 30 '18 at 23:00
  • 3
    While I upvoted this solution, it does have a limitation that is pretty ugly, in that it is not possible to add lines at values that lie between indices. But it turned out to be a great tutorial. – Daniel F Oct 24 '19 at 18:58
  • I changed the input a bit, so I can change the color and text. `[{ x: 10, text: "Sample test", color: "#ff0000" }]` And changed the code accordingly. – Ralph Bisschops Aug 31 '20 at 19:07
  • Changing "afterDatasetsDraw" to "beforeDatasetsDraw" draws the vertical line behind the chart instead of on top of! Thanks! – fellowworldcitizen Jan 07 '21 at 11:54
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

phifi
  • 2,783
  • 1
  • 21
  • 39
  • It works for me - without any further information, i wont be able to help you – phifi Dec 12 '17 at 12:55
  • I am trying to add this in Ionic project mobile. Did you created this for web ? – Akhilesh Mani Dec 12 '17 at 15:32
  • Yes, i created it for a website. maybe you should created a seperate question with more information to your actual problem and provide some source code. – phifi Dec 13 '17 at 16:30
  • Resolved Thanks ! https://stackoverflow.com/questions/47655173/chart-js-v2-draw-horizontal-baraverage-over-vertical-bar/47752998#47752998 – Akhilesh Mani Dec 14 '17 at 05:24
  • All chart.js plugins are messy. That can explain why they are «plugins» and not included in the core. This follow a lot of testings. Chart.js is okay, but don't use plugins, guys, make yours. – NVRM Oct 13 '18 at 07:38
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);
};
Brandon Johnson
  • 329
  • 5
  • 11
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

ddegasperi
  • 662
  • 9
  • 12
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.
Gray Kemmey
  • 976
  • 8
  • 12
  • this approach would be perfect if we can find a way to apply a linear axis for bar chart, add redundant data to display the chart is not really what I can expect – Raphaël VO Nov 15 '19 at 10:06
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
            },
          ],
        },
      }
    }
yamardr
  • 11
  • 2
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()
jumpjack
  • 841
  • 1
  • 11
  • 17