Skip to main content

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.
New in v1.5.0: Built-in legend support with zero configuration required!

Quick Start

Add a legend to any chart with color mapping in just one line:
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

Zero Configuration

Works automatically with smart defaults - just add .legend()

9 Positioning Options

Flexible positioning: corners, edges, floating with custom coordinates

Smart Symbol Detection

Automatically matches your chart type: squares, circles, lines

Dark Mode Ready

Legend text adapts automatically to light and dark themes

Positioning

Basic Positioning

Control legend placement with the position parameter:
// 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

Corner Positions

  • LegendPosition.topLeft
  • LegendPosition.topRight (default)
  • LegendPosition.bottomLeft
  • LegendPosition.bottomRight

Edge Positions

  • LegendPosition.top
  • LegendPosition.bottom
  • LegendPosition.left
  • LegendPosition.right

Free Positioning

  • LegendPosition.floating
  • Set custom coordinates with floatingOffset
  • Perfect for overlay layouts

Visual Examples

  • Default (Top Right)
  • Bottom Legend
  • Right Side
  • Floating Position
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.

Floating Legends

New Feature: Floating legends give you complete control over legend positioning with pixel-perfect placement!

Basic Floating Legend

Use floating position for custom positioning:
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:
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

Perfect for complex dashboards where legends need to float over chart content without taking up layout space.
.legend(
  position: LegendPosition.floating,
  floatingOffset: Offset(200, 50),
  backgroundColor: Colors.white.withValues(alpha: 0.9),
)
When you need precise control over legend placement in complex multi-chart layouts.
.legend(
  position: LegendPosition.floating,
  floatingOffset: Offset(chartWidth - 150, 30),
)
Programmatically adjust floating position based on screen size or device.
final offset = isMobile ? Offset(20, 20) : Offset(100, 50);

.legend(
  position: LegendPosition.floating,
  floatingOffset: offset,
)

Floating Legend Parameters

ParameterTypeDescription
positionLegendPosition.floatingRequired - enables floating mode
floatingOffsetOffset?Coordinates from top-left corner. Default: Offset(16, 16)
floatingDraggableboolEnable drag-to-reposition (future feature). Default: false
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.

Interactive Legends

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

Basic Interactive Legend

Enable click-to-toggle with one parameter:
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:
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

ParameterTypeDescription
interactiveboolEnable click-to-toggle functionality. Default: false
hiddenCategoriesSet<String>?Externally managed set of hidden categories. Optional
onToggleFunction(String, bool)?Callback when legend item is toggled. (category, visible)

Use Cases

Data Exploration

Users can focus on specific categories by hiding others, making complex charts easier to analyze.

Comparison Analysis

Quickly compare subsets of data by toggling relevant categories on and off.

Presentation Mode

Interactively show/hide data during presentations to highlight key insights.

User Preferences

Save user’s hidden categories to provide personalized dashboard views.

Interactive with Floating Position

Combine interactive and floating for maximum flexibility:
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();
Performance: Data filtering is efficient and only rebuilds the chart when categories are toggled.

Symbol Generation

Legends automatically choose appropriate symbols based on your chart type:
Chart TypeSymbolDescription
Bar Charts◼ SquareSolid colored squares for categorical bars
Line Charts━ LineHorizontal line segments matching chart lines
Scatter Plots● CircleCircular symbols for point data
Area Charts━ LineLine symbols representing area boundaries
Bubble Charts● CircleCircular symbols for bubble data
Symbol selection is automatic but can be overridden with custom styling if needed.

Dark Mode Support

Legend text automatically adapts to your chart theme:
CristalyseChart()
  .data(data)
  .mapping(x: 'month', y: 'sales', color: 'product')
  .geomBar()
  .theme(ChartTheme.defaultTheme()) // Light theme
  .legend() // Dark text on light background
  .build();
Theme Inheritance: Legend text uses theme.axisColor for perfect contrast in any theme.

Custom Styling

Background and Appearance

Add custom styling for professional dashboards:
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:
.legend(
  textStyle: TextStyle(
    fontSize: 13,
    fontWeight: FontWeight.w600,
    // Color automatically inherits from theme
  ),
  symbolSize: 16.0,
)
Theme Colors: Avoid hardcoding text colors. Let the legend inherit from your theme for proper dark mode support.

All Styling Options

ParameterTypeDefaultDescription
positionLegendPositiontopRightLegend placement
backgroundColorColor?nullLegend background color
textStyleTextStyle?nullText styling (inherits color from theme)
symbolSizedouble12.0Size of legend symbols
itemSpacingdouble8.0Space between legend items
spacingdouble12.0Space between legend and chart
borderRadiusdouble4.0Background border radius

Real-World Examples

Dashboard Analytics

Perfect for executive dashboards with multiple data series:
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:
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:
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

  • Use LegendPosition.bottom for mobile layouts
  • Keep symbol sizes readable on small screens
  • Consider horizontal orientation for many categories
  • Let legends inherit theme colors automatically
  • Use subtle backgrounds with transparency
  • Maintain consistent spacing across charts
  • Ensure category names are descriptive
  • Limit legend items to 6-8 for readability
  • Position legends to not obscure important data
  • Rely on automatic theme colors for contrast
  • Test legends in both light and dark modes
  • Ensure sufficient color differentiation

Advanced Usage

Multi-Series Line Charts

Perfect for time-series data with multiple metrics:
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:
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

Color Mapping Required: Legends only work when you have a color parameter in your .mapping() call.
// <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:
// 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()...),
  ],
)

Troubleshooting

Solution: Ensure you have a color parameter in your .mapping() call:
.mapping(x: 'month', y: 'sales', color: 'category') // Required
.legend()
Solution: Don’t override text color - let it inherit from theme:
// <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))
Solution: Choose a different position or add background:
.legend(
  position: LegendPosition.bottom, // Move to bottom
  backgroundColor: Colors.white.withValues(alpha: 0.9),
)

What’s Next?

  • Learn about Custom Themes to create branded chart experiences
  • Explore Interactions to add tooltips and hover effects
  • Check out Export to save charts with legends as SVG files
Pro Tip: Legends work seamlessly with all chart types and export formats. Your exported SVGs will include the positioned legends automatically!