diff options
Diffstat (limited to 'app/components/graph/graph.tsx')
-rw-r--r-- | app/components/graph/graph.tsx | 427 |
1 files changed, 246 insertions, 181 deletions
diff --git a/app/components/graph/graph.tsx b/app/components/graph/graph.tsx index 1f4f4b2..e064b38 100644 --- a/app/components/graph/graph.tsx +++ b/app/components/graph/graph.tsx @@ -12,7 +12,7 @@ import { flatten } from "ramda" import { ForceGraph2D, ForceGraph3D, ForceGraphVR, ForceGraphAR } from "react-force-graph" import * as d3 from "d3-force-3d" -import * as three from "three"; +import * as three from "three" import SpriteText from "three-spritetext" const CONTAINER: ViewStyle = { @@ -55,24 +55,24 @@ export const Graph = observer(function Graph(props: GraphProps): JSX.Element { const fg = fgRef.current //fg.d3Force('center').strength(0.05); if (physics.gravityOn) { - fg.d3Force("x", d3.forceX().strength(physics.gravity)); - fg.d3Force("y", d3.forceY().strength(physics.gravity)); + fg.d3Force("x", d3.forceX().strength(physics.gravity)) + fg.d3Force("y", d3.forceY().strength(physics.gravity)) if (physics.threedim) { if (physics.galaxy) { - fg.d3Force("y", d3.forceY().strength(physics.gravity / 5)); - fg.d3Force("z", d3.forceZ().strength(physics.gravity / 5)); + fg.d3Force("y", d3.forceY().strength(physics.gravity / 5)) + fg.d3Force("z", d3.forceZ().strength(physics.gravity / 5)) } else { - fg.d3Force("y", d3.forceY().strength(physics.gravity)); - fg.d3Force("z", d3.forceZ().strength(physics.gravity)); + fg.d3Force("y", d3.forceY().strength(physics.gravity)) + fg.d3Force("z", d3.forceZ().strength(physics.gravity)) } } else { - fg.d3Force("z", null); - }; + fg.d3Force("z", null) + } } else { - fg.d3Force("x", null); - fg.d3Force("y", null); - physics.threedim ? fg.d3Force("z", null) : null; - }; + fg.d3Force("x", null) + fg.d3Force("y", null) + physics.threedim ? fg.d3Force("z", null) : null + } fg.d3Force("link").strength(physics.linkStrength) fg.d3Force("link").iterations(physics.linkIts) physics.collision @@ -83,103 +83,103 @@ export const Graph = observer(function Graph(props: GraphProps): JSX.Element { // For the expandable version of the graph - - const nodesById = useMemo(() => { - const nodesById = Object.fromEntries(gData.nodes.map((node) => [node.index, node])) - console.log(nodesById); + console.log(nodesById) // link parent/children gData.nodes.forEach((node) => { - (((typeof physics.rootId) === "number") ? node.collapsed = node.index !== physics.rootId - : node.collapsed = node.id !== physics.rootId) + typeof physics.rootId === "number" + ? (node.collapsed = node.index !== physics.rootId) + : (node.collapsed = node.id !== physics.rootId) node.childLinks = [] }) - gData.links.forEach((link) => nodesById[link.sourceIndex].childLinks.push(link)); - return nodesById; - }, [gData]); - const getPrunedTree = useCallback(() => { - const visibleNodes = []; - const visibleLinks = []; - (function traverseTree(node = nodesById[physics.rootId]) { - visibleNodes.push(node); - if (node.collapsed) return - visibleLinks.push(...node.childLinks) - node.childLinks - .map(link => ((typeof link.targetIndex) === "object") ? link.targetIndex : nodesById[link.targetIndex]) // get child node - .forEach(traverseTree); - })(); - - return { nodes: visibleNodes, links: visibleLinks } - }, [nodesById]) - - const [prunedTree, setPrunedTree] = useState(getPrunedTree()) - - const handleNodeClick = useCallback((node) => { - node.collapsed = !node.collapsed // toggle collapse state - setPrunedTree(getPrunedTree()) - }, []); + gData.links.forEach((link) => nodesById[link.sourceIndex].childLinks.push(link)) + return nodesById + }, [gData]) + const getPrunedTree = useCallback(() => { + const visibleNodes = [] + const visibleLinks = [] + ;(function traverseTree(node = nodesById[physics.rootId]) { + visibleNodes.push(node) + if (node.collapsed) return + visibleLinks.push(...node.childLinks) + node.childLinks + .map((link) => + typeof link.targetIndex === "object" ? link.targetIndex : nodesById[link.targetIndex], + ) // get child node + .forEach(traverseTree) + })() + + return { nodes: visibleNodes, links: visibleLinks } + }, [nodesById]) + + const [prunedTree, setPrunedTree] = useState(getPrunedTree()) + + const handleNodeClick = useCallback((node) => { + node.collapsed = !node.collapsed // toggle collapse state + setPrunedTree(getPrunedTree()) + }, []) //highlighting - const [highlightNodes, setHighlightNodes] = useState(new Set()); - const [highlightLinks, setHighlightLinks] = useState(new Set()); - const [hoverNode, setHoverNode] = useState(null); + const [highlightNodes, setHighlightNodes] = useState(new Set()) + const [highlightLinks, setHighlightLinks] = useState(new Set()) + const [hoverNode, setHoverNode] = useState(null) const updateHighlight = () => { - setHighlightNodes(highlightNodes); - setHighlightLinks(highlightLinks); - }; - - const handleBackgroundClick = (event) => { - highlightNodes.clear(); - highlightLinks.clear(); + setHighlightNodes(highlightNodes) + setHighlightLinks(highlightLinks) + } - setSelectedNode(null); - updateHighlight(); - } + const handleBackgroundClick = (event) => { + highlightNodes.clear() + highlightLinks.clear() - const handleNodeHover = node => { - console.log("hover"); - if(!selectedNode){ - highlightNodes.clear(); - highlightLinks.clear(); - if (node) { - highlightNodes.add(node); - node.neighbors.forEach(neighbor => highlightNodes.add(neighbor)); - node.links.forEach(link => highlightLinks.add(link)); - } + setSelectedNode(null) + updateHighlight() + } - setHoverNode(node || null); - updateHighlight(); + const handleNodeHover = (node) => { + console.log("hover") + if (!selectedNode) { + highlightNodes.clear() + highlightLinks.clear() + if (node) { + highlightNodes.add(node) + node.neighbors.forEach((neighbor) => highlightNodes.add(neighbor)) + node.links.forEach((link) => highlightLinks.add(link)) + } + + setHoverNode(node || null) + updateHighlight() } - }; + } - const handleLinkHover = link => { - highlightNodes.clear(); - highlightLinks.clear(); + const handleLinkHover = (link) => { + highlightNodes.clear() + highlightLinks.clear() if (link) { - highlightLinks.add(link); - highlightNodes.add(link.source); - highlightNodes.add(link.target); + highlightLinks.add(link) + highlightNodes.add(link.source) + highlightNodes.add(link.target) } - updateHighlight(); - }; + updateHighlight() + } - // Normally the graph doesn't update when you just change the physics parameters - // This forces the graph to make a small update when you do - useEffect(()=>{ - fgRef.current.d3ReheatSimulation(); - }, [physics]); + // Normally the graph doesn't update when you just change the physics parameters + // This forces the graph to make a small update when you do + useEffect(() => { + fgRef.current.d3ReheatSimulation() + }, [physics]) /* const paintRing = useCallback((node, ctx) => { -* // add ring just for highlighted nodes -* ctx.beginPath(); -* ctx.arc(node.x, node.y, NODE_R * 1.4, 0, 2 * Math.PI, false); -* ctx.fillStyle = node === hoverNode ? 'red' : 'orange'; -* ctx.fill(); -* }, [hoverNode]); -*/ + * // add ring just for highlighted nodes + * ctx.beginPath(); + * ctx.arc(node.x, node.y, NODE_R * 1.4, 0, 2 * Math.PI, false); + * ctx.fillStyle = node === hoverNode ? 'red' : 'orange'; + * ctx.fill(); + * }, [hoverNode]); + */ /* autoPauseRedraw={false} linkWidth={link => highlightLinks.has(link) ? 5 : 1} @@ -195,26 +195,58 @@ onLinkHover={handleLinkHover} // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow" //} - const [selectedNode, setSelectedNode] = useState({}); + const [selectedNode, setSelectedNode] = useState({}) + + //shitty handler to check for doubleClicks + const [doubleClick, setDoubleClick] = useState(0) + const [localGraphData, setLocalGraphData] = useState({ nodes: [], links: [] }) + + const updateLocalGraph = (node) => { + console.log(localGraphData) + localGraphData.nodes.length ? setLocalGraphData({ nodes: [], links: [] }) : null + let g = localGraphData + console.log(g.nodes) + g.nodes.length ? null : g.nodes.push(node) //only add the clicked node if its the first + node.neighbors.forEach((neighbor) => { + if (neighbor !== node) { + const newNode: boolean = g.nodes.every((existingNode) => { + if (existingNode === neighbor) { + return false + } else { + return true + } + }) + newNode && g.nodes.push(neighbor) + } + }) + node.links.forEach((neighborLink) => { + const newLink: boolean = g.links.every((existingLink) => { + if (existingLink === neighborLink) { + return false + } else { + return true + } + }) + newLink && g.links.push(neighborLink) + }) + setLocalGraphData(g) + } - //shitty handler to check for doubleClicks - const [doubleClick, setDoubleClick] = useState(0) const selectClick = (node, event) => { - highlightNodes.clear(); - highlightLinks.clear(); - if(event.timeStamp - doubleClick < 400){ - + highlightNodes.clear() + highlightLinks.clear() + console.log(localGraphData) + if (event.timeStamp - doubleClick < 400) { + updateLocalGraph(node) } - if (node) { - highlightNodes.add(node); - node.neighbors.forEach(neighbor => highlightNodes.add(neighbor)); - node.links.forEach(link => highlightLinks.add(link)); + highlightNodes.add(node) + node.neighbors.forEach((neighbor) => highlightNodes.add(neighbor)) + node.links.forEach((link) => highlightLinks.add(link)) } - - setSelectedNode(node || null); - updateHighlight(); - setDoubleClick(event.timeStamp); + setSelectedNode(node || null) + updateHighlight() + setDoubleClick(event.timeStamp) } return ( @@ -224,32 +256,35 @@ onLinkHover={handleLinkHover} ref={fgRef} autoPauseRedraw={false} //graphData={gData} - graphData={physics.collapse ? prunedTree : gData} + graphData={ + localGraphData.nodes.length ? localGraphData : physics.collapse ? prunedTree : gData + } nodeAutoColorBy={physics.colorful ? "id" : undefined} nodeColor={ - !physics.colorful ? ( - (node) => { - if (highlightNodes.size === 0) { - return "rgb(100, 100, 100, 1)" - } else { - return highlightNodes.has(node) ? "purple" : "rgb(50, 50, 50, 0.5)" + !physics.colorful + ? (node) => { + if (highlightNodes.size === 0) { + return "rgb(100, 100, 100, 1)" + } else { + return highlightNodes.has(node) ? "purple" : "rgb(50, 50, 50, 0.5)" + } + // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow" } - // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow" - }) : undefined + : undefined } linkAutoColorBy={physics.colorful ? "target" : undefined} //linkAutoColorBy={(d) => gData.nodes[d.source].id % GROUPS} linkColor={ - !physics.colorful ? ( - (link) => { - if (highlightLinks.size === 0) { - return "rgb(50, 50, 50, 0.8)" - } else { - return highlightLinks.has(link) ? "purple" : "rgb(50, 50, 50, 0.2)" + !physics.colorful + ? (link) => { + if (highlightLinks.size === 0) { + return "rgb(50, 50, 50, 0.8)" + } else { + return highlightLinks.has(link) ? "purple" : "rgb(50, 50, 50, 0.2)" + } + // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow" } - // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow" - } - ) : undefined + : undefined //highlightLinks.has(link) ? "purple" : "grey" // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow" } @@ -258,43 +293,65 @@ onLinkHover={handleLinkHover} nodeLabel={(node) => node.title} //nodeVal ={(node)=> node.childLinks.length * 0.5 + 1} //d3VelocityDecay={visco} - linkWidth={link => highlightLinks.has(link) ? 3 * physics.linkWidth : physics.linkWidth} + linkWidth={(link) => + highlightLinks.has(link) ? 3 * physics.linkWidth : physics.linkWidth + } linkOpacity={physics.linkOpacity} nodeRelSize={physics.nodeRel} - nodeVal={node => highlightNodes.has(node) ? node.neighbors.length + 5 : node.neighbors.length + 3} + nodeVal={(node) => + highlightNodes.has(node) ? node.neighbors.length + 5 : node.neighbors.length + 3 + } linkDirectionalParticleWidth={physics.particleWidth} - nodeCanvasObject={ - (node, ctx, globalScale) => { - if (physics.labels) { - if (globalScale > physics.labelScale || highlightNodes.has(node)) { - const label = node.title.substring(0, Math.min(node.title.length, 30)); - const fontSize = 12 / globalScale; - ctx.font = `${fontSize}px Sans-Serif`; - const textWidth = ctx.measureText(label).width; - const bckgDimensions = [textWidth * 1.1, fontSize].map(n => n + fontSize * 0.5); // some padding - const fadeFactor = Math.min(3 * (globalScale - physics.labelScale) / physics.labelScale, 1); - - ctx.fillStyle = 'rgba(20, 20, 20, ' + - (highlightNodes.size === 0 ? .5 * fadeFactor : (highlightNodes.has(node) ? 0.5 : 0.15 * fadeFactor)) + ')'; - ctx.fillRect(node.x - bckgDimensions[0] / 2, node.y - bckgDimensions[1] / 2, ...bckgDimensions); - - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - ctx.fillStyle = 'rgb(255, 255, 255, ' + - (highlightNodes.size === 0 ? fadeFactor : (highlightNodes.has(node) ? 1 : 0.3 * fadeFactor)) + ')'; - ctx.fillText(label, node.x, node.y); - - node.__bckgDimensions = bckgDimensions; // to re-use in nodePointerAreaPaint - }; - }; - }} - nodeCanvasObjectMode={() => 'after'} + nodeCanvasObject={(node, ctx, globalScale) => { + if (physics.labels) { + if (globalScale > physics.labelScale || highlightNodes.has(node)) { + const label = node.title.substring(0, Math.min(node.title.length, 30)) + const fontSize = 12 / globalScale + ctx.font = `${fontSize}px Sans-Serif` + const textWidth = ctx.measureText(label).width + const bckgDimensions = [textWidth * 1.1, fontSize].map((n) => n + fontSize * 0.5) // some padding + const fadeFactor = Math.min( + (3 * (globalScale - physics.labelScale)) / physics.labelScale, + 1, + ) + + ctx.fillStyle = + "rgba(20, 20, 20, " + + (highlightNodes.size === 0 + ? 0.5 * fadeFactor + : highlightNodes.has(node) + ? 0.5 + : 0.15 * fadeFactor) + + ")" + ctx.fillRect( + node.x - bckgDimensions[0] / 2, + node.y - bckgDimensions[1] / 2, + ...bckgDimensions, + ) + + ctx.textAlign = "center" + ctx.textBaseline = "middle" + ctx.fillStyle = + "rgb(255, 255, 255, " + + (highlightNodes.size === 0 + ? fadeFactor + : highlightNodes.has(node) + ? 1 + : 0.3 * fadeFactor) + + ")" + ctx.fillText(label, node.x, node.y) + + node.__bckgDimensions = bckgDimensions // to re-use in nodePointerAreaPaint + } + } + }} + nodeCanvasObjectMode={() => "after"} onNodeHover={physics.hover ? handleNodeHover : null} //onLinkHover={physics.hover ? handleLinkHover : null} d3AlphaDecay={physics.alphaDecay} d3AlphaMin={physics.alphaTarget} d3VelocityDecay={physics.velocityDecay} - onBackgroundClick={handleBackgroundClick} + onBackgroundClick={handleBackgroundClick} /> ) : ( <ForceGraph3D @@ -304,29 +361,30 @@ onLinkHover={handleLinkHover} //graphData={physics.collapse ? prunedTree : gData} nodeAutoColorBy={physics.colorful ? "id" : undefined} nodeColor={ - !physics.colorful ? ( - (node) => { - if (highlightNodes.size === 0) { - return "rgb(100, 100, 100, 1)" - } else { - return highlightNodes.has(node) ? "purple" : "rgb(50, 50, 50, 0.5)" + !physics.colorful + ? (node) => { + if (highlightNodes.size === 0) { + return "rgb(100, 100, 100, 1)" + } else { + return highlightNodes.has(node) ? "purple" : "rgb(50, 50, 50, 0.5)" + } + // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow" } - // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow" - }) : undefined + : undefined } linkAutoColorBy={physics.colorful ? "target" : undefined} //linkAutoColorBy={(d) => gData.nodes[d.source].id % GROUPS} linkColor={ - !physics.colorful ? ( - (link) => { - if (highlightLinks.size === 0) { - return "rgb(50, 50, 50, 0.8)" - } else { - return highlightLinks.has(link) ? "purple" : "rgb(50, 50, 50, 0.2)" + !physics.colorful + ? (link) => { + if (highlightLinks.size === 0) { + return "rgb(50, 50, 50, 0.8)" + } else { + return highlightLinks.has(link) ? "purple" : "rgb(50, 50, 50, 0.2)" + } + // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow" } - // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow" - } - ) : undefined + : undefined //highlightLinks.has(link) ? "purple" : "grey" // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow" } @@ -335,10 +393,14 @@ onLinkHover={handleLinkHover} nodeLabel={(node) => node.title} //nodeVal ={(node)=> node.childLinks.length * 0.5 + 1} //d3VelocityDecay={visco} - linkWidth={link => highlightLinks.has(link) ? 3 * physics.linkWidth : physics.linkWidth} + linkWidth={(link) => + highlightLinks.has(link) ? 3 * physics.linkWidth : physics.linkWidth + } linkOpacity={physics.linkOpacity} nodeRelSize={physics.nodeRel} - nodeVal={node => highlightNodes.has(node) ? node.neighbors.length + 5 : node.neighbors.length + 3} + nodeVal={(node) => + highlightNodes.has(node) ? node.neighbors.length + 5 : node.neighbors.length + 3 + } linkDirectionalParticleWidth={physics.particleWidth} onNodeHover={physics.hover ? handleNodeHover : null} //onLinkHover={physics.hover ? handleLinkHover : null} @@ -346,21 +408,24 @@ onLinkHover={handleLinkHover} d3AlphaMin={physics.alphaTarget} d3VelocityDecay={physics.velocityDecay} nodeThreeObject={ - !physics.labels ? undefined - : ((node) => { + !physics.labels + ? undefined + : (node) => { if (highlightNodes.has(node)) { - console.log(node.title) - const sprite = new SpriteText(node.title.substring(0, 30)); - console.log("didnt crash here 2") - sprite.color = "#ffffff"; - sprite.textHeight = 8; - return sprite; - } else { return undefined }; - }) + console.log(node.title) + const sprite = new SpriteText(node.title.substring(0, 30)) + console.log("didnt crash here 2") + sprite.color = "#ffffff" + sprite.textHeight = 8 + return sprite + } else { + return undefined + } + } } nodeThreeObjectExtend={true} /> )} </View> - ); + ) }) |