Реализация генерации графиков и диаграмм в мобильном приложении
Графики в мобильном приложении — это не просто визуализация. Это касания, зум, анимации при появлении данных, корректное поведение при изменении ориентации экрана и поддержка 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 недели. Стоимость рассчитывается индивидуально.







