ChartJS 3 get height of stacked vertical bar - javascript

Ok. So I'm trying to use ChartJS with Datalabels 2.0 (RC) to show labels for stacked vertical bars. When the bar is small, I want those labels to appear outside of it.
Now here is the code I'm using: https://codepen.io/meexo/pen/xxqpELL
The problem I have is that using getProps on dataset, I am unable to retrieve the height of each bar. In the example above I tried to get height by using the getProps() introduced in ChartJS 3. If I console.log it, I see all kind of params, height included (though undefined). What am I doing wrong that height parameter is undefined?
I also tried to use getProps().y param for the code, but in that case it seems to apply only to one section of the stacked bar, not all. And what I want is for labels to appear above the stacked bar when the full height if it is lower than my threshold.
Any ideas?
BTW, though its my first question, its definitely not the first answer I found on StackOverflow :)
My code:
var ctx = document.getElementById("myChart");
Chart.register(ChartDataLabels);
var myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: 'red'
}, {
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
backgroundColor: 'blue'
}]
},
options: {
scales: {
x: {
stacked: true
},
y: {
stacked: true
}
},
plugins: {
datalabels: {
anchor: context => {
let bar = context.chart.getDatasetMeta(context.datasetIndex).data[context.dataIndex].getProps().height;
document.getElementById('myHeight').innerHTML = bar;
const threshold = 6;
if (bar + threshold > context.chart.chartArea.bottom) {
return 'end'
}
return 'center'
}
}
}
}
});

The behaviour comes from this part in the code:
const properties = {
horizontal,
base: vpixels.base,
enableBorderRadius: !stack || isFloatBar(parsed._custom) || (me.index === stack._top || me.index === stack._bottom),
x: horizontal ? vpixels.head : ipixels.center,
y: horizontal ? ipixels.center : vpixels.head,
height: horizontal ? ipixels.size : undefined,
width: horizontal ? undefined : ipixels.size
};
It sets the height on undefined for normal bar charts and the width on undefined on horizontal bar charts. Putted in a pr (https://github.com/chartjs/Chart.js/pull/9208) for fix so this should be fixed by the next release of chart.js
Then to get the bar height you have to change your mode to index and then you can use this function to get the total bar height:
onClick: function(event) {
var bar = this.getElementsAtEventForMode(event, 'index', {intersect: true}, true);
if (!bar.length) return; //return if not clicked on bar
var barHeight = bar.reduce((acc , val) => (acc + val.element.height), 0)
document.getElementById('myHeight').innerHTML = barHeight;
}
Example: https://codepen.io/leelenaleee/pen/qBrpLjX

Related

React ChartJS -- Scale Bar Chart Vertically With More Datasets

I'm using React ChartJS to build a bar chart which currently looks like this
The data that this is built on top of is dynamic -- that is, there can be many more industries (or fewer). I'd like to fix the bar width (say, at 30 pixels) and to have the chart grow taller according to how many datasets that I have. In other words, I don't care how tall the chart is, it should be a function of how many datasets that I have.
How would I accomplish this?
I'd share the code I've got already but it's pretty generic, can post it here if folks would find that useful.
First you could wrap the canvas in a div.
<div id="chartWrapper">
<canvas id="myChart"></canvas>
</div>
You can make use of the Plugin Core API and compute the chart height in the beforeRender hook depending on the defined data.barThickness and the visible datasets.
plugins: [{
beforeRender : chart => {
if (!chart.config.options.nonChartAreaHeight) {
var yAxis = chart.scales.y;
chart.config.options.nonChartAreaHeight = chart.height - (yAxis.bottom - yAxis.top);
}
const labelCount = chart.data.labels.length;
const datasetCount = chart.data.datasets.map((ds, i) => chart.getDatasetMeta(i)).filter(m => !m.hidden).length;
const chartAreaHeight = labelCount * datasetCount * chart.data.barThickness * 1.2;
document.getElementById("chartWrapper").style.height = (chartAreaHeight + chart.config.options.nonChartAreaHeight) + 'px';
}
}],
Then you also need to define option.maintainAspectRatio: false to prevent Chart.js from resizing the canvas.
Please take a look at below runnable code and see how it works.
new Chart('myChart', {
type: 'bar',
plugins: [{
beforeRender : chart => {
if (!chart.config.options.nonChartAreaHeight) {
var yAxis = chart.scales.y;
chart.config.options.nonChartAreaHeight = chart.height - (yAxis.bottom - yAxis.top);
}
const labelCount = chart.data.labels.length;
const datasetCount = chart.data.datasets.map((ds, i) => chart.getDatasetMeta(i)).filter(m => !m.hidden).length;
const chartAreaHeight = labelCount * datasetCount * chart.data.barThickness * 1.2;
document.getElementById("chartWrapper").style.height = (chartAreaHeight + chart.config.options.nonChartAreaHeight) + 'px';
}
}],
data: {
labels: ['1', '2', '3', '4'],
barThickness: 30, // change this to adapt the bar thickness
datasets: [{
label: 'Dataset 1',
data: [40, 50, 60, 80],
backgroundColor: 'red'
},
{
label: 'Dataset 2',
data: [100, 90, 80, 70],
backgroundColor: 'blue'
},
{
label: 'Dataset 3',
data: [60, 80, 70, 90],
backgroundColor: 'green'
}]
},
options: {
indexAxis: 'y',
maintainAspectRatio: false
}
});
div {
width: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.8.0/chart.min.js"></script>
<div id="chartWrapper">
<canvas id="myChart"></canvas>
</div>

Plot Ranged horizontal bar graph with holes using ChartJS

I already have a graph using chartJS as shown below and the code for that is :
r_chart_data = [[-0.001, 2.052],[-2.385, 2.052]];
dr_chart_data = [[-0.01, 1.83],[1.05, 2.83],[1.05, 2.83]];
myChart = new Chart(ctx, {
type: 'horizontalBar',
data: {
labels: label,
datasets: [{
label : 'DataSet1',
data: r_chart_data,
backgroundColor: 'rgb(255, 99, 132)'
},
{
label : 'DataSet2',
data: dr_chart_data,
backgroundColor: 'lightblue'
}
]
},
options: {
lineAtIndex : level,
scales: {
xAxes: [{
ticks: {
min: -5,
max: 5,
stepSize: 0.5,
}
}]
}
}
});
But what if my
r_chart_data[0] i.e. [-0.001,2.052]
is not a continuos range and instead its value is something like
[[-0.001,1.023],[1.5,2.052]]
then how should I plot the graph. Modified r_chart_data:
r_chart_data = [[[-0.001,1.023],[1.5,2.052]],[-2.385, 2.052]];
Similarly if I have a range of data sets that I need to plot in the form of a horizontal bar graph using ChartJS. The data set which are in the form of:
dataset1: [[-10,-4],[-2,2],[4,6]] // should be plotted in a single bar graph
dataset2: [[-2,1],[1.5,3.5],[5.2,8.9]] //should be plotted in a second bar
dataset3: [[2,4],[5,7]] //third bar
.. and so on
Moreover, each dataset need not have three range items. It can have one, two or any number of range items.

How to fix bars in Chart.js with long labels

I use Chart.js( Version: 2.7.2 ) in my application and some labels in resulting rows are rather long
var barCanvas = document.getElementById("canvasVoteNames");
var ctx = barCanvas.getContext('2d');
var numberWithCommas = function(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
};
var self = this;
var myChart = new Chart(ctx, { // stacked bar report https://jsfiddle.net/sdfx/hwx9awgn/
type: 'bar',
data: {
labels:monthsXCoordItems,
datasets: [
{
label: 'Correct Votes',
data: voteValuesCorrect,
borderWidth: 1, // The stroke width of the bar in pixels.
backgroundColor : formatColor('#05b932'), //rgba(0, 0, 0, 0.1), // The fill color of the bar. See Colors
borderColor: formatColor('#05b932'),// rgba(255, 0, 0, 0.1) // The color of the bar border.
hoverBackgroundColor : formatColor('#05b932'), // The fill colour of the bars when hovered.
hoverBorderColor: formatColor('#05b932'), // The stroke colour of the bars when hovered.
hoverBorderWidth : 1 // The stroke width of the bars when hovered.
},
{
label: 'Incorrect Votes',
data: voteValuesNoneCorrect,
borderWidth: 1, // The stroke width of the bar in pixels.
backgroundColor : formatColor('#b1a19a'), //rgba(0, 0, 0, 0.1), // The fill color of the bar. See Colors
borderColor: formatColor('#b1a19a'),// rgba(255, 0, 0, 0.1) // The color of the bar border.
hoverBackgroundColor : formatColor('#b1a19a'), // The fill colour of the bars when hovered.
hoverBorderColor: formatColor('#b1a19a'), // The stroke colour of the bars when hovered.
hoverBorderWidth : 1 // The stroke width of the bars when hovered.
},
]
},
options: { // options of Report By Vote Names
animation: {
duration: 10,
},
tooltips: { // tooltip text of Report By Vote Days ( 'bar' report )
mode: 'label',
callbacks: {
label: function(tooltipItem, data) {
return data.datasets[tooltipItem.datasetIndex].label + ": " + numberWithCommas(tooltipItem.yLabel);
}
}
}, // tooltips: { // tooltip text of Report By Vote Days ( 'bar' report )
scales: { // options for x and y scales
xAxes: [{
stacked: true, // Stacked bar charts can be used to show how one data series i
gridLines: { display: false },
}],
yAxes: [{
stacked: true, // Stacked bar charts can be used to show how one data series i
ticks: {
callback: function(value) { // on Y scale show only integer without decimals
if (Math.floor(value) === value) {
return value;
}
}, // callback: function(value) { return numberWithCommas(value); },
},
}],
}, // scales: { // options for x and y scales
legend: {display: true}
} // options: { // options of Report By Vote Names
}); // var myChart = new Chart(ctx, { // stacked bar report https://jsfiddle.net/sdfx/hwx9awgn/
}
The chart I got is what I need
https://imgur.com/a/n1SsW7w
but with long labels for any bar it does not look good and I did not find if there is a way to fix it somehow ?
Why labels has big marging, not as relative bars?
Some options for xAxes or additive legends?
Thanks!
You were using ChartJs version 2.1.3 in your JSFiddle, which does not seem to handle multiline labels
You can use multilines labels with the following solutions:
var dates = [["Some l-o-o-o-o-", "o-o-o-o-o-o-o-", "n-n-n-n-n-n-g-g-g-", "g-g-g-g label"], "DDD", ["EEE", "FFF", "GGG"], "HHH", "III"];
You can replace a label by an array, and each element of the array will be considered as a new line (See JSFiddle): https://jsfiddle.net/cyuwxh3q/
If your labels are generated dinamically, you can split them with a plugin in your chart configuration :
type: 'bar',
data: {...},
options: {...},
plugins: [{
beforeInit: function (chart) {
chart.data.labels.forEach(function (value, index, array) {
var a = [];
a.push(value.slice(0, 5));
var i = 1;
while(value.length > (i * 5)){
a.push(value.slice(i * 5, (i + 1) * 5));
i++;
}
array[index] = a;
})
}
}]
This function will turn each label into an array of element which length is less or equal to the given value (here 5) (See JSFiddle) : https://jsfiddle.net/jhr5nm17/
Those are two easy ways to handle long labels by replacing them by multiline labels, hope it helps.

Chart.js How to align two X-axis in bar chart?

I try to build a simple bar chart using chart.js with 2 x-axis: One on top and other on bottom.
The problem is two axis label and grid is not align. The top x-axis is align center and bottom x-axis is align left.
Please help me to make two x-axis align center. Thanks!
Fiddle:
jsfiddle.net/gtmvkq4n/2/
Screenshot:
You may be looking after the offset option
Example with multiple datasets:
var myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['A', 'B', 'C', 'D', 'E'],
datasets: [{
label: 'somelabel',
xAxisID:'bar-x-axis1',
data: [40, 50, 30, 12, 52]
},
{
// ... other dataset...
}]
},
options:{
scales:{
xAxes:[
{
id:'bar-x-axis1',
type:'category',
offset: true // <== that one!
},
{
// ... other X axis...
}]
}
}
});
http://www.chartjs.org/docs/latest/axes/cartesian/
Make your bar graph contains proper groupSpace, barSpace, barWidth
(groupSpace * barSpace) * n + groupSpace = 1
Make this equation is correct, given n is the number of bars

Align lines and bars in a mixed chart

I'm working on a mixed chart where I need to plot a stepped linechart on a barchart. The datapoints in my linechart are aligned in the middle of a bar by default like this:
I want to make the start of the line to align with the beginning of the bar like so:
I've been through the documentation of Chartjs but i can't find a way to make the line start at the beginning of my bar.
I setup a little example to explain my problem a little bit further:
var opc = $("#chart");
var myChart = new Chart(opc, {
type: 'bar',
data: {
labels: ['Item 1', 'Item 2', 'Item 3'],
datasets: [{
type: 'bar',
label: 'Bar Component',
data: [10, 20, 30],
}, {
type: 'line',
steppedLine: true,
label: 'Line Component',
data: [25, 5, 20],
fill: false
}]
},
options: {
scales: {
yAxes: [{
display: true,
ticks: {
suggestedMin: 0, // minimum will be 0, unless there is a lower value.
// OR //
beginAtZero: true // minimum value will be 0.
}
}]
}
}
});
Here is the link to a working fiddle: https://jsfiddle.net/7yheenc6/1/
I went through the documentation but couldn't find anything on how to fix this. I though it would be relatively simple to fix it through code, boy was I wrong, anyway I got it working, you need to manually change the code in chart.js source file. Here is the line you need to change
Before(chart.js):
Line number: 12891
function lineToPoint(previousPoint, point) {
var vm = point._view;
if (point._view.steppedLine === true) {
ctx.lineTo(point._view.x, previousPoint._view.y);
ctx.lineTo(point._view.x, point._view.y);
} else if (point._view.tension === 0) {
ctx.lineTo(vm.x, vm.y);
} else {
ctx.bezierCurveTo(
previousPoint._view.controlPointNextX,
previousPoint._view.controlPointNextY,
vm.controlPointPreviousX,
vm.controlPointPreviousY,
vm.x,
vm.y
);
}
}
After(chart.js):
Line number: 12891
function lineToPoint(previousPoint, point) {
var vm = point._view;
if (point._view.steppedLine === true) {
ctx.lineTo((point._view.x - previousPoint._view.x)/2 + previousPoint._view.x, previousPoint._view.y);
ctx.lineTo((point._view.x - previousPoint._view.x)/2 + previousPoint._view.x, point._view.y);
ctx.lineTo(point._view.x, point._view.y);
} else if (point._view.tension === 0) {
ctx.lineTo(vm.x, vm.y);
} else {
ctx.bezierCurveTo(
previousPoint._view.controlPointNextX,
previousPoint._view.controlPointNextY,
vm.controlPointPreviousX,
vm.controlPointPreviousY,
vm.x,
vm.y
);
}
}
Jsfiddle for reference: https://jsfiddle.net/Kai_Draord/7yheenc6/4/
Output:
I hope this solves your issue :)

Categories

Resources