Skip to content

Chart System

The chart system provides 10 built-in chart renderers, a factory for routing data to the correct renderer, and a color palette system. Charts consume ChartData (a list of DataPoint instances with a ChartType) and produce Flutter widgets.

Chart Types

The ChartType enum defines all supported chart types:

TypeDisplay NameRequires SeriesDescription
piePie ChartNoStandard pie chart with labeled slices
donutDonut ChartNoRing chart with a center hole
barBar ChartNoVertical bar chart
stackedBarStacked Bar ChartYesBars stacked by series
groupedBarGrouped Bar ChartYesSide-by-side bars grouped by label
lineLine ChartNoSingle line chart
multiLineMulti-Line ChartYesMultiple lines, one per series
areaArea ChartNoFilled area under a line
sparklineSparklineNoCompact inline chart (no axes)
gaugeGauge ChartNoSingle-value gauge/meter (1 data point)

Chart Type Validation

Each chart type enforces data requirements at construction time:

dart
// Pie/donut: every point needs label + value
ChartData(type: ChartType.pie, data: [
  DataPoint(label: 'Beginner', value: 45),
  DataPoint(label: 'Intermediate', value: 35),
  DataPoint(label: 'Advanced', value: 20),
]);

// Multi-series: every point needs a series field
ChartData(type: ChartType.groupedBar, data: [
  DataPoint(label: 'Jan', value: 120, series: 'Beginner'),
  DataPoint(label: 'Jan', value: 85, series: 'Advanced'),
  DataPoint(label: 'Feb', value: 140, series: 'Beginner'),
  DataPoint(label: 'Feb', value: 95, series: 'Advanced'),
]);

// Gauge: exactly one data point
ChartData(type: ChartType.gauge, data: [
  DataPoint(label: 'Completion', value: 87),
]);

BaseChartRenderer

All chart renderers extend BaseChartRenderer, which provides shared functionality:

dart
abstract class BaseChartRenderer {
  // Default 10-color palette
  static const List<Color> defaultPalette;

  // Color resolution: custom by series name → custom by index → default palette
  Color getColorForSeries(int index, Map<String, Color>? customColors, String? seriesName);

  // Parse custom colors from JSON: {"Series1": "#FF0000", "Series2": "#00FF00"}
  Map<String, Color>? parseSeriesColors(String? jsonString);

  // Get unique series names from chart data
  List<String> getUniqueSeries(ChartData chartData);

  // Build legend widget
  Widget buildLegend(BuildContext context, List<String> seriesNames, Map<String, Color>? seriesColors);

  // Subclasses implement this
  Widget build(
    BuildContext context,
    ChartData chartData, {
    String? title,
    double? height,
    bool showLegend = true,
    bool showGrid = true,
    bool showTooltips = true,
    bool enableAnimation = true,
    Map<String, Color>? seriesColors,
  });
}

Default Color Palette

The default palette provides 10 distinct colors. When there are more series than colors, the palette wraps around:

IndexColorHex
0Blue#2196F3
1Green#4CAF50
2Orange#FF9800
3Red#F44336
4Purple#9C27B0
5Teal#00BCD4
6Pink#E91E63
7Amber#FFC107
8Brown#795548
9Blue Grey#607D8B

Custom Colors

You can override colors by series name or by index:

dart
// By series name
final colors = {
  'Beginner': Color(0xFF4CAF50),
  'Intermediate': Color(0xFFFF9800),
  'Advanced': Color(0xFFF44336),
};

renderer.build(context, chartData, seriesColors: colors);

Colors can also be parsed from a JSON string, which is useful for storing color configuration in properties:

dart
final colorsJson = '{"Beginner": "#4CAF50", "Advanced": "#F44336"}';
final parsed = renderer.parseSeriesColors(colorsJson);

Supported color formats: #RRGGBB, #AARRGGBB, rgb(r,g,b), rgba(r,g,b,a).

ChartRendererFactory

The ChartRendererFactory is a singleton that routes ChartData to the appropriate renderer based on chart type:

dart
class ChartRendererFactory {
  static final ChartRendererFactory _instance;
  factory ChartRendererFactory();

  // Look up renderer by chart data
  BaseChartRenderer createRenderer(ChartData chartData);

  // Direct lookup by type
  BaseChartRenderer? getRenderer(ChartType type);

  // Registration
  void register(ChartType type, BaseChartRenderer renderer);
  void replace(ChartType type, BaseChartRenderer renderer);
  bool hasRenderer(ChartType type);
}

Built-in Renderers

The factory automatically registers all 10 built-in renderers on first access:

ChartTypeRenderer Class
piePieChartRenderer
donutDonutChartRenderer
barBarChartRenderer
stackedBarStackedBarChartRenderer
groupedBarGroupedBarChartRenderer
lineLineChartRenderer
multiLineMultiLineChartRenderer
areaAreaChartRenderer
sparklineSparklineChartRenderer
gaugeGaugeChartRenderer

Using the Factory

dart
final factory = ChartRendererFactory();

// Create chart data
final chartData = ChartData(
  type: ChartType.bar,
  data: [
    DataPoint(label: 'Dart Basics', value: 120),
    DataPoint(label: 'Flutter UI', value: 85),
    DataPoint(label: 'State Mgmt', value: 95),
  ],
);

// Get renderer and build widget
final renderer = factory.createRenderer(chartData);
final chartWidget = renderer.build(
  context,
  chartData,
  title: 'Enrollments per Course',
  height: 300,
  showLegend: false,
);

Replacing a Built-in Renderer

If you need a custom renderer for an existing chart type, use replace():

dart
final factory = ChartRendererFactory();
factory.replace(ChartType.bar, MyCustomBarChartRenderer());

Registering a Custom Chart Type

For entirely new chart types, you would need to extend ChartType (or use an existing type with replace). The factory throws a StateError if you try to render a type that has no registered renderer.

dart
// Fetch enrollment data over time
final dataSource = ApiChartDataSource();
final data = await dataSource.fetchData(
  mapping: ChartFieldMapping(
    labelField: 'month',
    valueField: 'enrollments',
    seriesField: 'level',
  ),
  filters: {
    'endpoint': 'https://api.lms.example.com/enrollments/monthly',
    'dataArrayPath': 'data',
  },
);

// Create multi-line chart data
final chartData = ChartData(type: ChartType.multiLine, data: data);

// Render with custom colors per course level
final factory = ChartRendererFactory();
final renderer = factory.createRenderer(chartData);

final chart = renderer.build(
  context,
  chartData,
  title: 'Enrollment Trends',
  height: 300,
  seriesColors: {
    'Beginner': const Color(0xFF4CAF50),
    'Intermediate': const Color(0xFFFF9800),
    'Advanced': const Color(0xFFF44336),
  },
);

LMS Example: Course Distribution Pie Chart

dart
// Static data for course distribution
final chartData = ChartData(
  type: ChartType.pie,
  data: [
    DataPoint(label: 'Beginner', value: 45),
    DataPoint(label: 'Intermediate', value: 35),
    DataPoint(label: 'Advanced', value: 20),
  ],
);

final factory = ChartRendererFactory();
final renderer = factory.createRenderer(chartData);

final chart = renderer.build(
  context,
  chartData,
  title: 'Course Distribution by Level',
  height: 250,
  showLegend: true,
  seriesColors: {
    'Beginner': const Color(0xFF4CAF50),
    'Intermediate': const Color(0xFF2196F3),
    'Advanced': const Color(0xFF9C27B0),
  },
);

Integrating Charts in Components

Charts are typically rendered inside ComponentDescriptor render callbacks:

dart
final courseDistributionChart = ComponentDescriptor(
  schemaType: 'lms.course_distribution',
  componentKey: 'lms_course_distribution',
  displayName: 'Course Distribution',
  icon: Icons.pie_chart,
  category: lmsCategory,
  minCellHeight: 250,
  properties: () => (PropertyCollectionBuilder()
    ..enumeration('chartType', 'Chart Type',
      values: ['pie', 'donut'],
      defaultValue: 'pie',
    )
    ..boolean('showLegend', 'Show Legend', defaultValue: true)
  ).buildCollection(),
  render: (context, props) {
    final type = ChartType.fromString(
      props.stringValue('chartType') ?? 'pie',
    );
    final showLegend = props.boolValue('showLegend') ?? true;

    final chartData = ChartData(
      type: type,
      data: [
        DataPoint(label: 'Beginner', value: 45),
        DataPoint(label: 'Intermediate', value: 35),
        DataPoint(label: 'Advanced', value: 20),
      ],
    );

    final renderer = ChartRendererFactory().createRenderer(chartData);
    return renderer.build(
      context,
      chartData,
      showLegend: showLegend,
    );
  },
);

Next Steps