Chart.js — 绘制任意垂直线

问题描述 投票:0回答:9

如何使用 Chart.js 在 x 轴上的特定点绘制垂直线?

特别是,我想在折线图上画一条线来指示当前日期。这是图表的模型: https://i.sstatic.net/VQDWR.png

enter image description here

javascript chart.js
9个回答
100
投票

更新 - 此答案适用于 Chart.js 1.x,如果您正在寻找 2.x 答案,请检查评论和其他答案。

您可以扩展折线图并在绘图函数中包含用于绘制线条的逻辑。


预览

enter image description here


HTML

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

脚本

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
});

选项属性 lineAtIndex 控制在哪个点绘制线。

小提琴 - http://jsfiddle.net/dbyze2ga/14/


74
投票

分享我的 Chartjs.org 版本 2.5 的解决方案。我想使用一个插件,使实现可重用。

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);

使用方法很简单:

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

上面的代码在位置 2,4 和 8 处插入红色垂直线,穿过这些位置处的第一个数据集的点。


10
投票

我强烈建议使用Chartjs-Plugin-Annotation

示例可以在 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"
            }
          }
        ]
      }
    }
  });
};

查看此处了解更多详细信息:https://stackoverflow.com/a/36431041


9
投票

使用 Chart.js 3.8.0 我使用了带有时间线(xAxis)和百分比(yAxis)的折线图/条形图的组合。请参阅文档

数据集配置提供了一个选项来设置

maxBarThickness
(我已经应用了 2 个),然后将 y 轴的最大值应用于条形图的每个数据条目。

数据集配置示例:

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,
    ...
  }
]

输出示例:

enter image description here


7
投票

我不得不费尽心思弄清楚如何使用 ChartJS 2.0 做类似的事情,所以我想我会分享。

这是基于重写图表原型的新方法,如下所述: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
投票

这里有一支笔可以实现类似的效果,无需使用 Chartjs-plugin-annotation,或者修改 Chart.js 的渲染方式,或任何其他插件:https://codepen.io/gkemmey/pen/qBWZbYM

方法

  1. 使用组合条形图/折线图,并使用条形图绘制垂直线。
  2. 使用两个 y 轴:一个用于条形图(我们不显示),另一个用于所有其他折线图数据集。
  3. 将条形图 y 轴强制为
    min: 0
    max: 1
    。每当您想要绘制垂直线时,请将
    { x: where_the_line_goes, y: 1 }
    之类的数据对象添加到条形图数据集中。
  4. 笔还向条形图数据集添加了一些自定义数据以及图例过滤器和标签回调,以从图例中排除条形图数据集,并控制垂直线上的标签。

优点

  1. 没有其他依赖项。没有自定义猴子修补/扩展。
  2. 注释插件似乎没有得到积极维护。例如,atm,它们的事件处理程序会抛出有关“防止被动事件默认”的错误
  3. 也许是专业人士:注释插件总是显示绘制的线条的标签,并且您必须使用它们的事件回调来获得悬停时显示的效果。 Chart.js 工具提示默认显示在悬停时。

缺点

  1. 我们在数据集配置中添加自定义数据,并希望它不会与 Chart.js 正在执行的任何操作发生冲突。这是 Chart.js 不希望出现的数据,但从 2.8 开始,也不会破坏它。

2
投票

@Tomáš Dvořák 答案的增强版

enter image description here

支持:

  • 标签的自定义文本
  • 线条+标签的自定义颜色
  • 标签的自定义对齐方式
  • 标签的自定义 X/Y 偏移
    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);

用途:

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()

2
投票

Tomáš Dvořák 的垂直线插件 Typescript 版本,适用于 Chart.js 2.9.4 及更高版本:

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) ) } }, }

然后注册插件:

Chart.plugins.register(verticalLinePlugin)

要使用该插件,只需将其作为图表选项中设置的插件属性之一即可:

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
投票
添加多条垂直线:

(叉@potatopeelings答案:

https://stackoverflow.com/a/30323431/3114914

演示:

jsfiddle

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); for(let item in this.options.lineAtIndex){ var point = this.datasets[0].points[this.options.lineAtIndex[item]['lableIndex']] var scale = this.scale // draw line this.chart.ctx.beginPath(); this.chart.ctx.moveTo(point.x, scale.startPoint + 24); this.chart.ctx.strokeStyle = this.options.lineAtIndex[item]['color']; this.chart.ctx.lineTo(point.x, scale.endPoint); this.chart.ctx.stroke(); // write TEXT this.chart.ctx.textAlign = 'center'; this.chart.ctx.fillText(item, point.x, scale.startPoint + 12); } } }); new Chart(ctx).LineWithLine(data, { datasetFill : false, lineAtIndex: { "Text1":{ "lableIndex":2, "color":'#ff0000' }, "Text2":{ "lableIndex":5, "color":'#3ca832' }, "Text3":{ "lableIndex":6, "color":'#2200ff' } } });
    
© www.soinside.com 2019 - 2024. All rights reserved.