generated from TBD54566975/tbd-project-template
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3adc0de
commit c349771
Showing
7 changed files
with
488 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,270 @@ | ||
import cytoscape from 'cytoscape' | ||
import dagre, { type DagreLayoutOptions } from 'cytoscape-dagre' | ||
import { useEffect, useRef, useState } from 'react' | ||
import type React from 'react' | ||
import { useStreamModules } from '../../api/modules/use-stream-modules' | ||
import type { FTLNode } from './GraphPane' | ||
import { getGraphData } from './graph-utils' | ||
|
||
cytoscape.use(dagre) | ||
|
||
interface NewGraphPaneProps { | ||
onTapped?: (item: FTLNode | null) => void | ||
} | ||
|
||
export const NewGraphPane: React.FC<NewGraphPaneProps> = () => { | ||
// Add state for tracking current group view | ||
// const [currentGroup, setCurrentGroup] = useState<string | null>(null) | ||
|
||
const modules = useStreamModules() | ||
|
||
// Modify static data to include children | ||
const staticData = { | ||
modules: [ | ||
{ | ||
id: 'mod1', | ||
title: 'Module 1', | ||
type: 'groupNode', | ||
children: [ | ||
{ id: 'mod1-1', title: 'Sub Module 1.1', type: 'node' }, | ||
{ id: 'mod1-2', title: 'Sub Module 1.2', type: 'node' }, | ||
], | ||
childConnections: [{ source: 'mod1-1', target: 'mod1-2' }], | ||
}, | ||
{ | ||
id: 'mod2', | ||
title: 'Module 2', | ||
type: 'groupNode', | ||
children: [ | ||
{ id: 'mod2-1', title: 'Sub Module 2.1', type: 'node' }, | ||
{ id: 'mod2-2', title: 'Sub Module 2.2', type: 'node' }, | ||
{ id: 'mod2-3', title: 'Sub Module 2.3', type: 'node' }, | ||
], | ||
childConnections: [{ source: 'mod2-1', target: 'mod2-2' }], | ||
}, | ||
{ | ||
id: 'mod3', | ||
title: 'Module 3', | ||
type: 'groupNode', | ||
children: [ | ||
{ id: 'mod3-1', title: 'Sub Module 3.1', type: 'node' }, | ||
{ id: 'mod3-2', title: 'Sub Module 3.2', type: 'node' }, | ||
], | ||
childConnections: [{ source: 'mod3-1', target: 'mod3-2' }], | ||
}, | ||
{ | ||
id: 'mod4', | ||
title: 'Module 4', | ||
type: 'groupNode', | ||
children: [ | ||
{ id: 'mod4-1', title: 'Sub Module 4.1', type: 'node' }, | ||
{ id: 'mod4-2', title: 'Sub Module 4.2', type: 'node' }, | ||
], | ||
childConnections: [{ source: 'mod4-1', target: 'mod4-2' }], | ||
}, | ||
], | ||
topology: { | ||
connections: [ | ||
// { source: 'mod1', target: 'mod2' }, | ||
{ source: 'mod2', target: 'mod4' }, | ||
{ source: 'mod3', target: 'mod4' }, | ||
{ source: 'mod2-1', target: 'mod1-1' }, | ||
{ source: 'mod2-2', target: 'mod1-1' }, | ||
], | ||
}, | ||
} | ||
|
||
const cyRef = useRef<HTMLDivElement>(null) | ||
const cyInstance = useRef<cytoscape.Core | null>(null) | ||
// const [, setSelectedNode] = React.useState<FTLNode | null>(null) | ||
const [nodePositions, setNodePositions] = useState<Record<string, { x: number; y: number }>>({}) | ||
|
||
// Initialize Cytoscape | ||
useEffect(() => { | ||
if (!cyRef.current) return | ||
|
||
cyInstance.current = cytoscape({ | ||
container: cyRef.current, | ||
userZoomingEnabled: true, | ||
userPanningEnabled: true, | ||
boxSelectionEnabled: false, | ||
style: [ | ||
{ | ||
selector: 'node', | ||
style: { | ||
'background-color': '#64748b', | ||
label: 'data(label)', | ||
'text-valign': 'center', | ||
'text-halign': 'center', | ||
shape: 'round-rectangle', | ||
width: '120px', | ||
height: '40px', | ||
'text-wrap': 'wrap', | ||
'text-max-width': '100px', | ||
'text-overflow-wrap': 'anywhere', | ||
'font-size': '12px', | ||
}, | ||
}, | ||
{ | ||
selector: 'edge', | ||
style: { | ||
width: 2, | ||
'line-color': '#6366f1', | ||
'curve-style': 'bezier', | ||
'target-arrow-shape': 'triangle', | ||
'target-arrow-color': '#6366f1', | ||
'arrow-scale': 1, | ||
}, | ||
}, | ||
{ | ||
selector: '$node > node', | ||
style: { | ||
'padding-top': '10px', | ||
'padding-left': '10px', | ||
'padding-bottom': '10px', | ||
'padding-right': '10px', | ||
'text-valign': 'top', | ||
'text-halign': 'center', | ||
'background-color': '#94a3b8', | ||
}, | ||
}, | ||
{ | ||
selector: 'node[type="groupNode"]', | ||
style: { | ||
'background-color': '#6366f1', | ||
'background-opacity': 0.8, | ||
shape: 'round-rectangle', | ||
width: '120px', | ||
height: '120px', | ||
'text-valign': 'top', // Default position at top | ||
'text-halign': 'center', | ||
'text-wrap': 'wrap', | ||
'text-max-width': '100px', | ||
'text-overflow-wrap': 'anywhere', | ||
'font-size': '14px', | ||
}, | ||
}, | ||
{ | ||
selector: ':parent', | ||
style: { | ||
'text-valign': 'top', | ||
'text-halign': 'center', | ||
'background-opacity': 0.3, | ||
}, | ||
}, | ||
{ | ||
selector: '.selected', | ||
style: { | ||
'background-color': '#3b82f6', | ||
'border-width': 2, | ||
'border-color': '#60a5fa', | ||
}, | ||
}, | ||
{ | ||
selector: 'node[type="node"]', | ||
style: { | ||
'background-color': 'data(backgroundColor)', | ||
shape: 'round-rectangle', | ||
width: '100px', | ||
height: '30px', | ||
'border-width': '1px', | ||
'border-color': '#475569', | ||
'text-wrap': 'wrap', | ||
'text-max-width': '80px', | ||
'text-overflow-wrap': 'anywhere', | ||
'font-size': '11px', | ||
}, | ||
}, | ||
], | ||
}) | ||
|
||
// Update zoom level event handler | ||
cyInstance.current.on('zoom', (evt) => { | ||
const zoom = evt.target.zoom() | ||
const elements = evt.target.elements() | ||
|
||
if (zoom < 1) { | ||
// Hide child nodes | ||
elements.nodes('node[type != "groupNode"]').style('opacity', 0) | ||
|
||
// Show only module-level edges (type="moduleConnection") | ||
elements.edges('[type = "moduleConnection"]').style('opacity', 1) | ||
elements.edges('[type = "childConnection"]').style('opacity', 0) | ||
|
||
// Move text inside and make it larger when zoomed out | ||
elements.nodes('node[type = "groupNode"]').style({ | ||
'text-valign': 'center', | ||
'text-halign': 'center', | ||
'font-size': '18px', | ||
'text-max-width': '100px', | ||
}) | ||
} else { | ||
// Show all nodes | ||
elements.nodes().style('opacity', 1) | ||
|
||
// Show only verb-level edges (type="childConnection") | ||
elements.edges('[type = "moduleConnection"]').style('opacity', 0) | ||
elements.edges('[type = "childConnection"]').style('opacity', 1) | ||
|
||
// Move text to top when zoomed in | ||
elements.nodes('node[type = "groupNode"]').style({ | ||
'text-valign': 'top', // Move text to top | ||
'text-halign': 'center', // Keep text centered horizontally | ||
'font-size': '14px', // Original font size | ||
'text-max-width': '100px', | ||
}) | ||
} | ||
}) | ||
|
||
return () => { | ||
cyInstance.current?.destroy() | ||
} | ||
}, []) | ||
|
||
// Modify the data loading effect | ||
useEffect(() => { | ||
if (!cyInstance.current) return | ||
|
||
const elements = getGraphData(modules.data, nodePositions) | ||
cyInstance.current.elements().remove() | ||
cyInstance.current.add(elements) | ||
|
||
// Run layout if needed | ||
if (staticData.modules.some((module) => !nodePositions[module.id])) { | ||
const layoutOptions: DagreLayoutOptions = { | ||
name: 'dagre', | ||
rankDir: 'TB', | ||
// ranker: 'network-simplex', | ||
nodeDimensionsIncludeLabels: true, | ||
rankSep: 5, | ||
nodeSep: 5, | ||
edgeSep: 20, | ||
padding: 5, | ||
ranker: 'tight-tree', | ||
spacingFactor: 2, | ||
} | ||
|
||
const layout = cyInstance.current.layout(layoutOptions) | ||
layout.run() | ||
|
||
// Save positions after layout is complete | ||
layout.on('layoutstop', () => { | ||
const newPositions = { ...nodePositions } | ||
for (const node of cyInstance.current?.nodes() || []) { | ||
newPositions[node.id()] = node.position() | ||
} | ||
setNodePositions(newPositions) | ||
}) | ||
} | ||
|
||
cyInstance.current.fit() | ||
}, [nodePositions, modules.data]) | ||
|
||
// Modify event handlers | ||
|
||
return ( | ||
<div style={{ width: '100%', height: '100%', position: 'relative' }}> | ||
<div ref={cyRef} style={{ width: '100%', height: '100%' }} /> | ||
</div> | ||
) | ||
} |
Oops, something went wrong.