summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/components/graph/3d.tsx62
-rw-r--r--app/components/graph/graph.tsx107
-rw-r--r--app/components/graph/graphbak.tsx448
-rw-r--r--app/components/graph/graphgood.tsx437
4 files changed, 961 insertions, 93 deletions
diff --git a/app/components/graph/3d.tsx b/app/components/graph/3d.tsx
new file mode 100644
index 0000000..a539d8c
--- /dev/null
+++ b/app/components/graph/3d.tsx
@@ -0,0 +1,62 @@
+
+ ref={fgRef}
+ //graphData={!physics.local ? gData : localGraphData }
+ graphData={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)"
+ }
+ }
+ : 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)"
+ }
+ }
+ : undefined
+ }
+ linkDirectionalParticles={physics.particles}
+ nodeLabel={(node) => node.title}
+ 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
+ }
+ linkDirectionalParticleWidth={physics.particleWidth}
+ onNodeHover={physics.hover ? handleNodeHover : null}
+ d3AlphaDecay={physics.alphaDecay}
+ d3AlphaMin={physics.alphaTarget}
+ d3VelocityDecay={physics.velocityDecay}
+ nodeThreeObject={
+ !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
+ }
+ }
+ }
+ nodeThreeObjectExtend={true}
+ onNodeClick={selectClick}
diff --git a/app/components/graph/graph.tsx b/app/components/graph/graph.tsx
index 8a3b6be..26f521c 100644
--- a/app/components/graph/graph.tsx
+++ b/app/components/graph/graph.tsx
@@ -8,11 +8,11 @@ import { flatten } from "ramda"
//import data from "../../data/miserables.json"
//import genRandomTree from "../../data/randomdata";
-//import rando from "../../data/rando.json"
+import rando from "../../data/rando.json"
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 = {
@@ -278,47 +278,35 @@ onLinkHover={handleLinkHover}
autoPauseRedraw={false}
//graphData={gData}
graphData={physics.local ? localGraphData : gData}
- nodeAutoColorBy={physics.colorful ? "id" : undefined}
- nodeColor={
- !physics.colorful
- ? (node) => {
+ nodeAutoColorBy={physics.colorful ? (node)=>node.index%GROUPS : 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)"
}
- // !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) => {
+ linkAutoColorBy={physics.colorful ? ((d) => gData.nodes[d.sourceIndex].id % GROUPS) : undefined}
+ 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)"
}
- // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow"
- }
- : undefined
- //highlightLinks.has(link) ? "purple" : "grey"
- // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow"
- }
+ }) : undefined}
linkDirectionalParticles={physics.particles}
- onNodeClick={!physics.collapse ? selectClick : handleNodeClick}
+ onNodeClick={selectClick}
nodeLabel={(node) => node.title}
- //nodeVal ={(node)=> node.childLinks.length * 0.5 + 1}
- //d3VelocityDecay={visco}
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
+ {
+ return highlightNodes.has(node) ? node.neighbors.length + 5 : node.neighbors.length + 3
+ }
}
linkDirectionalParticleWidth={physics.particleWidth}
nodeCanvasObject={(node, ctx, globalScale) => {
@@ -374,75 +362,8 @@ onLinkHover={handleLinkHover}
/>
) : (
<ForceGraph3D
- ref={fgRef}
- autoPauseRedraw={false}
- graphData={gData}
- //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)"
- }
- // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow"
- }
- : 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)"
- }
- // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow"
- }
- : undefined
- //highlightLinks.has(link) ? "purple" : "grey"
- // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow"
- }
- linkDirectionalParticles={physics.particles}
- //onNodeClick={!physics.collapse ? null : handleNodeClick}
- nodeLabel={(node) => node.title}
- //nodeVal ={(node)=> node.childLinks.length * 0.5 + 1}
- //d3VelocityDecay={visco}
- 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
- }
- linkDirectionalParticleWidth={physics.particleWidth}
- onNodeHover={physics.hover ? handleNodeHover : null}
- //onLinkHover={physics.hover ? handleLinkHover : null}
- d3AlphaDecay={physics.alphaDecay}
- d3AlphaMin={physics.alphaTarget}
- d3VelocityDecay={physics.velocityDecay}
- nodeThreeObject={
- !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
- }
- }
- }
- nodeThreeObjectExtend={true}
+ ref={fgRef}
+ graphData={rando}
/>
)}
</View>
diff --git a/app/components/graph/graphbak.tsx b/app/components/graph/graphbak.tsx
new file mode 100644
index 0000000..5291678
--- /dev/null
+++ b/app/components/graph/graphbak.tsx
@@ -0,0 +1,448 @@
+
+import * as React from "react"
+import { useState, useEffect, useRef, useMemo, useCallback } from "react"
+import { StyleProp, TextStyle, View, ViewStyle } from "react-native"
+import { observer } from "mobx-react-lite"
+import { color, typography } from "../../theme"
+import { Text } from "../"
+import { flatten } from "ramda"
+
+//import data from "../../data/miserables.json"
+//import genRandomTree from "../../data/randomdata";
+//import rando from "../../data/rando.json"
+
+import { ForceGraph2D, ForceGraph3D, ForceGraphVR, ForceGraphAR } from "react-force-graph"
+import * as d3 from "d3-force-3d"
+import * as three from "three"
+import SpriteText from "three-spritetext"
+
+const CONTAINER: ViewStyle = {
+ justifyContent: "center",
+}
+
+const TEXT: TextStyle = {
+ fontFamily: typography.primary,
+ fontSize: 14,
+ color: color.primary,
+}
+
+export interface GraphProps {
+ /**
+ * An optional style override useful for padding & margin.
+ */
+ style?: StyleProp<ViewStyle>
+ physics
+ gData
+ setPhysics
+ nodeIds: string[]
+}
+
+/**
+ * Describe your component here
+ */
+export const Graph = observer(function Graph(props: GraphProps): JSX.Element {
+ const { style, physics, setPhysics, gData, nodeIds } = props
+ const styles = flatten([CONTAINER, style])
+
+ const fgRef = useRef()
+
+ const GROUPS: number = 12
+ const NODE_R: number = 8
+ //const gData = genRandomTree(200);
+
+ //const [charge, setCharge] = useState(-30);
+ //const [link, setLink] = useState(-30);
+
+ useEffect(() => {
+ 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))
+ if (physics.threedim) {
+ if (physics.galaxy) {
+ 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))
+ }
+ } else {
+ fg.d3Force("z", null)
+ }
+ } else {
+ 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
+ ? fg.d3Force("collide", d3.forceCollide().radius(20))
+ : fg.d3Force("collide", null)
+ fg.d3Force("charge").strength(physics.charge)
+ })
+
+ // For the expandable version of the graph
+
+ const nodesById = useMemo(() => {
+ const nodesById = Object.fromEntries(gData.nodes.map((node) => [node.index, node]))
+ 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)
+ 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())
+ }, [])
+
+ //highlighting
+ 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()
+
+ setSelectedNode(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()
+
+ if (link) {
+ highlightLinks.add(link)
+ highlightNodes.add(link.source)
+ highlightNodes.add(link.target)
+ }
+
+ 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])
+ /* 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]);
+ */
+
+ /* autoPauseRedraw={false}
+linkWidth={link => highlightLinks.has(link) ? 5 : 1}
+linkDirectionalParticles={4}
+linkDirectionalParticleWidth={link => highlightLinks.has(link) ? 4 : 0}
+nodeCanvasObjectMode={node => highlightNodes.has(node) ? 'before' : undefined}
+nodeCanvasObject={paintRing}
+onNodeHover={handleNodeHover}
+onLinkHover={handleLinkHover}
+ nodeRelSize={NODE_R} */
+
+ //nodeColor={(node) =>
+ // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow"
+ //}
+
+ const [selectedNode, setSelectedNode] = useState({})
+
+ //shitty handler to check for doubleClicks
+ const [doubleClick, setDoubleClick] = useState(0)
+ const [localGraphData, setLocalGraphData] = useState({ nodes: [], links: [] })
+
+ useEffect(() => {
+ !physics.local && setPhysics({ ...physics, local: true })
+ }, [localGraphData])
+
+ const getLocalGraphData = (node) {
+ console.log(localGraphData)
+ localGraphData.nodes.length ? setLocalGraphData({nodes: [], links: []}) : null;
+ let g = localGraphData
+ console.log(g.nodes)
+ if (g.nodes.length === 0) {
+ console.log("length is 0")
+ node.local = true //keep track of these boys
+ g.nodes.push(node) //only add the clicked node if its the first
+ }
+ node.links.forEach((neighborLink) => {
+ if (!neighborLink.local) {
+ console.log("0")
+ neighborLink.local = true
+ g.links.push(neighborLink)
+ const targetNode = gData.nodes[neighborLink.targetIndex]
+ const sourceNode = gData.nodes[neighborLink.sourceIndex]
+ if (targetNode.id === node.id) {
+ console.log("1. I am the target, the source is ")
+ console.log(sourceNode)
+ if (!sourceNode.local) {
+ console.log("2. The source is not local")
+ sourceNode.local = true
+ g.nodes.push(sourceNode)
+ } else {
+ console.log("2.5 The source is already local")
+ }
+ } else {
+ console.log("3. I am the source")
+ if (!targetNode.local) {
+ console.log("4. The target is not local.")
+ targetNode.local = true
+ g.nodes.push(targetNode)
+ } else {
+ console.log("The target is already local")
+ }
+ }
+ }
+ })
+ setLocalGraphData(g)
+ };
+
+ const selectClick = (node, event) => {
+ highlightNodes.clear()
+ highlightLinks.clear()
+ console.log(localGraphData)
+ if (event.timeStamp - doubleClick < 400) {
+ getLocalGraphData(node);
+ }
+ if (node) {
+ highlightNodes.add(node)
+ node.neighbors.forEach((neighbor) => highlightNodes.add(neighbor))
+ node.links.forEach((link) => highlightLinks.add(link))
+ }
+
+ setSelectedNode(node || null)
+ updateHighlight()
+ setDoubleClick(event.timeStamp)
+ }
+
+ return (
+ <View>
+ {!physics.threedim ? (
+ <ForceGraph2D
+ ref={fgRef}
+ autoPauseRedraw={false}
+ //graphData={gData}
+ graphData={physics.local ? 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)"
+ }
+ // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow"
+ }
+ : 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)"
+ }
+ // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow"
+ }
+ : undefined
+ //highlightLinks.has(link) ? "purple" : "grey"
+ // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow"
+ }
+ linkDirectionalParticles={physics.particles}
+ onNodeClick={!physics.collapse ? selectClick : handleNodeClick}
+ nodeLabel={(node) => node.title}
+ //nodeVal ={(node)=> node.childLinks.length * 0.5 + 1}
+ //d3VelocityDecay={visco}
+ 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
+ }
+ 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
+ ? 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}
+ />
+ ) : (
+ <ForceGraph3D
+ ref={fgRef}
+ autoPauseRedraw={false}
+ graphData={gData}
+ //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)"
+ }
+ // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow"
+ }
+ : 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)"
+ }
+ // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow"
+ }
+ : undefined
+ //highlightLinks.has(link) ? "purple" : "grey"
+ // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow"
+ }
+ linkDirectionalParticles={physics.particles}
+ //onNodeClick={!physics.collapse ? null : handleNodeClick}
+ nodeLabel={(node) => node.title}
+ //nodeVal ={(node)=> node.childLinks.length * 0.5 + 1}
+ //d3VelocityDecay={visco}
+ 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
+ }
+ linkDirectionalParticleWidth={physics.particleWidth}
+ onNodeHover={physics.hover ? handleNodeHover : null}
+ //onLinkHover={physics.hover ? handleLinkHover : null}
+ d3AlphaDecay={physics.alphaDecay}
+ d3AlphaMin={physics.alphaTarget}
+ d3VelocityDecay={physics.velocityDecay}
+ nodeThreeObject={
+ !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
+ }
+ }
+ }
+ nodeThreeObjectExtend={true}
+ />
+ )}
+ </View>
+ )
+})
diff --git a/app/components/graph/graphgood.tsx b/app/components/graph/graphgood.tsx
new file mode 100644
index 0000000..f273a66
--- /dev/null
+++ b/app/components/graph/graphgood.tsx
@@ -0,0 +1,437 @@
+import * as React from "react"
+import { useState, useEffect, useRef, useMemo, useCallback } from "react"
+import { StyleProp, TextStyle, View, ViewStyle } from "react-native"
+import { observer } from "mobx-react-lite"
+import { color, typography } from "../../theme"
+import { Text } from "../"
+import { flatten } from "ramda"
+
+//import data from "../../data/miserables.json"
+//import genRandomTree from "../../data/randomdata";
+//import rando from "../../data/rando.json"
+
+import { ForceGraph2D, ForceGraph3D, ForceGraphVR, ForceGraphAR } from "react-force-graph"
+import * as d3 from "d3-force-3d"
+import * as three from "three"
+import SpriteText from "three-spritetext"
+
+const CONTAINER: ViewStyle = {
+ justifyContent: "center",
+}
+
+const TEXT: TextStyle = {
+ fontFamily: typography.primary,
+ fontSize: 14,
+ color: color.primary,
+}
+
+export interface GraphProps {
+ /**
+ * An optional style override useful for padding & margin.
+ */
+ style?: StyleProp<ViewStyle>
+ physics
+ setPhysics
+ gData
+ nodeIds: string[]
+}
+
+/**
+ * Describe your component here
+ */
+export const Graph = observer(function Graph(props: GraphProps): JSX.Element {
+ const { style, physics, setPhysics, gData, nodeIds } = props
+ const styles = flatten([CONTAINER, style])
+
+ const fgRef = useRef()
+
+ const GROUPS: number = 12
+ const NODE_R: number = 8
+ //const gData = genRandomTree(200);
+
+ //const [charge, setCharge] = useState(-30);
+ //const [link, setLink] = useState(-30);
+
+ useEffect(() => {
+ 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))
+ if (physics.threedim) {
+ if (physics.galaxy) {
+ 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))
+ }
+ } else {
+ fg.d3Force("z", null)
+ }
+ } else {
+ 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
+ ? fg.d3Force("collide", d3.forceCollide().radius(20))
+ : fg.d3Force("collide", null)
+ fg.d3Force("charge").strength(physics.charge)
+ })
+
+ // For the expandable version of the graph
+
+ const nodesById = useMemo(() => {
+ const nodesById = Object.fromEntries(gData.nodes.map((node) => [node.index, node]))
+ 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)
+ 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())
+ }, [])
+
+ //highlighting
+ 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()
+
+ setSelectedNode(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()
+
+ if (link) {
+ highlightLinks.add(link)
+ highlightNodes.add(link.source)
+ highlightNodes.add(link.target)
+ }
+
+ 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])
+ /* 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]);
+ */
+
+ /* autoPauseRedraw={false}
+linkWidth={link => highlightLinks.has(link) ? 5 : 1}
+linkDirectionalParticles={4}
+linkDirectionalParticleWidth={link => highlightLinks.has(link) ? 4 : 0}
+nodeCanvasObjectMode={node => highlightNodes.has(node) ? 'before' : undefined}
+nodeCanvasObject={paintRing}
+onNodeHover={handleNodeHover}
+onLinkHover={handleLinkHover}
+ nodeRelSize={NODE_R} */
+
+ //nodeColor={(node) =>
+ // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow"
+ //}
+
+ 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
+ if (!node.local) {
+ node.local = true
+ g.nodes.push(node)
+ }
+ node.neighbors.forEach((neighbor) => {
+ if (neighbor !== node || !neighbor.local) {
+ const newNode: boolean = g.nodes.every((existingNode) => {
+ if (existingNode === neighbor) {
+ return false
+ } else {
+ return true
+ }
+ })
+ if (newNode) {
+ neighbor.local = true
+ 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)
+ setPhysics({ ...physics, local: true })
+ }
+
+ const selectClick = (node, event) => {
+ 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))
+ }
+ setSelectedNode(node || null)
+ updateHighlight()
+ setDoubleClick(event.timeStamp)
+ }
+
+ return (
+ <View>
+ {!physics.threedim ? (
+ <ForceGraph2D
+ ref={fgRef}
+ autoPauseRedraw={false}
+ //graphData={gData}
+ graphData={physics.local ? 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)"
+ }
+ // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow"
+ }
+ : 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)"
+ }
+ // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow"
+ }
+ : undefined
+ //highlightLinks.has(link) ? "purple" : "grey"
+ // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow"
+ }
+ linkDirectionalParticles={physics.particles}
+ onNodeClick={!physics.collapse ? selectClick : handleNodeClick}
+ nodeLabel={(node) => node.title}
+ //nodeVal ={(node)=> node.childLinks.length * 0.5 + 1}
+ //d3VelocityDecay={visco}
+ 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
+ }
+ 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
+ ? 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}
+ />
+ ) : (
+ <ForceGraph3D
+ ref={fgRef}
+ autoPauseRedraw={false}
+ graphData={gData}
+ //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)"
+ }
+ // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow"
+ }
+ : 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)"
+ }
+ // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow"
+ }
+ : undefined
+ //highlightLinks.has(link) ? "purple" : "grey"
+ // !node.childLinks.length ? "green" : node.collapsed ? "red" : "yellow"
+ }
+ linkDirectionalParticles={physics.particles}
+ //onNodeClick={!physics.collapse ? null : handleNodeClick}
+ nodeLabel={(node) => node.title}
+ //nodeVal ={(node)=> node.childLinks.length * 0.5 + 1}
+ //d3VelocityDecay={visco}
+ 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
+ }
+ linkDirectionalParticleWidth={physics.particleWidth}
+ onNodeHover={physics.hover ? handleNodeHover : null}
+ //onLinkHover={physics.hover ? handleLinkHover : null}
+ d3AlphaDecay={physics.alphaDecay}
+ d3AlphaMin={physics.alphaTarget}
+ d3VelocityDecay={physics.velocityDecay}
+ nodeThreeObject={
+ !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
+ }
+ }
+ }
+ nodeThreeObjectExtend={true}
+ />
+ )}
+ </View>
+ )
+})