我正在尝试创建一个堆叠条形图,其中 x 轴上有一个小时段(上午 9 点、上午 10 点、上午 11 点……),y 轴上有计数。我希望按天(周一、周二……)和人(爱丽丝、鲍勃)分组的数据如下所示: 星期一、星期二、鲍勃、爱丽丝应该是可选择的选项(隐藏/显示),但条形上没有标签 - (这只是为了澄清数据)。
理想情况下,我想指定我的数据,例如:
const labels = ['9am','10am','11am','12pm'];
const data = {
labels: labels,
datasets: [
{
label: 'Monday',
data: [{x:'9am',person:'Bob',y:7},{x:'10am',person:'Bob',y:6},{x:'11am',person:'Bob',y:5},{x:'12pm',y:4},{x:'9am',person:'Alice',y:7},{x:'10am',person:'Alice',y:6},{x:'11am',person:'Alice',y:5},{x:'12pm',y:4}],
stack: data.x,
},
{
label: 'Tuesday',
data: [{x:'9am',person:'Bob',y:7},{x:'10am',person:'Bob',y:6},{x:'11am',person:'Bob',y:5},{x:'12pm',y:4},{x:'9am',person:'Alice',y:7},{x:'10am',person:'Alice',y:6},{x:'11am',person:'Alice',y:5},{x:'12pm',y:4}],
}
]
};
或(编辑1)
const labels = ['9am','10am','11am','12pm'];
const data = {
labels: labels,
datasets: [
{
label: 'Bob',
data: [{x:'9am',day:'Monday',y:7},{x:'10am',day:'Monday',y:6},{x:'11am',day:'Monday',y:5},{x:'12pm', day:'Monday', y:4},{x:'9am',day:'Tuesday',y:7},{x:'10am',day:'Tuesday',y:6},{x:'11am',day:'Tuesday',y:5},{x:'12pm',day:'Tuesday',y:4}],
backgroundColor: 'blue'
},
{
label: 'Alice',
data: [{x:'9am',day:'Monday',y:7},{x:'10am',day:'Monday',y:6},{x:'11am',day:'Monday',y:5},{x:'12pm',day:'Monday',y:4},{x:'9am',day:'Tuesday',y:7},{x:'10am',day:'Tuesday',y:6},{x:'11am',day:'Tuesday',y:5},{x:'12pm',day:'Tuesday',y:4}],
backgroundColor:'amber';
}
]
};
并让堆栈由某些数据项指定,例如“data.day”。
我尝试过的配置:
const config = {
type: 'bar',
data: data,
options: {
plugins: {
title: {
display: true,
text: 'Chart.js Bar Chart - Stacked'
},
},
responsive: true,
scales: {
x: {
stacked: true,
},
y: {
stacked: true
}
}
}
};
const labels = ['9am','10am','11am','12pm'];
const data = {
labels: labels,
datasets: [
{
label: 'Bob-Monday',
data: [{x:'9am',y:7},{x:'10am',y:6},{x:'11am',y:5},{x:'12pm',y:4}],
stack: 'Monday',
},
{
label: 'Alice-Monday',
data: [{x:'9am',y:7},{x:'10am',y:8},{x:'11am',y:10},{x:'12pm',y:9}],
stack: 'Monday',
},
{
label: 'Bob-Tue',
data: [{x:'9am',y:7},{x:'10am',y:6},{x:'11am',y:4},{x:'12pm',y:3}],
stack: 'Tue',
},
{
label: 'Alice-Tue',
data: [{x:'9am',y:7},{x:'10am',y:8},{x:'11am',y:10},{x:'12pm',y:9}],
stack: 'Tue',
}
]
};
const config = {
type: 'bar',
data: data,
options: {
plugins: {
title: {
display: true,
text: 'Chart.js Bar Chart - Stacked'
},
},
responsive: true,
scales: {
x: {
stacked: true,
},
y: {
stacked: true
}
}
}
};
const ctx = document.getElementById('myChart');
const chart = new Chart(ctx, config);
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<canvas id="myChart"></canvas>
在我看来,您的数据的索引是 两个变量,
person
和 day
并且因为
你还需要数据预处理,我会
建议相应地构建您的数据
并使用自定义插件
这将对数据进行预处理
它beforeInit
处理程序。 (形式上,数据是三维的,
因为还有小时变量,但因为
这是 x 轴上表示的变量,
我们将重点关注两个剩余变量)
问题的主要难点在于 自定义图例项目的要求。幸运的是, Chart.js 在以下方面很灵活 图例标签配置 允许自定义图例或画布 提供为
pointStyle
,
而选择和取消选择的效果
传奇项目可以通过完美控制
可定制的图例点击处理程序。
下面代码片段中的插件
datasets2d
,
首先将二维数据简单映射到
带堆栈的一维标准数据集
对应于内部label
的值。
然后,它采用图例定制功能
如上所述以实现所需的图例项
形式和功能。
const labels = ['9am', '10am', '11am', '12pm'];
const data = {
labels: labels,
datasets2d: [
{
label: 'Bob',
datasets: [
{
label: 'Monday',
data: [{x: '9am', y: 7}, {x: '10am', y: 6}, {x: '11am', y: 5}, {x: '12pm', y: 4}],
// or just data: [7, 6, 5, 4],
backgroundColor: 'rgba(54, 162, 235, 0.7)',
},
{
label: 'Tuesday',
data: [{x: '9am', y: 7}, {x: '10am', y: 6}, {x: '11am', y: 4}, {x: '12pm', y: 3}],
// or just data: [7, 6, 4, 3]
backgroundColor: 'rgba(54, 162, 235, 0.5)'
}
]
},
{
label: 'Alice',
datasets: [
{
label: 'Monday',
data: [{x: '9am', y: 7}, {x: '10am', y: 8}, {x: '11am', y: 10}, {x: '12pm', y: 9}],
// or just data: [7, 8, 10, 9],
backgroundColor: 'rgba(255, 99, 132, 0.7)'
},
{
label: 'Tuesday',
data: [{x: '9am', y: 7}, {x: '10am', y: 8}, {x: '11am', y: 10}, {x: '12pm', y: 9}],
// or just data: [7, 8, 10, 9]
backgroundColor: 'rgba(255, 99, 132, 0.5)'
}
]
}
]
};
const pluginDatasets2d = {
id: 'datasets2d',
_objMinusKeys(obj, minusKeys = []){
return Object.fromEntries(Object.entries(obj).filter(([key])=>!minusKeys.includes(key)));
},
_canvasMultiColor(width, height, colors, horizontal){
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d");
const nColors = colors.length, frac = 1/nColors;
const grad = horizontal ? ctx.createLinearGradient(0, 0, width, 0) : ctx.createLinearGradient(0, height, 0, 0);
grad.addColorStop(0, colors[0]);
for(let iColor = 1; iColor < nColors; iColor++){
grad.addColorStop(iColor*frac, colors[iColor-1]);
grad.addColorStop(iColor*frac, colors[iColor]);
}
grad.addColorStop(1, colors[nColors-1]);
ctx.fillStyle = grad;
ctx.fillRect(0, 0, width, height);
return canvas;
},
_generateLegendLabel(text, idx, colors, legendPointWidth, hidden, horizontal = true){
const legendPointHeight = legendPointWidth / 2;
return {
text: text,
datasetIndex: idx,
fillStyle: '#fff', // not used
fontColor: '#000',
hidden: hidden,
lineCap: 0, // not used
lineDash: [], // not used
lineDashOffset: 0, // not used
lineJoin: 0, // not used
lineWidth: 0, // not used
strokeStyle: '#fff', // not used
pointStyle: this._canvasMultiColor(
legendPointWidth, legendPointHeight, colors, horizontal),
rotation: 0
}
},
onLegendClick(e, legendItem, legend){
const indices = legendItem.datasetIndex; // multiple indices (array)
legendItem.hidden = !legendItem.hidden;
const chart = legend.chart;
for(const index of indices){
if(legendItem.hidden){
chart.setDatasetVisibility(index, false);
}
else{
chart.setDatasetVisibility(index, true);
}
}
chart.update();
// now update all legend items, since it is possible for a legend item
// to change hidden status as a result of a click on another item
for(const legendItem1 of legend.legendItems){
const indices1 = legendItem1.datasetIndex;
legendItem1.hidden = !indices1.map(index => chart.isDatasetVisible(index))
.reduce((s, b) => s || b);
}
},
// make closure with plugin, to be able to call other functions of the plugin
generateLabelsGen(plugin){
return function (chart){
const height = chart.legend.height;
if(!height) return;
const {stacks, labels, orderedStacks, orderedLabels} = chart._metasets.reduce(
({stacks, labels, orderedLabels, orderedStacks}, _meta, i) => {
const stack = _meta.stack,
label = _meta.label.replace(new RegExp(' \- ' + stack + '$'), '');
if(!stacks[stack]){
stacks[stack] = [i];
orderedStacks.push(stack);
}
else{
stacks[stack].push(i);
}
if(!labels[label]){
labels[label] = [i];
orderedLabels.push(label);
}
else{
labels[label].push(i);
}
return ({stacks, labels, orderedLabels, orderedStacks});
}, {labels: {}, stacks: {}, orderedLabels: [], orderedStacks: []});
const colorForMetaset =
idx => chart.getDatasetMeta(idx)._dataset.backgroundColor
|| 'rgba(192, 192, 192, 0.5)',
colorsForMetasets = a => a.map(colorForMetaset),
pointStyleWidth = chart.legend.options.labels.pointStyleWidth;
if(chart.legend.legendItems){
return orderedLabels.map(
label => plugin._generateLegendLabel(label, labels[label],
colorsForMetasets(labels[label]),
pointStyleWidth || 40, chart.legend.legendItems[0]?.hidden)
).concat(orderedStacks.map(
stack => plugin._generateLegendLabel(stack, stacks[stack],
colorsForMetasets(stacks[stack]),
pointStyleWidth || 40, chart.legend.legendItems[0]?.hidden, false)
));
}
}
},
beforeInit(chart) {
const datasets2d = chart.config.data.datasets2d;
const datasetsNewObj = {}, optionsNewObj = {};
for(const dataset2d of datasets2d){
const label = dataset2d.label || '',
// take options from dataset2d
optionsTop = this._objMinusKeys(dataset2d, ['label', 'data']);
for(const dataset of dataset2d.datasets){
const innerLabel = dataset.label || '',
newLabel = label + ' - ' + innerLabel;
optionsNewObj[newLabel] = {
...optionsTop,
// combine with options from dataset
...this._objMinusKeys(dataset, ['label', 'data']),
stack: innerLabel
};
datasetsNewObj[newLabel] = [];
for(const dataItem of dataset.data){
datasetsNewObj[newLabel].push(dataItem);
}
}
}
const datasetsNew = Object.entries(datasetsNewObj).map(
([label, data]) => ({data, label, ...optionsNewObj[label]})
);
chart.config.data.datasets = datasetsNew;
chart.legend.options.labels.usePointStyle = true;
chart.legend.options.labels.pointStyleWidth = chart.legend.options.labels.boxWidth;
chart.legend.options.labels.generateLabels = this.generateLabelsGen(this);
chart.legend.options.onClick = this.onLegendClick;
}
}
const config = {
type: 'bar',
data: data,
plugins:[
pluginDatasets2d
],
options: {
plugins: {
title: {
display: true,
text: 'Chart.js Bar Chart - Stacked'
},
legend:{
//position: "right"
}
},
responsive: true,
scales: {
x: {
stacked: true,
},
y: {
stacked: true
}
}
}
};
new Chart('myChart', config);
<div style="min-height: 60vh">
<canvas id="myChart"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script>
这是一个版本 多人、多日;添加或删除 一天或一个人就像改变一样简单 前两个数组的内容。