I'm trying to remove a dataset from label:
But the label callback is called twice, so i don't know how to do it.
I was expecting an array in the callback and that you can simply remove the one not needed.
here's what i have so far and tried:
var labels = [];
for (let index = 0; index < 12; index++) {
labels.push(index);
}
window.onload = function () {
var canvas = document.getElementById('elm-chart'),
ctx = canvas.getContext('2d');
var myLineChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: '-15',
data: [
{
x: 0,
y: 10,
},
{
x: 1,
y: 20,
},
],
borderColor: 'red',
},
{
label: '15',
data: [
{
x: 1,
y: 20,
},
{
x: 2,
y: 30,
},
],
borderColor: 'blue',
},
{
label: '25',
data: [
{
x: 2,
y: 30,
},
{
x: 3,
y: 35,
},
],
borderColor: 'yellow',
},
{
label: '-15',
data: [
{
x: 6,
y: -10,
},
{
x: 7,
y: -20,
},
],
borderColor: 'red',
},
{
label: '15',
data: [
{
x: 7,
y: -20,
},
{
x: 8,
y: -30,
},
],
borderColor: 'blue',
},
{
label: '25',
data: [
{
x: 8,
y: -30,
},
{
x: 9,
y: -35,
},
],
borderColor: 'yellow',
},
],
},
options: {
responsive: true,
plugins: {
legend: {
onClick: (evt, legendItem, legend) => {
let newVal = !legendItem.hidden;
legend.chart.data.datasets.forEach((dataset) => {
if (dataset.label === legendItem.text) {
dataset.hidden = newVal;
}
});
legend.chart.update();
},
labels: {
filter: (legendItem, chartData) => {
let entries = chartData.datasets.map((e) => e.label);
return entries.indexOf(legendItem.text) === legendItem.datasetIndex;
},
},
},
},
scales: {
x: {
type: 'linear',
ticks: {
stepSize: 30,
},
},
},
plugins: {
tooltip: {
callbacks: {
title: function (context) {
return [`test`, 'test2'];
},
label: function (context) {
console.log(context.dataset.label);
console.log(context.formattedValue);
console.log(context);
return [
`${context.dataset.label}:${context.formattedValue}`,
'test',
];
},
},
},
},
},
});
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.2.1/chart.min.js"></script>
<canvas id="elm-chart" width="640" height="480"></canvas>
So to be clear i don't want to remove the datapoint in the dataset. I just want to remove 1 datapoint from the label, the top one always
What you can do is instead of using the default tooltip instead use a html tooltip.
In the JS that makes the tooltip instead of looping over each active item you only take the first one and display that.
Official sample of html tooltip: https://www.chartjs.org/docs/master/samples/tooltip/html.html
Related
I have the following toy data:
export const playData = {
datasets: [
{
label: 'Dataset 1',
showLine: true,
data: [{ x: 1, y: 10, name: 'John' }, { x: 2, y: 5, name: 'Linda' }, { x: 3, y: 7, name: 'Erin' }, { x: 4, y: 4, name: 'Chloe' }, { x: 5, y: 8, name: 'Paul' }],
borderColor: 'rgb(255, 99, 132)',
backgroundColor: 'rgba(255, 99, 132, 0.5)',
},
};
I am then trying to make a custom tooltip showing this data, which works as expected:
export function Chart(props: { chartData: ChartData }) {
return <Scatter
data={props.chartData}
options={{
responsive: true,
scales: {
x: {
title: {
display: true,
text: 'Age (years)'
},
},
y: {
title: {
display: true,
text: 'Change in Height (inches)'
}
}
},
plugins: {
legend: {
position: 'top' as const,
},
tooltip: {
callbacks: {
label: (context) => {
return [context.raw.name, `age: ${context.parsed.x} years`, `height change: ${context.parsed.y} in`]
}
}
}
},
}}
/>;
}
But TS underlines the final context.raw.name and says
Property 'name' does not exist on type 'unknown'.ts(2339)
How can I fix this?
You can create your own custom interface by extending the one that is already being used and add the custom option you added to your dataset:
import { Chart, TooltipItem } from 'chart.js';
interface CustomTooltipItem extends TooltipItem<'scatter'> {
raw: {
x: number,
y: number,
name: string,
}
}
const ctx = document.getElementById("myChart") as HTMLCanvasElement;
const myChart = new Chart(ctx, {
type: 'scatter',
data: {
datasets: [{
label: '# of Votes',
data: [{ x: 1, y: 10, name: 'John' }, { x: 2, y: 5, name: 'Linda' }, { x: 3, y: 7, name: 'Erin' }, { x: 4, y: 4, name: 'Chloe' }, { x: 5, y: 8, name: 'Paul' }],
}]
},
options: {
plugins: {
tooltip: {
callbacks: {
label: (context: CustomTooltipItem) => {
return [context.raw.name, `age: ${context.parsed.x} years`, `height change: ${context.parsed.y} in`]
}
}
}
}
}
});
https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzgYQBYENYBo4BUIQA2MwYAkjAKYhxwC+cAZlBDQOQDGGsAdAFYBnNgG4AsACgJwAHZUojdB0ooArgJit8REuSo1KADyrSAJgLwFipCtQA8bAR3Qw5bAHyIJtKOgDuALk9xWloDQOkVEAAjSigsLxCAT3DImLiE2ml0EEpA9SgZAHN44PoJOgkJDghpdTgOGAM4AF44EwgOSMpZHkLKGABRQmpumAAhRLITAAoAIhBEtEwYWYBKOHRzAAlcAFkAGWR0aQA3TaGR2TFxatr4BaXYFrhpSl8UbhhphoMcBAy4DBEmBcnAHE4XLE2CUQm1nOhAv9SrCTPCBP0BIEANpI2GwwjoGKEQJsADEcAgjDgADUIFQhDC8bRUTAEXAcXAwnAAIw4ZI8gAMOCyORJACkIKhpGx6H9OYEAEx8wIAVmF2VBbH2MlRMrocq5AGZlXAAOzq0VggYFaWyxDyuAAFhNzpeGpJaEIEEoeoNqpNAA4LZqAAroFSEPUAXUZsLoUYB+oBEDAJBqmKCTLAhBUhRkGdxTMBlh0iIBTKchEIUUUAGsC+WiwSiYFvjUqEZAsg1BoQForLpqOtmh5C0W8VB+iooNJ2bcOzAeD5fDwRZQcAADdB9QIAEgQ88Mi7AmHRJh4BgYiUop43m9QlGAhVQ8C4xx3cH3h6MPBPUDPPCJAwMgbgmyLjmU4FMhUUEhDB0GJuUqzCEAA
I'm trying to create a bar chart that is grouped by the dataset rather than the label in chartjs3 and I'm having no luck. Before I switch over to building in D3, I wanted to check if this achievable within the confines of ChartJS.
Here is a link to a fiddle where I've been playing around with using different dataset structures and options from the Bar chart docs. I suspect that this is outside of the scope of the charting library, or is within the scope, but requires some custom components to be made – I would appreciate if anyone could direct me on what in particular I would need to extend (e.g. is it a case of a custom axes? or more?).
https://jsfiddle.net/f34ucs76/2/
const data = {
labels: [
'2000','2010','2020',
// 'Frogs','Monkeys','Squirrels','Bats'
],
datasets: [
{
label: 'Frogs',
data: [30.2,20.8,36.2],
backgroundColor: 'red',
//stack: 'Frogs',
},
{
label: 'Monkeys',
data: [16.3,13.0,22.3],
backgroundColor: 'blue',
//stack: 'Monkeys',
},
{
label: 'Squirrels',
data: [5.8,3.1,14.9],
backgroundColor: 'cyan',
//stack:'Squirrels',
},
{
label: 'Bats',
data: [0.1,3.6,2.6],
backgroundColor: 'aquamarine',
//stack:'Bats',
},
],
}
const options = {
maintainAspectRatio: false,
responsive: true,
interaction: {
intersect: false,
mode: 'index',
},
plugins: {
title: {
display: false,
},
subtitle: {
display: true,
align: 'start',
text: ['Source: Animals Index, June 2022'],
font: {
size: 12,
style: 'italic',
},
position: 'bottom',
padding: {
top: 20,
},
},
legend: {
display: true,
position: 'bottom',
},
tooltip: {
enabled: true,
},
},
scales: {
x: {
grid: {
display: false,
},
},
y: {
title: {
display: true,
text: 'Horsepower',
},
},
},
}
const mychart = new Chart(
document.getElementById('mychart'),
{
type: 'bar',
data: data,
options: options,
}
);
Your problem can be solved with Chart.js but the labels and the single dataset need to be generated.
Further you need to define your own legend by defining a plugins.legend.labels.generateLabels together with with a plugins.legend.labels.onClick function.
For further information, consult the Legend page from the Chart.js. documentation.
Please take a look at your amended and runnable code below and see how it works.
const baseData = {
labels: ['2000','2010','2020'],
datasets: [
{ label: 'Frogs', data: [30.2, 20.8, 36.2], bgColor: 'red', hidden: false },
{ label: 'Monkeys', data: [16.3, 13.0, 22.3], bgColor: 'blue', hidden: false },
{ label: 'Squirrels', data: [5.8,3.1, 14.9], bgColor: 'cyan', hidden: false },
{ label: 'Bats', data: [0.1, 3.6, 2.6], bgColor: 'aquamarine', hidden: false }
]
};
const iLastDs = baseData.datasets.length - 1;
const data = {
labels: baseData.datasets
.map(() => baseData.labels)
.map((labels, i) => i < iLastDs ? [...labels, null] : labels)
.flatMap(v => v),
datasets: [{
data: baseData.datasets
.map(ds => ds.data)
.map((data, i) => i < iLastDs ? [...data, null] : data)
.flatMap(v => v),
backgroundColor: baseData.datasets
.map(ds => ds.data.map(v => ds.bgColor))
.map((bgColors, i) => i < iLastDs ? [...bgColors, null] : bgColors)
.flatMap(v => v),
categoryPercentage: 1,
barPercentage: 0.9
}]
};
const options = {
maintainAspectRatio: true,
responsive: true,
interaction: {
intersect: true,
mode: 'index',
},
plugins: {
legend: {
position: 'bottom',
labels: {
generateLabels: chart => baseData.datasets.map((ds, i) => ({
datasetIndex: i,
text: ds.label,
fillStyle: ds.bgColor,
strokeStyle: 'lightgray',
hidden: baseData.datasets[i].hidden
}))
},
onClick: (event, legendItem, legend) => {
baseData.datasets[legendItem.datasetIndex].hidden = !baseData.datasets[legendItem.datasetIndex].hidden;
const iFirstValue = legendItem.datasetIndex + legendItem.datasetIndex * baseData.labels.length;
for (let i = iFirstValue; i < iFirstValue + baseData.labels.length; i++) {
legend.chart.toggleDataVisibility(i);
}
legend.chart.update();
}
},
tooltip: {
callbacks: {
title: ctx => {
const dsIndex = Math.floor(ctx[0].dataIndex / baseData.datasets.length);
return baseData.datasets[dsIndex].label + ' / ' + ctx[0].label;
}
}
}
},
scales: {
x: {
grid: {
display: false
}
},
y: {
title: {
display: true,
text: 'Horsepower',
}
}
}
};
new Chart('mychart', {
type: 'bar',
data,
options
});
span {
font-style: italic;
font-size: 12px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
<canvas id="mychart" height="100"></canvas>
<span>Source: Animals Index, June 2022</span>
I'm trying to display a stacked bar chart on my web page, but it's not working.
I've tried to follow the documentation about data structure, but it seems not working. The two data are made from an Ajax call and built in js.
https://www.chartjs.org/docs/latest/general/data-structures.html
I've made a jsfiddle if you want to look at what I've done.
https://jsfiddle.net/batmine3/h4w02y5g/6/
let myData = [
{
x: 'value1',
critical: 1,
high: 2,
medium: 0,
low: 5
},
{
x: 'value2',
critical: 2,
high: 0,
medium: 5,
low: 0
}
];
let myLabels = ['value1', 'value2'];
let stackedBar = new Chart($('#chartContainer'), {
type: 'bar',
data: {
labels: myLabels,
datasets:
[
{
label: 'critical',
data: myData,
borderColor: '#d30000',
backgroundColor: '#d30000',
parsing: {
yAxisKey: 'critical'
}
},
{
label: 'high',
data: myData,
borderColor: '#e97000',
backgroundColor: '#e97000',
parsing: {
yAxisKey: 'high'
}
},
{
label: 'medium',
data: myData,
borderColor: '#ff9c56',
backgroundColor: '#ff9c56',
parsing: {
yAxisKey: 'medium'
}
},
{
label: 'low',
data: myData,
borderColor: '#ffd063',
backgroundColor: '#ffd063',
parsing: {
yAxisKey: 'low'
}
}
]
},
options: {
scales: {
x: {
stacked: true,
ridLines: {
display: false
}
},
y: {
stacked: true,
beginAtZero: true
}
}
}
});
So after, investigating styles and formatting, it was just a problem of versions.
The parse options were not present in version 2.8.
I need to show the last bar value only. like this image:
I try this code but it shows all values.
var dataX = {
labels: [['Personne', 'seule'], ['Couple sans', 'enfant'], ['Couple avec', 'enfant(s)'], ['Famille', 'monoparentale'], 'Autres'],
datasets: [{
label: 'Data 1',
data: [33,28,25,8,2.5],
backgroundColor: '#00BBF1',
borderWidth: 0
},
{
label: 'Data 2',
data: [29,30,30,8,2],
backgroundColor: '#3CC6F4',
borderWidth: 0
},
{
label: 'Data 3',
data: [41,22,22,11,3],
backgroundColor: '#92D9F8',
borderWidth: 0
}]
};
var optionsX = {
tooltips: {
enabled: false
},
responsive: true,
maintainAspectRatio: false,
legend: false,
scales: {
xAxes: [{
gridLines : {
color: "#fff"
},
}],
yAxes: [{
gridLines : {
display : false
},
ticks: {
min: 0,
max: 50,
stepSize: 10,
callback: function(value) {
return value + "%"
},
}
}]
},
plugins: {
datalabels: {
color: '#59595B',
font: {
weight: 'bold',
size: 14,
},
align: 'end',
anchor: 'end',
formatter: function(value, context) {
return value +'%';
}
}
},
};
var ctx = document.getElementById('chart-one');
var myChart = new Chart(ctx, {
type: 'bar',
data: dataX,
options: optionsX
});
You can change the plugins.datalabels.formatter function as follows:
plugins: {
...
datalabels: {
formatter: (value, context) => context.datasetIndex == 2 ? value + '%' : ''
}
}
Please take a look at your amended code below and see how it works.
var dataX = {
labels: [
['Personne', 'seule'],
['Couple sans', 'enfant'],
['Couple avec', 'enfant(s)'],
['Famille', 'monoparentale'], 'Autres'
],
datasets: [{
label: 'Data 1',
data: [33, 28, 25, 8, 2.5],
backgroundColor: '#00BBF1',
borderWidth: 0
},
{
label: 'Data 2',
data: [29, 30, 30, 8, 2],
backgroundColor: '#3CC6F4',
borderWidth: 0
},
{
label: 'Data 3',
data: [41, 22, 22, 11, 3],
backgroundColor: '#92D9F8',
borderWidth: 0
}
]
};
var optionsX = {
tooltips: {
enabled: false
},
responsive: true,
maintainAspectRatio: false,
legend: false,
scales: {
xAxes: [{
gridLines: {
color: "#fff"
},
}],
yAxes: [{
gridLines: {
display: false
},
ticks: {
min: 0,
max: 50,
stepSize: 10,
callback: function(value) {
return value + "%"
},
}
}]
},
plugins: {
datalabels: {
color: '#59595B',
font: {
weight: 'bold',
size: 14,
},
align: 'end',
anchor: 'end',
formatter: (value, context) => context.datasetIndex == 2 ? value + '%' : ''
}
},
};
var ctx = document.getElementById('chart-one');
var myChart = new Chart(ctx, {
type: 'bar',
data: dataX,
options: optionsX
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels"></script>
<canvas id="chart-one"></canvas>
I try to have in the same time datalabels and the sum display in my stacked bar chart.
I use chartjs-plugin-datalabels.js to display datalabels or sum.
Above just with datalabels:
Above just with sum :
I would like to have both in the same chart.
I use the follow tricks to sum display :
<script>
const totalizer = {
id: 'totalizer',
beforeUpdate: chart => {
let totals = {}
let utmost = 0
chart.data.datasets.forEach((dataset, datasetIndex) => {
if (chart.isDatasetVisible(datasetIndex)) {
utmost = datasetIndex
dataset.data.forEach((value, index) => {
totals[index] = (totals[index] || 0) + value
})
}
})
chart.$totalizer = {
totals: totals,
utmost: utmost
}
}
}
new Chart('chart', {
type: 'bar',
data: {
labels: ["Note par thème du répondant", "Note par thème sectorielle", "Note par thème générale"],
datasets: [{
label: 'Thème 1',
data: [12, 19, 3],
backgroundColor: 'rgba(255, 206, 86)'
},
{
label: 'Thème 2',
data: [3,8,1],
backgroundColor: 'rgba(54, 162, 235)'
},
{
label: 'Thème 3',
data: [ 5, 2, 3],
backgroundColor: 'rgba(255, 255, 132)'
},{
label: 'Thème 4',
data: [ 5, 2, 3],
backgroundColor: 'rgba(255, 99, 255)'
},{
label: 'Thème 5',
data: [ 5, 2, 3],
backgroundColor: 'rgba(100, 250, 132)'
},{
label: 'Thème 6',
data: [ 5, 2, 3],
backgroundColor: 'rgba(89, 99, 132)'
},{
label: 'Thème 7',
data: [ 5, 2, 3],
backgroundColor: 'rgba(255, 0, 132)'
},{
label: 'Thème 8',
data: [ 5, 2, 3],
backgroundColor: 'rgba(0, 150, 255)'
},{
label: 'Thème 9',
data: [ 5, 2, 3],
backgroundColor: 'rgba(100, 100, 100)'
},{
label: 'Thème 10',
data: [ 10, 4, 7],
backgroundColor: 'rgba(255, 255, 0)'
}]
},
options: {
title :{
display :true,
text : "Mon titre"
},
scales: {
yAxes: [{
stacked: true,
ticks: {
beginAtZero: true,
suggestedMax: 100
}
}],
xAxes: [{
stacked: true
}]
},
plugins: {
datalabels: {
formatter: (value, ctx) => {
const total = ctx.chart.$totalizer.totals[ctx.dataIndex]
return total.toLocaleString('fr-FR', {})
},
align: 'end',
anchor: 'end',
display: function(ctx) {
return ctx.datasetIndex === ctx.chart.$totalizer.utmost
}
}
}
},
plugins: [totalizer]
});
</script>
I would appreciate any help. Thanks!
OK, I found a way to show Sum and datalabels in the same time.
I add new dataset to represent "Total data" with a backgroundColor transparent and I add this following code in my dataset Total:
datalabels: {
backgroundColor: function (context) {
return 'rgba(244,50,49)';
},
formatter: (value, ctx) => {
const total = ctx.chart.$totalizer.totals[ctx.dataIndex];
return total.toLocaleString('fr-FR', {})
},
align: 'end',
anchor: 'end',
display: function (ctx) {
return ctx.datasetIndex === ctx.chart.$totalizer.utmost
}
Full code bellow :
<script>
const totalizer = {
id: 'totalizer',
beforeUpdate: chart => {
let totals = {}
let utmost = 0
chart.data.datasets.forEach((dataset, datasetIndex) => {
if (chart.isDatasetVisible(datasetIndex)) {
utmost = datasetIndex
dataset.data.forEach((value, index) => {
totals[index] = (totals[index] || 0) + value
})
}
})
chart.$totalizer = {
totals: totals,
utmost: utmost
}
}
}
new Chart('chart', {
type: 'bar',
data: {
labels: ["Note par thème du répondant", "Note par thème sectorielle", "Note par thème générale"],
datasets: [{
label: 'Thème 1',
data: [10, 10, 10],
backgroundColor: 'rgba(68,114,196,0.6)'
}, {
label: 'Thème 2',
data: [13, 8, 11],
backgroundColor: 'rgba(237,125,49,0.6)'
}, {
label: 'Thème 3',
data: [5, 12, 13],
backgroundColor: 'rgba(165,165,165,0.6)'
}, {
label: 'Thème 4',
data: [9, 12, 13],
backgroundColor: 'rgba(255,192,0,0.6)'
}, {
label: 'Thème 5',
data: [5, 12, 9],
backgroundColor: 'rgba(91,155,213,0.6)'
}, {
label: 'Thème 6',
data: [9, 12, 5],
backgroundColor: 'rgba(112,173,71,0.6)'
}, {
label: 'Thème 7',
data: [5, 12, 10],
backgroundColor: 'rgba(38,68,120,0.6)'
}, {
label: 'Thème 8',
data: [15, 12, 10],
backgroundColor: 'rgba(99,99,99,0.6)'
}, {
label: 'Thème 9',
data: [15, 12, 11],
backgroundColor: 'rgba(37,94,145,0.6)'
}, {
label: 'Thème 10',
data: [10, 4, 7],
backgroundColor: 'rgba(24,91,62,0.6)'
}, {
label: 'Total',
data: [0, 0, 0],
backgroundColor: 'rgba(24,91,62,0)',
datalabels: {
backgroundColor: function (context) {
return 'rgba(244,50,49)';
},
formatter: (value, ctx) => {
const total = ctx.chart.$totalizer.totals[ctx.dataIndex];
return total.toLocaleString('fr-FR', {})
},
align: 'end',
anchor: 'end',
display: function (ctx) {
return ctx.datasetIndex === ctx.chart.$totalizer.utmost
}
}
}
]
},
options: {
title: {
text: "Mon titre",
display: true
},
scales: {
yAxes: [{
stacked: true,
ticks: {
beginAtZero: true,
suggestedMax: 100
}
}
],
xAxes: [{
stacked: true
}
]
},
plugins: {
datalabels: {
color: 'rgba(255,255,255)',
display: function (context) {
return context.chart.isDatasetVisible(context.datasetIndex);
},
backgroundColor: function (context) {
return context.dataset.backgroundColor;
},
borderRadius: 4,
font: {
weight: 'bold'
}
}
}
},
plugins: [totalizer]
});
</script>
Great! I just simplified solution without totilizer:
datasets: {
label: 'Total',
data: [120, 120, 120, 120, 120, 120, 120, 120, 120, 120],
backgroundColor: 'rgba(24,91,62,0)',
datalabels: {
backgroundColor: () => 'white',
formatter: (value, ctx) => {
let total = 0;
let index = ctx.dataIndex;
data.datasets.map((d, i) => {
if (i === 2) return;
total += d.data[index];
});
return total;
},
align: 'end',
anchor: 'end',
padding: 0,
},
},