Skip to main content

View Live Example

See heat map charts in action with interactive examples

Overview

Heat maps are perfect for visualizing 2D data patterns where color intensity represents value magnitude. Cristalyse heat maps support customizable color schemes (via colorGradient), interactive tooltips, and responsive layouts for effective data exploration.
API Note: Heat maps use .mappingHeatMap() instead of .mapping(), and color gradients are specified via the colorGradient parameter in .geomHeatMap(). If no colorGradient is specified, the default GradientColorScale.heatMap() gradient (dark blue → cyan → lime green → bright red) is automatically applied.

Basic Heat Map

Create a simple heat map to show data density:
final performanceData = [
  {'region': 'North', 'month': 'Jan', 'sales': 120},
  {'region': 'North', 'month': 'Feb', 'sales': 135},
  {'region': 'North', 'month': 'Mar', 'sales': 98},
  {'region': 'South', 'month': 'Jan', 'sales': 85},
  {'region': 'South', 'month': 'Feb', 'sales': 92},
  {'region': 'South', 'month': 'Mar', 'sales': 110},
  {'region': 'West', 'month': 'Jan', 'sales': 165},
  {'region': 'West', 'month': 'Feb', 'sales': 140},
  {'region': 'West', 'month': 'Mar', 'sales': 180},
];

CristalyseChart()
  .data(performanceData)
  .mappingHeatMap(x: 'month', y: 'region', value: 'sales')
  .geomHeatMap(
    cellSpacing: 2.0,
    cellBorderRadius: BorderRadius.circular(4),
    showValues: true,
    // Specify custom gradient or omit for default GradientColorScale.heatMap()
    colorGradient: [Colors.lightBlue.shade100, Colors.blue.shade800],
    interpolateColors: true,
  )
  .scaleXOrdinal()
  .scaleYOrdinal()
  .theme(ChartTheme.defaultTheme())
  .build()

Business Performance Heat Map

Monitor regional sales performance across months:
final businessData = [
  {'region': 'North America', 'month': 'Q1', 'sales': 450},
  {'region': 'North America', 'month': 'Q2', 'sales': 520},
  {'region': 'North America', 'month': 'Q3', 'sales': 380},
  {'region': 'North America', 'month': 'Q4', 'sales': 620},
  {'region': 'Europe', 'month': 'Q1', 'sales': 320},
  {'region': 'Europe', 'month': 'Q2', 'sales': 290},
  {'region': 'Europe', 'month': 'Q3', 'sales': 410},
  {'region': 'Europe', 'month': 'Q4', 'sales': 480},
  {'region': 'Asia Pacific', 'month': 'Q1', 'sales': 180},
  {'region': 'Asia Pacific', 'month': 'Q2', 'sales': 220},
  {'region': 'Asia Pacific', 'month': 'Q3', 'sales': 280},
  {'region': 'Asia Pacific', 'month': 'Q4', 'sales': 350},
];

CristalyseChart()
  .data(businessData)
  .mappingHeatMap(x: 'month', y: 'region', value: 'sales')
  .geomHeatMap(
    cellSpacing: 2.0,
    cellBorderRadius: BorderRadius.circular(6),
    showValues: true,
    colorGradient: [Colors.red.shade200, Colors.orange, Colors.green.shade600],
    interpolateColors: true,
    valueFormatter: (value) => '\$${value.toStringAsFixed(0)}K',
    valueTextStyle: TextStyle(
      fontSize: 12,
      fontWeight: FontWeight.w600,
    ),
  )
  .scaleXOrdinal()
  .scaleYOrdinal()
  .theme(ChartTheme.defaultTheme())
  .build()

System Monitoring Heat Map

Visualize server response times across hours and services:
final monitoringData = [
  {'hour': '00:00', 'service': 'API Gateway', 'responseTime': 45},
  {'hour': '00:00', 'service': 'Auth Service', 'responseTime': 32},
  {'hour': '00:00', 'service': 'Database', 'responseTime': 28},
  {'hour': '06:00', 'service': 'API Gateway', 'responseTime': 120},
  {'hour': '06:00', 'service': 'Auth Service', 'responseTime': 85},
  {'hour': '06:00', 'service': 'Database', 'responseTime': 95},
  {'hour': '12:00', 'service': 'API Gateway', 'responseTime': 200},
  {'hour': '12:00', 'service': 'Auth Service', 'responseTime': 150},
  {'hour': '12:00', 'service': 'Database', 'responseTime': 180},
  {'hour': '18:00', 'service': 'API Gateway', 'responseTime': 180},
  {'hour': '18:00', 'service': 'Auth Service', 'responseTime': 130},
  {'hour': '18:00', 'service': 'Database', 'responseTime': 160},
];

CristalyseChart()
  .data(monitoringData)
  .mappingHeatMap(x: 'hour', y: 'service', value: 'responseTime')
  .geomHeatMap(
    cellSpacing: 1.0,
    cellBorderRadius: BorderRadius.circular(2),
    showValues: true,
    minValue: 0,
    maxValue: 250,
    colorGradient: [
      Colors.green.shade400,
      Colors.yellow,
      Colors.red.shade600,
    ],
    interpolateColors: true,
    nullValueColor: Colors.grey.shade300,
    valueFormatter: (value) => '${value.toInt()}ms',
    valueTextStyle: TextStyle(
      fontSize: 10,
      fontWeight: FontWeight.w600,
      color: Colors.white,
    ),
  )
  .scaleXOrdinal()
  .scaleYOrdinal()
  .theme(ChartTheme.darkTheme())
  .build()

Correlation Matrix Heat Map

Display statistical correlations with diverging color scheme:
final correlationData = [
  {'var1': 'Revenue', 'var2': 'Marketing', 'correlation': 0.85},
  {'var1': 'Revenue', 'var2': 'Sales Team', 'correlation': 0.92},
  {'var1': 'Revenue', 'var2': 'Customer Sat', 'correlation': 0.74},
  {'var1': 'Marketing', 'var2': 'Revenue', 'correlation': 0.85},
  {'var1': 'Marketing', 'var2': 'Sales Team', 'correlation': 0.68},
  {'var1': 'Marketing', 'var2': 'Customer Sat', 'correlation': 0.45},
  {'var1': 'Sales Team', 'var2': 'Revenue', 'correlation': 0.92},
  {'var1': 'Sales Team', 'var2': 'Marketing', 'correlation': 0.68},
  {'var1': 'Sales Team', 'var2': 'Customer Sat', 'correlation': 0.58},
  {'var1': 'Customer Sat', 'var2': 'Revenue', 'correlation': 0.74},
  {'var1': 'Customer Sat', 'var2': 'Marketing', 'correlation': 0.45},
  {'var1': 'Customer Sat', 'var2': 'Sales Team', 'correlation': 0.58},
];

CristalyseChart()
  .data(correlationData)
  .mappingHeatMap(x: 'var1', y: 'var2', value: 'correlation')
  .geomHeatMap(
    cellSpacing: 2.0,
    cellBorderRadius: BorderRadius.circular(3),
    showValues: true,
    minValue: -1.0,
    maxValue: 1.0,
    // Diverging gradient: negative (blue) → neutral (white) → positive (red)
    colorGradient: [
      Colors.blue.shade700,
      Colors.white,
      Colors.red.shade700,
    ],
    interpolateColors: true,
    valueFormatter: (value) => value.toStringAsFixed(2),
    valueTextStyle: TextStyle(fontSize: 11),
    cellAspectRatio: 1.0, // Square cells
  )
  .scaleXOrdinal()
  .scaleYOrdinal()
  .theme(ChartTheme.defaultTheme())
  .build()

Styling Options

Color Gradients

Define custom color schemes for different data types:
// Single color gradient (low to high)
CristalyseChart()
  .data(data)
  .mappingHeatMap(x: 'x', y: 'y', value: 'value')
  .geomHeatMap(
    colorGradient: [Colors.white, Colors.deepPurple],
    interpolateColors: true,
  )
  .build()

// Multi-step gradient
CristalyseChart()
  .data(data)
  .mappingHeatMap(x: 'x', y: 'y', value: 'value')
  .geomHeatMap(
    colorGradient: [
      Colors.blue.shade900,
      Colors.blue.shade300,
      Colors.white,
      Colors.orange.shade300,
      Colors.red.shade900,
    ],
    interpolateColors: true,
  )
  .build()

// Diverging gradient
CristalyseChart()
  .data(data)
  .mappingHeatMap(x: 'x', y: 'y', value: 'value')
  .geomHeatMap(
    colorGradient: [Colors.blue, Colors.white, Colors.red],
    interpolateColors: true,
  )
  .build()

Cell Styling

Customize cell appearance and borders:
CristalyseChart()
  .data(data)
  .mappingHeatMap(x: 'x', y: 'y', value: 'value')
  .geomHeatMap(
    cellSpacing: 3.0,
    cellBorderRadius: BorderRadius.circular(8),
    cellAspectRatio: 1.5, // width:height ratio
    showValues: true,
  )
  .build()

Value Text Styling

Control value text appearance:
CristalyseChart()
  .data(data)
  .mappingHeatMap(x: 'x', y: 'y', value: 'value')
  .geomHeatMap(
    showValues: true,
    valueTextStyle: TextStyle(
      fontSize: 14,
      fontWeight: FontWeight.bold,
      color: Colors.black87,
    ),
  )
  .build()

Data Handling

Missing Values

Handle null or missing data points:
CristalyseChart()
  .data(dataWithNulls)
  .mappingHeatMap(x: 'x', y: 'y', value: 'value')
  .geomHeatMap(
    nullValueColor: Colors.grey.shade200,
  )
  .build()

Value Ranges

Set explicit minimum and maximum values:
CristalyseChart()
  .data(data)
  .mappingHeatMap(x: 'x', y: 'y', value: 'value')
  .geomHeatMap(
    minValue: 0,
    maxValue: 100,
    // values outside range are clamped
    colorGradient: [Colors.white, Colors.red],
    interpolateColors: true,
  )
  .build()

Value Formatting

Customize how values are displayed:
import 'package:intl/intl.dart';

// Currency formatting
CristalyseChart()
  .data(salesData)
  .mappingHeatMap(x: 'month', y: 'region', value: 'revenue')
  .geomHeatMap(
    showValues: true,
    valueFormatter: (value) => NumberFormat.simpleCurrency().format(value),
  )
  .build()

// Percentage formatting
CristalyseChart()
  .data(percentageData)
  .mappingHeatMap(x: 'category', y: 'segment', value: 'percentage')
  .geomHeatMap(
    showValues: true,
    valueFormatter: (value) => '${(value * 100).toStringAsFixed(1)}%',
  )
  .build()

// Custom formatting
CristalyseChart()
  .data(temperatureData)
  .mappingHeatMap(x: 'hour', y: 'day', value: 'temperature')
  .geomHeatMap(
    showValues: true,
    valueFormatter: (value) => '${value.toStringAsFixed(1)}°C',
  )
  .build()

Interactive Features

Hover Effects

Add rich tooltips on cell hover:
CristalyseChart()
  .data(data)
  .mappingHeatMap(x: 'x', y: 'y', value: 'value')
  .geomHeatMap()
  .interaction(
    tooltip: TooltipConfig(
      builder: (point) {
        return Container(
          padding: EdgeInsets.all(12),
          decoration: BoxDecoration(
            color: Colors.black.withOpacity(0.8),
            borderRadius: BorderRadius.circular(6),
          ),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                '${point.getDisplayValue('x')} - ${point.getDisplayValue('y')}',
                style: TextStyle(
                  color: Colors.white,
                  fontWeight: FontWeight.bold,
                ),
              ),
              Text(
                'Value: ${point.getDisplayValue('value')}',
                style: TextStyle(color: Colors.white),
              ),
            ],
          ),
        );
      },
    ),
  )
  .build()

Click Handlers

React to cell selection:
CristalyseChart()
  .data(data)
  .mappingHeatMap(x: 'x', y: 'y', value: 'value')
  .geomHeatMap()
  .interaction(
    click: ClickConfig(
      onTap: (point) {
        print('Clicked cell: ${point.data}');
        // Show detailed view
        showCellDetails(point.data);
      },
    ),
  )
  .build()

Animation Options

Fade In Animation

Cells appear with smooth fade transition:
CristalyseChart()
  .data(data)
  .mappingHeatMap(x: 'x', y: 'y', value: 'value')
  .geomHeatMap()
  .animate(
    duration: Duration(milliseconds: 1000),
    curve: Curves.easeInOut,
  )
  .build()

Staggered Animation

Each cell animates with a slight delay:
CristalyseChart()
  .data(data)
  .mappingHeatMap(x: 'x', y: 'y', value: 'value')
  .geomHeatMap()
  .animate(
    duration: Duration(milliseconds: 1500),
    curve: Curves.elasticOut,
    stagger: Duration(milliseconds: 50),
  )
  .build()

Best Practices

When to Use Heat Maps

Good for:
  • 2D categorical data visualization
  • Correlation matrices
  • Time-based patterns (hour vs day)
  • Geographic data on grids
  • Performance monitoring dashboards
Avoid for:
  • Continuous spatial data
  • Data with more than 20x20 cells
  • Precise value comparison
  • Single-dimension data

Design Tips

  • Use intuitive color schemes (cool to warm for intensity)
  • Ensure sufficient color contrast for accessibility
  • Limit grid size to maintain readability
  • Consider showing values for precise reading
  • Use diverging colors for data with meaningful zero point

Performance Considerations

  • Optimize for datasets with < 400 cells (20x20)
  • Use simpler styling for large grids
  • Consider data aggregation for very large datasets
  • Test color schemes for colorblind accessibility

Common Patterns

Website Analytics Heat Map

final analyticsData = [
  {'page': 'Home', 'hour': '9 AM', 'visitors': 245},
  {'page': 'Home', 'hour': '12 PM', 'visitors': 380},
  {'page': 'Home', 'hour': '6 PM', 'visitors': 520},
  {'page': 'Products', 'hour': '9 AM', 'visitors': 180},
  {'page': 'Products', 'hour': '12 PM', 'visitors': 290},
  {'page': 'Products', 'hour': '6 PM', 'visitors': 350},
];

CristalyseChart()
  .data(analyticsData)
  .mappingHeatMap(x: 'hour', y: 'page', value: 'visitors')
  .geomHeatMap(
    cellSpacing: 2.0,
    showValues: true,
    colorGradient: [Colors.blue.shade100, Colors.blue.shade800],
    interpolateColors: true,
  )
  .build()

Financial Risk Matrix

final riskData = [
  {'probability': 'Low', 'impact': 'Low', 'risk': 1},
  {'probability': 'Low', 'impact': 'Medium', 'risk': 2},
  {'probability': 'Low', 'impact': 'High', 'risk': 3},
  {'probability': 'Medium', 'impact': 'Low', 'risk': 2},
  {'probability': 'Medium', 'impact': 'Medium', 'risk': 4},
  {'probability': 'Medium', 'impact': 'High', 'risk': 6},
  {'probability': 'High', 'impact': 'Low', 'risk': 3},
  {'probability': 'High', 'impact': 'Medium', 'risk': 6},
  {'probability': 'High', 'impact': 'High', 'risk': 9},
];

CristalyseChart()
  .data(riskData)
  .mappingHeatMap(x: 'probability', y: 'impact', value: 'risk')
  .geomHeatMap(
    cellSpacing: 2.0,
    cellAspectRatio: 1.25,
    showValues: true,
    colorGradient: [Colors.green, Colors.yellow, Colors.red],
    interpolateColors: true,
  )
  .build()

Next Steps