WIP
This commit is contained in:
+47
-70
@@ -1,13 +1,6 @@
|
|||||||
import "./GraphBrowser.css";
|
import "./GraphBrowser.css";
|
||||||
import { type Component, createSignal, onMount } from "solid-js";
|
import { type Component, createSignal, onMount } from "solid-js";
|
||||||
import {
|
import type { Edge, Graph, Node } from "#/lib/Graph.ts";
|
||||||
drawEdge,
|
|
||||||
drawNode,
|
|
||||||
type Edge,
|
|
||||||
type Graph,
|
|
||||||
type Node,
|
|
||||||
type PositionPartial,
|
|
||||||
} from "#/lib/Graph.ts";
|
|
||||||
import {
|
import {
|
||||||
createVector,
|
createVector,
|
||||||
getDistance,
|
getDistance,
|
||||||
@@ -16,25 +9,39 @@ import {
|
|||||||
type Vector,
|
type Vector,
|
||||||
} from "#/lib/Vector.ts";
|
} from "#/lib/Vector.ts";
|
||||||
|
|
||||||
function drawGraph<T extends PositionPartial>(
|
type PhysicsPartial = {
|
||||||
graph: Graph<T>,
|
position: Vector;
|
||||||
ctx: CanvasRenderingContext2D,
|
|
||||||
): void {
|
|
||||||
for (const [node, neighbours] of graph.entries()) {
|
|
||||||
drawNode(node, ctx);
|
|
||||||
for (const neighbour of neighbours) {
|
|
||||||
drawEdge([node, neighbour], ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type PhysicsPartial = PositionPartial & {
|
|
||||||
velocity: Vector;
|
velocity: Vector;
|
||||||
acceleration: Vector;
|
acceleration: Vector;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function drawNode(
|
||||||
|
node: Node<PhysicsPartial>,
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
color: string = "#fff",
|
||||||
|
): void {
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.strokeStyle = color;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(node.position.x, node.position.y, 8, 0, 2 * Math.PI);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawEdge(
|
||||||
|
edge: Edge<PhysicsPartial>,
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
color: string = "#fff",
|
||||||
|
): void {
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.strokeStyle = color;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(edge[0].position.x, edge[0].position.y);
|
||||||
|
ctx.lineTo(edge[1].position.x, edge[1].position.y);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
function applyRepulsion(nodes: Array<PhysicsPartial>): void {
|
function applyRepulsion(nodes: Array<PhysicsPartial>): void {
|
||||||
const strength = 1.8;
|
const strength = 8;
|
||||||
|
|
||||||
nodes.forEach((node, index) => {
|
nodes.forEach((node, index) => {
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
@@ -50,7 +57,6 @@ function applyRepulsion(nodes: Array<PhysicsPartial>): void {
|
|||||||
const direction = createVector(dx, dy);
|
const direction = createVector(dx, dy);
|
||||||
|
|
||||||
const distanceSquare = dx * dx + dy * dy;
|
const distanceSquare = dx * dx + dy * dy;
|
||||||
|
|
||||||
const magnitude = strength / (distanceSquare + Number.EPSILON);
|
const magnitude = strength / (distanceSquare + Number.EPSILON);
|
||||||
|
|
||||||
scale(direction, magnitude);
|
scale(direction, magnitude);
|
||||||
@@ -61,17 +67,17 @@ function applyRepulsion(nodes: Array<PhysicsPartial>): void {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function doTheHookeThing<T extends PhysicsPartial>([a, b]: Edge<T>): void {
|
function hookEdge<T extends PhysicsPartial>([a, b]: Edge<T>): void {
|
||||||
const restLength = 48;
|
const restLength = 48;
|
||||||
const stiffness = 0.3;
|
const stiffness = 0.1;
|
||||||
|
|
||||||
const direction = createVector(0, 0);
|
const direction = createVector();
|
||||||
setDistanceNormal(direction, a.position, b.position);
|
setDistanceNormal(direction, a.position, b.position);
|
||||||
|
|
||||||
const distance = getDistance(a.position, b.position);
|
const distance = getDistance(a.position, b.position);
|
||||||
const magnitude = (distance - restLength) * stiffness;
|
const magnitude = (distance - restLength) * stiffness + Number.EPSILON;
|
||||||
|
|
||||||
scale(direction, magnitude / 2 + Number.EPSILON);
|
scale(direction, magnitude / 2);
|
||||||
|
|
||||||
a.acceleration.x -= direction.x;
|
a.acceleration.x -= direction.x;
|
||||||
a.acceleration.y -= direction.y;
|
a.acceleration.y -= direction.y;
|
||||||
@@ -80,43 +86,13 @@ function doTheHookeThing<T extends PhysicsPartial>([a, b]: Edge<T>): void {
|
|||||||
b.acceleration.y += direction.y;
|
b.acceleration.y += direction.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
function placeNodes<T extends PositionPartial>(graph: Graph<T>): void {
|
|
||||||
const visited: Array<Node<T>> = [];
|
|
||||||
const currentPosition = createVector();
|
|
||||||
const radius = 24;
|
|
||||||
const dist = radius * 1.5;
|
|
||||||
|
|
||||||
for (const [node, neighbours] of graph.entries()) {
|
|
||||||
if (visited.includes(node)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
visited.push(node);
|
|
||||||
|
|
||||||
node.position.x = currentPosition.x;
|
|
||||||
node.position.y = currentPosition.y;
|
|
||||||
|
|
||||||
neighbours.forEach((neighbour, index) => {
|
|
||||||
const fraction = (index + 1) / neighbours.length;
|
|
||||||
const rad = Math.PI * 2 * fraction;
|
|
||||||
|
|
||||||
neighbour.position.x = currentPosition.x + dist * Math.cos(rad);
|
|
||||||
neighbour.position.y = currentPosition.y + dist * Math.sin(rad);
|
|
||||||
visited.push(neighbour);
|
|
||||||
});
|
|
||||||
|
|
||||||
currentPosition.y += dist;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GraphBrowser: Component<{
|
export const GraphBrowser: Component<{
|
||||||
graph: Graph<PhysicsPartial & { label: string }>;
|
graph: Graph<PhysicsPartial & { label: string }>;
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
let canvasRef!: HTMLCanvasElement;
|
let canvasRef!: HTMLCanvasElement;
|
||||||
|
|
||||||
const [width, setWidth] = createSignal(300);
|
const [width] = createSignal(300);
|
||||||
const [height, setHeight] = createSignal(300);
|
const [height] = createSignal(300);
|
||||||
|
|
||||||
// placeNodes(props.graph);
|
|
||||||
|
|
||||||
const nodes: Array<Node<PhysicsPartial>> = [...props.graph.keys()];
|
const nodes: Array<Node<PhysicsPartial>> = [...props.graph.keys()];
|
||||||
|
|
||||||
@@ -135,17 +111,12 @@ export const GraphBrowser: Component<{
|
|||||||
alpha: false,
|
alpha: false,
|
||||||
})!;
|
})!;
|
||||||
|
|
||||||
const damping = 0.9;
|
const damping = 0.8;
|
||||||
|
|
||||||
let then = self.performance.now();
|
|
||||||
let delta = 0;
|
|
||||||
const animate = (timestamp: number): void => {
|
|
||||||
delta = timestamp - then;
|
|
||||||
then = timestamp;
|
|
||||||
|
|
||||||
|
const animate = (): void => {
|
||||||
applyRepulsion(nodes);
|
applyRepulsion(nodes);
|
||||||
for (const edge of edges) {
|
for (const edge of edges) {
|
||||||
doTheHookeThing(edge);
|
hookEdge(edge);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
@@ -167,12 +138,18 @@ export const GraphBrowser: Component<{
|
|||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.translate(width() / 2, height() / 2);
|
ctx.translate(width() / 2, height() / 2);
|
||||||
|
|
||||||
drawGraph(props.graph, ctx);
|
for (const node of nodes) {
|
||||||
|
drawNode(node, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const edge of edges) {
|
||||||
|
drawEdge(edge, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
requestAnimationFrame(animate);
|
requestAnimationFrame(animate);
|
||||||
};
|
};
|
||||||
|
|
||||||
animate(then);
|
animate();
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,56 +1,7 @@
|
|||||||
import type { Vector } from "./Vector.ts";
|
|
||||||
|
|
||||||
export type Node<T> = T;
|
export type Node<T> = T;
|
||||||
export type Edge<T> = [Node<T>, Node<T>];
|
export type Edge<T> = [Node<T>, Node<T>];
|
||||||
export type Graph<T> = Map<Node<T>, Array<Node<T>>>;
|
export type Graph<T> = Map<Node<T>, Array<Node<T>>>;
|
||||||
|
|
||||||
export type PositionPartial = {
|
|
||||||
position: Vector;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function drawNode(
|
|
||||||
node: Node<PositionPartial>,
|
|
||||||
ctx: CanvasRenderingContext2D,
|
|
||||||
color: string = "#fff",
|
|
||||||
): void {
|
|
||||||
ctx.lineWidth = 2;
|
|
||||||
ctx.strokeStyle = color;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(node.position.x, node.position.y, 8, 0, 2 * Math.PI);
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getNodeByPosition(
|
|
||||||
nodes: Array<Node<PositionPartial>>,
|
|
||||||
position: Vector,
|
|
||||||
): Node<PositionPartial> | null {
|
|
||||||
for (const node of nodes) {
|
|
||||||
if (
|
|
||||||
node.position.x <= position.x + 16 &&
|
|
||||||
node.position.x >= position.x - 16 &&
|
|
||||||
node.position.y <= position.y + 16 &&
|
|
||||||
node.position.y >= position.y - 16
|
|
||||||
) {
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function drawEdge(
|
|
||||||
edge: Edge<PositionPartial>,
|
|
||||||
ctx: CanvasRenderingContext2D,
|
|
||||||
color: string = "#fff",
|
|
||||||
): void {
|
|
||||||
ctx.lineWidth = 2;
|
|
||||||
ctx.strokeStyle = color;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(edge[0].position.x, edge[0].position.y);
|
|
||||||
ctx.lineTo(edge[1].position.x, edge[1].position.y);
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createGraph<T>(
|
export function createGraph<T>(
|
||||||
nodes: Array<Node<T>>,
|
nodes: Array<Node<T>>,
|
||||||
edges: Array<Edge<T>>,
|
edges: Array<Edge<T>>,
|
||||||
|
|||||||
Reference in New Issue
Block a user