Skip to content

Commit

Permalink
feat: improved perfomance, decreased rerenders, added color support f…
Browse files Browse the repository at this point in the history
…or links
  • Loading branch information
Rassl committed Oct 28, 2024
1 parent fdd2f18 commit 5f1a175
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 104 deletions.
64 changes: 33 additions & 31 deletions src/components/Universe/Graph/Connections/LineComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,44 @@
import { Line } from '@react-three/drei'
import gsap from 'gsap'
import { forwardRef, useEffect } from 'react'
import { memo, useEffect, useRef } from 'react'
import { Color, Vector3 } from 'three'
import { Line2 } from 'three-stdlib'

type LineComponentProps = {
source: Vector3
target: Vector3
isSelected: boolean
lineWidth: number
visible: boolean
}

const LineComponent = forwardRef<Line2, LineComponentProps>(
({ source, target, isSelected, lineWidth, visible }, ref) => {
useEffect(() => {
const line = (ref as React.MutableRefObject<Line2 | null>).current

if (line) {
gsap.fromTo(
line.material,
{ linewidth: 5 },
{
linewidth: isSelected ? 2 : lineWidth,
duration: 1,
},
)
}
}, [isSelected, lineWidth, ref])

const color = new Color(0xff0000)

return (
<Line ref={ref} color={color} isLine2 lineWidth={2} opacity={0.5} points={[source, target]} visible={visible} />
)
},
)

LineComponent.displayName = 'LineComponent'

export default LineComponent
const VECTOR = new Vector3(0, 0, 0)

// eslint-disable-next-line no-underscore-dangle
export const _LineComponent = (props: LineComponentProps) => {
const { isSelected, lineWidth, visible } = props
const ref = useRef<Line2>(null)

useEffect(() => {
const line = (ref as React.MutableRefObject<Line2 | null>).current

if (line) {
gsap.fromTo(
line.material,
{ linewidth: 5 },
{
linewidth: isSelected ? 2 : lineWidth,
duration: 1,
},
)
}
}, [isSelected, lineWidth, ref])

const color = new Color(0xff0000)

return (
<Line ref={ref} color={color} isLine2 lineWidth={2} opacity={0.5} points={[VECTOR, VECTOR]} visible={visible} />
)
}

_LineComponent.displayName = 'LineComponent'

export const LineComponent = memo(_LineComponent)
41 changes: 6 additions & 35 deletions src/components/Universe/Graph/Connections/index.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,25 @@
import { memo, useRef } from 'react'
import { Vector3 } from 'three'
import { Line2 } from 'three-stdlib'
import { memo } from 'react'
import { useDataStore } from '~/stores/useDataStore'
import { useGraphStore, useSelectedNode } from '~/stores/useGraphStore'
import { Link } from '~/types'
import { LinkPosition } from '..'
import LineComponent from './LineComponent'
import { LineComponent } from './LineComponent'

type Props = {
linksPositions: LinkPosition[]
}

export const Connections = memo(({ linksPositions }: Props) => {
export const Connections = memo(() => {
const data = useDataStore((s) => s.dataInitial)
const { showSelectionGraph } = useGraphStore((s) => s)
const selectedNode = useSelectedNode()

const lineRefs = useRef<Line2[]>([])
console.log('connection')

Check warning on line 12 in src/components/Universe/Graph/Connections/index.tsx

View workflow job for this annotation

GitHub Actions / eslint-run

Unexpected console statement

return (
<group name="simulation-3d-group__connections">
{data?.links.map((l: Link, index) => {
const source = new Vector3(
linksPositions[index]?.sx || 0,
linksPositions[index]?.sy || 0,
linksPositions[index]?.sz || 0,
)

const target = new Vector3(
linksPositions[index]?.tx || 0,
linksPositions[index]?.ty || 0,
linksPositions[index]?.tz || 0,
)

{data?.links.map((l: Link) => {
const isSelected = selectedNode?.ref_id === l.source || selectedNode?.ref_id === l.target

const lineWidth = selectedNode ? 0 : 0.5

return (
<LineComponent
key={l.ref_id}
ref={(el) => {
lineRefs.current[index] = el as Line2
}}
isSelected={isSelected}
lineWidth={lineWidth}
source={source}
target={target}
visible={!showSelectionGraph}
/>
<LineComponent key={l.ref_id} isSelected={isSelected} lineWidth={lineWidth} visible={!showSelectionGraph} />
)
})}
</group>
Expand Down
3 changes: 1 addition & 2 deletions src/components/Universe/Graph/Cubes/NodePoints/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const _NodePoints = () => {
const data = useDataStore((s) => s.dataInitial)
const { normalizedSchemasByType } = useSchemaStore((s) => s)
const nodeTypes = useNodeTypes()
const ringGeometry = useMemo(() => new TorusGeometry(30, 4, 16, 100), [])
const ringGeometry = useMemo(() => new TorusGeometry(30, 2, 16, 100), [])

return (
<>
Expand All @@ -53,7 +53,6 @@ const _NodePoints = () => {
limit={1000} // Optional: max amount of items (for calculating buffer size)
range={1000}
visible={!selectedNode}
// Optional: draw-range
>
<meshStandardMaterial />
{data?.nodes.map((node: NodeExtended) => {
Expand Down
19 changes: 15 additions & 4 deletions src/components/Universe/Graph/Cubes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const POINTER_IN_DELAY = 200
export const Cubes = memo(() => {
const selectedNode = useSelectedNode()
const hoveredNode = useHoveredNode()

const relativeIds = useSelectedNodeRelativeIds()
const { selectionGraphData, showSelectionGraph, setHoveredNode, setIsHovering } = useGraphStore((s) => s)

Expand Down Expand Up @@ -56,30 +57,40 @@ export const Cubes = memo(() => {
const onPointerOut = useCallback(
(e: ThreeEvent<PointerEvent>) => {
e.stopPropagation()
setIsHovering(false)
setHoveredNode(null)

if (hoverTimeoutRef.current) {
clearTimeout(hoverTimeoutRef.current)
hoverTimeoutRef.current = null
}

if (!hoveredNode) {
return
}

setIsHovering(false)
setHoveredNode(null)
},
[setIsHovering, setHoveredNode],
[setIsHovering, setHoveredNode, hoveredNode],
)

const onPointerIn = useCallback(
(e: ThreeEvent<PointerEvent>) => {
const objects = e.intersections.map((i) => i.object)
const object = objects[0]

if (hoverTimeoutRef.current) {
clearTimeout(hoverTimeoutRef.current)
hoverTimeoutRef.current = null
}

if (object?.userData?.ref_id) {
const node = object.userData as NodeExtended

if (!ignoreNodeEvent(node)) {
e.stopPropagation()
setIsHovering(true)

hoverTimeoutRef.current = setTimeout(() => {
setIsHovering(true)
setHoveredNode(node)
}, POINTER_IN_DELAY)
}
Expand Down
72 changes: 40 additions & 32 deletions src/components/Universe/Graph/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Box3, Color, Group, Sphere, Vector3 } from 'three'
import { Line2 } from 'three-stdlib'
import { useDataStore } from '~/stores/useDataStore'
import { useGraphStore } from '~/stores/useGraphStore'
import { useSchemaStore } from '~/stores/useSchemaStore'
import { NodeExtended } from '~/types'
import { Connections } from './Connections'
import { Cubes } from './Cubes'
Expand All @@ -25,6 +26,7 @@ export const Graph = () => {
const groupRef = useRef<Group>(null)
const cameraSettled = useRef<boolean>(false)
const linksPositionRef = useRef<LinkPosition[]>([])
const { normalizedSchemasByType } = useSchemaStore((s) => s)

const { setData, simulation, simulationCreate, simulationHelpers, graphStyle, setGraphRadius } = useGraphStore(
(s) => s,
Expand Down Expand Up @@ -88,13 +90,15 @@ export const Graph = () => {
const grPoints = groupRef.current.getObjectByName('simulation-3d-group__node-points') as Group
const grConnections = groupRef.current.getObjectByName('simulation-3d-group__connections') as Group

gr.children.forEach((mesh, index) => {
const simulationNode = simulation.nodes()[index]
if (gr) {
gr.children.forEach((mesh, index) => {
const simulationNode = simulation.nodes()[index]

if (simulationNode) {
mesh.position.set(simulationNode.x, simulationNode.y, simulationNode.z)
}
})
if (simulationNode) {
mesh.position.set(simulationNode.x, simulationNode.y, simulationNode.z)
}
})
}

if (grPoints) {
grPoints.children[0].children.forEach((mesh, index) => {
Expand All @@ -106,35 +110,39 @@ export const Graph = () => {
})
}

grConnections.children.forEach((r, i) => {
const link = dataInitial?.links[i]
const Line = r as Line2
if (grConnections) {
grConnections.children.forEach((r, i) => {
const link = dataInitial?.links[i]
const Line = r as Line2

if (link) {
const sourceNode = simulation.nodes().find((n: NodeExtended) => n.ref_id === link.source)
const targetNode = simulation.nodes().find((n: NodeExtended) => n.ref_id === link.target)
if (link) {
const sourceNode = simulation.nodes().find((n: NodeExtended) => n.ref_id === link.source)
const targetNode = simulation.nodes().find((n: NodeExtended) => n.ref_id === link.target)

const { x: sx, y: sy, z: sz } = sourceNode
const { x: tx, y: ty, z: tz } = targetNode
const { x: sx, y: sy, z: sz } = sourceNode
const { x: tx, y: ty, z: tz } = targetNode

linksPositionRef.current[i] = {
sx,
sy,
sz,
tx,
ty,
tz,
}
linksPositionRef.current[i] = {
sx,
sy,
sz,
tx,
ty,
tz,
}

const lineColor = normalizedSchemasByType[sourceNode.node_type]?.primary_color || 'white'

Line.geometry.setPositions([sx, sy, sz, tx, ty, tz])
Line.geometry.setPositions([sx, sy, sz, tx, ty, tz])

const { material } = Line
const { material } = Line

material.color = new Color('white')
material.transparent = true
material.opacity = 0.1
}
})
material.color = new Color(lineColor)
material.transparent = true
material.opacity = 0.2
}
})
}
}
})

Expand All @@ -153,7 +161,7 @@ export const Graph = () => {

cameraSettled.current = false
})
}, [dataInitial, simulation, setGraphRadius])
}, [dataInitial, simulation, setGraphRadius, normalizedSchemasByType])

if (!simulation) {
return null
Expand All @@ -162,11 +170,11 @@ export const Graph = () => {
return (
<group ref={groupRef}>
<Cubes />
<Earth />
{graphStyle === 'earth' && <Earth />}

{(isLoadingNew || isFetching) && <LoadingNodes />}

{graphStyle !== 'earth' && <Connections linksPositions={linksPositionRef.current} />}
{graphStyle !== 'earth' && <Connections />}
<NodeDetailsPanel />
</group>
)
Expand Down
24 changes: 24 additions & 0 deletions src/hooks/useTraceUpdate/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// @ts-nocheck
// @ts-ignore

import { useEffect, useRef } from 'react'

export function useTraceUpdate(props: { [s: string]: unknown } | ArrayLike<unknown>) {
const prev = useRef(props)

useEffect(() => {
const changedProps = Object.entries(props).reduce((ps, [k, v]) => {
if (prev.current[k] !== v) {
ps[k] = [prev.current[k], v]

Check failure on line 12 in src/hooks/useTraceUpdate/index.ts

View workflow job for this annotation

GitHub Actions / eslint-run

Assignment to property of function parameter 'ps'
}

return ps
}, {})

if (Object.keys(changedProps).length > 0) {
console.log('Changed props:', changedProps)
}

prev.current = props
})
}

0 comments on commit 5f1a175

Please sign in to comment.