Реалізація графіків та діаграм у мобільному приложенні
Графіки в мобільному приложенні — це не просто візуалізація. Це торкання, zoom, анімації при появі даних, коректна поведінка при зміні орієнтації екрана та підтримка Accessibility. UITableView не лагає, тому що він оптимізований роками — самописний LineChart без кешу точок та clip region лагає відразу.
Вибір бібліотеки: критерії, не маркетинг
Питання «яку бібліотеку взяти» вирішується через вимоги конкретного проекту:
| Критерій | fl_chart | syncfusion_flutter | MPAndroidChart | DGCharts (iOS) |
|---|---|---|---|---|
| Типи графіків | Line, Bar, Pie, Scatter, Radar | 30+ типів | 8 основних | 10+ типів |
| Zoom/Pan | Ні (потрібен кастом) | Так | Так | Так |
| Candlestick | Ні | Так | Так | Так |
| Ліцензія | MIT | Комерційна | Apache 2.0 | Apache 2.0 |
| Продуктивність при >1000 точок | Снижається | Добра | Добра | Добра |
Для фінансових приложень з candlestick та zoom — syncfusion або нативна. Для health/fitness з простими лінійними графіками — fl_chart достатньо. Для real-time даних з високою частотою оновлення — WebView + ECharts.
LineChart із реальними даними: підводні камені
Типова помилка — передавати в fl_chart весь масив із 5000 точок. LineChart рендерить кожну точку як FlSpot, і при touchData включеному — просчитує hit-testing для кожної. На mid-range Android — FPS падає нижче 20.
Правильно: downsampling перед рендерингом. Алгоритм LTTB (Largest-Triangle-Three-Buckets) зберігає візуальну форму кривої при зменшенні кількості точок:
List<FlSpot> lttbDownsample(List<FlSpot> data, int threshold) {
if (data.length <= threshold) return data;
final sampled = <FlSpot>[data.first];
final bucketSize = (data.length - 2) / (threshold - 2);
var a = 0;
for (var i = 0; i < threshold - 2; i++) {
final rangeStart = (i * bucketSize + 1).floor();
final rangeEnd = ((i + 1) * bucketSize + 1).floor().clamp(0, data.length);
final nextA = ((i + 1) * bucketSize + 1).floor().clamp(0, data.length - 1);
// Знаходимо точку з максимальною площею трикутника
double maxArea = -1;
int maxAreaPoint = rangeStart;
for (var j = rangeStart; j < rangeEnd; j++) {
final area = (data[a].x * (data[nextA].y - data[j].y) +
data[nextA].x * (data[j].y - data[a].y) +
data[j].x * (data[a].y - data[nextA].y))
.abs() / 2;
if (area > maxArea) {
maxArea = area;
maxAreaPoint = j;
}
}
sampled.add(data[maxAreaPoint]);
a = maxAreaPoint;
}
sampled.add(data.last);
return sampled;
}
500 точок замість 5000 — FPS повертається до 60.
Анімації при появі даних
Анімація лінії, «рисуючої» зліва направо — популярний UX-паттерн. На Flutter через AnimationController + TweenAnimationBuilder, обмежуючи кількість відображаних точок через t:
TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: const Duration(milliseconds: 800),
curve: Curves.easeOut,
builder: (context, value, _) {
final visiblePoints = (allSpots.length * value).round();
return LineChart(
LineChartData(
lineBarsData: [
LineChartBarData(spots: allSpots.take(visiblePoints).toList()),
],
),
);
},
)
Для bar chart — анімація росту столбців через fromY параметр та AnimatedContainer.
Real-time оновлення
Графіки з оновленням кожну секунду (метрики, біржові дані) вимагають setState тільки для змінених даних. Неправильно: викликати setState на весь екран — перебудовується все дерево віджетів. Правильно: ValueNotifier + ValueListenableBuilder навколо тільки віджета графіка:
final chartData = ValueNotifier<List<FlSpot>>([]);
// У віджеті:
ValueListenableBuilder<List<FlSpot>>(
valueListenable: chartData,
builder: (_, spots, __) => LineChart(LineChartData(
lineBarsData: [LineChartBarData(spots: spots)],
)),
)
// При отриманні нової точки:
void addDataPoint(double x, double y) {
final current = [...chartData.value, FlSpot(x, y)];
if (current.length > maxPoints) current.removeAt(0); // sliding window
chartData.value = current;
}
Accessibility
Графіки недоступні для VoiceOver / TalkBack без додаткової роботи. Мінімум: Semantics віджет з описом того, що показує графік. Для фінансових або медичних приложень — обов'язкова таблична альтернатива даним графіка, переключувана через кнопку.
Що входить у роботу
- Вибір бібліотеки під вимоги проекту з обґрунтуванням
- Реалізація потрібних типів графіків (line, bar, pie, candlestick тощо)
- Оптимізація продуктивності (downsampling, рендеринг)
- Анімації при завантаженні даних
- Інтерактивність: zoom, pan, tooltip при касанні
- Accessibility підтримка
Строки
Один тип графіка з базовою інтерактивністю: 2–3 дні. Набір з 3–5 типів з анімаціями та real-time оновленнями: 1–2 тижні. Вартість розраховується індивідуально.







