英文:
How to position positive value on top and negative value on bottom of stacked bar in chart js
问题
"formatter" 函数会给出每组堆叠条的总和,我试图在总和为正时将 "datalabels" 定位在上方,总和为负时将其定位在下方。请帮忙,非常感谢。
datalabels: {
anchor: (context) => {
const anchor = [];
let sum = ?;
if (parseFloat(sum) >= 0) {
anchor.push('end');
} else {
anchor.push('start');
}
return anchor;
},
align: (context) => {
const align = [];
let sum = ?;
if (parseFloat(sum) >= 0) {
align.push('end');
} else {
align.push('bottom');
}
return align;
},
formatter: (value, context) => {
const datasetArray = [];
context.chart.data.datasets.forEach((dataset) => {
if (dataset.data[context.dataIndex] != undefined) {
datasetArray.push(dataset.data[context.dataIndex]);
}
});
function totalSum(total, datapoint){
return +total + +datapoint;
}
let sum = datasetArray.reduce(totalSum);
if (context.datasetIndex === datasetArray.length - 1) {
return parseFloat(sum).toFixed(2) ;
} else {
return '';
}
},
}
<details>
<summary>英文:</summary>
The `formatter` function gives me the total of each group of stacked bars, I am trying to do the `datalabels` positioning it up if the total is positive and positioning it down if it is negative. Please any help would be very grateful.
[![enter image description here][1]][1]
datalabels: {
anchor: (context) =>{
const anchor = [];
let sum = ?;
if(parseFloat(sum) >=0){
anchor.push('end');
}else{
anchor.push('start');
}
return anchor;
},
align: (context) =>{
const align = [];
let sum = ?;
if(parseFloat(sum) >=0){
align.push('end');
}else{
align.push('bottom');
}
return align;
},
formatter:(value, context) =>{
const datasetArray = [];
context.chart.data.datasets.forEach((dataset) =>{
if(dataset.data[context.dataIndex] != undefined){
datasetArray.push(dataset.data[context.dataIndex]);
}
});
function totalSum(total, datapoint){
return +total + +datapoint;
}
let sum = datasetArray.reduce(totalSum);
if(context.datasetIndex === datasetArray.length - 1){
return parseFloat(sum).toFixed(2) ;
}else{
return '';
}
},
}
[1]: https://i.stack.imgur.com/urCEM.png
</details>
# 答案1
**得分**: 2
以下是您要翻译的内容:
"在这一点上,尚不清楚柱状图是如何堆叠的 - 有许多未知的选项。根据图像图表的视觉外观可能的组合如下:
```js
const N = 10;
const dataGenerator = (plus = 600, minus = 400, pNull = 1) =>
Array.from({length: N}, ()=>Math.random() < pNull ?
Math.round(Math.random()*(plus+minus)-minus)/4 : null)
const ctx1 = document.getElementById('chart1');
new Chart(ctx1, {
type: "bar",
plugins: [ChartDataLabels],
data: {
labels: Array from({length: N}, (_, i)=>'l'+(i+1)),
datasets: [
{
data: dataGenerator(),
stack: 'a',
},
{
data: dataGenerator() ,
stack: 'a',
},
{
data: dataGenerator(100, 50, 0.5),
stack: 'a',
},
]
},
options: {
indexAxis: 'x',
layout: {
padding: {
top: 20,
bottom: 20
}
},
animation: {
duration: 0
},
scales: {
x: {
ticks:{
display: false
}
},
y: {
stacked: true,
beginAtZero: true
}
},
plugins:{
legend:{
display: false
},
datalabels:{
formatter: (value, context) => {
const {dataIndex, datasetIndex, chart} = context;
const dataForDataIndex = chart.data.datasets.map(
dataset=>dataset.data[dataIndex] ?? 0
);
const total = dataForDataIndex.reduce((s, x)=>s+x)
// the index of the dataset that contains the last point (at dataIndex)
// with the same sign as the total - that is the one that should carry
// the total label
const datasetIndexLast = dataForDataIndex.findLastIndex(x => x * total > 0);
context.total = total;
return datasetIndexLast === datasetIndex ? total.toFixed(2) : null
},
anchor: (context) => {
return context.total > 0 ? 'end' : 'start';
},
align: (context) => {
return context.total > 0 ? 'top' : 'bottom';
},
clip: false
}
}
}
});
这背后的想法是启用所有来自数据集的柱形图的数据标签,但选择正确位置的数据集来表示 formatter
中的总和 - 也就是对于当前 dataIndex
具有相同符号的最后一个数据集。如果使用 order
选项,情况可能会有所不同。
该代码还利用了 context
对象在 formatter
、align
和 anchor
方法之间的共享,对于相同的 datasetIndex
,其中 formatter
总是第一个 - 如果 formatter
返回 null
,其他方法甚至都不会被调用。因此,它在 context
对象中保存了当前 datasetIndex
的总和,以便仅在第一次为数据集的 formatter
调用计算总和时由 align
和 anchor
调用。对于更安全的解决方案,不使用这个未记录的事实,可以在 align
和 anchor
中重新计算总和,或者使用外部对象缓存总和,这可以是优化计算的解决方案,只需在数据集的 formatter
首次调用时计算总和。"
英文:
It's unclear at this point how the bars are stacked - there are many unknown options. A possible combination based on the visual appearance of the image chart could be the following:
<!-- begin snippet: js hide: false console: false babel: false -->
<!-- language: lang-js -->
const N = 10;
const dataGenerator = (plus = 600, minus = 400, pNull = 1) =>
Array.from({length: N}, ()=>Math.random() < pNull ?
Math.round(Math.random()*(plus+minus)-minus)/4 : null)
const ctx1 = document.getElementById('chart1');
new Chart(ctx1, {
type: "bar",
plugins: [ChartDataLabels],
data: {
labels: Array.from({length: N}, (_, i)=>'l'+(i+1)),
datasets: [
{
data: dataGenerator(),
stack: 'a',
},
{
data: dataGenerator() ,
stack: 'a',
},
{
data: dataGenerator(100, 50, 0.5),
stack: 'a',
},
]
},
options: {
indexAxis: 'x',
layout: {
padding: {
top: 20,
bottom: 20
}
},
animation: {
duration: 0
},
scales: {
x: {
ticks:{
display: false
}
},
y: {
stacked: true,
beginAtZero: true
}
},
plugins:{
legend:{
display: false
},
datalabels:{
formatter: (value, context) => {
const {dataIndex, datasetIndex, chart} = context;
const dataForDataIndex = chart.data.datasets.map(
dataset=>dataset.data[dataIndex] ?? 0
);
const total = dataForDataIndex.reduce((s, x)=>s+x)
// the index of the dataset that contains the last point (at dataIndex)
// with the same sign as the total - that is the one that should carry
// the total label
const datasetIndexLast = dataForDataIndex.findLastIndex(x => x * total > 0);
context.total = total;
return datasetIndexLast === datasetIndex ? total.toFixed(2) : null
},
anchor: (context) => {
return context.total > 0 ? 'end' : 'start'
},
align: (context) => {
return context.total > 0 ? 'top' : 'bottom';
},
clip: false
}
}
}
});
<!-- language: lang-html -->
<div style="height:500px">
<canvas id="chart1"></canvas>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.3.0/chart.umd.js"
integrity="sha512-CMF3tQtjOoOJoOKlsS7/2loJlkyctwzSoDK/S40iAB+MqWSaf50uObGQSk5Ny/gfRhRCjNLvoxuCvdnERU4WGg=="
crossOrigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-datalabels/2.2.0/chartjs-plugin-datalabels.min.js" integrity="sha512-JPcRR8yFa8mmCsfrw4TNte1ZvF1e3+1SdGMslZvmrzDYxS69J7J49vkFL8u6u8PlPJK+H3voElBtUCzaXj+6ig==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- end snippet -->
The idea behind this is to have datalabels enabled for all bars from the dataset, but choose the one that is correctly positioned to represent the sum from the formatter
- namely the last dataset whose value (for the current dataIndex
) has the same sign as the sum. Things might be different if the order
option were used.
The code also uses the fact that the context
object is shared, for the same datasetIndex
, between the formatter
, align
and anchor
methods, formatter
being always the first -- if formatter
returns null
the others are not even called. Thus, it saves the sum for the current datasetIndex
in the context
object to be used by the only one point (per datasetIndex
) for which align
and anchor
are called. For a safer solution, that doesn't use this undocumented fact, one may recompute the sum in align
and anchor
, or cache the sum using an external object, which can be a solution to optimise the computation by only computing the sum once, for the first time the formatter
is called for a dataset.
答案2
得分: 0
你可以像这样使用 chartjs-plugin-datalabels
,如下所示:
Chart.defaults.set('plugins.datalabels', {
color: '#FE777B'
});
var chart = new Chart(ctx, {
options: {
plugins: {
// 修改此图表的所有标签的选项
datalabels: {
color: '#36A2EB',
anchor: () => /*在此处添加您的条件*/
}
}
},
data: {
datasets: [{
// 仅为此数据集的标签更改选项
datalabels: {
color: '#FFCE56'
}
}]
}
});
还可以查看 positioning 属性。
英文:
You can use the chartjs-plugin-datalabels
as shown here.
Chart.defaults.set('plugins.datalabels', {
color: '#FE777B'
});
var chart = new Chart(ctx, {
options: {
plugins: {
// Change options for ALL labels of THIS CHART
datalabels: {
color: '#36A2EB',
anchor: ()=>/*your condition here*/
}
}
},
data: {
datasets: [{
// Change options only for labels of THIS DATASET
datalabels: {
color: '#FFCE56'
}
}]
}
});
Also checkout the positioning property.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论