Skip to content

Commit

Permalink
Hacking away for visualizer: now showing docs in a very ugly way
Browse files Browse the repository at this point in the history
  • Loading branch information
KasperFyhn committed Dec 5, 2024
1 parent c655792 commit 7bb4b7c
Show file tree
Hide file tree
Showing 14 changed files with 381 additions and 178 deletions.
5 changes: 5 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@ repos:
rev: v0.5.7
hooks:
- id: ruff

- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.1.0 # Use the sha or tag you want to point at
hooks:
- id: prettier
29 changes: 8 additions & 21 deletions visualizer/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,14 @@
import "./App.css";
import {BrowserRouter, HashRouter, Link, Route, Routes} from "react-router-dom";
import {GraphViewer} from "./graph/GraphViewer";

function NavBar() {
return (
<div className="navbar">
<Link to={"/graph"}>Graph Viewer</Link>
</div>
);
}
import { GraphViewer } from "./graph/GraphViewer";
import React from "react";
import { ServiceContextProvider } from "./service/ServiceContextProvider";

export function App() {
// Create actual routes if/when more functionality is added to the application
return (
<HashRouter>
<Routes>
<Route
path="/"
element={<GraphViewer/>}
/>
</Routes>
</HashRouter>
);
return (
<ServiceContextProvider>
<GraphViewer />
</ServiceContextProvider>
);
}

export default App;
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React from "react";

interface FileUploadComponentProps {
onFileLoaded: (data: any ) => void;
interface JsonFileUploadComponentProps {
onFileLoaded: (data: any) => void;
}

const FileUploadComponent: React.FC<FileUploadComponentProps> = ({
const JsonFileUploadComponent: React.FC<JsonFileUploadComponentProps> = ({
onFileLoaded,
}: FileUploadComponentProps) => {
}: JsonFileUploadComponentProps) => {
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
Expand All @@ -29,4 +29,4 @@ const FileUploadComponent: React.FC<FileUploadComponentProps> = ({
);
};

export default FileUploadComponent;
export default JsonFileUploadComponent;
54 changes: 54 additions & 0 deletions visualizer/src/datasources/NdjsonFileUploadComp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from "react";

interface NdjsonFileUploadComponentProps {
onFileLoaded: (data: Generator<any, void, unknown>) => void;
}

const NdjsonFileUploadComponent: React.FC<NdjsonFileUploadComponentProps> = ({
onFileLoaded,
}: NdjsonFileUploadComponentProps) => {
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
const text = e.target?.result;
if (typeof text === "string") {
// Create a generator for NDJSON parsing
const parseNDJSON = function* (
input: string,
): Generator<any, void, unknown> {
const lines = input.split("\n");
for (const line of lines) {
if (line.trim()) {
try {
yield JSON.parse(line);
} catch (error) {
console.error("Invalid JSON line:", line, error);
}
}
}
};

try {
// Test if the file is a single JSON object
JSON.parse(text); // Throws if it's not a single JSON
alert("This file is a standard JSON file. NDJSON is expected.");
} catch {
// If not, assume it's NDJSON and pass the generator
onFileLoaded(parseNDJSON(text));
}
}
};
reader.readAsText(file);
}
};

return (
<div>
<input type="file" onChange={handleFileChange} />
</div>
);
};

export default NdjsonFileUploadComponent;
71 changes: 71 additions & 0 deletions visualizer/src/docs/DocService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
export abstract class DocService {
abstract getDocData(): Map<string, Doc>;

getDoc(id: string): Doc | undefined {
return this.getDocData().get(id);
}

getDocs(ids: string[]): Doc[] {
return ids
.map((id) => this.getDoc(id))
.filter((v): v is Doc => v !== undefined);
}
}

export interface Doc {
id: string;
text: string;
timestamp: string;
}

export class SampleDocService extends DocService {
readonly docData: Map<string, Doc> = new Map(
[
{
id: "1",
text: "sample text 1",
timestamp: "",
},
{
id: "2",
text: "sample text 1",
timestamp: "",
},
{
id: "3",
text: "sample text 1",
timestamp: "",
},
].map((d) => [d.id, d]),
);

getDocData(): Map<string, Doc> {
return this.docData;
}

getDoc(id: string): Doc | undefined {
return this.docData.get(id);
}
}

export class FileDocService extends DocService {
readonly docData: Map<string, Doc>;

getDocData(): Map<string, Doc> {
return this.docData;
}

constructor(docData: Doc[]) {
super();
this.docData = new Map(
docData.map((d) => [
d.id,
{
id: d.id,
text: d.text,
timestamp: d.timestamp,
},
]),
);
}
}
35 changes: 7 additions & 28 deletions visualizer/src/graph/GraphService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,15 @@ export function filter(
const representative: EnrichedEdge = group.at(0)!;
return {
...representative,
id: representative.from + '->' + representative.to,
id: representative.from + "->" + representative.to,
label: group
.slice(0, 3)
.map((e) => e.label)
.join(", "),
width:
Math.log(group.map((e) => e.stats.frequency).reduce((a, b) => a + b)),
group: group
width: Math.log(
group.map((e) => e.stats.frequency).reduce((a, b) => a + b),
),
group: group,
};
});

Expand All @@ -134,8 +135,8 @@ export function filter(
...node,
opacity: node.label?.toLowerCase().includes(filter.labelSearch) ? 1 : 0.2,
font: {
size: 14 + node.stats.frequency / 100
}
size: 14 + node.stats.frequency / 100,
},
}));

return { nodes, edges };
Expand All @@ -148,9 +149,6 @@ export interface DataBounds {
}

export abstract class GraphService {
private nodesMap: Map<string, EnrichedNode> | null = null;
private edgesMap: Map<string, EnrichedNode> | null = null;

abstract getGraph(): EnrichedGraphData;

getBounds(): DataBounds {
Expand Down Expand Up @@ -183,25 +181,6 @@ export abstract class GraphService {
.flatMap((edge) => [edge.from!.toString(), edge.to!.toString()]),
);
}

getNode(nodeId: string): EnrichedNode | undefined {
if (this.nodesMap === null) {
this.nodesMap = new Map(
this.getGraph().nodes.map((node) => [node.id!.toString(), node]),
);
}
return this.nodesMap.get(nodeId);
}

// getEdges(edgeFromAndTo: string): EnrichedEdge[] | undefined {
// if (this.edgesMap === null) {
// this.edgesMap = new Map(
// this.getGraph().edges.map((node) => [node.id!.toString(), node]),
// );
// }
//
// return undefined;
// }
}

export class SampleGraphService extends GraphService {
Expand Down
82 changes: 34 additions & 48 deletions visualizer/src/graph/GraphViewer.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,35 @@
import React, { useMemo, useRef, useState } from "react";
import {
EdgeGroup,
EnrichedNode,
FileGraphService,
filter,
GraphFilter,
GraphService,
SampleGraphService,
} from "./GraphService";
import FileUploadComponent from "../datasources/FileUploadComp";
import React, { useMemo, useState } from "react";
import { EdgeGroup, EnrichedNode, filter, GraphFilter } from "./GraphService";
import Graph, { GraphEvents, Options } from "react-vis-graph-wrapper";
import { GraphFilterControlPanel } from "./GraphFilterControlPanel";
import { GraphOptionsControlPanel } from "./GraphOptionsControlPanel";
import { NodeInfo } from "./NodeInfo";
import { EdgeInfo } from "./EdgeInfo";
import { NodeInfo } from "../inspector/NodeInfo";
import { EdgeInfo } from "../inspector/EdgeInfo";
import { useServiceContext } from "../service/ServiceContextProvider";

export interface GraphViewerProps {}

export const GraphViewer: React.FC = () => {
let graphServiceRef = useRef<GraphService>(new SampleGraphService());
const { getGraphService, getDocService } = useServiceContext();

const handleFileLoaded = (data: any) => {
graphServiceRef.current = new FileGraphService(data);
const top50 =
graphServiceRef.current
.getGraph()
.nodes.map((n) => n.stats.frequency)
.sort((a, b) => b - a)
.at(100) || 1;
let { minNodeFrequency, maxNodeFrequency, maxEdgeFrequency } =
graphServiceRef.current.getBounds();
setGraphFilter(
new GraphFilter(
minNodeFrequency,
top50,
maxNodeFrequency,
1,
Math.floor(top50 / 10),
maxEdgeFrequency,
),
);
};
const top50 =
getGraphService()
.getGraph()
.nodes.map((n) => n.stats.frequency)
.sort((a, b) => b - a)
.at(100) || 1;
let { minNodeFrequency, maxNodeFrequency, maxEdgeFrequency } =
getGraphService().getBounds();

const [graphFilter, setGraphFilter] = useState<GraphFilter>(
new GraphFilter(1, 1, 10, 1, 1, 10),
new GraphFilter(
minNodeFrequency,
top50,
maxNodeFrequency,
1,
Math.floor(top50 / 10),
maxEdgeFrequency,
),
);
const [subgraphNodes, setSubgraphNodes] = useState(new Set<string>());
const [selectedNode, setSelectedNode] = useState<EnrichedNode | undefined>(
Expand All @@ -54,8 +42,8 @@ export const GraphViewer: React.FC = () => {
const filteredGraphData = useMemo(() => {
const baseGraphData =
subgraphNodes.size > 0
? graphServiceRef.current.getSubGraph(subgraphNodes)
: graphServiceRef.current.getGraph();
? getGraphService().getSubGraph(subgraphNodes)
: getGraphService().getGraph();
return filter(graphFilter, baseGraphData);
}, [graphFilter, subgraphNodes]);

Expand Down Expand Up @@ -91,19 +79,21 @@ export const GraphViewer: React.FC = () => {
doubleClick: ({ nodes }) => {
const newSelected = new Set(subgraphNodes);
nodes.forEach((element: string) => {
Array.from(graphServiceRef.current.getConnectedNodes(element)).forEach(
(c) => newSelected.add(c),
Array.from(getGraphService().getConnectedNodes(element)).forEach((c) =>
newSelected.add(c),
);
});
setSubgraphNodes(newSelected);
},
selectNode: ({ nodes }) => {
selectNode: ({ nodes, edges }) => {
setSelectedEdge(undefined);
setSelectedNode(graphDataMaps.nodesMap.get(nodes[0]));
},
selectEdge: ({ edges }) => {
setSelectedNode(undefined);
setSelectedEdge(graphDataMaps.edgeGroupMap.get(edges[0]));
selectEdge: ({ nodes, edges }) => {
if (nodes.length < 1) {
setSelectedNode(undefined);
setSelectedEdge(graphDataMaps.edgeGroupMap.get(edges[0]));
}
},
deselectNode: () => {
setSelectedNode(undefined);
Expand All @@ -130,10 +120,6 @@ export const GraphViewer: React.FC = () => {

return (
<div>
<div className={"padded flex-container"}>
<FileUploadComponent onFileLoaded={handleFileLoaded} />
</div>
<hr />
<div className={"padded"}>
<GraphFilterControlPanel
graphFilter={graphFilter}
Expand Down
Loading

0 comments on commit 7bb4b7c

Please sign in to comment.