AncViz commited on
Commit
db6ded5
·
verified ·
1 Parent(s): 513b62b

Make the graph editable so the user can add documents, operations, connections, and so forth.

Browse files
Files changed (4) hide show
  1. README.md +8 -5
  2. index.html +221 -19
  3. script.js +540 -0
  4. style.css +119 -19
README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
- title: Graph Editor Pro
3
- emoji: 🏃
4
- colorFrom: yellow
5
- colorTo: red
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
  ---
2
+ title: Graph Editor Pro 🎨
3
+ colorFrom: purple
4
+ colorTo: pink
5
+ emoji: 🐳
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite-v3
10
  ---
11
 
12
+ # Welcome to your new DeepSite project!
13
+ This project was created with [DeepSite](https://huggingface.co/deepsite).
index.html CHANGED
@@ -1,19 +1,221 @@
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>Genealogy Graph Analyzer</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
10
+ <script src="https://unpkg.com/feather-icons"></script>
11
+ </head>
12
+ <body class="bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900 min-h-screen text-white">
13
+ <!-- Header -->
14
+ <header class="bg-black/30 backdrop-blur-lg border-b border-white/10">
15
+ <div class="container mx-auto px-4 py-4">
16
+ <div class="flex items-center justify-between">
17
+ <div class="flex items-center space-x-3">
18
+ <div class="w-10 h-10 bg-gradient-to-br from-purple-500 to-pink-500 rounded-lg flex items-center justify-center">
19
+ <i data-feather="git-branch" class="w-6 h-6"></i>
20
+ </div>
21
+ <h1 class="text-2xl font-bold bg-gradient-to-r from-purple-400 to-pink-400 bg-clip-text text-transparent">
22
+ Genealogy Graph Analyzer
23
+ </h1>
24
+ </div>
25
+ <div class="flex items-center space-x-4">
26
+ <button onclick="toggleEditMode()" id="editModeBtn" class="px-4 py-2 bg-purple-600 hover:bg-purple-700 rounded-lg transition-all duration-200 flex items-center space-x-2">
27
+ <i data-feather="edit-3" class="w-4 h-4"></i>
28
+ <span>Edit Mode</span>
29
+ </button>
30
+ <button onclick="resetGraph()" class="px-4 py-2 bg-white/10 hover:bg-white/20 rounded-lg transition-all duration-200 flex items-center space-x-2">
31
+ <i data-feather="refresh-cw" class="w-4 h-4"></i>
32
+ <span>Reset</span>
33
+ </button>
34
+ <button onclick="toggleFullscreen()" class="px-4 py-2 bg-white/10 hover:bg-white/20 rounded-lg transition-all duration-200">
35
+ <i data-feather="maximize" class="w-4 h-4"></i>
36
+ </button>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ </header>
41
+
42
+ <!-- Main Content -->
43
+ <main class="container mx-auto px-4 py-6">
44
+ <!-- Analysis Overview -->
45
+ <section class="mb-6 bg-black/20 backdrop-blur-lg rounded-xl p-6 border border-white/10">
46
+ <h2 class="text-xl font-semibold mb-4 flex items-center">
47
+ <i data-feather="file-text" class="w-5 h-5 mr-2 text-purple-400"></i>
48
+ Analysis Task
49
+ </h2>
50
+ <div class="grid md:grid-cols-2 gap-4">
51
+ <div class="bg-white/5 rounded-lg p-4">
52
+ <h3 class="text-sm font-medium text-gray-400 mb-2">Document 1</h3>
53
+ <div class="flex items-center space-x-3">
54
+ <div class="w-12 h-12 bg-blue-500/20 rounded-lg flex items-center justify-center">
55
+ <i data-feather="file" class="w-6 h-6 text-blue-400"></i>
56
+ </div>
57
+ <div>
58
+ <p class="font-medium">Tree123.json</p>
59
+ <p class="text-sm text-gray-400">Person: John Dow</p>
60
+ </div>
61
+ </div>
62
+ </div>
63
+ <div class="bg-white/5 rounded-lg p-4">
64
+ <h3 class="text-sm font-medium text-gray-400 mb-2">Document 2</h3>
65
+ <div class="flex items-center space-x-3">
66
+ <div class="w-12 h-12 bg-purple-500/20 rounded-lg flex items-center justify-center">
67
+ <i data-feather="file" class="w-6 h-6 text-purple-400"></i>
68
+ </div>
69
+ <div>
70
+ <p class="font-medium">Tree456.json</p>
71
+ <p class="text-sm text-gray-400">Person: John Dowe</p>
72
+ </div>
73
+ </div>
74
+ </div>
75
+ </div>
76
+ </section>
77
+
78
+ <!-- Graph Visualization -->
79
+ <section class="bg-black/20 backdrop-blur-lg rounded-xl p-6 border border-white/10">
80
+ <div class="flex items-center justify-between mb-4">
81
+ <h2 class="text-xl font-semibold flex items-center">
82
+ <i data-feather="git-branch" class="w-5 h-5 mr-2 text-purple-400"></i>
83
+ Directed Graph Visualization
84
+ </h2>
85
+ <div class="flex items-center space-x-2">
86
+ <button onclick="addNode()" class="p-2 bg-green-600 hover:bg-green-700 rounded-lg transition-all" title="Add Node">
87
+ <i data-feather="plus-circle" class="w-4 h-4"></i>
88
+ </button>
89
+ <button onclick="deleteSelectedNode()" class="p-2 bg-red-600 hover:bg-red-700 rounded-lg transition-all" title="Delete Node">
90
+ <i data-feather="trash-2" class="w-4 h-4"></i>
91
+ </button>
92
+ <button onclick="connectNodes()" class="p-2 bg-blue-600 hover:bg-blue-700 rounded-lg transition-all" title="Connect Nodes">
93
+ <i data-feather="link" class="w-4 h-4"></i>
94
+ </button>
95
+ <div class="w-px h-6 bg-white/20 mx-2"></div>
96
+ <button onclick="zoomIn()" class="p-2 bg-white/10 hover:bg-white/20 rounded-lg transition-all">
97
+ <i data-feather="zoom-in" class="w-4 h-4"></i>
98
+ </button>
99
+ <button onclick="zoomOut()" class="p-2 bg-white/10 hover:bg-white/20 rounded-lg transition-all">
100
+ <i data-feather="zoom-out" class="w-4 h-4"></i>
101
+ </button>
102
+ <button onclick="fitToScreen()" class="p-2 bg-white/10 hover:bg-white/20 rounded-lg transition-all">
103
+ <i data-feather="maximize-2" class="w-4 h-4"></i>
104
+ </button>
105
+ </div>
106
+ </div>
107
+ <div class="relative bg-gradient-to-br from-gray-900/50 to-black/50 rounded-lg overflow-hidden" style="height: 500px;">
108
+ <canvas id="graphCanvas" class="w-full h-full cursor-move"></canvas>
109
+ <!-- Edit Mode Indicator -->
110
+ <div id="editIndicator" class="absolute top-4 left-4 bg-green-600/90 backdrop-blur-lg rounded-lg px-3 py-2 text-sm font-medium hidden">
111
+ <i data-feather="edit-3" class="w-4 h-4 inline mr-2"></i>
112
+ Edit Mode Active
113
+ </div>
114
+ <!-- Node Edit Panel -->
115
+ <div id="editPanel" class="absolute top-4 right-4 bg-black/80 backdrop-blur-lg rounded-lg p-4 w-80 hidden">
116
+ <h3 class="font-semibold mb-3 text-purple-400">Edit Node</h3>
117
+ <div class="space-y-3">
118
+ <div>
119
+ <label class="text-xs text-gray-400 block mb-1">Node Label</label>
120
+ <input type="text" id="nodeLabel" class="w-full bg-white/10 rounded px-3 py-2 text-sm text-white border border-white/20 focus:border-purple-400 focus:outline-none">
121
+ </div>
122
+ <div>
123
+ <label class="text-xs text-gray-400 block mb-1">Node Type</label>
124
+ <select id="nodeType" class="w-full bg-white/10 rounded px-3 py-2 text-sm text-white border border-white/20 focus:border-purple-400 focus:outline-none">
125
+ <option value="document">Document</option>
126
+ <option value="operation">Operation</option>
127
+ <option value="result">Result</option>
128
+ <option value="data">Data</option>
129
+ </select>
130
+ </div>
131
+ <div>
132
+ <label class="text-xs text-gray-400 block mb-1">Node Color</label>
133
+ <input type="color" id="nodeColor" class="w-full h-10 bg-white/10 rounded border border-white/20 cursor-pointer">
134
+ </div>
135
+ <div class="flex space-x-2">
136
+ <button onclick="saveNodeChanges()" class="flex-1 bg-green-600 hover:bg-green-700 text-white rounded px-3 py-2 text-sm font-medium transition-all">
137
+ Save
138
+ </button>
139
+ <button onclick="cancelEdit()" class="flex-1 bg-white/10 hover:bg-white/20 text-white rounded px-3 py-2 text-sm font-medium transition-all">
140
+ Cancel
141
+ </button>
142
+ </div>
143
+ </div>
144
+ </div>
145
+ <!-- Floating Info Panel -->
146
+ <div id="infoPanel" class="absolute top-4 right-4 bg-black/80 backdrop-blur-lg rounded-lg p-4 w-80 hidden">
147
+ <h3 class="font-semibold mb-2 text-purple-400">Node Details</h3>
148
+ <div id="nodeInfo" class="text-sm space-y-2"></div>
149
+ </div>
150
+ </div>
151
+ </section>
152
+
153
+ <!-- Analysis Steps -->
154
+ <section class="mt-6 bg-black/20 backdrop-blur-lg rounded-xl p-6 border border-white/10">
155
+ <h2 class="text-xl font-semibold mb-4 flex items-center">
156
+ <i data-feather="list" class="w-5 h-5 mr-2 text-purple-400"></i>
157
+ Analysis Pipeline
158
+ </h2>
159
+ <div class="space-y-3">
160
+ <div class="flex items-center space-x-4 p-3 bg-white/5 rounded-lg hover:bg-white/10 transition-all cursor-pointer" onclick="highlightNode('doc1')">
161
+ <div class="w-10 h-10 bg-blue-500/20 rounded-full flex items-center justify-center">
162
+ <span class="text-blue-400 font-bold">1</span>
163
+ </div>
164
+ <div class="flex-1">
165
+ <p class="font-medium">Extract from Tree123.json</p>
166
+ <p class="text-sm text-gray-400">Operation: extract_person</p>
167
+ </div>
168
+ <i data-feather="arrow-right" class="w-4 h-4 text-gray-400"></i>
169
+ </div>
170
+ <div class="flex items-center space-x-4 p-3 bg-white/5 rounded-lg hover:bg-white/10 transition-all cursor-pointer" onclick="highlightNode('doc2')">
171
+ <div class="w-10 h-10 bg-purple-500/20 rounded-full flex items-center justify-center">
172
+ <span class="text-purple-400 font-bold">2</span>
173
+ </div>
174
+ <div class="flex-1">
175
+ <p class="font-medium">Extract from Tree456.json</p>
176
+ <p class="text-sm text-gray-400">Operation: extract_person</p>
177
+ </div>
178
+ <i data-feather="arrow-right" class="w-4 h-4 text-gray-400"></i>
179
+ </div>
180
+ <div class="flex items-center space-x-4 p-3 bg-white/5 rounded-lg hover:bg-white/10 transition-all cursor-pointer" onclick="highlightNode('comparison')">
181
+ <div class="w-10 h-10 bg-green-500/20 rounded-full flex items-center justify-center">
182
+ <span class="text-green-400 font-bold">3</span>
183
+ </div>
184
+ <div class="flex-1">
185
+ <p class="font-medium">Compare Names</p>
186
+ <p class="text-sm text-gray-400">Operation: compare_names (threshold=0.8)</p>
187
+ </div>
188
+ <i data-feather="arrow-right" class="w-4 h-4 text-gray-400"></i>
189
+ </div>
190
+ <div class="flex items-center space-x-4 p-3 bg-white/5 rounded-lg hover:bg-white/10 transition-all cursor-pointer" onclick="highlightNode('classification')">
191
+ <div class="w-10 h-10 bg-yellow-500/20 rounded-full flex items-center justify-center">
192
+ <span class="text-yellow-400 font-bold">4</span>
193
+ </div>
194
+ <div class="flex-1">
195
+ <p class="font-medium">Classify Same Person</p>
196
+ <p class="text-sm text-gray-400">Operation: classify_same_person (method=fact_based)</p>
197
+ </div>
198
+ <i data-feather="check-circle" class="w-4 h-4 text-green-400"></i>
199
+ </div>
200
+ </div>
201
+ </section>
202
+
203
+ <!-- Result Summary -->
204
+ <section class="mt-6 bg-gradient-to-r from-green-900/20 to-emerald-900/20 backdrop-blur-lg rounded-xl p-6 border border-green-500/20">
205
+ <div class="flex items-center space-x-3">
206
+ <div class="w-12 h-12 bg-green-500/20 rounded-full flex items-center justify-center">
207
+ <i data-feather="check" class="w-6 h-6 text-green-400"></i>
208
+ </div>
209
+ <div>
210
+ <h3 class="text-xl font-semibold text-green-400">Analysis Complete</h3>
211
+ <p class="text-gray-300">Classification: High probability of same person (85% match)</p>
212
+ </div>
213
+ </div>
214
+ </section>
215
+ </main>
216
+
217
+ <script src="script.js"></script>
218
+ <script>feather.replace();</script>
219
+ <script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
220
+ </body>
221
+ </html>
script.js ADDED
@@ -0,0 +1,540 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ // Graph visualization class
3
+ class DirectedGraph {
4
+ constructor(canvasId) {
5
+ this.canvas = document.getElementById(canvasId);
6
+ this.ctx = this.canvas.getContext('2d');
7
+ this.nodes = [];
8
+ this.edges = [];
9
+ this.selectedNode = null;
10
+ this.hoveredNode = null;
11
+ this.offset = { x: 0, y: 0 };
12
+ this.scale = 1;
13
+ this.isDragging = false;
14
+ this.dragStart = { x: 0, y: 0 };
15
+ this.editMode = false;
16
+ this.connectingMode = false;
17
+ this.connectingFrom = null;
18
+ this.tempConnection = null;
19
+ this.nodeIdCounter = 1;
20
+
21
+ this.setupCanvas();
22
+ this.createNodes();
23
+ this.createEdges();
24
+ this.setupEventListeners();
25
+ this.animate();
26
+ }
27
+ setupCanvas() {
28
+ const rect = this.canvas.parentElement.getBoundingClientRect();
29
+ this.canvas.width = rect.width;
30
+ this.canvas.height = rect.height;
31
+ }
32
+
33
+ createNodes() {
34
+ // Document nodes
35
+ this.nodes.push({
36
+ id: 'doc1',
37
+ x: 100,
38
+ y: 100,
39
+ radius: 40,
40
+ color: '#3b82f6',
41
+ label: 'Tree123.json',
42
+ data: { person: 'John Dow', type: 'document' }
43
+ });
44
+
45
+ this.nodes.push({
46
+ id: 'doc2',
47
+ x: 100,
48
+ y: 250,
49
+ radius: 40,
50
+ color: '#a855f7',
51
+ label: 'Tree456.json',
52
+ data: { person: 'John Dowe', type: 'document' }
53
+ });
54
+
55
+ // Extracted nodes
56
+ this.nodes.push({
57
+ id: 'extracted1',
58
+ x: 300,
59
+ y: 100,
60
+ radius: 35,
61
+ color: '#60a5fa',
62
+ label: 'Extracted 1',
63
+ data: { operation: 'extract_person', source: 'doc1' }
64
+ });
65
+
66
+ this.nodes.push({
67
+ id: 'extracted2',
68
+ x: 300,
69
+ y: 250,
70
+ radius: 35,
71
+ color: '#c084fc',
72
+ label: 'Extracted 2',
73
+ data: { operation: 'extract_person', source: 'doc2' }
74
+ });
75
+
76
+ // Comparison node
77
+ this.nodes.push({
78
+ id: 'comparison',
79
+ x: 500,
80
+ y: 175,
81
+ radius: 45,
82
+ color: '#34d399',
83
+ label: 'Comparison',
84
+ data: { operation: 'compare_names', threshold: 0.8, result: 0.85 }
85
+ });
86
+
87
+ // Classification node
88
+ this.nodes.push({
89
+ id: 'classification',
90
+ x: 700,
91
+ y: 175,
92
+ radius: 50,
93
+ color: '#fbbf24',
94
+ label: 'Classification',
95
+ data: {
96
+ operation: 'classify_same_person',
97
+ method: 'fact_based',
98
+ result: 'High probability match',
99
+ confidence: 85
100
+ }
101
+ });
102
+ }
103
+
104
+ createEdges() {
105
+ // Document to extraction
106
+ this.edges.push({ from: 'doc1', to: 'extracted1', label: 'extract' });
107
+ this.edges.push({ from: 'doc2', to: 'extracted2', label: 'extract' });
108
+
109
+ // Extraction to comparison
110
+ this.edges.push({ from: 'extracted1', to: 'comparison', label: 'input' });
111
+ this.edges.push({ from: 'extracted2', to: 'comparison', label: 'input' });
112
+
113
+ // Comparison to classification
114
+ this.edges.push({ from: 'comparison', to: 'classification', label: 'result' });
115
+ }
116
+
117
+ setupEventListeners() {
118
+ this.canvas.addEventListener('mousedown', (e) => this.handleMouseDown(e));
119
+ this.canvas.addEventListener('mousemove', (e) => this.handleMouseMove(e));
120
+ this.canvas.addEventListener('mouseup', () => this.handleMouseUp());
121
+ this.canvas.addEventListener('wheel', (e) => this.handleWheel(e));
122
+ this.canvas.addEventListener('click', (e) => this.handleClick(e));
123
+
124
+ window.addEventListener('resize', () => {
125
+ this.setupCanvas();
126
+ });
127
+ }
128
+ handleMouseDown(e) {
129
+ const rect = this.canvas.getBoundingClientRect();
130
+ const x = e.clientX - rect.left;
131
+ const y = e.clientY - rect.top;
132
+
133
+ if (this.editMode && this.hoveredNode) {
134
+ if (this.connectingMode) {
135
+ if (this.connectingFrom && this.connectingFrom !== this.hoveredNode) {
136
+ // Create connection
137
+ this.edges.push({
138
+ from: this.connectingFrom.id,
139
+ to: this.hoveredNode.id,
140
+ label: 'connection'
141
+ });
142
+ this.connectingFrom = null;
143
+ this.connectingMode = false;
144
+ this.canvas.classList.remove('edit-mode-cursor');
145
+ }
146
+ } else {
147
+ this.selectedNode = this.hoveredNode;
148
+ this.showEditPanel(this.selectedNode);
149
+ }
150
+ } else if (!this.editMode) {
151
+ this.isDragging = true;
152
+ this.dragStart = { x: x - this.offset.x, y: y - this.offset.y };
153
+ this.canvas.style.cursor = 'grabbing';
154
+ } else if (this.editMode && !this.hoveredNode) {
155
+ // Add new node at click position
156
+ const worldX = (x - this.offset.x) / this.scale;
157
+ const worldY = (y - this.offset.y) / this.scale;
158
+ this.addNodeAt(worldX, worldY);
159
+ }
160
+ }
161
+ handleMouseMove(e) {
162
+ const rect = this.canvas.getBoundingClientRect();
163
+ const x = (e.clientX - rect.left - this.offset.x) / this.scale;
164
+ const y = (e.clientY - rect.top - this.offset.y) / this.scale;
165
+
166
+ if (this.isDragging && !this.editMode) {
167
+ this.offset.x = e.clientX - rect.left - this.dragStart.x;
168
+ this.offset.y = e.clientY - rect.top - this.dragStart.y;
169
+ } else if (this.connectingMode && this.connectingFrom) {
170
+ this.tempConnection = { x: (e.clientX - rect.left - this.offset.x) / this.scale, y: (e.clientY - rect.top - this.offset.y) / this.scale };
171
+ } else {
172
+ // Check for hover over nodes
173
+ this.hoveredNode = null;
174
+ for (const node of this.nodes) {
175
+ const dist = Math.sqrt((x - node.x) ** 2 + (y - node.y) ** 2);
176
+ if (dist <= node.radius) {
177
+ this.hoveredNode = node;
178
+ if (this.editMode) {
179
+ if (this.connectingMode) {
180
+ this.canvas.style.cursor = 'crosshair';
181
+ } else {
182
+ this.canvas.style.cursor = 'pointer';
183
+ }
184
+ } else {
185
+ this.canvas.style.cursor = 'pointer';
186
+ }
187
+ break;
188
+ }
189
+ }
190
+ if (!this.hoveredNode) {
191
+ this.canvas.style.cursor = this.editMode ? (this.connectingMode ? 'crosshair' : 'crosshair') : 'move';
192
+ }
193
+ }
194
+ }
195
+ handleMouseUp() {
196
+ this.isDragging = false;
197
+ this.canvas.style.cursor = this.editMode ? 'crosshair' : 'move';
198
+ }
199
+ handleWheel(e) {
200
+ e.preventDefault();
201
+ const delta = e.deltaY > 0 ? 0.9 : 1.1;
202
+ this.scale *= delta;
203
+ this.scale = Math.max(0.5, Math.min(2, this.scale));
204
+ }
205
+
206
+ handleClick(e) {
207
+ if (this.hoveredNode) {
208
+ this.selectedNode = this.hoveredNode;
209
+ this.showNodeInfo(this.selectedNode);
210
+ }
211
+ }
212
+ showNodeInfo(node) {
213
+ const infoPanel = document.getElementById('infoPanel');
214
+ const nodeInfo = document.getElementById('nodeInfo');
215
+
216
+ infoPanel.classList.remove('hidden');
217
+ document.getElementById('editPanel').classList.add('hidden');
218
+
219
+ let html = `
220
+ <div class="space-y-2">
221
+ <p><span class="text-gray-400">ID:</span> <span class="font-medium">${node.id}</span></p>
222
+ <p><span class="text-gray-400">Label:</span> <span class="font-medium">${node.label}</span></p>
223
+ <p><span class="text-gray-400">Type:</span> <span class="font-medium">${node.data.type || 'custom'}</span></p>
224
+ `;
225
+
226
+ if (node.data.person) {
227
+ html += `<p><span class="text-gray-400">Person:</span> <span class="font-medium">${node.data.person}</span></p>`;
228
+ }
229
+
230
+ if (node.data.operation) {
231
+ html += `<p><span class="text-gray-400">Operation:</span> <span class="font-medium">${node.data.operation}</span></p>`;
232
+ }
233
+
234
+ if (node.data.result) {
235
+ html += `<p><span class="text-gray-400">Result:</span> <span class="font-medium text-green-400">${node.data.result}</span></p>`;
236
+ }
237
+
238
+ if (node.data.confidence) {
239
+ html += `<p><span class="text-gray-400">Confidence:</span> <span class="font-medium">${node.data.confidence}%</span></p>`;
240
+ }
241
+
242
+ html += `</div>`;
243
+ nodeInfo.innerHTML = html;
244
+ }
245
+
246
+ showEditPanel(node) {
247
+ const editPanel = document.getElementById('editPanel');
248
+ document.getElementById('infoPanel').classList.add('hidden');
249
+ editPanel.classList.remove('hidden');
250
+
251
+ document.getElementById('nodeLabel').value = node.label;
252
+ document.getElementById('nodeType').value = node.data.type || 'custom';
253
+ document.getElementById('nodeColor').value = node.color;
254
+ }
255
+
256
+ addNodeAt(x, y) {
257
+ const newNode = {
258
+ id: 'node_' + this.nodeIdCounter++,
259
+ x: x,
260
+ y: y,
261
+ radius: 35,
262
+ color: '#' + Math.floor(Math.random()*16777215).toString(16),
263
+ label: 'New Node',
264
+ data: { type: 'custom' }
265
+ };
266
+ this.nodes.push(newNode);
267
+ this.selectedNode = newNode;
268
+ this.showEditPanel(newNode);
269
+ }
270
+
271
+ deleteNode(nodeId) {
272
+ this.nodes = this.nodes.filter(n => n.id !== nodeId);
273
+ this.edges = this.edges.filter(e => e.from !== nodeId && e.to !== nodeId);
274
+ if (this.selectedNode && this.selectedNode.id === nodeId) {
275
+ this.selectedNode = null;
276
+ document.getElementById('editPanel').classList.add('hidden');
277
+ }
278
+ }
279
+ draw() {
280
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
281
+
282
+ this.ctx.save();
283
+ this.ctx.translate(this.offset.x, this.offset.y);
284
+ this.ctx.scale(this.scale, this.scale);
285
+
286
+ // Draw edges
287
+ this.ctx.strokeStyle = 'rgba(168, 85, 247, 0.3)';
288
+ this.ctx.lineWidth = 2;
289
+ for (const edge of this.edges) {
290
+ const fromNode = this.nodes.find(n => n.id === edge.from);
291
+ const toNode = this.nodes.find(n => n.id === edge.to);
292
+
293
+ if (fromNode && toNode) {
294
+ this.drawArrow(fromNode, toNode, edge.label);
295
+ }
296
+ }
297
+
298
+ // Draw temporary connection line
299
+ if (this.connectingMode && this.connectingFrom && this.tempConnection) {
300
+ this.ctx.strokeStyle = '#10b981';
301
+ this.ctx.lineWidth = 3;
302
+ this.ctx.setLineDash([5, 5]);
303
+ this.ctx.beginPath();
304
+ this.ctx.moveTo(this.connectingFrom.x, this.connectingFrom.y);
305
+ this.ctx.lineTo(this.tempConnection.x, this.tempConnection.y);
306
+ this.ctx.stroke();
307
+ this.ctx.setLineDash([]);
308
+ }
309
+
310
+ // Draw nodes
311
+ for (const node of this.nodes) {
312
+ this.drawNode(node);
313
+ }
314
+
315
+ this.ctx.restore();
316
+ }
317
+ drawNode(node) {
318
+ const isHovered = this.hoveredNode === node;
319
+ const isSelected = this.selectedNode === node;
320
+ const isConnectingFrom = this.connectingFrom === node;
321
+
322
+ // Node glow effect
323
+ if (isHovered || isSelected || isConnectingFrom) {
324
+ const gradient = this.ctx.createRadialGradient(node.x, node.y, 0, node.x, node.y, node.radius * 2);
325
+ gradient.addColorStop(0, (isConnectingFrom ? '#10b981' : node.color) + '60');
326
+ gradient.addColorStop(1, 'transparent');
327
+ this.ctx.fillStyle = gradient;
328
+ this.ctx.beginPath();
329
+ this.ctx.arc(node.x, node.y, node.radius * 2, 0, Math.PI * 2);
330
+ this.ctx.fill();
331
+ }
332
+
333
+ // Node circle
334
+ this.ctx.fillStyle = node.color;
335
+ this.ctx.beginPath();
336
+ this.ctx.arc(node.x, node.y, isHovered ? node.radius * 1.1 : node.radius, 0, Math.PI * 2);
337
+ this.ctx.fill();
338
+
339
+ // Node border
340
+ if (isConnectingFrom) {
341
+ this.ctx.strokeStyle = '#10b981';
342
+ this.ctx.lineWidth = 4;
343
+ } else if (isSelected) {
344
+ this.ctx.strokeStyle = '#fff';
345
+ this.ctx.lineWidth = 3;
346
+ } else {
347
+ this.ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
348
+ this.ctx.lineWidth = 2;
349
+ }
350
+ this.ctx.stroke();
351
+
352
+ // Node label
353
+ this.ctx.fillStyle = '#fff';
354
+ this.ctx.font = 'bold 12px system-ui';
355
+ this.ctx.textAlign = 'center';
356
+ this.ctx.textBaseline = 'middle';
357
+ this.ctx.fillText(node.label, node.x, node.y);
358
+ }
359
+ drawArrow(fromNode, toNode, label) {
360
+ const dx = toNode.x - fromNode.x;
361
+ const dy = toNode.y - fromNode.y;
362
+ const angle = Math.atan2(dy, dx);
363
+
364
+ const startX = fromNode.x + fromNode.radius * Math.cos(angle);
365
+ const startY = fromNode.y + fromNode.radius * Math.sin(angle);
366
+ const endX = toNode.x - toNode.radius * Math.cos(angle);
367
+ const endY = toNode.y - toNode.radius * Math.sin(angle);
368
+
369
+ // Draw line
370
+ this.ctx.beginPath();
371
+ this.ctx.moveTo(startX, startY);
372
+ this.ctx.lineTo(endX, endY);
373
+ this.ctx.stroke();
374
+
375
+ // Draw arrowhead
376
+ const arrowLength = 15;
377
+ const arrowAngle = Math.PI / 6;
378
+
379
+ this.ctx.beginPath();
380
+ this.ctx.moveTo(endX, endY);
381
+ this.ctx.lineTo(
382
+ endX - arrowLength * Math.cos(angle - arrowAngle),
383
+ endY - arrowLength * Math.sin(angle - arrowAngle)
384
+ );
385
+ this.ctx.moveTo(endX, endY);
386
+ this.ctx.lineTo(
387
+ endX - arrowLength * Math.cos(angle + arrowAngle),
388
+ endY - arrowLength * Math.sin(angle + arrowAngle)
389
+ );
390
+ this.ctx.stroke();
391
+
392
+ // Draw label
393
+ const midX = (startX + endX) / 2;
394
+ const midY = (startY + endY) / 2;
395
+
396
+ this.ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
397
+ this.ctx.font = '10px system-ui';
398
+ this.ctx.textAlign = 'center';
399
+ this.ctx.fillText(label, midX, midY - 10);
400
+ }
401
+
402
+ animate() {
403
+ this.draw();
404
+ requestAnimationFrame(() => this.animate());
405
+ }
406
+ }
407
+
408
+ // Initialize graph
409
+ let graph;
410
+
411
+ document.addEventListener('DOMContentLoaded', () => {
412
+ graph = new DirectedGraph('graphCanvas');
413
+ });
414
+ // Control functions
415
+ function zoomIn() {
416
+ if (graph) {
417
+ graph.scale *= 1.2;
418
+ graph.scale = Math.min(2, graph.scale);
419
+ }
420
+ }
421
+
422
+ function zoomOut() {
423
+ if (graph) {
424
+ graph.scale *= 0.8;
425
+ graph.scale = Math.max(0.5, graph.scale);
426
+ }
427
+ }
428
+
429
+ function fitToScreen() {
430
+ if (graph) {
431
+ graph.scale = 1;
432
+ graph.offset = { x: 0, y: 0 };
433
+ }
434
+ }
435
+
436
+ function resetGraph() {
437
+ if (graph) {
438
+ graph.selectedNode = null;
439
+ graph.hoveredNode = null;
440
+ graph.connectingMode = false;
441
+ graph.connectingFrom = null;
442
+ document.getElementById('infoPanel').classList.add('hidden');
443
+ document.getElementById('editPanel').classList.add('hidden');
444
+ fitToScreen();
445
+ }
446
+ }
447
+
448
+ function toggleFullscreen() {
449
+ const graphContainer = document.getElementById('graphCanvas').parentElement;
450
+ if (!document.fullscreenElement) {
451
+ graphContainer.requestFullscreen();
452
+ } else {
453
+ document.exitFullscreen();
454
+ }
455
+ }
456
+
457
+ function toggleEditMode() {
458
+ if (!graph) return;
459
+
460
+ graph.editMode = !graph.editMode;
461
+ const editBtn = document.getElementById('editModeBtn');
462
+ const editIndicator = document.getElementById('editIndicator');
463
+
464
+ if (graph.editMode) {
465
+ editBtn.classList.remove('bg-purple-600', 'hover:bg-purple-700');
466
+ editBtn.classList.add('bg-orange-600', 'hover:bg-orange-700');
467
+ editBtn.querySelector('span').textContent = 'Exit Edit';
468
+ editIndicator.classList.remove('hidden');
469
+ graph.canvas.classList.add('edit-mode-cursor');
470
+ } else {
471
+ editBtn.classList.remove('bg-orange-600', 'hover:bg-orange-700');
472
+ editBtn.classList.add('bg-purple-600', 'hover:bg-purple-700');
473
+ editBtn.querySelector('span').textContent = 'Edit Mode';
474
+ editIndicator.classList.add('hidden');
475
+ graph.canvas.classList.remove('edit-mode-cursor');
476
+ graph.connectingMode = false;
477
+ graph.connectingFrom = null;
478
+ document.getElementById('editPanel').classList.add('hidden');
479
+ }
480
+ }
481
+
482
+ function addNode() {
483
+ if (graph && graph.editMode) {
484
+ const centerX = (graph.canvas.width / 2 - graph.offset.x) / graph.scale;
485
+ const centerY = (graph.canvas.height / 2 - graph.offset.y) / graph.scale;
486
+ graph.addNodeAt(centerX, centerY);
487
+ }
488
+ }
489
+
490
+ function deleteSelectedNode() {
491
+ if (graph && graph.selectedNode && graph.editMode) {
492
+ graph.deleteNode(graph.selectedNode.id);
493
+ }
494
+ }
495
+
496
+ function connectNodes() {
497
+ if (graph && graph.editMode) {
498
+ graph.connectingMode = !graph.connectingMode;
499
+ if (graph.connectingMode) {
500
+ graph.canvas.style.cursor = 'crosshair';
501
+ } else {
502
+ graph.connectingFrom = null;
503
+ graph.canvas.style.cursor = 'crosshair';
504
+ }
505
+ }
506
+ }
507
+
508
+ function saveNodeChanges() {
509
+ if (graph && graph.selectedNode) {
510
+ graph.selectedNode.label = document.getElementById('nodeLabel').value;
511
+ graph.selectedNode.data.type = document.getElementById('nodeType').value;
512
+ graph.selectedNode.color = document.getElementById('nodeColor').value;
513
+ document.getElementById('editPanel').classList.add('hidden');
514
+ }
515
+ }
516
+
517
+ function cancelEdit() {
518
+ document.getElementById('editPanel').classList.add('hidden');
519
+ if (graph) {
520
+ graph.selectedNode = null;
521
+ }
522
+ }
523
+
524
+ function highlightNode(nodeId) {
525
+ if (graph) {
526
+ const node = graph.nodes.find(n => n.id === nodeId ||
527
+ (n.data && n.data.source === nodeId) ||
528
+ (n.id === 'extracted1' && nodeId === 'doc1') ||
529
+ (n.id === 'extracted2' && nodeId === 'doc2'));
530
+
531
+ if (node) {
532
+ graph.selectedNode = node;
533
+ graph.showNodeInfo(node);
534
+
535
+ // Pan to node
536
+ graph.offset.x = -node.x * graph.scale + graph.canvas.width / 2;
537
+ graph.offset.y = -node.y * graph.scale + graph.canvas.height / 2;
538
+ }
539
+ }
540
+ }
style.css CHANGED
@@ -1,28 +1,128 @@
1
- body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
 
4
  }
5
 
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
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
+ /* Custom animations */
2
+ @keyframes pulse-glow {
3
+ 0%, 100% { box-shadow: 0 0 20px rgba(168, 85, 247, 0.5); }
4
+ 50% { box-shadow: 0 0 40px rgba(168, 85, 247, 0.8); }
5
  }
6
 
7
+ @keyframes float {
8
+ 0%, 100% { transform: translateY(0px); }
9
+ 50% { transform: translateY(-10px); }
10
  }
11
 
12
+ .pulse-glow {
13
+ animation: pulse-glow 2s infinite;
 
 
 
14
  }
15
 
16
+ .float-animation {
17
+ animation: float 3s ease-in-out infinite;
 
 
 
 
18
  }
19
 
20
+ /* Glassmorphism effects */
21
+ .glass {
22
+ background: rgba(255, 255, 255, 0.05);
23
+ backdrop-filter: blur(10px);
24
+ border: 1px solid rgba(255, 255, 255, 0.1);
25
+ }
26
+
27
+ /* Custom scrollbar */
28
+ ::-webkit-scrollbar {
29
+ width: 8px;
30
+ height: 8px;
31
+ }
32
+
33
+ ::-webkit-scrollbar-track {
34
+ background: rgba(0, 0, 0, 0.3);
35
+ border-radius: 4px;
36
+ }
37
+
38
+ ::-webkit-scrollbar-thumb {
39
+ background: rgba(168, 85, 247, 0.5);
40
+ border-radius: 4px;
41
+ }
42
+
43
+ ::-webkit-scrollbar-thumb:hover {
44
+ background: rgba(168, 85, 247, 0.7);
45
+ }
46
+
47
+ /* Node hover effects */
48
+ .node-hover {
49
+ filter: brightness(1.2);
50
+ transition: filter 0.3s ease;
51
+ }
52
+
53
+ /* Connection lines */
54
+ .connection-line {
55
+ stroke: rgba(168, 85, 247, 0.6);
56
+ stroke-width: 2;
57
+ fill: none;
58
+ stroke-dasharray: 5, 5;
59
+ animation: dash 20s linear infinite;
60
+ }
61
+
62
+ @keyframes dash {
63
+ to {
64
+ stroke-dashoffset: -100;
65
+ }
66
+ }
67
+
68
+ /* Active node highlight */
69
+ .active-node {
70
+ filter: drop-shadow(0 0 20px rgba(168, 85, 247, 0.8));
71
+ transform: scale(1.1);
72
+ transition: all 0.3s ease;
73
+ }
74
+
75
+ /* Gradient text */
76
+ .gradient-text {
77
+ background: linear-gradient(90deg, #a855f7, #ec4899);
78
+ -webkit-background-clip: text;
79
+ -webkit-text-fill-color: transparent;
80
+ background-clip: text;
81
+ }
82
+
83
+ /* Smooth transitions */
84
+ * {
85
+ transition: all 0.3s ease;
86
+ }
87
+ /* Canvas container */
88
+ #graphCanvas {
89
+ display: block;
90
+ background: radial-gradient(circle at center, rgba(168, 85, 247, 0.1) 0%, transparent 70%);
91
+ }
92
+
93
+ /* Edit mode styles */
94
+ .edit-mode-cursor {
95
+ cursor: crosshair !important;
96
+ }
97
+
98
+ .connecting-line {
99
+ stroke: #10b981;
100
+ stroke-width: 3;
101
+ stroke-dasharray: 5, 5;
102
+ animation: dash 1s linear infinite;
103
+ }
104
+
105
+ .selected-for-connection {
106
+ filter: drop-shadow(0 0 15px #10b981);
107
+ animation: pulse-glow 1s infinite;
108
+ }
109
+
110
+ /* Edit panel styles */
111
+ #editPanel input, #editPanel select {
112
+ color: white;
113
+ }
114
+
115
+ #editPanel input[type="color"] {
116
+ -webkit-appearance: none;
117
+ border: none;
118
+ width: 100%;
119
+ }
120
+
121
+ #editPanel input[type="color"]::-webkit-color-swatch-wrapper {
122
+ padding: 0;
123
+ }
124
+
125
+ #editPanel input[type="color"]::-webkit-color-swatch {
126
+ border: 1px solid rgba(255, 255, 255, 0.2);
127
+ border-radius: 4px;
128
  }