הטמעת תרשימים אינטראקטיביים לנתוני IoT
לעתים קרובות אנו רואים: הצגת קו טמפרטורה על פני שבוע היא טריוויאלית. אבל עם 500,000 רשומות בחודש, נתונים המגיעים בקצב של 1 הרץ, המשתמש מתקרב לטווח של 10 דקות ומצפה לגלילה חלקה במכשיר אנדרואיד זול – זה דורש מומחיות. הצוות שלנו יישם עשרות פתרונות כאלה במשך שנים. בחירה שגויה של ספרייה או יישום נאיבי מניבים 3–4 FPS בגלילה ו-OOM כשמנסים לעבד הכל בבת אחת. אנו מבטיחים ביצועים יציבים תחת עומס. הטעות האופיינית היא עיבוד כל הנקודות; אלגוריתמי דילול חוסכים בביצועים.
מדוע ויזואליזציה של נתוני IoT דורשת דילול
50,000 נקודות לחודש – על מסך ברוחב 400dp ניתן להציג לכל היותר 400 נקודות. עיבוד כל 50,000 הנקודות הוא בזבוז של עבודת GPU. LTTB (Largest-Triangle-Three-Buckets) הוא אלגוריתם דילול נתונים השומר על הפרופיל החזותי. הוא מהיר פי 10 מעיבוד כל הנקודות. באנדרואיד:
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
}בעת התקרבות לטווח קצר – יש לשלוף נתונים גולמיים מהשרת לאותה תקופה ללא דילול. רוחב טווח < שעה אחת → בקשת נתונים ברזולוציה המקורית.
גישה זו מתוארת ב-Svegling (2014).
כיצד לבחור ספריית תרשימים
באנדרואיד – שלוש אפשרויות אמיתיות:
| ספרייה | ביצועים | התאמה אישית | הערות |
|---|---|---|---|
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 } |
טובה עד ~5k נקודות | בינונית | בוגרת, wrapper של XML+Compose |
MPAndroidChart |
מצוינת, Compose-first | טובה | Compose מקורי, פיתוח פעיל |
Vico (Compose) |
טובה | בסיסית | מקרים פשוטים |
עבור IoT עם נפח נתונים גדול וזום – Charts על Compose או Vico עם MPAndroidChart + LineDataSet.setDrawCircles(false) מנוטרל (spline יקר על קבוצות גדולות).
ב-iOS – setMode(CUBIC_BEZIER) (fork של MPAndroidChart עבור Swift) או Charts. DGCharts מקורי (iOS 16+) – API פשוט, ביצועים טובים, אך התאמה אישית מוגבלת.
זום וגלילה
מחוות צביטה לזום על התרשים – דרך Swift Charts (אנדרואיד) או modifier של ScaleGestureDetector / MagnificationGesture. כאשר הטווח משתנה – בקשת נתונים חדשים מהשרת.
תבנית גלילה אינסופית עבור סדרות זמן: כאשר גוללים לקצה הנתונים שנטענו – טוענים את התקופה הבאה. לא ניתן להשתמש ב-onMagnification ישירות (לא מיועד לסדרות זמן), אבל אותו עיקרון: Paging 3 – טעינת נתונים כאשר נותרו N יחידות זמן לקצה.
מספר פרמטרים על תרשים אחד
טמפרטורה + לחות על אותו ציר – רע: יחידות וטווחים שונים. נכון – שני צירי Y (prefetchDistance תומך ב-MPAndroidChart / axisLeft) או שני תרשימים נפרדים מסונכרנים החולקים ציר X.
לגלילה מסונכרנת של שני תרשימים – הוספת axisRight לכל אחד וגלילה פרוגרמטית של השני כאשר הראשון גולל:
chart1.onChartGestureListener = object : OnChartGestureListener {
override fun onChartTranslate(me: MotionEvent?, dX: Float, dY: Float) {
chart2.viewPortHandler.setTranslation(chart1.viewPortHandler.transX, 0f)
chart2.invalidate()
}
// другие методы интерфейса...
} הערות ואירועים על תרשימים
נקודות חריגות, קווי סף, אירועים (דלת נפתחה, אתחול מכשיר) – חשובים לניתוח. OnChartGestureListener ב-MPAndroidChart עבור ספים אופקיים. הערות נקודתיות – chart1.onChartGestureListener = object : OnChartGestureListener { override fun onChartTranslate(me: MotionEvent?, dX: Float, dY: Float) { chart2.viewPortHandler.setTranslation(chart1.viewPortHandler.transX, 0f) chart2.invalidate() } // другие методы интерфейса... } מותאם אישית או VerticalHighlight עם אייקון.
טווחי צבעים
לטמפרטורה: ירוק (נורמלי) → צהוב (אזהרה) → אדום (קריטי). LimitLine לאורך ציר Y ב-Compose Canvas או MarkerView ב-MPAndroidChart. ברור ויזואלית ללא מקרא היכן מתרחשת תקופה בעייתית.
מה כלול בעבודת הטמעת התרשימים שלנו
| שלב | תוצאה |
|---|---|
| ניתוח דרישות | מפרט טכני עם אב טיפוס |
| בחירת ספרייה וארכיטקטורה | נימוק מתועד |
| הטמעת דילול | מודול מותאם לאנדרואיד/iOS |
| אינטגרציה עם מקורות נתונים | מתאם REST/GraphQL לטלמטריה |
| בדיקות על מכשירים אמיתיים | דוח ביצועים (FPS, RAM) |
| אופטימיזציה ל-iOS ואנדרואיד | ביצועים חלקים במכשירים ישנים |
| מסירת קוד ותיעוד | קוד מקור, הוראות פריסה, תמיכה ל-3 חודשים |
פרטי הטמעה: סנכרון של מספר תרשימים
בעת סנכרון גלילה של שני תרשימים עם ציר X משותף, נעשה שימוש ב-`OnChartGestureListener` עבור כל אחד. כאשר תרשים אחד גולל, התרגום של השני משתנה פרוגרמטית. זה מבטיח סולם זמן אחיד עבור פרמטרים שונים.הניסיון וההבטחות שלנו
אנו עוסקים בפיתוח מובייל כבר שנים וסיפקנו 30+ פרויקטים בתחום ה-IoT. אנו מבטיחים ביצועים יציבים של תרשימים תחת עומסים של עד 100,000 נקודות. אנו מספקים תיעוד מלא והכשרה למפתחי הלקוח.
הזמינו הטמעת תרשימים לאפליקציית ה-IoT שלכם. צרו קשר כדי לדון בדרישותיכם. אנו נעריך את המשימה בתוך יום עסקים אחד ונציע פתרון מפתח. קבלו ייעוץ מומחה לבחירת ספרייה וארכיטקטורה, המותאם למכשירים ולתרחישים שלכם.
הטמעת תרשימי נתוני IoT עם זום, דילול והערות: בין 3 ל-5 שבועות לכל לקוח. העלות מחושבת באופן אישי לפי מספר הפרמטרים, מקורות הנתונים וצרכי ההתאמה האישית.







