יישום תרשימים באפליקציות מובייל: ספריות ואופטימיזציה
תרשימי קו שגמגמו ב-1000 נקודות נתונים? מגע לא מגיב, אנימציות רועדות—כאב מוכר. 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; } לא מפגר כי הוא מותאם כבר שנים, אבל 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()), ], ), ); }, ) מותאם אישית ללא שמירת נקודות ואזורי חיתוך מתחיל לגמגם כבר מהאלף נקודות הראשונות. ללא גישה נכונה, תרשימים הופכים למקור באגים ותלונות משתמשים. הניסיון שלנו—10+ שנים בפיתוח מובייל, מעל 50 פרויקטים עם תרשימים—מאפשר לנו להימנע מהמלכודות האלה. חסכו עד 70% מזמן האופטימיזציה. צרו קשר כדי לדון בפרויקט שלכם.
למה בחירת ספרייה היא החלטה מפתח?
השאלה "באיזו ספרייה להשתמש" נפתרת לפי דרישות הפרויקט הספציפיות. בחירה שגויה מובילה לעבודה חוזרת וביצועים גרועים.
| קריטריונים | fl_chart | syncfusion_flutter | MPAndroidChart | DGCharts (iOS) | react-native-chart-kit |
|---|---|---|---|---|---|
| סוגי תרשימים | קו, עמודות, עוגה, פיזור, רדאר | 30+ סוגים | 8 סוגים בסיסיים | 10+ סוגים | קו, עמודות, עוגה, התקדמות |
| זום/גלילה | לא (דורש התאמה אישית) | כן | כן | כן | לא |
| תרשים נרות | לא | כן | כן | כן | לא |
| רישיון | MIT | מסחרי | Apache 2.0 | Apache 2.0 | MIT |
| ביצועים מעל 1000 נקודות | מתדרדרים | טובים | טובים | טובים | בינוניים |
לאפליקציות פיננסיות עם תרשים נרות וזום—בחרו Syncfusion או ספרייה מקורית. לאפליקציות בריאות/כושר עם תרשימי קו פשוטים—fl_chart מספיק. ל-React Native, השתמשו ב-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; } עם תרשימים פשוטים. לנתונים בזמן אמת בתדירות גבוהה—WebView + ECharts.
מה זה Downsampling ולמה זה קריטי?
Downsampling מפחית את מספר נקודות הנתונים לפני העיבוד. אלגוריתם LTTB שומר על הצורה החזותית תוך השלכת נקודות מיותרות. זה נותן חיסכון של עד 90% בזמן בהשוואה לעיבוד מלא. ללא downsampling, תרשימים עם 10,000 נקודות במכשירי Android בינוניים יורדים ל-15 FPS.
איך לייעל LineChart בלי לאבד איכות?
טעות אופיינית היא העברת כל המערך של 5000 נקודות ל-fl_chart. ה-LineChart מעבד כל נקודה כ-FlSpot, ועם נתוני מגע מופעלים, הוא מחשב בדיקת פגיעה לכל אחת. במכשירי Android בינוניים, FPS יורד מתחת ל-20.
גישה נכונה: הפעילו downsampling לפני העיבוד. LTTB מהיר פי 10 מעיבוד מלא ונותן תוצאות זהות ויזואלית ברוב המקרים.
דוגמת יישום LTTB ב-Flutter
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. LTTB מפחית את זמן העיבוד פי 10 בהשוואה למערך נתונים מלא.
השוואת שיטות Downsampling
| שיטה | מהירות | שמירת צורה | מקרה שימוש |
|---|---|---|---|
| LTTB | בינונית | מצוינת | תרשימי קו |
| Douglas-Peucker | גבוהה | טובה | מצולעים, נתונים גיאוגרפיים |
| אקראי | גבוהה מאוד | חלשה | אב-טיפוס מהיר |
לתרשימים במובייל, LTTB הוא האיזון האופטימלי.
מדריך שילוב Downsampling ב-3 שלבים
- בחרו אלגוריתם LTTB—יחס המהירות-איכות הטוב ביותר לתרשימי קו.
-
הכינו נתונים—סננו null ו-NaN, המירו למערך
FlSpot. -
שלבו ב-LineChart—החליפו את מערך המקור בתוצאת
lttbDownsampleלפני העברה ל-LineChartData.
אנימציות כניסת נתונים
אנימציית ציור קו משמאל לימין היא דפוס 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(),
),
],
),
);
},
)לתרשימי עמודות, הנפישו את גידול העמודות באמצעות פרמטר fromY עם AnimatedContainer.
עדכונים בזמן אמת ללא אובדן FPS
תרשימים שמתעדכנים כל שנייה (מדדים, נתוני מניות) דורשים קריאה ל-setState רק עבור נתונים שהשתנו. הדרך הלא נכונה: קריאה ל-setState על כל המסך—בונה מחדש את כל עץ הווידג'טים. הדרך הנכונה: השתמשו ב-ValueNotifier + ValueListenableBuilder רק סביב ווידג'ט התרשים.
- הגדירו מספר מקסימלי של נקודות מוצגות (חלון נע).
- השתמשו ב-
ValueNotifierכדי להחזיק את רשימת הנקודות. - עדכנו את ה-notifier כשמגיעים נתונים חדשים.
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;
} הפיכת תרשימים לנגישים
תרשימים בלתי נראים ל-VoiceOver/TalkBack ללא עבודה נוספת. מינימום: עטפו את התרשים בווידג'ט Semantics שמתאר מה הוא מציג. לאפליקציות פיננסיות או רפואיות, ספקו טבלה חלופית הניתנת להפעלה. זה עומד ב-WCAG 2.1 ובהנחיות סקירת App Store (סעיף 5.1).
מה כלול בעבודה שלנו
- בחירת ספרייה מוצדקת לפי דרישות הפרויקט
- יישום סוגי תרשימים נדרשים (קו, עמודות, עוגה, נרות וכו')
- אופטימיזציית ביצועים (downsampling, עיבוד)
- אנימציות כניסת נתונים
- אינטראקטיביות: זום, גלילה, tooltip במגע
- תמיכה בנגישות
- אינטגרציה עם מקורות נתונים בזמן אמת
לוח זמנים ועלות
סוג תרשים אחד עם אינטראקטיביות בסיסית: 2–3 ימים. סט של 3–5 סוגים עם אנימציות ועדכונים בזמן אמת: 1–2 שבועות. העלות נקבעת באופן אישי—צרו קשר להערכת פרויקט. חסכו עד 50% מזמן הפיתוח באמצעות downsampling. הזמינו פתרון מפתח ביד: קבלו ייעוץ על בחירת ספריות ואופטימיזציה. המהנדסים שלנו יעזרו לכם לחסוך זמן ותקציב.







