Chart JS Crosshair - Linked Charts without linked Legends - javascript

I have an application using Chart JS and the great extension chartjs-plugin-crosshair to provide zoom and a vertical line on hover. It's ability to 'link' charts is critical as it highlights values on separate charts across the same x-axis.
The issue is the linked charts also have linked legends. I've created a simple example: https://codepen.io/sheixt/pen/JjGvbVJ
Here is an extract of the Chart option config (see the link for the full script):
const options = {
plugins: {
crosshair: {
sync: {
enabled: true
}
}
},
tooltips: {
mode: "interpolate",
intersect: false,
callbacks: {
title: function (a, d) {
return a[0].xLabel.toFixed(2);
},
label: function (i, d) {
return d.datasets[i.datasetIndex].label + ": " + i.yLabel.toFixed(2);
}
}
}
};
As you can see, if you "turn off" a dataset (e.g. Dataset 1, A in chart 1, D in chart 2, and G in chart 3), the dataset is removed from all of the linked charts.
I have a series of charts that are based on the same x-axis data so the crosshair line & the tooltip appearing on all of the linked charts is ideal. But as each dataset that is plotted is not the same across the various charts, I do not want it to disappear on click.
So in my example deselecting Dataset 1 on chart 1 A would be removed but D in chart 2, and G in chart 3 should remain.
Is this feasible?

You can extend the chart and write your own type with horizontal and vertical arbitrary lines as it is shown in this answer.
Here is an update if you need it for version 2
HTML
<canvas id="chart" width="600" height="400"></canvas>
SCRIPT
var ctx = document.getElementById('chart').getContext('2d');
Chart.defaults.crosshair = Chart.defaults.line;
Chart.controllers.crosshair = Chart.controllers.line.extend({
draw: function (params) {
Chart.controllers.line.prototype.draw.call(this, params);
if (this.chart.tooltip._active && this.chart.tooltip._active.length) {
var activePoint = this.chart.tooltip._active[0],
ctx = this.chart.ctx,
x = activePoint.tooltipPosition().x,
y = activePoint.tooltipPosition().y,
topY = this.chart.scales['y-axis-0'].top,
bottomY = this.chart.scales['y-axis-0'].bottom,
startX = this.chart.scales['x-axis-0'].left,
endX = this.chart.scales['x-axis-0'].right;
// draw line
ctx.save();
ctx.beginPath();
ctx.moveTo(x, topY);
ctx.lineTo(x, bottomY);
ctx.moveTo(startX, y);
ctx.lineTo(endX, y);
ctx.lineWidth = 2.5;
ctx.strokeStyle = 'rgb(55, 55, 55)';
ctx.stroke();
ctx.restore();
}
}
});
var chart = new Chart(ctx, {
// The type of chart we want to create
type: 'crosshair',
// The data for our dataset
data: {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [{
label: 'My First dataset',
backgroundColor: 'rgba(255, 255, 255,0)',
borderColor: 'rgb(255, 99, 132)',
data: [0, 10, 5, 2, 20, 30, 45]
}]
},
// Configuration options go here
options: {}
});
Result
var ctx = document.getElementById('chart').getContext('2d');
Chart.defaults.crosshair = Chart.defaults.line;
Chart.controllers.crosshair = Chart.controllers.line.extend({
draw: function (params) {
Chart.controllers.line.prototype.draw.call(this, params);
if (this.chart.tooltip._active && this.chart.tooltip._active.length) {
var activePoint = this.chart.tooltip._active[0],
ctx = this.chart.ctx,
x = activePoint.tooltipPosition().x,
y = activePoint.tooltipPosition().y,
topY = this.chart.scales['y-axis-0'].top,
bottomY = this.chart.scales['y-axis-0'].bottom,
startX = this.chart.scales['x-axis-0'].left,
endX = this.chart.scales['x-axis-0'].right;
// draw line
ctx.save();
ctx.beginPath();
ctx.moveTo(x, topY);
ctx.lineTo(x, bottomY);
ctx.moveTo(startX, y);
ctx.lineTo(endX, y);
ctx.lineWidth = 2.5;
ctx.strokeStyle = 'rgb(55, 55, 55)';
ctx.stroke();
ctx.restore();
}
}
});
var chart = new Chart(ctx, {
// The type of chart we want to create
type: 'crosshair',
// The data for our dataset
data: {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [{
label: 'My First dataset',
backgroundColor: 'rgba(255, 255, 255,0)',
borderColor: 'rgb(255, 99, 132)',
data: [0, 10, 5, 2, 20, 30, 45]
}]
},
// Configuration options go here
options: {}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js#2.8.0"></script>
<canvas id="chart" width="600" height="400"></canvas>

Related

How to draw the stroke behind bars in Chart.js?

I've wrote a custom Bar Chart in Chart.JS which on dataset hover highlight the bars by drawing a stroke on it the issue is that stroke is drawn over bars while i would make it something like 'background color' instead.
Like the bars are visible because the stroke color opacity is set to 0.05 while if i set it to 1 obviously those will not be visible anymore.
The code
class CustomBar extends Chart.BarController {
draw() {
super.draw(arguments);
if (this.chart.tooltip._active && this.chart.tooltip._active.length) {
const points = this.chart.tooltip._active[0];
const ctx = this.chart.ctx;
const x = points.element.x;
const topY = points.element.y + 150;
const width = points.element.width;
const bottomY = 0;
ctx.save();
ctx.beginPath();
ctx.moveTo(x, topY * 100);
ctx.lineTo(x + width * 1.3, bottomY);
ctx.lineWidth = width * 4.3;
ctx.strokeStyle = 'rgba(0, 0, 0, 0.05)';
ctx.stroke();
ctx.restore();
}
}
}
CustomBar.id = 'shadowBar';
CustomBar.defaults = Chart.BarController.defaults;
Chart.register(CustomBar);
You will need a custom plugin for this, in there you can specify that you want it to draw before the datasets are being drawn. You can do that like so:
Chart.register({
id: 'barShadow',
beforeDatasetsDraw: (chart, args, opts) => {
const {
ctx,
tooltip,
chartArea: {
bottom
}
} = chart;
if (!tooltip || !tooltip._active[0]) {
return
}
const point = tooltip._active[0];
const element = point.element;
const x = element.x;
const topY = -(element.height + 150);
const width = element.width;
const bottomY = 0;
const xOffset = opts.xOffset || 0;
const shadowColor = opts.color || 'rgba(0, 0, 0, 1)';
ctx.save();
ctx.beginPath();
ctx.fillStyle = shadowColor;
ctx.fillRect(x - (element.width / 2) + xOffset, bottom, width * 1.3 * 4.3, topY);
ctx.restore();
}
});
const options = {
type: 'bar',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: 'orange'
},
{
label: '# of Points',
data: [7, 11, 5, 8, 3, 7],
backgroundColor: 'pink'
}
]
},
options: {
plugins: {
barShadow: {
xOffset: -10,
color: 'red'
}
}
}
}
const ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.js"></script>
</body>

Draw a horizontal and vertical line on mouse hover in chart js

I am stuck with a problem on chart js while creating line chart. I want to create a chart with the specified data and also need to have horizontal and vertical line while I hover on intersection point. I am able to create vertical line on hover but can not find any solution where I can draw both the line. Here is my code to draw vertical line on hover.
window.lineOnHover = function(){
Chart.defaults.LineWithLine = Chart.defaults.line;
Chart.controllers.LineWithLine = Chart.controllers.line.extend({
draw: function(ease) {
Chart.controllers.line.prototype.draw.call(this, ease);
if (this.chart.tooltip._active && this.chart.tooltip._active.length) {
var activePoint = this.chart.tooltip._active[0],
ctx = this.chart.ctx,
x = activePoint.tooltipPosition().x,
topY = this.chart.legend.bottom,
bottomY = this.chart.chartArea.bottom;
// draw line
ctx.save();
ctx.beginPath();
ctx.moveTo(x, topY);
ctx.lineTo(x, bottomY);
ctx.lineWidth = 1;
ctx.setLineDash([3,3]);
ctx.strokeStyle = '#FF4949';
ctx.stroke();
ctx.restore();
}
}
});
}
//create chart
var backhaul_wan_mos_chart = new Chart(backhaul_wan_mos_chart, {
type: 'LineWithLine',
data: {
labels: ['Aug 1', 'Aug 2', 'Aug 3', 'Aug 4', 'Aug 5', 'Aug 6', 'Aug 7', 'Aug 8'],
datasets: [{
label: 'Series 1',
data: [15, 16, 17, 18, 16, 18, 17, 14, 19, 16, 15, 15, 17],
pointRadius: 0,
fill: false,
borderDash: [3, 3],
borderColor: '#0F1731',
// backgroundColor: '#FF9CE9',
// pointBackgroundColor: ['#FB7BDF'],
borderWidth: 1
}],
// lineAtIndex: 2,
},
options: {
tooltips: {
intersect: false
},
legend: {
display: false
},
scales: {
xAxes: [{
gridLines: {
offsetGridLines: true
},
ticks: {
fontColor: '#878B98',
fontStyle: "600",
fontSize: 10,
fontFamily: "Poppins"
}
}],
yAxes: [{
display: true,
stacked: true,
ticks: {
min: 0,
max: 50,
stepSize: 10,
fontColor: '#878B98',
fontStyle: "500",
fontSize: 10,
fontFamily: "Poppins"
}
}]
},
responsive: true,
}
});
my output of the code is as follow in WAN MoS Score graph --
So I want to have an horizontal line with the same vertical line together when I hover on the intersection (plotted) point..
Please help my guys..Thanks in advance.
You can just add a second draw block for the y coordinate that you get from the tooltip, first you move to the left of the chartArea that you can get the same way you got bottom and top and then you move to the right on the same Y
Chart.defaults.LineWithLine = Chart.defaults.line;
Chart.controllers.LineWithLine = Chart.controllers.line.extend({
draw: function(ease) {
Chart.controllers.line.prototype.draw.call(this, ease);
if (this.chart.tooltip._active && this.chart.tooltip._active.length) {
var activePoint = this.chart.tooltip._active[0],
ctx = this.chart.ctx,
x = activePoint.tooltipPosition().x,
y = activePoint.tooltipPosition().y,
topY = this.chart.legend.bottom,
bottomY = this.chart.chartArea.bottom,
left = this.chart.chartArea.left,
right = this.chart.chartArea.right;
// Set line opts
ctx.save();
ctx.lineWidth = 1;
ctx.setLineDash([3, 3]);
ctx.strokeStyle = '#FF4949';
// draw vertical line
ctx.beginPath();
ctx.moveTo(x, topY);
ctx.lineTo(x, bottomY);
ctx.stroke();
// Draw horizontal line
ctx.beginPath();
ctx.moveTo(left, y);
ctx.lineTo(right, y);
ctx.stroke();
ctx.restore();
}
}
});
var options = {
type: 'LineWithLine',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderWidth: 1
},
{
label: '# of Points',
data: [7, 11, 5, 8, 3, 7],
borderWidth: 1
}
]
},
options: {
}
}
var ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.js"></script>
</body>
Edit:
You should use a custom plugin for this since you dont draw everytime you move the cursor and you can enforce this by using a custom plugin:
const options = {
type: 'line',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderWidth: 1
},
{
label: '# of Points',
data: [7, 11, 5, 8, 3, 7],
borderWidth: 1
}
]
},
options: {
plugins: {
corsair: {
dash: [2, 2],
color: 'red',
width: 3
}
}
},
plugins: [{
id: 'corsair',
afterInit: (chart) => {
chart.corsair = {
x: 0,
y: 0
}
},
afterEvent: (chart, evt) => {
const {
chartArea: {
top,
bottom,
left,
right
}
} = chart;
const {
x,
y
} = evt;
if (x < left || x > right || y < top || y > bottom) {
chart.corsair = {
x,
y,
draw: false
}
chart.draw();
return;
}
chart.corsair = {
x,
y,
draw: true
}
chart.draw();
},
afterDatasetsDraw: (chart, _, opts) => {
const {
ctx,
chartArea: {
top,
bottom,
left,
right
}
} = chart;
const {
x,
y,
draw
} = chart.corsair;
if (!draw) {
return;
}
ctx.lineWidth = opts.width || 0;
ctx.setLineDash(opts.dash || []);
ctx.strokeStyle = opts.color || 'black'
ctx.save();
ctx.beginPath();
ctx.moveTo(x, bottom);
ctx.lineTo(x, top);
ctx.moveTo(left, y);
ctx.lineTo(right, y);
ctx.stroke();
ctx.restore();
}
}]
}
const ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.js"></script>
</body>
Edit:
Updated answer for v3
const options = {
type: 'line',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderWidth: 1
},
{
label: '# of Points',
data: [7, 11, 5, 8, 3, 7],
borderWidth: 1
}
]
},
options: {
plugins: {
corsair: {
dash: [2, 2],
color: 'red',
width: 3
}
}
},
plugins: [{
id: 'corsair',
afterInit: (chart) => {
chart.corsair = {
x: 0,
y: 0
}
},
afterEvent: (chart, evt) => {
const {
chartArea: {
top,
bottom,
left,
right
}
} = chart;
const {
event: {
x,
y
}
} = evt;
if (x < left || x > right || y < top || y > bottom) {
chart.corsair = {
x,
y,
draw: false
}
chart.draw();
return;
}
chart.corsair = {
x,
y,
draw: true
}
chart.draw();
},
afterDatasetsDraw: (chart, _, opts) => {
const {
ctx,
chartArea: {
top,
bottom,
left,
right
}
} = chart;
const {
x,
y,
draw
} = chart.corsair;
if (!draw) {
return;
}
ctx.lineWidth = opts.width || 0;
ctx.setLineDash(opts.dash || []);
ctx.strokeStyle = opts.color || 'black'
ctx.save();
ctx.beginPath();
ctx.moveTo(x, bottom);
ctx.lineTo(x, top);
ctx.moveTo(left, y);
ctx.lineTo(right, y);
ctx.stroke();
ctx.restore();
}
}]
}
const ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.8.0/chart.js"></script>
</body>
This is 2022, the current version of ChartJS is 4.0.1. So, I recommend to use this new implementation.
First, let's define a plugin. ChartJS's plugins has an id parameter, in this case less say corsair.
Then we define default variables for our plugin, like width, color and line dash. Additionally, our plugin will have three parameters: x, y, and draw. x and y are the values of the mousemove event and draw represents the inChartArea parameter, this parameter defines if the event occurred inside of the chart area or not.
Finally, we capture the afterDraw hook to draw a vertical and horizontal lines based on the x and y values if the event was triggered inside of the chart area.
ChartJS has various hooks to capture different parts of the chart render cycle.
const plugin = {
id: 'corsair',
defaults: {
width: 1,
color: '#FF4949',
dash: [3, 3],
},
afterInit: (chart, args, opts) => {
chart.corsair = {
x: 0,
y: 0,
}
},
afterEvent: (chart, args) => {
const {inChartArea} = args
const {type,x,y} = args.event
chart.corsair = {x, y, draw: inChartArea}
chart.draw()
},
beforeDatasetsDraw: (chart, args, opts) => {
const {ctx} = chart
const {top, bottom, left, right} = chart.chartArea
const {x, y, draw} = chart.corsair
if (!draw) return
ctx.save()
ctx.beginPath()
ctx.lineWidth = opts.width
ctx.strokeStyle = opts.color
ctx.setLineDash(opts.dash)
ctx.moveTo(x, bottom)
ctx.lineTo(x, top)
ctx.moveTo(left, y)
ctx.lineTo(right, y)
ctx.stroke()
ctx.restore()
}
}
const data = {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderWidth: 1
},
{
label: '# of Points',
data: [7, 11, 5, 8, 3, 7],
borderWidth: 1
}
]
}
const options = {
maintainAspectRatio: false,
hover: {
mode: 'index',
intersect: false,
},
plugins: {
corsair: {
color: 'black',
}
}
}
const config = {
type: 'line',
data,
options,
plugins: [plugin],
}
const $chart = document.getElementById('chart')
const chart = new Chart($chart, config)
<div class="wrapper" style="width: 98vw; height: 180px">
<canvas id="chart"></canvas>
</div>
<script src="https://unpkg.com/chart.js#4.0.1/dist/chart.umd.js"></script>
I have done exactly this (but vertical line only) in a previous version of one of my projects. Unfortunately this feature has been removed but the older source code file can still be accessed via my github.
The key is this section of the code:
Chart.defaults.LineWithLine = Chart.defaults.line;
Chart.controllers.LineWithLine = Chart.controllers.line.extend({
draw: function(ease) {
Chart.controllers.line.prototype.draw.call(this, ease);
if (this.chart.tooltip._active && this.chart.tooltip._active.length) {
var activePoint = this.chart.tooltip._active[0],
ctx = this.chart.ctx,
x = activePoint.tooltipPosition().x,
topY = this.chart.legend.bottom,
bottomY = this.chart.chartArea.bottom;
// draw line
ctx.save();
ctx.beginPath();
ctx.moveTo(x, topY);
ctx.lineTo(x, bottomY);
ctx.lineWidth = 0.5;
ctx.strokeStyle = '#A6A6A6';
ctx.stroke();
ctx.restore();
}
}
});
Another caveat is that the above code works with Chart.js 2.8 and I am aware that the current version of Chart.js is 3.1. I haven't read the official manual on the update but my personal experience is that this update is not 100% backward-compatible--so not sure if it still works if you need Chart.js 3. (But sure you may try 2.8 first and if it works you can then somehow tweak the code to make it work on 3.1)

ChartJS - Moving vertical line is display on top of tooltip

Hello,
I've followed this post (Moving vertical line when hovering over the chart using chart.js) to draw a vertical line on my chart.
With a single dataset, it's working just fine.
But for a multiple datasets display (with stacked options on the y-axis), the vertical line is drawn over the chart's tooltip.
Neither setting the z-index of the chart's tooltip nor the vertical line could solve my problem. Since I can't find any property to do that.
Do you have any idea/suggestion to solve this issue?
I'm using react-chart-js 2 with chart-js ^2.9.4 as a peer dependency.
You can use a custom plugin that draws after all the datasets have drawn but before the tooltip is drawn:
var options = {
type: 'line',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderWidth: 1
},
{
label: '# of Points',
data: [7, 11, 5, 8, 3, 7],
borderWidth: 1
}
]
},
options: {
scales: {
yAxes: [{
stacked: true
}]
},
plugins: {
customLine: {
width: 5,
color: 'pink'
}
}
},
plugins: [{
id: 'customLine',
afterDatasetsDraw: (chart, x, opts) => {
const width = opts.width || 1;
const color = opts.color || 'black'
if (!chart.active || chart.active.length === 0) {
return;
}
const {
chartArea: {
top,
bottom
}
} = chart;
const xValue = chart.active[0]._model.x
ctx.lineWidth = width;
ctx.strokeStyle = color;
ctx.beginPath();
ctx.moveTo(xValue, top);
ctx.lineTo(xValue, bottom);
ctx.stroke();
}
}]
}
var ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.js"></script>
</body>

ChartJS - Show values in the center of each bar

See image below, I'm trying to set the value of each bar in the center of my stacked bar; so far I only got on the top and sometimes the position is off (see the 4% yellow in the third bar)
This is the code:
context.data.datasets.forEach(function (dataset) {
for (var i = 0; i < dataset.data.length; i++) {
var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model,
scale_max = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._yScale.maxHeight;
var textY = model.y + 50;
if ((scale_max - model.y) / scale_max >= 0.5)
textY = model.y + 20;
fadeIn(ctx, dataset.data[i], model.x, textY, model.y > topThreshold, step);
}
});
var fadeIn = function(ctx, obj, x, y, black, step) {
var ctx = modifyCtx(ctx);
var alpha = 0;
ctx.fillStyle = black ? 'rgba(' + outsideFontColor + ',' + step + ')' : 'rgba(' + insideFontColor + ',' + step + ')';
ctx.fillText(obj.toString() + "%", x, y);
};
This can be done with the Plugin Core API. The API offers different hooks that may be used for executing custom code (that's probably what you already do). In your case, you can use the afterDraw hook as follows to draw text at the desired positions.
afterDraw: chart => {
let ctx = chart.chart.ctx;
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = "12px Arial";
let xAxis = chart.scales['x-axis-0'];
let yAxis = chart.scales['y-axis-0'];
let datasets = chart.chart.data.datasets.filter(ds => !ds._meta[0].hidden);
xAxis.ticks.forEach((value, xIndex) => {
let x = xAxis.getPixelForTick(xIndex);
datasets.forEach((dataset, iDataset) => {
if (dataset.data[xIndex] > 3) {
let yValue = datasets.slice(0, iDataset)
.map(ds => ds.data[xIndex])
.reduce((a, b) => a + b, 0) +
dataset.data[xIndex] / 2;
let y = yAxis.getPixelForValue(yValue);
ctx.fillStyle = dataset.textColor;
ctx.fillText(dataset.data[xIndex] + '%', x, y);
}
});
});
ctx.restore();
}
Please take a look at below runnable code and see how it works.
const chart = new Chart('myChart', {
type: 'bar',
plugins: [{
afterDraw: chart => {
let ctx = chart.chart.ctx;
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = "12px Arial";
let xAxis = chart.scales['x-axis-0'];
let yAxis = chart.scales['y-axis-0'];
let datasets = chart.chart.data.datasets.filter(ds => !ds._meta[0].hidden);
xAxis.ticks.forEach((value, xIndex) => {
let x = xAxis.getPixelForTick(xIndex);
datasets.forEach((dataset, iDataset) => {
if (dataset.data[xIndex] > 3) {
let yValue = datasets.slice(0, iDataset)
.map(ds => ds.data[xIndex])
.reduce((a, b) => a + b, 0) +
dataset.data[xIndex] / 2;
let y = yAxis.getPixelForValue(yValue);
ctx.fillStyle = dataset.textColor;
ctx.fillText(dataset.data[xIndex] + '%', x, y);
}
});
});
ctx.restore();
}
}],
data: {
labels: ['A', 'B', 'C', 'D', 'E'],
datasets: [{
label: 'Dataset 1',
data: [2.5, 48, 9, 17, 23],
backgroundColor: 'red',
textColor: 'white'
}, {
label: 'Dataset 2',
data: [2.5, 4, 4, 11, 11],
backgroundColor: 'orange',
textColor: 'black'
}, {
label: 'Dataset 3',
data: [95, 48, 87, 72, 66],
backgroundColor: 'green',
textColor: 'white'
}]
},
options: {
scales: {
xAxes: [{
stacked: true
}],
yAxes: [{
stacked: true,
ticks: {
beginAtZero: true
}
}]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script>
<canvas id="myChart" height="160"></canvas>

Extend bar chart on Chart JS 2 into a new type of Chart

I'm actualy using Chart JS 2.0.1 to draw charts on a page.
My customers asked me to add a line in a bar chart so that they can see the limit they can't go over. Like that: Bar chart with line on y axes
So, I'm trying to extend the Bar Chart into a new one which takes a parameter called lineAtValue which provides the y value for the line.
I succeeded in extending the bar chart but it overrides the others bar charts displayed in the page and I don't need that in the other Bar Charts.
Here is what I did : http://jsfiddle.net/d5ye1xpe/
And I'd like to be able to have something like this one : jsfiddle.net/L3uhpvd5/ (sorry I can't upload more than two links) with the
Chart.barWithLine(ctx,config);
But with the version 2.0.1 of Chart JS
Thanks,
Ptournem
If this helps, I rewrite #Ptournem answer to be a valid 2.3.0 plugin with some sort of configutation
Chart.plugins.register({
config: {
/** #type {rbg|rgba|hex} Stroke color */
strokeColor: "rgb(255, 0, 0)",
/** #type {int} Column width */
lineWidth: 1,
},
afterDatasetsDraw: function(chartInstance, easing) {
var value = chartInstance.config.lineAtValue;
if (typeof value === 'undefined') return;
var ctx = chartInstance.chart.ctx,
xaxis = chartInstance.scales['x-axis-0'],
yaxis = chartInstance.scales['y-axis-0'];
ctx.save();
ctx.beginPath();
ctx.moveTo(xaxis.left, yaxis.getPixelForValue(value));
ctx.lineWidth = this.config.lineWidth;
ctx.strokeStyle = this.config.strokeColor;
ctx.lineTo(xaxis.right, yaxis.getPixelForValue(value));
ctx.stroke();
ctx.restore();
},
// IPlugin interface
afterDatasetsUpdate: function(chartInstance) {},
afterDraw: function(chartInstance, easing) {},
afterEvent: function(chartInstance, event) {},
afterInit: function(chartInstance) {},
afterScaleUpdate: function(chartInstance) {},
afterUpdate: function(chartInstance) {},
beforeRender: function(chartInstance) {},
beforeDatasetsDraw: function(chartInstance, easing) {},
beforeDatasetsUpdate: function(chartInstance) {},
beforeDraw: function(chartInstance, easing) {},
beforeEvent: function(chartInstance, event) {},
beforeInit: function(chartInstance) {},
beforeUpdate: function(chartInstance) {},
destroy: function(chartInstance) {},
resize: function(chartInstance, newChartSize) {},
});
Mixed type charts are supported by Chart 2.x versions.
You can create config like following :-
var config = {
type: 'bar',
data: {
labels: ["January", "February", "March", "April", "May", "June", "July"],
datasets: [{
type: 'bar',
label: "My First dataset",
data: [65, 0, 80, 81, 56, 85, 40],
fill: false
},{
type: 'line',
label: "My Second dataset",
data: [80, 80, 80, 80, 80, 80, 80],
fill: false,
borderColor: 'red',
pointStyle: 'line',
pointBorderWidth: 3
}]
}
};
Created Js Fiddle here: https://jsfiddle.net/nehadeshpande/eu70wzo4/
Please let me know if this is helpful.
Thanks,
Neha
This is helpful but I found it not that optimized to add new Dataset just for a line that is actually not a data.
I finally suceeded in creating the new type that extend the bar type and add a line if the value is provided.
// Store the original Draw function
var originalLineDraw = Chart.controllers.bar.prototype.draw;
// extend the new type
Chart.helpers.extend(Chart.controllers.bar.prototype, {
draw: function () {
// use the base draw function
originalLineDraw.apply(this, arguments);
// get chart and context
var chart = this.chart;
var ctx = chart.chart.ctx;
// get lineAtValue value
var value = chart.config.lineAtValue;
// stop if it doesn't exist
if (typeof value === "undefined") {
return;
}
// draw the line
var xaxis = chart.scales['x-axis-0'];
var yaxis = chart.scales['y-axis-0'];
ctx.save();
ctx.beginPath();
ctx.moveTo(xaxis.left, yaxis.getPixelForValue(value));
ctx.strokeStyle = '#ff0000';
ctx.lineTo(xaxis.right, yaxis.getPixelForValue(value));
ctx.stroke();
ctx.restore();
}
});
But thank you for your help =)

Categories