MikeDoes commited on
Commit
0cdd242
·
verified ·
1 Parent(s): 8a25df3

Upload 3 files

Browse files
Files changed (3) hide show
  1. index.html +92 -18
  2. temporal-graph-canva.js +217 -0
  3. temporal-graph-timestep.js +128 -0
index.html CHANGED
@@ -1,19 +1,93 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Workflow Visualizer</title>
7
+ <link rel="icon" href="data:,">
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script type="module">
10
+ import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
11
+ window.mermaid = mermaid;
12
+ mermaid.initialize({
13
+ startOnLoad: true,
14
+ theme: 'base',
15
+ themeVariables: {
16
+ 'primaryColor': '#ffffff',
17
+ 'primaryTextColor': '#000000',
18
+ 'primaryBorderColor': '#000000',
19
+ 'lineColor': '#000000',
20
+ 'secondaryColor': '#ffffff',
21
+ 'tertiaryColor': '#ffffff',
22
+ }
23
+ });
24
+ </script>
25
+ <style>
26
+ .mermaid svg {
27
+ max-width: 100%;
28
+ max-height: 100%;
29
+ }
30
+ textarea {
31
+ font-family: monospace;
32
+ }
33
+ </style>
34
+ <script type="module" src="temporal-graph-timestep.js"></script>
35
+ <script type="module" src="temporal-graph-canva.js"></script>
36
+ </head>
37
+ <body class="bg-white min-h-screen flex flex-col relative">
38
+
39
+ <div class="container mx-auto px-4 py-8 h-screen flex flex-col">
40
+ <h1 class="text-3xl font-bold mb-4 text-center shrink-0">Workflow Visualizer</h1>
41
+ <temporal-graph-canva id="mygraph" class="h-full flex-grow flex flex-col" current-timestep="0" view-mode="single"></temporal-graph-canva>
42
+ </div>
43
+
44
+ <div class="absolute top-4 right-4 z-50 bg-white rounded-lg shadow-2xl border border-gray-200 w-1/3 max-w-lg">
45
+ <button id="toggleInputBtn" class="w-full text-left p-3 font-bold text-lg hover:bg-gray-50 rounded-t-lg">
46
+ Workflow Data ✏️
47
+ </button>
48
+ <div id="inputContainer" class="p-4 border-t border-gray-200">
49
+ <label for="tsvInput" class="block text-sm font-medium text-gray-700 mb-1">Paste your TSV data here:</label>
50
+ <textarea id="tsvInput" rows="10" class="w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-[#8590F8] focus:border-[#8590F8]"></textarea>
51
+ <button id="visualizeBtn" class="mt-2 px-4 py-2 bg-[#8590F8] text-white rounded hover:bg-[#7E7E7E] transition-colors">Visualize</button>
52
+ </div>
53
+ </div>
54
+
55
+
56
+ <script>
57
+ const tsvInput = document.getElementById('tsvInput');
58
+ const visualizeBtn = document.getElementById('visualizeBtn');
59
+ const graphCanva = document.getElementById('mygraph');
60
+
61
+ function parseTSVToKnowledgeGraph(tsvText) {
62
+ if (!tsvText) return [];
63
+ const lines = tsvText.trim().split('\n').slice(1);
64
+ return lines.map(line => {
65
+ const parts = line.split('\t');
66
+ if (parts.length < 4) return null;
67
+ const [subject, relation, object, stageStr] = parts;
68
+ const stage = parseInt(stageStr.replace(/\D/g, ''), 10) || 0;
69
+ return [subject.trim(), relation.trim(), object.trim(), stage];
70
+ }).filter(Boolean);
71
+ }
72
+
73
+ function renderGraph() {
74
+ try {
75
+ const knowledge_graph = parseTSVToKnowledgeGraph(tsvInput.value);
76
+ graphCanva.render(knowledge_graph);
77
+ } catch (error) {
78
+ console.error('Error parsing or rendering the graph:', error);
79
+ alert('Could not parse or render the graph. Please check the console for errors.');
80
+ }
81
+ }
82
+
83
+ visualizeBtn.addEventListener('click', renderGraph);
84
+
85
+ const toggleInputBtn = document.getElementById('toggleInputBtn');
86
+ const inputContainer = document.getElementById('inputContainer');
87
+ toggleInputBtn.addEventListener('click', () => {
88
+ inputContainer.classList.toggle('hidden');
89
+ });
90
+ </script>
91
+ </body>
92
  </html>
93
+ <!-- /* All rights reserved Michael Anthony 2025 */-->
temporal-graph-canva.js ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ===========================================================
2
+ temporal-graph-canva.js
3
+ =========================================================== */
4
+ import './temporal-graph-timestep.js';
5
+
6
+ class TemporalGraphCanva extends HTMLElement {
7
+ constructor() {
8
+ super();
9
+ this._knowledge_graph = [];
10
+ this._maxTimestep = 0;
11
+ this._cursorIndex = -1;
12
+ this._keyHandler = this._keyHandler.bind(this);
13
+ }
14
+
15
+ /* ---------- observed attributes ---------- */
16
+ static get observedAttributes() { return ['current-timestep','view-mode']; }
17
+ get currentTimestep() { return parseInt(this.getAttribute('current-timestep') || '0'); }
18
+ set currentTimestep(v){ this.setAttribute('current-timestep', v); this._cursorIndex=-1; }
19
+ get viewMode() { return this.getAttribute('view-mode') || 'single'; }
20
+ set viewMode(v) { this.setAttribute('view-mode', v); this._cursorIndex=-1; }
21
+
22
+ /* ---------- lifecycle ---------- */
23
+ connectedCallback() { document.addEventListener('keydown', this._keyHandler); }
24
+ disconnectedCallback(){ document.removeEventListener('keydown', this._keyHandler); }
25
+ async attributeChangedCallback(n,o,v){ if(o!==v) await this._render(); }
26
+ async render(kg){
27
+ this._knowledge_graph=kg;
28
+ if (!kg || kg.length === 0) {
29
+ this._maxTimestep = 0;
30
+ } else {
31
+ this._maxTimestep=Math.max(...kg.map(r=>r[3]));
32
+ }
33
+ await this._render();
34
+ }
35
+
36
+ /* ---------- keyboard ---------- */
37
+ _keyHandler(e){
38
+ const k=e.key.toLowerCase();
39
+ if(this.viewMode==='single' && (k==='q'||k==='e')){
40
+ const total=this._relationCount();
41
+ if(total){
42
+ if(k==='e'){ this._cursorIndex++; if(this._cursorIndex>total-1) this._cursorIndex=-1; }
43
+ else { this._cursorIndex--; if(this._cursorIndex< -1) this._cursorIndex=total-1; }
44
+ this._render();
45
+ }
46
+ return;
47
+ }
48
+ if(this.viewMode==='single'){
49
+ if(k==='arrowleft'||k==='a') this._navigate(-1);
50
+ else if(k==='arrowright'||k==='d') this._navigate(1);
51
+ else if(k==='s') this.currentTimestep=this._maxTimestep+1;
52
+ }
53
+ if(k==='f') this._toggleView();
54
+ }
55
+
56
+ /* ---------- helpers ---------- */
57
+ _relationCount(){
58
+ const t=this.currentTimestep;
59
+ if(t===this._maxTimestep+1){
60
+ return new Set(this._knowledge_graph.map(([s,r,t])=>`${s}|${r}|${t}`)).size;
61
+ }
62
+ if(t<0 || t>this._maxTimestep) return 0;
63
+ return new Set(
64
+ this._knowledge_graph.filter(r=>r[3]===t).map(([s,r,t])=>`${s}|${r}|${t}`)
65
+ ).size;
66
+ }
67
+
68
+ /* ---------- render ---------- */
69
+ async _render(){
70
+ this.innerHTML='';
71
+
72
+ const wrapper=document.createElement('div');
73
+ wrapper.className='h-full flex flex-col bg-white rounded-lg shadow-lg p-4 flex-grow overflow-hidden';
74
+
75
+ const container=document.createElement('div');
76
+ container.className=this.viewMode==='single'
77
+ ? 'flex justify-center items-center w-full h-full'
78
+ : 'grid grid-cols-1 md:grid-cols-2 gap-0 w-full h-full overflow-auto';
79
+
80
+ if (!this._knowledge_graph || this._knowledge_graph.length === 0) {
81
+ container.innerHTML = `<div class="flex items-center justify-center h-full text-gray-500">Paste TSV data and click Visualize.</div>`;
82
+ } else {
83
+ /* each real timestep */
84
+ for(let ts=0;ts<=this._maxTimestep;ts++){
85
+ const el=document.createElement('temporal-graph-timestep');
86
+ el.data={
87
+ knowledge_graph:this._knowledge_graph,
88
+ timestep:ts,
89
+ cursorIndex:(this.viewMode==='single' && ts===this.currentTimestep) ? this._cursorIndex : -1
90
+ };
91
+ if(this.viewMode==='single') el.style.display = ts===this.currentTimestep ? 'flex':'none';
92
+ container.appendChild(el);
93
+ }
94
+
95
+ /* summary page */
96
+ const summary=document.createElement('temporal-graph-timestep');
97
+ summary.data={
98
+ knowledge_graph:this._knowledge_graph,
99
+ timestep:'summary',
100
+ cursorIndex:(this.viewMode==='single' && this.currentTimestep===this._maxTimestep+1)
101
+ ? this._cursorIndex : -1
102
+ };
103
+ if(this.viewMode==='single')
104
+ summary.style.display = this.currentTimestep===this._maxTimestep+1 ? 'flex':'none';
105
+ container.appendChild(summary);
106
+ }
107
+
108
+ wrapper.appendChild(container);
109
+ this.appendChild(wrapper);
110
+ this.appendChild(this._buildNav());
111
+ this._updateNavState();
112
+
113
+ /* run Mermaid only on visible diagrams */
114
+ try {
115
+ const sel='.mermaid:not([style*="display: none"])';
116
+ if(this.querySelector(sel)) await window.mermaid.run({querySelector:sel});
117
+ } catch(err) { console.warn('Mermaid render warning:', err); }
118
+ }
119
+
120
+ /* ---------- navigation bar ---------- */
121
+ _buildNav(){
122
+ const nav=document.createElement('div');
123
+ nav.className='bg-white shadow-lg p-4 flex flex-wrap justify-center items-center space-x-4';
124
+
125
+ const mkBtn=(txt,fn)=>{
126
+ const b=document.createElement('button'); b.textContent=txt;
127
+ b.className='px-4 py-2 bg-[#8590F8] text-white rounded hover:bg-[#7E7E7E] transition-colors disabled:opacity-50 disabled:cursor-not-allowed';
128
+ b.addEventListener('click',fn); return b;
129
+ };
130
+
131
+ const prev = mkBtn('Previous', ()=>this._navigate(-1));
132
+ const next = mkBtn('Next', ()=>this._navigate( 1));
133
+ const toggle=mkBtn(this.viewMode==='single'?'View All':'View Single', ()=>this._toggleView());
134
+ const dl = mkBtn('Download SVG', ()=>this._downloadSVG());
135
+
136
+ const indicators=document.createElement('div');
137
+ indicators.className='flex flex-wrap justify-center space-x-2 my-2';
138
+ for(let i=0;i<=this._maxTimestep+1;i++){
139
+ const b=document.createElement('button');
140
+ b.textContent=i===this._maxTimestep+1?'S':i+1;
141
+ b.className='w-8 h-8 rounded-full bg-[#C5C5C5] text-[#1A1A1A] flex items-center justify-center font-bold hover:bg-[#7E7E7E] hover:text-white transition-colors m-1';
142
+ b.addEventListener('click',()=>{this.currentTimestep=i;});
143
+ indicators.appendChild(b);
144
+ }
145
+
146
+ nav.append(prev,indicators,next,toggle,dl);
147
+ this._prevB=prev; this._nextB=next; this._toggleB=toggle; this._indWrap=indicators;
148
+ return nav;
149
+ }
150
+
151
+ _navigate(dx){
152
+ const total=this._maxTimestep+2;
153
+ let n=this.currentTimestep+dx;
154
+ if(n<0) n=0; if(n>=total) n=total-1;
155
+ this.currentTimestep=n;
156
+ }
157
+
158
+ _toggleView(){
159
+ this.viewMode = this.viewMode==='single' ? 'all' : 'single';
160
+ this._toggleB.textContent = this.viewMode==='single' ? 'View All' : 'View Single';
161
+ }
162
+
163
+ _updateNavState(){
164
+ const total=this._maxTimestep+2;
165
+ const noData = !this._knowledge_graph || this._knowledge_graph.length === 0;
166
+
167
+ if(this.viewMode==='all' || noData){
168
+ this._prevB.disabled=this._nextB.disabled=true;
169
+ if(this._indWrap) this._indWrap.querySelectorAll('button').forEach(b=>b.disabled=true);
170
+ } else {
171
+ this._prevB.disabled = this.currentTimestep===0;
172
+ this._nextB.disabled = this.currentTimestep===total-1;
173
+ if (this._indWrap) this._indWrap.querySelectorAll('button').forEach((b,i)=>{
174
+ b.disabled=false;
175
+ if(i===this.currentTimestep){
176
+ b.classList.replace('bg-[#C5C5C5]','bg-[#8590F8]');
177
+ b.classList.replace('text-[#1A1A1A]','text-white');
178
+ } else {
179
+ b.classList.replace('bg-[#8590F8]','bg-[#C5C5C5]');
180
+ b.classList.replace('text-white','text-[#1A1A1A]');
181
+ }
182
+ });
183
+ }
184
+ }
185
+
186
+ /* ---------- download SVG ---------- */
187
+ async _downloadSVG(){
188
+ let svg;
189
+ if(this.viewMode==='single'){
190
+ svg=this.querySelector('.mermaid:not([style*="display: none"]) svg');
191
+ } else {
192
+ const svgs=[...this.querySelectorAll('.mermaid svg')];
193
+ const combo=document.createElementNS('http://www.w3.org/2000/svg','svg');
194
+ let y=0;
195
+ svgs.forEach(s=>{
196
+ const g=document.createElementNS('http://www.w3.org/2000/svg','g');
197
+ g.innerHTML=s.innerHTML;
198
+ g.setAttribute('transform',`translate(0,${y})`);
199
+ combo.appendChild(g);
200
+ y+=parseInt(s.getAttribute('height'))+20||20;
201
+ });
202
+ combo.setAttribute('width',Math.max(...svgs.map(s=>parseInt(s.getAttribute('width'))||0)));
203
+ combo.setAttribute('height',y);
204
+ svg=combo;
205
+ }
206
+ if(!svg) return console.error('downloadSVG: no SVG element');
207
+ const xml=new XMLSerializer().serializeToString(svg);
208
+ const src=/^<svg[^>]+xmlns=/.test(xml)?xml:xml.replace(/^<svg/,'<svg xmlns="http://www.w3.org/2000/svg"');
209
+ const url='data:image/svg+xml;charset=utf-8,' + encodeURIComponent('<?xml version="1.0"?>\n'+src);
210
+ const a=document.createElement('a'); a.href=url; a.download='temporal_graph.svg';
211
+ document.body.appendChild(a); a.click(); document.body.removeChild(a);
212
+ }
213
+ }
214
+
215
+ customElements.define('temporal-graph-canva', TemporalGraphCanva);
216
+
217
+ /* All rights reserved Michael Anthony 2025 */
temporal-graph-timestep.js ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ===========================================================
2
+ temporal-graph-timestep.js
3
+ =========================================================== */
4
+ class TemporalGraphTimestep extends HTMLElement {
5
+ constructor() {
6
+ super();
7
+ this._knowledge_graph = [];
8
+ this._timestep = 0;
9
+ this._cursorIndex = -1;
10
+ }
11
+
12
+ connectedCallback() {
13
+ this.className =
14
+ 'mermaid w-[100%] h-[100%] flex items-center justify-center';
15
+ }
16
+
17
+ /** Accepts { knowledge_graph, timestep, cursorIndex } */
18
+ set data(v) {
19
+ this._knowledge_graph = v.knowledge_graph;
20
+ this._timestep = v.timestep;
21
+ this._cursorIndex = 'cursorIndex' in v ? v.cursorIndex : -1;
22
+ this.render();
23
+ }
24
+
25
+ generateMermaidDiagram() {
26
+ /* ---------- Mermaid init block: 36px with !important ---------- */
27
+ const initCSS = `%%{init:{
28
+ "themeCSS": ".node text{font-size:36px !important;} .edgeLabel text{font-size:36px !important;}"
29
+ }}%%`;
30
+
31
+ const first = this._knowledge_graph.find(r => r[3] === 0);
32
+ const title = first ? first[2] : 'Temporal Graph';
33
+
34
+ /* ---------- preprocess ---------- */
35
+ const unique = [];
36
+ const times = {};
37
+ const seq = {};
38
+ const firstOcc = {};
39
+ const allNodes = new Set();
40
+
41
+ this._knowledge_graph.forEach(([s,r,t,ts])=>{
42
+ const k=`${s}|${r}|${t}`;
43
+ allNodes.add(s); allNodes.add(t);
44
+
45
+ if(!times[k]){unique.push({s,r,t});times[k]=[];}
46
+ times[k].push(ts);
47
+
48
+ if(!seq[ts]) seq[ts]={cnt:1};
49
+ if(!seq[ts][k]){
50
+ seq[ts][k]=seq[ts].cnt++;
51
+ if(!firstOcc[k]) firstOcc[k]={ts,seq:seq[ts][k]};
52
+ }
53
+ });
54
+
55
+ const global=unique.map(o=>{
56
+ const k=`${o.s}|${o.r}|${o.t}`;return {...o,...firstOcc[k]};
57
+ }).sort((a,b)=>a.ts===b.ts? a.seq-b.seq : a.ts-b.ts);
58
+
59
+ // **FIXED HERE**: The bug from before was a typo in this line.
60
+ const orderIdx={}; global.forEach((o,i)=>orderIdx[`${o.s}|${o.r}|${o.t}`]=i);
61
+
62
+ /* ---------- build diagram ---------- */
63
+ let code = `${initCSS}
64
+ ---
65
+ title: ${title}
66
+ ---
67
+ graph LR
68
+ subgraph " "
69
+ direction LR
70
+ `;
71
+ let linkStyle='', nodeStyle='', linkIdx=0;
72
+ const activeNodes=new Set();
73
+
74
+ unique.forEach(({s,r,t})=>{
75
+ const k=`${s}|${r}|${t}`;
76
+ // **RESTORED**: Using original logic for node IDs.
77
+ const sId=s.replace(/\s+/g,'');
78
+ const tId=t.replace(/\s+/g,'');
79
+ const active=(this._timestep==='summary')||times[k].includes(this._timestep);
80
+
81
+ let lb=r, sq, stp;
82
+ if(active){
83
+ if(this._timestep==='summary'){sq=firstOcc[k].seq;stp=firstOcc[k].ts+1;}
84
+ else{sq=seq[this._timestep][k];stp=this._timestep+1;}
85
+ lb=`${stp}.${sq} ${r}`;
86
+ }
87
+
88
+ const highlight=
89
+ active && this._cursorIndex>=0 &&
90
+ (
91
+ (this._timestep==='summary' && orderIdx[k]===this._cursorIndex) ||
92
+ (this._timestep!=='summary' && sq-1===this._cursorIndex)
93
+ );
94
+
95
+ // **RESTORED**: Using original logic for creating nodes and links.
96
+ if(active){
97
+ code += ` ${sId}[${s}] -->|${lb}| ${tId}[${t}]\n`;
98
+ activeNodes.add(s); activeNodes.add(t);
99
+ if(highlight){
100
+ linkStyle+=` linkStyle ${linkIdx} stroke:#8590F8,stroke-width:4px,color:#8590F8\n`;
101
+ nodeStyle+=
102
+ ` style ${sId} fill:#1A1A1A,stroke:#1A1A1A,color:#ffffff\n`+
103
+ ` style ${tId} fill:#8590F8,stroke:#8590F8,color:#ffffff\n`;
104
+ }
105
+ } else {
106
+ code += ` ${sId}[${s}] -.-|${lb}| ${tId}[${t}]\n`;
107
+ linkStyle+=` linkStyle ${linkIdx} stroke:#ffffff,stroke-width:2px,color:#ffffff\n`;
108
+ }
109
+ linkIdx++;
110
+ });
111
+
112
+ // **RESTORED**: Using original logic for hiding inactive nodes.
113
+ allNodes.forEach(n=>{
114
+ if(!activeNodes.has(n)){
115
+ nodeStyle+=` style ${n.replace(/\s+/g,'')} fill:#ffffff,stroke:#ffffff,color:#ffffff\n`;
116
+ }
117
+ });
118
+
119
+ return code + nodeStyle + linkStyle + 'end\n';
120
+ }
121
+
122
+ // **RESTORED**: Using original, simple render method.
123
+ render() { this.textContent = this.generateMermaidDiagram(); }
124
+ }
125
+
126
+ customElements.define('temporal-graph-timestep', TemporalGraphTimestep);
127
+
128
+ /* All rights reserved Michael Anthony */