diff --git a/index.html b/index.html
index 685506f..13f12c4 100644
--- a/index.html
+++ b/index.html
@@ -12128,6 +12128,7 @@
var moved=codeNodeDragFrame.moved;
codeNodeDragFrame.moved=null;
if(moved&&moved.length)redrawMovedGraphNodes(moved);
+ if(updateHullsRef.current)updateHullsRef.current();
}
node.call(d3.drag().on('start',function(e,d){
d.fx=d.x;d.fy=d.y;
@@ -12148,7 +12149,8 @@
if(!isCodeViewDragBusy())setCodeViewInteractionBusy(codeViewDragBusyRoot(),true);
}
codeNodeDragPrev[d.id]={x:d.x,y:d.y};
- var siblings=translateCodeViewSiblings(sim.nodes(),d,dx,dy,codeCardPathsRef.current);
+ var moveGroup=e.sourceEvent&&(e.sourceEvent.shiftKey||e.sourceEvent.altKey);
+ var siblings=moveGroup?translateCodeViewSiblings(sim.nodes(),d,dx,dy,codeCardPathsRef.current):[];
if(codeViewDragRefresh('move')){
redrawGraphLinksAndNodes();
if(updateHullsRef.current)updateHullsRef.current();
@@ -12157,12 +12159,14 @@
scheduleCodeViewDragFrame(codeNodeDragFrame,requestAnimationFrame,paintCodeNodeDragFrame);
}
if(codeViewDragShouldDrawMinimap('move')&&drawMinimapRef.current)drawMinimapRef.current();
+ if(updateHullsRef.current)updateHullsRef.current();
return;
}
}).on('end',function(e,d){
if(!keepReadable){
if(!e.active)sim.alphaTarget(0);
d.fx=null;d.fy=null;
+ if(updateHullsRef.current)updateHullsRef.current();
return;
}
d.fx=d.x;d.fy=d.y;
@@ -12170,6 +12174,7 @@
flushCodeViewDragFrame(codeNodeDragFrame,cancelAnimationFrame);
codeNodeDragFrame.moved=null;
setCodeViewInteractionBusy(codeViewDragBusyRoot(),false);
+ if(updateHullsRef.current)updateHullsRef.current();
var end=noteCodeCardPointerEnd(leftoverDragMoved);
leftoverIgnoreClick=end.ignoreNextClick;
if(leftoverIgnoreClick)setTimeout(function(){leftoverIgnoreClick=false;},400);
@@ -12240,6 +12245,197 @@
});
return grouped;
}
+ var cellCurveGen=d3.line().curve(d3.curveCatmullRomClosed.alpha(0.5));
+ function buildTwoCellBridge(p1,p2){
+ var dx=p2.x-p1.x,dy=p2.y-p1.y;
+ var d=Math.hypot(dx,dy);
+ var r1=p1.r,r2=p2.r;
+ if(d<=Math.abs(r1-r2)){
+ var bigger=r1>=r2?p1:p2;
+ return buildHumanCellMembrane([bigger]);
+ }
+ var baseAngle=Math.atan2(dy,dx);
+ var ux=dx/d,uy=dy/d,nx=-uy,ny=ux;
+ var stretchDist=Math.max(0,d-(r1+r2)*0.5);
+ var minWaist=Math.min(r1,r2)*0.28;
+ var maxWaist=(r1+r2)*0.52;
+ var waistR=Math.max(minWaist,maxWaist*Math.max(0.15,1-(stretchDist/280)*0.75));
+ var pts=[],arcSamples=9;
+ for(var i=0;i<=arcSamples;i++){
+ var a=baseAngle+Math.PI/2+(i/arcSamples)*Math.PI;
+ pts.push([p1.x+Math.cos(a)*r1,p1.y+Math.sin(a)*r1]);
+ }
+ var midX=(p1.x+p2.x)*0.5,midY=(p1.y+p2.y)*0.5;
+ pts.push([midX-nx*waistR,midY-ny*waistR]);
+ for(var j=0;j<=arcSamples;j++){
+ var a2=baseAngle-Math.PI/2+(j/arcSamples)*Math.PI;
+ pts.push([p2.x+Math.cos(a2)*r2,p2.y+Math.sin(a2)*r2]);
+ }
+ pts.push([midX+nx*waistR,midY+ny*waistR]);
+ return cellCurveGen(pts);
+ }
+ function buildStretchedClusterPath(clusterNodes,stretchedNode){
+ var restPts=[];
+ var samples=16;
+ clusterNodes.forEach(function(n){
+ for(var k=0;kMath.PI)a-=Math.PI*2;
+ while(a<-Math.PI)a+=Math.PI*2;
+ if(amaxA){maxA=a;idxMax=i;}
+ }
+
+ if(idxMin===-1||idxMax===-1||idxMin===idxMax)return null;
+
+ var d=minDist;
+ var r1=anchor.r,r2=sr;
+ var stretchDist=Math.max(0,d-(r1+r2)*0.5);
+ var minWaist=Math.min(r1,r2)*0.28;
+ var maxWaist=(r1+r2)*0.5;
+ var waistR=Math.max(minWaist,maxWaist*Math.max(0.18,1-(stretchDist/280)*0.72));
+
+ var ux=(sx-anchor.x)/d,uy=(sy-anchor.y)/d;
+ var nx=-uy,ny=ux;
+ var midX=(anchor.x+sx)*0.5,midY=(anchor.y+sy)*0.5;
+ var w1=[midX-nx*waistR,midY-ny*waistR];
+ var w2=[midX+nx*waistR,midY+ny*waistR];
+
+ var chain1=[];
+ var cur=idxMin;
+ while(true){
+ chain1.push(cur);
+ if(cur===idxMax)break;
+ cur=(cur+1)%nH;
+ }
+
+ var chain2=[];
+ cur=idxMax;
+ while(true){
+ chain2.push(cur);
+ if(cur===idxMin)break;
+ cur=(cur+1)%nH;
+ }
+
+ var sumD1=0;
+ for(var c=0;c=avgD2?chain1:chain2;
+ var startIdx=farChain[0];
+ var endIdx=farChain[farChain.length-1];
+
+ var endPt=restHull[endIdx];
+ var startPt=restHull[startIdx];
+ var firstWaist=Math.hypot(endPt[0]-w1[0],endPt[1]-w1[1])Math.PI*2)sweep-=Math.PI*2;
+ while(sweep<0)sweep+=Math.PI*2;
+ if(sweep<=Math.PI)sweep-=Math.PI*2;
+
+ var sArc=[];
+ var arcSamples=9;
+ for(var s=1;srSum*1.15&&minDist>maxStretchDist){
+ maxStretchDist=minDist;
+ stretchedNode=ni;
+ anchorNode=closest;
+ }
+ }
+ if(stretchedNode){
+ var restNodes=pts.filter(function(n){return n!==stretchedNode;});
+ var stretchedPath=buildStretchedClusterPath(restNodes,stretchedNode);
+ if(stretchedPath)return stretchedPath;
+ }
+ var samples=16,ptsAll=[];
+ pts.forEach(function(n){
+ for(var i=0;i