Skip to content

Commit

Permalink
Add test problem graph using reactflow
Browse files Browse the repository at this point in the history
  • Loading branch information
Elscrux authored and schweikart committed Mar 28, 2024
1 parent 892fac0 commit 037e35d
Show file tree
Hide file tree
Showing 6 changed files with 240 additions and 0 deletions.
46 changes: 46 additions & 0 deletions src/api/ToolboxAPI.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { MetaSolverSetting } from "./data-model/MetaSolverSettings";
import { ProblemGraph } from "./data-model/ProblemGraph";
import { ProblemSolver } from "./data-model/ProblemSolver";
import { Solution } from "./data-model/Solution";
import { SolutionStatus } from "./data-model/SolutionStatus";
Expand Down Expand Up @@ -40,6 +41,17 @@ export async function postProblem<T>(
export async function fetchSolvers(
problemType: string
): Promise<ProblemSolver[]> {
// Return mock promise
// todo remove
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{ id: "tsp1", name: "TSP 1" },
{ id: "tsp2", name: "TSP 2" },
]);
}, 1000);
});

return fetch(`${baseUrl()}/solvers/${problemType}`, {
method: "GET",
headers: {
Expand Down Expand Up @@ -93,3 +105,37 @@ export async function fetchMetaSolverSettings(
return [];
});
}

export async function fetchProblemGraph(): Promise<ProblemGraph> {
return new Promise((resolve) => {
return resolve({
start: {
problemType: "vehicle-routing",
status: SolutionStatus.SOLVED,
solver: {
id: "vr-solver-1",
name: "VR Solver 1"
},
solutionId: 1,
subRoutines: [
{
problemType: "clustering",
status: SolutionStatus.COMPUTING,
solutionId: 2,
solver: {
id: "clustering-solver-1",
name: "Clustering Solver 1"
},
subRoutines: []
},
{
problemType: "travelling-salesman",
status: SolutionStatus.PENDING_USER_ACTION,
solutionId: 3,
subRoutines: []
}
]
}
});
});
}
14 changes: 14 additions & 0 deletions src/api/data-model/ProblemGraph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ProblemSolver } from "./ProblemSolver";
import { SolutionStatus } from "./SolutionStatus";

export interface ProblemGraph {
start: ProblemNode;
}

export interface ProblemNode {
problemType: string;
status: SolutionStatus;
solutionId: number;
solver?: ProblemSolver;
subRoutines: ProblemNode[];
}
1 change: 1 addition & 0 deletions src/api/data-model/SolutionStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export enum SolutionStatus {
INVALID,
COMPUTING,
SOLVED,
PENDING_USER_ACTION,
}
159 changes: 159 additions & 0 deletions src/components/solvers/Graph/ProblemGraphView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { useEffect } from "react";
import { Controls, Edge, Node, ReactFlow, useEdgesState, useNodesState } from "reactflow";
import { ProblemGraph, ProblemNode } from "../../../api/data-model/ProblemGraph";
import { SolutionStatus } from "../../../api/data-model/SolutionStatus";
import { fetchSolvers } from "../../../api/ToolboxAPI";
import 'reactflow/dist/style.css';

export const ProblemGraphView = (props: { graph: ProblemGraph | null }) => {
const [nodes, setNodes, onNodesChange] = useNodesState<Node[]>([]);
const [edges, setEdges, onEdgesChange] = useEdgesState<Edge[]>([]);

useEffect(() => {
if (!props.graph) return;

let { nodes, edges } = problemGraphToReactFlow(props.graph);
setEdges(edges);
setNodes(nodes);

function problemGraphToReactFlow(graph: ProblemGraph): { nodes: Node[]; edges: Edge[] } {
function getNodeId(node: ProblemNode) {
return node.problemType + node.solutionId.toString();
}

function getEdgeId(from: ProblemNode, to: ProblemNode) {
return getNodeId(from) + "-" + getNodeId(to);
}

function getPositionX(levelInfo: LevelInfo | null) {
return levelInfo == null
? 0
: levelInfo.index * 200 - levelInfo.count * 50;
}

function getPositionY(level: number) {
return level * 150;
}

interface LevelInfo {
index: number;
count: number;
}

let nodes: Node[] = [];
let edges: Edge[] = [];

function addNode(
problemNode: ProblemNode,
level: number,
levelInfo: LevelInfo | null
) {
let id = getNodeId(problemNode);
let label =
problemNode.problemType +
(problemNode.solver == null ? "" : " - " + problemNode.solver?.name);
let position = {
x: getPositionX(levelInfo),
y: getPositionY(level)
};
let data = { label: label };
let type: string;
if (level == 0) {
type = "input";
} else if (problemNode.subRoutines.length == 0) {
type = "output";
} else {
type = "default";
}

let node: Node = {
id: id,
data: data,
position: position,
type: type
};

if (problemNode.status == SolutionStatus.PENDING_USER_ACTION) {
node.style = {
background: "teal"
};

fetchSolvers(problemNode.problemType).then((solvers) => {
node.type = "default";
for (let i = 0; i < solvers.length; i++) {
// Add new node manually
let solverId = solvers[i].id.toString();
nodes.push({
id: solverId,
data: { label: solvers[i].name },
position: {
x:
node.position.x +
getPositionX({ index: i, count: solvers.length }),
y: getPositionY(level + 1)
},
type: "output",
style: {
background: "teal"
}
});

edges.push({
id: id + "-" + solverId,
type: "step",
source: id,
target: solverId
});
}
node.style = {
background: "grey"
};

setNodes(nodes);
setEdges(edges);
});
}

nodes.push(node);

for (let i = 0; i < problemNode.subRoutines.length; i++) {
const subRoutine = problemNode.subRoutines[i];
edges.push({
id: getEdgeId(problemNode, subRoutine),
source: id,
target: getNodeId(subRoutine),
animated: subRoutine.status == SolutionStatus.COMPUTING
});
addNode(subRoutine, level + 1, {
index: i,
count: problemNode.subRoutines.length
});
}
}

addNode(graph.start, 0, null);

return { nodes, edges };
}
}, [props.graph, setEdges, setNodes]);

return (
<div
style={{
width: "50vw",
height: "50vh",
border: "2px solid black",
borderRadius: "15px"
}}
>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
fitView>
<Controls />
</ReactFlow>
</div>
);
};
18 changes: 18 additions & 0 deletions src/components/solvers/Graph/TestGraphDisplay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useEffect, useState } from "react";
import { ProblemGraph } from "../../../api/data-model/ProblemGraph";
import { fetchProblemGraph } from "../../../api/ToolboxAPI";
import { ProblemGraphView } from "./ProblemGraphView";

export const TestGraphDisplay = () => {
let [graph, setGraph] = useState<ProblemGraph | null>(null);

useEffect(() => {
fetchProblemGraph()
.then(graph => {
setGraph(graph);
});
}, []);

return (
<ProblemGraphView graph={graph} />);
};
2 changes: 2 additions & 0 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Head from "next/head";
import { baseUrl } from "../api/ToolboxAPI";
import { ProblemChooser } from "../components/landing-page/ProblemChooser";
import { Layout } from "../components/layout/Layout";
import { TestGraphDisplay } from "../components/solvers/Graph/TestGraphDisplay";

const Home: NextPage = () => {
return (
Expand All @@ -14,6 +15,7 @@ const Home: NextPage = () => {
<link rel="icon" href="/favicon.ico" />
{/* TODO: replace favicon */}
</Head>
<TestGraphDisplay/>
<Text color="text" align="justify" as="b">
Welcome to the ProvideQ Toolbox!
</Text>
Expand Down

0 comments on commit 037e35d

Please sign in to comment.