Реализация интерактивных графиков для IoT-мониторинга
Мы часто видим: отобразить линию температуры за неделю просто. Но когда данных 500 000 записей за месяц, данные приходят с частотой 1 Гц, пользователь зумирует до диапазона в 10 минут, и график должен прокручиваться плавно на бюджетном Android — нужен опытный подход. Наша команда за 5 лет реализовала десятки подобных решений. Неправильный выбор библиотеки или наивная реализация даёт 3–4 FPS на скролл и OOM при попытке отрендерить всё сразу. Мы гарантируем стабильную работу под нагрузкой. Типичная ошибка — отрисовывать все точки; алгоритмы прореживания спасают производительность.
Почему визуализация IoT-данных требует downsampling?
50 000 точек за месяц — на экране шириной 400dp поместится максимум 400 точек. Отображать все 50 000 — пустая работа GPU. LTTB (Largest-Triangle-Three-Buckets) — алгоритм прореживания данных с сохранением визуального профиля. Он быстрее отрисовки всех точек в 10 раз. На Android:
fun lttbDownsample(data: List<DataPoint>, threshold: Int): List<DataPoint> {
if (data.size <= threshold) return data
val sampled = mutableListOf<DataPoint>()
sampled.add(data.first())
val bucketSize = (data.size - 2).toDouble() / (threshold - 2)
var a = 0
for (i in 0 until threshold - 2) {
val bucketStart = ((i + 1) * bucketSize).toInt() + 1
val bucketEnd = minOf(((i + 2) * bucketSize).toInt() + 1, data.size - 1)
val nextA = bucketStart until bucketEnd
val avgX = nextA.sumOf { data[it].x } / nextA.count()
val avgY = nextA.sumOf { data[it].y } / nextA.count()
var maxArea = -1.0
var maxPoint = bucketStart
for (j in bucketStart until bucketEnd) {
val area = Math.abs(
(data[a].x - avgX) * (data[j].y - data[a].y) - (data[a].x - data[j].x) * (avgY - data[a].y)
) * 0.5
if (area > maxArea) {
maxArea = area
maxPoint = j
}
}
sampled.add(data[maxPoint])
a = maxPoint
}
sampled.add(data.last())
return sampled
}При зуме до короткого диапазона — загружать исходные данные с сервера для этого периода без downsampling. Ширина диапазона < 1 часа → запрос данных с оригинальным разрешением.
Этот подход описан в работе Свеглинга (2014).
Как выбрать библиотеку для графика?
На Android — три реальных варианта:
| Библиотека | Производительность | Кастомизация | Особенности |
|---|---|---|---|
MPAndroidChart |
Хорошая до ~5к точек | Средняя | Mature, XML+Compose wrapper |
Vico |
Отличная, Compose-first | Хорошая | Нативный Compose, активная разработка |
Charts (Compose) |
Хорошая | Базовая | Простые кейсы |
Для IoT с большим объёмом данных и зумом — Vico на Compose или MPAndroidChart с LineDataSet.setDrawCircles(false) + setMode(CUBIC_BEZIER) выключенным (сплайн = дорого на больших наборах).
На iOS — Charts (fork MPAndroidChart для Swift) или DGCharts. Нативный Swift Charts (iOS 16+) — простой API, хорошая производительность, но ограниченная кастомизация.
Зум и скролл
Жест pinch-to-zoom для графика — через ScaleGestureDetector (Android) или MagnificationGesture / onMagnification modifier. При изменении диапазона — запрос новых данных с сервера.
Паттерн «бесконечный скролл» для временных серий: при скролле к краю загруженных данных — подгрузить следующий период. Paging 3 для этого использовать нельзя напрямую (он не для временных серий), но принцип тот же: prefetchDistance — загружать данные когда до края осталось N единиц времени.
Несколько параметров на одном графике
Температура + влажность на одной оси — плохо: разные единицы и диапазоны. Правильно — две оси Y (MPAndroidChart поддерживает через axisLeft / axisRight) или два отдельных синхронизированных графика с общей осью X.
При синхронизированном скролле двух графиков — добавить OnChartGestureListener к каждому и при скролле одного программно скроллить второй:
chart1.onChartGestureListener = object : OnChartGestureListener {
override fun onChartTranslate(me: MotionEvent?, dX: Float, dY: Float) {
chart2.viewPortHandler.setTranslation(chart1.viewPortHandler.transX, 0f)
chart2.invalidate()
}
// другие методы интерфейса...
} Аннотации и события на графике
Точки аномалий, пороговые линии, события (открытие двери, перезагрузка устройства) — важны для анализа. LimitLine в MPAndroidChart для горизонтальных порогов. Точечные аннотации — кастомный MarkerView или VerticalHighlight с иконкой.
Цветовые диапазоны
Для температуры: зелёный (норма) → жёлтый (предупреждение) → красный (критично). LinearGradient по Y-оси в Compose Canvas или GradientColor в MPAndroidChart. Визуально сразу понятно без легенды, где проблемный период.
Что входит в нашу работу по реализации графиков
| Этап | Результат |
|---|---|
| Анализ требований | Техническое задание с прототипом |
| Выбор библиотеки и архитектуры | Документация с обоснованием |
| Реализация downsampling | Оптимизированный модуль для Android/iOS |
| Интеграция с источниками данных | REST/GraphQL адаптер для телеметрии |
| Тестирование на реальных устройствах | Отчёт по производительности (FPS, RAM) |
| Оптимизация под iOS и Android | Плавная работа на устройствах предыдущих поколений |
| Поставка кода и документации | Исходники, инструкция по деплою, поддержка 3 месяца |
Детали реализации: синхронизация нескольких графиков
При синхронизированном скролле двух графиков с общей осью X используется `OnChartGestureListener` для каждого. При скролле одного программно меняется translation второго. Это обеспечивает единую временную шкалу для разных параметров.Наш опыт и гарантии
Мы занимаемся мобильной разработкой более 5 лет, реализовали 30+ проектов в сфере IoT. Гарантируем стабильную работу графиков под нагрузкой до 100 000 точек. Предоставляем полную документацию и обучение разработчиков заказчика.
Закажите реализацию графиков для вашего IoT-приложения. Свяжитесь с нами, чтобы обсудить требования. Оценим задачу за один рабочий день и предложим решение под ключ. Получите консультацию по выбору библиотеки и архитектуры, адаптированную под ваши устройства и сценарии.
Реализация графиков IoT-данных с зумом, downsampling и аннотациями: от 3 до 5 недель на один клиент. Стоимость рассчитывается индивидуально в зависимости от числа параметров, источников данных и требований к кастомизации.







