jbrowse-plugin-graphgenomeviewer 4.2.0 → 4.4.0
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.
- package/dist/chunks/{GraphGenomeView-QOBHGMLE.js → GraphGenomeView-NHYVE7GZ.js} +2 -2
- package/dist/chunks/{GraphTrackSettingsDialog-RTSJTM6A.js → GraphTrackSettingsDialog-LXYUYPZS.js} +2 -2
- package/dist/chunks/{LinearGraphDisplay-TPXW6POY.js → LinearGraphDisplay-742CW5KL.js} +2 -2
- package/dist/chunks/chunk-5GUQPO7Y.js +3 -0
- package/dist/chunks/chunk-5GUQPO7Y.js.map +7 -0
- package/dist/chunks/{chunk-4C4CMI6K.js → chunk-CNFN6EVE.js} +2 -2
- package/dist/chunks/chunk-QHMGWDDH.js +2 -0
- package/dist/chunks/chunk-QHMGWDDH.js.map +7 -0
- package/dist/chunks/chunk-UTWFU4X6.js +2 -0
- package/dist/chunks/chunk-UTWFU4X6.js.map +7 -0
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +5 -5
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +4 -4
- package/package.json +2 -2
- package/src/GraphGenomeView/components/GraphCanvas.tsx +6 -53
- package/src/GraphGenomeView/components/WalkRowContextMenu.tsx +63 -0
- package/src/GraphGenomeView/components/WalkRowsOverlay.tsx +1 -1
- package/src/GraphGenomeView/components/WalkStrip.tsx +210 -0
- package/src/GraphGenomeView/components/WalkStripLocator.tsx +49 -0
- package/src/GraphGenomeView/components/usePaneGestures.ts +1 -0
- package/src/GraphGenomeView/model.test.ts +42 -3
- package/src/GraphGenomeView/model.ts +226 -105
- package/src/GraphGenomeView/viewModel.ts +23 -7
- package/src/version.ts +1 -1
- package/dist/chunks/chunk-ECKLYJWP.js +0 -2
- package/dist/chunks/chunk-ECKLYJWP.js.map +0 -7
- package/dist/chunks/chunk-HW55QCJG.js +0 -3
- package/dist/chunks/chunk-HW55QCJG.js.map +0 -7
- package/dist/chunks/chunk-SIP5IHWK.js +0 -2
- package/dist/chunks/chunk-SIP5IHWK.js.map +0 -7
- /package/dist/chunks/{GraphGenomeView-QOBHGMLE.js.map → GraphGenomeView-NHYVE7GZ.js.map} +0 -0
- /package/dist/chunks/{GraphTrackSettingsDialog-RTSJTM6A.js.map → GraphTrackSettingsDialog-LXYUYPZS.js.map} +0 -0
- /package/dist/chunks/{LinearGraphDisplay-TPXW6POY.js.map → LinearGraphDisplay-742CW5KL.js.map} +0 -0
- /package/dist/chunks/{chunk-4C4CMI6K.js.map → chunk-CNFN6EVE.js.map} +0 -0
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{b as v,g as ie}from"./chunk-7BKUWQE5.js";import{a as ae,b as ce}from"./chunk-KULNKXPT.js";import{a as ze}from"./chunk-FY3BTKY4.js";var Kt=ze((Zn,De)=>{De.exports=JBrowseExports["tss-react/mui"]});function Zt(e){let t=n=>Math.round(n*100)/100;return e.map((n,r)=>`${r?"L":"M"}${t(n.x)},${t(n.y)}`).join("")}function ue(e,t,n,r,s){let o=Math.hypot(r-t,n-e);if(o===0)return[n,r];let a=(n-e)/o,c=(r-t)/o;return[n+s*a,r+s*c]}function V(e,t){let n=1-t,r=n*n*n,s=3*n*n*t,o=3*n*t*t,a=t*t*t;return{x:r*e.x0+s*e.cx0+o*e.cx1+a*e.x1,y:r*e.y0+s*e.cy0+o*e.cy1+a*e.y1}}function xe(e){let t=e[Math.floor(e.length/2)];return t?V(t,.5):void 0}function ye(e){let t=1/0,n=1/0,r=-1/0,s=-1/0;for(let o of e)t=Math.min(t,o.x0,o.cx0,o.cx1,o.x1),r=Math.max(r,o.x0,o.cx0,o.cx1,o.x1),n=Math.min(n,o.y0,o.cy0,o.cy1,o.y1),s=Math.max(s,o.y0,o.cy0,o.cy1,o.y1);return{minX:t,minY:n,maxX:r,maxY:s}}function $e(e,t,n){let r=V(e,t),s=V(e,n),o=(n-t)/3,a=de(e,t),c=de(e,n);return{x0:r.x,y0:r.y,cx0:r.x+a.x*o,cy0:r.y+a.y*o,cx1:s.x-c.x*o,cy1:s.y-c.y*o,x1:s.x,y1:s.y}}function de(e,t){let n=1-t,r=3*n*n,s=6*n*t,o=3*t*t;return{x:r*(e.cx0-e.x0)+s*(e.cx1-e.cx0)+o*(e.x1-e.cx1),y:r*(e.cy0-e.y0)+s*(e.cy1-e.cy0)+o*(e.y1-e.cy1)}}function en(e,t){let n=e.map(a=>Math.hypot(a.cx0-a.x0,a.cy0-a.y0)+Math.hypot(a.cx1-a.cx0,a.cy1-a.cy0)+Math.hypot(a.x1-a.cx1,a.y1-a.cy1)),r=n.reduce((a,c)=>a+c,0),s=Math.max(3,Math.round(r/t)|1),o=[];for(let a=0;a<s;a+=2){let c=a/s*r,i=(a+1)/s*r,u=[],l=0;e.forEach((h,p)=>{let d=n[p],m=l+d;d>0&&m>c&&l<i&&u.push($e(h,Math.max(0,(c-l)/d),Math.min(1,(i-l)/d))),l=m}),u.length&&o.push(u)}return o}function tn(e,t,n){let r=new Array(e.length);for(let s=0;s<e.length;s++){let o=e[s];r[s]={x0:o.x0+t,y0:o.y0+n,cx0:o.cx0+t,cy0:o.cy0+n,cx1:o.cx1+t,cy1:o.cy1+n,x1:o.x1+t,y1:o.y1+n}}return r}function X(e,t){let n=e.length-1;return t==="end"?{at:e[n],inward:e[n-1]}:{at:e[0],inward:e[1]}}var We=.5,Ye=80;function me(e){let t=1/0,n=-1/0;for(let r of e)t=Math.min(t,r.x),n=Math.max(n,r.x);return{min:t,max:n}}function le(e,t){return Math.max(0,t.min-e,e-t.max)}function pe(e,t,n){let r=le(e[0].x,t),s=le(e[e.length-1].x,t);return r===s?n:r<s?"start":"end"}function Ue(e,t){return{from:pe(e,me(t),"end"),to:pe(t,me(e),"start")}}var je=.75,Ve=1.15,qe=.35,Ke=60;function Je(e,t,n,r,s,o){let a=Math.hypot(n-e,r-t),c=a===0?1:(n-e)/a,i=a===0?0:(r-t)/a,u=0,l=1/0,h=-1/0;for(let f of s){let b=f.x-e,x=f.y-t,y=b*-i+x*c;Math.abs(y)>Math.abs(u)&&(u=y);let N=b*c+x*i;l=Math.min(l,N),h=Math.max(h,N)}let p=s.length?h-l:0,d=Math.max(Math.abs(u)*Ve/je,p*qe),m=o===void 0?d:Math.min(d,o);return u<0?-m:m}function be({scaleX:e,scaleY:t}){return t/e}var Qe=3;function U(e,t,n){let r=t.x-e.x,s=(t.y-e.y)*n,o=Math.hypot(r,s);return o===0?void 0:{x:r/o,y:s/o}}function nn(e,t,n,r,s=Qe){let o=be(r),a=U(e[e.length-1],t[0],o)??U(e[0],e.at(-1),o)??U(t[0],t.at(-1),o)??{x:1,y:0},c=[];for(let i=0;i<n;i++){let u=(i-(n-1)/2)*s;c.push({x:-a.y*u,y:a.x*u/o})}return c}function j(e,t){return e.map(n=>({x:n.x,y:n.y*t}))}function Ze(e,t){for(let n of e)n.y0/=t,n.cy0/=t,n.cy1/=t,n.y1/=t;return e}var et=12,tt=40;function fe(e,t){let n=0;for(let o=1;o<e.length;o++)n+=Math.hypot(e[o].x-e[o-1].x,e[o].y-e[o-1].y);let r=n*t;return Math.min(tt,Math.max(et,r/2))/Math.max(t,Number.EPSILON)}function on(e){return e.from!==e.to?!1:e.fromStrand&&e.toStrand&&e.fromStrand!==e.toStrand?e.fromStrand==="+"?"end":"start":!0}function nt(e,t,n,r){let{at:s,inward:o}=e,a=o?s.x-o.x:1,c=o?s.y-o.y:0,i=Math.hypot(a,c)||1,u=a/i,l=c/i,h=s.x+n,p=s.y+r,d=h+u*t/3,m=p+l*t/3,f=h+u*t,b=p+l*t,x=-l*t/2,y=u*t/2;return[{x0:h,y0:p,cx0:d,cy0:m,cx1:f+x,cy1:b+y,x1:f,y1:b},{x0:f,y0:b,cx0:f-x,cy0:b-y,cx1:d,cy1:m,x1:h,y1:p}]}function q(e,t,n,r,s,o,a=[],c){let i=o.scaleX,u=be(o),l=c??(o.pixelRows?Ke:void 0);if(u!==1)return Ze(q(j(e,u),j(t,u),n,r,s*u,{scaleX:i,scaleY:i},j(a,u),l===void 0?void 0:l*u),u);if(typeof n=="string")return nt(X(e,n),fe(e,i),r,s);let h=n?{from:"end",to:"start"}:Ue(e,t),p=X(e,h.from),d=X(t,h.to),m=p.at,f=d.at,b=m.x+r,x=m.y+s,y=f.x+r,N=f.y+s;if(n){let E=1,L=0;if(p.inward){let C=m.x-p.inward.x,R=m.y-p.inward.y,G=Math.hypot(C,R);G>0&&(E=C/G,L=R/G)}let g=fe(e,i),A=-L,M=E,S=(m.x+f.x)/2+r+A*g,w=(m.y+f.y)/2+s+M*g,B=b+E*g,O=x+L*g,P=y-E*g,_=N-L*g;return[{x0:b,y0:x,cx0:B,cy0:O,cx1:B+A*g,cy1:O+M*g,x1:S,y1:w},{x0:S,y0:w,cx0:P+A*g,cy0:_+M*g,cx1:P,cy1:_,x1:y,y1:N}]}else{let E=p.inward??{x:m.x-(f.x-m.x),y:m.y-(f.y-m.y)},L=d.inward??{x:f.x-(m.x-f.x),y:f.y-(m.y-f.y)},g=Math.hypot(y-b,N-x),A=(te,ne,He,Xe)=>{let oe=ne.x-te.x,re=ne.y-te.y,se=Math.hypot(oe,re),ke=se===0?0:(He*oe+Xe*re)/se;return Math.max(0,Math.min(g*We,Ye,ke*.5))},M=f.x-m.x,S=f.y-m.y,[w,B]=ue(E.x,E.y,m.x,m.y,A(E,m,M,S)),[O,P]=ue(L.x,L.y,f.x,f.y,A(L,f,-M,-S)),_=a.length?Je(b,x,y,N,a,l):0,C=g===0?1:g,R=(y-b)/C,G=(N-x)/C,H=-G*_,Q=R*_,Z=R*Math.abs(_),ee=G*Math.abs(_);return[{x0:b,y0:x,cx0:w+r+H-Z,cy0:B+s+Q-ee,cx1:O+r+H+Z,cy1:P+s+Q+ee,x1:y,y1:N}]}}function he(e,t){let n=e[0],r=e.at(-1);return Math.hypot(n.x-t.x,n.y-t.y)<Math.hypot(r.x-t.x,r.y-t.y)?"start":"end"}function ge(e,t,n){let r=X(e,he(e,n[0])),s=X(t,he(t,n.at(-1))),o=[r.at,...n,s.at],a=i=>{let u=o[i],l=p=>Math.hypot(p.x-u.x,p.y-u.y),h=(p,d)=>{let m=p?l(p):0,f=l(d);return m===0?{x:d.x-u.x,y:d.y-u.y}:{x:(u.x-p.x)/m*f,y:(u.y-p.y)/m*f}};if(i===0)return h(r.inward,o[1]);if(i===o.length-1){let p=h(s.inward,o[i-1]);return{x:-p.x,y:-p.y}}return{x:(o[i+1].x-o[i-1].x)/2,y:(o[i+1].y-o[i-1].y)/2}},c=[];for(let i=0;i<o.length-1;i++){let u=o[i],l=o[i+1],h=a(i),p=a(i+1);c.push({x0:u.x,y0:u.y,cx0:u.x+h.x/3,cy0:u.y+h.y/3,cx1:l.x-p.x/3,cy1:l.y-p.y/3,x1:l.x,y1:l.y})}return c}var ot=1;function rt(e,t){return t.flatMap(n=>e[n]??[])}function Ne(e,t,n,r){let s=e[t.from],o=e[t.to];if(!(!s?.length||!o?.length))return r?ge(s,o,r):q(s,o,t.from===t.to,0,0,n,rt(e,t.bypassed))}function st(e){let t=new Map;for(let n of e.nodes)if(v(n)){let r=t.get(n.stable.refName);r?r.push(n):t.set(n.stable.refName,[n])}for(let n of t.values())n.sort((r,s)=>r.stable.start-s.stable.start);return t}function at(e,t,n){let r=[];if(e)for(let s=ie(e,t);s<e.length;s++){let o=e[s];if(o.stable.start>=n)break;o.stable.start+o.length<=n&&r.push(o.id)}return r}function ct(e,t,n,r){let{fromStrand:s,toStrand:o}=e;if(s===void 0||o===void 0)return!0;let a=s===(t.stable.strand??"+"),c=o===(n.stable.strand??"+");return r?a&&c:!a&&!c}function cn(e){let t=new Map(e.nodes.map(s=>[s.id,s])),n=st(e),r=[];for(let s=0;s<e.edges.length;s++){let o=e.edges[s],a=t.get(o.from),c=t.get(o.to);if(a!==void 0&&c!==void 0&&v(a)&&v(c)&&a.stable.refName===c.stable.refName){let i=a.stable.start<=c.stable.start,[u,l]=i?[a,c]:[c,a],h=u.stable.start+u.length,p=l.stable.start,d=p-h;if(d>=ot&&ct(o,a,c,i)){let m=u.stable.refName;r.push({edgeIndex:s,from:o.from,to:o.to,refName:m,start:h,end:p,bp:d,bypassed:at(n.get(m),h,p)})}}}return r}function T(e){return e.endsWith("-")?"-":"+"}function Le(e){return e==="+"?"-":"+"}function it(e){let t=(e.fromStrand??T(e.from))===T(e.from)?"end":"start",n=(e.toStrand??T(e.to))===T(e.to)?"start":"end";return{from:t,to:n}}function mn(e,t=new Set){let n=e.edges.map(it),r=new Map,s=(d,m,f)=>{let b=`${d}|${m}`,x=r.get(b);x?x.push(f):r.set(b,[f])};e.edges.forEach((d,m)=>{s(d.from,n[m].from,m),s(d.to,n[m].to,m)});let o=d=>{let m=r.get(`${d}|end`);if(m?.length!==1)return;let f=m[0],b=e.edges[f];return b.from===d&&b.to!==d&&!t.has(d)&&!t.has(b.to)&&n[f].from==="end"&&n[f].to==="start"&&r.get(`${b.to}|start`)?.length===1?f:void 0},a=new Set,c=new Map,i=new Map;for(let d of e.nodes){let m=r.get(`${d.id}|start`),f=m?.length===1?e.edges[m[0]].from:void 0;if(c.has(d.id)||f!==void 0&&o(f)===m[0])continue;let b=[d.id];for(;;){let x=o(b.at(-1)),y=x===void 0?void 0:e.edges[x].to;if(y===void 0||c.has(y))break;a.add(x),b.push(y)}for(let x of b)c.set(x,d.id);b.length>1&&i.set(d.id,b)}for(let d of e.nodes)c.has(d.id)||c.set(d.id,d.id);let u=new Map(e.nodes.map(d=>[d.id,d])),l=[];for(let d of e.nodes){if(c.get(d.id)!==d.id)continue;let m=i.get(d.id);if(!m){l.push(d);continue}let f=m.map(y=>u.get(y)),b=f.filter(v),x=b.length?b.reduce((y,N)=>y.stable.start<=N.stable.start?y:N):f[0];l.push({id:d.id,name:d.name,length:f.reduce((y,N)=>y+N.length,0),depth:d.depth,stable:x.stable})}let h=new Set,p=[];return e.edges.forEach((d,m)=>{let f=c.get(d.from),b=c.get(d.to),x=n[m],y=`${f}|${x.from}>${b}|${x.to}`;a.has(m)||h.has(y)||(h.add(y),p.push({from:f,to:b,...x.from==="start"?{fromStrand:Le(T(f))}:{},...x.to==="end"?{toStrand:Le(T(b))}:{}}))}),{graph:{...e,nodes:l,edges:p,paths:void 0,pathVisits:void 0},runs:i}}function ln(e,t,n){let r={...e};for(let[s,o]of t){let a=e[s];if(!a?.length)continue;delete r[s];let c=J(a),i=c.at(-1),u=o.map(p=>Math.max(n(p),0)),l=u.reduce((p,d)=>p+d,0)||o.length,h=0;o.forEach((p,d)=>{let m=l?u[d]/l*i:i/o.length,f=d===o.length-1?i:h+m;r[p]=Me(a,c,h,f),h=f})}return r}function pn(e,t,n){if(e.length===0)return[];let r=J(e),s=r.at(-1);return Me(e,r,t*s,n*s)}function Se(e){let t=J(e);return K(e,t,t.at(-1)/2)}function J(e){let t=[0];for(let n=1;n<e.length;n++)t.push(t[n-1]+Math.hypot(e[n].x-e[n-1].x,e[n].y-e[n-1].y));return t}function K(e,t,n){if(n<=0)return{...e[0]};let r=1;for(;r<t.length&&t[r]<n;)r++;if(r>=t.length)return{...e.at(-1)};let s=t[r]-t[r-1],o=s>0?(n-t[r-1])/s:0,a=e[r-1],c=e[r];return{x:a.x+(c.x-a.x)*o,y:a.y+(c.y-a.y)*o}}function Me(e,t,n,r){let s=[K(e,t,n)];for(let o=0;o<e.length;o++)t[o]>n&&t[o]<r&&s.push({...e[o]});return s.push(K(e,t,r)),s}function ut(e){return e.length*6.2+8}function dt(e,t){return e.x0<t.x1&&e.x1>t.x0&&e.y0<t.y1&&e.y1>t.y0}function Ee(e,t){return e.x1<=0||e.x0>=t.width||e.y1<=0||e.y0>=t.height}function mt(e,t){return e.x0>=0&&e.x1<=t.width&&e.y0>=0&&e.y1<=t.height}function lt(e,t=[]){let n=[...t];return r=>Ee(r,e)||n.some(s=>dt(s,r))?!1:(n.push(r),!0)}function hn(e,t,n){let r=[],o=(a,c)=>{let i=ut(c),u=a.y>t.height/2?-1:1;for(let l=0;l<=(a.stack??0);l++){let h=a.y+u*l*19,p={x0:a.x-i/2,x1:a.x+i/2,y0:h-11-4,y1:h+4};if(Ee(p,t))return;if(mt(p,t)&&n(p))return{...a,text:c,y:h,w:i}}};for(let a of e){let c=o(a,a.text)??(a.fallback===void 0?void 0:o(a,a.fallback));c&&r.push(c)}return r}var pt=26,ft=4e4,ht=8,xt=8,yt=5.7,bt=6,gt=13,k=3,z=2;function W(e){return(e.length*yt+bt+k)/2}var I=(gt+k)/2,Nt=W("1 bp");function F(e,t,n){return{x0:t-e,x1:t+e,y0:n-I,y1:n+I}}function Ln(e,t){return{x0:6,x1:6+e.length*6.2+8+k,y0:t-8-k/2,y1:t+8+k/2}}function $(e,t,n){return n<t?e:Math.min(Math.max(e,t),n)}function we(e){return String(e).replace(/\B(?=(\d{3})+$)/g,",")}function Pe(e){return e>=1e6?`${+(e/1e6).toFixed(1)} Mb`:e>=1e3?`${+(e/1e3).toFixed(1)} kb`:`${e} bp`}function Ae(e){return`${Pe(e)} del`}function Lt(e,t,n,r){let s=e[0],o=e[e.length-1],a=(t.x-(s.x0+o.x1)/2)*n,c=(t.y-(s.y0+o.y1)/2)*r,i=Math.hypot(a,c);return i===0?{x:0,y:-1}:{x:a/i,y:c/i}}function St(e,t){return Math.abs(t.x)*(e.x1-e.x0)/2+Math.abs(t.y)*(e.y1-e.y0)/2}function Mt(e,t){let n=(e.x1-e.x0)/2,r=(e.y1-e.y0)/2;return Math.min(t.x===0?1/0:n/Math.abs(t.x),t.y===0?1/0:r/Math.abs(t.y))}function Et(e,t,n){let r=0;for(let s=1;s<e.length;s++){let o=e[s-1],a=e[s];r+=Math.hypot((a.x-o.x)*t,(a.y-o.y)*n)}return r}function At(e,t){let n=1/0,r=1/0,s=-1/0,o=-1/0;for(let a of e)for(let c of t[a]??[])n=Math.min(n,c.x),s=Math.max(s,c.x),r=Math.min(r,c.y),o=Math.max(o,c.y);return n===1/0?void 0:{minX:n,minY:r,maxX:s,maxY:o}}var _e=new WeakMap;function _t(e,t){let n=_e.get(e);if(n?.nodeLengths===t)return n.order;let r=Object.entries(e).filter(([s,o])=>o.length>0&&t.has(s)).sort(([s],[o])=>t.get(o)-t.get(s));return _e.set(e,{nodeLengths:t,order:r}),r}var Be=new WeakMap;function Bt(e,t,n,r,s){let{scaleX:o,scaleY:a}=n,c=Be.get(e);if(c?.deletions===t&&c.routes===s&&c.scaleX===o&&c.scaleY===a&&c.version===r)return c.arcs;let i=[];for(let u of[...t].sort((l,h)=>h.bp-l.bp)){let l=Ne(e,u,n,s?.[u.edgeIndex]),h=l?xe(l):void 0;if(l&&h){let{minX:p,minY:d,maxX:m,maxY:f}=ye(l);i.push({deletion:u,curves:l,apex:h,extent:Math.max((m-p)*o,(f-d)*a)})}}return Be.set(e,{deletions:t,routes:s,scaleX:o,scaleY:a,version:r,arcs:i}),i}function Sn({nodePositions:e,nodeLengths:t,deletions:n,deletionRoutes:r,alleleDeletions:s,axis:o,translateX:a,translateY:c,width:i,height:u,version:l=0}){let{scaleX:h,scaleY:p}=o,d=[],m=[];for(let{deletion:x,curves:y,apex:N,extent:E}of Bt(e,n,o,l,r))if(E>=pt){let L=Ae(x.bp),g=W(L),A=F(g,0,0),M=N.x*h+a,S=N.y*p+c,w=E>=g*2/z,B=Lt(y,N,h,p),O=St(A,B)+xt,P=w?M:$(M+B.x*O,g,i-g),_=w?S:$(S+B.y*O,I,u-I),C=Math.hypot(P-M,_-S),R;if(C>0){let G={x:(P-M)/C,y:(_-S)/C},H=Mt(A,G);R={arcX:M,arcY:S,labelX:P-G.x*H,labelY:_-G.y*H}}d.push({label:{key:`del:${x.edgeIndex}`,text:L,x:P,y:_,kind:"deletion",leader:R},box:F(g,P,_)})}let f=new Set,b=[...s??[]].sort((x,y)=>y.bp-x.bp);for(let x of b){let y=At(x.nodeIds,e);if(!y)continue;let N=Math.max(y.minX*h+a,0),E=Math.min(y.maxX*h+a,i),L=Math.max(y.minY*p+c,0),g=Math.min(y.maxY*p+c,u);if(E<N||g<L)continue;let A=Math.max(E-N,g-L),M=Ae(x.bp),S=W(M);if(A>=S*2/z){for(let O of x.nodeIds)f.add(O);let w=$((N+E)/2,S,i-S),B=$((L+g)/2,I,u-I);d.push({label:{key:`alleledel:${x.nodeIds.join(",")}`,text:M,x:w,y:B,kind:"deletion"},box:F(S,w,B)})}}for(let[x,y]of _t(e,t)){if(f.has(x))continue;let{x:N,y:E}=Se(y),L=E*p+c;if(L+I<=0||L-I>=u)continue;let g=Et(y,h,p);if(g<Nt*2/z)continue;let A=Pe(t.get(x)),M=W(A);if(g>=M*2/z){let S=N*h+a;m.push({label:{key:`node:${x}`,text:A,x:S,y:L,kind:"node"},box:F(M,S,L)})}}return{deletions:d,nodes:m}}function Mn(e,t){return Math.max(ht,Math.floor(e*t/ft))}function En(e,t,n=1/0){let r=[];for(let{label:s,box:o}of e){if(r.length>=n)break;t(o)&&r.push(s)}return r}var Pt=/:(\d+)-\d+$/;function Y(e){let t=Pt.exec(e);return t?{name:e.slice(0,t.index),start:+t[1]}:{name:e,start:0}}function wn(e,t){let n=[],r=new Map;for(let s of e){let o=ae(s.name),a=s.start;for(let c of s.steps){let i={path:s.name,sample:o,start:a,strand:c.strand},u=r.get(c.id);u?u.push(i):r.set(c.id,[i]),a+=t(c.id)}n.push({name:s.name,sample:o,start:s.start,length:a-s.start})}return{anchorPaths:n,pathVisits:r}}function Gt(e,t){return e.find(r=>r.sample===t||r.name===t)??e[0]}var Ot=1;function Ct(e,t,n){let r=e;if(t&&t.length>0){let s=t.find(o=>o.path===n)??t[0];r={...e,stable:{refName:s.path,start:s.start,rank:s.path===n?0:Ot,strand:s.strand},samples:[...new Set(t.map(o=>o.sample))].sort()}}return r}function Rt(e,t){let{anchorPaths:n,pathVisits:r}=e,s=n&&r?Gt(n,t):void 0;return s&&r?{...e,nodes:e.nodes.map(o=>Ct(o,r.get(o.name),s.name)),anchoredBy:"paths",referencePath:s.name}:e}function Pn(e,t){return e.anchoredBy==="tags"?e:Rt(e,t)}function Gn(e,t){if(!e.anchorPaths)return;let n=new Map(e.nodes.map(o=>[o.id,o.length])),r=(o,a=o.length)=>{let c=0;for(let i=0;i<a;i++)c+=n.get(o[i])??0;return c},s=new Map;for(let o of e.paths??[]){let a=Y(o.name).name;s.set(a,[...s.get(a)??[],o])}return e.anchorPaths.map(o=>{let a=s.get(o.name),c=a?.length===1?a[0]:void 0,i=c&&t.get(c.name);return c&&i?{...o,start:o.start+r(c.nodeIds,i.first),length:r(i.nodeIds)}:o})}function vn(e){let t=0;for(let n=0;n<e.length;n++)t=e.charCodeAt(n)+((t<<5)-t);return Math.abs(t%360)}function It(e,t){return e*360/t}var vt=.7,Tt=.5,Dt=16;function Tn(e){return e>0&&e<=Dt}function Ht(e,t){return`hsl(${It(e,t)}, ${vt*100}%, ${Tt*100}%)`}function Dn(e,t){return`hsl(0, 0%, ${t>1?25+45*e/(t-1):45}%)`}function Xt(e){let t=Y(e).name.split("#"),n=t.join("#");if(t.length>=3&&ce(t[0])){let r=t.slice(2).join("#");return[r,r,n]}return[t[0],t.slice(0,2).join("#"),n]}function kt(e,t){let{start:n}=Y(e);return t.map(r=>`${r} @${we(n)}`)}function Hn(e,t){let n=new Map;e.forEach((i,u)=>{n.has(i.name)||n.set(i.name,u)});let r=[...n].map(([i,u])=>({name:i,i:u})),s=r.map(i=>Xt(i.name)),o=[0,1,2].flatMap(i=>[r.map((u,l)=>s[l][i]),r.map((u,l)=>kt(u.name,s[l])[i])]),c=o.find(i=>new Set(i).size===r.length)??o.at(-1);return r.map(({name:i,i:u},l)=>({name:i,label:c[l],color:t?.[u]??Ht(u,e.length)}))}function zt(e,t){return Math.max(e.nodeLengthPerMegabase*t/1e6,e.minimumNodeLength)}function Oe(e,t=5){let r=e.nodes.reduce((o,a)=>o+a.length,0)/1e6,s=Math.max(e.nodes.length*40,500);return{nodeLengthPerMegabase:r>0?s/r:1e4,minimumNodeLength:Math.max(5,t),edgeLength:5,nodeSegmentLength:20}}var D={power:1,effect:1};function Ce(e){return e.power===1&&e.effect===1}function Re(e,t){let n=e.nodes.length,r=e.nodes.reduce((a,c)=>a+c.length,0),s=n>0?r/n:0,o=n>0?Math.max(n*40,500)/n:40;return a=>{if(s<=0)return o;let c=(a/s)**t.power;return Math.max(o*((c-1)*t.effect+1),5)}}var Ge=1e3,Ie={law:D,minNodeLength:0};function kn(e,t=Ie){let{law:n,minNodeLength:r}=t,s=Oe(e,r);if(Ce(n))return{nodes:e.nodes,opts:s};let o=Re(e,n);return{nodes:e.nodes.map(a=>({...a,length:Math.round(o(a.length)*Ge)})),opts:{...s,nodeLengthPerMegabase:1e6/Ge,minimumNodeLength:5}}}function zn(e,t=Ie){if(Ce(t.law)){let n=Oe(e,t.minNodeLength);return r=>zt(n,r)}return Re(e,t.law)}var ve=[{value:"auto",label:"Proportional",description:"Bandage's own scale: a node draws proportional to its length, so a 1 bp allele is a speck.",law:D,minNodeFactor:0},{value:"open",label:"Open bubbles",description:"Give every allele a drawn length, so a bubble reads as two arms rather than a knot.",law:D,minNodeFactor:2.5},{value:"wide",label:"Wide bubbles",description:"As open, spread further. Best on a window of a dozen bubbles.",law:D,minNodeFactor:10},{value:"compress",label:"Compress lengths",description:"Pull the longest and shortest nodes towards the graph\u2019s mean, so a cut spanning kb and bp fits one pane.",law:{power:.5,effect:.5},minNodeFactor:0}],Wn=ve.map(e=>e.value);function Yn(e){let t=ve.find(n=>n.value===e);return{law:t?.law??D,minNodeLength:(t?.minNodeFactor??0)*40}}var $t=[{value:"auto",label:"Auto"},{value:"uniform",label:"Uniform"},{value:"random",label:"Random"},{value:"rainbow",label:"Rainbow"},{value:"depth",label:"Depth"},{value:"node-length",label:"Node Length"},{value:"stable-rank",label:"Stable rank"},{value:"reference-position",label:"Reference position"},{value:"grey",label:"Grey"}],jn=$t.map(e=>e.value);function Vn(e,t){return e!=="auto"?e:t?.anchoredBy?"reference-position":"uniform"}var Wt=[{value:"depth",label:"By depth",description:"Thicker the more paths carry a node, on the square root of its depth against the mean. A graph with one depth everywhere draws uniform."},{value:"uniform",label:"Uniform",description:"Every node the same thickness."}],Kn=Wt.map(e=>e.value),Yt=.3,Te=3;function Ut(e){let t=0,n=0;for(let r of e.nodes)t+=r.depth*r.length,n+=r.length;return n>0?t/n:0}function jt(e,t){return!(t>0)||!(e.depth>0)?1:Math.min(Te,Math.max(Yt,Math.sqrt(e.depth/t)))}function Vt(e,t,n,r){return t*(n==="depth"?jt(e,r):1)}function qt(e,t){return e*(t==="depth"?Te:1)}function Jn(e,t,n,r){let s=e?Ut(e):0;return{maxHalfWidthPx:qt(n,r)/2,halfWidthPx:o=>{let a=t?.get(o);return a?Vt(a,n,r,s)/2:n/2}}}export{Y as a,wn as b,Gt as c,Rt as d,Pn as e,Gn as f,Zt as g,ye as h,en as i,tn as j,be as k,nn as l,on as m,q as n,ge as o,rt as p,cn as q,mn as r,ln as s,pn as t,lt as u,hn as v,Ln as w,we as x,Pe as y,Sn as z,Mn as A,En as B,vn as C,It as D,vt as E,Tt as F,Dt as G,Tn as H,Ht as I,Dn as J,Hn as K,zt as L,kn as M,zn as N,ve as O,Wn as P,Yn as Q,$t as R,jn as S,Vn as T,Wt as U,Kn as V,Ut as W,Vt as X,Jn as Y,Kt as Z};
|
|
2
|
+
//# sourceMappingURL=chunk-UTWFU4X6.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["global-externals:tss-react/mui", "../../packages/core/src/util/geometry.ts", "../../packages/core/src/deletionEdges.ts", "../../packages/core/src/layout/mergeRuns.ts", "../../packages/core/src/overlayLabels.ts", "../../packages/core/src/graphLabels.ts", "../../packages/core/src/pathAnchoring.ts", "../../packages/core/src/pathColors.ts", "../../packages/core/src/layout/drawnScale.ts", "../../packages/core/src/bubbleSpreads.ts", "../../packages/core/src/colorSchemes.ts", "../../packages/core/src/nodeWidths.ts"],
|
|
4
|
+
"sourcesContent": ["module.exports = JBrowseExports[\"tss-react/mui\"];", "import { ROW_HEIGHT_PX } from '../layout/rowSpacing'\n\nimport type { GraphEdge, NodeSegment } from '../types'\n\n// An SVG path along a polyline, rounded to two decimals.\nexport function svgPath(points: NodeSegment[]) {\n const round = (v: number) => Math.round(v * 100) / 100\n return points\n .map((p, i) => `${i ? 'L' : 'M'}${round(p.x)},${round(p.y)}`)\n .join('')\n}\n\nexport function projectLine(\n x1: number,\n y1: number,\n x2: number,\n y2: number,\n distance: number,\n): [number, number] {\n const d = Math.hypot(y2 - y1, x2 - x1)\n if (d === 0) {\n return [x2, y2]\n }\n const vx = (x2 - x1) / d\n const vy = (y2 - y1) / d\n return [x2 + distance * vx, y2 + distance * vy]\n}\n\nexport interface BezierCurve {\n x0: number\n y0: number\n cx0: number\n cy0: number\n cx1: number\n cy1: number\n x1: number\n y1: number\n}\n\n// The cubic's own point at t, by de Casteljau. Used to place a label ON a curve\n// rather than at a position derived from what the curve was supposed to be: an\n// apex recomputed from the bulge and the endpoints agrees with the drawing only\n// as long as both derive it the same way, and they did not.\nexport function curvePointAt(c: BezierCurve, t: number) {\n const u = 1 - t\n const a = u * u * u\n const b = 3 * u * u * t\n const d = 3 * u * t * t\n const e = t * t * t\n return {\n x: a * c.x0 + b * c.cx0 + d * c.cx1 + e * c.x1,\n y: a * c.y0 + b * c.cy0 + d * c.cy1 + e * c.y1,\n }\n}\n\n// Halfway along a run of curves, measured in curves rather than in arc length: a\n// run is one cubic for every edge this is used on (computeEdgeCurves returns a\n// single curve except for self-loops), so the two agree where it matters and the\n// cheap version cannot be wrong by more than a curve.\nexport function curveMidpoint(curves: BezierCurve[]) {\n const mid = curves[Math.floor(curves.length / 2)]\n return mid ? curvePointAt(mid, 0.5) : undefined\n}\n\n// Control-polygon bounds, which contain the curve. Used to ask how big a drawn\n// edge is on screen, where the answer has to hold under any layout: a bulge\n// alone is in whatever units the layout works in.\nexport function curveBounds(curves: BezierCurve[]) {\n let minX = Infinity\n let minY = Infinity\n let maxX = -Infinity\n let maxY = -Infinity\n for (const c of curves) {\n minX = Math.min(minX, c.x0, c.cx0, c.cx1, c.x1)\n maxX = Math.max(maxX, c.x0, c.cx0, c.cx1, c.x1)\n minY = Math.min(minY, c.y0, c.cy0, c.cy1, c.y1)\n maxY = Math.max(maxY, c.y0, c.cy0, c.cy1, c.y1)\n }\n return { minX, minY, maxX, maxY }\n}\n\n// The sub-curve over [t0, t1], exact rather than sampled: de Casteljau twice,\n// which is what keeps a dashed curve on the same path as the solid one.\nfunction subCurve(c: BezierCurve, t0: number, t1: number): BezierCurve {\n const p0 = curvePointAt(c, t0)\n const p1 = curvePointAt(c, t1)\n // The Hermite tangents of the whole cubic at t0/t1, scaled by the sub-span,\n // are the sub-curve's control-point offsets.\n const dt = (t1 - t0) / 3\n const d0 = curveTangentAt(c, t0)\n const d1 = curveTangentAt(c, t1)\n return {\n x0: p0.x,\n y0: p0.y,\n cx0: p0.x + d0.x * dt,\n cy0: p0.y + d0.y * dt,\n cx1: p1.x - d1.x * dt,\n cy1: p1.y - d1.y * dt,\n x1: p1.x,\n y1: p1.y,\n }\n}\n\nfunction curveTangentAt(c: BezierCurve, t: number) {\n const u = 1 - t\n const a = 3 * u * u\n const b = 6 * u * t\n const d = 3 * t * t\n return {\n x: a * (c.cx0 - c.x0) + b * (c.cx1 - c.cx0) + d * (c.x1 - c.cx1),\n y: a * (c.cy0 - c.y0) + b * (c.cy1 - c.cy0) + d * (c.y1 - c.cy1),\n }\n}\n\n// A curve chopped into dashes, each its own run so a renderer strokes them as\n// separate paths. Dashing is geometry here rather than a stroke style because\n// the two backends do not share one: Canvas2D has setLineDash, a GPU backend\n// tessellating these curves does not, and a dash pattern that only exists in one\n// of them is a figure that changes with the renderer.\n//\n// `dashWorld` is the period in layout units, so a caller passing screen px over\n// the current scale gets dashes of a constant on-screen size. The pattern runs\n// over the curves as one line, so an edge drawn as many short pieces still\n// reads as dashed.\nexport function dashCurves(\n curves: BezierCurve[],\n dashWorld: number,\n): BezierCurve[][] {\n // Control-polygon length overestimates the arc, which errs toward more\n // dashes rather than toward a curve that reads as solid.\n const lengths = curves.map(\n c =>\n Math.hypot(c.cx0 - c.x0, c.cy0 - c.y0) +\n Math.hypot(c.cx1 - c.cx0, c.cy1 - c.cy0) +\n Math.hypot(c.x1 - c.cx1, c.y1 - c.cy1),\n )\n const total = lengths.reduce((a, b) => a + b, 0)\n // Odd, so a dash sits at each end and the line reads as attached to both\n // nodes rather than floating between them.\n const spans = Math.max(3, Math.round(total / dashWorld) | 1)\n const out: BezierCurve[][] = []\n for (let i = 0; i < spans; i += 2) {\n const from = (i / spans) * total\n const to = ((i + 1) / spans) * total\n const dash: BezierCurve[] = []\n let start = 0\n curves.forEach((c, k) => {\n const length = lengths[k]!\n const end = start + length\n if (length > 0 && end > from && start < to) {\n dash.push(\n subCurve(\n c,\n Math.max(0, (from - start) / length),\n Math.min(1, (to - start) / length),\n ),\n )\n }\n start = end\n })\n if (dash.length) {\n out.push(dash)\n }\n }\n return out\n}\n\nexport function translateCurves(\n curves: BezierCurve[],\n dx: number,\n dy: number,\n): BezierCurve[] {\n const out: BezierCurve[] = new Array(curves.length)\n for (let i = 0; i < curves.length; i++) {\n const c = curves[i]!\n out[i] = {\n x0: c.x0 + dx,\n y0: c.y0 + dy,\n cx0: c.cx0 + dx,\n cy0: c.cy0 + dy,\n cx1: c.cx1 + dx,\n cy1: c.cy1 + dy,\n x1: c.x1 + dx,\n y1: c.y1 + dy,\n }\n }\n return out\n}\n\ntype Side = 'start' | 'end'\n\n// The attachment point, and the point just inside the node from it \u2014 which is\n// what gives the outward tangent, so a curve leaves along the node it leaves\n// rather than straight at its partner.\nfunction attachment(segments: NodeSegment[], side: Side) {\n const last = segments.length - 1\n return side === 'end'\n ? { at: segments[last]!, inward: segments[last - 1] }\n : { at: segments[0]!, inward: segments[1] }\n}\n\n// Ceilings on how far a control point runs along its own node's direction: a\n// fraction of the separation the curve has to cover, so endpoints that nearly\n// touch cannot fold the cubic back over itself, and an absolute cap so a\n// graph-wide edge does not sprout a huge hook.\nconst TANGENT_SPAN_FACTOR = 0.5\nconst MAX_TANGENT = 80\n\n// Which drawn end of each node the edge joins.\n//\n// A link states the orientation it reads each segment in, but a node is drawn\n// in whatever direction its layout placed it \u2014 reference-forward on the\n// anchored rows, run-sweep order off them, arbitrary under FMMM \u2014 and nothing\n// records that direction, so the strands cannot be turned into drawn ends. What\n// the drawing itself says is which ends face each other, so that is what is\n// read: each node attaches at whichever of its two ends is nearer the OTHER\n// node's drawn span, with the forward reading (leave the from-node's end, enter\n// the to-node's start) winning a tie.\n//\n// Nearer to the span, not to the other attachment point. A bubble's two nodes\n// usually overlap in x, and there the distances to a point differ by a hair\n// that says nothing \u2014 enough to hang a node's entry and exit edges off the same\n// end, which draws them crossed. Overlapping spans give both ends distance 0,\n// i.e. a tie, and the tie is what keeps such a bubble forward. What survives is\n// the case the rule is for: `L s381 - s2087 +` then `L s2087 + s378 -` is\n// NCTC86 crossing its locus right to left, and joining last-point to\n// first-point regardless drew the second link 7 kb backwards across the segment\n// it rejoins \u2014 the long X in pangenome/rgfa_subgraph_launch. There the spans do\n// not overlap and the far end loses by 7 kb. Pinned by bubbleCrossing.test.ts.\nfunction spanOf(segments: NodeSegment[]) {\n let min = Infinity\n let max = -Infinity\n for (const s of segments) {\n min = Math.min(min, s.x)\n max = Math.max(max, s.x)\n }\n return { min, max }\n}\n\nfunction distanceToSpan(x: number, span: { min: number; max: number }) {\n return Math.max(0, span.min - x, x - span.max)\n}\n\nfunction facingSide(\n segments: NodeSegment[],\n other: { min: number; max: number },\n tieBreak: Side,\n): Side {\n const startGap = distanceToSpan(segments[0]!.x, other)\n const endGap = distanceToSpan(segments[segments.length - 1]!.x, other)\n return startGap === endGap ? tieBreak : startGap < endGap ? 'start' : 'end'\n}\n\nfunction facingSides(fromSegments: NodeSegment[], toSegments: NodeSegment[]) {\n return {\n from: facingSide(fromSegments, spanOf(toSegments), 'end'),\n to: facingSide(toSegments, spanOf(fromSegments), 'start'),\n }\n}\n\n// A cubic whose two control points both sit `b` off the chord reaches 3b/4 at\n// its midpoint: (P0 + 3C0 + 3C1 + P1)/8 with the endpoints on the chord.\nconst APEX_FRACTION = 0.75\n// How far past the run the apex goes, so the arc reads as passing AROUND the\n// reference it skips rather than through it.\nconst BOW_CLEARANCE = 1.15\n\n// How far, and which way, to bow an arc so that it encloses the run of nodes it\n// bypasses -- the deletion case. Both come from where that run actually lies\n// relative to this edge's own chord, which is the only thing that can answer\n// either question.\n//\n// It used to be a magnitude alone, `0.35 * the summed drawn length of the\n// bypassed nodes`, bowed to a hardcoded side. Both halves were wrong, and the\n// amylase 94.2 kb arc showed each: summed path length is not spatial reach, so a\n// chain that snakes (as an FMMM chain does) sizes an arc far past where its own\n// nodes are; and the fixed sign is a coin flip on which side those nodes fell.\n// Measured on that figure, the bypassed run reached 739 units to one side of a\n// 16-unit chord while the arc bowed 719 units to the other -- a balloon in empty\n// space, enclosing nothing, beside the run it was supposed to name. The two arcs\n// in the same figure that read correctly were the two whose runs happened to\n// fall on the hardcoded side.\n//\n// Sign comes from the FARTHEST point rather than the mean, because the arc has\n// to clear the whole run, not most of it.\n//\n// The magnitude needs two terms, and the perpendicular one alone is not enough:\n// in an anchored layout the backbone is a straight line, so a bypassed run lies\n// exactly ON the chord, reaches nowhere off it, and an arc sized by clearance\n// alone collapses back into the stub at a joint this exists to prevent. That is\n// every anchored figure, and it is what the unit tests caught. So the run's\n// extent ALONG the chord carries the collinear case, at the same 0.35 the old\n// rule used, which is what keeps those figures where they are; clearance takes\n// over exactly when the run has gone somewhere the arc would otherwise miss.\nconst ALONG_FRACTION = 0.35\n// The most bow a deletion gets in a ROW layout, in screen px, which is what y\n// already is there.\n//\n// A cap rather than a smaller fraction, because the fraction is the part that is\n// wrong: `span * ALONG_FRACTION` scales the bow with the amount of reference\n// skipped, and in a row layout the arc's own SPAN states that exactly \u2014 its two\n// feet are the deletion's two coordinates on the backbone \u2014 while the label\n// prints it in bp. So the bow carries no information there, and letting it grow\n// with the event turns a big deletion into a loop enclosing a dozen unrelated\n// nodes and edges while a small one stays the flat tie under the backbone that\n// reads correctly. Reported as \"the dashed lines frankly look weird for the\n// 'deletion'\" on `pangenome/hprc_cfhr_deletion`, whose 84.7 kb arc dived 5.5\n// rows into the rank rows while its 28.6 kb and 11.2 kb neighbours sat just\n// under the reference row: four deletions in one frame, drawn as four different\n// kinds of mark.\n//\n// Three rows: enough to clear the reference row's own tube and to read as an arc\n// across several hundred px of backbone, not enough to reach the second rank\n// row. The isotropic layout is NOT capped \u2014 there the run the arc bows around is\n// scattered rather than collinear, so the bow is what makes the bubble's two\n// arms comparable, which is the clearance term's whole job.\nconst MAX_ROW_BOW_PX = 3 * ROW_HEIGHT_PX\n\nexport function bowAround(\n p1x: number,\n p1y: number,\n p2x: number,\n p2y: number,\n toward: NodeSegment[],\n // Ceiling on the returned magnitude, in the units this function is working in.\n // Undefined leaves the bow uncapped; see MAX_ROW_BOW_PX.\n maxBow?: number,\n) {\n const chord = Math.hypot(p2x - p1x, p2y - p1y)\n const ux = chord === 0 ? 1 : (p2x - p1x) / chord\n const uy = chord === 0 ? 0 : (p2y - p1y) / chord\n let reach = 0\n let minAlong = Infinity\n let maxAlong = -Infinity\n for (const p of toward) {\n const dx = p.x - p1x\n const dy = p.y - p1y\n const perp = dx * -uy + dy * ux\n if (Math.abs(perp) > Math.abs(reach)) {\n reach = perp\n }\n const along = dx * ux + dy * uy\n minAlong = Math.min(minAlong, along)\n maxAlong = Math.max(maxAlong, along)\n }\n const span = toward.length ? maxAlong - minAlong : 0\n const uncapped = Math.max(\n (Math.abs(reach) * BOW_CLEARANCE) / APEX_FRACTION,\n span * ALONG_FRACTION,\n )\n const size = maxBow === undefined ? uncapped : Math.min(uncapped, maxBow)\n // A run that sits on the chord has no side of its own; the arc keeps the one\n // it has always taken there, so a collinear layout draws exactly as before.\n return reach < 0 ? -size : size\n}\n\n// How the drawing maps layout units to screen pixels, both axes together.\n//\n// **One object rather than two arguments, and that is the point of it.** Every\n// measurement in this file \u2014 a chord length, a tangent projection, a bow \u2014 mixes\n// x and y in one `hypot`, so it needs both. On a row layout they differ: x is\n// reference bp and y is a row pitch in screen px (LayoutResult.pixelRows), and\n// the same drop of 20 is a fifth of a pane one way and twenty bases the other.\n// Passing them separately meant a caller could pass one and default the other,\n// which compiles, draws a wrong picture, and reports nothing. Three callers\n// build these curves \u2014 the drawing, the hit index, and the label that rides them\n// \u2014 and they have to agree; a pair that cannot be half-passed is what makes that\n// structural instead of remembered.\n//\n// The two numbers are the model's own `scaleX`/`scaleY`, handed over unchanged\n// rather than pre-divided, so nothing downstream can combine an x scale from one\n// moment with a y scale from another.\nexport interface AxisScale {\n scaleX: number\n scaleY: number\n // Whether the layout's y is already SCREEN PIXELS \u2014 true for the two row\n // layouts, false for the isotropic simulation (see rowSpacing.ts and the\n // model's axisScaleOf). It rides here rather than being threaded to each\n // caller because the three functions that build a deletion's curve \u2014 the\n // drawing, the hit index and the label that rides it \u2014 all take an `axis` and\n // all three have to agree about the bow; a fourth parameter only some of them\n // passed is how a label and its arc end up in different corners of the\n // drawing, which has happened here before.\n //\n // NOT derivable as `scaleY !== scaleX`: on a row layout scaleY is pinned to 1\n // and scaleX is the zoom, so the two coincide at one zoom level and anything\n // keyed on the difference would silently stop applying there.\n pixelRows?: boolean\n}\n\n// y in x units: what makes the two axes comparable. 1 on an isotropic layout\n// (FMMM), where every number below is exactly what it was.\nexport function yToXOf({ scaleX, scaleY }: AxisScale) {\n return scaleY / scaleX\n}\n\n// How far apart two adjacent path ribbons run, in world x units.\nexport const PATH_RIBBON_SPACING = 3\n\n// A polyline's overall direction in DRAWN space, or undefined when it has none.\nfunction drawnDirection(\n a: { x: number; y: number },\n b: { x: number; y: number },\n yToX: number,\n) {\n const dx = b.x - a.x\n const dy = (b.y - a.y) * yToX\n const len = Math.hypot(dx, dy)\n return len === 0 ? undefined : { x: dx / len, y: dy / len }\n}\n\n// Where each of an edge's path ribbons is drawn, as an offset from the edge\n// itself, in world units \u2014 the one derivation of it, because three callers need\n// the same answer: the geometry that draws the ribbons, the spatial index that\n// bounds them and the hit test that measures against them. Two of those already\n// held their own copy of these four lines.\n//\n// The fan runs perpendicular to the edge, and the perpendicular is taken in\n// DRAWN space: on a row layout x is bp and y is screen px, so a perpendicular\n// taken from the raw coordinates is perpendicular to nothing on screen. `yToX`\n// converts before the direction is measured and the y component is converted\n// back, the same round trip `pointNormalsOf` makes for a node's own outline. On\n// an isotropic layout `yToX` is 1 and this is the arithmetic it always did.\n//\n// **Two abutting nodes have no chord to be perpendicular to, and in an anchored\n// layout that is every backbone link rather than a corner case**: consecutive\n// segments meet at exactly one point, so the chord is zero-length. Both callers\n// used to give up there, which left the whole backbone chain with no edge\n// geometry at all whenever `drawPaths` was on \u2014 no curve, no arrowhead, nothing\n// to hover \u2014 and the abutting nodes hid it. A node's own drawn direction answers\n// what the chord cannot, and for a backbone running along x it fans the ribbons\n// across the row, which is where they belong.\nexport function pathRibbonOffsets(\n fromSegments: NodeSegment[],\n toSegments: NodeSegment[],\n count: number,\n axis: AxisScale,\n spacing = PATH_RIBBON_SPACING,\n) {\n const yToX = yToXOf(axis)\n const dir = drawnDirection(\n fromSegments[fromSegments.length - 1]!,\n toSegments[0]!,\n yToX,\n ) ??\n drawnDirection(fromSegments[0]!, fromSegments.at(-1)!, yToX) ??\n drawnDirection(toSegments[0]!, toSegments.at(-1)!, yToX) ?? { x: 1, y: 0 }\n const offsets: { x: number; y: number }[] = []\n for (let i = 0; i < count; i++) {\n const distance = (i - (count - 1) / 2) * spacing\n offsets.push({ x: -dir.y * distance, y: (dir.x * distance) / yToX })\n }\n return offsets\n}\n\nfunction scaleYOf(points: NodeSegment[], yToX: number) {\n return points.map(p => ({ x: p.x, y: p.y * yToX }))\n}\n\nfunction unscaleYOf(curves: BezierCurve[], yToX: number) {\n for (const c of curves) {\n c.y0 /= yToX\n c.cy0 /= yToX\n c.cy1 /= yToX\n c.y1 /= yToX\n }\n return curves\n}\n\n// How far a self-loop or hairpin reaches off its node, in layout units: half\n// the node's drawn length, held between two screen sizes. Bandage uses a fixed\n// `edgeLength` in scene units, which fits one layout's units and no other's: a\n// fixed 50 units was 50 kb of drawing under the engine (1 unit per kb) and 50\n// bp on an anchored row, and under the engine it drew a 100 px oval around a\n// node a tenth that long.\nconst SELF_LOOP_MIN_PX = 12\nconst SELF_LOOP_MAX_PX = 40\n\nfunction selfLoopReach(segments: NodeSegment[], scale: number) {\n let length = 0\n for (let i = 1; i < segments.length; i++) {\n length += Math.hypot(\n segments[i]!.x - segments[i - 1]!.x,\n segments[i]!.y - segments[i - 1]!.y,\n )\n }\n const nodePx = length * scale\n const reachPx = Math.min(\n SELF_LOOP_MAX_PX,\n Math.max(SELF_LOOP_MIN_PX, nodePx / 2),\n )\n return reachPx / Math.max(scale, Number.EPSILON)\n}\n\n// How an edge joins a node to itself: `true` is a loop from the end back\n// round to the start, a Side is a hairpin that leaves that end and returns to\n// it, and `false` is an edge between two nodes.\nexport type SelfLink = boolean | Side\n\n// `L a + a -` leaves a's end and arrives at the start of a read backwards,\n// which is a's end again, and `L a - a +` is the same at its start. Drawn end\n// to start like a cycle it named the wrong ends, which is the difference\n// between an inverted repeat and a tandem one. Bandage draws the same case\n// with its own path (makeSpecialPathConnectingNodeToReverseComplement).\nexport function selfLinkOf(edge: GraphEdge): SelfLink {\n if (edge.from !== edge.to) {\n return false\n }\n return edge.fromStrand && edge.toStrand && edge.fromStrand !== edge.toStrand\n ? edge.fromStrand === '+'\n ? 'end'\n : 'start'\n : true\n}\n\n// A teardrop off one end, after Bandage's: it leaves along the node, turns at\n// the apex and comes back in on itself.\nfunction hairpinCurves(\n attach: { at: NodeSegment; inward: NodeSegment | undefined },\n reach: number,\n offsetX: number,\n offsetY: number,\n): BezierCurve[] {\n const { at, inward } = attach\n const dx = inward ? at.x - inward.x : 1\n const dy = inward ? at.y - inward.y : 0\n const len = Math.hypot(dx, dy) || 1\n const ux = dx / len\n const uy = dy / len\n const px = at.x + offsetX\n const py = at.y + offsetY\n const nearX = px + (ux * reach) / 3\n const nearY = py + (uy * reach) / 3\n const apexX = px + ux * reach\n const apexY = py + uy * reach\n const sideX = (-uy * reach) / 2\n const sideY = (ux * reach) / 2\n return [\n {\n x0: px,\n y0: py,\n cx0: nearX,\n cy0: nearY,\n cx1: apexX + sideX,\n cy1: apexY + sideY,\n x1: apexX,\n y1: apexY,\n },\n {\n x0: apexX,\n y0: apexY,\n cx0: apexX - sideX,\n cy0: apexY - sideY,\n cx1: nearX,\n cy1: nearY,\n x1: px,\n y1: py,\n },\n ]\n}\n\nexport function computeEdgeCurves(\n fromSegments: NodeSegment[],\n toSegments: NodeSegment[],\n selfLink: SelfLink,\n offsetX: number,\n offsetY: number,\n axis: AxisScale,\n // The nodes this edge bypasses, for a deletion; empty for every other edge.\n // Given them, the curve bows around them.\n //\n // Topologically a deletion is a bubble whose reference arm is a run of long\n // nodes and whose own arm is a bare link, so at the engine's 5-unit edge\n // length it collapses into a stub at a joint and the one event a graph shows\n // better than a linear view is invisible. Bowing it around that run is what\n // makes the two arms comparable, which is what a reader has to see to read it\n // as an alternative route.\n //\n // Taken as nodes rather than as a precomputed number because the chord is\n // needed to turn them into one, and only this function knows the chord -- it\n // picks the attachment ends. Three callers build this curve (the drawing, the\n // hit index and the label that rides it) and a second derivation of it is a\n // second thing to keep in step; that is exactly how the label and the arc once\n // came out in different corners of the drawing.\n bypassed: NodeSegment[] = [],\n // Only the recursion below passes this: the cap belongs to the layout, so it\n // is read off `axis` at the top level and handed down already converted into\n // the isotropic body's units.\n maxBowIn?: number,\n): BezierCurve[] {\n const scale = axis.scaleX\n const yToX = yToXOf(axis)\n const maxBow = maxBowIn ?? (axis.pixelRows ? MAX_ROW_BOW_PX : undefined)\n if (yToX !== 1) {\n // Recur once with y already in x units, then put the answer back. The\n // isotropic body below is the only implementation, so an anisotropic\n // drawing cannot drift from the one every force-directed figure uses.\n //\n // It costs three small arrays per edge, which was worth measuring before\n // being clever about: over 8,000 deletion-bearing edges \u2014 the top of the\n // range this view draws at all \u2014 the whole pass is 1.9 ms isotropic against\n // 2.8 ms here, once per debounced rebuild, against the ~9 ms of geometry the\n // toolbar already reports. Inlining the conversion would buy 0.9 ms and cost\n // the single implementation, so it is not done.\n return unscaleYOf(\n computeEdgeCurves(\n scaleYOf(fromSegments, yToX),\n scaleYOf(toSegments, yToX),\n selfLink,\n offsetX,\n offsetY * yToX,\n { scaleX: scale, scaleY: scale },\n scaleYOf(bypassed, yToX),\n // the cap is a y measure and the body works in x units, so it converts\n // with everything else that crosses this boundary\n maxBow === undefined ? undefined : maxBow * yToX,\n ),\n yToX,\n )\n }\n if (typeof selfLink === 'string') {\n return hairpinCurves(\n attachment(fromSegments, selfLink),\n selfLoopReach(fromSegments, scale),\n offsetX,\n offsetY,\n )\n }\n const sides = selfLink\n ? { from: 'end' as Side, to: 'start' as Side }\n : facingSides(fromSegments, toSegments)\n const fromAttach = attachment(fromSegments, sides.from)\n const toAttach = attachment(toSegments, sides.to)\n const fromEnd = fromAttach.at\n const toStart = toAttach.at\n\n const p1x = fromEnd.x + offsetX\n const p1y = fromEnd.y + offsetY\n const p2x = toStart.x + offsetX\n const p2y = toStart.y + offsetY\n\n if (selfLink) {\n let segDirX = 1\n let segDirY = 0\n if (fromAttach.inward) {\n const dx = fromEnd.x - fromAttach.inward.x\n const dy = fromEnd.y - fromAttach.inward.y\n const len = Math.hypot(dx, dy)\n if (len > 0) {\n segDirX = dx / len\n segDirY = dy / len\n }\n }\n\n const ext = selfLoopReach(fromSegments, scale)\n const perpX = -segDirY\n const perpY = segDirX\n const midX = (fromEnd.x + toStart.x) / 2 + offsetX + perpX * ext\n const midY = (fromEnd.y + toStart.y) / 2 + offsetY + perpY * ext\n const cp1x = p1x + segDirX * ext\n const cp1y = p1y + segDirY * ext\n const cp2x = p2x - segDirX * ext\n const cp2y = p2y - segDirY * ext\n\n return [\n {\n x0: p1x,\n y0: p1y,\n cx0: cp1x,\n cy0: cp1y,\n cx1: cp1x + perpX * ext,\n cy1: cp1y + perpY * ext,\n x1: midX,\n y1: midY,\n },\n {\n x0: midX,\n y0: midY,\n cx0: cp2x + perpX * ext,\n cy0: cp2y + perpY * ext,\n cx1: cp2x,\n cy1: cp2y,\n x1: p2x,\n y1: p2y,\n },\n ]\n } else {\n // The point just inside each node from where the edge attaches, so\n // projectLine extends OUTWARD from the node. A node drawn as a single point\n // has no direction of its own; mirroring the chord through the attachment\n // leaves it pointing at its partner, i.e. a straight leader.\n const fromPrev = fromAttach.inward ?? {\n x: fromEnd.x - (toStart.x - fromEnd.x),\n y: fromEnd.y - (toStart.y - fromEnd.y),\n }\n const toNext = toAttach.inward ?? {\n x: toStart.x - (fromEnd.x - toStart.x),\n y: toStart.y - (fromEnd.y - toStart.y),\n }\n\n const dist = Math.hypot(p2x - p1x, p2y - p1y)\n // Each control point leaves along its OWN node's direction, so the tangent\n // extension may only run as far as the other endpoint actually lies in that\n // direction. Sizing it by the whole separation instead cannot work on an\n // anchored layout, where the separation is mostly the row drop: both edges\n // of a bubble then leave sideways and come back, and the pair draws as a\n // crossed bowtie rather than as a bubble (pinned by bubbleCrossing.test.ts,\n // which fails on 17 of the pggb subgraph's alleles under a flat\n // `dist * 0.35`). Projecting the separation onto the tangent is rotation\n // invariant, so a force-directed layout, whose edges do run along their\n // nodes, is unchanged; and it is clamped at 0, so an edge that doubles back\n // gets a straight leader instead of a loop.\n const tangentProj = (\n prev: { x: number; y: number },\n at: { x: number; y: number },\n towardX: number,\n towardY: number,\n ) => {\n const dx = at.x - prev.x\n const dy = at.y - prev.y\n const len = Math.hypot(dx, dy)\n const along = len === 0 ? 0 : (towardX * dx + towardY * dy) / len\n return Math.max(\n 0,\n Math.min(dist * TANGENT_SPAN_FACTOR, MAX_TANGENT, along * 0.5),\n )\n }\n const towardToX = toStart.x - fromEnd.x\n const towardToY = toStart.y - fromEnd.y\n const [cx1, cy1] = projectLine(\n fromPrev.x,\n fromPrev.y,\n fromEnd.x,\n fromEnd.y,\n tangentProj(fromPrev, fromEnd, towardToX, towardToY),\n )\n const [cx2, cy2] = projectLine(\n toNext.x,\n toNext.y,\n toStart.x,\n toStart.y,\n tangentProj(toNext, toStart, -towardToX, -towardToY),\n )\n\n // Perpendicular to the chord, so the bow is symmetric about it, and spread\n // the two control points APART along it. Perpendicular alone draws a hairpin\n // whenever the bulge exceeds the chord \u2014 which is the normal case for a\n // deletion, whose endpoints the simulation leaves adjacent however much\n // reference it skips \u2014 and a hairpin reads as a stray line rather than as a\n // route. The along-chord spread opens it into an arch.\n //\n // The spread takes the MAGNITUDE, not the signed bulge: it decides how open\n // the arch is, not which way it faces, and letting it flip swaps the two\n // control points past each other and folds the cubic into a cusp.\n const bulge = bypassed.length\n ? bowAround(p1x, p1y, p2x, p2y, bypassed, maxBow)\n : 0\n const chordLen = dist === 0 ? 1 : dist\n const ux = (p2x - p1x) / chordLen\n const uy = (p2y - p1y) / chordLen\n const bulgeX = -uy * bulge\n const bulgeY = ux * bulge\n const spreadX = ux * Math.abs(bulge)\n const spreadY = uy * Math.abs(bulge)\n\n return [\n {\n x0: p1x,\n y0: p1y,\n cx0: cx1 + offsetX + bulgeX - spreadX,\n cy0: cy1 + offsetY + bulgeY - spreadY,\n cx1: cx2 + offsetX + bulgeX + spreadX,\n cy1: cy2 + offsetY + bulgeY + spreadY,\n x1: p2x,\n y1: p2y,\n },\n ]\n }\n}\n\nfunction nearerEnd(segments: NodeSegment[], to: NodeSegment): Side {\n const start = segments[0]!\n const end = segments.at(-1)!\n return Math.hypot(start.x - to.x, start.y - to.y) <\n Math.hypot(end.x - to.x, end.y - to.y)\n ? 'start'\n : 'end'\n}\n\n// An edge drawn along the route a layout gave it: a Catmull-Rom spline from the\n// end of `from` nearer the route, through the route, into the end of `to`\n// nearer it, leaving and entering along each node's own direction.\nexport function routedEdgeCurves(\n fromSegments: NodeSegment[],\n toSegments: NodeSegment[],\n route: NodeSegment[],\n): BezierCurve[] {\n const fromAttach = attachment(\n fromSegments,\n nearerEnd(fromSegments, route[0]!),\n )\n const toAttach = attachment(toSegments, nearerEnd(toSegments, route.at(-1)!))\n const points = [fromAttach.at, ...route, toAttach.at]\n const tangent = (i: number) => {\n const p = points[i]!\n const span = (q: NodeSegment) => Math.hypot(q.x - p.x, q.y - p.y)\n const along = (inward: NodeSegment | undefined, next: NodeSegment) => {\n const len = inward ? span(inward) : 0\n const reach = span(next)\n return len === 0\n ? { x: next.x - p.x, y: next.y - p.y }\n : {\n x: ((p.x - inward!.x) / len) * reach,\n y: ((p.y - inward!.y) / len) * reach,\n }\n }\n if (i === 0) {\n return along(fromAttach.inward, points[1]!)\n }\n if (i === points.length - 1) {\n const out = along(toAttach.inward, points[i - 1]!)\n return { x: -out.x, y: -out.y }\n }\n return {\n x: (points[i + 1]!.x - points[i - 1]!.x) / 2,\n y: (points[i + 1]!.y - points[i - 1]!.y) / 2,\n }\n }\n const curves: BezierCurve[] = []\n for (let i = 0; i < points.length - 1; i++) {\n const a = points[i]!\n const b = points[i + 1]!\n const ta = tangent(i)\n const tb = tangent(i + 1)\n curves.push({\n x0: a.x,\n y0: a.y,\n cx0: a.x + ta.x / 3,\n cy0: a.y + ta.y / 3,\n cx1: b.x - tb.x / 3,\n cy1: b.y - tb.y / 3,\n x1: b.x,\n y1: b.y,\n })\n }\n return curves\n}\n", "import { firstNodeAtOrAfter, isBackbone } from './anchoredNodes'\nimport { computeEdgeCurves, routedEdgeCurves } from './util/geometry'\n\nimport type { AnchoredNode } from './anchoredNodes'\nimport type { Graph, GraphEdge, NodeSegment } from './types'\nimport type { AxisScale } from './util/geometry'\n\n// A deletion is the one kind of variation this view could not draw, and the\n// reason is structural rather than cosmetic: extra sequence is a *node*, so it\n// gets a tube, a colour and a hover. Missing sequence is an **edge** \u2014 a link\n// from one backbone segment to another that is not its neighbour \u2014 and every\n// edge in the drawing was painted the same grey, so the graph said nothing about\n// the events a linear view is worst at.\n//\n// It needs no new data. Both endpoints of such an edge carry rGFA coordinates on\n// the same stable sequence, so the bp the haplotype skips is the gap between\n// them, and the whole classification is arithmetic on SN/SO plus the segment\n// lengths already parsed. `agent-docs/reference/PANGENOME_GRAPHS.md` in\n// jbrowse-components measured what this finds on the hosted HPRC graph: 8\n// deletions in the 200 kb MHC class II window against 78 attributed alleles, 1\n// in the 70 kb C4 window.\n//\n// Two things this deliberately does not do:\n//\n// - it does not attribute the deletion to a haplotype. A backbone-to-backbone\n// skip has GRCh38 at both ends, so there is no `SN` naming a donor, and the\n// same reference note records why: a deletion only carries a donor when it\n// also carries novel sequence, and then it is not a clean deletion any more.\n// - it does not treat a same-position link as a deletion. Adjacent backbone\n// segments abut (gap 0) and a substitution's flanks can overlap by the\n// aligned bases, so only a positive gap counts.\n\n// A skip has to clear this to be drawn as a deletion. minigraph's SV-resolution\n// graph does not record anything smaller than tens of bp, so this is not a\n// biological threshold but a guard against calling a rounding artifact a\n// deletion \u2014 abutting segments are the common case and they must not light up.\nconst MIN_DELETION_BP = 1\n\nexport interface DeletionEdge {\n // index into graph.edges, which is what the renderer keys its curve ranges by\n edgeIndex: number\n // the edge's own endpoints, so anything that needs the arc as it is DRAWN can\n // rebuild it (deletionArcCurves) instead of approximating it from `bypassed`\n from: string\n to: string\n refName: string\n start: number\n end: number\n bp: number\n // Backbone node ids whose reference interval lies inside the skipped span:\n // the route a haplotype taking this edge does NOT take. An anchored layout\n // bows the deletion's arc around them; a force layout sizes its route off\n // them (deletionRoutes.ts).\n bypassed: string[]\n}\n\n// Where the backbone this deletion bypasses is drawn, as points. `bowAround`\n// turns them into how far and which way the arc bows; this only has to say which\n// points they are, and is shared so the geometry that draws the arc, the index\n// that hit-tests it and the overlay that labels it all bow around the same run.\nexport function bypassedPoints(\n nodePositions: Record<string, { x: number; y: number }[]>,\n bypassed: string[],\n) {\n return bypassed.flatMap(id => nodePositions[id] ?? [])\n}\n\n// The deletion's arc exactly as the renderer draws it, for anything that has to\n// agree with the drawing: the label that rides it, and hit detection.\n//\n// This exists because the label used to be placed at an apex derived from the\n// BYPASSED nodes while the curve was drawn between the EDGE's endpoints. In an\n// anchored layout the bypassed run lies between the endpoints, so the two agreed\n// and the divergence was invisible; under FMMM the endpoints are wherever the\n// simulation left them and the bypassed run is elsewhere, so the words and the\n// arc they named came out in different corners of the drawing\n// (`pangenome/hprc_cfhr_deletion`, reviewed 2026-07-31). One function is the fix:\n// a second derivation of the same curve is a second thing to keep in step.\nexport function deletionArcCurves(\n nodePositions: Record<string, { x: number; y: number }[]>,\n deletion: DeletionEdge,\n axis: AxisScale,\n route?: NodeSegment[],\n) {\n const from = nodePositions[deletion.from]\n const to = nodePositions[deletion.to]\n if (!from?.length || !to?.length) {\n return undefined\n }\n return route\n ? routedEdgeCurves(from, to, route)\n : computeEdgeCurves(\n from,\n to,\n deletion.from === deletion.to,\n 0,\n 0,\n axis,\n bypassedPoints(nodePositions, deletion.bypassed),\n )\n}\n\n// The backbone by stable sequence, each sorted by start, so the run a deletion\n// bypasses is a slice rather than a scan. Filtering the whole backbone per\n// candidate edge is O(edges x backbone), which at the node budget this view\n// accepts (20,000) is hundreds of millions of comparisons for a pass that runs\n// once per graph.\nfunction backboneBySequence(graph: Graph) {\n const bySequence = new Map<string, AnchoredNode[]>()\n for (const node of graph.nodes) {\n if (isBackbone(node)) {\n const list = bySequence.get(node.stable.refName)\n if (list) {\n list.push(node)\n } else {\n bySequence.set(node.stable.refName, [node])\n }\n }\n }\n for (const list of bySequence.values()) {\n list.sort((a, b) => a.stable.start - b.stable.start)\n }\n return bySequence\n}\n\n// Backbone nodes lying wholly inside [start, end) on `refName`. Sorted by\n// start, so the candidates are one contiguous run and the walk stops at the\n// first node starting past the end; only the upper bound still needs testing\n// per node, since a long node can start inside and finish outside.\nfunction bypassedNodes(\n nodes: AnchoredNode[] | undefined,\n start: number,\n end: number,\n) {\n const bypassed: string[] = []\n if (nodes) {\n for (let i = firstNodeAtOrAfter(nodes, start); i < nodes.length; i++) {\n const node = nodes[i]!\n if (node.stable.start >= end) {\n break\n }\n if (node.stable.start + node.length <= end) {\n bypassed.push(node.id)\n }\n }\n }\n return bypassed\n}\n\n// A link leaves the END of `from` as it reads it and arrives at the START of\n// `to`. Read the way the reference reads a node, its end is its right side, so\n// a link leaves `from` on the right when the two readings agree and arrives at\n// `to` on the left when they do.\n//\n// A deletion joins the right side of the left node to the left side of the\n// right one, from whichever strand the file states it: `L 1 + 3 +`, or the\n// same link as `L 3 - 1 -`. `L 3 + 1 +` joins the right side of the RIGHT node\n// to the left side of the left one, sequence read again, a duplication. A link\n// with one reading against the reference lands on a node's far side, an\n// inversion. Neither skips anything. Coordinates alone could not tell them\n// from a deletion: each was drawn as one, and hidden with them by default.\n//\n// A link stating no strands is judged by its coordinates, as all once were.\nfunction skipsBetween(\n edge: GraphEdge,\n from: AnchoredNode,\n to: AnchoredNode,\n forwards: boolean,\n) {\n const { fromStrand, toStrand } = edge\n if (fromStrand === undefined || toStrand === undefined) {\n return true\n }\n const leavesRight = fromStrand === (from.stable.strand ?? '+')\n const arrivesLeft = toStrand === (to.stable.strand ?? '+')\n return forwards ? leavesRight && arrivesLeft : !leavesRight && !arrivesLeft\n}\n\n// Every edge that skips reference sequence, in graph.edges order.\nexport function deletionEdges(graph: Graph): DeletionEdge[] {\n const byId = new Map(graph.nodes.map(n => [n.id, n]))\n const backbone = backboneBySequence(graph)\n const found: DeletionEdge[] = []\n for (let edgeIndex = 0; edgeIndex < graph.edges.length; edgeIndex++) {\n const edge = graph.edges[edgeIndex]!\n const from = byId.get(edge.from)\n const to = byId.get(edge.to)\n if (\n from !== undefined &&\n to !== undefined &&\n isBackbone(from) &&\n isBackbone(to) &&\n from.stable.refName === to.stable.refName\n ) {\n // Which endpoint is upstream comes from the coordinates, since a file\n // may state a link from either strand. Whether it SKIPS what lies\n // between comes from its sides: a deletion leaves the right side of the\n // left node and arrives at the left side of the right one.\n const forwards = from.stable.start <= to.stable.start\n const [left, right] = forwards ? [from, to] : [to, from]\n const start = left.stable.start + left.length\n const end = right.stable.start\n const bp = end - start\n if (bp >= MIN_DELETION_BP && skipsBetween(edge, from, to, forwards)) {\n const refName = left.stable.refName\n found.push({\n edgeIndex,\n from: edge.from,\n to: edge.to,\n refName,\n start,\n end,\n bp,\n bypassed: bypassedNodes(backbone.get(refName), start, end),\n })\n }\n }\n }\n return found\n}\n", "import { isBackbone } from '../anchoredNodes'\n\nimport type { Graph, GraphEdge, GraphNode, NodeSegment } from '../types'\n\n// A base-level graph chops its sequence into short nodes at every variant and\n// every kilobase, so a cut of it is thousands of nodes strung in unbranching\n// runs. FMMM only needs the runs: each becomes one node, laid out as one\n// chain, and its positions are split back onto the members afterwards, so the\n// drawing, the hit index and the labels never see the merged graph.\n//\n// A node joins the run before it when the run's end side and the node's start\n// side each hold exactly one link, and it is that link read forward. Sides, not\n// in- and out-edges: a graph holds one node per segment, so `L a + b -` joins\n// b's END, and counting it as an in-edge of b both merged across it and handed\n// the engine a link attached at the wrong end. A self loop keeps its node alone.\n\nexport interface MergedRuns {\n graph: Graph\n // merged node id to the member ids in chain order; a run of one is absent\n runs: Map<string, string[]>\n}\n\ntype Side = 'start' | 'end'\n\n// The strand a node's id carries, which is the one its drawn chain reads.\nfunction ownStrand(id: string) {\n return id.endsWith('-') ? '-' : '+'\n}\n\nfunction flip(strand: '+' | '-') {\n return strand === '+' ? '-' : '+'\n}\n\nfunction sidesOf(edge: GraphEdge) {\n const from: Side =\n (edge.fromStrand ?? ownStrand(edge.from)) === ownStrand(edge.from)\n ? 'end'\n : 'start'\n const to: Side =\n (edge.toStrand ?? ownStrand(edge.to)) === ownStrand(edge.to)\n ? 'start'\n : 'end'\n return { from, to }\n}\n\nexport function mergeRuns(\n graph: Graph,\n alone: ReadonlySet<string> = new Set(),\n): MergedRuns {\n const sides = graph.edges.map(sidesOf)\n // node side -> indexes of the edges attached there\n const at = new Map<string, number[]>()\n const attach = (id: string, side: Side, ei: number) => {\n const key = `${id}|${side}`\n const list = at.get(key)\n if (list) {\n list.push(ei)\n } else {\n at.set(key, [ei])\n }\n }\n graph.edges.forEach((edge, ei) => {\n attach(edge.from, sides[ei]!.from, ei)\n attach(edge.to, sides[ei]!.to, ei)\n })\n // the edge that carries node `a`'s run on into the next node, if one does\n const joinAfter = (a: string) => {\n const out = at.get(`${a}|end`)\n if (out?.length !== 1) {\n return undefined\n }\n const ei = out[0]!\n const edge = graph.edges[ei]!\n return edge.from === a &&\n edge.to !== a &&\n !alone.has(a) &&\n !alone.has(edge.to) &&\n sides[ei]!.from === 'end' &&\n sides[ei]!.to === 'start' &&\n at.get(`${edge.to}|start`)?.length === 1\n ? ei\n : undefined\n }\n const joined = new Set<number>()\n const runOf = new Map<string, string>()\n const runs = new Map<string, string[]>()\n for (const node of graph.nodes) {\n const into = at.get(`${node.id}|start`)\n const pred = into?.length === 1 ? graph.edges[into[0]!]!.from : undefined\n if (\n runOf.has(node.id) ||\n (pred !== undefined && joinAfter(pred) === into![0])\n ) {\n continue\n }\n const run = [node.id]\n for (;;) {\n const ei = joinAfter(run.at(-1)!)\n const next = ei === undefined ? undefined : graph.edges[ei]!.to\n if (next === undefined || runOf.has(next)) {\n break\n }\n joined.add(ei!)\n run.push(next)\n }\n for (const id of run) {\n runOf.set(id, node.id)\n }\n if (run.length > 1) {\n runs.set(node.id, run)\n }\n }\n // a cycle with no branch has no run start; the loop above never reached it\n for (const node of graph.nodes) {\n if (!runOf.has(node.id)) {\n runOf.set(node.id, node.id)\n }\n }\n\n const byId = new Map(graph.nodes.map(n => [n.id, n]))\n const nodes: GraphNode[] = []\n for (const node of graph.nodes) {\n if (runOf.get(node.id) !== node.id) {\n continue\n }\n const run = runs.get(node.id)\n if (!run) {\n nodes.push(node)\n continue\n }\n const members = run.map(id => byId.get(id)!)\n const backbone = members.filter(isBackbone)\n const first = backbone.length\n ? backbone.reduce((a, b) => (a.stable.start <= b.stable.start ? a : b))\n : members[0]!\n nodes.push({\n id: node.id,\n name: node.name,\n length: members.reduce((sum, n) => sum + n.length, 0),\n depth: node.depth,\n stable: first.stable,\n })\n }\n // A link can only reach a run at its free ends, the first member's start and\n // the last's end, since every inner side holds exactly the join. So a\n // member's side is the run's side, and a flipped one crosses over as the\n // strand opposite the run's own.\n const seen = new Set<string>()\n const edges: GraphEdge[] = []\n graph.edges.forEach((edge, ei) => {\n const from = runOf.get(edge.from)!\n const to = runOf.get(edge.to)!\n const side = sides[ei]!\n const key = `${from}|${side.from}>${to}|${side.to}`\n if (joined.has(ei) || seen.has(key)) {\n return\n }\n seen.add(key)\n edges.push({\n from,\n to,\n ...(side.from === 'start' ? { fromStrand: flip(ownStrand(from)) } : {}),\n ...(side.to === 'end' ? { toStrand: flip(ownStrand(to)) } : {}),\n })\n })\n return {\n graph: { ...graph, nodes, edges, paths: undefined, pathVisits: undefined },\n runs,\n }\n}\n\n// Each member takes the stretch of its run's polyline its weight entitles it\n// to, in chain order, with the shared points interpolated so the members abut.\nexport function splitRuns(\n positions: Record<string, NodeSegment[]>,\n runs: Map<string, string[]>,\n weightOf: (id: string) => number,\n) {\n const out: Record<string, NodeSegment[]> = { ...positions }\n for (const [id, members] of runs) {\n const line = positions[id]\n if (!line?.length) {\n continue\n }\n delete out[id]\n const arc = arcPrefix(line)\n const total = arc.at(-1)!\n const weights = members.map(m => Math.max(weightOf(m), 0))\n const weightSum = weights.reduce((a, b) => a + b, 0) || members.length\n let from = 0\n members.forEach((member, k) => {\n const share = weightSum\n ? (weights[k]! / weightSum) * total\n : total / members.length\n const to = k === members.length - 1 ? total : from + share\n out[member] = slice(line, arc, from, to)\n from = to\n })\n }\n return out\n}\n\n// The stretch of a polyline between two fractions of its arc length, with the\n// ends interpolated. What splitRuns cuts a run with, and what the gene pins cut\n// a backbone node with to place an exon.\nexport function polylineSlice(line: NodeSegment[], from: number, to: number) {\n if (line.length === 0) {\n return []\n }\n const arc = arcPrefix(line)\n const total = arc.at(-1)!\n return slice(line, arc, from * total, to * total)\n}\n\n// The point halfway along a polyline by arc length, where a node's label goes.\nexport function polylineMidpoint(line: NodeSegment[]): NodeSegment {\n const arc = arcPrefix(line)\n return pointAt(line, arc, arc.at(-1)! / 2)\n}\n\n// Cumulative distance to each point of a polyline, starting at 0.\nfunction arcPrefix(line: NodeSegment[]) {\n const arc = [0]\n for (let i = 1; i < line.length; i++) {\n arc.push(\n arc[i - 1]! +\n Math.hypot(line[i]!.x - line[i - 1]!.x, line[i]!.y - line[i - 1]!.y),\n )\n }\n return arc\n}\n\nfunction pointAt(line: NodeSegment[], arc: number[], s: number): NodeSegment {\n if (s <= 0) {\n return { ...line[0]! }\n }\n let i = 1\n while (i < arc.length && arc[i]! < s) {\n i++\n }\n if (i >= arc.length) {\n return { ...line.at(-1)! }\n }\n const span = arc[i]! - arc[i - 1]!\n const t = span > 0 ? (s - arc[i - 1]!) / span : 0\n const a = line[i - 1]!\n const b = line[i]!\n return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t }\n}\n\nfunction slice(line: NodeSegment[], arc: number[], from: number, to: number) {\n const points = [pointAt(line, arc, from)]\n for (let i = 0; i < line.length; i++) {\n if (arc[i]! > from && arc[i]! < to) {\n points.push({ ...line[i]! })\n }\n }\n points.push(pointAt(line, arc, to))\n return points\n}\n", "// Every label drawn over the graph claims a box in screen px from one\n// occupancy, so a label that would land on one already placed, or off the pane,\n// is dropped whichever overlay it belongs to. Callers take in priority order.\n// A chip is drawn whole or not at all.\n\nexport interface Box {\n x0: number\n x1: number\n y0: number\n y1: number\n}\n\nexport const LABEL_PX = 11\nexport const LABEL_CHAR_PX = 6.2\nexport const LABEL_PAD = 4\n\nexport interface LabelCandidate<T> {\n item: T\n x: number\n y: number\n text: string\n // how many rows down to try when the spot is taken, for a label that is\n // worth a column beside its neighbours rather than dropping\n stack?: number\n // a shorter text to try where `text` does not fit, such as against the\n // pane's edge\n fallback?: string\n}\n\nexport interface PlacedLabel<T> extends LabelCandidate<T> {\n w: number\n}\n\nexport type TakeBox = (box: Box) => boolean\n\nexport function labelWidth(text: string) {\n return text.length * LABEL_CHAR_PX + LABEL_PAD * 2\n}\n\nexport function overlaps(a: Box, b: Box) {\n return a.x0 < b.x1 && a.x1 > b.x0 && a.y0 < b.y1 && a.y1 > b.y0\n}\n\ninterface Frame {\n width: number\n height: number\n}\n\nfunction offFrame(box: Box, frame: Frame) {\n return (\n box.x1 <= 0 ||\n box.x0 >= frame.width ||\n box.y1 <= 0 ||\n box.y0 >= frame.height\n )\n}\n\nfunction insideFrame(box: Box, frame: Frame) {\n return (\n box.x0 >= 0 &&\n box.x1 <= frame.width &&\n box.y0 >= 0 &&\n box.y1 <= frame.height\n )\n}\n\nexport function occupancy(frame: Frame, reserved: Box[] = []): TakeBox {\n const placed = [...reserved]\n return box => {\n if (offFrame(box, frame) || placed.some(p => overlaps(p, box))) {\n return false\n }\n placed.push(box)\n return true\n }\n}\n\nexport function placeLabels<T>(\n candidates: LabelCandidate<T>[],\n frame: Frame,\n take: TakeBox,\n): PlacedLabel<T>[] {\n const out: PlacedLabel<T>[] = []\n const row = LABEL_PX + LABEL_PAD * 2\n // a stack grows away from the nearer edge of the pane\n const place = (c: LabelCandidate<T>, text: string) => {\n const w = labelWidth(text)\n const direction = c.y > frame.height / 2 ? -1 : 1\n for (let tries = 0; tries <= (c.stack ?? 0); tries++) {\n const y = c.y + direction * tries * row\n const box = {\n x0: c.x - w / 2,\n x1: c.x + w / 2,\n y0: y - LABEL_PX - LABEL_PAD,\n y1: y + LABEL_PAD,\n }\n if (offFrame(box, frame)) {\n return undefined\n }\n if (insideFrame(box, frame) && take(box)) {\n return { ...c, text, y, w }\n }\n }\n return undefined\n }\n for (const c of candidates) {\n const placed =\n place(c, c.text) ??\n (c.fallback === undefined ? undefined : place(c, c.fallback))\n if (placed) {\n out.push(placed)\n }\n }\n return out\n}\n", "import { deletionArcCurves } from './deletionEdges'\nimport { polylineMidpoint } from './layout/mergeRuns'\nimport { occupancy } from './overlayLabels'\nimport { curveBounds, curveMidpoint } from './util/geometry'\n\nimport type { DeletionEdge } from './deletionEdges'\nimport type { DeletionRoutes } from './layout/deletionRoutes'\nimport type { Box, TakeBox } from './overlayLabels'\nimport type { AlleleDeletion, NodeSegment } from './types'\nimport type { AxisScale, BezierCurve } from './util/geometry'\n\n// What to write on the drawing, and where. Bandage labels a node with its name,\n// its length or its depth; a segment id means nothing to a reader of a pangenome\n// (\"s462766\"), so the label here is the one fact that carries the story \u2014 how\n// much sequence the alternative is worth. A node says its length, a deletion arc\n// says what it skips.\n//\n// **Every length written here is positive, and a deletion says it is one.**\n// A deletion arc used to be labelled \"\u221294.2 kb\", and a review read that as the\n// node's sequence length being negative, which is the only thing a bare signed\n// number beside a graph can mean. \"skips 94.2 kb\" replaced it and left a reader\n// asking 94.2 kb of what; \"skips 94.2 kb of reference\" answered that and still\n// drew \"it is sort of unclear what 'skips X bp of reference' means. is there any\n// better wording?\" on a third round. So the label now names the event rather\n// than describing the arc's mechanics: \"94.2 kb del\" abbreviates the term a\n// reader already has for reference sequence a haplotype does not carry, and\n// against the reference is what a deletion is measured from by default.\n//\n// **Two rules decide which labels there are, and both are about the drawing\n// rather than about a threshold on the graph.**\n//\n// A label has to fit the node it names, within a factor of two: past that it is\n// a label with a node attached rather than a node with a label. The rule used to\n// be a flat minimum drawn length (46 screen px) regardless of how long the text\n// was, which reads as arbitrary from the outside \u2014 review asked why some nodes\n// state a length and others do not, and nothing in the picture could answer it.\n// Measured against the text, the answer is in the picture: the node is shorter\n// than the words.\n//\n// And a label is dropped when its box would overlap one already placed. Labels\n// go biggest-sequence-first, so what survives a crowd is the allele that\n// matters; this is also what fixes labels printing on top of each other (\"\u221210\n// kb3.2 kb\" in the amylase figure).\n//\n// Between them they hold at both ends of the scale a pangenome graph spans: 63\n// kb-scale nodes at the amylase locus nearly all carry a length, and a 154-node\n// base-level pggb graph of 1-100 bp segments carries almost none \u2014 where every\n// label would have said \"1 bp\" and covered the segment it named.\n//\n// Collision is measured in SCREEN pixels, because that is where the text has its\n// size: the same two labels clear each other zoomed in and collide zoomed out,\n// so a wide view sheds labels on its own. This is why there is no \"show labels\"\n// toggle \u2014 a control whose only job is to undo a bad default is a bad default.\n\n// A deletion is worth saying even when its arc is small, because the arc is the\n// only thing on screen that represents it; but not when it is a dot.\n//\n// Measured on the arc's DRAWN EXTENT (the larger side of its bounds, in screen\n// px), not on its bulge. Bulge is in whatever units the layout works in: under\n// FMMM it is a fraction of the bypassed nodes' drawn length at the engine's own\n// scale, and in an anchored layout it is a fraction of their length in bp. The\n// same deletion therefore cleared this in one layout and not the other, which is\n// how `pangenome/hprc_cfhr_deletion` lost the labels on its 2.2 kb and 9.3 kb\n// arcs by changing nothing but the layout. Extent is what a reader sees either\n// way, and it is the same quantity the node rule below uses.\nconst MIN_DELETION_LABEL_PX = 26\n\n// One node label per this much pane, and never fewer than a handful: at a\n// 1400 by 600 pane that is about twenty.\nconst PX_PER_NODE_LABEL = 40_000\nconst MIN_NODE_LABELS = 8\n\n// A deletion clearing that floor still need not be able to CARRY its label: the\n// text is a fixed 26 characters however small the event's arc is, so at the LPA\n// KIV-2 locus `skips 27.7 kb of reference` came out four times the width of the\n// arc it was centred on, reading as a caption dropped on the bubble beside it\n// rather than as that arc's name (`pangenome/hprc_lpa_kiv2`, reviewed\n// 2026-07-31).\n//\n// A node in the same position simply loses its label, and that is right for a\n// node \u2014 the tube is still drawn, still coloured, still hoverable, and the graph\n// is full of them. It is wrong for a deletion, which is one dashed curve\n// standing for sequence that is not in the picture at all: dropped, nothing\n// tells a reader the arc is an event rather than another link.\n//\n// So the label moves off the arc instead and takes a leader line with it. It is\n// displaced along the direction the arc bows \u2014 which is the side the bulge was\n// drawn INTO, so it is the open side by construction \u2014 far enough that its box\n// clears the curve, and the leader states the association the proximity no\n// longer does.\nconst LEADER_GAP_PX = 8\n\n// The label's own box, for collision. Mirrors GraphCanvas's nodeLabelStyle:\n// 10px text in 3px of horizontal padding, on a 13px line. Character width is an\n// average rather than a measurement \u2014 placement runs on every pan and zoom, and\n// a canvas text metric per candidate costs more than the occasional\n// half-character of slack is worth.\nconst LABEL_CHAR_PX = 5.7\nconst LABEL_PAD_PX = 6\nconst LABEL_HEIGHT_PX = 13\n// Breathing room, so two labels that merely touch still read as two.\nconst LABEL_GAP_PX = 3\n// How much wider than its node a label may be before it is the label that is\n// being drawn rather than the node.\nconst MAX_LABEL_OVERHANG = 2\n\n// Half the box a piece of label text occupies. Split out of `labelBox` because\n// the width is wanted before there is anywhere to put the box: a displaced\n// deletion label is clamped by its own half-width, and the node pass below\n// rejects on size before it has a position at all.\nfunction labelHalfWidth(text: string) {\n return (text.length * LABEL_CHAR_PX + LABEL_PAD_PX + LABEL_GAP_PX) / 2\n}\n\n// Constant whatever the label says, so a candidate's vertical cull costs\n// nothing and can be taken before its text exists.\nconst LABEL_HALF_HEIGHT = (LABEL_HEIGHT_PX + LABEL_GAP_PX) / 2\n\n// The narrowest box `formatBp` can ask for, `1 bp`. A node drawn shorter than\n// this cannot carry ANY label, so the check happens before the length is\n// formatted: on a 7,499-node cut nearly every node fails it, and building a\n// string and a box per node only to discard both was most of this pass\n// (measured 37 ms per frame, and this pass runs on every pan).\nconst MIN_LABEL_HALF_WIDTH = labelHalfWidth('1 bp')\n\n// translate(-50%, -50%) in the style, so the anchor is the box's centre.\nfunction boxAt(halfW: number, x: number, y: number): Box {\n return {\n x0: x - halfW,\n x1: x + halfW,\n y0: y - LABEL_HALF_HEIGHT,\n y1: y + LABEL_HALF_HEIGHT,\n }\n}\n\n// The box a row label occupies, from the same metrics RowLabels renders with\n// (11px text, 4px of horizontal padding, a 16px line, centred on its row). Lives\n// here rather than in the component so the two halves of the collision test\n// cannot drift apart.\nexport function rowLabelBox(text: string, screenY: number): Box {\n return {\n x0: 6,\n x1: 6 + text.length * 6.2 + 8 + LABEL_GAP_PX,\n y0: screenY - 8 - LABEL_GAP_PX / 2,\n y1: screenY + 8 + LABEL_GAP_PX / 2,\n }\n}\n\n// Slide a value into [lo, hi], leaving it alone when the range is empty \u2014 a box\n// wider than the canvas has nowhere to fit, and pinning it to one edge is worse\n// than the overflow.\nfunction clamp(v: number, lo: number, hi: number) {\n return hi < lo ? v : Math.min(Math.max(v, lo), hi)\n}\n\n// A displaced label's tether, in the same screen pixels the label is placed in.\n// Both ends are computed here rather than in the component, because the label's\n// end is the point where its own box meets the line \u2014 geometry the placement\n// already has and the painter would have to rederive. It stops at the box edge\n// rather than at the centre because the label's background is translucent, so a\n// line run under it shows through the text.\nexport interface Leader {\n arcX: number\n arcY: number\n labelX: number\n labelY: number\n}\n\nexport interface GraphLabel {\n key: string\n text: string\n // screen pixels, already through the transform the canvas draws with: the\n // collision test needs them anyway, and returning layout units would have the\n // caller redo the arithmetic that decided which labels there are\n x: number\n y: number\n kind: 'node' | 'deletion'\n // present only on a label that had to move off the thing it names\n leader?: Leader\n}\n\n// Locale-independent, unlike toLocaleString: a figure regenerated on a machine\n// with a different locale would otherwise differ from the committed one.\nexport function groupDigits(n: number) {\n return String(n).replace(/\\B(?=(\\d{3})+$)/g, ',')\n}\n\n// bp in the unit that reads. Deliberately coarse: a label is a glance, and\n// \"12,345 bp\" costs twice the width of \"12 kb\" to say the same thing at the\n// scale a pangenome allele lives at.\nexport function formatBp(bp: number) {\n if (bp >= 1_000_000) {\n return `${+(bp / 1_000_000).toFixed(1)} Mb`\n }\n return bp >= 1000 ? `${+(bp / 1000).toFixed(1)} kb` : `${bp} bp`\n}\n\nfunction deletionText(bp: number) {\n return `${formatBp(bp)} del`\n}\n\n// Which way the arc bows, as a unit vector: its chord's midpoint towards its\n// apex. A deletion's bulge is perpendicular to the chord by construction, so\n// this points away from the backbone the arc leaves and rejoins \u2014 the side the\n// curve was opened into, and the side with room in it.\n//\n// Taken on SCREEN, which is where the label it aims has to clear the curve. That\n// used to be free \u2014 with one scale for both axes the transform is a similarity,\n// so a layout direction is a screen direction \u2014 and on a row layout it is not:\n// the two axes are bp and px and the arc leaves at a different angle in each.\nfunction arcOutward(\n curves: BezierCurve[],\n apex: { x: number; y: number },\n scaleX: number,\n scaleY: number,\n) {\n const first = curves[0]!\n const last = curves[curves.length - 1]!\n const dx = (apex.x - (first.x0 + last.x1) / 2) * scaleX\n const dy = (apex.y - (first.y0 + last.y1) / 2) * scaleY\n const len = Math.hypot(dx, dy)\n // A curve with no bow has no outward side; up is as good as any, and this is\n // unreachable for a deletion, whose bulge is what gives it extent at all.\n return len === 0 ? { x: 0, y: -1 } : { x: dx / len, y: dy / len }\n}\n\n// How far the box's own boundary is from its centre in a given direction \u2014 the\n// support function of an axis-aligned rectangle. Displacing a label by this plus\n// a gap puts its near EDGE that gap away from the point, whatever angle the\n// leader leaves at, which a single radius cannot do for a box six times wider\n// than it is tall.\nfunction boxSupport(box: Box, dir: { x: number; y: number }) {\n return (\n (Math.abs(dir.x) * (box.x1 - box.x0)) / 2 +\n (Math.abs(dir.y) * (box.y1 - box.y0)) / 2\n )\n}\n\n// Where a ray along `dir` through the box's centre crosses the box, again from\n// the centre \u2014 which is where a leader coming in along that ray has to stop.\n//\n// This is not `boxSupport`, and the difference is the whole leader: support is\n// the distance to the supporting line perpendicular to `dir`, which a box six\n// times wider than it is tall touches at a corner, so a diagonal approach hits\n// the box's real edge four or five times sooner. Stopping the line at the\n// support distance drew an 8px stub at the arc with 50px of white between it and\n// the text it was supposed to tie to.\nfunction boxRayCrossing(box: Box, dir: { x: number; y: number }) {\n const halfW = (box.x1 - box.x0) / 2\n const halfH = (box.y1 - box.y0) / 2\n return Math.min(\n dir.x === 0 ? Infinity : halfW / Math.abs(dir.x),\n dir.y === 0 ? Infinity : halfH / Math.abs(dir.y),\n )\n}\n\n// The node's own extent along its polyline, IN SCREEN PX \u2014 what the label has\n// to fit inside. Measured through the transform rather than in layout units and\n// scaled afterwards, because on a row layout the two axes are in different units\n// and there is no single number to scale a mixed length by.\nfunction drawnLength(segments: NodeSegment[], scaleX: number, scaleY: number) {\n let total = 0\n for (let i = 1; i < segments.length; i++) {\n const a = segments[i - 1]!\n const b = segments[i]!\n total += Math.hypot((b.x - a.x) * scaleX, (b.y - a.y) * scaleY)\n }\n return total\n}\n\n// The extent of a run's drawn points, in layout units. One pass rather than\n// four `Math.min(...points.map(...))` spreads over the same array: those\n// allocate a mapped copy per axis, and a run long enough \u2014 a chain of alleles\n// across a whole-file import \u2014 overflows the argument limit and throws\n// RangeError instead of labelling anything. Same reason `backboneSpan` is a\n// fold.\nfunction runBounds(\n nodeIds: string[],\n nodePositions: Record<string, NodeSegment[]>,\n) {\n let minX = Infinity\n let minY = Infinity\n let maxX = -Infinity\n let maxY = -Infinity\n for (const id of nodeIds) {\n for (const p of nodePositions[id] ?? []) {\n minX = Math.min(minX, p.x)\n maxX = Math.max(maxX, p.x)\n minY = Math.min(minY, p.y)\n maxY = Math.max(maxY, p.y)\n }\n }\n return minX === Infinity ? undefined : { minX, minY, maxX, maxY }\n}\n\n// Both caches below are keyed the way hit detection keys its spatial indexes: a\n// WeakMap on the positions object, so two graph views cannot evict each other\n// and an entry dies with the layout it describes, plus an explicit check of\n// everything the value actually depends on. A node drag mutates the positions\n// in place, so their identity cannot report that they moved \u2014 hence `version`,\n// which the model bumps on exactly that (`positionsVersion`).\n//\n// What makes the caching worth having is that this whole pass reruns on every\n// pan: the labels are placed in screen space, so a translate changes where they\n// go, and neither the order they are considered in nor the arcs they ride\n// depends on it.\n\n// Node label candidates in the order they are considered \u2014 biggest allele\n// first \u2014 which is a function of the sequence lengths alone.\nconst nodeOrderCache = new WeakMap<\n Record<string, NodeSegment[]>,\n {\n nodeLengths: Map<string, number>\n order: [string, NodeSegment[]][]\n }\n>()\n\nfunction labelOrder(\n nodePositions: Record<string, NodeSegment[]>,\n nodeLengths: Map<string, number>,\n) {\n const cached = nodeOrderCache.get(nodePositions)\n if (cached?.nodeLengths === nodeLengths) {\n return cached.order\n }\n const order = Object.entries(nodePositions)\n .filter(([id, segments]) => segments.length > 0 && nodeLengths.has(id))\n .sort(([a], [b]) => nodeLengths.get(b)! - nodeLengths.get(a)!)\n nodeOrderCache.set(nodePositions, { nodeLengths, order })\n return order\n}\n\n// One deletion arc as the renderer draws it, plus the two things placement\n// reads off it: where its apex is, and how big it is on screen.\ninterface ArcPlacement {\n deletion: DeletionEdge\n curves: BezierCurve[]\n apex: { x: number; y: number }\n extent: number\n}\n\nconst arcCache = new WeakMap<\n Record<string, NodeSegment[]>,\n {\n deletions: DeletionEdge[]\n routes: DeletionRoutes | undefined\n scaleX: number\n scaleY: number\n version: number\n arcs: ArcPlacement[]\n }\n>()\n\n// Biggest deletion first, so an arc that survives a crowd is the one carrying\n// the most sequence.\nfunction arcPlacements(\n nodePositions: Record<string, NodeSegment[]>,\n deletions: DeletionEdge[],\n axis: AxisScale,\n version: number,\n routes: DeletionRoutes | undefined,\n) {\n const { scaleX, scaleY } = axis\n const cached = arcCache.get(nodePositions)\n if (\n cached?.deletions === deletions &&\n cached.routes === routes &&\n cached.scaleX === scaleX &&\n cached.scaleY === scaleY &&\n cached.version === version\n ) {\n return cached.arcs\n }\n const arcs: ArcPlacement[] = []\n for (const deletion of [...deletions].sort((a, b) => b.bp - a.bp)) {\n const curves = deletionArcCurves(\n nodePositions,\n deletion,\n axis,\n routes?.[deletion.edgeIndex],\n )\n const apex = curves ? curveMidpoint(curves) : undefined\n if (curves && apex) {\n const { minX, minY, maxX, maxY } = curveBounds(curves)\n arcs.push({\n deletion,\n curves,\n apex,\n // in screen px, so the two axes' units cannot be added by accident\n extent: Math.max((maxX - minX) * scaleX, (maxY - minY) * scaleY),\n })\n }\n }\n arcCache.set(nodePositions, {\n deletions,\n routes,\n scaleX,\n scaleY,\n version,\n arcs,\n })\n return arcs\n}\n\ninterface SizeLabelArgs {\n nodePositions: Record<string, NodeSegment[]>\n // bp per node id, so this module never has to know what a GraphNode is\n nodeLengths: Map<string, number>\n deletions: DeletionEdge[]\n deletionRoutes?: DeletionRoutes\n // Deletions the layout found that carry a segment, so `deletions` above\n // cannot: empty under a layout drawn at sequence scale, where a node's own\n // length IS what its drawn extent means. See AlleleDeletion.\n alleleDeletions?: AlleleDeletion[]\n // Screen px per layout unit, both axes. They are equal on an isotropic layout\n // and differ on a row layout, whose y is already px (scaleY === 1). One value\n // so a caller cannot pass one and default the other \u2014 see AxisScale.\n axis: AxisScale\n translateX: number\n translateY: number\n width: number\n height: number\n // Bumped whenever the layout's positions are mutated in place, i.e. by a node\n // drag. Everything cached across frames here is keyed by it \u2014 see the caches\n // above \u2014 and reading it is also what makes a dragged node's label follow it.\n version?: number\n}\n\nexport interface SizeLabelCandidate {\n label: GraphLabel\n box: Box\n}\n\n// Deletions come first, so an arc keeps its label against the nodes around it:\n// the arc is the only thing in the drawing that represents sequence which is\n// not there, and a reader who cannot read it has no other route to it.\nexport function sizeLabelCandidates({\n nodePositions,\n nodeLengths,\n deletions,\n deletionRoutes,\n alleleDeletions,\n axis,\n translateX,\n translateY,\n width,\n height,\n version = 0,\n}: SizeLabelArgs) {\n const { scaleX, scaleY } = axis\n const deletionCandidates: SizeLabelCandidate[] = []\n const nodeCandidates: SizeLabelCandidate[] = []\n for (const { deletion, curves, apex, extent } of arcPlacements(\n nodePositions,\n deletions,\n axis,\n version,\n deletionRoutes,\n )) {\n if (extent >= MIN_DELETION_LABEL_PX) {\n const text = deletionText(deletion.bp)\n const halfW = labelHalfWidth(text)\n const box = boxAt(halfW, 0, 0)\n const arcX = apex.x * scaleX + translateX\n const arcY = apex.y * scaleY + translateY\n // The node rule, applied to an arc: a label wider than twice what it\n // names is a label with an arc attached. Under it the label stays\n // centred on the curve, which is what every arc big enough to hold its\n // own name already does.\n const fits = extent >= (halfW * 2) / MAX_LABEL_OVERHANG\n // Displaced by the SUPPORT distance, so the whole box clears the arc\n // whatever angle it leaves at, then the leader is stopped at the box's\n // real edge along the same ray.\n const bow = arcOutward(curves, apex, scaleX, scaleY)\n const offset = boxSupport(box, bow) + LEADER_GAP_PX\n // ...and then slid back into the frame if that put it outside one. A\n // tethered label chooses where it goes, and the cull below keeps any box\n // merely OVERLAPPING the canvas, so displacing blind is how the MHC force\n // layout came out with a clipped `\u2026ips 1.5 kb of reference` against its\n // left edge. Sliding beats dropping and beats picking the arc's other\n // side: the leader is redrawn to wherever the words ended up, so it stays\n // unambiguous however far along the edge they had to move.\n const x = fits ? arcX : clamp(arcX + bow.x * offset, halfW, width - halfW)\n const y = fits\n ? arcY\n : clamp(\n arcY + bow.y * offset,\n LABEL_HALF_HEIGHT,\n height - LABEL_HALF_HEIGHT,\n )\n // From where the label actually sits, not from the bow, or the line\n // points off at the position the label would have had.\n const away = Math.hypot(x - arcX, y - arcY)\n let leader\n if (away > 0) {\n const dir = { x: (x - arcX) / away, y: (y - arcY) / away }\n const crossing = boxRayCrossing(box, dir)\n leader = {\n arcX,\n arcY,\n labelX: x - dir.x * crossing,\n labelY: y - dir.y * crossing,\n }\n }\n deletionCandidates.push({\n label: {\n key: `del:${deletion.edgeIndex}`,\n text,\n x,\n y,\n kind: 'deletion',\n leader,\n },\n box: boxAt(halfW, x, y),\n })\n }\n }\n\n // Segment-carrying deletions, next: the arcs above and these are the same\n // event and are said in the same words, so they queue together ahead of the\n // node lengths and biggest-first among themselves.\n //\n // A run's label rides the middle of what the layout DREW for it, which is the\n // reference the allele replaces, and it has to fit that extent by the same\n // factor-of-two rule a node label does \u2014 so the sliver a 2 bp skip occupies in\n // a base-level graph carries nothing, and the 7.1 kb one CFT073 leaves in the\n // E. coli pggb graph carries `7 kb del`.\n //\n // The nodes it covers are then barred from labelling themselves. Their length\n // is a true fact and the wrong one to print here: `93 bp` written across a bar\n // drawn 7.1 kb wide is read as the bar's size, which is the misreading this\n // whole label exists to fix. It stays in the tooltip and the details panel.\n const labelledByDeletion = new Set<string>()\n const alleleRuns = [...(alleleDeletions ?? [])].sort((a, b) => b.bp - a.bp)\n for (const run of alleleRuns) {\n const drawn = runBounds(run.nodeIds, nodePositions)\n if (!drawn) {\n continue\n }\n // ON SCREEN, not in layout units, and that is the whole subtlety here. A\n // run drawn over the reference it replaces can be far wider than the window\n // \u2014 the E. coli one starts 7 kb before the left edge \u2014 so its true midpoint\n // is nowhere, and a label placed there is culled for being off-canvas and\n // the bar goes unnamed. The visible slice is what a reader has, so the\n // label rides the middle of that, and the fit test asks whether the words\n // fit what is SHOWN rather than what is drawn.\n const shownLeft = Math.max(drawn.minX * scaleX + translateX, 0)\n const shownRight = Math.min(drawn.maxX * scaleX + translateX, width)\n const shownTop = Math.max(drawn.minY * scaleY + translateY, 0)\n const shownBottom = Math.min(drawn.maxY * scaleY + translateY, height)\n if (shownRight < shownLeft || shownBottom < shownTop) {\n continue\n }\n const extent = Math.max(shownRight - shownLeft, shownBottom - shownTop)\n const text = deletionText(run.bp)\n const halfW = labelHalfWidth(text)\n if (extent >= (halfW * 2) / MAX_LABEL_OVERHANG) {\n for (const id of run.nodeIds) {\n labelledByDeletion.add(id)\n }\n const x = clamp((shownLeft + shownRight) / 2, halfW, width - halfW)\n const y = clamp(\n (shownTop + shownBottom) / 2,\n LABEL_HALF_HEIGHT,\n height - LABEL_HALF_HEIGHT,\n )\n deletionCandidates.push({\n label: {\n key: `alleledel:${run.nodeIds.join(',')}`,\n text,\n x,\n y,\n kind: 'deletion',\n },\n box: boxAt(halfW, x, y),\n })\n }\n }\n\n // Biggest allele first: in a crowd, the label that survives should be the one\n // carrying the most sequence, not whichever node the layout happened to emit\n // first. The order is `labelOrder`'s, cached across frames; the three rejects\n // below are what a frame actually decides, cheapest first \u2014 a node barred by\n // a run's label, then one whose row is off the pane (the box's height is a\n // constant, so this needs no text), then one too short to hold the shortest\n // label there is.\n for (const [id, segments] of labelOrder(nodePositions, nodeLengths)) {\n if (labelledByDeletion.has(id)) {\n continue\n }\n const { x, y } = polylineMidpoint(segments)\n const screenY = y * scaleY + translateY\n if (\n screenY + LABEL_HALF_HEIGHT <= 0 ||\n screenY - LABEL_HALF_HEIGHT >= height\n ) {\n continue\n }\n const drawn = drawnLength(segments, scaleX, scaleY)\n // The same comparison the real fit test makes, against the narrowest box\n // any label can have \u2014 so nothing that would have passed is rejected here.\n if (drawn < (MIN_LABEL_HALF_WIDTH * 2) / MAX_LABEL_OVERHANG) {\n continue\n }\n const text = formatBp(nodeLengths.get(id)!)\n const halfW = labelHalfWidth(text)\n if (drawn >= (halfW * 2) / MAX_LABEL_OVERHANG) {\n const screenX = x * scaleX + translateX\n nodeCandidates.push({\n label: {\n key: `node:${id}`,\n text,\n x: screenX,\n y: screenY,\n kind: 'node',\n },\n box: boxAt(halfW, screenX, screenY),\n })\n }\n }\n\n return { deletions: deletionCandidates, nodes: nodeCandidates }\n}\n\n// Node labels are rationed to the pane's area: a base-level cut of hundreds of\n// short nodes fits a length on most of them at a moderate zoom, and a drawing\n// where every node states its length is a table with a graph under it. Biggest\n// first, so the ration goes to the alleles that carry sequence; zooming in\n// gives more area to fewer nodes and the labels come back.\nexport function nodeLabelBudget(width: number, height: number) {\n return Math.max(\n MIN_NODE_LABELS,\n Math.floor((width * height) / PX_PER_NODE_LABEL),\n )\n}\n\nexport function placeSizeLabels(\n candidates: SizeLabelCandidate[],\n take: TakeBox,\n budget = Infinity,\n) {\n const labels: GraphLabel[] = []\n for (const { label, box } of candidates) {\n if (labels.length >= budget) {\n break\n }\n if (take(box)) {\n labels.push(label)\n }\n }\n return labels\n}\n\nexport function graphLabels({\n reserved,\n ...args\n}: SizeLabelArgs & { reserved?: Box[] }): GraphLabel[] {\n const take = occupancy(args, reserved)\n const { deletions, nodes } = sizeLabelCandidates(args)\n return [\n ...placeSizeLabels(deletions, take),\n ...placeSizeLabels(nodes, take, nodeLabelBudget(args.width, args.height)),\n ]\n}\n", "import { REFERENCE_RANK } from './anchoredNodes'\nimport { panSNSample } from './pansn'\n\nimport type {\n Graph,\n GraphNode,\n GraphPath,\n PathOrigin,\n PathVisit,\n} from './types'\n\n// Reference coordinates for a GFA that tags none of its segments with one.\n//\n// rGFA writes the fact per segment (SN/SO/SR). A pggb / odgi / Minigraph-Cactus\n// graph writes the same fact once per path and leaves the arithmetic to the\n// reader: walking a path's steps in order, accumulating segment lengths,\n// assigns every segment that path visits an interval on the path's own\n// sequence. Same information, different encoding \u2014 so a general GFA is not a\n// degraded rGFA, and the anchored layouts work on it once the walk is done.\n//\n// It carries one thing rGFA cannot: a segment's *carriage*, the set of\n// assemblies that actually traverse it. rGFA's SR is build order, so there the\n// most a segment can say is which assembly first contributed it.\n\n// `odgi extract` names an extracted path for the interval it covers on its own\n// sequence: `K12#1#chr:1004500-1004961`. That suffix is the only place a cut\n// subgraph states where in the genome it sits, so it is split off here rather\n// than carried around as part of the name \u2014 no assembly has a sequence called\n// `K12#1#chr:1004500-1004961`, and PanSN parsing of it yields a contig no\n// linear view can open.\n//\n// Anchored on the digits rather than on a trailing colon, because a stable name\n// may legitimately contain one (the same hazard gfaParser's tag splitting has).\nconst RANGE_SUFFIX = /:(\\d+)-\\d+$/\n\nexport function pathOrigin(pathName: string) {\n const match = RANGE_SUFFIX.exec(pathName)\n return match\n ? { name: pathName.slice(0, match.index), start: +match[1]! }\n : { name: pathName, start: 0 }\n}\n\nexport interface PathSteps {\n name: string\n start: number\n steps: { id: string; strand: '+' | '-' }[]\n}\n\n// The single walk. Every segment visit lands in `pathVisits` at the bp its own\n// path reaches it at; `anchorPaths` is what the walk learned about the paths\n// themselves, which is what a reference-path picker lists.\nexport function surveyPaths(\n paths: PathSteps[],\n lengthOf: (segmentId: string) => number,\n) {\n const anchorPaths: PathOrigin[] = []\n const pathVisits = new Map<string, PathVisit[]>()\n for (const path of paths) {\n const sample = panSNSample(path.name)\n let pos = path.start\n for (const step of path.steps) {\n const visit = {\n path: path.name,\n sample,\n start: pos,\n strand: step.strand,\n }\n const existing = pathVisits.get(step.id)\n if (existing) {\n existing.push(visit)\n } else {\n pathVisits.set(step.id, [visit])\n }\n pos += lengthOf(step.id)\n }\n anchorPaths.push({\n name: path.name,\n sample,\n start: path.start,\n length: pos - path.start,\n })\n }\n return { anchorPaths, pathVisits }\n}\n\n// Which path x is drawn on. This is a choice, not a fact: a general GFA's path\n// names are arbitrary and nothing in the file marks one of them as the\n// reference, so a preference is matched against a path's PanSN sample name (the\n// assembly a subgraph was cut against, which is how the launch path knows) and\n// then against its full name. With no preference the first path in the file\n// wins, which is where pggb and odgi leave the reference.\nexport function chooseReferencePath(\n anchorPaths: PathOrigin[],\n preferred: string | undefined,\n) {\n const named = anchorPaths.find(\n p => p.sample === preferred || p.name === preferred,\n )\n return named ?? anchorPaths[0]\n}\n\n// Rank on a path-derived graph is the one distinction the paths support: on the\n// reference path or off it. rGFA's higher ranks are minigraph's build order,\n// which a path GFA has no equivalent of, so drawing more rows than this would\n// be drawing structure the file does not state.\nconst OFF_REFERENCE_RANK = 1\n\n// A segment the reference path visits sits on the backbone at the offset that\n// visit reaches it. One it never visits is an alternate allele, placed on the\n// coordinates of the first path that does carry it \u2014 the same asymmetry rGFA\n// has, where a rank>0 segment's SO is an offset on the assembly that\n// contributed the sequence rather than on the reference.\n//\n// **First visit wins** when a path reaches the same segment more than once, and\n// collapsed repeats make that real rather than hypothetical: pggb folds the\n// E. coli rRNA operons into shared segments, so every strain's path walks them\n// twice (`odgi depth` reaches 10 over a five-strain graph at chr:4,167,000 and\n// chr:3,942,000). A node draws as one tube at one x, so the alternative to\n// picking a copy is one tube spanning both, claiming reference the segment does\n// not occupy. The repeat stays visible as depth: such a node's traversal count\n// is a multiple of the path count.\nfunction anchorNode(\n node: GraphNode,\n visits: PathVisit[] | undefined,\n reference: string,\n): GraphNode {\n let anchored = node\n if (visits && visits.length > 0) {\n const anchor = visits.find(v => v.path === reference) ?? visits[0]!\n anchored = {\n ...node,\n stable: {\n refName: anchor.path,\n start: anchor.start,\n rank: anchor.path === reference ? REFERENCE_RANK : OFF_REFERENCE_RANK,\n strand: anchor.strand,\n },\n samples: [...new Set(visits.map(v => v.sample))].sort(),\n }\n }\n return anchored\n}\n\n// Re-runnable against a different reference path at no parsing cost, which is\n// what the picker needs: the walk is already recorded in `pathVisits`, and only\n// which path counts as rank 0 changes.\nexport function anchorFromPaths(graph: Graph, preferred: string | undefined) {\n const { anchorPaths, pathVisits } = graph\n const reference =\n anchorPaths && pathVisits\n ? chooseReferencePath(anchorPaths, preferred)\n : undefined\n return reference && pathVisits\n ? ({\n ...graph,\n nodes: graph.nodes.map(node =>\n anchorNode(node, pathVisits.get(node.name), reference.name),\n ),\n anchoredBy: 'paths',\n referencePath: reference.name,\n } satisfies Graph)\n : graph\n}\n\n// rGFA states coordinates on every segment, so only a graph that states none\n// has to derive them. A GFA with neither tags nor paths comes back untouched\n// and falls through to the force layout, which is the honest answer for it.\nexport function anchorGraph(graph: Graph, preferred: string | undefined) {\n return graph.anchoredBy === 'tags' ? graph : anchorFromPaths(graph, preferred)\n}\n\n// `graph`'s origins once each walk is cut down to the steps `kept` gives it,\n// by path name, the first of them at index `first` of the whole walk: it\n// starts that much further along its sequence and covers only those steps. A\n// walk in several pieces cannot say which piece an origin belongs to, so its\n// origin stands.\nexport function trimOrigins(\n graph: Graph,\n kept: Map<string, { first: number; nodeIds: string[] }>,\n) {\n if (!graph.anchorPaths) {\n return undefined\n }\n const lengthOf = new Map(graph.nodes.map(n => [n.id, n.length]))\n const bp = (ids: string[], end = ids.length) => {\n let sum = 0\n for (let i = 0; i < end; i++) {\n sum += lengthOf.get(ids[i]!) ?? 0\n }\n return sum\n }\n const byOrigin = new Map<string, GraphPath[]>()\n for (const path of graph.paths ?? []) {\n const origin = pathOrigin(path.name).name\n byOrigin.set(origin, [...(byOrigin.get(origin) ?? []), path])\n }\n return graph.anchorPaths.map(o => {\n const pieces = byOrigin.get(o.name)\n const path = pieces?.length === 1 ? pieces[0]! : undefined\n const stretch = path && kept.get(path.name)\n return path && stretch\n ? {\n ...o,\n start: o.start + bp(path.nodeIds, stretch.first),\n length: bp(stretch.nodeIds),\n }\n : o\n })\n}\n", "import { groupDigits } from './graphLabels'\nimport { isGenericSample } from './pansn'\nimport { pathOrigin } from './pathAnchoring'\n\nimport type { GraphPath } from './types'\n\n// Deterministic colour from a string (djb2-style hash to an HSL hue). Fine for\n// the `random` node scheme, where a collision between two of thousands of nodes\n// means nothing. NOT what the path ribbons use: see pathHueAt.\nexport function nameHue(name: string) {\n let hash = 0\n for (let i = 0; i < name.length; i++) {\n hash = name.charCodeAt(i) + ((hash << 5) - hash)\n }\n return Math.abs(hash % 360)\n}\n\n// Ribbon hues are spread evenly over the wheel by position in the path list,\n// not hashed from the path name. A hash draws from the wheel at random, and at\n// five paths two of them landing on the same hue is common rather than\n// unlucky: `Sakai#1#chr:1983339-1983535` and `NCTC86#1#chr:1709109-1710286`\n// hash 26 degrees apart, which is two purples and a legend that cannot be read\n// off the drawing. Evenly spaced, the worst case is the best available.\n//\n// The cost is that a path's colour depends on how many paths there are, so it\n// moves when the cut changes. That is what the legend is for, and it is the\n// same trade the Bandage demo this was ported from made.\nexport function pathHueAt(index: number, count: number) {\n return (index * 360) / count\n}\n\nexport const PATH_SATURATION = 0.7\nexport const PATH_LIGHTNESS = 0.5\n\n// Past this many paths a graph cannot be drawn per path at all, and the WHOLE\n// of `drawPaths` goes off with it: the node stripes, the edge ribbons and the\n// key naming them.\n//\n// One number for all three because they are one statement, and they used to\n// disagree. Only the stripes were capped. So a Minigraph-Cactus cut, which\n// writes one W record per haplotype and walks every backbone link with all of\n// them, drew: no stripes at all, a key hundreds of rows tall, and one bezier\n// stroke per path per edge \u2014 measured at 99,800 strokes and 64 ms of geometry\n// for 200 paths over 499 links, against 2,495 strokes for the five-path pggb\n// cut the figures use. Every one of those is its own `beginPath`/`stroke` on\n// the Canvas2D backend, every frame.\n//\n// 16 is where the drawing stops SAYING anything, not where it stops being fast.\n// `pathHueAt` spreads the wheel evenly, so 16 hues are 22 degrees apart and\n// still tellable and 200 are one degree; the ribbon fan is PATH_RIBBON_SPACING\n// per path, so 16 of them is a legible fan and 200 is a band wider than the\n// nodes; and a key past about 16 rows is taller than the drawing beside it.\n// Above the cap the honest picture is the plain one \u2014 which is exactly what a\n// graph carrying no paths at all already draws.\nexport const MAX_PATH_COLORS = 16\n\n// Whether this graph's paths can be drawn as colours. Resolve it once and hand\n// the answer down: the geometry, the hit index and the key each used to decide\n// for themselves, which is how they came to disagree.\nexport function pathColorsLegible(pathCount: number) {\n return pathCount > 0 && pathCount <= MAX_PATH_COLORS\n}\n\nexport function pathCssColor(index: number, count: number) {\n return `hsl(${pathHueAt(index, count)}, ${PATH_SATURATION * 100}%, ${\n PATH_LIGHTNESS * 100\n }%)`\n}\n\n// Beside reads, which take the blues and the reds, or boxes a node scheme\n// tints, a tube map's paths step from dark grey to light, as sequenceTubeMap\n// draws them under reads, so no tube shares a hue that means something else\nexport function pathGreyCssColor(index: number, count: number) {\n const lightness = count > 1 ? 25 + (45 * index) / (count - 1) : 45\n return `hsl(0, 0%, ${lightness}%)`\n}\n\n// A path is named the way its file names it \u2014 `K12#1#chr:1004500-1004961` for a\n// P record `odgi extract` wrote, `HG00738#1` for a W record \u2014 and neither is a\n// legend entry. The label is the least of that name which still tells the paths\n// apart: the sample where samples are unique, the haplotype too where they are\n// not, the whole stable name where even that repeats. Widening it for every row\n// rather than only for the colliding ones, because a legend where one entry is\n// `HG00738#1` and its neighbour is `HG01071` reads as two kinds of thing.\n// A generic path, which vg files under a placeholder sample\n// (`_gbwt_ref#0#GI262359905`), is named by its contig alone.\nfunction labelTiers(name: string) {\n const parts = pathOrigin(name).name.split('#')\n const full = parts.join('#')\n if (parts.length >= 3 && isGenericSample(parts[0]!)) {\n const contig = parts.slice(2).join('#')\n return [contig, contig, full]\n }\n return [parts[0]!, parts.slice(0, 2).join('#'), full]\n}\n\n// A collapsed repeat puts one sequence through the same segments more than\n// once, and `odgi extract` emits one path per visit \u2014 nine visits over five\n// E. coli chromosomes at the rRNA operons, where every name tier above collides\n// because the sequence really is the same one. What separates those is the\n// offset each name carried, which is the one thing pathOrigin strips off.\nfunction offsetTiers(name: string, tiers: string[]) {\n const { start } = pathOrigin(name)\n return tiers.map(t => `${t} @${groupDigits(start)}`)\n}\n\nexport interface PathLegendEntry {\n name: string\n label: string\n color: string\n}\n\n// `colors` is what each path was actually drawn in, where that is not\n// pathCssColor's hue. One entry per name: the records of a walk cut in pieces\n// are one walk, drawn in the first record's colour.\nexport function pathLegend(\n paths: GraphPath[],\n colors?: readonly string[],\n): PathLegendEntry[] {\n const firstOf = new Map<string, number>()\n paths.forEach((path, i) => {\n if (!firstOf.has(path.name)) {\n firstOf.set(path.name, i)\n }\n })\n const entries = [...firstOf].map(([name, i]) => ({ name, i }))\n const named = entries.map(e => labelTiers(e.name))\n // narrowest first, and the offset is tried before the next name tier: two\n // copies of one operon are told apart by where they are, not by widening a\n // name that is genuinely identical\n const candidates = [0, 1, 2].flatMap(t => [\n entries.map((_, k) => named[k]![t]!),\n entries.map((e, k) => offsetTiers(e.name, named[k]!)[t]!),\n ])\n const distinct = candidates.find(\n labels => new Set(labels).size === entries.length,\n )\n // Nothing separates two paths with the same name and the same offset, so the\n // widest labelling is as close as any can get.\n const labels = distinct ?? candidates.at(-1)!\n return entries.map(({ name, i }, k) => ({\n name,\n label: labels[k]!,\n color: colors?.[i] ?? pathCssColor(i, paths.length),\n }))\n}\n", "import type { Graph, GraphNode } from '../types'\n\nexport interface BandageScaleOpts {\n nodeLengthPerMegabase: number\n minimumNodeLength: number\n nodeSegmentLength: number\n edgeLength: number\n}\n\n// Node drawn length, in FMMM units, as a function of bp. Used both by the layouts\n// that run here and by the options handed to the WASM engine, so a graph is drawn\n// at the same scale whichever one draws it. Bandage's own auto rule\n// (`AssemblyGraph::determineGraphInfo`):\n// pick units-per-megabase so the *mean* drawn node length lands on 40, with a\n// floor on total drawn length so a small graph doesn't collapse to a speck.\n//\n// This has to be derived per graph, not fixed. The scale that suits a bacterial\n// assembly's 10-100 kb contigs is wrong by orders of magnitude for a minigraph\n// pangenome window, whose nodes run 300 bp to a few kb: a fixed 1000 units/Mbp\n// makes every node shorter than the edges between them and the graph reads as\n// beads on a string, while 1 unit/bp makes each node a long sweeping curve and\n// the graph sprawls. For the 53-node E. coli window this lands near 35,000.\n//\n// It matters most at the small end. The WASM wrapper's own default of 1000\n// units/Mbp puts *every* node in a 400 bp pggb window below minimumNodeLength,\n// so all 31 clamp to the floor and a 1 bp SNP allele draws the same size as the\n// 164 bp backbone segment beside it \u2014 which is what made that figure read as a\n// chain of same-sized two-node bubbles. Bandage draws the same file with a 75:1\n// length ratio, the SNP alleles as specks.\n//\n// The remaining three are upstream Bandage's shipped defaults\n// (`program/settings.cpp`), which the WASM wrapper's own header does not match \u2014\n// it defaults all of them to 1.0, so they have to be passed explicitly.\nconst MEAN_NODE_LENGTH = 40\nconst MIN_TOTAL_GRAPH_LENGTH = 500\n\n// Bandage's own floor on a node's drawn length. Right for an assembly graph,\n// whose nodes are kb to Mb, and the reason a variation graph draws as a rope:\n// see BUBBLE_SPREADS, which is how a view asks for more.\nconst BANDAGE_MINIMUM_NODE_LENGTH = 5\n\n// The engine's own rule (`settings.h`, `getDrawnNodeLength`), so a seed computed\n// here spans exactly the chain the engine builds from the same numbers.\nexport function drawnNodeLength(opts: BandageScaleOpts, length: number) {\n return Math.max(\n (opts.nodeLengthPerMegabase * length) / 1_000_000,\n opts.minimumNodeLength,\n )\n}\n\nexport function bandageAutoScale(\n graph: Graph,\n // Floor on a node's drawn length, in the same FMMM units as the scale below.\n // Anything at or under Bandage's own floor leaves the drawing exactly as it\n // was, which is what keeps `'auto'` byte-identical to the shipped behaviour.\n minNodeLength = BANDAGE_MINIMUM_NODE_LENGTH,\n): BandageScaleOpts {\n const totalLength = graph.nodes.reduce((sum, n) => sum + n.length, 0)\n const megabases = totalLength / 1_000_000\n const target = Math.max(\n graph.nodes.length * MEAN_NODE_LENGTH,\n MIN_TOTAL_GRAPH_LENGTH,\n )\n return {\n nodeLengthPerMegabase: megabases > 0 ? target / megabases : 10_000,\n minimumNodeLength: Math.max(BANDAGE_MINIMUM_NODE_LENGTH, minNodeLength),\n edgeLength: 5,\n nodeSegmentLength: 20,\n }\n}\n\n// How a node's bp becomes its drawn length, as a power law against the graph's\n// own mean. The idiom is Bandage's, moved onto the axis a pangenome needs it on:\n// upstream derives a node's *width* from its depth exactly this way\n// (`graph/graphicsitemnode.cpp`, `getNodeWidth`), at a `depthPower` of 0.5 and a\n// `depthEffectOnWidth` of 0.5, because depth in an assembly graph spans orders\n// of magnitude and drawing it linearly is unreadable.\n//\n// drawn = meanDrawn * (((bp / meanBp) ^ power - 1) * effect + 1)\n//\n// Length in a *variation* graph spans those same orders of magnitude \u2014 a 1 bp\n// allele beside a 16 kb backbone segment \u2014 and upstream still maps it linearly,\n// because its own nodes are assembled contigs where that range does not arise.\n// That linear map is what draws a pggb window as one rope: past the small end\n// every allele falls under `minimumNodeLength` and clamps, so both arms of a\n// bubble land inside one node thickness of each other.\n//\n// power 1 with effect 1 is exactly proportional, so Bandage's shipped behaviour\n// is a point in this family rather than a mechanism sitting beside it. See\n// BUBBLE_SPREADS for the points a view can ask for.\nexport interface DrawnLengthLaw {\n // exponent applied to a node's length relative to the graph's mean\n power: number\n // how much of that relative length survives. 0 draws every node at the mean,\n // 1 applies the power law undamped.\n effect: number\n}\n\nexport const PROPORTIONAL_LENGTH: DrawnLengthLaw = { power: 1, effect: 1 }\n\nexport function isProportional(law: DrawnLengthLaw) {\n return law.power === 1 && law.effect === 1\n}\n\n// Drawn length in FMMM units for one node of `graph`, under `law`.\nexport function drawnLengthFor(graph: Graph, law: DrawnLengthLaw) {\n const count = graph.nodes.length\n const totalLength = graph.nodes.reduce((sum, n) => sum + n.length, 0)\n const meanBp = count > 0 ? totalLength / count : 0\n // The mean drawn length `bandageAutoScale` already targets, small-graph floor\n // included, so a compressed drawing comes out the same overall size as the\n // proportional one it replaces. That is the property a per-node floor does\n // not have, and the reason raising one is unusable at scale: it lifts the\n // hundreds of non-branching chain nodes along with the alleles, which on a\n // 521-node cut inflates the drawing past what zoom-to-fit can frame.\n const meanDrawn =\n count > 0\n ? Math.max(count * MEAN_NODE_LENGTH, MIN_TOTAL_GRAPH_LENGTH) / count\n : MEAN_NODE_LENGTH\n return (bp: number) => {\n if (meanBp <= 0) {\n return meanDrawn\n }\n const relative = (bp / meanBp) ** law.power\n return Math.max(\n meanDrawn * ((relative - 1) * law.effect + 1),\n BANDAGE_MINIMUM_NODE_LENGTH,\n )\n }\n}\n\n// The engine's own length rule is linear with a floor and takes two numbers\n// (`settings.h`, `getDrawnNodeLength`), so a non-linear law cannot be expressed\n// through them. It can be expressed in the lengths themselves: node length\n// crosses the RPC boundary only to become a drawn length \u2014 `getLength()` has\n// exactly two call sites upstream, both inside that rule \u2014 so handing the engine\n// pre-transformed lengths and a 1:1 scale draws the law without touching the\n// WASM build. Scaled by this factor first because the binding reads a node's\n// length as an `unsigned`, and rounding drawn units straight to integers would\n// quantise the small end where the whole point is legibility.\nconst LAYOUT_LENGTH_PRECISION = 1000\n\n// How a view asks for its node lengths: a law, plus Bandage's own per-node\n// floor. Orthogonal on purpose \u2014 see BUBBLE_SPREADS for which does what.\nexport interface NodeLengthSpread {\n law: DrawnLengthLaw\n // in the same FMMM units as the drawn lengths. At or under Bandage's own\n // floor this leaves the drawing exactly as it was.\n minNodeLength: number\n}\n\nexport const PROPORTIONAL_SPREAD: NodeLengthSpread = {\n law: PROPORTIONAL_LENGTH,\n minNodeLength: 0,\n}\n\nexport interface LayoutScaling {\n // the nodes to hand the engine. Under any law but the proportional one their\n // `length` is a scaled drawn length rather than a bp count, which is why this\n // is a separate array and never the graph's own nodes.\n nodes: GraphNode[]\n opts: BandageScaleOpts\n}\n\nexport function layoutScaling(\n graph: Graph,\n spread: NodeLengthSpread = PROPORTIONAL_SPREAD,\n): LayoutScaling {\n const { law, minNodeLength } = spread\n const opts = bandageAutoScale(graph, minNodeLength)\n // The proportional law is what every committed force figure was drawn\n // against, and it is already exactly what `bandageAutoScale` expresses, so it\n // keeps the untransformed path rather than round-tripping through a rounding\n // step FMMM would be free to amplify. Every floor-based spread comes through\n // here too, unchanged.\n if (isProportional(law)) {\n return { nodes: graph.nodes, opts }\n }\n const drawn = drawnLengthFor(graph, law)\n return {\n nodes: graph.nodes.map(node => ({\n ...node,\n length: Math.round(drawn(node.length) * LAYOUT_LENGTH_PRECISION),\n })),\n opts: {\n ...opts,\n nodeLengthPerMegabase: 1_000_000 / LAYOUT_LENGTH_PRECISION,\n // Bandage's own floor only. A law and a raised floor are alternatives\n // rather than a stack: the law has already lifted the small end, and a\n // floor on top of it would flatten exactly what it just spread.\n minimumNodeLength: BANDAGE_MINIMUM_NODE_LENGTH,\n },\n }\n}\n\n// What `bp` of sequence draws as under `layoutScaling(graph, spread)`.\nexport function drawnLengthOf(\n graph: Graph,\n spread: NodeLengthSpread = PROPORTIONAL_SPREAD,\n) {\n if (isProportional(spread.law)) {\n const opts = bandageAutoScale(graph, spread.minNodeLength)\n return (bp: number) => drawnNodeLength(opts, bp)\n }\n return drawnLengthFor(graph, spread.law)\n}\n\n// The mean drawn node length `bandageAutoScale` targets, exported so\n// BUBBLE_SPREADS can state its laws against it rather than against unanchored\n// constants.\nexport { MEAN_NODE_LENGTH }\n", "import { MEAN_NODE_LENGTH, PROPORTIONAL_LENGTH } from './layout/drawnScale'\n\nimport type { NodeLengthSpread } from './layout/drawnScale'\n\n// How far apart the force layout draws the two arms of a bubble, as the law\n// turning a node's bp into its drawn length. One table drives the persisted\n// enum, the dropdown and the value handed to the engine, the same way\n// LAYOUT_MODES and COLOR_SCHEMES do.\n//\n// Why this needs to be a control rather than a better default: the drawn-length\n// law is the one thing standing between a variation graph and a legible force\n// layout, and compressing it costs the property that node length is proportional\n// to bp. Bandage maps length linearly, which suits an assembly graph whose nodes\n// are kb to Mb. In a pangenome an allele is 1-50 bp against a backbone segment\n// of several kb, so on that map every alt clamps to the engine's 5-unit floor,\n// both arms of a bubble land inside one node thickness of each other, and FMMM\n// has nothing to spread \u2014 zoom-to-fit then delivers a single rope with the\n// variation invisible inside it. Compressing the law opens the bubbles and\n// flattens the length ratio, and which of those a reader wants depends on the\n// figure, so the view asks rather than assumes.\n//\n// The compression is Bandage's own, taken off the axis upstream uses it on: it\n// draws node *width* from depth as a power law against the mean, at power 0.5\n// and effect 0.5, for the same reason. 'open' is those two numbers. See\n// DrawnLengthLaw.\n//\n// Measured on an odgi extract of GRCh38#0#chrM:8,200-8,400 from HPRC release 2's\n// chrM pggb graph (61 nodes, 84 edges, 234 haplotype paths), fitted to a 900x560\n// pane: proportional is one squiggle with a single knot in it, and the bubbles\n// come apart into legible lenses once the law compresses.\n//\n// The floor and the law are different instruments and both are kept, because\n// neither does the other's job:\n//\n// - a FLOOR lifts the small end and leaves everything above it proportional. It\n// is what a figure wants when one long node has to stay long \u2014 the ribbons in\n// `pangenome/pggb_haplotype_paths` separate along a 1.2 kb deletion arc, and\n// shortening that arc towards the mean piles them up. Its cost is that it is a\n// per-node minimum, so it lifts a graph's non-branching chain nodes along with\n// its alleles and the drawing inflates with node count: on the 521-node pggb\n// cut in `pangenome/graph_resolution` it reached ~52,000 units and a 1.4% fit,\n// which is why that figure could once afford a spread on its 7-node half only.\n// - the LAW compresses both ends towards the mean, so the drawing stays the size\n// it already was whatever the node count, and a cut spanning 6 bp to 16 kb\n// fits one pane. Its cost is the top end: a node that has to read as long no\n// longer does.\n//\n// So a figure about bubble shape at scale takes 'compress', and a figure about\n// what is drawn along one long edge takes 'open'.\n//\n// 'auto' is the proportional map, unchanged, so every existing figure and the\n// `bandageAutoScale` length-proportionality test are untouched by this existing.\nexport const BUBBLE_SPREADS = [\n {\n value: 'auto',\n label: 'Proportional',\n description:\n \"Bandage's own scale: a node draws proportional to its length, so a 1 bp allele is a speck.\",\n law: PROPORTIONAL_LENGTH,\n minNodeFactor: 0,\n },\n {\n value: 'open',\n label: 'Open bubbles',\n description:\n 'Give every allele a drawn length, so a bubble reads as two arms rather than a knot.',\n law: PROPORTIONAL_LENGTH,\n minNodeFactor: 2.5,\n },\n {\n value: 'wide',\n label: 'Wide bubbles',\n description:\n 'As open, spread further. Best on a window of a dozen bubbles.',\n law: PROPORTIONAL_LENGTH,\n minNodeFactor: 10,\n },\n {\n value: 'compress',\n label: 'Compress lengths',\n description:\n 'Pull the longest and shortest nodes towards the graph\\u2019s mean, so a cut spanning kb and bp fits one pane.',\n law: { power: 0.5, effect: 0.5 },\n minNodeFactor: 0,\n },\n] as const\n\nexport type BubbleSpread = (typeof BUBBLE_SPREADS)[number]['value']\n\nexport const BUBBLE_SPREAD_VALUES = BUBBLE_SPREADS.map(s => s.value)\n\n// The law and floor behind a spread, resolved so the model has one place that\n// knows how a spread becomes numbers. An unknown value draws proportionally\n// rather than throwing, which is what a session persisted by a newer build\n// degrades to.\nexport function spreadFor(spread: string): NodeLengthSpread {\n const entry = BUBBLE_SPREADS.find(s => s.value === spread)\n return {\n law: entry?.law ?? PROPORTIONAL_LENGTH,\n minNodeLength: (entry?.minNodeFactor ?? 0) * MEAN_NODE_LENGTH,\n }\n}\n", "import type { Graph } from './types'\n\n// One table drives the persisted enum and both colour dropdowns, the same way\n// LAYOUT_MODES drives the layout enum and its menu. The values and their labels\n// used to live in two lists in two directories, so adding a scheme meant editing\n// both and a mismatch showed up as a dropdown entry that selected nothing.\n//\n// Kept out of the components directory because the model needs it too.\nexport const COLOR_SCHEMES = [\n // Mirrors `layoutModes`' 'auto': a value the model resolves rather than a\n // colour of its own. It exists because 'uniform' was the default, so a\n // launched graph opened flat grey and both tutorials spent a step saying \"now\n // pick a colour\" \u2014 one figure literally drives that click. A graph with\n // reference coordinates resolves to 'reference-position', which is the one\n // ramp a linear track beside it can be painted with too; a graph with none\n // keeps 'uniform', because there the ramp would be a hue nothing earned.\n //\n // Resolve through `effectiveColorScheme`; the dropdown reads the raw prop, so\n // \"Auto\" stays selectable and visible as what it is.\n { value: 'auto', label: 'Auto' },\n { value: 'uniform', label: 'Uniform' },\n { value: 'random', label: 'Random' },\n { value: 'rainbow', label: 'Rainbow' },\n { value: 'depth', label: 'Depth' },\n { value: 'node-length', label: 'Node Length' },\n { value: 'stable-rank', label: 'Stable rank' },\n { value: 'reference-position', label: 'Reference position' },\n { value: 'grey', label: 'Grey' },\n] as const\n\nexport type ColorScheme = (typeof COLOR_SCHEMES)[number]['value']\n\n// What the renderer can actually paint with: every scheme except the one that\n// stands for \"work it out\". Stated as its own type so `getNodeColor`'s switch\n// has no 'auto' arm to forget.\nexport type ResolvedColorScheme = Exclude<ColorScheme, 'auto'>\n\nexport const COLOR_SCHEME_VALUES = COLOR_SCHEMES.map(s => s.value)\n\n// 'auto' asks the GRAPH, not the layout: a hue along the reference means\n// something whenever the segments have reference coordinates, force-directed\n// drawings included. A tube map, whose tubes carry the colour, is the one\n// drawing the view keeps clear under it.\nexport function resolveColorScheme(\n scheme: ColorScheme,\n graph: Graph | undefined,\n): ResolvedColorScheme {\n return scheme !== 'auto'\n ? scheme\n : graph?.anchoredBy\n ? 'reference-position'\n : 'uniform'\n}\n", "import type { Graph, GraphNode } from './types'\nimport type { NodeInk } from './util/hitDetection'\n\n// How thick a node draws. Bandage widens a node by its depth, the square root\n// of its depth against the graph's mean, so a contig every read covers is fat\n// and a spurious one is a hair. In a pangenome cut depth is carriage, how many\n// walks go through the node, and the same rule makes the reference thick and a\n// private allele thin. One table drives the persisted enum and the dropdown,\n// the way BUBBLE_SPREADS does.\nexport const NODE_WIDTHS = [\n {\n value: 'depth',\n label: 'By depth',\n description:\n 'Thicker the more paths carry a node, on the square root of its depth against the mean. A graph with one depth everywhere draws uniform.',\n },\n {\n value: 'uniform',\n label: 'Uniform',\n description: 'Every node the same thickness.',\n },\n] as const\n\nexport type NodeWidth = (typeof NODE_WIDTHS)[number]['value']\n\nexport const NODE_WIDTH_VALUES = NODE_WIDTHS.map(w => w.value)\n\nconst MIN_FACTOR = 0.3\nconst MAX_FACTOR = 3\n\n// Length-weighted, as Bandage's mean depth is, so a cut's long reference nodes\n// set the norm and the alleles read against it.\nexport function meanDepth(graph: Graph) {\n let weighted = 0\n let length = 0\n for (const node of graph.nodes) {\n weighted += node.depth * node.length\n length += node.length\n }\n return length > 0 ? weighted / length : 0\n}\n\nexport function depthWidthFactor(node: GraphNode, mean: number) {\n if (!(mean > 0) || !(node.depth > 0)) {\n return 1\n }\n return Math.min(\n MAX_FACTOR,\n Math.max(MIN_FACTOR, Math.sqrt(node.depth / mean)),\n )\n}\n\n// How wide a node is drawn, in screen px. One function for the geometry and\n// for the hit test, so the pointer finds a node exactly where its ink is.\nexport function nodeWidthPx(\n node: GraphNode,\n contigThickness: number,\n nodeWidth: NodeWidth,\n mean: number,\n) {\n return (\n contigThickness * (nodeWidth === 'depth' ? depthWidthFactor(node, mean) : 1)\n )\n}\n\nexport function maxNodeWidthPx(contigThickness: number, nodeWidth: NodeWidth) {\n return contigThickness * (nodeWidth === 'depth' ? MAX_FACTOR : 1)\n}\n\n// How far each node's ink reaches from its centreline, for the hit test: the\n// widths the geometry draws with, from the same function.\nexport function nodeInk(\n graph: Graph | undefined,\n nodeById: ReadonlyMap<string, GraphNode> | undefined,\n contigThickness: number,\n nodeWidth: NodeWidth,\n): NodeInk {\n const mean = graph ? meanDepth(graph) : 0\n return {\n maxHalfWidthPx: maxNodeWidthPx(contigThickness, nodeWidth) / 2,\n halfWidthPx: id => {\n const node = nodeById?.get(id)\n return node\n ? nodeWidthPx(node, contigThickness, nodeWidth, mean) / 2\n : contigThickness / 2\n },\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["require_mui", "__commonJSMin", "exports", "module", "svgPath", "points", "round", "v", "p", "i", "projectLine", "x1", "y1", "x2", "y2", "distance", "d", "vx", "vy", "curvePointAt", "c", "u", "a", "b", "e", "curveMidpoint", "curves", "mid", "curveBounds", "minX", "minY", "maxX", "maxY", "subCurve", "t0", "t1", "p0", "p1", "dt", "d0", "curveTangentAt", "d1", "dashCurves", "dashWorld", "lengths", "total", "spans", "out", "from", "to", "dash", "start", "k", "length", "end", "translateCurves", "dx", "dy", "attachment", "segments", "side", "last", "TANGENT_SPAN_FACTOR", "MAX_TANGENT", "spanOf", "min", "max", "s", "distanceToSpan", "x", "span", "facingSide", "other", "tieBreak", "startGap", "endGap", "facingSides", "fromSegments", "toSegments", "APEX_FRACTION", "BOW_CLEARANCE", "ALONG_FRACTION", "MAX_ROW_BOW_PX", "bowAround", "p1x", "p1y", "p2x", "p2y", "toward", "maxBow", "chord", "ux", "uy", "reach", "minAlong", "maxAlong", "perp", "along", "uncapped", "size", "yToXOf", "scaleX", "scaleY", "PATH_RIBBON_SPACING", "drawnDirection", "yToX", "len", "pathRibbonOffsets", "count", "axis", "spacing", "dir", "offsets", "scaleYOf", "unscaleYOf", "SELF_LOOP_MIN_PX", "SELF_LOOP_MAX_PX", "selfLoopReach", "scale", "nodePx", "selfLinkOf", "edge", "hairpinCurves", "attach", "offsetX", "offsetY", "at", "inward", "px", "py", "nearX", "nearY", "apexX", "apexY", "sideX", "sideY", "computeEdgeCurves", "selfLink", "bypassed", "maxBowIn", "sides", "fromAttach", "toAttach", "fromEnd", "toStart", "segDirX", "segDirY", "ext", "perpX", "perpY", "midX", "midY", "cp1x", "cp1y", "cp2x", "cp2y", "fromPrev", "toNext", "dist", "tangentProj", "prev", "towardX", "towardY", "towardToX", "towardToY", "cx1", "cy1", "cx2", "cy2", "bulge", "chordLen", "bulgeX", "bulgeY", "spreadX", "spreadY", "nearerEnd", "routedEdgeCurves", "route", "tangent", "q", "next", "ta", "tb", "MIN_DELETION_BP", "bypassedPoints", "nodePositions", "bypassed", "id", "deletionArcCurves", "deletion", "axis", "route", "from", "to", "routedEdgeCurves", "computeEdgeCurves", "backboneBySequence", "graph", "bySequence", "node", "isBackbone", "list", "a", "b", "bypassedNodes", "nodes", "start", "end", "i", "firstNodeAtOrAfter", "skipsBetween", "edge", "forwards", "fromStrand", "toStrand", "leavesRight", "arrivesLeft", "deletionEdges", "byId", "n", "backbone", "found", "edgeIndex", "left", "right", "bp", "refName", "ownStrand", "id", "flip", "strand", "sidesOf", "edge", "from", "to", "mergeRuns", "graph", "alone", "sides", "at", "attach", "side", "ei", "key", "list", "joinAfter", "a", "out", "joined", "runOf", "runs", "node", "into", "pred", "run", "next", "byId", "n", "nodes", "members", "backbone", "isBackbone", "first", "b", "sum", "seen", "edges", "splitRuns", "positions", "weightOf", "line", "arc", "arcPrefix", "total", "weights", "m", "weightSum", "member", "k", "share", "slice", "polylineSlice", "polylineMidpoint", "pointAt", "i", "s", "span", "t", "points", "labelWidth", "text", "overlaps", "a", "b", "offFrame", "box", "frame", "insideFrame", "occupancy", "reserved", "placed", "p", "placeLabels", "candidates", "take", "out", "place", "c", "w", "direction", "tries", "y", "MIN_DELETION_LABEL_PX", "PX_PER_NODE_LABEL", "MIN_NODE_LABELS", "LEADER_GAP_PX", "LABEL_CHAR_PX", "LABEL_PAD_PX", "LABEL_HEIGHT_PX", "LABEL_GAP_PX", "MAX_LABEL_OVERHANG", "labelHalfWidth", "text", "LABEL_HALF_HEIGHT", "MIN_LABEL_HALF_WIDTH", "boxAt", "halfW", "x", "y", "rowLabelBox", "screenY", "clamp", "v", "lo", "hi", "groupDigits", "n", "formatBp", "bp", "deletionText", "arcOutward", "curves", "apex", "scaleX", "scaleY", "first", "last", "dx", "dy", "len", "boxSupport", "box", "dir", "boxRayCrossing", "halfH", "drawnLength", "segments", "total", "i", "a", "b", "runBounds", "nodeIds", "nodePositions", "minX", "minY", "maxX", "maxY", "id", "p", "nodeOrderCache", "labelOrder", "nodeLengths", "cached", "order", "arcCache", "arcPlacements", "deletions", "axis", "version", "routes", "arcs", "deletion", "deletionArcCurves", "curveMidpoint", "curveBounds", "sizeLabelCandidates", "deletionRoutes", "alleleDeletions", "translateX", "translateY", "width", "height", "deletionCandidates", "nodeCandidates", "extent", "arcX", "arcY", "fits", "bow", "offset", "away", "leader", "crossing", "labelledByDeletion", "alleleRuns", "run", "drawn", "shownLeft", "shownRight", "shownTop", "shownBottom", "polylineMidpoint", "screenX", "nodeLabelBudget", "placeSizeLabels", "candidates", "take", "budget", "labels", "label", "RANGE_SUFFIX", "pathOrigin", "pathName", "match", "surveyPaths", "paths", "lengthOf", "anchorPaths", "pathVisits", "path", "sample", "panSNSample", "pos", "step", "visit", "existing", "chooseReferencePath", "preferred", "p", "OFF_REFERENCE_RANK", "anchorNode", "node", "visits", "reference", "anchored", "anchor", "v", "anchorFromPaths", "graph", "anchorGraph", "trimOrigins", "kept", "n", "bp", "ids", "end", "sum", "byOrigin", "origin", "pieces", "stretch", "nameHue", "name", "hash", "i", "pathHueAt", "index", "count", "PATH_SATURATION", "PATH_LIGHTNESS", "MAX_PATH_COLORS", "pathColorsLegible", "pathCount", "pathCssColor", "pathGreyCssColor", "labelTiers", "parts", "pathOrigin", "full", "isGenericSample", "contig", "offsetTiers", "tiers", "start", "t", "groupDigits", "pathLegend", "paths", "colors", "firstOf", "path", "entries", "named", "e", "candidates", "_", "k", "labels", "drawnNodeLength", "opts", "length", "bandageAutoScale", "graph", "minNodeLength", "megabases", "sum", "n", "target", "PROPORTIONAL_LENGTH", "isProportional", "law", "drawnLengthFor", "count", "totalLength", "meanBp", "meanDrawn", "bp", "relative", "LAYOUT_LENGTH_PRECISION", "PROPORTIONAL_SPREAD", "layoutScaling", "spread", "drawn", "node", "drawnLengthOf", "BUBBLE_SPREADS", "PROPORTIONAL_LENGTH", "BUBBLE_SPREAD_VALUES", "s", "spreadFor", "spread", "entry", "COLOR_SCHEMES", "COLOR_SCHEME_VALUES", "s", "resolveColorScheme", "scheme", "graph", "NODE_WIDTHS", "NODE_WIDTH_VALUES", "w", "MIN_FACTOR", "MAX_FACTOR", "meanDepth", "graph", "weighted", "length", "node", "depthWidthFactor", "mean", "nodeWidthPx", "contigThickness", "nodeWidth", "maxNodeWidthPx", "nodeInk", "nodeById", "id"]
|
|
7
|
+
}
|