How to set Custom tooltip event with Chartjs version 2.X - javascript

Hi I am trying to attach click event on datapoints with Chart.js line graph.
However, the official document does not have such an information. I found some source which make custom events on tooltip, but they looks like using version 1.X.(add custom event on chartjs) - at version 2.x, can not access Chart.defaults -
So what I have to do is something like below.
I will add click event on data points of Chart.js line graph
hover event will be remain.
FYI, the codes I am trying to write is here.
function makeGraphSuitableData(items, type){
var object = {
labels : []
, data : []
}
if(type == 'month') {
object.labels = ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"];
object.data = new Array(12);
for(var i = 0; i < object.labels.length; i++) {
var datas = items.filter(function(item){
return (item.reg_month == object.labels[i]);
})
console.log('datas', datas);
if(datas && datas.length > 0){
var single = datas[0];
object.data[(object.labels[i] * 1) - 1] = single.frequency;
} else {
object.data[(object.labels[i] * 1) - 1] = 0;
}
}
object.labels = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
}
return object;
}
function drawGraph(holder, statistics){
var statistics = [
{
labels : "01"
, frequency : "100"
}, {
labels : "02"
, frequency : "150"
}
]
var canvas = holder.querySelector('canvas');
if(!canvas){
var canvas = document.createElement('canvas');
canvas.width = 400; canvas.height = 150;
holder.appendChild(canvas);
}
var graphObject = makeGraphSuitableData(statistics, 'month');
// Bar-Graph
var type = 'line'; //bar, line
var displayLabel = 'Expose counts';
switch (type){
case 'line':
var myChart = new Chart(canvas, {
type: type,
data: {
labels: graphObject.labels,
datasets: [{
label: displayLabel,
data: graphObject.data,
fill: false,
lineTension: 0,
backgroundColor: "rgba(75,192,192,0.4)",
borderColor: "rgba(75,192,192,1)",
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
pointBorderColor: "rgba(75,192,192,1)",
pointBackgroundColor: "#fff",
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: "rgba(75,192,192,1)",
pointHoverBorderColor: "rgba(220,220,220,1)",
pointHoverBorderWidth: 2,
pointRadius: 1,
pointHitRadius: 10,
spanGaps: false
}]
}
});
canvas.addEventListener('click', function(e){
var x = myChart;
window.x = x;
}, false);
break;
}
}
Thanks for reading and your answers. getPointsAtEvent looks disappeared in version 2.X.
====== UPDATES 24MAR ======
To clear my point, I updated the post.
In my thought, in order to add click event on datapoints, need to get datapoints objects.
But I can not see how to access those points.
I have examined the Chart object but could not find getDataPoints() or getTooltips(). Access to those object should be a solution, I guess.
FIDDLE

The correct (documented) way to do this, per the api, is to use the .getElementAtEvent() prototype method.
The other answer kind of works, but does not work if your chart has more than 1 dataset (e.g. line). Plus, it relies on undocumented objects/properties in the chart.js object that could change at any time with a new release.
document.getElementById("canvas").onclick = function(evt){
var activePoint = myChart.getElementAtEvent(event);
// make sure click was on an actual point
if (activePoint.length > 0) {
var clickedDatasetIndex = activePoint[0]._datasetIndex;
var clickedElementindex = activePoint[0]._index;
var label = myChart.data.labels[clickedElementindex];
var value = myChart.data.datasets[clickedDatasetIndex].data[clickedElementindex];
alert("Clicked: " + label + " - " + value);
}
};
Here is a codepen example demonstrating the above code.

There is always a way to solve any issue. It took about half an hour but I found it.
in your jsfiddle, just change below:
canvas.addEventListener('click', function(e){
var x = myChart;
}, false);
break;
to
canvas.addEventListener('click', function(e){
var x = myChart;
if(x.active[0]){
var value= x.tooltip._data.datasets["0"].data[x.active["0"]._index];
var label = x.tooltip._data.labels[x.active["0"]._index];
console.log(value + " at "+ label);
}
}, false);
break;
You will have access to the point via label and value. enjoy :)

Related

Shift background color in Chartjs

I need to plot a time series in near-real time. I am not a Javascript developer but for several reasons I ended up using Chartjs. What I want to do is a graph that shows the last 60 seconds (or a known interval) and every second it moves to the right and the oldest instants "go out" from the chart. At the same time I would like to color the background between two points in different colors depending on a value in a list (if for the moment the color is assigned at random is fine). Copying the code around a bit I wrote this code.
HTML:
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.4.0/Chart.min.js"></script>
<div style="width:90%;">
<canvas id="canvas"></canvas>
</div>
JS:
// decimal rounding algorithm
// see: https://plnkr.co/edit/uau8BlS1cqbvWPCHJeOy?p=preview
var roundNumber = function (num, scale) {
var number = Math.round(num * Math.pow(10, scale)) / Math.pow(10, scale);
if(num - number > 0) {
return (number + Math.floor(2 * Math.round((num - number) * Math.pow(10, (scale + 1))) / 10) / Math.pow(10, scale));
} else {
return number;
}
};
// save the original line element so we can still call it's
// draw method after we build the linear gradient
var origLineElement = Chart.elements.Line;
// define a new line draw method so that we can build a linear gradient
// based on the position of each point
Chart.elements.Line = Chart.Element.extend({
draw: function() {
var vm = this._view;
var backgroundColors = this._chart.controller.data.datasets[this._datasetIndex].backgroundColor;
var points = this._children;
var ctx = this._chart.ctx;
var minX = points[0]._model.x;
var maxX = points[points.length - 1]._model.x;
var linearGradient = ctx.createLinearGradient(minX, 0, maxX, 0);
// iterate over each point to build the gradient
points.forEach(function(point, i) {
// `addColorStop` expects a number between 0 and 1, so we
// have to normalize the x position of each point between 0 and 1
// and round to make sure the positioning isn't too percise
// (otherwise it won't line up with the point position)
var colorStopPosition = roundNumber((point._model.x - minX) / (maxX - minX), 2);
// special case for the first color stop
if (i === 0) {
linearGradient.addColorStop(0, backgroundColors[i]);
} else {
// only add a color stop if the color is different
if (backgroundColors[i] !== backgroundColors[i-1]) {
// add a color stop for the prev color and for the new color at the same location
// this gives a solid color gradient instead of a gradient that fades to the next color
linearGradient.addColorStop(colorStopPosition, backgroundColors[i - 1]);
linearGradient.addColorStop(colorStopPosition, backgroundColors[i]);
}
}
});
// save the linear gradient in background color property
// since this is what is used for ctx.fillStyle when the fill is rendered
vm.backgroundColor = linearGradient;
// now draw the lines (using the original draw method)
origLineElement.prototype.draw.apply(this);
}
});
// we have to overwrite the datasetElementType property in the line controller
// because it is set before we can extend the line element (this ensures that
// the line element used by the chart is the one that we extended above)
Chart.controllers.line = Chart.controllers.line.extend({
datasetElementType: Chart.elements.Line,
});
// set colors
var chartColors = {
red: 'rgb(232, 30, 30)',
orange: 'rgb(252, 173, 38)',
yellow: 'rgb(249, 249, 3)',
green: 'rgb(30, 208, 10)',
black: 'rgb(0, 0, 0)'
};
var labels = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
// colors used as the point background colors as well as the fill colors
var fillColors = [chartColors.green, chartColors.green, chartColors.red, chartColors.red, chartColors.red, chartColors.red, chartColors.yellow, chartColors.yellow, chartColors.green, chartColors.green, chartColors.green, chartColors.red];
var lineData = [13, 20, 0, 0, 0, 0, 2, 5, 20, 18, 17, 0];
var data = {
labels: labels,
datasets: [{
data: lineData,
label: "My Dataset",
backgroundColor: fillColors,
borderColor: chartColors.black,
pointBackgroundColor: chartColors.black,
fill: true,
}]
};
var options = {
responsive: true,
title: {
display: true,
text:'Machine Rolling Time Series' // the title could be changed
},
legend: {
display: false,
},
scales: {
xAxes: [{
gridLines: {
offsetGridLines: true
},
}]
}
};
var maximumPoints = 12;// with this variable you can decide how many points are display on the chart
function addData(chart, label, data) {
chart.data.labels.push(label);
chart.data.datasets.forEach((dataset) => {
var d = data[0];
dataset.data.push(d);
data.shift();
});
var canRemoveData = false;
chart.data.datasets.forEach((dataset) => {
if (dataset.data.length > maximumPoints) {
if (!canRemoveData) {
canRemoveData = true;
chart.data.labels.shift();
}
dataset.data.shift();
}
});
chart.update();
}
var ctx = document.getElementById("canvas").getContext("2d");
var myLineChart = new Chart(ctx , {
type: "line",
data: data,
options: options
});
var index = new Date().toLocaleString();
setInterval(function() {
var data = [];
myLineChart.data.datasets.forEach((dataset) => {
data.push(Math.random() * 100);
});
addData(myLineChart, index, data);
index = new Date().toLocaleString();
}, 1000);
At this point I can't shift the background color that remains stucked in the starting position. I wonder if someone could help me to make move the background color when a new point is added to the chart.
Thanks for the help!
PS: I know there are other "errors" in the code, I'm trying first to accomplish the bigger things then I'll think about simpliest tasks.

Exponential decrease

I am stuck getting an exponential decrease from 100 to 0.
This is what I managed to get:
var time_data = [];
var value_data = [];
var left = 100;
var i = 1;
while (i <= 30)
{
left = left-Math.pow(i,i);
if(left < 0)
{
left = 0;
}
value_data.push(left);
time_data.push(i);
i+=1;
}
var canvas = document.getElementById('myChart');
var data = {
labels: time_data,
datasets: [
{
label: "My First dataset",
fill: false,
lineTension: 0.1,
backgroundColor: "rgba(75,192,192,0.4)",
borderColor: "rgba(75,192,192,1)",
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
pointBorderColor: "rgba(75,192,192,1)",
pointBackgroundColor: "#fff",
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: "rgba(75,192,192,1)",
pointHoverBorderColor: "rgba(220,220,220,1)",
pointHoverBorderWidth: 2,
pointRadius: 5,
pointHitRadius: 10,
data: value_data,
}
]
};
var option = {
showLines: true
};
var myLineChart = Chart.Line(canvas,{
data:data,
options:option
});
function adddata(){
myLineChart.data.datasets[0].data[7] = 50;
myLineChart.data.labels[7] = "test add";
myLineChart.update();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.3/Chart.min.js"></script>
<canvas id="myChart" width="400" height="250"></canvas>
while (i <= 30)
{
left = left-Math.pow(i,i);
if(left < 0)
{
left = 0;
}
And this is what I need to get
The problem is that don't know how to get a reverse version of Math.pow.
I would appreciate any help.
According to the graph, what you want is to start at (0,10) and then divide y by 2 every time x is incremented by 1.
Try this:
var time_data = [];
var value_data = [];
for (let i=0;i<=10;++i) {
time_data.push(i);
value_data.push(10/Math.pow(2,i));
}
This can be mathematically written as a decreasing exponential: 10 * 2^(-x) or if you prefer 10 * exp(-ln(2) * x)
If you're looking for exponential decay, do you need to use Math.pow()? What's wrong with something like
left = left*factor
Where 0 < factor < 1?
Looking at some documentation for Math.pow(), the two arguments are base and exponent. In your example, left - 30^30 doesn't make much sense because it will result in a vastly negative number and then default to 0.
An expression using Math.pow() that will give the ith term of an exponential decay sequence is
start_value * Math.pow(factor, i)
Where again, 0 < factor < 1. The reduction in the value that you start with is compounded exponentially over each iteration.

Chart.js Line, different fill color for negative point

I need to change the fill color (internal area) in a Line Chart.js when the point is negative.
The code is simple and basic:
$(document).ready(function(){
var ctx = $("#myChart").get(0).getContext("2d");
var data = {
labels: ["January", "February", "March", "April", "May", "June", "July"],
datasets: [
{
label: "My First dataset",
//fillColor : "rgba(60,91,87,1)",
// String - the color to fill the area under the line with if fill is true
backgroundColor: "rgba(75,192,192,0.4)",
strokeColor : "rgba(60,91,87,1)",
pointColor : "rgba(60,91,87,1)",
pointStrokeColor : "#58606d",
// The actual data
data: [65, 59, 80, -81, 56, 55, -40],
// String - If specified, binds the dataset to a certain y-axis. If not specified, the first y-axis is used. First id is y-axis-0
yAxisID: "y-axis-0",
}
]
};
var options = {
scales: {
yAxes: [{
display: true,
ticks: {
suggestedMin: 0, // minimum will be 0, unless there is a lower value.
// OR //
beginAtZero: true // minimum value will be 0.
}
}]
}
};
var myLineChart = new Chart(ctx, {
type: 'line',
data: data,
options: options
});
// myLineChart.data.datasets[0].metaDataset._points[3]._model.backgroundColor = "red";
// if (myLineChart.datasets[0].points[4].value < 0) {
// myLineChart.datasets[0].points[4].fillColor = "red";
// myLineChart.update();
// }
})
I'm trying to get this result:
You can extend the line chart to do this.
Preview
Script
Chart.defaults.NegativeTransparentLine = Chart.helpers.clone(Chart.defaults.line);
Chart.controllers.NegativeTransparentLine = Chart.controllers.line.extend({
update: function () {
// get the min and max values
var min = Math.min.apply(null, this.chart.data.datasets[0].data);
var max = Math.max.apply(null, this.chart.data.datasets[0].data);
var yScale = this.getScaleForId(this.getDataset().yAxisID);
// figure out the pixels for these and the value 0
var top = yScale.getPixelForValue(max);
var zero = yScale.getPixelForValue(0);
var bottom = yScale.getPixelForValue(min);
// build a gradient that switches color at the 0 point
var ctx = this.chart.chart.ctx;
var gradient = ctx.createLinearGradient(0, top, 0, bottom);
var ratio = Math.min((zero - top) / (bottom - top), 1);
gradient.addColorStop(0, 'rgba(75,192,192,0.4)');
gradient.addColorStop(ratio, 'rgba(75,192,192,0.4)');
gradient.addColorStop(ratio, 'rgba(0,0,0,0)');
gradient.addColorStop(1, 'rgba(0,0,0,0)');
this.chart.data.datasets[0].backgroundColor = gradient;
return Chart.controllers.line.prototype.update.apply(this, arguments);
}
});
and then
...
var myLineChart = new Chart(ctx, {
type: 'NegativeTransparentLine',
data: {
...
Fiddle - http://jsfiddle.net/g2r2q5Lu/
To get #potatopeelings code above to work with chart.js 2.5.x you need to add yAxisID : 'y-axis-0' into your datasets, as below.
var myLineChart = new Chart(ctx, {
type: 'NegativeTransparentLine',
data: {
labels: ["January", "February", "March", "April", "May", "June", "July"],
datasets: [{
yAxisID : 'y-axis-0',
....
i update the method to work with multiple datasets.
Chart.defaults.NegativeTransparentLine = Chart.helpers.clone(Chart.defaults.line);
Chart.controllers.NegativeTransparentLine = Chart.controllers.line.extend({
update: function () {
for(let i=0; i< this.chart.data.datasets.length; i++) {
// get the min and max values
var min = Math.min.apply(null, this.chart.data.datasets[i].data);
var max = Math.max.apply(null, this.chart.data.datasets[i].data);
var yScale = this.getScaleForId(this.chart.data.datasets[i].yAxisID);
// figure out the pixels for these and the value 0
var top = yScale.getPixelForValue(max);
var zero = yScale.getPixelForValue(0);
var bottom = yScale.getPixelForValue(min);
// build a gradient that switches color at the 0 point
var ctx = this.chart.chart.ctx;
var gradient = ctx.createLinearGradient(0, top, 0, bottom);
var ratio = Math.min((zero - top) / (bottom - top), 1);
gradient.addColorStop(0, 'rgba(55,210,99,0.4)');
gradient.addColorStop(ratio, 'rgba(55,210,99,0.4)');
gradient.addColorStop(ratio, 'rgba(247,100,120,0.4)');
gradient.addColorStop(1, 'rgba(247,100,120,0.4)');
this.chart.data.datasets[i].backgroundColor = gradient;
}
return Chart.controllers.line.prototype.update.apply(this, arguments);
}
});
Tested on chart.js 2.8.0 on Angular 8
import { Component, OnInit, ViewChild } from '#angular/core';
import { Chart, ChartDataSets, ChartOptions } from 'chart.js';
import { Color, Label } from 'ng2-charts';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
public lineChartData: ChartDataSets[] = [
{ data: [89, 0, -80, 81, 56, -55, 40], label: 'Series A', yAxisID: 'y-axis-0' },
{ data: [-890, 0, 800, -810, -560, 550, -400], label: 'Series B', yAxisID: 'y-axis-0' },
];
public lineChartLabels: Label[] = ['January', 'February', 'March', 'April', 'May', 'June', 'July'];
public lineChartOptions: (ChartOptions & { annotation: any }) = {
responsive: true,
};
public lineChartColors: Color[] = [
{
backgroundColor: 'rgba(255,0,0,0.3)',
},
{
backgroundColor: 'rgba(0,255,0,0.3)',
},
];
public lineChartLegend = true;
public lineChartType = 'line';
public lineChartPlugins = [];
constructor() {
Chart.defaults.NegativeTransparentLine = Chart.helpers.clone(Chart.defaults.line);
Chart.controllers.NegativeTransparentLine = Chart.controllers.line.extend({
update: function () {
for(let i=0; i< this.chart.data.datasets.length; i++) {
// get the min and max values
var min = Math.min.apply(null, this.chart.data.datasets[i].data);
var max = Math.max.apply(null, this.chart.data.datasets[i].data);
var yScale = this.getScaleForId(this.chart.data.datasets[i].yAxisID);
// figure out the pixels for these and the value 0
var top = yScale.getPixelForValue(max);
var zero = yScale.getPixelForValue(0);
var bottom = yScale.getPixelForValue(min);
// build a gradient that switches color at the 0 point
var ctx = this.chart.chart.ctx;
var gradient = ctx.createLinearGradient(0, top, 0, bottom);
var ratio = Math.min((zero - top) / (bottom - top), 1);
gradient.addColorStop(0, 'rgba(55,210,99,0.4)');
gradient.addColorStop(ratio, 'rgba(55,210,99,0.4)');
gradient.addColorStop(ratio, 'rgba(247,100,120,0.4)');
gradient.addColorStop(1, 'rgba(247,100,120,0.4)');
this.chart.data.datasets[i].backgroundColor = gradient;
}
return Chart.controllers.line.prototype.update.apply(this, arguments);
}
});
this.lineChartType = 'NegativeTransparentLine';
}
ngOnInit() {
}
}
<div style="display: block;">
<canvas baseChart width="400" height="400"
[datasets]="lineChartData"
[labels]="lineChartLabels"
[options]="lineChartOptions"
[colors]="lineChartColors"
[legend]="lineChartLegend"
[chartType]="lineChartType"
[plugins]="lineChartPlugins">
</canvas>
</div>
This is derived from this post. It works for Chart.js v2.9.4 and doesn't require any external code or creating a custom chart type. Simply add this plugins object to your chart options. (note that the plugins object is separate from the options object. If you put the plugins object inside of the options object, it won't work.)
new Chart(document.querySelector(`canvas`), {
type: 'line',
data: {
labels: your_labels,
datasets: [{
data: your_data
}]
},
options: {
maintainAspectRatio: false, //allow the graph to resize to its container
scales: {
yAxes: [{
ticks: {
beginAtZero: true //make sure zero line exists on the graph
}
}]
}
}, //<-make sure plugins is outside of the options object
plugins: [{
beforeRender: function(graph) {
let gradient = graph.ctx.createLinearGradient(0, 0, 0, graph.height), //create a gradient for the background
zero_line = graph.scales[`y-axis-0`].getPixelForValue(0) / graph.height; //calculate where the zero line is plotted on the graph
gradient.addColorStop(0, `rgba(0,200,0,.2)`); //good color faded out
gradient.addColorStop(zero_line, `rgba(0,200,0,.8)`); //good color at zero line
gradient.addColorStop(zero_line, `rgba(200,0,0,.8)`); //bad color at zero line
gradient.addColorStop(1, `rgba(200,0,0,.2)`); //bad color faded out
graph.data.datasets[0]._meta[0].$filler.el._model.backgroundColor = gradient; //set the graphs background to the gradient we just made
}
}]
});
Obviously for more complex graphs you'll need to update dataset indexes and axis names, but for simple graphs, it's this simple.
#potatopeelings code will work if your dataset data format is in [1,2,3,...] form
If your data format is in [{x: 1 , y: 1},...] form, you need to change var min and var max to:
var min = this.chart.data.datasets[0].data.reduce((min, p) => p.y < min ? p.y : min, this.chart.data.datasets[0].data[0].y);
var max = this.chart.data.datasets[0].data.reduce((max, p) => p.y > max ? p.y : max, this.chart.data.datasets[0].data[0].y);
Tested on ChartJS 2.7.3
#potatopeelings The gradient messed up if all data was negative or positive, here's how I fixed it. (Changed the gradient colours but the fix is still there)
Chart.defaults.NegativeTransparentLine = Chart.helpers.clone(Chart.defaults.line);
Chart.controllers.NegativeTransparentLine = Chart.controllers.line.extend({
update: function () {
// get the min and max values
var min = Math.min.apply(null, this.chart.data.datasets[0].data);
var max = Math.max.apply(null, this.chart.data.datasets[0].data);
var yScale = this.getScaleForId(this.getDataset().yAxisID);
// figure out the pixels for these and the value 0
var top = yScale.getPixelForValue(max);
var zero = yScale.getPixelForValue(0);
var bottom = yScale.getPixelForValue(min);
// build a gradient that switches color at the 0 point
var ctx = this.chart.chart.ctx;
var gradient = ctx.createLinearGradient(0, top, 0, bottom);
var ratio = Math.min((zero - top) / (bottom - top), 1);
if(ratio < 0){
ratio = 0;
gradient.addColorStop(1, 'rgba(0,255,0,1)');
}else if(ratio == 1){
gradient.addColorStop(1, 'rgba(255,0,0,1)');
}else{
gradient.addColorStop(0, 'rgba(255,0,0,1)');
gradient.addColorStop(ratio, 'rgba(255,0,0,1)');
gradient.addColorStop(ratio, 'rgba(0,255,0,1)');
gradient.addColorStop(1, 'rgba(0,255,0,1)');
}
console.log(ratio)
this.chart.data.datasets[0].backgroundColor = gradient;
return Chart.controllers.line.prototype.update.apply(this, arguments);
}
});

Chart.js BarChart not appearing

I am trying to get a BarChart appear using MVC, EF and json. For some reason the Bar Chart does not load and Google Chrome shows the following error "Uncaught TypeError: Cannot read property 'length' of undefined" in the console.
I am struggling to find the error in my code and hope someone can help me.
DashboardController
[AjaxOnly]
public ActionResult WeeklyLatenessSummary()
{
ChartHelper chart = new ChartHelper();
DateTime d = DateTime.Today;
int offset = d.DayOfWeek - DayOfWeek.Monday;
offset = (offset < 0) ? 6 : offset;
DateTime startDate = d.AddDays(-offset);
DateTime endDate = startDate.AddDays(7);
var data = (from a in _db.Attendances
join at in _db.AttendanceTypes on a.Type equals at.AttendanceTypeID
where a.Date >= startDate
&& a.Date < endDate
&& at.AttendanceTypeCode == "L"
group at by at.AttendanceTypeDescription into g
select new
{
value = g.Count()
}).ToList();
return Json(JsonConvert.SerializeObject(chart.PopulateBarChart("Weekly Lateness", data)), JsonRequestBehavior.AllowGet);
}
JSON Result
"[{\"label\":\"Weekly Lateness\",\"data\":\"3\",\"fillColor\":\"#F7464A\",\"strokeColor\":\"#F7464A\",\"highlightFill\":\"#FF5A5E\",\"highlightStroke\":\"#FF5A5E\"}]"
View
<div class="col-md-12">
<div class=" panel panel-success">
<div class="panel-heading">
<h3 class="panel-title">Weekly Lateness Summary</h3>
</div><!--End of panel-heading-->
<div class="panel-body">
<canvas id="weekly-lateness" width="650" height="300" class="center-block"></canvas>
</div><!--End of panel-body-->
</div><!--End of panel-->
</div><!--End of col-md-12-->
smChart.js
var ctx3 = $("#weekly-lateness").get(0).getContext("2d");
var barChartOptions = {
//Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
scaleBeginAtZero: true,
//Boolean - Whether grid lines are shown across the chart
scaleShowGridLines: true,
//String - Colour of the grid lines
scaleGridLineColor: "rgba(0,0,0,.05)",
//Number - Width of the grid lines
scaleGridLineWidth: 1,
//Boolean - Whether to show horizontal lines (except X axis)
scaleShowHorizontalLines: true,
//Boolean - Whether to show vertical lines (except Y axis)
scaleShowVerticalLines: true,
//Boolean - If there is a stroke on each bar
barShowStroke: true,
//Number - Pixel width of the bar stroke
barStrokeWidth: 2,
//Number - Spacing between each of the X value sets
barValueSpacing: 5,
//Number - Spacing between data sets within X values
barDatasetSpacing: 1,
//String - A legend template
legendTemplate: "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].fillColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
}
var getDaysInMonth = function (month, year) {
return new Date(year, month, 0).getDate();
}
var dates = [];
for (var i = 1; i <= getDaysInMonth(8,2015); i++) {
dates.push(i);
}
window.onload = function () {
$.ajax({
type: "GET",
dataType: "json",
url: "/Dashboard/WeeklyLatenessSummary/",
success: function (data) {
var json = JSON.parse(data);
var chartData = [];
for (var k in json) {
chartData.push(json[k])
}
if (chartData.length == 0) {
$('#weekly-lateness').hide();
$('#weekly-lateness').before("<h3 class='text-center'>No Data</h3>");
}
else {
var myPieChart3 = new Chart(ctx3).Bar(chartData, barChartOptions)
}
}
});
}
You're building the chartData variable incorrectly. For reference, here is what it should look like (from http://www.chartjs.org/docs/#bar-chart)
{
labels: ["January", "February", "March", "April", "May", "June", "July"],
datasets: [
{
label: "My First dataset",
fillColor: "rgba(220,220,220,0.5)",
strokeColor: "rgba(220,220,220,0.8)",
highlightFill: "rgba(220,220,220,0.75)",
highlightStroke: "rgba(220,220,220,1)",
data: [65, 59, 80, 81, 56, 55, 40]
},
{
label: "My Second dataset",
fillColor: "rgba(151,187,205,0.5)",
strokeColor: "rgba(151,187,205,0.8)",
highlightFill: "rgba(151,187,205,0.75)",
highlightStroke: "rgba(151,187,205,1)",
data: [28, 48, 40, 19, 86, 27, 90]
}
] };
The labels (not to be confused with label which is the series name)correspond to the x axis entries and the there is a datasets element for each series. Each series has as many entries in its data array as there are entries in labels
With the sample JSON you've given, here's one way to make the data appear
var chartData = {
labels: ['Potatoes'],
datasets: []
};
for (var k in json) {
chartData.datasets.push(json[k])
}
It will be easier to change it based on your requirements about what needs to be displayed and on which dimension.
I would venture to say it is bombing on your legend template when it is rendered.
datasets is undefined
legendTemplate: "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].fillColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"

Chart.js — drawing an arbitrary vertical line

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:
http://i.stack.imgur.com/VQDWR.png
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
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/
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.
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
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);
};
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:
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
Use a combo bar / line chart, and use the bar chart to draw the vertical lines.
Use two y-axes: one for the bar chart (which we don't display), and one for all your other line chart datasets.
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.
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
No other dependencies. No custom monkey patching / extending.
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"
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
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.
Enhanced version of #Tomáš Dvořák answer
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()

Categories