Can I offset the bars in the chart.js stacked bar chart like so:
Based on this answer, I created a runnable code snippet that illustrates how it could be done. I'm not using stacked bars because the use case is not clear to me and the image from the question rather looks like a bar with shadows.
const dataset = [40, 80, 50, 60, 70];
const offset = 8;
Chart.pluginService.register({
afterUpdate: function(chart) {
var dataset = chart.config.data.datasets[1];
for (var i = 0; i < dataset._meta[0].data.length; i++) {
var model = dataset._meta[0].data[i]._model;
model.x += offset;
model.controlPointNextX += offset;
model.controlPointPreviousX += offset;
}
}
});
var data = {
labels: ["A", "B", "C", "D", "E"],
datasets: [{
backgroundColor: [
'rgba(255, 99, 132)',
'rgba(255, 206, 86)',
'rgba(54, 162, 235)',
'rgba(75, 192, 192)',
'rgba(153, 102, 255)'
],
borderWidth: 1,
data: dataset,
xAxisID: "bar-x-axis1",
categoryPercentage: 0.5,
barPercentage: 0.5,
},
{
backgroundColor: 'rgba(0, 0, 0, 0.2)',
data: dataset.map(v => v + offset),
xAxisID: "bar-x-axis2",
categoryPercentage: 0.5,
barPercentage: 0.5
}
]
};
var options = {
legend: {
display: false
},
tooltips: {
enabled: false
},
scales: {
xAxes: [
{
id: "bar-x-axis2"
},
{
id: "bar-x-axis1",
offset: true,
ticks: {
display: false
}
}
],
yAxes: [{
id: "bar-y-axis1",
ticks: {
beginAtZero: true,
stepSize: 50
}
}]
}
};
var ctx = document.getElementById("myChart").getContext("2d");
var myBarChart = new Chart(ctx, {
type: 'bar',
data: data,
options: options
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="myChart" height="60"></canvas>
For Chart.js v3, you can use a different approach.
In the beforeDatasetsDraw, you save the define the shadow properties on the canvas through CanvasRenderingContext2D. Make sure to first save the state of the rendering context by invoking ctx.save().
In the afterDatasetsDraw hook, you need to restore the state of the rendering context by invoking ctx.restore().
Convert the bars into floating bars through data: data.map(v => [-20, v]) to makes sure, the shadows appear from the base of the bars.
Define a tooltip.callbacks.label function that provides the initial values in the tooltips.
Please take a look at below runnable code and see how it works.
const data = [40, 80, 50, 60, 70];
const offset = 10;
new Chart('myChart', {
type: 'bar',
plugins: [{
beforeDatasetsDraw: chart => {
const ctx = chart.ctx;
ctx.save();
ctx.shadowOffsetX = offset;
ctx.shadowOffsetY = -offset;
ctx.shadowBlur = 5;
ctx.shadowColor = 'rgb(220, 220, 220)';
},
afterDatasetsDraw: chart => chart.ctx.restore()
}],
data: {
labels: ["A", "B", "C", "D", "E"],
datasets: [{
label: 'My Data',
data: data.map(v => [-20, v]),
backgroundColor: [
'rgba(255, 99, 132)',
'rgba(255, 206, 86)',
'rgba(54, 162, 235)',
'rgba(75, 192, 192)',
'rgba(153, 102, 255)'
],
categoryPercentage: 0.8
}]
},
options: {
plugins: {
tooltip: {
callbacks: {
label: ctx => data[ctx.dataIndex]
}
}
},
scales: {
y: {
min: 0,
max: Math.max(...data) + offset
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.1/chart.min.js"></script>
<canvas id="myChart" height="100"></canvas>
Related
As the title suggests, I would like to create a pie chart like an attachment using chart.js in Node-RED.
It's a pie chart showing the activity record of the day, but I'm worried because I can't set the two-axis label.
You can use chartjs-plugin-datalabels and define a dataset with weight: 0 for displaying the numbers.
{
data: [...Array(24)].map(v => 1),
weight: 0,
rotation: -7.5,
borderWidth: 20,
datalabels: {
formatter: (v, ctx) => ctx.dataIndex
}
}
For improving the positioning of the numbers, please consult the chartjs-plugin-datalabels documentation here.
Please take a look at the runnable code below and see how it works.
Chart.register(ChartDataLabels);
new Chart('myChart', {
type: 'pie',
data: {
labels: ['red', 'blue', 'green', 'gray'],
datasets: [{
data: [...Array(24)].map(v => 1),
weight: 0,
rotation: -7.5,
borderWidth: 20,
datalabels: {
formatter: (v, ctx) => ctx.dataIndex
}
},
{
label: 'My Dataset',
data: [48, 56, 33, 44],
backgroundColor: ['rgba(255, 0, 0, 0.2)', 'rgba(0, 0, 255, 0.2)', 'rgba(0, 255, 0, 0.2)', 'rgba(124, 124, 124, 0.2)'],
datalabels: {
textAlign: 'center',
formatter: (v, ctx) => {
const dataset = ctx.chart.data.datasets[1];
return ctx.chart.data.labels[ctx.dataIndex] + ': ' + dataset.data[ctx.dataIndex];
}
}
}
]
},
options: {
responsive: false,
plugins: {
legend: {
display: false
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.8.0/chart.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels#2"></script>
<canvas id="myChart" height="300"></canvas>
I'm working on a project that could display sensing data on local web pages in real time with Django and I made this stream live data chart. But I don't need to this realtime x-axis ticks. I want to make the x-axis starting at zero and increasing 1 step by one second.
I think "xAxes type realtime" is the key, but I'm new to this and it's hard for me to solve alone, so I need your help.
enter image description here
<script>
var chartColors = {
red: 'rgb(255, 99, 132)',
orange: 'rgb(255, 159, 64)',
yellow: 'rgb(255, 205, 86)',
green: 'rgb(75, 192, 192)',
blue: 'rgb(54, 162, 235)',
purple: 'rgb(153, 102, 255)',
grey: 'rgb(201, 203, 207)'
};
function onRefresh(chart) {
var now = Date.now();
chart.data.datasets.forEach(function(dataset) {
dataset.data.push({
x: now,
y: GetData()
});
});
}
var color = Chart.helpers.color;
var config = {
type: 'line',
data: {
datasets: [{
backgroundColor: [
'rgba(100, 200, 200, 0)'
],
borderColor: chartColors.blue,
fill: false,
lineTension: 0,
borderDash: [8, 0],
data: [],
borderWidth:3,
pointRadius:0,
}]
},
options: {
legend:{
display:false
},
title: {
display: true,
text: 'Some Chart',
fontSize:20,
fontFamily:"Tahoma"
},
scales: {
xAxes: [{
ticks:{
beginAtZero: true
},
gridLines:{
display:true
},
type: 'realtime',
display:true,
position:'bottom',
realtime: {
duration: 20000,
refresh: 1000,
delay: 0,
onRefresh: onRefresh
}
}],
yAxes: [{
gridLines:{
display:true
},
scaleLabel: {
display: true,
labelString: 'value'
}
}]
},
}
};
window.onload = function() {
var ctx = document.getElementById('myChart').getContext('2d');
window.myChart = new Chart(ctx, config);
};
</script>
Is there any other way to make streaming live graph with Django, please let me know.
I solved this myself. The key was about 'xAxes ticks callback'. thank you.
I use Chart.js and stack groups bar chart.
The problem is that I'd like to show the data from the biggest (higher) to the smallest (lower) to make it more readable.
Screenshot:
Use external tooltips, in the var bodyLines from the example are the items of the tooltip, if you process the array beforehand that it is sorted correctly you will have a sorted tooltip:
https://www.chartjs.org/docs/latest/configuration/tooltip.html#external-custom-tooltips
Using external tooltip as base from chartjs samples:
Chart.defaults.pointHitDetectionRadius = 1;
var getOrCreateTooltip = function(chart) {
var tooltipEl = chart.canvas.parentNode.querySelector('div');
if (!tooltipEl) {
tooltipEl = document.createElement('div');
tooltipEl.classList.add('chartjs-tooltip');
tooltipEl.innerHTML = '<table></table>';
chart.canvas.parentNode.appendChild(tooltipEl);
}
return tooltipEl;
};
var customTooltips = function(context) {
// Tooltip Element
var chart = context.chart;
var tooltipEl = getOrCreateTooltip(chart);
// Hide if no tooltip
var tooltip = context.tooltip;
if (tooltip.opacity === 0) {
tooltipEl.style.opacity = 0;
return;
}
// Set caret Position
tooltipEl.classList.remove('above', 'below', 'no-transform');
if (tooltip.yAlign) {
tooltipEl.classList.add(tooltip.yAlign);
} else {
tooltipEl.classList.add('no-transform');
}
function getBody(bodyItem) {
return bodyItem.lines;
}
// Set Text
if (tooltip.body) {
var titleLines = tooltip.title || [];
var bodyLines = tooltip.body.map(getBody);
// make object by parsing the last number with regex (the value) and then sort it, add index to it for color later on
var objectBodyLines = bodyLines.map((input, index) => ({number: /(\d+)(?!.*\d)/.exec(input)[0], whole: input[0], index})).sort((a,b) => {return (a.number > b.number) ? 1 : ((b.number > a.number) ? -1 : 0)});
var innerHtml = '<thead>';
titleLines.forEach(function(title) {
innerHtml += '<tr><th>' + title + '</th></tr>';
});
innerHtml += '</thead><tbody>';
objectBodyLines.forEach(function(body, i) {
var colors = tooltip.labelColors[body.index];
var style = 'background:' + colors.backgroundColor;
style += '; border-color:' + colors.borderColor;
style += '; border-width: 2px';
var span = '<span class="chartjs-tooltip-key" style="' + style + '"></span>';
innerHtml += '<tr><td>' + span + body.whole + '</td></tr>';
});
innerHtml += '</tbody>';
var tableRoot = tooltipEl.querySelector('table');
tableRoot.innerHTML = innerHtml;
}
var positionY = chart.canvas.offsetTop;
var positionX = chart.canvas.offsetLeft;
// Display, position, and set styles for font
tooltipEl.style.opacity = 1;
tooltipEl.style.left = positionX + tooltip.caretX + 'px';
tooltipEl.style.top = positionY + tooltip.caretY + 'px';
tooltipEl.style.font = tooltip.options.bodyFont.string;
tooltipEl.style.padding = tooltip.yPadding + 'px ' + tooltip.xPadding + 'px';
};
var lineChartData = {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [{
label: 'My First dataset',
borderColor: window.chartColors.red,
pointBackgroundColor: window.chartColors.red,
fill: false,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor()
]
}, {
label: 'My Second dataset',
borderColor: window.chartColors.blue,
pointBackgroundColor: window.chartColors.blue,
fill: false,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor()
]
}]
};
window.onload = function() {
var chartEl = document.getElementById('chart');
window.myLine = new Chart(chartEl, {
type: 'line',
data: lineChartData,
options: {
plugins: {
title: {
display: true,
text: 'Chart.js Line Chart - Custom Tooltips'
},
tooltip: {
enabled: false,
mode: 'index',
intersect: false,
position: 'nearest',
custom: customTooltips
}
}
}
});
};
Only adjustments I made is transform the bodylines into a new array with info I could use (can do this also straigt on the bodylines var), in the objectBodyLines.forEach loop pick the right part from my object to fill the tooltip and set the color.
After quick extra test its not totally working, guess I made a mistake somewhere in the sort I guess but it should still give you a good starting point to work from
In Chart JS 3.4.1 you have the tooltip.itemSort option. I suspect you have a stacked bar chart. So this will work fine for ASC (lowest to highest) or DESC (highest to lowest) order of the tooltip.
That is the one you can use to sort the tooltip without too much hassle. I have added a complete video covering this topic as well which explains the process in depth: how to sort tooltip in Chart JS.
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// setup
const labels = ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'];
const datapoint = [12, 19, 3, 5, 2, 3];
const backgroundColor = ['rgba(255, 99, 132, 0.2)', 'rgba(54, 162, 235, 0.2)', 'rgba(255, 206, 86, 0.2)', 'rgba(75, 192, 192, 0.2)', 'rgba(153, 102, 255, 0.2)', 'rgba(255, 159, 64, 0.2)'];
const borderColor = ['rgba(255, 99, 132, 1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 206, 86, 1)', 'rgba(75, 192, 192, 1)', 'rgba(153, 102, 255, 1)', 'rgba(255, 159, 64, 1)'];
const labels2 = ['green', 'blue', 'black', 'brown', 'purple', 'pink'];
const datapoint2 = [3, 7, 13, 9, 8, 13];
const backgroundColor2 = ['green', 'blue', 'black', 'brown', 'purple', 'pink'];
const borderColor2 = ['green', 'blue', 'black', 'brown', 'purple', 'pink'];
const data = {
labels: labels,
datasets: [{
label: '# of Votes',
data: datapoint,
backgroundColor: backgroundColor,
borderColor: borderColor,
borderWidth: 1
}, {
label: '# of Sales',
data: datapoint2,
backgroundColor: backgroundColor2,
borderColor: borderColor2,
borderWidth: 1
}]
};
// config
const config = {
type: 'bar',
data,
options: {
plugins: {
tooltip: {
itemSort: function (a, b) {
return b.raw - a.raw;
}
}
},
scales: {
x: {
stacked: true
},
y: {
stacked: true,
beginAtZero: true
}
}
}
};
// render init block
const myChart = new Chart(
document.getElementById('myChart'),
config
);
</script>
I try to display stacked line chart but it doesn't work.
Could you advise where I should change with the following code?
window.onload=function(){
var ctx = document.getElementById("myChart").getContext("2d");
var data = {
labels: {{subcategories|tojson}},
datasets: [
{
1st_sample_dataset
},
{
2nd_sample_dataset
}
]
},
options = {
scales: {
yAxes: [{
stacked: true
}]
}
};
new Chart(ctx).Line(data, {
onAnimationComplete: function () {
var sourceCanvas = this.chart.ctx.canvas;
var targetCtx = document.getElementById("myChartAxis").getContext("2d");
targetCtx.canvas.width = copyWidth;
targetCtx.drawImage(sourceCanvas, 0, 0, copyWidth, copyHeight, 0, 0, copyWidth, copyHeight);
}
});
}
And this is actual display.
From your code I presume you're using an old version of chart.js.
new Chart(ctx).Line(data, {
...
Charts are now created in a different way.
var myChart = new Chart(ctx, {
type: 'line',
...
If you switch to version 2.9.3., it works fine as the following code sample illustrates.
new Chart(document.getElementById('myChartAxis'), {
type: 'line',
data: {
labels: ['A', 'B', 'C', 'D'],
datasets: [
{
label: 'WARNINGS',
data: [1, 2, 3, 2],
borderColor: 'rgb(255, 159, 64)',
backgroundColor: 'rgba(255, 159, 64, 0.2)'
},
{
label: 'ERRORS',
data: [1, 2, 1, 3],
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.2)'
}
]
},
options: {
scales: {
yAxes: [{
stacked: true
}]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.min.js"></script>
<canvas id="myChartAxis" height="90"></canvas>
I have the below stacked group chart.
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js"></script>
<script>
var barChartData = {
labels: ['January', 'March', 'June', 'September', 'December'],
datasets: [{
label: 'Apple',
borderColor: 'rgba(255, 50, 50,1)',
backgroundColor: 'rgba(255, 50, 50,0.5)',
stack: 'Stack 0',
data: [3,6,4,8,2]
}, {
label: 'Orange',
borderColor: 'rgba(255, 160, 242,1)',
backgroundColor: 'rgba(255, 160, 242,0.5)',
stack: 'Stack 0',
data: [2,1,3,0,1]
}, {
label: 'Pear',
borderColor: 'rgba(79, 158, 255,1)',
backgroundColor: 'rgba(79, 158, 255,0.5)',
stack: 'Stack 0',
data: [3,4,1,5,2]
}, {
label: 'Apple',
borderColor: 'rgba(255, 50, 50,1)',
backgroundColor: 'rgba(255, 50, 50,0.5)',
stack: 'Stack 1',
data: [7,16,10,8,5]
},{
label: 'Mango',
borderColor: 'rgba(100, 244, 97,1)',
backgroundColor: 'rgba(100, 244, 97,0.5)',
stack: 'Stack 1',
data: [4,9,12,5,7]
}, {
label: 'Banana',
borderColor: 'rgba(255, 252, 91,1)',
backgroundColor: 'rgba(255, 252, 91,0.5)',
stack: 'Stack 1',
data: [3,6,13,14,5]
}, {
label: 'Orange',
borderColor: 'rgba(255, 160, 242,1)',
backgroundColor: 'rgba(255, 160, 242,0.5)',
stack: 'Stack 1',
data: [7,12,3,0,2]
}, {
label: 'Cherry',
borderColor: 'rgba(255, 132, 38,1)',
backgroundColor: 'rgba(255, 132, 38,0.5)',
stack: 'Stack 1',
data: [8,4,7,11,6]
}, {
label: 'Pear',
borderColor: 'rgba(79, 158, 255,1)',
backgroundColor: 'rgba(79, 158, 255,0.5)',
stack: 'Stack 1',
data: [8,14,9,12,16]
}]
};
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'bar',
data: barChartData,
options: {
legend: {
display:true,
},
tooltips: {
mode: 'x',
intersect: false
},
responsive: true,
scales: {
xAxes: [{
stacked: true,
}],
yAxes: [{
stacked: true
}]
}
}
});
</script>
I would like to do two things:
1) Remove the redundant Apple, Orange and Pear labels
2) Highlight/Darken just the border on the individual stacks in the graph with the colors of those individual stacks. Like how the legend is.
Any help would be greatly appreciated.
Image of my graph
You can add custom logic to legend options like:
options: {
legend: {
display: true,
labels: {
generateLabels: function (chart) { ...
You can reach the labels here, customize their color, text, you can group them etc. So in this case you have to group the labels by their text first like
const labelTexts = []
const newLabels = []
labels = Chart.defaults.global.legend.labels.generateLabels(chart);
// group labels
labels.map((label) => {
if (labelTexts.indexOf(label.text) === -1) {
labelTexts.push(label.text)
}
})
labelTexts.map((text) => {
labels.map((label) => {
if (label.text === text) {
newLabels.push(label)
}
})
})
Then you can remove the redundant labels like:
for (var i = 0; i < newLabels.length - 2; i++) {
if (newLabels[i].text !== newLabels[i + 1].text) {
newLabels2.push(newLabels[i])
}
}
Check a working example here