Skip to content

Commit 8c3db05

Browse files
committed
add file loading
1 parent a264a48 commit 8c3db05

9 files changed

Lines changed: 186 additions & 49 deletions

File tree

package-lock.json

Lines changed: 53 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
"rdflib": "^2.2.35",
1818
"react": "^18.3.1",
1919
"react-dom": "^18.3.1",
20-
"react-force-graph-3d": "^1.24.4"
20+
"react-force-graph-3d": "^1.24.4",
21+
"three": "^0.169.0"
2122
},
2223
"devDependencies": {
2324
"@eslint/js": "^9.11.1",
2425
"@types/react": "^18.3.10",
2526
"@types/react-dom": "^18.3.0",
27+
"@types/three": "^0.169.0",
2628
"@vitejs/plugin-react": "^4.3.2",
2729
"eslint": "^9.11.1",
2830
"eslint-plugin-react-hooks": "^5.1.0-rc.0",

src/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import Graph from "./components/Graph.tsx";
1+
import Content from "./components/Content.tsx";
22
import Layout from "./components/Layout.tsx";
33

44
function App() {
55
return (
66
<Layout>
7-
<Graph />
7+
<Content />
88
</Layout>
99
);
1010
}

src/components/Content.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Box } from "@chakra-ui/react";
2+
import Graph from "./Graph.tsx";
3+
import Selections from "./Selections.tsx";
4+
import { useState } from "react";
5+
import { GraphData } from "react-force-graph-3d";
6+
7+
const Content = () => {
8+
const [graphData, setGraphData] = useState<GraphData>({ nodes: [], links: [] });
9+
10+
return (
11+
<Box width="100%" height="100%">
12+
<Box mb={{ base: 8, md: 2 }}>
13+
<Selections setGraphData={setGraphData} />
14+
</Box>
15+
<Box width="100%" height="60vh" overflow="hidden">
16+
<Graph graphData={graphData} />
17+
</Box>
18+
</Box>
19+
);
20+
};
21+
22+
export default Content;

src/components/Graph.tsx

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,12 @@
1-
import React, { useState, useEffect } from "react";
2-
import ForceGraph3D from "react-force-graph-3d";
1+
import React from "react";
2+
import ForceGraph3D, { GraphData, NodeObject, LinkObject } from "react-force-graph-3d";
33
import * as THREE from "three";
4-
import { GraphData, Node } from "./Graph.tsx";
5-
import { createGraph, rdfGraphToNodes } from "../utils.ts";
64

7-
const Graph: React.FC = () => {
8-
const [graphData, setGraphData] = useState<GraphData>({ nodes: [], links: [] });
9-
10-
// Function to load and parse the RDF file
11-
const loadAndParseRDF = async (fileUrl: string, callback: (data: GraphData) => void) => {
12-
const response = await fetch(fileUrl);
13-
const rdfData = await response.text();
14-
const store = createGraph(rdfData, "http://schema.org/");
15-
const graphData = rdfGraphToNodes(store);
16-
callback(graphData);
17-
};
5+
interface GraphProps {
6+
graphData: GraphData;
7+
}
188

9+
const Graph: React.FC<GraphProps> = ({ graphData }: GraphProps) => {
1910
const getGroupColor = (group: string) => {
2011
const colors: { [key: string]: string } = {
2112
person: "red",
@@ -31,13 +22,7 @@ const Graph: React.FC = () => {
3122
return colors[group] || "gray"; // Default to 3 if group not found
3223
};
3324

34-
useEffect(() => {
35-
// loadAndParseRDF("/data/dataset_juelichdata.ttl", setGraphData);
36-
loadAndParseRDF("/data/Helmholtz_KG-sample.ttl", setGraphData);
37-
// loadAndParseRDF("/data/software_rodare.ttl", setGraphData);
38-
}, []);
39-
40-
const getNodeById = (id: string) => graphData.nodes.find((node: Node) => node.id === id);
25+
const getNodeById = (id: string) => graphData.nodes.find((node: NodeObject) => node.id === id);
4126

4227
return (
4328
<ForceGraph3D
@@ -49,8 +34,8 @@ const Graph: React.FC = () => {
4934
return getGroupColor(sourceNode?.group || "");
5035
}}
5136
linkWidth={2}
52-
nodeLabel={(d) => `${(d as Node).label || (d as Node).id}`} // Show the label or fallback to id
53-
linkLabel={(d) => `${(d as Link).label}`} // Show the label for links
37+
nodeLabel={(d) => `${(d as NodeObject).label || (d as NodeObject).id}`} // Show the label or fallback to id
38+
linkLabel={(d) => `${(d as LinkObject).label}`} // Show the label for links
5439
linkDirectionalArrowLength={2.5}
5540
linkDirectionalArrowRelPos={1}
5641
linkCurvature={0.2}

src/components/Layout.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,7 @@ export default function Layout({ children }: LayoutProps) {
1717
<Flex minHeight="100vh" display="flex" flexDirection="column" width="100vw">
1818
<Navbar />
1919
<Box flex="1" width={contentWidth} mx="auto" justifyContent="center">
20-
{/* Ensure the child component takes the full available space */}
21-
<Box height="100%" width="100%" display="flex" justifyContent="center" alignItems="center">
22-
{children}
23-
</Box>
20+
{children}
2421
</Box>
2522
{/*<Footer />*/}
2623
</Flex>

src/components/Selections.tsx

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { Box, Button, Checkbox, FormControl, FormLabel, Input, VStack } from "@chakra-ui/react";
2+
import React, { useState, Dispatch, SetStateAction } from "react";
3+
import { createGraph, rdfGraphToNodes } from "../utils";
4+
import { GraphData } from "react-force-graph-3d";
5+
6+
const parseRDF = async (
7+
rdfData: string,
8+
removeUnconnectedNodes: boolean,
9+
callback: (data: GraphData) => void
10+
) => {
11+
const store = createGraph(rdfData, "http://schema.org/");
12+
const graphData = rdfGraphToNodes(store, removeUnconnectedNodes);
13+
callback(graphData);
14+
};
15+
16+
interface SelectionsProps {
17+
setGraphData: Dispatch<SetStateAction<GraphData>>;
18+
}
19+
20+
const Selections: React.FC<SelectionsProps> = ({ setGraphData }: SelectionsProps) => {
21+
const [file, setFile] = useState<File | null>(null);
22+
const [isChecked, setIsChecked] = useState(false);
23+
24+
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
25+
const selectedFile = event.target.files?.[0] || null;
26+
setFile(selectedFile);
27+
};
28+
29+
const handleCheckboxChange = (event: React.ChangeEvent<HTMLInputElement>) => {
30+
setIsChecked(event.target.checked);
31+
};
32+
33+
const handleSubmit = () => {
34+
if (!file) {
35+
console.log("No file selected.");
36+
return;
37+
}
38+
39+
const reader = new FileReader();
40+
41+
// Add the load event listener
42+
reader.onload = () => {
43+
const fileContent = reader.result;
44+
console.log("File Content:", fileContent); // Log the content of the file
45+
if (typeof fileContent === "string") {
46+
// Ensure the content is a string
47+
parseRDF(fileContent, isChecked, setGraphData);
48+
} else {
49+
console.error("File content is not a string.");
50+
}
51+
};
52+
53+
// Add error event listener for debugging
54+
reader.onerror = (error) => {
55+
console.error("Error reading file:", error);
56+
};
57+
58+
console.log("Reading file:", file.name);
59+
reader.readAsText(file); // Read the file as text
60+
};
61+
62+
return (
63+
<Box p={4} borderWidth="1px" borderRadius="lg" width="100%" mx="auto">
64+
<VStack spacing={4}>
65+
<FormControl>
66+
<FormLabel htmlFor="file-upload">Upload a file</FormLabel>
67+
<Input id="file-upload" type="file" accept=".rdf, .ttl" onChange={handleFileChange} />
68+
</FormControl>
69+
70+
<FormControl>
71+
<Checkbox isChecked={isChecked} onChange={handleCheckboxChange}>
72+
Remove nodes that are not linked
73+
</Checkbox>
74+
</FormControl>
75+
76+
<Button colorScheme="blue" onClick={handleSubmit}>
77+
Submit
78+
</Button>
79+
</VStack>
80+
</Box>
81+
);
82+
};
83+
84+
export default Selections;

src/graph.d.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
// Type definitions for the graph data
2-
interface Node {
3-
id: string;
2+
type NodeType = {
3+
id: string;
44
label?: string;
55
group?: string;
66
}
77

8-
interface Link {
8+
type LinkType = {
99
source: string;
1010
target: string;
1111
label?: string;
1212
}
13-
14-
interface GraphData {
15-
nodes: Node[];
16-
links: Link[];
17-
}

src/utils.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as rdflib from "rdflib";
2-
import { Node, Link, GraphData } from "./components/Graph.tsx";
2+
import { GraphData, LinkObject, NodeObject } from "react-force-graph-3d";
33

44
const createGraph = (rdfData: string, baseUrl: string): rdflib.Store => {
55
const store = rdflib.graph();
@@ -16,22 +16,22 @@ const createGraph = (rdfData: string, baseUrl: string): rdflib.Store => {
1616
};
1717

1818
const rdfGraphToNodes = (store: rdflib.Store, removeUnconnectedNodes: boolean): GraphData => {
19-
const nodesMap = new Map<string, Node>();
20-
const edges: Link[] = [];
19+
const nodesMap = new Map<string, NodeObject<NodeType>>();
20+
const edges: LinkObject<NodeType, LinkType>[] = [];
2121

2222
const safeUpdateElement = (id: string, label?: string, group?: string): void => {
2323
if (!nodesMap.has(id)) {
2424
// create the node
25-
const newNode = { id: id, label: label ?? id, group: group ?? "" };
25+
const newNode = { id: id, label: label ?? id, group: group ?? "" } as NodeType;
2626

2727
nodesMap.set(id, newNode);
2828
} else {
2929
// get the node
3030
const node = nodesMap.get(id);
3131
const updatedNode = {
3232
id: id,
33-
label: label ?? node.label,
34-
group: group ?? node.group,
33+
label: label ?? node?.label,
34+
group: group ?? node?.group,
3535
};
3636

3737
nodesMap.set(id, updatedNode);
@@ -96,13 +96,13 @@ const rdfGraphToNodes = (store: rdflib.Store, removeUnconnectedNodes: boolean):
9696
else return graphData;
9797
};
9898

99-
const removeNonConnectedNodes = (graphData: GraphData): Node[] => {
99+
const removeNonConnectedNodes = (graphData: GraphData): NodeObject[] => {
100100
const connectedNodeIds = new Set(
101-
graphData.links.flatMap((edge: Link) => [edge.source, edge.target])
101+
graphData.links.flatMap(({ source, target }: NodeObject) => [source, target] as LinkObject)
102102
);
103103

104104
// Keep only nodes that are connected by edges
105-
return graphData.nodes.filter((node: Node) => connectedNodeIds.has(node.id));
105+
return graphData.nodes.filter((node: NodeObject) => connectedNodeIds.has(node.id));
106106
};
107107

108108
const typeToGroup = (type: string): string => {

0 commit comments

Comments
 (0)