or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

content-formatting.mdevent-handling.mdindex.mdlabel-positioning.mdlabel-styling.mdmulti-label.mdplugin-integration.md
tile.json

multi-label.mddocs/

Multi-label System

Advanced multi-label system allowing multiple labels per data point with individual styling and positioning, perfect for complex data visualization requirements.

Capabilities

Multi-label Configuration

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:

  • Root Options: Default settings inherited by all labels
  • Named Labels: Individual labels with unique keys and specific configurations
  • Label Inheritance: Named labels inherit root options and override specific properties
  • Null Labels: Set label to null to disable it for specific datasets

Usage 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) + '%';
      }
    }
  }
}

Label Inheritance and Override

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
    }
  }
}]

Positioning Multiple Labels

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) => '★'
    }
  }
}

Dynamic Multi-label Scenarios

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;
      }
    }
  }
}

Chart Type Specific Multi-labels

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) : '';
      }
    }
  }
}

Advanced Multi-label Patterns

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
    }
  }
}