graf: přidána funkce pro manuální pozicování vrcholů a perzistence pozice
This commit is contained in:
		
							parent
							
								
									bd5e5a6a48
								
							
						
					
					
						commit
						a264dbead9
					
				
					 6 changed files with 241 additions and 28 deletions
				
			
		|  | @ -11,6 +11,7 @@ | |||
|   let clicked: string[] = []; | ||||
|   let graph: Graph; | ||||
|   let currentTask: TaskDescriptor | null = null; | ||||
|   let nodeDraggingEnabled: boolean = false; | ||||
| 
 | ||||
|   function clickTask(e: CustomEvent<TaskDescriptor>) { | ||||
|     // ukladani seznamu poslednich kliknuti | ||||
|  | @ -39,6 +40,11 @@ | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   async function saveCurrentStateWithPositions() { | ||||
|     tasks.positions = graph.getNodePositions(); | ||||
|     await saveTasks(tasks); | ||||
|   } | ||||
| 
 | ||||
|   async function saveCurrentState() { | ||||
|     await saveTasks(tasks); | ||||
|   } | ||||
|  | @ -106,22 +112,36 @@ | |||
|         {repulsionForce} | ||||
|         on:selectTask={clickTask} | ||||
|         on:preSelectTask={startHovering} | ||||
|         bind:this={graph} /> | ||||
|         bind:this={graph}  | ||||
|         {nodeDraggingEnabled} /> | ||||
|     </div> | ||||
|   </div> | ||||
|   <div class="right"> | ||||
|     <div class="toolbox"> | ||||
|       <div>Toolbox</div> | ||||
|       <div> | ||||
|         <button disabled={clicked.length <= 1} on:click={addEdge}>Přidat hranu {clicked[clicked.length - 2]} -> {clicked[clicked.length - 1]}</button> | ||||
|         <button disabled={clicked.length <= 1} on:click={addEdge}>Přidat hranu {clicked[clicked.length - 2]} | ||||
|           -> {clicked[clicked.length - 1]}</button> | ||||
|       </div> | ||||
|       <div> | ||||
|         <button on:click={graph.runSimulation}>Spustit simulaci</button> | ||||
|       </div> | ||||
|       <div><button on:click={graph.runSimulation}>Spustit simulaci</button></div> | ||||
|       <div> | ||||
|         Repulsion force: <input type="number" bind:value={repulsionForce} name="repulsionForceInput" max="1000" min="-10000" /> | ||||
|       </div> | ||||
|       <div> | ||||
|         <button on:click={saveCurrentState}>Uložit aktuální stav</button> | ||||
|       </div> | ||||
|       <div> | ||||
|         <button on:click={saveCurrentStateWithPositions}>Uložit aktuální stav | ||||
|           včetně pozic nodů</button> | ||||
|       </div> | ||||
|       <div> | ||||
|         <label> | ||||
|           <input type="checkbox" bind:checked={nodeDraggingEnabled} /> Povolit přesouvání | ||||
|           vrcholů | ||||
|         </label> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="taskDetails"> | ||||
|       {#if currentTask != null} | ||||
|  |  | |||
|  | @ -6,11 +6,12 @@ | |||
|   import type { TasksFile, TaskDescriptor } from "./task-loader"; | ||||
|   import { createNodesAndEdges } from "./graph-types"; | ||||
|   import { taskForce } from "./task-force"; | ||||
|   import { zoom } from "d3"; | ||||
| 
 | ||||
|   export let tasks: TasksFile; | ||||
|   let hoveredTask: null | string = null; | ||||
|   export let repulsionForce: number = -1000; | ||||
|   export let nodeDraggingEnabled: boolean = false; | ||||
| 
 | ||||
|   let hoveredTask: null | string = null; | ||||
| 
 | ||||
|   // Svelte automatically fills these with a reference | ||||
|   let container: HTMLElement; | ||||
|  | @ -23,7 +24,7 @@ | |||
|   let [nodes, edges] = createNodesAndEdges(tasks); | ||||
|   function hack() { | ||||
|     [nodes, edges] = createNodesAndEdges(tasks, nodes, edges); | ||||
|     runSimulation(); | ||||
|     //runSimulation(); | ||||
|   } | ||||
|   $: { | ||||
|     tasks; | ||||
|  | @ -74,12 +75,18 @@ | |||
|       nodes = nodes; | ||||
|     } | ||||
|   } | ||||
|   const zoomer = d3.zoom().scaleExtent([0.1, 2]) | ||||
| 
 | ||||
|   $: { | ||||
|     // zoomer.extent([[-clientWidth / 2,-clientHeight / 2],[clientWidth,clientHeight]]) | ||||
|   export function getNodePositions(): Map<string, [number, number]> { | ||||
|     let res = new Map(); | ||||
|     for (let n of nodes) { | ||||
|       if (n.x != undefined && n.y != undefined) { | ||||
|         res.set(n.id, [n.x, n.y]); | ||||
|       } | ||||
|     }  | ||||
|     return res | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   // start simulation and center view on create | ||||
|   onMount(() => { | ||||
|     // set center of the SVG at (0,0) | ||||
|  | @ -89,10 +96,9 @@ | |||
|     function zoomed(e) { | ||||
|       svg.attr("transform", e.transform); | ||||
|     } | ||||
|     const zoomer = d3.zoom().scaleExtent([0.1, 2]) | ||||
|     zoomer.on("zoom", zoomed); | ||||
|     d3.select(container).call(zoomer); | ||||
| 
 | ||||
|     runSimulation(); | ||||
|   }); | ||||
| </script> | ||||
| 
 | ||||
|  | @ -129,7 +135,9 @@ | |||
|             {task} | ||||
|             on:taskClick | ||||
|             on:click={nodeClick(task.task)} | ||||
|             on:hoveringChange={nodeHover(task.task)} /> | ||||
|             on:hoveringChange={nodeHover(task.task)} | ||||
|             on:positionChange={() => { tasks = tasks; }} | ||||
|             draggingEnabled={nodeDraggingEnabled} /> | ||||
|         {/each} | ||||
|       </g> | ||||
|     </g> | ||||
|  |  | |||
|  | @ -1,9 +1,12 @@ | |||
| <script lang="ts"> | ||||
|   import * as d3 from "d3"; | ||||
| 
 | ||||
|   import { createEventDispatcher, onMount } from "svelte"; | ||||
|   import type { TaskId } from "./graph-types"; | ||||
| 
 | ||||
| 
 | ||||
|   export let task: TaskId; | ||||
|   export let draggingEnabled: boolean = false; | ||||
| 
 | ||||
|   let hovering: boolean = false; | ||||
|   let text_element: SVGTextElement; | ||||
| 
 | ||||
|  | @ -31,6 +34,34 @@ | |||
|     const bbox = text_element.getBBox(); | ||||
|     ellipse_rx = bbox.width / 2 + 8; | ||||
|   }); | ||||
| 
 | ||||
|   // dragging | ||||
|   let dragging: boolean = false; | ||||
|   function dragStart(e: MouseEvent) { | ||||
|     if (!draggingEnabled) return; | ||||
| 
 | ||||
|     dragging = true; | ||||
|     e.preventDefault() | ||||
|     e.stopPropagation(); | ||||
|   } | ||||
|   function drag(e: MouseEvent) { | ||||
|     if (!draggingEnabled) return; | ||||
|     if (!dragging) return; | ||||
| 
 | ||||
|     let [x, y] = d3.pointer(e); | ||||
|     task.x = x; | ||||
|     task.y = y; | ||||
|     eventDispatcher("positionChange"); | ||||
|     e.preventDefault() | ||||
|     e.stopPropagation() | ||||
|   } | ||||
|   function dragStop(e: MouseEvent) { | ||||
|     if (!draggingEnabled) return; | ||||
| 
 | ||||
|     dragging = false; | ||||
|     e.preventDefault(); | ||||
|     e.stopPropagation(); | ||||
|   } | ||||
| </script> | ||||
| 
 | ||||
| <style> | ||||
|  | @ -45,7 +76,7 @@ | |||
|   } | ||||
| </style> | ||||
| 
 | ||||
| <g on:mouseenter={enter} on:mouseleave={leave} on:click={click}> | ||||
| <g on:mouseenter={enter} on:mouseleave={leave} on:click={click} on:mousedown={dragStart} on:mouseup={dragStop} on:mousemove={drag}> | ||||
|   <ellipse rx={ellipse_rx} ry={20} {cx} {cy} /> | ||||
|   <text | ||||
|     bind:this={text_element} | ||||
|  |  | |||
|  | @ -21,11 +21,14 @@ function toMapById(nodes: TaskId[]): Map<string, TaskId> { | |||
| function createNodes(tasks: TasksFile, old?: TaskId[]): TaskId[] { | ||||
|     let m = (old == undefined) ? new Map<string, TaskId>() : toMapById(old); | ||||
| 
 | ||||
|     let res = tasks.tasks.map((t) => { | ||||
|     let res: TaskId[] = tasks.tasks.map((t) => { | ||||
|         return { id: t.id, task: t }; | ||||
|     }); | ||||
| 
 | ||||
|     for (let t of res) { | ||||
|         if (tasks.positions.has(t.id)) { | ||||
|             [t.x, t.y] = tasks.positions.get(t.id)! | ||||
|         } | ||||
|         if (m.has(t.id)) { | ||||
|             Object.assign(t, m.get(t.id)) | ||||
|         } | ||||
|  |  | |||
|  | @ -9,20 +9,31 @@ export type TaskDescriptor = { | |||
| export type TasksFile = { | ||||
|     tasks: TaskDescriptor[] | ||||
|     clusters: { [name: string]: string[] } | ||||
|     positions: Map<string, [number, number]> | ||||
| } | ||||
| 
 | ||||
| export type TaskMap = Map<string, TaskDescriptor>; | ||||
| 
 | ||||
| export async function loadTasks(): Promise<TasksFile> { | ||||
|     const r = await fetch("/tasks.json") | ||||
|     return await r.json() | ||||
|     const j = await r.json() | ||||
|     if (j.positions == null) | ||||
|         j.positions = new Map(); | ||||
|     else | ||||
|         j.positions = new Map(Object.entries(j.positions)) | ||||
|     return j | ||||
| } | ||||
| 
 | ||||
| export async function saveTasks(tasks: TasksFile) { | ||||
|     let p: any = {} | ||||
|     for (let [key, val] of tasks.positions.entries()) | ||||
|         p[key] = val; | ||||
|     const data = {...tasks, positions: p} | ||||
| 
 | ||||
|     // request options
 | ||||
|     const options = { | ||||
|         method: 'POST', | ||||
|         body: JSON.stringify(tasks, null, 4), | ||||
|         body: JSON.stringify(data, null, 4), | ||||
|         headers: { | ||||
|             'Content-Type': 'application/json' | ||||
|         } | ||||
|  |  | |||
							
								
								
									
										162
									
								
								tasks.json
									
									
									
									
									
								
							
							
						
						
									
										162
									
								
								tasks.json
									
									
									
									
									
								
							|  | @ -158,52 +158,62 @@ | |||
|             "requires": [] | ||||
|         }, | ||||
|         { | ||||
|             "id": "29-Z3-2","type": "open-data", | ||||
|             "id": "29-Z3-2", | ||||
|             "type": "open-data", | ||||
|             "comment": "Písemka z angličtiny — voser implementovat, easy dřevorubecký řešení, optimálně trie, což na Z IMHO hard", | ||||
|             "requires": [] | ||||
|         }, | ||||
|         { | ||||
|             "id": "26-Z1-4","type": "open-data", | ||||
|             "id": "26-Z1-4", | ||||
|             "type": "open-data", | ||||
|             "comment": "Hroch v jezeře - BFS či jiné prohledávání, počítání velikosti komponent v 2D poli, ", | ||||
|             "requires": [] | ||||
|         }, | ||||
|         { | ||||
|             "id": "26-Z4-4","type": "open-data", | ||||
|             "id": "26-Z4-4", | ||||
|             "type": "open-data", | ||||
|             "comment": "Hlídači v labyrintu - policajti hlídající na grafu, konkrétně na stromě, rekurze, technicky asi až DP", | ||||
|             "requires": [] | ||||
|         }, | ||||
|         { | ||||
|             "id": "26-Z3-4","type": "open-data", | ||||
|             "id": "26-Z3-4", | ||||
|             "type": "open-data", | ||||
|             "comment": "Tvar labyrintu - nejdelší cesta ve stromě, graf", | ||||
|             "requires": [] | ||||
|         }, | ||||
|         { | ||||
|             "id": "29-Z1-4","type": "open-data", | ||||
|             "id": "29-Z1-4", | ||||
|             "type": "open-data", | ||||
|             "comment": "Zuzčin výlet — DFS (topologické pořadí)", | ||||
|             "requires": [] | ||||
|         }, | ||||
|         { | ||||
|             "id": "31-Z1-2","type": "open-data", | ||||
|             "id": "31-Z1-2", | ||||
|             "type": "open-data", | ||||
|             "comment": "BFS (šachovnice, custom figurka, nejkratší cesta)  ", | ||||
|             "requires": [] | ||||
|         }, | ||||
|         { | ||||
|             "id": "31-Z3-2","type": "open-data", | ||||
|             "id": "31-Z3-2", | ||||
|             "type": "open-data", | ||||
|             "comment": "DFS (hledání cesty v grafu po písmenech)", | ||||
|             "requires": [] | ||||
|         }, | ||||
|         { | ||||
|             "id": "31-Z3-3","type": "open-data", | ||||
|             "id": "31-Z3-3", | ||||
|             "type": "open-data", | ||||
|             "comment": "barvení bipartitního grafu (hledání partit), na vstupu hrany", | ||||
|             "requires": [] | ||||
|         }, | ||||
|         { | ||||
|             "id": "26-Z4-2","type": "open-data", | ||||
|             "id": "26-Z4-2", | ||||
|             "type": "open-data", | ||||
|             "comment": "Sbírání vajíček - hledení mediánu, musí se to ale vymyslet, nejkratší cesta při chození tam a zpět", | ||||
|             "requires": [] | ||||
|         }, | ||||
|         { | ||||
|             "id": "26-Z3-1","type": "open-data", | ||||
|             "id": "26-Z3-1", | ||||
|             "type": "open-data", | ||||
|             "comment": "Zámky labyrintu - hromada ifů, vhodné možná na code review, hledání čísla z trojice takového, že je trojice aritmetrická posloupnost", | ||||
|             "requires": [] | ||||
|         } | ||||
|  | @ -259,5 +269,135 @@ | |||
|         "Nápad": [ | ||||
|             "26-Z4-2" | ||||
|         ] | ||||
|     }, | ||||
|     "positions": { | ||||
|         "start": [ | ||||
|             60.9366784537015, | ||||
|             15.282753057729023 | ||||
|         ], | ||||
|         "jak-resit-ulohy": [ | ||||
|             57.60463651559811, | ||||
|             85.05400883098152 | ||||
|         ], | ||||
|         "31-Z1-1": [ | ||||
|             176, | ||||
|             112 | ||||
|         ], | ||||
|         "26-Z1-1": [ | ||||
|             -86.4374292583939, | ||||
|             114.1704716787137 | ||||
|         ], | ||||
|         "26-Z2-1": [ | ||||
|             -115, | ||||
|             184 | ||||
|         ], | ||||
|         "27-Z2-1": [ | ||||
|             -30, | ||||
|             181 | ||||
|         ], | ||||
|         "26-Z1-2": [ | ||||
|             -324, | ||||
|             235 | ||||
|         ], | ||||
|         "26-Z4-3": [ | ||||
|             -220.83782675574338, | ||||
|             190.72741511636147 | ||||
|         ], | ||||
|         "29-Z3-1": [ | ||||
|             -158, | ||||
|             276 | ||||
|         ], | ||||
|         "31-Z1-4": [ | ||||
|             -245, | ||||
|             284 | ||||
|         ], | ||||
|         "29-Z1-1": [ | ||||
|             154, | ||||
|             199 | ||||
|         ], | ||||
|         "29-Z2-1": [ | ||||
|             259, | ||||
|             272 | ||||
|         ], | ||||
|         "29-Z4-3": [ | ||||
|             164, | ||||
|             364 | ||||
|         ], | ||||
|         "26-Z2-4": [ | ||||
|             403.46860248688955, | ||||
|             23.996420431821353 | ||||
|         ], | ||||
|         "29-Z1-3": [ | ||||
|             -442.93377199520177, | ||||
|             -73.3461550905827 | ||||
|         ], | ||||
|         "26-Z2-2": [ | ||||
|             -64.3269017874483, | ||||
|             -91.7677899309802 | ||||
|         ], | ||||
|         "26-Z3-3": [ | ||||
|             450.612997715014, | ||||
|             -65.45002256579735 | ||||
|         ], | ||||
|         "26-Z4-1": [ | ||||
|             -551.4441557449455, | ||||
|             -38.58561957493338 | ||||
|         ], | ||||
|         "29-Z3-3": [ | ||||
|             302.6193712343388, | ||||
|             13.535655571354772 | ||||
|         ], | ||||
|         "26-Z1-3": [ | ||||
|             39.68234662392814, | ||||
|             -102.30393169322402 | ||||
|         ], | ||||
|         "26-Z2-3": [ | ||||
|             -320.2744362098852, | ||||
|             -72.1967458848867 | ||||
|         ], | ||||
|         "26-Z3-2": [ | ||||
|             644.167217052611, | ||||
|             -0.4551191547699971 | ||||
|         ], | ||||
|         "29-Z3-2": [ | ||||
|             -368.51198400620785, | ||||
|             1.6854832115582556 | ||||
|         ], | ||||
|         "26-Z1-4": [ | ||||
|             147.37372961796666, | ||||
|             -89.40554252368531 | ||||
|         ], | ||||
|         "26-Z4-4": [ | ||||
|             335.75328660449225, | ||||
|             -81.50932588628959 | ||||
|         ], | ||||
|         "26-Z3-4": [ | ||||
|             -480.95284944300494, | ||||
|             29.587091058893556 | ||||
|         ], | ||||
|         "29-Z1-4": [ | ||||
|             554.2061258687584, | ||||
|             -44.42093615098819 | ||||
|         ], | ||||
|         "31-Z1-2": [ | ||||
|             -236.06692326455527, | ||||
|             -77.65095973572805 | ||||
|         ], | ||||
|         "31-Z3-2": [ | ||||
|             -154.07095173801807, | ||||
|             -65.44506844403092 | ||||
|         ], | ||||
|         "31-Z3-3": [ | ||||
|             514.5773720938831, | ||||
|             41.05028681292239 | ||||
|         ], | ||||
|         "26-Z4-2": [ | ||||
|             -633.6757896792913, | ||||
|             1.4284188628810082 | ||||
|         ], | ||||
|         "26-Z3-1": [ | ||||
|             234.170971842641, | ||||
|             -59.230241172550606 | ||||
|         ] | ||||
|     } | ||||
| } | ||||
| } | ||||
		Reference in a new issue