Is it possible to put a range at the right side of the line chart to compare the distance between the last 2 points of the 2 lines?
This can be achieved via a custom plugin making direct draw calls to the canvas, an example of which I've included below. Note that the code makes a lot of assumptions based on your screenshot and should be considered as a starting point rather than a perfect drop-in solution.
let myChart = new Chart(document.getElementById('chart'), {
type: 'line',
data: {
labels: ['a', 'b', 'c', 'd', 'e', 'f', 'g'],
datasets: [{
label: 'Group1',
data: [-1000, -2000, -2000, -3000, -4000, -3000, -5000],
backgroundColor: '#F48496'
}, {
label: 'Group2',
data: [-4000, -4000, -3000, -6000, -6000, -5000, -9000],
backgroundColor: '#61B2E9'
}]
},
options: {
maintainAspectRatio: false,
layout: {
padding: {
right: 100
}
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
},
plugins: {
afterRender: function(c) {
let
// calculate difference between values of last two points in first and second datasets.
d = c.config.data.datasets[0].data[c.config.data.datasets[0].data.length - 1] - c.config.data.datasets[1].data[c.config.data.datasets[1].data.length - 1],
// position of last point in first dataset.
xy0 = c.getDatasetMeta(0).data[c.getDatasetMeta(0).data.length - 1]._model,
// position of last point in second dataset.
xy1 = c.getDatasetMeta(1).data[c.getDatasetMeta(1).data.length - 1]._model;
c.ctx.save();
// draw the line.
c.ctx.strokeStyle = 'black';
c.ctx.beginPath();
c.ctx.moveTo(xy0.x + 10, xy0.y);
c.ctx.lineTo(xy0.x + 15, xy0.y); // draw the upper horizontal line.
c.ctx.lineTo(xy0.x + 15, xy1.y); // draw the vertical line.
c.ctx.lineTo(xy1.x + 10, xy1.y); // draw the lower horizontal line.
c.ctx.stroke();
// draw the text.
c.ctx.font = '20px sans-serif';
c.ctx.fillStyle = 'black';
c.ctx.fillText(
d, // text
c.chartArea.right + 25, // text x position
xy0.y + ((xy1.y - xy0.y) / 2) // text y position
);
c.ctx.restore();
}
}
});
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js"></script>
<canvas id="chart"></canvas>
Related
I would like to set the color of y-axis tick labels in Chart.js bar and line charts based on the numeric label value. Specifically, I'd like negative values to be rendered red. Additionally rather than displaying "-1", "-2", etc., I'd like to override to display "(1)", "(2)", etc.
I've seen examples for changing tick labels based on index / position, but not conditionally based on the label value. Thanks in advance for any guidance.
You can define the scriptable option scales.x.ticks.color as an array of colors that depend on the corresponding data value each. The following definition for example shows red tick labels for every bar of a value less than 10.
scales: {
x: {
ticks: {
color: data.map(v => v < 10 ? 'red' : undefined)
}
}
}
For further information, consult Tick Configuration from the Chart.js documentation.
Please take a look at below runnable code and see how it works.
const data = [4, 12, 5, 13, 15, 8];
new Chart('myChart', {
type: 'bar',
data: {
labels: ['A', 'B', 'C', 'D', 'E', 'F'],
datasets: [{
label: 'Dataset',
data: data,
}]
},
options: {
responsive: false,
scales: {
x: {
ticks: {
color: data.map(v => v < 10 ? 'red' : undefined)
}
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
<canvas id="myChart" height="180"></canvas>
Take the ChartsJS line plot example (https://www.chartjs.org/docs/latest/charts/line.html). How can I change Y axis so that it simply shows two labels only: 'less' at 25% of the max Y value and 'more' at 75% of it?
This can be done by defining an afterBuildTicks together with a ticks.callback function on the y-axis.
Please take a look at below runnable code and see how it works.
new Chart('my-chart', {
type: 'line',
data: {
labels: ['A', 'B', 'C', 'D', 'E', 'F', 'G'],
datasets: [{
label: 'My Data',
data: [65, 59, 80, 81, 56, 55, 40],
borderColor: '#1f77b4'
}]
},
options: {
scales: {
y: {
afterBuildTicks: axis => {
let values = axis.ticks.map(t => t.value);
let max = Math.max(...values);
let min = Math.min(...values);
axis.ticks = [
{ value: min + (max - min) * 0.25 },
{ value: min + (max - min) * 0.75 }
]
},
ticks: {
callback: (v, i) => i ? 'more' : 'less'
}
}
}
}
});
canvas {
max-height: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.1/chart.min.js"></script>
<canvas id="my-chart"></canvas>
I have this chart:
...which is displaying exactly how I want it to with one exception... The data in the bars is for between the two times in the x axis... so all the labels need shifting to lie on the grid lines, not between them as default for a bar chart. So the red and blue bar is data between 8:00 and 9:00. I hope I've explained that clearly enough.
I'm trawling through the Chart.js docs and it just doesn't seem like this is possible! I know I could change my labels to be, for example, 8pm - 9pm, but that seems a much more visually clunky way of doing it. Is there a way anyone know of achieving this? Ideally there would be another '12am' on the last vertical grid line too.
You can draw the tick lables at the desired position directly on to the canvas using the Plugin Core API. It offers number of hooks that may be used for performing custom code. In below code snippet, I use the afterDraw hook to draw my own labels on the xAxis.
const hours = ['00', '01', '02', '03', '04', '05', '06'];
const values = [0, 0, 0, 0, 10, 6, 0];
const chart = new Chart(document.getElementById('myChart'), {
type: 'bar',
plugins: [{
afterDraw: chart => {
var xAxis = chart.scales['x-axis-0'];
var tickDistance = xAxis.width / (xAxis.ticks.length - 1);
xAxis.ticks.forEach((value, index) => {
if (index > 0) {
var x = -tickDistance + tickDistance * 0.66 + tickDistance * index;
var y = chart.height - 10;
chart.ctx.save();
chart.ctx.fillText(value == '0am' ? '12am' : value, x, y);
chart.ctx.restore();
}
});
}
}],
data: {
labels: hours,
datasets: [{
label: 'Dataset 1',
data: values,
categoryPercentage: 0.99,
barPercentage: 0.99,
backgroundColor: 'blue'
}]
},
options: {
responsive: true,
legend: {
display: false
},
scales: {
xAxes: [{
type: 'time',
time: {
parser: 'HH',
unit: 'hour',
displayFormats: {
hour: 'Ha'
},
tooltipFormat: 'Ha'
},
gridLines: {
offsetGridLines: true
},
ticks: {
min: moment(hours[0], 'HH').subtract(1, 'hours'),
fontColor: 'white'
}
}]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="myChart" height="90"></canvas>
Is there a way to skip the initial animation?
I've tried setting duration to 0 and then changing it to 2000.
But it seems that permanently disables animations until i destroy and recreate the chart.
What I'm trying to do:
Initially display a line-chart with a flat line in the middle of graph.
Then on data change i want it to animate to data positions.
You can initially set the animation duration to 0 and then either:
immediately set the animation duration, or
specify the duration when calling the update() method.
Example:
let max = 10,
min = -10,
myChart = new Chart(document.getElementById('myChart'), {
type: 'line',
data: {
labels: ['a', 'b', 'c'],
datasets: [{
label: 'series1',
data: [5, 5, 5]
}]
},
options: {
maintainAspectRatio: false,
scales: {
yAxes: [{
ticks: {
max: 10,
beginAtZero: true
}
}]
},
animation: {
duration: 0
}
}
});
// 1. immediately set the animation duration.
//myChart.config.options.animation.duration = 1000;
setInterval(function() {
let a = getRandomInt(0, 3), // index to modify.
b = getRandomInt(0, 11); // new value.
myChart.config.data.datasets[0].data[a] = b;
myChart.update(1000); // 2. specify the duration.
}, 2000); // update the chart every 2 seconds with a random value.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random#Getting_a_random_integer_between_two_values
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min; //The maximum is exclusive and the minimum is inclusive
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js"></script>
<canvas id="myChart"></canvas>
How to align zeros on chart with multi axes, if there are both positive and negative values in dataset?
I want zeroes to be on the same line.
I dont like this:
Graph image
link to jsfiddle
new Chart(canvas, {
type: 'line',
data: {
labels: ['1', '2', '3', '4', '5'],
datasets: [{
label: 'A',
yAxisID: 'A',
data: [-10, 96, 84, 76, 69]
}, {
label: 'B',
yAxisID: 'B',
data: [-2, 3, 5, 2, 3]
}]
},
options: {
scales: {
yAxes: [{
id: 'A',
type: 'linear',
position: 'left',
}, {
id: 'B',
type: 'linear',
position: 'right',
}]
}
}
});
actualy, The example on the official web page has the same problem. Looks so messy.
The answer above is great if you know the ranges in advance and can specify min and max for each ticks object in advance. If, like me, you're trying to produce the effect with generated data, you'll need something that computes these values for you. The following does this without recourse to hard-coded values. It does generate ugly max / min values - but there's another fix for that: Hide min and max values from y Axis in Chart.js. It may also generate less than ideal spacing in which case a simple fudge factor applied to the min and max values may be used.
function scaleDataAxesToUnifyZeroes (datasets, options) {
let axes = options.scales.yAxes
// Determine overall max/min values for each dataset
datasets.forEach(function (line) {
let axis = line.yAxisID ? axes.filter(ax => ax.id === line.yAxisID)[0] : axes[0]
axis.min_value = Math.min(...line.data, axis.min_value || 0)
axis.max_value = Math.max(...line.data, axis.max_value || 0)
})
// Which gives the overall range of each axis
axes.forEach(axis => {
axis.range = axis.max_value - axis.min_value
// Express the min / max values as a fraction of the overall range
axis.min_ratio = axis.min_value / axis.range
axis.max_ratio = axis.max_value / axis.range
})
// Find the largest of these ratios
let largest = axes.reduce((a, b) => ({
min_ratio: Math.min(a.min_ratio, b.min_ratio),
max_ratio: Math.max(a.max_ratio, b.max_ratio)
}))
// Then scale each axis accordingly
axes.forEach(axis => {
axis.ticks = axis.ticks || { }
axis.ticks.min = largest.min_ratio * axis.range
axis.ticks.max = largest.max_ratio * axis.range
})
}
You can do this by setting the ticks option on your axes to control the max, min, and stepSize. To make the zeros align, you need to make the axes symmetric. stepSize is optional if you want the gridlines to be aligned as well.
In your example:
yAxes: [{
id: 'A',
type: 'linear',
position: 'left',
ticks : {
max: 100,
min: -50,
stepSize: 50
}
}, {
id: 'B',
type: 'linear',
position: 'right',
ticks : {
max: 6,
min: -3,
stepSize: 3
}
}]
See updated fiddle: https://jsfiddle.net/x885kpe1/1/