Chart.js stacked bar chart in opposite direction - javascript

I am trying to achieve something like this using chart.js. I wanted to show data of male/female according to each age group:
Here is my chart options:
var options = {
layout: {
padding: {
top: 5,
}
},
scales:
{
yAxes: [{
display: true,
barPercentage: 0.4,
ticks: {
fontSize: 12
},
stacked: true,
}],
xAxes: [{
stacked: true,
}]
},
responsive: true,
maintainAspectRatio: false,
legend: {
display: false,
},
animation: {
animateScale: true,
animateRotate: true
},
};
var opt = {
type: "horizontalBar",
data: {
labels: ageGroup,
datasets: [{
label: 'Male',
data: maleData,
backgroundColor: '#2196F3',
hoverBackgroundColor: '#2196F3'
},
{
label: 'Female',
data: femaleData,
backgroundColor: '#E91E63',
hoverBackgroundColor: '#E91E63'
}]
},
options: options
};
I changed the positive in femaleData array into a negative number to achieve the result above:
for (var i = 0; i < femaleData.length; i++) {
femaleData[i] = -Math.abs(femaleData[i]);
}
However, the y-axis at 0 is not centralized as it pushed to the right hand side since left hand side got more data. I not even sure if this is the correct way to set the chart in opposite direction. How can I do this correctly?

as per the requirements mentioned in OP­'s comment section
ꜱʜᴏᴡ ᴘᴏꜱɪᴛɪᴠᴇ ᴠᴀʟᴜᴇꜱ ᴏɴ x-ᴀxɪꜱ
use the following callback function for x-axis ticks :
callback: function(t, i) {
return t < 0 ? Math.abs(t) : t;
}
ꜱʜᴏᴡ ᴘᴏꜱɪᴛɪᴠᴇ ᴠᴀʟᴜᴇ ᴏɴ ᴛᴏᴏʟᴛɪᴘ
use the following callback function for tooltips :
callbacks: {
label: function(t, d) {
var datasetLabel = d.datasets[t.datasetIndex].label;
var xLabel = Math.abs(t.xLabel);
return datasetLabel + ': ' + xLabel;
}
}
ᴡᴏʀᴋɪɴɢ ᴇxᴀᴍᴘʟᴇ ⧩
var ageGroup = ['0-10', '11-20', '21-30', '31-40', '41-50', '51-60', '61-70', '71-80', '80+'];
var maleData = [30, 0, 0, 0, 10, 0, 0, 0, 0];
var femaleData = [0, 0, 0, -20, -50, -20, 0, 0, 0];
var options = {
layout: {
padding: {
top: 5,
}
},
scales: {
yAxes: [{
display: true,
barPercentage: 0.4,
ticks: {
fontSize: 12
},
stacked: true,
}],
xAxes: [{
stacked: true,
ticks: {
callback: function(t, i) {
return t < 0 ? Math.abs(t) : t;
}
}
}]
},
tooltips: {
callbacks: {
label: function(t, d) {
var datasetLabel = d.datasets[t.datasetIndex].label;
var xLabel = Math.abs(t.xLabel);
return datasetLabel + ': ' + xLabel;
}
}
},
responsive: true,
//maintainAspectRatio: false,
legend: {
display: false,
},
animation: {
animateScale: true,
animateRotate: true
},
};
var opt = {
type: "horizontalBar",
data: {
labels: ageGroup,
datasets: [{
label: 'Male',
data: maleData,
backgroundColor: '#2196F3',
hoverBackgroundColor: '#2196F3'
}, {
label: 'Female',
data: femaleData,
backgroundColor: '#E91E63',
hoverBackgroundColor: '#E91E63'
}]
},
options: options
};
new Chart(ctx, opt);
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.min.js"></script>
<canvas id="ctx"></canvas>

Related

Skip chartjs tick label if they overlap

This is the final piece following on from this post
I have the following (ongoing) chartjs setup (thanks to #winnder_joiner)
const d0 = new Date("2023-02-15T00:00:00.721Z").getTime()
const d1 = new Date("2023-02-15T00:03:00.721Z").getTime()
const d2 = new Date("2023-02-15T02:30:00.721Z").getTime()
const d3 = new Date("2023-02-15T03:20:00.721Z").getTime()
const d4 = new Date("2023-02-15T05:05:00.721Z").getTime()
let values = [d0, d1, d2, d3, d4];
let data = {
labels: [''],
datasets: [{
label: 'up',
axis: 'y',
data: [d1],
backgroundColor: 'red',
},{
label: 'down',
//axis: 'y',
data: [d2],
backgroundColor: 'yellow',
},{
label: 'out',
// axis: 'y',
data: [d3],
backgroundColor: 'green',
},{
label: 'up',
// axis: 'y',
data: [d4],
backgroundColor: 'red',
}
]
};
const config = {
data,
type: 'bar',
options:{
elements: {
bar: {
borderWidth: 0
}
},
ticks: {
display: true
},
interaction: {
mode: 'dataset'
},
// tooltip: {
// mode: 'dataset'
// },
hover: {
mode: 'dataset'
},
plugins: {
legend: {
display: false,
},
title: {
display: false,
},
},
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
border: {
display: false,
},
min: d0,
ticks: {
source: 'auto',
maxRotation: 80,
minRotation: 60,
autoSkip: false,
// autoSkipPadding: 0,
//padding: 0,
//align: 'inner',
//crossAlign: 'near',
callback: function(value, index, ticks) {
return moment(value).format('HH:mm');
}
},
afterBuildTicks: axis => axis.ticks = values.map(v => ({ value: v }))
},
y: {
grid: {
display: false
},
stacked: true
},
}
}};
new Chart(document.getElementById("chart"), config);
This can also be seen here
The callback afterBuildTicks allows the ticks to be draw at the actual data points - which is good. If we don't use afterBuildTicks it just automatically works out some points to draw the ticks.
The only problem occurs if two data points happen to be very close, eg we get the lables overlapping....
My question here is..
Is there any way to have it skip drawing the ticks label if there is such an overlap?
Well As far as I know, you can prevent the overlap with the config (more than you already did), BUT you could do it yourself just calculating the diff between the ticks. You would have to eyeball die minDiff, and in the tick generation return null if the ticks are too close.
I tried it in the following example:
(It is not prefect, because if there are many after each other, that are too short, "too many" ticks might be skipped)
In this example 11:29 could/should be shown, because 11:00 was not displayed.
const d0 = moment.duration('08:50:00').asMinutes();
const d1 = moment.duration('09:00:00').asMinutes();
const d2 = moment.duration('10:45:00').asMinutes();
const d22 = moment.duration('11:00:00').asMinutes();
const d23 = moment.duration('11:29:00').asMinutes();
const d3 = moment.duration('17:35:00').asMinutes();
const d4 = moment.duration('19:00:00').asMinutes();
let values = [d0, d1, d2, d22, d23, d3, d4];
let data = {
labels: [''],
datasets: [{
label: 'up',
axis: 'y',
data: [d1],
backgroundColor: 'red',
},{
label: 'down',
axis: 'y',
data: [d2],
backgroundColor: 'yellow',
},{
label: 'uppsy',
axis: 'y',
data: [d22],
backgroundColor: 'green',
},{
label: 'uppsy1',
axis: 'y',
data: [d23],
backgroundColor: 'red',
},{
label: 'out',
axis: 'y',
data: [d3],
backgroundColor: 'yellow',
},{
label: 'up',
axis: 'y',
data: [d4],
backgroundColor: 'red',
}
]
};
const config = {
data,
type: 'bar',
options:{
plugins: {
tooltip: {
mode: 'dataset',
callbacks: {
label: function(item){
return moment().startOf('day').add({ minute: item.raw}).format('HH:mm');
}
}
},
legend: {
display: false,
},
title: {
display: false,
},
},
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
min: d0,
border: { display: false },
ticks: {
source: 'auto',
maxRotation: 80,
minRotation: 60,
autoSkip: false,
callback: function(value, index, ticks) {
let minDiff = 30;
if(index > 0 && value - ticks[index-1].value < minDiff){
console.info(ticks[index-1].value - value )
return null;
}
return moment().startOf('day').add({ minute: value}).format('HH:mm');
}
},
afterBuildTicks: axis => axis.ticks = values.map(v => ({ value: v }))
},
y: {
stacked: true,
grid: { display: false },
},
}
}};
new Chart(document.getElementById("chart"), config);
<script src="//cdn.jsdelivr.net/npm/chart.js"></script>
<script src="//cdn.jsdelivr.net/npm/moment#^2"></script>
<script src="//cdn.jsdelivr.net/npm/chartjs-adapter-moment#^1"></script>
<div class="chart" style="height:84px; width:350px;">
<canvas id="chart" ></canvas>
</div>
Update -maybe better solution:
You could filter the possible ticks before drawing the ticks, this makes it easier to see, if the minDiff is applied in the correct manner.
values = values.reduce( (pre, curr, index) => {
if(index == 0 || (curr - pre[pre.length-1] > minDiff )){
pre.push(curr);
}
return pre;
}, [])
Here the full working demo:
const d0 = moment.duration('08:50:00').asMinutes();
const d1 = moment.duration('09:00:00').asMinutes();
const d2 = moment.duration('10:45:00').asMinutes();
const d22 = moment.duration('11:00:00').asMinutes();
const d23 = moment.duration('11:29:00').asMinutes();
const d3 = moment.duration('17:35:00').asMinutes();
const d4 = moment.duration('19:00:00').asMinutes();
let minDiff = 30;
let values = [d0, d1, d2, d22, d23, d3, d4];
/* prepare the ticks */
values = values.reduce( (pre, curr, index) => {
if(index == 0 || (curr - pre[pre.length-1] > minDiff )){
pre.push(curr);
}
return pre;
}, [])
let data = {
labels: [''],
datasets: [{
label: 'up',
axis: 'y',
data: [d1],
backgroundColor: 'red',
},{
label: 'down',
axis: 'y',
data: [d2],
backgroundColor: 'yellow',
},{
label: 'uppsy',
axis: 'y',
data: [d22],
backgroundColor: 'green',
},{
label: 'uppsy1',
axis: 'y',
data: [d23],
backgroundColor: 'red',
},{
label: 'out',
axis: 'y',
data: [d3],
backgroundColor: 'yellow',
},{
label: 'up',
axis: 'y',
data: [d4],
backgroundColor: 'red',
}
]
};
const config = {
data,
type: 'bar',
options:{
plugins: {
tooltip: {
mode: 'dataset',
callbacks: {
label: function(item){
return moment().startOf('day').add({ minute: item.raw}).format('HH:mm');
}
}
},
legend: {
display: false,
},
title: {
display: false,
},
},
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
min: d0,
border: { display: false },
ticks: {
source: 'auto',
maxRotation: 80,
minRotation: 60,
autoSkip: false,
callback: function(value, index, ticks) {
return moment().startOf('day').add({ minute: value}).format('HH:mm');
}
},
afterBuildTicks: axis => axis.ticks = values.map(v => ({ value: v }))
},
y: {
stacked: true,
grid: { display: false },
},
}
}};
new Chart(document.getElementById("chart"), config);
<script src="//cdn.jsdelivr.net/npm/chart.js"></script>
<script src="//cdn.jsdelivr.net/npm/moment#^2"></script>
<script src="//cdn.jsdelivr.net/npm/chartjs-adapter-moment#^1"></script>
<div class="chart" style="height:84px; width:350px;">
<canvas id="chart" ></canvas>
</div>

chart js 3.9.1 - barchart - margin left

how do i reduce the space between the bar and the y scale? I tried with padding but it doesn't work
Thanks
This is my configuration,I upload the data at runtime and update the graph
Chart.register(ChartDataLabels);
var options = {
tooltips: {
enabled: false
},
plugins:
{
datalabels:
{
color: 'white'
},
legend: {
position: 'top', labels:
{
font: {size: 10},
usePointStyle: true
}
}
},
scales:
{
x: {
barPercentage: 6.5,
categoryPercentage: 0.1
},
y: {
barPercentage: 1,
categoryPercentage: 1,
beginAtZero: true,
grace: '3%',
ticks: {
beginAtZero: true
}
}
}
};
var barChart = new Chart(document.getElementById("chart3"), {
type: 'bar',
data:
{
labels: [""]
},
options:options
});
for (i = 0; i < parsed[0].length; i++)
{
barChart.data.datasets.push({ label: parsed[0][i].label, backgroundColor: parsed[0][i].backgroundColor, data: ["" + parsed[0][i].data + ""], pointStyle:'circle' });
}
barChart.update();
The issue sounds being related to categoryPercentage and barPercentage options that you are set in the axis but this is not the right place.
Move those options in the chart.options and it should work.
Try using categoryPercentage: 1.
var options = {
categoryPercentage: 1,
plugins:
{
datalabels:
{
color: 'white'
},
legend: {
position: 'top', labels:
{
font: {size: 10},
usePointStyle: true
}
}
tooltip: {
enabled: false
},
},
scales:
{
y: {
beginAtZero: true,
grace: '3%',
}
}
};

ChartJS How to set color to just one bar

I have this barChart and I'm with difficult to set a color by script to just one bar of the index.
Bellow is my chartbar code:
var ctx = document.getElementById("myBarChart");
var myBarChart = new Chart(ctx, {
type: 'bar',
data: {
labels: [],
datasets: [{
label: "Valor",
backgroundColor: "rgba(2,117,216,1)",
borderColor: "rgba(2,117,216,1)",
data: [],
}],
},
options: {
plugins: {
datalabels: {
anchor: 'end',
align: 'top',
formatter: function (value, context) {
if (value != 0) {
return new Date(value * 1000).toISOString().substr(11, 8);
} else {
value = " "
return value;
}
},
font: {
weight: 'bold'
}
}
},
responsive: true,
tooltips: {
/* intersect: false, */
callbacks: {
title: function (tooltipItem, data) {
return data['labels'][tooltipItem[0]['index']];
},
label: function (tooltipItem, data) {
return new Date(parseInt(data['datasets'][0]['data'][tooltipItem[
'index']]) * 1000).toISOString().substr(11,
8);
},
},
},
scales: {
xAxes: [{
time: {
unit: 'month'
},
gridLines: {
display: false
},
}],
yAxes: [{
ticks: {
min: 0,
suggestedMax: 36000,
stepSize: 3600,
beginAtZero: true,
},
}],
},
}
});
I have tried this:
myBarChart.data.datasets[0].backgroundColor[0]
But it doesnt work.
If someone know how to do this in the last version of the chartjs, please help me.
You can provide an array to the backgroundColor property. In this array you can fill it with the default color except for the index where you want the bar to be a different collor

How to reduce number of multiple gridlines in stack bar graph chart.js

I am working on chart.js for implementing stack bar graph. That graph has few issues like
Not showing tick size vertically at left
Showing unwanted horizontal gridlines
Displaying horizontal thick line on top instead of bottom
This is my code
public stackbar()
{
Chart.defaults.global.datasets.bar.barPercentage = 0.5;
Chart.defaults.global.datasets.bar.categoryPercentage = 0.5;
var colors = ['#299DFF','#80FFFF','#F8362B',];
var chBar = document.getElementById("mychart");
var chartData = {
labels: ["Product"],
datasets: [{
label: 'P2',
data: [29566],
backgroundColor: colors[0]
},
{
label: 'P3',
data: [O2],
backgroundColor: colors[1]
},
{
label: 'P4',
data: [3],
backgroundColor: colors[2]
}
]
};
if (chBar) {
new Chart(chBar, {
type: 'bar',
data: chartData,
options: {
scales: {
xAxes: [{
gridLines: {
display:false
},
//barPercentage: 0.5,
//categoryPercentage: 0.5
barPercentage: 0.5,
categoryPercentage: 0.5
}
],
yAxes: [{
gridLines: {
display:true
},
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 '';
},
suggestedMax: 80,
padding: 25
}
}]
},
legend: {
display: true,
maxWidth: 100,
padding:30,
fullWidth:true,
position: 'bottom',
lineHeight: 12,
justifyContent: 'space-between',
labels: {
fontSize: 10,
usePointStyle: true
}
},
}
});
}
This is the screenshot
How can I fix these issues?
The horizontal grid lines can be removed through the following configuration in the chart options:
yAxes: [{
gridLines: {
display: false
},
I changed this in your code and removed a few unnecessary definitions and it looks just fine to me as you can see in the following runnable code.
var colors = ['#299DFF', '#80FFFF', '#F8362B'];
var chartData = {
labels: ["Product"],
datasets: [{
label: 'P2',
data: [29566],
backgroundColor: colors[0]
},
{
label: 'P3',
data: [2],
backgroundColor: colors[1]
},
{
label: 'P4',
data: [3],
backgroundColor: colors[2]
}
]
};
new Chart('myChart', {
type: 'bar',
data: chartData,
options: {
scales: {
xAxes: [{
barPercentage: 0.5,
categoryPercentage: 0.5
}
],
yAxes: [{
gridLines: {
display: false
},
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 '';
}
}
}]
},
legend: {
display: true,
maxWidth: 100,
padding: 30,
fullWidth: true,
position: 'bottom',
lineHeight: 12,
justifyContent: 'space-between',
labels: {
fontSize: 10,
usePointStyle: true
}
}
}
});
<script src="https://cdn.jsdelivr.net/npm/chart.js#2.8.0"></script>
<canvas id="myChart" height="150"></canvas>

How to display final point value in charts.js

I'm using Charts.js library to display a line graph from data which is constantly updated. Is there a way to display only the final point value at all times? I want it to do this to monitor the added values at all times.
javascript code:
var config2 = {
type: 'line',
data: {
labels: 0,
datasets: [{
label: "No selection",
lineTension: 0,
borderColor: "rgba(222, 44, 31)",
pointBackgroundColor: "#fff675",
fill: false,
data: 0,
}
]
},
options: {
responsive: true,
title: {
display: false,
text: 'Chart.js Time Point Data'
},
scales: {
x: {
type: 'time',
display: true,
scaleLabel: {
display: true,
labelString: 'Date'
},
ticks: {
major: {
enabled: true
},
fontStyle: function(context) {
return context.tick && context.tick.major ? 'bold' : undefined;
},
fontColor: function(context) {
return context.tick && context.tick.major ? '#FF0000' : undefined;
}
}
},
y: {
display: true,
scaleLabel: {
display: true,
labelString: 'value'
}
}
}
}
};
Each time, a new value is available, you can simply remove outdated labels and dataset.data values once a certain limit is reached. This can be done using Array.shift(), which removes the first element from an array. Once these array are updated, you need to invoke chart.update().
var maxValues = 4;
setInterval(() => {
chart.data.labels.push(new Date());
chart.data.datasets[0].data.push(Math.floor((Math.random() * 20) + 1));
if (chart.data.labels.length > maxValues) {
chart.data.labels.shift();
chart.data.datasets[0].data.shift();
}
chart.update();
}, 1000);
For displaying the value on the last added data point, you can use the Plugin Core API. It offers different hooks that may be used for executing custom code. In below runnable code snippet, I use the afterDraw hook to draw text directly on the canvas.
var chart = new Chart('chart', {
type: "line",
plugins: [{
afterDraw: chart => {
var ctx = chart.chart.ctx;
var xAxis = chart.scales['x-axis-0'];
var yAxis = chart.scales['y-axis-0'];
var iLastValue = chart.data.labels.length - 1;
var lastValue = chart.data.datasets[0].data[iLastValue];
var x = xAxis.getPixelForValue(chart.data.labels[iLastValue]);
var y = yAxis.getPixelForValue(lastValue);
ctx.save();
ctx.textAlign = 'center';
ctx.font = '14px Arial';
ctx.fillStyle = "red";
ctx.fillText('Value: ' + lastValue, x, y - 15);
ctx.restore();
}
}],
responsive: true,
maintainAspectRatio: false,
data: {
labels: [],
datasets: [{
label: "Data",
data: [],
fill: false,
lineTension: 0,
backgroundColor: "white",
borderColor: "red",
}]
},
options: {
layout: {
padding: {
right: 50
}
},
scales: {
xAxes: [{
type: 'time',
ticks: {
source: 'auto'
},
time: {
unit: 'second',
displayFormats: {
second: 'mm:ss'
},
tooltipFormat: 'mm:ss'
},
}],
yAxes: [{
ticks: {
min: 0,
max: 20,
stepSize: 5
}
}]
}
}
});
var maxValues = 4;
setInterval(() => {
chart.data.labels.push(new Date());
chart.data.datasets[0].data.push(Math.floor((Math.random() * 20) + 1));
if (chart.data.labels.length > maxValues) {
chart.data.labels.shift();
chart.data.datasets[0].data.shift();
}
chart.update();
}, 1000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.min.js"></script>
<canvas id="chart" height="90"></canvas>

Categories

Resources