diff --git a/frontend/src/Editor.svelte b/frontend/src/Editor.svelte index db332a2..4c5632b 100644 --- a/frontend/src/Editor.svelte +++ b/frontend/src/Editor.svelte @@ -7,7 +7,7 @@ export let tasks: TasksFile; - let repulsionForce: number = -600; + let repulsionForce: number = -1000; let clicked: string[] = []; let graph: Graph; let currentTask: TaskDescriptor | null = null; diff --git a/frontend/src/Graph.svelte b/frontend/src/Graph.svelte index 1c48c1b..a164296 100644 --- a/frontend/src/Graph.svelte +++ b/frontend/src/Graph.svelte @@ -5,10 +5,11 @@ import * as d3 from "d3"; import type { TasksFile, TaskDescriptor } from "./task-loader"; import { createNodesAndEdges } from "./graph-types"; + import { taskForce } from "./task-force"; export let tasks: TasksFile; let hoveredTask: null | string = null; - export let repulsionForce: number = -600; + export let repulsionForce: number = -1000; // Svelte automatically fills these with a reference let container: HTMLElement; @@ -61,7 +62,8 @@ ) .force("charge", d3.forceManyBody().strength(repulsionForce)) // 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("y", d3.forceY()) // attracts elements to the zero Y coord + .force("y", d3.forceY().strength(0.5)) // attracts elements to the zero Y coord + .force("dependencies", taskForce()) .on("tick", ticked) .on("end", ticked); diff --git a/frontend/src/GraphEdge.svelte b/frontend/src/GraphEdge.svelte index c56e8ab..7fe75dd 100644 --- a/frontend/src/GraphEdge.svelte +++ b/frontend/src/GraphEdge.svelte @@ -10,5 +10,8 @@ $: y2 = edge === undefined || edge.target === undefined || edge.target.y === undefined ? 0 : edge.target.y; - + + + + \ No newline at end of file diff --git a/frontend/src/task-force.ts b/frontend/src/task-force.ts new file mode 100644 index 0000000..fa30e90 --- /dev/null +++ b/frontend/src/task-force.ts @@ -0,0 +1,50 @@ +import type { TaskId } from "./graph-types"; + +/* copied from graph-types.ts */ +function toMapById(nodes: TaskId[]): Map { + let nodeMap = new Map(); + for (let task of nodes) { + if (task.id in nodeMap) + throw 'duplicate IDs'; + nodeMap.set(task.id, task); + } + return nodeMap; +} + +export function taskForce(): d3.Force { + let myNodes: TaskId[] | null = null; + let deps: Map = new Map(); + let idMap: Map = new Map(); + + function getNumberOfDeps(task: TaskId): number { + if (deps.has(task.id)) return deps.get(task.id)!; + + if (task.task.requires.length == 0) return 0; + + let res = 0; + for (let r of task.task.requires) { + res += getNumberOfDeps(idMap.get(r)!) + 1; + } + deps.set(task.id, res); + return res; + } + + let force: d3.Force = function(alpha: number) { + if (myNodes == null) throw 'nodes not initialized'; + + for (let task of myNodes) { + if (task.vy == null) { + task.vy = 0 + } + + task.vy += getNumberOfDeps(task) * 25 * alpha; + } + } + + force.initialize = function(nodes: TaskId[]) { + myNodes = nodes; + idMap = toMapById(myNodes); + } + + return force; +}