Publish Audio Model Architecture Atlas
Browse files- .gitattributes +3 -0
- README.md +12 -7
- app.js +186 -0
- assets/dagre-LICENSE +19 -0
- assets/dagre.min.js +801 -0
- assets/fonts/LICENSE.txt +155 -0
- assets/fonts/NotoSans-Bold.ttf +3 -0
- assets/fonts/NotoSans-Regular.ttf +3 -0
- assets/fonts/NotoSerifDisplay-Regular.ttf +3 -0
- assets/icons/LICENSE +15 -0
- assets/icons/arrow-left.svg +16 -0
- assets/icons/arrow-up-right.svg +16 -0
- assets/icons/chevron-right.svg +15 -0
- assets/icons/download.svg +17 -0
- assets/icons/maximize.svg +18 -0
- assets/icons/minus.svg +15 -0
- assets/icons/plus.svg +16 -0
- assets/icons/x.svg +16 -0
- graph.js +45 -0
- index.html +32 -18
- registry.js +0 -0
- style.css +9 -28
.gitattributes
CHANGED
|
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
assets/fonts/NotoSans-Bold.ttf filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
assets/fonts/NotoSans-Regular.ttf filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
assets/fonts/NotoSerifDisplay-Regular.ttf filter=lfs diff=lfs merge=lfs -text
|
README.md
CHANGED
|
@@ -1,12 +1,17 @@
|
|
| 1 |
---
|
| 2 |
-
title: Audio Model Architecture Atlas
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: static
|
|
|
|
| 7 |
pinned: false
|
| 8 |
-
license: apache-2.0
|
| 9 |
-
short_description: Interactive architecture atlas for 100+ audio models
|
| 10 |
---
|
| 11 |
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: audio.cpp / Audio Model Architecture Atlas
|
| 3 |
+
emoji: 🧩
|
| 4 |
+
colorFrom: green
|
| 5 |
+
colorTo: blue
|
| 6 |
sdk: static
|
| 7 |
+
app_file: index.html
|
| 8 |
pinned: false
|
|
|
|
|
|
|
| 9 |
---
|
| 10 |
|
| 11 |
+
# audio.cpp / Audio Model Architecture Atlas
|
| 12 |
+
|
| 13 |
+
Architecture diagrams for [audio.cpp](https://github.com/0xShug0/audio.cpp).
|
| 14 |
+
Static HTML, JavaScript, SVG and bundled fonts. No runtime server or build step.
|
| 15 |
+
Architecture sources are linked in the app. Font and icon licenses are in assets/.
|
| 16 |
+
Browser code and displayed data can be inspected by visitors; a static Space
|
| 17 |
+
cannot conceal them. Only deployment assets are included, not authoring tools.
|
app.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const db = window.ARCHITECTURE;
|
| 2 |
+
const $ = id => document.getElementById(id);
|
| 3 |
+
const esc = value => String(value ?? '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
| 4 |
+
const icon = name => `<img src="assets/icons/${name}.svg" alt="">`;
|
| 5 |
+
const byId = new Map(db.models.map(m=>[m.id,m]));
|
| 6 |
+
const roleNames = {ar:'Autoregressive backbone',flow:'Generation architecture',encoder:'Encoder',codec:'Codec / vocoder',head:'Prediction',dsp:'Frontend / DSP',io:'Input / output'};
|
| 7 |
+
const state = {mode:'shared',selected:new Set(['higgs_audio_tts','soprano_tts','miotts']),model:'higgs_audio_tts',compare:null,component:'ar-qwen3',node:null,routes:{},blocks:[],zoom:1};
|
| 8 |
+
let overview=null, graphResult=null;
|
| 9 |
+
const route = model => model.routes[state.routes[model.id] || 0];
|
| 10 |
+
const technology = model => [...new Set(model.routes.flatMap(r=>r.nodes.filter(n=>n.component).map(n=>db.components[n.component].name)))];
|
| 11 |
+
const visible = () => {
|
| 12 |
+
const q=$('search').value.toLowerCase();
|
| 13 |
+
return db.models.filter(m=>(!$('task').value||m.task===$('task').value)&&[m.name,m.family,...m.variants,...technology(m)].join(' ').toLowerCase().includes(q)).sort((a,b)=>a.name.localeCompare(b.name));
|
| 14 |
+
};
|
| 15 |
+
$('count').textContent=db.models.length;
|
| 16 |
+
$('coverage').textContent=`Run ${db.models.length} models locally with audio.cpp`;
|
| 17 |
+
$('snapshot').textContent=`Spec snapshot ${db.revision.slice(0,8)}`;
|
| 18 |
+
$('task').innerHTML='<option value="">All tasks</option>'+db.tasks.map(t=>`<option value="${t.id}">${esc(t.name)}</option>`).join('');
|
| 19 |
+
$('task').value='';
|
| 20 |
+
|
| 21 |
+
function focusComponent(id,node=null){state.component=id;state.node=node;state.blocks=[];}
|
| 22 |
+
function selectedNode(){
|
| 23 |
+
if(state.mode==='shared')return null;
|
| 24 |
+
const comparison=state.node?.startsWith('compare:')&&state.compare;
|
| 25 |
+
const model=byId.get(comparison?state.compare:state.model);
|
| 26 |
+
return state.node?route(model).nodes.find(n=>n.id===(comparison?state.node.slice(8):state.node)):route(model).nodes.find(n=>n.component===state.component);
|
| 27 |
+
}
|
| 28 |
+
function firstComponent(model){return route(model).nodes.find(n=>n.component&&db.components[n.component].role==='ar')?.component||route(model).nodes.find(n=>n.component)?.component;}
|
| 29 |
+
function openModel(id){
|
| 30 |
+
if(state.mode==='shared')overview={component:state.component,blocks:[...state.blocks],zoom:state.zoom,scroll:[$('graph').scrollLeft,$('graph').scrollTop]};
|
| 31 |
+
state.mode='model';state.model=id;state.compare=null;focusComponent(firstComponent(byId.get(id)));state.zoom=1;
|
| 32 |
+
render();fit();$('title').focus({preventScroll:true});
|
| 33 |
+
}
|
| 34 |
+
function back(){
|
| 35 |
+
state.mode='shared';state.compare=null;
|
| 36 |
+
if(overview){state.component=overview.component;state.blocks=[...overview.blocks];state.zoom=overview.zoom;}
|
| 37 |
+
render();if(overview){[$('graph').scrollLeft,$('graph').scrollTop]=overview.scroll;}
|
| 38 |
+
}
|
| 39 |
+
function toggle(id){state.selected.has(id)?state.selected.delete(id):state.selected.add(id);render();fit();}
|
| 40 |
+
function library(){
|
| 41 |
+
$('models').innerHTML=visible().map(m=>{
|
| 42 |
+
const active=state.mode==='shared'?state.selected.has(m.id):state.model===m.id;
|
| 43 |
+
return `<div class="model-row"><button class="model ${active?'active':''}" aria-pressed="${active}" data-model="${m.id}"><span class="model-name">${esc(m.name)}</span><small>${esc(technology(m).join(' · '))}</small></button><button class="open-model" data-open="${m.id}" aria-label="Open ${esc(m.name)} pipeline" title="Open ${esc(m.name)} pipeline">${icon('arrow-up-right')}</button></div>`;
|
| 44 |
+
}).join('')||'<p class="empty">No matching models</p>';
|
| 45 |
+
$('models').querySelectorAll('[data-model]').forEach(b=>b.onclick=()=>state.mode==='shared'?toggle(b.dataset.model):openModel(b.dataset.model));
|
| 46 |
+
$('models').querySelectorAll('[data-open]').forEach(b=>b.onclick=()=>openModel(b.dataset.open));
|
| 47 |
+
}
|
| 48 |
+
function graphNode(n,prefix=''){
|
| 49 |
+
const c=n.component?db.components[n.component]:null;
|
| 50 |
+
return {id:prefix+n.id,label:c?.name||n.label,role:c?.role||n.kind||'input',component:n.component,optional:n.optional,description:n.detail||c?.summary||n.label,action:c?'component':null};
|
| 51 |
+
}
|
| 52 |
+
function modelGraph(model,prefix=''){
|
| 53 |
+
const r=route(model);
|
| 54 |
+
return {nodes:r.nodes.map(n=>graphNode(n,prefix)),edges:r.edges.map(e=>({...e,from:prefix+e.from,to:prefix+e.to}))};
|
| 55 |
+
}
|
| 56 |
+
function sharedGraph(){
|
| 57 |
+
const filtered=visible();
|
| 58 |
+
const chosen=$('scope-filter').value==='selected'?filtered.filter(m=>state.selected.has(m.id)):filtered;
|
| 59 |
+
const nodes=new Map(),edges=new Map();
|
| 60 |
+
for(const model of chosen){
|
| 61 |
+
const r=route(model),mapped=new Map();
|
| 62 |
+
for(const n of r.nodes){
|
| 63 |
+
if(!n.component)continue;
|
| 64 |
+
const c=db.components[n.component];
|
| 65 |
+
if(!$('frontends').checked&&['dsp','io'].includes(c.role))continue;
|
| 66 |
+
const repeat=r.nodes.filter(other=>other.component===n.component).length>1;
|
| 67 |
+
const key=n.component+(repeat?'@'+n.id:'');
|
| 68 |
+
mapped.set(n.id,key);
|
| 69 |
+
if(!nodes.has(key))nodes.set(key,{...graphNode(n),id:key,models:new Set()});
|
| 70 |
+
nodes.get(key).models.add(model.id);
|
| 71 |
+
}
|
| 72 |
+
// Collapse omitted input / DSP nodes without inventing conditioning edges.
|
| 73 |
+
function successors(id,visited=new Set()){
|
| 74 |
+
if(visited.has(id))return [];
|
| 75 |
+
visited.add(id);
|
| 76 |
+
return r.edges.filter(e=>e.from===id).flatMap(e=>mapped.has(e.to)?[mapped.get(e.to)]:successors(e.to,new Set(visited)));
|
| 77 |
+
}
|
| 78 |
+
for(const [id,key] of mapped){
|
| 79 |
+
const next=successors(id);
|
| 80 |
+
const targets=next.length?next:['model:'+model.id];
|
| 81 |
+
for(const target of targets){const ekey=key+'|'+target;if(!edges.has(ekey))edges.set(ekey,{from:key,to:target,models:new Set()});edges.get(ekey).models.add(model.id);}
|
| 82 |
+
}
|
| 83 |
+
nodes.set('model:'+model.id,{id:'model:'+model.id,label:model.name,role:'model',action:'toggle',model:model.id,models:new Set([model.id]),pressed:state.selected.has(model.id)});
|
| 84 |
+
}
|
| 85 |
+
const focused=chosen.some(m=>state.selected.has(m.id));
|
| 86 |
+
const dim=ids=>focused&&![...ids].some(id=>state.selected.has(id));
|
| 87 |
+
return {nodes:[...nodes.values()].map(n=>({...n,dim:dim(n.models)})),edges:[...edges.values()].map(e=>({...e,dim:dim(e.models),highlight:!dim(e.models)}))};
|
| 88 |
+
}
|
| 89 |
+
function graph(){
|
| 90 |
+
let data;
|
| 91 |
+
if(state.mode==='shared')data=sharedGraph();
|
| 92 |
+
else{
|
| 93 |
+
data=modelGraph(byId.get(state.model));
|
| 94 |
+
if(state.compare){const other=modelGraph(byId.get(state.compare),'compare:');data.nodes.push(...other.nodes);data.edges.push(...other.edges);}
|
| 95 |
+
}
|
| 96 |
+
if(!data.nodes.length){$('graph').innerHTML='<p class="empty">No models selected in this filter.</p>';graphResult=null;return;}
|
| 97 |
+
graphResult=AtlasGraph.render($('graph'),data.nodes,data.edges,{active:state.mode==='shared'?state.component:state.node||selectedNode()?.id,onNode:n=>{
|
| 98 |
+
if(n.action==='toggle'){toggle(n.model);return;}
|
| 99 |
+
focusComponent(n.component,state.mode==='model'?n.id:null);
|
| 100 |
+
if(state.mode==='shared'){
|
| 101 |
+
const users=visible().filter(m=>route(m).nodes.some(n=>n.component===state.component));
|
| 102 |
+
const all=users.every(m=>state.selected.has(m.id));users.forEach(m=>all?state.selected.delete(m.id):state.selected.add(m.id));
|
| 103 |
+
}
|
| 104 |
+
render();
|
| 105 |
+
}});
|
| 106 |
+
sizeGraph();
|
| 107 |
+
}
|
| 108 |
+
function sizeGraph(){if(graphResult){graphResult.svg.style.width=graphResult.width*state.zoom+'px';graphResult.svg.style.height=graphResult.height*state.zoom+'px';}$('zoom-level').textContent=Math.round(state.zoom*100)+'%';}
|
| 109 |
+
function fit(){if(graphResult){state.zoom=Math.min(1,($('graph').clientWidth-8)/graphResult.width,($('graph').clientHeight-8)/graphResult.height);sizeGraph();}}
|
| 110 |
+
function setZoom(value){state.zoom=Math.max(.15,Math.min(2.5,value));sizeGraph();}
|
| 111 |
+
function links(urls){return urls.map((url,i)=>`<a href="${esc(url)}" target="_blank" rel="noopener noreferrer">${i===0?'Architecture source':'Reference '+(i+1)} ↗</a>`).join('');}
|
| 112 |
+
function inspect(){
|
| 113 |
+
const c=db.components[state.component];
|
| 114 |
+
if(!c){$('component-title').textContent='Select a component';return;}
|
| 115 |
+
const [ink,fill]=AtlasGraph.colors[c.role]||AtlasGraph.colors.dsp;
|
| 116 |
+
$('component-title').textContent=c.name;$('component-role').textContent=roleNames[c.role]||c.role;$('component-role').style.cssText=`color:${ink};background:${fill}`;
|
| 117 |
+
$('component-description').textContent=c.summary;
|
| 118 |
+
const instance=selectedNode()?.detail;
|
| 119 |
+
$('instance-description').hidden=!instance;$('instance-description').textContent=instance||'';
|
| 120 |
+
$('evidence').innerHTML=links(c.sources||[]);
|
| 121 |
+
const stack=c.diagram?[c.diagram,...state.blocks]:[];
|
| 122 |
+
$('block-nav').innerHTML=stack.slice(0,-1).map((id,i)=>`<button data-depth="${i}">${esc(db.diagrams[id].title)}</button>`).join('');
|
| 123 |
+
$('block-nav').querySelectorAll('button').forEach(b=>b.onclick=()=>{state.blocks=state.blocks.slice(0,Number(b.dataset.depth));inspect();});
|
| 124 |
+
if(!stack.length){$('scope').textContent='Block diagram not yet curated';$('blocks').replaceChildren();$('block-caption').textContent='';return;}
|
| 125 |
+
const diagram=db.diagrams[stack.at(-1)];
|
| 126 |
+
$('scope').textContent=diagram.title;$('block-caption').textContent=diagram.scope;
|
| 127 |
+
AtlasGraph.render($('blocks'),diagram.blocks.map(n=>({...n,role:n.kind||c.role,action:n.expand?'expand':null,sublabel:n.expand?'EXPAND BLOCK':'',actionLabel:n.expand?'Expand '+n.label:n.label})),diagram.edges.map(([from,to])=>({from,to})),{compact:true,residuals:diagram.residuals,onNode:n=>{state.blocks.push(n.expand);inspect();}});
|
| 128 |
+
}
|
| 129 |
+
function peers(){
|
| 130 |
+
const c=db.components[state.component];$('relation-name').textContent=c?'/ '+c.name:'';
|
| 131 |
+
const related=db.models.filter(m=>(state.mode==='shared'||m.id!==state.model)&&m.routes.some(r=>r.nodes.some(n=>n.component===state.component)));
|
| 132 |
+
$('peers').innerHTML=related.map(m=>`<button data-peer="${m.id}" title="${state.mode==='shared'?'Open pipeline':'Compare pipeline'}">${esc(m.name)} ${state.mode==='shared'?'↗':'↔'}</button>`).join('')||'<p class="empty">No other family uses this component in the current registry.</p>';
|
| 133 |
+
$('peers').querySelectorAll('button').forEach(b=>b.onclick=()=>{if(state.mode==='shared')openModel(b.dataset.peer);else{state.compare=b.dataset.peer;focusComponent(state.component);render();fit();}});
|
| 134 |
+
}
|
| 135 |
+
function heading(){
|
| 136 |
+
const model=byId.get(state.model),shared=state.mode==='shared';
|
| 137 |
+
$('breadcrumb').hidden=shared;$('breadcrumb-model').textContent=model.name;
|
| 138 |
+
$('view-label').textContent=shared?'ARCHITECTURE EXPLORER':'MODEL DETAIL';
|
| 139 |
+
$('title').textContent=shared?'Shared building blocks':state.compare?model.name+' + '+byId.get(state.compare).name:model.name;
|
| 140 |
+
$('title').tabIndex=-1;
|
| 141 |
+
$('family').hidden=shared;$('family').textContent=model.family+(state.compare?' / '+byId.get(state.compare).family:'');
|
| 142 |
+
$('variants').hidden=shared;$('variants').innerHTML=model.variants.map(v=>`<span>${esc(v)}</span>`).join('');
|
| 143 |
+
$('subtitle').textContent=shared?`${state.selected.size} selected · ${visible().length} matching models`:db.tasks.find(t=>t.id===model.task).name;
|
| 144 |
+
$('clear').hidden=shared?!state.selected.size:!state.compare;$('clear').textContent=shared?'Clear selection':'Clear comparison';
|
| 145 |
+
$('model-info').hidden=shared;$('summary').textContent=model.summary;
|
| 146 |
+
$('detail-status').textContent=model.detailStatus;
|
| 147 |
+
$('inputs').innerHTML=(route(model).inputs||model.inputs).map(i=>`<span>${esc(i.type.toUpperCase())} · ${esc(i.label)}<em>${i.required?'required':'optional'}</em></span>`).join('');
|
| 148 |
+
$('model-links').innerHTML=links(model.sources.slice(0,2))+`<a href="https://github.com/0xShug0/audio.cpp/blob/${db.revision}/${esc(model.usageDoc)}" target="_blank" rel="noopener noreferrer">audio.cpp usage ↗</a>`;
|
| 149 |
+
$('graph-label').textContent=shared?'SHARED ARCHITECTURE':state.compare?'PIPELINE COMPARISON':'MODEL PIPELINE';
|
| 150 |
+
$('scope-filter').hidden=!shared;$('frontend-control').hidden=!shared;
|
| 151 |
+
$('variant-wrap').hidden=shared||model.routes.length<2;
|
| 152 |
+
$('variant').innerHTML=model.routes.map((r,i)=>`<option value="${i}">${esc(r.name)}</option>`).join('');$('variant').value=state.routes[model.id]||0;
|
| 153 |
+
$('packages').hidden=shared||!model.packages.length;
|
| 154 |
+
$('package-list').innerHTML=model.packages.map(p=>`<div>${esc(p.display_name)}<br><code>${esc(p.id)}</code></div>`).join('');
|
| 155 |
+
}
|
| 156 |
+
function render(){heading();library();graph();inspect();peers();}
|
| 157 |
+
$('search').oninput=()=>{render();fit();};$('task').onchange=()=>{render();fit();};$('scope-filter').onchange=()=>{graph();fit();};$('frontends').onchange=()=>{graph();fit();};
|
| 158 |
+
$('variant').onchange=()=>{state.routes[state.model]=Number($('variant').value);focusComponent(firstComponent(byId.get(state.model)));render();fit();};
|
| 159 |
+
$('back').onclick=back;$('clear').onclick=()=>{if(state.mode==='shared')state.selected.clear();else{state.compare=null;focusComponent(firstComponent(byId.get(state.model)));}render();fit();};
|
| 160 |
+
$('zoom-in').onclick=()=>setZoom(state.zoom*1.2);$('zoom-out').onclick=()=>setZoom(state.zoom/1.2);$('fit').onclick=fit;
|
| 161 |
+
$('export').onclick=()=>{
|
| 162 |
+
if(!graphResult){$('status').textContent='No graph to export.';return;}
|
| 163 |
+
const svg=graphResult.svg.cloneNode(true);svg.removeAttribute('style');
|
| 164 |
+
const style=document.createElementNS('http://www.w3.org/2000/svg','style');style.textContent='text{font-family:"Noto Sans",sans-serif}.dim{opacity:.2}';svg.prepend(style);
|
| 165 |
+
const url=URL.createObjectURL(new Blob([new XMLSerializer().serializeToString(svg)],{type:'image/svg+xml'}));
|
| 166 |
+
const a=document.createElement('a');a.href=url;a.download=state.mode==='shared'?'shared-architecture.svg':state.model+'-pipeline.svg';a.click();setTimeout(()=>URL.revokeObjectURL(url),1000);
|
| 167 |
+
};
|
| 168 |
+
document.querySelector('.brand').onclick=e=>{e.preventDefault();if(state.mode==='model')back();};
|
| 169 |
+
const inspector=document.querySelector('.inspector');
|
| 170 |
+
$('expand-detail').onclick=()=>{
|
| 171 |
+
if($('detail-dialog').open){$('detail-dialog').close();return;}
|
| 172 |
+
$('detail-dialog').append(inspector);
|
| 173 |
+
$('expand-detail').innerHTML=icon('x');
|
| 174 |
+
$('expand-detail').title='Close component detail';
|
| 175 |
+
$('expand-detail').setAttribute('aria-label','Close component detail');
|
| 176 |
+
$('detail-dialog').showModal();
|
| 177 |
+
};
|
| 178 |
+
$('detail-dialog').onclose=()=>{
|
| 179 |
+
document.querySelector('.workspace').append(inspector);
|
| 180 |
+
$('expand-detail').innerHTML=icon('maximize');
|
| 181 |
+
$('expand-detail').title='Expand component detail';
|
| 182 |
+
$('expand-detail').setAttribute('aria-label','Expand component detail');
|
| 183 |
+
$('expand-detail').focus({preventScroll:true});
|
| 184 |
+
};
|
| 185 |
+
window.addEventListener('resize',fit);
|
| 186 |
+
document.fonts.ready.then(()=>{render();fit();});
|
assets/dagre-LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Copyright (c) 2012-2014 Chris Pettitt
|
| 2 |
+
|
| 3 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 4 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 5 |
+
in the Software without restriction, including without limitation the rights
|
| 6 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 7 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 8 |
+
furnished to do so, subject to the following conditions:
|
| 9 |
+
|
| 10 |
+
The above copyright notice and this permission notice shall be included in
|
| 11 |
+
all copies or substantial portions of the Software.
|
| 12 |
+
|
| 13 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 14 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 15 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 16 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 17 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 18 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
| 19 |
+
THE SOFTWARE.
|
assets/dagre.min.js
ADDED
|
@@ -0,0 +1,801 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.dagre=f()}})(function(){var define,module,exports;return function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r}()({1:[function(require,module,exports){
|
| 2 |
+
/*
|
| 3 |
+
Copyright (c) 2012-2014 Chris Pettitt
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in
|
| 13 |
+
all copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
| 21 |
+
THE SOFTWARE.
|
| 22 |
+
*/
|
| 23 |
+
module.exports={graphlib:require("@dagrejs/graphlib"),layout:require("./lib/layout"),debug:require("./lib/debug"),util:{time:require("./lib/util").time,notime:require("./lib/util").notime},version:require("./lib/version")}},{"./lib/debug":6,"./lib/layout":8,"./lib/util":27,"./lib/version":28,"@dagrejs/graphlib":29}],2:[function(require,module,exports){"use strict";let greedyFAS=require("./greedy-fas");let uniqueId=require("./util").uniqueId;module.exports={run:run,undo:undo};function run(g){let fas=g.graph().acyclicer==="greedy"?greedyFAS(g,weightFn(g)):dfsFAS(g);fas.forEach(e=>{let label=g.edge(e);g.removeEdge(e);label.forwardName=e.name;label.reversed=true;g.setEdge(e.w,e.v,label,uniqueId("rev"))});function weightFn(g){return e=>{return g.edge(e).weight}}}function dfsFAS(g){let fas=[];let stack={};let visited={};function dfs(v){if(Object.hasOwn(visited,v)){return}visited[v]=true;stack[v]=true;g.outEdges(v).forEach(e=>{if(Object.hasOwn(stack,e.w)){fas.push(e)}else{dfs(e.w)}});delete stack[v]}g.nodes().forEach(dfs);return fas}function undo(g){g.edges().forEach(e=>{let label=g.edge(e);if(label.reversed){g.removeEdge(e);let forwardName=label.forwardName;delete label.reversed;delete label.forwardName;g.setEdge(e.w,e.v,label,forwardName)}})}},{"./greedy-fas":7,"./util":27}],3:[function(require,module,exports){let util=require("./util");module.exports=addBorderSegments;function addBorderSegments(g){function dfs(v){let children=g.children(v);let node=g.node(v);if(children.length){children.forEach(dfs)}if(Object.hasOwn(node,"minRank")){node.borderLeft=[];node.borderRight=[];for(let rank=node.minRank,maxRank=node.maxRank+1;rank<maxRank;++rank){addBorderNode(g,"borderLeft","_bl",v,node,rank);addBorderNode(g,"borderRight","_br",v,node,rank)}}}g.children().forEach(dfs)}function addBorderNode(g,prop,prefix,sg,sgNode,rank){let label={width:0,height:0,rank:rank,borderType:prop};let prev=sgNode[prop][rank-1];let curr=util.addDummyNode(g,"border",label,prefix);sgNode[prop][rank]=curr;g.setParent(curr,sg);if(prev){g.setEdge(prev,curr,{weight:1})}}},{"./util":27}],4:[function(require,module,exports){"use strict";module.exports={adjust:adjust,undo:undo};function adjust(g){let rankDir=g.graph().rankdir.toLowerCase();if(rankDir==="lr"||rankDir==="rl"){swapWidthHeight(g)}}function undo(g){let rankDir=g.graph().rankdir.toLowerCase();if(rankDir==="bt"||rankDir==="rl"){reverseY(g)}if(rankDir==="lr"||rankDir==="rl"){swapXY(g);swapWidthHeight(g)}}function swapWidthHeight(g){g.nodes().forEach(v=>swapWidthHeightOne(g.node(v)));g.edges().forEach(e=>swapWidthHeightOne(g.edge(e)))}function swapWidthHeightOne(attrs){let w=attrs.width;attrs.width=attrs.height;attrs.height=w}function reverseY(g){g.nodes().forEach(v=>reverseYOne(g.node(v)));g.edges().forEach(e=>{let edge=g.edge(e);edge.points.forEach(reverseYOne);if(Object.hasOwn(edge,"y")){reverseYOne(edge)}})}function reverseYOne(attrs){attrs.y=-attrs.y}function swapXY(g){g.nodes().forEach(v=>swapXYOne(g.node(v)));g.edges().forEach(e=>{let edge=g.edge(e);edge.points.forEach(swapXYOne);if(Object.hasOwn(edge,"x")){swapXYOne(edge)}})}function swapXYOne(attrs){let x=attrs.x;attrs.x=attrs.y;attrs.y=x}},{}],5:[function(require,module,exports){
|
| 24 |
+
/*
|
| 25 |
+
* Simple doubly linked list implementation derived from Cormen, et al.,
|
| 26 |
+
* "Introduction to Algorithms".
|
| 27 |
+
*/
|
| 28 |
+
class List{constructor(){let sentinel={};sentinel._next=sentinel._prev=sentinel;this._sentinel=sentinel}dequeue(){let sentinel=this._sentinel;let entry=sentinel._prev;if(entry!==sentinel){unlink(entry);return entry}}enqueue(entry){let sentinel=this._sentinel;if(entry._prev&&entry._next){unlink(entry)}entry._next=sentinel._next;sentinel._next._prev=entry;sentinel._next=entry;entry._prev=sentinel}toString(){let strs=[];let sentinel=this._sentinel;let curr=sentinel._prev;while(curr!==sentinel){strs.push(JSON.stringify(curr,filterOutLinks));curr=curr._prev}return"["+strs.join(", ")+"]"}}function unlink(entry){entry._prev._next=entry._next;entry._next._prev=entry._prev;delete entry._next;delete entry._prev}function filterOutLinks(k,v){if(k!=="_next"&&k!=="_prev"){return v}}module.exports=List},{}],6:[function(require,module,exports){let util=require("./util");let Graph=require("@dagrejs/graphlib").Graph;module.exports={debugOrdering:debugOrdering};
|
| 29 |
+
/* istanbul ignore next */function debugOrdering(g){let layerMatrix=util.buildLayerMatrix(g);let h=new Graph({compound:true,multigraph:true}).setGraph({});g.nodes().forEach(v=>{h.setNode(v,{label:v});h.setParent(v,"layer"+g.node(v).rank)});g.edges().forEach(e=>h.setEdge(e.v,e.w,{},e.name));layerMatrix.forEach((layer,i)=>{let layerV="layer"+i;h.setNode(layerV,{rank:"same"});layer.reduce((u,v)=>{h.setEdge(u,v,{style:"invis"});return v})});return h}},{"./util":27,"@dagrejs/graphlib":29}],7:[function(require,module,exports){let Graph=require("@dagrejs/graphlib").Graph;let List=require("./data/list");
|
| 30 |
+
/*
|
| 31 |
+
* A greedy heuristic for finding a feedback arc set for a graph. A feedback
|
| 32 |
+
* arc set is a set of edges that can be removed to make a graph acyclic.
|
| 33 |
+
* The algorithm comes from: P. Eades, X. Lin, and W. F. Smyth, "A fast and
|
| 34 |
+
* effective heuristic for the feedback arc set problem." This implementation
|
| 35 |
+
* adjusts that from the paper to allow for weighted edges.
|
| 36 |
+
*/module.exports=greedyFAS;let DEFAULT_WEIGHT_FN=()=>1;function greedyFAS(g,weightFn){if(g.nodeCount()<=1){return[]}let state=buildState(g,weightFn||DEFAULT_WEIGHT_FN);let results=doGreedyFAS(state.graph,state.buckets,state.zeroIdx);
|
| 37 |
+
// Expand multi-edges
|
| 38 |
+
return results.flatMap(e=>g.outEdges(e.v,e.w))}function doGreedyFAS(g,buckets,zeroIdx){let results=[];let sources=buckets[buckets.length-1];let sinks=buckets[0];let entry;while(g.nodeCount()){while(entry=sinks.dequeue()){removeNode(g,buckets,zeroIdx,entry)}while(entry=sources.dequeue()){removeNode(g,buckets,zeroIdx,entry)}if(g.nodeCount()){for(let i=buckets.length-2;i>0;--i){entry=buckets[i].dequeue();if(entry){results=results.concat(removeNode(g,buckets,zeroIdx,entry,true));break}}}}return results}function removeNode(g,buckets,zeroIdx,entry,collectPredecessors){let results=collectPredecessors?[]:undefined;g.inEdges(entry.v).forEach(edge=>{let weight=g.edge(edge);let uEntry=g.node(edge.v);if(collectPredecessors){results.push({v:edge.v,w:edge.w})}uEntry.out-=weight;assignBucket(buckets,zeroIdx,uEntry)});g.outEdges(entry.v).forEach(edge=>{let weight=g.edge(edge);let w=edge.w;let wEntry=g.node(w);wEntry["in"]-=weight;assignBucket(buckets,zeroIdx,wEntry)});g.removeNode(entry.v);return results}function buildState(g,weightFn){let fasGraph=new Graph;let maxIn=0;let maxOut=0;g.nodes().forEach(v=>{fasGraph.setNode(v,{v:v,in:0,out:0})});
|
| 39 |
+
// Aggregate weights on nodes, but also sum the weights across multi-edges
|
| 40 |
+
// into a single edge for the fasGraph.
|
| 41 |
+
g.edges().forEach(e=>{let prevWeight=fasGraph.edge(e.v,e.w)||0;let weight=weightFn(e);let edgeWeight=prevWeight+weight;fasGraph.setEdge(e.v,e.w,edgeWeight);maxOut=Math.max(maxOut,fasGraph.node(e.v).out+=weight);maxIn=Math.max(maxIn,fasGraph.node(e.w)["in"]+=weight)});let buckets=range(maxOut+maxIn+3).map(()=>new List);let zeroIdx=maxIn+1;fasGraph.nodes().forEach(v=>{assignBucket(buckets,zeroIdx,fasGraph.node(v))});return{graph:fasGraph,buckets:buckets,zeroIdx:zeroIdx}}function assignBucket(buckets,zeroIdx,entry){if(!entry.out){buckets[0].enqueue(entry)}else if(!entry["in"]){buckets[buckets.length-1].enqueue(entry)}else{buckets[entry.out-entry["in"]+zeroIdx].enqueue(entry)}}function range(limit){const range=[];for(let i=0;i<limit;i++){range.push(i)}return range}},{"./data/list":5,"@dagrejs/graphlib":29}],8:[function(require,module,exports){"use strict";let acyclic=require("./acyclic");let normalize=require("./normalize");let rank=require("./rank");let normalizeRanks=require("./util").normalizeRanks;let parentDummyChains=require("./parent-dummy-chains");let removeEmptyRanks=require("./util").removeEmptyRanks;let nestingGraph=require("./nesting-graph");let addBorderSegments=require("./add-border-segments");let coordinateSystem=require("./coordinate-system");let order=require("./order");let position=require("./position");let util=require("./util");let Graph=require("@dagrejs/graphlib").Graph;module.exports=layout;function layout(g,opts){let time=opts&&opts.debugTiming?util.time:util.notime;time("layout",()=>{let layoutGraph=time(" buildLayoutGraph",()=>buildLayoutGraph(g));time(" runLayout",()=>runLayout(layoutGraph,time,opts));time(" updateInputGraph",()=>updateInputGraph(g,layoutGraph))})}function runLayout(g,time,opts){time(" makeSpaceForEdgeLabels",()=>makeSpaceForEdgeLabels(g));time(" removeSelfEdges",()=>removeSelfEdges(g));time(" acyclic",()=>acyclic.run(g));time(" nestingGraph.run",()=>nestingGraph.run(g));time(" rank",()=>rank(util.asNonCompoundGraph(g)));time(" injectEdgeLabelProxies",()=>injectEdgeLabelProxies(g));time(" removeEmptyRanks",()=>removeEmptyRanks(g));time(" nestingGraph.cleanup",()=>nestingGraph.cleanup(g));time(" normalizeRanks",()=>normalizeRanks(g));time(" assignRankMinMax",()=>assignRankMinMax(g));time(" removeEdgeLabelProxies",()=>removeEdgeLabelProxies(g));time(" normalize.run",()=>normalize.run(g));time(" parentDummyChains",()=>parentDummyChains(g));time(" addBorderSegments",()=>addBorderSegments(g));time(" order",()=>order(g,opts));time(" insertSelfEdges",()=>insertSelfEdges(g));time(" adjustCoordinateSystem",()=>coordinateSystem.adjust(g));time(" position",()=>position(g));time(" positionSelfEdges",()=>positionSelfEdges(g));time(" removeBorderNodes",()=>removeBorderNodes(g));time(" normalize.undo",()=>normalize.undo(g));time(" fixupEdgeLabelCoords",()=>fixupEdgeLabelCoords(g));time(" undoCoordinateSystem",()=>coordinateSystem.undo(g));time(" translateGraph",()=>translateGraph(g));time(" assignNodeIntersects",()=>assignNodeIntersects(g));time(" reversePoints",()=>reversePointsForReversedEdges(g));time(" acyclic.undo",()=>acyclic.undo(g))}
|
| 42 |
+
/*
|
| 43 |
+
* Copies final layout information from the layout graph back to the input
|
| 44 |
+
* graph. This process only copies whitelisted attributes from the layout graph
|
| 45 |
+
* to the input graph, so it serves as a good place to determine what
|
| 46 |
+
* attributes can influence layout.
|
| 47 |
+
*/function updateInputGraph(inputGraph,layoutGraph){inputGraph.nodes().forEach(v=>{let inputLabel=inputGraph.node(v);let layoutLabel=layoutGraph.node(v);if(inputLabel){inputLabel.x=layoutLabel.x;inputLabel.y=layoutLabel.y;inputLabel.rank=layoutLabel.rank;if(layoutGraph.children(v).length){inputLabel.width=layoutLabel.width;inputLabel.height=layoutLabel.height}}});inputGraph.edges().forEach(e=>{let inputLabel=inputGraph.edge(e);let layoutLabel=layoutGraph.edge(e);inputLabel.points=layoutLabel.points;if(Object.hasOwn(layoutLabel,"x")){inputLabel.x=layoutLabel.x;inputLabel.y=layoutLabel.y}});inputGraph.graph().width=layoutGraph.graph().width;inputGraph.graph().height=layoutGraph.graph().height}let graphNumAttrs=["nodesep","edgesep","ranksep","marginx","marginy"];let graphDefaults={ranksep:50,edgesep:20,nodesep:50,rankdir:"tb"};let graphAttrs=["acyclicer","ranker","rankdir","align"];let nodeNumAttrs=["width","height"];let nodeDefaults={width:0,height:0};let edgeNumAttrs=["minlen","weight","width","height","labeloffset"];let edgeDefaults={minlen:1,weight:1,width:0,height:0,labeloffset:10,labelpos:"r"};let edgeAttrs=["labelpos"];
|
| 48 |
+
/*
|
| 49 |
+
* Constructs a new graph from the input graph, which can be used for layout.
|
| 50 |
+
* This process copies only whitelisted attributes from the input graph to the
|
| 51 |
+
* layout graph. Thus this function serves as a good place to determine what
|
| 52 |
+
* attributes can influence layout.
|
| 53 |
+
*/function buildLayoutGraph(inputGraph){let g=new Graph({multigraph:true,compound:true});let graph=canonicalize(inputGraph.graph());g.setGraph(Object.assign({},graphDefaults,selectNumberAttrs(graph,graphNumAttrs),util.pick(graph,graphAttrs)));inputGraph.nodes().forEach(v=>{let node=canonicalize(inputGraph.node(v));const newNode=selectNumberAttrs(node,nodeNumAttrs);Object.keys(nodeDefaults).forEach(k=>{if(newNode[k]===undefined){newNode[k]=nodeDefaults[k]}});g.setNode(v,newNode);g.setParent(v,inputGraph.parent(v))});inputGraph.edges().forEach(e=>{let edge=canonicalize(inputGraph.edge(e));g.setEdge(e,Object.assign({},edgeDefaults,selectNumberAttrs(edge,edgeNumAttrs),util.pick(edge,edgeAttrs)))});return g}
|
| 54 |
+
/*
|
| 55 |
+
* This idea comes from the Gansner paper: to account for edge labels in our
|
| 56 |
+
* layout we split each rank in half by doubling minlen and halving ranksep.
|
| 57 |
+
* Then we can place labels at these mid-points between nodes.
|
| 58 |
+
*
|
| 59 |
+
* We also add some minimal padding to the width to push the label for the edge
|
| 60 |
+
* away from the edge itself a bit.
|
| 61 |
+
*/function makeSpaceForEdgeLabels(g){let graph=g.graph();graph.ranksep/=2;g.edges().forEach(e=>{let edge=g.edge(e);edge.minlen*=2;if(edge.labelpos.toLowerCase()!=="c"){if(graph.rankdir==="TB"||graph.rankdir==="BT"){edge.width+=edge.labeloffset}else{edge.height+=edge.labeloffset}}})}
|
| 62 |
+
/*
|
| 63 |
+
* Creates temporary dummy nodes that capture the rank in which each edge's
|
| 64 |
+
* label is going to, if it has one of non-zero width and height. We do this
|
| 65 |
+
* so that we can safely remove empty ranks while preserving balance for the
|
| 66 |
+
* label's position.
|
| 67 |
+
*/function injectEdgeLabelProxies(g){g.edges().forEach(e=>{let edge=g.edge(e);if(edge.width&&edge.height){let v=g.node(e.v);let w=g.node(e.w);let label={rank:(w.rank-v.rank)/2+v.rank,e:e};util.addDummyNode(g,"edge-proxy",label,"_ep")}})}function assignRankMinMax(g){let maxRank=0;g.nodes().forEach(v=>{let node=g.node(v);if(node.borderTop){node.minRank=g.node(node.borderTop).rank;node.maxRank=g.node(node.borderBottom).rank;maxRank=Math.max(maxRank,node.maxRank)}});g.graph().maxRank=maxRank}function removeEdgeLabelProxies(g){g.nodes().forEach(v=>{let node=g.node(v);if(node.dummy==="edge-proxy"){g.edge(node.e).labelRank=node.rank;g.removeNode(v)}})}function translateGraph(g){let minX=Number.POSITIVE_INFINITY;let maxX=0;let minY=Number.POSITIVE_INFINITY;let maxY=0;let graphLabel=g.graph();let marginX=graphLabel.marginx||0;let marginY=graphLabel.marginy||0;function getExtremes(attrs){let x=attrs.x;let y=attrs.y;let w=attrs.width;let h=attrs.height;minX=Math.min(minX,x-w/2);maxX=Math.max(maxX,x+w/2);minY=Math.min(minY,y-h/2);maxY=Math.max(maxY,y+h/2)}g.nodes().forEach(v=>getExtremes(g.node(v)));g.edges().forEach(e=>{let edge=g.edge(e);if(Object.hasOwn(edge,"x")){getExtremes(edge)}});minX-=marginX;minY-=marginY;g.nodes().forEach(v=>{let node=g.node(v);node.x-=minX;node.y-=minY});g.edges().forEach(e=>{let edge=g.edge(e);edge.points.forEach(p=>{p.x-=minX;p.y-=minY});if(Object.hasOwn(edge,"x")){edge.x-=minX}if(Object.hasOwn(edge,"y")){edge.y-=minY}});graphLabel.width=maxX-minX+marginX;graphLabel.height=maxY-minY+marginY}function assignNodeIntersects(g){g.edges().forEach(e=>{let edge=g.edge(e);let nodeV=g.node(e.v);let nodeW=g.node(e.w);let p1,p2;if(!edge.points){edge.points=[];p1=nodeW;p2=nodeV}else{p1=edge.points[0];p2=edge.points[edge.points.length-1]}edge.points.unshift(util.intersectRect(nodeV,p1));edge.points.push(util.intersectRect(nodeW,p2))})}function fixupEdgeLabelCoords(g){g.edges().forEach(e=>{let edge=g.edge(e);if(Object.hasOwn(edge,"x")){if(edge.labelpos==="l"||edge.labelpos==="r"){edge.width-=edge.labeloffset}switch(edge.labelpos){case"l":edge.x-=edge.width/2+edge.labeloffset;break;case"r":edge.x+=edge.width/2+edge.labeloffset;break}}})}function reversePointsForReversedEdges(g){g.edges().forEach(e=>{let edge=g.edge(e);if(edge.reversed){edge.points.reverse()}})}function removeBorderNodes(g){g.nodes().forEach(v=>{if(g.children(v).length){let node=g.node(v);let t=g.node(node.borderTop);let b=g.node(node.borderBottom);let l=g.node(node.borderLeft[node.borderLeft.length-1]);let r=g.node(node.borderRight[node.borderRight.length-1]);node.width=Math.abs(r.x-l.x);node.height=Math.abs(b.y-t.y);node.x=l.x+node.width/2;node.y=t.y+node.height/2}});g.nodes().forEach(v=>{if(g.node(v).dummy==="border"){g.removeNode(v)}})}function removeSelfEdges(g){g.edges().forEach(e=>{if(e.v===e.w){var node=g.node(e.v);if(!node.selfEdges){node.selfEdges=[]}node.selfEdges.push({e:e,label:g.edge(e)});g.removeEdge(e)}})}function insertSelfEdges(g){var layers=util.buildLayerMatrix(g);layers.forEach(layer=>{var orderShift=0;layer.forEach((v,i)=>{var node=g.node(v);node.order=i+orderShift;(node.selfEdges||[]).forEach(selfEdge=>{util.addDummyNode(g,"selfedge",{width:selfEdge.label.width,height:selfEdge.label.height,rank:node.rank,order:i+ ++orderShift,e:selfEdge.e,label:selfEdge.label},"_se")});delete node.selfEdges})})}function positionSelfEdges(g){g.nodes().forEach(v=>{var node=g.node(v);if(node.dummy==="selfedge"){var selfNode=g.node(node.e.v);var x=selfNode.x+selfNode.width/2;var y=selfNode.y;var dx=node.x-x;var dy=selfNode.height/2;g.setEdge(node.e,node.label);g.removeNode(v);node.label.points=[{x:x+2*dx/3,y:y-dy},{x:x+5*dx/6,y:y-dy},{x:x+dx,y:y},{x:x+5*dx/6,y:y+dy},{x:x+2*dx/3,y:y+dy}];node.label.x=node.x;node.label.y=node.y}})}function selectNumberAttrs(obj,attrs){return util.mapValues(util.pick(obj,attrs),Number)}function canonicalize(attrs){var newAttrs={};if(attrs){Object.entries(attrs).forEach(([k,v])=>{if(typeof k==="string"){k=k.toLowerCase()}newAttrs[k]=v})}return newAttrs}},{"./acyclic":2,"./add-border-segments":3,"./coordinate-system":4,"./nesting-graph":9,"./normalize":10,"./order":15,"./parent-dummy-chains":20,"./position":22,"./rank":24,"./util":27,"@dagrejs/graphlib":29}],9:[function(require,module,exports){let util=require("./util");module.exports={run:run,cleanup:cleanup};
|
| 68 |
+
/*
|
| 69 |
+
* A nesting graph creates dummy nodes for the tops and bottoms of subgraphs,
|
| 70 |
+
* adds appropriate edges to ensure that all cluster nodes are placed between
|
| 71 |
+
* these boundaries, and ensures that the graph is connected.
|
| 72 |
+
*
|
| 73 |
+
* In addition we ensure, through the use of the minlen property, that nodes
|
| 74 |
+
* and subgraph border nodes to not end up on the same rank.
|
| 75 |
+
*
|
| 76 |
+
* Preconditions:
|
| 77 |
+
*
|
| 78 |
+
* 1. Input graph is a DAG
|
| 79 |
+
* 2. Nodes in the input graph has a minlen attribute
|
| 80 |
+
*
|
| 81 |
+
* Postconditions:
|
| 82 |
+
*
|
| 83 |
+
* 1. Input graph is connected.
|
| 84 |
+
* 2. Dummy nodes are added for the tops and bottoms of subgraphs.
|
| 85 |
+
* 3. The minlen attribute for nodes is adjusted to ensure nodes do not
|
| 86 |
+
* get placed on the same rank as subgraph border nodes.
|
| 87 |
+
*
|
| 88 |
+
* The nesting graph idea comes from Sander, "Layout of Compound Directed
|
| 89 |
+
* Graphs."
|
| 90 |
+
*/function run(g){let root=util.addDummyNode(g,"root",{},"_root");let depths=treeDepths(g);let depthsArr=Object.values(depths);let height=util.applyWithChunking(Math.max,depthsArr)-1;// Note: depths is an Object not an array
|
| 91 |
+
let nodeSep=2*height+1;g.graph().nestingRoot=root;
|
| 92 |
+
// Multiply minlen by nodeSep to align nodes on non-border ranks.
|
| 93 |
+
g.edges().forEach(e=>g.edge(e).minlen*=nodeSep);
|
| 94 |
+
// Calculate a weight that is sufficient to keep subgraphs vertically compact
|
| 95 |
+
let weight=sumWeights(g)+1;
|
| 96 |
+
// Create border nodes and link them up
|
| 97 |
+
g.children().forEach(child=>dfs(g,root,nodeSep,weight,height,depths,child));
|
| 98 |
+
// Save the multiplier for node layers for later removal of empty border
|
| 99 |
+
// layers.
|
| 100 |
+
g.graph().nodeRankFactor=nodeSep}function dfs(g,root,nodeSep,weight,height,depths,v){let children=g.children(v);if(!children.length){if(v!==root){g.setEdge(root,v,{weight:0,minlen:nodeSep})}return}let top=util.addBorderNode(g,"_bt");let bottom=util.addBorderNode(g,"_bb");let label=g.node(v);g.setParent(top,v);label.borderTop=top;g.setParent(bottom,v);label.borderBottom=bottom;children.forEach(child=>{dfs(g,root,nodeSep,weight,height,depths,child);let childNode=g.node(child);let childTop=childNode.borderTop?childNode.borderTop:child;let childBottom=childNode.borderBottom?childNode.borderBottom:child;let thisWeight=childNode.borderTop?weight:2*weight;let minlen=childTop!==childBottom?1:height-depths[v]+1;g.setEdge(top,childTop,{weight:thisWeight,minlen:minlen,nestingEdge:true});g.setEdge(childBottom,bottom,{weight:thisWeight,minlen:minlen,nestingEdge:true})});if(!g.parent(v)){g.setEdge(root,top,{weight:0,minlen:height+depths[v]})}}function treeDepths(g){var depths={};function dfs(v,depth){var children=g.children(v);if(children&&children.length){children.forEach(child=>dfs(child,depth+1))}depths[v]=depth}g.children().forEach(v=>dfs(v,1));return depths}function sumWeights(g){return g.edges().reduce((acc,e)=>acc+g.edge(e).weight,0)}function cleanup(g){var graphLabel=g.graph();g.removeNode(graphLabel.nestingRoot);delete graphLabel.nestingRoot;g.edges().forEach(e=>{var edge=g.edge(e);if(edge.nestingEdge){g.removeEdge(e)}})}},{"./util":27}],10:[function(require,module,exports){"use strict";let util=require("./util");module.exports={run:run,undo:undo};
|
| 101 |
+
/*
|
| 102 |
+
* Breaks any long edges in the graph into short segments that span 1 layer
|
| 103 |
+
* each. This operation is undoable with the denormalize function.
|
| 104 |
+
*
|
| 105 |
+
* Pre-conditions:
|
| 106 |
+
*
|
| 107 |
+
* 1. The input graph is a DAG.
|
| 108 |
+
* 2. Each node in the graph has a "rank" property.
|
| 109 |
+
*
|
| 110 |
+
* Post-condition:
|
| 111 |
+
*
|
| 112 |
+
* 1. All edges in the graph have a length of 1.
|
| 113 |
+
* 2. Dummy nodes are added where edges have been split into segments.
|
| 114 |
+
* 3. The graph is augmented with a "dummyChains" attribute which contains
|
| 115 |
+
* the first dummy in each chain of dummy nodes produced.
|
| 116 |
+
*/function run(g){g.graph().dummyChains=[];g.edges().forEach(edge=>normalizeEdge(g,edge))}function normalizeEdge(g,e){let v=e.v;let vRank=g.node(v).rank;let w=e.w;let wRank=g.node(w).rank;let name=e.name;let edgeLabel=g.edge(e);let labelRank=edgeLabel.labelRank;if(wRank===vRank+1)return;g.removeEdge(e);let dummy,attrs,i;for(i=0,++vRank;vRank<wRank;++i,++vRank){edgeLabel.points=[];attrs={width:0,height:0,edgeLabel:edgeLabel,edgeObj:e,rank:vRank};dummy=util.addDummyNode(g,"edge",attrs,"_d");if(vRank===labelRank){attrs.width=edgeLabel.width;attrs.height=edgeLabel.height;attrs.dummy="edge-label";attrs.labelpos=edgeLabel.labelpos}g.setEdge(v,dummy,{weight:edgeLabel.weight},name);if(i===0){g.graph().dummyChains.push(dummy)}v=dummy}g.setEdge(v,w,{weight:edgeLabel.weight},name)}function undo(g){g.graph().dummyChains.forEach(v=>{let node=g.node(v);let origLabel=node.edgeLabel;let w;g.setEdge(node.edgeObj,origLabel);while(node.dummy){w=g.successors(v)[0];g.removeNode(v);origLabel.points.push({x:node.x,y:node.y});if(node.dummy==="edge-label"){origLabel.x=node.x;origLabel.y=node.y;origLabel.width=node.width;origLabel.height=node.height}v=w;node=g.node(v)}})}},{"./util":27}],11:[function(require,module,exports){module.exports=addSubgraphConstraints;function addSubgraphConstraints(g,cg,vs){let prev={},rootPrev;vs.forEach(v=>{let child=g.parent(v),parent,prevChild;while(child){parent=g.parent(child);if(parent){prevChild=prev[parent];prev[parent]=child}else{prevChild=rootPrev;rootPrev=child}if(prevChild&&prevChild!==child){cg.setEdge(prevChild,child);return}child=parent}});
|
| 117 |
+
/*
|
| 118 |
+
function dfs(v) {
|
| 119 |
+
var children = v ? g.children(v) : g.children();
|
| 120 |
+
if (children.length) {
|
| 121 |
+
var min = Number.POSITIVE_INFINITY,
|
| 122 |
+
subgraphs = [];
|
| 123 |
+
children.forEach(function(child) {
|
| 124 |
+
var childMin = dfs(child);
|
| 125 |
+
if (g.children(child).length) {
|
| 126 |
+
subgraphs.push({ v: child, order: childMin });
|
| 127 |
+
}
|
| 128 |
+
min = Math.min(min, childMin);
|
| 129 |
+
});
|
| 130 |
+
_.sortBy(subgraphs, "order").reduce(function(prev, curr) {
|
| 131 |
+
cg.setEdge(prev.v, curr.v);
|
| 132 |
+
return curr;
|
| 133 |
+
});
|
| 134 |
+
return min;
|
| 135 |
+
}
|
| 136 |
+
return g.node(v).order;
|
| 137 |
+
}
|
| 138 |
+
dfs(undefined);
|
| 139 |
+
*/}},{}],12:[function(require,module,exports){module.exports=barycenter;function barycenter(g,movable=[]){return movable.map(v=>{let inV=g.inEdges(v);if(!inV.length){return{v:v}}else{let result=inV.reduce((acc,e)=>{let edge=g.edge(e),nodeU=g.node(e.v);return{sum:acc.sum+edge.weight*nodeU.order,weight:acc.weight+edge.weight}},{sum:0,weight:0});return{v:v,barycenter:result.sum/result.weight,weight:result.weight}}})}},{}],13:[function(require,module,exports){let Graph=require("@dagrejs/graphlib").Graph;let util=require("../util");module.exports=buildLayerGraph;
|
| 140 |
+
/*
|
| 141 |
+
* Constructs a graph that can be used to sort a layer of nodes. The graph will
|
| 142 |
+
* contain all base and subgraph nodes from the request layer in their original
|
| 143 |
+
* hierarchy and any edges that are incident on these nodes and are of the type
|
| 144 |
+
* requested by the "relationship" parameter.
|
| 145 |
+
*
|
| 146 |
+
* Nodes from the requested rank that do not have parents are assigned a root
|
| 147 |
+
* node in the output graph, which is set in the root graph attribute. This
|
| 148 |
+
* makes it easy to walk the hierarchy of movable nodes during ordering.
|
| 149 |
+
*
|
| 150 |
+
* Pre-conditions:
|
| 151 |
+
*
|
| 152 |
+
* 1. Input graph is a DAG
|
| 153 |
+
* 2. Base nodes in the input graph have a rank attribute
|
| 154 |
+
* 3. Subgraph nodes in the input graph has minRank and maxRank attributes
|
| 155 |
+
* 4. Edges have an assigned weight
|
| 156 |
+
*
|
| 157 |
+
* Post-conditions:
|
| 158 |
+
*
|
| 159 |
+
* 1. Output graph has all nodes in the movable rank with preserved
|
| 160 |
+
* hierarchy.
|
| 161 |
+
* 2. Root nodes in the movable layer are made children of the node
|
| 162 |
+
* indicated by the root attribute of the graph.
|
| 163 |
+
* 3. Non-movable nodes incident on movable nodes, selected by the
|
| 164 |
+
* relationship parameter, are included in the graph (without hierarchy).
|
| 165 |
+
* 4. Edges incident on movable nodes, selected by the relationship
|
| 166 |
+
* parameter, are added to the output graph.
|
| 167 |
+
* 5. The weights for copied edges are aggregated as need, since the output
|
| 168 |
+
* graph is not a multi-graph.
|
| 169 |
+
*/function buildLayerGraph(g,rank,relationship){let root=createRootNode(g),result=new Graph({compound:true}).setGraph({root:root}).setDefaultNodeLabel(v=>g.node(v));g.nodes().forEach(v=>{let node=g.node(v),parent=g.parent(v);if(node.rank===rank||node.minRank<=rank&&rank<=node.maxRank){result.setNode(v);result.setParent(v,parent||root);
|
| 170 |
+
// This assumes we have only short edges!
|
| 171 |
+
g[relationship](v).forEach(e=>{let u=e.v===v?e.w:e.v,edge=result.edge(u,v),weight=edge!==undefined?edge.weight:0;result.setEdge(u,v,{weight:g.edge(e).weight+weight})});if(Object.hasOwn(node,"minRank")){result.setNode(v,{borderLeft:node.borderLeft[rank],borderRight:node.borderRight[rank]})}}});return result}function createRootNode(g){var v;while(g.hasNode(v=util.uniqueId("_root")));return v}},{"../util":27,"@dagrejs/graphlib":29}],14:[function(require,module,exports){"use strict";let zipObject=require("../util").zipObject;module.exports=crossCount;
|
| 172 |
+
/*
|
| 173 |
+
* A function that takes a layering (an array of layers, each with an array of
|
| 174 |
+
* ordererd nodes) and a graph and returns a weighted crossing count.
|
| 175 |
+
*
|
| 176 |
+
* Pre-conditions:
|
| 177 |
+
*
|
| 178 |
+
* 1. Input graph must be simple (not a multigraph), directed, and include
|
| 179 |
+
* only simple edges.
|
| 180 |
+
* 2. Edges in the input graph must have assigned weights.
|
| 181 |
+
*
|
| 182 |
+
* Post-conditions:
|
| 183 |
+
*
|
| 184 |
+
* 1. The graph and layering matrix are left unchanged.
|
| 185 |
+
*
|
| 186 |
+
* This algorithm is derived from Barth, et al., "Bilayer Cross Counting."
|
| 187 |
+
*/function crossCount(g,layering){let cc=0;for(let i=1;i<layering.length;++i){cc+=twoLayerCrossCount(g,layering[i-1],layering[i])}return cc}function twoLayerCrossCount(g,northLayer,southLayer){
|
| 188 |
+
// Sort all of the edges between the north and south layers by their position
|
| 189 |
+
// in the north layer and then the south. Map these edges to the position of
|
| 190 |
+
// their head in the south layer.
|
| 191 |
+
let southPos=zipObject(southLayer,southLayer.map((v,i)=>i));let southEntries=northLayer.flatMap(v=>{return g.outEdges(v).map(e=>{return{pos:southPos[e.w],weight:g.edge(e).weight}}).sort((a,b)=>a.pos-b.pos)});
|
| 192 |
+
// Build the accumulator tree
|
| 193 |
+
let firstIndex=1;while(firstIndex<southLayer.length)firstIndex<<=1;let treeSize=2*firstIndex-1;firstIndex-=1;let tree=new Array(treeSize).fill(0);
|
| 194 |
+
// Calculate the weighted crossings
|
| 195 |
+
let cc=0;southEntries.forEach(entry=>{let index=entry.pos+firstIndex;tree[index]+=entry.weight;let weightSum=0;while(index>0){if(index%2){weightSum+=tree[index+1]}index=index-1>>1;tree[index]+=entry.weight}cc+=entry.weight*weightSum});return cc}},{"../util":27}],15:[function(require,module,exports){"use strict";let initOrder=require("./init-order");let crossCount=require("./cross-count");let sortSubgraph=require("./sort-subgraph");let buildLayerGraph=require("./build-layer-graph");let addSubgraphConstraints=require("./add-subgraph-constraints");let Graph=require("@dagrejs/graphlib").Graph;let util=require("../util");module.exports=order;
|
| 196 |
+
/*
|
| 197 |
+
* Applies heuristics to minimize edge crossings in the graph and sets the best
|
| 198 |
+
* order solution as an order attribute on each node.
|
| 199 |
+
*
|
| 200 |
+
* Pre-conditions:
|
| 201 |
+
*
|
| 202 |
+
* 1. Graph must be DAG
|
| 203 |
+
* 2. Graph nodes must be objects with a "rank" attribute
|
| 204 |
+
* 3. Graph edges must have the "weight" attribute
|
| 205 |
+
*
|
| 206 |
+
* Post-conditions:
|
| 207 |
+
*
|
| 208 |
+
* 1. Graph nodes will have an "order" attribute based on the results of the
|
| 209 |
+
* algorithm.
|
| 210 |
+
*/function order(g,opts){if(opts&&typeof opts.customOrder==="function"){opts.customOrder(g,order);return}let maxRank=util.maxRank(g),downLayerGraphs=buildLayerGraphs(g,util.range(1,maxRank+1),"inEdges"),upLayerGraphs=buildLayerGraphs(g,util.range(maxRank-1,-1,-1),"outEdges");let layering=initOrder(g);assignOrder(g,layering);if(opts&&opts.disableOptimalOrderHeuristic){return}let bestCC=Number.POSITIVE_INFINITY,best;for(let i=0,lastBest=0;lastBest<4;++i,++lastBest){sweepLayerGraphs(i%2?downLayerGraphs:upLayerGraphs,i%4>=2);layering=util.buildLayerMatrix(g);let cc=crossCount(g,layering);if(cc<bestCC){lastBest=0;best=Object.assign({},layering);bestCC=cc}}assignOrder(g,best)}function buildLayerGraphs(g,ranks,relationship){return ranks.map(function(rank){return buildLayerGraph(g,rank,relationship)})}function sweepLayerGraphs(layerGraphs,biasRight){let cg=new Graph;layerGraphs.forEach(function(lg){let root=lg.graph().root;let sorted=sortSubgraph(lg,root,cg,biasRight);sorted.vs.forEach((v,i)=>lg.node(v).order=i);addSubgraphConstraints(lg,cg,sorted.vs)})}function assignOrder(g,layering){Object.values(layering).forEach(layer=>layer.forEach((v,i)=>g.node(v).order=i))}},{"../util":27,"./add-subgraph-constraints":11,"./build-layer-graph":13,"./cross-count":14,"./init-order":16,"./sort-subgraph":18,"@dagrejs/graphlib":29}],16:[function(require,module,exports){"use strict";let util=require("../util");module.exports=initOrder;
|
| 211 |
+
/*
|
| 212 |
+
* Assigns an initial order value for each node by performing a DFS search
|
| 213 |
+
* starting from nodes in the first rank. Nodes are assigned an order in their
|
| 214 |
+
* rank as they are first visited.
|
| 215 |
+
*
|
| 216 |
+
* This approach comes from Gansner, et al., "A Technique for Drawing Directed
|
| 217 |
+
* Graphs."
|
| 218 |
+
*
|
| 219 |
+
* Returns a layering matrix with an array per layer and each layer sorted by
|
| 220 |
+
* the order of its nodes.
|
| 221 |
+
*/function initOrder(g){let visited={};let simpleNodes=g.nodes().filter(v=>!g.children(v).length);let simpleNodesRanks=simpleNodes.map(v=>g.node(v).rank);let maxRank=util.applyWithChunking(Math.max,simpleNodesRanks);let layers=util.range(maxRank+1).map(()=>[]);function dfs(v){if(visited[v])return;visited[v]=true;let node=g.node(v);layers[node.rank].push(v);g.successors(v).forEach(dfs)}let orderedVs=simpleNodes.sort((a,b)=>g.node(a).rank-g.node(b).rank);orderedVs.forEach(dfs);return layers}},{"../util":27}],17:[function(require,module,exports){"use strict";let util=require("../util");module.exports=resolveConflicts;
|
| 222 |
+
/*
|
| 223 |
+
* Given a list of entries of the form {v, barycenter, weight} and a
|
| 224 |
+
* constraint graph this function will resolve any conflicts between the
|
| 225 |
+
* constraint graph and the barycenters for the entries. If the barycenters for
|
| 226 |
+
* an entry would violate a constraint in the constraint graph then we coalesce
|
| 227 |
+
* the nodes in the conflict into a new node that respects the contraint and
|
| 228 |
+
* aggregates barycenter and weight information.
|
| 229 |
+
*
|
| 230 |
+
* This implementation is based on the description in Forster, "A Fast and
|
| 231 |
+
* Simple Hueristic for Constrained Two-Level Crossing Reduction," thought it
|
| 232 |
+
* differs in some specific details.
|
| 233 |
+
*
|
| 234 |
+
* Pre-conditions:
|
| 235 |
+
*
|
| 236 |
+
* 1. Each entry has the form {v, barycenter, weight}, or if the node has
|
| 237 |
+
* no barycenter, then {v}.
|
| 238 |
+
*
|
| 239 |
+
* Returns:
|
| 240 |
+
*
|
| 241 |
+
* A new list of entries of the form {vs, i, barycenter, weight}. The list
|
| 242 |
+
* `vs` may either be a singleton or it may be an aggregation of nodes
|
| 243 |
+
* ordered such that they do not violate constraints from the constraint
|
| 244 |
+
* graph. The property `i` is the lowest original index of any of the
|
| 245 |
+
* elements in `vs`.
|
| 246 |
+
*/function resolveConflicts(entries,cg){let mappedEntries={};entries.forEach((entry,i)=>{let tmp=mappedEntries[entry.v]={indegree:0,in:[],out:[],vs:[entry.v],i:i};if(entry.barycenter!==undefined){tmp.barycenter=entry.barycenter;tmp.weight=entry.weight}});cg.edges().forEach(e=>{let entryV=mappedEntries[e.v];let entryW=mappedEntries[e.w];if(entryV!==undefined&&entryW!==undefined){entryW.indegree++;entryV.out.push(mappedEntries[e.w])}});let sourceSet=Object.values(mappedEntries).filter(entry=>!entry.indegree);return doResolveConflicts(sourceSet)}function doResolveConflicts(sourceSet){let entries=[];function handleIn(vEntry){return uEntry=>{if(uEntry.merged){return}if(uEntry.barycenter===undefined||vEntry.barycenter===undefined||uEntry.barycenter>=vEntry.barycenter){mergeEntries(vEntry,uEntry)}}}function handleOut(vEntry){return wEntry=>{wEntry["in"].push(vEntry);if(--wEntry.indegree===0){sourceSet.push(wEntry)}}}while(sourceSet.length){let entry=sourceSet.pop();entries.push(entry);entry["in"].reverse().forEach(handleIn(entry));entry.out.forEach(handleOut(entry))}return entries.filter(entry=>!entry.merged).map(entry=>{return util.pick(entry,["vs","i","barycenter","weight"])})}function mergeEntries(target,source){let sum=0;let weight=0;if(target.weight){sum+=target.barycenter*target.weight;weight+=target.weight}if(source.weight){sum+=source.barycenter*source.weight;weight+=source.weight}target.vs=source.vs.concat(target.vs);target.barycenter=sum/weight;target.weight=weight;target.i=Math.min(source.i,target.i);source.merged=true}},{"../util":27}],18:[function(require,module,exports){let barycenter=require("./barycenter");let resolveConflicts=require("./resolve-conflicts");let sort=require("./sort");module.exports=sortSubgraph;function sortSubgraph(g,v,cg,biasRight){let movable=g.children(v);let node=g.node(v);let bl=node?node.borderLeft:undefined;let br=node?node.borderRight:undefined;let subgraphs={};if(bl){movable=movable.filter(w=>w!==bl&&w!==br)}let barycenters=barycenter(g,movable);barycenters.forEach(entry=>{if(g.children(entry.v).length){let subgraphResult=sortSubgraph(g,entry.v,cg,biasRight);subgraphs[entry.v]=subgraphResult;if(Object.hasOwn(subgraphResult,"barycenter")){mergeBarycenters(entry,subgraphResult)}}});let entries=resolveConflicts(barycenters,cg);expandSubgraphs(entries,subgraphs);let result=sort(entries,biasRight);if(bl){result.vs=[bl,result.vs,br].flat(true);if(g.predecessors(bl).length){let blPred=g.node(g.predecessors(bl)[0]),brPred=g.node(g.predecessors(br)[0]);if(!Object.hasOwn(result,"barycenter")){result.barycenter=0;result.weight=0}result.barycenter=(result.barycenter*result.weight+blPred.order+brPred.order)/(result.weight+2);result.weight+=2}}return result}function expandSubgraphs(entries,subgraphs){entries.forEach(entry=>{entry.vs=entry.vs.flatMap(v=>{if(subgraphs[v]){return subgraphs[v].vs}return v})})}function mergeBarycenters(target,other){if(target.barycenter!==undefined){target.barycenter=(target.barycenter*target.weight+other.barycenter*other.weight)/(target.weight+other.weight);target.weight+=other.weight}else{target.barycenter=other.barycenter;target.weight=other.weight}}},{"./barycenter":12,"./resolve-conflicts":17,"./sort":19}],19:[function(require,module,exports){let util=require("../util");module.exports=sort;function sort(entries,biasRight){let parts=util.partition(entries,entry=>{return Object.hasOwn(entry,"barycenter")});let sortable=parts.lhs,unsortable=parts.rhs.sort((a,b)=>b.i-a.i),vs=[],sum=0,weight=0,vsIndex=0;sortable.sort(compareWithBias(!!biasRight));vsIndex=consumeUnsortable(vs,unsortable,vsIndex);sortable.forEach(entry=>{vsIndex+=entry.vs.length;vs.push(entry.vs);sum+=entry.barycenter*entry.weight;weight+=entry.weight;vsIndex=consumeUnsortable(vs,unsortable,vsIndex)});let result={vs:vs.flat(true)};if(weight){result.barycenter=sum/weight;result.weight=weight}return result}function consumeUnsortable(vs,unsortable,index){let last;while(unsortable.length&&(last=unsortable[unsortable.length-1]).i<=index){unsortable.pop();vs.push(last.vs);index++}return index}function compareWithBias(bias){return(entryV,entryW)=>{if(entryV.barycenter<entryW.barycenter){return-1}else if(entryV.barycenter>entryW.barycenter){return 1}return!bias?entryV.i-entryW.i:entryW.i-entryV.i}}},{"../util":27}],20:[function(require,module,exports){module.exports=parentDummyChains;function parentDummyChains(g){let postorderNums=postorder(g);g.graph().dummyChains.forEach(v=>{let node=g.node(v);let edgeObj=node.edgeObj;let pathData=findPath(g,postorderNums,edgeObj.v,edgeObj.w);let path=pathData.path;let lca=pathData.lca;let pathIdx=0;let pathV=path[pathIdx];let ascending=true;while(v!==edgeObj.w){node=g.node(v);if(ascending){while((pathV=path[pathIdx])!==lca&&g.node(pathV).maxRank<node.rank){pathIdx++}if(pathV===lca){ascending=false}}if(!ascending){while(pathIdx<path.length-1&&g.node(pathV=path[pathIdx+1]).minRank<=node.rank){pathIdx++}pathV=path[pathIdx]}g.setParent(v,pathV);v=g.successors(v)[0]}})}
|
| 247 |
+
// Find a path from v to w through the lowest common ancestor (LCA). Return the
|
| 248 |
+
// full path and the LCA.
|
| 249 |
+
function findPath(g,postorderNums,v,w){let vPath=[];let wPath=[];let low=Math.min(postorderNums[v].low,postorderNums[w].low);let lim=Math.max(postorderNums[v].lim,postorderNums[w].lim);let parent;let lca;
|
| 250 |
+
// Traverse up from v to find the LCA
|
| 251 |
+
parent=v;do{parent=g.parent(parent);vPath.push(parent)}while(parent&&(postorderNums[parent].low>low||lim>postorderNums[parent].lim));lca=parent;
|
| 252 |
+
// Traverse from w to LCA
|
| 253 |
+
parent=w;while((parent=g.parent(parent))!==lca){wPath.push(parent)}return{path:vPath.concat(wPath.reverse()),lca:lca}}function postorder(g){let result={};let lim=0;function dfs(v){let low=lim;g.children(v).forEach(dfs);result[v]={low:low,lim:lim++}}g.children().forEach(dfs);return result}},{}],21:[function(require,module,exports){"use strict";let Graph=require("@dagrejs/graphlib").Graph;let util=require("../util");
|
| 254 |
+
/*
|
| 255 |
+
* This module provides coordinate assignment based on Brandes and Köpf, "Fast
|
| 256 |
+
* and Simple Horizontal Coordinate Assignment."
|
| 257 |
+
*/module.exports={positionX:positionX,findType1Conflicts:findType1Conflicts,findType2Conflicts:findType2Conflicts,addConflict:addConflict,hasConflict:hasConflict,verticalAlignment:verticalAlignment,horizontalCompaction:horizontalCompaction,alignCoordinates:alignCoordinates,findSmallestWidthAlignment:findSmallestWidthAlignment,balance:balance};
|
| 258 |
+
/*
|
| 259 |
+
* Marks all edges in the graph with a type-1 conflict with the "type1Conflict"
|
| 260 |
+
* property. A type-1 conflict is one where a non-inner segment crosses an
|
| 261 |
+
* inner segment. An inner segment is an edge with both incident nodes marked
|
| 262 |
+
* with the "dummy" property.
|
| 263 |
+
*
|
| 264 |
+
* This algorithm scans layer by layer, starting with the second, for type-1
|
| 265 |
+
* conflicts between the current layer and the previous layer. For each layer
|
| 266 |
+
* it scans the nodes from left to right until it reaches one that is incident
|
| 267 |
+
* on an inner segment. It then scans predecessors to determine if they have
|
| 268 |
+
* edges that cross that inner segment. At the end a final scan is done for all
|
| 269 |
+
* nodes on the current rank to see if they cross the last visited inner
|
| 270 |
+
* segment.
|
| 271 |
+
*
|
| 272 |
+
* This algorithm (safely) assumes that a dummy node will only be incident on a
|
| 273 |
+
* single node in the layers being scanned.
|
| 274 |
+
*/function findType1Conflicts(g,layering){let conflicts={};function visitLayer(prevLayer,layer){let
|
| 275 |
+
// last visited node in the previous layer that is incident on an inner
|
| 276 |
+
// segment.
|
| 277 |
+
k0=0,
|
| 278 |
+
// Tracks the last node in this layer scanned for crossings with a type-1
|
| 279 |
+
// segment.
|
| 280 |
+
scanPos=0,prevLayerLength=prevLayer.length,lastNode=layer[layer.length-1];layer.forEach((v,i)=>{let w=findOtherInnerSegmentNode(g,v),k1=w?g.node(w).order:prevLayerLength;if(w||v===lastNode){layer.slice(scanPos,i+1).forEach(scanNode=>{g.predecessors(scanNode).forEach(u=>{let uLabel=g.node(u),uPos=uLabel.order;if((uPos<k0||k1<uPos)&&!(uLabel.dummy&&g.node(scanNode).dummy)){addConflict(conflicts,u,scanNode)}})});scanPos=i+1;k0=k1}});return layer}layering.length&&layering.reduce(visitLayer);return conflicts}function findType2Conflicts(g,layering){let conflicts={};function scan(south,southPos,southEnd,prevNorthBorder,nextNorthBorder){let v;util.range(southPos,southEnd).forEach(i=>{v=south[i];if(g.node(v).dummy){g.predecessors(v).forEach(u=>{let uNode=g.node(u);if(uNode.dummy&&(uNode.order<prevNorthBorder||uNode.order>nextNorthBorder)){addConflict(conflicts,u,v)}})}})}function visitLayer(north,south){let prevNorthPos=-1,nextNorthPos,southPos=0;south.forEach((v,southLookahead)=>{if(g.node(v).dummy==="border"){let predecessors=g.predecessors(v);if(predecessors.length){nextNorthPos=g.node(predecessors[0]).order;scan(south,southPos,southLookahead,prevNorthPos,nextNorthPos);southPos=southLookahead;prevNorthPos=nextNorthPos}}scan(south,southPos,south.length,nextNorthPos,north.length)});return south}layering.length&&layering.reduce(visitLayer);return conflicts}function findOtherInnerSegmentNode(g,v){if(g.node(v).dummy){return g.predecessors(v).find(u=>g.node(u).dummy)}}function addConflict(conflicts,v,w){if(v>w){let tmp=v;v=w;w=tmp}let conflictsV=conflicts[v];if(!conflictsV){conflicts[v]=conflictsV={}}conflictsV[w]=true}function hasConflict(conflicts,v,w){if(v>w){let tmp=v;v=w;w=tmp}return!!conflicts[v]&&Object.hasOwn(conflicts[v],w)}
|
| 281 |
+
/*
|
| 282 |
+
* Try to align nodes into vertical "blocks" where possible. This algorithm
|
| 283 |
+
* attempts to align a node with one of its median neighbors. If the edge
|
| 284 |
+
* connecting a neighbor is a type-1 conflict then we ignore that possibility.
|
| 285 |
+
* If a previous node has already formed a block with a node after the node
|
| 286 |
+
* we're trying to form a block with, we also ignore that possibility - our
|
| 287 |
+
* blocks would be split in that scenario.
|
| 288 |
+
*/function verticalAlignment(g,layering,conflicts,neighborFn){let root={},align={},pos={};
|
| 289 |
+
// We cache the position here based on the layering because the graph and
|
| 290 |
+
// layering may be out of sync. The layering matrix is manipulated to
|
| 291 |
+
// generate different extreme alignments.
|
| 292 |
+
layering.forEach(layer=>{layer.forEach((v,order)=>{root[v]=v;align[v]=v;pos[v]=order})});layering.forEach(layer=>{let prevIdx=-1;layer.forEach(v=>{let ws=neighborFn(v);if(ws.length){ws=ws.sort((a,b)=>pos[a]-pos[b]);let mp=(ws.length-1)/2;for(let i=Math.floor(mp),il=Math.ceil(mp);i<=il;++i){let w=ws[i];if(align[v]===v&&prevIdx<pos[w]&&!hasConflict(conflicts,v,w)){align[w]=v;align[v]=root[v]=root[w];prevIdx=pos[w]}}}})});return{root:root,align:align}}function horizontalCompaction(g,layering,root,align,reverseSep){
|
| 293 |
+
// This portion of the algorithm differs from BK due to a number of problems.
|
| 294 |
+
// Instead of their algorithm we construct a new block graph and do two
|
| 295 |
+
// sweeps. The first sweep places blocks with the smallest possible
|
| 296 |
+
// coordinates. The second sweep removes unused space by moving blocks to the
|
| 297 |
+
// greatest coordinates without violating separation.
|
| 298 |
+
let xs={},blockG=buildBlockGraph(g,layering,root,reverseSep),borderType=reverseSep?"borderLeft":"borderRight";function iterate(setXsFunc,nextNodesFunc){let stack=blockG.nodes();let elem=stack.pop();let visited={};while(elem){if(visited[elem]){setXsFunc(elem)}else{visited[elem]=true;stack.push(elem);stack=stack.concat(nextNodesFunc(elem))}elem=stack.pop()}}
|
| 299 |
+
// First pass, assign smallest coordinates
|
| 300 |
+
function pass1(elem){xs[elem]=blockG.inEdges(elem).reduce((acc,e)=>{return Math.max(acc,xs[e.v]+blockG.edge(e))},0)}
|
| 301 |
+
// Second pass, assign greatest coordinates
|
| 302 |
+
function pass2(elem){let min=blockG.outEdges(elem).reduce((acc,e)=>{return Math.min(acc,xs[e.w]-blockG.edge(e))},Number.POSITIVE_INFINITY);let node=g.node(elem);if(min!==Number.POSITIVE_INFINITY&&node.borderType!==borderType){xs[elem]=Math.max(xs[elem],min)}}iterate(pass1,blockG.predecessors.bind(blockG));iterate(pass2,blockG.successors.bind(blockG));
|
| 303 |
+
// Assign x coordinates to all nodes
|
| 304 |
+
Object.keys(align).forEach(v=>xs[v]=xs[root[v]]);return xs}function buildBlockGraph(g,layering,root,reverseSep){let blockGraph=new Graph,graphLabel=g.graph(),sepFn=sep(graphLabel.nodesep,graphLabel.edgesep,reverseSep);layering.forEach(layer=>{let u;layer.forEach(v=>{let vRoot=root[v];blockGraph.setNode(vRoot);if(u){var uRoot=root[u],prevMax=blockGraph.edge(uRoot,vRoot);blockGraph.setEdge(uRoot,vRoot,Math.max(sepFn(g,v,u),prevMax||0))}u=v})});return blockGraph}
|
| 305 |
+
/*
|
| 306 |
+
* Returns the alignment that has the smallest width of the given alignments.
|
| 307 |
+
*/function findSmallestWidthAlignment(g,xss){return Object.values(xss).reduce((currentMinAndXs,xs)=>{let max=Number.NEGATIVE_INFINITY;let min=Number.POSITIVE_INFINITY;Object.entries(xs).forEach(([v,x])=>{let halfWidth=width(g,v)/2;max=Math.max(x+halfWidth,max);min=Math.min(x-halfWidth,min)});const newMin=max-min;if(newMin<currentMinAndXs[0]){currentMinAndXs=[newMin,xs]}return currentMinAndXs},[Number.POSITIVE_INFINITY,null])[1]}
|
| 308 |
+
/*
|
| 309 |
+
* Align the coordinates of each of the layout alignments such that
|
| 310 |
+
* left-biased alignments have their minimum coordinate at the same point as
|
| 311 |
+
* the minimum coordinate of the smallest width alignment and right-biased
|
| 312 |
+
* alignments have their maximum coordinate at the same point as the maximum
|
| 313 |
+
* coordinate of the smallest width alignment.
|
| 314 |
+
*/function alignCoordinates(xss,alignTo){let alignToVals=Object.values(alignTo),alignToMin=util.applyWithChunking(Math.min,alignToVals),alignToMax=util.applyWithChunking(Math.max,alignToVals);["u","d"].forEach(vert=>{["l","r"].forEach(horiz=>{let alignment=vert+horiz,xs=xss[alignment];if(xs===alignTo)return;let xsVals=Object.values(xs);let delta=alignToMin-util.applyWithChunking(Math.min,xsVals);if(horiz!=="l"){delta=alignToMax-util.applyWithChunking(Math.max,xsVals)}if(delta){xss[alignment]=util.mapValues(xs,x=>x+delta)}})})}function balance(xss,align){return util.mapValues(xss.ul,(num,v)=>{if(align){return xss[align.toLowerCase()][v]}else{let xs=Object.values(xss).map(xs=>xs[v]).sort((a,b)=>a-b);return(xs[1]+xs[2])/2}})}function positionX(g){let layering=util.buildLayerMatrix(g);let conflicts=Object.assign(findType1Conflicts(g,layering),findType2Conflicts(g,layering));let xss={};let adjustedLayering;["u","d"].forEach(vert=>{adjustedLayering=vert==="u"?layering:Object.values(layering).reverse();["l","r"].forEach(horiz=>{if(horiz==="r"){adjustedLayering=adjustedLayering.map(inner=>{return Object.values(inner).reverse()})}let neighborFn=(vert==="u"?g.predecessors:g.successors).bind(g);let align=verticalAlignment(g,adjustedLayering,conflicts,neighborFn);let xs=horizontalCompaction(g,adjustedLayering,align.root,align.align,horiz==="r");if(horiz==="r"){xs=util.mapValues(xs,x=>-x)}xss[vert+horiz]=xs})});let smallestWidth=findSmallestWidthAlignment(g,xss);alignCoordinates(xss,smallestWidth);return balance(xss,g.graph().align)}function sep(nodeSep,edgeSep,reverseSep){return(g,v,w)=>{let vLabel=g.node(v);let wLabel=g.node(w);let sum=0;let delta;sum+=vLabel.width/2;if(Object.hasOwn(vLabel,"labelpos")){switch(vLabel.labelpos.toLowerCase()){case"l":delta=-vLabel.width/2;break;case"r":delta=vLabel.width/2;break}}if(delta){sum+=reverseSep?delta:-delta}delta=0;sum+=(vLabel.dummy?edgeSep:nodeSep)/2;sum+=(wLabel.dummy?edgeSep:nodeSep)/2;sum+=wLabel.width/2;if(Object.hasOwn(wLabel,"labelpos")){switch(wLabel.labelpos.toLowerCase()){case"l":delta=wLabel.width/2;break;case"r":delta=-wLabel.width/2;break}}if(delta){sum+=reverseSep?delta:-delta}delta=0;return sum}}function width(g,v){return g.node(v).width}},{"../util":27,"@dagrejs/graphlib":29}],22:[function(require,module,exports){"use strict";let util=require("../util");let positionX=require("./bk").positionX;module.exports=position;function position(g){g=util.asNonCompoundGraph(g);positionY(g);Object.entries(positionX(g)).forEach(([v,x])=>g.node(v).x=x)}function positionY(g){let layering=util.buildLayerMatrix(g);let rankSep=g.graph().ranksep;let prevY=0;layering.forEach(layer=>{const maxHeight=layer.reduce((acc,v)=>{const height=g.node(v).height;if(acc>height){return acc}else{return height}},0);layer.forEach(v=>g.node(v).y=prevY+maxHeight/2);prevY+=maxHeight+rankSep})}},{"../util":27,"./bk":21}],23:[function(require,module,exports){"use strict";var Graph=require("@dagrejs/graphlib").Graph;var slack=require("./util").slack;module.exports=feasibleTree;
|
| 315 |
+
/*
|
| 316 |
+
* Constructs a spanning tree with tight edges and adjusted the input node's
|
| 317 |
+
* ranks to achieve this. A tight edge is one that is has a length that matches
|
| 318 |
+
* its "minlen" attribute.
|
| 319 |
+
*
|
| 320 |
+
* The basic structure for this function is derived from Gansner, et al., "A
|
| 321 |
+
* Technique for Drawing Directed Graphs."
|
| 322 |
+
*
|
| 323 |
+
* Pre-conditions:
|
| 324 |
+
*
|
| 325 |
+
* 1. Graph must be a DAG.
|
| 326 |
+
* 2. Graph must be connected.
|
| 327 |
+
* 3. Graph must have at least one node.
|
| 328 |
+
* 5. Graph nodes must have been previously assigned a "rank" property that
|
| 329 |
+
* respects the "minlen" property of incident edges.
|
| 330 |
+
* 6. Graph edges must have a "minlen" property.
|
| 331 |
+
*
|
| 332 |
+
* Post-conditions:
|
| 333 |
+
*
|
| 334 |
+
* - Graph nodes will have their rank adjusted to ensure that all edges are
|
| 335 |
+
* tight.
|
| 336 |
+
*
|
| 337 |
+
* Returns a tree (undirected graph) that is constructed using only "tight"
|
| 338 |
+
* edges.
|
| 339 |
+
*/function feasibleTree(g){var t=new Graph({directed:false});
|
| 340 |
+
// Choose arbitrary node from which to start our tree
|
| 341 |
+
var start=g.nodes()[0];var size=g.nodeCount();t.setNode(start,{});var edge,delta;while(tightTree(t,g)<size){edge=findMinSlackEdge(t,g);delta=t.hasNode(edge.v)?slack(g,edge):-slack(g,edge);shiftRanks(t,g,delta)}return t}
|
| 342 |
+
/*
|
| 343 |
+
* Finds a maximal tree of tight edges and returns the number of nodes in the
|
| 344 |
+
* tree.
|
| 345 |
+
*/function tightTree(t,g){function dfs(v){g.nodeEdges(v).forEach(e=>{var edgeV=e.v,w=v===edgeV?e.w:edgeV;if(!t.hasNode(w)&&!slack(g,e)){t.setNode(w,{});t.setEdge(v,w,{});dfs(w)}})}t.nodes().forEach(dfs);return t.nodeCount()}
|
| 346 |
+
/*
|
| 347 |
+
* Finds the edge with the smallest slack that is incident on tree and returns
|
| 348 |
+
* it.
|
| 349 |
+
*/function findMinSlackEdge(t,g){const edges=g.edges();return edges.reduce((acc,edge)=>{let edgeSlack=Number.POSITIVE_INFINITY;if(t.hasNode(edge.v)!==t.hasNode(edge.w)){edgeSlack=slack(g,edge)}if(edgeSlack<acc[0]){return[edgeSlack,edge]}return acc},[Number.POSITIVE_INFINITY,null])[1]}function shiftRanks(t,g,delta){t.nodes().forEach(v=>g.node(v).rank+=delta)}},{"./util":26,"@dagrejs/graphlib":29}],24:[function(require,module,exports){"use strict";var rankUtil=require("./util");var longestPath=rankUtil.longestPath;var feasibleTree=require("./feasible-tree");var networkSimplex=require("./network-simplex");module.exports=rank;
|
| 350 |
+
/*
|
| 351 |
+
* Assigns a rank to each node in the input graph that respects the "minlen"
|
| 352 |
+
* constraint specified on edges between nodes.
|
| 353 |
+
*
|
| 354 |
+
* This basic structure is derived from Gansner, et al., "A Technique for
|
| 355 |
+
* Drawing Directed Graphs."
|
| 356 |
+
*
|
| 357 |
+
* Pre-conditions:
|
| 358 |
+
*
|
| 359 |
+
* 1. Graph must be a connected DAG
|
| 360 |
+
* 2. Graph nodes must be objects
|
| 361 |
+
* 3. Graph edges must have "weight" and "minlen" attributes
|
| 362 |
+
*
|
| 363 |
+
* Post-conditions:
|
| 364 |
+
*
|
| 365 |
+
* 1. Graph nodes will have a "rank" attribute based on the results of the
|
| 366 |
+
* algorithm. Ranks can start at any index (including negative), we'll
|
| 367 |
+
* fix them up later.
|
| 368 |
+
*/function rank(g){switch(g.graph().ranker){case"network-simplex":networkSimplexRanker(g);break;case"tight-tree":tightTreeRanker(g);break;case"longest-path":longestPathRanker(g);break;default:networkSimplexRanker(g)}}
|
| 369 |
+
// A fast and simple ranker, but results are far from optimal.
|
| 370 |
+
var longestPathRanker=longestPath;function tightTreeRanker(g){longestPath(g);feasibleTree(g)}function networkSimplexRanker(g){networkSimplex(g)}},{"./feasible-tree":23,"./network-simplex":25,"./util":26}],25:[function(require,module,exports){"use strict";var feasibleTree=require("./feasible-tree");var slack=require("./util").slack;var initRank=require("./util").longestPath;var preorder=require("@dagrejs/graphlib").alg.preorder;var postorder=require("@dagrejs/graphlib").alg.postorder;var simplify=require("../util").simplify;module.exports=networkSimplex;
|
| 371 |
+
// Expose some internals for testing purposes
|
| 372 |
+
networkSimplex.initLowLimValues=initLowLimValues;networkSimplex.initCutValues=initCutValues;networkSimplex.calcCutValue=calcCutValue;networkSimplex.leaveEdge=leaveEdge;networkSimplex.enterEdge=enterEdge;networkSimplex.exchangeEdges=exchangeEdges;
|
| 373 |
+
/*
|
| 374 |
+
* The network simplex algorithm assigns ranks to each node in the input graph
|
| 375 |
+
* and iteratively improves the ranking to reduce the length of edges.
|
| 376 |
+
*
|
| 377 |
+
* Preconditions:
|
| 378 |
+
*
|
| 379 |
+
* 1. The input graph must be a DAG.
|
| 380 |
+
* 2. All nodes in the graph must have an object value.
|
| 381 |
+
* 3. All edges in the graph must have "minlen" and "weight" attributes.
|
| 382 |
+
*
|
| 383 |
+
* Postconditions:
|
| 384 |
+
*
|
| 385 |
+
* 1. All nodes in the graph will have an assigned "rank" attribute that has
|
| 386 |
+
* been optimized by the network simplex algorithm. Ranks start at 0.
|
| 387 |
+
*
|
| 388 |
+
*
|
| 389 |
+
* A rough sketch of the algorithm is as follows:
|
| 390 |
+
*
|
| 391 |
+
* 1. Assign initial ranks to each node. We use the longest path algorithm,
|
| 392 |
+
* which assigns ranks to the lowest position possible. In general this
|
| 393 |
+
* leads to very wide bottom ranks and unnecessarily long edges.
|
| 394 |
+
* 2. Construct a feasible tight tree. A tight tree is one such that all
|
| 395 |
+
* edges in the tree have no slack (difference between length of edge
|
| 396 |
+
* and minlen for the edge). This by itself greatly improves the assigned
|
| 397 |
+
* rankings by shorting edges.
|
| 398 |
+
* 3. Iteratively find edges that have negative cut values. Generally a
|
| 399 |
+
* negative cut value indicates that the edge could be removed and a new
|
| 400 |
+
* tree edge could be added to produce a more compact graph.
|
| 401 |
+
*
|
| 402 |
+
* Much of the algorithms here are derived from Gansner, et al., "A Technique
|
| 403 |
+
* for Drawing Directed Graphs." The structure of the file roughly follows the
|
| 404 |
+
* structure of the overall algorithm.
|
| 405 |
+
*/function networkSimplex(g){g=simplify(g);initRank(g);var t=feasibleTree(g);initLowLimValues(t);initCutValues(t,g);var e,f;while(e=leaveEdge(t)){f=enterEdge(t,g,e);exchangeEdges(t,g,e,f)}}
|
| 406 |
+
/*
|
| 407 |
+
* Initializes cut values for all edges in the tree.
|
| 408 |
+
*/function initCutValues(t,g){var vs=postorder(t,t.nodes());vs=vs.slice(0,vs.length-1);vs.forEach(v=>assignCutValue(t,g,v))}function assignCutValue(t,g,child){var childLab=t.node(child);var parent=childLab.parent;t.edge(child,parent).cutvalue=calcCutValue(t,g,child)}
|
| 409 |
+
/*
|
| 410 |
+
* Given the tight tree, its graph, and a child in the graph calculate and
|
| 411 |
+
* return the cut value for the edge between the child and its parent.
|
| 412 |
+
*/function calcCutValue(t,g,child){var childLab=t.node(child);var parent=childLab.parent;
|
| 413 |
+
// True if the child is on the tail end of the edge in the directed graph
|
| 414 |
+
var childIsTail=true;
|
| 415 |
+
// The graph's view of the tree edge we're inspecting
|
| 416 |
+
var graphEdge=g.edge(child,parent);
|
| 417 |
+
// The accumulated cut value for the edge between this node and its parent
|
| 418 |
+
var cutValue=0;if(!graphEdge){childIsTail=false;graphEdge=g.edge(parent,child)}cutValue=graphEdge.weight;g.nodeEdges(child).forEach(e=>{var isOutEdge=e.v===child,other=isOutEdge?e.w:e.v;if(other!==parent){var pointsToHead=isOutEdge===childIsTail,otherWeight=g.edge(e).weight;cutValue+=pointsToHead?otherWeight:-otherWeight;if(isTreeEdge(t,child,other)){var otherCutValue=t.edge(child,other).cutvalue;cutValue+=pointsToHead?-otherCutValue:otherCutValue}}});return cutValue}function initLowLimValues(tree,root){if(arguments.length<2){root=tree.nodes()[0]}dfsAssignLowLim(tree,{},1,root)}function dfsAssignLowLim(tree,visited,nextLim,v,parent){var low=nextLim;var label=tree.node(v);visited[v]=true;tree.neighbors(v).forEach(w=>{if(!Object.hasOwn(visited,w)){nextLim=dfsAssignLowLim(tree,visited,nextLim,w,v)}});label.low=low;label.lim=nextLim++;if(parent){label.parent=parent}else{
|
| 419 |
+
// TODO should be able to remove this when we incrementally update low lim
|
| 420 |
+
delete label.parent}return nextLim}function leaveEdge(tree){return tree.edges().find(e=>tree.edge(e).cutvalue<0)}function enterEdge(t,g,edge){var v=edge.v;var w=edge.w;
|
| 421 |
+
// For the rest of this function we assume that v is the tail and w is the
|
| 422 |
+
// head, so if we don't have this edge in the graph we should flip it to
|
| 423 |
+
// match the correct orientation.
|
| 424 |
+
if(!g.hasEdge(v,w)){v=edge.w;w=edge.v}var vLabel=t.node(v);var wLabel=t.node(w);var tailLabel=vLabel;var flip=false;
|
| 425 |
+
// If the root is in the tail of the edge then we need to flip the logic that
|
| 426 |
+
// checks for the head and tail nodes in the candidates function below.
|
| 427 |
+
if(vLabel.lim>wLabel.lim){tailLabel=wLabel;flip=true}var candidates=g.edges().filter(edge=>{return flip===isDescendant(t,t.node(edge.v),tailLabel)&&flip!==isDescendant(t,t.node(edge.w),tailLabel)});return candidates.reduce((acc,edge)=>{if(slack(g,edge)<slack(g,acc)){return edge}return acc})}function exchangeEdges(t,g,e,f){var v=e.v;var w=e.w;t.removeEdge(v,w);t.setEdge(f.v,f.w,{});initLowLimValues(t);initCutValues(t,g);updateRanks(t,g)}function updateRanks(t,g){var root=t.nodes().find(v=>!g.node(v).parent);var vs=preorder(t,root);vs=vs.slice(1);vs.forEach(v=>{var parent=t.node(v).parent,edge=g.edge(v,parent),flipped=false;if(!edge){edge=g.edge(parent,v);flipped=true}g.node(v).rank=g.node(parent).rank+(flipped?edge.minlen:-edge.minlen)})}
|
| 428 |
+
/*
|
| 429 |
+
* Returns true if the edge is in the tree.
|
| 430 |
+
*/function isTreeEdge(tree,u,v){return tree.hasEdge(u,v)}
|
| 431 |
+
/*
|
| 432 |
+
* Returns true if the specified node is descendant of the root node per the
|
| 433 |
+
* assigned low and lim attributes in the tree.
|
| 434 |
+
*/function isDescendant(tree,vLabel,rootLabel){return rootLabel.low<=vLabel.lim&&vLabel.lim<=rootLabel.lim}},{"../util":27,"./feasible-tree":23,"./util":26,"@dagrejs/graphlib":29}],26:[function(require,module,exports){"use strict";const{applyWithChunking}=require("../util");module.exports={longestPath:longestPath,slack:slack};
|
| 435 |
+
/*
|
| 436 |
+
* Initializes ranks for the input graph using the longest path algorithm. This
|
| 437 |
+
* algorithm scales well and is fast in practice, it yields rather poor
|
| 438 |
+
* solutions. Nodes are pushed to the lowest layer possible, leaving the bottom
|
| 439 |
+
* ranks wide and leaving edges longer than necessary. However, due to its
|
| 440 |
+
* speed, this algorithm is good for getting an initial ranking that can be fed
|
| 441 |
+
* into other algorithms.
|
| 442 |
+
*
|
| 443 |
+
* This algorithm does not normalize layers because it will be used by other
|
| 444 |
+
* algorithms in most cases. If using this algorithm directly, be sure to
|
| 445 |
+
* run normalize at the end.
|
| 446 |
+
*
|
| 447 |
+
* Pre-conditions:
|
| 448 |
+
*
|
| 449 |
+
* 1. Input graph is a DAG.
|
| 450 |
+
* 2. Input graph node labels can be assigned properties.
|
| 451 |
+
*
|
| 452 |
+
* Post-conditions:
|
| 453 |
+
*
|
| 454 |
+
* 1. Each node will be assign an (unnormalized) "rank" property.
|
| 455 |
+
*/function longestPath(g){var visited={};function dfs(v){var label=g.node(v);if(Object.hasOwn(visited,v)){return label.rank}visited[v]=true;let outEdgesMinLens=g.outEdges(v).map(e=>{if(e==null){return Number.POSITIVE_INFINITY}return dfs(e.w)-g.edge(e).minlen});var rank=applyWithChunking(Math.min,outEdgesMinLens);if(rank===Number.POSITIVE_INFINITY){rank=0}return label.rank=rank}g.sources().forEach(dfs)}
|
| 456 |
+
/*
|
| 457 |
+
* Returns the amount of slack for the given edge. The slack is defined as the
|
| 458 |
+
* difference between the length of the edge and its minimum length.
|
| 459 |
+
*/function slack(g,e){return g.node(e.w).rank-g.node(e.v).rank-g.edge(e).minlen}},{"../util":27}],27:[function(require,module,exports){
|
| 460 |
+
/* eslint "no-console": off */
|
| 461 |
+
"use strict";let Graph=require("@dagrejs/graphlib").Graph;module.exports={addBorderNode:addBorderNode,addDummyNode:addDummyNode,applyWithChunking:applyWithChunking,asNonCompoundGraph:asNonCompoundGraph,buildLayerMatrix:buildLayerMatrix,intersectRect:intersectRect,mapValues:mapValues,maxRank:maxRank,normalizeRanks:normalizeRanks,notime:notime,partition:partition,pick:pick,predecessorWeights:predecessorWeights,range:range,removeEmptyRanks:removeEmptyRanks,simplify:simplify,successorWeights:successorWeights,time:time,uniqueId:uniqueId,zipObject:zipObject};
|
| 462 |
+
/*
|
| 463 |
+
* Adds a dummy node to the graph and return v.
|
| 464 |
+
*/function addDummyNode(g,type,attrs,name){let v;do{v=uniqueId(name)}while(g.hasNode(v));attrs.dummy=type;g.setNode(v,attrs);return v}
|
| 465 |
+
/*
|
| 466 |
+
* Returns a new graph with only simple edges. Handles aggregation of data
|
| 467 |
+
* associated with multi-edges.
|
| 468 |
+
*/function simplify(g){let simplified=(new Graph).setGraph(g.graph());g.nodes().forEach(v=>simplified.setNode(v,g.node(v)));g.edges().forEach(e=>{let simpleLabel=simplified.edge(e.v,e.w)||{weight:0,minlen:1};let label=g.edge(e);simplified.setEdge(e.v,e.w,{weight:simpleLabel.weight+label.weight,minlen:Math.max(simpleLabel.minlen,label.minlen)})});return simplified}function asNonCompoundGraph(g){let simplified=new Graph({multigraph:g.isMultigraph()}).setGraph(g.graph());g.nodes().forEach(v=>{if(!g.children(v).length){simplified.setNode(v,g.node(v))}});g.edges().forEach(e=>{simplified.setEdge(e,g.edge(e))});return simplified}function successorWeights(g){let weightMap=g.nodes().map(v=>{let sucs={};g.outEdges(v).forEach(e=>{sucs[e.w]=(sucs[e.w]||0)+g.edge(e).weight});return sucs});return zipObject(g.nodes(),weightMap)}function predecessorWeights(g){let weightMap=g.nodes().map(v=>{let preds={};g.inEdges(v).forEach(e=>{preds[e.v]=(preds[e.v]||0)+g.edge(e).weight});return preds});return zipObject(g.nodes(),weightMap)}
|
| 469 |
+
/*
|
| 470 |
+
* Finds where a line starting at point ({x, y}) would intersect a rectangle
|
| 471 |
+
* ({x, y, width, height}) if it were pointing at the rectangle's center.
|
| 472 |
+
*/function intersectRect(rect,point){let x=rect.x;let y=rect.y;
|
| 473 |
+
// Rectangle intersection algorithm from:
|
| 474 |
+
// http://math.stackexchange.com/questions/108113/find-edge-between-two-boxes
|
| 475 |
+
let dx=point.x-x;let dy=point.y-y;let w=rect.width/2;let h=rect.height/2;if(!dx&&!dy){throw new Error("Not possible to find intersection inside of the rectangle")}let sx,sy;if(Math.abs(dy)*w>Math.abs(dx)*h){
|
| 476 |
+
// Intersection is top or bottom of rect.
|
| 477 |
+
if(dy<0){h=-h}sx=h*dx/dy;sy=h}else{
|
| 478 |
+
// Intersection is left or right of rect.
|
| 479 |
+
if(dx<0){w=-w}sx=w;sy=w*dy/dx}return{x:x+sx,y:y+sy}}
|
| 480 |
+
/*
|
| 481 |
+
* Given a DAG with each node assigned "rank" and "order" properties, this
|
| 482 |
+
* function will produce a matrix with the ids of each node.
|
| 483 |
+
*/function buildLayerMatrix(g){let layering=range(maxRank(g)+1).map(()=>[]);g.nodes().forEach(v=>{let node=g.node(v);let rank=node.rank;if(rank!==undefined){layering[rank][node.order]=v}});return layering}
|
| 484 |
+
/*
|
| 485 |
+
* Adjusts the ranks for all nodes in the graph such that all nodes v have
|
| 486 |
+
* rank(v) >= 0 and at least one node w has rank(w) = 0.
|
| 487 |
+
*/function normalizeRanks(g){let nodeRanks=g.nodes().map(v=>{let rank=g.node(v).rank;if(rank===undefined){return Number.MAX_VALUE}return rank});let min=applyWithChunking(Math.min,nodeRanks);g.nodes().forEach(v=>{let node=g.node(v);if(Object.hasOwn(node,"rank")){node.rank-=min}})}function removeEmptyRanks(g){
|
| 488 |
+
// Ranks may not start at 0, so we need to offset them
|
| 489 |
+
let nodeRanks=g.nodes().map(v=>g.node(v).rank);let offset=applyWithChunking(Math.min,nodeRanks);let layers=[];g.nodes().forEach(v=>{let rank=g.node(v).rank-offset;if(!layers[rank]){layers[rank]=[]}layers[rank].push(v)});let delta=0;let nodeRankFactor=g.graph().nodeRankFactor;Array.from(layers).forEach((vs,i)=>{if(vs===undefined&&i%nodeRankFactor!==0){--delta}else if(vs!==undefined&&delta){vs.forEach(v=>g.node(v).rank+=delta)}})}function addBorderNode(g,prefix,rank,order){let node={width:0,height:0};if(arguments.length>=4){node.rank=rank;node.order=order}return addDummyNode(g,"border",node,prefix)}function splitToChunks(array,chunkSize=CHUNKING_THRESHOLD){const chunks=[];for(let i=0;i<array.length;i+=chunkSize){const chunk=array.slice(i,i+chunkSize);chunks.push(chunk)}return chunks}const CHUNKING_THRESHOLD=65535;function applyWithChunking(fn,argsArray){if(argsArray.length>CHUNKING_THRESHOLD){const chunks=splitToChunks(argsArray);return fn.apply(null,chunks.map(chunk=>fn.apply(null,chunk)))}else{return fn.apply(null,argsArray)}}function maxRank(g){const nodes=g.nodes();const nodeRanks=nodes.map(v=>{let rank=g.node(v).rank;if(rank===undefined){return Number.MIN_VALUE}return rank});return applyWithChunking(Math.max,nodeRanks)}
|
| 490 |
+
/*
|
| 491 |
+
* Partition a collection into two groups: `lhs` and `rhs`. If the supplied
|
| 492 |
+
* function returns true for an entry it goes into `lhs`. Otherwise it goes
|
| 493 |
+
* into `rhs.
|
| 494 |
+
*/function partition(collection,fn){let result={lhs:[],rhs:[]};collection.forEach(value=>{if(fn(value)){result.lhs.push(value)}else{result.rhs.push(value)}});return result}
|
| 495 |
+
/*
|
| 496 |
+
* Returns a new function that wraps `fn` with a timer. The wrapper logs the
|
| 497 |
+
* time it takes to execute the function.
|
| 498 |
+
*/function time(name,fn){let start=Date.now();try{return fn()}finally{console.log(name+" time: "+(Date.now()-start)+"ms")}}function notime(name,fn){return fn()}let idCounter=0;function uniqueId(prefix){var id=++idCounter;return toString(prefix)+id}function range(start,limit,step=1){if(limit==null){limit=start;start=0}let endCon=i=>i<limit;if(step<0){endCon=i=>limit<i}const range=[];for(let i=start;endCon(i);i+=step){range.push(i)}return range}function pick(source,keys){const dest={};for(const key of keys){if(source[key]!==undefined){dest[key]=source[key]}}return dest}function mapValues(obj,funcOrProp){let func=funcOrProp;if(typeof funcOrProp==="string"){func=val=>val[funcOrProp]}return Object.entries(obj).reduce((acc,[k,v])=>{acc[k]=func(v,k);return acc},{})}function zipObject(props,values){return props.reduce((acc,key,i)=>{acc[key]=values[i];return acc},{})}},{"@dagrejs/graphlib":29}],28:[function(require,module,exports){module.exports="1.1.4"},{}],29:[function(require,module,exports){
|
| 499 |
+
/**
|
| 500 |
+
* Copyright (c) 2014, Chris Pettitt
|
| 501 |
+
* All rights reserved.
|
| 502 |
+
*
|
| 503 |
+
* Redistribution and use in source and binary forms, with or without
|
| 504 |
+
* modification, are permitted provided that the following conditions are met:
|
| 505 |
+
*
|
| 506 |
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
| 507 |
+
* list of conditions and the following disclaimer.
|
| 508 |
+
*
|
| 509 |
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 510 |
+
* this list of conditions and the following disclaimer in the documentation
|
| 511 |
+
* and/or other materials provided with the distribution.
|
| 512 |
+
*
|
| 513 |
+
* 3. Neither the name of the copyright holder nor the names of its contributors
|
| 514 |
+
* may be used to endorse or promote products derived from this software without
|
| 515 |
+
* specific prior written permission.
|
| 516 |
+
*
|
| 517 |
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
| 518 |
+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
| 519 |
+
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 520 |
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 521 |
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 522 |
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 523 |
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 524 |
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 525 |
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 526 |
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 527 |
+
*/
|
| 528 |
+
var lib=require("./lib");module.exports={Graph:lib.Graph,json:require("./lib/json"),alg:require("./lib/alg"),version:lib.version}},{"./lib":45,"./lib/alg":36,"./lib/json":46}],30:[function(require,module,exports){module.exports=components;function components(g){var visited={};var cmpts=[];var cmpt;function dfs(v){if(Object.hasOwn(visited,v))return;visited[v]=true;cmpt.push(v);g.successors(v).forEach(dfs);g.predecessors(v).forEach(dfs)}g.nodes().forEach(function(v){cmpt=[];dfs(v);if(cmpt.length){cmpts.push(cmpt)}});return cmpts}},{}],31:[function(require,module,exports){module.exports=dfs;
|
| 529 |
+
/*
|
| 530 |
+
* A helper that preforms a pre- or post-order traversal on the input graph
|
| 531 |
+
* and returns the nodes in the order they were visited. If the graph is
|
| 532 |
+
* undirected then this algorithm will navigate using neighbors. If the graph
|
| 533 |
+
* is directed then this algorithm will navigate using successors.
|
| 534 |
+
*
|
| 535 |
+
* If the order is not "post", it will be treated as "pre".
|
| 536 |
+
*/function dfs(g,vs,order){if(!Array.isArray(vs)){vs=[vs]}var navigation=g.isDirected()?v=>g.successors(v):v=>g.neighbors(v);var orderFunc=order==="post"?postOrderDfs:preOrderDfs;var acc=[];var visited={};vs.forEach(v=>{if(!g.hasNode(v)){throw new Error("Graph does not have node: "+v)}orderFunc(v,navigation,visited,acc)});return acc}function postOrderDfs(v,navigation,visited,acc){var stack=[[v,false]];while(stack.length>0){var curr=stack.pop();if(curr[1]){acc.push(curr[0])}else{if(!Object.hasOwn(visited,curr[0])){visited[curr[0]]=true;stack.push([curr[0],true]);forEachRight(navigation(curr[0]),w=>stack.push([w,false]))}}}}function preOrderDfs(v,navigation,visited,acc){var stack=[v];while(stack.length>0){var curr=stack.pop();if(!Object.hasOwn(visited,curr)){visited[curr]=true;acc.push(curr);forEachRight(navigation(curr),w=>stack.push(w))}}}function forEachRight(array,iteratee){var length=array.length;while(length--){iteratee(array[length],length,array)}return array}},{}],32:[function(require,module,exports){var dijkstra=require("./dijkstra");module.exports=dijkstraAll;function dijkstraAll(g,weightFunc,edgeFunc){return g.nodes().reduce(function(acc,v){acc[v]=dijkstra(g,v,weightFunc,edgeFunc);return acc},{})}},{"./dijkstra":33}],33:[function(require,module,exports){var PriorityQueue=require("../data/priority-queue");module.exports=dijkstra;var DEFAULT_WEIGHT_FUNC=()=>1;function dijkstra(g,source,weightFn,edgeFn){return runDijkstra(g,String(source),weightFn||DEFAULT_WEIGHT_FUNC,edgeFn||function(v){return g.outEdges(v)})}function runDijkstra(g,source,weightFn,edgeFn){var results={};var pq=new PriorityQueue;var v,vEntry;var updateNeighbors=function(edge){var w=edge.v!==v?edge.v:edge.w;var wEntry=results[w];var weight=weightFn(edge);var distance=vEntry.distance+weight;if(weight<0){throw new Error("dijkstra does not allow negative edge weights. "+"Bad edge: "+edge+" Weight: "+weight)}if(distance<wEntry.distance){wEntry.distance=distance;wEntry.predecessor=v;pq.decrease(w,distance)}};g.nodes().forEach(function(v){var distance=v===source?0:Number.POSITIVE_INFINITY;results[v]={distance:distance};pq.add(v,distance)});while(pq.size()>0){v=pq.removeMin();vEntry=results[v];if(vEntry.distance===Number.POSITIVE_INFINITY){break}edgeFn(v).forEach(updateNeighbors)}return results}},{"../data/priority-queue":43}],34:[function(require,module,exports){var tarjan=require("./tarjan");module.exports=findCycles;function findCycles(g){return tarjan(g).filter(function(cmpt){return cmpt.length>1||cmpt.length===1&&g.hasEdge(cmpt[0],cmpt[0])})}},{"./tarjan":41}],35:[function(require,module,exports){module.exports=floydWarshall;var DEFAULT_WEIGHT_FUNC=()=>1;function floydWarshall(g,weightFn,edgeFn){return runFloydWarshall(g,weightFn||DEFAULT_WEIGHT_FUNC,edgeFn||function(v){return g.outEdges(v)})}function runFloydWarshall(g,weightFn,edgeFn){var results={};var nodes=g.nodes();nodes.forEach(function(v){results[v]={};results[v][v]={distance:0};nodes.forEach(function(w){if(v!==w){results[v][w]={distance:Number.POSITIVE_INFINITY}}});edgeFn(v).forEach(function(edge){var w=edge.v===v?edge.w:edge.v;var d=weightFn(edge);results[v][w]={distance:d,predecessor:v}})});nodes.forEach(function(k){var rowK=results[k];nodes.forEach(function(i){var rowI=results[i];nodes.forEach(function(j){var ik=rowI[k];var kj=rowK[j];var ij=rowI[j];var altDistance=ik.distance+kj.distance;if(altDistance<ij.distance){ij.distance=altDistance;ij.predecessor=kj.predecessor}})})});return results}},{}],36:[function(require,module,exports){module.exports={components:require("./components"),dijkstra:require("./dijkstra"),dijkstraAll:require("./dijkstra-all"),findCycles:require("./find-cycles"),floydWarshall:require("./floyd-warshall"),isAcyclic:require("./is-acyclic"),postorder:require("./postorder"),preorder:require("./preorder"),prim:require("./prim"),tarjan:require("./tarjan"),topsort:require("./topsort")}},{"./components":30,"./dijkstra":33,"./dijkstra-all":32,"./find-cycles":34,"./floyd-warshall":35,"./is-acyclic":37,"./postorder":38,"./preorder":39,"./prim":40,"./tarjan":41,"./topsort":42}],37:[function(require,module,exports){var topsort=require("./topsort");module.exports=isAcyclic;function isAcyclic(g){try{topsort(g)}catch(e){if(e instanceof topsort.CycleException){return false}throw e}return true}},{"./topsort":42}],38:[function(require,module,exports){var dfs=require("./dfs");module.exports=postorder;function postorder(g,vs){return dfs(g,vs,"post")}},{"./dfs":31}],39:[function(require,module,exports){var dfs=require("./dfs");module.exports=preorder;function preorder(g,vs){return dfs(g,vs,"pre")}},{"./dfs":31}],40:[function(require,module,exports){var Graph=require("../graph");var PriorityQueue=require("../data/priority-queue");module.exports=prim;function prim(g,weightFunc){var result=new Graph;var parents={};var pq=new PriorityQueue;var v;function updateNeighbors(edge){var w=edge.v===v?edge.w:edge.v;var pri=pq.priority(w);if(pri!==undefined){var edgeWeight=weightFunc(edge);if(edgeWeight<pri){parents[w]=v;pq.decrease(w,edgeWeight)}}}if(g.nodeCount()===0){return result}g.nodes().forEach(function(v){pq.add(v,Number.POSITIVE_INFINITY);result.setNode(v)});
|
| 537 |
+
// Start from an arbitrary node
|
| 538 |
+
pq.decrease(g.nodes()[0],0);var init=false;while(pq.size()>0){v=pq.removeMin();if(Object.hasOwn(parents,v)){result.setEdge(v,parents[v])}else if(init){throw new Error("Input graph is not connected: "+g)}else{init=true}g.nodeEdges(v).forEach(updateNeighbors)}return result}},{"../data/priority-queue":43,"../graph":44}],41:[function(require,module,exports){module.exports=tarjan;function tarjan(g){var index=0;var stack=[];var visited={};// node id -> { onStack, lowlink, index }
|
| 539 |
+
var results=[];function dfs(v){var entry=visited[v]={onStack:true,lowlink:index,index:index++};stack.push(v);g.successors(v).forEach(function(w){if(!Object.hasOwn(visited,w)){dfs(w);entry.lowlink=Math.min(entry.lowlink,visited[w].lowlink)}else if(visited[w].onStack){entry.lowlink=Math.min(entry.lowlink,visited[w].index)}});if(entry.lowlink===entry.index){var cmpt=[];var w;do{w=stack.pop();visited[w].onStack=false;cmpt.push(w)}while(v!==w);results.push(cmpt)}}g.nodes().forEach(function(v){if(!Object.hasOwn(visited,v)){dfs(v)}});return results}},{}],42:[function(require,module,exports){function topsort(g){var visited={};var stack={};var results=[];function visit(node){if(Object.hasOwn(stack,node)){throw new CycleException}if(!Object.hasOwn(visited,node)){stack[node]=true;visited[node]=true;g.predecessors(node).forEach(visit);delete stack[node];results.push(node)}}g.sinks().forEach(visit);if(Object.keys(visited).length!==g.nodeCount()){throw new CycleException}return results}class CycleException extends Error{constructor(){super(...arguments)}}module.exports=topsort;topsort.CycleException=CycleException},{}],43:[function(require,module,exports){
|
| 540 |
+
/**
|
| 541 |
+
* A min-priority queue data structure. This algorithm is derived from Cormen,
|
| 542 |
+
* et al., "Introduction to Algorithms". The basic idea of a min-priority
|
| 543 |
+
* queue is that you can efficiently (in O(1) time) get the smallest key in
|
| 544 |
+
* the queue. Adding and removing elements takes O(log n) time. A key can
|
| 545 |
+
* have its priority decreased in O(log n) time.
|
| 546 |
+
*/
|
| 547 |
+
class PriorityQueue{_arr=[];_keyIndices={};
|
| 548 |
+
/**
|
| 549 |
+
* Returns the number of elements in the queue. Takes `O(1)` time.
|
| 550 |
+
*/size(){return this._arr.length}
|
| 551 |
+
/**
|
| 552 |
+
* Returns the keys that are in the queue. Takes `O(n)` time.
|
| 553 |
+
*/keys(){return this._arr.map(function(x){return x.key})}
|
| 554 |
+
/**
|
| 555 |
+
* Returns `true` if **key** is in the queue and `false` if not.
|
| 556 |
+
*/has(key){return Object.hasOwn(this._keyIndices,key)}
|
| 557 |
+
/**
|
| 558 |
+
* Returns the priority for **key**. If **key** is not present in the queue
|
| 559 |
+
* then this function returns `undefined`. Takes `O(1)` time.
|
| 560 |
+
*
|
| 561 |
+
* @param {Object} key
|
| 562 |
+
*/priority(key){var index=this._keyIndices[key];if(index!==undefined){return this._arr[index].priority}}
|
| 563 |
+
/**
|
| 564 |
+
* Returns the key for the minimum element in this queue. If the queue is
|
| 565 |
+
* empty this function throws an Error. Takes `O(1)` time.
|
| 566 |
+
*/min(){if(this.size()===0){throw new Error("Queue underflow")}return this._arr[0].key}
|
| 567 |
+
/**
|
| 568 |
+
* Inserts a new key into the priority queue. If the key already exists in
|
| 569 |
+
* the queue this function returns `false`; otherwise it will return `true`.
|
| 570 |
+
* Takes `O(n)` time.
|
| 571 |
+
*
|
| 572 |
+
* @param {Object} key the key to add
|
| 573 |
+
* @param {Number} priority the initial priority for the key
|
| 574 |
+
*/add(key,priority){var keyIndices=this._keyIndices;key=String(key);if(!Object.hasOwn(keyIndices,key)){var arr=this._arr;var index=arr.length;keyIndices[key]=index;arr.push({key:key,priority:priority});this._decrease(index);return true}return false}
|
| 575 |
+
/**
|
| 576 |
+
* Removes and returns the smallest key in the queue. Takes `O(log n)` time.
|
| 577 |
+
*/removeMin(){this._swap(0,this._arr.length-1);var min=this._arr.pop();delete this._keyIndices[min.key];this._heapify(0);return min.key}
|
| 578 |
+
/**
|
| 579 |
+
* Decreases the priority for **key** to **priority**. If the new priority is
|
| 580 |
+
* greater than the previous priority, this function will throw an Error.
|
| 581 |
+
*
|
| 582 |
+
* @param {Object} key the key for which to raise priority
|
| 583 |
+
* @param {Number} priority the new priority for the key
|
| 584 |
+
*/decrease(key,priority){var index=this._keyIndices[key];if(priority>this._arr[index].priority){throw new Error("New priority is greater than current priority. "+"Key: "+key+" Old: "+this._arr[index].priority+" New: "+priority)}this._arr[index].priority=priority;this._decrease(index)}_heapify(i){var arr=this._arr;var l=2*i;var r=l+1;var largest=i;if(l<arr.length){largest=arr[l].priority<arr[largest].priority?l:largest;if(r<arr.length){largest=arr[r].priority<arr[largest].priority?r:largest}if(largest!==i){this._swap(i,largest);this._heapify(largest)}}}_decrease(index){var arr=this._arr;var priority=arr[index].priority;var parent;while(index!==0){parent=index>>1;if(arr[parent].priority<priority){break}this._swap(index,parent);index=parent}}_swap(i,j){var arr=this._arr;var keyIndices=this._keyIndices;var origArrI=arr[i];var origArrJ=arr[j];arr[i]=origArrJ;arr[j]=origArrI;keyIndices[origArrJ.key]=i;keyIndices[origArrI.key]=j}}module.exports=PriorityQueue},{}],44:[function(require,module,exports){"use strict";var DEFAULT_EDGE_NAME="\0";var GRAPH_NODE="\0";var EDGE_KEY_DELIM="";
|
| 585 |
+
// Implementation notes:
|
| 586 |
+
//
|
| 587 |
+
// * Node id query functions should return string ids for the nodes
|
| 588 |
+
// * Edge id query functions should return an "edgeObj", edge object, that is
|
| 589 |
+
// composed of enough information to uniquely identify an edge: {v, w, name}.
|
| 590 |
+
// * Internally we use an "edgeId", a stringified form of the edgeObj, to
|
| 591 |
+
// reference edges. This is because we need a performant way to look these
|
| 592 |
+
// edges up and, object properties, which have string keys, are the closest
|
| 593 |
+
// we're going to get to a performant hashtable in JavaScript.
|
| 594 |
+
class Graph{_isDirected=true;_isMultigraph=false;_isCompound=false;
|
| 595 |
+
// Label for the graph itself
|
| 596 |
+
_label;
|
| 597 |
+
// Defaults to be set when creating a new node
|
| 598 |
+
_defaultNodeLabelFn=()=>undefined;
|
| 599 |
+
// Defaults to be set when creating a new edge
|
| 600 |
+
_defaultEdgeLabelFn=()=>undefined;
|
| 601 |
+
// v -> label
|
| 602 |
+
_nodes={};
|
| 603 |
+
// v -> edgeObj
|
| 604 |
+
_in={};
|
| 605 |
+
// u -> v -> Number
|
| 606 |
+
_preds={};
|
| 607 |
+
// v -> edgeObj
|
| 608 |
+
_out={};
|
| 609 |
+
// v -> w -> Number
|
| 610 |
+
_sucs={};
|
| 611 |
+
// e -> edgeObj
|
| 612 |
+
_edgeObjs={};
|
| 613 |
+
// e -> label
|
| 614 |
+
_edgeLabels={};
|
| 615 |
+
/* Number of nodes in the graph. Should only be changed by the implementation. */_nodeCount=0;
|
| 616 |
+
/* Number of edges in the graph. Should only be changed by the implementation. */_edgeCount=0;_parent;_children;constructor(opts){if(opts){this._isDirected=Object.hasOwn(opts,"directed")?opts.directed:true;this._isMultigraph=Object.hasOwn(opts,"multigraph")?opts.multigraph:false;this._isCompound=Object.hasOwn(opts,"compound")?opts.compound:false}if(this._isCompound){
|
| 617 |
+
// v -> parent
|
| 618 |
+
this._parent={};
|
| 619 |
+
// v -> children
|
| 620 |
+
this._children={};this._children[GRAPH_NODE]={}}}
|
| 621 |
+
/* === Graph functions ========= */
|
| 622 |
+
/**
|
| 623 |
+
* Whether graph was created with 'directed' flag set to true or not.
|
| 624 |
+
*/isDirected(){return this._isDirected}
|
| 625 |
+
/**
|
| 626 |
+
* Whether graph was created with 'multigraph' flag set to true or not.
|
| 627 |
+
*/isMultigraph(){return this._isMultigraph}
|
| 628 |
+
/**
|
| 629 |
+
* Whether graph was created with 'compound' flag set to true or not.
|
| 630 |
+
*/isCompound(){return this._isCompound}
|
| 631 |
+
/**
|
| 632 |
+
* Sets the label of the graph.
|
| 633 |
+
*/setGraph(label){this._label=label;return this}
|
| 634 |
+
/**
|
| 635 |
+
* Gets the graph label.
|
| 636 |
+
*/graph(){return this._label}
|
| 637 |
+
/* === Node functions ========== */
|
| 638 |
+
/**
|
| 639 |
+
* Sets the default node label. If newDefault is a function, it will be
|
| 640 |
+
* invoked ach time when setting a label for a node. Otherwise, this label
|
| 641 |
+
* will be assigned as default label in case if no label was specified while
|
| 642 |
+
* setting a node.
|
| 643 |
+
* Complexity: O(1).
|
| 644 |
+
*/setDefaultNodeLabel(newDefault){this._defaultNodeLabelFn=newDefault;if(typeof newDefault!=="function"){this._defaultNodeLabelFn=()=>newDefault}return this}
|
| 645 |
+
/**
|
| 646 |
+
* Gets the number of nodes in the graph.
|
| 647 |
+
* Complexity: O(1).
|
| 648 |
+
*/nodeCount(){return this._nodeCount}
|
| 649 |
+
/**
|
| 650 |
+
* Gets all nodes of the graph. Note, the in case of compound graph subnodes are
|
| 651 |
+
* not included in list.
|
| 652 |
+
* Complexity: O(1).
|
| 653 |
+
*/nodes(){return Object.keys(this._nodes)}
|
| 654 |
+
/**
|
| 655 |
+
* Gets list of nodes without in-edges.
|
| 656 |
+
* Complexity: O(|V|).
|
| 657 |
+
*/sources(){var self=this;return this.nodes().filter(v=>Object.keys(self._in[v]).length===0)}
|
| 658 |
+
/**
|
| 659 |
+
* Gets list of nodes without out-edges.
|
| 660 |
+
* Complexity: O(|V|).
|
| 661 |
+
*/sinks(){var self=this;return this.nodes().filter(v=>Object.keys(self._out[v]).length===0)}
|
| 662 |
+
/**
|
| 663 |
+
* Invokes setNode method for each node in names list.
|
| 664 |
+
* Complexity: O(|names|).
|
| 665 |
+
*/setNodes(vs,value){var args=arguments;var self=this;vs.forEach(function(v){if(args.length>1){self.setNode(v,value)}else{self.setNode(v)}});return this}
|
| 666 |
+
/**
|
| 667 |
+
* Creates or updates the value for the node v in the graph. If label is supplied
|
| 668 |
+
* it is set as the value for the node. If label is not supplied and the node was
|
| 669 |
+
* created by this call then the default node label will be assigned.
|
| 670 |
+
* Complexity: O(1).
|
| 671 |
+
*/setNode(v,value){if(Object.hasOwn(this._nodes,v)){if(arguments.length>1){this._nodes[v]=value}return this}this._nodes[v]=arguments.length>1?value:this._defaultNodeLabelFn(v);if(this._isCompound){this._parent[v]=GRAPH_NODE;this._children[v]={};this._children[GRAPH_NODE][v]=true}this._in[v]={};this._preds[v]={};this._out[v]={};this._sucs[v]={};++this._nodeCount;return this}
|
| 672 |
+
/**
|
| 673 |
+
* Gets the label of node with specified name.
|
| 674 |
+
* Complexity: O(|V|).
|
| 675 |
+
*/node(v){return this._nodes[v]}
|
| 676 |
+
/**
|
| 677 |
+
* Detects whether graph has a node with specified name or not.
|
| 678 |
+
*/hasNode(v){return Object.hasOwn(this._nodes,v)}
|
| 679 |
+
/**
|
| 680 |
+
* Remove the node with the name from the graph or do nothing if the node is not in
|
| 681 |
+
* the graph. If the node was removed this function also removes any incident
|
| 682 |
+
* edges.
|
| 683 |
+
* Complexity: O(1).
|
| 684 |
+
*/removeNode(v){var self=this;if(Object.hasOwn(this._nodes,v)){var removeEdge=e=>self.removeEdge(self._edgeObjs[e]);delete this._nodes[v];if(this._isCompound){this._removeFromParentsChildList(v);delete this._parent[v];this.children(v).forEach(function(child){self.setParent(child)});delete this._children[v]}Object.keys(this._in[v]).forEach(removeEdge);delete this._in[v];delete this._preds[v];Object.keys(this._out[v]).forEach(removeEdge);delete this._out[v];delete this._sucs[v];--this._nodeCount}return this}
|
| 685 |
+
/**
|
| 686 |
+
* Sets node p as a parent for node v if it is defined, or removes the
|
| 687 |
+
* parent for v if p is undefined. Method throws an exception in case of
|
| 688 |
+
* invoking it in context of noncompound graph.
|
| 689 |
+
* Average-case complexity: O(1).
|
| 690 |
+
*/setParent(v,parent){if(!this._isCompound){throw new Error("Cannot set parent in a non-compound graph")}if(parent===undefined){parent=GRAPH_NODE}else{
|
| 691 |
+
// Coerce parent to string
|
| 692 |
+
parent+="";for(var ancestor=parent;ancestor!==undefined;ancestor=this.parent(ancestor)){if(ancestor===v){throw new Error("Setting "+parent+" as parent of "+v+" would create a cycle")}}this.setNode(parent)}this.setNode(v);this._removeFromParentsChildList(v);this._parent[v]=parent;this._children[parent][v]=true;return this}_removeFromParentsChildList(v){delete this._children[this._parent[v]][v]}
|
| 693 |
+
/**
|
| 694 |
+
* Gets parent node for node v.
|
| 695 |
+
* Complexity: O(1).
|
| 696 |
+
*/parent(v){if(this._isCompound){var parent=this._parent[v];if(parent!==GRAPH_NODE){return parent}}}
|
| 697 |
+
/**
|
| 698 |
+
* Gets list of direct children of node v.
|
| 699 |
+
* Complexity: O(1).
|
| 700 |
+
*/children(v=GRAPH_NODE){if(this._isCompound){var children=this._children[v];if(children){return Object.keys(children)}}else if(v===GRAPH_NODE){return this.nodes()}else if(this.hasNode(v)){return[]}}
|
| 701 |
+
/**
|
| 702 |
+
* Return all nodes that are predecessors of the specified node or undefined if node v is not in
|
| 703 |
+
* the graph. Behavior is undefined for undirected graphs - use neighbors instead.
|
| 704 |
+
* Complexity: O(|V|).
|
| 705 |
+
*/predecessors(v){var predsV=this._preds[v];if(predsV){return Object.keys(predsV)}}
|
| 706 |
+
/**
|
| 707 |
+
* Return all nodes that are successors of the specified node or undefined if node v is not in
|
| 708 |
+
* the graph. Behavior is undefined for undirected graphs - use neighbors instead.
|
| 709 |
+
* Complexity: O(|V|).
|
| 710 |
+
*/successors(v){var sucsV=this._sucs[v];if(sucsV){return Object.keys(sucsV)}}
|
| 711 |
+
/**
|
| 712 |
+
* Return all nodes that are predecessors or successors of the specified node or undefined if
|
| 713 |
+
* node v is not in the graph.
|
| 714 |
+
* Complexity: O(|V|).
|
| 715 |
+
*/neighbors(v){var preds=this.predecessors(v);if(preds){const union=new Set(preds);for(var succ of this.successors(v)){union.add(succ)}return Array.from(union.values())}}isLeaf(v){var neighbors;if(this.isDirected()){neighbors=this.successors(v)}else{neighbors=this.neighbors(v)}return neighbors.length===0}
|
| 716 |
+
/**
|
| 717 |
+
* Creates new graph with nodes filtered via filter. Edges incident to rejected node
|
| 718 |
+
* are also removed. In case of compound graph, if parent is rejected by filter,
|
| 719 |
+
* than all its children are rejected too.
|
| 720 |
+
* Average-case complexity: O(|E|+|V|).
|
| 721 |
+
*/filterNodes(filter){var copy=new this.constructor({directed:this._isDirected,multigraph:this._isMultigraph,compound:this._isCompound});copy.setGraph(this.graph());var self=this;Object.entries(this._nodes).forEach(function([v,value]){if(filter(v)){copy.setNode(v,value)}});Object.values(this._edgeObjs).forEach(function(e){if(copy.hasNode(e.v)&©.hasNode(e.w)){copy.setEdge(e,self.edge(e))}});var parents={};function findParent(v){var parent=self.parent(v);if(parent===undefined||copy.hasNode(parent)){parents[v]=parent;return parent}else if(parent in parents){return parents[parent]}else{return findParent(parent)}}if(this._isCompound){copy.nodes().forEach(v=>copy.setParent(v,findParent(v)))}return copy}
|
| 722 |
+
/* === Edge functions ========== */
|
| 723 |
+
/**
|
| 724 |
+
* Sets the default edge label or factory function. This label will be
|
| 725 |
+
* assigned as default label in case if no label was specified while setting
|
| 726 |
+
* an edge or this function will be invoked each time when setting an edge
|
| 727 |
+
* with no label specified and returned value * will be used as a label for edge.
|
| 728 |
+
* Complexity: O(1).
|
| 729 |
+
*/setDefaultEdgeLabel(newDefault){this._defaultEdgeLabelFn=newDefault;if(typeof newDefault!=="function"){this._defaultEdgeLabelFn=()=>newDefault}return this}
|
| 730 |
+
/**
|
| 731 |
+
* Gets the number of edges in the graph.
|
| 732 |
+
* Complexity: O(1).
|
| 733 |
+
*/edgeCount(){return this._edgeCount}
|
| 734 |
+
/**
|
| 735 |
+
* Gets edges of the graph. In case of compound graph subgraphs are not considered.
|
| 736 |
+
* Complexity: O(|E|).
|
| 737 |
+
*/edges(){return Object.values(this._edgeObjs)}
|
| 738 |
+
/**
|
| 739 |
+
* Establish an edges path over the nodes in nodes list. If some edge is already
|
| 740 |
+
* exists, it will update its label, otherwise it will create an edge between pair
|
| 741 |
+
* of nodes with label provided or default label if no label provided.
|
| 742 |
+
* Complexity: O(|nodes|).
|
| 743 |
+
*/setPath(vs,value){var self=this;var args=arguments;vs.reduce(function(v,w){if(args.length>1){self.setEdge(v,w,value)}else{self.setEdge(v,w)}return w});return this}
|
| 744 |
+
/**
|
| 745 |
+
* Creates or updates the label for the edge (v, w) with the optionally supplied
|
| 746 |
+
* name. If label is supplied it is set as the value for the edge. If label is not
|
| 747 |
+
* supplied and the edge was created by this call then the default edge label will
|
| 748 |
+
* be assigned. The name parameter is only useful with multigraphs.
|
| 749 |
+
*/setEdge(){var v,w,name,value;var valueSpecified=false;var arg0=arguments[0];if(typeof arg0==="object"&&arg0!==null&&"v"in arg0){v=arg0.v;w=arg0.w;name=arg0.name;if(arguments.length===2){value=arguments[1];valueSpecified=true}}else{v=arg0;w=arguments[1];name=arguments[3];if(arguments.length>2){value=arguments[2];valueSpecified=true}}v=""+v;w=""+w;if(name!==undefined){name=""+name}var e=edgeArgsToId(this._isDirected,v,w,name);if(Object.hasOwn(this._edgeLabels,e)){if(valueSpecified){this._edgeLabels[e]=value}return this}if(name!==undefined&&!this._isMultigraph){throw new Error("Cannot set a named edge when isMultigraph = false")}
|
| 750 |
+
// It didn't exist, so we need to create it.
|
| 751 |
+
// First ensure the nodes exist.
|
| 752 |
+
this.setNode(v);this.setNode(w);this._edgeLabels[e]=valueSpecified?value:this._defaultEdgeLabelFn(v,w,name);var edgeObj=edgeArgsToObj(this._isDirected,v,w,name);
|
| 753 |
+
// Ensure we add undirected edges in a consistent way.
|
| 754 |
+
v=edgeObj.v;w=edgeObj.w;Object.freeze(edgeObj);this._edgeObjs[e]=edgeObj;incrementOrInitEntry(this._preds[w],v);incrementOrInitEntry(this._sucs[v],w);this._in[w][e]=edgeObj;this._out[v][e]=edgeObj;this._edgeCount++;return this}
|
| 755 |
+
/**
|
| 756 |
+
* Gets the label for the specified edge.
|
| 757 |
+
* Complexity: O(1).
|
| 758 |
+
*/edge(v,w,name){var e=arguments.length===1?edgeObjToId(this._isDirected,arguments[0]):edgeArgsToId(this._isDirected,v,w,name);return this._edgeLabels[e]}
|
| 759 |
+
/**
|
| 760 |
+
* Gets the label for the specified edge and converts it to an object.
|
| 761 |
+
* Complexity: O(1)
|
| 762 |
+
*/edgeAsObj(){const edge=this.edge(...arguments);if(typeof edge!=="object"){return{label:edge}}return edge}
|
| 763 |
+
/**
|
| 764 |
+
* Detects whether the graph contains specified edge or not. No subgraphs are considered.
|
| 765 |
+
* Complexity: O(1).
|
| 766 |
+
*/hasEdge(v,w,name){var e=arguments.length===1?edgeObjToId(this._isDirected,arguments[0]):edgeArgsToId(this._isDirected,v,w,name);return Object.hasOwn(this._edgeLabels,e)}
|
| 767 |
+
/**
|
| 768 |
+
* Removes the specified edge from the graph. No subgraphs are considered.
|
| 769 |
+
* Complexity: O(1).
|
| 770 |
+
*/removeEdge(v,w,name){var e=arguments.length===1?edgeObjToId(this._isDirected,arguments[0]):edgeArgsToId(this._isDirected,v,w,name);var edge=this._edgeObjs[e];if(edge){v=edge.v;w=edge.w;delete this._edgeLabels[e];delete this._edgeObjs[e];decrementOrRemoveEntry(this._preds[w],v);decrementOrRemoveEntry(this._sucs[v],w);delete this._in[w][e];delete this._out[v][e];this._edgeCount--}return this}
|
| 771 |
+
/**
|
| 772 |
+
* Return all edges that point to the node v. Optionally filters those edges down to just those
|
| 773 |
+
* coming from node u. Behavior is undefined for undirected graphs - use nodeEdges instead.
|
| 774 |
+
* Complexity: O(|E|).
|
| 775 |
+
*/inEdges(v,u){var inV=this._in[v];if(inV){var edges=Object.values(inV);if(!u){return edges}return edges.filter(edge=>edge.v===u)}}
|
| 776 |
+
/**
|
| 777 |
+
* Return all edges that are pointed at by node v. Optionally filters those edges down to just
|
| 778 |
+
* those point to w. Behavior is undefined for undirected graphs - use nodeEdges instead.
|
| 779 |
+
* Complexity: O(|E|).
|
| 780 |
+
*/outEdges(v,w){var outV=this._out[v];if(outV){var edges=Object.values(outV);if(!w){return edges}return edges.filter(edge=>edge.w===w)}}
|
| 781 |
+
/**
|
| 782 |
+
* Returns all edges to or from node v regardless of direction. Optionally filters those edges
|
| 783 |
+
* down to just those between nodes v and w regardless of direction.
|
| 784 |
+
* Complexity: O(|E|).
|
| 785 |
+
*/nodeEdges(v,w){var inEdges=this.inEdges(v,w);if(inEdges){return inEdges.concat(this.outEdges(v,w))}}}function incrementOrInitEntry(map,k){if(map[k]){map[k]++}else{map[k]=1}}function decrementOrRemoveEntry(map,k){if(!--map[k]){delete map[k]}}function edgeArgsToId(isDirected,v_,w_,name){var v=""+v_;var w=""+w_;if(!isDirected&&v>w){var tmp=v;v=w;w=tmp}return v+EDGE_KEY_DELIM+w+EDGE_KEY_DELIM+(name===undefined?DEFAULT_EDGE_NAME:name)}function edgeArgsToObj(isDirected,v_,w_,name){var v=""+v_;var w=""+w_;if(!isDirected&&v>w){var tmp=v;v=w;w=tmp}var edgeObj={v:v,w:w};if(name){edgeObj.name=name}return edgeObj}function edgeObjToId(isDirected,edgeObj){return edgeArgsToId(isDirected,edgeObj.v,edgeObj.w,edgeObj.name)}module.exports=Graph},{}],45:[function(require,module,exports){
|
| 786 |
+
// Includes only the "core" of graphlib
|
| 787 |
+
module.exports={Graph:require("./graph"),version:require("./version")}},{"./graph":44,"./version":47}],46:[function(require,module,exports){var Graph=require("./graph");module.exports={write:write,read:read};
|
| 788 |
+
/**
|
| 789 |
+
* Creates a JSON representation of the graph that can be serialized to a string with
|
| 790 |
+
* JSON.stringify. The graph can later be restored using json.read.
|
| 791 |
+
*/function write(g){var json={options:{directed:g.isDirected(),multigraph:g.isMultigraph(),compound:g.isCompound()},nodes:writeNodes(g),edges:writeEdges(g)};if(g.graph()!==undefined){json.value=structuredClone(g.graph())}return json}function writeNodes(g){return g.nodes().map(function(v){var nodeValue=g.node(v);var parent=g.parent(v);var node={v:v};if(nodeValue!==undefined){node.value=nodeValue}if(parent!==undefined){node.parent=parent}return node})}function writeEdges(g){return g.edges().map(function(e){var edgeValue=g.edge(e);var edge={v:e.v,w:e.w};if(e.name!==undefined){edge.name=e.name}if(edgeValue!==undefined){edge.value=edgeValue}return edge})}
|
| 792 |
+
/**
|
| 793 |
+
* Takes JSON as input and returns the graph representation.
|
| 794 |
+
*
|
| 795 |
+
* @example
|
| 796 |
+
* var g2 = graphlib.json.read(JSON.parse(str));
|
| 797 |
+
* g2.nodes();
|
| 798 |
+
* // ['a', 'b']
|
| 799 |
+
* g2.edges()
|
| 800 |
+
* // [ { v: 'a', w: 'b' } ]
|
| 801 |
+
*/function read(json){var g=new Graph(json.options).setGraph(json.value);json.nodes.forEach(function(entry){g.setNode(entry.v,entry.value);if(entry.parent){g.setParent(entry.v,entry.parent)}});json.edges.forEach(function(entry){g.setEdge({v:entry.v,w:entry.w,name:entry.name},entry.value)});return g}},{"./graph":44}],47:[function(require,module,exports){module.exports="2.2.4"},{}]},{},[1])(1)});
|
assets/fonts/LICENSE.txt
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
| 2 |
+
Upstream-Name: Noto
|
| 3 |
+
Upstream-Contact: https://github.com/googlei18n/noto-fonts/issues
|
| 4 |
+
http://groups.google.com/group/noto-font
|
| 5 |
+
Source: https://github.com/googlei18n/noto-fonts
|
| 6 |
+
|
| 7 |
+
Files: *
|
| 8 |
+
Copyright:
|
| 9 |
+
2010,2012-2020, Google Inc.
|
| 10 |
+
2015-2020, Google LLC.
|
| 11 |
+
License-Grant:
|
| 12 |
+
This Font Software is licensed under the SIL Open Font License,
|
| 13 |
+
Version 1.1.
|
| 14 |
+
License: OFL-1.1
|
| 15 |
+
|
| 16 |
+
Files:
|
| 17 |
+
debian/*
|
| 18 |
+
Copyright:
|
| 19 |
+
2013-2020, Jonas Smedegaard <dr@jones.dk>
|
| 20 |
+
2020, Purism, SPC
|
| 21 |
+
2013, Vasudev Kamath <kamathvasudev@gmail.com>
|
| 22 |
+
License-Grant:
|
| 23 |
+
This program is free software;
|
| 24 |
+
you can redistribute it and/or modify it
|
| 25 |
+
under the terms of the GNU General Public License
|
| 26 |
+
as published by the Free Software Foundation;
|
| 27 |
+
either version 3, or (at your option) any later version.
|
| 28 |
+
License: GPL-3+
|
| 29 |
+
|
| 30 |
+
Files:
|
| 31 |
+
debian/NotoMono/NotoMono-Regular.ttf
|
| 32 |
+
Copyright:
|
| 33 |
+
2007, Google Inc.
|
| 34 |
+
License-Grant:
|
| 35 |
+
This Font Software is licensed under the SIL Open Font License,
|
| 36 |
+
Version 1.1.
|
| 37 |
+
License: OFL-1.1
|
| 38 |
+
|
| 39 |
+
License: OFL-1.1
|
| 40 |
+
PREAMBLE
|
| 41 |
+
.
|
| 42 |
+
The goals of the Open Font License (OFL) are to stimulate
|
| 43 |
+
worldwide development of collaborative font projects,
|
| 44 |
+
to support the font creation efforts
|
| 45 |
+
of academic and linguistic communities,
|
| 46 |
+
and to provide a free and open framework
|
| 47 |
+
in which fonts may be shared and improved
|
| 48 |
+
in partnership with others.
|
| 49 |
+
.
|
| 50 |
+
The OFL allows the licensed fonts to be used, studied,
|
| 51 |
+
modified and redistributed freely
|
| 52 |
+
as long as they are not sold by themselves.
|
| 53 |
+
The fonts, including any derivative works,
|
| 54 |
+
can be bundled, embedded,
|
| 55 |
+
redistributed and/or sold with any software
|
| 56 |
+
provided that any reserved names are not used
|
| 57 |
+
by derivative works.
|
| 58 |
+
The fonts and derivatives, however,
|
| 59 |
+
cannot be released under any other type of license.
|
| 60 |
+
The requirement for fonts to remain under this license
|
| 61 |
+
does not apply to any document
|
| 62 |
+
created using the fonts or their derivatives.
|
| 63 |
+
.
|
| 64 |
+
DEFINITIONS
|
| 65 |
+
.
|
| 66 |
+
"Font Software" refers to the set of files
|
| 67 |
+
released by the Copyright Holder(s) under this license
|
| 68 |
+
and clearly marked as such.
|
| 69 |
+
This may include source files, build scripts and documentation.
|
| 70 |
+
.
|
| 71 |
+
"Reserved Font Name" refers to any names specified as such
|
| 72 |
+
after the copyright statement(s).
|
| 73 |
+
.
|
| 74 |
+
"Original Version" refers to the collection
|
| 75 |
+
of Font Software components
|
| 76 |
+
as distributed by the Copyright Holder(s).
|
| 77 |
+
.
|
| 78 |
+
"Modified Version" refers to any derivative
|
| 79 |
+
made by adding to, deleting, or substituting --
|
| 80 |
+
in part or in whole --
|
| 81 |
+
any of the components of the Original Version,
|
| 82 |
+
by changing formats
|
| 83 |
+
or by porting the Font Software to a new environment.
|
| 84 |
+
.
|
| 85 |
+
"Author" refers to any designer, engineer, programmer,
|
| 86 |
+
technical writer or other person
|
| 87 |
+
who contributed to the Font Software.
|
| 88 |
+
.
|
| 89 |
+
PERMISSION & CONDITIONS
|
| 90 |
+
.
|
| 91 |
+
Permission is hereby granted, free of charge,
|
| 92 |
+
to any person obtaining a copy of the Font Software,
|
| 93 |
+
to use, study, copy, merge, embed, modify, redistribute,
|
| 94 |
+
and sell modified and unmodified copies of the Font Software,
|
| 95 |
+
subject to the following conditions:
|
| 96 |
+
.
|
| 97 |
+
1) Neither the Font Software nor any of its individual components,
|
| 98 |
+
in Original or Modified Versions,
|
| 99 |
+
may be sold by itself.
|
| 100 |
+
.
|
| 101 |
+
2) Original or Modified Versions of the Font Software
|
| 102 |
+
may be bundled, redistributed and/or sold with any software,
|
| 103 |
+
provided that each copy contains the above copyright notice
|
| 104 |
+
and this license.
|
| 105 |
+
These can be included either as stand-alone text files,
|
| 106 |
+
human-readable headers or in the appropriate
|
| 107 |
+
machine-readable metadata fields within text or binary files
|
| 108 |
+
as long as those fields can be easily viewed by the user.
|
| 109 |
+
.
|
| 110 |
+
3) No Modified Version of the Font Software may use
|
| 111 |
+
the Reserved Font Name(s)
|
| 112 |
+
unless explicit written permission is granted
|
| 113 |
+
by the corresponding Copyright Holder.
|
| 114 |
+
This restriction only applies to the primary font name
|
| 115 |
+
as presented to the users.
|
| 116 |
+
.
|
| 117 |
+
4) The name(s) of the Copyright Holder(s)
|
| 118 |
+
or the Author(s) of the Font Software
|
| 119 |
+
shall not be used to promote,
|
| 120 |
+
endorse or advertise any Modified Version,
|
| 121 |
+
except to acknowledge the contribution(s)
|
| 122 |
+
of the Copyright Holder(s) and the Author(s)
|
| 123 |
+
or with their explicit written permission.
|
| 124 |
+
.
|
| 125 |
+
5) The Font Software,
|
| 126 |
+
modified or unmodified, in part or in whole,
|
| 127 |
+
must be distributed entirely under this license,
|
| 128 |
+
and must not be distributed under any other license.
|
| 129 |
+
The requirement for fonts to remain under this license
|
| 130 |
+
does not apply to any document
|
| 131 |
+
created using the Font Software.
|
| 132 |
+
.
|
| 133 |
+
TERMINATION
|
| 134 |
+
.
|
| 135 |
+
This license becomes null and void
|
| 136 |
+
if any of the above conditions are not met.
|
| 137 |
+
.
|
| 138 |
+
DISCLAIMER
|
| 139 |
+
.
|
| 140 |
+
THE FONT SOFTWARE IS PROVIDED "AS IS",
|
| 141 |
+
WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
| 142 |
+
INCLUDING BUT NOT LIMITED TO
|
| 143 |
+
ANY WARRANTIES OF MERCHANTABILITY,
|
| 144 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
| 145 |
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT.
|
| 146 |
+
IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE
|
| 147 |
+
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
| 148 |
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT,
|
| 149 |
+
INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
|
| 150 |
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
| 151 |
+
ARISING FROM, OUT OF THE USE OR INABILITY TO USE
|
| 152 |
+
THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
| 153 |
+
|
| 154 |
+
License: GPL-3+
|
| 155 |
+
License-Reference: /usr/share/common-licenses/GPL-3
|
assets/fonts/NotoSans-Bold.ttf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e83493c945848ecd4a9ad0f6d19164541a0d3e23a9c952304a00a46e00272ac5
|
| 3 |
+
size 515752
|
assets/fonts/NotoSans-Regular.ttf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:89c3c497f618fdaa0b2d1e98fef93582f28c71debd2c4a8cdf41f190ced2909d
|
| 3 |
+
size 512672
|
assets/fonts/NotoSerifDisplay-Regular.ttf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:bcdc596729c24044e28c54815ecbe6004f83b10169a49a51160b22f59116249f
|
| 3 |
+
size 559120
|
assets/icons/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
ISC License
|
| 2 |
+
|
| 3 |
+
Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2022.
|
| 4 |
+
|
| 5 |
+
Permission to use, copy, modify, and/or distribute this software for any
|
| 6 |
+
purpose with or without fee is hereby granted, provided that the above
|
| 7 |
+
copyright notice and this permission notice appear in all copies.
|
| 8 |
+
|
| 9 |
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
| 10 |
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
| 11 |
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
| 12 |
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
| 13 |
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
| 14 |
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
| 15 |
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
assets/icons/arrow-left.svg
ADDED
|
|
assets/icons/arrow-up-right.svg
ADDED
|
|
assets/icons/chevron-right.svg
ADDED
|
|
assets/icons/download.svg
ADDED
|
|
assets/icons/maximize.svg
ADDED
|
|
assets/icons/minus.svg
ADDED
|
|
assets/icons/plus.svg
ADDED
|
|
assets/icons/x.svg
ADDED
|
|
graph.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* SVG rendering for authored DAGs. Dagre handles branching and shared nodes. */
|
| 2 |
+
window.AtlasGraph = (() => {
|
| 3 |
+
let serial = 0;
|
| 4 |
+
const NS = 'http://www.w3.org/2000/svg';
|
| 5 |
+
const colors = {ar:['#315fc1','#e9effc'],flow:['#a63975','#fae9f3'],encoder:['#007e70','#e3f4ef'],codec:['#956609','#faf0d6'],head:['#557b31','#edf4e4'],dsp:['#58777d','#eef3f4'],input:['#75888b','#fff'],output:['#75888b','#fff'],model:['#263e43','#f3f7f5']};
|
| 6 |
+
const roles = {ar:'AR BACKBONE',flow:'GENERATION',encoder:'ENCODER',codec:'CODEC / VOCODER',head:'PREDICTION',dsp:'FRONTEND / DSP',input:'INPUT',output:'OUTPUT',model:'MODEL'};
|
| 7 |
+
function element(name,attrs={},text){const n=document.createElementNS(NS,name);for(const [k,v] of Object.entries(attrs))n.setAttribute(k,v);if(text!==undefined)n.textContent=text;return n;}
|
| 8 |
+
const measure=document.createElement('canvas').getContext('2d');
|
| 9 |
+
function wrap(text,width,font){measure.font=font;const out=[];let line='';for(const word of text.split(/\s+/)){const next=line?line+' '+word:word;if(line&&measure.measureText(next).width>width){out.push(line);line=word;}else line=next;}if(line)out.push(line);return out;}
|
| 10 |
+
function render(container,nodes,edges,{onNode,active,compact=false,residuals=[]}={}){
|
| 11 |
+
const graph=new dagre.graphlib.Graph({multigraph:true});
|
| 12 |
+
graph.setGraph({rankdir:'TB',ranksep:compact?28:66,nodesep:compact?25:34,marginx:28,marginy:25});graph.setDefaultEdgeLabel(()=>({}));
|
| 13 |
+
for(const node of nodes){const io=['input','output'].includes(node.role);const width=compact?205:230;
|
| 14 |
+
const lines=wrap(node.label,width-28,`700 ${compact?12:14}px Noto`);
|
| 15 |
+
graph.setNode(node.id,{...node,width,height:(io?27:40)+lines.length*(compact?18:20),lines});
|
| 16 |
+
}
|
| 17 |
+
edges.forEach((edge,i)=>graph.setEdge(edge.from,edge.to,{...edge,width:edge.label?135:0,height:edge.label?16:0},String(i)));
|
| 18 |
+
dagre.layout(graph);
|
| 19 |
+
const {width,height}=graph.graph(),id='arrow-'+serial++;
|
| 20 |
+
const svg=element('svg',{xmlns:NS,viewBox:`0 0 ${width} ${height}`,width,height,role:'group','aria-label':'Architecture diagram'});
|
| 21 |
+
const defs=element('defs'),marker=element('marker',{id,markerWidth:7,markerHeight:7,refX:6,refY:3.5,orient:'auto'});
|
| 22 |
+
marker.append(element('path',{d:'M0 0 L7 3.5 L0 7',fill:'none',stroke:'#90a6a9'}));defs.append(marker);svg.append(defs);
|
| 23 |
+
for(const e of graph.edges()){const data=graph.edge(e),points=data.points;
|
| 24 |
+
const path=element('path',{d:points.map((p,i)=>(i?'L':'M')+p.x+' '+p.y).join(' '),fill:'none',stroke:data.dim?'#c3cecf':'#92aaa9','stroke-width':data.highlight?2:1.3,'stroke-dasharray':data.optional?'5 4':'none','marker-end':`url(#${id})`,opacity:data.dim?.24:1});
|
| 25 |
+
path.append(element('title',{},data.label||'Data flow'));svg.append(path);
|
| 26 |
+
if(data.label){const label=element('text',{x:data.x,y:data.y+4,'text-anchor':'middle',fill:'#637b7e','font-size':10,'paint-order':'stroke',stroke:'white','stroke-width':4,'stroke-linejoin':'round'},data.label);svg.append(label);}
|
| 27 |
+
}
|
| 28 |
+
for(const [a,b] of residuals){const start=graph.node(a),end=graph.node(b);const x=Math.min(start.x-start.width/2,end.x-end.width/2)-15;
|
| 29 |
+
svg.append(element('path',{d:`M${start.x-start.width/2} ${start.y-start.height/2} H${x} V${end.y} H${end.x-end.width/2}`,stroke:'#789698',fill:'none','stroke-dasharray':'3 2'}));
|
| 30 |
+
}
|
| 31 |
+
for(const nodeId of graph.nodes()){const n=graph.node(nodeId),[ink,fill]=colors[n.role]||colors.dsp;
|
| 32 |
+
const g=element('g',{class:'node'+(n.dim?' dim':''),'data-node':n.id,transform:`translate(${n.x-n.width/2},${n.y-n.height/2})`});
|
| 33 |
+
if(n.action){g.setAttribute('tabindex','0');g.setAttribute('role','button');g.setAttribute('aria-label',n.actionLabel||n.label);if(n.pressed!==undefined)g.setAttribute('aria-pressed',n.pressed);}
|
| 34 |
+
const io=['input','output'].includes(n.role);
|
| 35 |
+
g.append(element('rect',{width:n.width,height:n.height,rx:io?2:5,fill,stroke:ink,'stroke-opacity':io?.3:.5,'stroke-width':n.id===active?2:1,'stroke-dasharray':n.optional?'5 4':'none'}));
|
| 36 |
+
g.append(element('text',{x:n.width/2,y:16,'text-anchor':'middle','font-size':8,fill:ink},n.sublabel??roles[n.role]??''));
|
| 37 |
+
n.lines.forEach((l,i)=>g.append(element('text',{x:n.width/2,y:io?33+i*18:38+i*(compact?18:20),'text-anchor':'middle','font-weight':io?400:700,'font-size':compact?12:14,fill:ink},l)));
|
| 38 |
+
g.append(element('title',{},n.description||n.label));
|
| 39 |
+
if(n.action){const invoke=()=>onNode(n);g.addEventListener('click',invoke);g.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();invoke();}});}
|
| 40 |
+
svg.append(g);
|
| 41 |
+
}
|
| 42 |
+
container.replaceChildren(svg);return {width,height,svg};
|
| 43 |
+
}
|
| 44 |
+
return {render,colors};
|
| 45 |
+
})();
|
index.html
CHANGED
|
@@ -1,19 +1,33 @@
|
|
| 1 |
<!doctype html>
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
</
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="theme-color" content="#007e70"><title>audio.cpp / Audio Model Architecture Atlas</title><link rel="stylesheet" href="style.css"></head>
|
| 4 |
+
<body>
|
| 5 |
+
<header><a class="brand" href="#">audio.cpp / Audio Model Architecture Atlas</a><div class="repo-links"><a id="coverage" href="https://github.com/0xShug0/audio.cpp" target="_blank" rel="noopener noreferrer"></a><span>Enjoy the atlas? Star us on <a href="https://github.com/0xShug0/audio.cpp" target="_blank" rel="noopener noreferrer">GitHub</a> or leave a like on <a href="https://huggingface.co/audio-cpp/audio.cpp-gguf" target="_blank" rel="noopener noreferrer">Hugging Face</a> :)</span></div></header>
|
| 6 |
+
<div class="workspace">
|
| 7 |
+
<aside class="library" aria-label="Model library">
|
| 8 |
+
<div class="library-title">Models <span id="count"></span></div>
|
| 9 |
+
<input id="search" type="search" placeholder="Search model or technology" aria-label="Search model, family or technology">
|
| 10 |
+
<label for="task">Task</label><select id="task"></select>
|
| 11 |
+
<div id="models"></div>
|
| 12 |
+
</aside>
|
| 13 |
+
<main>
|
| 14 |
+
<nav id="breadcrumb" aria-label="Architecture navigation" hidden><button id="back"><img src="assets/icons/arrow-left.svg" alt="">Shared architecture</button><span>/</span><span id="breadcrumb-model"></span></nav>
|
| 15 |
+
<div class="heading"><div><div id="view-label" class="eyebrow"></div><div class="title-line"><h1 id="title"></h1><code id="family"></code></div><div id="variants"></div><div id="subtitle"></div></div><button id="clear" class="quiet" hidden>Clear selection</button></div>
|
| 16 |
+
<section id="model-info" hidden><p id="summary"></p><div id="inputs"></div><p id="detail-status"></p><div id="model-links"></div></section>
|
| 17 |
+
<div class="toolbar">
|
| 18 |
+
<div class="graph-scope"><span id="graph-label" class="eyebrow"></span><select id="scope-filter" aria-label="Graph scope"><option value="selected">Selected models</option><option value="filtered">All filtered models</option></select></div>
|
| 19 |
+
<div class="graph-controls"><button id="zoom-out" class="icon" title="Zoom out" aria-label="Zoom out"><img src="assets/icons/minus.svg" alt=""></button><output id="zoom-level">100%</output><button id="zoom-in" class="icon" title="Zoom in" aria-label="Zoom in"><img src="assets/icons/plus.svg" alt=""></button><button id="fit" class="icon" title="Fit graph" aria-label="Fit graph"><img src="assets/icons/maximize.svg" alt=""></button><button id="export" class="icon" title="Download graph SVG" aria-label="Download graph SVG"><img src="assets/icons/download.svg" alt=""></button></div>
|
| 20 |
+
</div>
|
| 21 |
+
<div class="graph-options"><div class="legend"><span class="ar">AR backbone</span><span class="flow">Generation</span><span class="encoder">Encoder</span><span class="codec">Codec</span><span class="head">Prediction</span></div><label id="frontend-control"><input id="frontends" type="checkbox">Frontends</label></div>
|
| 22 |
+
<div id="variant-wrap" hidden><label for="variant">Architecture route</label><select id="variant"></select></div>
|
| 23 |
+
<div id="graph" aria-label="Architecture graph"></div>
|
| 24 |
+
<section class="relatives"><div class="section-label">RELATED MODELS <span id="relation-name"></span></div><div id="peers"></div></section>
|
| 25 |
+
<details id="packages" hidden><summary>Published packages</summary><div id="package-list"></div></details>
|
| 26 |
+
</main>
|
| 27 |
+
<aside class="inspector" aria-label="Component detail"><button id="expand-detail" class="icon" title="Expand component detail" aria-label="Expand component detail"><img src="assets/icons/maximize.svg" alt=""></button><div class="eyebrow">COMPONENT DETAIL</div><h2 id="component-title"></h2><span id="component-role" class="badge"></span><p id="component-description"></p><p id="instance-description"></p><div id="block-nav"></div><div id="scope"></div><div id="blocks"></div><div id="block-caption"></div><div id="evidence"></div></aside>
|
| 28 |
+
</div>
|
| 29 |
+
<footer><span>Based on models supported by audio.cpp</span><span id="snapshot"></span><span id="status" role="status"></span></footer>
|
| 30 |
+
<dialog id="detail-dialog" aria-labelledby="component-title"></dialog>
|
| 31 |
+
<noscript>This architecture atlas requires JavaScript.</noscript>
|
| 32 |
+
<script src="registry.js"></script><script src="assets/dagre.min.js"></script><script src="graph.js"></script><script src="app.js"></script>
|
| 33 |
+
</body></html>
|
registry.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
style.css
CHANGED
|
@@ -1,28 +1,9 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
}
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
}
|
| 10 |
-
|
| 11 |
-
p {
|
| 12 |
-
color: rgb(107, 114, 128);
|
| 13 |
-
font-size: 15px;
|
| 14 |
-
margin-bottom: 10px;
|
| 15 |
-
margin-top: 5px;
|
| 16 |
-
}
|
| 17 |
-
|
| 18 |
-
.card {
|
| 19 |
-
max-width: 620px;
|
| 20 |
-
margin: 0 auto;
|
| 21 |
-
padding: 16px;
|
| 22 |
-
border: 1px solid lightgray;
|
| 23 |
-
border-radius: 16px;
|
| 24 |
-
}
|
| 25 |
-
|
| 26 |
-
.card p:last-child {
|
| 27 |
-
margin-bottom: 0;
|
| 28 |
-
}
|
|
|
|
| 1 |
+
@font-face{font-family:Noto;src:url('assets/fonts/NotoSans-Regular.ttf')}@font-face{font-family:Noto;src:url('assets/fonts/NotoSans-Bold.ttf');font-weight:700}@font-face{font-family:Display;src:url('assets/fonts/NotoSerifDisplay-Regular.ttf')}
|
| 2 |
+
#expand-detail{float:right}#detail-dialog{width:900px;max-width:calc(100% - 32px);max-height:90vh;padding:0;border:1px solid #ccd9da;border-radius:6px;color:#24383d}#detail-dialog::backdrop{background:#183a3d66}#detail-dialog .inspector{border:0;padding:28px}#detail-dialog #blocks{max-width:none}#detail-dialog #blocks svg{width:auto;max-width:100%;margin:auto}#detail-dialog #component-description,#detail-dialog #block-caption{max-width:680px}
|
| 3 |
+
*{box-sizing:border-box}body{margin:0;background:#fff;color:#24383d;font:14px Noto,sans-serif;letter-spacing:0}button,input,select{font:inherit}button,select,summary{cursor:pointer}button{color:inherit}a{color:#007e70}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible,.node:focus-visible{outline:2px solid #007e70;outline-offset:2px}[hidden]{display:none!important}
|
| 4 |
+
header{height:72px;border-bottom:1px solid #dce4e5;padding:0 26px;display:flex;justify-content:space-between;align-items:center;gap:20px}.brand{font-size:23px;font-weight:700;color:#263e43;text-decoration:none}.brand span{font-size:16px;font-weight:400;color:#6a8083;margin-left:14px}#coverage{font-size:11px;color:#007e70}.workspace{display:grid;grid-template-columns:255px minmax(400px,1fr) 345px;min-height:calc(100vh - 112px)}.library{background:#f8faf9;border-right:1px solid #dce4e5;padding:25px 16px}.library-title{display:flex;justify-content:space-between;font-weight:700;font-size:18px;margin-bottom:18px}.library-title span{font-weight:400;color:#819597}input[type=search],select{width:100%;padding:9px 10px;border:1px solid #ccd9da;background:white;border-radius:4px;color:#344e52;min-width:0}input[type=search]{font-size:12px}label{display:block;font-size:11px;color:#667f82;margin:17px 0 6px}#models{margin-top:18px;max-height:calc(100vh - 295px);overflow:auto;overscroll-behavior:contain}.model-row{display:flex;align-items:stretch}.model{min-width:0;flex:1;text-align:left;border:0;border-left:3px solid transparent;background:none;padding:11px 8px;border-radius:0}.model:hover{background:#edf3f0}.model.active{background:#e3f0eb;border-color:#007e70;color:#005e54}.model-name{font-weight:700;font-size:12px;overflow-wrap:anywhere}.model small{display:block;font-size:10px;color:#72878a;margin-top:5px;overflow-wrap:anywhere}.open-model{background:none;border:0;width:29px;flex-shrink:0}.open-model img{width:15px;opacity:.6}.open-model:hover{background:#e3f0eb}
|
| 5 |
+
main{padding:28px 30px;min-width:0}.eyebrow,.section-label{font-weight:700;font-size:10px;color:#698185}.heading{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}.heading>div{min-width:0}.title-line{display:flex;flex-wrap:wrap;align-items:center;gap:10px 16px}h1{font-family:Display,serif;font-size:35px;font-weight:400;line-height:1.2;margin:12px 0;overflow-wrap:anywhere}#family{font-size:11px;color:#60797d;background:#edf3f1;padding:5px 8px;overflow-wrap:anywhere;border-radius:3px}#variants{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:10px}#variants span{font-size:11px;color:#637b7e;border-bottom:1px solid #cbdcda;padding:2px 0;margin-right:10px}#subtitle{font-size:11px;color:#72888b}#model-info{margin-top:17px}p{color:#657d80;line-height:1.7;font-size:12px}#inputs{display:flex;flex-wrap:wrap;gap:7px}#inputs span{font-size:10px;padding:5px 8px;background:#f1f5f4;border-radius:3px}#inputs em{font-style:normal;color:#809492;margin-left:5px}#model-links{display:flex;gap:14px;flex-wrap:wrap;margin-top:12px;font-size:11px}#model-links a{overflow-wrap:anywhere}#breadcrumb{display:flex;align-items:center;gap:10px;font-size:11px;color:#72888b;margin-bottom:22px}#back{display:flex;align-items:center;gap:6px;color:#007e70;font-size:11px;background:none;border:0;padding:0}#back img{width:15px}
|
| 6 |
+
.toolbar{display:flex;justify-content:space-between;gap:12px;align-items:center;flex-wrap:wrap;border-bottom:1px solid #dce4e5;margin-top:22px;padding:8px 0}.graph-scope{display:flex;gap:12px;align-items:center;min-width:0}.graph-scope select{width:auto;font-size:11px;padding:6px}.graph-controls{display:flex;align-items:center;gap:5px}.icon{border:1px solid transparent;background:white;border-radius:4px;width:29px;height:29px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.icon:hover{background:#edf4f1;border-color:#d7e2df}.icon img{width:16px;height:16px}output{font-size:10px;color:#7a8e90;min-width:34px;text-align:center}.graph-options{display:flex;justify-content:space-between;gap:10px;align-items:center;margin:12px 0;flex-wrap:wrap}.legend{display:flex;gap:10px;flex-wrap:wrap;font-size:10px}.legend span:before{content:'';display:inline-block;width:6px;height:6px;border-radius:50%;background:currentColor;margin-right:4px}.ar{color:#315fc1}.flow{color:#a63975}.encoder{color:#007e70}.codec{color:#956609}.head{color:#557b31}#frontend-control{margin:0;display:flex;align-items:center;gap:5px}input[type=checkbox]{accent-color:#007e70}#variant-wrap{display:flex;align-items:center;gap:10px;margin:15px 0}#variant-wrap label{margin:0}#variant{font-size:11px;padding:6px}#graph{height:570px;max-height:72vh;min-height:340px;overflow:auto;background:radial-gradient(#dbe6e3 .8px,transparent .8px);background-size:18px 18px;overscroll-behavior:contain}#graph svg{display:block;margin:0 auto;max-width:none}svg text{font-family:Noto,sans-serif;letter-spacing:0}.node{outline:none}.node[role=button]{cursor:pointer}.node[role=button]:hover rect,.node:focus-visible rect{stroke-width:2}.dim{opacity:.2}.dim:hover{opacity:.7}.empty{padding:25px;color:#71878b;font-size:12px}.quiet,#peers button{background:white;border:1px solid #cfddda;padding:8px 10px;border-radius:4px;font-size:11px}.quiet{white-space:nowrap}.quiet:hover,#peers button:hover{border-color:#007e70}.relatives{padding-top:19px;border-top:1px solid #dce4e5;margin-top:18px}#relation-name{font-weight:400;margin-left:8px}#peers{display:flex;gap:7px;flex-wrap:wrap;margin-top:12px}#packages{margin-top:22px;font-size:11px}#package-list{display:grid;gap:8px;padding-top:12px;color:#6d8487}#package-list code{font-size:10px;overflow-wrap:anywhere}
|
| 7 |
+
.inspector{background:#fcfdfc;border-left:1px solid #dce4e5;padding:29px 22px;min-width:0}h2{font-family:Display,serif;font-size:28px;font-weight:400;margin:12px 0;overflow-wrap:anywhere}.badge{display:inline-block;font-size:10px;border-radius:3px;padding:5px 7px}#instance-description{border-left:2px solid #d9e5e1;padding-left:10px}#block-nav{display:flex;gap:5px;flex-wrap:wrap;margin-top:20px}#block-nav button{background:none;border:0;border-bottom:1px solid #c8d9d4;color:#007e70;font-size:10px;padding:4px}#scope{font-size:11px;font-weight:700;color:#647d80;padding-top:16px;border-top:1px solid #dce4e5;margin-top:16px}#blocks{overflow:auto;margin-top:12px}#blocks svg{width:100%;height:auto;display:block}#block-caption{font-size:10px;line-height:1.6;color:#788e90;margin-top:10px}#evidence{display:flex;flex-direction:column;gap:9px;font-size:11px;line-height:1.5;margin-top:22px}#evidence a{overflow-wrap:anywhere}footer{min-height:40px;border-top:1px solid #dce4e5;display:flex;justify-content:space-between;gap:12px;padding:12px 24px;font-size:10px;color:#75898a;flex-wrap:wrap}
|
| 8 |
+
@media(min-width:1750px){.workspace{grid-template-columns:280px minmax(450px,1fr) 380px}main{padding:32px 50px}}@media(max-width:1150px){.workspace{grid-template-columns:225px minmax(0,1fr)}.inspector{grid-column:2;border-top:1px solid #dce4e5}.library{grid-row:1/3}#blocks{max-width:420px}}@media(max-width:650px){header{height:auto;padding:16px;flex-wrap:wrap}.brand{font-size:19px}.brand span{font-size:12px;margin-left:5px}#coverage{font-size:10px}.workspace{display:block}.library{padding:16px;border-bottom:1px solid #dce4e5}#models{max-height:170px}main{padding:23px 16px}.inspector{padding:25px 20px}h1{font-size:29px}.heading{flex-wrap:wrap}#family{font-size:10px}#graph{height:460px}.graph-scope{flex-wrap:wrap}.graph-controls{margin-left:auto}#breadcrumb{overflow-wrap:anywhere}#blocks{max-width:100%}}
|
| 9 |
+
.repo-links{display:flex;flex-direction:column;gap:5px;font-size:11px;color:#667f82}.repo-links a{color:#007e70}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|