Searched a lot about this but din't got any answer on this based on chart.js. I had already asked this question here in which I was using highchart.js and got the solution but now I am using chart.js library and trying to find the solution.
Below is my code I have tried. I need to find the intersection point between these 2 line graphs combo. See the Graph Image .
var config = {
type: 'bar',
data: {
labels: ["Year 0", "Year 1", "Year 2", "Year 3", "Year 4", "Year 5", "Year 6"],
datasets: [{
type: 'line',
label: 'Cost',
data: [150, 15, 25, 14, 10, 7],
borderColor: '#E35500',
fill: false,
lineTension: 0,
borderJoinStyle: 'miter',
}, {
type: 'line',
label: 'Cash Flow',
data: [20, 180, 170, 220, 160, 190],
borderColor: '#FFC000',
fill: false,
lineTension: 0,
borderJoinStyle: 'miter',
xAxes: [{
barPercentage: 0.4
}]
},
{
type: 'line',
label: 'Accumulative Flow',
data: [0, -10, 20, 30, 40, 50],
borderColor: 'red',
fill: false,
lineTension: 0,
borderJoinStyle: 'miter',
xAxes: [{
barPercentage: 0.4
}]
}, {
type: 'bar',
label: 'Benifit(One time)',
backgroundColor: "#005998",
data: [40, 50, 60, 80, 50, 60],
}, {
type: 'bar',
label: 'Benifit(Recurring)',
backgroundColor: "#0FAAFF",
data: [120, 150, 150, 180, 120, 140],
}
]
},
options: {
title: {
display: true,
text: 'Custom Chart Title'
},
scales: {
xAxes: [{
time: {
displayFormats: {
quarter: ' YYYY'
}
},
stacked: true,
beginAtZero: true,
barPercentage: 0.3,
id: 'x-axis-label',
position: 'bottom',
scaleStartValue: 20,
gridLines: {
display: false
},
}],
yAxes: [{
stacked: true,
id: 'y-axis-label',
ticks: {
max: 300,
min: -50,
stepSize: 50,
},
position: 'left',
gridLines: {
display: false
},
}]
},
legend: {
position: 'right'
},
maintainAspectRatio: false,
scaleBeginAtZero: true
}
};
var ctx = document.getElementById("myChart").getContext("2d");
new Chart(ctx, config);
.GraphContain {
max-height: 500px;
position: relative;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.0/moment.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.bundle.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.min.js"></script>
<div class="GraphContain">
<canvas id="myChart" width="400" height="400"></canvas>
</div>
Anyone having any idea?? need a genius on this!!!
Here we are )))
https://jsfiddle.net/Falseclock/5nbecn0z/
If you need to show intersections with X axis, then just imitate line with Y=0
https://jsfiddle.net/Falseclock/8g0ucdb1/
var ORDER_STATS = {
"2016" : [10, 181, 194, -56, 130, 181, 179, 189, 30, 60, 193, 154],
"2015" : [124, -50, 152, 187, 10, 164, 129, -16, 115, 119, 129, 171],
"2014" : [-90, 80, 30, 59, 100, -30, 60, 116, 191, 181, -60, 106]
};
var colors = ['206,191,26','119,206,26','26,200,206','236,124,98','206,26,140','26,77,206'];
// Definning X
var ordersChartData = {
labels : ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],
datasets : []
}
Object.keys(ORDER_STATS).forEach(function (key) {
color = colors.shift();
ordersChartData.datasets.push(
{
label: key,
lineTension: 0,
type: 'line',
backgroundColor: "rgba("+color+",0.1)",
borderColor: "rgba("+color+",1)",
borderWidth: 2,
pointBackgroundColor : "rgba("+color+",1)",
pointBorderColor: "#fff",
pointBorderWidth: 1,
pointRadius: 4,
pointHoverBackgroundColor : "#fff",
pointHoverBorderColor: "rgba("+color+",1)",
pointHoverBorderWidth: 1,
data : ORDER_STATS[key]
}
);
});
var ctx = document.getElementById("myChart").getContext("2d");
Chart.defaults.global.defaultFontColor = 'grey';
Chart.defaults.global.defaultFontFamily = "Tahoma";
Chart.defaults.global.defaultFontSize = 11;
Chart.defaults.global.defaultFontStyle = 'normal';
var myChart = new Chart(ctx, {
type: 'line',
data: ordersChartData,
defaultFontSize: 11,
options: {
responsive: true,
title: {
display: true,
text: 'Intersection realization',
fontColor: "#444",
fontFamily: 'Tahoma',
padding: 0
},
legend: {
display: true,
labels: {
fontColor: 'grey',
usePointStyle: true
}
},
tooltips: {
mode: "index",
intersect: true,
position: 'nearest',
bodySpacing: 4
}
}
});
Chart.plugins.register({
afterDatasetsDraw: function(chartInstance, easing) {
var Y = chartInstance.scales['y-axis-0'];
var X = chartInstance.scales['x-axis-0'];
zeroPointY = Y.top + ((Y.bottom - Y.top) / (Y.ticks.length -1) * Y.zeroLineIndex);
zeroPointX = Y.right;
yScale = (Y.bottom - Y.top) / (Y.end - Y.start);
xScale = (X.right - X.left) / (X.ticks.length - 1);
var intersects = findIntersects(ORDER_STATS['2015'], ORDER_STATS['2014'] );
var context = chartInstance.chart.ctx;
intersects.forEach(function (result, idx) {
context.fillStyle = 'red';
context.beginPath();
context.arc((result.x * xScale) + zeroPointX, (Y.end - Y.start) - (result.y * yScale) - ((Y.end - Y.start) - zeroPointY), 3, 0, 2 * Math.PI, true);
context.fill();
});
}
});
function findIntersects(line1, line2)
{
var intersects = [];
line1.forEach(function(val,idx) {
var line1StartX = idx;
var line1StartY = line1[idx];
var line1EndX = idx + 1;
var line1EndY = line1[idx + 1];
var line2StartX = idx;
var line2StartY = line2[idx];
var line2EndX = idx + 1;
var line2EndY = line2[idx+1];
result = checkLineIntersection(line1StartX, line1StartY, line1EndX, line1EndY, line2StartX, line2StartY, line2EndX, line2EndY);
if (result.onLine1 && result.onLine2) {
intersects.push(result);
}
});
return intersects;
}
function checkLineIntersection(line1StartX, line1StartY, line1EndX, line1EndY, line2StartX, line2StartY, line2EndX, line2EndY) {
// if the lines intersect, the result contains the x and y of the intersection (treating the lines as infinite) and booleans for whether line segment 1 or line segment 2 contain the point
var denominator, a, b, numerator1, numerator2, result = {
x: null,
y: null,
onLine1: false,
onLine2: false
};
denominator = ((line2EndY - line2StartY) * (line1EndX - line1StartX)) - ((line2EndX - line2StartX) * (line1EndY - line1StartY));
if (denominator == 0) {
return result;
}
a = line1StartY - line2StartY;
b = line1StartX - line2StartX;
numerator1 = ((line2EndX - line2StartX) * a) - ((line2EndY - line2StartY) * b);
numerator2 = ((line1EndX - line1StartX) * a) - ((line1EndY - line1StartY) * b);
a = numerator1 / denominator;
b = numerator2 / denominator;
// if we cast these lines infinitely in both directions, they intersect here:
result.x = line1StartX + (a * (line1EndX - line1StartX));
result.y = line1StartY + (a * (line1EndY - line1StartY));
/*
// it is worth noting that this should be the same as:
x = line2StartX + (b * (line2EndX - line2StartX));
y = line2StartX + (b * (line2EndY - line2StartY));
*/
// if line1 is a segment and line2 is infinite, they intersect if:
if (a > 0 && a < 1) {
result.onLine1 = true;
}
// if line2 is a segment and line1 is infinite, they intersect if:
if (b > 0 && b < 1) {
result.onLine2 = true;
}
// if line1 and line2 are segments, they intersect if both of the above are true
return result;
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.js"></script>
<canvas id="myChart" width="650" height="241" style="display: block; width: 650px; height: 241px;"></canvas>
Related
I have data and I am creating a stacked bar chart, I want one of the datasets to be a dashed bar chart.
The live: demo
Here is what I have done so far.
HTML.
<canvas id="myChart"></canvas>
And Js.
var ctx = document.getElementById("myChart").getContext('2d');
var myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ["January", "February", "March", "April", "May", "June", "July"],
datasets: [{
label: 'Employee',
backgroundColor: "blue",
data: [12, 59, 5, 56, 58,12, 59, 87, 45],
borderColor:"#45c490",
borderWidth: 0
}, {
label: 'Engineer',
backgroundColor: "orange",
data: [12, 59, 5, 56, 58,12, 59, 85, 23],
borderColor:"#caf270",
borderWidth: 0
}, {
label: 'Government',
backgroundColor: "pink",
data: [12, 59, 5, 56, 58,12, 59, 65, 51],
borderColor: "rgba(255,99,132,1)",
borderWidth: 0
}, {
label: 'Political parties',
backgroundColor: "gray",
data: [12, 59, 5, 56, 58, 12, 59, 12, 74],
borderColor:"#caf270",
borderWidth: 0
}],
},
options: {
tooltips: {
displayColors: true,
callbacks:{
mode: 'x',
},
},
scales: {
xAxes: [{
stacked: true,
gridLines: {
display: false,
}
}],
yAxes: [{
stacked: true,
ticks: {
beginAtZero: true,
},
type: 'linear',
}]
},
responsive: true,
maintainAspectRatio: false,
legend: { position: 'bottom' },
},
plugins: [{
afterDatasetDraw: function(chart, args, options) {
args.meta.data.forEach(function(element) {
var borderWidth = 3;
var ctx = chart.ctx;
var vm = element._view;
var half = vm.width / 2;
var left = vm.x - half;
var right = vm.x + half;
var top = vm.y;
var width = right - left;
var height = chart.chartArea.bottom - top + (borderWidth / 2) - 1;
let label = vm.datasetLabel;
if(label.includes("Government")){
ctx.setLineDash([5,5]);
borderWidth=1;
}else{
ctx.setLineDash([0,0]);
borderWidth=1
}
ctx.beginPath();
ctx.lineWidth = borderWidth;
ctx.strokeStyle = vm.borderColor;
ctx.moveTo(left, top);
ctx.lineTo(left, top + height);
ctx.moveTo(left, top);
ctx.lineTo(left + width, top);
ctx.moveTo(left + width, top);
ctx.lineTo(left + width, top + height);
ctx.stroke();
ctx.save();
});
}
}]
});
I want only if the dataset label is Government then create the dashed bar otherwise just normal
The problem
In the picture above you can see the government dataset has dashed and the blue border.
How can I eliminate this blue border here?
The issue should be in the height calculation because the plugin was thought for no-stacked bar.:
var height = chart.chartArea.bottom - top + (borderWidth / 2) - 1;
As you can see, the height is calculated assuming to go to the bottom of chart area.
You can try the following, overriding the above row:
const valueTop = element._yScale.getValueForPixel(top);
const value = chart.data.datasets[element._datasetIndex].data[element._index];
const bottom = element._yScale.getPixelForValue(valueTop - value);
var height = bottom - top + (borderWidth / 2) - 1;
I have a mixed chart (scatter and line), hence I need 2 xAxis.
There's an extra grid that appears on the bottom of the chart, because that's where the other x axis normally has its labels (I removed them because I don't need them)
I'm trying to get rid of that extra grid, but I can't find how to do it. I've tried to specify the ticks color as white but the grid's still there.
Look at the image, it's that solitary extra grid at the bottom (marked with an horizontal bracket):
This is my code:
const ctx = document.getElementById('myChart');
const labels1 = ['A','R','Fyo','A-MAC','HR','Do','Rs','Dpr','GM','GF','EPK','EPS','Is1','Is2','Is3','FP','INVAR','INVER'];
const lasIs = (ctx, value) => ctx.p0DataIndex > 11 ? value: undefined;
const markedIdxs = [0,5,10,15,20,25,30,35,44, //... and many more];
const markedVals = [0,5,10,15,20,25,30,35,10,// ... and many more];
const data1 = {
labels: labels1,
datasets: [
{
type: 'line',
label: 'Line Dataset',
data: [65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,65],
pointRadius: 0,
borderWidth: 1,
borderColor: '#0070C5',
xAxisID: 'x2'
},
{
type: 'line',
label: 'Line Dataset',
data: [50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50],
pointRadius: 0,
borderWidth: 1,
borderColor: '#0070C5',
xAxisID: 'x2'
},
{
type: 'line',
label: 'Line Dataset',
data: [102, 38, 54, 56, 102, 38, 54, 56, 102, 38, 54, 56, 102, 38, 54, 87, 62, 91],
pointStyle: 'circle',
pointRadius: 5,
borderWidth: 2,
borderColor: 'rgba(0, 120, 161,0.5)',
backgroundColor: 'rgba(0, 120, 161,0.5)',
fill: true,
segment: {
borderColor: ctx => lasIs (ctx, 'rgba(0, 168, 172, 0.5'),
backgroundColor: ctx => lasIs (ctx, 'rgba(0, 168, 172, 0.5'),
},
xAxisID: 'x2'
},
{
type: 'scatter',
pointStyle: 'dash',
pointRadius: 6,
borderColor: 'rgba(0, 0, 255,0.2)',
xAxisID: 'x',
data: [{x:1, y:36}, {x:1, y:37}, {x:1, y:39}, {x:1, y:40}, {x:1, y:42}... //and many more]
},
],
};
const multiplos5 = {
id: 'multiplos5',
afterDatasetsDraw(chart, args, options) {
const {ctx} = chart;
ctx.save();
ctx.font = "8px Verdana";
ctx.fillStyle = "#0070C5";
yaSeDibujo=84;
paddLeft = 0;
for (i=0; i < markedIdxs.length; i++) {
if (markedVals[i] < 10)
paddLeft=8;
else
paddLeft=12;
ctx.fillText(markedVals[i], chart.getDatasetMeta(3).data[markedIdxs[i]].x - paddLeft, chart.getDatasetMeta(3).data[markedIdxs[i]].y + 3);
}
}
};
Chart.register(multiplos5);
const myChart = new Chart(ctx, {
data: data1,
options: {
scales: {
x2: { // add extra axes
min: 0,
max: 18,
position: 'bottom',
type: 'category',
ticks: {color: '#0070C5'},
offset: true
},
x: {
min: 1,
max: 18,
ticks: {stepSize: 1, display: false},
offset: true
},
y: {
min: 20,
max: 120,
ticks: {stepSize: 10, color: '#555555', crossAlign: 'start'},
grid:{color:'#f1f1f1', display:true},
},
y2: {
position: 'right',
min: 20,
max: 120,
ticks: {stepSize: 10, color: '#555555'},
grid:{display:false}
},
},
plugins: {
multiplos5,
legend: false,
tooltip: {
displayColors: false,
filter: function (tooltipItem) {
return tooltipItem.datasetIndex == 2;
},
callbacks: {
//title: "el tÃtulo",
label: function(context) {
let label = labels2[context.dataIndex] + ': ' + context.parsed.y;
return label;
}
}
}
},
},
});
Chart.js version is 3.6.2
Any help/workaround will be really appreciated.
Regards!!
Forgot to mention that the axis I'm talking about is 'x' (xAxisID: 'x')
In your config for the X axis where you set the ticks to display false, if you instead do this in the root of the X scale config it will hide that line so you will get:
scales:{
x: {
display: false,
min: 1,
max: 18,
offset: true
}
}
My Chart.js plot uses two Y-Axis: both of them must have the same scale and max/min values. I update the second Y-Axis step depending on a boolean value I dinamically set and everything is working flawless. Still, I want to update the second Y-Axis (redrawn it) when I trigger a legend event (clicking on one of the legends). I can't really make it work: the plot refresh, but my second Y-Axis step doesn't change.
Here my draw function. I've read the Chart.js documentation I've found here, but I can really understand how to fill my legend onClick event.
function drawChart(pointsArray, curveFeatures) {
let minX = parseFloat(curveFeatures.minX);
let minY = parseFloat(curveFeatures.minY);
let maxX = parseFloat(curveFeatures.maxX);
let maxY = parseFloat(curveFeatures.maxY);
let stepResult = parseInt(curveFeatures.stepResult);
let startX = parseFloat(curveFeatures.startX);
let endX = parseFloat(curveFeatures.endX);
let upperLimit = parseFloat(curveFeatures.upperLimit);
let lowerLimit = parseFloat(curveFeatures.lowerLimit);
let stepSize;
let borderColour;
let backgroundColour;
if (stepResult === 1) {
stepSize = 0.5;
maxY = parseFloat(maxY.toFixed(2)) + stepSize;
minY = parseFloat(minY.toFixed(2)) - stepSize;
borderColour = "rgba(1, 165, 15, 1)";
backgroundColour = "rgba(1, 165, 15, 0.2)";
} else {
stepSize = 0.01;
maxY = parseFloat(maxY.toFixed(2));
minY = parseFloat(minY.toFixed(2));
borderColour = "rgba(252, 45, 45, 1)";
backgroundColour = "rgba(252, 45, 45, 0.2)";
}
let ctx = document.getElementById("myChart").getContext("2d");
let myChart = new Chart(ctx, {
type: "line",
data: {
datasets: [{
label: "Press Signal",
cubicInterpolationMode: "monotone",
fill: false,
borderWidth: 5,
radius: 1,
borderColor: borderColour,
backgroundColor: backgroundColour,
pointBorderColor: borderColour,
pointBackgroundColor: backgroundColour,
data: pointsArray
}, {
label: "Evaluation Windows",
cubicInterpolationMode: "monotone",
fill: true,
borderWidth: 2,
radius: 2,
borderColor: "rgb(94, 233, 255, 1)",
backgroundColor: "rgb(94, 233, 255, 0.2)",
pointBorderColor: "rgb(94, 233, 255, 1)",
pointBackgroundColor: "rgb(94, 233, 255, 0.2)",
data: [{
x: startX,
y: lowerLimit
}, {
x: startX,
y: upperLimit
}, {
x: endX,
y: upperLimit
}, {
x: endX,
y: lowerLimit
}, {
x: startX,
y: lowerLimit
}]
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
animation: {
duration: 2000
},
layout: {
padding: {
left: 50,
right: 50,
top: 50,
bottom: 10
}
},
title: {
display: true,
text: "Press Signal",
fontSize: 30,
padding: 20
},
legend: {
labels: {
fontSize: 16
},
onClick: function(e, legendItem) {
stepSize = 0.01;
// Should I do things, here?
ci.update();
}
},
scales: {
xAxes: [{
display: true,
type: "linear",
position: "bottom",
ticks: {
fontSize: 14,
},
scaleLabel: {
display: true,
fontSize: 16,
fontColor: "black",
labelString: "Position (Abs.) [mm]"
}
}],
yAxes: [{
display: true,
type: "linear",
position: "left",
ticks: {
fontSize: 14,
},
scaleLabel: {
display: true,
fontSize: 16,
fontColor: "black",
labelString: "Force [kN]"
}
}, {
display: true,
type: "linear",
position: "right",
ticks: {
fontSize: 14,
beginAtZero: false,
max: maxY,
min: minY,
stepSize: stepSize
},
afterTickToLabelConversion: function(scaleInstance) {
if (stepResult === 1) {
// set the first and last tick to null so it does not display
// note, ticks[0] is the last tick and ticks[length - 1] is the first
scaleInstance.ticks[0] = null;
scaleInstance.ticks[scaleInstance.ticks.length - 1] = null;
// need to do the same thing for this similiar array which is used internally
scaleInstance.ticksAsNumbers[0] = null;
scaleInstance.ticksAsNumbers[scaleInstance.ticksAsNumbers.length - 1] = null;
}
}
}]
}
}
});
}
I've found my way. Here a snippet for whoever will have my same problem:
onClick: function(event, legendItem) {
let index = 1;
myChart.data.datasets[index].hidden = !myChart.data.datasets[index].hidden;
if (myChart.data.datasets[index].hidden) {
myChart.options.scales.yAxes[index].ticks.max = maxY;
myChart.options.scales.yAxes[index].ticks.min = minY;
myChart.options.scales.yAxes[index].ticks.stepSize = stepSize;
} else {
if (stepResult === 0) {
myChart.options.scales.yAxes[index].ticks.max = 3.5;
myChart.options.scales.yAxes[index].ticks.min = 0;
myChart.options.scales.yAxes[index].ticks.stepSize = 0.5;
}
}
myChart.update();
}
I'm trying to write a chart.js (v.2.7) plugin to show errorbars on my scatter / line plots.
I managed to get the pixel positions from the chart and draw the errorbars on the canvas, but i cannot get the timing to look right. I want them to appear after the lines animate, then stay attached (& move with) if the datasets are hidden->shown.
I've tried:
The afterDraw / afterDataset(s)Draw / beforeDraw hooks: the error bars are already on the plot before the lines animate (as in example). When hidden->shown, the error bars are in place.
afterRender / afterEvent hook: draws them after the animation finishes, but then redraws them everytime the datasets are hidden->shown (after a pause)
beforeRender / any earlier hooks: gives no errorbars
setTimout() on the draw function, or at various places inside it: does nothing
sleep() before the draw function or other places: slows down the whole animation but the errorsbars are unaffected.
I couldn't find a way to invoke plugin functions after animation or get to then via the options.animation.onComplete
Is there a way to get the errorbars to behave as in the example, but the initial appearance occurs after the line animation (with a plugin?)
var errorbarPlugin = {
calcPoints: function(chartInstance, dataList){
var ds = chartInstance.data.datasets
var meta = chartInstance.getDatasetMeta(0)
var yScale = chartInstance.scales[meta.yAxisID];
var xScale = chartInstance.scales[meta.xAxisID];
var yList = []; var xList = [];
for(var i = 0; i < dataList.length; i++){
var yValue = dataList[i].y
var yPixel = yScale.getPixelForValue(yValue)
yList.push(yPixel)
var xValue = dataList[i].x
var xPixel = xScale.getPixelForValue(xValue)
xList.push(xPixel)
}
return {yList: yList, xList: xList}
},
calcErrorbars: function(chartInstance, ds_num){
var ds = chartInstance.data.datasets
var data_list = ds[ds_num].data
var isHidden = ds[ds_num]._meta[Object.keys(chartInstance.data.datasets[ds_num]._meta)[0]].hidden;
var yList = this.calcPoints(chartInstance, data_list).yList
var xList = this.calcPoints(chartInstance, data_list).xList
if(ds[ds_num].errors){
var errors = ds[ds_num].errors
} else {errors = 0}
return [xList, yList, errors, isHidden]
},
drawErrorbars: function(chartInstance){
var ctx = chartInstance.chart.ctx
var ds = chartInstance.data.datasets
for(var ds_num = 0; ds_num < ds.length; ds_num++){
var errCalc = this.calcErrorbars(chartInstance, ds_num)
var isHidden = errCalc[3]
var yList = errCalc[1]
var xList = errCalc[0]
var errors = errCalc[2]
var errWidth = 3
var capLen = 5
if(!isHidden){
for(var k = 0; k < xList.length; k++){
ctx.strokeStyle = "red"
ctx.beginPath();
ctx.moveTo(xList[k], yList[k]-errors[k]);
ctx.lineTo(xList[k], yList[k]+errors[k]);
ctx.moveTo(xList[k]-capLen, yList[k]+errors[k]);
ctx.lineTo(xList[k]+capLen, yList[k]+errors[k]);
ctx.moveTo(xList[k]-capLen, yList[k]-errors[k]);
ctx.lineTo(xList[k]+capLen, yList[k]-errors[k]);
ctx.stroke()
}
}
}
},
afterDatasetsDraw: function(chartInstance) {
this.drawErrorbars(chartInstance)
},
}
<html>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.js"></script>
<script type="text/javascript" src="{% static 'js/charts/errorbarPlugin3.js' %}"></script>
<div id="canvas-holder" class="col-sm-3">
<canvas id="chart-gamma" width="200" height="200"/></canvas>
</div>
<script defer>
var gammaChartData = {
datasets: [
{
label: 'Red',
data: [{x: 15, y: 30}, {x: 35, y: 17}, {x: 55, y: 37}, {x: 72, y: 45},],
borderColor: "red",
errors: [10, 28, 30, 34],
},
]
}
var ctx_gamma = document.getElementById("chart-gamma").getContext("2d");
window.onload = function() {
var gamma_chart = new Chart(ctx_gamma, {
type: 'scatter',
data: gammaChartData,
plugins: [errorbarPlugin],
options: {showLines: true},
});
};
</script>
</body>
</html>
Edit: shortened snippet by removing formatting and default options
Here's what I came up with (snippet below)
The errorbars can either be fixed to the data or shown independantly, and animate in / are hidden / revealed with the data.
The plugin can add x or y axis errors, repesented as bars (with/without caps) or ovals / circles (filled or transparent).
"use strict";
var errorbarPlugin = {
afterDraw: function (chart) {
var type = chart.config.type;
var plugConfig = chart.config.options.errorbarPlugin;
if (plugConfig) {
if (plugConfig.showErrors) {
var showErrors = plugConfig.showErrors;
}
}
else
showErrors = true;
if (showErrors !== false) {
if (["line", "scatter"].includes(type)) {
errorbarPlugin.scatterErrorbars(chart);
}
else if (type == "bar") {
console.log("Bar charts not supported yet");
}
}
},
scatterErrorbars: function (chart) {
var ctx = chart.ctx;
var plugConfig = chart.config.options.errorbarPlugin;
chart.data.datasets.forEach(function (dataset, i) {
var ds = dataset;
var meta = chart.getDatasetMeta(i);
var showErrors;
(ds.showErrors === false) ? showErrors = false : showErrors = true;
var errWidth;
(ds.errWidth) ? errWidth = ds.errWidth : errWidth = 1;
var showCap;
(ds.showCap) ? showCap = ds.showCap : showCap = true;
var capLen;
(ds.capLen) ? capLen = ds.capLen : capLen = 3;
var errStyle;
(ds.errStyle) ? errStyle = ds.errStyle : errStyle = "T";
var errFillColor;
(ds.errFillColor) ? errFillColor = ds.errFillColor : errFillColor = "rgba(0,0,0,0)";
if (!meta.hidden && showErrors) {
meta.data.forEach(function (element, index) {
var x_point = element._model.x;
var y_point = element._model.y;
var errColor;
(ds.errColor) ? errColor = ds.errColor : errColor = element._view.borderColor;
var dataPoint = ds.data[index];
var yError;
var xError;
if (typeof (dataPoint) === "object" && 'r' in dataPoint) {
yError = dataPoint.r;
}
else if (ds.errors) {
yError = ds.errors[index];
}
else {
yError = null;
}
if (typeof (dataPoint) === "object" && dataPoint.e) {
xError = dataPoint.e;
}
else if (ds.xErrors) {
xError = ds.xErrors[index];
}
else {
xError = null;
}
var position = element.tooltipPosition();
if (errStyle == "circle") {
ctx.beginPath();
ctx.arc(position.x, position.y, yError, 0, 2 * Math.PI, false);
if (ds.hidden === true && meta.hidden === null) {
ctx.strokeStyle = "rgba(0,0,0,0)";
ctx.fillStyle = "rgba(0,0,0,0)";
}
else {
ctx.strokeStyle = errColor;
ctx.fillStyle = errFillColor;
}
console.log(meta.hidden);
ctx.fill();
ctx.stroke();
}
else if (errStyle == "oval" || errStyle == "ellipse") {
if (xError) {
var scaleFac = (xError) / yError;
}
else
scaleFac = 10 / yError;
ctx.beginPath();
ctx.save();
ctx.scale(scaleFac, 1);
ctx.arc(position.x / scaleFac, position.y, yError, 0, 2 * Math.PI, false);
ctx.restore();
if (ds.hidden === true && meta.hidden === null) {
ctx.strokeStyle = "rgba(0,0,0,0)";
}
else {
ctx.strokeStyle = errColor;
}
ctx.stroke();
}
else {
ctx.beginPath();
ctx.moveTo(position.x, position.y - yError);
ctx.lineTo(position.x, position.y + yError);
if (xError) {
ctx.moveTo(position.x - xError, position.y);
ctx.lineTo(position.x + xError, position.y);
}
if (ds.hidden === true && meta.hidden === null) {
ctx.strokeStyle = "rgba(0,0,0,0)";
}
else {
ctx.strokeStyle = errColor;
}
ctx.stroke();
if (showCap) {
ctx.beginPath();
ctx.moveTo(position.x - capLen, position.y - yError);
ctx.lineTo(position.x + capLen, position.y - yError);
ctx.moveTo(position.x - capLen, position.y + yError);
ctx.lineTo(position.x + capLen, position.y + yError);
if (xError) {
ctx.moveTo(position.x - xError, position.y - capLen);
ctx.lineTo(position.x - xError, position.y + capLen);
ctx.moveTo(position.x + xError, position.y - capLen);
ctx.lineTo(position.x + xError, position.y + capLen);
}
ctx.stroke();
}
}
});
}
});
}
};
<!DOCTYPE html>
<!--DOCTYPE html -->
<html>
<head>
<script src='https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.min.js'></script>
<body>
<div style = "position:relative;
width:60%;" >
<div id="canvas-holder" class="col-sm-6">
<canvas id="chart-gamma" width="500" height="500"/></canvas>
</div>
<div id="canvas-holderbf2" class="col-sm-6">
<canvas id="chart-humid" width="500" height="500"/></canvas>
</div>
<script defer>
Chart.defaults.global.legend.display = true
Chart.defaults.global.legend.position = 'right'
// Chart.defaults.global.legend.onHover = function(){}
// Chart.defaults.global.legend.onClick = function(){}
Chart.defaults.global.legend.labels.usePointStyle = true
Chart.defaults.global.legend.labels.fontsize = 12
Chart.defaults.global.legend.labels.padding = 10
var gammaChartData = {
datasets: [
{
label: 'Eu',
data: [{x: 5, y: 45}, {x: 10, y: 100}, {x: 25, y: 120}, {x: 50, y: 125}, {x: 100, y: 150}, {x: 120, y: 250},],
borderColor: "red",
//fillColor: "pink",
errors: [15, 20, 30, 12, 10, 10],
xErrors: [3, 7, 16, 12, 12, 30, 10],
//hidden: true,
errColor: "blue",
errStyle: "circle",
errFillColor: "pink",
hidden: true,
errWidth: 2,
showCap: true,
capLen: 3,
showErrors: true,
},
{
label: 'Am',
data: [{x: 15, y: 85, r: 14}, {x: 25, y: 37, r: 8}, {x: 62, y: 135, r: 44},],
borderColor: "blue",
errColor: "red",
errStyle: "circle",
showErrors: true,
},
]
}
var options_gamma = {
animation: {
duration: 1000,
},
errorbarPlugin: {
showErrors: true,
marginsOfError: [100, 50, 10],
},
elements: {
line: { fill: false,
borderWidth: 1,
},
point: { radius: 0,
pointStyle: 'circle',
borderWidth: 1,
hitRadius: 18, //size if should hover
// hoverBorderWidth: 13,
hoverRadius: 10, //size when hovered
},
},
annotation: {
annotations: [{
id: 'h-line-01', // optional
type: 'line',
mode: 'horizontal',
scaleID: 'y-axis-0',
value: '125',
borderColor: 'red',
borderDash: [2, 2],
borderWidth: 2,
label: {
enabled: true,
backgroundColor: 'rgba(255,255,255,1)', // Background color of label, default below
//fontFamily: "sans-serif", // Font family of text, inherits from global
fontStyle: "normal", // Font style of text, default "bold"
fontSize: 12, // Font size of text, inherits from global
fontColor: "red",// Font color of text, default below
xPadding: 5,// Padding of label to add top/bottom, default below
yPadding: 5,// Radius of label rectangle, default below
cornerRadius: 10, // Anchor position of label on line, can be one of: top, bottom, left, right, center. Default below.
position: "left", // Adjustment along x-axis (left-right) of label relative to above number (can be negative)
// For horizontal lines positioned left or right, negative values move the label toward the edge, and negative values toward the center.
xAdjust: 290, // Adjustment along y-axis (top-bottom) of label relative to above number (can be negative)
// For vertical lines positioned top or bottom, negative values move the label toward the edge, and negative values toward the center.
yAdjust: 0, // Whether the label is enabled and should be displayed
// Text to display in label - default is null
content: "Max"
},
onClick: function(e) { // Fires when the user clicks this annotation on the chart (be sure to enable the event in the events array below).
}
}],
},
responsive: true,
showLines: true,
hoverMode: 'single', // should always use single for a scatter chart
legend: {},
scales: {
yAxes: [{
display: true,
position: 'left',
id: 'y-axis-0',
ticks: {min: 0, //beginAtZero:true,
max: 200,
//display: true,
//fontColor: "black"
},
scaleLabel: {display: true, labelString: 'Number'},
gridLines: {color: "black",
//display: true,
drawOnChartArea: false,
zeroLineColor: "black",
//drawTicks: true,
},
}],
xAxes: [{
display: true,
type: 'linear',
id: 'x-axis-0',
position: 'bottom',
ticks: {min: 0,
max: 100,
//display: true,
//fontColor: "black",
},
scaleLabel: {display: true, labelString: 'Volume'},
gridLines: {color: "black",
zeroLineColor: "black",
drawOnChartArea: false,
},
}],
},
}
var ctx_gamma = document.getElementById("chart-gamma").getContext("2d");
var humidChartData = {
datasets: [
{
label: 'B errors',
data: [{x: 5, y: 45}, {x: 10, y: 100}, {x: 25, y: 120}, {x: 50, y: 125}, {x: 100, y: 150}, {x: 120, y: 250},],
borderColor: "green",
errors: [15, 20, 30, 12, 10, 10],
xErrors: [3, 7, 16, 12, 12, 30, 10],
errStyle: "oval",
showLine: false,
errColor: "border",
//pointBackgroundColor: "white",
//pointBordercolor: "white",
backgroundColor: "rgba(0,0,0,0)",
hidden: true,
errWidth: 2,
showCap: true,
capLen: 3,
radius: 0,
showErrors: true,
},
{
label: 'B trend',
data: [{x: 5, y: 45}, {x: 10, y: 100}, {x: 25, y: 120}, {x: 50, y: 125}, {x: 100, y: 150}, {x: 120, y: 250},],
borderColor: "green",
errors: [15, 20, 30, 12, 10, 10],
xErrors: [3, 7, 16, 12, 12, 30, 10],
pointStyle: "line",
showErrors: false,
radius: 0,
},
{
label: 'B data',
data: [{x: 5, y: 45}, {x: 10, y: 100}, {x: 25, y: 120}, {x: 50, y: 125}, {x: 100, y: 150}, {x: 120, y: 250},],
borderColor: "green",
backgroundColor: "green",
errors: [15, 20, 30, 12, 10, 10],
xErrors: [3, 7, 16, 12, 12, 30, 10],
showErrors: false,
showLine: false,
},
{
label: '',
data: [],
borderColor: "rgba(0,0,0,0)",
backgroundColor: "rgba(0,0,0,0)",
},
{
label: 'C data',
data: [{x: 15, y: 85, r: 14}, {x: 25, y: 37, r: 8}, {x: 62, y: 135, r: 44},],
borderColor: "blue",
backgroundColor: "rgba(0,0,0,0)",
xErrors: [3, 7, 16, 12, 12, 30, 10],
showLine: true,
showErrors: true,
},
]
}
var options_humid = {
hoverMode: 'single',
elements: {
line: { fill: false,
borderWidth: 2,
},
point: { radius: 3,
pointStyle: 'circle',
borderWidth: 1,
hitRadius: 0,
// hoverBorderWidth: 13,
hoverRadius: 9,
},
},
responsive: true,
showLines: true,
hoverMode: 'single', // should always use single for a scatter chart
legend: {
labels: {
usePointStyle: true,
// generateLabels: function() { }
}
},
scales: {
yAxes: [{
display: true,
position: 'left',
id: 'y-axis-0',
ticks: {min: 0, //beginAtZero:true,
max: 300 },
scaleLabel: {display: true, labelString: 'Number'},
gridLines: {zeroLineColor: "black", },
}],
xAxes: [{
display: true,
type: 'linear',
id: 'x-axis-0',
position: 'bottom',
ticks: {min: 0,
max: 200 },
scaleLabel: {display: true, labelString: 'Month'},
gridLines: {zeroLineColor: "black", },
}],
},
}
var ctx_humid = document.getElementById("chart-humid").getContext("2d");
window.onload = function() {
var humidChart = new Chart(ctx_humid, {
type: 'line',
data: humidChartData,
plugins: [errorbarPlugin],
options: options_humid,
});
var gamma_chart = new Chart(ctx_gamma, {
type: 'scatter',
data: gammaChartData,
plugins: [errorbarPlugin],
options: options_gamma,
});
};
</script>
</div>
</body>
</html>
I had a similar issue with rendered text in
plugins:[{
afterDatasetsDraw: function(chart, options) {
var ctx = chart.ctx;
ctx.font = Chart.defaults.global.defaultFontStyle;
ctx.fillStyle = "#666666";
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
chart.data.datasets.forEach(function(dataset, i) {
var meta = chart.controller.getDatasetMeta(i);
meta.data.forEach(function (bar, index) {
ctx.fillText(Math.round(dataset.data[index]), bar._model.x, bar._model.y);
});
});
}
}]
To fix, inside the meta.data.forEach(function (bar, index) { ... } loop, simply use:
var position = bar.tooltipPosition();
ctx.fillText(Math.round(dataset.data[index]), position.x, position.y);
(This is in #Yobmod's post, but it seems the key is to use the bar.tooltipPosition() location for the location of whatever is rendered.)
So I'm using Chart.js to plot a graph on my web app,
What I'm trying to achieve is to control the starting point of the graph itself, so I could control where the scales are located
This is what I have so far:
Eventually, I'm trying to make the scale appear at the center of each "stripe".
I've seen in the documentation that you can extend scales and charts to customize it to your own needs but I tried fiddling with it with no success regarding what I need.
This is an example of what I'm trying to achieve:
Code:
var type = 'line';
var daysArray = new Array(30).join().split(',').map(function (item, index) { ++index; return (index % 5 == 0 || index == 1) ? index : '' })
var data = {
labels: this.daysArray,
datasets: [{
borderColor: "#707bbb",
showLine: false,
fill: false,
pointBackgroundColor: "#707bbb",
pointRadius: 3,
data: [65, 59, 80, 81, 56, 20, 32, 65, 59, 80, 81, 56, 20, 32]
},
{
borderColor: "#2972c5",
borderWidth: 1,
fill: false,
pointRadius: 0,
data: [102, 42, 36, 98, 15, 15, 19, 65, 59, 80, 81, 56, 20, 32]
}
]
};
var options = {
bezierCurve: false,
responsive: true,
maintainAspectRatio: false,
display: false,
scales: {
yAxes: [{
display: false
}],
xAxes: [{
gridLines: {
drawOnChartArea: true,
drawBorder: false
},
ticks: {
fontStyle: "normal",
maxRotation: 0,
labelOffset: 1
}
}]
},
legend: {
display: false
}
};
Chart.pluginService.register({
beforeDatasetsDraw: function (chart, easing) {
if (chart.chartArea) {
var noOfLabels = chart.chart.config.data.labels.length;
var helpers = Chart.helpers;
var ctx = chart.chart.ctx;
//
ctx.save();
var chartArea = chart.chartArea;
var chartWidth = chartArea.right - chartArea.left;
var chartHeight = Math.abs(chartArea.top - chartArea.bottom);
var stripWidth = chartWidth / noOfLabels;
var pattern = document.createElement('canvas');
pattern.width = stripWidth * 2;
pattern.height = chartHeight;
var pctx = pattern.getContext('2d');
pctx.fillStyle = "#f0f2f5";
pctx.fillRect(0, 0, stripWidth, chartHeight);
pctx.fillStyle = "#ffffff";
pctx.fillRect(stripWidth, 0, stripWidth, chartHeight);
var ptr = ctx.createPattern(pattern, "repeat");
ctx.fillStyle = ptr;
ctx.fillRect(chartArea.left, chartArea.top, chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
ctx.restore();
ctx.save();
}
},
});
Chart.scaleService.getScaleConstructor("category").extend({
margins: {
left: 100
},
paddingLeft: 300
});
this.chart = new Chart(document.querySelector('canvas'), {
type: type,
data: data,
options: options
});
The code is a bit messy because I tried playing with some options and thought I would leave it just there
Anyone has an idea what to do?