diff --git a/svelte/src/scripts/draw/drag-and-drop.ts b/svelte/src/scripts/draw/drag-and-drop.ts index fe90ec33..3c589852 100644 --- a/svelte/src/scripts/draw/drag-and-drop.ts +++ b/svelte/src/scripts/draw/drag-and-drop.ts @@ -25,18 +25,23 @@ export function addDragAndDrop( d3 // eslint-disable-next-line @typescript-eslint/no-explicit-any .drag() - .on('drag', e => { + .on('drag', (e,d) => { // Typing const source = e.sourceEvent as MouseEvent; const node = e.subject as GraphDataNode; // Calculate node movement, keeping the transformation (specifically the scaling) in mind. - const xt = node.x! + source.movementX * (1 / (drawSettings.transformation?.k ?? 1)); - const yt = node.y! + source.movementY * (1 / (drawSettings.transformation?.k ?? 1)); + const deltaX = source.movementX * (1 / ((drawSettings.transformation?.k ?? 1) * (d.level + 1))); + const deltaY = source.movementY * (1 / ((drawSettings.transformation?.k ?? 1) * (d.level + 1))); + const xt = node.x! + deltaX; + const yt = node.y! + deltaY; // For now, we are not going to resize or move the parent node // Hence, the coordinates should be clamped to a range if (node.parent) { + // Increase parent size and move it if it's starting to move outside the boundary + node.parent.x! = notNaN(node.parent.x! + deltaX); + node.parent.y! = notNaN(node.parent.y! + deltaY); node.x = clamp( xt, -0.5 * node.parent.width! + 0.5 * node.width!, @@ -51,9 +56,8 @@ export function addDragAndDrop( node.x = notNaN(xt); node.y = notNaN(yt); } - // Rerender nodes - updateNodePosition(node, svgElement); + updateNodePosition(node, deltaX, deltaY); renderLinks(links, nodesDictionary, linkCanvas, drawSettings); }), diff --git a/svelte/src/scripts/draw/nodes-render.ts b/svelte/src/scripts/draw/nodes-render.ts index d625b6c7..0b3f3667 100644 --- a/svelte/src/scripts/draw/nodes-render.ts +++ b/svelte/src/scripts/draw/nodes-render.ts @@ -1,8 +1,8 @@ import * as d3 from 'd3'; -import {toHTMLToken} from '$helper'; +import {clamp, notNaN, toHTMLToken} from '$helper'; import type {DrawSettingsInterface, GraphDataNode} from '$types'; -import { NormalizeWeight } from './helper/normalize-weight'; -import { renderInfoBox } from './helper/info-box'; +import {NormalizeWeight} from './helper/normalize-weight'; +import {renderInfoBox} from './helper/info-box'; /** * Adds lift and collapse buttons to all groups with id .nodes (generated by renderNodes) @@ -75,15 +75,49 @@ export function renderNodeLabels(svgElement: Element, drawSettings: DrawSettings * @param svgElement */ -export function updateNodePosition(node: GraphDataNode, svgElement: Element) { - ( - d3.select(svgElement).selectAll(`#group-${toHTMLToken(node.id)}`) as d3.Selection< - d3.BaseType, - GraphDataNode, - Element, - unknown - > - ).attr('transform', n => `translate(${n.x} ${n.y})`); +export function updateNodePosition(node: GraphDataNode, deltaX: number, deltaY: number) { + // Update the graphical node position + const nodeSvg = d3.select(`#group-${toHTMLToken(node.id)}`) as d3.Selection< + d3.BaseType, + GraphDataNode, + Element, + unknown + >; + nodeSvg.attr('transform', function (n) { + return `translate(${n.x} ${n.y})`; + }); + const xt = node.x! + deltaX; + const yt = node.y! + deltaY; + + // Update the parent data + if (node.parent) { + node.x = clamp( + xt, + -0.5 * node.parent.width! + 0.5 * node.width!, + +0.5 * node.parent.width! - 0.5 * node.width!, + ); + node.y = clamp( + yt, + -0.5 * node.parent.height! + 0.5 * node.height!, + +0.5 * node.parent.height! - 0.5 * node.height!, + ); + node.parent.x! = notNaN(node.parent.x! + deltaX); + node.parent.y! = notNaN(node.parent.y! + deltaY); + // const nodeRect = d3.select(`#rect-${toHTMLToken(node.parent.id)}`) as d3.Selection< + // d3.BaseType, + // GraphDataNode, + // Element, + // unknown + // >; + // const newX = Number(nodeRect.attr("x")) + deltaX + // const newY = Number(nodeRect.attr("y")) + deltaY + // // console.log(newX) + // nodeRect + // .attr("x", newX) + // .attr("y", newY) + + updateNodePosition(node.parent, deltaX, deltaY); + } } /** * Render given nodes onto given svgElement. @@ -129,11 +163,17 @@ export function renderNodes( .on('mouseover', function (event, data) { d3.select(this).attr('fill-opacity', '0.2'); data.outgoingLinks.forEach(link => { - d3.select(`#line-${toHTMLToken(link.id)}`).style('stroke-width', NormalizeWeight(link.weight) + 4); + d3.select(`#line-${toHTMLToken(link.id)}`).style( + 'stroke-width', + NormalizeWeight(link.weight) + 4, + ); }); data.incomingLinks.forEach(link => { // hightlight - d3.select(`#line-${toHTMLToken(link.id)}`).style('stroke-width', NormalizeWeight(link.weight) + 4); + d3.select(`#line-${toHTMLToken(link.id)}`).style( + 'stroke-width', + NormalizeWeight(link.weight) + 4, + ); }); // render info box @@ -142,14 +182,18 @@ export function renderNodes( .on('mouseout', function (event, data) { d3.select(this).attr('fill-opacity', '0.1'); data.outgoingLinks.forEach(link => { - d3.select(`#line-${toHTMLToken(link.id)}`).style('stroke-width', NormalizeWeight(link.weight)); + d3.select(`#line-${toHTMLToken(link.id)}`).style( + 'stroke-width', + NormalizeWeight(link.weight), + ); }); data.incomingLinks.forEach(link => { - d3.select(`#line-${toHTMLToken(link.id)}`).style('stroke-width', NormalizeWeight(link.weight)); + d3.select(`#line-${toHTMLToken(link.id)}`).style( + 'stroke-width', + NormalizeWeight(link.weight), + ); }); - }) - - + }); nodes.forEach(node => { const element = document.getElementById(`group-${toHTMLToken(node.id)}`)!;