Implement Dummy Graph-View
This commit is contained in:
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
59
index.html
Normal file
59
index.html
Normal file
@ -0,0 +1,59 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<!-- TODO: We want a toolbar at the top, the model visualization on the left -->
|
||||
<!-- and the graph on the right. -->
|
||||
<!-- The toolbar should have a model selector, an initial state editor, -->
|
||||
<!-- an initial state selector (from presets) and a generate button. -->
|
||||
<body style="margin: 5px">
|
||||
<div
|
||||
id="toolbar"
|
||||
style="
|
||||
width: fit-content;
|
||||
height: 26px;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 5px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
"
|
||||
>
|
||||
<button id="select_model_button">Select Model</button>
|
||||
<button id="select_state_button">Select State</button>
|
||||
<button id="generate_graph_button">Generate Graph</button>
|
||||
<button id="reset_view_button">Reset View</button>
|
||||
<button id="clear_graph_button">Clear Graph</button>
|
||||
</div>
|
||||
|
||||
<div id="columns" style="display: flex">
|
||||
<div
|
||||
id="model"
|
||||
style="
|
||||
/* Body m-left / 2 + graph m-left / 2 + body m-right / 2 + borders x4 / 2 */
|
||||
/* 2.5px + 2.5px + 2.5px + 2px */
|
||||
width: calc(50vw - 9.5px);
|
||||
/* Bar height + body m-top + bar m-bot + body m-bot + border x2 */
|
||||
/* 26px + 5px + 5px + 5px + 2px */
|
||||
height: calc(100vh - 43px);
|
||||
border-style: solid;
|
||||
border-color: black;
|
||||
border-width: 1px;
|
||||
"
|
||||
></div>
|
||||
<div
|
||||
id="graph"
|
||||
style="
|
||||
width: calc(50vw - 7.5px);
|
||||
height: calc(100vh - 43px);
|
||||
margin-left: 5px;
|
||||
border-style: solid;
|
||||
border-color: black;
|
||||
border-width: 1px;
|
||||
"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<!-- Load the script last, such that the dom is populated beforehand -->
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
1416
package-lock.json
generated
Normal file
1416
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
package.json
Normal file
18
package.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "my3dgraph",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.6.2",
|
||||
"vite": "^7.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"3d-force-graph": "^1.78.4"
|
||||
}
|
||||
}
|
1
public/vite.svg
Normal file
1
public/vite.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
94
src/graph.js
Normal file
94
src/graph.js
Normal file
@ -0,0 +1,94 @@
|
||||
import ForceGraph3D from "3d-force-graph";
|
||||
|
||||
export const generate_sample_data = () => {
|
||||
// Example data:
|
||||
// {
|
||||
// "nodes": [
|
||||
// {
|
||||
// "id": "id1",
|
||||
// "name": "name1",
|
||||
// "val": 1
|
||||
// },
|
||||
// {
|
||||
// "id": "id2",
|
||||
// "name": "name2",
|
||||
// "val": 10
|
||||
// },
|
||||
// ...
|
||||
// ],
|
||||
// "links": [
|
||||
// {
|
||||
// "source": "id1",
|
||||
// "target": "id2"
|
||||
// },
|
||||
// ...
|
||||
// ]
|
||||
// }
|
||||
let data = {
|
||||
nodes: [...Array(50).keys()].map((i) => ({ id: i })),
|
||||
links: [...Array(50).keys()]
|
||||
.filter((id) => id)
|
||||
.map((id) => ({
|
||||
source: id,
|
||||
target: Math.round(Math.random() * (id - 1)),
|
||||
})),
|
||||
};
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const generate_graph = (data) => {
|
||||
// TODO: Highlight the current state by coloring the node
|
||||
let graph = ForceGraph3D()(document.getElementById("graph"))
|
||||
// Input the data into the graph
|
||||
.graphData(data)
|
||||
|
||||
// Set up the styling
|
||||
.backgroundColor("#FFFFFF")
|
||||
.nodeColor(["#555555"])
|
||||
.linkColor(["#000000"])
|
||||
|
||||
// Set up the interactions
|
||||
.onNodeHover(
|
||||
(node) => (document.body.style.cursor = node ? "pointer" : null),
|
||||
)
|
||||
.onNodeClick((node) => {
|
||||
// TODO: Visualize the clicked state in the GameView
|
||||
const distance = 40;
|
||||
const distRatio = 1 + distance / Math.hypot(node.x, node.y, node.z);
|
||||
graph.cameraPosition(
|
||||
{ x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio },
|
||||
{ x: node.x, y: node.y, z: node.z },
|
||||
1000,
|
||||
);
|
||||
});
|
||||
|
||||
reset_graph_view(graph);
|
||||
|
||||
return graph;
|
||||
};
|
||||
|
||||
const get_viewport_dims = () => {
|
||||
let vw = Math.max(
|
||||
document.documentElement.clientWidth || 0,
|
||||
window.innerWidth || 0,
|
||||
);
|
||||
let vh = Math.max(
|
||||
document.documentElement.clientHeight || 0,
|
||||
window.innerHeight || 0,
|
||||
);
|
||||
|
||||
return [vw, vh];
|
||||
};
|
||||
|
||||
export const reset_graph_view = (graph) => {
|
||||
if (graph === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [vw, vh] = get_viewport_dims();
|
||||
graph
|
||||
.width(vw / 2 - 9.5)
|
||||
.height(vh - 43)
|
||||
.zoomToFit();
|
||||
};
|
28
src/main.js
Normal file
28
src/main.js
Normal file
@ -0,0 +1,28 @@
|
||||
import "./graph.js";
|
||||
import {
|
||||
generate_graph,
|
||||
reset_graph_view,
|
||||
generate_sample_data,
|
||||
} from "./graph.js";
|
||||
|
||||
let data = null;
|
||||
let graph = null;
|
||||
|
||||
const clear = () => {
|
||||
document.getElementById("graph").innerHTML = "";
|
||||
graph = null;
|
||||
data = null;
|
||||
};
|
||||
|
||||
// Set up button event-handlers
|
||||
document
|
||||
.getElementById("generate_graph_button")
|
||||
.addEventListener("click", () => {
|
||||
clear();
|
||||
data = generate_sample_data();
|
||||
graph = generate_graph(data);
|
||||
});
|
||||
document.getElementById("reset_view_button").addEventListener("click", () => {
|
||||
reset_graph_view(graph);
|
||||
});
|
||||
document.getElementById("clear_graph_button").addEventListener("click", clear);
|
Reference in New Issue
Block a user