我需要渲染一组连续块的时间序列数据。
我需要描述一系列条形图,这些条形图可以跨越许多小时或几分钟,具有自己的Y值。
我不确定ChartJS是否应该用于此,但我已经考虑扩展Bar类型,但似乎很难编码每个条的宽度相同。 Scale Class内部用于标签,图表宽度等,而不仅仅是条形本身。
我正在尝试在Excel中实现类似的功能:http://peltiertech.com/variable-width-column-charts/
还有其他人不得不提出类似的东西吗?
答案 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);
答案 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的代码,我刚刚发布是为了帮助,因为这是一个常见问题。
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
};
}