[Chartjs]-Chart.js dynamic bar width

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.enter image description here

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/


enter image description here

Leave a comment