פיתוח ויזואליזציה של גרף רשת באתר
שימו לב: כאשר מספר התלויות בארכיטקטורת מיקרוסרוויסים עולה על מאה, רשימות סטנדרטיות מפסיקות לעבוד. ויזואליזציה של גרפי קשרים (Network Graph) היא הדרך היחידה לראות אילו שירותים תלויים במסד נתונים מיושן או היכן נוצרים מעגלים. בפועל, גרף מיקרוסרוויסים מכיל לעיתים קרובות 10,000+ צמתים — ללא פריסה מונחית כוח (force-directed layout) ו-WebGL, העיבוד היה תולה את הדפדפן. הבעיה מחמירה כאשר הנתונים מתעדכנים דינמית: כל בקשת API בונה מחדש את הגרף, וללא אופטימיזציה, האינטראקטיביות הופכת לבלתי אפשרית. בפרויקט לקוח אחד, צמצמנו את זמן טעינת הגרף מ-12 ל-2 שניות באמצעות וירטואליזציה של צמתים ועיבוד LOD, וחסכנו ללקוח $1.8k–2.6k בחודש באיתור תקלות. ללא גישות כאלה, הגרף נשאר בלתי קריא וחסר תועלת לניתוח.
בחירת אלגוריתם הפריסה הנכון היא החלטה קריטית הקובעת את חוויית המשתמש. פריסה מונחית כוח מספקת קיבוץ טבעי, המאפשר זיהוי מהיר של צמתים מבודדים וקבוצות צפופות. כתוצאה מכך, הצוות יכול למצוא במהירות תלויות מעגליות ולמנוע כשלים מדורגים.
מדוע פריסה מונחית כוח היא הבסיס ל-Network Graph?
פריסה מונחית כוח מדמה פיזית צמתים: הם דוחים זה את זה, וקישורים מושכים. התוצאה היא סידור טבעי שבו צמתים מחוברים קרובים וצמתים לא מחוברים רחוקים. זה המפתח לקריאות. ללא אלגוריתם כזה, הגרף הופך לספגטי כאוטי.
באיזו ספרייה לבחור לויזואליזציה של גרפים?
| ספרייה | ביצועים (צמתים) | מורכבות | מתי לבחור |
|---|---|---|---|
| D3 Force (מומלץ) | עד 10k | גבוהה | כשצריך שליטה מלאה, עיבודים מותאמים אישית |
| react-force-graph | עד 5k (2D/3D) | בינונית | פרויקט React, התחלה מהירה |
| Sigma.js | 50k+ | בינונית | גרפים גדולים עם WebGL |
| Cytoscape.js | עד 10k | נמוכה | נתונים ביולוגיים/רפואיים |
D3 Force לעומת Sigma.js: ניתוח השוואתי
Sigma.js מעבד 50k צמתים פי 5 מהר יותר מ-D3 Force בזכות WebGL. לפרויקטים עם >10k צמתים, Sigma.js הוא הבחירה האופטימלית, אם כי D3 Force מציע גמישות רבה יותר בהתאמה אישית. התיעוד הרשמי של Sigma.js מאשר שהספרייה מעבדת עד 100k צמתים על מחשב נייד רגיל.
השוואה בין עיבוד 2D ו-3D
| פרמטר | 2D | 3D |
|---|---|---|
| ביצועים | גבוהים יותר (פחות חישובים) | נמוכים יותר (קואורדינטת Z) |
| אינטואיטיביות | תצוגה מוכרת | דורש שליטה במסלול |
| יישום | תלויות שירות, רשתות חברתיות | נתונים גיאוגרפיים, מבנים מולקולריים |
הבחירה בין 2D ל-3D תלויה במשימה. לויזואליזציה של תלויות שירות, 2D בדרך כלל מספיק. 3D נחוץ לנתונים גיאוגרפיים או מבנים מולקולריים. שיטת הוויזואליזציה נקבעת על ידי סוג הנתונים ודרישות האינטראקטיביות.
איך אנחנו עושים את זה: מחסנית טכנולוגית ומקרה בוחן
לויזואליזציה של תלויות מיקרוסרוויסים, אנו משתמשים בגרף JS המבוסס על react-force-graph. אנו מעדיפים react-force-graph — הוא מספק קומפוננטת React מוכנה עם האצת WebGL. לפרויקט גדול מהפרקטיקה שלנו (5000+ צמתים), השתמשנו ב-Sigma.js: נתונים גולמיים נטענו דרך graphology, פריסה הוגדרה באמצעות force-atlas2.
בפועל, אנו משתמשים לעיתים קרובות בגישה היברידית: בשלב החקירה, אנו בונים אב טיפוס על D3 Force, ואז מעבירים ל-Sigma.js או react-force-graph לייצור. זה מאפשר אימות מהיר של השערות ללא אופטימיזציה עמוקה. ללקוח פינטק, פיתחנו גרף ניטור עסקאות שבו כל צומת הוא חשבון וקישורים הם העברות. הגרף התעדכן בזמן אמת ועמוד בעד 1000 שינויים בשנייה. לקוח אחר צמצם את זמן חיפוש התלויות מ-4 שעות ל-15 דקות, וחסך מעל $1.4k–1.9k בחודש. אם אתם רוצים להעריך אם המחסנית שלכם מתאימה ל-Network Graph, צרו קשר לבדיקה.
דוגמה לגרף בסיסי עם react-force-graph
npm install react-force-graph-2d import ForceGraph2D from 'react-force-graph-2d';
import { useCallback, useRef, useState } from 'react';
interface Node {
id: string;
name: string;
type: 'service' | 'database' | 'external';
group: number;
}
interface Link {
source: string;
target: string;
label?: string;
weight?: number;
}
function ServiceDependencyGraph({ nodes, links }) {
const graphRef = useRef();
const [selectedNode, setSelectedNode] = useState(null);
const [hoveredNode, setHoveredNode] = useState(null);
// Подсветка связей выбранного узла
const highlightedLinks = useMemo(() => {
if (!selectedNode) return new Set();
return new Set(
links
.filter(l => l.source === selectedNode.id || l.target === selectedNode.id)
.map(l => `${l.source}-${l.target}`)
);
}, [selectedNode, links]);
const nodeColor = useCallback((node) => {
const colors = {
service: '#3b82f6',
database: '#22c55e',
external: '#f59e0b'
};
if (selectedNode && node.id !== selectedNode.id) {
const isRelated = links.some(l =>
(l.source === selectedNode.id && l.target === node.id) ||
(l.target === selectedNode.id && l.source === node.id)
);
return isRelated ? colors[node.type] : '#d1d5db';
}
return colors[node.type] ?? '#6b7280';
}, [selectedNode, links]);
const nodeCanvasObject = useCallback((node, ctx, globalScale) => {
const label = node.name;
const fontSize = 12 / globalScale;
const r = 6;
// Узел
ctx.beginPath();
ctx.arc(node.x, node.y, r, 0, 2 * Math.PI);
ctx.fillStyle = nodeColor(node);
ctx.fill();
if (selectedNode?.id === node.id) {
ctx.strokeStyle = '#1d4ed8';
ctx.lineWidth = 2 / globalScale;
ctx.stroke();
}
// Метка
ctx.font = `${fontSize}px Sans-Serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = '#374151';
ctx.fillText(label, node.x, node.y + r + fontSize);
}, [nodeColor, selectedNode]);
const linkColor = useCallback((link) => {
const key = `${link.source.id ?? link.source}-${link.target.id ?? link.target}`;
return highlightedLinks.has(key) ? '#3b82f6' : '#e5e7eb';
}, [highlightedLinks]);
return (
<div className="relative border border-gray-200 rounded-xl overflow-hidden" style={{ height: 600 }}>
<ForceGraph2D
ref={graphRef}
graphData={{ nodes, links }}
nodeId="id"
nodeLabel="name"
nodeCanvasObject={nodeCanvasObject}
nodeCanvasObjectMode={() => 'replace'}
linkColor={linkColor}
linkWidth={link => (link.weight ?? 1) * 0.5}
linkDirectionalArrowLength={3}
linkDirectionalArrowRelPos={1}
linkLabel="label"
onNodeClick={(node) => {
setSelectedNode(prev => prev?.id === node.id ? null : node);
}}
onNodeHover={setHoveredNode}
onBackgroundClick={() => setSelectedNode(null)}
d3AlphaDecay={0.02}
d3VelocityDecay={0.3}
warmupTicks={50}
cooldownTicks={200}
/>
{/* Инфопанель */}
{selectedNode && (
<div className="absolute top-4 right-4 bg-white border rounded-lg shadow-lg p-4 w-64">
<h3 className="font-semibold text-gray-800">{selectedNode.name}</h3>
<p className="text-sm text-gray-500 capitalize">{selectedNode.type}</p>
<div className="mt-3">
<p className="text-xs text-gray-500">Входящие связи:</p>
<ul className="text-sm">
{links
.filter(l => (l.target.id ?? l.target) === selectedNode.id)
.map(l => <li key={l.source.id ?? l.source}>← {l.source.name ?? l.source}</li>)}
</ul>
</div>
</div>
)}
{/* Легенда */}
<div className="absolute bottom-4 left-4 bg-white/90 rounded-lg p-3 text-xs">
<div className="flex items-center gap-2 mb-1">
<div className="w-3 h-3 rounded-full bg-blue-500" />
Сервис
</div>
<div className="flex items-center gap-2 mb-1">
<div className="w-3 h-3 rounded-full bg-green-500" />
База данных
</div>
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-yellow-500" />
Внешний API
</div>
</div>
</div>
);
} גרף תלת-ממדי
import ForceGraph3D from 'react-force-graph-3d';
function Graph3D({ data }) {
return (
<ForceGraph3D
graphData={data}
nodeAutoColorBy="group"
nodeLabel="name"
linkDirectionalParticles={2}
linkDirectionalParticleSpeed={0.006}
backgroundColor="#0f172a"
/>
);
}
גרפים גדולים עם Sigma.js
npm install sigma graphology @react-sigma/core Sigma.js מעבד דרך WebGL — מתמודד עם 50k+ צמתים. השתמשו ב-Sigma.js אם נדרש ביצועים מקסימליים.
import { SigmaContainer, useLoadGraph, useRegisterEvents } from '@react-sigma/core';
import Graph from 'graphology';
function LargeGraph({ graphData }) {
return (
<SigmaContainer style={{ height: 600 }} settings={{ nodeProgramClasses: {} }}>
<GraphLoader data={graphData} />
</SigmaContainer>
);
} תהליך העבודה
- אנליטיקה — חקר מקורות נתונים לויזואליזציה של גרפים, הבהרת דרישות אינטראקטיביות (זום, הדגשה, פילטרים).
- עיצוב — בחירת פריסה (מונחית כוח, מעגלית, היררכית), הגדרת ערכת צבעים וגדלים.
- פיתוח — יישום קומפוננטה, אופטימיזציה של עיבוד (וירטואליזציה של צמתים, LOD).
- בדיקות — בדיקה על נתונים אמיתיים, מדידת FPS, תיקון באגים.
- פריסה — שילוב באתר, הגדרת CDN לנכסים כבדים.
לוחות זמנים ועלות
לוחות זמנים: מ-1.5 שבועות (גרף בסיסי) עד חודש (פתרון מקיף עם Sigma.js + 3D). העלות מחושבת באופן אישי לאחר בדיקת נתונים. פיתוח גרף בסיסי מתחיל מ-$1.4k–1.9k ($1,800). תקציב פרויקט טיפוסי: מ-$2,000 עד $10,000 בהתאם למורכבות ונפח הנתונים. כפי שהפרקטיקה מראה, הגרף מחזיר את עצמו תוך חודש מיישום על ידי צמצום זמן איתור השגיאות. לדוגמה, לקוח אחד צמצם את זמן חיפוש התלויות מ-4 שעות ל-15 דקות, וחסך מעל $1.4k–1.9k בחודש. אנו מבטיחים פעולה יציבה במכשירים ניידים ודפדפנים ישנים. הזמינו בדיקה — נקבע את הפתרון האופטימלי לנתונים שלכם.
מה כלול בעבודה
- התאמת הגרף למחסנית שלכם (React, Vue, Angular)
- קוד מקור כחבילת npm או קומפוננטה
- תיעוד: הפניה ל-API ומדריך התאמה אישית
- גישה: מאגר קוד מקור (GitHub)
- הדרכה: מפגש מקוון של שעה לצוות שלכם
- תמיכה: שבועיים של תמיכה טכנית כולל תיקוני באגים וייעוץ
- אינטגרציית בדיקה עם הנתונים שלכם
טעויות נפוצות בפיתוח Network Graph
- התעלמות מביצועים: ללא WebGL, הגרף מפגר ב-1000 צמתים.
- אין חסימת אירועים (throttling): כל ריחוף מעל אלמנט גורם לציור מחדש.
- אזורי לחיצה קטנים מדי — המשתמש לא יכול לבחור צומת.
יש לנו ניסיון רב שנים בוויזואליזציה של נתונים — יישמנו מעל 50 פרויקטים, כולל גרפים לבנקים ומערכות לוגיסטיקה. הזמינו פיתוח גרף מותאם לנתונים שלכם, או קבלו ייעוץ ממהנדס. אופטימיזציה דרך WebGL וקיבוץ מאפשרת עיבוד גרפים עם 100,000+ צמתים על מחשב נייד רגיל. בדקו אם המחסנית הנוכחית שלכם מתאימה ל-Network Graph על ידי הזמנת בדיקה. צרו קשר כדי לדון בפרויקט שלכם.







