How to edit style of negative x grid lines on Chart.js? - javascript

I want to draw dashed lines on the x-axis for the negative value on my chart, how I can do that?

You can draw the grid lines labels directly on the canvas using the Plugin Core API. It offers a number of hooks that can be used to perform custom code. In your case, you could use the afterDraw as follows:
plugins: [{
afterDraw: chart => {
var ctx = chart.chart.ctx;
ctx.save();
ctx.globalCompositeOperation = "destination-over";
var xAxis = chart.scales["x-axis-0"];
var yAxis = chart.scales["y-axis-0"];
yAxis.ticks.forEach((v, i) => {
var y = yAxis.getPixelForTick(i);
ctx.strokeStyle = 'rgb(128, 128, 128, 0.3)';
ctx.lineWidth = 1;
ctx.setLineDash(v < 0 ? [10, 5] : []);
ctx.beginPath();
ctx.moveTo(xAxis.left, y);
ctx.lineTo(xAxis.right, y);
ctx.stroke();
});
ctx.restore();
}
}],
This code uses CanvasRenderingContext2D to draw lines of different style. It iterate over yAxis.ticks using the Array.forEach() method. The line style (i.e. a dash pattern) is defined using method setLineDash() depending on the value v.
ctx.setLineDash(v < 0 ? [10, 5] : []);
If v is below zero, I provide pattern [10, 5], otherwise an empty array [] in order to obtain a solid line.
Please have a look at below runnable code snippet to see how it works.
new Chart(document.getElementById("chart"), {
type: "bar",
plugins: [{
afterDraw : chart => {
var ctx = chart.chart.ctx;
ctx.save();
ctx.globalCompositeOperation = "destination-over";
var xAxis = chart.scales["x-axis-0"];
var yAxis = chart.scales["y-axis-0"];
yAxis.ticks.forEach((v, i) => {
var y = yAxis.getPixelForTick(i);
ctx.strokeStyle = 'rgb(128, 128, 128, 0.3)';
ctx.lineWidth = 1;
ctx.setLineDash(v < 0 ? [10, 5] : []);
ctx.beginPath();
ctx.moveTo(xAxis.left, y);
ctx.lineTo(xAxis.right, y);
ctx.stroke();
});
ctx.restore();
}
}],
data: {
labels: ["A", "B", "C"],
datasets: [{
label: "Dataset 1",
data: [10, 15, 10],
backgroundColor: ["rgba(255, 99, 132, 0.2)", "rgba(255, 159, 64, 0.2)", "rgba(255, 205, 86, 0.2)", "rgba(75, 192, 192, 0.2)"],
borderColor: ["rgb(255, 99, 132)", "rgb(255, 159, 64)", "rgb(255, 205, 86)"],
borderWidth: 1
},
{
label: "Dataset 2",
data: [-15, -10, -15],
backgroundColor: "rgba(0, 255, 0, 0.2)",
borderColor: "rgb(0, 255, 0)",
borderWidth: 1
}
]
},
options: {
layout: {
padding: {
left: 12
}
},
legend: {
display: false
},
scales: {
xAxes: [{
stacked: true,
gridLines: {
display: false
}
}],
yAxes: [{
stacked: true,
gridLines: {
drawOnChartArea: false
},
ticks: {
stepSize: 5
}
}]
}
}
});
canvas {
max-width: 400px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="chart" height="200"></canvas>

Related

How to make customized stepSize for each horizontal bar in Chart.js graph

I have implemented a graph with four horizontal bars. Last one has nearly 35k records so the stepSize automatically is 2250. The first bar has only 20 records.
First bar's 20 records are not showing any color as the numbers are less at compare to stepSize 2250.
This is my code
scales: {
xAxes: [
{
ticks: {
beginAtZero: true,
stepSize: 50,
},
stacked: true
}
],
yAxes: [
{
ticks: {
fontSize: 12
},
stacked: true
}
]
},
animation: {
onComplete: function() {
var chartInstance = this.chart;
var ctx = chartInstance.ctx;
ctx.textAlign = "left";
ctx.fillStyle = "#fff";
//draw total count
charData.datasets[0].data.forEach(function(data, index) {
var total = this.data.datasets[0].data[index];
var meta = chartInstance.controller.getDatasetMeta(0);
var posX = meta.data[index]._model.x;
var posY = meta.data[index]._model.y;
ctx.fillStyle = "black";
if(total.toString().length>=5)
ctx.fillText(total, posX -40, posY + 2);
else if(total==0)
ctx.fillText(total, posX -4, posY + 4);
else
ctx.fillText(total, posX - 10, posY + 4);
}, this);
}
This is output
How can I fix this issue?
Your problem is not related to ticks.stepSize, this option simply controls how to create the ticks but doesn't change the size of the bars.
You can define the x-axis as a logarithmic cartesian axis as shown in the runnable code snippet below.
new Chart('myChart', {
type: 'horizontalBar',
data: {
labels: ['0-12 hr', '12-24 hr', '1-3 day', '3-15 day'],
datasets: [{
label: '',
data: [20, 0, 0, 34343],
backgroundColor: ["rgba(255, 99, 132, 0.2)", "rgba(255, 159, 64, 0.2)", "rgba(255, 205, 86, 0.2)", "rgba(75, 192, 192, 0.2)"],
borderColor: ["rgb(255, 99, 132)", "rgb(255, 159, 64)", "rgb(255, 205, 86)", "rgb(75, 192, 192)"],
borderWidth: 1
}]
},
options: {
legend: {
display: false
},
scales: {
xAxes: [{
type: 'logarithmic',
ticks: {
beginAtZero: true,
userCallback: (value, index) => {
const remain = value / (Math.pow(10, Math.floor(Chart.helpers.log10(value))));
if (remain == 1 || remain == 2 || remain == 5 || index == 0) {
return value.toLocaleString();
}
return '';
}
},
gridLines: {
display: false
}
}]
}
}
});
canvas {
max-width: 400px;
}
<script src="https://cdn.jsdelivr.net/npm/chart.js#2.8.0"></script>
<canvas id="myChart" height="150"></canvas>

ChartJS: Two Titles for positive and negative part of y axis

I'm using a stacked bar chart in ChartJS for plotting the numbers of two contrary datasets. I've managed this by subtracting the numbers of the second dataset from 0, hence we get [-1, -2, -3] instead of [1, 2, 3].
Commonly we would identify the two datasets by a legend.
But is there an option to add two titles on the y-axis, where the top of the y-axis, i.e. on the positive part of the scale, is labeled with "Dataset 1" and the bottom of the y-axis, i.e. on the negative part of the scale, is labeled with "Dataset 2"?
You can draw the y-axis scale labels directly on the canvas using the Plugin Core API. It offers a number of hooks that can be used to perform custom code. In your case, you could use the afterDraw hook as follows:
plugins: [{
afterDraw: chart => {
var ctx = chart.chart.ctx;
ctx.save();
var xAxis = chart.scales["x-axis-0"];
var yAxis = chart.scales["y-axis-0"];
ctx.fillStyle = "rgba(0, 0, 0, 0.8)";
ctx.textAlign = "center";
var fontSize = 12;
ctx.font = fontSize + "px Arial";
ctx.rotate(-Math.PI / 2);
var yZero = yAxis.getPixelForValue(0);
ctx.fillText("Dataset 1", yZero / -2, fontSize);
ctx.fillText("Dataset 2", (yAxis.bottom + yZero) / -2, fontSize);
ctx.restore();
}
}],
You also need to define a value for layout.padding.left inside the chart options in order to avoid that your scale labels overlap the tick labels.
options: {
layout: {
padding: {
left: 12
}
},
Please have a look at below runnable code snippet.
new Chart(document.getElementById("chart"), {
type: "bar",
plugins: [{
afterDraw: chart => {
var ctx = chart.chart.ctx;
ctx.save();
var xAxis = chart.scales["x-axis-0"];
var yAxis = chart.scales["y-axis-0"];
ctx.fillStyle = "rgba(0, 0, 0, 0.8)";
ctx.textAlign = "center";
var fontSize = 12;
ctx.font = fontSize + "px Arial";
ctx.rotate(-Math.PI / 2);
var yZero = yAxis.getPixelForValue(0);
ctx.fillText("Dataset 1", yZero / -2, fontSize);
ctx.fillText("Dataset 2", (yAxis.bottom + yZero) / -2, fontSize);
ctx.restore();
}
}],
data: {
labels: ["A", "B", "C"],
datasets: [{
label: "Dataset 1",
data: [1, 2, 3],
backgroundColor: ["rgba(255, 99, 132, 0.2)", "rgba(255, 159, 64, 0.2)", "rgba(255, 205, 86, 0.2)", "rgba(75, 192, 192, 0.2)"],
borderColor: ["rgb(255, 99, 132)", "rgb(255, 159, 64)", "rgb(255, 205, 86)"],
borderWidth: 1
},
{
label: "Dataset 2",
data: [-1, -2, -3],
backgroundColor: ["rgba(255, 99, 132, 0.2)", "rgba(255, 159, 64, 0.2)", "rgba(255, 205, 86, 0.2)", "rgba(75, 192, 192, 0.2)"],
borderColor: ["rgb(255, 99, 132)", "rgb(255, 159, 64)", "rgb(255, 205, 86)"],
borderWidth: 1
}
]
},
options: {
layout: {
padding: {
left: 12
}
},
legend: {
display: false
},
scales: {
xAxes: [{
stacked: true,
}],
yAxes: [{
stacked: true
}]
}
}
});
canvas {
max-width: 400px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="chart" height="200"></canvas>

Different styles in ticks on ChartJS

Is it possible to separate styles in ticks for xAxes or draw by some other function
near.Callback - just operate with values.
For example:
You can make use of the Plugin Core API. It offers different hooks that may be used for executing custom code. In below code snippet, I use the afterDraw hook to draw text of different styles underneath each bars.
Note that no computation of text size is needed when composing your own labels labels. You can simply add a space to the first text section and use ctx.textAlign 'right', then use ctx.textAlign 'left' for drawing the second text section.
When drawing your own tick labels, you need to instruct Chart.js not to display the default labels. This can be done through the following definition inside the chart options.
scales: {
xAxes: [{
ticks: {
display: false
}
}],
You also need to define some padding for the bottom of the chart, otherwise you won't see your custom tick labels.
layout: {
padding: {
bottom: 20
}
},
new Chart(document.getElementById('myChart'), {
type: 'bar',
plugins: [{
afterDraw: chart => {
var ctx = chart.chart.ctx;
var xAxis = chart.scales['x-axis-0'];
var yAxis = chart.scales['y-axis-0'];
ctx.save();
chart.data.labels.forEach((l, i) => {
var value = chart.data.datasets[0].data[i];
var x = xAxis.getPixelForValue(l);
ctx.textAlign = 'right';
ctx.font = '12px Arial';
ctx.fillText(l + ' ', x, yAxis.bottom + 18);
ctx.textAlign = 'left';
ctx.font = 'bold 14px Arial';
ctx.fillStyle = 'blue';
ctx.fillText(value, x, yAxis.bottom + 17);
});
ctx.restore();
}
}],
data: {
labels: ['Error', 'Warn', 'Info'],
datasets: [{
label: 'My First Dataset',
data: [30, 59, 80],
fill: false,
backgroundColor: ['rgba(255, 99, 132, 0.2)', 'rgba(255, 159, 64, 0.2)', 'rgba(255, 205, 86, 0.2)'],
borderColor: ['rgb(255, 99, 132)', 'rgb(255, 159, 64)', 'rgb(255, 205, 86)', 'rgb(75, 192, 192)'],
borderWidth: 1
}]
},
options: {
layout: {
padding: {
bottom: 20
}
},
scales: {
xAxes: [{
ticks: {
display: false
}
}],
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
});
canvas {
max-width: 400px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.min.js"></script>
<canvas id="myChart" width="10" height="5"></canvas>

Pie chart.js - display a no data held message

I'm using chart.js Version: 2.8.0 to display a bar chart and a pie chart.
The non-empty bar and pie charts display as I want.
However, when the bar and pie charts are empty or have zero data to display, is there a standardised option to display a "No data to display!" message for both the bar and pie charts that can be shown in place of the empty or zero data.
I've searched google for a plugin and SO for a solution, but the options I've found either don't work at all or don't work for the latest version of chartjs.
Here is my empty pie chart:
new Chart(document.getElementById('pieChartExample01'), {
type: 'pie',
data: {
labels: [
'Views',
'Print Requests',
'PDF Downloads',
'DOCX Downloads',
],
datasets: [{
backgroundColor: [
'rgba(71, 101, 160, 0.3)', // #4765a0.
'rgba(0, 0, 0, 0.3)', // #000000.
'rgba(52, 137, 219, 0.3)', // #3489db.
'rgba(179, 179, 179, 0.3)', // #b3b3b3.
],
hoverBackgroundColor: [
'rgba(71, 101, 160, 0.6)', // #4765a0.
'rgba(0, 0, 0, 0.6)', // #000000.
'rgba(52, 137, 219, 0.6)', // #3489db.
'rgba(179, 179, 179, 0.6)', // #b3b3b3.
],
borderWidth: 1,
hoverBorderWidth: 2,
borderColor: [
'rgba(71, 101, 160, 1)', // #4765a0.
'rgba(0, 0, 0, 1)', // #000000.
'rgba(52, 137, 219, 1)', // #3489db.
'rgba(179, 179, 179, 1)', // #b3b3b3.
],
borderAlign: 'inner',
data: [0, 0, 0, 0]
}]
},
options: {
title: {
display: false,
text: 'Overall Activity'
}
}
});
<canvas id="pieChartExample01" width="25" height="25"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js#2.8.0"></script>
This is what I would like the empty pie chart to display as (preferably with the labels):
UPDATE TO ANSWER - 13th April, 2019
Using the answer provided by Core972, I decided to extrapolate the accepted answer so that the message of choice can be displayed over a bar chart and a pie chart with the data labels displayed rather than just a blank canvas.
Here's the solution that I came up with that works with pie and bar charts.
I haven't tested the other types of charts, but I'm presuming they would work with the same approach.
A few pointers to note:
When there is zero data to display, the borders must also be zero, otherwise an annoying border displayed as a single line is displayed. Use a simple if else condition to hide show the borders if necessary.
Use an if else condition to display / hide the message. If there is data, hide the message, else display the message if data is zero.
I've only tested this approach with Chrome & Firefox and seems to work OK.
I hope that this can help someone! Enjoy!
PIE CHART WITH DATA LABELS DISPLAYED AND A FLOATING MESSAGE:
new Chart(document.getElementById('pieChartExample01'), {
type: 'pie',
data: {
labels: [
'Views',
'Print Requests',
'PDF Downloads',
'DOCX Downloads',
],
datasets: [{
backgroundColor: [
'rgba(71, 101, 160, 0.3)', // #4765a0.
'rgba(0, 0, 0, 0.3)', // #000000.
'rgba(52, 137, 219, 0.3)', // #3489db.
'rgba(179, 179, 179, 0.3)', // #b3b3b3.
],
hoverBackgroundColor: [
'rgba(71, 101, 160, 0.6)', // #4765a0.
'rgba(0, 0, 0, 0.6)', // #000000.
'rgba(52, 137, 219, 0.6)', // #3489db.
'rgba(179, 179, 179, 0.6)', // #b3b3b3.
],
borderWidth: 0,
hoverBorderWidth: 0,
borderColor: [
'rgba(71, 101, 160, 1)', // #4765a0.
'rgba(0, 0, 0, 1)', // #000000.
'rgba(52, 137, 219, 1)', // #3489db.
'rgba(179, 179, 179, 1)', // #b3b3b3.
],
borderAlign: 'inner',
data: [0, 0, 0, 0]
}]
},
options: {
title: {
display: false,
text: 'Overall Activity'
}
}
});
<div style="width: 100%; height: 100%; position: relative;">
<div style="text-align: center; width: 100%; height: 100%; position: absolute; left: 0; top: 100px; z-index: 20;">
<b>No data for you today!</b>
</div>
<canvas id="pieChartExample01" width="25" height="25"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js#2.8.0"></script>
</div>
Use this plugin I slightly modified which checks if each dataset item is zero:
<script>
Chart.plugins.register({
afterDraw: function(chart) {
if (chart.data.datasets[0].data.every(item => item === 0)) {
let ctx = chart.chart.ctx;
let width = chart.chart.width;
let height = chart.chart.height;
chart.clear();
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('No data to display', width / 2, height / 2);
ctx.restore();
}
}
});
</script>
With this, if all dataset items are 0, it will display No data to display in place of the chart.
Here an example working with chart.js 2.8.0
<canvas id="pieChartExample01" width="25" height="25"></canvas>
<div id="no-data">Nothing to display</div>
...
options: {
title: {
display: false,
text: 'Overall Activity'
},
animation: {
onComplete: function(animation) {
var firstSet = animation.chart.config.data.datasets[0].data,
dataSum = firstSet.reduce((accumulator, currentValue) => accumulator + currentValue);
if (typeof firstSet !== "object" || dataSum === 0) {
document.getElementById('no-data').style.display = 'block';
document.getElementById('pieChartExample01').style.display = 'none'
}
}
}
}
Fiddle
For React (react-chartjs-2)
Use this plugin
const plugins = [
{
afterDraw: function (chart) {
console.log(chart);
if (chart.data.datasets[0].data.length < 1) {
let ctx = chart.ctx;
let width = chart.width;
let height = chart.height;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.font = "30px Arial";
ctx.fillText("No data to display", width / 2, height / 2);
ctx.restore();
}
},
},
];
Use this plugin
<Line height={120} data={props.data} options={options} plugins={plugins} />
<Pie height={320} width={500} data={props.data} options={options} plugins={plugins} />
For those using version 3.x of the library, here's how I did it
const plugin = {
id: 'emptyChart',
afterDraw(chart, args, options) {
const { datasets } = chart.data;
let hasData = false;
for (let dataset of datasets) {
//set this condition according to your needs
if (dataset.data.length > 0 && dataset.data.some(item => item !== 0)) {
hasData = true;
break;
}
}
if (!hasData) {
//type of ctx is CanvasRenderingContext2D
//https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D
//modify it to your liking
const { chartArea: { left, top, right, bottom }, ctx } = chart;
const centerX = (left + right) / 2;
const centerY = (top + bottom) / 2;
chart.clear();
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('No data to display', centerX, centerY);
ctx.restore();
}
}
};
And just use the above like so:
const chart1 = new Chart(ctx, {
plugins: [plugin]
});
You need to set some data in data: [0, 0, 0, 0]
It's imposible to display pie graph without any data i try to change only data and all it's good.
...
data: [2, 3, 4, 5]
...
https://jsfiddle.net/myeLq37j/

Chart.js - Writing Labels Inside of Horizontal Bars?

In Chart.js, is there any way to write the labels inside of the horizontal bars in a "horizontalBar" chart? As in something like:
Is anything similar to this possible in Chart.js?
Thanks!
There is now on option "mirror" to make the label appear inside the bar.
Example of "options" config for a horizontalBar chart :
options: {
scales: {
yAxes: [{ticks: {mirror: true}}]
}
}
Doc : http://www.chartjs.org/docs/latest/axes/cartesian/#common-configuration
With reference to the original solution to displaying data value by using HTML5 Canvas fillText() method in the animation's onComplete, the code below will get you what you need:
The key to custom-displaying any chart related data is inspecting the chart's dataset as in this.data.datasets below. I did this by inspecting where the different values are in this dataset, as well as the position to place them for the fillText method.
Inspecting the chart's dataset: Image
Script (Chart.js 2.0 & up):
var barOptions = {
events: false,
showTooltips: false,
legend: {
display: false
},
scales:{
yAxes: [{
display: false
}]
},
animation: {
onComplete: function () {
var ctx = this.chart.ctx;
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontFamily, 'normal', Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'left';
ctx.textBaseline = 'bottom';
this.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,
left = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._xScale.left;
ctx.fillStyle = '#444'; // label color
var label = model.label;
ctx.fillText(label, left + 15, model.y + 8);
}
});
}
}
};
jsFiddle: http://jsfiddle.net/jfvy12h9/
You can achieve this effect in chartjs 2.0 + by rotating the labels 90 degrees and applying negative padding, so that the label moves up inside the 'bar'. Something like this:
new Chart(document.getElementById(id), {
type: 'bar',
data: data,
options: {
scales: {
xAxes: [
{
ticks: {
autoSkip: false,
maxRotation: 90,
minRotation: 90,
padding: -110
}
}
]
}
}
});
See the tick configuration documentation for more information.
It seems like there is no direct option for this right now. A possible implementation is iterating over bars/columns by using onAnimationComplete hook. You can view this question for detailed explanations how to display data values on Chart.js
I would like you remind that this has a drawback. Since it uses onAnimationComplete, whenever an animation happens on chart, values will be disappear for a second and appear again.
Since on "bar" chart doesn't work the "mirror" option...
I've found a workaround solution:
Fiddle link
I used a chart.js 2.8.0 version modified in this link
<html>
<body>
<canvas id="myChart" width="800" height="600">
</canvas>
</body>
</html>
var ctx = document.getElementById("myChart").getContext("2d");
var myChart = new Chart(ctx, {
type: "bar",
data: {
labels: ["ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT"],
datasets: [{
label: "Quota Eni mensile",
backgroundColor: [
"rgba(255, 255, 153, 1)",
"rgba(255, 255, 153, 1)",
"rgba(255, 255, 153, 1)",
"rgba(255, 255, 153, 1)",
"rgba(255, 255, 153, 1)",
"rgba(255, 255, 153, 1)",
"rgba(255, 255, 153, 1)",
"rgba(255, 255, 153, 1)"
],
borderColor: [
"rgba(255, 255, 153, 1)",
"rgba(255, 255, 153, 1)",
"rgba(255, 255, 153, 1)",
"rgba(255, 255, 153, 1)",
"rgba(255, 255, 153, 1)",
"rgba(255, 255, 153, 1)",
"rgba(255, 255, 153, 1)",
"rgba(255, 255, 153, 1)"
],
borderWidth: 1,
data: [10, 11, 18, 10, 13, 28, 12, 16]
}
]
},
options: {
legend: {
display: false
},
scales: {
xAxes: [{
ticks:{
maxRotation: 90,
minRotation: 90,
display: "top"
},
}],
yAxes: [{
display: false
}]
},
tooltips: {
enabled: true
},
maintainAspectRatio: true,
responsive: true
}
});
Chart.plugins.register({
/* Adjust axis labelling font size according to chart size */
id: "responsiveXlabels",
beforeDraw: function(c) {
var chartHeight = c.chart.height;
var size = (chartHeight * 2.5) / 100;
var xAxis = c.scales["x-axis-0"];
xAxis.options.ticks.minor.fontSize = size;
xAxis.height = 10;
xAxis.minSize.height = 10;
c.options.scales.xAxes[0].ticks.padding = -size * 4.5;
xAxis.margins.bottom = size * 12.5;
},
afterDraw: function(c) {
c.options.scales.xAxes[0].ticks.padding = -158;
}
});
the responsiveXlabel plugin is used to transform text on resize.
There's only one thing remained to solve: align text at the bottom of the axis.
Hope this helps who wants to implement mirroring on X Axis and satisfy responsive request.

Categories