Browse Source

graph now works (somehow)

mj-deploy
Vašek Šraier 4 years ago
parent
commit
652c245d99
  1. 59
      frontend/src/App.svelte
  2. 47
      frontend/src/Graph.svelte
  3. 18
      frontend/src/GraphNode.svelte
  4. 15
      frontend/src/Hoverable.svelte
  5. 30
      frontend/src/TasksLoader.svelte
  6. 15
      frontend/src/task-loader.ts

59
frontend/src/App.svelte

@ -1,30 +1,43 @@
<script lang="ts"> <script lang="ts">
import Graph from "./Graph.svelte"; import Graph from "./Graph.svelte";
</script> import GraphNode from "./GraphNode.svelte";
import { loadTasks } from "./task-loader";
import type { TasksFile } from "./task-loader";
import TasksLoader from "./TasksLoader.svelte";
<main> const tasksPromise: Promise<TasksFile> = loadTasks();
<h1>Cool graf</h1> </script>
<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>

47
frontend/src/Graph.svelte

@ -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

@ -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

@ -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

@ -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}

15
frontend/src/task-loader.ts

@ -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);
} }
} }