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







