graph: zooming and dragging, changed default repulsion force

This commit is contained in:
Vašek Šraier 2020-09-28 11:40:06 +02:00
parent 2006216a99
commit b1dcf45db6
4 changed files with 44 additions and 20 deletions

View file

@ -8,10 +8,16 @@
let selectedTask: string | null = null; let selectedTask: string | null = null;
let clickedTask: string | null = null; let clickedTask: string | null = null;
let repulsionForce: number = -600;
let clicked: string[] = []; let clicked: string[] = [];
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.push(selectedTask);
if (clicked.length > 3) if (clicked.length > 3)
@ -41,6 +47,9 @@
} }
}); });
tasks = tasks; tasks = tasks;
// run simulation
toggleDivnaPromena();
} else { } else {
alert("Nope, prvni musis nekam klikat..."); alert("Nope, prvni musis nekam klikat...");
} }
@ -48,7 +57,7 @@
let hovnoDivnaPromenaKteraJeFaktFuj = true; let hovnoDivnaPromenaKteraJeFaktFuj = true;
function toggleDivnaPromena() { function toggleDivnaPromena() {
hovnoDivnaPromenaKteraJeFaktFuj = ! hovnoDivnaPromenaKteraJeFaktFuj; hovnoDivnaPromenaKteraJeFaktFuj = !hovnoDivnaPromenaKteraJeFaktFuj;
} }
</script> </script>
@ -62,7 +71,7 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
margin: 0; margin: 0;
height: 100vh; height: 99vh;
width: 100%; width: 100%;
} }
@ -106,7 +115,12 @@
<div class="left"> <div class="left">
<div class="lastClicked">Last clicked: <b>{clicked.join(' | ')}</b></div> <div class="lastClicked">Last clicked: <b>{clicked.join(' | ')}</b></div>
<div class="graph"> <div class="graph">
<Graph {tasks} bind:selectedTask on:selectTask={clickTask} runSimulationWeirdHack={hovnoDivnaPromenaKteraJeFaktFuj} /> <Graph
{tasks}
{repulsionForce}
bind:selectedTask
on:selectTask={clickTask}
runSimulationWeirdHack={hovnoDivnaPromenaKteraJeFaktFuj} />
</div> </div>
</div> </div>
<div class="right"> <div class="right">
@ -115,12 +129,14 @@
<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> <div>
<button on:click={toggleDivnaPromena}>Spustit simulaci</button> Repulsion force: <input type="number" bind:value={repulsionForce} name="repulsionForceInput" max="1000" min="-10000" />
</div> </div>
</div> </div>
<div class="taskDetails"> <div class="taskDetails">
{#if currentTask != null} {#if currentTask != null}
start
<h3>{currentTask}</h3> <h3>{currentTask}</h3>
<span>{taskMap.get(currentTask).comment}</span> <span>{taskMap.get(currentTask).comment}</span>
<ul> <ul>

View file

@ -10,6 +10,7 @@
export let tasks: TasksFile; export let tasks: TasksFile;
export let selectedTask: null | string = null; export let selectedTask: null | string = null;
export let repulsionForce: number = -600;
$: nodes = tasks.tasks; $: nodes = tasks.tasks;
$: edges = createLinksFromTaskMap(tasks); $: edges = createLinksFromTaskMap(tasks);
@ -45,7 +46,7 @@
}) // This provide the id of a node }) // This provide the id of a node
.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(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("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)
@ -61,19 +62,29 @@
// run on create // run on create
onMount(() => { onMount(() => {
// set the dimensions and margins of the graph // set the dimensions and margins of the graph
var margin = { top: 10, right: 30, bottom: 30, left: 40 }, const width = container.clientWidth;
width = container.clientWidth - margin.left - margin.right, const height = container.clientHeight;
height = container.clientHeight - margin.top - margin.bottom;
// resize the svg object // resize the svg object
var svg = d3 var svg = d3
.select(container) .select(container)
.select("svg") .select("svg")
.attr("width", width + margin.left + margin.right) .attr("width", width)
.attr("height", height + margin.top + margin.bottom) .attr("height", height)
.attr("viewBox", [-width / 2, -height / 2, width, height]) .attr("viewBox", [-width / 2, -height / 2, width, height])
.select("g") .select("g");
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
function zoomed(e) {
let { transform } = e;
svg.attr("transform", transform);
}
d3.select(container).call(
d3
.zoom()
.scaleExtent([0.1, 2])
.on("zoom", zoomed)
);
runSimulation(); runSimulation();
}); });
@ -85,7 +96,6 @@
runSimulation(); runSimulation();
} }
// now it's safe to stop vomitting 🤮 // now it's safe to stop vomitting 🤮
</script> </script>
<style> <style>
@ -104,6 +114,7 @@
{#each nodes as task} {#each nodes as task}
<GraphNode <GraphNode
{task} {task}
on:taskClick
on:click={nodeClick(task)} on:click={nodeClick(task)}
on:hoveringChange={nodeHover(task)} /> on:hoveringChange={nodeHover(task)} />
{/each} {/each}

View file

@ -1,7 +1,5 @@
<script lang="ts"> <script lang="ts">
import type { SimulationLinkDatum } from "d3"; import type { SimulationLinkDatum } from "d3";
import { onMount } from "svelte";
import type { TaskDescriptor } from "./task-loader"; import type { TaskDescriptor } from "./task-loader";

View file

@ -1,17 +1,15 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher, onMount } from "svelte"; import { createEventDispatcher, onMount } from "svelte";
import type { TaskDescriptor } from "./task-loader"; import type { TaskDescriptor } from "./task-loader";
export let task: TaskDescriptor; export let task: TaskDescriptor;
let hovering: boolean = false; let hovering: boolean = false;
let text_element: SVGTextElement; let text_element: SVGTextElement;
const eventDispatcher = createEventDispatcher()
$: cx = task === undefined || task.x === undefined ? 0 : task.x; $: cx = task === undefined || task.x === undefined ? 0 : task.x;
$: cy = task === undefined || task.y === undefined ? 0 : task.y; $: cy = task === undefined || task.y === undefined ? 0 : task.y;
const eventDispatcher = createEventDispatcher()
function enter() { function enter() {
hovering = true; hovering = true;
eventDispatcher("hoveringChange", hovering) eventDispatcher("hoveringChange", hovering)
@ -26,6 +24,7 @@
eventDispatcher("click", e) eventDispatcher("click", e)
} }
// automatically size the bubbles to fit the text
let ellipse_rx = 20; let ellipse_rx = 20;
let ellipse_ry = 20; let ellipse_ry = 20;
onMount(() => { onMount(() => {