-
Notifications
You must be signed in to change notification settings - Fork 7
Open
Labels
enhancementNew feature or requestNew feature or request
Milestone
Description
WIP
🎯 Problem Statement
Currently, @gravity-ui/graph lacks a unified system for programmatically managing component states (highlighting, focusing, prioritizing). Developers need to manually implement that.
💡 Proposed Solution
Implement a Universal Highlight System that provides clean programming interfaces for managing component states
Core Principles
📝 Detailed Requirements
1. Two Fundamentally Different Highlight Modes
🎯 Mode 1: highlight() - Selective Highlighting
// ONLY highlights specified entities, others remain UNCHANGED
graph.highlight({
block: ['user-1', 'user-2'],
connection: ['critical-link']
});
// Result:
// user-1: HighlightVisualMode.Highlight (20)
// user-2: HighlightVisualMode.Highlight (20)
// critical-link: HighlightVisualMode.Highlight (20)
// ALL OTHER entities: undefined (normal state - untouched)🔍 Mode 2: focus() - Spotlight with Dimming
// Highlights targets AND lowlights EVERYTHING ELSE
graph.focus({
block: ['important-node'],
connection: ['main-flow']
});
// Result:
// important-node: HighlightVisualMode.Highlight (20)
// main-flow: HighlightVisualMode.Highlight (20)
// ALL OTHER entities: HighlightVisualMode.Lowlight (10) ← KEY DIFFERENCE!🔄 Mode Management
// Clear all states - everything returns to normal
graph.clearHighlight();
// Result: ALL entities = undefined (normal state)⚡ Critical Differences Explained
| Aspect | highlight() |
focus() |
|---|---|---|
| Target entities | Highlight (20) |
Highlight (20) |
| Non-target entities | undefined (unchanged) |
Lowlight (10) (dimmed) |
| Use case | "Show important items" | "Focus attention, hide noise" |
| Performance | ✅ Fast (only targets change) | |
| Visual impact | 🎯 Selective emphasis | 🔍 Dramatic contrast |
2. Universal ID System
// Core prefixes (built-in library support)
block:user-123 // Blocks
connection:link-456 // Connections
anchor:user-123:output // Anchors
// User extensions (unlimited extensibility)
group:team-alpha // Custom groups
layer:background // Custom layers
plugin:my-element // Plugin components
myApp:special-feature // Application-specific3. Real-World Usage Scenarios
🎯 When to use highlight()
// ✅ Show search results (don't hide other data)
const searchResults = ['user-123', 'user-456'];
graph.highlight({ block: searchResults });
// ✅ Mark validation errors (keep context visible)
const invalidNodes = validateGraph();
graph.highlight({ block: invalidNodes.map(n => n.id) });
// ✅ Show related elements (preserve workflow context)
graph.highlight({
block: ['selected-node'],
connection: getConnectedEdges('selected-node'),
anchor: getRelatedAnchors('selected-node')
});🔍 When to use focus()
// ✅ Critical path analysis (hide distractions)
const criticalPath = findCriticalPath();
graph.focus({
block: criticalPath.nodes,
connection: criticalPath.edges
});
// ✅ Debug specific workflow (isolate problem area)
graph.focus({
block: ['error-source'],
connection: ['failed-connection']
});
// ✅ Presentation mode (spotlight key elements)
graph.focus({ block: ['demo-node-1', 'demo-node-2'] });⚡ Performance Impact
// highlight() - FAST: Only changes target entities
graph.highlight({ block: ['node-1'] });
// Changes: 1 entity state
// Performance: O(targets)
// focus() - SLOWER: Changes ALL entities in graph
graph.focus({ block: ['node-1'] });
// Changes: ALL entity states (targets + non-targets)
// Performance: O(all entities)
// For large graphs (1000+ entities), consider highlight() for frequent operations4. Component Integration
// Extends
class GraphComponent {
// 1. Define unique ID
public abstract getHighlightId(): string {
return `myPlugin:${this.id}`;
}
afterInit() {
this.onSignal(
computed(() => this.store.graph.highlightService.getEntityHighlightMode(this.getHighlightId())),
(mode) => this.setState({ highlightMode: mode }),
);
}
}
class BaseConnection extends GraphComponent {
public getHighlightId(): string {
return `connection:${this.state.id}`;
}
}
class Block extends GraphComponent {
public getHighlightId(): string {
return `block:${this.state.id}`;
}
}
class Anchor extends GraphComponent {
public getHighlightId(): string {
return `anchor:${this.id}`;
}
}5. Internal State Management Logic
enum HighlightVisualMode {
Highlight = 20, // High priority state
Lowlight = 10, // Low priority state
// Normal = undefined (neutral state)
}
type HighlightServiceMode = 'highlight' | 'focus';
// Core logic - how getEntityHighlightMode() works differently
class HighlightService {
public getEntityHighlightMode(entityId: string): HighlightVisualMode | undefined {
const state = this.$state.value;
if (!state.active) return undefined;
const isTargeted = state.entities.has(entityId);
if (state.mode === 'highlight') {
// ✨ HIGHLIGHT MODE: Only targets get state, others stay normal
return isTargeted ? HighlightVisualMode.Highlight : undefined;
} else { // focus mode
// 🔥 FOCUS MODE: Targets get highlighted, EVERYONE ELSE gets lowlighted
return isTargeted ? HighlightVisualMode.Highlight : HighlightVisualMode.Lowlight;
}
}
}🔄 Mode Switching Behavior
// Starting state: everything normal (undefined)
// Initial: all entities = undefined
graph.highlight({ block: ['A', 'B'] });
// Result: A=Highlight(20), B=Highlight(20), others=undefined
graph.focus({ block: ['C'] }); // ← REPLACES previous state!
// Result: C=Highlight(20), A=Lowlight(10), B=Lowlight(10), others=Lowlight(10)
graph.highlight({ connection: ['X'] }); // ← REPLACES focus!
// Result: X=Highlight(20), A=undefined, B=undefined, C=undefined, others=undefined6. Event System
// React to highlight changes
graph.on('highlight-changed', (event) => {
console.log('Mode:', event.mode); // 'highlight' | 'focus' | undefined
console.log('Entities:', event.entities); // ['block:id1', 'connection:id2']
console.log('Previous:', event.previous); // Previous state
});🚀 Usage Examples
Side-by-Side Mode Comparison
Scenario: Graph with 5 nodes (A, B, C, D, E)
// BEFORE: All nodes normal
// A=undefined, B=undefined, C=undefined, D=undefined, E=undefined
// =====================================================
// 🎯 HIGHLIGHT MODE - Selective emphasis
// =====================================================
graph.highlight({ block: ['A', 'B'] });
// RESULT: Only targets change, others UNTOUCHED
// A=Highlight(20) ← highlighted
// B=Highlight(20) ← highlighted
// C=undefined ← normal (unchanged)
// D=undefined ← normal (unchanged)
// E=undefined ← normal (unchanged)// =====================================================
// 🔍 FOCUS MODE - Dramatic spotlight effect
// =====================================================
graph.focus({ block: ['A', 'B'] });
// RESULT: Targets highlighted, EVERYTHING ELSE dimmed
// A=Highlight(20) ← highlighted
// B=Highlight(20) ← highlighted
// C=Lowlight(10) ← dimmed ⚠️
// D=Lowlight(10) ← dimmed ⚠️
// E=Lowlight(10) ← dimmed ⚠️Real-World Impact
| Graph Size | highlight() Changes |
focus() Changes |
|---|---|---|
| 10 entities | 2 entities | 10 entities (all!) |
| 100 entities | 5 entities | 100 entities (all!) |
| 1000 entities | 10 entities | 1000 entities (all!) |
📊 Performance: highlight() scales with targets, focus() scales with total graph size!
Visual State Diagram
INITIAL STATE: All entities normal
┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐
│ A │ │ B │ │ C │ │ D │ │ E │
└───┘ └───┘ └───┘ └───┘ └───┘
⚪ ⚪ ⚪ ⚪ ⚪ (all undefined)
graph.highlight({ block: ['A', 'B'] });
┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐
│ A │ │ B │ │ C │ │ D │ │ E │
└───┘ └───┘ └───┘ └───┘ └───┘
🟢 🟢 ⚪ ⚪ ⚪ ← Only targets change!
graph.focus({ block: ['A', 'B'] });
┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐
│ A │ │ B │ │ C │ │ D │ │ E │
└───┘ └───┘ └───┘ └───┘ └───┘
🟢 🟢 🔴 🔴 🔴 ← ALL entities change!
Legend:
🟢 Highlight(20) 🔴 Lowlight(10) ⚪ Normal(undefined)
External Integration
// Analytics integration
graph.on('highlight-changed', (event) => {
analytics.track('graph_highlight', {
mode: event.mode,
entityCount: event.entities.length
});
event.preventDefault(); // if you want to prevent default behavior
});Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request