explorer: clickable legend filter (toggle relationship types in map + ego views)
Browse files
app.py
CHANGED
|
@@ -240,10 +240,15 @@ td.bar span{display:block;height:11px;background:var(--bar);opacity:.85}
|
|
| 240 |
td.num{text-align:right;color:var(--muted);font-variant-numeric:tabular-nums;width:48px}
|
| 241 |
#wrap{display:flex;height:calc(100vh - 132px);position:relative}
|
| 242 |
#net{flex:2.3;min-width:0}
|
| 243 |
-
#legend{position:absolute;left:14px;bottom:14px;background:rgba(255,255,248,.
|
| 244 |
-
border:1px solid var(--rule);padding:8px 11px;font-size:11px;line-height:1.
|
| 245 |
-
box-shadow:0 1px 3px rgba(0,0,0,.06)}
|
| 246 |
-
#legend .lt{white-space:nowrap}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
@media(max-width:760px){#legend{display:none}}
|
| 248 |
#side{flex:1;max-width:440px;overflow:auto;padding:22px 28px;border-left:1px solid var(--rule)}
|
| 249 |
#side h2{font-weight:400;font-size:19px;margin:0;word-break:break-all}
|
|
@@ -289,13 +294,40 @@ const COLORS={exact_copy:'#8a8a80',filtered_subset:'#4a6b8a',subset_modified:'#5
|
|
| 289 |
subset_reformatted:'#39597a',cleaned:'#6a8a5a',regenerated_variant:'#b08a3e',
|
| 290 |
modified_variant:'#9a8a6a',augmentation:'#7a6a9a',augmentation_modified:'#8a7aaa',
|
| 291 |
translation:'#a8566a',reformat:'#4a8a8a',partial_overlap:'#aaaaa0',declared:'#c8c8b8'};
|
| 292 |
-
// corner colour key
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 298 |
document.getElementById('legend').innerHTML=h;})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
|
| 300 |
// ---------- Overview ----------
|
| 301 |
function bars(rows,opts){
|
|
@@ -342,7 +374,7 @@ async function map(){
|
|
| 342 |
shape:'dot',size:6+Math.sqrt(n.derivatives||1)*2.4,
|
| 343 |
color:{background:'#43586b',border:'#43586b'},
|
| 344 |
font:{color:'#111',face:'Palatino, Georgia, serif',size:13,strokeWidth:4,strokeColor:'#fffff8'}}));
|
| 345 |
-
const edges=g.edges.map(e=>({from:e.from,to:e.to,arrows:{to:{scaleFactor:.4}},
|
| 346 |
color:{color:COLORS[e.type]||'#999',opacity:.9},width:1.6,title:e.type+' · '+e.confidence}));
|
| 347 |
network=new vis.Network(document.getElementById('net'),{nodes,edges},
|
| 348 |
{physics:{stabilization:{iterations:160,updateInterval:50},
|
|
@@ -350,6 +382,7 @@ async function map(){
|
|
| 350 |
nodes:{borderWidth:0},edges:{smooth:false},interaction:{hover:true,hideEdgesOnDrag:true,tooltipDelay:120}});
|
| 351 |
network.on('stabilizationIterationsDone',()=>network.setOptions({physics:false}));
|
| 352 |
network.on('click',p=>{if(p.nodes.length)showExplorer(p.nodes[0]);});
|
|
|
|
| 353 |
document.getElementById('side').innerHTML='<h2>Lineage map</h2><div class="meta">'+
|
| 354 |
g.nodes.length.toLocaleString()+' source datasets · '+g.edges.length+' multi-level chain links</div>'+
|
| 355 |
'<p class="row">Node <b>size</b> = number of derivatives. Edge <b>colour</b> = relationship type (key, lower-left). '+
|
|
@@ -363,11 +396,11 @@ async function go(){
|
|
| 363 |
fetch('/related?dataset='+encodeURIComponent(ds)).then(x=>x.json())]);
|
| 364 |
const top=new Set(g.nodes.slice().sort((a,b)=>(b.downloads||0)-(a.downloads||0)).slice(0,40).map(n=>n.id));
|
| 365 |
const big=g.nodes.length>250;
|
| 366 |
-
const nodes=g.nodes.map(n=>({id:n.id,label:(n.focus||top.has(n.id)||!big)?n.label:undefined,title:n.title,shape:'dot',
|
| 367 |
size:n.focus?16:(6+Math.log10((n.downloads||0)+10)*2.2),
|
| 368 |
color:{background:n.focus?'#c1440e':'#43586b',border:n.focus?'#c1440e':'#43586b'},
|
| 369 |
font:{color:'#111',face:'Palatino, Georgia, serif',size:n.focus?16:12,strokeWidth:4,strokeColor:'#fffff8'}}));
|
| 370 |
-
const edges=g.edges.map(e=>({from:e.from,to:e.to,arrows:{to:{scaleFactor:.45}},
|
| 371 |
color:{color:COLORS[e.type]||'#999',opacity:.8},width:e.source==='inferred'?1.2:.6,
|
| 372 |
dashes:e.source==='declared',title:e.type+' · conf '+e.confidence+' · '+e.source}));
|
| 373 |
// adaptive layout: hub-shaped (many children of focus) -> force (spreads radially);
|
|
@@ -387,6 +420,7 @@ async function go(){
|
|
| 387 |
network=new vis.Network(document.getElementById('net'),{nodes,edges},opts);
|
| 388 |
if(useForce)network.on('stabilizationIterationsDone',()=>network.setOptions({physics:false}));
|
| 389 |
network.on('click',p=>{if(p.nodes.length){showExplorer(p.nodes[0]);}});
|
|
|
|
| 390 |
renderSide(r);
|
| 391 |
}
|
| 392 |
function lst(title,arr){
|
|
|
|
| 240 |
td.num{text-align:right;color:var(--muted);font-variant-numeric:tabular-nums;width:48px}
|
| 241 |
#wrap{display:flex;height:calc(100vh - 132px);position:relative}
|
| 242 |
#net{flex:2.3;min-width:0}
|
| 243 |
+
#legend{position:absolute;left:14px;bottom:14px;background:rgba(255,255,248,.94);
|
| 244 |
+
border:1px solid var(--rule);padding:8px 11px;font-size:11px;line-height:1.75;max-width:210px;
|
| 245 |
+
box-shadow:0 1px 3px rgba(0,0,0,.06);user-select:none}
|
| 246 |
+
#legend .lt{white-space:nowrap;cursor:pointer;padding:1px 4px;margin:0 -4px;border-radius:3px}
|
| 247 |
+
#legend .lt:hover{background:#f1f1e6}
|
| 248 |
+
#legend .lt.off{opacity:.35;text-decoration:line-through}
|
| 249 |
+
#legend .reset{margin-top:5px;padding-top:4px;border-top:1px solid var(--rule);
|
| 250 |
+
font-style:italic;color:var(--muted);cursor:pointer}
|
| 251 |
+
#legend .reset:hover{color:var(--ink)}
|
| 252 |
@media(max-width:760px){#legend{display:none}}
|
| 253 |
#side{flex:1;max-width:440px;overflow:auto;padding:22px 28px;border-left:1px solid var(--rule)}
|
| 254 |
#side h2{font-weight:400;font-size:19px;margin:0;word-break:break-all}
|
|
|
|
| 294 |
subset_reformatted:'#39597a',cleaned:'#6a8a5a',regenerated_variant:'#b08a3e',
|
| 295 |
modified_variant:'#9a8a6a',augmentation:'#7a6a9a',augmentation_modified:'#8a7aaa',
|
| 296 |
translation:'#a8566a',reformat:'#4a8a8a',partial_overlap:'#aaaaa0',declared:'#c8c8b8'};
|
| 297 |
+
// corner colour key — clickable filter (toggle type visibility)
|
| 298 |
+
const LEGEND_TYPES=['exact_copy','filtered_subset','subset_modified','subset_reformatted','cleaned',
|
| 299 |
+
'regenerated_variant','augmentation','augmentation_modified','translation','reformat','modified_variant',
|
| 300 |
+
'partial_overlap','combined','declared'];
|
| 301 |
+
const ACTIVE_TYPES=new Set(LEGEND_TYPES);
|
| 302 |
+
let currentNetwork=null;
|
| 303 |
+
(function(){let h='<div style="font-style:italic;color:var(--muted);margin-bottom:3px">relationship · click to filter</div>';
|
| 304 |
+
for(const t of LEGEND_TYPES)
|
| 305 |
+
h+='<div class="lt" data-type="'+t+'"><span class="dot" style="background:'+(COLORS[t]||'#999')+'"></span>'+
|
| 306 |
+
t.replace(/_/g,' ')+(t==='declared'?' (dashed)':'')+'</div>';
|
| 307 |
+
h+='<div class="reset" data-reset="1">reset all</div>';
|
| 308 |
document.getElementById('legend').innerHTML=h;})();
|
| 309 |
+
function applyFilter(){
|
| 310 |
+
if(!currentNetwork)return;
|
| 311 |
+
const edgesDS=currentNetwork.body.data.edges, nodesDS=currentNetwork.body.data.nodes;
|
| 312 |
+
const allEdges=edgesDS.get();
|
| 313 |
+
edgesDS.update(allEdges.map(e=>({id:e.id, hidden:!ACTIVE_TYPES.has(e.__type)})));
|
| 314 |
+
// hide nodes that have NO visible incident edge (except the focus)
|
| 315 |
+
const visible=new Set();
|
| 316 |
+
for(const e of allEdges) if(ACTIVE_TYPES.has(e.__type)){visible.add(e.from);visible.add(e.to);}
|
| 317 |
+
nodesDS.update(nodesDS.get().map(n=>({id:n.id, hidden:!visible.has(n.id) && !n.__focus})));
|
| 318 |
+
}
|
| 319 |
+
document.getElementById('legend').addEventListener('click',ev=>{
|
| 320 |
+
const reset=ev.target.closest('[data-reset]');
|
| 321 |
+
if(reset){LEGEND_TYPES.forEach(t=>ACTIVE_TYPES.add(t));
|
| 322 |
+
document.querySelectorAll('#legend .lt').forEach(el=>el.classList.remove('off'));
|
| 323 |
+
applyFilter();return;}
|
| 324 |
+
const lt=ev.target.closest('.lt');
|
| 325 |
+
if(!lt||!lt.dataset.type)return;
|
| 326 |
+
const t=lt.dataset.type;
|
| 327 |
+
if(ACTIVE_TYPES.has(t)){ACTIVE_TYPES.delete(t);lt.classList.add('off');}
|
| 328 |
+
else{ACTIVE_TYPES.add(t);lt.classList.remove('off');}
|
| 329 |
+
applyFilter();
|
| 330 |
+
});
|
| 331 |
|
| 332 |
// ---------- Overview ----------
|
| 333 |
function bars(rows,opts){
|
|
|
|
| 374 |
shape:'dot',size:6+Math.sqrt(n.derivatives||1)*2.4,
|
| 375 |
color:{background:'#43586b',border:'#43586b'},
|
| 376 |
font:{color:'#111',face:'Palatino, Georgia, serif',size:13,strokeWidth:4,strokeColor:'#fffff8'}}));
|
| 377 |
+
const edges=g.edges.map(e=>({from:e.from,to:e.to,__type:e.type,arrows:{to:{scaleFactor:.4}},
|
| 378 |
color:{color:COLORS[e.type]||'#999',opacity:.9},width:1.6,title:e.type+' · '+e.confidence}));
|
| 379 |
network=new vis.Network(document.getElementById('net'),{nodes,edges},
|
| 380 |
{physics:{stabilization:{iterations:160,updateInterval:50},
|
|
|
|
| 382 |
nodes:{borderWidth:0},edges:{smooth:false},interaction:{hover:true,hideEdgesOnDrag:true,tooltipDelay:120}});
|
| 383 |
network.on('stabilizationIterationsDone',()=>network.setOptions({physics:false}));
|
| 384 |
network.on('click',p=>{if(p.nodes.length)showExplorer(p.nodes[0]);});
|
| 385 |
+
currentNetwork=network;applyFilter();
|
| 386 |
document.getElementById('side').innerHTML='<h2>Lineage map</h2><div class="meta">'+
|
| 387 |
g.nodes.length.toLocaleString()+' source datasets · '+g.edges.length+' multi-level chain links</div>'+
|
| 388 |
'<p class="row">Node <b>size</b> = number of derivatives. Edge <b>colour</b> = relationship type (key, lower-left). '+
|
|
|
|
| 396 |
fetch('/related?dataset='+encodeURIComponent(ds)).then(x=>x.json())]);
|
| 397 |
const top=new Set(g.nodes.slice().sort((a,b)=>(b.downloads||0)-(a.downloads||0)).slice(0,40).map(n=>n.id));
|
| 398 |
const big=g.nodes.length>250;
|
| 399 |
+
const nodes=g.nodes.map(n=>({id:n.id,__focus:n.focus,label:(n.focus||top.has(n.id)||!big)?n.label:undefined,title:n.title,shape:'dot',
|
| 400 |
size:n.focus?16:(6+Math.log10((n.downloads||0)+10)*2.2),
|
| 401 |
color:{background:n.focus?'#c1440e':'#43586b',border:n.focus?'#c1440e':'#43586b'},
|
| 402 |
font:{color:'#111',face:'Palatino, Georgia, serif',size:n.focus?16:12,strokeWidth:4,strokeColor:'#fffff8'}}));
|
| 403 |
+
const edges=g.edges.map(e=>({from:e.from,to:e.to,__type:e.type,arrows:{to:{scaleFactor:.45}},
|
| 404 |
color:{color:COLORS[e.type]||'#999',opacity:.8},width:e.source==='inferred'?1.2:.6,
|
| 405 |
dashes:e.source==='declared',title:e.type+' · conf '+e.confidence+' · '+e.source}));
|
| 406 |
// adaptive layout: hub-shaped (many children of focus) -> force (spreads radially);
|
|
|
|
| 420 |
network=new vis.Network(document.getElementById('net'),{nodes,edges},opts);
|
| 421 |
if(useForce)network.on('stabilizationIterationsDone',()=>network.setOptions({physics:false}));
|
| 422 |
network.on('click',p=>{if(p.nodes.length){showExplorer(p.nodes[0]);}});
|
| 423 |
+
currentNetwork=network;applyFilter();
|
| 424 |
renderSide(r);
|
| 425 |
}
|
| 426 |
function lst(title,arr){
|