From 7f2dc36e4409ddaacf99881b7cabf3ce20eab582 Mon Sep 17 00:00:00 2001 From: dextmorgn Date: Mon, 17 Nov 2025 12:27:28 +0100 Subject: [PATCH] feat(app): remove md files and update styles --- .../src/components/graphs/graph-main.tsx | 5 +- .../components/graphs/webgl-graph-viewer.tsx | 37 -- .../src/components/graphs/webgl/CHANGELOG.md | 395 ----------------- .../components/graphs/webgl/DECLUTTERING.md | 418 ------------------ .../components/graphs/webgl/LABEL_STYLES.md | 194 -------- .../src/components/graphs/webgl/README.md | 358 --------------- 6 files changed, 2 insertions(+), 1405 deletions(-) delete mode 100644 flowsint-app/src/components/graphs/webgl-graph-viewer.tsx delete mode 100644 flowsint-app/src/components/graphs/webgl/CHANGELOG.md delete mode 100644 flowsint-app/src/components/graphs/webgl/DECLUTTERING.md delete mode 100644 flowsint-app/src/components/graphs/webgl/LABEL_STYLES.md delete mode 100644 flowsint-app/src/components/graphs/webgl/README.md diff --git a/flowsint-app/src/components/graphs/graph-main.tsx b/flowsint-app/src/components/graphs/graph-main.tsx index 62b320de..4e5e96ba 100644 --- a/flowsint-app/src/components/graphs/graph-main.tsx +++ b/flowsint-app/src/components/graphs/graph-main.tsx @@ -1,9 +1,8 @@ import { useGraphStore } from '@/stores/graph-store' import React, { useRef, useCallback } from 'react' -// import GraphViewer from './graph-viewer' +import GraphViewer from './graph-viewer' // import WebGLGraphViewer from './webgl' import ContextMenu from './context-menu' -import GraphViewer from './graph-viewer' import { useParams } from '@tanstack/react-router' const GraphMain = () => { @@ -58,12 +57,12 @@ const GraphMain = () => { return (
{/* */} void - onNodeRightClick?: (node: GraphNode, event: MouseEvent) => void - onBackgroundClick?: () => void - showIcons?: boolean - showLabels?: boolean - layoutMode?: 'none' | 'force' | 'dagre' -} - -/** - * WebGL Graph Viewer Component - * High-performance graph visualization using WebGL (Pixi.js) and D3-force - * - * @deprecated This is a legacy wrapper. Import from './webgl' for the new modular version. - */ -const WebGLGraphViewerLegacy: React.FC = (props) => { - return -} - -export default WebGLGraphViewerLegacy diff --git a/flowsint-app/src/components/graphs/webgl/CHANGELOG.md b/flowsint-app/src/components/graphs/webgl/CHANGELOG.md deleted file mode 100644 index a3c587f1..00000000 --- a/flowsint-app/src/components/graphs/webgl/CHANGELOG.md +++ /dev/null @@ -1,395 +0,0 @@ -# Changelog - WebGL Graph Viewer - -## [2.3.0] - Layout Modes Architecture - -### šŸ—ļø Architecture Refactoring: Unified View System - -**Major architectural change**: Graph views consolidated into a single "graph" view with switchable layout modes. - -#### ✨ New Features - -##### Unified View Architecture -- **4 Views instead of 5**: `graph`, `table`, `relationships`, `map` -- **Graph view with layout modes**: `none` (default), `force`, `dagre` -- **Dynamic layout switching**: Change layouts without changing views -- **Layout buttons**: Only visible when in graph view - -##### Layout Mode Support -```typescript -type LayoutMode = 'none' | 'force' | 'dagre' - -// none: Static grid layout (default) -// force: D3 force-directed physics simulation -// dagre: Hierarchical top-down layout -``` - -##### Store Updates -```typescript -// graph-controls-store.ts -view: 'graph' | 'table' | 'map' | 'relationships' -layoutMode: 'none' | 'force' | 'dagre' -setLayoutMode: (mode: LayoutMode) => void -``` - -### šŸ”§ Technical Changes - -#### Updated Files - -**`use-force-simulation.ts`** -- Now supports 3 layout modes -- Static grid layout for `layoutMode === 'none'` -- Force simulation only runs when `layoutMode === 'force'` -- Dagre hierarchical layout for `layoutMode === 'dagre'` - -**`toolbar.tsx`** -- Layout buttons (Force/Dagre) only visible in graph view -- View buttons always visible -- Updated disabled states to use `view !== 'graph'` - -**`graph-main.tsx`** -- Passes `layoutMode` prop to WebGLGraphViewer -- Removed separate hierarchy/force view logic - -**`WebGLGraphViewer`** -- Added `layoutMode?: LayoutMode` prop -- Defaults to `'none'` (static grid) - -### šŸ“ Migration Guide - -#### For Users - -**Before:** -- Separate views: "Force" and "Hierarchy" -- Switching between force/hierarchy changed the entire view - -**After:** -- Single "Graph" view -- Toggle between Force/Dagre layouts within graph view -- Default: Static grid (no automatic layout) - -#### For Developers - -**Old imports (still work):** -```typescript -import WebGLGraphViewer from './webgl-graph-viewer' -// Legacy wrapper, automatically redirects to new module -``` - -**New imports (recommended):** -```typescript -import WebGLGraphViewer from './webgl' -``` - -**Props:** -```typescript - -``` - -### šŸŽÆ User Experience - -**Graph View Workflow:** -1. Open graph view (default: static grid layout) -2. Click "Force Layout" button → activates physics simulation -3. Click "Dagre Layout" button → applies hierarchical layout -4. Layout buttons hidden when switching to table/map/relationships views - -### āš ļø Breaking Changes - -None! Fully backward compatible via legacy wrapper. - -### šŸ“Š Performance - -- Static grid layout (none): Instant rendering, no CPU overhead -- Force layout: Same performance as before -- Dagre layout: One-time calculation, then static - ---- - -## [2.2.0] - Smart Label Decluttering - -### šŸš€ Major Feature: Intelligent Label Selection - -**No more overlapping labels!** Complete redesign of the labeling system with collision detection and priority-based selection. - -#### ✨ New Features - -##### Smart Decluttering Algorithm -- **Collision detection**: Labels never overlap (4px minimum margin) -- **Priority-based selection**: Important nodes get labels first -- **Bounding box calculation**: Accurate screen-space dimensions -- **Greedy algorithm**: Optimal label placement without overlap -- **Spatial hashing**: Fast collision detection for large graphs (10,000+ nodes) - -##### Priority Scoring System -```typescript -priority = connectionScore * 0.7 + centerScore * 0.3 -``` -- **Connection score (70%)**: Highly connected nodes prioritized -- **Center score (30%)**: Viewport-centered nodes prioritized -- **Selection-aware**: Selected/highlighted nodes always show labels - -##### Performance Optimizations -- Small graphs (< 500 nodes): Accurate O(n²) collision detection -- Large graphs (≄ 500 nodes): Fast O(1) spatial hashing -- Max 200 labels by default (configurable) -- Throttled recalculation (200ms) -- Zoom hiding (labels hidden during active zoom) - -### šŸ“Š Performance Impact - -| Graph Size | Before | After | Result | -|------------|--------|-------|--------| -| 100 nodes | 60 FPS (overlaps) | 60 FPS | āœ… No overlap | -| 1,000 nodes | 50 FPS (overlaps) | 58 FPS | āœ… +16% + Clean | -| 5,000 nodes | 45 FPS (overlaps) | 55 FPS | āœ… +22% + Clean | -| 10,000 nodes | 35 FPS (overlaps) | 50 FPS | āœ… +43% + Clean | - -**Benefits:** -- šŸŽØ **Cleaner visuals**: No overlapping labels -- 🧠 **Smarter selection**: Important nodes prioritized -- šŸš€ **Better performance**: Fewer labels = faster rendering -- šŸ“ **Viewport-aware**: Relevant labels shown - -### šŸ”§ Technical Changes - -#### New Module: `label-decluttering.ts` -```typescript -// New functions -calculateLabelBoundingBox() // Exact screen-space dimensions -boundingBoxesOverlap() // Collision detection -selectLabelsWithDecluttering() // Accurate selection -selectLabelsWithDeclutteringFast() // Fast selection for large graphs -``` - -#### New Type: `LabelBoundingBox` -```typescript -interface LabelBoundingBox { - nodeId: string - x: number - y: number - width: number - height: number - priority: number // 0-1, higher = more important -} -``` - -#### Updated: `use-graph-renderer.ts` -- Replaced `calculateVisibleLabels()` with `selectLabelsWithDeclutteringFast()` -- Integrated viewport and camera position -- Added decluttering to zoom throttling - -### šŸ“ New Files - -- `utils/label-decluttering.ts` - Core decluttering algorithms -- `DECLUTTERING.md` - Comprehensive documentation - -### šŸ“ Documentation - -- New [DECLUTTERING.md](./DECLUTTERING.md) with detailed explanation -- Updated [README.md](./README.md) with decluttering section -- Updated exports with new functions - -### āš ļø Breaking Changes - -None! Fully backward compatible. - -### šŸ”„ Migration - -No migration needed - decluttering is automatic! - -Old function still available (deprecated): -```typescript -// Old way (deprecated, no collision detection) -calculateVisibleLabels(nodes, zoomLevel) - -// New way (automatic in renderer) -selectLabelsWithDeclutteringFast(...) -``` - ---- - -## [2.1.0] - Styled Label Backgrounds - -### ✨ New Features - -#### Cosmograph-Inspired Label Backgrounds -- **Beautiful label backgrounds** with rounded corners -- **Subtle borders** for better visibility -- **Configurable styling** (padding, radius, colors, opacity) -- **Responsive alpha** based on highlight state -- **Performance optimized** - backgrounds update with labels - -#### Visual Improvements -- Labels now stand out clearly against any background -- Better readability in dense graphs -- Professional, polished appearance -- Consistent with modern graph visualization tools - -### šŸŽØ Style Configuration - -```ts -NODE_LABEL_PADDING_X: 6 // Horizontal padding -NODE_LABEL_PADDING_Y: 3 // Vertical padding -NODE_LABEL_BORDER_RADIUS: 4 // Rounded corners -NODE_LABEL_BORDER_WIDTH: 1 // Border thickness -LABEL_BG_COLOR: 0x1a1a1a // Dark background -LABEL_BG_BORDER_COLOR: 0x404040 // Subtle gray border -NODE_LABEL_BG_ALPHA: 0.92 // 92% opacity -NODE_LABEL_BG_BORDER_ALPHA: 0.3 // 30% opacity -``` - -### šŸ”§ Technical Changes - -- Added `NodeLabelObjects` type with `text` and `background` -- Updated `createNodeLabel()` to return label objects -- Modified `updateNodeLabel()` to manage backgrounds -- Optimized rendering order (backgrounds before text) - ---- - -## [2.0.0] - LOD System + Context-Aware Labels - -### šŸš€ Major Features - -#### Level of Details (LOD) System -- **Adaptive rendering** based on zoom level -- **4 LOD levels**: minimal, low, medium, high -- **Automatic optimization** - no configuration needed -- **Performance gain**: +80% when zoomed out - -#### Context-Aware Edge Labels -- Edge labels **only appear when nodes are active** -- Active = hovered, selected, or highlighted -- **90% reduction** in visual clutter -- **50% fewer labels** rendered on average - -### ✨ Improvements - -#### Architecture -- **Modular structure**: 16 specialized files -- **Hooks-based**: Clean separation of concerns -- **Type-safe**: Full TypeScript coverage -- **Testable**: Each module can be tested independently - -#### Performance -- **Texture caching**: Icons loaded once, reused globally -- **Batch rendering**: Edges grouped by style -- **Early returns**: Skip calculations for invisible elements -- **Manual rendering**: Better control over frame timing - -#### Developer Experience -- **LOD Indicator**: Debug component for development -- **Comprehensive docs**: README with examples -- **Public exports**: Hooks and utilities available -- **Backward compatible**: Old imports still work - -### šŸ“Š Performance Metrics - -| Nodes | Zoom Out (0.25x) | Medium (1.0x) | Zoomed In (2.0x) | -|-------|------------------|---------------|------------------| -| 1K | 60 FPS | 60 FPS | 60 FPS | -| 5K | 60 FPS | 55 FPS | 50 FPS | -| 10K | 55-60 FPS | 50 FPS | 45 FPS | -| 50K | 45-60 FPS | 35 FPS | 25-35 FPS | - -### šŸ”§ Technical Details - -#### LOD Thresholds -```ts -LOD_ICON_MIN_ZOOM: 0.6 // Icons visible at zoom >= 0.6 -LOD_NODE_LABEL_MIN_ZOOM: 0.5 // Node labels visible at zoom >= 0.5 -LOD_EDGE_LABEL_MIN_ZOOM: 1.2 // Edge labels enabled at zoom >= 1.2 -LOD_HIGH_DETAIL_ZOOM: 1.5 // High quality mode at zoom >= 1.5 -``` - -#### Edge Label Logic -```ts -// Show edge label if: -// 1. Zoom >= LOD_EDGE_LABEL_MIN_ZOOM -// 2. Source node is active OR target node is active -// Active = hovered || selected || highlighted -``` - -### šŸ“ File Structure - -``` -webgl/ -ā”œā”€ā”€ index.tsx # Main component (150 lines) -ā”œā”€ā”€ constants.ts # Configuration -ā”œā”€ā”€ exports.ts # Public API -ā”œā”€ā”€ README.md # Documentation -ā”œā”€ā”€ CHANGELOG.md # This file -ā”œā”€ā”€ components/ -│ └── lod-indicator.tsx # Debug component -ā”œā”€ā”€ hooks/ # Business logic -│ ā”œā”€ā”€ use-pixi-app.ts -│ ā”œā”€ā”€ use-force-simulation.ts -│ ā”œā”€ā”€ use-graph-interactions.ts -│ ā”œā”€ā”€ use-zoom-pan.ts -│ └── use-graph-renderer.ts -ā”œā”€ā”€ renderers/ # Rendering logic -│ ā”œā”€ā”€ node-renderer.ts -│ ā”œā”€ā”€ edge-renderer.ts -│ └── label-renderer.ts -ā”œā”€ā”€ utils/ # Utilities -│ ā”œā”€ā”€ texture-cache.ts -│ ā”œā”€ā”€ visibility-calculator.ts -│ └── color-utils.ts -└── types/ - └── graph.types.ts # TypeScript types -``` - -### šŸŽÆ Breaking Changes - -None - fully backward compatible! - -### šŸ› Bug Fixes - -- Fixed edge label overlap issues -- Fixed icon flickering on zoom -- Fixed memory leak on component unmount - -### šŸ“ Migration Guide - -#### From v1.0 (monolithic) -```tsx -// Old way - still works! -import WebGLGraphViewer from './webgl-graph-viewer' - -// New way - recommended -import WebGLGraphViewer from './webgl' -``` - -#### Using new features -```tsx -// Enable LOD indicator for debugging -import { LODIndicator } from './webgl/exports' - -// Use LOD utilities -import { getLODLevel, shouldShowNodeIcons } from './webgl/exports' - -const lodLevel = getLODLevel(zoomLevel) -console.log(`Current LOD: ${lodLevel}`) // 'minimal', 'low', 'medium', 'high' -``` - -### šŸ‘„ Contributors - -- Refactored from monolithic 1000-line component -- Implemented modular architecture -- Added LOD system -- Context-aware edge labels - ---- - -## [1.0.0] - Initial Release - -- Basic WebGL graph visualization -- Pixi.js + D3-force -- Node interactions (hover, click, drag) -- Zoom/pan controls -- Basic icon and label support diff --git a/flowsint-app/src/components/graphs/webgl/DECLUTTERING.md b/flowsint-app/src/components/graphs/webgl/DECLUTTERING.md deleted file mode 100644 index bbab1f16..00000000 --- a/flowsint-app/src/components/graphs/webgl/DECLUTTERING.md +++ /dev/null @@ -1,418 +0,0 @@ -# Label Decluttering System - -This document explains the intelligent label decluttering system that prevents labels from overlapping. - -## Overview - -The decluttering system automatically selects which labels to display based on: -1. **Collision detection** - Labels never overlap -2. **Priority scoring** - Important nodes get labels first -3. **Spatial awareness** - Viewport-centered labels preferred -4. **Selection state** - Selected/highlighted nodes always show labels - -## Architecture - -### Before: Simple Selection -```typescript -// Old system (basic, no collision detection) -calculateVisibleLabels(nodes, zoomLevel) -// - Selected top N nodes by connection count -// - No collision detection -// - Labels could overlap completely -``` - -### After: Intelligent Decluttering -```typescript -// New system (collision-aware, priority-based) -selectLabelsWithDeclutteringFast( - nodes, - nodeSize, - zoomLevel, - fontSizeSetting, - viewportWidth, - viewportHeight, - cameraX, - cameraY, - selectedNodeIds, - highlightedNodeIds -) -// - Calculates real bounding boxes -// - Detects collisions -// - Greedy selection by priority -// - Optimized with spatial hashing for large graphs -``` - -## Key Components - -### 1. Bounding Box Calculation - -Each label's exact screen space is calculated: - -```typescript -interface LabelBoundingBox { - nodeId: string - x: number // Center X position - y: number // Top Y position - width: number // Background width (text + padding) - height: number // Background height (text + padding) - priority: number // 0-1, higher = more important -} -``` - -**Calculation includes:** -- Text length estimation (`fontSize * 0.6` per character) -- Background padding (`NODE_LABEL_PADDING_X`, `NODE_LABEL_PADDING_Y`) -- Label position (below node: `node.y + nodeSize + fontSize * 0.6`) -- Zoom-adjusted font size - -### 2. Priority Scoring - -Labels are prioritized using multiple factors: - -```typescript -priority = connectionScore * 0.7 + centerScore * 0.3 -``` - -**Connection Score (70% weight):** -- Normalized by neighbor count -- `Math.min(node.neighbors.length / 20, 1)` -- Highly connected nodes = more important - -**Center Score (30% weight):** -- Distance from viewport center -- Closer to center = more important -- Calculated as: `1 - distanceFromCenter / maxDistance` - -**Result:** -- Nodes with many connections in viewport center get highest priority -- Peripheral or less-connected nodes deprioritized - -### 3. Collision Detection - -Two methods for different graph sizes: - -#### Accurate Detection (< 500 nodes) -```typescript -function boundingBoxesOverlap(a: LabelBoundingBox, b: LabelBoundingBox): boolean { - // Add 4px margin for breathing room - const margin = 4 - - // Calculate bounds with margin - const aLeft = a.x - a.width / 2 - margin - const aRight = a.x + a.width / 2 + margin - const aTop = a.y - margin - const aBottom = a.y + a.height + margin - - // Check for overlap - return !(aRight < bLeft || aLeft > bRight || aBottom < bTop || aTop > bBottom) -} -``` - -#### Spatial Hashing (≄ 500 nodes) -For large graphs, uses a grid-based approach: - -```typescript -const gridSize = 100 // 100px grid cells -const occupiedCells = new Set() - -// Mark cell as occupied -const cellKey = `${Math.floor(x / gridSize)},${Math.floor(y / gridSize)}` -occupiedCells.add(cellKey) - -// Also mark adjacent cells (9 cells total) -for (let dx = -1; dx <= 1; dx++) { - for (let dy = -1; dy <= 1; dy++) { - occupiedCells.add(`${cellX + dx},${cellY + dy}`) - } -} -``` - -**Benefits:** -- O(1) collision check instead of O(n²) -- 10-100x faster for large graphs -- Slight accuracy tradeoff acceptable for performance - -### 4. Greedy Selection Algorithm - -Labels are selected in order of priority: - -```typescript -// Step 1: Sort all labels by priority (highest first) -boundingBoxes.sort((a, b) => b.priority - a.priority) - -// Step 2: Always add selected/highlighted nodes first -const placedBboxes = [...selectedBboxes, ...highlightedBboxes] -visibleSet.add(selectedNodeIds) -visibleSet.add(highlightedNodeIds) - -// Step 3: Greedily add non-colliding labels -for (const candidate of regularBboxes) { - let hasCollision = false - - for (const placed of placedBboxes) { - if (boundingBoxesOverlap(candidate, placed)) { - hasCollision = true - break - } - } - - if (!hasCollision) { - placedBboxes.push(candidate) - visibleSet.add(candidate.nodeId) - } -} -``` - -**Guarantees:** -- āœ… Selected/highlighted nodes always have visible labels -- āœ… No two labels ever overlap (4px minimum margin) -- āœ… Higher priority nodes get labels before lower priority -- āœ… Maximum label density without collisions - -## Performance Optimizations - -### 1. Algorithm Selection -```typescript -if (nodes.length < 500) { - // Use accurate collision detection - selectLabelsWithDecluttering(...) -} else { - // Use spatial hashing - selectLabelsWithDeclutteringFast(..., maxLabels: 200) -} -``` - -### 2. Throttling -```typescript -// Recalculate max once every 200ms during zoom -if (now - lastLabelUpdateRef.current > 200) { - lastLabelUpdateRef.current = now - setVisibleLabels(selectLabelsWithDeclutteringFast(...)) -} -``` - -### 3. Zoom Hiding -```typescript -// Hide ALL labels during active zoom (< 300ms since last zoom change) -if (isZoomingRef.current) { - labelObjects.text.visible = false - labelObjects.background.visible = false - return -} -``` - -### 4. Frame Limiting -```typescript -// Still limit to 100 labels rendered per frame -const MAX_LABELS_PER_FRAME = 100 -let renderedLabelCount = 0 - -// In render loop: -if (renderedLabelCount >= MAX_LABELS_PER_FRAME) { - label.visible = false - return -} -``` - -## Performance Metrics - -| Graph Size | Old System | New System | Improvement | -|------------|-----------|------------|-------------| -| 100 nodes | 60 FPS | 60 FPS | Same | -| 500 nodes | 55 FPS (overlaps) | 60 FPS (clean) | +9% + No overlap | -| 1,000 nodes | 50 FPS (overlaps) | 58 FPS (clean) | +16% + No overlap | -| 5,000 nodes | 45 FPS (overlaps) | 55 FPS (clean) | +22% + No overlap | -| 10,000 nodes | 35 FPS (overlaps) | 50 FPS (clean) | +43% + No overlap | - -**Benefits:** -- šŸš€ **Better performance** (fewer labels = faster rendering) -- šŸŽØ **Cleaner visuals** (no overlapping labels) -- 🧠 **Smarter selection** (important nodes prioritized) -- šŸ“ **Viewport-aware** (relevant labels shown) - -## Customization - -### Adjust Priority Weighting -```typescript -// In label-decluttering.ts > calculateNodePriority() -priority = connectionScore * 0.7 + centerScore * 0.3 -// ^^^^ ^^^^ -// Importance Position - -// Make position more important: -priority = connectionScore * 0.5 + centerScore * 0.5 - -// Make connections more important: -priority = connectionScore * 0.9 + centerScore * 0.1 -``` - -### Adjust Collision Margin -```typescript -// In boundingBoxesOverlap() -const margin = 4 // Increase for more spacing -``` - -### Adjust Grid Size (Large Graphs) -```typescript -// In selectLabelsWithDeclutteringFast() -const gridSize = 100 // Smaller = more accurate, slower - // Larger = faster, less accurate -``` - -### Adjust Max Labels -```typescript -selectLabelsWithDeclutteringFast( - ..., - maxLabels: 200 // Increase for more labels, decrease for performance -) -``` - -## Integration - -The decluttering system is automatically used in `use-graph-renderer.ts`: - -```typescript -// On zoom change (throttled) -const visibleLabels = selectLabelsWithDeclutteringFast( - simulationNodes, - forceSettings.nodeSize.value, - zoomLevel, - forceSettings.nodeLabelFontSize?.value ?? 50, - viewport.width, - viewport.height, - -stage.position.x / stage.scale.x, // Camera X - -stage.position.y / stage.scale.y, // Camera Y - selectedNodeIds, - highlightedNodeIds -) -setVisibleLabels(visibleLabels) - -// In render loop -const shouldShow = showLabelsLOD && visibleLabels.has(node.id) && !isZoomingRef.current -updateNodeLabel(labelObjects, node, nodeSize, shouldShow, ...) -``` - -## Future Enhancements - -Potential improvements: - -1. **Dynamic repulsion**: Slightly adjust label positions to fit more -2. **Multi-line labels**: Wrap long text instead of truncating -3. **Leader lines**: Connect labels to nodes when repositioned -4. **Semantic zoom**: Different label content at different zoom levels -5. **Animation**: Smooth fade in/out when labels change -6. **GPU acceleration**: Move collision detection to compute shader - -## Comparison: Before vs After - -### Before (Simple Selection) -``` - ā— ā— - Long Label Name Another Label - ā— - Third Label - -āŒ Labels overlap -āŒ Hard to read -āŒ No spatial awareness -āŒ Random selection -``` - -### After (Decluttering) -``` - ā— ā— - Long Label Name - - ā— - Another Label - - ā— - -āœ… No overlap (4px margin) -āœ… Clear and readable -āœ… Viewport-centered priority -āœ… Smart selection -``` - -## Debug & Testing - -### Visualize Bounding Boxes (Development) -```typescript -// In use-graph-renderer.ts render loop -if (process.env.NODE_ENV === 'development') { - const bbox = calculateLabelBoundingBox(node, ...) - if (bbox) { - const debugRect = new Graphics() - debugRect.rect( - bbox.x - bbox.width / 2, - bbox.y, - bbox.width, - bbox.height - ) - debugRect.stroke({ width: 1, color: 0xff0000, alpha: 0.5 }) - debugContainer.addChild(debugRect) - } -} -``` - -### Log Priority Scores -```typescript -const visibleLabels = selectLabelsWithDeclutteringFast(...) -console.log('Visible labels:', visibleLabels.size) -console.log('Top priority nodes:', - boundingBoxes.slice(0, 10).map(b => ({ - id: b.nodeId, - priority: b.priority - })) -) -``` - -## API Reference - -### `calculateLabelBoundingBox()` -Calculates the exact screen-space bounding box for a node label. - -**Parameters:** -- `node: SimulationNode` - The node -- `nodeSize: number` - Visual node size -- `zoomLevel: number` - Current zoom -- `fontSizeSetting: number` - Font size % -- `viewportWidth: number` - Viewport width -- `viewportHeight: number` - Viewport height -- `cameraX: number` - Camera X position -- `cameraY: number` - Camera Y position - -**Returns:** `LabelBoundingBox | null` - -### `boundingBoxesOverlap()` -Checks if two bounding boxes collide (with 4px margin). - -**Parameters:** -- `a: LabelBoundingBox` - First box -- `b: LabelBoundingBox` - Second box - -**Returns:** `boolean` - True if overlap - -### `selectLabelsWithDecluttering()` -Accurate greedy selection with full collision detection. - -**Use for:** < 500 nodes - -**Returns:** `Set` - Node IDs with visible labels - -### `selectLabelsWithDeclutteringFast()` -Fast selection with spatial hashing for large graphs. - -**Use for:** ≄ 500 nodes - -**Parameters:** -- All same as `calculateLabelBoundingBox()` -- `selectedNodeIds: Set` - Always show -- `highlightedNodeIds: Set` - Always show -- `maxLabels: number` - Max labels (default: 200) - -**Returns:** `Set` - Node IDs with visible labels - -## License - -Internal use only. diff --git a/flowsint-app/src/components/graphs/webgl/LABEL_STYLES.md b/flowsint-app/src/components/graphs/webgl/LABEL_STYLES.md deleted file mode 100644 index 5dbc7d24..00000000 --- a/flowsint-app/src/components/graphs/webgl/LABEL_STYLES.md +++ /dev/null @@ -1,194 +0,0 @@ -# Label Styling Guide - -This document explains the label background styling system inspired by Cosmograph. - -## Visual Appearance - -### Node Labels with Backgrounds (Active Only) - -**Important**: Backgrounds are shown ONLY for active nodes (hovered or selected) to maintain optimal performance. - -``` -Normal Node: Active Node (hover/selected): - ā— ā— - Node Label ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” - │ Node Label │ ← Background appears - ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ -``` - -### Anatomy of a Label Background - -``` - ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ← Border (1px, #404040, 30% opacity) - │ ā”Œā”€ Padding (6px horizontal) │ - │ │ │ - │ │ Text Content │ ← Text (#e0e0e0) - │ │ │ - │ └─ Padding (3px vertical) │ - ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ - Background (#1a1a1a, 92% opacity) - Border Radius: 4px -``` - -## Configuration - -### Default Values - -```typescript -NODE_LABEL_PADDING_X: 6 // 6px left and right -NODE_LABEL_PADDING_Y: 3 // 3px top and bottom -NODE_LABEL_BORDER_RADIUS: 4 // 4px rounded corners -NODE_LABEL_BORDER_WIDTH: 1 // 1px border -LABEL_BG_COLOR: 0x1a1a1a // Dark gray (#1a1a1a) -LABEL_BG_BORDER_COLOR: 0x404040 // Medium gray (#404040) -NODE_LABEL_BG_ALPHA: 0.92 // 92% opacity -NODE_LABEL_BG_BORDER_ALPHA: 0.3 // 30% opacity -``` - -### State-Based Styling - -Labels and backgrounds adapt based on interaction state: - -| State | Label Visible | Background Visible | Alpha | -|-------|---------------|-------------------|-------| -| **Normal** | āœ… (if in visible set) | āŒ No background | 1.0 | -| **Hovered** | āœ… Always | āœ… **Background shown** | 1.0 | -| **Selected** | āœ… Always | āœ… **Background shown** | 1.0 | -| **Dimmed** | āœ… (if visible) | āŒ No background | 0.2 | - -**Performance Note**: Backgrounds only render for active nodes (hovered/selected), reducing GPU load by ~99%. - -## Customization Examples - -### Lighter Background -```typescript -LABEL_BG_COLOR: 0x2a2a2a // Lighter gray -NODE_LABEL_BG_ALPHA: 0.95 // More opaque -``` - -### More Prominent Border -```typescript -LABEL_BG_BORDER_COLOR: 0x606060 // Lighter border -NODE_LABEL_BG_BORDER_ALPHA: 0.5 // More visible -NODE_LABEL_BORDER_WIDTH: 2 // Thicker -``` - -### More Padding -```typescript -NODE_LABEL_PADDING_X: 8 // More horizontal space -NODE_LABEL_PADDING_Y: 4 // More vertical space -``` - -### Sharper Corners -```typescript -NODE_LABEL_BORDER_RADIUS: 2 // Less rounded -``` - -### Glass Morphism Effect -```typescript -LABEL_BG_COLOR: 0x1a1a1a -NODE_LABEL_BG_ALPHA: 0.7 // More transparent -LABEL_BG_BORDER_COLOR: 0xffffff // White border -NODE_LABEL_BG_BORDER_ALPHA: 0.2 // Subtle glow -``` - -## Rendering Details - -### Rendering Order - -1. **Background graphics** (rendered first) -2. **Text content** (rendered on top) - -This ensures text is always readable and clickable. - -### Dynamic Sizing - -Backgrounds automatically adapt to text size: -```typescript -const textMetrics = label.getBounds() -const bgWidth = textMetrics.width + PADDING_X * 2 -const bgHeight = textMetrics.height + PADDING_Y * 2 -``` - -### Performance Optimizations - -āœ… **Context-Aware Rendering**: Backgrounds shown ONLY for active nodes (hovered/selected) -āœ… **Dimension Estimation**: Fast approximation instead of expensive `getBounds()` -āœ… **Dirty Checking**: Only redraw when dimensions change -āœ… **Cached Calculations**: Store width to avoid recalculation -āœ… **Combined Draw Calls**: Fill and stroke in one operation -āœ… **GPU Compositing**: Alpha blending handled by hardware - -**Result**: ~99% reduction in background rendering cost! šŸš€ - -## Comparison with Cosmograph - -| Feature | Cosmograph | This Implementation | -|---------|-----------|---------------------| -| Background | āœ… | āœ… | -| Border | āœ… | āœ… | -| Rounded corners | āœ… | āœ… | -| Adaptive opacity | āœ… | āœ… | -| Configurable | Limited | āœ… Fully | -| Performance | High | High | - -## Tips - -1. **Readability**: Keep background alpha > 0.8 for best readability -2. **Contrast**: Ensure border color contrasts with background -3. **Padding**: More padding = clearer labels but larger footprint -4. **Border**: Keep border subtle (alpha 0.2-0.4) to avoid visual noise - -## Examples in Code - -### Basic Label -```typescript -const label = createNodeLabel("John Doe") -// Returns: { text: Text, background: Graphics } -``` - -### Update Label -```typescript -updateNodeLabel( - labelObjects, - node, - nodeSize, - shouldShow, - zoomLevel, - fontSizeSetting, - isHighlighted, - hasAnyHighlight -) -// Automatically updates both text and background -``` - -### Custom Styling (in constants.ts) -```typescript -export const GRAPH_CONSTANTS = { - // ... other constants - - // Custom label style - NODE_LABEL_PADDING_X: 8, - NODE_LABEL_PADDING_Y: 5, - NODE_LABEL_BORDER_RADIUS: 6, - LABEL_BG_COLOR: 0x2a2a2a, - NODE_LABEL_BG_ALPHA: 0.95, - - // ... rest of constants -} -``` - -## Browser Rendering - -Labels use Pixi.js Graphics API which: -- Renders via WebGL for maximum performance -- Supports anti-aliasing for smooth borders -- Hardware accelerated alpha blending -- Batches similar draw calls automatically - -## Accessibility - -While this is a canvas-based visualization (not DOM), consider: -- High contrast mode support via color constants -- Font size adjustments via zoom level -- Clear backgrounds improve readability for all users diff --git a/flowsint-app/src/components/graphs/webgl/README.md b/flowsint-app/src/components/graphs/webgl/README.md deleted file mode 100644 index 299be5db..00000000 --- a/flowsint-app/src/components/graphs/webgl/README.md +++ /dev/null @@ -1,358 +0,0 @@ -# WebGL Graph Viewer - -A high-performance, modular graph visualization component built with Pixi.js (WebGL) and D3-force. - -## Features - -- **High Performance**: WebGL-based rendering with Pixi.js -- **Physics Simulation**: D3-force for realistic node interactions -- **Modular Architecture**: Clean separation of concerns -- **Type Safe**: Full TypeScript support -- **Customizable**: Extensive configuration options -- **Interactive**: Zoom, pan, drag, hover, and click interactions -- **Smart Decluttering**: Intelligent label selection with collision detection -- **Progressive Disclosure**: Priority-based label visibility -- **Icon Support**: SVG icon rendering with texture caching -- **Styled Labels**: Cosmograph-inspired label backgrounds with borders -- **Context-Aware**: Edge labels only appear when nodes are active - -## Architecture - -``` -webgl/ -ā”œā”€ā”€ index.tsx # Main component -ā”œā”€ā”€ constants.ts # Configuration constants -ā”œā”€ā”€ types/ -│ └── graph.types.ts # TypeScript types -ā”œā”€ā”€ hooks/ -│ ā”œā”€ā”€ use-pixi-app.ts # Pixi.js app management -│ ā”œā”€ā”€ use-force-simulation.ts # D3-force simulation -│ ā”œā”€ā”€ use-graph-interactions.ts # User interactions -│ ā”œā”€ā”€ use-zoom-pan.ts # Zoom/pan controls -│ └── use-graph-renderer.ts # Main rendering logic -ā”œā”€ā”€ renderers/ -│ ā”œā”€ā”€ node-renderer.ts # Node rendering -│ ā”œā”€ā”€ edge-renderer.ts # Edge rendering -│ └── label-renderer.ts # Label rendering -└── utils/ - ā”œā”€ā”€ texture-cache.ts # Icon texture caching - ā”œā”€ā”€ visibility-calculator.ts # LOD thresholds - ā”œā”€ā”€ label-decluttering.ts # Smart label selection - └── color-utils.ts # Color utilities -``` - -## Usage - -### Basic Example - -```tsx -import WebGLGraphViewer from '@/components/graphs/webgl' - -function MyGraph() { - const nodes = [ - { id: '1', data: { label: 'Node 1', type: 'person' } }, - { id: '2', data: { label: 'Node 2', type: 'company' } }, - ] - - const edges = [ - { source: '1', target: '2', label: 'works at' }, - ] - - return ( - console.log('Clicked:', node)} - showIcons={true} - showLabels={true} - /> - ) -} -``` - -### With Custom Handlers - -```tsx - { - if (event.ctrlKey) { - // Multi-select - } else { - // Single select - } - }} - onNodeRightClick={(node, event) => { - // Show context menu - }} - onBackgroundClick={() => { - // Deselect all - }} - className="my-graph" - style={{ width: '100%', height: '600px' }} -/> -``` - -## Props - -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `nodes` | `GraphNode[]` | Required | Array of graph nodes | -| `edges` | `GraphEdge[]` | Required | Array of graph edges | -| `className` | `string` | `''` | CSS class name | -| `style` | `React.CSSProperties` | `undefined` | Inline styles | -| `onNodeClick` | `(node, event) => void` | `undefined` | Node click handler | -| `onNodeRightClick` | `(node, event) => void` | `undefined` | Node right-click handler | -| `onBackgroundClick` | `() => void` | `undefined` | Background click handler | -| `showIcons` | `boolean` | `true` | Show node icons | -| `showLabels` | `boolean` | `true` | Show node/edge labels | - -## Performance Optimizations - -### 1. Smart Label Decluttering - -**Intelligent label selection with collision detection** - -The graph uses an advanced decluttering algorithm that: -- āœ… **Prevents overlap**: Labels never collide (4px minimum margin) -- āœ… **Priority-based**: Important nodes (highly connected, viewport-centered) get labels first -- āœ… **Collision detection**: Real bounding box calculations -- āœ… **Spatial hashing**: O(1) collision checks for large graphs (10,000+ nodes) -- āœ… **Selection-aware**: Selected/highlighted nodes always show labels - -**How it works:** - -```typescript -// Combines multiple factors for smart selection -priority = connectionScore * 0.7 + centerScore * 0.3 - -// Greedy algorithm: -// 1. Sort nodes by priority (highest first) -// 2. Always add selected/highlighted nodes -// 3. Add remaining nodes if they don't collide -``` - -**Performance:** -- Small graphs (< 500 nodes): Accurate collision detection -- Large graphs (≄ 500 nodes): Fast spatial hashing -- 200 max labels by default (configurable) - -See [DECLUTTERING.md](./DECLUTTERING.md) for detailed documentation. - -### 2. Level of Details (LOD) System -Automatically adjusts visual complexity based on zoom level: - -| Zoom Level | LOD | Features Visible | -|------------|-----|------------------| -| < 0.5 | **Minimal** | Nodes + Edges only | -| 0.5 - 0.6 | **Low** | + Basic node labels | -| 0.6 - 1.2 | **Medium** | + Icons | -| 1.2 - 1.5 | **High** | + Edge labels* | -| > 1.5 | **Ultra** | All features at max quality | - -**Edge labels are context-aware**: They only appear when at least one of their connected nodes is active (hovered or selected), reducing visual clutter and improving performance. - -```ts -import { getLODLevel, shouldShowNodeIcons } from './exports' - -const lodLevel = getLODLevel(zoomLevel) // 'minimal', 'low', 'medium', 'high' -const showIcons = shouldShowNodeIcons(zoomLevel) // boolean -``` - -**Benefits:** -- šŸš€ **60 FPS maintained** even with 10,000+ nodes when zoomed out -- šŸ’¾ **Reduced GPU load** by hiding unnecessary details -- ⚔ **Instant zoom** response without lag - -### 3. Texture Caching -Icons are loaded once and cached globally: -```ts -import { textureCache } from './utils/texture-cache' - -// Preload textures -await textureCache.preloadBatch(['person', 'company']) - -// Get cached texture -const texture = textureCache.get('person') -``` - -### 4. Batch Rendering -Edges are grouped by style and rendered in batches: -- Highlighted edges (orange) -- Dimmed edges (low opacity) -- Default edges (normal) - -### 5. Progressive Label Disclosure (Deprecated) - -**Note:** Progressive disclosure is now handled by the Smart Decluttering system (see above). - - -Labels appear based on: -- Node importance (connection count) -- Current zoom level -- Selection/highlight state -- **LOD thresholds** - -**Smart Edge Labels:** -- Edge labels are **context-aware** and only appear when their nodes are active -- Active = hovered, selected, or highlighted -- This reduces visual clutter by 90% in typical use cases -- Performance improvement: ~50% fewer labels rendered at any time - -### 6. Manual Rendering -Pixi.js app uses manual rendering (`autoStart: false`) for better control. - -### 7. Optimized Updates -- Refs used for values that don't need re-renders -- Memoized calculations -- Debounced resize handler -- **LOD-based early returns** (skip calculations for invisible elements) - -## Customization - -### Constants -Edit `constants.ts` to customize: -- Node sizes and colors -- Edge widths and colors -- Zoom limits -- Label visibility thresholds -- **LOD thresholds** (when to show/hide features) -- **Label styling** (background, border, padding) -- Animation durations - -**LOD Configuration:** -```ts -// In constants.ts -LOD_ICON_MIN_ZOOM: 0.6, // Show icons when zoom >= 0.6 -LOD_NODE_LABEL_MIN_ZOOM: 0.5, // Show node labels when zoom >= 0.5 -LOD_EDGE_LABEL_MIN_ZOOM: 1.2, // Enable edge labels when zoom >= 1.2 - // (only shown when nodes are active) -LOD_HIGH_DETAIL_ZOOM: 1.5, // High detail mode when zoom >= 1.5 -``` - -**Label Styling:** -```ts -// Node label backgrounds (Cosmograph-inspired) -NODE_LABEL_PADDING_X: 6, // Horizontal padding -NODE_LABEL_PADDING_Y: 3, // Vertical padding -NODE_LABEL_BORDER_RADIUS: 4, // Rounded corners -NODE_LABEL_BORDER_WIDTH: 1, // Border thickness -LABEL_BG_COLOR: 0x1a1a1a, // Background color -LABEL_BG_BORDER_COLOR: 0x404040, // Border color -NODE_LABEL_BG_ALPHA: 0.92, // Background opacity -NODE_LABEL_BG_BORDER_ALPHA: 0.3, // Border opacity -``` - -**Edge Label Behavior:** -Edge labels are now **context-aware** - they only appear when: -1. Zoom level >= `LOD_EDGE_LABEL_MIN_ZOOM` (LOD check) -2. At least one connected node is **active** (hovered or selected) - -This dramatically reduces visual clutter while keeping relevant information visible. - -### Renderers -Modify renderer functions to change visual appearance: -- `renderNode()` - Node shapes and colors -- `renderEdges()` - Edge styles -- `updateNodeLabel()` - Label positioning and styling - -### Hooks -Extend or replace hooks for custom behavior: -- `useForceSimulation()` - Custom physics -- `useGraphInteractions()` - Custom interactions -- `useZoomPan()` - Custom zoom/pan behavior - -## Debugging - -### LOD Indicator -Enable the LOD indicator to see current zoom level and active LOD mode: - -```tsx -// In index.tsx, uncomment the import and component: -import { LODIndicator } from './components/lod-indicator' - -// In the return statement: - -``` - -The indicator shows: -- Current LOD level (color-coded) -- Description of visible features -- Exact zoom value - -**Color Legend:** -- šŸ”“ Red: Minimal (nodes + edges only) -- 🟠 Orange: Low (+ basic labels) -- 🟔 Yellow: Medium (+ icons) -- 🟢 Green: High (all features) - -### Performance Profiling -```tsx -// Use Chrome DevTools Performance tab -// Look for "render" calls in the flame graph -// LOD should reduce GPU time when zoomed out -``` - -## Testing - -### Unit Tests -```bash -npm test -- webgl -``` - -### Visual Testing -```tsx -// In Storybook -import WebGLGraphViewer from '@/components/graphs/webgl' - -export default { - title: 'Graph/WebGLGraphViewer', - component: WebGLGraphViewer, -} -``` - -### LOD Testing -1. Zoom out completely - should see minimal LOD (no icons, no labels) -2. Zoom to 0.6x - icons should appear -3. Zoom to 1.2x - edge labels **enabled** (but only visible on hover/select) -4. Hover over a node - edge labels for connected edges should appear -5. Select a node - edge labels remain visible -6. Deselect/unhover - edge labels disappear -7. Use LODIndicator component to verify thresholds - -## Performance Metrics - -Tested with LOD system: -- **1,000 nodes**: 60 FPS (all zoom levels) -- **5,000 nodes**: 60 FPS (zoomed out), 50-60 FPS (zoomed in) -- **10,000 nodes**: 55-60 FPS (zoomed out), 40-50 FPS (zoomed in) -- **50,000 nodes**: 45-60 FPS (zoomed out), 25-35 FPS (zoomed in) - -Performance depends on: -- Device capabilities -- **Zoom level (LOD)** ⭐ Major factor -- Number of visible labels -- Active interactions - -**LOD Impact:** -- Zoomed out (LOD: minimal): **+80% performance** -- Medium zoom (LOD: medium): **+40% performance** -- Fully zoomed in (LOD: high): Baseline performance -- **Context-aware edge labels**: **+50% fewer labels** rendered on average - -## Browser Compatibility - -- Chrome 90+ -- Firefox 88+ -- Safari 14+ -- Edge 90+ - -Requires WebGL support. - -## License - -Internal use only.