spytial-core 2.8.0 → 2.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -863,7 +863,7 @@ Expecting `+Jp.join(", ")+", got '"+(this.terminals_[lo]||lo)+"'":Pp="Parse erro
|
|
|
863
863
|
</svg>
|
|
864
864
|
</div>
|
|
865
865
|
<div id="error" style="display: none; color: red;"></div>
|
|
866
|
-
`;}initializeD3(){ws||(ws=window.d3),this.svg=ws.select(this.root.querySelector("#svg")),this.container=this.svg.select(".zoomable"),ws.zoom?(this.zoomBehavior=ws.zoom().scaleExtent([.01,20]).on("start",()=>{ws.event.sourceEvent&&(this.userHasManuallyZoomed=true);}).on("zoom",()=>{this.container.attr("transform",ws.event.transform),this.updateZoomControlStates(),this.updateSmallNodeClasses();}),this.svg.call(this.zoomBehavior),this.initializeZoomControls()):console.warn("D3 zoom behavior not available. Ensure D3 v4+ is loaded.");}initializeZoomControls(){let n=this.root.querySelector("#zoom-in"),o=this.root.querySelector("#zoom-out"),s=this.root.querySelector("#zoom-fit");n&&n.addEventListener("click",()=>{this.userHasManuallyZoomed=true,this.zoomIn();}),o&&o.addEventListener("click",()=>{this.userHasManuallyZoomed=true,this.zoomOut();}),s&&s.addEventListener("click",()=>{this.resetViewToFitContent();});let e=this.root.querySelector("#routing-mode");if(e){let u=this.layoutFormat||"default";e.value=u,e.addEventListener("change",()=>{this.handleRoutingModeChange(e.value);});}let i=this.root.querySelector("#theme-mode");i&&(this.updateThemeDropdown(),i.addEventListener("change",()=>{this.setTheme(i.value),this.dispatchEvent(new CustomEvent("theme-changed",{detail:{theme:i.value},bubbles:true}));}));let a=this.root.querySelector("#screenshot-btn");a&&a.addEventListener("click",()=>{this.takeScreenshot();}),this.updateZoomControlStates();}handleRoutingModeChange(n){this.setAttribute("layoutFormat",n),this.currentLayout&&this.colaLayout&&(n==="grid"?this.gridify(10,25,10):this.routeEdges(),this.dispatchEvent(new CustomEvent("routing-mode-changed",{detail:{mode:n}})));}updateRoutingModeDropdown(){let n=this.shadowRoot?.querySelector("#routing-mode");if(n){let o=this.layoutFormat||"default";n.value=o;}}initializeInputModeHandlers(){this.inputModeEnabled&&this.attachInputModeListeners();}attachInputModeListeners(){this.inputModeListenersAttached||(document.addEventListener("keydown",this.handleInputModeKeydown),document.addEventListener("keyup",this.handleInputModeKeyup),window.addEventListener("blur",this.handleInputModeBlur),this.inputModeListenersAttached=true);}detachInputModeListeners(){this.inputModeListenersAttached&&(document.removeEventListener("keydown",this.handleInputModeKeydown),document.removeEventListener("keyup",this.handleInputModeKeyup),window.removeEventListener("blur",this.handleInputModeBlur),this.inputModeListenersAttached=false);}activateInputMode(){this.isInputModeActive=true,this.svg&&this.svg.classed("input-mode",true),this.disableNodeDragging(),this.disableZoom(),this.updateEdgeEndpointMarkers(),this.dispatchEvent(new CustomEvent("input-mode-activated",{detail:{active:true}}));}deactivateInputMode(){this.isInputModeActive=false,this.svg&&this.svg.classed("input-mode",false),this.cleanupEdgeCreation(),this.enableNodeDragging(),this.enableZoom(),this.updateEdgeEndpointMarkers(),this.dispatchEvent(new CustomEvent("input-mode-deactivated",{detail:{active:false}}));}disableNodeDragging(){this.svgNodes&&this.colaLayout&&this.svgNodes.on(".drag",null);}enableNodeDragging(){if(this.svgNodes&&this.colaLayout&&this.colaLayout.drag){let n=this.colaLayout.drag();this.setupNodeDragHandlers(n),this.svgNodes.call(n);}}disableZoom(){this.svg&&this.zoomBehavior&&(this.storedTransform=ws.zoomTransform(this.svg.node()),this.svg.on(".zoom",null));}enableZoom(){this.svg&&this.zoomBehavior&&(this.svg.call(this.zoomBehavior),this.storedTransform&&this.svg.call(this.zoomBehavior.transform,this.storedTransform));}zoomIn(){this.svg&&this.zoomBehavior&&this.svg.transition().duration(200).call(this.zoomBehavior.scaleBy,1.5);}zoomOut(){this.svg&&this.zoomBehavior&&this.svg.transition().duration(200).call(this.zoomBehavior.scaleBy,1/1.5);}updateZoomControlStates(){if(!this.svg||!this.zoomBehavior)return;let o=ws.zoomTransform(this.svg.node()).k,[s,e]=this.zoomBehavior.scaleExtent(),i=this.root.querySelector("#zoom-in"),a=this.root.querySelector("#zoom-out");i&&(i.disabled=o>=e),a&&(a.disabled=o<=s);}cleanupEdgeCreation(){this.edgeCreationState.temporaryEdge&&this.edgeCreationState.temporaryEdge.remove(),this.edgeCreationState={isCreating:false,sourceNode:null,temporaryEdge:null};}setupNodeDragHandlers(n){n.on("start.cnd",o=>{this.userHasManuallyZoomed=true;let s={x:o.x,y:o.y};this.dragStartPositions.set(o.id,s),this.dispatchEvent(new CustomEvent("node-drag-start",{detail:{id:o.id,position:s}}));}).on("end.cnd",o=>{let s=this.dragStartPositions.get(o.id);this.dragStartPositions.delete(o.id);let e={id:o.id,previous:s,current:{x:o.x,y:o.y}};this.dispatchEvent(new CustomEvent("node-drag-end",{detail:e}));});}startEdgeCreation(n){this.isInputModeActive&&(this.cleanupEdgeCreation(),this.edgeCreationState.isCreating=true,this.edgeCreationState.sourceNode=n,this.edgeCreationState.temporaryEdge=this.container.append("line").attr("class","temporary-edge").attr("x1",n.x).attr("y1",n.y).attr("x2",n.x).attr("y2",n.y).attr("stroke","#007bff").attr("stroke-width",2).attr("stroke-dasharray","5,5").attr("opacity",.7),this.svg.on("mousemove.edgecreation",()=>{if(this.edgeCreationState.isCreating&&this.edgeCreationState.temporaryEdge){let[o,s]=ws.mouse(this.container.node());this.edgeCreationState.temporaryEdge.attr("x2",o).attr("y2",s);}}));}async finishEdgeCreation(n){if(!this.isInputModeActive||!this.edgeCreationState.isCreating||!this.edgeCreationState.sourceNode)return;let o=this.edgeCreationState.sourceNode;if(o.id===n.id&&!await this.showConfirmDialog(`Are you sure you want to create a self-loop edge on "${o.label||o.id}"?`)){this.cleanupEdgeCreation();return}this.svg.on("mousemove.edgecreation",null),await this.showEdgeLabelInput(o,n);}async showEdgeLabelInput(n,o){let s=await this.showPromptDialog(`Enter label for edge from "${n.label||n.id}" to "${o.label||o.id}":`,"");s!==null&&await this.createNewEdge(n,o,s||""),this.cleanupEdgeCreation();}async createNewEdge(n,o,s){if(!this.currentLayout)return;let e=this.currentLayout.nodes.findIndex(g=>g.id===n.id),i=this.currentLayout.nodes.findIndex(g=>g.id===o.id);if(e===-1||i===-1){console.error("Could not find node indices for edge creation");return}let u={id:`edge_${n.id}_${o.id}_${Date.now()}`,source:e,target:i,label:s,relName:s,color:"#333",isUserCreated:true};this.currentLayout.links.push(u),await this.updateExternalStateForNewEdge(n,o,s),this.dispatchEvent(new CustomEvent("edge-created",{detail:{edge:u,sourceNode:n,targetNode:o}})),this.rerenderGraph();}async updateExternalStateForNewEdge(n,o,s){if(s.trim())try{let e={atoms:[n.id,o.id],types:[n.type||"untyped",o.type||"untyped"]};console.log(`Dispatching edge creation request: ${s}(${n.id}, ${o.id})`);let i=new CustomEvent("edge-creation-requested",{detail:{relationId:s,sourceNodeId:n.id,targetNodeId:o.id,tuple:e},bubbles:!0});this.dispatchEvent(i);}catch(e){console.error("Failed to update external state for new edge:",e);}}rerenderGraph(){!this.currentLayout||!this.colaLayout||(this.colaLayout.links(this.currentLayout.links),this.container.selectAll(".link-group").remove(),this.renderLinks(this.currentLayout.links,this.colaLayout),this.updatePositions());}async editEdgeLabel(n){if(!this.isInputModeActive)return;let o=n.relName||n.label||"",s=n.label||n.relName||"",e=await this.showEdgeEditDialog("Edit edge label:",s);if(e==="DELETE"){await this.deleteEdge(n);return}if(e!==null&&e!==s){let i=e,a=this.getNodeFromEdge(n,"source"),u=this.getNodeFromEdge(n,"target");await this.updateExternalStateForEdgeModification(a,u,o,i),n.label=i,n.relName=i,this.dispatchEvent(new CustomEvent("edge-modified",{detail:{edge:n,oldLabel:currentLabel,newLabel:i}})),this.rerenderGraph();}}getNodeFromEdge(n,o){if(!this.currentLayout)return null;let s=typeof n[o]=="number"?n[o]:n[o].index;return this.currentLayout.nodes[s]||null}async updateExternalStateForEdgeModification(n,o,s,e){if(!(!n||!o))try{let i={atoms:[n.id,o.id],types:[n.type||"untyped",o.type||"untyped"]};console.log(`Dispatching edge modification request: ${s} -> ${e}`);let a=new CustomEvent("edge-modification-requested",{detail:{oldRelationId:s,newRelationId:e,sourceNodeId:n.id,targetNodeId:o.id,tuple:i},bubbles:!0});this.dispatchEvent(a);}catch(i){console.error("Failed to update external state for edge modification:",i);}}resolveTransitionMode(n){if(n?.transitionMode==="replace")return "replace";if(n?.transitionMode==="morph")return "morph";let o=this.getAttribute("transition-mode");return o==="replace"?"replace":o==="morph"||n?.policy?"morph":"replace"}async renderLayout(n,o){if(!u6(n))throw new Error("Invalid instance layout provided. Expected an InstanceLayout instance.");let s=this.resolveTransitionMode(o),e=s==="morph",i=s==="replace",a,u=false;if(o?.policy&&o.prevInstance&&o.currInstance){let p=this.buildPolicyRawState(o),l=this.getViewportBoundsInLayoutSpace(p.transform),c=o.policy.apply({priorState:p,prevInstance:o.prevInstance,currInstance:o.currInstance,spec:{constraints:{orientation:{relative:[],cyclic:[]},alignment:[],grouping:{groups:[],subgroups:[]}},directives:{sizes:[],hiddenAtoms:[],icons:[],projections:[],edgeStyles:[]}},viewportBounds:l});a=c.effectivePriorState,u=c.useReducedIterations,this.lastSeedState=a??null;}else if(o?.priorPositions)a=o.priorPositions,u=true;else if(e&&this.currentLayout?.nodes?.length){let p=this.getLayoutState();p.positions.length>0&&(a=p,u=true);}let g=!!(a&&a.positions.length>0),E=this.hasValidTransform(a?.transform),m=g?{priorPositions:a,lockUnconstrainedNodes:u}:void 0;if(this.applyViewportRenderPolicy(g,E),this.svg&&this.zoomBehavior&&ws)try{if(E){let p=a.transform,l=ws.zoomIdentity.translate(p.x,p.y).scale(p.k);this.svg.call(this.zoomBehavior.transform,l);}else if(!g){let p=ws.zoomIdentity;this.svg.call(this.zoomBehavior.transform,p);}}catch(p){console.warn("Failed to set zoom transform:",p);}try{if(!ws)throw new Error("D3 library not available. Please ensure D3 v4 is loaded from CDN.");if(!q0){if(!window.cola)throw new Error("WebCola library not available. Please ensure vendor/cola.js is loaded.");q0=window.cola;}if((!this.container||!this.svg)&&this.initializeD3(),!this.container)throw new Error("Failed to initialize D3 container. SVG elements may not be available.");i?(this.showLoading(),this.updateLoadingProgress("Translating layout...")):this.hideLoading();let l=this.root.querySelector("#svg-container").getBoundingClientRect(),c=l.width||800,h=l.height||600,d=await new exports.WebColaTranslator().translate(n,c,h,m);i&&this.updateLoadingProgress(`Computing layout for ${d.nodes.length} nodes...`);let v=d.nodes.length,_=Er.INITIAL_UNCONSTRAINED_ITERATIONS,A=Er.INITIAL_USER_CONSTRAINT_ITERATIONS,b=Er.INITIAL_ALL_CONSTRAINTS_ITERATIONS;g&&u&&(_=0,A=Math.min(10,A),b=Math.min(20,b)),v>100?(_=Math.max(g?0:5,Math.floor(_*.5)),A=Math.max(25,Math.floor(A*.5)),b=Math.max(100,Math.floor(b*.5))):v>50&&(_=Math.max(g?0:8,Math.floor(_*.8)),A=Math.max(40,Math.floor(A*.8)),b=Math.max(150,Math.floor(b*.75)));let{scaledConstraints:T,linkLength:M,groupCompactness:k}=this.getScaledDetails(d.constraints,DK,d.nodes,d.groups,d.links);i&&this.updateLoadingProgress("Applying constraints and initializing...");let B=g?.1:.001,F=q0.d3adaptor(ws).linkDistance(M).convergenceThreshold(B).avoidOverlaps(!0).handleDisconnected(!0).nodes(d.nodes).links(d.links).constraints(T).groups(d.groups).groupCompactness(k).size([d.FIG_WIDTH,d.FIG_HEIGHT]);if(e&&this.currentLayout?.nodes?.length){this.morphOldPositions=new Map;for(let z of this.currentLayout.nodes)z.x!=null&&z.y!=null&&this.morphOldPositions.set(z.id,{x:z.x,y:z.y});this.applyMorphExitSnapshot(d);}else this.morphOldPositions=null;this.currentLayout=d,this.colaLayout=F,e&&this.container&&this.snapshotOldGraph(),this.container.selectAll("*").remove(),this.renderGroups(d.groups,F),this.renderLinks(d.links,F),this.renderNodes(d.nodes,F),e&&this.container&&this.container.attr("opacity",0);let Q=0,R=_+A+b,$=!0;F.on("tick",()=>{if($){if(Q++,Q%20===0){let z=Math.min(95,Math.round(Q/R*100));i&&this.updateLoadingProgress(`Computing layout... ${z}%`);}return}this.layoutFormat==="grid"?this.gridUpdatePositions():this.updatePositions();}).on("end",()=>{$=!1,i&&this.updateLoadingProgress("Finalizing..."),e&&this.morphOldPositions&&this.morphOldPositions.size>0?(this.container&&this.container.attr("opacity",1),this.startMorphExitAnimation(),this.layoutFormat==="grid"?this.gridUpdatePositions():this.updatePositions(),this.hideEnteringElements(),this.animateMorphSlide()):(this.container&&this.container.attr("opacity",1),this.layoutFormat==="grid"?(this.gridUpdatePositions(),this.gridify(10,25,10)):(this.updatePositions(),this.routeEdges())),this.isUnsatCore&&this.showErrorIcon(),this.dispatchRelationsAvailableEvent(),this.dispatchEvent(new CustomEvent("layout-complete",{detail:{nodePositions:this.getNodePositions()}})),this.updateRoutingModeDropdown(),i&&this.hideLoading();});try{F.start(_,A,b,Er.GRID_SNAP_ITERATIONS);}catch(z){console.warn("WebCola layout start encountered an error, trying alternative approach:",z);try{F.start();}catch(Z){throw console.error("Both WebCola start methods failed:",Z),new Error(`WebCola layout failed to start: ${Z.message}`)}}}catch(p){console.error("Error rendering layout:",p),this.showError(`Layout rendering failed: ${p.message}`);}}snapshotOldGraph(){if(!this.svg||!this.container)return;this.svg.selectAll(".morph-old-graph").interrupt().remove();let n=this.container.node();if(!n)return;let o=n.cloneNode(true);o.setAttribute("class","morph-old-graph"),o.style.pointerEvents="none";let s=this.svg.node();s&&s.appendChild(o);}applyMorphExitSnapshot(n){if(!this.svg||!this.container)return;this.svg.selectAll(".morph-exit-layer").interrupt().remove();let o=this.currentLayout?.nodes||[],s=this.currentLayout?.links||[],e=new Set(n.nodes.map(c=>c.id)),i=new Set(n.links.map(c=>c.id)),a=new Set(o.map(c=>c.id)),u=new Set(s.map(c=>c.id));this.morphEnteringNodeIds=new Set([...e].filter(c=>!a.has(c))),this.morphEnteringEdgeIds=new Set([...i].filter(c=>!u.has(c)));let g=new Set([...a].filter(c=>!e.has(c))),E=new Set([...u].filter(c=>!i.has(c)));if(g.size===0&&E.size===0)return;let m=this.svg.node();if(!m)return;let p=document.createElementNS("http://www.w3.org/2000/svg","g");p.setAttribute("class","morph-exit-layer"),p.style.pointerEvents="none";let l=this.container.attr("transform");l&&p.setAttribute("transform",l),this.svgNodes&&this.svgNodes.each(function(c){g.has(c.id)&&p.appendChild(this.cloneNode(true));}),this.svgLinkGroups&&this.svgLinkGroups.each(function(c){let h=c.id,f=c.source?.id??"",d=c.target?.id??"";(E.has(h)||g.has(f)||g.has(d))&&p.appendChild(this.cloneNode(true));}),this.svgGroups&&this.svgGroups.each(function(c){c.id&&!n.groups.some(h=>h.id===c.id)&&p.appendChild(this.cloneNode(true));}),p.childElementCount!==0&&m.appendChild(p);}startMorphExitAnimation(){if(!this.svg)return;this.svg.selectAll(".morph-old-graph").remove();let n=this.svg.select(".morph-exit-layer");if(n.empty())return;let o=this.morphExitDurationMs;n.selectAll("g.link-group, g.inferredLinkGroup, g.alignmentLinkGroup").each(function(){let s=this.querySelector("path[data-link-id]");if(!s)return;let e=s.getTotalLength();!e||e<=0||(s.setAttribute("stroke-dasharray",String(e)),s.setAttribute("stroke-dashoffset","0"),ws.select(this).selectAll(".linklabel, .arrowhead").attr("opacity",0),ws.select(s).transition().duration(o).ease(ws.easeCubicIn).attr("stroke-dashoffset",e));}),n.selectAll("g.node, rect.group").transition().duration(o).ease(ws.easeCubicOut).attr("opacity",0),n.transition().duration(o).on("end",function(){ws.select(this).remove();});}hideEnteringElements(){let n=this.morphEnteringNodeIds,o=this.morphEnteringEdgeIds;n.size===0&&o.size===0||(this.svgNodes&&n.size>0&&this.svgNodes.filter(s=>n.has(s.id)).attr("opacity",0),this.svgLinkGroups&&(o.size>0||n.size>0)&&this.svgLinkGroups.filter(s=>{if(o.has(s.id))return true;let e=s.source?.id??"",i=s.target?.id??"";return n.has(e)||n.has(i)}).attr("opacity",0));}applyMorphEnterTransition(){let n=this.morphEnteringNodeIds,o=this.morphEnteringEdgeIds;if(!(!(n.size>0||o.size>0)&&this.morphEnteringNodeIds.size===0&&this.morphEnteringEdgeIds.size===0)){if(this.svgNodes&&n.size>0&&this.svgNodes.filter(e=>n.has(e.id)).attr("opacity",0).transition().delay(this.morphEnterDelayMs).duration(this.morphEnterDurationMs).ease(ws.easeCubicOut).attr("opacity",1),this.svgLinkGroups&&(o.size>0||n.size>0)&&this.svgLinkGroups.filter(e=>{if(o.has(e.id))return true;let i=e.source?.id??"",a=e.target?.id??"";return n.has(i)||n.has(a)}).each(function(){let e=this.querySelector("path[data-link-id]");if(!e)return;let i=e.getTotalLength();!i||i<=0||(ws.select(e).attr("stroke-dasharray",i).attr("stroke-dashoffset",i),ws.select(this).selectAll(".linklabel, .arrowhead").attr("opacity",0));}).attr("opacity",1).transition().delay(this.morphEnterDelayMs).duration(this.morphEnterDurationMs).ease(ws.easeCubicOut).tween("draw-in",function(){let e=this.querySelector("path[data-link-id]");if(!e)return ()=>{};let i=e.getTotalLength();if(!i||i<=0)return ()=>{};let a=ws.interpolateNumber(i,0);return u=>{e.setAttribute("stroke-dashoffset",String(a(u)));}}).on("end",function(){let e=this.querySelector("path[data-link-id]");e&&(e.removeAttribute("stroke-dasharray"),e.removeAttribute("stroke-dashoffset")),ws.select(this).selectAll(".linklabel, .arrowhead").attr("opacity",1);}),this.svgGroups&&this.svgGroupLabels){new Set((this.currentLayout?.groups||[]).map(i=>i.id).filter(Boolean));}this.morphEnteringNodeIds=new Set,this.morphEnteringEdgeIds=new Set;}}animateMorphSlide(){let n=this.morphOldPositions;if(!n||n.size===0||!this.currentLayout?.nodes){this.morphOldPositions=null;return}let o=this.currentLayout.nodes,s=this.morphSlideDurationMs;if(s<=0){this.morphOldPositions=null;return}let e=[];for(let u of o){let g=n.get(u.id);g&&e.push({node:u,oldX:g.x,oldY:g.y,finalX:u.x,finalY:u.y});}if(e.length===0){this.morphOldPositions=null;return}for(let u of e)u.node.x=u.oldX,u.node.y=u.oldY;this.updatePositions();let i=u=>1-Math.pow(1-u,3),a=performance.now();this.morphSlideTimer&&(this.morphSlideTimer.stop(),this.morphSlideTimer=null),this.morphSlideTimer=ws.timer(()=>{let u=performance.now()-a,g=Math.min(1,u/s),E=i(g);for(let m of e)m.node.x=m.oldX+(m.finalX-m.oldX)*E,m.node.y=m.oldY+(m.finalY-m.oldY)*E;if(this.updatePositions(),g>=1){this.morphSlideTimer.stop(),this.morphSlideTimer=null;for(let m of e)m.node.x=m.finalX,m.node.y=m.finalY;this.updatePositions(),this.layoutFormat!=="grid"&&this.routeEdges(),this.applyMorphEnterTransition(),this.morphOldPositions=null;}});}setTransitionMode(n){this.setAttribute("transition-mode",n);}setMorphSpeed(n){this.setAttribute("morph-speed",String(Math.max(0,Math.min(n,1))));}clear(){if(this.colaLayout)try{this.colaLayout.stop?.();}catch{}this.container&&this.container.selectAll("*").remove(),this.svg&&this.svg.selectAll(".morph-exit-layer").interrupt().remove(),this.morphSlideTimer&&(this.morphSlideTimer.stop(),this.morphSlideTimer=null),this.morphOldPositions=null,this.currentLayout=null,this.colaLayout=null,this.svgNodes=null,this.svgLinks=null,this.svgGroups=null,this.edgeRoutingCache.edgesBetweenNodes.clear(),this.edgeRoutingCache.alignmentEdges.clear(),this.dragStartPositions.clear(),this.hideLoading();}getNodePositions(){return this.currentLayout?.nodes?this.currentLayout.nodes.map(n=>({id:n.id,x:n.x,y:n.y})):[]}getCurrentTransform(){if(this.svg&&this.svg.node())try{let n=ws.zoomTransform(this.svg.node());return {k:n.k,x:n.x,y:n.y}}catch{return {k:1,x:0,y:0}}return {k:1,x:0,y:0}}getLayoutState(){return {positions:this.getNodePositions(),transform:this.getCurrentTransform()}}getLastSeedState(){return this.lastSeedState?{positions:this.lastSeedState.positions.map(n=>({...n})),transform:{...this.lastSeedState.transform}}:null}addToolbarControl(n){let o=this.shadowRoot?.querySelector("#graph-toolbar");o&&o.appendChild(n);}getToolbar(){return this.shadowRoot?.querySelector("#graph-toolbar")||null}renderGroups(n,o){if(!this.currentLayout.nodes||this.currentLayout.nodes.length===0){console.warn("Cannot render groups: nodes not available");return}this.svgGroups=this.setupGroups(n,this.currentLayout.nodes,o);}setupLinks(n,o){let s=this.container.selectAll(".link-group").data(n).enter().append("g").attr("class","link-group");return this.setupLinkPaths(s),this.setupLinkLabels(s),this.setupEdgeEndpointMarkers(s),s}setupLinkPaths(n){n.append("path").attr("class",o=>this.isAlignmentEdge(o)?"alignmentLink":this.isInferredEdge(o)?"inferredLink":"link").attr("data-link-id",o=>o.id||"").attr("stroke",o=>this.edgeStrokeColor(o)).attr("fill","none").attr("opacity",o=>this.isAlignmentEdge(o)?0:null).style("stroke-width",o=>this.isAlignmentEdge(o)?"0":o.weight!=null?`${o.weight}px`:null).attr("stroke-dasharray",o=>this.isAlignmentEdge(o)?null:this.getEdgeDasharray(o.style)).attr("marker-end",o=>this.isAlignmentEdge(o)?"none":"url(#end-arrow)").attr("marker-start",o=>this.isAlignmentEdge(o)||!o.bidirectional?"none":"url(#start-arrow)").on("click.inputmode",o=>{this.isInputModeActive&&!this.isAlignmentEdge(o)&&(ws.event.stopPropagation(),this.editEdgeLabel(o).catch(s=>{console.error("Error editing edge label:",s);}));}).style("cursor",()=>this.isInputModeActive?"pointer":"default"),n.filter(o=>!!o.highlight&&!this.isAlignmentEdge(o)).insert("path",":first-child").attr("class","highlight-underlay").attr("stroke",o=>o.highlight).attr("fill","none").attr("stroke-linecap","round").style("stroke-width",o=>`${(o.weight!=null?o.weight:2)+8}px`).attr("opacity",.45).style("pointer-events","none");}getEdgeDasharray(n){if(!n)return null;switch(n.toLowerCase()){case "dotted":return "1,4";case "dashed":return "6,4";case "solid":return null;default:return null}}setupLinkLabels(n){n.filter(o=>!this.isAlignmentEdge(o)&&(this.isInferredEdge(o)||o.showLabel!==false)).append("text").attr("class","linklabel").attr("text-anchor","middle").attr("dominant-baseline","middle").attr("font-family",this.getFontFamily()).attr("pointer-events","none").text(o=>o.label||o.relName||"");}setupEdgeEndpointMarkers(n){n.filter(o=>!this.isAlignmentEdge(o)).append("circle").attr("class","edge-endpoint-marker target-marker").attr("r",8).attr("fill","#007bff").attr("stroke","white").attr("stroke-width",2).attr("opacity",0).attr("cursor","move").style("pointer-events","none").call(ws.drag().on("start",o=>this.startEdgeEndpointDrag(o,"target")).on("drag",o=>this.dragEdgeEndpoint(o,"target")).on("end",o=>this.endEdgeEndpointDrag(o,"target"))),n.filter(o=>!this.isAlignmentEdge(o)).append("circle").attr("class","edge-endpoint-marker source-marker").attr("r",8).attr("fill","#28a745").attr("stroke","white").attr("stroke-width",2).attr("opacity",0).attr("cursor","move").style("pointer-events","none").call(ws.drag().on("start",o=>this.startEdgeEndpointDrag(o,"source")).on("drag",o=>this.dragEdgeEndpoint(o,"source")).on("end",o=>this.endEdgeEndpointDrag(o,"source")));}startEdgeEndpointDrag(n,o){ws.event.sourceEvent.stopPropagation(),this.edgeDragState.isDragging=true,this.edgeDragState.edge=n,this.edgeDragState.endpoint=o,console.log(`\u{1F535} Started dragging ${o} endpoint of edge:`,n.id);}dragEdgeEndpoint(n,o){if(!this.edgeDragState.isDragging)return;let[s,e]=ws.mouse(this.container.node()),i=o==="target"?".target-marker":".source-marker";this.container.selectAll(".link-group").filter(a=>a.id===n.id).select(i).attr("cx",s).attr("cy",e);}async endEdgeEndpointDrag(n,o){if(!this.edgeDragState.isDragging)return;let[s,e]=ws.mouse(this.container.node()),i=this.findNodeAtPosition(s,e);i?(console.log(`\u{1F517} Reconnecting ${o} to node:`,i.id),await this.reconnectEdge(n,o,i)):(console.log("\u{1F5D1}\uFE0F No node found - deleting edge:",n.id),await this.deleteEdge(n)),this.edgeDragState={isDragging:false,edge:null,endpoint:null,dragMarker:null},this.rerenderGraph();}findNodeAtPosition(n,o){if(!this.currentLayout?.nodes)return null;for(let s of this.currentLayout.nodes){let e=(s.visualWidth??s.width??0)/2,i=(s.visualHeight??s.height??0)/2;if(n>=s.x-e&&n<=s.x+e&&o>=s.y-i&&o<=s.y+i)return s}return null}async reconnectEdge(n,o,s){let e=this.getNodeFromEdge(n,"source"),i=this.getNodeFromEdge(n,"target");if(!e||!i){console.error("Could not find source or target node");return}let a,u;if(o==="source"?(a=s,u=i):(a=e,u=s),a.id===e.id&&u.id===i.id){console.log("\u23ED\uFE0F Edge already connected to this node, no change needed");return}let g=n.relName||n.label||"";if(!g.trim()){console.warn("Edge has no relation name, cannot reconnect");return}let E={atoms:[e.id,i.id],types:[e.type||"untyped",i.type||"untyped"]},m={atoms:[a.id,u.id],types:[a.type||"untyped",u.type||"untyped"]};console.log(`Reconnecting edge: ${g} from ${e.id}->${i.id} to ${a.id}->${u.id}`);let p=new CustomEvent("edge-reconnection-requested",{detail:{relationId:g,oldTuple:E,newTuple:m,oldSourceNodeId:e.id,oldTargetNodeId:i.id,newSourceNodeId:a.id,newTargetNodeId:u.id},bubbles:true});this.dispatchEvent(p);let l=this.currentLayout.nodes.findIndex(h=>h.id===a.id),c=this.currentLayout.nodes.findIndex(h=>h.id===u.id);l!==-1&&c!==-1&&(n.source=l,n.target=c);}async deleteEdge(n){let o=this.getNodeFromEdge(n,"source"),s=this.getNodeFromEdge(n,"target");if(!o||!s){console.error("Could not find source or target node for edge deletion");return}let e=n.relName||n.label||"";if(!e.trim()){console.warn("Edge has no relation name, cannot delete from data instance"),this.removeEdgeFromLayout(n);return}let i=[];if(n.groupId&&n.keyNodeId&&this.currentLayout){let u=(this.currentLayout.groups||[]).find(g=>g.id===n.groupId);if(u){let g=this.currentLayout.groups||[],E=this.collectGroupNodeIndices(u,g),m=this.currentLayout.nodes.find(p=>p.id===n.keyNodeId);for(let p of E){let l=this.currentLayout.nodes[p];l&&m&&i.push({atoms:[m.id,l.id],types:[m.type||"untyped",l.type||"untyped"]});}}}i.length===0&&i.push({atoms:[o.id,s.id],types:[o.type||"untyped",s.type||"untyped"]}),console.log(`\u{1F5D1}\uFE0F Deleting edge: ${e} (${i.length} tuple(s))`);let a=new CustomEvent("edge-modification-requested",{detail:{oldRelationId:e,newRelationId:"",sourceNodeId:o.id,targetNodeId:s.id,tuples:i},bubbles:true});this.dispatchEvent(a),this.removeEdgeFromLayout(n);}removeEdgeFromLayout(n){if(!this.currentLayout?.links)return;let o=this.currentLayout.links.findIndex(s=>s.id===n.id);o!==-1&&(this.currentLayout.links.splice(o,1),console.log(`\u2705 Edge removed from layout: ${n.id}`));}setupGroups(n,o,s){let e=this.setupGroupRectangles(n,o,s);return this.svgGroupLabels=this.setupGroupLabels(n,s),e}setupGroupRectangles(n,o,s){return this.container.selectAll(".group").data(n).enter().append("rect").attr("class",i=>this.isDisconnectedGroup(i)?"disconnectedNode":this.isErrorGroup(i)?"error-group":"group").attr("rx",Er.GROUP_BORDER_RADIUS).attr("ry",Er.GROUP_BORDER_RADIUS).style("fill",i=>this.isDisconnectedGroup(i)?"transparent":o[i.keyNode]?.color||"#cccccc").attr("fill-opacity",Er.GROUP_FILL_OPACITY).attr("stroke",i=>this.isDisconnectedGroup(i)?"none":o[i.keyNode]?.color||"#999999").attr("stroke-width",i=>this.isDisconnectedGroup(i)||this.isErrorGroup(i)?1:Er.GROUP_STROKE_WIDTH).attr("stroke-opacity",i=>this.isDisconnectedGroup(i)||this.isErrorGroup(i)?1:Er.GROUP_STROKE_OPACITY).call(s.drag)}resolveGroupLeafToNodeIndex(n){if(typeof n=="number"&&Number.isInteger(n)&&n>=0)return n;if(n&&typeof n=="object"){let o=n;if(typeof o.index=="number"&&Number.isInteger(o.index)&&o.index>=0)return o.index;if(typeof o.id=="string"&&this.currentLayout?.nodes){let s=this.currentLayout.nodes.findIndex(e=>e.id===o.id);return s>=0?s:null}}return null}collectGroupNodeIndices(n,o){let s=new Set,e=new Set,i=(u,g)=>{if(u){if(typeof g=="number"){if(e.has(g))return;e.add(g);}Array.isArray(u.leaves)&&u.leaves.forEach(E=>{let m=this.resolveGroupLeafToNodeIndex(E);m!==null&&s.add(m);}),Array.isArray(u.groups)&&u.groups.forEach(E=>{typeof E=="number"&&Number.isInteger(E)&&E>=0&&E<o.length&&i(o[E],E);});}},a=o.indexOf(n);return i(n,a>=0?a:void 0),Array.from(s)}getNodeMainLabelFontSize(n){return 14}calculateGroupLabelFontSize(n,o){if(!this.currentLayout?.nodes?.length)return Er.DEFAULT_FONT_SIZE;let s=this.collectGroupNodeIndices(n,o);if(s.length===0)return Er.DEFAULT_FONT_SIZE;let e=s.map(u=>this.currentLayout.nodes[u]).filter(u=>!!u).map(u=>this.getNodeMainLabelFontSize(u));if(e.length===0)return Er.DEFAULT_FONT_SIZE;let i=e.reduce((u,g)=>u+g,0)/e.length,a=Math.max(Er.MIN_FONT_SIZE,Math.min(i,Er.MAX_FONT_SIZE));return Math.round(a*10)/10}setupGroupLabels(n,o){let s=this.currentLayout?.nodes||[];return this.svgGroupLabelBgs=this.container.selectAll(".groupLabelBg").data(n).enter().append("rect").attr("class","groupLabelBg").attr("rx",6).attr("ry",6).attr("fill",this.getCanvasBackground()).attr("fill-opacity",.92).attr("stroke",e=>s[e.keyNode]?.color||"#999999").attr("stroke-width",1).attr("stroke-opacity",.3).attr("pointer-events","none").style("display","none"),this.container.selectAll(".groupLabel").data(n).enter().append("text").attr("class","groupLabel").attr("text-anchor","middle").attr("dominant-baseline","hanging").attr("font-family",this.getFontFamily()).attr("font-size",e=>{let i=this.calculateGroupLabelFontSize(e,n);return e._groupLabelFontSize=i,`${i}px`}).attr("font-weight","bold").attr("fill","#333").attr("pointer-events","none").text(e=>e.showLabel||false?(e.padding&&(e.padding=Math.max(e.padding,Er.GROUP_LABEL_PADDING)),e.name||""):"").call(o.drag)}renderLinks(n,o){this.edgeRoutingCache.alignmentEdges.clear(),this.svgLinkGroups=this.setupLinks(n,o);}setupNodes(n,o){let s=o.drag();this.setupNodeDragHandlers(s);let e=this.container.selectAll(".node").data(n).enter().append("g").attr("class",i=>{let a=this.isErrorNode(i)?"error-node":"node";return this.isErrorNode(i)&&this.isSmallNode(i)?a+" small-error-node":a}).call(s).on("mousedown.inputmode",i=>{this.isInputModeActive&&(ws.event.stopPropagation(),this.startEdgeCreation(i));}).on("mouseup.inputmode",i=>{this.isInputModeActive&&this.edgeCreationState.isCreating&&(ws.event.stopPropagation(),this.finishEdgeCreation(i).catch(a=>{console.error("Error finishing edge creation:",a);}));}).on("mouseover",function(i){ws.select(this).append("title").attr("class","node-tooltip").text(`ID: ${i.id}`);}).on("mouseout",function(){ws.select(this).select("title.node-tooltip").remove();});return this.setupNodeRectangles(e),this.setupNodeIcons(e),this.setupMostSpecificTypeLabels(e),this.setupNodeLabels(e),e}setupNodeRectangles(n){n.append("rect").attr("width",o=>o.visualWidth??o.width).attr("height",o=>o.visualHeight??o.height).attr("x",o=>-(o.visualWidth??o.width)/2).attr("y",o=>-(o.visualHeight??o.height)/2).attr("stroke",o=>this.nodeBorderColor(o)).attr("rx",Er.NODE_BORDER_RADIUS).attr("ry",Er.NODE_BORDER_RADIUS).attr("stroke-width",Er.NODE_STROKE_WIDTH).attr("fill",o=>this.nodeFillColor(o));}setupNodeIcons(n){n.filter(o=>o.icon).append("image").attr("xlink:href",o=>o.icon).attr("width",o=>{let s=o.visualWidth??o.width;return o.showLabels?s*Er.SMALL_IMG_SCALE_FACTOR:s}).attr("height",o=>{let s=o.visualHeight??o.height;return o.showLabels?s*Er.SMALL_IMG_SCALE_FACTOR:s}).attr("x",o=>{let s=o.visualWidth??o.width;return o.showLabels?o.x+s-s*Er.SMALL_IMG_SCALE_FACTOR:o.x-s/2}).attr("y",o=>{let s=o.visualHeight??o.height;return o.y-s/2}).append("title").text(o=>o.label||o.name||o.id||"Node").on("error",function(o,s){ws.select(this).attr("xlink:href","img/default.png"),console.error(`Failed to load icon for node ${s.id}: ${s.icon}`);});}setupMostSpecificTypeLabels(n){n.append("text").attr("class","mostSpecificTypeLabel").style("fill",o=>this.nodeTypeLabelColor(o)).text(o=>o.mostSpecificType||"");}getTextMeasurementContext(){return this.textMeasurementCanvas||(this.textMeasurementCanvas=document.createElement("canvas")),this.textMeasurementCanvas.getContext("2d")}measureTextWidth(n,o,s){let e=this.getTextMeasurementContext();return e.font=`${o}px ${s??this.getFontFamily()}`,e.measureText(n).width}setupNodeLabels(n){let o=11*1.35;n.append("text").attr("class","label").attr("text-anchor","middle").attr("dominant-baseline","middle").attr("font-family",this.getFontFamily()).attr("fill","black").each((s,e,i)=>{if(this.isHiddenNode(s)||!s.showLabels)return;let a=ws.select(i[e]),u=s.label||s.name||s.id||"Node",g=Object.entries(s.attributes||{}).sort(([l],[c])=>l.localeCompare(c)),E=Object.entries(s.labels||{}),m=E.length+g.length,p=m>0?-m*o*.5:0;s._labelVerticalOffset=p,s._labelLineHeight=o,a.attr("font-size",`${14}px`),a.append("tspan").attr("x",0).attr("dy",`${p}px`).attr("class","main-label-tspan").style("font-weight","bold").style("font-size",`${14}px`).text(u);for(let[,l]of E){let c=Array.isArray(l)?l.join(", "):String(l);a.append("tspan").attr("x",0).attr("dy",`${o}px`).style("font-size",`${11}px`).style("font-style","italic").text(c);}for(let[l,c]of g)a.append("tspan").attr("x",0).attr("dy",`${o}px`).style("font-size",`${11}px`).text(`${l}: ${c}`);});}renderNodes(n,o){this.svgNodes=this.setupNodes(n,o);}resolveGroupEdgeEndpoints(n){if(!n.groupId)return {source:n.source,target:n.target};let o=this.currentLayout?.groups||[],s=o.find(u=>u.id===n.groupId);if(this._groupEdgeDebugLogged||(this._groupEdgeDebugLogged=new Set),this._groupEdgeDebugLogged.has(n.id)||(this._groupEdgeDebugLogged.add(n.id),console.log("[groupEdge DEBUG]",{edgeId:n.id,groupId:n.groupId,keyNodeId:n.keyNodeId,sourceId:n.source?.id,targetId:n.target?.id,sourceIsInt:typeof n.source=="number",targetIsInt:typeof n.target=="number",groupFound:!!s,groupId_on_group:s?.id,availableGroupIds:o.map(u=>u.id)})),!s)return {source:n.source,target:n.target};let e=n.source,i=n.target;n.source?.id===n.keyNodeId?i=s:n.target?.id===n.keyNodeId?e=s:console.warn("[groupEdge] keyNodeId matched neither side",{keyNodeId:n.keyNodeId,sourceId:n.source?.id,targetId:n.target?.id});let a=e===s?n.source:i===s?n.target:null;if(a&&!s.bounds&&a.x!=null&&q0?.Rectangle){let u=(a.visualWidth??a.width??50)/2,g=(a.visualHeight??a.height??30)/2;s.bounds=new q0.Rectangle(a.x-u,a.x+u,a.y-g,a.y+g);}return {source:e,target:i}}updateNodePositionsOnly(){this.svgNodes&&(this.svgNodes.select("rect").attr("x",n=>n.x!=null?n.x-(n.visualWidth??n.width)/2:0).attr("y",n=>n.y!=null?n.y-(n.visualHeight??n.height)/2:0).attr("width",n=>n.visualWidth??n.width).attr("height",n=>n.visualHeight??n.height),this.svgNodes.select("image").attr("x",n=>{if(n.x==null)return 0;let o=n.visualWidth??n.width;return n.showLabels?n.x+o/2-o*Er.SMALL_IMG_SCALE_FACTOR:n.x-o/2}).attr("y",n=>{if(n.y==null)return 0;let o=n.visualHeight??n.height;return n.y-o/2}),this.svgNodes.select(".mostSpecificTypeLabel").attr("x",n=>n.x!=null?n.x-(n.visualWidth??n.width??0)/2+5:0).attr("y",n=>n.y!=null?n.y-(n.visualHeight??n.height??0)/2+10:0),this.svgNodes.select(".label").attr("x",n=>n.x??0).attr("y",n=>n.y??0).each((n,o,s)=>{if(n.x==null)return;let e=n._labelVerticalOffset||0,i=n._labelLineHeight||12;ws.select(s[o]).selectAll("tspan").attr("x",n.x).attr("dy",(a,u)=>u===0?`${e}px`:`${i}px`);}));}updatePositions(){this.svgGroups.attr("x",n=>n.bounds.x).attr("y",n=>n.bounds.y).attr("width",n=>n.bounds.width()).attr("height",n=>n.bounds.height()).lower(),this.svgNodes.select("rect").each(n=>{n.bounds&&(n.innerBounds=n.bounds.inflate(-1));}).attr("x",n=>n.x-(n.visualWidth??n.width)/2).attr("y",n=>n.y-(n.visualHeight??n.height)/2).attr("width",n=>n.visualWidth??n.width).attr("height",n=>n.visualHeight??n.height),this.svgNodes.select("image").attr("x",n=>{let o=n.visualWidth??n.width;return n.showLabels?n.x+o/2-o*Er.SMALL_IMG_SCALE_FACTOR:n.x-o/2}).attr("y",n=>{let o=n.visualHeight??n.height;return n.showLabels,n.y-o/2}),this.svgNodes.select(".mostSpecificTypeLabel").attr("x",n=>n.x-(n.visualWidth??n.width??0)/2+5).attr("y",n=>n.y-(n.visualHeight??n.height??0)/2+10).raise(),this.svgNodes.select(".label").attr("x",n=>n.x).attr("y",n=>n.y).each((n,o,s)=>{let e=n._labelVerticalOffset||0,i=n._labelLineHeight||12;ws.select(s[o]).selectAll("tspan").attr("x",n.x).attr("dy",(a,u)=>u===0?`${e}px`:`${i}px`);}).raise(),this.svgLinkGroups.select("path[data-link-id]").attr("d",n=>{let{source:o,target:s}=this.resolveGroupEdgeEndpoints(n),e=this.getStableEdgePath(o,s,n);return this.lineFunction(e)}).attr("marker-end",n=>this.isAlignmentEdge(n)?"none":"url(#end-arrow)").attr("marker-start",n=>this.isAlignmentEdge(n)||!n.bidirectional?"none":"url(#start-arrow)").raise(),this.svgLinkGroups.select("path.highlight-underlay").attr("d",function(){return this.parentNode?.querySelector("path[data-link-id]")?.getAttribute("d")??null}),this.svgLinkGroups.select(".linklabel").attr("x",n=>{let o=this.shadowRoot?.querySelector(`path[data-link-id="${n.id}"]`);return o?this.calculateNewPosition(o,"x"):(n.source.x+n.target.x)/2}).attr("y",n=>{let o=this.shadowRoot?.querySelector(`path[data-link-id="${n.id}"]`);return o?this.calculateNewPosition(o,"y"):(n.source.y+n.target.y)/2}).style("font-size",()=>{let n=this.getCurrentZoomScale(),o=12,s=n<1?o/Math.sqrt(n):o;return `${Math.min(s,16)}px`}).raise(),this.updateEdgeEndpointMarkers(),this.svgGroupLabels.attr("x",n=>n.bounds?n.bounds.x+n.bounds.width()/2:0).attr("y",n=>{if(!n.bounds)return 0;let o=n._groupLabelFontSize||Er.DEFAULT_FONT_SIZE;return n.bounds.y+Er.GROUP_LABEL_PILL_OFFSET_Y+Math.max(4,o*.35)}).attr("text-anchor","middle").each(function(n){let o=this;if(o.textContent&&n.bounds){let s=o.getBBox();n._labelBBox={x:s.x,y:s.y,width:s.width,height:s.height};}else n._labelBBox=null;}),this.svgGroupLabelBgs&&(this.svgGroupLabelBgs.style("display",n=>n._labelBBox?null:"none").attr("x",n=>n._labelBBox?n._labelBBox.x-Er.GROUP_LABEL_PILL_PADDING_X:0).attr("y",n=>n._labelBBox?n._labelBBox.y-Er.GROUP_LABEL_PILL_PADDING_Y:0).attr("width",n=>n._labelBBox?n._labelBBox.width+Er.GROUP_LABEL_PILL_PADDING_X*2:0).attr("height",n=>n._labelBBox?n._labelBBox.height+Er.GROUP_LABEL_PILL_PADDING_Y*2:0),this.svgGroupLabelBgs.raise()),this.svgGroupLabels.raise(),this.svgLinkGroups.selectAll("marker").raise(),this.svgLinkGroups.selectAll(".linklabel").raise(),this.svgNodes.selectAll(".error-node").raise();}updateEdgeEndpointMarkers(){this.svgLinkGroups&&(this.svgLinkGroups.select(".target-marker").attr("cx",n=>{let o=this.shadowRoot?.querySelector(`path[data-link-id="${n.id}"]`);if(o){let s=o.getTotalLength();return o.getPointAtLength(s).x}return n.target.x||0}).attr("cy",n=>{let o=this.shadowRoot?.querySelector(`path[data-link-id="${n.id}"]`);if(o){let s=o.getTotalLength();return o.getPointAtLength(s).y}return n.target.y||0}).attr("opacity",this.isInputModeActive?.8:0).style("pointer-events",this.isInputModeActive?"all":"none").raise(),this.svgLinkGroups.select(".source-marker").attr("cx",n=>{let o=this.shadowRoot?.querySelector(`path[data-link-id="${n.id}"]`);return o?o.getPointAtLength(0).x:n.source.x||0}).attr("cy",n=>{let o=this.shadowRoot?.querySelector(`path[data-link-id="${n.id}"]`);return o?o.getPointAtLength(0).y:n.source.y||0}).attr("opacity",this.isInputModeActive?.8:0).style("pointer-events",this.isInputModeActive?"all":"none").raise());}gridUpdatePositions(){this.ensureNodeBounds(true);let n=this.container.selectAll(".node"),o=this.container.selectAll(".mostSpecificTypeLabel"),s=this.container.selectAll(".label"),e=this.container.selectAll(".group"),i=this.container.selectAll(".groupLabel");n.select("rect").each(function(g){g.innerBounds=g.bounds.inflate(-1);}).attr("x",function(g){return g.bounds.x}).attr("y",function(g){return g.bounds.y}).attr("width",function(g){return g.bounds.width()}).attr("height",function(g){return g.bounds.height()}),n.select("image").attr("x",function(g){let E=g.visualWidth??g.width;return g.showLabels?g.x+E/2-E*Er.SMALL_IMG_SCALE_FACTOR:g.bounds.x}).attr("y",function(g){let E=g.visualHeight??g.height;return g.showLabels?g.y-E/2:g.bounds.y}),o.attr("x",function(g){return g.bounds.x+5}).attr("y",function(g){return g.bounds.y+10}).raise(),s.attr("x",g=>g.x).attr("y",g=>g.y).each(function(g){var E=0;ws.select(this).selectAll("tspan").attr("x",g.x).attr("dy",function(){return E+=1,E===1?"0em":"1em"});}).raise(),e.attr("x",function(g){return g.bounds.x}).attr("y",function(g){return g.bounds.y}).attr("width",function(g){return g.bounds.width()}).attr("height",function(g){return g.bounds.height()}).lower(),i.attr("x",function(g){return g.bounds.x+g.bounds.width()/2}).attr("y",function(g){let E=g._groupLabelFontSize||Er.DEFAULT_FONT_SIZE;return g.bounds.y+Er.GROUP_LABEL_PILL_OFFSET_Y+Math.max(4,E*.35)}).attr("text-anchor","middle").each(function(g){let E=this;if(E.textContent&&g.bounds){let m=E.getBBox();g._labelBBox={x:m.x,y:m.y,width:m.width,height:m.height};}else g._labelBBox=null;}),this.container.selectAll(".groupLabelBg").style("display",g=>g._labelBBox?null:"none").attr("x",g=>g._labelBBox?g._labelBBox.x-Er.GROUP_LABEL_PILL_PADDING_X:0).attr("y",g=>g._labelBBox?g._labelBBox.y-Er.GROUP_LABEL_PILL_PADDING_Y:0).attr("width",g=>g._labelBBox?g._labelBBox.width+Er.GROUP_LABEL_PILL_PADDING_X*2:0).attr("height",g=>g._labelBBox?g._labelBBox.height+Er.GROUP_LABEL_PILL_PADDING_Y*2:0).raise(),i.raise();let u=this.container.selectAll(".link-group");u.select("path").attr("d",g=>{if(g.source?.id===g.target?.id){let d=this.createGridSelfLoopRoute(g);return this.gridLineFunction(d)}let{source:E,target:m}=this.resolveGroupEdgeEndpoints(g),p=d=>d.bounds?typeof d.bounds.cx=="function"?{x:d.bounds.cx(),y:d.bounds.cy()}:{x:(d.bounds.x+d.bounds.X)/2,y:(d.bounds.y+d.bounds.Y)/2}:{x:d.x??0,y:d.y??0},l=p(E),c=p(m),h=c.x-l.x,f=c.y-l.y;if(Math.abs(h)>Math.abs(f)){let d=l.x+h/2;return this.gridLineFunction([{x:l.x,y:l.y},{x:d,y:l.y},{x:d,y:c.y},{x:c.x,y:c.y}])}else {let d=l.y+f/2;return this.gridLineFunction([{x:l.x,y:l.y},{x:l.x,y:d},{x:c.x,y:d},{x:c.x,y:c.y}])}}),u.select("text.linklabel").attr("x",g=>{let E=this.shadowRoot?.querySelector(`path[data-link-id="${g.id}"]`);if(E){let l=E.getTotalLength();return E.getPointAtLength(l/2).x}let m=g.source?.x??g.source?.bounds?.cx()??0,p=g.target?.x??g.target?.bounds?.cx()??0;return (m+p)/2}).attr("y",g=>{let E=this.shadowRoot?.querySelector(`path[data-link-id="${g.id}"]`);if(E){let l=E.getTotalLength();return E.getPointAtLength(l/2).y}let m=g.source?.y??g.source?.bounds?.cy()??0,p=g.target?.y??g.target?.bounds?.cy()??0;return (m+p)/2}).raise();}routeEdges(){try{this.ensureNodeBounds(!0),typeof this.colaLayout?.prepareEdgeRouting=="function"&&this.colaLayout.prepareEdgeRouting(Er.VIEWBOX_PADDING/Er.EDGE_ROUTE_MARGIN_DIVISOR),this.buildEdgeRoutingCaches(),this.sortEdgePortsByAngle(),this.useTautRouter&&this.buildRouterObstacleCache(),this.computeAllRoutes(),this.optimizeCrossings(),this.applyRoutesToSVG(),this.updateLinkLabelsAfterRouting(),this.fitViewportToContent();}catch(n){console.error("Error in edge routing:",n),this.showError(`Edge routing failed: ${n.message}`);}finally{this.routerObstacleCache=null;}}ensureNodeBounds(n=false){if(!(!this.currentLayout?.nodes||!q0?.Rectangle))for(let o of this.currentLayout.nodes){if(!n&&o.bounds&&typeof o.bounds.rayIntersection=="function"){let E=o.bounds.cx(),m=o.bounds.cy(),p=1;if(Math.abs(E-(o.x||0))<p&&Math.abs(m-(o.y||0))<p)continue}let s=(o.visualWidth??o.width??50)/2,e=(o.visualHeight??o.height??30)/2,i=(o.x||0)-s,a=(o.x||0)+s,u=(o.y||0)-e,g=(o.y||0)+e;o.bounds=new q0.Rectangle(i,a,u,g),o.innerBounds=o.bounds.inflate(-1);}}buildEdgeRoutingCaches(){this.edgeRoutingCache.edgesBetweenNodes.clear(),this.edgeRoutingCache.alignmentEdges.clear(),this.edgeRoutingCache.nodeEdgesBySide.clear(),this.currentLayout?.links&&(this.currentLayout.links.forEach(n=>{n.id?.startsWith("_alignment_")&&this.edgeRoutingCache.alignmentEdges.add(n.id);}),this.currentLayout.links.forEach(n=>{if(this.isAlignmentEdge(n))return;let o=n.source.id,s=n.target.id,e=this.getNodePairKey(o,s);if(this.edgeRoutingCache.edgesBetweenNodes.has(e)||this.edgeRoutingCache.edgesBetweenNodes.set(e,[]),this.edgeRoutingCache.edgesBetweenNodes.get(e).push(n),o===s||n.id?.startsWith("_g_"))return;let i=n.source.x||0,a=n.source.y||0,u=n.target.x||0,g=n.target.y||0,E=u-i,m=g-a,p=Math.atan2(m,E),l=this.getDominantDirection(p),c=this.getDominantDirection(p+Math.PI),h=v=>v==="up"?"top":v==="down"?"bottom":v==="left"||v==="right"?v:null,f=h(l),d=h(c);f&&(this.edgeRoutingCache.nodeEdgesBySide.has(o)||this.edgeRoutingCache.nodeEdgesBySide.set(o,{top:[],bottom:[],left:[],right:[]}),this.edgeRoutingCache.nodeEdgesBySide.get(o)[f].push({edge:n,role:"source",remoteX:u,remoteY:g})),d&&(this.edgeRoutingCache.nodeEdgesBySide.has(s)||this.edgeRoutingCache.nodeEdgesBySide.set(s,{top:[],bottom:[],left:[],right:[]}),this.edgeRoutingCache.nodeEdgesBySide.get(s)[d].push({edge:n,role:"target",remoteX:i,remoteY:a}));}));}sortEdgePortsByAngle(){let n=(o,s)=>{let e=String(o.edge?.id??""),i=String(s.edge?.id??"");return e<i?-1:e>i?1:0};for(let[,o]of this.edgeRoutingCache.nodeEdgesBySide){for(let s of ["top","bottom"]){let e=o[s];if(!(e.length<=1)){e.sort((i,a)=>i.remoteX-a.remoteX||n(i,a));for(let i=0;i<e.length;i++){let a=e[i];a.role==="source"?(a.edge._sourcePortIndex=i,a.edge._sourcePortCount=e.length):(a.edge._targetPortIndex=i,a.edge._targetPortCount=e.length);}}}for(let s of ["left","right"]){let e=o[s];if(!(e.length<=1)){e.sort((i,a)=>i.remoteY-a.remoteY||n(i,a));for(let i=0;i<e.length;i++){let a=e[i];a.role==="source"?(a.edge._sourcePortIndex=i,a.edge._sourcePortCount=e.length):(a.edge._targetPortIndex=i,a.edge._targetPortCount=e.length);}}}}}getNodePairKey(n,o){return n<o?`${n}:${o}`:`${o}:${n}`}route(n=[],o=[],s,e){n.forEach(a=>{let u=a.bounds||a.innerBounds||this.createFallbackBounds(a);a.routerNode={name:a.name,bounds:u};}),o.forEach(a=>{a.bounds||console.warn("Grid routing group missing bounds; routing may be degraded.",a),a.routerNode={bounds:a.bounds?.inflate(-e)??a.bounds,children:(typeof a.groups<"u"?a.groups.map(u=>n.length+u.id):[]).concat(typeof a.leaves<"u"?a.leaves.map(u=>u.index):[])};});let i=n.concat(o).map((a,u)=>a.routerNode?(a.routerNode.id=u,a.routerNode):null).filter(Boolean);return new q0.GridRouter(i,{getChildren:a=>a.children,getBounds:a=>a.bounds},s-e)}gridify(n,o,s){if(this.isGridifyingInProgress){console.warn("[gridify] Already in progress, skipping re-entrant call");return}this.isGridifyingInProgress=true;try{this.gridifyInternal(n,o,s);}catch(e){console.log("Error routing edges in GridRouter"),console.error(e);try{this.fallbackGridRouting(this.currentLayout?.links??[]);}catch(a){console.error("Fallback grid routing also failed:",a);}let i=document.getElementById("runtime_messages");if(i){let a=document.createElement("div");a.className="alert alert-danger alert-dismissible fade show",a.setAttribute("role","alert"),a.innerHTML="Runtime (WebCola) error when gridifying edges. You may have to click and drag these nodes slightly to un-stick layout.",i.querySelectorAll(".alert").forEach(g=>{g.innerHTML===a.innerHTML&&g.remove();}),i.appendChild(a);}}finally{this.isGridifyingInProgress=false;}}gridifyInternal(n,o,s){let e=this.currentLayout?.nodes??[],i=this.currentLayout?.groups??[],a=this.currentLayout?.links??[];if(e.length===0){console.warn("No nodes available for GridRouter; skipping gridify.");return}if(a.length===0){console.warn("No edges to route in GridRouter");return}console.log("[gridify] Node positions BEFORE ensureNodeBounds:"),e.slice(0,3).forEach(f=>{console.log(` ${f.id}: x=${f.x?.toFixed(2)}, y=${f.y?.toFixed(2)}, bounds.cx=${f.bounds?.cx?.()?.toFixed(2)}, bounds.x=${f.bounds?.x?.toFixed(2)}`);}),this.ensureNodeBounds(true);let u=e.filter(f=>!Number.isFinite(f.x)||!Number.isFinite(f.y));if(u.length>0){console.warn("[gridify] Found nodes with invalid positions, falling back to default routing:",u.map(f=>({id:f.id,x:f.x,y:f.y}))),this.fallbackGridRouting(a);return}console.log("[gridify] Node positions AFTER ensureNodeBounds:"),e.slice(0,3).forEach(f=>{console.log(` ${f.id}: x=${f.x?.toFixed(2)}, y=${f.y?.toFixed(2)}, bounds.cx=${f.bounds?.cx?.()?.toFixed(2)}, bounds.x=${f.bounds?.x?.toFixed(2)}`);}),this.buildEdgeRoutingCaches(),this.sortEdgePortsByAngle();let g=this.route(e,i,o,s),E=[],m=a.filter(f=>{let d=f?.source?.routerNode&&f?.target?.routerNode,v=f?.source?.id===f?.target?.id;return d&&!v}),p=a.filter(f=>f?.source?.id===f?.target?.id);console.log("[gridify] Total edges:",a.length,"Routable:",m.length,"Self-loops:",p.length),m.length+p.length!==a.length&&a.filter(d=>(!d?.source?.routerNode||!d?.target?.routerNode)&&d?.source?.id!==d?.target?.id).forEach(d=>{console.warn("[gridify] Unroutable edge:",d.id,"source routerNode:",!!d?.source?.routerNode,"target routerNode:",!!d?.target?.routerNode,"source:",d?.source?.id,"x:",d?.source?.x,"y:",d?.source?.y,"target:",d?.target?.id,"x:",d?.target?.x,"y:",d?.target?.y);}),E=g.routeEdges(m,n,function(f){return f.source.routerNode.id},function(f){return f.target.routerNode.id});let l=new Map,c=m.length<=Er.CROSSING_OPTIMIZATION_EDGE_THRESHOLD;m.forEach((f,d)=>{let v=E[d];if(f?.id&&v){let _=this.adjustGridRouteForEdge(f,v);c&&(_=this.flattenGridRouteBends(_,e,f)),l.set(f.id,_);}}),console.log("[gridify] Routes generated:",l.size,"out of",m.length),this.container.selectAll(".link-group").data(a,f=>f.id??f).select("path").attr("d",f=>{if(f.source?.id===f.target?.id){let M=this.createGridSelfLoopRoute(f);return this.gridLineFunction(M)}let d=l.get(f.id);if(!d){let M=f.source?.x??f.source?.bounds?.cx()??0,k=f.source?.y??f.source?.bounds?.cy()??0,B=f.target?.x??f.target?.bounds?.cx()??0,F=f.target?.y??f.target?.bounds?.cy()??0;console.log("[gridify] Fallback path for edge:",f.id,"from",f.source?.id,"(",M,",",k,")","to",f.target?.id,"(",B,",",F,")");let Q=B-M,R=F-k;if(Math.abs(Q)>Math.abs(R)){let $=M+Q/2;return this.gridLineFunction([{x:M,y:k},{x:$,y:k},{x:$,y:F},{x:B,y:F}])}else {let $=k+R/2;return this.gridLineFunction([{x:M,y:k},{x:M,y:$},{x:B,y:$},{x:B,y:F}])}}let b=q0.GridRouter.getRoutePath(d,5,3,7);return this.adjustGridRouteForArrowPositioning(f,b.routepath,d)||b.routepath}),this.gridUpdateLinkLabels(a,l),this.fitViewportToContent(),this.dispatchEvent(new Event("relationsAvailable"));}fallbackGridRouting(n){this.container.selectAll(".link-group").data(n,s=>s.id??s).select("path").attr("d",s=>{if(s.source?.id===s.target?.id){let m=this.createGridSelfLoopRoute(s);return this.gridLineFunction(m)}let e=s.source?.x??s.source?.bounds?.cx()??0,i=s.source?.y??s.source?.bounds?.cy()??0,a=s.target?.x??s.target?.bounds?.cx()??0,u=s.target?.y??s.target?.bounds?.cy()??0,g=a-e,E=u-i;if(Math.abs(g)>Math.abs(E)){let m=e+g/2;return this.gridLineFunction([{x:e,y:i},{x:m,y:i},{x:m,y:u},{x:a,y:u}])}else {let m=i+E/2;return this.gridLineFunction([{x:e,y:i},{x:e,y:m},{x:a,y:m},{x:a,y:u}])}}),this.fitViewportToContent();}gridUpdateLinkLabels(n,o){this.container.selectAll(".link-group").filter(e=>!this.isAlignmentEdge(e)).select("text.linklabel").attr("x",e=>{let i=this.shadowRoot?.querySelector(`path[data-link-id="${e.id}"]`);if(i)try{let u=i.getTotalLength();return i.getPointAtLength(u/2).x}catch{}return this.getGridRouteMidpoint(e,o)?.x??e.source?.x??e.source?.bounds?.cx()??0}).attr("y",e=>{let i=this.shadowRoot?.querySelector(`path[data-link-id="${e.id}"]`);if(i)try{let u=i.getTotalLength();return i.getPointAtLength(u/2).y}catch{}return this.getGridRouteMidpoint(e,o)?.y??e.source?.y??e.source?.bounds?.cy()??0}).attr("text-anchor","middle").attr("dominant-baseline","middle");}getGridRouteMidpoint(n,o){let s=o.get(n.id);if(!s){let m=n.source?.x??n.source?.bounds?.cx()??0,p=n.source?.y??n.source?.bounds?.cy()??0,l=n.target?.x??n.target?.bounds?.cx()??0,c=n.target?.y??n.target?.bounds?.cy()??0;return {x:(m+l)/2,y:(p+c)/2}}let e=[];if(s.forEach(m=>{e.length===0&&m.length>0&&e.push(m[0]),m.length>1&&e.push(m[1]);}),e.length<2)return null;let i=0,a=[];for(let m=0;m<e.length-1;m++){let p=e[m+1].x-e[m].x,l=e[m+1].y-e[m].y,c=Math.sqrt(p*p+l*l);a.push(c),i+=c;}let u=i/2,g=0;for(let m=0;m<a.length;m++){let p=a[m];if(g+p>=u){let l=u-g,c=p>0?l/p:0;return {x:e[m].x+c*(e[m+1].x-e[m].x),y:e[m].y+c*(e[m+1].y-e[m].y)}}g+=p;}let E=Math.floor(e.length/2);return e[E]}adjustGridRouteForEdge(n,o){if(!n?.id?.startsWith("_g_"))return o;let s=this.gridRouteToPoints(o);if(s.length<2)return o;let e=this.routeGroupEdge(n,s);return this.pointsToGridRoute(e)}adjustGridRouteForArrowPositioning(n,o,s){if(!o||!n.source||!n.target)return null;try{let e=this.gridRouteToPoints(s);if(e.length<2)return null;let i=n.source,a=n.target,u=this.getVisibleBounds(i)??i.bounds??{x:i.x-(i.width||0)/2,y:i.y-(i.height||0)/2,width:()=>i.width||0,height:()=>i.height||0},g=this.getVisibleBounds(a)??a.bounds??{x:a.x-(a.width||0)/2,y:a.y-(a.height||0)/2,width:()=>a.width||0,height:()=>a.height||0},m=this.getTouchDirection(u,g,5);if(m!=="none"){let{sourcePoint:l,targetPoint:c,middlePoints:h}=this.computePerpendicularRoute(u,g,m),f=[l,...h,c];return this.gridLineFunction(f)}e[0]=this.clipEndpointToVisibleBoundary(e[0],e[1],u),e[e.length-1]=this.clipEndpointToVisibleBoundary(e[e.length-1],e[e.length-2],g);let p=this.applyPortBasedEndpointsOrthogonal(n,e);return this.gridLineFunction(p)}catch(e){return console.warn("Error adjusting grid route for arrow positioning:",e),null}}clipEndpointToVisibleBoundary(n,o,s){let e=s.x+s.width()/2,i=s.y+s.height()/2,a=s.x,u=typeof s.X=="number"?s.X:s.x+s.width(),g=s.y,E=typeof s.Y=="number"?s.Y:s.y+s.height(),m=.5,p=o.x-n.x,l=o.y-n.y;if(Math.abs(p)<m&&Math.abs(l)>=m){let h=n.y>i?E:g;return {x:n.x,y:h}}return Math.abs(l)<m&&Math.abs(p)>=m?{x:n.x>e?u:a,y:n.y}:this.getRectangleIntersection(e,i,o.x,o.y,s)??n}getTouchDirection(n,o,s){let e=n.x,i=n.x+n.width(),a=n.y,u=n.y+n.height(),g=o.x,E=o.x+o.width(),m=o.y,p=o.y+o.height(),l=Math.max(0,Math.max(g-i,e-E)),c=Math.max(0,Math.max(m-u,a-p)),h=!(u<m||p<a),f=!(i<g||E<e);return l<=s&&h?"horizontal":c<=s&&f?"vertical":"none"}computePerpendicularRoute(n,o,s){let e=n.width(),i=n.height(),a=o.width(),u=o.height(),g=n.x+e/2,E=n.y+i/2,m=o.x+a/2,p=o.y+u/2,l=15;if(s==="horizontal")if(E<=p){let h=Math.min(n.y,o.y)-l;return {sourcePoint:{x:g,y:n.y},targetPoint:{x:m,y:o.y},middlePoints:[{x:g,y:h},{x:m,y:h}]}}else {let h=Math.max(n.y+i,o.y+u)+l;return {sourcePoint:{x:g,y:n.y+i},targetPoint:{x:m,y:o.y+u},middlePoints:[{x:g,y:h},{x:m,y:h}]}}else if(g<=m){let h=Math.min(n.x,o.x)-l;return {sourcePoint:{x:n.x,y:E},targetPoint:{x:o.x,y:p},middlePoints:[{x:h,y:E},{x:h,y:p}]}}else {let h=Math.max(n.x+e,o.x+a)+l;return {sourcePoint:{x:n.x+e,y:E},targetPoint:{x:o.x+a,y:p},middlePoints:[{x:h,y:E},{x:h,y:p}]}}}areBoundsNear(n,o,s){let e=n.x,i=n.x+n.width(),a=n.y,u=n.y+n.height(),g=o.x,E=o.x+o.width(),m=o.y,p=o.y+o.height(),l=Math.max(0,Math.max(g-i,e-E)),c=Math.max(0,Math.max(m-u,a-p));return Math.sqrt(l*l+c*c)<=s}chooseBoundaryPoint(n,o,s,e){let i=Math.max(1,s.width()),a=Math.max(1,s.height()),u=[{x:s.x,y:s.y+a/2},{x:s.x+i,y:s.y+a/2},{x:s.x+i/2,y:s.y},{x:s.x+i/2,y:s.y+a}],g=u[0],E=-1/0;for(let m of u){let p=m.x-e.x,l=m.y-e.y,c=Math.sqrt(p*p+l*l);c>E&&(E=c,g=m);}return g}normalizeNodeBounds(n){let o=n.visualWidth??n.width??50,s=n.visualHeight??n.height??30,e=n.bounds||{x:n.x-o/2,y:n.y-s/2,width:()=>o,height:()=>s};return {x:typeof e.x=="number"||e.X!==void 0?e.x:n.x-o/2,y:typeof e.y=="number"?e.y:n.y-s/2,width:()=>typeof e.width=="function"?e.width():e.X!==void 0?e.X-e.x:o,height:()=>typeof e.height=="function"?e.height():e.Y!==void 0?e.Y-e.y:s}}lineIntersectsRect(n,o,s){let e=s.x,i=s.x+s.width(),a=s.y,u=s.y+s.height(),g=Math.min(n.x,o.x),E=Math.max(n.x,o.x),m=Math.min(n.y,o.y),p=Math.max(n.y,o.y);if(E<e||g>i||p<a||m>u)return false;let l=n.x>=e&&n.x<=i&&n.y>=a&&n.y<=u,c=o.x>=e&&o.x<=i&&o.y>=a&&o.y<=u;if(l||c)return true;let h=o.x-n.x,f=o.y-n.y,d=(_,A,b)=>{if(f===0)return false;let T=(_-n.y)/f;if(T<0||T>1)return false;let M=n.x+T*h;return M>=A&&M<=b},v=(_,A,b)=>{if(h===0)return false;let T=(_-n.x)/h;if(T<0||T>1)return false;let M=n.y+T*f;return M>=A&&M<=b};return d(a,e,i)||d(u,e,i)||v(e,a,u)||v(i,a,u)}findBlockingNodes(n,o,s,e){if(!this.currentLayout?.nodes)return [];let i=this.normalizeNodeBounds(n),a=this.normalizeNodeBounds(o),u={x:i.x+i.width()/2,y:i.y+i.height()/2},g={x:a.x+a.width()/2,y:a.y+a.height()/2},E=[];for(let m of this.currentLayout.nodes){if(m.id===s||m.id===e)continue;let p=this.normalizeNodeBounds(m);if(this.lineIntersectsRect(u,g,p)){let l={x:p.x+p.width()/2,y:p.y+p.height()/2},c=Math.sqrt(Math.pow(l.x-u.x,2)+Math.pow(l.y-u.y,2));E.push({node:m,bounds:p,distance:c});}}return E.sort((m,p)=>m.distance-p.distance),E.map(m=>({node:m.node,bounds:m.bounds}))}computeRouteAroundBlockingNodes(n,o,s){let i=Math.min(n.x,o.x),a=Math.max(n.x+n.width(),o.x+o.width()),u=Math.min(n.y,o.y),g=Math.max(n.y+n.height(),o.y+o.height());for(let{bounds:f}of s)i=Math.min(i,f.x),a=Math.max(a,f.x+f.width()),u=Math.min(u,f.y),g=Math.max(g,f.y+f.height());let E=n.x+n.width()/2,m=n.y+n.height()/2,p=o.x+o.width()/2,l=o.y+o.height()/2,c=Math.abs(p-E);if(Math.abs(l-m)>c)if(E<=p){let d=i-15;return {sourcePoint:{x:n.x,y:m},targetPoint:{x:o.x,y:l},middlePoints:[{x:d,y:m},{x:d,y:l}]}}else {let d=a+15;return {sourcePoint:{x:n.x+n.width(),y:m},targetPoint:{x:o.x+o.width(),y:l},middlePoints:[{x:d,y:m},{x:d,y:l}]}}else if(m<=l){let d=u-15;return {sourcePoint:{x:E,y:n.y},targetPoint:{x:p,y:o.y},middlePoints:[{x:E,y:d},{x:p,y:d}]}}else {let d=g+15;return {sourcePoint:{x:E,y:n.y+n.height()},targetPoint:{x:p,y:o.y+o.height()},middlePoints:[{x:E,y:d},{x:p,y:d}]}}}getNearTouchPerpendicularRoute(n){if(!n.source||!n.target||n.source.id===n.target.id)return null;let o=n.source,s=n.target,e=this.normalizeNodeBounds(o),i=this.normalizeNodeBounds(s),u=this.getTouchDirection(e,i,5);if(u!=="none"){let{sourcePoint:E,targetPoint:m,middlePoints:p}=this.computePerpendicularRoute(e,i,u);return [E,...p,m]}let g=this.findBlockingNodes(o,s,o.id,s.id);if(g.length>0){let{sourcePoint:E,targetPoint:m,middlePoints:p}=this.computeRouteAroundBlockingNodes(e,i,g);return [E,...p,m]}return null}gridRouteToPoints(n){let o=[];return n.forEach((s,e)=>{e===0&&o.push({x:s[0].x,y:s[0].y}),o.push({x:s[1].x,y:s[1].y});}),o}pointsToGridRoute(n){let o=[];for(let s=0;s<n.length-1;s+=1)o.push([n[s],n[s+1]]);return o}flattenGridRouteBends(n,o,s){try{if(!n||n.length<2)return n;let e=this.gridRouteToPoints(n);if(e.length<3)return n;let i=6,a=e;for(let u=0;u<i;u++){let g=a.length;if(a=this.dropCollinearGridPoints(a),a=this.flattenGridRouteZShapes(a,o,s),a=this.flattenGridRouteUBumps(a,o,s),a.length===g)break}return a.length===e.length?n:this.pointsToGridRoute(a)}catch(e){return console.warn("[flattenGridRouteBends] Failed; returning original route:",e),n}}dropCollinearGridPoints(n){if(n.length<3)return n;let o=[n[0]];for(let s=1;s<n.length-1;s++){let e=o[o.length-1],i=n[s],a=n[s+1],u=e.x===i.x&&i.x===a.x,g=e.y===i.y&&i.y===a.y;u||g||o.push(i);}return o.push(n[n.length-1]),o}flattenGridRouteZShapes(n,o,s){let a=new Set;s?.source?.id&&a.add(s.source.id),s?.target?.id&&a.add(s.target.id);let u=n.slice(),g=0,E=0;for(;E+3<u.length&&g<16;){let m=u[E],p=u[E+1],l=u[E+2],c=u[E+3],h=m.y===p.y,f=l.y===c.y;if(!(h===f&&(h?m.y!==c.y:m.x!==c.x))){E++;continue}let v=[{x:m.x,y:c.y},{x:c.x,y:m.y}],_=false;for(let A of v)if(this.isOrthogonalSegmentClearOfNodes(m,A,o,a,2)&&this.isOrthogonalSegmentClearOfNodes(A,c,o,a,2)){u.splice(E+1,2,A),g++,_=true;break}_||E++;}return u}flattenGridRouteUBumps(n,o,s){if(n.length<5)return n;let e=16,i=2,a=new Set;s?.source?.id&&a.add(s.source.id),s?.target?.id&&a.add(s.target.id);let u=n.slice(),g=0,E=0;for(;E+4<u.length&&g<e;){let m=u[E],p=u[E+4],l=Math.abs(m.x-p.x)<.001,c=Math.abs(m.y-p.y)<.001;if(!(l||c)){E++;continue}if(l&&c){E++;continue}this.isOrthogonalSegmentClearOfNodes(m,p,o,a,i)?(u.splice(E+1,3),g++):E++;}return u}isOrthogonalSegmentClearOfNodes(n,o,s,e,i){let a=n.y===o.y,u=n.x===o.x;if(!a&&!u)return false;for(let g of s){if(e.has(g.id))continue;let E=g.bounds||g.innerBounds;if(!E)continue;let m=typeof E.width=="function"?E.width():0,p=typeof E.height=="function"?E.height():0,l=(typeof E.x=="number"?E.x:0)-i,c=(E.X!==void 0?E.X:(E.x||0)+m)+i,h=(typeof E.y=="number"?E.y:0)-i,f=(E.Y!==void 0?E.Y:(E.y||0)+p)+i;if(a){if(n.y<h||n.y>f)continue;let d=Math.min(n.x,o.x);if(Math.max(n.x,o.x)<l||d>c)continue;return false}else {if(n.x<l||n.x>c)continue;let d=Math.min(n.y,o.y);if(Math.max(n.y,o.y)<h||d>f)continue;return false}}return true}createFallbackBounds(n){if(!q0?.Rectangle)return null;let o=((n.visualWidth??n.width)||50)/2,s=((n.visualHeight??n.height)||30)/2,e=(n.x||0)-o,i=(n.x||0)+o,a=(n.y||0)-s,u=(n.y||0)+s;return new q0.Rectangle(e,i,a,u)}getRectangleIntersection(n,o,s,e,i){let a=i.x,u=i.x+i.width(),g=i.y,E=i.y+i.height(),m=s-n,p=e-o;if(m===0&&p===0)return {x:n,y:o};let l=0,c=1;if(m!==0){let f=(a-n)/m,d=(u-n)/m;l=Math.max(l,Math.min(f,d)),c=Math.min(c,Math.max(f,d));}if(p!==0){let f=(g-o)/p,d=(E-o)/p;l=Math.max(l,Math.min(f,d)),c=Math.min(c,Math.max(f,d));}if(l>c)return null;let h=l>0?l:c;return {x:n+h*m,y:o+h*p}}computeAllRoutes(){this.computedRoutes.clear(),this.container.selectAll(".link-group path").each(n=>{try{let o=this.computeSingleRoute(n);o&&this.computedRoutes.set(n.id,o);}catch(o){console.error(`Error routing edge ${n.id} from ${n.source.id} to ${n.target.id}:`,o),this.showRuntimeAlert(n.source.id,n.target.id),this.computedRoutes.set(n.id,[{x:n.source.x||0,y:n.source.y||0},{x:n.target.x||0,y:n.target.y||0}]);}});}applyRoutesToSVG(){this.container.selectAll(".link-group path").attr("d",n=>{let o=this.computedRoutes.get(n.id);return o?this.useTautRouter&&n.source.id!==n.target.id&&!this.isAlignmentEdge(n)?this.filletPath(o):this.lineFunction(this.addTangentGuides(o)):null});}addTangentGuides(n){if(n.length<=2)return n;let o=8,s=2,e=[...n],i=e.length-1,a=e[i].x-e[i-1].x,u=e[i].y-e[i-1].y,g=Math.sqrt(a*a+u*u);if(g>.5){let l=a/g,c=u/g,h=Math.min(o,g*.5),f=Math.min(s,g*.2);h>f+.1?e.splice(i,0,{x:e[i].x-l*h,y:e[i].y-c*h},{x:e[i].x-l*f,y:e[i].y-c*f}):e.splice(i,0,{x:e[i].x-l*f,y:e[i].y-c*f});}let E=e[1].x-e[0].x,m=e[1].y-e[0].y,p=Math.sqrt(E*E+m*m);if(p>.5){let l=E/p,c=m/p,h=Math.min(o,p*.5),f=Math.min(s,p*.2);h>f+.1?e.splice(1,0,{x:e[0].x+l*f,y:e[0].y+c*f},{x:e[0].x+l*h,y:e[0].y+c*h}):e.splice(1,0,{x:e[0].x+l*f,y:e[0].y+c*f});}return e}tryDirectLineRoute(n){if(!n?.source||!n?.target||n.source.id===n.target.id||this.getAllEdgesBetweenNodes(n.source.id,n.target.id).length>1)return null;let s=this.getRenderedBounds(n.source),e=this.getRenderedBounds(n.target);if(this.getTouchDirection(s,e,5)!=="none")return null;let a={x:s.x+s.width()/2,y:s.y+s.height()/2},u={x:e.x+e.width()/2,y:e.y+e.height()/2};if(this.currentLayout?.nodes)for(let m of this.currentLayout.nodes){if(m.id===n.source.id||m.id===n.target.id)continue;let p=this.normalizeNodeBounds(m);if(this.lineIntersectsRect(a,u,p))return null}let g=this.clipLineToRectExit(a,u,s),E=this.clipLineToRectExit(u,a,e);return [g,E]}applyPortBasedEndpointsToDirectRoute(n,o){let s=n._sourcePortCount>1,e=n._targetPortCount>1;if(!s&&!e)return o;let i=this.applyPortBasedEndpoints(n,o.map(g=>({...g}))),a=this.getRenderedBounds(n.source),u=this.getRenderedBounds(n.target);return this.isPointOnRectPerimeter(i[0],a)&&this.isPointOnRectPerimeter(i[i.length-1],u)?i:o}getRenderedBounds(n){let o=this.getVisibleBounds(n);return o?{x:o.x,y:o.y,width:o.width,height:o.height}:this.normalizeNodeBounds(n)}isPointOnRectPerimeter(n,o,s=1){let e=o.x,i=o.x+o.width(),a=o.y,u=o.y+o.height(),g=n.x>=e-s&&n.x<=i+s,E=n.y>=a-s&&n.y<=u+s;return !g||!E?false:Math.abs(n.x-e)<=s||Math.abs(n.x-i)<=s||Math.abs(n.y-a)<=s||Math.abs(n.y-u)<=s}clipLineToRectExit(n,o,s){let e=o.x-n.x,i=o.y-n.y;if(e===0&&i===0)return {x:n.x,y:n.y};let a=s.x,u=s.x+s.width(),g=s.y,E=s.y+s.height(),m=[];e>0?m.push((u-n.x)/e):e<0&&m.push((a-n.x)/e),i>0?m.push((E-n.y)/i):i<0&&m.push((g-n.y)/i);let p=1/0;for(let l of m)l>0&&l<p&&(p=l);return Number.isFinite(p)?{x:n.x+p*e,y:n.y+p*i}:{x:n.x,y:n.y}}computeSingleRoute(n){if(this.isAlignmentEdge(n))return [{x:n.source.x||0,y:n.source.y||0},{x:n.target.x||0,y:n.target.y||0}];if(n.source.id===n.target.id)return this.createSelfLoopRoute(n);if(this.useTautRouter)return this.computeTautRoute(n);if(!n.id?.startsWith("_g_")){let i=this.tryDirectLineRoute(n);if(i)return this.applyPortBasedEndpointsToDirectRoute(n,i)}let o=[{x:n.source.x||0,y:n.source.y||0},{x:n.target.x||0,y:n.target.y||0}],s;if(typeof this.colaLayout?.routeEdge=="function")try{if(s=this.colaLayout.routeEdge(n),!s||!Array.isArray(s)||s.length<2||!s[0]||!s[1]||s[0].x===void 0||s[0].y===void 0)throw new Error(`WebCola failed to route edge ${n.id} from ${n.source.id} to ${n.target.id}`)}catch(i){return console.log("Error routing edge",n.id,`from ${n.source.id} to ${n.target.id}`),console.error(i),o}else s=o;if(n.id?.startsWith("_g_"))return this.routeGroupEdge(n,s);s=this.handleMultipleEdgeRouting(n,s),s=this.applyPortBasedEndpoints(n,s);let e=this.getNearTouchPerpendicularRoute(n);return e||s}computeTautRoute(n){if(n.id?.startsWith("_g_")){let u=this.getRenderedBounds(n.source),g=this.getRenderedBounds(n.target),E={x:u.x+u.width()/2,y:u.y+u.height()/2},m={x:g.x+g.width()/2,y:g.y+g.height()/2},p=[this.clipLineToRectExit(E,m,u),this.clipLineToRectExit(m,E,g)];return this.routeGroupEdge(n,p)}let o=this.getPortAttachment(n,"source"),s=this.getPortAttachment(n,"target"),e=this.buildRouterObstacles(n),i=this.routeTautPolyline(o,s,e),a=this.handleMultipleEdgeRouting(n,i.map(u=>({...u})));return this.routePolylineClips(a,e)?i:a}routePolylineClips(n,o){for(let s=0;s<n.length-1;s++)if(this.anyObstacleBlocks(n[s],n[s+1],o,-1,-1))return true;return false}getPortAttachment(n,o){let s=o==="source"?n.source:n.target,e=o==="source"?n.target:n.source,i=this.getRenderedBounds(s),a=this.getRenderedBounds(e),u={x:i.x+i.width()/2,y:i.y+i.height()/2},g={x:a.x+a.width()/2,y:a.y+a.height()/2},E=this.clipLineToRectExit(u,g,i),m=o==="source"?n._sourcePortCount:n._targetPortCount;if(m!==void 0&&m>1){let p=o==="source"?[{...E},{...g}]:[{...g},{...E}],l=this.applyPortBasedEndpoints(n,p),c=o==="source"?l[0]:l[l.length-1];c&&this.isPointOnRectPerimeter(c,i)&&(E=c);}return {point:E,normal:this.sideNormal(E,i)}}sideNormal(n,o){let s=o.x,e=o.x+o.width(),i=o.y,a=o.y+o.height(),u=Math.abs(n.x-s),g=Math.abs(n.x-e),E=Math.abs(n.y-i),m=Math.abs(n.y-a),p=Math.min(u,g,E,m);return p===u?{x:-1,y:0}:p===g?{x:1,y:0}:p===E?{x:0,y:-1}:{x:0,y:1}}buildRouterObstacleCache(){let n=[],o=Er.EDGE_CLEARANCE_PX;for(let s of this.currentLayout?.nodes||[]){let e=this.getRenderedBounds(s),i=e.width(),a=e.height();i<=0&&a<=0||n.push({id:s.id,minX:e.x-o,minY:e.y-o,maxX:e.x+i+o,maxY:e.y+a+o});}this.routerObstacleCache=n;}buildRouterObstacles(n){let o=n.source.id,s=n.target.id;if(this.routerObstacleCache)return this.routerObstacleCache.filter(a=>a.id!==o&&a.id!==s);let e=[];if(!this.currentLayout?.nodes)return e;let i=Er.EDGE_CLEARANCE_PX;for(let a of this.currentLayout.nodes){if(a.id===o||a.id===s)continue;let u=this.getRenderedBounds(a),g=u.width(),E=u.height();g<=0&&E<=0||e.push({minX:u.x-i,minY:u.y-i,maxX:u.x+g+i,maxY:u.y+E+i});}return e}routeTautPolyline(n,o,s){let e=n.point,i=o.point;if(!this.anyObstacleBlocks(e,i,s,-1,-1))return [{x:e.x,y:e.y},{x:i.x,y:i.y}];let a=Er.EDGE_STUB_LENGTH_PX,u=Er.EDGE_CLEARANCE_PX+a,g=Math.min(e.x,i.x)-u,E=Math.max(e.x,i.x)+u,m=Math.min(e.y,i.y)-u,p=Math.max(e.y,i.y)+u,l=[];for(let gt of s)gt.maxX<g||gt.minX>E||gt.maxY<m||gt.minY>p||l.push(gt);if(l.length===0||l.length>Er.MAX_ROUTER_OBSTACLES)return this.lBendFallback(e,i,s);let c={x:e.x+n.normal.x*a,y:e.y+n.normal.y*a},h={x:i.x+o.normal.x*a,y:i.y+o.normal.y*a},f=!this.pointInAnyObstacle(c,l)&&!this.anyObstacleBlocks(e,c,l,-1,-1),d=!this.pointInAnyObstacle(h,l)&&!this.anyObstacleBlocks(i,h,l,-1,-1),v=f?{...c,owner:-1}:{x:e.x,y:e.y,owner:-1},_=d?{...h,owner:-1}:{x:i.x,y:i.y,owner:-1},A=[v,_];l.forEach((gt,yt)=>{A.push({x:gt.minX,y:gt.minY,owner:yt},{x:gt.maxX,y:gt.minY,owner:yt},{x:gt.maxX,y:gt.maxY,owner:yt},{x:gt.minX,y:gt.maxY,owner:yt});});let b=A.length,T=0,M=1,k=1/0,B=1/0,F=-1/0,Q=-1/0;for(let gt of A)gt.x<k&&(k=gt.x),gt.x>F&&(F=gt.x),gt.y<B&&(B=gt.y),gt.y>Q&&(Q=gt.y);let R=s.filter(gt=>!(gt.maxX<k||gt.minX>F||gt.maxY<B||gt.minY>Q)),$=(gt,yt)=>!this.anyObstacleBlocks(gt,yt,R,-1,-1),z=new Array(b).fill(1/0),Z=new Array(b).fill(-1),dt=new Array(b).fill(false);z[T]=0;for(let gt=0;gt<b;gt++){let yt=-1,mt=1/0;for(let At=0;At<b;At++)!dt[At]&&z[At]<mt&&(mt=z[At],yt=At);if(yt===-1||yt===M)break;dt[yt]=true;for(let At=0;At<b;At++){if(dt[At]||At===yt||!$(A[yt],A[At]))continue;let Wt=A[yt].x-A[At].x,ie=A[yt].y-A[At].y,re=Math.sqrt(Wt*Wt+ie*ie);z[yt]+re<z[At]&&(z[At]=z[yt]+re,Z[At]=yt);}}if(!Number.isFinite(z[M]))return this.lBendFallback(e,i,s);let tt=[];for(let gt=M;gt!==-1;gt=Z[gt])tt.push({x:A[gt].x,y:A[gt].y});tt.reverse();let ct=[];return f&&ct.push({x:e.x,y:e.y}),ct.push(...tt),d&&ct.push({x:i.x,y:i.y}),this.simplifyCollinear(ct)}lBendFallback(n,o,s){for(let e of [{x:o.x,y:n.y},{x:n.x,y:o.y}])if(!this.pointInAnyObstacle(e,s)&&!this.anyObstacleBlocks(n,e,s,-1,-1)&&!this.anyObstacleBlocks(e,o,s,-1,-1))return this.simplifyCollinear([{x:n.x,y:n.y},e,{x:o.x,y:o.y}]);return [{x:n.x,y:n.y},{x:o.x,y:o.y}]}anyObstacleBlocks(n,o,s,e,i){for(let a=0;a<s.length;a++)if(!(a===e||a===i)&&this.segmentEntersRect(n,o,s[a]))return true;return false}segmentEntersRect(n,o,s){let i=0,a=1,u=o.x-n.x,g=o.y-n.y,E=[-u,u,-g,g],m=[n.x-s.minX,s.maxX-n.x,n.y-s.minY,s.maxY-n.y];for(let c=0;c<4;c++)if(Math.abs(E[c])<1e-6){if(m[c]<0)return false}else {let h=m[c]/E[c];if(E[c]<0){if(h>a)return false;h>i&&(i=h);}else {if(h<i)return false;h<a&&(a=h);}}if(a-i<=1e-6)return false;let p=n.x+(i+a)/2*u,l=n.y+(i+a)/2*g;return p>s.minX+1e-6&&p<s.maxX-1e-6&&l>s.minY+1e-6&&l<s.maxY-1e-6}pointInAnyObstacle(n,o){for(let s of o)if(n.x>s.minX&&n.x<s.maxX&&n.y>s.minY&&n.y<s.maxY)return true;return false}simplifyCollinear(n){if(n.length<=2)return n;let o=[n[0]];for(let s=1;s<n.length-1;s++){let e=o[o.length-1],i=n[s],a=n[s+1],u=Math.abs(i.x-e.x)<1e-6&&Math.abs(i.y-e.y)<1e-6,g=(i.x-e.x)*(a.y-e.y)-(i.y-e.y)*(a.x-e.x);!u&&Math.abs(g)>1e-6&&o.push(i);}return o.push(n[n.length-1]),o}filletPath(n){if(!n||n.length===0)return "";if(n.length===1)return `M ${n[0].x} ${n[0].y}`;if(n.length===2)return `M ${n[0].x} ${n[0].y} L ${n[1].x} ${n[1].y}`;let o=Er.EDGE_CLEARANCE_PX,s=`M ${n[0].x} ${n[0].y}`;for(let i=1;i<n.length-1;i++){let a=n[i-1],u=n[i],g=n[i+1],E=a.x-u.x,m=a.y-u.y,p=g.x-u.x,l=g.y-u.y,c=Math.hypot(E,m),h=Math.hypot(p,l);if(c<1e-6||h<1e-6){s+=` L ${u.x} ${u.y}`;continue}let f=Math.min(o,c/2,h/2),d={x:u.x+E/c*f,y:u.y+m/c*f},v={x:u.x+p/h*f,y:u.y+l/h*f};s+=` L ${d.x} ${d.y} Q ${u.x} ${u.y} ${v.x} ${v.y}`;}let e=n[n.length-1];return s+=` L ${e.x} ${e.y}`,s}optimizeCrossings(){if(this.useTautRouter||this.computedRoutes.size<2)return;let n=0;if(this.currentLayout?.links)for(let i of this.currentLayout.links)this.isAlignmentEdge(i)||i.source.id!==i.target.id&&this.computedRoutes.get(i.id)&&n++;if(n>Er.CROSSING_OPTIMIZATION_EDGE_THRESHOLD)return;let o=Er.MAX_CROSSING_OPTIMIZATION_BUDGET_MS,s=typeof performance<"u"&&typeof performance.now=="function"?performance.now():Date.now(),e=()=>(typeof performance<"u"&&typeof performance.now=="function"?performance.now():Date.now())-s;for(let i=0;i<Er.MAX_CROSSING_OPTIMIZATION_PASSES;i++){if(e()>o){console.warn(`[optimizeCrossings] Budget (${o}ms) exceeded after pass ${i}; accepting current routing.`);return}let a=this.detectEdgeCrossings();if(a.length===0)break;if(e()>o){console.warn(`[optimizeCrossings] Budget exceeded after detection on pass ${i}; accepting current routing.`);return}if(!this.resolveEdgeCrossings(a))break}}detectEdgeCrossings(){let n=[],o=[],s=[];if(this.currentLayout?.links)for(let i of this.currentLayout.links){if(this.isAlignmentEdge(i)||i.source.id===i.target.id)continue;let a=this.computedRoutes.get(i.id);!a||a.length<2||(n.push(i.id),o.push(a),s.push({sourceId:i.source.id,targetId:i.target.id}));}let e=[];for(let i=0;i<n.length;i++)for(let a=i+1;a<n.length;a++){let u=s[i],g=s[a],E=u.sourceId===g.sourceId||u.sourceId===g.targetId,m=u.targetId===g.sourceId||u.targetId===g.targetId;if(!(E&&m)&&this.routesCross(o[i],o[a])){let p=this.getRouteLength(o[i])+this.getRouteLength(o[a]);e.push([n[i],n[a],p]);}}return e.sort((i,a)=>a[2]-i[2]),e.map(([i,a])=>[i,a])}routesCross(n,o){for(let s=0;s<n.length-1;s++)for(let e=0;e<o.length-1;e++)if(this.segmentsIntersect(n[s],n[s+1],o[e],o[e+1]))return true;return false}segmentsIntersect(n,o,s,e){let i=this.cross(s,e,n),a=this.cross(s,e,o),u=this.cross(n,o,s),g=this.cross(n,o,e);return (i>0&&a<0||i<0&&a>0)&&(u>0&&g<0||u<0&&g>0)}cross(n,o,s){return (o.x-n.x)*(s.y-n.y)-(o.y-n.y)*(s.x-n.x)}resolveEdgeCrossings(n){let o=false;for(let[s,e]of n){let i=this.computedRoutes.get(s),a=this.computedRoutes.get(e);if(!i||!a)continue;let u=this.findEdgeById(s),g=this.findEdgeById(e),E=u&&g?this.findSharedNodeId(u,g):null;if(u&&g&&E&&this.tryResolveByPortSwap(u,g,E)){o=true;continue}let m=this.findCrossingSegments(i,a);if(!m)continue;let p=this.getRouteLength(i),l=this.getRouteLength(a),[c,h]=p<=l?[i,s]:[a,e],f=c===i?a:i,d=c===i?m.segIdxA:m.segIdxB,v={dx:c[d+1].x-c[d].x,dy:c[d+1].y-c[d].y},_=Math.sqrt(v.dx*v.dx+v.dy*v.dy);if(_<1)continue;let A=-v.dy/_,b=v.dx/_,T=false,M=Er.CROSSING_NUDGE_DISTANCE;for(let k of [1,-1]){let B=(c[d].x+c[d+1].x)/2+A*M*k,F=(c[d].y+c[d+1].y)/2+b*M*k,Q=[...c];if(Q.splice(d+1,0,{x:B,y:F}),!this.routesCross(Q,f)){this.computedRoutes.set(h,Q),o=true,T=true;break}}if(!T){let k=this.rerouteAroundEdge(c,f);k&&!this.routesCross(k,f)&&(this.computedRoutes.set(h,k),o=true);}}return o}findEdgeById(n){if(!this.currentLayout?.links)return null;for(let o of this.currentLayout.links)if(o.id===n)return o;return null}findSharedNodeId(n,o){let s=n.source.id,e=n.target.id,i=o.source.id,a=o.target.id,u=s===i||s===a,g=e===i||e===a;return u&&g?null:u?s:g?e:null}tryResolveByPortSwap(n,o,s){let e=n,i=o,a=this.computedRoutes.get(n.id),u=this.computedRoutes.get(o.id);if(!a||!u)return false;let g=n.source.id===s,E=o.source.id===s,m=g?"_sourcePortIndex":"_targetPortIndex",p=g?"_sourcePortCount":"_targetPortCount",l=E?"_sourcePortIndex":"_targetPortIndex",c=E?"_sourcePortCount":"_targetPortCount",h=e[m],f=i[l],d=e[p],v=i[c];if(h===void 0||f===void 0||d===void 0||v===void 0||d!==v||h===f)return false;e[m]=f,i[l]=h;let _=this.applyPortBasedEndpoints(e,a.map(b=>({...b}))),A=this.applyPortBasedEndpoints(i,u.map(b=>({...b})));return this.routesCross(_,A)?(e[m]=h,i[l]=f,false):(this.computedRoutes.set(n.id,_),this.computedRoutes.set(o.id,A),true)}rerouteAroundEdge(n,o){let s=1/0,e=-1/0,i=1/0,a=-1/0;for(let c of o)s=Math.min(s,c.x),e=Math.max(e,c.x),i=Math.min(i,c.y),a=Math.max(a,c.y);let u=Er.CROSSING_NUDGE_DISTANCE,g=n[0],E=n[n.length-1],m=e-s,p=a-i,l=[];if(p>=m){let c=s-u,h=e+u;for(let f of [c,h]){let d=[g,{x:f,y:g.y},{x:f,y:E.y},E];this.routesCross(d,o)||l.push(d);}if(l.length===0)for(let f of [i-u,a+u]){let d=[g,{x:g.x,y:f},{x:E.x,y:f},E];this.routesCross(d,o)||l.push(d);}}else {let c=i-u,h=a+u;for(let f of [c,h]){let d=[g,{x:g.x,y:f},{x:E.x,y:f},E];this.routesCross(d,o)||l.push(d);}if(l.length===0)for(let f of [s-u,e+u]){let d=[g,{x:f,y:g.y},{x:f,y:E.y},E];this.routesCross(d,o)||l.push(d);}}return l.length===0?null:(l.sort((c,h)=>this.getRouteLength(c)-this.getRouteLength(h)),l[0])}findCrossingSegments(n,o){for(let s=0;s<n.length-1;s++)for(let e=0;e<o.length-1;e++)if(this.segmentsIntersect(n[s],n[s+1],o[e],o[e+1]))return {segIdxA:s,segIdxB:e};return null}createSelfLoopRoute(n){let o=n.source,s=o.bounds;if(!s)return [{x:o.x-8,y:o.y},{x:o.x-8,y:o.y-20},{x:o.x,y:o.y-28},{x:o.x+8,y:o.y-20},{x:o.x+8,y:o.y}];let e=s.X-s.x,i=s.Y-s.y,a=s.x+e/2,u=s.y+i/2,g=this.getSelfLoopIndex(n),E=g%4,p=1+Math.floor(g/4)*Er.SELF_LOOP_CURVATURE_SCALE,l,c=0,h=0,f=0,d=0;switch(E){case 0:l={x:s.X,y:u},c=1,f=0,h=0,d=1;break;case 1:l={x:a,y:s.Y},c=0,f=-1,h=1,d=0;break;case 2:l={x:s.x,y:u},c=-1,f=0,h=0,d=-1;break;default:l={x:a,y:s.y},c=0,f=1,h=-1,d=0;break}let v=Math.min(e,i),A=Math.min((E===0||E===2?i:e)*.45,v*.5),b=v*.7*p,T=A*.55,M={x:l.x-f*A/2,y:l.y-d*A/2},k={x:l.x+f*A/2,y:l.y+d*A/2},B={x:M.x+c*T,y:M.y+h*T},F={x:k.x+c*T,y:k.y+h*T},Q={x:l.x+c*b,y:l.y+h*b};return [M,B,Q,F,k]}createGridSelfLoopRoute(n){let o=n.source,s=o.bounds;if(!s)return [{x:o.x-10,y:o.y},{x:o.x-10,y:o.y-24},{x:o.x+10,y:o.y-24},{x:o.x+10,y:o.y}];let e=s.X-s.x,i=s.Y-s.y,a=s.x+e/2,u=s.y+i/2,g=this.getSelfLoopIndex(n),E=g%4,p=1+Math.floor(g/4)*Er.SELF_LOOP_CURVATURE_SCALE,l=Math.min(e,i),h=Math.min((E===0||E===2?i:e)*.45,l*.5),f=l*.55*p,d,v,_,A;switch(E){case 0:d={x:s.X,y:u-h/2},v={x:s.X+f,y:u-h/2},_={x:s.X+f,y:u+h/2},A={x:s.X,y:u+h/2};break;case 1:d={x:a+h/2,y:s.Y},v={x:a+h/2,y:s.Y+f},_={x:a-h/2,y:s.Y+f},A={x:a-h/2,y:s.Y};break;case 2:d={x:s.x,y:u+h/2},v={x:s.x-f,y:u+h/2},_={x:s.x-f,y:u-h/2},A={x:s.x,y:u-h/2};break;default:d={x:a-h/2,y:s.y},v={x:a-h/2,y:s.y-f},_={x:a+h/2,y:s.y-f},A={x:a+h/2,y:s.y};break}return [d,v,_,A]}getSelfLoopIndex(n){let o=n.source.id,s=this.getNodePairKey(o,o),e=this.edgeRoutingCache.edgesBetweenNodes.get(s);if(e){let i=e.findIndex(a=>a.id===n.id);return i>=0?i:0}if(this.currentLayout?.links){let i=0;for(let a of this.currentLayout.links)if(a.source.id===o&&a.target.id===o){if(a.id===n.id)return i;i++;}}return 0}routeGroupEdge(n,o){let s=n.groupId?(this.currentLayout?.groups||[]).find(e=>e.id===n.groupId):null;if(!s)console.warn("[routeGroupEdge] Group not found for edge:",n.id,"\u2014 groupId:",n.groupId);else {if(!s.bounds&&q0?.Rectangle){let e=n.source?.id===n.keyNodeId?o[o.length-1]:o[0];e&&(s.bounds=new q0.Rectangle(e.x-50,e.x+50,e.y-30,e.y+30));}if(s.bounds)if(n.source?.id===n.keyNodeId)o[o.length-1]=this.closestPointOnRect(s.bounds,o[0]);else if(n.target?.id===n.keyNodeId){let e=s.bounds.inflate?.(-1)??s.bounds;o[0]=this.closestPointOnRect(e,o[o.length-1]);}else console.warn("[routeGroupEdge] keyNodeId matched neither side",{keyNodeId:n.keyNodeId,sourceId:n.source?.id,targetId:n.target?.id});}return o.length>2&&o.splice(1,o.length-2),o}handleMultipleEdgeRouting(n,o){let s=this.getAllEdgesBetweenNodes(n.source.id,n.target.id);if(s.length<=1)return o;if(o.length===2){let E={x:(o[0].x+o[1].x)/2,y:(o[0].y+o[1].y)/2};o.splice(1,0,E);}let e=o[1].x-o[0].x,i=o[1].y-o[0].y,a=Math.atan2(i,e),u=this.getRouteLength(o),g=s.findIndex(E=>E.id===n.id);if(g!==-1){o=this.applyEdgeOffsetWithIndex(n,o,s,a,g,u);let E=this.calculateCurvatureWithIndex(s,n.id,g),m=this.clampCurvature(E);o=this.applyCurvatureToRoute(o,m,a,u);}return o}getAllEdgesBetweenNodes(n,o){if(!this.currentLayout?.links)return [];let s=this.getNodePairKey(n,o);return this.edgeRoutingCache.edgesBetweenNodes.has(s)?this.edgeRoutingCache.edgesBetweenNodes.get(s):this.currentLayout.links.filter(e=>!this.isAlignmentEdge(e)&&(e.source.id===n&&e.target.id===o||e.source.id===o&&e.target.id===n))}calculateCurvature(n,o,s,e){if(e.startsWith("_alignment_"))return 0;let i=n.length,a=n.findIndex(u=>u.id===e);return i<=1?0:(a%2===0?1:-1)*(Math.floor(a/2)+1)*Er.CURVATURE_BASE_MULTIPLIER*i}calculateCurvatureWithIndex(n,o,s){let e=n.length;if(e<=1)return 0;let i=n[s],a=i?._sourcePortIndex,u=i?._sourcePortCount;return typeof a=="number"&&typeof u=="number"&&u>1?(a-(u-1)/2)/((u-1)/2)*u*Er.CURVATURE_BASE_MULTIPLIER:(s%2===0?1:-1)*(Math.floor(s/2)+1)*Er.CURVATURE_BASE_MULTIPLIER*e}applyEdgeOffset(n,o,s,e){let i=s.findIndex(u=>u.id===n.id),a=this.getRouteLength(o);return this.applyEdgeOffsetWithIndex(n,o,s,e,i,a)}applyEdgeOffsetWithIndex(n,o,s,e,i,a){let u=this.getDominantDirection(e),g=n._sourcePortIndex,E=n._sourcePortCount,m=n._targetPortIndex,p=n._targetPortCount,l=g!==void 0&&E!==void 0&&E>1,c=m!==void 0&&p!==void 0&&p>1;if(l||c)this.applyPortBasedOffset(o,n,u,l,c);else {let h=(i%2===0?1:-1)*(Math.floor(i/2)+1)*Er.MIN_EDGE_DISTANCE,f=this.clampOffset(h,a);u==="right"||u==="left"?(o[0].y+=f,o[o.length-1].y+=f):(u==="up"||u==="down")&&(o[0].x+=f,o[o.length-1].x+=f);}return n.source.innerBounds&&(o[0]=this.adjustPointToRectanglePerimeter(o[0],n.source.innerBounds)),n.target.innerBounds&&(o[o.length-1]=this.adjustPointToRectanglePerimeter(o[o.length-1],n.target.innerBounds)),o}computePortMargin(n,o){let s=n*Er.PORT_MARGIN_FRACTION;if(o<=1||(n-2*s)/o>=Er.MIN_PORT_PERIMETER_SPACING)return s;let i=(n-o*Er.MIN_PORT_PERIMETER_SPACING)/2;return Math.max(Er.MIN_ABSOLUTE_PORT_MARGIN_PX,i)}applyPortBasedEndpoints(n,o){if(!o||o.length<2||n.source.id===n.target.id)return o;let s=n._sourcePortIndex!==void 0&&n._sourcePortCount!==void 0&&n._sourcePortCount>1,e=n._targetPortIndex!==void 0&&n._targetPortCount!==void 0&&n._targetPortCount>1;if(!s&&!e)return o;let i=(n.target.x||0)-(n.source.x||0),a=(n.target.y||0)-(n.source.y||0),u=Math.atan2(a,i),g=this.getDominantDirection(u);if(s){let E=this.normalizeNodeBounds(n.source),m=n._sourcePortIndex,p=n._sourcePortCount;if(g==="right"||g==="left"){let l=E.height(),c=this.computePortMargin(l,p),h=l-2*c;o[0]={...o[0],y:E.y+c+(m+.5)*h/p};}else if(g==="up"||g==="down"){let l=E.width(),c=this.computePortMargin(l,p),h=l-2*c;o[0]={...o[0],x:E.x+c+(m+.5)*h/p};}}if(e){let E=this.normalizeNodeBounds(n.target),m=n._targetPortIndex,p=n._targetPortCount,l=g==="right"?"left":g==="left"?"right":g==="up"?"down":"up";if(l==="right"||l==="left"){let c=E.height(),h=this.computePortMargin(c,p),f=c-2*h,d=o.length-1;o[d]={...o[d],y:E.y+h+(m+.5)*f/p};}else if(l==="up"||l==="down"){let c=E.width(),h=this.computePortMargin(c,p),f=c-2*h,d=o.length-1;o[d]={...o[d],x:E.x+h+(m+.5)*f/p};}}return o}applyPortBasedEndpointsOrthogonal(n,o){if(!o||o.length<2||n?.source?.id===n?.target?.id)return o;let s=n._sourcePortIndex!==void 0&&n._sourcePortCount!==void 0&&n._sourcePortCount>1,e=n._targetPortIndex!==void 0&&n._targetPortCount!==void 0&&n._targetPortCount>1;if(!s&&!e)return o;let i=o.slice();return s&&(i=this.shiftRouteEndpointToPort(i,n.source,n._sourcePortIndex,n._sourcePortCount,"start")),e&&(i=this.shiftRouteEndpointToPort(i,n.target,n._targetPortIndex,n._targetPortCount,"end")),i}shiftRouteEndpointToPort(n,o,s,e,i){if(n.length<2)return n;let a=this.getVisibleBounds(o);if(!a)return n;let u=i==="start"?0:n.length-1,g=i==="start"?1:n.length-2,E=n[u],m=n[g],p=a.x,l=a.X,c=a.y,h=a.Y,f=l-p,d=h-c,v=1,_,A=Math.abs(E.x-p),b=Math.abs(E.x-l),T=Math.abs(E.y-c),M=Math.abs(E.y-h),k=Math.min(A,b,T,M);k===A?_="left":k===b?_="right":k===T?_="top":_="bottom";let B=_==="left"||_==="right"?d:f;if(B<=0)return n;let F=this.computePortMargin(B,e),Q=B-2*F,R=F+(s+.5)*Q/e,$;switch(_){case "left":$={x:p,y:c+R};break;case "right":$={x:l,y:c+R};break;case "top":$={x:p+R,y:c};break;case "bottom":$={x:p+R,y:h};break}let z=n.slice();if(Math.abs($.x-m.x)<v||Math.abs($.y-m.y)<v)return z[u]=$,z;let dt;return _==="left"||_==="right"?dt={x:m.x,y:$.y}:dt={x:$.x,y:m.y},i==="start"?(z[0]=$,z.splice(1,0,dt)):(z[z.length-1]=$,z.splice(z.length-1,0,dt)),z}applyPortBasedOffset(n,o,s,e,i){if(e){let a=this.normalizeNodeBounds(o.source),u=o._sourcePortIndex,g=o._sourcePortCount;if(s==="right"||s==="left"){let E=a.height(),m=this.computePortMargin(E,g),p=E-2*m,l=a.y+m+(u+.5)*p/g;n[0].y=l;}else if(s==="up"||s==="down"){let E=a.width(),m=this.computePortMargin(E,g),p=E-2*m,l=a.x+m+(u+.5)*p/g;n[0].x=l;}}if(i){let a=this.normalizeNodeBounds(o.target),u=o._targetPortIndex,g=o._targetPortCount,E=s==="right"?"left":s==="left"?"right":s==="up"?"down":"up";if(E==="right"||E==="left"){let m=a.height(),p=this.computePortMargin(m,g),l=m-2*p,c=a.y+p+(u+.5)*l/g;n[n.length-1].y=c;}else if(E==="up"||E==="down"){let m=a.width(),p=this.computePortMargin(m,g),l=m-2*p,c=a.x+p+(u+.5)*l/g;n[n.length-1].x=c;}}}clampOffset(n,o){let s=Math.max(Er.MIN_EDGE_DISTANCE,o*Er.MAX_EDGE_OFFSET_RATIO);return Math.max(-s,Math.min(s,n))}getRouteLength(n){return n.length<2?0:n.slice(1).reduce((o,s,e)=>{let i=n[e],a=s.x-i.x,u=s.y-i.y;return o+Math.sqrt(a*a+u*u)},0)}clampCurvature(n){return Math.max(-Er.MAX_EDGE_CURVATURE_RATIO,Math.min(Er.MAX_EDGE_CURVATURE_RATIO,n))}applyCurvatureToRoute(n,o,s,e){return o===0||n.forEach((i,a)=>{if(a>0&&a<n.length-1){let u=o*Math.abs(Math.sin(s))*e,g=o*Math.abs(Math.cos(s))*e;i.x+=u,i.y+=g;}}),n}getDominantDirection(n){return n=(n+Math.PI)%(2*Math.PI)-Math.PI,n>=-Math.PI/4&&n<=Math.PI/4?"right":n>Math.PI/4&&n<3*Math.PI/4?"up":n>=3*Math.PI/4||n<=-3*Math.PI/4?"left":n>-3*Math.PI/4&&n<-Math.PI/4?"down":null}closestPointOnRect(n,o){if(!n)return o;let{x:s,y:e,X:i,Y:a}=n,u=Math.max(s,Math.min(o.x,i)),g=Math.max(e,Math.min(o.y,a));return {x:u,y:g}}getVisibleBounds(n){if(!n)return null;let o=n.x??(n.bounds&&typeof n.bounds.cx=="function"?n.bounds.cx():void 0),s=n.y??(n.bounds&&typeof n.bounds.cy=="function"?n.bounds.cy():void 0);if(o===void 0||s===void 0)return null;let e,i;return n.visualWidth!==void 0||n.visualHeight!==void 0?(e=(n.visualWidth??n.width??0)/2,i=(n.visualHeight??n.height??0)/2):Array.isArray(n.leaves)||Array.isArray(n.groups)?n.bounds&&typeof n.bounds.width=="function"?(e=n.bounds.width()/2-Er.GROUP_VISUAL_MARGIN_PX,i=n.bounds.height()/2-Er.GROUP_VISUAL_MARGIN_PX):n.bounds&&n.bounds.X!==void 0&&(e=(n.bounds.X-n.bounds.x)/2-Er.GROUP_VISUAL_MARGIN_PX,i=(n.bounds.Y-n.bounds.y)/2-Er.GROUP_VISUAL_MARGIN_PX):(n.width!==void 0||n.height!==void 0)&&(e=(n.width??0)/2,i=(n.height??0)/2),e===void 0||i===void 0||e<=0&&i<=0?null:(e=Math.max(0,e),i=Math.max(0,i),{cx:()=>o,cy:()=>s,width:()=>e*2,height:()=>i*2,x:o-e,X:o+e,y:s-i,Y:s+i})}getStableEdgeAnchor(n,o){if(!n)return o;let s,e,i,a;if(typeof n.cx=="function")s=n.cx(),e=n.cy(),i=n.width()/2,a=n.height()/2;else if(n.x!==void 0&&n.X!==void 0)s=(n.x+n.X)/2,e=(n.y+n.Y)/2,i=(n.X-n.x)/2,a=(n.Y-n.y)/2;else return o;let u=o.x-s,g=o.y-e,E=Math.abs(u)/i,m=Math.abs(g)/a;return E>m?u>0?{x:s+i,y:e}:{x:s-i,y:e}:g>0?{x:s,y:e+a}:{x:s,y:e-a}}getStableEdgePath(n,o,s){let e={x:n.x||0,y:n.y||0},i={x:o.x||0,y:o.y||0},a=this.getVisibleBounds(n)??n.bounds??n.innerBounds,u=this.getVisibleBounds(o)??o.bounds??o.innerBounds,g=a?this.getStableEdgeAnchor(a,i):e,E=u?this.getStableEdgeAnchor(u,e):i;return s&&a&&(g=this.applyPortOffsetToAnchor(g,a,s._sourcePortIndex,s._sourcePortCount,e,i)),s&&u&&(E=this.applyPortOffsetToAnchor(E,u,s._targetPortIndex,s._targetPortCount,i,e)),[g,E]}applyPortOffsetToAnchor(n,o,s,e,i,a){if(s===void 0||e===void 0||e<=1)return n;let u=typeof o.x=="number"?o.x:0,g=o.X!==void 0?o.X:u+(typeof o.width=="function"?o.width():0),E=typeof o.y=="number"?o.y:0,m=o.Y!==void 0?o.Y:E+(typeof o.height=="function"?o.height():0),p=g-u,l=m-E,c=1;if(Math.abs(n.x-u)<c||Math.abs(n.x-g)<c){let h=this.computePortMargin(l,e),f=l-2*h,d=E+h+(s+.5)*f/e;return {x:n.x,y:d}}else if(Math.abs(n.y-E)<c||Math.abs(n.y-m)<c){let h=this.computePortMargin(p,e),f=p-2*h;return {x:u+h+(s+.5)*f/e,y:n.y}}return n}adjustPointToRectanglePerimeter(n,o){return o?this.closestPointOnRect(o,n):n}updateLinkLabelsAfterRouting(){this.container.selectAll(".link-group .linklabel").attr("x",n=>{let o=this.shadowRoot?.querySelector(`path[data-link-id="${n.id}"]`);if(!o)return 0;let s=o.getTotalLength();return o.getPointAtLength(s/2).x}).attr("y",n=>{let o=this.shadowRoot?.querySelector(`path[data-link-id="${n.id}"]`);if(!o)return 0;let s=o.getTotalLength();return o.getPointAtLength(s/2).y}).attr("text-anchor","middle"),this.resolveLinkLabelOverlaps(),this.container.selectAll(".link-group .linklabel").raise();}resolveLinkLabelOverlaps(){let n=Array.from(this.container.selectAll(".link-group .linklabel").nodes());if(n.length<2)return;let o=4,s=28,e=2,i=new Map,a=new Map;for(let u=0;u<o;u++){let g=false;for(let E=0;E<n.length;E++){let m=n[E];for(let p=E+1;p<n.length;p++){let l=n[p];if(!K2t(m,l))continue;let c=m.getBBox(),h=l.getBBox(),f=c.x+c.width/2,d=c.y+c.height/2,v=h.x+h.width/2,_=h.y+h.height/2,A=(c.width+h.width)/2-Math.abs(f-v),b=(c.height+h.height)/2-Math.abs(d-_);if(!(A<=0||b<=0)){if(b<=A){let T=(b+e)/2,M=d>=_?1:-1;this.applyLabelDisplacement(m,0,M*T,i,a,s),this.applyLabelDisplacement(l,0,-M*T,i,a,s);}else {let T=(A+e)/2,M=f>=v?1:-1;this.applyLabelDisplacement(m,M*T,0,i,a,s),this.applyLabelDisplacement(l,-M*T,0,i,a,s);}g=true;}}}if(!g)break}}applyLabelDisplacement(n,o,s,e,i,a){let u=e.get(n)||0,g=i.get(n)||0,E=u+o,m=g+s,p=Math.hypot(E,m);if(p>a){let f=a/p;E*=f,m*=f;}let l=ws.select(n),c=parseFloat(l.attr("x")||"0")+(E-u),h=parseFloat(l.attr("y")||"0")+(m-g);l.attr("x",c).attr("y",h),e.set(n,E),i.set(n,m);}fitViewportToContent(n=false){let o=this.svg?.node();if(!o||!this.zoomBehavior||this.userHasManuallyZoomed&&!this.isInitialRender&&!n)return;let s=this.calculateContentBounds();if(!s)return;let e=o.clientWidth||o.parentElement?.clientWidth||800,i=o.clientHeight||o.parentElement?.clientHeight||600,a=Er.VIEWBOX_PADDING*4,u=(e-a*2)/s.width,g=(i-a*2)/s.height,E=Math.min(u,g,1),[m,p]=this.zoomBehavior.scaleExtent(),l=Math.max(m,Math.min(p,E)),c=s.x+s.width/2,h=s.y+s.height/2,f=e/2-c*l,d=i/2-h*l,v=ws.zoomIdentity.translate(f,d).scale(l);this.isInitialRender?(this.svg.call(this.zoomBehavior.transform,v),this.isInitialRender=false):this.svg.transition().duration(300).ease(ws.easeCubicOut).call(this.zoomBehavior.transform,v),this.updateZoomControlStates();}resetViewToFitContent(){this.userHasManuallyZoomed=false,this.fitViewportToContent(true);}calculateContentBounds(){try{if(!this.currentLayout||!this.container)return null;let n=1/0,o=1/0,s=-1/0,e=-1/0,i=(c,h,f,d)=>{[c,h,f,d].every(Number.isFinite)&&(n=Math.min(n,c),s=Math.max(s,h),o=Math.min(o,f),e=Math.max(e,d));},a=this.currentLayout.nodes;a&&a.length>0&&a.forEach(c=>{if(this.isHiddenNode(c)||typeof c.x!="number"||typeof c.y!="number")return;let h=c.visualWidth??c.width??0,f=c.visualHeight??c.height??0,d=h/2,v=f/2;i(c.x-d,c.x+d,c.y-v,c.y+v);});let u=this,g=this.container.selectAll(".link-group path");g.empty()||g.each(function(c){if(!(c?.id&&u.isAlignmentEdge(c)))try{let h=this.getBBox();(h.width>0||h.height>0)&&i(h.x,h.x+h.width,h.y,h.y+h.height);}catch{}});let E=this.container.selectAll(".node, .error-node");E.empty()||E.each(function(c){if(!u.isHiddenNode(c))try{let h=this.getBBox();(h.width>0||h.height>0)&&i(h.x,h.x+h.width,h.y,h.y+h.height);}catch{}});let m=this.container.selectAll("text");m.empty()||m.each(function(c){if(!(c&&(typeof c.name=="string"||typeof c.id=="string")&&u.isHiddenNode(c)))try{let h=this.getBBox();(h.width>0||h.height>0)&&i(h.x-5,h.x+h.width+5,h.y-5,h.y+h.height+5);}catch{}});let p=this.container.selectAll(".group");return p.empty()||p.each(function(){try{let c=this.getBBox();(c.width>0||c.height>0)&&i(c.x,c.x+c.width,c.y,c.y+c.height);}catch{}}),n===1/0||o===1/0||s===-1/0||e===-1/0?(console.warn("Could not calculate content bounds - no valid elements found"),null):{x:n,y:o,width:s-n,height:e-o}}catch(n){return console.error("Error calculating content bounds:",n),null}}dispatchRelationsAvailableEvent(){let n=this.getAllRelations(),o=new CustomEvent("relations-available",{detail:{relations:n,count:n.length,timestamp:Date.now(),graphId:this.id||"unknown"},bubbles:true,cancelable:true});this.dispatchEvent(o);}getAllRelations(){if(!this.currentLayout?.links)return [];let n=new Set(this.currentLayout.links.filter(o=>!this.isAlignmentEdge(o)).map(o=>o.relName).filter(Boolean));return Array.from(n)}highlightRelation(n){return this.currentLayout?.links?(this.svgLinkGroups.filter(o=>o.relName===n&&!this.isAlignmentEdge(o)).selectAll("path").classed("highlighted",true),true):false}clearHighlightRelation(n){return this.currentLayout?.links?(this.svgLinkGroups.filter(o=>o.relName===n&&!this.isAlignmentEdge(o)).selectAll("path").classed("highlighted",false),true):false}highlightNodes(n){if(!this.currentLayout?.nodes||!this.svgNodes||!n||n.length===0)return false;let o=new Set(n),s=false;return this.svgNodes.each((e,i,a)=>{o.has(e.id)&&(ws.select(a[i]).classed("highlighted",true),s=true);}),s}highlightNodePairs(n,o={}){if(!this.currentLayout?.nodes||!this.svgNodes||!n||n.length===0)return false;let{showBadges:s=false}=o,e=new Set,i=new Set;n.forEach((u,g)=>{if(!Array.isArray(u)){console.warn(`highlightNodePairs: Pair at index ${g} is not an array, skipping`);return}if(u.length!==2){console.warn(`highlightNodePairs: Pair at index ${g} has ${u.length} elements (expected 2), skipping`);return}let[E,m]=u;E&&e.add(E),m&&i.add(m);});let a=false;return this.svgNodes.each((u,g,E)=>{let m=ws.select(E[g]);e.has(u.id)&&(m.classed("highlighted-first",true),a=true,s&&this.addHighlightBadge(m,u,"1","#007aff")),i.has(u.id)&&(m.classed("highlighted-second",true),a=true,s&&(e.has(u.id)?this.addHighlightBadge(m,u,"1,2","#9B59B6"):this.addHighlightBadge(m,u,"2","#ff3b30")));}),a}clearNodeHighlights(){return this.svgNodes?(this.svgNodes.classed("highlighted",false).classed("highlighted-first",false).classed("highlighted-second",false).selectAll(".highlight-badge, .highlight-badge-bg").remove(),true):false}addHighlightBadge(n,o,s,e){n.selectAll(".highlight-badge, .highlight-badge-bg").remove();let i=16,a=4,u=(o.width||0)/2-i/2-a,g=-(o.height||0)/2+i/2+a;n.append("circle").attr("class","highlight-badge-bg").attr("cx",u).attr("cy",g).attr("r",i/2).attr("fill",e),n.append("text").attr("class","highlight-badge").attr("x",u).attr("y",g).attr("dy","0.35em").text(s);}showRuntimeAlert(n,o){console.warn(`Runtime (WebCola) error when laying out an edge from ${n} to ${o}. You may have to click and drag these nodes slightly to un-stick layout.`);}getCSS(){return `
|
|
866
|
+
`;}initializeD3(){ws||(ws=window.d3),this.svg=ws.select(this.root.querySelector("#svg")),this.container=this.svg.select(".zoomable"),ws.zoom?(this.zoomBehavior=ws.zoom().scaleExtent([.01,20]).on("start",()=>{ws.event.sourceEvent&&(this.userHasManuallyZoomed=true);}).on("zoom",()=>{this.container.attr("transform",ws.event.transform),this.updateZoomControlStates(),this.updateSmallNodeClasses();}),this.svg.call(this.zoomBehavior),this.initializeZoomControls()):console.warn("D3 zoom behavior not available. Ensure D3 v4+ is loaded.");}initializeZoomControls(){let n=this.root.querySelector("#zoom-in"),o=this.root.querySelector("#zoom-out"),s=this.root.querySelector("#zoom-fit");n&&n.addEventListener("click",()=>{this.userHasManuallyZoomed=true,this.zoomIn();}),o&&o.addEventListener("click",()=>{this.userHasManuallyZoomed=true,this.zoomOut();}),s&&s.addEventListener("click",()=>{this.resetViewToFitContent();});let e=this.root.querySelector("#routing-mode");if(e){let u=this.layoutFormat||"default";e.value=u,e.addEventListener("change",()=>{this.handleRoutingModeChange(e.value);});}let i=this.root.querySelector("#theme-mode");i&&(this.updateThemeDropdown(),i.addEventListener("change",()=>{this.setTheme(i.value),this.dispatchEvent(new CustomEvent("theme-changed",{detail:{theme:i.value},bubbles:true}));}));let a=this.root.querySelector("#screenshot-btn");a&&a.addEventListener("click",()=>{this.takeScreenshot();}),this.updateZoomControlStates();}handleRoutingModeChange(n){this.setAttribute("layoutFormat",n),this.currentLayout&&this.colaLayout&&(n==="grid"?this.gridify(10,25,10):this.routeEdges(),this.dispatchEvent(new CustomEvent("routing-mode-changed",{detail:{mode:n}})));}updateRoutingModeDropdown(){let n=this.shadowRoot?.querySelector("#routing-mode");if(n){let o=this.layoutFormat||"default";n.value=o;}}initializeInputModeHandlers(){this.inputModeEnabled&&this.attachInputModeListeners();}attachInputModeListeners(){this.inputModeListenersAttached||(document.addEventListener("keydown",this.handleInputModeKeydown),document.addEventListener("keyup",this.handleInputModeKeyup),window.addEventListener("blur",this.handleInputModeBlur),this.inputModeListenersAttached=true);}detachInputModeListeners(){this.inputModeListenersAttached&&(document.removeEventListener("keydown",this.handleInputModeKeydown),document.removeEventListener("keyup",this.handleInputModeKeyup),window.removeEventListener("blur",this.handleInputModeBlur),this.inputModeListenersAttached=false);}activateInputMode(){this.isInputModeActive=true,this.svg&&this.svg.classed("input-mode",true),this.disableNodeDragging(),this.disableZoom(),this.updateEdgeEndpointMarkers(),this.dispatchEvent(new CustomEvent("input-mode-activated",{detail:{active:true}}));}deactivateInputMode(){this.isInputModeActive=false,this.svg&&this.svg.classed("input-mode",false),this.cleanupEdgeCreation(),this.enableNodeDragging(),this.enableZoom(),this.updateEdgeEndpointMarkers(),this.dispatchEvent(new CustomEvent("input-mode-deactivated",{detail:{active:false}}));}disableNodeDragging(){this.svgNodes&&this.colaLayout&&this.svgNodes.on(".drag",null);}enableNodeDragging(){if(this.svgNodes&&this.colaLayout&&this.colaLayout.drag){let n=this.colaLayout.drag();this.setupNodeDragHandlers(n),this.svgNodes.call(n);}}disableZoom(){this.svg&&this.zoomBehavior&&(this.storedTransform=ws.zoomTransform(this.svg.node()),this.svg.on(".zoom",null));}enableZoom(){this.svg&&this.zoomBehavior&&(this.svg.call(this.zoomBehavior),this.storedTransform&&this.svg.call(this.zoomBehavior.transform,this.storedTransform));}zoomIn(){this.svg&&this.zoomBehavior&&this.svg.transition().duration(200).call(this.zoomBehavior.scaleBy,1.5);}zoomOut(){this.svg&&this.zoomBehavior&&this.svg.transition().duration(200).call(this.zoomBehavior.scaleBy,1/1.5);}updateZoomControlStates(){if(!this.svg||!this.zoomBehavior)return;let o=ws.zoomTransform(this.svg.node()).k,[s,e]=this.zoomBehavior.scaleExtent(),i=this.root.querySelector("#zoom-in"),a=this.root.querySelector("#zoom-out");i&&(i.disabled=o>=e),a&&(a.disabled=o<=s);}cleanupEdgeCreation(){this.edgeCreationState.temporaryEdge&&this.edgeCreationState.temporaryEdge.remove(),this.edgeCreationState={isCreating:false,sourceNode:null,temporaryEdge:null};}setupNodeDragHandlers(n){n.on("start.cnd",o=>{this.userHasManuallyZoomed=true;let s={x:o.x,y:o.y};this.dragStartPositions.set(o.id,s),this.dispatchEvent(new CustomEvent("node-drag-start",{detail:{id:o.id,position:s}}));}).on("end.cnd",o=>{let s=this.dragStartPositions.get(o.id);this.dragStartPositions.delete(o.id);let e={id:o.id,previous:s,current:{x:o.x,y:o.y}};this.dispatchEvent(new CustomEvent("node-drag-end",{detail:e}));});}startEdgeCreation(n){this.isInputModeActive&&(this.cleanupEdgeCreation(),this.edgeCreationState.isCreating=true,this.edgeCreationState.sourceNode=n,this.edgeCreationState.temporaryEdge=this.container.append("line").attr("class","temporary-edge").attr("x1",n.x).attr("y1",n.y).attr("x2",n.x).attr("y2",n.y).attr("stroke","#007bff").attr("stroke-width",2).attr("stroke-dasharray","5,5").attr("opacity",.7),this.svg.on("mousemove.edgecreation",()=>{if(this.edgeCreationState.isCreating&&this.edgeCreationState.temporaryEdge){let[o,s]=ws.mouse(this.container.node());this.edgeCreationState.temporaryEdge.attr("x2",o).attr("y2",s);}}));}async finishEdgeCreation(n){if(!this.isInputModeActive||!this.edgeCreationState.isCreating||!this.edgeCreationState.sourceNode)return;let o=this.edgeCreationState.sourceNode;if(o.id===n.id&&!await this.showConfirmDialog(`Are you sure you want to create a self-loop edge on "${o.label||o.id}"?`)){this.cleanupEdgeCreation();return}this.svg.on("mousemove.edgecreation",null),await this.showEdgeLabelInput(o,n);}async showEdgeLabelInput(n,o){let s=await this.showPromptDialog(`Enter label for edge from "${n.label||n.id}" to "${o.label||o.id}":`,"");s!==null&&await this.createNewEdge(n,o,s||""),this.cleanupEdgeCreation();}async createNewEdge(n,o,s){if(!this.currentLayout)return;let e=this.currentLayout.nodes.findIndex(g=>g.id===n.id),i=this.currentLayout.nodes.findIndex(g=>g.id===o.id);if(e===-1||i===-1){console.error("Could not find node indices for edge creation");return}let u={id:`edge_${n.id}_${o.id}_${Date.now()}`,source:e,target:i,label:s,relName:s,color:"#333",isUserCreated:true};this.currentLayout.links.push(u),await this.updateExternalStateForNewEdge(n,o,s),this.dispatchEvent(new CustomEvent("edge-created",{detail:{edge:u,sourceNode:n,targetNode:o}})),this.rerenderGraph();}async updateExternalStateForNewEdge(n,o,s){if(s.trim())try{let e={atoms:[n.id,o.id],types:[n.type||"untyped",o.type||"untyped"]};console.log(`Dispatching edge creation request: ${s}(${n.id}, ${o.id})`);let i=new CustomEvent("edge-creation-requested",{detail:{relationId:s,sourceNodeId:n.id,targetNodeId:o.id,tuple:e},bubbles:!0});this.dispatchEvent(i);}catch(e){console.error("Failed to update external state for new edge:",e);}}rerenderGraph(){!this.currentLayout||!this.colaLayout||(this.colaLayout.links(this.currentLayout.links),this.container.selectAll(".link-group").remove(),this.renderLinks(this.currentLayout.links,this.colaLayout),this.updatePositions());}async editEdgeLabel(n){if(!this.isInputModeActive)return;let o=n.relName||n.label||"",s=n.label||n.relName||"",e=await this.showEdgeEditDialog("Edit edge label:",s);if(e==="DELETE"){await this.deleteEdge(n);return}if(e!==null&&e!==s){let i=e,a=this.getNodeFromEdge(n,"source"),u=this.getNodeFromEdge(n,"target");await this.updateExternalStateForEdgeModification(a,u,o,i),n.label=i,n.relName=i,this.dispatchEvent(new CustomEvent("edge-modified",{detail:{edge:n,oldLabel:currentLabel,newLabel:i}})),this.rerenderGraph();}}getNodeFromEdge(n,o){if(!this.currentLayout)return null;let s=typeof n[o]=="number"?n[o]:n[o].index;return this.currentLayout.nodes[s]||null}async updateExternalStateForEdgeModification(n,o,s,e){if(!(!n||!o))try{let i={atoms:[n.id,o.id],types:[n.type||"untyped",o.type||"untyped"]};console.log(`Dispatching edge modification request: ${s} -> ${e}`);let a=new CustomEvent("edge-modification-requested",{detail:{oldRelationId:s,newRelationId:e,sourceNodeId:n.id,targetNodeId:o.id,tuple:i},bubbles:!0});this.dispatchEvent(a);}catch(i){console.error("Failed to update external state for edge modification:",i);}}resolveTransitionMode(n){if(n?.transitionMode==="replace")return "replace";if(n?.transitionMode==="morph")return "morph";let o=this.getAttribute("transition-mode");return o==="replace"?"replace":o==="morph"||n?.policy?"morph":"replace"}async renderLayout(n,o){if(!u6(n))throw new Error("Invalid instance layout provided. Expected an InstanceLayout instance.");let s=this.resolveTransitionMode(o),e=s==="morph",i=s==="replace",a,u=false;if(o?.policy&&o.prevInstance&&o.currInstance){let p=this.buildPolicyRawState(o),l=this.getViewportBoundsInLayoutSpace(p.transform),c=o.policy.apply({priorState:p,prevInstance:o.prevInstance,currInstance:o.currInstance,spec:{constraints:{orientation:{relative:[],cyclic:[]},alignment:[],grouping:{groups:[],subgroups:[]}},directives:{sizes:[],hiddenAtoms:[],icons:[],projections:[],edgeStyles:[]}},viewportBounds:l});a=c.effectivePriorState,u=c.useReducedIterations,this.lastSeedState=a??null;}else if(o?.priorPositions)a=o.priorPositions,u=true;else if(e&&this.currentLayout?.nodes?.length){let p=this.getLayoutState();p.positions.length>0&&(a=p,u=true);}let g=!!(a&&a.positions.length>0),E=this.hasValidTransform(a?.transform),m=g?{priorPositions:a,lockUnconstrainedNodes:u}:void 0;if(this.applyViewportRenderPolicy(g,E),this.svg&&this.zoomBehavior&&ws)try{if(E){let p=a.transform,l=ws.zoomIdentity.translate(p.x,p.y).scale(p.k);this.svg.call(this.zoomBehavior.transform,l);}else if(!g){let p=ws.zoomIdentity;this.svg.call(this.zoomBehavior.transform,p);}}catch(p){console.warn("Failed to set zoom transform:",p);}try{if(!ws)throw new Error("D3 library not available. Please ensure D3 v4 is loaded from CDN.");if(!q0){if(!window.cola)throw new Error("WebCola library not available. Please ensure vendor/cola.js is loaded.");q0=window.cola;}if((!this.container||!this.svg)&&this.initializeD3(),!this.container)throw new Error("Failed to initialize D3 container. SVG elements may not be available.");i?(this.showLoading(),this.updateLoadingProgress("Translating layout...")):this.hideLoading();let l=this.root.querySelector("#svg-container").getBoundingClientRect(),c=l.width||800,h=l.height||600,d=await new exports.WebColaTranslator().translate(n,c,h,m);i&&this.updateLoadingProgress(`Computing layout for ${d.nodes.length} nodes...`);let v=d.nodes.length,_=Er.INITIAL_UNCONSTRAINED_ITERATIONS,A=Er.INITIAL_USER_CONSTRAINT_ITERATIONS,b=Er.INITIAL_ALL_CONSTRAINTS_ITERATIONS;g&&u&&(_=0,A=Math.min(10,A),b=Math.min(20,b)),v>100?(_=Math.max(g?0:5,Math.floor(_*.5)),A=Math.max(25,Math.floor(A*.5)),b=Math.max(100,Math.floor(b*.5))):v>50&&(_=Math.max(g?0:8,Math.floor(_*.8)),A=Math.max(40,Math.floor(A*.8)),b=Math.max(150,Math.floor(b*.75)));let{scaledConstraints:T,linkLength:M,groupCompactness:k}=this.getScaledDetails(d.constraints,DK,d.nodes,d.groups,d.links);i&&this.updateLoadingProgress("Applying constraints and initializing...");let B=g?.1:.001,F=q0.d3adaptor(ws).linkDistance(M).convergenceThreshold(B).avoidOverlaps(!0).handleDisconnected(!0).nodes(d.nodes).links(d.links).constraints(T).groups(d.groups).groupCompactness(k).size([d.FIG_WIDTH,d.FIG_HEIGHT]);if(e&&this.currentLayout?.nodes?.length){this.morphOldPositions=new Map;for(let z of this.currentLayout.nodes)z.x!=null&&z.y!=null&&this.morphOldPositions.set(z.id,{x:z.x,y:z.y});this.applyMorphExitSnapshot(d);}else this.morphOldPositions=null;this.currentLayout=d,this.colaLayout=F,e&&this.container&&this.snapshotOldGraph(),this.container.selectAll("*").remove(),this.renderGroups(d.groups,F),this.renderLinks(d.links,F),this.renderNodes(d.nodes,F),e&&this.container&&this.container.attr("opacity",0);let Q=0,R=_+A+b,$=!0;F.on("tick",()=>{if(!(!this.currentLayout||!this.svgNodes)){if($){if(Q++,Q%20===0){let z=Math.min(95,Math.round(Q/R*100));i&&this.updateLoadingProgress(`Computing layout... ${z}%`);}return}this.layoutFormat==="grid"?this.gridUpdatePositions():this.updatePositions();}}).on("end",()=>{!this.currentLayout||!this.svgNodes||($=!1,i&&this.updateLoadingProgress("Finalizing..."),e&&this.morphOldPositions&&this.morphOldPositions.size>0?(this.container&&this.container.attr("opacity",1),this.startMorphExitAnimation(),this.layoutFormat==="grid"?this.gridUpdatePositions():this.updatePositions(),this.hideEnteringElements(),this.animateMorphSlide()):(this.container&&this.container.attr("opacity",1),this.layoutFormat==="grid"?(this.gridUpdatePositions(),this.gridify(10,25,10)):(this.updatePositions(),this.routeEdges())),this.isUnsatCore&&this.showErrorIcon(),this.dispatchRelationsAvailableEvent(),this.dispatchEvent(new CustomEvent("layout-complete",{detail:{nodePositions:this.getNodePositions()}})),this.updateRoutingModeDropdown(),i&&this.hideLoading());});try{F.start(_,A,b,Er.GRID_SNAP_ITERATIONS);}catch(z){console.warn("WebCola layout start encountered an error, trying alternative approach:",z);try{F.start();}catch(Z){throw console.error("Both WebCola start methods failed:",Z),new Error(`WebCola layout failed to start: ${Z.message}`)}}}catch(p){console.error("Error rendering layout:",p),this.showError(`Layout rendering failed: ${p.message}`);}}snapshotOldGraph(){if(!this.svg||!this.container)return;this.svg.selectAll(".morph-old-graph").interrupt().remove();let n=this.container.node();if(!n)return;let o=n.cloneNode(true);o.setAttribute("class","morph-old-graph"),o.style.pointerEvents="none";let s=this.svg.node();s&&s.appendChild(o);}applyMorphExitSnapshot(n){if(!this.svg||!this.container)return;this.svg.selectAll(".morph-exit-layer").interrupt().remove();let o=this.currentLayout?.nodes||[],s=this.currentLayout?.links||[],e=new Set(n.nodes.map(c=>c.id)),i=new Set(n.links.map(c=>c.id)),a=new Set(o.map(c=>c.id)),u=new Set(s.map(c=>c.id));this.morphEnteringNodeIds=new Set([...e].filter(c=>!a.has(c))),this.morphEnteringEdgeIds=new Set([...i].filter(c=>!u.has(c)));let g=new Set([...a].filter(c=>!e.has(c))),E=new Set([...u].filter(c=>!i.has(c)));if(g.size===0&&E.size===0)return;let m=this.svg.node();if(!m)return;let p=document.createElementNS("http://www.w3.org/2000/svg","g");p.setAttribute("class","morph-exit-layer"),p.style.pointerEvents="none";let l=this.container.attr("transform");l&&p.setAttribute("transform",l),this.svgNodes&&this.svgNodes.each(function(c){g.has(c.id)&&p.appendChild(this.cloneNode(true));}),this.svgLinkGroups&&this.svgLinkGroups.each(function(c){let h=c.id,f=c.source?.id??"",d=c.target?.id??"";(E.has(h)||g.has(f)||g.has(d))&&p.appendChild(this.cloneNode(true));}),this.svgGroups&&this.svgGroups.each(function(c){c.id&&!n.groups.some(h=>h.id===c.id)&&p.appendChild(this.cloneNode(true));}),p.childElementCount!==0&&m.appendChild(p);}startMorphExitAnimation(){if(!this.svg)return;this.svg.selectAll(".morph-old-graph").remove();let n=this.svg.select(".morph-exit-layer");if(n.empty())return;let o=this.morphExitDurationMs;n.selectAll("g.link-group, g.inferredLinkGroup, g.alignmentLinkGroup").each(function(){let s=this.querySelector("path[data-link-id]");if(!s)return;let e=s.getTotalLength();!e||e<=0||(s.setAttribute("stroke-dasharray",String(e)),s.setAttribute("stroke-dashoffset","0"),ws.select(this).selectAll(".linklabel, .arrowhead").attr("opacity",0),ws.select(s).transition().duration(o).ease(ws.easeCubicIn).attr("stroke-dashoffset",e));}),n.selectAll("g.node, rect.group").transition().duration(o).ease(ws.easeCubicOut).attr("opacity",0),n.transition().duration(o).on("end",function(){ws.select(this).remove();});}hideEnteringElements(){let n=this.morphEnteringNodeIds,o=this.morphEnteringEdgeIds;n.size===0&&o.size===0||(this.svgNodes&&n.size>0&&this.svgNodes.filter(s=>n.has(s.id)).attr("opacity",0),this.svgLinkGroups&&(o.size>0||n.size>0)&&this.svgLinkGroups.filter(s=>{if(o.has(s.id))return true;let e=s.source?.id??"",i=s.target?.id??"";return n.has(e)||n.has(i)}).attr("opacity",0));}applyMorphEnterTransition(){let n=this.morphEnteringNodeIds,o=this.morphEnteringEdgeIds;if(!(!(n.size>0||o.size>0)&&this.morphEnteringNodeIds.size===0&&this.morphEnteringEdgeIds.size===0)){if(this.svgNodes&&n.size>0&&this.svgNodes.filter(e=>n.has(e.id)).attr("opacity",0).transition().delay(this.morphEnterDelayMs).duration(this.morphEnterDurationMs).ease(ws.easeCubicOut).attr("opacity",1),this.svgLinkGroups&&(o.size>0||n.size>0)&&this.svgLinkGroups.filter(e=>{if(o.has(e.id))return true;let i=e.source?.id??"",a=e.target?.id??"";return n.has(i)||n.has(a)}).each(function(){let e=this.querySelector("path[data-link-id]");if(!e)return;let i=e.getTotalLength();!i||i<=0||(ws.select(e).attr("stroke-dasharray",i).attr("stroke-dashoffset",i),ws.select(this).selectAll(".linklabel, .arrowhead").attr("opacity",0));}).attr("opacity",1).transition().delay(this.morphEnterDelayMs).duration(this.morphEnterDurationMs).ease(ws.easeCubicOut).tween("draw-in",function(){let e=this.querySelector("path[data-link-id]");if(!e)return ()=>{};let i=e.getTotalLength();if(!i||i<=0)return ()=>{};let a=ws.interpolateNumber(i,0);return u=>{e.setAttribute("stroke-dashoffset",String(a(u)));}}).on("end",function(){let e=this.querySelector("path[data-link-id]");e&&(e.removeAttribute("stroke-dasharray"),e.removeAttribute("stroke-dashoffset")),ws.select(this).selectAll(".linklabel, .arrowhead").attr("opacity",1);}),this.svgGroups&&this.svgGroupLabels){new Set((this.currentLayout?.groups||[]).map(i=>i.id).filter(Boolean));}this.morphEnteringNodeIds=new Set,this.morphEnteringEdgeIds=new Set;}}animateMorphSlide(){let n=this.morphOldPositions;if(!n||n.size===0||!this.currentLayout?.nodes){this.morphOldPositions=null;return}let o=this.currentLayout.nodes,s=this.morphSlideDurationMs;if(s<=0){this.morphOldPositions=null;return}let e=[];for(let u of o){let g=n.get(u.id);g&&e.push({node:u,oldX:g.x,oldY:g.y,finalX:u.x,finalY:u.y});}if(e.length===0){this.morphOldPositions=null;return}for(let u of e)u.node.x=u.oldX,u.node.y=u.oldY;this.updatePositions();let i=u=>1-Math.pow(1-u,3),a=performance.now();this.morphSlideTimer&&(this.morphSlideTimer.stop(),this.morphSlideTimer=null),this.morphSlideTimer=ws.timer(()=>{let u=performance.now()-a,g=Math.min(1,u/s),E=i(g);for(let m of e)m.node.x=m.oldX+(m.finalX-m.oldX)*E,m.node.y=m.oldY+(m.finalY-m.oldY)*E;if(this.updatePositions(),g>=1){this.morphSlideTimer.stop(),this.morphSlideTimer=null;for(let m of e)m.node.x=m.finalX,m.node.y=m.finalY;this.updatePositions(),this.layoutFormat!=="grid"&&this.routeEdges(),this.applyMorphEnterTransition(),this.morphOldPositions=null;}});}setTransitionMode(n){this.setAttribute("transition-mode",n);}setMorphSpeed(n){this.setAttribute("morph-speed",String(Math.max(0,Math.min(n,1))));}clear(){if(this.colaLayout){try{this.colaLayout.stop?.();}catch{}this.colaLayout.on?.("tick",null),this.colaLayout.on?.("end",null);}this.container&&this.container.selectAll("*").remove(),this.svg&&this.svg.selectAll(".morph-exit-layer").interrupt().remove(),this.morphSlideTimer&&(this.morphSlideTimer.stop(),this.morphSlideTimer=null),this.morphOldPositions=null,this.currentLayout=null,this.colaLayout=null,this.svgNodes=null,this.svgLinks=null,this.svgGroups=null,this.edgeRoutingCache.edgesBetweenNodes.clear(),this.edgeRoutingCache.alignmentEdges.clear(),this.dragStartPositions.clear(),this.hideLoading();}getNodePositions(){return this.currentLayout?.nodes?this.currentLayout.nodes.map(n=>({id:n.id,x:n.x,y:n.y})):[]}getCurrentTransform(){if(this.svg&&this.svg.node())try{let n=ws.zoomTransform(this.svg.node());return {k:n.k,x:n.x,y:n.y}}catch{return {k:1,x:0,y:0}}return {k:1,x:0,y:0}}getLayoutState(){return {positions:this.getNodePositions(),transform:this.getCurrentTransform()}}getLastSeedState(){return this.lastSeedState?{positions:this.lastSeedState.positions.map(n=>({...n})),transform:{...this.lastSeedState.transform}}:null}addToolbarControl(n){let o=this.shadowRoot?.querySelector("#graph-toolbar");o&&o.appendChild(n);}getToolbar(){return this.shadowRoot?.querySelector("#graph-toolbar")||null}renderGroups(n,o){if(!this.currentLayout.nodes||this.currentLayout.nodes.length===0){console.warn("Cannot render groups: nodes not available");return}this.svgGroups=this.setupGroups(n,this.currentLayout.nodes,o);}setupLinks(n,o){let s=this.container.selectAll(".link-group").data(n).enter().append("g").attr("class","link-group");return this.setupLinkPaths(s),this.setupLinkLabels(s),this.setupEdgeEndpointMarkers(s),s}setupLinkPaths(n){n.append("path").attr("class",o=>this.isAlignmentEdge(o)?"alignmentLink":this.isInferredEdge(o)?"inferredLink":"link").attr("data-link-id",o=>o.id||"").attr("stroke",o=>this.edgeStrokeColor(o)).attr("fill","none").attr("opacity",o=>this.isAlignmentEdge(o)?0:null).style("stroke-width",o=>this.isAlignmentEdge(o)?"0":o.weight!=null?`${o.weight}px`:null).attr("stroke-dasharray",o=>this.isAlignmentEdge(o)?null:this.getEdgeDasharray(o.style)).attr("marker-end",o=>this.isAlignmentEdge(o)?"none":"url(#end-arrow)").attr("marker-start",o=>this.isAlignmentEdge(o)||!o.bidirectional?"none":"url(#start-arrow)").on("click.inputmode",o=>{this.isInputModeActive&&!this.isAlignmentEdge(o)&&(ws.event.stopPropagation(),this.editEdgeLabel(o).catch(s=>{console.error("Error editing edge label:",s);}));}).style("cursor",()=>this.isInputModeActive?"pointer":"default"),n.filter(o=>!!o.highlight&&!this.isAlignmentEdge(o)).insert("path",":first-child").attr("class","highlight-underlay").attr("stroke",o=>o.highlight).attr("fill","none").attr("stroke-linecap","round").style("stroke-width",o=>`${(o.weight!=null?o.weight:2)+8}px`).attr("opacity",.45).style("pointer-events","none");}getEdgeDasharray(n){if(!n)return null;switch(n.toLowerCase()){case "dotted":return "1,4";case "dashed":return "6,4";case "solid":return null;default:return null}}setupLinkLabels(n){n.filter(o=>!this.isAlignmentEdge(o)&&(this.isInferredEdge(o)||o.showLabel!==false)).append("text").attr("class","linklabel").attr("text-anchor","middle").attr("dominant-baseline","middle").attr("font-family",this.getFontFamily()).attr("pointer-events","none").text(o=>o.label||o.relName||"");}setupEdgeEndpointMarkers(n){n.filter(o=>!this.isAlignmentEdge(o)).append("circle").attr("class","edge-endpoint-marker target-marker").attr("r",8).attr("fill","#007bff").attr("stroke","white").attr("stroke-width",2).attr("opacity",0).attr("cursor","move").style("pointer-events","none").call(ws.drag().on("start",o=>this.startEdgeEndpointDrag(o,"target")).on("drag",o=>this.dragEdgeEndpoint(o,"target")).on("end",o=>this.endEdgeEndpointDrag(o,"target"))),n.filter(o=>!this.isAlignmentEdge(o)).append("circle").attr("class","edge-endpoint-marker source-marker").attr("r",8).attr("fill","#28a745").attr("stroke","white").attr("stroke-width",2).attr("opacity",0).attr("cursor","move").style("pointer-events","none").call(ws.drag().on("start",o=>this.startEdgeEndpointDrag(o,"source")).on("drag",o=>this.dragEdgeEndpoint(o,"source")).on("end",o=>this.endEdgeEndpointDrag(o,"source")));}startEdgeEndpointDrag(n,o){ws.event.sourceEvent.stopPropagation(),this.edgeDragState.isDragging=true,this.edgeDragState.edge=n,this.edgeDragState.endpoint=o,console.log(`\u{1F535} Started dragging ${o} endpoint of edge:`,n.id);}dragEdgeEndpoint(n,o){if(!this.edgeDragState.isDragging)return;let[s,e]=ws.mouse(this.container.node()),i=o==="target"?".target-marker":".source-marker";this.container.selectAll(".link-group").filter(a=>a.id===n.id).select(i).attr("cx",s).attr("cy",e);}async endEdgeEndpointDrag(n,o){if(!this.edgeDragState.isDragging)return;let[s,e]=ws.mouse(this.container.node()),i=this.findNodeAtPosition(s,e);i?(console.log(`\u{1F517} Reconnecting ${o} to node:`,i.id),await this.reconnectEdge(n,o,i)):(console.log("\u{1F5D1}\uFE0F No node found - deleting edge:",n.id),await this.deleteEdge(n)),this.edgeDragState={isDragging:false,edge:null,endpoint:null,dragMarker:null},this.rerenderGraph();}findNodeAtPosition(n,o){if(!this.currentLayout?.nodes)return null;for(let s of this.currentLayout.nodes){let e=(s.visualWidth??s.width??0)/2,i=(s.visualHeight??s.height??0)/2;if(n>=s.x-e&&n<=s.x+e&&o>=s.y-i&&o<=s.y+i)return s}return null}async reconnectEdge(n,o,s){let e=this.getNodeFromEdge(n,"source"),i=this.getNodeFromEdge(n,"target");if(!e||!i){console.error("Could not find source or target node");return}let a,u;if(o==="source"?(a=s,u=i):(a=e,u=s),a.id===e.id&&u.id===i.id){console.log("\u23ED\uFE0F Edge already connected to this node, no change needed");return}let g=n.relName||n.label||"";if(!g.trim()){console.warn("Edge has no relation name, cannot reconnect");return}let E={atoms:[e.id,i.id],types:[e.type||"untyped",i.type||"untyped"]},m={atoms:[a.id,u.id],types:[a.type||"untyped",u.type||"untyped"]};console.log(`Reconnecting edge: ${g} from ${e.id}->${i.id} to ${a.id}->${u.id}`);let p=new CustomEvent("edge-reconnection-requested",{detail:{relationId:g,oldTuple:E,newTuple:m,oldSourceNodeId:e.id,oldTargetNodeId:i.id,newSourceNodeId:a.id,newTargetNodeId:u.id},bubbles:true});this.dispatchEvent(p);let l=this.currentLayout.nodes.findIndex(h=>h.id===a.id),c=this.currentLayout.nodes.findIndex(h=>h.id===u.id);l!==-1&&c!==-1&&(n.source=l,n.target=c);}async deleteEdge(n){let o=this.getNodeFromEdge(n,"source"),s=this.getNodeFromEdge(n,"target");if(!o||!s){console.error("Could not find source or target node for edge deletion");return}let e=n.relName||n.label||"";if(!e.trim()){console.warn("Edge has no relation name, cannot delete from data instance"),this.removeEdgeFromLayout(n);return}let i=[];if(n.groupId&&n.keyNodeId&&this.currentLayout){let u=(this.currentLayout.groups||[]).find(g=>g.id===n.groupId);if(u){let g=this.currentLayout.groups||[],E=this.collectGroupNodeIndices(u,g),m=this.currentLayout.nodes.find(p=>p.id===n.keyNodeId);for(let p of E){let l=this.currentLayout.nodes[p];l&&m&&i.push({atoms:[m.id,l.id],types:[m.type||"untyped",l.type||"untyped"]});}}}i.length===0&&i.push({atoms:[o.id,s.id],types:[o.type||"untyped",s.type||"untyped"]}),console.log(`\u{1F5D1}\uFE0F Deleting edge: ${e} (${i.length} tuple(s))`);let a=new CustomEvent("edge-modification-requested",{detail:{oldRelationId:e,newRelationId:"",sourceNodeId:o.id,targetNodeId:s.id,tuples:i},bubbles:true});this.dispatchEvent(a),this.removeEdgeFromLayout(n);}removeEdgeFromLayout(n){if(!this.currentLayout?.links)return;let o=this.currentLayout.links.findIndex(s=>s.id===n.id);o!==-1&&(this.currentLayout.links.splice(o,1),console.log(`\u2705 Edge removed from layout: ${n.id}`));}setupGroups(n,o,s){let e=this.setupGroupRectangles(n,o,s);return this.svgGroupLabels=this.setupGroupLabels(n,s),e}setupGroupRectangles(n,o,s){return this.container.selectAll(".group").data(n).enter().append("rect").attr("class",i=>this.isDisconnectedGroup(i)?"disconnectedNode":this.isErrorGroup(i)?"error-group":"group").attr("rx",Er.GROUP_BORDER_RADIUS).attr("ry",Er.GROUP_BORDER_RADIUS).style("fill",i=>this.isDisconnectedGroup(i)?"transparent":o[i.keyNode]?.color||"#cccccc").attr("fill-opacity",Er.GROUP_FILL_OPACITY).attr("stroke",i=>this.isDisconnectedGroup(i)?"none":o[i.keyNode]?.color||"#999999").attr("stroke-width",i=>this.isDisconnectedGroup(i)||this.isErrorGroup(i)?1:Er.GROUP_STROKE_WIDTH).attr("stroke-opacity",i=>this.isDisconnectedGroup(i)||this.isErrorGroup(i)?1:Er.GROUP_STROKE_OPACITY).call(s.drag)}resolveGroupLeafToNodeIndex(n){if(typeof n=="number"&&Number.isInteger(n)&&n>=0)return n;if(n&&typeof n=="object"){let o=n;if(typeof o.index=="number"&&Number.isInteger(o.index)&&o.index>=0)return o.index;if(typeof o.id=="string"&&this.currentLayout?.nodes){let s=this.currentLayout.nodes.findIndex(e=>e.id===o.id);return s>=0?s:null}}return null}collectGroupNodeIndices(n,o){let s=new Set,e=new Set,i=(u,g)=>{if(u){if(typeof g=="number"){if(e.has(g))return;e.add(g);}Array.isArray(u.leaves)&&u.leaves.forEach(E=>{let m=this.resolveGroupLeafToNodeIndex(E);m!==null&&s.add(m);}),Array.isArray(u.groups)&&u.groups.forEach(E=>{typeof E=="number"&&Number.isInteger(E)&&E>=0&&E<o.length&&i(o[E],E);});}},a=o.indexOf(n);return i(n,a>=0?a:void 0),Array.from(s)}getNodeMainLabelFontSize(n){return 14}calculateGroupLabelFontSize(n,o){if(!this.currentLayout?.nodes?.length)return Er.DEFAULT_FONT_SIZE;let s=this.collectGroupNodeIndices(n,o);if(s.length===0)return Er.DEFAULT_FONT_SIZE;let e=s.map(u=>this.currentLayout.nodes[u]).filter(u=>!!u).map(u=>this.getNodeMainLabelFontSize(u));if(e.length===0)return Er.DEFAULT_FONT_SIZE;let i=e.reduce((u,g)=>u+g,0)/e.length,a=Math.max(Er.MIN_FONT_SIZE,Math.min(i,Er.MAX_FONT_SIZE));return Math.round(a*10)/10}setupGroupLabels(n,o){let s=this.currentLayout?.nodes||[];return this.svgGroupLabelBgs=this.container.selectAll(".groupLabelBg").data(n).enter().append("rect").attr("class","groupLabelBg").attr("rx",6).attr("ry",6).attr("fill",this.getCanvasBackground()).attr("fill-opacity",.92).attr("stroke",e=>s[e.keyNode]?.color||"#999999").attr("stroke-width",1).attr("stroke-opacity",.3).attr("pointer-events","none").style("display","none"),this.container.selectAll(".groupLabel").data(n).enter().append("text").attr("class","groupLabel").attr("text-anchor","middle").attr("dominant-baseline","hanging").attr("font-family",this.getFontFamily()).attr("font-size",e=>{let i=this.calculateGroupLabelFontSize(e,n);return e._groupLabelFontSize=i,`${i}px`}).attr("font-weight","bold").attr("fill","#333").attr("pointer-events","none").text(e=>e.showLabel||false?(e.padding&&(e.padding=Math.max(e.padding,Er.GROUP_LABEL_PADDING)),e.name||""):"").call(o.drag)}renderLinks(n,o){this.edgeRoutingCache.alignmentEdges.clear(),this.svgLinkGroups=this.setupLinks(n,o);}setupNodes(n,o){let s=o.drag();this.setupNodeDragHandlers(s);let e=this.container.selectAll(".node").data(n).enter().append("g").attr("class",i=>{let a=this.isErrorNode(i)?"error-node":"node";return this.isErrorNode(i)&&this.isSmallNode(i)?a+" small-error-node":a}).call(s).on("mousedown.inputmode",i=>{this.isInputModeActive&&(ws.event.stopPropagation(),this.startEdgeCreation(i));}).on("mouseup.inputmode",i=>{this.isInputModeActive&&this.edgeCreationState.isCreating&&(ws.event.stopPropagation(),this.finishEdgeCreation(i).catch(a=>{console.error("Error finishing edge creation:",a);}));}).on("mouseover",function(i){ws.select(this).append("title").attr("class","node-tooltip").text(`ID: ${i.id}`);}).on("mouseout",function(){ws.select(this).select("title.node-tooltip").remove();});return this.setupNodeRectangles(e),this.setupNodeIcons(e),this.setupMostSpecificTypeLabels(e),this.setupNodeLabels(e),e}setupNodeRectangles(n){n.append("rect").attr("width",o=>o.visualWidth??o.width).attr("height",o=>o.visualHeight??o.height).attr("x",o=>-(o.visualWidth??o.width)/2).attr("y",o=>-(o.visualHeight??o.height)/2).attr("stroke",o=>this.nodeBorderColor(o)).attr("rx",Er.NODE_BORDER_RADIUS).attr("ry",Er.NODE_BORDER_RADIUS).attr("stroke-width",Er.NODE_STROKE_WIDTH).attr("fill",o=>this.nodeFillColor(o));}setupNodeIcons(n){n.filter(o=>o.icon).append("image").attr("xlink:href",o=>o.icon).attr("width",o=>{let s=o.visualWidth??o.width;return o.showLabels?s*Er.SMALL_IMG_SCALE_FACTOR:s}).attr("height",o=>{let s=o.visualHeight??o.height;return o.showLabels?s*Er.SMALL_IMG_SCALE_FACTOR:s}).attr("x",o=>{let s=o.visualWidth??o.width;return o.showLabels?o.x+s-s*Er.SMALL_IMG_SCALE_FACTOR:o.x-s/2}).attr("y",o=>{let s=o.visualHeight??o.height;return o.y-s/2}).append("title").text(o=>o.label||o.name||o.id||"Node").on("error",function(o,s){ws.select(this).attr("xlink:href","img/default.png"),console.error(`Failed to load icon for node ${s.id}: ${s.icon}`);});}setupMostSpecificTypeLabels(n){n.append("text").attr("class","mostSpecificTypeLabel").style("fill",o=>this.nodeTypeLabelColor(o)).text(o=>o.mostSpecificType||"");}getTextMeasurementContext(){return this.textMeasurementCanvas||(this.textMeasurementCanvas=document.createElement("canvas")),this.textMeasurementCanvas.getContext("2d")}measureTextWidth(n,o,s){let e=this.getTextMeasurementContext();return e.font=`${o}px ${s??this.getFontFamily()}`,e.measureText(n).width}setupNodeLabels(n){let o=11*1.35;n.append("text").attr("class","label").attr("text-anchor","middle").attr("dominant-baseline","middle").attr("font-family",this.getFontFamily()).attr("fill","black").each((s,e,i)=>{if(this.isHiddenNode(s)||!s.showLabels)return;let a=ws.select(i[e]),u=s.label||s.name||s.id||"Node",g=Object.entries(s.attributes||{}).sort(([l],[c])=>l.localeCompare(c)),E=Object.entries(s.labels||{}),m=E.length+g.length,p=m>0?-m*o*.5:0;s._labelVerticalOffset=p,s._labelLineHeight=o,a.attr("font-size",`${14}px`),a.append("tspan").attr("x",0).attr("dy",`${p}px`).attr("class","main-label-tspan").style("font-weight","bold").style("font-size",`${14}px`).text(u);for(let[,l]of E){let c=Array.isArray(l)?l.join(", "):String(l);a.append("tspan").attr("x",0).attr("dy",`${o}px`).style("font-size",`${11}px`).style("font-style","italic").text(c);}for(let[l,c]of g)a.append("tspan").attr("x",0).attr("dy",`${o}px`).style("font-size",`${11}px`).text(`${l}: ${c}`);});}renderNodes(n,o){this.svgNodes=this.setupNodes(n,o);}resolveGroupEdgeEndpoints(n){if(!n.groupId)return {source:n.source,target:n.target};let o=this.currentLayout?.groups||[],s=o.find(u=>u.id===n.groupId);if(this._groupEdgeDebugLogged||(this._groupEdgeDebugLogged=new Set),this._groupEdgeDebugLogged.has(n.id)||(this._groupEdgeDebugLogged.add(n.id),console.log("[groupEdge DEBUG]",{edgeId:n.id,groupId:n.groupId,keyNodeId:n.keyNodeId,sourceId:n.source?.id,targetId:n.target?.id,sourceIsInt:typeof n.source=="number",targetIsInt:typeof n.target=="number",groupFound:!!s,groupId_on_group:s?.id,availableGroupIds:o.map(u=>u.id)})),!s)return {source:n.source,target:n.target};let e=n.source,i=n.target;n.source?.id===n.keyNodeId?i=s:n.target?.id===n.keyNodeId?e=s:console.warn("[groupEdge] keyNodeId matched neither side",{keyNodeId:n.keyNodeId,sourceId:n.source?.id,targetId:n.target?.id});let a=e===s?n.source:i===s?n.target:null;if(a&&!s.bounds&&a.x!=null&&q0?.Rectangle){let u=(a.visualWidth??a.width??50)/2,g=(a.visualHeight??a.height??30)/2;s.bounds=new q0.Rectangle(a.x-u,a.x+u,a.y-g,a.y+g);}return {source:e,target:i}}updateNodePositionsOnly(){this.svgNodes&&(this.svgNodes.select("rect").attr("x",n=>n.x!=null?n.x-(n.visualWidth??n.width)/2:0).attr("y",n=>n.y!=null?n.y-(n.visualHeight??n.height)/2:0).attr("width",n=>n.visualWidth??n.width).attr("height",n=>n.visualHeight??n.height),this.svgNodes.select("image").attr("x",n=>{if(n.x==null)return 0;let o=n.visualWidth??n.width;return n.showLabels?n.x+o/2-o*Er.SMALL_IMG_SCALE_FACTOR:n.x-o/2}).attr("y",n=>{if(n.y==null)return 0;let o=n.visualHeight??n.height;return n.y-o/2}),this.svgNodes.select(".mostSpecificTypeLabel").attr("x",n=>n.x!=null?n.x-(n.visualWidth??n.width??0)/2+5:0).attr("y",n=>n.y!=null?n.y-(n.visualHeight??n.height??0)/2+10:0),this.svgNodes.select(".label").attr("x",n=>n.x??0).attr("y",n=>n.y??0).each((n,o,s)=>{if(n.x==null)return;let e=n._labelVerticalOffset||0,i=n._labelLineHeight||12;ws.select(s[o]).selectAll("tspan").attr("x",n.x).attr("dy",(a,u)=>u===0?`${e}px`:`${i}px`);}));}updatePositions(){this.svgGroups.attr("x",n=>n.bounds.x).attr("y",n=>n.bounds.y).attr("width",n=>n.bounds.width()).attr("height",n=>n.bounds.height()).lower(),this.svgNodes.select("rect").each(n=>{n.bounds&&(n.innerBounds=n.bounds.inflate(-1));}).attr("x",n=>n.x-(n.visualWidth??n.width)/2).attr("y",n=>n.y-(n.visualHeight??n.height)/2).attr("width",n=>n.visualWidth??n.width).attr("height",n=>n.visualHeight??n.height),this.svgNodes.select("image").attr("x",n=>{let o=n.visualWidth??n.width;return n.showLabels?n.x+o/2-o*Er.SMALL_IMG_SCALE_FACTOR:n.x-o/2}).attr("y",n=>{let o=n.visualHeight??n.height;return n.showLabels,n.y-o/2}),this.svgNodes.select(".mostSpecificTypeLabel").attr("x",n=>n.x-(n.visualWidth??n.width??0)/2+5).attr("y",n=>n.y-(n.visualHeight??n.height??0)/2+10).raise(),this.svgNodes.select(".label").attr("x",n=>n.x).attr("y",n=>n.y).each((n,o,s)=>{let e=n._labelVerticalOffset||0,i=n._labelLineHeight||12;ws.select(s[o]).selectAll("tspan").attr("x",n.x).attr("dy",(a,u)=>u===0?`${e}px`:`${i}px`);}).raise(),this.svgLinkGroups.select("path[data-link-id]").attr("d",n=>{let{source:o,target:s}=this.resolveGroupEdgeEndpoints(n),e=this.getStableEdgePath(o,s,n);return this.lineFunction(e)}).attr("marker-end",n=>this.isAlignmentEdge(n)?"none":"url(#end-arrow)").attr("marker-start",n=>this.isAlignmentEdge(n)||!n.bidirectional?"none":"url(#start-arrow)").raise(),this.svgLinkGroups.select("path.highlight-underlay").attr("d",function(){return this.parentNode?.querySelector("path[data-link-id]")?.getAttribute("d")??null}),this.svgLinkGroups.select(".linklabel").attr("x",n=>{let o=this.shadowRoot?.querySelector(`path[data-link-id="${n.id}"]`);return o?this.calculateNewPosition(o,"x"):(n.source.x+n.target.x)/2}).attr("y",n=>{let o=this.shadowRoot?.querySelector(`path[data-link-id="${n.id}"]`);return o?this.calculateNewPosition(o,"y"):(n.source.y+n.target.y)/2}).style("font-size",()=>{let n=this.getCurrentZoomScale(),o=12,s=n<1?o/Math.sqrt(n):o;return `${Math.min(s,16)}px`}).raise(),this.updateEdgeEndpointMarkers(),this.svgGroupLabels.attr("x",n=>n.bounds?n.bounds.x+n.bounds.width()/2:0).attr("y",n=>{if(!n.bounds)return 0;let o=n._groupLabelFontSize||Er.DEFAULT_FONT_SIZE;return n.bounds.y+Er.GROUP_LABEL_PILL_OFFSET_Y+Math.max(4,o*.35)}).attr("text-anchor","middle").each(function(n){let o=this;if(o.textContent&&n.bounds){let s=o.getBBox();n._labelBBox={x:s.x,y:s.y,width:s.width,height:s.height};}else n._labelBBox=null;}),this.svgGroupLabelBgs&&(this.svgGroupLabelBgs.style("display",n=>n._labelBBox?null:"none").attr("x",n=>n._labelBBox?n._labelBBox.x-Er.GROUP_LABEL_PILL_PADDING_X:0).attr("y",n=>n._labelBBox?n._labelBBox.y-Er.GROUP_LABEL_PILL_PADDING_Y:0).attr("width",n=>n._labelBBox?n._labelBBox.width+Er.GROUP_LABEL_PILL_PADDING_X*2:0).attr("height",n=>n._labelBBox?n._labelBBox.height+Er.GROUP_LABEL_PILL_PADDING_Y*2:0),this.svgGroupLabelBgs.raise()),this.svgGroupLabels.raise(),this.svgLinkGroups.selectAll("marker").raise(),this.svgLinkGroups.selectAll(".linklabel").raise(),this.svgNodes.selectAll(".error-node").raise();}updateEdgeEndpointMarkers(){!this.svgLinkGroups||!this.isConnected||(this.svgLinkGroups.select(".target-marker").attr("cx",n=>{let o=this.getEdgePathPoint(n.id,"end");return o?o.x:n.target.x||0}).attr("cy",n=>{let o=this.getEdgePathPoint(n.id,"end");return o?o.y:n.target.y||0}).attr("opacity",this.isInputModeActive?.8:0).style("pointer-events",this.isInputModeActive?"all":"none").raise(),this.svgLinkGroups.select(".source-marker").attr("cx",n=>{let o=this.getEdgePathPoint(n.id,"start");return o?o.x:n.source.x||0}).attr("cy",n=>{let o=this.getEdgePathPoint(n.id,"start");return o?o.y:n.source.y||0}).attr("opacity",this.isInputModeActive?.8:0).style("pointer-events",this.isInputModeActive?"all":"none").raise());}getEdgePathPoint(n,o){let s=this.shadowRoot?.querySelector(`path[data-link-id="${n}"]`);if(!s)return null;try{return s.getPointAtLength(o==="end"?s.getTotalLength():0)}catch{return null}}gridUpdatePositions(){this.ensureNodeBounds(true);let n=this.container.selectAll(".node"),o=this.container.selectAll(".mostSpecificTypeLabel"),s=this.container.selectAll(".label"),e=this.container.selectAll(".group"),i=this.container.selectAll(".groupLabel");n.select("rect").each(function(g){g.innerBounds=g.bounds.inflate(-1);}).attr("x",function(g){return g.bounds.x}).attr("y",function(g){return g.bounds.y}).attr("width",function(g){return g.bounds.width()}).attr("height",function(g){return g.bounds.height()}),n.select("image").attr("x",function(g){let E=g.visualWidth??g.width;return g.showLabels?g.x+E/2-E*Er.SMALL_IMG_SCALE_FACTOR:g.bounds.x}).attr("y",function(g){let E=g.visualHeight??g.height;return g.showLabels?g.y-E/2:g.bounds.y}),o.attr("x",function(g){return g.bounds.x+5}).attr("y",function(g){return g.bounds.y+10}).raise(),s.attr("x",g=>g.x).attr("y",g=>g.y).each(function(g){var E=0;ws.select(this).selectAll("tspan").attr("x",g.x).attr("dy",function(){return E+=1,E===1?"0em":"1em"});}).raise(),e.attr("x",function(g){return g.bounds.x}).attr("y",function(g){return g.bounds.y}).attr("width",function(g){return g.bounds.width()}).attr("height",function(g){return g.bounds.height()}).lower(),i.attr("x",function(g){return g.bounds.x+g.bounds.width()/2}).attr("y",function(g){let E=g._groupLabelFontSize||Er.DEFAULT_FONT_SIZE;return g.bounds.y+Er.GROUP_LABEL_PILL_OFFSET_Y+Math.max(4,E*.35)}).attr("text-anchor","middle").each(function(g){let E=this;if(E.textContent&&g.bounds){let m=E.getBBox();g._labelBBox={x:m.x,y:m.y,width:m.width,height:m.height};}else g._labelBBox=null;}),this.container.selectAll(".groupLabelBg").style("display",g=>g._labelBBox?null:"none").attr("x",g=>g._labelBBox?g._labelBBox.x-Er.GROUP_LABEL_PILL_PADDING_X:0).attr("y",g=>g._labelBBox?g._labelBBox.y-Er.GROUP_LABEL_PILL_PADDING_Y:0).attr("width",g=>g._labelBBox?g._labelBBox.width+Er.GROUP_LABEL_PILL_PADDING_X*2:0).attr("height",g=>g._labelBBox?g._labelBBox.height+Er.GROUP_LABEL_PILL_PADDING_Y*2:0).raise(),i.raise();let u=this.container.selectAll(".link-group");u.select("path").attr("d",g=>{if(g.source?.id===g.target?.id){let d=this.createGridSelfLoopRoute(g);return this.gridLineFunction(d)}let{source:E,target:m}=this.resolveGroupEdgeEndpoints(g),p=d=>d.bounds?typeof d.bounds.cx=="function"?{x:d.bounds.cx(),y:d.bounds.cy()}:{x:(d.bounds.x+d.bounds.X)/2,y:(d.bounds.y+d.bounds.Y)/2}:{x:d.x??0,y:d.y??0},l=p(E),c=p(m),h=c.x-l.x,f=c.y-l.y;if(Math.abs(h)>Math.abs(f)){let d=l.x+h/2;return this.gridLineFunction([{x:l.x,y:l.y},{x:d,y:l.y},{x:d,y:c.y},{x:c.x,y:c.y}])}else {let d=l.y+f/2;return this.gridLineFunction([{x:l.x,y:l.y},{x:l.x,y:d},{x:c.x,y:d},{x:c.x,y:c.y}])}}),u.select("text.linklabel").attr("x",g=>{let E=this.shadowRoot?.querySelector(`path[data-link-id="${g.id}"]`);if(E){let l=E.getTotalLength();return E.getPointAtLength(l/2).x}let m=g.source?.x??g.source?.bounds?.cx()??0,p=g.target?.x??g.target?.bounds?.cx()??0;return (m+p)/2}).attr("y",g=>{let E=this.shadowRoot?.querySelector(`path[data-link-id="${g.id}"]`);if(E){let l=E.getTotalLength();return E.getPointAtLength(l/2).y}let m=g.source?.y??g.source?.bounds?.cy()??0,p=g.target?.y??g.target?.bounds?.cy()??0;return (m+p)/2}).raise();}routeEdges(){try{this.ensureNodeBounds(!0),typeof this.colaLayout?.prepareEdgeRouting=="function"&&this.colaLayout.prepareEdgeRouting(Er.VIEWBOX_PADDING/Er.EDGE_ROUTE_MARGIN_DIVISOR),this.buildEdgeRoutingCaches(),this.sortEdgePortsByAngle(),this.useTautRouter&&this.buildRouterObstacleCache(),this.computeAllRoutes(),this.optimizeCrossings(),this.applyRoutesToSVG(),this.updateLinkLabelsAfterRouting(),this.fitViewportToContent();}catch(n){console.error("Error in edge routing:",n),this.showError(`Edge routing failed: ${n.message}`);}finally{this.routerObstacleCache=null;}}ensureNodeBounds(n=false){if(!(!this.currentLayout?.nodes||!q0?.Rectangle))for(let o of this.currentLayout.nodes){if(!n&&o.bounds&&typeof o.bounds.rayIntersection=="function"){let E=o.bounds.cx(),m=o.bounds.cy(),p=1;if(Math.abs(E-(o.x||0))<p&&Math.abs(m-(o.y||0))<p)continue}let s=(o.visualWidth??o.width??50)/2,e=(o.visualHeight??o.height??30)/2,i=(o.x||0)-s,a=(o.x||0)+s,u=(o.y||0)-e,g=(o.y||0)+e;o.bounds=new q0.Rectangle(i,a,u,g),o.innerBounds=o.bounds.inflate(-1);}}buildEdgeRoutingCaches(){this.edgeRoutingCache.edgesBetweenNodes.clear(),this.edgeRoutingCache.alignmentEdges.clear(),this.edgeRoutingCache.nodeEdgesBySide.clear(),this.currentLayout?.links&&(this.currentLayout.links.forEach(n=>{n.id?.startsWith("_alignment_")&&this.edgeRoutingCache.alignmentEdges.add(n.id);}),this.currentLayout.links.forEach(n=>{if(this.isAlignmentEdge(n))return;let o=n.source.id,s=n.target.id,e=this.getNodePairKey(o,s);if(this.edgeRoutingCache.edgesBetweenNodes.has(e)||this.edgeRoutingCache.edgesBetweenNodes.set(e,[]),this.edgeRoutingCache.edgesBetweenNodes.get(e).push(n),o===s||n.id?.startsWith("_g_"))return;let i=n.source.x||0,a=n.source.y||0,u=n.target.x||0,g=n.target.y||0,E=u-i,m=g-a,p=Math.atan2(m,E),l=this.getDominantDirection(p),c=this.getDominantDirection(p+Math.PI),h=v=>v==="up"?"top":v==="down"?"bottom":v==="left"||v==="right"?v:null,f=h(l),d=h(c);f&&(this.edgeRoutingCache.nodeEdgesBySide.has(o)||this.edgeRoutingCache.nodeEdgesBySide.set(o,{top:[],bottom:[],left:[],right:[]}),this.edgeRoutingCache.nodeEdgesBySide.get(o)[f].push({edge:n,role:"source",remoteX:u,remoteY:g})),d&&(this.edgeRoutingCache.nodeEdgesBySide.has(s)||this.edgeRoutingCache.nodeEdgesBySide.set(s,{top:[],bottom:[],left:[],right:[]}),this.edgeRoutingCache.nodeEdgesBySide.get(s)[d].push({edge:n,role:"target",remoteX:i,remoteY:a}));}));}sortEdgePortsByAngle(){let n=(o,s)=>{let e=String(o.edge?.id??""),i=String(s.edge?.id??"");return e<i?-1:e>i?1:0};for(let[,o]of this.edgeRoutingCache.nodeEdgesBySide){for(let s of ["top","bottom"]){let e=o[s];if(!(e.length<=1)){e.sort((i,a)=>i.remoteX-a.remoteX||n(i,a));for(let i=0;i<e.length;i++){let a=e[i];a.role==="source"?(a.edge._sourcePortIndex=i,a.edge._sourcePortCount=e.length):(a.edge._targetPortIndex=i,a.edge._targetPortCount=e.length);}}}for(let s of ["left","right"]){let e=o[s];if(!(e.length<=1)){e.sort((i,a)=>i.remoteY-a.remoteY||n(i,a));for(let i=0;i<e.length;i++){let a=e[i];a.role==="source"?(a.edge._sourcePortIndex=i,a.edge._sourcePortCount=e.length):(a.edge._targetPortIndex=i,a.edge._targetPortCount=e.length);}}}}}getNodePairKey(n,o){return n<o?`${n}:${o}`:`${o}:${n}`}route(n=[],o=[],s,e){n.forEach(a=>{let u=a.bounds||a.innerBounds||this.createFallbackBounds(a);a.routerNode={name:a.name,bounds:u};}),o.forEach(a=>{a.bounds||console.warn("Grid routing group missing bounds; routing may be degraded.",a),a.routerNode={bounds:a.bounds?.inflate(-e)??a.bounds,children:(typeof a.groups<"u"?a.groups.map(u=>n.length+u.id):[]).concat(typeof a.leaves<"u"?a.leaves.map(u=>u.index):[])};});let i=n.concat(o).map((a,u)=>a.routerNode?(a.routerNode.id=u,a.routerNode):null).filter(Boolean);return new q0.GridRouter(i,{getChildren:a=>a.children,getBounds:a=>a.bounds},s-e)}gridify(n,o,s){if(this.isGridifyingInProgress){console.warn("[gridify] Already in progress, skipping re-entrant call");return}this.isGridifyingInProgress=true;try{this.gridifyInternal(n,o,s);}catch(e){console.log("Error routing edges in GridRouter"),console.error(e);try{this.fallbackGridRouting(this.currentLayout?.links??[]);}catch(a){console.error("Fallback grid routing also failed:",a);}let i=document.getElementById("runtime_messages");if(i){let a=document.createElement("div");a.className="alert alert-danger alert-dismissible fade show",a.setAttribute("role","alert"),a.innerHTML="Runtime (WebCola) error when gridifying edges. You may have to click and drag these nodes slightly to un-stick layout.",i.querySelectorAll(".alert").forEach(g=>{g.innerHTML===a.innerHTML&&g.remove();}),i.appendChild(a);}}finally{this.isGridifyingInProgress=false;}}gridifyInternal(n,o,s){let e=this.currentLayout?.nodes??[],i=this.currentLayout?.groups??[],a=this.currentLayout?.links??[];if(e.length===0){console.warn("No nodes available for GridRouter; skipping gridify.");return}if(a.length===0){console.warn("No edges to route in GridRouter");return}console.log("[gridify] Node positions BEFORE ensureNodeBounds:"),e.slice(0,3).forEach(f=>{console.log(` ${f.id}: x=${f.x?.toFixed(2)}, y=${f.y?.toFixed(2)}, bounds.cx=${f.bounds?.cx?.()?.toFixed(2)}, bounds.x=${f.bounds?.x?.toFixed(2)}`);}),this.ensureNodeBounds(true);let u=e.filter(f=>!Number.isFinite(f.x)||!Number.isFinite(f.y));if(u.length>0){console.warn("[gridify] Found nodes with invalid positions, falling back to default routing:",u.map(f=>({id:f.id,x:f.x,y:f.y}))),this.fallbackGridRouting(a);return}console.log("[gridify] Node positions AFTER ensureNodeBounds:"),e.slice(0,3).forEach(f=>{console.log(` ${f.id}: x=${f.x?.toFixed(2)}, y=${f.y?.toFixed(2)}, bounds.cx=${f.bounds?.cx?.()?.toFixed(2)}, bounds.x=${f.bounds?.x?.toFixed(2)}`);}),this.buildEdgeRoutingCaches(),this.sortEdgePortsByAngle();let g=this.route(e,i,o,s),E=[],m=a.filter(f=>{let d=f?.source?.routerNode&&f?.target?.routerNode,v=f?.source?.id===f?.target?.id;return d&&!v}),p=a.filter(f=>f?.source?.id===f?.target?.id);console.log("[gridify] Total edges:",a.length,"Routable:",m.length,"Self-loops:",p.length),m.length+p.length!==a.length&&a.filter(d=>(!d?.source?.routerNode||!d?.target?.routerNode)&&d?.source?.id!==d?.target?.id).forEach(d=>{console.warn("[gridify] Unroutable edge:",d.id,"source routerNode:",!!d?.source?.routerNode,"target routerNode:",!!d?.target?.routerNode,"source:",d?.source?.id,"x:",d?.source?.x,"y:",d?.source?.y,"target:",d?.target?.id,"x:",d?.target?.x,"y:",d?.target?.y);}),E=g.routeEdges(m,n,function(f){return f.source.routerNode.id},function(f){return f.target.routerNode.id});let l=new Map,c=m.length<=Er.CROSSING_OPTIMIZATION_EDGE_THRESHOLD;m.forEach((f,d)=>{let v=E[d];if(f?.id&&v){let _=this.adjustGridRouteForEdge(f,v);c&&(_=this.flattenGridRouteBends(_,e,f)),l.set(f.id,_);}}),console.log("[gridify] Routes generated:",l.size,"out of",m.length),this.container.selectAll(".link-group").data(a,f=>f.id??f).select("path").attr("d",f=>{if(f.source?.id===f.target?.id){let M=this.createGridSelfLoopRoute(f);return this.gridLineFunction(M)}let d=l.get(f.id);if(!d){let M=f.source?.x??f.source?.bounds?.cx()??0,k=f.source?.y??f.source?.bounds?.cy()??0,B=f.target?.x??f.target?.bounds?.cx()??0,F=f.target?.y??f.target?.bounds?.cy()??0;console.log("[gridify] Fallback path for edge:",f.id,"from",f.source?.id,"(",M,",",k,")","to",f.target?.id,"(",B,",",F,")");let Q=B-M,R=F-k;if(Math.abs(Q)>Math.abs(R)){let $=M+Q/2;return this.gridLineFunction([{x:M,y:k},{x:$,y:k},{x:$,y:F},{x:B,y:F}])}else {let $=k+R/2;return this.gridLineFunction([{x:M,y:k},{x:M,y:$},{x:B,y:$},{x:B,y:F}])}}let b=q0.GridRouter.getRoutePath(d,5,3,7);return this.adjustGridRouteForArrowPositioning(f,b.routepath,d)||b.routepath}),this.gridUpdateLinkLabels(a,l),this.fitViewportToContent(),this.dispatchEvent(new Event("relationsAvailable"));}fallbackGridRouting(n){this.container.selectAll(".link-group").data(n,s=>s.id??s).select("path").attr("d",s=>{if(s.source?.id===s.target?.id){let m=this.createGridSelfLoopRoute(s);return this.gridLineFunction(m)}let e=s.source?.x??s.source?.bounds?.cx()??0,i=s.source?.y??s.source?.bounds?.cy()??0,a=s.target?.x??s.target?.bounds?.cx()??0,u=s.target?.y??s.target?.bounds?.cy()??0,g=a-e,E=u-i;if(Math.abs(g)>Math.abs(E)){let m=e+g/2;return this.gridLineFunction([{x:e,y:i},{x:m,y:i},{x:m,y:u},{x:a,y:u}])}else {let m=i+E/2;return this.gridLineFunction([{x:e,y:i},{x:e,y:m},{x:a,y:m},{x:a,y:u}])}}),this.fitViewportToContent();}gridUpdateLinkLabels(n,o){this.container.selectAll(".link-group").filter(e=>!this.isAlignmentEdge(e)).select("text.linklabel").attr("x",e=>{let i=this.shadowRoot?.querySelector(`path[data-link-id="${e.id}"]`);if(i)try{let u=i.getTotalLength();return i.getPointAtLength(u/2).x}catch{}return this.getGridRouteMidpoint(e,o)?.x??e.source?.x??e.source?.bounds?.cx()??0}).attr("y",e=>{let i=this.shadowRoot?.querySelector(`path[data-link-id="${e.id}"]`);if(i)try{let u=i.getTotalLength();return i.getPointAtLength(u/2).y}catch{}return this.getGridRouteMidpoint(e,o)?.y??e.source?.y??e.source?.bounds?.cy()??0}).attr("text-anchor","middle").attr("dominant-baseline","middle");}getGridRouteMidpoint(n,o){let s=o.get(n.id);if(!s){let m=n.source?.x??n.source?.bounds?.cx()??0,p=n.source?.y??n.source?.bounds?.cy()??0,l=n.target?.x??n.target?.bounds?.cx()??0,c=n.target?.y??n.target?.bounds?.cy()??0;return {x:(m+l)/2,y:(p+c)/2}}let e=[];if(s.forEach(m=>{e.length===0&&m.length>0&&e.push(m[0]),m.length>1&&e.push(m[1]);}),e.length<2)return null;let i=0,a=[];for(let m=0;m<e.length-1;m++){let p=e[m+1].x-e[m].x,l=e[m+1].y-e[m].y,c=Math.sqrt(p*p+l*l);a.push(c),i+=c;}let u=i/2,g=0;for(let m=0;m<a.length;m++){let p=a[m];if(g+p>=u){let l=u-g,c=p>0?l/p:0;return {x:e[m].x+c*(e[m+1].x-e[m].x),y:e[m].y+c*(e[m+1].y-e[m].y)}}g+=p;}let E=Math.floor(e.length/2);return e[E]}adjustGridRouteForEdge(n,o){if(!n?.id?.startsWith("_g_"))return o;let s=this.gridRouteToPoints(o);if(s.length<2)return o;let e=this.routeGroupEdge(n,s);return this.pointsToGridRoute(e)}adjustGridRouteForArrowPositioning(n,o,s){if(!o||!n.source||!n.target)return null;try{let e=this.gridRouteToPoints(s);if(e.length<2)return null;let i=n.source,a=n.target,u=this.getVisibleBounds(i)??i.bounds??{x:i.x-(i.width||0)/2,y:i.y-(i.height||0)/2,width:()=>i.width||0,height:()=>i.height||0},g=this.getVisibleBounds(a)??a.bounds??{x:a.x-(a.width||0)/2,y:a.y-(a.height||0)/2,width:()=>a.width||0,height:()=>a.height||0},m=this.getTouchDirection(u,g,5);if(m!=="none"){let{sourcePoint:l,targetPoint:c,middlePoints:h}=this.computePerpendicularRoute(u,g,m),f=[l,...h,c];return this.gridLineFunction(f)}e[0]=this.clipEndpointToVisibleBoundary(e[0],e[1],u),e[e.length-1]=this.clipEndpointToVisibleBoundary(e[e.length-1],e[e.length-2],g);let p=this.applyPortBasedEndpointsOrthogonal(n,e);return this.gridLineFunction(p)}catch(e){return console.warn("Error adjusting grid route for arrow positioning:",e),null}}clipEndpointToVisibleBoundary(n,o,s){let e=s.x+s.width()/2,i=s.y+s.height()/2,a=s.x,u=typeof s.X=="number"?s.X:s.x+s.width(),g=s.y,E=typeof s.Y=="number"?s.Y:s.y+s.height(),m=.5,p=o.x-n.x,l=o.y-n.y;if(Math.abs(p)<m&&Math.abs(l)>=m){let h=n.y>i?E:g;return {x:n.x,y:h}}return Math.abs(l)<m&&Math.abs(p)>=m?{x:n.x>e?u:a,y:n.y}:this.getRectangleIntersection(e,i,o.x,o.y,s)??n}getTouchDirection(n,o,s){let e=n.x,i=n.x+n.width(),a=n.y,u=n.y+n.height(),g=o.x,E=o.x+o.width(),m=o.y,p=o.y+o.height(),l=Math.max(0,Math.max(g-i,e-E)),c=Math.max(0,Math.max(m-u,a-p)),h=!(u<m||p<a),f=!(i<g||E<e);return l<=s&&h?"horizontal":c<=s&&f?"vertical":"none"}computePerpendicularRoute(n,o,s){let e=n.width(),i=n.height(),a=o.width(),u=o.height(),g=n.x+e/2,E=n.y+i/2,m=o.x+a/2,p=o.y+u/2,l=15;if(s==="horizontal")if(E<=p){let h=Math.min(n.y,o.y)-l;return {sourcePoint:{x:g,y:n.y},targetPoint:{x:m,y:o.y},middlePoints:[{x:g,y:h},{x:m,y:h}]}}else {let h=Math.max(n.y+i,o.y+u)+l;return {sourcePoint:{x:g,y:n.y+i},targetPoint:{x:m,y:o.y+u},middlePoints:[{x:g,y:h},{x:m,y:h}]}}else if(g<=m){let h=Math.min(n.x,o.x)-l;return {sourcePoint:{x:n.x,y:E},targetPoint:{x:o.x,y:p},middlePoints:[{x:h,y:E},{x:h,y:p}]}}else {let h=Math.max(n.x+e,o.x+a)+l;return {sourcePoint:{x:n.x+e,y:E},targetPoint:{x:o.x+a,y:p},middlePoints:[{x:h,y:E},{x:h,y:p}]}}}areBoundsNear(n,o,s){let e=n.x,i=n.x+n.width(),a=n.y,u=n.y+n.height(),g=o.x,E=o.x+o.width(),m=o.y,p=o.y+o.height(),l=Math.max(0,Math.max(g-i,e-E)),c=Math.max(0,Math.max(m-u,a-p));return Math.sqrt(l*l+c*c)<=s}chooseBoundaryPoint(n,o,s,e){let i=Math.max(1,s.width()),a=Math.max(1,s.height()),u=[{x:s.x,y:s.y+a/2},{x:s.x+i,y:s.y+a/2},{x:s.x+i/2,y:s.y},{x:s.x+i/2,y:s.y+a}],g=u[0],E=-1/0;for(let m of u){let p=m.x-e.x,l=m.y-e.y,c=Math.sqrt(p*p+l*l);c>E&&(E=c,g=m);}return g}normalizeNodeBounds(n){let o=n.visualWidth??n.width??50,s=n.visualHeight??n.height??30,e=n.bounds||{x:n.x-o/2,y:n.y-s/2,width:()=>o,height:()=>s};return {x:typeof e.x=="number"||e.X!==void 0?e.x:n.x-o/2,y:typeof e.y=="number"?e.y:n.y-s/2,width:()=>typeof e.width=="function"?e.width():e.X!==void 0?e.X-e.x:o,height:()=>typeof e.height=="function"?e.height():e.Y!==void 0?e.Y-e.y:s}}lineIntersectsRect(n,o,s){let e=s.x,i=s.x+s.width(),a=s.y,u=s.y+s.height(),g=Math.min(n.x,o.x),E=Math.max(n.x,o.x),m=Math.min(n.y,o.y),p=Math.max(n.y,o.y);if(E<e||g>i||p<a||m>u)return false;let l=n.x>=e&&n.x<=i&&n.y>=a&&n.y<=u,c=o.x>=e&&o.x<=i&&o.y>=a&&o.y<=u;if(l||c)return true;let h=o.x-n.x,f=o.y-n.y,d=(_,A,b)=>{if(f===0)return false;let T=(_-n.y)/f;if(T<0||T>1)return false;let M=n.x+T*h;return M>=A&&M<=b},v=(_,A,b)=>{if(h===0)return false;let T=(_-n.x)/h;if(T<0||T>1)return false;let M=n.y+T*f;return M>=A&&M<=b};return d(a,e,i)||d(u,e,i)||v(e,a,u)||v(i,a,u)}findBlockingNodes(n,o,s,e){if(!this.currentLayout?.nodes)return [];let i=this.normalizeNodeBounds(n),a=this.normalizeNodeBounds(o),u={x:i.x+i.width()/2,y:i.y+i.height()/2},g={x:a.x+a.width()/2,y:a.y+a.height()/2},E=[];for(let m of this.currentLayout.nodes){if(m.id===s||m.id===e)continue;let p=this.normalizeNodeBounds(m);if(this.lineIntersectsRect(u,g,p)){let l={x:p.x+p.width()/2,y:p.y+p.height()/2},c=Math.sqrt(Math.pow(l.x-u.x,2)+Math.pow(l.y-u.y,2));E.push({node:m,bounds:p,distance:c});}}return E.sort((m,p)=>m.distance-p.distance),E.map(m=>({node:m.node,bounds:m.bounds}))}computeRouteAroundBlockingNodes(n,o,s){let i=Math.min(n.x,o.x),a=Math.max(n.x+n.width(),o.x+o.width()),u=Math.min(n.y,o.y),g=Math.max(n.y+n.height(),o.y+o.height());for(let{bounds:f}of s)i=Math.min(i,f.x),a=Math.max(a,f.x+f.width()),u=Math.min(u,f.y),g=Math.max(g,f.y+f.height());let E=n.x+n.width()/2,m=n.y+n.height()/2,p=o.x+o.width()/2,l=o.y+o.height()/2,c=Math.abs(p-E);if(Math.abs(l-m)>c)if(E<=p){let d=i-15;return {sourcePoint:{x:n.x,y:m},targetPoint:{x:o.x,y:l},middlePoints:[{x:d,y:m},{x:d,y:l}]}}else {let d=a+15;return {sourcePoint:{x:n.x+n.width(),y:m},targetPoint:{x:o.x+o.width(),y:l},middlePoints:[{x:d,y:m},{x:d,y:l}]}}else if(m<=l){let d=u-15;return {sourcePoint:{x:E,y:n.y},targetPoint:{x:p,y:o.y},middlePoints:[{x:E,y:d},{x:p,y:d}]}}else {let d=g+15;return {sourcePoint:{x:E,y:n.y+n.height()},targetPoint:{x:p,y:o.y+o.height()},middlePoints:[{x:E,y:d},{x:p,y:d}]}}}getNearTouchPerpendicularRoute(n){if(!n.source||!n.target||n.source.id===n.target.id)return null;let o=n.source,s=n.target,e=this.normalizeNodeBounds(o),i=this.normalizeNodeBounds(s),u=this.getTouchDirection(e,i,5);if(u!=="none"){let{sourcePoint:E,targetPoint:m,middlePoints:p}=this.computePerpendicularRoute(e,i,u);return [E,...p,m]}let g=this.findBlockingNodes(o,s,o.id,s.id);if(g.length>0){let{sourcePoint:E,targetPoint:m,middlePoints:p}=this.computeRouteAroundBlockingNodes(e,i,g);return [E,...p,m]}return null}gridRouteToPoints(n){let o=[];return n.forEach((s,e)=>{e===0&&o.push({x:s[0].x,y:s[0].y}),o.push({x:s[1].x,y:s[1].y});}),o}pointsToGridRoute(n){let o=[];for(let s=0;s<n.length-1;s+=1)o.push([n[s],n[s+1]]);return o}flattenGridRouteBends(n,o,s){try{if(!n||n.length<2)return n;let e=this.gridRouteToPoints(n);if(e.length<3)return n;let i=6,a=e;for(let u=0;u<i;u++){let g=a.length;if(a=this.dropCollinearGridPoints(a),a=this.flattenGridRouteZShapes(a,o,s),a=this.flattenGridRouteUBumps(a,o,s),a.length===g)break}return a.length===e.length?n:this.pointsToGridRoute(a)}catch(e){return console.warn("[flattenGridRouteBends] Failed; returning original route:",e),n}}dropCollinearGridPoints(n){if(n.length<3)return n;let o=[n[0]];for(let s=1;s<n.length-1;s++){let e=o[o.length-1],i=n[s],a=n[s+1],u=e.x===i.x&&i.x===a.x,g=e.y===i.y&&i.y===a.y;u||g||o.push(i);}return o.push(n[n.length-1]),o}flattenGridRouteZShapes(n,o,s){let a=new Set;s?.source?.id&&a.add(s.source.id),s?.target?.id&&a.add(s.target.id);let u=n.slice(),g=0,E=0;for(;E+3<u.length&&g<16;){let m=u[E],p=u[E+1],l=u[E+2],c=u[E+3],h=m.y===p.y,f=l.y===c.y;if(!(h===f&&(h?m.y!==c.y:m.x!==c.x))){E++;continue}let v=[{x:m.x,y:c.y},{x:c.x,y:m.y}],_=false;for(let A of v)if(this.isOrthogonalSegmentClearOfNodes(m,A,o,a,2)&&this.isOrthogonalSegmentClearOfNodes(A,c,o,a,2)){u.splice(E+1,2,A),g++,_=true;break}_||E++;}return u}flattenGridRouteUBumps(n,o,s){if(n.length<5)return n;let e=16,i=2,a=new Set;s?.source?.id&&a.add(s.source.id),s?.target?.id&&a.add(s.target.id);let u=n.slice(),g=0,E=0;for(;E+4<u.length&&g<e;){let m=u[E],p=u[E+4],l=Math.abs(m.x-p.x)<.001,c=Math.abs(m.y-p.y)<.001;if(!(l||c)){E++;continue}if(l&&c){E++;continue}this.isOrthogonalSegmentClearOfNodes(m,p,o,a,i)?(u.splice(E+1,3),g++):E++;}return u}isOrthogonalSegmentClearOfNodes(n,o,s,e,i){let a=n.y===o.y,u=n.x===o.x;if(!a&&!u)return false;for(let g of s){if(e.has(g.id))continue;let E=g.bounds||g.innerBounds;if(!E)continue;let m=typeof E.width=="function"?E.width():0,p=typeof E.height=="function"?E.height():0,l=(typeof E.x=="number"?E.x:0)-i,c=(E.X!==void 0?E.X:(E.x||0)+m)+i,h=(typeof E.y=="number"?E.y:0)-i,f=(E.Y!==void 0?E.Y:(E.y||0)+p)+i;if(a){if(n.y<h||n.y>f)continue;let d=Math.min(n.x,o.x);if(Math.max(n.x,o.x)<l||d>c)continue;return false}else {if(n.x<l||n.x>c)continue;let d=Math.min(n.y,o.y);if(Math.max(n.y,o.y)<h||d>f)continue;return false}}return true}createFallbackBounds(n){if(!q0?.Rectangle)return null;let o=((n.visualWidth??n.width)||50)/2,s=((n.visualHeight??n.height)||30)/2,e=(n.x||0)-o,i=(n.x||0)+o,a=(n.y||0)-s,u=(n.y||0)+s;return new q0.Rectangle(e,i,a,u)}getRectangleIntersection(n,o,s,e,i){let a=i.x,u=i.x+i.width(),g=i.y,E=i.y+i.height(),m=s-n,p=e-o;if(m===0&&p===0)return {x:n,y:o};let l=0,c=1;if(m!==0){let f=(a-n)/m,d=(u-n)/m;l=Math.max(l,Math.min(f,d)),c=Math.min(c,Math.max(f,d));}if(p!==0){let f=(g-o)/p,d=(E-o)/p;l=Math.max(l,Math.min(f,d)),c=Math.min(c,Math.max(f,d));}if(l>c)return null;let h=l>0?l:c;return {x:n+h*m,y:o+h*p}}computeAllRoutes(){this.computedRoutes.clear(),this.container.selectAll(".link-group path").each(n=>{try{let o=this.computeSingleRoute(n);o&&this.computedRoutes.set(n.id,o);}catch(o){console.error(`Error routing edge ${n.id} from ${n.source.id} to ${n.target.id}:`,o),this.showRuntimeAlert(n.source.id,n.target.id),this.computedRoutes.set(n.id,[{x:n.source.x||0,y:n.source.y||0},{x:n.target.x||0,y:n.target.y||0}]);}});}applyRoutesToSVG(){this.container.selectAll(".link-group path").attr("d",n=>{let o=this.computedRoutes.get(n.id);return o?this.useTautRouter&&n.source.id!==n.target.id&&!this.isAlignmentEdge(n)?this.filletPath(o):this.lineFunction(this.addTangentGuides(o)):null});}addTangentGuides(n){if(n.length<=2)return n;let o=8,s=2,e=[...n],i=e.length-1,a=e[i].x-e[i-1].x,u=e[i].y-e[i-1].y,g=Math.sqrt(a*a+u*u);if(g>.5){let l=a/g,c=u/g,h=Math.min(o,g*.5),f=Math.min(s,g*.2);h>f+.1?e.splice(i,0,{x:e[i].x-l*h,y:e[i].y-c*h},{x:e[i].x-l*f,y:e[i].y-c*f}):e.splice(i,0,{x:e[i].x-l*f,y:e[i].y-c*f});}let E=e[1].x-e[0].x,m=e[1].y-e[0].y,p=Math.sqrt(E*E+m*m);if(p>.5){let l=E/p,c=m/p,h=Math.min(o,p*.5),f=Math.min(s,p*.2);h>f+.1?e.splice(1,0,{x:e[0].x+l*f,y:e[0].y+c*f},{x:e[0].x+l*h,y:e[0].y+c*h}):e.splice(1,0,{x:e[0].x+l*f,y:e[0].y+c*f});}return e}tryDirectLineRoute(n){if(!n?.source||!n?.target||n.source.id===n.target.id||this.getAllEdgesBetweenNodes(n.source.id,n.target.id).length>1)return null;let s=this.getRenderedBounds(n.source),e=this.getRenderedBounds(n.target);if(this.getTouchDirection(s,e,5)!=="none")return null;let a={x:s.x+s.width()/2,y:s.y+s.height()/2},u={x:e.x+e.width()/2,y:e.y+e.height()/2};if(this.currentLayout?.nodes)for(let m of this.currentLayout.nodes){if(m.id===n.source.id||m.id===n.target.id)continue;let p=this.normalizeNodeBounds(m);if(this.lineIntersectsRect(a,u,p))return null}let g=this.clipLineToRectExit(a,u,s),E=this.clipLineToRectExit(u,a,e);return [g,E]}applyPortBasedEndpointsToDirectRoute(n,o){let s=n._sourcePortCount>1,e=n._targetPortCount>1;if(!s&&!e)return o;let i=this.applyPortBasedEndpoints(n,o.map(g=>({...g}))),a=this.getRenderedBounds(n.source),u=this.getRenderedBounds(n.target);return this.isPointOnRectPerimeter(i[0],a)&&this.isPointOnRectPerimeter(i[i.length-1],u)?i:o}getRenderedBounds(n){let o=this.getVisibleBounds(n);return o?{x:o.x,y:o.y,width:o.width,height:o.height}:this.normalizeNodeBounds(n)}isPointOnRectPerimeter(n,o,s=1){let e=o.x,i=o.x+o.width(),a=o.y,u=o.y+o.height(),g=n.x>=e-s&&n.x<=i+s,E=n.y>=a-s&&n.y<=u+s;return !g||!E?false:Math.abs(n.x-e)<=s||Math.abs(n.x-i)<=s||Math.abs(n.y-a)<=s||Math.abs(n.y-u)<=s}clipLineToRectExit(n,o,s){let e=o.x-n.x,i=o.y-n.y;if(e===0&&i===0)return {x:n.x,y:n.y};let a=s.x,u=s.x+s.width(),g=s.y,E=s.y+s.height(),m=[];e>0?m.push((u-n.x)/e):e<0&&m.push((a-n.x)/e),i>0?m.push((E-n.y)/i):i<0&&m.push((g-n.y)/i);let p=1/0;for(let l of m)l>0&&l<p&&(p=l);return Number.isFinite(p)?{x:n.x+p*e,y:n.y+p*i}:{x:n.x,y:n.y}}computeSingleRoute(n){if(this.isAlignmentEdge(n))return [{x:n.source.x||0,y:n.source.y||0},{x:n.target.x||0,y:n.target.y||0}];if(n.source.id===n.target.id)return this.createSelfLoopRoute(n);if(this.useTautRouter)return this.computeTautRoute(n);if(!n.id?.startsWith("_g_")){let i=this.tryDirectLineRoute(n);if(i)return this.applyPortBasedEndpointsToDirectRoute(n,i)}let o=[{x:n.source.x||0,y:n.source.y||0},{x:n.target.x||0,y:n.target.y||0}],s;if(typeof this.colaLayout?.routeEdge=="function")try{if(s=this.colaLayout.routeEdge(n),!s||!Array.isArray(s)||s.length<2||!s[0]||!s[1]||s[0].x===void 0||s[0].y===void 0)throw new Error(`WebCola failed to route edge ${n.id} from ${n.source.id} to ${n.target.id}`)}catch(i){return console.log("Error routing edge",n.id,`from ${n.source.id} to ${n.target.id}`),console.error(i),o}else s=o;if(n.id?.startsWith("_g_"))return this.routeGroupEdge(n,s);s=this.handleMultipleEdgeRouting(n,s),s=this.applyPortBasedEndpoints(n,s);let e=this.getNearTouchPerpendicularRoute(n);return e||s}computeTautRoute(n){if(n.id?.startsWith("_g_")){let u=this.getRenderedBounds(n.source),g=this.getRenderedBounds(n.target),E={x:u.x+u.width()/2,y:u.y+u.height()/2},m={x:g.x+g.width()/2,y:g.y+g.height()/2},p=[this.clipLineToRectExit(E,m,u),this.clipLineToRectExit(m,E,g)];return this.routeGroupEdge(n,p)}let o=this.getPortAttachment(n,"source"),s=this.getPortAttachment(n,"target"),e=this.buildRouterObstacles(n),i=this.routeTautPolyline(o,s,e),a=this.handleMultipleEdgeRouting(n,i.map(u=>({...u})));return this.routePolylineClips(a,e)?i:a}routePolylineClips(n,o){for(let s=0;s<n.length-1;s++)if(this.anyObstacleBlocks(n[s],n[s+1],o,-1,-1))return true;return false}getPortAttachment(n,o){let s=o==="source"?n.source:n.target,e=o==="source"?n.target:n.source,i=this.getRenderedBounds(s),a=this.getRenderedBounds(e),u={x:i.x+i.width()/2,y:i.y+i.height()/2},g={x:a.x+a.width()/2,y:a.y+a.height()/2},E=this.clipLineToRectExit(u,g,i),m=o==="source"?n._sourcePortCount:n._targetPortCount;if(m!==void 0&&m>1){let p=o==="source"?[{...E},{...g}]:[{...g},{...E}],l=this.applyPortBasedEndpoints(n,p),c=o==="source"?l[0]:l[l.length-1];c&&this.isPointOnRectPerimeter(c,i)&&(E=c);}return {point:E,normal:this.sideNormal(E,i)}}sideNormal(n,o){let s=o.x,e=o.x+o.width(),i=o.y,a=o.y+o.height(),u=Math.abs(n.x-s),g=Math.abs(n.x-e),E=Math.abs(n.y-i),m=Math.abs(n.y-a),p=Math.min(u,g,E,m);return p===u?{x:-1,y:0}:p===g?{x:1,y:0}:p===E?{x:0,y:-1}:{x:0,y:1}}buildRouterObstacleCache(){let n=[],o=Er.EDGE_CLEARANCE_PX;for(let s of this.currentLayout?.nodes||[]){let e=this.getRenderedBounds(s),i=e.width(),a=e.height();i<=0&&a<=0||n.push({id:s.id,minX:e.x-o,minY:e.y-o,maxX:e.x+i+o,maxY:e.y+a+o});}this.routerObstacleCache=n;}buildRouterObstacles(n){let o=n.source.id,s=n.target.id;if(this.routerObstacleCache)return this.routerObstacleCache.filter(a=>a.id!==o&&a.id!==s);let e=[];if(!this.currentLayout?.nodes)return e;let i=Er.EDGE_CLEARANCE_PX;for(let a of this.currentLayout.nodes){if(a.id===o||a.id===s)continue;let u=this.getRenderedBounds(a),g=u.width(),E=u.height();g<=0&&E<=0||e.push({minX:u.x-i,minY:u.y-i,maxX:u.x+g+i,maxY:u.y+E+i});}return e}routeTautPolyline(n,o,s){let e=n.point,i=o.point;if(!this.anyObstacleBlocks(e,i,s,-1,-1))return [{x:e.x,y:e.y},{x:i.x,y:i.y}];let a=Er.EDGE_STUB_LENGTH_PX,u=Er.EDGE_CLEARANCE_PX+a,g=Math.min(e.x,i.x)-u,E=Math.max(e.x,i.x)+u,m=Math.min(e.y,i.y)-u,p=Math.max(e.y,i.y)+u,l=[];for(let gt of s)gt.maxX<g||gt.minX>E||gt.maxY<m||gt.minY>p||l.push(gt);if(l.length===0||l.length>Er.MAX_ROUTER_OBSTACLES)return this.lBendFallback(e,i,s);let c={x:e.x+n.normal.x*a,y:e.y+n.normal.y*a},h={x:i.x+o.normal.x*a,y:i.y+o.normal.y*a},f=!this.pointInAnyObstacle(c,l)&&!this.anyObstacleBlocks(e,c,l,-1,-1),d=!this.pointInAnyObstacle(h,l)&&!this.anyObstacleBlocks(i,h,l,-1,-1),v=f?{...c,owner:-1}:{x:e.x,y:e.y,owner:-1},_=d?{...h,owner:-1}:{x:i.x,y:i.y,owner:-1},A=[v,_];l.forEach((gt,yt)=>{A.push({x:gt.minX,y:gt.minY,owner:yt},{x:gt.maxX,y:gt.minY,owner:yt},{x:gt.maxX,y:gt.maxY,owner:yt},{x:gt.minX,y:gt.maxY,owner:yt});});let b=A.length,T=0,M=1,k=1/0,B=1/0,F=-1/0,Q=-1/0;for(let gt of A)gt.x<k&&(k=gt.x),gt.x>F&&(F=gt.x),gt.y<B&&(B=gt.y),gt.y>Q&&(Q=gt.y);let R=s.filter(gt=>!(gt.maxX<k||gt.minX>F||gt.maxY<B||gt.minY>Q)),$=(gt,yt)=>!this.anyObstacleBlocks(gt,yt,R,-1,-1),z=new Array(b).fill(1/0),Z=new Array(b).fill(-1),dt=new Array(b).fill(false);z[T]=0;for(let gt=0;gt<b;gt++){let yt=-1,mt=1/0;for(let At=0;At<b;At++)!dt[At]&&z[At]<mt&&(mt=z[At],yt=At);if(yt===-1||yt===M)break;dt[yt]=true;for(let At=0;At<b;At++){if(dt[At]||At===yt||!$(A[yt],A[At]))continue;let Wt=A[yt].x-A[At].x,ie=A[yt].y-A[At].y,re=Math.sqrt(Wt*Wt+ie*ie);z[yt]+re<z[At]&&(z[At]=z[yt]+re,Z[At]=yt);}}if(!Number.isFinite(z[M]))return this.lBendFallback(e,i,s);let tt=[];for(let gt=M;gt!==-1;gt=Z[gt])tt.push({x:A[gt].x,y:A[gt].y});tt.reverse();let ct=[];return f&&ct.push({x:e.x,y:e.y}),ct.push(...tt),d&&ct.push({x:i.x,y:i.y}),this.simplifyCollinear(ct)}lBendFallback(n,o,s){for(let e of [{x:o.x,y:n.y},{x:n.x,y:o.y}])if(!this.pointInAnyObstacle(e,s)&&!this.anyObstacleBlocks(n,e,s,-1,-1)&&!this.anyObstacleBlocks(e,o,s,-1,-1))return this.simplifyCollinear([{x:n.x,y:n.y},e,{x:o.x,y:o.y}]);return [{x:n.x,y:n.y},{x:o.x,y:o.y}]}anyObstacleBlocks(n,o,s,e,i){for(let a=0;a<s.length;a++)if(!(a===e||a===i)&&this.segmentEntersRect(n,o,s[a]))return true;return false}segmentEntersRect(n,o,s){let i=0,a=1,u=o.x-n.x,g=o.y-n.y,E=[-u,u,-g,g],m=[n.x-s.minX,s.maxX-n.x,n.y-s.minY,s.maxY-n.y];for(let c=0;c<4;c++)if(Math.abs(E[c])<1e-6){if(m[c]<0)return false}else {let h=m[c]/E[c];if(E[c]<0){if(h>a)return false;h>i&&(i=h);}else {if(h<i)return false;h<a&&(a=h);}}if(a-i<=1e-6)return false;let p=n.x+(i+a)/2*u,l=n.y+(i+a)/2*g;return p>s.minX+1e-6&&p<s.maxX-1e-6&&l>s.minY+1e-6&&l<s.maxY-1e-6}pointInAnyObstacle(n,o){for(let s of o)if(n.x>s.minX&&n.x<s.maxX&&n.y>s.minY&&n.y<s.maxY)return true;return false}simplifyCollinear(n){if(n.length<=2)return n;let o=[n[0]];for(let s=1;s<n.length-1;s++){let e=o[o.length-1],i=n[s],a=n[s+1],u=Math.abs(i.x-e.x)<1e-6&&Math.abs(i.y-e.y)<1e-6,g=(i.x-e.x)*(a.y-e.y)-(i.y-e.y)*(a.x-e.x);!u&&Math.abs(g)>1e-6&&o.push(i);}return o.push(n[n.length-1]),o}filletPath(n){if(!n||n.length===0)return "";if(n.length===1)return `M ${n[0].x} ${n[0].y}`;if(n.length===2)return `M ${n[0].x} ${n[0].y} L ${n[1].x} ${n[1].y}`;let o=Er.EDGE_CLEARANCE_PX,s=`M ${n[0].x} ${n[0].y}`;for(let i=1;i<n.length-1;i++){let a=n[i-1],u=n[i],g=n[i+1],E=a.x-u.x,m=a.y-u.y,p=g.x-u.x,l=g.y-u.y,c=Math.hypot(E,m),h=Math.hypot(p,l);if(c<1e-6||h<1e-6){s+=` L ${u.x} ${u.y}`;continue}let f=Math.min(o,c/2,h/2),d={x:u.x+E/c*f,y:u.y+m/c*f},v={x:u.x+p/h*f,y:u.y+l/h*f};s+=` L ${d.x} ${d.y} Q ${u.x} ${u.y} ${v.x} ${v.y}`;}let e=n[n.length-1];return s+=` L ${e.x} ${e.y}`,s}optimizeCrossings(){if(this.useTautRouter||this.computedRoutes.size<2)return;let n=0;if(this.currentLayout?.links)for(let i of this.currentLayout.links)this.isAlignmentEdge(i)||i.source.id!==i.target.id&&this.computedRoutes.get(i.id)&&n++;if(n>Er.CROSSING_OPTIMIZATION_EDGE_THRESHOLD)return;let o=Er.MAX_CROSSING_OPTIMIZATION_BUDGET_MS,s=typeof performance<"u"&&typeof performance.now=="function"?performance.now():Date.now(),e=()=>(typeof performance<"u"&&typeof performance.now=="function"?performance.now():Date.now())-s;for(let i=0;i<Er.MAX_CROSSING_OPTIMIZATION_PASSES;i++){if(e()>o){console.warn(`[optimizeCrossings] Budget (${o}ms) exceeded after pass ${i}; accepting current routing.`);return}let a=this.detectEdgeCrossings();if(a.length===0)break;if(e()>o){console.warn(`[optimizeCrossings] Budget exceeded after detection on pass ${i}; accepting current routing.`);return}if(!this.resolveEdgeCrossings(a))break}}detectEdgeCrossings(){let n=[],o=[],s=[];if(this.currentLayout?.links)for(let i of this.currentLayout.links){if(this.isAlignmentEdge(i)||i.source.id===i.target.id)continue;let a=this.computedRoutes.get(i.id);!a||a.length<2||(n.push(i.id),o.push(a),s.push({sourceId:i.source.id,targetId:i.target.id}));}let e=[];for(let i=0;i<n.length;i++)for(let a=i+1;a<n.length;a++){let u=s[i],g=s[a],E=u.sourceId===g.sourceId||u.sourceId===g.targetId,m=u.targetId===g.sourceId||u.targetId===g.targetId;if(!(E&&m)&&this.routesCross(o[i],o[a])){let p=this.getRouteLength(o[i])+this.getRouteLength(o[a]);e.push([n[i],n[a],p]);}}return e.sort((i,a)=>a[2]-i[2]),e.map(([i,a])=>[i,a])}routesCross(n,o){for(let s=0;s<n.length-1;s++)for(let e=0;e<o.length-1;e++)if(this.segmentsIntersect(n[s],n[s+1],o[e],o[e+1]))return true;return false}segmentsIntersect(n,o,s,e){let i=this.cross(s,e,n),a=this.cross(s,e,o),u=this.cross(n,o,s),g=this.cross(n,o,e);return (i>0&&a<0||i<0&&a>0)&&(u>0&&g<0||u<0&&g>0)}cross(n,o,s){return (o.x-n.x)*(s.y-n.y)-(o.y-n.y)*(s.x-n.x)}resolveEdgeCrossings(n){let o=false;for(let[s,e]of n){let i=this.computedRoutes.get(s),a=this.computedRoutes.get(e);if(!i||!a)continue;let u=this.findEdgeById(s),g=this.findEdgeById(e),E=u&&g?this.findSharedNodeId(u,g):null;if(u&&g&&E&&this.tryResolveByPortSwap(u,g,E)){o=true;continue}let m=this.findCrossingSegments(i,a);if(!m)continue;let p=this.getRouteLength(i),l=this.getRouteLength(a),[c,h]=p<=l?[i,s]:[a,e],f=c===i?a:i,d=c===i?m.segIdxA:m.segIdxB,v={dx:c[d+1].x-c[d].x,dy:c[d+1].y-c[d].y},_=Math.sqrt(v.dx*v.dx+v.dy*v.dy);if(_<1)continue;let A=-v.dy/_,b=v.dx/_,T=false,M=Er.CROSSING_NUDGE_DISTANCE;for(let k of [1,-1]){let B=(c[d].x+c[d+1].x)/2+A*M*k,F=(c[d].y+c[d+1].y)/2+b*M*k,Q=[...c];if(Q.splice(d+1,0,{x:B,y:F}),!this.routesCross(Q,f)){this.computedRoutes.set(h,Q),o=true,T=true;break}}if(!T){let k=this.rerouteAroundEdge(c,f);k&&!this.routesCross(k,f)&&(this.computedRoutes.set(h,k),o=true);}}return o}findEdgeById(n){if(!this.currentLayout?.links)return null;for(let o of this.currentLayout.links)if(o.id===n)return o;return null}findSharedNodeId(n,o){let s=n.source.id,e=n.target.id,i=o.source.id,a=o.target.id,u=s===i||s===a,g=e===i||e===a;return u&&g?null:u?s:g?e:null}tryResolveByPortSwap(n,o,s){let e=n,i=o,a=this.computedRoutes.get(n.id),u=this.computedRoutes.get(o.id);if(!a||!u)return false;let g=n.source.id===s,E=o.source.id===s,m=g?"_sourcePortIndex":"_targetPortIndex",p=g?"_sourcePortCount":"_targetPortCount",l=E?"_sourcePortIndex":"_targetPortIndex",c=E?"_sourcePortCount":"_targetPortCount",h=e[m],f=i[l],d=e[p],v=i[c];if(h===void 0||f===void 0||d===void 0||v===void 0||d!==v||h===f)return false;e[m]=f,i[l]=h;let _=this.applyPortBasedEndpoints(e,a.map(b=>({...b}))),A=this.applyPortBasedEndpoints(i,u.map(b=>({...b})));return this.routesCross(_,A)?(e[m]=h,i[l]=f,false):(this.computedRoutes.set(n.id,_),this.computedRoutes.set(o.id,A),true)}rerouteAroundEdge(n,o){let s=1/0,e=-1/0,i=1/0,a=-1/0;for(let c of o)s=Math.min(s,c.x),e=Math.max(e,c.x),i=Math.min(i,c.y),a=Math.max(a,c.y);let u=Er.CROSSING_NUDGE_DISTANCE,g=n[0],E=n[n.length-1],m=e-s,p=a-i,l=[];if(p>=m){let c=s-u,h=e+u;for(let f of [c,h]){let d=[g,{x:f,y:g.y},{x:f,y:E.y},E];this.routesCross(d,o)||l.push(d);}if(l.length===0)for(let f of [i-u,a+u]){let d=[g,{x:g.x,y:f},{x:E.x,y:f},E];this.routesCross(d,o)||l.push(d);}}else {let c=i-u,h=a+u;for(let f of [c,h]){let d=[g,{x:g.x,y:f},{x:E.x,y:f},E];this.routesCross(d,o)||l.push(d);}if(l.length===0)for(let f of [s-u,e+u]){let d=[g,{x:f,y:g.y},{x:f,y:E.y},E];this.routesCross(d,o)||l.push(d);}}return l.length===0?null:(l.sort((c,h)=>this.getRouteLength(c)-this.getRouteLength(h)),l[0])}findCrossingSegments(n,o){for(let s=0;s<n.length-1;s++)for(let e=0;e<o.length-1;e++)if(this.segmentsIntersect(n[s],n[s+1],o[e],o[e+1]))return {segIdxA:s,segIdxB:e};return null}createSelfLoopRoute(n){let o=n.source,s=o.bounds;if(!s)return [{x:o.x-8,y:o.y},{x:o.x-8,y:o.y-20},{x:o.x,y:o.y-28},{x:o.x+8,y:o.y-20},{x:o.x+8,y:o.y}];let e=s.X-s.x,i=s.Y-s.y,a=s.x+e/2,u=s.y+i/2,g=this.getSelfLoopIndex(n),E=g%4,p=1+Math.floor(g/4)*Er.SELF_LOOP_CURVATURE_SCALE,l,c=0,h=0,f=0,d=0;switch(E){case 0:l={x:s.X,y:u},c=1,f=0,h=0,d=1;break;case 1:l={x:a,y:s.Y},c=0,f=-1,h=1,d=0;break;case 2:l={x:s.x,y:u},c=-1,f=0,h=0,d=-1;break;default:l={x:a,y:s.y},c=0,f=1,h=-1,d=0;break}let v=Math.min(e,i),A=Math.min((E===0||E===2?i:e)*.45,v*.5),b=v*.7*p,T=A*.55,M={x:l.x-f*A/2,y:l.y-d*A/2},k={x:l.x+f*A/2,y:l.y+d*A/2},B={x:M.x+c*T,y:M.y+h*T},F={x:k.x+c*T,y:k.y+h*T},Q={x:l.x+c*b,y:l.y+h*b};return [M,B,Q,F,k]}createGridSelfLoopRoute(n){let o=n.source,s=o.bounds;if(!s)return [{x:o.x-10,y:o.y},{x:o.x-10,y:o.y-24},{x:o.x+10,y:o.y-24},{x:o.x+10,y:o.y}];let e=s.X-s.x,i=s.Y-s.y,a=s.x+e/2,u=s.y+i/2,g=this.getSelfLoopIndex(n),E=g%4,p=1+Math.floor(g/4)*Er.SELF_LOOP_CURVATURE_SCALE,l=Math.min(e,i),h=Math.min((E===0||E===2?i:e)*.45,l*.5),f=l*.55*p,d,v,_,A;switch(E){case 0:d={x:s.X,y:u-h/2},v={x:s.X+f,y:u-h/2},_={x:s.X+f,y:u+h/2},A={x:s.X,y:u+h/2};break;case 1:d={x:a+h/2,y:s.Y},v={x:a+h/2,y:s.Y+f},_={x:a-h/2,y:s.Y+f},A={x:a-h/2,y:s.Y};break;case 2:d={x:s.x,y:u+h/2},v={x:s.x-f,y:u+h/2},_={x:s.x-f,y:u-h/2},A={x:s.x,y:u-h/2};break;default:d={x:a-h/2,y:s.y},v={x:a-h/2,y:s.y-f},_={x:a+h/2,y:s.y-f},A={x:a+h/2,y:s.y};break}return [d,v,_,A]}getSelfLoopIndex(n){let o=n.source.id,s=this.getNodePairKey(o,o),e=this.edgeRoutingCache.edgesBetweenNodes.get(s);if(e){let i=e.findIndex(a=>a.id===n.id);return i>=0?i:0}if(this.currentLayout?.links){let i=0;for(let a of this.currentLayout.links)if(a.source.id===o&&a.target.id===o){if(a.id===n.id)return i;i++;}}return 0}routeGroupEdge(n,o){let s=n.groupId?(this.currentLayout?.groups||[]).find(e=>e.id===n.groupId):null;if(!s)console.warn("[routeGroupEdge] Group not found for edge:",n.id,"\u2014 groupId:",n.groupId);else {if(!s.bounds&&q0?.Rectangle){let e=n.source?.id===n.keyNodeId?o[o.length-1]:o[0];e&&(s.bounds=new q0.Rectangle(e.x-50,e.x+50,e.y-30,e.y+30));}if(s.bounds)if(n.source?.id===n.keyNodeId)o[o.length-1]=this.closestPointOnRect(s.bounds,o[0]);else if(n.target?.id===n.keyNodeId){let e=s.bounds.inflate?.(-1)??s.bounds;o[0]=this.closestPointOnRect(e,o[o.length-1]);}else console.warn("[routeGroupEdge] keyNodeId matched neither side",{keyNodeId:n.keyNodeId,sourceId:n.source?.id,targetId:n.target?.id});}return o.length>2&&o.splice(1,o.length-2),o}handleMultipleEdgeRouting(n,o){let s=this.getAllEdgesBetweenNodes(n.source.id,n.target.id);if(s.length<=1)return o;if(o.length===2){let E={x:(o[0].x+o[1].x)/2,y:(o[0].y+o[1].y)/2};o.splice(1,0,E);}let e=o[1].x-o[0].x,i=o[1].y-o[0].y,a=Math.atan2(i,e),u=this.getRouteLength(o),g=s.findIndex(E=>E.id===n.id);if(g!==-1){o=this.applyEdgeOffsetWithIndex(n,o,s,a,g,u);let E=this.calculateCurvatureWithIndex(s,n.id,g),m=this.clampCurvature(E);o=this.applyCurvatureToRoute(o,m,a,u);}return o}getAllEdgesBetweenNodes(n,o){if(!this.currentLayout?.links)return [];let s=this.getNodePairKey(n,o);return this.edgeRoutingCache.edgesBetweenNodes.has(s)?this.edgeRoutingCache.edgesBetweenNodes.get(s):this.currentLayout.links.filter(e=>!this.isAlignmentEdge(e)&&(e.source.id===n&&e.target.id===o||e.source.id===o&&e.target.id===n))}calculateCurvature(n,o,s,e){if(e.startsWith("_alignment_"))return 0;let i=n.length,a=n.findIndex(u=>u.id===e);return i<=1?0:(a%2===0?1:-1)*(Math.floor(a/2)+1)*Er.CURVATURE_BASE_MULTIPLIER*i}calculateCurvatureWithIndex(n,o,s){let e=n.length;if(e<=1)return 0;let i=n[s],a=i?._sourcePortIndex,u=i?._sourcePortCount;return typeof a=="number"&&typeof u=="number"&&u>1?(a-(u-1)/2)/((u-1)/2)*u*Er.CURVATURE_BASE_MULTIPLIER:(s%2===0?1:-1)*(Math.floor(s/2)+1)*Er.CURVATURE_BASE_MULTIPLIER*e}applyEdgeOffset(n,o,s,e){let i=s.findIndex(u=>u.id===n.id),a=this.getRouteLength(o);return this.applyEdgeOffsetWithIndex(n,o,s,e,i,a)}applyEdgeOffsetWithIndex(n,o,s,e,i,a){let u=this.getDominantDirection(e),g=n._sourcePortIndex,E=n._sourcePortCount,m=n._targetPortIndex,p=n._targetPortCount,l=g!==void 0&&E!==void 0&&E>1,c=m!==void 0&&p!==void 0&&p>1;if(l||c)this.applyPortBasedOffset(o,n,u,l,c);else {let h=(i%2===0?1:-1)*(Math.floor(i/2)+1)*Er.MIN_EDGE_DISTANCE,f=this.clampOffset(h,a);u==="right"||u==="left"?(o[0].y+=f,o[o.length-1].y+=f):(u==="up"||u==="down")&&(o[0].x+=f,o[o.length-1].x+=f);}return n.source.innerBounds&&(o[0]=this.adjustPointToRectanglePerimeter(o[0],n.source.innerBounds)),n.target.innerBounds&&(o[o.length-1]=this.adjustPointToRectanglePerimeter(o[o.length-1],n.target.innerBounds)),o}computePortMargin(n,o){let s=n*Er.PORT_MARGIN_FRACTION;if(o<=1||(n-2*s)/o>=Er.MIN_PORT_PERIMETER_SPACING)return s;let i=(n-o*Er.MIN_PORT_PERIMETER_SPACING)/2;return Math.max(Er.MIN_ABSOLUTE_PORT_MARGIN_PX,i)}applyPortBasedEndpoints(n,o){if(!o||o.length<2||n.source.id===n.target.id)return o;let s=n._sourcePortIndex!==void 0&&n._sourcePortCount!==void 0&&n._sourcePortCount>1,e=n._targetPortIndex!==void 0&&n._targetPortCount!==void 0&&n._targetPortCount>1;if(!s&&!e)return o;let i=(n.target.x||0)-(n.source.x||0),a=(n.target.y||0)-(n.source.y||0),u=Math.atan2(a,i),g=this.getDominantDirection(u);if(s){let E=this.normalizeNodeBounds(n.source),m=n._sourcePortIndex,p=n._sourcePortCount;if(g==="right"||g==="left"){let l=E.height(),c=this.computePortMargin(l,p),h=l-2*c;o[0]={...o[0],y:E.y+c+(m+.5)*h/p};}else if(g==="up"||g==="down"){let l=E.width(),c=this.computePortMargin(l,p),h=l-2*c;o[0]={...o[0],x:E.x+c+(m+.5)*h/p};}}if(e){let E=this.normalizeNodeBounds(n.target),m=n._targetPortIndex,p=n._targetPortCount,l=g==="right"?"left":g==="left"?"right":g==="up"?"down":"up";if(l==="right"||l==="left"){let c=E.height(),h=this.computePortMargin(c,p),f=c-2*h,d=o.length-1;o[d]={...o[d],y:E.y+h+(m+.5)*f/p};}else if(l==="up"||l==="down"){let c=E.width(),h=this.computePortMargin(c,p),f=c-2*h,d=o.length-1;o[d]={...o[d],x:E.x+h+(m+.5)*f/p};}}return o}applyPortBasedEndpointsOrthogonal(n,o){if(!o||o.length<2||n?.source?.id===n?.target?.id)return o;let s=n._sourcePortIndex!==void 0&&n._sourcePortCount!==void 0&&n._sourcePortCount>1,e=n._targetPortIndex!==void 0&&n._targetPortCount!==void 0&&n._targetPortCount>1;if(!s&&!e)return o;let i=o.slice();return s&&(i=this.shiftRouteEndpointToPort(i,n.source,n._sourcePortIndex,n._sourcePortCount,"start")),e&&(i=this.shiftRouteEndpointToPort(i,n.target,n._targetPortIndex,n._targetPortCount,"end")),i}shiftRouteEndpointToPort(n,o,s,e,i){if(n.length<2)return n;let a=this.getVisibleBounds(o);if(!a)return n;let u=i==="start"?0:n.length-1,g=i==="start"?1:n.length-2,E=n[u],m=n[g],p=a.x,l=a.X,c=a.y,h=a.Y,f=l-p,d=h-c,v=1,_,A=Math.abs(E.x-p),b=Math.abs(E.x-l),T=Math.abs(E.y-c),M=Math.abs(E.y-h),k=Math.min(A,b,T,M);k===A?_="left":k===b?_="right":k===T?_="top":_="bottom";let B=_==="left"||_==="right"?d:f;if(B<=0)return n;let F=this.computePortMargin(B,e),Q=B-2*F,R=F+(s+.5)*Q/e,$;switch(_){case "left":$={x:p,y:c+R};break;case "right":$={x:l,y:c+R};break;case "top":$={x:p+R,y:c};break;case "bottom":$={x:p+R,y:h};break}let z=n.slice();if(Math.abs($.x-m.x)<v||Math.abs($.y-m.y)<v)return z[u]=$,z;let dt;return _==="left"||_==="right"?dt={x:m.x,y:$.y}:dt={x:$.x,y:m.y},i==="start"?(z[0]=$,z.splice(1,0,dt)):(z[z.length-1]=$,z.splice(z.length-1,0,dt)),z}applyPortBasedOffset(n,o,s,e,i){if(e){let a=this.normalizeNodeBounds(o.source),u=o._sourcePortIndex,g=o._sourcePortCount;if(s==="right"||s==="left"){let E=a.height(),m=this.computePortMargin(E,g),p=E-2*m,l=a.y+m+(u+.5)*p/g;n[0].y=l;}else if(s==="up"||s==="down"){let E=a.width(),m=this.computePortMargin(E,g),p=E-2*m,l=a.x+m+(u+.5)*p/g;n[0].x=l;}}if(i){let a=this.normalizeNodeBounds(o.target),u=o._targetPortIndex,g=o._targetPortCount,E=s==="right"?"left":s==="left"?"right":s==="up"?"down":"up";if(E==="right"||E==="left"){let m=a.height(),p=this.computePortMargin(m,g),l=m-2*p,c=a.y+p+(u+.5)*l/g;n[n.length-1].y=c;}else if(E==="up"||E==="down"){let m=a.width(),p=this.computePortMargin(m,g),l=m-2*p,c=a.x+p+(u+.5)*l/g;n[n.length-1].x=c;}}}clampOffset(n,o){let s=Math.max(Er.MIN_EDGE_DISTANCE,o*Er.MAX_EDGE_OFFSET_RATIO);return Math.max(-s,Math.min(s,n))}getRouteLength(n){return n.length<2?0:n.slice(1).reduce((o,s,e)=>{let i=n[e],a=s.x-i.x,u=s.y-i.y;return o+Math.sqrt(a*a+u*u)},0)}clampCurvature(n){return Math.max(-Er.MAX_EDGE_CURVATURE_RATIO,Math.min(Er.MAX_EDGE_CURVATURE_RATIO,n))}applyCurvatureToRoute(n,o,s,e){return o===0||n.forEach((i,a)=>{if(a>0&&a<n.length-1){let u=o*Math.abs(Math.sin(s))*e,g=o*Math.abs(Math.cos(s))*e;i.x+=u,i.y+=g;}}),n}getDominantDirection(n){return n=(n+Math.PI)%(2*Math.PI)-Math.PI,n>=-Math.PI/4&&n<=Math.PI/4?"right":n>Math.PI/4&&n<3*Math.PI/4?"up":n>=3*Math.PI/4||n<=-3*Math.PI/4?"left":n>-3*Math.PI/4&&n<-Math.PI/4?"down":null}closestPointOnRect(n,o){if(!n)return o;let{x:s,y:e,X:i,Y:a}=n,u=Math.max(s,Math.min(o.x,i)),g=Math.max(e,Math.min(o.y,a));return {x:u,y:g}}getVisibleBounds(n){if(!n)return null;let o=n.x??(n.bounds&&typeof n.bounds.cx=="function"?n.bounds.cx():void 0),s=n.y??(n.bounds&&typeof n.bounds.cy=="function"?n.bounds.cy():void 0);if(o===void 0||s===void 0)return null;let e,i;return n.visualWidth!==void 0||n.visualHeight!==void 0?(e=(n.visualWidth??n.width??0)/2,i=(n.visualHeight??n.height??0)/2):Array.isArray(n.leaves)||Array.isArray(n.groups)?n.bounds&&typeof n.bounds.width=="function"?(e=n.bounds.width()/2-Er.GROUP_VISUAL_MARGIN_PX,i=n.bounds.height()/2-Er.GROUP_VISUAL_MARGIN_PX):n.bounds&&n.bounds.X!==void 0&&(e=(n.bounds.X-n.bounds.x)/2-Er.GROUP_VISUAL_MARGIN_PX,i=(n.bounds.Y-n.bounds.y)/2-Er.GROUP_VISUAL_MARGIN_PX):(n.width!==void 0||n.height!==void 0)&&(e=(n.width??0)/2,i=(n.height??0)/2),e===void 0||i===void 0||e<=0&&i<=0?null:(e=Math.max(0,e),i=Math.max(0,i),{cx:()=>o,cy:()=>s,width:()=>e*2,height:()=>i*2,x:o-e,X:o+e,y:s-i,Y:s+i})}getStableEdgeAnchor(n,o){if(!n)return o;let s,e,i,a;if(typeof n.cx=="function")s=n.cx(),e=n.cy(),i=n.width()/2,a=n.height()/2;else if(n.x!==void 0&&n.X!==void 0)s=(n.x+n.X)/2,e=(n.y+n.Y)/2,i=(n.X-n.x)/2,a=(n.Y-n.y)/2;else return o;let u=o.x-s,g=o.y-e,E=Math.abs(u)/i,m=Math.abs(g)/a;return E>m?u>0?{x:s+i,y:e}:{x:s-i,y:e}:g>0?{x:s,y:e+a}:{x:s,y:e-a}}getStableEdgePath(n,o,s){let e={x:n.x||0,y:n.y||0},i={x:o.x||0,y:o.y||0},a=this.getVisibleBounds(n)??n.bounds??n.innerBounds,u=this.getVisibleBounds(o)??o.bounds??o.innerBounds,g=a?this.getStableEdgeAnchor(a,i):e,E=u?this.getStableEdgeAnchor(u,e):i;return s&&a&&(g=this.applyPortOffsetToAnchor(g,a,s._sourcePortIndex,s._sourcePortCount,e,i)),s&&u&&(E=this.applyPortOffsetToAnchor(E,u,s._targetPortIndex,s._targetPortCount,i,e)),[g,E]}applyPortOffsetToAnchor(n,o,s,e,i,a){if(s===void 0||e===void 0||e<=1)return n;let u=typeof o.x=="number"?o.x:0,g=o.X!==void 0?o.X:u+(typeof o.width=="function"?o.width():0),E=typeof o.y=="number"?o.y:0,m=o.Y!==void 0?o.Y:E+(typeof o.height=="function"?o.height():0),p=g-u,l=m-E,c=1;if(Math.abs(n.x-u)<c||Math.abs(n.x-g)<c){let h=this.computePortMargin(l,e),f=l-2*h,d=E+h+(s+.5)*f/e;return {x:n.x,y:d}}else if(Math.abs(n.y-E)<c||Math.abs(n.y-m)<c){let h=this.computePortMargin(p,e),f=p-2*h;return {x:u+h+(s+.5)*f/e,y:n.y}}return n}adjustPointToRectanglePerimeter(n,o){return o?this.closestPointOnRect(o,n):n}updateLinkLabelsAfterRouting(){this.container.selectAll(".link-group .linklabel").attr("x",n=>{let o=this.shadowRoot?.querySelector(`path[data-link-id="${n.id}"]`);if(!o)return 0;let s=o.getTotalLength();return o.getPointAtLength(s/2).x}).attr("y",n=>{let o=this.shadowRoot?.querySelector(`path[data-link-id="${n.id}"]`);if(!o)return 0;let s=o.getTotalLength();return o.getPointAtLength(s/2).y}).attr("text-anchor","middle"),this.resolveLinkLabelOverlaps(),this.container.selectAll(".link-group .linklabel").raise();}resolveLinkLabelOverlaps(){let n=Array.from(this.container.selectAll(".link-group .linklabel").nodes());if(n.length<2)return;let o=4,s=28,e=2,i=new Map,a=new Map;for(let u=0;u<o;u++){let g=false;for(let E=0;E<n.length;E++){let m=n[E];for(let p=E+1;p<n.length;p++){let l=n[p];if(!K2t(m,l))continue;let c=m.getBBox(),h=l.getBBox(),f=c.x+c.width/2,d=c.y+c.height/2,v=h.x+h.width/2,_=h.y+h.height/2,A=(c.width+h.width)/2-Math.abs(f-v),b=(c.height+h.height)/2-Math.abs(d-_);if(!(A<=0||b<=0)){if(b<=A){let T=(b+e)/2,M=d>=_?1:-1;this.applyLabelDisplacement(m,0,M*T,i,a,s),this.applyLabelDisplacement(l,0,-M*T,i,a,s);}else {let T=(A+e)/2,M=f>=v?1:-1;this.applyLabelDisplacement(m,M*T,0,i,a,s),this.applyLabelDisplacement(l,-M*T,0,i,a,s);}g=true;}}}if(!g)break}}applyLabelDisplacement(n,o,s,e,i,a){let u=e.get(n)||0,g=i.get(n)||0,E=u+o,m=g+s,p=Math.hypot(E,m);if(p>a){let f=a/p;E*=f,m*=f;}let l=ws.select(n),c=parseFloat(l.attr("x")||"0")+(E-u),h=parseFloat(l.attr("y")||"0")+(m-g);l.attr("x",c).attr("y",h),e.set(n,E),i.set(n,m);}fitViewportToContent(n=false){let o=this.svg?.node();if(!o||!this.zoomBehavior||this.userHasManuallyZoomed&&!this.isInitialRender&&!n)return;let s=this.calculateContentBounds();if(!s)return;let e=o.clientWidth||o.parentElement?.clientWidth||800,i=o.clientHeight||o.parentElement?.clientHeight||600,a=Er.VIEWBOX_PADDING*4,u=(e-a*2)/s.width,g=(i-a*2)/s.height,E=Math.min(u,g,1),[m,p]=this.zoomBehavior.scaleExtent(),l=Math.max(m,Math.min(p,E)),c=s.x+s.width/2,h=s.y+s.height/2,f=e/2-c*l,d=i/2-h*l,v=ws.zoomIdentity.translate(f,d).scale(l);this.isInitialRender?(this.svg.call(this.zoomBehavior.transform,v),this.isInitialRender=false):this.svg.transition().duration(300).ease(ws.easeCubicOut).call(this.zoomBehavior.transform,v),this.updateZoomControlStates();}resetViewToFitContent(){this.userHasManuallyZoomed=false,this.fitViewportToContent(true);}calculateContentBounds(){try{if(!this.currentLayout||!this.container)return null;let n=1/0,o=1/0,s=-1/0,e=-1/0,i=(c,h,f,d)=>{[c,h,f,d].every(Number.isFinite)&&(n=Math.min(n,c),s=Math.max(s,h),o=Math.min(o,f),e=Math.max(e,d));},a=this.currentLayout.nodes;a&&a.length>0&&a.forEach(c=>{if(this.isHiddenNode(c)||typeof c.x!="number"||typeof c.y!="number")return;let h=c.visualWidth??c.width??0,f=c.visualHeight??c.height??0,d=h/2,v=f/2;i(c.x-d,c.x+d,c.y-v,c.y+v);});let u=this,g=this.container.selectAll(".link-group path");g.empty()||g.each(function(c){if(!(c?.id&&u.isAlignmentEdge(c)))try{let h=this.getBBox();(h.width>0||h.height>0)&&i(h.x,h.x+h.width,h.y,h.y+h.height);}catch{}});let E=this.container.selectAll(".node, .error-node");E.empty()||E.each(function(c){if(!u.isHiddenNode(c))try{let h=this.getBBox();(h.width>0||h.height>0)&&i(h.x,h.x+h.width,h.y,h.y+h.height);}catch{}});let m=this.container.selectAll("text");m.empty()||m.each(function(c){if(!(c&&(typeof c.name=="string"||typeof c.id=="string")&&u.isHiddenNode(c)))try{let h=this.getBBox();(h.width>0||h.height>0)&&i(h.x-5,h.x+h.width+5,h.y-5,h.y+h.height+5);}catch{}});let p=this.container.selectAll(".group");return p.empty()||p.each(function(){try{let c=this.getBBox();(c.width>0||c.height>0)&&i(c.x,c.x+c.width,c.y,c.y+c.height);}catch{}}),n===1/0||o===1/0||s===-1/0||e===-1/0?(console.warn("Could not calculate content bounds - no valid elements found"),null):{x:n,y:o,width:s-n,height:e-o}}catch(n){return console.error("Error calculating content bounds:",n),null}}dispatchRelationsAvailableEvent(){let n=this.getAllRelations(),o=new CustomEvent("relations-available",{detail:{relations:n,count:n.length,timestamp:Date.now(),graphId:this.id||"unknown"},bubbles:true,cancelable:true});this.dispatchEvent(o);}getAllRelations(){if(!this.currentLayout?.links)return [];let n=new Set(this.currentLayout.links.filter(o=>!this.isAlignmentEdge(o)).map(o=>o.relName).filter(Boolean));return Array.from(n)}highlightRelation(n){return this.currentLayout?.links?(this.svgLinkGroups.filter(o=>o.relName===n&&!this.isAlignmentEdge(o)).selectAll("path").classed("highlighted",true),true):false}clearHighlightRelation(n){return this.currentLayout?.links?(this.svgLinkGroups.filter(o=>o.relName===n&&!this.isAlignmentEdge(o)).selectAll("path").classed("highlighted",false),true):false}highlightNodes(n){if(!this.currentLayout?.nodes||!this.svgNodes||!n||n.length===0)return false;let o=new Set(n),s=false;return this.svgNodes.each((e,i,a)=>{o.has(e.id)&&(ws.select(a[i]).classed("highlighted",true),s=true);}),s}highlightNodePairs(n,o={}){if(!this.currentLayout?.nodes||!this.svgNodes||!n||n.length===0)return false;let{showBadges:s=false}=o,e=new Set,i=new Set;n.forEach((u,g)=>{if(!Array.isArray(u)){console.warn(`highlightNodePairs: Pair at index ${g} is not an array, skipping`);return}if(u.length!==2){console.warn(`highlightNodePairs: Pair at index ${g} has ${u.length} elements (expected 2), skipping`);return}let[E,m]=u;E&&e.add(E),m&&i.add(m);});let a=false;return this.svgNodes.each((u,g,E)=>{let m=ws.select(E[g]);e.has(u.id)&&(m.classed("highlighted-first",true),a=true,s&&this.addHighlightBadge(m,u,"1","#007aff")),i.has(u.id)&&(m.classed("highlighted-second",true),a=true,s&&(e.has(u.id)?this.addHighlightBadge(m,u,"1,2","#9B59B6"):this.addHighlightBadge(m,u,"2","#ff3b30")));}),a}clearNodeHighlights(){return this.svgNodes?(this.svgNodes.classed("highlighted",false).classed("highlighted-first",false).classed("highlighted-second",false).selectAll(".highlight-badge, .highlight-badge-bg").remove(),true):false}addHighlightBadge(n,o,s,e){n.selectAll(".highlight-badge, .highlight-badge-bg").remove();let i=16,a=4,u=(o.width||0)/2-i/2-a,g=-(o.height||0)/2+i/2+a;n.append("circle").attr("class","highlight-badge-bg").attr("cx",u).attr("cy",g).attr("r",i/2).attr("fill",e),n.append("text").attr("class","highlight-badge").attr("x",u).attr("y",g).attr("dy","0.35em").text(s);}showRuntimeAlert(n,o){console.warn(`Runtime (WebCola) error when laying out an edge from ${n} to ${o}. You may have to click and drag these nodes slightly to un-stick layout.`);}getCSS(){return `
|
|
867
867
|
${this.getFontImports()}
|
|
868
868
|
:host {
|
|
869
869
|
display: block;
|
|
@@ -1476,7 +1476,7 @@ Expecting `+Jp.join(", ")+", got '"+(this.terminals_[lo]||lo)+"'":Pp="Parse erro
|
|
|
1476
1476
|
<button class="modal-button primary" data-action="ok">OK</button>
|
|
1477
1477
|
</div>
|
|
1478
1478
|
</div>
|
|
1479
|
-
`;let i=e.querySelector(".modal-input");e.addEventListener("click",u=>{let g=u.target;if(g.classList.contains("modal-overlay"))this.root.removeChild(e),s(null);else if(g.dataset.action==="cancel")this.root.removeChild(e),s(null);else if(g.dataset.action==="delete")this.root.removeChild(e),s("DELETE");else if(g.dataset.action==="ok"){let E=i.value;this.root.removeChild(e),s(E);}});let a=u=>{if(u.key==="Enter"){let g=i.value;this.root.removeChild(e),document.removeEventListener("keydown",a),s(g);}else u.key==="Escape"&&(this.root.removeChild(e),document.removeEventListener("keydown",a),s(null));};document.addEventListener("keydown",a),this.root.appendChild(e),i.focus(),i.select();})}async takeScreenshot(){let n=this.shadowRoot?.querySelector("#svg");if(!n){console.warn("No SVG element found for screenshot.");return}try{let o=n.cloneNode(!0),s=n.getAttribute("viewBox"),e=n.clientWidth||800,i=n.clientHeight||600;if(s){let h=s.split(/[\s,]+/).map(Number);h.length===4&&(e=h[2],i=h[3]),o.setAttribute("viewBox",s);}o.setAttribute("width",String(e)),o.setAttribute("height",String(i)),o.removeAttribute("preserveAspectRatio"),this.inlineComputedStyles(n,o),await this.convertImagesToBase64(o);let a=document.createElementNS("http://www.w3.org/2000/svg","rect");a.setAttribute("width","100%"),a.setAttribute("height","100%"),a.setAttribute("fill",this.getCanvasBackground()),o.insertBefore(a,o.firstChild);let g=new XMLSerializer().serializeToString(o),E=Er.SCREENSHOT_SCALE,m=await this.svgStringToPngBlob(g,e,i,E);if(!m){console.error("Failed to generate PNG blob from SVG.");return}let p=URL.createObjectURL(m),l=document.createElement("a");l.href=p;let c=new Date().toISOString().replace(/[:.]/g,"-").slice(0,19);l.download=`graph-screenshot-${c}.png`,document.body.appendChild(l),l.click(),document.body.removeChild(l),URL.revokeObjectURL(p);}catch(o){console.error("Screenshot failed:",o);}}inlineComputedStyles(n,o){let s=["font-family","font-size","font-weight","font-style","fill","stroke","stroke-width","stroke-dasharray","stroke-linejoin","opacity","text-anchor","dominant-baseline","paint-order","visibility","display"],e=n.children,i=o.children;if(n instanceof HTMLElement||n instanceof SVGElement){let u=getComputedStyle(n),g=o;for(let E of s){let m=u.getPropertyValue(E);m&&g.style.setProperty(E,m);}}let a=Math.min(e.length,i.length);for(let u=0;u<a;u++)this.inlineComputedStyles(e[u],i[u]);}async convertImagesToBase64(n){let o=n.querySelectorAll("image"),s=[];o.forEach(e=>{let i=e.getAttribute("href")||e.getAttributeNS("http://www.w3.org/1999/xlink","href");if(!i||i.startsWith("data:"))return;let a=this.fetchImageAsBase64(i).then(u=>{e.setAttribute("href",u),e.removeAttributeNS("http://www.w3.org/1999/xlink","href");}).catch(()=>{e.remove();});s.push(a);}),await Promise.all(s);}fetchImageAsBase64(n){return new Promise((o,s)=>{let e=new Image;e.crossOrigin="anonymous",e.onload=()=>{let i=document.createElement("canvas");i.width=e.naturalWidth,i.height=e.naturalHeight;let a=i.getContext("2d");if(!a){s(new Error("No canvas context"));return}a.drawImage(e,0,0),o(i.toDataURL("image/png"));},e.onerror=()=>s(new Error(`Failed to load: ${n}`)),e.src=n;})}svgStringToPngBlob(n,o,s,e){return new Promise(i=>{let a=new Blob([n],{type:"image/svg+xml;charset=utf-8"}),u=URL.createObjectURL(a),g=new Image;g.onload=()=>{let E=document.createElement("canvas");E.width=o*e,E.height=s*e;let m=E.getContext("2d");if(!m){URL.revokeObjectURL(u),i(null);return}m.scale(e,e),m.fillStyle=this.getCanvasBackground(),m.fillRect(0,0,o,s),m.drawImage(g,0,0,o,s),E.toBlob(p=>{URL.revokeObjectURL(u),i(p);},"image/png");},g.onerror=()=>{URL.revokeObjectURL(u),i(null);},g.src=u;})}disconnectedCallback(){this.dispose();}dispose(){this.detachInputModeListeners(),this.deactivateInputMode(),this.svg&&(this.svg.on(".zoom",null),this.svg.selectAll("*").remove()),this.container&&this.container.selectAll("*").remove(),this.svgNodes&&(this.svgNodes.on(".drag",null),this.svgNodes.on(".cnd",null)),this.colaLayout&&(typeof this.colaLayout.stop=="function"&&this.colaLayout.stop(),this.colaLayout.on("tick",null),this.colaLayout.on("end",null)),this.currentLayout=null,this.colaLayout=null,this.svgNodes=null,this.svgLinkGroups=null,this.svgGroups=null,this.svgGroupLabels=null,this.svgGroupLabelBgs=null,this.zoomBehavior=null,this.storedTransform=null,this.dragStartPositions.clear(),this.cleanupEdgeCreation(),this.textMeasurementCanvas&&(this.textMeasurementCanvas=null);}getMemoryStats(){return {nodeCount:this.currentLayout?.nodes?.length||0,edgeCount:this.currentLayout?.links?.length||0,groupCount:this.currentLayout?.groups?.length||0,constraintCount:this.currentLayout?.constraints?.length||0,hasActiveLayout:!!this.colaLayout}}};Er.DEFAULT_SVG_WIDTH=800,Er.DEFAULT_SVG_HEIGHT=600,Er.SMALL_IMG_SCALE_FACTOR=.3,Er.NODE_BORDER_RADIUS=6,Er.NODE_STROKE_WIDTH=1.5,Er.DEFAULT_CANVAS_BG="#fffff8",Er.DARK_THEME={"--cnd-canvas-bg":"#1e1e1e","--cnd-label-text":"#e6e6e6","--cnd-edge-color":"#7d828b","--cnd-node-border":"#8b919b","--cnd-edge-highlight":"#f0f0f0","--cnd-inferred-highlight":"#9aa0a8","--cnd-group-fill":"rgba(140, 140, 140, 0.12)","--cnd-group-stroke":"#7a7f87","--cnd-loading-bg":"rgba(30, 32, 37, 0.95)","--cnd-loading-text":"#c9cdd4","--cnd-loading-dot":"#5dade2","--cnd-panel-bg":"#23262d","--cnd-panel-text":"#e6e6e6","--cnd-panel-text-muted":"#a7adb8","--cnd-panel-border":"#3a3f49","--cnd-toolbar-bg":"rgba(30, 32, 37, 0.95)","--cnd-control-bg":"#2b2f37","--cnd-control-bg-hover":"#343a44","--cnd-control-border":"#3a3f49"},Er.DEFAULT_FONT_FAMILY="'Atkinson Hyperlegible', system-ui, -apple-system, sans-serif",Er.DEFAULT_FONT_SIZE=10,Er.MIN_FONT_SIZE=6,Er.MAX_FONT_SIZE=16,Er.TEXT_PADDING=8,Er.SCREENSHOT_SCALE=3,Er.DISCONNECTED_NODE_PREFIX="_d_",Er.GROUP_BORDER_RADIUS=8,Er.GROUP_FILL_OPACITY=.1,Er.GROUP_STROKE_OPACITY=.4,Er.GROUP_STROKE_WIDTH=1.5,Er.GROUP_LABEL_PADDING=20,Er.GROUP_LABEL_PILL_PADDING_X=8,Er.GROUP_LABEL_PILL_PADDING_Y=3,Er.GROUP_LABEL_PILL_OFFSET_Y=4,Er.DEFAULT_GROUP_COMPACTNESS=1e-5,Er.EDGE_ROUTE_MARGIN_DIVISOR=3,Er.CURVATURE_BASE_MULTIPLIER=.15,Er.MIN_EDGE_DISTANCE=10,Er.MAX_EDGE_OFFSET_RATIO=.35,Er.MAX_EDGE_CURVATURE_RATIO=.6,Er.SELF_LOOP_CURVATURE_SCALE=.2,Er.VIEWBOX_PADDING=10,Er.EDGE_CLEARANCE_PX=6,Er.EDGE_STUB_LENGTH_PX=10,Er.MAX_ROUTER_OBSTACLES=24,Er.MAX_CROSSING_OPTIMIZATION_PASSES=3,Er.CROSSING_OPTIMIZATION_EDGE_THRESHOLD=50,Er.MAX_CROSSING_OPTIMIZATION_BUDGET_MS=30,Er.CROSSING_NUDGE_DISTANCE=12,Er.PORT_MARGIN_FRACTION=.15,Er.MIN_PORT_PERIMETER_SPACING=10,Er.MIN_ABSOLUTE_PORT_MARGIN_PX=2,Er.GROUP_VISUAL_MARGIN_PX=10,Er.INITIAL_UNCONSTRAINED_ITERATIONS=10,Er.INITIAL_USER_CONSTRAINT_ITERATIONS=50,Er.INITIAL_ALL_CONSTRAINTS_ITERATIONS=200,Er.GRID_SNAP_ITERATIONS=1,Er.LOADING_INDICATOR_DELAY_MS=180,Er.MORPH_BASE_EXIT_DURATION_MS=1100,Er.MORPH_BASE_ENTER_DURATION_MS=1e3,Er.MORPH_BASE_ENTER_DELAY_MS=200;D1=Er;typeof customElements<"u"&&typeof HTMLElement<"u"&&customElements.define("webcola-cnd-graph",D1);});var kK;exports.JSONDataInstance=void 0;exports.DataInstanceNormalizer=void 0;var VA=Tr(()=>{kK=mi(l2()),exports.JSONDataInstance=class t{constructor(r,n={}){this.atoms=[];this.relations=[];this.types=[];this.errors=[];this.eventListeners=new Map;try{let o=typeof r=="string"?JSON.parse(r):r;if(!o||typeof o!="object")throw new Error("Invalid data: expected object with atoms and relations");if(!Array.isArray(o.atoms))throw new Error("Invalid data: atoms must be an array");if(!Array.isArray(o.relations))throw new Error("Invalid data: relations must be an array");let s=exports.DataInstanceNormalizer.normalize(o,n);this.atoms=s.atoms,this.relations=s.relations,this.types=s.types,this.errors=s.errors;}catch(o){throw new Error(`Failed to create JSONDataInstance: ${o instanceof Error?o.message:String(o)}`)}}addEventListener(r,n){this.eventListeners.has(r)||this.eventListeners.set(r,new Set),this.eventListeners.get(r).add(n);}removeEventListener(r,n){let o=this.eventListeners.get(r);o&&o.delete(n);}emitEvent(r){let n=this.eventListeners.get(r.type);n&&n.forEach(o=>{try{o(r);}catch(s){console.error("Error in data instance event listener:",s);}});}isAtomBuiltin(r){return false}getAtomType(r){let n=this.atoms.find(s=>s.id===r);if(!n)throw new Error(`Atom with ID '${r}' not found`);let o=this.types.find(s=>s.id===n.type);if(!o)throw new Error(`Type '${n.type}' not found for atom '${r}'`);return o}getTypes(){return this.types}getAtoms(){return this.atoms}getRelations(){return this.relations}getTopLevelTypeId(r){let n=this.types.find(o=>o.id===r);return n&&n.types.length>0?n.types[n.types.length-1]:r}typeIsOfType(r,n){let o=this.types.find(s=>s.id===r);return o?o.types.includes(n):r===n}applyProjections(r){if(r.length===0)return this.clone();let n={};for(let g of r){let E=this.atoms.find(p=>p.id===g);if(!E)throw new Error(`Cannot project over atom '${g}': atom not found`);let m=this.getTopLevelTypeId(E.type);if(n[m])throw new Error(`Cannot project over '${g}' and '${n[m]}'. Both are of type '${m}'`);n[m]=g;}let o=Object.keys(n),s=Object.values(n),e=new Set(s),i=this.types.map(g=>{let E=o.some(m=>this.typeIsOfType(g.id,m));return {...g,atoms:E?[]:g.atoms.filter(m=>!e.has(m.id))}}),a=this.relations.map(g=>{if(!g.types.some(c=>o.some(h=>this.typeIsOfType(c,h))))return g;let m=[];g.types.forEach((c,h)=>{o.some(f=>this.typeIsOfType(c,f))&&m.push(h);});let p=g.tuples.filter(c=>m.every(h=>{let f=c.atoms[h],d=g.types[h],v=o.find(A=>this.typeIsOfType(d,A));if(!v)return true;let _=n[v];return f===_})).map(c=>({atoms:c.atoms.filter((h,f)=>!m.includes(f)),types:c.types.filter((h,f)=>!m.includes(f))})).filter(c=>c.atoms.length>0),l=g.types.filter((c,h)=>!m.includes(h));return {...g,types:l,tuples:p}}).filter(g=>g.tuples.length>0||g.types.length>0),u=this.atoms.filter(g=>{let E=this.getTopLevelTypeId(g.type);return !o.includes(E)});return new t({atoms:u,relations:a,types:i})}generateGraph(r=false,n=false){let o=new kK.Graph({directed:true,multigraph:true});if(this.atoms.forEach(s=>{o.setNode(s.id,{id:s.id,label:s.label,type:s.type,isBuiltin:this.isAtomBuiltin(s)});}),this.relations.forEach(s=>{s.tuples.forEach((e,i)=>{if(e.atoms.length>=2){let a=e.atoms[0],u=e.atoms[e.atoms.length-1],g=e.atoms.slice(1,-1),E=s.name;if(g.length>0){let p=g.map(l=>{let c=this.atoms.find(h=>h.id===l);return c?c.label:l});E=`${s.name}[${p.join(", ")}]`;}let m=`${s.id}_${i}`;o.setEdge(a,u,E,m);}else if(e.atoms.length===1){let a=e.atoms[0],u=`${s.id}_${i}`;o.setEdge(a,a,s.name,u);}});}),r||n){let s=[];o.nodes().forEach(e=>{let i=o.inEdges(e)||[],a=o.outEdges(e)||[];if(i.length===0&&a.length===0){let E=o.node(e)?.isBuiltin||false;(r||E&&n)&&s.push(e);}}),s.forEach(e=>{o.removeNode(e);});}return o}addAtom(r){if(this.atoms.some(o=>o.id===r.id))throw new Error(`Atom with ID '${r.id}' already exists`);this.atoms.push(r);let n=this.types.find(o=>o.id===r.type);n||(n={id:r.type,types:[r.type],atoms:[],isBuiltin:false},this.types.push(n)),n.atoms.push(r),this.emitEvent({type:"atomAdded",data:{atom:r}});}addRelationTuple(r,n){for(let s of n.atoms)if(!this.atoms.some(e=>e.id===s))throw new Error(`Cannot add tuple: referenced atom '${s}' does not exist`);let o=this.relations.find(s=>s.id===r||s.name===r);if(!o)o={id:r,name:r,types:[...n.types],tuples:[]},this.relations.push(o);else {let s=new Set(o.types);for(let e of n.types)s.has(e)||o.types.push(e);}o.tuples.push(n),this.emitEvent({type:"relationTupleAdded",data:{relationId:r,tuple:n}});}removeAtom(r){let n=this.atoms.findIndex(e=>e.id===r);if(n===-1)throw new Error(`Cannot remove atom: atom with ID '${r}' not found`);let o=this.atoms[n];this.atoms.splice(n,1);let s=this.types.find(e=>e.id===o.type);s&&(s.atoms=s.atoms.filter(e=>e.id!==r));for(let e of this.relations)e.tuples=e.tuples.filter(i=>!i.atoms.includes(r));this.emitEvent({type:"atomRemoved",data:{atomId:r}});}removeRelationTuple(r,n){let o=this.relations.find(i=>i.id===r||i.name===r);if(!o)throw new Error(`Cannot remove tuple: relation '${r}' not found`);let s=(i,a)=>i.atoms.length!==a.atoms.length?false:i.atoms.every((u,g)=>u===a.atoms[g]),e=o.tuples.length;if(o.tuples=o.tuples.filter(i=>!s(i,n)),o.tuples.length===e)throw new Error(`Tuple not found in relation '${r}'`);this.emitEvent({type:"relationTupleRemoved",data:{relationId:r,tuple:n}});}reify(){return {atoms:[...this.atoms],relations:this.relations.map(r=>({...r,tuples:[...r.tuples]})),types:this.types.map(r=>({...r,atoms:[...r.atoms]}))}}addFromDataInstance(r,n){if(!r)return false;let o=new Map;return r.getAtoms().forEach(s=>{let e=this.isAtomBuiltin(s);if(n&&e){let u=this.atoms.find(g=>g.type===s.type&&g.label===s.label);if(u){o.set(s.id,u.id);return}}let i=`atom_${this.atoms.length+1}`;o.set(s.id,i);let a={...s,id:i};this.addAtom(a);}),r.getRelations().forEach(s=>{let e=s.tuples.map(a=>({atoms:a.atoms.map(u=>o.get(u)||u),types:a.types})),i=this.relations.find(a=>a.id===s.id||a.name===s.name);if(i){let a=new Set(i.tuples.map(u=>JSON.stringify(u)));e.forEach(u=>{let g=JSON.stringify(u);a.has(g)||(i.tuples.push(u),a.add(g));});}else this.relations.push({...s,tuples:e});}),r.getTypes().forEach(s=>{let e=this.types.find(i=>i.id===s.id);if(!e)this.types.push({...s,atoms:s.atoms.map(i=>({...i,id:o.get(i.id)||i.id}))});else {let i=new Set(e.atoms.map(a=>a.id));s.atoms.forEach(a=>{let u=o.get(a.id)||a.id;i.has(u)||(e.atoms.push({...a,id:u}),i.add(u));});}}),true}getErrors(){return [...this.errors]}isValid(){return this.errors.length===0}getStatistics(){return {atomCount:this.atoms.length,relationCount:this.relations.length,typeCount:this.types.length,tupleCount:this.relations.reduce((r,n)=>r+n.tuples.length,0),errorCount:this.errors.length,hasBuiltinTypes:this.types.some(r=>r.isBuiltin)}}clone(){return new t(this.reify())}},exports.DataInstanceNormalizer=class t{static mergeRelations(r){let n=new Map;for(let o of r){let s=n.get(o.name);if(s){let e=new Set(s.tuples.map(a=>JSON.stringify(a)));for(let a of o.tuples){let u=JSON.stringify(a);e.has(u)||(s.tuples.push(a),e.add(u));}let i=new Set(s.types);for(let a of o.types)i.has(a)||(s.types.push(a),i.add(a));}else n.set(o.name,{id:o.id||o.name,name:o.name,types:[...o.types],tuples:[...o.tuples]});}return Array.from(n.values())}static inferTypes(r){let n=new Map;for(let o of r)n.has(o.type)||n.set(o.type,{id:o.type,types:[o.type],atoms:[],isBuiltin:t.isBuiltinType(o.type)}),n.get(o.type).atoms.push(o);return Array.from(n.values())}static isBuiltinType(r){return new Set(["String","Int","Bool","seq/Int","univ","none","Entity","Object","Node","Edge","Atom"]).has(r)}static deduplicateAtoms(r){let n=new Map,o=new Set;for(let s of r)n.has(s.id)?o.add(s.id):n.set(s.id,s);return o.size>0&&console.warn(`Found duplicate atoms with IDs: ${Array.from(o).join(", ")}`),Array.from(n.values())}static validateReferences(r,n){let o=new Set(r.map(e=>e.id)),s=[];for(let e of n)for(let i=0;i<e.tuples.length;i++){let a=e.tuples[i];for(let u=0;u<a.atoms.length;u++){let g=a.atoms[u];o.has(g)||s.push(`Relation "${e.name}" tuple ${i} position ${u}: references unknown atom "${g}"`);}}return {isValid:s.length===0,errors:s}}static normalize(r,n={}){let o={mergeRelations:true,inferTypes:true,validateReferences:true,deduplicateAtoms:true,...n},s=r.atoms||[],e=r.relations||[],i=r.types||[],a=[];if(o.deduplicateAtoms&&s.length>0){let u=s.length;s=this.deduplicateAtoms(s),s.length<u&&a.push(`Removed ${u-s.length} duplicate atoms`);}if(o.mergeRelations&&e.length>0){let u=e.length;e=this.mergeRelations(e),e.length<u&&a.push(`Merged ${u-e.length} duplicate relations`);}if(o.inferTypes&&i.length===0&&s.length>0&&(i=this.inferTypes(s),a.push(`Inferred ${i.length} types from atoms`)),o.validateReferences){let u=this.validateReferences(s,e);a.push(...u.errors);}return {atoms:s,relations:e,types:i,errors:a}}};});var FK={};px(FK,{StructuredInputGraph:()=>exports.StructuredInputGraph});var MK;exports.StructuredInputGraph=void 0;var YA=Tr(()=>{TE();VA();R3();cm();d2();MK=window.d3v4||window.d3,exports.StructuredInputGraph=class extends D1{constructor(n){super(true);this.evaluator=null;this.layoutInstance=null;this.cndSpecString="";this.controlsContainer=null;this.customTypes=new Set;this.relationAtomPositions=["",""];this.currentConstraintError=null;this.selectedNodeId=null;this._activePopover=null;let o=n||new exports.JSONDataInstance({atoms:[],relations:[]});this.setDataInstance(o),this.initializeStructuredInput(),this.addEventListener("edge-creation-requested",this.handleEdgeCreationRequest.bind(this)),this.addEventListener("edge-modification-requested",this.handleEdgeModificationRequest.bind(this)),this.addEventListener("edge-reconnection-requested",this.handleEdgeReconnectionRequest.bind(this));}static get observedAttributes(){return ["cnd-spec","data-instance","show-export","theme","background"]}attributeChangedCallback(n,o,s){if(o!==s)switch(n){case "cnd-spec":this.parseCnDSpec(s);break;case "data-instance":this.updateDataInstance(s);break;case "show-export":break;case "theme":case "background":super.attributeChangedCallback(n,o,s);break}}initializeStructuredInput(){requestAnimationFrame(()=>{this.createControlsInterface();});}createControlsInterface(){if(!this.shadowRoot)return;let n=document.createElement("style");n.textContent=this.getControlsCSS(),this.shadowRoot.appendChild(n);let o=this.shadowRoot.querySelector("#graph-toolbar");o&&(this.controlsContainer=document.createElement("div"),this.controlsContainer.style.display="contents",this.controlsContainer.innerHTML=this.getControlsHTML(),o.appendChild(this.controlsContainer)),this.bindControlEvents(),this.setupCanvasInteractions();}getControlsHTML(){return `
|
|
1479
|
+
`;let i=e.querySelector(".modal-input");e.addEventListener("click",u=>{let g=u.target;if(g.classList.contains("modal-overlay"))this.root.removeChild(e),s(null);else if(g.dataset.action==="cancel")this.root.removeChild(e),s(null);else if(g.dataset.action==="delete")this.root.removeChild(e),s("DELETE");else if(g.dataset.action==="ok"){let E=i.value;this.root.removeChild(e),s(E);}});let a=u=>{if(u.key==="Enter"){let g=i.value;this.root.removeChild(e),document.removeEventListener("keydown",a),s(g);}else u.key==="Escape"&&(this.root.removeChild(e),document.removeEventListener("keydown",a),s(null));};document.addEventListener("keydown",a),this.root.appendChild(e),i.focus(),i.select();})}async takeScreenshot(){let n=this.shadowRoot?.querySelector("#svg");if(!n){console.warn("No SVG element found for screenshot.");return}try{let o=n.cloneNode(!0),s=n.getAttribute("viewBox"),e=n.clientWidth||800,i=n.clientHeight||600;if(s){let h=s.split(/[\s,]+/).map(Number);h.length===4&&(e=h[2],i=h[3]),o.setAttribute("viewBox",s);}o.setAttribute("width",String(e)),o.setAttribute("height",String(i)),o.removeAttribute("preserveAspectRatio"),this.inlineComputedStyles(n,o),await this.convertImagesToBase64(o);let a=document.createElementNS("http://www.w3.org/2000/svg","rect");a.setAttribute("width","100%"),a.setAttribute("height","100%"),a.setAttribute("fill",this.getCanvasBackground()),o.insertBefore(a,o.firstChild);let g=new XMLSerializer().serializeToString(o),E=Er.SCREENSHOT_SCALE,m=await this.svgStringToPngBlob(g,e,i,E);if(!m){console.error("Failed to generate PNG blob from SVG.");return}let p=URL.createObjectURL(m),l=document.createElement("a");l.href=p;let c=new Date().toISOString().replace(/[:.]/g,"-").slice(0,19);l.download=`graph-screenshot-${c}.png`,document.body.appendChild(l),l.click(),document.body.removeChild(l),URL.revokeObjectURL(p);}catch(o){console.error("Screenshot failed:",o);}}inlineComputedStyles(n,o){let s=["font-family","font-size","font-weight","font-style","fill","stroke","stroke-width","stroke-dasharray","stroke-linejoin","opacity","text-anchor","dominant-baseline","paint-order","visibility","display"],e=n.children,i=o.children;if(n instanceof HTMLElement||n instanceof SVGElement){let u=getComputedStyle(n),g=o;for(let E of s){let m=u.getPropertyValue(E);m&&g.style.setProperty(E,m);}}let a=Math.min(e.length,i.length);for(let u=0;u<a;u++)this.inlineComputedStyles(e[u],i[u]);}async convertImagesToBase64(n){let o=n.querySelectorAll("image"),s=[];o.forEach(e=>{let i=e.getAttribute("href")||e.getAttributeNS("http://www.w3.org/1999/xlink","href");if(!i||i.startsWith("data:"))return;let a=this.fetchImageAsBase64(i).then(u=>{e.setAttribute("href",u),e.removeAttributeNS("http://www.w3.org/1999/xlink","href");}).catch(()=>{e.remove();});s.push(a);}),await Promise.all(s);}fetchImageAsBase64(n){return new Promise((o,s)=>{let e=new Image;e.crossOrigin="anonymous",e.onload=()=>{let i=document.createElement("canvas");i.width=e.naturalWidth,i.height=e.naturalHeight;let a=i.getContext("2d");if(!a){s(new Error("No canvas context"));return}a.drawImage(e,0,0),o(i.toDataURL("image/png"));},e.onerror=()=>s(new Error(`Failed to load: ${n}`)),e.src=n;})}svgStringToPngBlob(n,o,s,e){return new Promise(i=>{let a=new Blob([n],{type:"image/svg+xml;charset=utf-8"}),u=URL.createObjectURL(a),g=new Image;g.onload=()=>{let E=document.createElement("canvas");E.width=o*e,E.height=s*e;let m=E.getContext("2d");if(!m){URL.revokeObjectURL(u),i(null);return}m.scale(e,e),m.fillStyle=this.getCanvasBackground(),m.fillRect(0,0,o,s),m.drawImage(g,0,0,o,s),E.toBlob(p=>{URL.revokeObjectURL(u),i(p);},"image/png");},g.onerror=()=>{URL.revokeObjectURL(u),i(null);},g.src=u;})}disconnectedCallback(){try{this.dispose();}catch(n){console.warn("WebColaCnDGraph: error during disconnect cleanup",n);}}dispose(){this.detachInputModeListeners(),this.deactivateInputMode(),this.svg&&(this.svg.on(".zoom",null),this.svg.selectAll("*").remove()),this.container&&this.container.selectAll("*").remove(),this.svgNodes&&(this.svgNodes.on(".drag",null),this.svgNodes.on(".cnd",null)),this.colaLayout&&(typeof this.colaLayout.stop=="function"&&this.colaLayout.stop(),this.colaLayout.on("tick",null),this.colaLayout.on("end",null)),this.currentLayout=null,this.colaLayout=null,this.svgNodes=null,this.svgLinkGroups=null,this.svgGroups=null,this.svgGroupLabels=null,this.svgGroupLabelBgs=null,this.zoomBehavior=null,this.storedTransform=null,this.dragStartPositions.clear(),this.cleanupEdgeCreation(),this.textMeasurementCanvas&&(this.textMeasurementCanvas=null);}getMemoryStats(){return {nodeCount:this.currentLayout?.nodes?.length||0,edgeCount:this.currentLayout?.links?.length||0,groupCount:this.currentLayout?.groups?.length||0,constraintCount:this.currentLayout?.constraints?.length||0,hasActiveLayout:!!this.colaLayout}}};Er.DEFAULT_SVG_WIDTH=800,Er.DEFAULT_SVG_HEIGHT=600,Er.SMALL_IMG_SCALE_FACTOR=.3,Er.NODE_BORDER_RADIUS=6,Er.NODE_STROKE_WIDTH=1.5,Er.DEFAULT_CANVAS_BG="#fffff8",Er.DARK_THEME={"--cnd-canvas-bg":"#1e1e1e","--cnd-label-text":"#e6e6e6","--cnd-edge-color":"#7d828b","--cnd-node-border":"#8b919b","--cnd-edge-highlight":"#f0f0f0","--cnd-inferred-highlight":"#9aa0a8","--cnd-group-fill":"rgba(140, 140, 140, 0.12)","--cnd-group-stroke":"#7a7f87","--cnd-loading-bg":"rgba(30, 32, 37, 0.95)","--cnd-loading-text":"#c9cdd4","--cnd-loading-dot":"#5dade2","--cnd-panel-bg":"#23262d","--cnd-panel-text":"#e6e6e6","--cnd-panel-text-muted":"#a7adb8","--cnd-panel-border":"#3a3f49","--cnd-toolbar-bg":"rgba(30, 32, 37, 0.95)","--cnd-control-bg":"#2b2f37","--cnd-control-bg-hover":"#343a44","--cnd-control-border":"#3a3f49"},Er.DEFAULT_FONT_FAMILY="'Atkinson Hyperlegible', system-ui, -apple-system, sans-serif",Er.DEFAULT_FONT_SIZE=10,Er.MIN_FONT_SIZE=6,Er.MAX_FONT_SIZE=16,Er.TEXT_PADDING=8,Er.SCREENSHOT_SCALE=3,Er.DISCONNECTED_NODE_PREFIX="_d_",Er.GROUP_BORDER_RADIUS=8,Er.GROUP_FILL_OPACITY=.1,Er.GROUP_STROKE_OPACITY=.4,Er.GROUP_STROKE_WIDTH=1.5,Er.GROUP_LABEL_PADDING=20,Er.GROUP_LABEL_PILL_PADDING_X=8,Er.GROUP_LABEL_PILL_PADDING_Y=3,Er.GROUP_LABEL_PILL_OFFSET_Y=4,Er.DEFAULT_GROUP_COMPACTNESS=1e-5,Er.EDGE_ROUTE_MARGIN_DIVISOR=3,Er.CURVATURE_BASE_MULTIPLIER=.15,Er.MIN_EDGE_DISTANCE=10,Er.MAX_EDGE_OFFSET_RATIO=.35,Er.MAX_EDGE_CURVATURE_RATIO=.6,Er.SELF_LOOP_CURVATURE_SCALE=.2,Er.VIEWBOX_PADDING=10,Er.EDGE_CLEARANCE_PX=6,Er.EDGE_STUB_LENGTH_PX=10,Er.MAX_ROUTER_OBSTACLES=24,Er.MAX_CROSSING_OPTIMIZATION_PASSES=3,Er.CROSSING_OPTIMIZATION_EDGE_THRESHOLD=50,Er.MAX_CROSSING_OPTIMIZATION_BUDGET_MS=30,Er.CROSSING_NUDGE_DISTANCE=12,Er.PORT_MARGIN_FRACTION=.15,Er.MIN_PORT_PERIMETER_SPACING=10,Er.MIN_ABSOLUTE_PORT_MARGIN_PX=2,Er.GROUP_VISUAL_MARGIN_PX=10,Er.INITIAL_UNCONSTRAINED_ITERATIONS=10,Er.INITIAL_USER_CONSTRAINT_ITERATIONS=50,Er.INITIAL_ALL_CONSTRAINTS_ITERATIONS=200,Er.GRID_SNAP_ITERATIONS=1,Er.LOADING_INDICATOR_DELAY_MS=180,Er.MORPH_BASE_EXIT_DURATION_MS=1100,Er.MORPH_BASE_ENTER_DURATION_MS=1e3,Er.MORPH_BASE_ENTER_DELAY_MS=200;D1=Er;typeof customElements<"u"&&typeof HTMLElement<"u"&&customElements.define("webcola-cnd-graph",D1);});var kK;exports.JSONDataInstance=void 0;exports.DataInstanceNormalizer=void 0;var VA=Tr(()=>{kK=mi(l2()),exports.JSONDataInstance=class t{constructor(r,n={}){this.atoms=[];this.relations=[];this.types=[];this.errors=[];this.eventListeners=new Map;try{let o=typeof r=="string"?JSON.parse(r):r;if(!o||typeof o!="object")throw new Error("Invalid data: expected object with atoms and relations");if(!Array.isArray(o.atoms))throw new Error("Invalid data: atoms must be an array");if(!Array.isArray(o.relations))throw new Error("Invalid data: relations must be an array");let s=exports.DataInstanceNormalizer.normalize(o,n);this.atoms=s.atoms,this.relations=s.relations,this.types=s.types,this.errors=s.errors;}catch(o){throw new Error(`Failed to create JSONDataInstance: ${o instanceof Error?o.message:String(o)}`)}}addEventListener(r,n){this.eventListeners.has(r)||this.eventListeners.set(r,new Set),this.eventListeners.get(r).add(n);}removeEventListener(r,n){let o=this.eventListeners.get(r);o&&o.delete(n);}emitEvent(r){let n=this.eventListeners.get(r.type);n&&n.forEach(o=>{try{o(r);}catch(s){console.error("Error in data instance event listener:",s);}});}isAtomBuiltin(r){return false}getAtomType(r){let n=this.atoms.find(s=>s.id===r);if(!n)throw new Error(`Atom with ID '${r}' not found`);let o=this.types.find(s=>s.id===n.type);if(!o)throw new Error(`Type '${n.type}' not found for atom '${r}'`);return o}getTypes(){return this.types}getAtoms(){return this.atoms}getRelations(){return this.relations}getTopLevelTypeId(r){let n=this.types.find(o=>o.id===r);return n&&n.types.length>0?n.types[n.types.length-1]:r}typeIsOfType(r,n){let o=this.types.find(s=>s.id===r);return o?o.types.includes(n):r===n}applyProjections(r){if(r.length===0)return this.clone();let n={};for(let g of r){let E=this.atoms.find(p=>p.id===g);if(!E)throw new Error(`Cannot project over atom '${g}': atom not found`);let m=this.getTopLevelTypeId(E.type);if(n[m])throw new Error(`Cannot project over '${g}' and '${n[m]}'. Both are of type '${m}'`);n[m]=g;}let o=Object.keys(n),s=Object.values(n),e=new Set(s),i=this.types.map(g=>{let E=o.some(m=>this.typeIsOfType(g.id,m));return {...g,atoms:E?[]:g.atoms.filter(m=>!e.has(m.id))}}),a=this.relations.map(g=>{if(!g.types.some(c=>o.some(h=>this.typeIsOfType(c,h))))return g;let m=[];g.types.forEach((c,h)=>{o.some(f=>this.typeIsOfType(c,f))&&m.push(h);});let p=g.tuples.filter(c=>m.every(h=>{let f=c.atoms[h],d=g.types[h],v=o.find(A=>this.typeIsOfType(d,A));if(!v)return true;let _=n[v];return f===_})).map(c=>({atoms:c.atoms.filter((h,f)=>!m.includes(f)),types:c.types.filter((h,f)=>!m.includes(f))})).filter(c=>c.atoms.length>0),l=g.types.filter((c,h)=>!m.includes(h));return {...g,types:l,tuples:p}}).filter(g=>g.tuples.length>0||g.types.length>0),u=this.atoms.filter(g=>{let E=this.getTopLevelTypeId(g.type);return !o.includes(E)});return new t({atoms:u,relations:a,types:i})}generateGraph(r=false,n=false){let o=new kK.Graph({directed:true,multigraph:true});if(this.atoms.forEach(s=>{o.setNode(s.id,{id:s.id,label:s.label,type:s.type,isBuiltin:this.isAtomBuiltin(s)});}),this.relations.forEach(s=>{s.tuples.forEach((e,i)=>{if(e.atoms.length>=2){let a=e.atoms[0],u=e.atoms[e.atoms.length-1],g=e.atoms.slice(1,-1),E=s.name;if(g.length>0){let p=g.map(l=>{let c=this.atoms.find(h=>h.id===l);return c?c.label:l});E=`${s.name}[${p.join(", ")}]`;}let m=`${s.id}_${i}`;o.setEdge(a,u,E,m);}else if(e.atoms.length===1){let a=e.atoms[0],u=`${s.id}_${i}`;o.setEdge(a,a,s.name,u);}});}),r||n){let s=[];o.nodes().forEach(e=>{let i=o.inEdges(e)||[],a=o.outEdges(e)||[];if(i.length===0&&a.length===0){let E=o.node(e)?.isBuiltin||false;(r||E&&n)&&s.push(e);}}),s.forEach(e=>{o.removeNode(e);});}return o}addAtom(r){if(this.atoms.some(o=>o.id===r.id))throw new Error(`Atom with ID '${r.id}' already exists`);this.atoms.push(r);let n=this.types.find(o=>o.id===r.type);n||(n={id:r.type,types:[r.type],atoms:[],isBuiltin:false},this.types.push(n)),n.atoms.push(r),this.emitEvent({type:"atomAdded",data:{atom:r}});}addRelationTuple(r,n){for(let s of n.atoms)if(!this.atoms.some(e=>e.id===s))throw new Error(`Cannot add tuple: referenced atom '${s}' does not exist`);let o=this.relations.find(s=>s.id===r||s.name===r);if(!o)o={id:r,name:r,types:[...n.types],tuples:[]},this.relations.push(o);else {let s=new Set(o.types);for(let e of n.types)s.has(e)||o.types.push(e);}o.tuples.push(n),this.emitEvent({type:"relationTupleAdded",data:{relationId:r,tuple:n}});}removeAtom(r){let n=this.atoms.findIndex(e=>e.id===r);if(n===-1)throw new Error(`Cannot remove atom: atom with ID '${r}' not found`);let o=this.atoms[n];this.atoms.splice(n,1);let s=this.types.find(e=>e.id===o.type);s&&(s.atoms=s.atoms.filter(e=>e.id!==r));for(let e of this.relations)e.tuples=e.tuples.filter(i=>!i.atoms.includes(r));this.emitEvent({type:"atomRemoved",data:{atomId:r}});}removeRelationTuple(r,n){let o=this.relations.find(i=>i.id===r||i.name===r);if(!o)throw new Error(`Cannot remove tuple: relation '${r}' not found`);let s=(i,a)=>i.atoms.length!==a.atoms.length?false:i.atoms.every((u,g)=>u===a.atoms[g]),e=o.tuples.length;if(o.tuples=o.tuples.filter(i=>!s(i,n)),o.tuples.length===e)throw new Error(`Tuple not found in relation '${r}'`);this.emitEvent({type:"relationTupleRemoved",data:{relationId:r,tuple:n}});}reify(){return {atoms:[...this.atoms],relations:this.relations.map(r=>({...r,tuples:[...r.tuples]})),types:this.types.map(r=>({...r,atoms:[...r.atoms]}))}}addFromDataInstance(r,n){if(!r)return false;let o=new Map;return r.getAtoms().forEach(s=>{let e=this.isAtomBuiltin(s);if(n&&e){let u=this.atoms.find(g=>g.type===s.type&&g.label===s.label);if(u){o.set(s.id,u.id);return}}let i=`atom_${this.atoms.length+1}`;o.set(s.id,i);let a={...s,id:i};this.addAtom(a);}),r.getRelations().forEach(s=>{let e=s.tuples.map(a=>({atoms:a.atoms.map(u=>o.get(u)||u),types:a.types})),i=this.relations.find(a=>a.id===s.id||a.name===s.name);if(i){let a=new Set(i.tuples.map(u=>JSON.stringify(u)));e.forEach(u=>{let g=JSON.stringify(u);a.has(g)||(i.tuples.push(u),a.add(g));});}else this.relations.push({...s,tuples:e});}),r.getTypes().forEach(s=>{let e=this.types.find(i=>i.id===s.id);if(!e)this.types.push({...s,atoms:s.atoms.map(i=>({...i,id:o.get(i.id)||i.id}))});else {let i=new Set(e.atoms.map(a=>a.id));s.atoms.forEach(a=>{let u=o.get(a.id)||a.id;i.has(u)||(e.atoms.push({...a,id:u}),i.add(u));});}}),true}getErrors(){return [...this.errors]}isValid(){return this.errors.length===0}getStatistics(){return {atomCount:this.atoms.length,relationCount:this.relations.length,typeCount:this.types.length,tupleCount:this.relations.reduce((r,n)=>r+n.tuples.length,0),errorCount:this.errors.length,hasBuiltinTypes:this.types.some(r=>r.isBuiltin)}}clone(){return new t(this.reify())}},exports.DataInstanceNormalizer=class t{static mergeRelations(r){let n=new Map;for(let o of r){let s=n.get(o.name);if(s){let e=new Set(s.tuples.map(a=>JSON.stringify(a)));for(let a of o.tuples){let u=JSON.stringify(a);e.has(u)||(s.tuples.push(a),e.add(u));}let i=new Set(s.types);for(let a of o.types)i.has(a)||(s.types.push(a),i.add(a));}else n.set(o.name,{id:o.id||o.name,name:o.name,types:[...o.types],tuples:[...o.tuples]});}return Array.from(n.values())}static inferTypes(r){let n=new Map;for(let o of r)n.has(o.type)||n.set(o.type,{id:o.type,types:[o.type],atoms:[],isBuiltin:t.isBuiltinType(o.type)}),n.get(o.type).atoms.push(o);return Array.from(n.values())}static isBuiltinType(r){return new Set(["String","Int","Bool","seq/Int","univ","none","Entity","Object","Node","Edge","Atom"]).has(r)}static deduplicateAtoms(r){let n=new Map,o=new Set;for(let s of r)n.has(s.id)?o.add(s.id):n.set(s.id,s);return o.size>0&&console.warn(`Found duplicate atoms with IDs: ${Array.from(o).join(", ")}`),Array.from(n.values())}static validateReferences(r,n){let o=new Set(r.map(e=>e.id)),s=[];for(let e of n)for(let i=0;i<e.tuples.length;i++){let a=e.tuples[i];for(let u=0;u<a.atoms.length;u++){let g=a.atoms[u];o.has(g)||s.push(`Relation "${e.name}" tuple ${i} position ${u}: references unknown atom "${g}"`);}}return {isValid:s.length===0,errors:s}}static normalize(r,n={}){let o={mergeRelations:true,inferTypes:true,validateReferences:true,deduplicateAtoms:true,...n},s=r.atoms||[],e=r.relations||[],i=r.types||[],a=[];if(o.deduplicateAtoms&&s.length>0){let u=s.length;s=this.deduplicateAtoms(s),s.length<u&&a.push(`Removed ${u-s.length} duplicate atoms`);}if(o.mergeRelations&&e.length>0){let u=e.length;e=this.mergeRelations(e),e.length<u&&a.push(`Merged ${u-e.length} duplicate relations`);}if(o.inferTypes&&i.length===0&&s.length>0&&(i=this.inferTypes(s),a.push(`Inferred ${i.length} types from atoms`)),o.validateReferences){let u=this.validateReferences(s,e);a.push(...u.errors);}return {atoms:s,relations:e,types:i,errors:a}}};});var FK={};px(FK,{StructuredInputGraph:()=>exports.StructuredInputGraph});var MK;exports.StructuredInputGraph=void 0;var YA=Tr(()=>{TE();VA();R3();cm();d2();MK=window.d3v4||window.d3,exports.StructuredInputGraph=class extends D1{constructor(n){super(true);this.evaluator=null;this.layoutInstance=null;this.cndSpecString="";this.controlsContainer=null;this.customTypes=new Set;this.relationAtomPositions=["",""];this.currentConstraintError=null;this.selectedNodeId=null;this._activePopover=null;let o=n||new exports.JSONDataInstance({atoms:[],relations:[]});this.setDataInstance(o),this.initializeStructuredInput(),this.addEventListener("edge-creation-requested",this.handleEdgeCreationRequest.bind(this)),this.addEventListener("edge-modification-requested",this.handleEdgeModificationRequest.bind(this)),this.addEventListener("edge-reconnection-requested",this.handleEdgeReconnectionRequest.bind(this));}static get observedAttributes(){return ["cnd-spec","data-instance","show-export","theme","background"]}attributeChangedCallback(n,o,s){if(o!==s)switch(n){case "cnd-spec":this.parseCnDSpec(s);break;case "data-instance":this.updateDataInstance(s);break;case "show-export":break;case "theme":case "background":super.attributeChangedCallback(n,o,s);break}}initializeStructuredInput(){requestAnimationFrame(()=>{this.createControlsInterface();});}createControlsInterface(){if(!this.shadowRoot)return;let n=document.createElement("style");n.textContent=this.getControlsCSS(),this.shadowRoot.appendChild(n);let o=this.shadowRoot.querySelector("#graph-toolbar");o&&(this.controlsContainer=document.createElement("div"),this.controlsContainer.style.display="contents",this.controlsContainer.innerHTML=this.getControlsHTML(),o.appendChild(this.controlsContainer)),this.bindControlEvents(),this.setupCanvasInteractions();}getControlsHTML(){return `
|
|
1480
1480
|
<div class="si-toolbar-group">
|
|
1481
1481
|
<button class="si-tb-btn" data-action="add-atom" title="Add Node">+ Node</button>
|
|
1482
1482
|
<button class="si-tb-btn" data-action="add-relation" title="Add Relation">+ Relation</button>
|