Advanced multi-label system allowing multiple labels per data point with individual styling and positioning, perfect for complex data visualization requirements.
Display multiple labels per data point, each with its own styling and positioning options.
interface Options extends LabelOptions {
/** Multi-label definition object */
labels?: Record<string, LabelOptions | null>;
}
interface LabelOptions {
// All standard label options apply to each named label
anchor?: Indexable<Anchor> | Scriptable<Anchor>;
align?: Indexable<Align> | Scriptable<Align>;
color?: Indexable<Color> | Scriptable<Color>;
formatter?: (value: any, context: Context) => any | null;
// ... all other label options
}Key Concepts:
null to disable it for specific datasetsUsage Examples:
// Basic multi-label setup
datalabels: {
// Root options (inherited by all labels)
color: 'black',
font: { size: 12 },
labels: {
title: {
align: 'top',
formatter: (value, context) => context.dataset.label
},
value: {
align: 'center',
formatter: (value) => value
},
percentage: {
align: 'bottom',
formatter: (value, context) => {
const sum = context.dataset.data.reduce((a, b) => a + b);
return Math.round((value / sum) * 100) + '%';
}
}
}
}Control how root options are inherited and overridden by named labels.
Usage Examples:
// Inheritance pattern
datalabels: {
// Root defaults
color: 'white',
backgroundColor: 'rgba(0,0,0,0.7)',
borderRadius: 4,
padding: 4,
labels: {
main: {
// Inherits all root options
formatter: (value) => value
},
secondary: {
// Overrides specific options, inherits others
color: 'yellow',
font: { weight: 'bold' },
formatter: (value, context) => context.dataset.label
}
}
}
// Disable specific labels per dataset
datasets: [{
data: [10, 20, 30],
datalabels: {
labels: {
main: { formatter: (v) => v },
detail: null // Disable detail label for this dataset
}
}
}]Position multiple labels around data points without overlap.
Usage Examples:
// Radial positioning
datalabels: {
labels: {
north: {
align: 'top',
offset: 10,
formatter: (value) => '↑ ' + value
},
south: {
align: 'bottom',
offset: 10,
formatter: (value) => '↓ ' + value
},
east: {
align: 'right',
offset: 10,
formatter: (value) => value + ' →'
},
west: {
align: 'left',
offset: 10,
formatter: (value) => '← ' + value
}
}
}
// Layered labels
datalabels: {
labels: {
background: {
backgroundColor: 'black',
color: 'white',
padding: 8,
borderRadius: 6,
formatter: (value) => value
},
foreground: {
color: 'red',
font: { weight: 'bold', size: 10 },
align: 'top',
offset: -15,
formatter: (value, context) => '★'
}
}
}Create context-aware multi-label configurations.
Usage Examples:
// Conditional labels based on value
datalabels: {
labels: {
value: {
formatter: (value) => value
},
warning: {
color: 'red',
align: 'top',
offset: 15,
formatter: (value, context) => {
return value < 10 ? '⚠️ Low' : null;
}
},
trend: {
color: 'green',
align: 'right',
offset: 20,
formatter: (value, context) => {
const prevValue = context.dataIndex > 0 ?
context.dataset.data[context.dataIndex - 1] : value;
return value > prevValue ? '↗️' : value < prevValue ? '↘️' : '→';
}
}
}
}
// Dataset-specific multi-labels
datalabels: {
labels: {
primary: {
formatter: function(value, context) {
// Different format per dataset
switch (context.datasetIndex) {
case 0: return '$' + value;
case 1: return value + '%';
case 2: return value + ' units';
default: return value;
}
}
},
secondary: {
align: 'top',
color: 'gray',
font: { size: 10 },
formatter: function(value, context) {
return context.dataset.label;
}
}
}
}Optimize multi-label configurations for different chart types.
Bar Charts:
// Inside and outside labels
datalabels: {
labels: {
inside: {
anchor: 'center',
align: 'center',
color: 'white',
formatter: (value) => value
},
outside: {
anchor: 'end',
align: 'end',
offset: 5,
color: 'black',
formatter: (value, context) => {
const sum = context.dataset.data.reduce((a, b) => a + b);
return Math.round((value / sum) * 100) + '%';
}
}
}
}Pie Charts:
// Value inside, label outside
datalabels: {
labels: {
name: {
anchor: 'end',
align: 'end',
offset: 25,
formatter: (value, context) => {
return context.chart.data.labels[context.dataIndex];
}
},
value: {
anchor: 'center',
color: 'white',
font: { weight: 'bold' },
formatter: (value) => value
},
percent: {
anchor: 'center',
align: 'bottom',
color: 'white',
font: { size: 10 },
formatter: (value, context) => {
const sum = context.dataset.data.reduce((a, b) => a + b);
return Math.round((value / sum) * 100) + '%';
}
}
}
}Line Charts:
// Point value and trend indicator
datalabels: {
labels: {
value: {
align: 'top',
offset: 8,
formatter: (value) => value
},
trend: {
align: function(context) {
const current = context.dataset.data[context.dataIndex];
const prev = context.dataIndex > 0 ?
context.dataset.data[context.dataIndex - 1] : current;
return current > prev ? 45 : current < prev ? -45 : 0;
},
offset: 15,
color: function(context) {
const current = context.dataset.data[context.dataIndex];
const prev = context.dataIndex > 0 ?
context.dataset.data[context.dataIndex - 1] : current;
return current > prev ? 'green' : current < prev ? 'red' : 'gray';
},
formatter: (value, context) => {
const current = value;
const prev = context.dataIndex > 0 ?
context.dataset.data[context.dataIndex - 1] : current;
const diff = current - prev;
return diff !== 0 ? (diff > 0 ? '+' : '') + diff.toFixed(1) : '';
}
}
}
}Complex scenarios using multiple labels with sophisticated logic.
Statistical Dashboard:
datalabels: {
labels: {
rank: {
anchor: 'start',
align: 'top',
color: 'blue',
font: { size: 8, weight: 'bold' },
formatter: (value, context) => {
const sorted = [...context.dataset.data]
.sort((a, b) => b - a);
return '#' + (sorted.indexOf(value) + 1);
}
},
value: {
anchor: 'center',
align: 'center',
font: { size: 14, weight: 'bold' },
formatter: (value) => value
},
percentage: {
anchor: 'end',
align: 'bottom',
color: 'gray',
font: { size: 8 },
formatter: (value, context) => {
const sum = context.dataset.data.reduce((a, b) => a + b);
return (value / sum * 100).toFixed(1) + '%';
}
},
category: {
anchor: 'center',
align: 'top',
offset: 20,
formatter: (value, context) => {
if (value > 100) return 'High';
if (value > 50) return 'Medium';
return 'Low';
}
}
}
}Responsive Multi-labels:
datalabels: {
labels: {
full: {
display: function(context) {
return context.chart.width > 600;
},
formatter: (value, context) => {
return `${context.dataset.label}: ${value} (${
Math.round(value / context.dataset.data.reduce((a,b) => a+b) * 100)
}%)`;
}
},
compact: {
display: function(context) {
return context.chart.width <= 600 && context.chart.width > 300;
},
formatter: (value) => value
},
minimal: {
display: function(context) {
return context.chart.width <= 300;
},
formatter: (value) => value > 50 ? value : null
}
}
}