-
Notifications
You must be signed in to change notification settings - Fork 50
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
Showing
11 changed files
with
1,152 additions
and
22 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
123 changes: 123 additions & 0 deletions
123
browser/data-browser/src/chunks/GraphViewer/FloatingEdge.tsx
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,123 @@ | ||
import React, { useCallback } from 'react'; | ||
import { | ||
useStore as useFlowStore, | ||
getBezierPath, | ||
EdgeText, | ||
EdgeProps, | ||
Node, | ||
} from 'reactflow'; | ||
import styled, { useTheme } from 'styled-components'; | ||
import { getEdgeParams, getSelfReferencePath } from './getEdgeParams'; | ||
import { EdgeData } from './buildGraph'; | ||
|
||
const getPathData = ( | ||
sourceNode: Node, | ||
targetNode: Node, | ||
overlapping: boolean, | ||
) => { | ||
// Self referencing edges use a custom path. | ||
if (sourceNode.id === targetNode.id) { | ||
return getSelfReferencePath(sourceNode); | ||
} | ||
|
||
const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams( | ||
sourceNode, | ||
targetNode, | ||
overlapping, | ||
); | ||
|
||
return getBezierPath({ | ||
sourceX: sx, | ||
sourceY: sy, | ||
sourcePosition: sourcePos, | ||
targetPosition: targetPos, | ||
targetX: tx, | ||
targetY: ty, | ||
}); | ||
}; | ||
|
||
function Label({ text }: { text: string }): JSX.Element | string { | ||
const parts = text.split('\n'); | ||
|
||
if (parts.length === 1) { | ||
return text; | ||
} | ||
|
||
// SVG does not have any auto word wrap so we split the lines manually and offset them. | ||
return ( | ||
<> | ||
{parts.map((part, i) => ( | ||
<tspan x={0} dy={i === 0 ? '-0.3em' : '1.2em'} key={part}> | ||
{part} | ||
</tspan> | ||
))} | ||
</> | ||
); | ||
} | ||
|
||
/** | ||
* A custom edge that doesn't clutter the graph as mutch as the default edge. | ||
* It casts a ray from the center of the source node to the center of the target node then draws a bezier curve between the two intersecting border of the nodes. | ||
*/ | ||
export function FloatingEdge({ | ||
id, | ||
source, | ||
target, | ||
markerEnd, | ||
style, | ||
label, | ||
data, | ||
}: EdgeProps<EdgeData>) { | ||
const theme = useTheme(); | ||
const sourceNode = useFlowStore( | ||
useCallback(store => store.nodeInternals.get(source), [source]), | ||
); | ||
const targetNode = useFlowStore( | ||
useCallback(store => store.nodeInternals.get(target), [target]), | ||
); | ||
|
||
if (!sourceNode || !targetNode) { | ||
return null; | ||
} | ||
|
||
const [path, labelX, labelY] = getPathData( | ||
sourceNode, | ||
targetNode, | ||
!!data?.overlapping, | ||
); | ||
|
||
return ( | ||
<> | ||
<Path | ||
id={id} | ||
className='react-flow__edge-path' | ||
d={path} | ||
markerEnd={markerEnd} | ||
style={style} | ||
/> | ||
<EdgeText | ||
x={labelX} | ||
y={labelY} | ||
label={<Label text={label as string} />} | ||
labelStyle={{ | ||
fill: theme.colors.text, | ||
}} | ||
labelShowBg | ||
labelBgStyle={{ fill: theme.colors.bg1 }} | ||
labelBgPadding={[2, 4]} | ||
labelBgBorderRadius={2} | ||
/> | ||
</> | ||
); | ||
} | ||
|
||
const Path = styled.path` | ||
flex-direction: column; | ||
display: flex; | ||
flex-grow: 1; | ||
height: 100%; | ||
& .react-flow__handle { | ||
opacity: 0; | ||
} | ||
`; |
75 changes: 75 additions & 0 deletions
75
browser/data-browser/src/chunks/GraphViewer/OntologyGraph.tsx
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,75 @@ | ||
import { Resource, useStore } from '@tomic/react'; | ||
import React, { useCallback } from 'react'; | ||
import ReactFlow, { | ||
Controls, | ||
useReactFlow, | ||
Node, | ||
ReactFlowProvider, | ||
} from 'reactflow'; | ||
import 'reactflow/dist/style.css'; | ||
import './reactFlowOverrides.css'; | ||
import { buildGraph } from './buildGraph'; | ||
import { FloatingEdge } from './FloatingEdge'; | ||
import { useGraph } from './useGraph'; | ||
import { useEffectOnce } from '../../hooks/useEffectOnce'; | ||
import { toAnchorId } from '../../views/OntologyPage/toAnchorId'; | ||
|
||
const edgeTypes = { | ||
floating: FloatingEdge, | ||
}; | ||
|
||
interface OntologyGraphProps { | ||
ontology: Resource; | ||
} | ||
|
||
/** | ||
* !ASYNC COMPONENT, DO NOT IMPORT DIRECTLY! | ||
* Displays an ontology as a graph. | ||
*/ | ||
export default function OntologyGraph({ | ||
...props | ||
}: OntologyGraphProps): JSX.Element { | ||
return ( | ||
<ReactFlowProvider> | ||
<OntologyGraphInner {...props} /> | ||
</ReactFlowProvider> | ||
); | ||
} | ||
|
||
function OntologyGraphInner({ ontology }: OntologyGraphProps): JSX.Element { | ||
const store = useStore(); | ||
const { fitView } = useReactFlow(); | ||
|
||
const { nodes, edges, setGraph, handleNodeChange, handleNodeDoubleClick } = | ||
useGraph(ontology); | ||
|
||
useEffectOnce(() => { | ||
buildGraph(ontology, store).then(([n, e]) => { | ||
setGraph(n, e); | ||
|
||
requestAnimationFrame(() => { | ||
fitView(); | ||
}); | ||
}); | ||
}); | ||
|
||
const handleClick = useCallback((_: React.MouseEvent, node: Node) => { | ||
const domId = toAnchorId(node.id); | ||
|
||
document.getElementById(domId)?.scrollIntoView({ behavior: 'smooth' }); | ||
}, []); | ||
|
||
return ( | ||
<ReactFlow | ||
fitView | ||
nodes={nodes} | ||
edges={edges} | ||
edgeTypes={edgeTypes} | ||
onNodesChange={handleNodeChange} | ||
onNodeClick={handleClick} | ||
onNodeDoubleClick={handleNodeDoubleClick} | ||
> | ||
<Controls position='top-left' showInteractive={false} /> | ||
</ReactFlow> | ||
); | ||
} |
Oops, something went wrong.