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:
| Type | Display Name | Requires Series | Description |
|---|---|---|---|
pie | Pie Chart | No | Standard pie chart with labeled slices |
donut | Donut Chart | No | Ring chart with a center hole |
bar | Bar Chart | No | Vertical bar chart |
stackedBar | Stacked Bar Chart | Yes | Bars stacked by series |
groupedBar | Grouped Bar Chart | Yes | Side-by-side bars grouped by label |
line | Line Chart | No | Single line chart |
multiLine | Multi-Line Chart | Yes | Multiple lines, one per series |
area | Area Chart | No | Filled area under a line |
sparkline | Sparkline | No | Compact inline chart (no axes) |
gauge | Gauge Chart | No | Single-value gauge/meter (1 data point) |
Chart Type Validation
Each chart type enforces data requirements at construction time:
// 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:
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:
| Index | Color | Hex |
|---|---|---|
| 0 | Blue | #2196F3 |
| 1 | Green | #4CAF50 |
| 2 | Orange | #FF9800 |
| 3 | Red | #F44336 |
| 4 | Purple | #9C27B0 |
| 5 | Teal | #00BCD4 |
| 6 | Pink | #E91E63 |
| 7 | Amber | #FFC107 |
| 8 | Brown | #795548 |
| 9 | Blue Grey | #607D8B |
Custom Colors
You can override colors by series name or by index:
// 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:
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:
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:
| ChartType | Renderer Class |
|---|---|
pie | PieChartRenderer |
donut | DonutChartRenderer |
bar | BarChartRenderer |
stackedBar | StackedBarChartRenderer |
groupedBar | GroupedBarChartRenderer |
line | LineChartRenderer |
multiLine | MultiLineChartRenderer |
area | AreaChartRenderer |
sparkline | SparklineChartRenderer |
gauge | GaugeChartRenderer |
Using the Factory
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():
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.
LMS Example: Enrollment Trends Line Chart
// 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
// 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:
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
- Data Sources -- Connect charts to live API data
- Custom Chart Component Example -- Build a custom chart with entity integration
- Data Binding Guide -- Field mapping and polling