Spaces:
Running on Zero
Running on Zero
Show the NLP Knowledge Graph immediately on completion and use the live GUI in submission text
888cbc1 verified Download backend/graph_view.html from cmuchancel/patent2sysml: direct link, hf CLI and curl.
- Browser
- Download file 4.5 kB
-
https://huggingface.co/spaces/cmuchancel/patent2sysml/resolve/main/backend/graph_view.html
- Command line
-
hf download hf://spaces/cmuchancel/patent2sysml/backend/graph_view.html
-
curl -L -o graph_view.html https://huggingface.co/spaces/cmuchancel/patent2sysml/resolve/main/backend/graph_view.html
4.5 kB
| <html><head><meta charset="utf-8"><style> | |
| body{font:14px system-ui;color:#243349;margin:0;background:#fafbfd}header{display:flex;gap:10px;padding:12px}input{padding:8px;border:1px solid #ccd3dd;border-radius:6px}button{border:1px solid #ccd3dd;border-radius:6px;background:white;padding:8px;cursor:pointer}svg{width:100%;height:430px;touch-action:none}circle{fill:#dfecfa;stroke:#6382a6;cursor:grab}text{pointer-events:none;font-size:12px}line{stroke:#a6b4c5}#info{white-space:pre-wrap;overflow:auto;height:88px;margin:0;padding:12px;border-top:1px solid #ddd} .match circle{fill:#ffdf89} | |
| </style></head><body><header><input id="search" aria-label="Search graph" placeholder="Search nodes"><button id="fit">Fit graph</button></header><svg id="canvas" aria-label="Knowledge graph"><g id="scene"></g></svg><pre id="info">Select a node to inspect its evidence and properties. Drag nodes; pan or scroll to zoom.</pre><script> | |
| const data=__GRAPH_DATA__, svg=document.querySelector('svg'), scene=document.querySelector('#scene'), info=document.querySelector('#info'), ns='http://www.w3.org/2000/svg'; | |
| let scale=1, tx=0,ty=0,drag=null;const nodes=data.nodes.map((n,i)=>({...n,key:String(n.id??n.text),x:400+Math.cos(i*2.399)*Math.sqrt(i+1)*36,y:215+Math.sin(i*2.399)*Math.sqrt(i+1)*26})); | |
| const byKey=new Map(nodes.map(n=>[n.key,n]));nodes.forEach(n=>{if(!byKey.has(n.text))byKey.set(n.text,n)}); | |
| function el(tag,attrs,parent){const e=document.createElementNS(ns,tag);for(const[k,v]of Object.entries(attrs))e.setAttribute(k,v);parent.append(e);return e} | |
| const edges=data.edges.map(e=>{const a=byKey.get(String(e.source)),b=byKey.get(String(e.target));if(!a||!b)return null;return {...e,a,b,line:el('line',{},scene),caption:el('text',{},scene)}}).filter(Boolean);edges.forEach(e=>e.caption.textContent=e.property??''); | |
| nodes.forEach(n=>{n.group=el('g',{},scene);el('circle',{r:13},n.group);const t=el('text',{x:18,y:4},n.group);t.textContent=n.text??n.key;n.group.addEventListener('pointerdown',ev=>{drag={node:n,x:ev.clientX,y:ev.clientY};svg.setPointerCapture(ev.pointerId);ev.stopPropagation();info.textContent=JSON.stringify(data.nodes[nodes.indexOf(n)],null,2)})}); | |
| function draw(){scene.setAttribute('transform',`translate(${tx},${ty}) scale(${scale})`);nodes.forEach(n=>n.group.setAttribute('transform',`translate(${n.x},${n.y})`));edges.forEach(e=>{for(const[k,v]of Object.entries({x1:e.a.x,y1:e.a.y,x2:e.b.x,y2:e.b.y}))e.line.setAttribute(k,v);e.caption.setAttribute('x',(e.a.x+e.b.x)/2);e.caption.setAttribute('y',(e.a.y+e.b.y)/2-5)})} | |
| svg.addEventListener('pointerdown',e=>{drag={x:e.clientX,y:e.clientY};svg.setPointerCapture(e.pointerId)});svg.addEventListener('pointermove',e=>{if(!drag)return;const dx=e.clientX-drag.x,dy=e.clientY-drag.y;if(drag.node){drag.node.x+=dx/scale;drag.node.y+=dy/scale}else{tx+=dx;ty+=dy}drag.x=e.clientX;drag.y=e.clientY;draw()});svg.addEventListener('pointerup',()=>drag=null);svg.addEventListener('pointercancel',()=>drag=null); | |
| svg.addEventListener('wheel',e=>{e.preventDefault();const box=svg.getBoundingClientRect(),x=e.clientX-box.left,y=e.clientY-box.top,next=Math.max(.1,Math.min(8,scale*Math.exp(-e.deltaY*.001)));tx=x-(x-tx)*next/scale;ty=y-(y-ty)*next/scale;scale=next;draw()},{passive:false}); | |
| function fit(){if(!nodes.length){info.textContent='No entities extracted yet.';return true}if(!svg.clientWidth||!svg.clientHeight)return false;const minX=Math.min(...nodes.map(n=>n.x))-30,maxX=Math.max(...nodes.map(n=>n.x))+180,minY=Math.min(...nodes.map(n=>n.y))-30,maxY=Math.max(...nodes.map(n=>n.y))+30;scale=Math.min(svg.clientWidth/(maxX-minX),svg.clientHeight/(maxY-minY),1.5)*.9;tx=(svg.clientWidth-(maxX-minX)*scale)/2-minX*scale;ty=(svg.clientHeight-(maxY-minY)*scale)/2-minY*scale;draw();return true} | |
| // Gradio can mount this iframe before revealing its results panel. Draw the | |
| // geometry immediately, then fit on the first measurable frame. A tab switch | |
| // or a second resize notification must not be necessary to initialize it. | |
| let fitFrame=0; | |
| function fitWhenVisible(){if(fitFrame)cancelAnimationFrame(fitFrame);fitFrame=0;if(!fit())fitFrame=requestAnimationFrame(fitWhenVisible)} | |
| document.querySelector('#fit').onclick=fitWhenVisible;document.querySelector('#search').oninput=e=>{const query=e.target.value.toLowerCase();nodes.forEach(n=>n.group.classList.toggle('match',!!query&&(n.text??n.key).toLowerCase().includes(query)))};draw();new ResizeObserver(fitWhenVisible).observe(svg);fitWhenVisible(); | |
| </script></body></html> | |