graf: nepouzivaji se primo data z tasks.json pro renderovani
This commit is contained in:
parent
7668b12abb
commit
2494eab6e7
6 changed files with 85 additions and 68 deletions
|
@ -1,41 +1,27 @@
|
||||||
<script type="ts">
|
<script type="ts">
|
||||||
import Graph from "./Graph.svelte";
|
import Graph from "./Graph.svelte";
|
||||||
import { nonNull } from "./helpers";
|
import { nonNull } from "./helpers";
|
||||||
import { grabAssignment } from "./ksp-task-grabber";
|
|
||||||
import type { TaskDescriptor, TasksFile } from "./task-loader";
|
import type { TaskDescriptor, TasksFile } from "./task-loader";
|
||||||
import { saveTasks, createTaskMap, getCategories } from "./task-loader";
|
import { saveTasks, getCategories } from "./task-loader";
|
||||||
import TaskDisplay from "./TaskDisplay.svelte";
|
import TaskDisplay from "./TaskDisplay.svelte";
|
||||||
|
|
||||||
export let tasks: TasksFile;
|
export let tasks: TasksFile;
|
||||||
|
|
||||||
let selectedTask: string | null = null;
|
|
||||||
let clickedTask: string | null = null;
|
|
||||||
let repulsionForce: number = -600;
|
let repulsionForce: number = -600;
|
||||||
let clicked: string[] = [];
|
let clicked: string[] = [];
|
||||||
|
let graph: Graph;
|
||||||
|
let currentTask: TaskDescriptor | null = null;
|
||||||
|
|
||||||
function clickTask(e: CustomEvent<TaskDescriptor>) {
|
function clickTask(e: CustomEvent<TaskDescriptor>) {
|
||||||
// sanity check
|
|
||||||
if (selectedTask == null) {
|
|
||||||
alert("tohle je divny event");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ukladani seznamu poslednich kliknuti
|
// ukladani seznamu poslednich kliknuti
|
||||||
clicked.push(selectedTask);
|
clicked = [...clicked, e.detail.id];
|
||||||
if (clicked.length > 3)
|
if (clicked.length > 3)
|
||||||
clicked = clicked.slice(clicked.length - 3, clicked.length);
|
clicked = clicked.slice(clicked.length - 3, clicked.length);
|
||||||
clicked = clicked;
|
|
||||||
|
|
||||||
// trackovani, kam se naposledy kliknulo
|
|
||||||
if (clickedTask == selectedTask) {
|
|
||||||
clickedTask = null;
|
|
||||||
} else {
|
|
||||||
clickedTask = selectedTask;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$: taskMap = createTaskMap(tasks);
|
function startHovering(e: CustomEvent<TaskDescriptor>) {
|
||||||
$: currentTask = clickedTask != null ? clickedTask : selectedTask;
|
currentTask = e.detail;
|
||||||
|
}
|
||||||
|
|
||||||
function addEdge() {
|
function addEdge() {
|
||||||
if (clicked.length > 1) {
|
if (clicked.length > 1) {
|
||||||
|
@ -44,21 +30,15 @@
|
||||||
|
|
||||||
tasks.tasks.forEach((t) => {
|
tasks.tasks.forEach((t) => {
|
||||||
if (t.id == first) {
|
if (t.id == first) {
|
||||||
t.requires.push(second);
|
t.requires = [...t.requires, second];
|
||||||
t = t;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
tasks = tasks;
|
tasks = tasks;
|
||||||
|
|
||||||
// run simulation
|
|
||||||
graph.runSimulation()
|
|
||||||
} else {
|
} else {
|
||||||
alert("Nope, prvni musis nekam klikat...");
|
alert("Nope, prvni musis nekam klikat...");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let graph: Graph
|
|
||||||
|
|
||||||
async function saveCurrentState() {
|
async function saveCurrentState() {
|
||||||
await saveTasks(tasks);
|
await saveTasks(tasks);
|
||||||
}
|
}
|
||||||
|
@ -122,8 +102,8 @@
|
||||||
<Graph
|
<Graph
|
||||||
{tasks}
|
{tasks}
|
||||||
{repulsionForce}
|
{repulsionForce}
|
||||||
bind:selectedTask
|
|
||||||
on:selectTask={clickTask}
|
on:selectTask={clickTask}
|
||||||
|
on:preSelectTask={startHovering}
|
||||||
bind:this={graph} />
|
bind:this={graph} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -133,7 +113,7 @@
|
||||||
<div>
|
<div>
|
||||||
<button on:click={addEdge}>Pridat hranu - posledni vyzaduje predposledni</button>
|
<button on:click={addEdge}>Pridat hranu - posledni vyzaduje predposledni</button>
|
||||||
</div>
|
</div>
|
||||||
<div><button on:click={toggleDivnaPromena}>Spustit simulaci</button></div>
|
<div><button on:click={graph.runSimulation}>Spustit simulaci</button></div>
|
||||||
<div>
|
<div>
|
||||||
Repulsion force: <input type="number" bind:value={repulsionForce} name="repulsionForceInput" max="1000" min="-10000" />
|
Repulsion force: <input type="number" bind:value={repulsionForce} name="repulsionForceInput" max="1000" min="-10000" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -143,14 +123,14 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="taskDetails">
|
<div class="taskDetails">
|
||||||
{#if currentTask != null}
|
{#if currentTask != null}
|
||||||
<h3>{currentTask}</h3>
|
<h3>{currentTask.id}</h3>
|
||||||
<span>{nonNull(taskMap.get(currentTask)).comment}</span>
|
<span>{nonNull(currentTask).comment}</span>
|
||||||
<ul>
|
<ul>
|
||||||
{#each getCategories(tasks, currentTask) as cat}
|
{#each getCategories(tasks, currentTask.id) as cat}
|
||||||
<li>{cat}</li>
|
<li>{cat}</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
<TaskDisplay taskId={currentTask} />
|
<TaskDisplay taskId={currentTask.id} />
|
||||||
{:else}
|
{:else}
|
||||||
<h3>Nothing selected...</h3>
|
<h3>Nothing selected...</h3>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
import GraphEdge from "./GraphEdge.svelte";
|
import GraphEdge from "./GraphEdge.svelte";
|
||||||
import { createEventDispatcher, onMount } from "svelte";
|
import { createEventDispatcher, onMount } from "svelte";
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
import { createLinksFromTaskMap } from "./task-loader";
|
|
||||||
import type { TasksFile, TaskDescriptor } from "./task-loader";
|
import type { TasksFile, TaskDescriptor } from "./task-loader";
|
||||||
|
import { createNodesAndEdges } from "./graph-types";
|
||||||
|
|
||||||
export let tasks: TasksFile;
|
export let tasks: TasksFile;
|
||||||
let hoveredTask: null | string = null;
|
let hoveredTask: null | string = null;
|
||||||
|
@ -16,8 +16,17 @@
|
||||||
let clientWidth: number;
|
let clientWidth: number;
|
||||||
let svgElement: SVGElement;
|
let svgElement: SVGElement;
|
||||||
|
|
||||||
$: nodes = tasks.tasks;
|
// this prevents svelte from updating nodes and edges
|
||||||
$: edges = createLinksFromTaskMap(tasks);
|
// when we update nodes and edges
|
||||||
|
let [nodes, edges] = createNodesAndEdges(tasks);
|
||||||
|
function hack() {
|
||||||
|
[nodes, edges] = createNodesAndEdges(tasks);
|
||||||
|
runSimulation();
|
||||||
|
}
|
||||||
|
$: {
|
||||||
|
tasks;
|
||||||
|
hack();
|
||||||
|
}
|
||||||
|
|
||||||
const eventDispatcher = createEventDispatcher();
|
const eventDispatcher = createEventDispatcher();
|
||||||
|
|
||||||
|
@ -32,9 +41,8 @@
|
||||||
hoveredTask = task.id;
|
hoveredTask = task.id;
|
||||||
eventDispatcher("preSelectTask", task);
|
eventDispatcher("preSelectTask", task);
|
||||||
} else {
|
} else {
|
||||||
if (hoveredTask == task.id)
|
if (hoveredTask == task.id) hoveredTask = null;
|
||||||
hoveredTask = null;
|
eventDispatcher("unPreSelectTask", task);
|
||||||
eventDispatcher("unPreSelectTask", task);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -83,6 +91,8 @@
|
||||||
}
|
}
|
||||||
let zoomer = d3.zoom().scaleExtent([0.1, 2]).on("zoom", zoomed);
|
let zoomer = d3.zoom().scaleExtent([0.1, 2]).on("zoom", zoomed);
|
||||||
d3.select(container).call(zoomer);
|
d3.select(container).call(zoomer);
|
||||||
|
|
||||||
|
runSimulation();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -93,6 +103,8 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
{@debug tasks}
|
||||||
|
|
||||||
<div bind:this={container} bind:clientHeight bind:clientWidth>
|
<div bind:this={container} bind:clientHeight bind:clientWidth>
|
||||||
<svg bind:this={svgElement}>
|
<svg bind:this={svgElement}>
|
||||||
<g>
|
<g>
|
||||||
|
@ -103,8 +115,8 @@
|
||||||
<GraphNode
|
<GraphNode
|
||||||
{task}
|
{task}
|
||||||
on:taskClick
|
on:taskClick
|
||||||
on:click={nodeClick(task)}
|
on:click={nodeClick(task.task)}
|
||||||
on:hoveringChange={nodeHover(task)} />
|
on:hoveringChange={nodeHover(task.task)} />
|
||||||
{/each}
|
{/each}
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { SimulationLinkDatum } from "d3";
|
import type { SimulationLinkDatum } from "d3";
|
||||||
|
import type { TaskId } from "./graph-types";
|
||||||
|
|
||||||
import type { TaskDescriptor } from "./task-loader";
|
export let edge: SimulationLinkDatum<TaskId>;
|
||||||
|
|
||||||
export let edge: SimulationLinkDatum<TaskDescriptor>;
|
|
||||||
|
|
||||||
$: x1 = edge === undefined || edge.source === undefined || edge.source.x === undefined ? 0 : edge.source.x;
|
$: x1 = edge === undefined || edge.source === undefined || edge.source.x === undefined ? 0 : edge.source.x;
|
||||||
$: y1 = edge === undefined || edge.source === undefined || edge.source.y === undefined ? 0 : edge.source.y;
|
$: y1 = edge === undefined || edge.source === undefined || edge.source.y === undefined ? 0 : edge.source.y;
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher, onMount } from "svelte";
|
import { createEventDispatcher, onMount } from "svelte";
|
||||||
import type { TaskDescriptor } from "./task-loader";
|
import type { TaskId } from "./graph-types";
|
||||||
|
|
||||||
export let task: TaskDescriptor;
|
|
||||||
|
export let task: TaskId;
|
||||||
let hovering: boolean = false;
|
let hovering: boolean = false;
|
||||||
let text_element: SVGTextElement;
|
let text_element: SVGTextElement;
|
||||||
|
|
||||||
|
|
45
frontend/src/graph-types.ts
Normal file
45
frontend/src/graph-types.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import type { SimulationLinkDatum, SimulationNodeDatum } from "d3";
|
||||||
|
import type { TaskDescriptor, TasksFile } from "./task-loader";
|
||||||
|
import { createTaskMap } from "./task-loader";
|
||||||
|
|
||||||
|
|
||||||
|
export type TaskId = {
|
||||||
|
id: string;
|
||||||
|
task: TaskDescriptor;
|
||||||
|
} & SimulationNodeDatum;
|
||||||
|
|
||||||
|
function createNodes(tasks: TasksFile, old?: TaskId[]): TaskId[] {
|
||||||
|
return tasks.tasks.map((t) => {
|
||||||
|
return { id: t.id, task: t };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createNodesAndEdges(tasks: TasksFile, oldNodes?, oldEdges?): [TaskId[], SimulationLinkDatum<TaskId>[]] {
|
||||||
|
let nodes = createNodes(tasks, oldNodes);
|
||||||
|
|
||||||
|
// create mapping from ID to node
|
||||||
|
let nodeMap = new Map<string, TaskId>();
|
||||||
|
for (let task of nodes) {
|
||||||
|
if (task.id in nodeMap) throw 'duplicate IDs';
|
||||||
|
nodeMap.set(task.id, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
let links: SimulationLinkDatum<TaskId>[] = [];
|
||||||
|
for (const task of tasks.tasks) {
|
||||||
|
const src = nodeMap.get(task.id)!;
|
||||||
|
for (const id of task.requires) {
|
||||||
|
const t = nodeMap.get(id);
|
||||||
|
|
||||||
|
if (t === undefined) throw `missing task with id ${id}`;
|
||||||
|
|
||||||
|
const l: SimulationLinkDatum<TaskId> =
|
||||||
|
{
|
||||||
|
source: src,
|
||||||
|
target: t
|
||||||
|
};
|
||||||
|
links.push(l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [nodes, links];
|
||||||
|
}
|
|
@ -4,8 +4,7 @@ export type TaskDescriptor = {
|
||||||
id: string
|
id: string
|
||||||
requires: string[]
|
requires: string[]
|
||||||
comment?: string
|
comment?: string
|
||||||
} & SimulationNodeDatum
|
}
|
||||||
|
|
||||||
|
|
||||||
export type TasksFile = {
|
export type TasksFile = {
|
||||||
tasks: TaskDescriptor[]
|
tasks: TaskDescriptor[]
|
||||||
|
@ -44,25 +43,6 @@ export function createTaskMap(tasks: TasksFile): TaskMap {
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createLinksFromTaskMap(tasks: TasksFile): SimulationLinkDatum<TaskDescriptor>[] {
|
|
||||||
let links: SimulationLinkDatum<TaskDescriptor>[] = [];
|
|
||||||
|
|
||||||
const taskMap = createTaskMap(tasks);
|
|
||||||
|
|
||||||
for (const task of tasks.tasks) {
|
|
||||||
for (const id of task.requires) {
|
|
||||||
const t = taskMap.get(id);
|
|
||||||
|
|
||||||
if (t === undefined) throw `missing task with id ${id}`;
|
|
||||||
|
|
||||||
const l: SimulationLinkDatum<TaskDescriptor> = { source: t, target: task };
|
|
||||||
links.push(l);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return links;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCategories(tasks: TasksFile, taskId: string): string[] {
|
export function getCategories(tasks: TasksFile, taskId: string): string[] {
|
||||||
let res: string[] = [];
|
let res: string[] = [];
|
||||||
for (let [cat, ids] of Object.entries(tasks.clusters)) {
|
for (let [cat, ids] of Object.entries(tasks.clusters)) {
|
||||||
|
|
Reference in a new issue