> ## 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.

# Interactions

> Add tooltips, hover effects, clicks, panning, and zooming to your charts

## Overview

Cristalyse provides rich interaction capabilities that make your charts engaging and informative. From simple tooltips to complex pan and zoom operations, every interaction is optimized for performance and accessibility. Support for pinch gestures, scroll wheel, and floating buttons brings intuitive zooming to all platforms.

## Tooltip Interactions

### Basic Tooltips

Show contextual information when users hover over data points:

```dart theme={null}
CristalyseChart()
  .data(data)
  .mapping(x: 'month', y: 'revenue')
  .geomPoint()
  .tooltip(DefaultTooltips.simple('revenue'))
  .build()
```

### Multi-Column Tooltips

Display multiple data fields in formatted tooltips:

```dart theme={null}
CristalyseChart()
  .data(data)
  .mapping(x: 'week', y: 'sales', color: 'region')
  .geomBar()
  .tooltip(DefaultTooltips.multi({
    'week': 'Week',
    'sales': 'Sales ($)',
    'region': 'Region',
    'growth': 'Growth Rate',
  }))
  .build()
```

### Custom Tooltip Builders

Create rich, branded tooltips with custom styling:

```dart theme={null}
CristalyseChart()
  .data(data)
  .mapping(x: 'date', y: 'price', color: 'symbol')
  .geomLine()
  .tooltip((point) {
    return Container(
      padding: const EdgeInsets.all(12),
      decoration: BoxDecoration(
        color: Colors.black.withOpacity(0.9),
        borderRadius: BorderRadius.circular(8),
        border: Border.all(color: Colors.white24),
        boxShadow: const [
          BoxShadow(
            color: Colors.black26,
            blurRadius: 12.0,
            offset: Offset(0, 4),
          ),
        ],
      ),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            '${point.getDisplayValue('symbol')}',
            style: const TextStyle(
              color: Colors.white,
              fontWeight: FontWeight.bold,
              fontSize: 14,
            ),
          ),
          const SizedBox(height: 4),
          Text(
            'Price: \$${point.getDisplayValue('price')}',
            style: const TextStyle(color: Colors.white),
          ),
          Text(
            'Date: ${point.getDisplayValue('date')}',
            style: const TextStyle(color: Colors.white70, fontSize: 12),
          ),
        ],
      ),
    );
  })
  .build()
```

### Tooltip Configuration

Fine-tune tooltip behavior and appearance:

```dart theme={null}
CristalyseChart()
  .data(data)
  .mapping(x: 'x', y: 'y')
  .geomPoint()
  .interaction(
    tooltip: TooltipConfig(
      builder: DefaultTooltips.simple('value'),
      showDelay: const Duration(milliseconds: 100),
      hideDelay: const Duration(milliseconds: 500),
      followPointer: true,
      backgroundColor: const Color(0xFF323232),
      textColor: Colors.white,
      borderRadius: 8.0,
      padding: const EdgeInsets.all(12),
      shadow: const BoxShadow(
        color: Colors.black26,
        blurRadius: 8.0,
        offset: Offset(0, 2),
      ),
    ),
  )
  .build()
```

## Hover Interactions

### Basic Hover Detection

Respond to mouse hover events on data points:

```dart theme={null}
CristalyseChart()
  .data(data)
  .mapping(x: 'category', y: 'value')
  .geomBar()
  .onHover((point) {
    if (point != null) {
      print('Hovering over: ${point.getDisplayValue('category')}');
    } else {
      print('No longer hovering');
    }
  })
  .build()
```

### Advanced Hover Configuration

Control hover sensitivity and debouncing:

```dart theme={null}
CristalyseChart()
  .data(data)
  .mapping(x: 'x', y: 'y')
  .geomPoint(size: 8.0)
  .interaction(
    hover: HoverConfig(
      onHover: (point) {
        // Handle hover start
        if (point != null) {
          setState(() {
            hoveredPoint = point;
          });
        }
      },
      onExit: (point) {
        // Handle hover end
        setState(() {
          hoveredPoint = null;
        });
      },
      hitTestRadius: 20.0, // Generous hit area
      debounce: const Duration(milliseconds: 50),
    ),
  )
  .build()
```

## Click Interactions

### Simple Click Handlers

Handle tap events on data points:

```dart theme={null}
CristalyseChart()
  .data(data)
  .mapping(x: 'month', y: 'revenue')
  .geomBar()
  .onClick((point) {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => DetailPage(data: point.data),
      ),
    );
  })
  .build()
```

### Multiple Click Types

Support different interaction patterns:

```dart theme={null}
CristalyseChart()
  .data(data)
  .mapping(x: 'x', y: 'y', color: 'category')
  .geomPoint()
  .interaction(
    click: ClickConfig(
      onTap: (point) {
        showBottomSheet(
          context: context,
          builder: (context) => DataPointDetails(point: point),
        );
      },
      onDoubleTap: (point) {
        showDialog(
          context: context,
          builder: (context) => AlertDialog(
            title: Text('Edit Data Point'),
            content: EditDataPointForm(point: point),
          ),
        );
      },
      onLongPress: (point) {
        Clipboard.setData(ClipboardData(
          text: 'Value: ${point.getDisplayValue('y')}',
        ));
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('Copied to clipboard')),
        );
      },
      hitTestRadius: 15.0,
    ),
  )
  .build()
```

## Pan Interactions

### Basic Panning

Enable horizontal scrolling through large datasets:

```dart theme={null}
CristalyseChart()
  .data(largeTimeSeriesData)
  .mapping(x: 'timestamp', y: 'value')
  .geomLine()
  .onPan((info) {
    print('Visible range: ${info.visibleMinX} to ${info.visibleMaxX}');
    // Update data source or trigger lazy loading
    if (info.state == PanState.end) {
      fetchDataForRange(info.visibleMinX, info.visibleMaxX);
    }
  })
  .build()
```

### Advanced Pan Configuration

Full control over pan behavior:

```dart theme={null}
CristalyseChart()
  .data(data)
  .mapping(x: 'time', y: 'metric')
  .geomLine()
  .geomPoint()
  .interaction(
    pan: PanConfig(
      enabled: true,
      updateXDomain: true,  // Enable X-axis panning
      updateYDomain: false, // Disable Y-axis panning
      onPanStart: (info) {
        setState(() {
          isPanning = true;
        });
      },
      onPanUpdate: (info) {
        setState(() {
          visibleRange = '${info.visibleMinX?.toStringAsFixed(1)} - ${info.visibleMaxX?.toStringAsFixed(1)}';
        });
      },
      onPanEnd: (info) {
        setState(() {
          isPanning = false;
        });
        // Fetch new data for visible range
        loadDataForVisibleRange(info.visibleMinX, info.visibleMaxX);
      },
      throttle: const Duration(milliseconds: 16), // 60 FPS
    ),
  )
  .build()
```

### Programmatic Pan Control

Control chart panning programmatically with external UI controls:

```dart theme={null}
class ProgrammaticPanChart extends StatefulWidget {
  @override
  _ProgrammaticPanChartState createState() => _ProgrammaticPanChartState();
}

class _ProgrammaticPanChartState extends State<ProgrammaticPanChart> {
  final panController = PanController();
  double visibleMinX = 500;
  double visibleMaxX = 1500;
  
  @override
  void dispose() {
    panController.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // External controls
        Row(
          children: [
            ElevatedButton(
              onPressed: () {
                panController.panTo(PanInfo(
                  visibleMinX: visibleMinX,
                  visibleMaxX: visibleMaxX,
                  state: PanState.update,
                ));
              },
              child: Text('Pan to Range'),
            ),
            ElevatedButton(
              onPressed: () => panController.panReset(),
              child: Text('Reset View'),
            ),
          ],
        ),
        
        // Chart with controller
        Expanded(
          child: CristalyseChart()
            .data(data)
            .mapping(x: 'x', y: 'y')
            .geomLine()
            .interaction(
              pan: PanConfig(
                enabled: true,
                controller: panController,  // Connect controller
                onPanUpdate: (info) {
                  setState(() {
                    visibleMinX = info.visibleMinX ?? visibleMinX;
                    visibleMaxX = info.visibleMaxX ?? visibleMaxX;
                  });
                },
              ),
            )
            .build(),
        ),
      ],
    );
  }
}
```

**Key Features:**

* `panTo()`: Jump to specific data range programmatically
* `panReset()`: Return to original chart view
* Works alongside gesture-based panning
* Full lifecycle management with dispose()

**Use Cases:**

* Zoom buttons for specific time ranges ("Last 7 days", "Last month")
* Coordinated panning across multiple charts
* Jump to bookmarked positions
* Reset button after exploration
* External slider controls for pan position

### Zoom Interactions

Enable intuitive zooming with pinch gestures, scroll wheel, and UI buttons:

#### Basic Zoom Setup

Quick zoom configuration with one method:

```dart theme={null}
CristalyseChart()
  .data(data)
  .mapping(x: 'day', y: 'revenue')
  .geomLine()
  .onZoom((info) {
    print('Zoom scale: ${info.scaleX}x');
    print('Visible range: ${info.visibleMinX} to ${info.visibleMaxX}');
  })
  .build()
```

#### Zoom Modes

Zoom can be configured for different axis combinations:

```dart theme={null}
// X-axis only (default)
CristalyseChart()
  .data(data)
  .mapping(x: 'time', y: 'value')
  .geomLine()
  .onZoom(
    (info) => print('X zoom: ${info.scaleX}x'),
    axis: ZoomAxis.x,
  )
  .build()

// Y-axis only
CristalyseChart()
  .data(data)
  .mapping(x: 'time', y: 'value')
  .geomLine()
  .onZoom(
    (info) => print('Y zoom: ${info.scaleY}x'),
    axis: ZoomAxis.y,
  )
  .build()

// Both axes simultaneously
CristalyseChart()
  .data(data)
  .mapping(x: 'x', y: 'y')
  .geomPoint()
  .onZoom(
    (info) => print('Scale X: ${info.scaleX}x, Y: ${info.scaleY}x'),
    axis: ZoomAxis.both,
  )
  .build()
```

#### Advanced Zoom Configuration

Full control over zoom behavior and appearance:

```dart theme={null}
CristalyseChart()
  .data(data)
  .mapping(x: 'timestamp', y: 'metric', color: 'series')
  .geomLine(strokeWidth: 2.0)
  .geomPoint()
  .interaction(
    zoom: ZoomConfig(
      enabled: true,
      axes: ZoomAxis.both,                    // Zoom X and Y axes
      maxScale: 16.0,                         // Maximum 16x zoom
      minScale: 1.0,                          // Minimum 1x (no zoom out past original)
      wheelSensitivity: 0.0015,               // Scroll wheel sensitivity (0.0005-0.0035)
      buttonStep: 1.4,                        // Zoom 40% per button press
      showButtons: true,                      // Show +/- floating buttons
      buttonAlignment: Alignment.bottomRight, // Button position
      buttonPadding: const EdgeInsets.all(20), // Space from chart edge
      onZoomStart: (info) {
        print('Zoom started at ${info.visibleMinX}');
      },
      onZoomUpdate: (info) {
        print('Zooming - Scale: ${info.scaleX}x');
        // Update UI in real-time
        setState(() {
          currentZoomLevel = info.scaleX ?? 1.0;
          visibleRange = '${info.visibleMinX?.toStringAsFixed(2)} - ${info.visibleMaxX?.toStringAsFixed(2)}';
        });
      },
      onZoomEnd: (info) {
        print('Zoom ended');
        // Fetch new data for visible range if needed
        if (info.visibleMinX != null && info.visibleMaxX != null) {
          loadDataForRange(info.visibleMinX!, info.visibleMaxX!);
        }
      },
    ),
  )
  .build()
```

#### Zoom Input Methods

**Pinch Gesture (Touch):**

* Two-finger pinch to zoom in/out
* Zoom centered at pinch focal point
* Works with configured axes

**Scroll Wheel (Desktop):**

* Scroll up to zoom in, scroll down to zoom out
* Zoom centered at cursor position
* Configurable sensitivity with `wheelSensitivity`
* Precise zoom control for time-series and technical charts

**Floating Buttons (All Platforms):**

* Optional +/- buttons for touch-friendly zoom
* Configurable positioning with `buttonAlignment`
* Adjustable zoom increment with `buttonStep`
* Accessible for users with limited touch capability

**Single Finger (Touch):**

* Single finger performs pan gesture
* Works alongside zoom when multi-touch is detected

#### Zoom Information

The `ZoomInfo` object provides comprehensive zoom state data:

```dart theme={null}
ZoomInfo(
  // Visible axis ranges in data coordinates
  visibleMinX: 100.0,
  visibleMaxX: 500.0,
  visibleMinY: 10.0,
  visibleMaxY: 90.0,
  
  // Zoom scale factors (1.0 = no zoom, 4.0 = 4x zoom)
  scaleX: 2.0,      // 2x zoom on X-axis
  scaleY: 1.5,      // 1.5x zoom on Y-axis
  
  // Current zoom lifecycle state
  state: ZoomState.update,  // start, update, or end
)
```

Use `ZoomState` to differentiate interaction phases:

* `ZoomState.start` - User begins zoom gesture
* `ZoomState.update` - Zoom in progress (fires continuously)
* `ZoomState.end` - User completes zoom gesture

#### Combined Zoom & Pan

Zoom and pan work seamlessly together:

```dart theme={null}
CristalyseChart()
  .data(timeSeriesData)
  .mapping(x: 'timestamp', y: 'value', color: 'category')
  .geomLine(strokeWidth: 2.0)
  .geomPoint(size: 5.0)
  .interaction(
    zoom: ZoomConfig(
      enabled: true,
      axes: ZoomAxis.x,  // Zoom on X-axis only (time dimension)
      wheelSensitivity: 0.0015,
      showButtons: true,
      onZoomUpdate: (info) {
        setState(() {
          zoomDisplay = 'Zoom: ${info.scaleX?.toStringAsFixed(1)}x • '
              'Range: ${info.visibleMinX?.toStringAsFixed(0)} - ${info.visibleMaxX?.toStringAsFixed(0)}';
        });
      },
    ),
    pan: PanConfig(
      enabled: true,
      updateXDomain: true,  // Pan X-axis
      updateYDomain: false, // Keep Y-axis fixed
      throttle: const Duration(milliseconds: 16),
      onPanUpdate: (info) => print('Panning to ${info.visibleMinX}'),
    ),
    tooltip: TooltipConfig(
      builder: DefaultTooltips.multi({
        'timestamp': 'Time',
        'value': 'Value',
        'category': 'Category',
      }),
    ),
  )
  .build()
```

#### Use Cases

**Time-Series Exploration:**

```dart theme={null}
// Users can zoom into specific time periods
// then pan to explore surrounding data
CristalyseChart()
  .data(stockData)
  .mapping(x: 'date', y: 'price')
  .geomLine()
  .interaction(
    zoom: ZoomConfig(
      axes: ZoomAxis.x,
      showButtons: true,  // Easy zoom on mobile
    ),
    pan: PanConfig(enabled: true, updateXDomain: true),
  )
  .build()
```

**Scientific Visualizations:**

```dart theme={null}
// Independent X/Y zooming for scatter plots
CristalyseChart()
  .data(measurementData)
  .mapping(x: 'temperature', y: 'pressure', color: 'material')
  .geomPoint(size: 8.0)
  .interaction(
    zoom: ZoomConfig(
      axes: ZoomAxis.both,  // Zoom both axes independently
      maxScale: 32.0,       // Allow deep zoom for detailed analysis
    ),
  )
  .build()
```

**Mobile Dashboards:**

```dart theme={null}
// Touch-friendly zoom buttons for accessibility
CristalyseChart()
  .data(mobileData)
  .mapping(x: 'hour', y: 'users')
  .geomBar()
  .interaction(
    zoom: ZoomConfig(
      axes: ZoomAxis.x,
      showButtons: true,
      buttonAlignment: Alignment.topRight,  // Accessible location
      buttonStep: 1.5,                      // Larger steps for mobile
    ),
  )
  .build()
```

### Pan with Data Loading

Implement infinite scrolling patterns:

```dart theme={null}
class PanningChart extends StatefulWidget {
  @override
  _PanningChartState createState() => _PanningChartState();
}

class _PanningChartState extends State<PanningChart> {
  List<Map<String, dynamic>> visibleData = [];
  double currentMinX = 0;
  double currentMaxX = 100;

  @override
  void initState() {
    super.initState();
    loadDataForRange(currentMinX, currentMaxX);
  }

  void loadDataForRange(double? minX, double? maxX) async {
    if (minX == null || maxX == null) return;
    
    // Simulate API call
    final newData = await fetchTimeSeriesData(minX, maxX);
    setState(() {
      visibleData = newData;
      currentMinX = minX;
      currentMaxX = maxX;
    });
  }

  @override
  Widget build(BuildContext context) {
    return CristalyseChart()
      .data(visibleData)
      .mapping(x: 'timestamp', y: 'value', color: 'series')
      .geomLine(strokeWidth: 2.0)
      .scaleXContinuous(min: currentMinX, max: currentMaxX)
      .interaction(
        pan: PanConfig(
          enabled: true,
          updateXDomain: true,
          onPanEnd: (info) => loadDataForRange(
            info.visibleMinX, 
            info.visibleMaxX,
          ),
        ),
        tooltip: TooltipConfig(
          builder: DefaultTooltips.multi({
            'timestamp': 'Time',
            'value': 'Value',
            'series': 'Series',
          }),
        ),
      )
      .build();
  }
}
```

## Combined Interactions

### Rich Interactive Dashboard

Combine multiple interaction types for powerful user experience:

```dart theme={null}
CristalyseChart()
  .data(data)
  .mapping(x: 'date', y: 'price', color: 'symbol', size: 'volume')
  .geomPoint(alpha: 0.8)
  .interaction(
    tooltip: TooltipConfig(
      builder: (point) => RichTooltip(
        title: point.getDisplayValue('symbol'),
        fields: {
          'price': 'Price',
          'volume': 'Volume',
          'change': 'Change %',
        },
        point: point,
      ),
      showDelay: const Duration(milliseconds: 50),
      hideDelay: const Duration(milliseconds: 300),
    ),
    hover: HoverConfig(
      onHover: (point) => highlightRelatedPoints(point),
      onExit: (point) => clearHighlights(),
      hitTestRadius: 12.0,
    ),
    click: ClickConfig(
      onTap: (point) => showStockDetails(point),
      onDoubleTap: (point) => addToWatchlist(point),
    ),
    pan: PanConfig(
      enabled: true,
      updateXDomain: true,
      onPanUpdate: (info) => updateVisibleTimeRange(info),
    ),
  )
  .build()
```

## Performance Optimization

### Large Dataset Interactions

Optimize interactions for thousands of data points:

```dart theme={null}
CristalyseChart()
  .data(largeDataset) // 10,000+ points
  .mapping(x: 'x', y: 'y', color: 'category')
  .geomPoint(size: 3.0, alpha: 0.7)
  .interaction(
    tooltip: TooltipConfig(
      builder: DefaultTooltips.simple('y'),
      showDelay: const Duration(milliseconds: 10), // Fast response
    ),
    hover: HoverConfig(
      hitTestRadius: 8.0, // Smaller hit area for performance
      debounce: const Duration(milliseconds: 16), // 60 FPS
    ),
    pan: PanConfig(
      enabled: true,
      throttle: const Duration(milliseconds: 32), // 30 FPS for panning
    ),
  )
  .build()
```

### Memory-Efficient Tooltips

Reuse tooltip widgets to reduce memory allocation:

```dart theme={null}
class PerformantTooltips {
  static final _tooltipPool = <Widget>[];
  
  static Widget pooled(DataPointInfo point) {
    // Reuse existing tooltip widgets when possible
    return Text('Value: ${point.getDisplayValue('value')}');
  }
}

CristalyseChart()
  .data(data)
  .mapping(x: 'x', y: 'y')
  .geomPoint()
  .tooltip(PerformantTooltips.pooled)
  .build()
```

## Accessibility

### Screen Reader Support

Ensure interactions work with assistive technologies:

```dart theme={null}
CristalyseChart()
  .data(data)
  .mapping(x: 'month', y: 'revenue')
  .geomBar()
  .interaction(
    tooltip: TooltipConfig(
      builder: (point) => Semantics(
        label: 'Revenue for ${point.getDisplayValue('month')}: \$${point.getDisplayValue('revenue')}k',
        child: DefaultTooltips.simple('revenue')(point),
      ),
    ),
    click: ClickConfig(
      onTap: (point) {
        // Announce selection to screen readers
        SemanticsService.announce(
          'Selected ${point.getDisplayValue('month')} with revenue \$${point.getDisplayValue('revenue')}k',
          TextDirection.ltr,
        );
      },
    ),
  )
  .build()
```

### Keyboard Navigation

Support keyboard-only users:

```dart theme={null}
class KeyboardNavigableChart extends StatefulWidget {
  @override
  _KeyboardNavigableChartState createState() => _KeyboardNavigableChartState();
}

class _KeyboardNavigableChartState extends State<KeyboardNavigableChart> {
  int? selectedIndex;

  @override
  Widget build(BuildContext context) {
    return Focus(
      onKey: (node, event) {
        if (event is RawKeyDownEvent) {
          if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
            setState(() {
              selectedIndex = (selectedIndex ?? -1) + 1;
              selectedIndex = selectedIndex!.clamp(0, data.length - 1);
            });
            return KeyEventResult.handled;
          }
          // Handle other keys...
        }
        return KeyEventResult.ignored;
      },
      child: CristalyseChart()
        .data(data)
        .mapping(x: 'x', y: 'y')
        .geomPoint()
        .build(),
    );
  }
}
```

## Best Practices

<AccordionGroup>
  <Accordion title="Hit Areas">
    * Use generous hit test radii (15-30px) for touch devices
    * Smaller hit areas (8-15px) for mouse-only interfaces
    * Consider overlapping points in dense visualizations
    * Test on actual devices for optimal sizing
  </Accordion>

  <Accordion title="Performance">
    * Debounce rapid interactions to prevent performance issues
    * Use throttling for pan operations (16-32ms intervals)
    * Zoom updates are throttled internally and cannot be configured
    * Optimize tooltip rendering for large datasets
    * Consider disabling interactions during animations
  </Accordion>

  <Accordion title="User Experience">
    * Provide immediate visual feedback for all interactions
    * Use consistent interaction patterns across charts
    * Show loading states during data fetch operations
    * Implement undo/redo for destructive actions
  </Accordion>

  <Accordion title="Accessibility">
    * Support keyboard navigation where appropriate
    * Provide alternative text for screen readers
    * Ensure sufficient color contrast in tooltips
    * Test with assistive technologies
  </Accordion>
</AccordionGroup>

## Interaction Examples

<CardGroup cols={2}>
  <Card title="Rich Tooltips" icon="message-circle">
    Multi-column tooltips with custom styling and animations
  </Card>

  <Card title="Click Actions" icon="mouse-pointer">
    Navigation, details, and context menus triggered by clicks
  </Card>

  <Card title="Zoom Interactions" icon="search-plus">
    Pinch, scroll wheel, and button-based zoom for all platforms
  </Card>

  <Card title="Pan Interactions" icon="move">
    Explore large datasets with smooth panning gestures
  </Card>

  <Card title="Hover Effects" icon="eye">
    Visual highlights and data previews on mouse hover
  </Card>
</CardGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Animations" icon="sparkles" href="/features/animations">
    Combine interactions with smooth animations
  </Card>

  <Card title="Theming" icon="palette" href="/features/theming">
    Style interaction elements to match your design
  </Card>

  <Card title="Performance" icon="gauge" href="/advanced/performance">
    Optimize interactions for large datasets
  </Card>

  <Card title="Export" icon="download" href="/features/export">
    Export charts while preserving interaction data
  </Card>
</CardGroup>
