CHART.JS How can I offset/move/adjust the labels on the y-axis to be in the middle of the gridlines instead of centered on the gridlines? - javascript

I am using chart.js inside of an Ionic/Angular application to build a line graph. The big problem I am running into now is that I would like to have the labels along the y axis (Green, Yellow, Orange, Red) positioned between the horizontal gridlines instead of centered on the gridlines as they are currently in the image here...
I tried using the plugin mentioned in this question with no such luck. I've also tried using the offsetGridlines property in the actual chart.js documentation again with no luck.
Free bonus points if anyone can also help out with having the actual axis line at the bottom to be the same width as the other horizontal gridlines
Here is the code that deals with this particular aspect of the chart...
options: {
legend: {
display: false
},
scaleShowVerticalLines: false,
layout: {
padding: 25
},
scales: {
xAxes: [{
gridLines: {
display: false
},
ticks: {
display: false
}
}],
yAxes: [{
gridLines: {
drawBorder: false
},
ticks: {
min: 0,
max: 100,
stepSize: 25,
fontColor: 'white',
callback: function(value) {
if (value === 25) {
return 'Red';
} else if (value === 50) {
return 'Orange';
} else if (value === 75) {
return 'Yellow';
} else if (value === 100){
return 'Green';
} else {
return '';
}
}
}
}]
}
},

I've annotated the changes in the below snippet, but to briefly explain:
I integrated the plugin you linked to but modified it to set the original ticks to transparent and therefore removed the beforeDraw closure which didn't seem to work.
For bonus points: the trick is to remove the x-axis border and then style the 'zero line' to match the other grid lines.
var ctx = document.getElementById("chart").getContext('2d');
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 0],
datasets: [{
data: [25, 35, 20, 45, 65, 26, 49, 70, 75, 87],
borderColor: 'white',
fill: false
}]
},
options: {
legend: {
display: false
},
scaleShowVerticalLines: false,
layout: {
padding: 25
},
scales: {
xAxes: [{
gridLines: {
display: false,
drawBorder: false, // bonus points.
},
ticks: {
display: false
}
}],
yAxes: [{
gridLines: {
drawBorder: false,
color: 'rgba(0,0,0,.3)', // bonus points.
zeroLineColor: 'rgba(0,0,0,.3)' // bonus points.
},
ticks: {
min: 0,
max: 100,
stepSize: 25,
fontColor: 'transparent', // changed from 'white'
callback: function(value) {
if (value === 25) {
return 'Red';
} else if (value === 50) {
return 'Orange';
} else if (value === 75) {
return 'Yellow';
} else if (value === 100) {
return 'Green';
} else {
return '';
}
}
}
}]
}
},
plugins: [{
afterDraw: function(chart) {
var ctx = chart.ctx;
var yAxis = chart.scales['y-axis-0'];
var tickGap = yAxis.getPixelForTick(1) - yAxis.getPixelForTick(0);
// loop through ticks array
Chart.helpers.each(yAxis.ticks, function(tick, index) {
if (index === yAxis.ticks.length - 1) return;
var xPos = yAxis.right;
var yPos = yAxis.getPixelForTick(index);
var xPadding = 10;
// draw tick
ctx.save();
ctx.textBaseline = 'middle';
ctx.textAlign = 'right';
ctx.fillStyle = 'white';
ctx.fillText(tick, xPos - xPadding, yPos + tickGap / 2);
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" style="background-color:#296390"></canvas>

Related

How to plot a single value with line in line chart graph using charts.js?

I need to plot a single value in line chart. Currently i am using charts.JS library for line graph purpose.
The data will be varied some times i'll get the single data inside the data set at that time i need to plot the single value with line in the line chart.
I tried with the charts.js annotation plugin but it wasn't met my requirements. which is like it wis overlapping the plotted point in the graph area.
CODE WHICH I HAD TRIED
createLineChart() {
this.lineChart = new Chart(this.lineCanvas.nativeElement, {
type: "line",
data: {
labels:[],
datasets: [
{
fill: false,
backgroundColor: "#0168FF",
borderColor: "#0168FF",
pointBackgroundColor: "white", // wite point fill
pointBorderWidth: 1, // point border width
lineTension: 0,
pointBorderColor: "blue",
pointRadius: 4,
},
],
},
options: {
scales: {
yAxes: [
{
ticks: {
padding: 20,
beginAtZero: true,
min: 0,
stepSize: 100,
},
gridLines: {
drawBorder: false,
},
},
],
xAxes: [
{
// offset: true,
ticks: {
display: false,
//beginAtZero: true,
min: 0,
},
gridLines: {
zeroLineColor: "transparent",
drawBorder: false,
display: false,
},
//offset:true,
},
],
legend: {
display: false,
},
tooltips: {
enabled: false,
},
},
drawTime: "afterDraw", // (default)
} as ChartOptions,
// plugins: [ChartAnnotation]
},
});
}
To generate dynamic data and plot in the graph area.
generateRandomDataSet(size) {
let yaxisArr = [];
let xaxisArr = [];
let random_data:any = this.getRandomData(size)
let maxYTickVal = Math.max.apply(Math, random_data.map((val) => {return val.yaxis}));
let maxVal = Math.ceil((maxYTickVal+1) / 10) * 10
for(let data of random_data) {
yaxisArr.push(data.yaxis)
xaxisArr.push(data.xaxis)
}
console.log("X-Axis array values : "+xaxisArr)
console.log("Y-Axis array values : "+yaxisArr)
this.lineChart.data.datasets[0].data = yaxisArr
this.lineChart.config.data.labels = []
this.lineChart.config.data.labels = xaxisArr
this.lineChart.config.options.scales.yAxes[0].ticks.max =maxVal
this.lineChart.config.options.scales.yAxes[0].ticks.stepSize = maxVal/2
this.lineChart.update()
}
getRandomData(arraySize) {
let data = []
for(var i=1; i<=arraySize; i++) {
let number = Math.floor(Math.random() * 200) + 1
data.push({'xaxis':i,'yaxis':number})
}
return data
}
with the above code i am getting like
what i need to have
You can define an animation.onComplete function as follows to draw the line in case a single data value is present.
animation: {
onComplete: e => {
var ctx = chart.chart.ctx;
var data = chart.config.data.datasets[0].data;
if (data[0] == null) {
var xAxis = chart.scales['x-axis-0'];
var yAxis = chart.scales['y-axis-0'];
var y = yAxis.getPixelForValue(data[1]);
ctx.save();
ctx.globalCompositeOperation='destination-over';
ctx.strokeStyle = 'blue'
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(xAxis.left, y);
ctx.lineTo(xAxis.right, y);
ctx.stroke();
ctx.restore();
}
}
},
This function expects the data array to be of format [null, <value>, null] in case a single value is present, otherwise it will be hard to horizontally center the data point (see this answer). It's up to you to change the generateRandomDataSet() function in a way that it provides such data.
Please have a look at your changed code below.
const chart = new Chart('line-chart', {
type: "line",
data: {
labels: ['', 'A', ''],
datasets: [{
data: [null, 120, null],
fill: false,
backgroundColor: "#0168FF",
borderColor: "#0168FF",
pointBackgroundColor: "white",
pointBorderWidth: 1,
lineTension: 0,
pointBorderColor: "blue",
pointRadius: 4,
}],
},
options: {
legend: {
display: false
},
tooltips: {
enabled: false
},
animation: {
onComplete: e => {
var ctx = chart.chart.ctx;
var data = chart.config.data.datasets[0].data;
if (data[0] == null) {
var xAxis = chart.scales['x-axis-0'];
var yAxis = chart.scales['y-axis-0'];
var y = yAxis.getPixelForValue(data[1]);
ctx.save();
ctx.globalCompositeOperation='destination-over';
ctx.strokeStyle = 'blue'
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(xAxis.left, y);
ctx.lineTo(xAxis.right, y);
ctx.stroke();
ctx.restore();
}
}
},
scales: {
yAxes: [{
ticks: {
padding: 20,
min: 0,
stepSize: 100
},
gridLines: {
drawBorder: false
}
}],
xAxes: [{
ticks: {
display: false
},
gridLines: {
zeroLineColor: "transparent",
drawBorder: false,
display: false
}
}]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="line-chart" height="80"></canvas>

Chart.js : How I change the x axes ticks labels alignment in any sizes?

How can I move my labels on my x axes in between another x axes label. Nothing seems to work and I was unable to find anything on the docs. Is there a workaround? I'm using line chart time series.
https://www.chartjs.org/samples/latest/scales/time/financial.html
Currently, with the code I have its generating the figure below:
var cfg = {
elements:{
point: {
radius: 4
}
},
data: {
datasets: [
{
label: 'vsy',
backgroundColor: color(window.chartColors.red).alpha(0.5).rgbString(),
borderColor: window.chartColors.red,
data: firstData,
type: 'line',
pointRadius: 2,
fill: false,
lineTension: 0,
borderWidth: 2
},
{
label: 'de vsy',
backgroundColor: color(window.chartColors.blue).alpha(0.5).rgbString(),
borderColor: window.chartColors.blue,
data: dataMaker(15),
type: 'line',
pointRadius: 2,
fill: false,
lineTension: 0,
borderWidth: 2
}
],
},
options: {
animation: {
duration: 0
},
scales: {
xAxes: [{
type: 'time',
distribution: 'series',
offset: true,
time: {
unit: 'month',
displayFormats: {
month: 'MMM'
}
},
ticks: {
autoSkip: true,
autoSkipPadding: 75,
sampleSize: 100
},
}],
yAxes: [{
gridLines: {
drawBorder: false
}
}]
},
tooltips: {
intersect: false,
mode: 'index',
}
}
};
This is what I have now:
I want the labels on the x-axis to be on center instead of below the y axis grid line.
Thanks to uminder, with his comment it solves the issue but now I have a conflicting tooltip which lie on a same grid. When I hover to april line first point it shows me mar 30 which lies just above it and vice versa.
I fixed it by changing the mode to nearest but why is it activating the another point?
The option you're looking for is offsetGridLines.
If true, grid lines will be shifted to be between labels.
xAxes: [{
...
gridLines: {
offsetGridLines: true
}
In most cases, this produces the expected result. Unfortunately it doesn't work for time axes as documented in Chart.js issue #403. Thanks to Antti Hukkanen, there exists a workaround.
Please have a look at below runnable code snippet to see how it works.
function generateData() {
var unit = 'day';
function randomNumber(min, max) {
return Math.random() * (max - min) + min;
}
function randomPoint(date, lastClose) {
var open = randomNumber(lastClose * 0.95, lastClose * 1.05).toFixed(2);
var close = randomNumber(open * 0.95, open * 1.05).toFixed(2);
return {
t: date.valueOf(),
y: close
};
}
var date = moment().subtract(1, 'years');
var now = moment();
var data = [];
for (; data.length < 600 && date.isBefore(now); date = date.clone().add(1, unit).startOf(unit)) {
data.push(randomPoint(date, data.length > 0 ? data[data.length - 1].y : 30));
}
return data;
}
var TimeCenterScale = Chart.scaleService.getScaleConstructor('time').extend({
getPixelForTick: function(index) {
var ticks = this.getTicks();
if (index < 0 || index >= ticks.length) {
return null;
}
// Get the pixel value for the current tick.
var px = this.getPixelForOffset(ticks[index].value);
// Get the next tick's pixel value.
var nextPx = this.right;
var nextTick = ticks[index + 1];
if (nextTick) {
nextPx = this.getPixelForOffset(nextTick.value);
}
// Align the labels in the middle of the current and next tick.
return px + (nextPx - px) / 2;
},
});
// Register the scale type
var defaults = Chart.scaleService.getScaleDefaults('time');
Chart.scaleService.registerScaleType('timecenter', TimeCenterScale, defaults);
var cfg = {
data: {
datasets: [{
label: 'CHRT - Chart.js Corporation',
backgroundColor: 'red',
borderColor: 'red',
data: generateData(),
type: 'line',
pointRadius: 0,
fill: false,
lineTension: 0,
borderWidth: 2
}]
},
options: {
animation: {
duration: 0
},
scales: {
xAxes: [{
type: 'timecenter',
time: {
unit: 'month',
stepSize: 1,
displayFormats: {
month: 'MMM'
}
},
gridLines: {
offsetGridLines: true
}
}],
yAxes: [{
gridLines: {
drawBorder: false
}
}]
},
tooltips: {
intersect: false,
mode: 'index'
}
}
};
var chart = new Chart('chart1', cfg);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="chart1" height="90"></canvas>
For chartJs v3 you can use offset property:
scales: {
x: {
grid: {
offset: true
}
},
...
}

Want to show small part of js Bar Chart when value is zero

I have these bar charts that will sometimes show zero values to the user. I'd like to show a bit of the bar chart so it does not look completely empty, but the data is connected to the visual representation of the bar. Is there any way I could have the top number say zero but the value be 3(or something small) in each column? Here is the code and a screenshots. One screenshot has data, the other is zero. I'd like the zero graph to show just a bit of the orange and green when at zero. Thanks
<canvas id="bar-chart" width="900" height="350"></canvas>
<script type="text/javascript">
var ctx = document.getElementById("bar-chart");
debugger;
var data = {
labels: ["Page Views", "Data Requests"],
datasets: [{
data: [<?php echo $databaseClass->totalViewsSelectedMonth($user_id, $year, 1);
?>, <?php
echo $databaseClass->isRfYearMonth($user_id, $year, 1);
?>],
backgroundColor: ["orange", "green"]
}]
}
var myChart = new Chart(ctx, {
type: 'bar',
data: data,
options: {
"hover": {
"animationDuration": 0
},
"animation": {
"duration": 1,
"onComplete": function() {
var chartInstance = this.chart,
ctx = chartInstance.ctx;
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, Chart.defaults.global.defaultFontStyle, Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
this.data.datasets.forEach(function(dataset, i) {
var meta = chartInstance.controller.getDatasetMeta(i);
meta.data.forEach(function(bar, index) {
var data = dataset.data[index];
ctx.fillText(data, bar._model.x, bar._model.y - 5);
});
});
}
},
legend: {
"display": false
},
tooltips: {
"enabled": false
},
scales: {
yAxes: [{
display: false,
gridLines: {
display: false
},
ticks: {
max: Math.max(...data.datasets[0].data) + 20,
display: false,
beginAtZero: true
}
}],
xAxes: [{
gridLines: {
display: false
},
ticks: {
beginAtZero: true
}
}]
}
}
});
</script>
It looks like you can update the axes of your charts to a new default. So, using a modified form of their example, something like the following (modified to fit your code):
// ...
scales: {
yAxes: [{
display: false,
gridLines: {
display: false
},
ticks: {
max: Math.max(...data.datasets[0].data) + 20,
min: -3, // ADDED
display: false,
beginAtZero: true
}
}],
// ...
... would do what you're looking for - with the side benefit that 0 and 3 wouldn't look like the same value, the relative bar size should stay accurate.

Chart.js - How to offset bars from ZeroLine

I've spent hours already trying to figure out how to offset the horizontal bars from the zero line on X-Axis so it doesn't overlap when the width of the line is bigger than 1.
Appreciate all the help.
Example is here on CodePen (hope it will show up): https://codepen.io/RomanKl/pen/mzmegG
var barOptions = {
tooltips: {
enabled: false
},
hover :{
animationDuration:0
},
scales: {
xAxes: [{
ticks: {
beginAtZero:true,
min: 0,
max: 10000,
fontFamily: "'Open Sans Bold', sans-serif",
fontSize:12,
callback: function(value, index, values) {
return Math.round(value/1000) + 'k';
}
},
scaleLabel:{
display:false
},
gridLines: {
color: ['#000', '#efefef', '#efefef', '#efefef', '#efefef', '#efefef', '#efefef', '#efefef', '#efefef', '#efefef', '#efefef'],
lineWidth: [4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
zeroLineWidth: 4,
zeroLineColor: '#000',
},
}],
yAxes: [{
gridLines: {
display: false,
},
ticks: {
fontFamily: "'Open Sans Bold', sans-serif",
fontSize:14,
},
}]
},
legend:{
display:false
},
animation: {
onComplete: function () {
var chartInstance = this.chart;
var ctx = chartInstance.ctx;
ctx.textAlign = "left";
ctx.font = "1.6rem Open Sans";
ctx.fillStyle = "#fff";
Chart.helpers.each(this.data.datasets.forEach(function (dataset, i) {
var meta = chartInstance.controller.getDatasetMeta(i);
Chart.helpers.each(meta.data.forEach(function (bar, index) {
data = dataset.data[index];
data = data.toFixed(0).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
if(i==0){
ctx.fillText(data, 80, bar._model.y+4);
} else {
ctx.fillText(data, bar._model.x-25, bar._model.y+4);
}
}),this)
}),this);
}
},
};
var ctx = document.getElementById("Chart1");
var myChart = new Chart(ctx, {
type: 'horizontalBar',
borderSkipped: 'bottom',
data: {
labels: ["Aug.'17", "Aug.'18"],
datasets: [{
data: [6336, 6892],
backgroundColor: "rgba(63,103,126,1)",
hoverBackgroundColor: "rgba(50,90,100,1)"
}]
},
options: barOptions,
});
So I've brutally hacked it in the end. I've tried adding a border and then skip it everywhere except the start where I would set the colour to transparent but guess what? Border doesn't show up at the start. It's a requested feature not yet implemented in chart.js as of this posting.
In the end I've used a diabolical solution of using two stacked datasets while setting the first one via data to the desired offset and then setting backgroundColor to transparent and shifting data labels to the left via plugin.
you can see the result in the codepen link posted in the question.

Add Data Labels onto a bubble chart on chart.js

I have used a Bubble Chart on Chart.js to create sliders to show comparable performance and they currently look a bit like this:
What am I trying to do
I want to add data labels just above / in my 'bubbles' with my values in. Much like the '10' you can see on each bubble here.
What have I done to achieve this
This is not standard Chart.js functionality but I found this post which was discussing a similar issue for bar / line charts.
I've installed the plugin that post suggested but the data label it shows is for the radius of the bubble and I want to it to be the x-axis of the bubble.
I've also tried to use the code from some of the answers on that post, but with absolutely no luck.
My Code
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.0/Chart.bundle.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels"></script>
<div class="container" >
<h2>Chart.js — Line Chart Demo</h2>
<div>
<canvas id="myChart"></canvas>
</div>
</div>
<script>
var ctx = document.getElementById('myChart').getContext('2d');
ctx.height = 1000;
var myChart = new Chart(ctx, {
type: 'bubble',
data: {
datasets: [
{
label: 'Your Data',
data: [
{x: 78.7, y: 0, r: 10, name: "Performance"}
],
backgroundColor: "rgba(153,255,51,0.6)"
},
{
label: 'Average',
data: [
{x: 100.7, y: 0, r: 10, name: "Performance"} // The labe needs to be X. not R.
],
backgroundColor: "rgba(255,0,128,0.6)"
}
]
},
options: {
maintainAspectRatio: false,
scales: {
yAxes: [{
id: 'first-y-axis',
type: 'linear',
ticks: {
min: 0,
max: 1,
stepSize: 1,
display: false
},
gridLines: {
display: false,
drawBorder: false
}
}],
xAxes: [{
ticks: {
min: 50, // Controls where axis starts
max: 120 // Controls where axis finishes
},
gridLines: {
display: false,
lineWidth: 3 // Width of bottom line
}
}]
}
}
});
</script>
Thanks in advance
I've managed to find the answer to this question, basically by taking apart the bubble chart example from the chartjs-plugin-datalabels plugin.
Below is a working example. Pay attention to the section in options that says 'plugin'.
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.0/Chart.bundle.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels"></script>
<div class="container" >
<h2>Chart.js — Line Chart Demo</h2>
<div>
<canvas id="myChart"></canvas>
</div>
</div>
<script>
var ctx = document.getElementById('myChart').getContext('2d');
ctx.height = 1000;
var myChart = new Chart(ctx, {
type: 'bubble',
data: {
datasets: [
{
label: 'Your Data',
data: [
{x: 78.7, y: 0, r: 10, name: "Performance"}
],
backgroundColor: "rgba(153,255,51,0.6)"
},
{
label: 'Average',
data: [
{x: 100.7, y: 0, r: 10, name: "Performance"} // The labe needs to be
],
backgroundColor: "rgba(255,0,128,0.6)"
}
]
},
options: {
plugins: { // Look at this bit
datalabels: {
anchor: function(context) {
var value = context.dataset.data[context.dataIndex];
return value.x < 50 ? 'end' : 'center';
},
align: function(context) {
var value = context.dataset.data[context.dataIndex];
return value.x < 50 ? 'end' : 'center';
},
color: function(context) {
var value = context.dataset.data[context.dataIndex];
return value.x < 50 ? context.dataset.backgroundColor : 'white';
},
font: {
weight: 'bold'
},
formatter: function(value) {
return Math.round(value.x);
},
offset: 2,
padding: 0
}
},
maintainAspectRatio: false,
scales: {
yAxes: [{
id: 'first-y-axis',
type: 'linear',
ticks: {
min: 0,
max: 1,
stepSize: 1,
display: false
},
gridLines: {
display: false,
drawBorder: false
}
}],
xAxes: [{
ticks: {
min: 50, // Controls where axis starts
max: 120 // Controls where axis finishes
},
gridLines: {
display: false,
lineWidth: 3 // Width of bottom line
}
}]
}
}
});
</script>
If all you want to do is changing the label, there is an easier solution. From the docs of chartjs-plugin-datalabels:
Data values are converted to string ('' + value). If value is an object, the following rules apply first:
value = value.label if defined and not null
else value = value.r if defined and not null
else value = 'key[0]: value[key[0]], key[1]: value[key[1]], ...'
Therefore, it is sufficient to specify a label in your data points:
data: [{ x: 78.7, y: 0, r: 10, name: "Performance", label: `${Math.round(x)}` }],

Categories