> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cristalyse.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Built-in Legends

> Add professional legends to your charts with automatic positioning and dark mode support

## Overview

Cristalyse provides built-in legend support that automatically generates beautiful legends from your chart's color mapping data. With just `.legend()`, you get intelligent symbol generation, smart positioning, and full dark mode compatibility.

<Info>
  **New in v1.5.0**: Built-in legend support with zero configuration required!
</Info>

## Quick Start

Add a legend to any chart with color mapping in just one line:

```dart theme={null}
CristalyseChart()
  .data(salesData)
  .mapping(x: 'quarter', y: 'revenue', color: 'product')
  .geomBar(style: BarStyle.grouped)
  .legend() // ✨ That's it! Auto-positioned legend with product categories
  .build();
```

## Key Features

<CardGroup cols={2}>
  <Card title="Zero Configuration" icon="magic-wand-sparkles">
    Works automatically with smart defaults - just add `.legend()`
  </Card>

  <Card title="9 Positioning Options" icon="arrows-to-circle">
    Flexible positioning: corners, edges, floating with custom coordinates
  </Card>

  <Card title="Smart Symbol Detection" icon="eye">
    Automatically matches your chart type: squares, circles, lines
  </Card>

  <Card title="Dark Mode Ready" icon="moon">
    Legend text adapts automatically to light and dark themes
  </Card>
</CardGroup>

## Positioning

### Basic Positioning

Control legend placement with the `position` parameter:

```dart theme={null}
// Bottom legend (horizontal layout)
.legend(position: LegendPosition.bottom)

// Right side legend (vertical layout)  
.legend(position: LegendPosition.right)

// Top-right corner (default)
.legend(position: LegendPosition.topRight)
```

### All Position Options

<CardGroup cols={3}>
  <Card title="Corner Positions">
    * `LegendPosition.topLeft`
    * `LegendPosition.topRight` (default)
    * `LegendPosition.bottomLeft`
    * `LegendPosition.bottomRight`
  </Card>

  <Card title="Edge Positions">
    * `LegendPosition.top`
    * `LegendPosition.bottom`
    * `LegendPosition.left`
    * `LegendPosition.right`
  </Card>

  <Card title="Free Positioning">
    * `LegendPosition.floating`
    * Set custom coordinates with `floatingOffset`
    * Perfect for overlay layouts
  </Card>
</CardGroup>

### Visual Examples

<Tabs>
  <Tab title="Default (Top Right)">
    ```dart theme={null}
    CristalyseChart()
      .data(data)
      .mapping(x: 'month', y: 'sales', color: 'region')
      .geomBar()
      .legend() // Default: topRight position
      .build();
    ```

    Legend appears in the top-right corner with vertical layout.
  </Tab>

  <Tab title="Bottom Legend">
    ```dart theme={null}
    CristalyseChart()
      .data(data)
      .mapping(x: 'month', y: 'sales', color: 'region')
      .geomBar()
      .legend(position: LegendPosition.bottom)
      .build();
    ```

    Legend appears at bottom with horizontal layout, perfect for mobile.
  </Tab>

  <Tab title="Right Side">
    ```dart theme={null}
    CristalyseChart()
      .data(data)
      .mapping(x: 'month', y: 'sales', color: 'region')
      .geomBar()
      .legend(position: LegendPosition.right)
      .build();
    ```

    Legend appears on the right side with vertical layout.
  </Tab>

  <Tab title="Floating Position">
    ```dart theme={null}
    CristalyseChart()
      .data(data)
      .mapping(x: 'month', y: 'sales', color: 'region')
      .geomBar()
      .legend(
        position: LegendPosition.floating,
        floatingOffset: Offset(100, 30), // x: 100, y: 30 from top-left
      )
      .build();
    ```

    Legend floats at specified coordinates with absolute positioning.
  </Tab>
</Tabs>

## Floating Legends

<Info>
  **New Feature**: Floating legends give you complete control over legend positioning with pixel-perfect placement!
</Info>

### Basic Floating Legend

Use floating position for custom positioning:

```dart theme={null}
CristalyseChart()
  .data(salesData)
  .mapping(x: 'quarter', y: 'revenue', color: 'product')
  .geomBar()
  .legend(
    position: LegendPosition.floating,
    floatingOffset: Offset(50, 20), // x: 50px, y: 20px from top-left
  )
  .build();
```

### Styled Floating Legend

Combine floating position with custom styling:

```dart theme={null}
CristalyseChart()
  .data(data)
  .mapping(x: 'date', y: 'value', color: 'metric')
  .geomLine()
  .legend(
    position: LegendPosition.floating,
    floatingOffset: Offset(120, 40),
    backgroundColor: Colors.white.withValues(alpha: 0.95),
    textStyle: TextStyle(fontSize: 12, fontWeight: FontWeight.w600),
    symbolSize: 14.0,
    itemSpacing: 10.0,
    borderRadius: 8.0,
  )
  .build();
```

### Use Cases for Floating Legends

<AccordionGroup>
  <Accordion title="Dashboard Overlays" icon="layer-group">
    Perfect for complex dashboards where legends need to float over chart content without taking up layout space.

    ```dart theme={null}
    .legend(
      position: LegendPosition.floating,
      floatingOffset: Offset(200, 50),
      backgroundColor: Colors.white.withValues(alpha: 0.9),
    )
    ```
  </Accordion>

  <Accordion title="Custom Chart Layouts" icon="compass-drafting">
    When you need precise control over legend placement in complex multi-chart layouts.

    ```dart theme={null}
    .legend(
      position: LegendPosition.floating,
      floatingOffset: Offset(chartWidth - 150, 30),
    )
    ```
  </Accordion>

  <Accordion title="Responsive Designs" icon="mobile">
    Programmatically adjust floating position based on screen size or device.

    ```dart theme={null}
    final offset = isMobile ? Offset(20, 20) : Offset(100, 50);

    .legend(
      position: LegendPosition.floating,
      floatingOffset: offset,
    )
    ```
  </Accordion>
</AccordionGroup>

### Floating Legend Parameters

| Parameter           | Type                      | Description                                                  |
| ------------------- | ------------------------- | ------------------------------------------------------------ |
| `position`          | `LegendPosition.floating` | Required - enables floating mode                             |
| `floatingOffset`    | `Offset?`                 | Coordinates from top-left corner. Default: `Offset(16, 16)`  |
| `floatingDraggable` | `bool`                    | Enable drag-to-reposition (future feature). Default: `false` |

<Tip>
  **Coordinate System**: The `floatingOffset` uses the Flutter coordinate system where `Offset(x, y)` positions the legend `x` pixels from the left edge and `y` pixels from the top edge.
</Tip>

## Interactive Legends

<Info>
  **New Feature**: Interactive legends allow users to click legend items to show/hide data categories!
</Info>

### Basic Interactive Legend

Enable click-to-toggle with one parameter:

```dart theme={null}
CristalyseChart()
  .data(salesData)
  .mapping(x: 'quarter', y: 'revenue', color: 'product')
  .geomBar(style: BarStyle.grouped)
  .legend(interactive: true) // ✨ Click to toggle visibility
  .build();
```

**Features**:

* **Click legend items** to show/hide categories
* **Visual feedback**: Hidden items appear dimmed with strikethrough
* **Smooth animations**: Data fades in/out gracefully
* **Auto-managed state**: No additional code needed

### External State Management

For advanced control, manage hidden categories externally:

```dart theme={null}
class MyChart extends StatefulWidget {
  @override
  State<MyChart> createState() => _MyChartState();
}

class _MyChartState extends State<MyChart> {
  final Set<String> hiddenCategories = {};

  @override
  Widget build(BuildContext context) {
    return CristalyseChart()
      .data(salesData)
      .mapping(x: 'quarter', y: 'revenue', color: 'product')
      .geomBar()
      .legend(
        interactive: true,
        hiddenCategories: hiddenCategories,
        onToggle: (category, visible) {
          setState(() {
            if (visible) {
              hiddenCategories.remove(category);
            } else {
              hiddenCategories.add(category);
            }
          });
          print('$category is now ${visible ? "visible" : "hidden"}');
        },
      )
      .build();
  }
}
```

### Interactive Legend Parameters

| Parameter          | Type                      | Description                                                 |
| ------------------ | ------------------------- | ----------------------------------------------------------- |
| `interactive`      | `bool`                    | Enable click-to-toggle functionality. Default: `false`      |
| `hiddenCategories` | `Set<String>?`            | Externally managed set of hidden categories. Optional       |
| `onToggle`         | `Function(String, bool)?` | Callback when legend item is toggled. `(category, visible)` |

### Use Cases

<CardGroup cols={2}>
  <Card title="Data Exploration" icon="magnifying-glass">
    Users can focus on specific categories by hiding others, making complex charts easier to analyze.
  </Card>

  <Card title="Comparison Analysis" icon="chart-line">
    Quickly compare subsets of data by toggling relevant categories on and off.
  </Card>

  <Card title="Presentation Mode" icon="presentation-screen">
    Interactively show/hide data during presentations to highlight key insights.
  </Card>

  <Card title="User Preferences" icon="sliders">
    Save user's hidden categories to provide personalized dashboard views.
  </Card>
</CardGroup>

### Interactive with Floating Position

Combine interactive and floating for maximum flexibility:

```dart theme={null}
CristalyseChart()
  .data(data)
  .mapping(x: 'date', y: 'value', color: 'metric')
  .geomLine()
  .legend(
    position: LegendPosition.floating,
    floatingOffset: Offset(120, 40),
    interactive: true, // Click-to-toggle enabled
    backgroundColor: Colors.white.withValues(alpha: 0.95),
    borderRadius: 8.0,
  )
  .build();
```

<Tip>
  **Performance**: Data filtering is efficient and only rebuilds the chart when categories are toggled.
</Tip>

## Symbol Generation

Legends automatically choose appropriate symbols based on your chart type:

| Chart Type        | Symbol   | Description                                   |
| ----------------- | -------- | --------------------------------------------- |
| **Bar Charts**    | ◼ Square | Solid colored squares for categorical bars    |
| **Line Charts**   | ━ Line   | Horizontal line segments matching chart lines |
| **Scatter Plots** | ● Circle | Circular symbols for point data               |
| **Area Charts**   | ━ Line   | Line symbols representing area boundaries     |
| **Bubble Charts** | ● Circle | Circular symbols for bubble data              |

<Note>
  Symbol selection is automatic but can be overridden with custom styling if needed.
</Note>

## Dark Mode Support

Legend text automatically adapts to your chart theme:

<CodeGroup>
  ```dart Light Theme theme={null}
  CristalyseChart()
    .data(data)
    .mapping(x: 'month', y: 'sales', color: 'product')
    .geomBar()
    .theme(ChartTheme.defaultTheme()) // Light theme
    .legend() // Dark text on light background
    .build();
  ```

  ```dart Dark Theme theme={null}
  CristalyseChart()
    .data(data)
    .mapping(x: 'month', y: 'sales', color: 'product')
    .geomBar()
    .theme(ChartTheme.darkTheme()) // Dark theme
    .legend() // Light text on dark background
    .build();
  ```
</CodeGroup>

<Tip>
  **Theme Inheritance**: Legend text uses `theme.axisColor` for perfect contrast in any theme.
</Tip>

## Custom Styling

### Background and Appearance

Add custom styling for professional dashboards:

```dart theme={null}
CristalyseChart()
  .data(data)
  .mapping(x: 'quarter', y: 'revenue', color: 'product')
  .geomBar()
  .legend(
    position: LegendPosition.right,
    backgroundColor: Colors.white.withValues(alpha: 0.95),
    borderRadius: 8.0,
    symbolSize: 14.0,
    itemSpacing: 12.0,
  )
  .build();
```

### Text Styling

Customize legend text while preserving theme colors:

```dart theme={null}
.legend(
  textStyle: TextStyle(
    fontSize: 13,
    fontWeight: FontWeight.w600,
    // Color automatically inherits from theme
  ),
  symbolSize: 16.0,
)
```

<Warning>
  **Theme Colors**: Avoid hardcoding text colors. Let the legend inherit from your theme for proper dark mode support.
</Warning>

### All Styling Options

| Parameter         | Type             | Default    | Description                              |
| ----------------- | ---------------- | ---------- | ---------------------------------------- |
| `position`        | `LegendPosition` | `topRight` | Legend placement                         |
| `backgroundColor` | `Color?`         | `null`     | Legend background color                  |
| `textStyle`       | `TextStyle?`     | `null`     | Text styling (inherits color from theme) |
| `symbolSize`      | `double`         | `12.0`     | Size of legend symbols                   |
| `itemSpacing`     | `double`         | `8.0`      | Space between legend items               |
| `spacing`         | `double`         | `12.0`     | Space between legend and chart           |
| `borderRadius`    | `double`         | `4.0`      | Background border radius                 |

## Real-World Examples

### Dashboard Analytics

Perfect for executive dashboards with multiple data series:

```dart theme={null}
CristalyseChart()
  .data(regionalSales)
  .mapping(x: 'month', y: 'revenue', color: 'region')
  .geomLine(strokeWidth: 3.0)
  .geomPoint(size: 6.0)
  .legend(
    position: LegendPosition.bottom,
    backgroundColor: Colors.grey.shade50,
    borderRadius: 12.0,
  )
  .build();
```

### Mobile-Optimized

Bottom legends work great on mobile devices:

```dart theme={null}
CristalyseChart()
  .data(performanceData)
  .mapping(x: 'date', y: 'value', color: 'metric')
  .geomArea(alpha: 0.7)
  .legend(position: LegendPosition.bottom) // Mobile-friendly
  .build();
```

### Dark Theme Dashboard

Professional dark mode with custom styling:

```dart theme={null}
CristalyseChart()
  .data(analyticsData)
  .mapping(x: 'week', y: 'users', color: 'platform')
  .geomBar(style: BarStyle.grouped)
  .theme(ChartTheme.darkTheme())
  .legend(
    position: LegendPosition.topRight,
    backgroundColor: Colors.black.withValues(alpha: 0.7),
    borderRadius: 8.0,
    symbolSize: 14.0,
  )
  .build();
```

## Best Practices

<AccordionGroup>
  <Accordion title="Mobile Optimization" icon="phone">
    * Use `LegendPosition.bottom` for mobile layouts
    * Keep symbol sizes readable on small screens
    * Consider horizontal orientation for many categories
  </Accordion>

  <Accordion title="Visual Design" icon="palette">
    * Let legends inherit theme colors automatically
    * Use subtle backgrounds with transparency
    * Maintain consistent spacing across charts
  </Accordion>

  <Accordion title="Data Clarity" icon="bar-chart">
    * Ensure category names are descriptive
    * Limit legend items to 6-8 for readability
    * Position legends to not obscure important data
  </Accordion>

  <Accordion title="Accessibility" icon="accessibility">
    * Rely on automatic theme colors for contrast
    * Test legends in both light and dark modes
    * Ensure sufficient color differentiation
  </Accordion>
</AccordionGroup>

## Advanced Usage

### Multi-Series Line Charts

Perfect for time-series data with multiple metrics:

```dart theme={null}
CristalyseChart()
  .data(timeSeriesData)
  .mapping(x: 'date', y: 'value', color: 'metric')
  .geomLine(strokeWidth: 2.5)
  .scaleXContinuous()
  .scaleYContinuous(
    labels: (value) => '${value.toStringAsFixed(1)}K',
  )
  .legend(position: LegendPosition.topLeft)
  .build();
```

### Grouped Bar Charts with Custom Colors

Combine custom palettes with legends:

```dart theme={null}
final brandColors = {
  'iOS': const Color(0xFF007ACC),
  'Android': const Color(0xFF3DDC84),
  'Web': const Color(0xFFFF6B35),
};

CristalyseChart()
  .data(platformData)
  .mapping(x: 'quarter', y: 'users', color: 'platform')
  .geomBar(style: BarStyle.grouped)
  .customPalette(categoryColors: brandColors)
  .legend(position: LegendPosition.right)
  .build();
```

## Requirements

<Note>
  **Color Mapping Required**: Legends only work when you have a `color` parameter in your `.mapping()` call.
</Note>

```dart theme={null}
// <Icon icon="check" /> Works - has color mapping
.mapping(x: 'month', y: 'sales', color: 'product')
.legend()

// <Icon icon="x" /> Won't show - no color mapping
.mapping(x: 'month', y: 'sales')
.legend() // Legend will be empty
```

## Migration Guide

### From Manual Legends

If you were manually creating legends, you can now use the built-in functionality:

<CodeGroup>
  ```dart Before (Manual) theme={null}
  // Manual legend creation with custom widgets
  Column(
    children: [
      Row(
        children: [
          Container(width: 12, height: 12, color: Colors.blue),
          SizedBox(width: 8),
          Text('Product A'),
        ],
      ),
      // ... more manual legend items
      Expanded(child: CristalyseChart()...),
    ],
  )
  ```

  ```dart After (Built-in) theme={null}
  // Automatic legend with one line
  CristalyseChart()
    .data(data)
    .mapping(x: 'month', y: 'sales', color: 'product')
    .geomBar()
    .legend() // ✨ Automatically creates legend for all products
    .build();
  ```
</CodeGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Legend not appearing">
    **Solution**: Ensure you have a `color` parameter in your `.mapping()` call:

    ```dart theme={null}
    .mapping(x: 'month', y: 'sales', color: 'category') // Required
    .legend()
    ```
  </Accordion>

  <Accordion title="Legend text too dark in dark theme">
    **Solution**: Don't override text color - let it inherit from theme:

    ```dart theme={null}
    // <Icon icon="check" /> Good - inherits theme color
    .legend(textStyle: TextStyle(fontSize: 14))

    // <Icon icon="x" /> Bad - hardcoded color
    .legend(textStyle: TextStyle(fontSize: 14, color: Colors.black))
    ```
  </Accordion>

  <Accordion title="Legend overlapping chart content">
    **Solution**: Choose a different position or add background:

    ```dart theme={null}
    .legend(
      position: LegendPosition.bottom, // Move to bottom
      backgroundColor: Colors.white.withValues(alpha: 0.9),
    )
    ```
  </Accordion>
</AccordionGroup>

## Y-Axis Titles in Legends

<Info>
  **New in v1.13.0**: Display Y-axis titles alongside legend entries for multi-axis charts!
</Info>

For dual-axis charts, show axis titles in the legend to clarify which data series maps to which Y-axis:

```dart theme={null}
CristalyseChart()
  .data(dualAxisData)
  .mapping(x: 'month', y: 'revenue')
  .mappingY2('conversion_rate')
  .geomBar(yAxis: YAxis.primary)
  .geomLine(yAxis: YAxis.secondary)
  .scaleYContinuous(title: 'Revenue ($K)')
  .scaleY2Continuous(title: 'Conversion Rate (%)')
  .legend(
    position: LegendPosition.bottom,
    showTitles: true,  // Show axis titles in legend
  )
  .build();
```

**Features**:

* Displays Y-axis titles alongside respective data series
* Improves legend readability for multi-axis charts
* Helps users understand which metric uses which axis
* Works with all legend positioning options

### Parameters

| Parameter    | Type   | Default | Description                             |
| ------------ | ------ | ------- | --------------------------------------- |
| `showTitles` | `bool` | `false` | Display Y-axis titles in legend entries |

<Tip>
  **Pro Tip**: Combine `showYAxisTitles` with descriptive axis titles for maximum clarity in multi-axis charts.
</Tip>

## What's Next?

* Learn about [Custom Themes](/advanced/custom-themes) to create branded chart experiences
* Explore [Interactions](/features/interactions) to add tooltips and hover effects
* Check out [Export](/features/export) to save charts with legends as SVG files

<Tip>
  **Pro Tip**: Legends work seamlessly with all chart types and export formats. Your exported SVGs will include the positioned legends automatically!
</Tip>
