ChartJS:align zeros on chart with multi axes - chart.js

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/

Related

How to set color of Chart.js tick label based on tick value - negative values red

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>

custom ticks in ChartsJS line plot

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>

Chart.js display x axis labels ON ticks in bar chart, not between

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>

Range at the end of the line chart

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>

Chartjs linechart with only one point - how to center

I have a chartjs linechart diagram to show the sales of different products on a range of dates. The user can select a date range (for example from 2015-12-01 to 2015-12-10) to view the sales per day and thats fine and its working.
But if the user selects only one day (range from for example 2015-12-01 to 2015-12-01), he gets the correct diagram, but it doesn't look good:
As you can see, the points are stick to the y-axis. Is there a possibility, to center the points on the diagram?
Thats how it should look like:
Instead of hardcoding the labels and values with blank parameters, use the offset property.
const options = {
scales: {
x: {
offset: true
}
}
}
Documentation: https://www.chartjs.org/docs/latest/axes/cartesian/linear.html#common-options-to-all-cartesian-axes
You can check the length of your labels (or data) arrays and add dummy non-renderable points to the left and right by using empty string labels and null value, like so
var chartData = {
labels: ['', "A", ''],
datasets: [
{
fillColor: "rgba(255, 52, 21, 0.2)",
pointColor: "#da3e2f",
strokeColor: "#da3e2f",
data: [null, 20, null]
},
{
fillColor: "rgba(52, 21, 255, 0.2)",
strokeColor: "#1C57A8",
pointColor: "#1C57A8",
data: [null, 30, null]
},
]
}
Fiddle - https://jsfiddle.net/pf24vg16/
Wanted to add to the above answer and say that I got a similar effect on a time series scatter plot using this:
if (values.length === 1) {
const arrCopy = Object.assign({}, values);
values.unshift({x: arrCopy[0].x - 86400000, y: null});
values.push({x: arrCopy[0].x + 2 * 86400000, y: null});
}
That only handles for a single point, however. To add in functionality for multiple points, I did the following:
const whether = (array) => {
const len = array.length;
let isSame = false;
for (let i = 1; i < len; i++) {
if (array[0].x - array[i].x >= 43200000) {
isSame = false;
break;
} else {
isSame = true;
}
}
return isSame;
}
if (values.length === 1 || whether(arr[0])) {
const arrCopy = Object.assign({}, values);
values.unshift({x: arrCopy[0].x - 86400000, y: null});
values.push({x: arrCopy[0].x + 2 * 86400000, y: null});
}
You might notice I'm just subtracting/adding a day in milliseconds into the x values. To be honest, I was just having the worst of times with moment.js and gave up haha. Hope this helps someone else!
Note: my code has a tolerance of 43200000, or 12 hours, on the time. You could use moment.js to compare days if you have better luck with it than I did tonight :)
For your specific problem, try to modify the options->scales->xAxes option like so:
options: {
title: {
display: true,
text: 'mytitle1'
},
scales: {
xAxes: [{
type: 'linear',
ticks: {
suggestedMin: 0,
suggestedMax: (11.12*2),
stepSize: 1 //interval between ticks
}
}],
More info at: Chart JS: Ignoring x values and putting point data on first available labels