Реалізація інтерактивних графіків для 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 тижнів на один клієнт. Вартість розраховується індивідуально залежно від кількості параметрів, джерел даних та вимог до кастомізації.







