diff --git a/src/components/Universe/Controls/CameraAnimations/index.ts b/src/components/Universe/Controls/CameraAnimations/index.ts index e9f04569f..bc8fca123 100644 --- a/src/components/Universe/Controls/CameraAnimations/index.ts +++ b/src/components/Universe/Controls/CameraAnimations/index.ts @@ -24,7 +24,7 @@ export const useCameraAnimations = ( const isUserDragging = useControlStore((s) => s.isUserDragging) - const { graphStyle, graphRadius, disableCameraRotation } = useGraphStore((s) => s) + const { graphRadius, disableCameraRotation } = useGraphStore((s) => s) useEffect(() => { if (!enabled) { @@ -33,22 +33,24 @@ export const useCameraAnimations = ( } }, [enabled]) - useEffect(() => { - if (cameraControlsRef.current && graphRadius) { - if (graphStyle === 'sphere') { - cameraControlsRef.current.maxDistance = 8000 - cameraControlsRef.current.minDistance = 200 - cameraControlsRef.current.setTarget(0, 0, 500, true) - } else { - cameraControlsRef.current.maxDistance = cameraControlsRef.current.getDistanceToFitSphere(graphRadius + 200) - cameraControlsRef.current.minDistance = 100 - } - } - }, [graphRadius, graphStyle, cameraControlsRef]) + // useEffect(() => { + // if (cameraControlsRef.current && graphRadius) { + // cameraControlsRef.current.maxDistance = cameraControlsRef.current.getDistanceToFitSphere(graphRadius + 200) + // cameraControlsRef.current.minDistance = 100 + // } + // }, [graphRadius, cameraControlsRef]) useEffect(() => { if (!selectedNode && cameraControlsRef.current) { - cameraControlsRef.current.setLookAt(initialCameraPosition.x, initialCameraPosition.y, graphRadius, 0, 0, 0, true) + cameraControlsRef.current.setLookAt( + initialCameraPosition.x, + initialCameraPosition.y, + graphRadius + 200, + 0, + 0, + 0, + true, + ) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedNode, graphRadius]) diff --git a/src/components/Universe/Graph/Connections/LineComponent.tsx b/src/components/Universe/Graph/Connections/LineComponent.tsx index 8c9b9177a..92737f371 100644 --- a/src/components/Universe/Graph/Connections/LineComponent.tsx +++ b/src/components/Universe/Graph/Connections/LineComponent.tsx @@ -5,6 +5,7 @@ import { memo, useEffect, useRef } from 'react' import { Line2 } from 'three-stdlib' import { useGraphStore } from '~/stores/useGraphStore' import { LINE_WIDTH } from '../../constants' +import { fontProps } from '../Cubes/Text/constants' type LineComponentProps = { label: string @@ -78,8 +79,8 @@ const _LineComponent = (props: LineComponentProps) => { points={[sourceX, sourceY, sourceZ, targetX, targetY, targetZ]} /> - - {label}1 + + {label} diff --git a/src/components/Universe/Graph/Cubes/SelectionDataNodes/Connections/Connection/index.tsx b/src/components/Universe/Graph/Cubes/SelectionDataNodes/Connections/Connection/index.tsx index 7372715c3..8c5f202d5 100644 --- a/src/components/Universe/Graph/Cubes/SelectionDataNodes/Connections/Connection/index.tsx +++ b/src/components/Universe/Graph/Cubes/SelectionDataNodes/Connections/Connection/index.tsx @@ -1,6 +1,7 @@ import { Line, Text } from '@react-three/drei' import { memo, useRef } from 'react' import { Line2 } from 'three-stdlib' +import { fontProps } from '../../../Text/constants' type LineComponentProps = { label: string @@ -29,7 +30,9 @@ const _Connection = (props: LineComponentProps) => { points={[sourceX, sourceY, sourceZ, targetX, targetY, targetZ]} /> - + + + {label} diff --git a/src/components/Universe/Graph/Cubes/SelectionDataNodes/Node/index.tsx b/src/components/Universe/Graph/Cubes/SelectionDataNodes/Node/index.tsx index 3f04d8abf..1f07b1446 100644 --- a/src/components/Universe/Graph/Cubes/SelectionDataNodes/Node/index.tsx +++ b/src/components/Universe/Graph/Cubes/SelectionDataNodes/Node/index.tsx @@ -57,12 +57,16 @@ export const Node = ({ onClick, node, selected, rounded = true }: Props) => { ) } -const Wrapper = styled(Flex)`` +const Wrapper = styled(Flex)` + background: black; +` const Text = styled(Flex)` color: ${colors.white}; margin-left: 16px; font-weight: 700; + width: 100px; + font-size: 16px; ` const Tag = styled(Flex)` @@ -90,8 +94,11 @@ const Tag = styled(Flex)` ` const Selected = styled(Tag)` - width: 300px; - height: 150px; + width: 200px; + height: 100px; + flex-direction: row; + justify-content: center; + align-items: center; ` const IconButton = styled(Flex)` diff --git a/src/components/Universe/Graph/Cubes/SelectionDataNodes/index.tsx b/src/components/Universe/Graph/Cubes/SelectionDataNodes/index.tsx index 4c4e693e2..b8e52c5fe 100644 --- a/src/components/Universe/Graph/Cubes/SelectionDataNodes/index.tsx +++ b/src/components/Universe/Graph/Cubes/SelectionDataNodes/index.tsx @@ -1,25 +1,26 @@ import { Html } from '@react-three/drei' import { forceLink, forceManyBody, forceRadial, forceSimulation } from 'd3-force-3d' -import { memo, useCallback, useEffect, useRef, useState } from 'react' +import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { Box3, Color, Group, Sphere, Vector3 } from 'three' import { Line2 } from 'three-stdlib' import { useShallow } from 'zustand/react/shallow' import { usePrevious } from '~/hooks/usePrevious' +import { fetchNodeEdges } from '~/network/fetchGraphData' import { useDataStore } from '~/stores/useDataStore' import { useGraphStore, useSelectedNode, useSelectedNodeRelativeIds } from '~/stores/useGraphStore' import { useSchemaStore } from '~/stores/useSchemaStore' import { ForceSimulation } from '~/transformers/forceSimulation' -import { GraphData, Link, NodeExtended } from '~/types' +import { GraphData, Link, Node, NodeExtended } from '~/types' import { LinkPosition } from '../..' import { Connections } from './Connections' -import { Node } from './Node' +import { Node as GraphNode } from './Node' const MAX_LENGTH = 6 export const SelectionDataNodes = memo(() => { const [simulation2d, setSimulation2D] = useState(null) - const { dataInitial } = useDataStore((s) => s) + const { dataInitial, nodesNormalized, addNewNode } = useDataStore((s) => s) const selectedNode = useSelectedNode() const groupRef = useRef(null) @@ -31,28 +32,87 @@ export const SelectionDataNodes = memo(() => { const { normalizedSchemasByType } = useSchemaStore((s) => s) - const { selectionGraphData, setSelectionData, setSelectedNode, setSelectionGraphRadius } = useGraphStore( - useShallow((s) => s), - ) + const { selectionGraphData, selectionPath, setSelectionData, setSelectedNode, setSelectionGraphRadius } = + useGraphStore(useShallow((s) => s)) + + const pathNodes = useMemo(() => { + const nodes: NodeExtended[] = selectionPath + .slice(-3, -1) + .filter((id) => !!nodesNormalized.get(id)) + .map((i, index) => { + const node = nodesNormalized.get(i) as unknown as Node + + return { ...node, fx: 0, fy: -(index + 1) * 200, fz: 0, x: 0, y: 0, z: 0 } + }) + + return nodes + }, [nodesNormalized, selectionPath]) + + useEffect(() => { + const init = async () => { + if (selectedNode?.ref_id && selectedNode.ref_id !== prevSelectedNodeId) { + try { + const data = await fetchNodeEdges(selectedNode.ref_id, 0, 5) + + if (data) { + const filteredNodes: NodeExtended[] = data.nodes.filter( + (node, index) => node.ref_id !== selectedNode.ref_id && index < 7, + ) + + const graphNodes = filteredNodes.map((node: Node) => ({ ...node, x: 0, y: 0, z: 0 })) + + const nodes: NodeExtended[] = [ + ...graphNodes, + { ...selectedNode, x: 0, y: 0, z: 0, fx: 0, fy: 0, fz: 0 } as NodeExtended, + ] + + const links = data.edges.filter( + (link: Link) => + nodes.some((node: NodeExtended) => node.ref_id === link.target) && + nodes.some((node: NodeExtended) => node.ref_id === link.source), + ) + + setSelectionData({ nodes, links: links as unknown as GraphData['links'] }) + setSimulation2D(null) + linksPositionRef.current = new Map() + + // + + addNewNode({ nodes: filteredNodes, edges: links }) + } + } catch (error) { + console.error(error) + } + } + } + + if (selectedNode) { + init() + } + }, [addNewNode, prevSelectedNodeId, selectedNode, setSelectionData]) useEffect(() => { - const structuredNodes = structuredClone(dataInitial?.nodes || []) + return + const structuredLinks = structuredClone(dataInitial?.links || []) if (prevSelectedNodeId === selectedNode?.ref_id) { return } - const nodes = structuredNodes - .filter( - (f: NodeExtended) => f.ref_id === selectedNode?.ref_id || selectedNodeRelativeIds.includes(f?.ref_id || ''), - ) - .map((n: NodeExtended) => { - const fixedPosition = n.ref_id === selectedNode?.ref_id ? { fx: 0, fy: 0, fz: 0 } : {} + const graphNodes: NodeExtended[] = selectedNodeRelativeIds + .filter((id) => !!nodesNormalized.get(id)) + .map((id: string) => { + const node = nodesNormalized.get(id) as unknown as Node - return { ...n, x: 0, y: 0, z: 0, ...fixedPosition } + return { ...node, x: 0, y: 0, z: 0 } }) + const nodes: NodeExtended[] = [ + ...graphNodes, + { ...selectedNode, x: 0, y: 0, z: 0, fx: 0, fy: 0, fz: 0 } as NodeExtended, + ] + if (nodes) { const links = structuredLinks.filter( (link: Link) => @@ -64,7 +124,7 @@ export const SelectionDataNodes = memo(() => { setSimulation2D(null) linksPositionRef.current = new Map() } - }, [dataInitial, selectedNode, selectedNodeRelativeIds, setSelectionData, prevSelectedNodeId]) + }, [dataInitial, selectedNode, selectedNodeRelativeIds, setSelectionData, prevSelectedNodeId, nodesNormalized]) useEffect(() => { if (simulation2d || !selectionGraphData.nodes.length) { @@ -84,8 +144,8 @@ export const SelectionDataNodes = memo(() => { .id((d: NodeExtended) => d.ref_id) .distance(() => 150), ) - .force('radial', forceRadial(500, 0, 0, 0).strength(0)) - .force('charge', forceManyBody().strength(-1000)) + .force('radial', forceRadial(20, 0, 0, 0).strength(0)) + .force('charge', forceManyBody().strength(-500)) .alpha(1) .restart() @@ -94,13 +154,6 @@ export const SelectionDataNodes = memo(() => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectionGraphData, simulation2d]) - useEffect( - () => () => { - setSelectionData({ nodes: [], links: [] }) - }, - [setSelectionData], - ) - useEffect(() => { if (!simulation2d) { return @@ -124,8 +177,8 @@ export const SelectionDataNodes = memo(() => { const grConnections = groupRef.current.getObjectByName('simulation-3d-group__connections') as Group grConnections.children.forEach((g, i) => { - const r = g.children[0] // Assuming Line is the first child - const text = g.children[1] // Assuming Text is the second child + const r = g.children[0] + const text = g.children[1] if (r instanceof Line2) { const Line = r as Line2 @@ -139,26 +192,31 @@ export const SelectionDataNodes = memo(() => { return } - const { x: sx, y: sy, z: sz } = sourceNode - const { x: tx, y: ty, z: tz } = targetNode + const { x: sx, y: sy } = sourceNode + const { x: tx, y: ty } = targetNode - // Set positions for the link linksPositionRef.current.set(link.ref_id, { sx, sy, - sz, tx, ty, - tz, + sz: 0, + tz: 0, }) - const midPoint = new Vector3((sx + tx) / 2, (sy + ty) / 2, (sz + tz) / 2) + const midPoint = new Vector3((sx + tx) / 2, (sy + ty) / 2, 0) + + text.position.set(midPoint.x, midPoint.y, 1) + + let angle = Math.atan2(ty - sy, tx - sx) + + if (tx < sx || (Math.abs(tx - sx) < 0.01 && ty < sy)) { + angle += Math.PI + } - // Position the text - text.position.set(midPoint.x, midPoint.y, midPoint.z) + text.rotation.set(0, 0, angle) - // Set the line positions - Line.geometry.setPositions([sx, sy, sz, tx, ty, tz]) + Line.geometry.setPositions([sx, sy, 0, tx, ty, 0]) const { material } = Line @@ -191,18 +249,37 @@ export const SelectionDataNodes = memo(() => { ) return ( - - {selectionGraphData?.nodes.map((node) => ( - - - handleSelect(node)} selected={node.ref_id === selectedNode?.ref_id} /> - - - - - ))} - - + <> + + {selectionGraphData?.nodes.map((node) => ( + + + handleSelect(node)} + selected={node.ref_id === selectedNode?.ref_id} + /> + + + ))} + + + {false && ( + + {pathNodes.map((node) => ( + + + handleSelect(node)} + selected={node.ref_id === selectedNode?.ref_id} + /> + + + ))} + + )} + ) }) diff --git a/src/components/Universe/Graph/Cubes/index.tsx b/src/components/Universe/Graph/Cubes/index.tsx index 8cfb6bcd6..60aa4579f 100644 --- a/src/components/Universe/Graph/Cubes/index.tsx +++ b/src/components/Universe/Graph/Cubes/index.tsx @@ -7,42 +7,12 @@ import { useDataStore, useNodeTypes } from '~/stores/useDataStore' import { useGraphStore, useHoveredNode, useSelectedNode } from '~/stores/useGraphStore' import { NodeExtended } from '~/types' import { colors } from '~/utils' +import { COLORS_MAP } from '../constant' import { NodePoints } from './NodePoints' import { NodeWrapper } from './NodeWrapper' -import { RelevanceBadges } from './RelevanceBadges' const POINTER_IN_DELAY = 200 -const COLORS_MAP = [ - '#fff', - '#9747FF', - '#00887A', - '#0098A6', - '#0288D1', - '#33691E', - '#465A65', - '#512DA7', - '#5C6BC0', - '#5D4038', - '#662C00', - '#689F39', - '#6B1B00', - '#750000', - '#78909C', - '#7E57C2', - '#8C6E63', - '#AA47BC', - '#BF360C', - '#C2175B', - '#EC407A', - '#EF6C00', - '#F5511E', - '#FF9696', - '#FFC064', - '#FFCD29', - '#FFEA60', -] - export const Cubes = memo(() => { const selectedNode = useSelectedNode() const hoveredNode = useHoveredNode() @@ -150,7 +120,7 @@ export const Cubes = memo(() => { index={index} node={node} scale={node.scale || 1} - stopFrames={hideUniverse} + stopFrames={false} /> ) })} @@ -160,7 +130,6 @@ export const Cubes = memo(() => { - ) }) diff --git a/src/components/Universe/Graph/constant.ts b/src/components/Universe/Graph/constant.ts index 5c05f711f..c80036e18 100644 --- a/src/components/Universe/Graph/constant.ts +++ b/src/components/Universe/Graph/constant.ts @@ -19,3 +19,33 @@ export const getNodeColorByType = (nodeType: string, returnString?: boolean) => return color } + +export const COLORS_MAP = [ + '#fff', + '#9747FF', + '#00887A', + '#0098A6', + '#0288D1', + '#33691E', + '#465A65', + '#512DA7', + '#5C6BC0', + '#5D4038', + '#662C00', + '#689F39', + '#6B1B00', + '#750000', + '#78909C', + '#7E57C2', + '#8C6E63', + '#AA47BC', + '#BF360C', + '#C2175B', + '#EC407A', + '#EF6C00', + '#F5511E', + '#FF9696', + '#FFC064', + '#FFCD29', + '#FFEA60', +] diff --git a/src/components/Universe/Graph/index.tsx b/src/components/Universe/Graph/index.tsx index 2d653449f..17d26bd6a 100644 --- a/src/components/Universe/Graph/index.tsx +++ b/src/components/Universe/Graph/index.tsx @@ -24,7 +24,6 @@ export type LinkPosition = { export const Graph = () => { const { dataInitial, isLoadingNew, isFetching, dataNew, resetDataNew } = useDataStore((s) => s) const groupRef = useRef(null) - const cameraSettled = useRef(false) const { normalizedSchemasByType } = useSchemaStore((s) => s) const linksPositionRef = useRef(new Map()) @@ -78,25 +77,6 @@ export const Graph = () => { return } - simulation.on('tick', () => { - if (!cameraSettled.current && simulation.alpha() < 0.1) { - const nodesVector = simulation.nodes().map((i: NodeExtended) => new Vector3(i.x, i.y, i.z)) - - const boundingBox = new Box3().setFromPoints(nodesVector) - - const boundingSphere = new Sphere() - - boundingBox.getBoundingSphere(boundingSphere) - - const sphereRadius = Math.min(5000, boundingSphere.radius) - - if (false) { - setGraphRadius(sphereRadius) - cameraSettled.current = true - } - } - }) - simulation.on('end', () => { const nodesVector = simulation.nodes().map((i: NodeExtended) => { // eslint-disable-next-line no-param-reassign @@ -182,7 +162,15 @@ export const Graph = () => { const boundingBox = new Box3().setFromPoints(nodesVector) - console.log(boundingBox) + const boundingSphere = new Sphere() + + boundingBox.getBoundingSphere(boundingSphere) + + const sphereRadius = Math.min(5000, boundingSphere.radius) + + console.log(sphereRadius) + + setGraphRadius(sphereRadius) }) }, [dataInitial, simulation, setGraphRadius, normalizedSchemasByType]) diff --git a/src/components/Universe/index.tsx b/src/components/Universe/index.tsx index 2226c8875..2ee57bdb2 100644 --- a/src/components/Universe/index.tsx +++ b/src/components/Universe/index.tsx @@ -178,7 +178,7 @@ const _Universe = () => { - + diff --git a/src/stores/useGraphStore/index.ts b/src/stores/useGraphStore/index.ts index 02c54d9c2..c91a9c43f 100644 --- a/src/stores/useGraphStore/index.ts +++ b/src/stores/useGraphStore/index.ts @@ -82,6 +82,7 @@ export type GraphStore = { simulationHelpers: SimulationHelpers isHovering: boolean activeEdge: Link | null + selectionPath: string[] setDisableCameraRotation: (rotation: boolean) => void setScrollEventsDisabled: (rotation: boolean) => void @@ -99,6 +100,7 @@ export type GraphStore = { simulationCreate: (nodes: Node[], links: Link[]) => void setIsHovering: (isHovering: boolean) => void removeSimulation: () => void + addToSelectionPath: (id: string) => void } const defaultData: Omit< @@ -121,6 +123,7 @@ const defaultData: Omit< | 'simulationCreate' | 'setIsHovering' | 'removeSimulation' + | 'addToSelectionPath' > = { data: null, simulation: null, @@ -138,6 +141,7 @@ const defaultData: Omit< showSelectionGraph: false, simulationHelpers: defaultSimulationHelpers, isHovering: false, + selectionPath: [], } export const useGraphStore = create()((set, get) => ({ @@ -158,6 +162,11 @@ export const useGraphStore = create()((set, get) => ({ setActiveEdge: (activeEdge) => { set({ activeEdge }) }, + addToSelectionPath: (id: string) => { + const { selectionPath } = get() + + set({ selectionPath: [...selectionPath, id] }) + }, setSelectedNode: (selectedNode) => { if (!selectedNode) { set({ @@ -165,10 +174,11 @@ export const useGraphStore = create()((set, get) => ({ selectedNode: null, disableCameraRotation: false, showSelectionGraph: false, + selectionPath: [], }) } - const { selectedNode: stateSelectedNode, simulation } = get() + const { selectedNode: stateSelectedNode, simulation, selectionPath } = get() if (stateSelectedNode?.ref_id !== selectedNode?.ref_id) { const selectedNodeWithCoordinates = @@ -179,6 +189,7 @@ export const useGraphStore = create()((set, get) => ({ selectedNode: selectedNodeWithCoordinates, disableCameraRotation: true, showSelectionGraph: !!selectedNode, + selectionPath: [...selectionPath, selectedNodeWithCoordinates.ref_id], }) } }, diff --git a/src/types/index.ts b/src/types/index.ts index 9fdd137db..f33828606 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -5,6 +5,55 @@ type QueryData = { ref_id: string } +export type Node = { + boost?: number | null + children?: string[] + x: number + y: number + z: number + edge_count: number + hidden?: boolean + colors?: string[] + date?: number + description?: string + episode_title?: string + hosts?: Guests[] + guests?: (null | string | Guests)[] + id?: string + image_url?: string + sender_pic?: string + sender_alias?: string + message_content?: string + keyword?: boolean + label: string + source_link?: string + link?: string + name: string + node_type: string + ref_id: string + scale?: number + show_title?: string + text?: string + timestamp?: string + topics?: string[] + type?: string + weight?: number + tweet_id?: string + posted_by?: PostedBy + twitter_handle?: string + profile_picture?: string + verified?: boolean + unique_id?: string + properties?: { [key: string]: never | undefined } + media_url?: string + start?: number + end?: number + longitude?: number + latitude?: number + coordinates?: Coordinates + audio?: Audio[] +} + export type FetchDataResponse = { nodes: Node[] edges: Link[] @@ -69,49 +118,6 @@ export type NodeRequest = { } } -export type Node = { - boost?: number | null - children?: string[] - x: number - y: number - z: number - edge_count: number - hidden?: boolean - colors?: string[] - date?: number - description?: string - episode_title?: string - hosts?: Guests[] - guests?: (null | string | Guests)[] - id?: string - image_url?: string - sender_pic?: string - sender_alias?: string - message_content?: string - keyword?: boolean - label: string - source_link?: string - link?: string - name: string - node_type: string - ref_id: string - scale?: number - show_title?: string - text?: string - timestamp?: string - topics?: string[] - type?: string - weight?: number - tweet_id?: string - posted_by?: PostedBy - twitter_handle?: string - profile_picture?: string - verified?: boolean - unique_id?: string - properties?: { [key: string]: never | undefined } - media_url?: string -} - export type DataSeriesNode = { id: string image_url?: string @@ -147,13 +153,6 @@ export type NodeExtended = Node & { fx?: number fy?: number fz?: number - start?: number - end?: number - longitude?: number - latitude?: number - coordinates?: Coordinates - audio?: Audio[] - properties?: { [key: string]: never | undefined } } export type Link = {