2👍
I found I needed to do this and the answer by @potatopeelings was great, but out of date for version 2 of Chartjs. I did something similar by creating my own controller/chart type via extending bar:
//controller.barw.js
module.exports = function(Chart) {
var helpers = Chart.helpers;
Chart.defaults.barw = {
hover: {
mode: 'label'
},
scales: {
xAxes: [{
type: 'category',
// Specific to Bar Controller
categoryPercentage: 0.8,
barPercentage: 0.9,
// grid line settings
gridLines: {
offsetGridLines: true
}
}],
yAxes: [{
type: 'linear'
}]
}
};
Chart.controllers.barw = Chart.controllers.bar.extend({
/**
* @private
*/
getRuler: function() {
var me = this;
var scale = me.getIndexScale();
var options = scale.options;
var stackCount = me.getStackCount();
var fullSize = scale.isHorizontal()? scale.width : scale.height;
var tickSize = fullSize / scale.ticks.length;
var categorySize = tickSize * options.categoryPercentage;
var fullBarSize = categorySize / stackCount;
var barSize = fullBarSize * options.barPercentage;
barSize = Math.min(
helpers.getValueOrDefault(options.barThickness, barSize),
helpers.getValueOrDefault(options.maxBarThickness, Infinity));
return {
fullSize: fullSize,
stackCount: stackCount,
tickSize: tickSize,
categorySize: categorySize,
categorySpacing: tickSize - categorySize,
fullBarSize: fullBarSize,
barSize: barSize,
barSpacing: fullBarSize - barSize,
scale: scale
};
},
/**
* @private
*/
calculateBarIndexPixels: function(datasetIndex, index, ruler) {
var me = this;
var scale = ruler.scale;
var options = scale.options;
var isCombo = me.chart.isCombo;
var stackIndex = me.getStackIndex(datasetIndex);
var base = scale.getPixelForValue(null, index, datasetIndex, isCombo);
var size = ruler.barSize;
var dataset = me.chart.data.datasets[datasetIndex];
if(dataset.weights) {
var total = dataset.weights.reduce((m, x) => m + x, 0);
var perc = dataset.weights[index] / total;
var offset = 0;
for(var i = 0; i < index; i++) {
offset += dataset.weights[i] / total;
}
var pixelOffset = Math.round(ruler.fullSize * offset);
var base = scale.isHorizontal() ? scale.left : scale.top;
base += pixelOffset;
size = Math.round(ruler.fullSize * perc);
size -= ruler.categorySpacing;
size -= ruler.barSpacing;
}
base -= isCombo? ruler.tickSize / 2 : 0;
base += ruler.fullBarSize * stackIndex;
base += ruler.categorySpacing / 2;
base += ruler.barSpacing / 2;
return {
size: size,
base: base,
head: base + size,
center: base + size / 2
};
},
});
};
Then you need to add it to your chartjs instance like this:
import Chart from 'chart.js'
import barw from 'controller.barw'
barw(Chart); //add plugin to chartjs
and finally, similar to the other answer, the weights of the bar widths need to be added to the data set:
var data = {
labels: ['A', 'B', 'C', 'D', 'E', 'F', 'G'],
datasets: [
{
label: "My First dataset",
fillColor: "rgba(220,220,220,0.5)",
strokeColor: "rgba(220,220,220,0.8)",
highlightFill: "rgba(220,220,220,0.7)",
highlightStroke: "rgba(220,220,220,1)",
data: [65, 59, 80, 30, 56, 65, 40],
weights: [1, 0.9, 1, 2, 1, 4, 0.3]
},
]
};
This will hopefully get someone onto the right track. What I have certainly isn’t perfect, but if you make sure you have the right number of weight to data points, you should be right.
Best of luck.
1👍
This is based on the @Shane’s code, I just posted to help, since is a common question.
calculateBarIndexPixels: function (datasetIndex, index, ruler) {
const options = ruler.scale.options;
const range = options.barThickness === 'flex' ? computeFlexCategoryTraits(index, ruler, options) : computeFitCategoryTraits(index, ruler, options);
const barSize = range.chunk;
const stackIndex = this.getStackIndex(datasetIndex, this.getMeta().stack);
let center = range.start + range.chunk * stackIndex + range.chunk / 2;
let size = range.chunk * range.ratio;
let start = range.start;
const dataset = this.chart.data.datasets[datasetIndex];
if (dataset.weights) {
//the max weight should be one
size = barSize * dataset.weights[index];
const meta = this.chart.controller.getDatasetMeta(0);
const lastModel = index > 0 ? meta.data[index - 1]._model : null;
//last column takes the full bar
if (lastModel) {
//start could be last center plus half of last column width
start = lastModel.x + lastModel.width / 2;
}
center = start + size * stackIndex + size / 2;
}
return {
size: size,
base: center - size / 2,
head: center + size / 2,
center: center
};
}
0👍
For Chart.js you can create a new extension based on the bar class to do this. It’s a bit involved though – however most of it is a copy paste of the bar type library code
Chart.types.Bar.extend({
name: "BarAlt",
// all blocks that don't have a comment are a direct copy paste of the Chart.js library code
initialize: function (data) {
// the sum of all widths
var widthSum = data.datasets[0].data2.reduce(function (a, b) { return a + b }, 0);
// cumulative sum of all preceding widths
var cumulativeSum = [ 0 ];
data.datasets[0].data2.forEach(function (e, i, arr) {
cumulativeSum.push(cumulativeSum[i] + e);
})
var options = this.options;
// completely rewrite this class to calculate the x position and bar width's based on data2
this.ScaleClass = Chart.Scale.extend({
offsetGridLines: true,
calculateBarX: function (barIndex) {
var xSpan = this.width - this.xScalePaddingLeft;
var x = this.xScalePaddingLeft + (cumulativeSum[barIndex] / widthSum * xSpan) - this.calculateBarWidth(barIndex) / 2;
return x + this.calculateBarWidth(barIndex);
},
calculateBarWidth: function (index) {
var xSpan = this.width - this.xScalePaddingLeft;
return (xSpan * data.datasets[0].data2[index] / widthSum);
}
});
this.datasets = [];
if (this.options.showTooltips) {
Chart.helpers.bindEvents(this, this.options.tooltipEvents, function (evt) {
var activeBars = (evt.type !== 'mouseout') ? this.getBarsAtEvent(evt) : [];
this.eachBars(function (bar) {
bar.restore(['fillColor', 'strokeColor']);
});
Chart.helpers.each(activeBars, function (activeBar) {
activeBar.fillColor = activeBar.highlightFill;
activeBar.strokeColor = activeBar.highlightStroke;
});
this.showTooltip(activeBars);
});
}
this.BarClass = Chart.Rectangle.extend({
strokeWidth: this.options.barStrokeWidth,
showStroke: this.options.barShowStroke,
ctx: this.chart.ctx
});
Chart.helpers.each(data.datasets, function (dataset, datasetIndex) {
var datasetObject = {
label: dataset.label || null,
fillColor: dataset.fillColor,
strokeColor: dataset.strokeColor,
bars: []
};
this.datasets.push(datasetObject);
Chart.helpers.each(dataset.data, function (dataPoint, index) {
datasetObject.bars.push(new this.BarClass({
value: dataPoint,
label: data.labels[index],
datasetLabel: dataset.label,
strokeColor: dataset.strokeColor,
fillColor: dataset.fillColor,
highlightFill: dataset.highlightFill || dataset.fillColor,
highlightStroke: dataset.highlightStroke || dataset.strokeColor
}));
}, this);
}, this);
this.buildScale(data.labels);
// remove the labels - they won't be positioned correctly anyway
this.scale.xLabels.forEach(function (e, i, arr) {
arr[i] = '';
})
this.BarClass.prototype.base = this.scale.endPoint;
this.eachBars(function (bar, index, datasetIndex) {
// change the way the x and width functions are called
Chart.helpers.extend(bar, {
width: this.scale.calculateBarWidth(index),
x: this.scale.calculateBarX(index),
y: this.scale.endPoint
});
bar.save();
}, this);
this.render();
},
draw: function (ease) {
var easingDecimal = ease || 1;
this.clear();
var ctx = this.chart.ctx;
this.scale.draw(1);
Chart.helpers.each(this.datasets, function (dataset, datasetIndex) {
Chart.helpers.each(dataset.bars, function (bar, index) {
if (bar.hasValue()) {
bar.base = this.scale.endPoint;
// change the way the x and width functions are called
bar.transition({
x: this.scale.calculateBarX(index),
y: this.scale.calculateY(bar.value),
width: this.scale.calculateBarWidth(index)
}, easingDecimal).draw();
}
}, this);
}, this);
}
});
You pass in the widths like below
var data = {
labels: ['A', 'B', 'C', 'D', 'E', 'F', 'G'],
datasets: [
{
label: "My First dataset",
fillColor: "rgba(220,220,220,0.5)",
strokeColor: "rgba(220,220,220,0.8)",
highlightFill: "rgba(220,220,220,0.7)",
highlightStroke: "rgba(220,220,220,1)",
data: [65, 59, 80, 30, 56, 65, 40],
data2: [10, 20, 30, 20, 10, 40, 10]
},
]
};
and you call it like so
var ctx = document.getElementById('canvas').getContext('2d');
var myLineChart = new Chart(ctx).BarAlt(data);
Fiddle – http://jsfiddle.net/moye0cp4/
- [Chartjs]-Chart.js how to get Combined Bar and line charts?
- [Chartjs]-ChartJS hide labels on small screen sizes