Chart.js动态条宽

时间:2015-08-21 10:52:23

标签: bar-chart chart.js

我需要渲染一组连续块的时间序列数据。

我需要描述一系列条形图,这些条形图可以跨越许多小时或几分钟,具有自己的Y值。

我不确定ChartJS是否应该用于此,但我已经考虑扩展Bar类型,但似乎很难编码每个条的宽度相同。 Scale Class内部用于标签,图表宽度等,而不仅仅是条形本身。

我正在尝试在Excel中实现类似的功能:http://peltiertech.com/variable-width-column-charts/

还有其他人不得不提出类似的东西吗?

enter image description here

3 个答案:

答案 0 :(得分:0)

对于Chart.js,您可以基于bar类创建一个新扩展来执行此操作。虽然它有点涉及 - 但是大部分都是条形码库代码的复制粘贴

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);
    }
});

你传递的宽度如下

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]
        },
    ]
};

你就这么称呼它

var ctx = document.getElementById('canvas').getContext('2d');
var myLineChart = new Chart(ctx).BarAlt(data);

小提琴 - http://jsfiddle.net/moye0cp4/

enter image description here

答案 1 :(得分:0)

我发现我需要这样做,@ potatopeelings的答案很棒,但是Chartjs的第2版过时了。我通过扩展栏创建自己的控制器/图表类型做了类似的事情:

//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
            };
        },
    });
};

然后你需要将它添加到你的chartjs实例中:

import Chart from 'chart.js'
import barw from 'controller.barw'

barw(Chart); //add plugin to chartjs

最后,与其他答案类似,需要将条宽的权重添加到数据集中:

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]
        },
    ]
};

这有望让某人走上正轨。我当然不是完美的,但如果你确保你对数据点有正确的权重数量,你应该是对的。

祝你好运。

答案 2 :(得分:0)

这是基于@Shane的代码,我刚刚发布是为了帮助,因为这是一个常见问题。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
  };
}