graph now works (somehow)
This commit is contained in:
parent
e0818aa61c
commit
652c245d99
6 changed files with 130 additions and 54 deletions
|
@ -1,30 +1,43 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Graph from "./Graph.svelte";
|
import Graph from "./Graph.svelte";
|
||||||
|
import GraphNode from "./GraphNode.svelte";
|
||||||
|
import { loadTasks } from "./task-loader";
|
||||||
|
import type { TasksFile } from "./task-loader";
|
||||||
|
import TasksLoader from "./TasksLoader.svelte";
|
||||||
|
|
||||||
|
const tasksPromise: Promise<TasksFile> = loadTasks();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main>
|
|
||||||
<h1>Cool graf</h1>
|
|
||||||
<Graph />
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
main {
|
main {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
max-width: 240px;
|
max-width: 240px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
color: #ff3e00;
|
color: #ff3e00;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-size: 4em;
|
font-size: 4em;
|
||||||
font-weight: 100;
|
font-weight: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 640px) {
|
@media (min-width: 640px) {
|
||||||
main {
|
main {
|
||||||
max-width: none;
|
max-width: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<h1>Cool graf</h1>
|
||||||
|
<!--
|
||||||
|
<svg height=200 width=200>
|
||||||
|
<GraphNode task="null" />
|
||||||
|
</svg>
|
||||||
|
-->
|
||||||
|
<TasksLoader promise={tasksPromise} let:data={t}>
|
||||||
|
<Graph tasks={t} />
|
||||||
|
</TasksLoader>
|
||||||
|
</main>
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
<script type="ts">
|
<script type="ts">
|
||||||
|
import GraphNode from "./GraphNode.svelte";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
import * as tasksLoader from "./task-loader";
|
import { createLinksFromTaskMap } from "./task-loader";
|
||||||
|
import type { TasksFile } from "./task-loader";
|
||||||
|
|
||||||
|
export let tasks: TasksFile;
|
||||||
|
let nodes = tasks.tasks;
|
||||||
|
let edges = createLinksFromTaskMap(tasks);
|
||||||
|
|
||||||
// Svelte automatically fills this with a reference
|
// Svelte automatically fills this with a reference
|
||||||
let container: HTMLElement;
|
let container: HTMLElement;
|
||||||
|
@ -12,20 +18,16 @@
|
||||||
width = container.clientWidth - margin.left - margin.right,
|
width = container.clientWidth - margin.left - margin.right,
|
||||||
height = container.clientHeight - margin.top - margin.bottom;
|
height = container.clientHeight - margin.top - margin.bottom;
|
||||||
|
|
||||||
// append the svg object to the body of the page
|
// resize the svg object
|
||||||
var svg = d3
|
var svg = d3
|
||||||
.select(container)
|
.select(container)
|
||||||
.append("svg")
|
.select("svg")
|
||||||
.attr("width", width + margin.left + margin.right)
|
.attr("width", width + margin.left + margin.right)
|
||||||
.attr("height", height + margin.top + margin.bottom)
|
.attr("height", height + margin.top + margin.bottom)
|
||||||
.attr("viewBox", [-width / 2, -height / 2, width, height])
|
.attr("viewBox", [-width / 2, -height / 2, width, height])
|
||||||
.append("g")
|
.select("g")
|
||||||
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
||||||
|
|
||||||
const tasks = await tasksLoader.loadTasks();
|
|
||||||
let nodes = tasks.tasks;
|
|
||||||
let edges = tasksLoader.createLinksFromTaskMap(tasks);
|
|
||||||
|
|
||||||
// Initialize the links
|
// Initialize the links
|
||||||
var link = svg
|
var link = svg
|
||||||
.selectAll("line")
|
.selectAll("line")
|
||||||
|
@ -34,14 +36,13 @@
|
||||||
.append("line")
|
.append("line")
|
||||||
.style("stroke", "#aaa");
|
.style("stroke", "#aaa");
|
||||||
|
|
||||||
// Initialize the nodes
|
/*var node = svg
|
||||||
var node = svg
|
.selectAll("g")
|
||||||
.selectAll("circle")
|
|
||||||
.data(nodes)
|
.data(nodes)
|
||||||
.enter()
|
.enter()
|
||||||
.append("circle")
|
.append("circle")
|
||||||
.attr("r", 20)
|
.attr("r", 20)
|
||||||
.style("fill", "#69b3a2");
|
.style("fill", "#69b3a2");*/
|
||||||
|
|
||||||
// Let's list the force we wanna apply on the network
|
// Let's list the force we wanna apply on the network
|
||||||
var simulation = d3
|
var simulation = d3
|
||||||
|
@ -56,8 +57,8 @@
|
||||||
.links(edges) // and this the list of links
|
.links(edges) // and this the list of links
|
||||||
)
|
)
|
||||||
.force("charge", d3.forceManyBody().strength(-400)) // This adds repulsion between nodes. Play with the -400 for the repulsion strength
|
.force("charge", d3.forceManyBody().strength(-400)) // This adds repulsion between nodes. Play with the -400 for the repulsion strength
|
||||||
.force('x', d3.forceX()) // attracts elements to the zero X coord
|
.force("x", d3.forceX()) // attracts elements to the zero X coord
|
||||||
.force('y', d3.forceY()) // attracts elements to the zero Y coord
|
.force("y", d3.forceY()) // attracts elements to the zero Y coord
|
||||||
.on("tick", ticked)
|
.on("tick", ticked)
|
||||||
.on("end", ticked);
|
.on("end", ticked);
|
||||||
|
|
||||||
|
@ -77,13 +78,7 @@
|
||||||
return d.target.y;
|
return d.target.y;
|
||||||
});
|
});
|
||||||
|
|
||||||
node
|
nodes = nodes;
|
||||||
.attr("cx", function (d) {
|
|
||||||
return d.x + 6;
|
|
||||||
})
|
|
||||||
.attr("cy", function (d) {
|
|
||||||
return d.y - 6;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -95,4 +90,12 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div bind:this={container} />
|
<div bind:this={container}>
|
||||||
|
<svg>
|
||||||
|
<g>
|
||||||
|
{#each nodes as task}
|
||||||
|
<GraphNode {task} />
|
||||||
|
{/each}
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
18
frontend/src/GraphNode.svelte
Normal file
18
frontend/src/GraphNode.svelte
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { TaskDescriptor } from "./task-loader";
|
||||||
|
import Hoverable from "./Hoverable.svelte";
|
||||||
|
|
||||||
|
export let task: TaskDescriptor;
|
||||||
|
|
||||||
|
$: cx = task === undefined || task.x === undefined ? 0 : task.x + 6;
|
||||||
|
$: cy = task === undefined || task.y === undefined ? 0 : task.y - 6;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Hoverable let:hovering={focused}>
|
||||||
|
{#if !focused}
|
||||||
|
<circle r="20" style="fill: #69b3a2" {cx} {cy} />
|
||||||
|
{:else}
|
||||||
|
<circle r="20" style="fill: #ffb3a2" {cx} {cy} />
|
||||||
|
{/if} -->
|
||||||
|
<text x={cx} y={cy}>{task.id}</text>
|
||||||
|
</Hoverable>
|
15
frontend/src/Hoverable.svelte
Normal file
15
frontend/src/Hoverable.svelte
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<script>
|
||||||
|
let hovering;
|
||||||
|
|
||||||
|
function enter() {
|
||||||
|
hovering = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function leave() {
|
||||||
|
hovering = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<g on:mouseenter={enter} on:mouseleave={leave}>
|
||||||
|
<slot hovering={hovering}></slot>
|
||||||
|
</g>
|
30
frontend/src/TasksLoader.svelte
Normal file
30
frontend/src/TasksLoader.svelte
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { TasksFile } from "./task-loader";
|
||||||
|
|
||||||
|
export let promise: Promise<TasksFile>;
|
||||||
|
|
||||||
|
|
||||||
|
let data: TasksFile | null = null;
|
||||||
|
let err: any | null = null;
|
||||||
|
promise.then(
|
||||||
|
(d) => {
|
||||||
|
data = d;
|
||||||
|
},
|
||||||
|
(e) => {
|
||||||
|
err = e;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if data == null && err == null}
|
||||||
|
<div>Loading...</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if data != null }
|
||||||
|
<slot {data} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if err != null }
|
||||||
|
<div>Error - {err}</div>
|
||||||
|
{/if}
|
|
@ -1,8 +1,10 @@
|
||||||
|
import type { SimulationNodeDatum, SimulationLinkDatum } from "d3";
|
||||||
|
|
||||||
export type TaskDescriptor = {
|
export type TaskDescriptor = {
|
||||||
id: string
|
id: string
|
||||||
requires: []
|
requires: []
|
||||||
comment?: string
|
comment?: string
|
||||||
}
|
} & SimulationNodeDatum
|
||||||
|
|
||||||
|
|
||||||
export type TasksFile = {
|
export type TasksFile = {
|
||||||
|
@ -12,11 +14,6 @@ export type TasksFile = {
|
||||||
|
|
||||||
export type TaskMap = Map<string, TaskDescriptor>;
|
export type TaskMap = Map<string, TaskDescriptor>;
|
||||||
|
|
||||||
export type Link<T> = {
|
|
||||||
source: T,
|
|
||||||
target: T
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadTasks(): Promise<TasksFile> {
|
export async function loadTasks(): Promise<TasksFile> {
|
||||||
const r = await fetch("/tasks.json")
|
const r = await fetch("/tasks.json")
|
||||||
return await r.json()
|
return await r.json()
|
||||||
|
@ -34,8 +31,8 @@ export function createTaskMap(tasks: TasksFile): TaskMap {
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createLinksFromTaskMap(tasks: TasksFile): Link<TaskDescriptor>[] {
|
export function createLinksFromTaskMap(tasks: TasksFile): SimulationLinkDatum<TaskDescriptor>[] {
|
||||||
let links: Link<TaskDescriptor>[] = [];
|
let links: SimulationLinkDatum<TaskDescriptor>[] = [];
|
||||||
|
|
||||||
const taskMap = createTaskMap(tasks);
|
const taskMap = createTaskMap(tasks);
|
||||||
|
|
||||||
|
@ -45,7 +42,7 @@ export function createLinksFromTaskMap(tasks: TasksFile): Link<TaskDescriptor>[]
|
||||||
|
|
||||||
if (t === undefined) throw `missing task with id ${id}`;
|
if (t === undefined) throw `missing task with id ${id}`;
|
||||||
|
|
||||||
const l: Link<TaskDescriptor> = {source: t, target: task};
|
const l: SimulationLinkDatum<TaskDescriptor> = {source: t, target: task};
|
||||||
links.push(l);
|
links.push(l);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue