hesamation commited on
Commit
35e5b82
·
1 Parent(s): 36ae7a7

initial app

Browse files
.gitignore CHANGED
@@ -1 +1,2 @@
1
- instructions.md
 
 
1
+ instructions.md
2
+ Model-Atlas
README.md ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Daily Paper Atlas
2
+
3
+ A visualization tool for exploring papers and authors network graphs, inspired by the [Model Atlas](https://arxiv.org/abs/2503.10633) paper.
4
+
5
+ ## Features
6
+
7
+ - Interactive graph visualization of papers and authors
8
+ - Search functionality to find specific nodes
9
+ - Color coding by node attributes
10
+ - Group filtering
11
+ - Node details panel
12
+ - Responsive zoom and navigation
13
+
14
+ ## Setup
15
+
16
+ 1. Convert your GRAPHML file to JSON format:
17
+
18
+ ```bash
19
+ python graphml_to_json.py graph.graphml paper_atlas_data.json paper_atlas_data.json.gz
20
+ ```
21
+
22
+ 2. Start the web server:
23
+
24
+ ```bash
25
+ python app.py
26
+ ```
27
+
28
+ 3. Open your browser to http://localhost:7860
29
+
30
+ ## Technology Stack
31
+
32
+ - Python for server and data processing
33
+ - SigmaJS for graph visualization
34
+ - jQuery for DOM manipulation
35
+ - Pako.js for client-side decompression
36
+ - HTML/CSS for the interface
37
+
38
+ ## Data Format
39
+
40
+ The application expects a GraphML file that will be converted to a JSON format suitable for SigmaJS. The GraphML file should include node attributes like:
41
+ - label
42
+ - type (e.g., "author", "paper")
43
+ - x, y (coordinates for positioning)
44
+ - size
45
+ - r, g, b (color values)
46
+
47
+ ## Credits
48
+
49
+ This visualization is inspired by the work described in ["Charting and Navigating Hugging Face's Model Atlas"](https://arxiv.org/abs/2503.10633) by Eliahu Horwitz, Nitzan Kurer, Jonathan Kahana, Liel Amar, and Yedid Hoshen.
app.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import requests
4
+ from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
5
+ from urllib.parse import parse_qs, urlparse
6
+
7
+
8
+ class RequestHandler(SimpleHTTPRequestHandler):
9
+ def do_GET(self):
10
+ if self.path == "/":
11
+ self.path = "index.html"
12
+
13
+ return SimpleHTTPRequestHandler.do_GET(self)
14
+
15
+ else:
16
+ return SimpleHTTPRequestHandler.do_GET(self)
17
+
18
+
19
+ print("Starting server on port 7860... Open http://localhost:7860 in your browser")
20
+ server = ThreadingHTTPServer(("", 7860), RequestHandler)
21
+ server.serve_forever()
check_json.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ import json
3
+ import gzip
4
+ import os
5
+
6
+ # File paths
7
+ json_file = 'paper_atlas_data.json'
8
+ compressed_file = 'paper_atlas_data.json.gz'
9
+
10
+ # Check if we need to decompress
11
+ if os.path.exists(compressed_file) and (not os.path.exists(json_file) or os.path.getsize(json_file) == 0):
12
+ print(f"Decompressing {compressed_file} to {json_file}")
13
+ with gzip.open(compressed_file, 'rb') as f_in:
14
+ with open(json_file, 'wb') as f_out:
15
+ f_out.write(f_in.read())
16
+
17
+ # Check if JSON file exists and has content
18
+ if not os.path.exists(json_file) or os.path.getsize(json_file) == 0:
19
+ print(f"Error: {json_file} doesn't exist or is empty!")
20
+ exit(1)
21
+
22
+ # Try to load the JSON data
23
+ try:
24
+ with open(json_file, 'r') as f:
25
+ data = json.load(f)
26
+
27
+ # Check structure
28
+ if 'nodes' not in data or 'edges' not in data:
29
+ print("Error: JSON data doesn't have expected 'nodes' and 'edges' properties!")
30
+ exit(1)
31
+
32
+ # Add x,y coordinates to nodes that don't have them
33
+ nodes_fixed = 0
34
+ for node in data['nodes']:
35
+ if 'x' not in node or 'y' not in node:
36
+ # Assign random coordinates
37
+ import random
38
+ node['x'] = random.uniform(-10, 10)
39
+ node['y'] = random.uniform(-10, 10)
40
+ nodes_fixed += 1
41
+
42
+ if nodes_fixed > 0:
43
+ print(f"Fixed {nodes_fixed} nodes without coordinates")
44
+
45
+ # Save the fixed JSON
46
+ with open(json_file, 'w') as f:
47
+ json.dump(data, f)
48
+
49
+ # Update the compressed file
50
+ with open(json_file, 'rb') as f_in:
51
+ with gzip.open(compressed_file, 'wb') as f_out:
52
+ f_out.write(f_in.read())
53
+
54
+ print("Updated JSON files with fixes")
55
+
56
+ print(f"JSON data is valid with {len(data['nodes'])} nodes and {len(data['edges'])} edges")
57
+
58
+ except json.JSONDecodeError as e:
59
+ print(f"Error: Invalid JSON format: {e}")
60
+ exit(1)
61
+ except Exception as e:
62
+ print(f"Error: {e}")
63
+ exit(1)
config.json ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "type": "network",
3
+ "version": "1.0",
4
+ "data": "paper_atlas_data.json",
5
+ "logo": {
6
+ "file": "",
7
+ "link": "",
8
+ "text": "Daily Paper Atlas"
9
+ },
10
+ "text": {
11
+ "more": "<p><strong>About:</strong> This visualization represents a network of research papers and authors, showing connections and relationships between them. The network is based on the graph.graphml file and converted to a format suitable for web visualization.</p>",
12
+ "intro": "A visualization of research papers and authors network. This interactive map shows relationships between papers and the researchers who created them.",
13
+ "title": "Daily Paper Atlas"
14
+ },
15
+ "legend": {
16
+ "edgeLabel": "Connection",
17
+ "colorLabel": "Node Type",
18
+ "nodeLabel": "Paper/Author"
19
+ },
20
+ "features": {
21
+ "search": true,
22
+ "groupSelectorAttribute": "type",
23
+ "hoverBehavior": "dim",
24
+ "zoomButtons": true,
25
+ "fancyBox": true,
26
+ "forceAtlas2": true,
27
+ "forceAtlas2Time": 5000,
28
+ "defaultColorAttribute": "type",
29
+ "labelThreshold": 8
30
+ },
31
+ "informationPanel": {
32
+ "groupByEdgeDirection": false,
33
+ "imageAttribute": false
34
+ },
35
+ "sigma": {
36
+ "drawingProperties": {
37
+ "defaultEdgeType": "curve",
38
+ "defaultHoverLabelBGColor": "#002147",
39
+ "defaultLabelBGColor": "#ddd",
40
+ "activeFontStyle": "bold",
41
+ "defaultLabelColor": "#000",
42
+ "labelThreshold": 8,
43
+ "defaultLabelHoverColor": "#fff",
44
+ "fontStyle": "bold",
45
+ "hoverFontStyle": "bold",
46
+ "defaultLabelSize": 14
47
+ },
48
+ "graphProperties": {
49
+ "maxEdgeSize": 2,
50
+ "minEdgeSize": 0.5,
51
+ "minNodeSize": 2,
52
+ "maxNodeSize": 10
53
+ },
54
+ "mouseProperties": {
55
+ "maxRatio": 20,
56
+ "minRatio": 0.75
57
+ }
58
+ },
59
+ "nodeTypes": {
60
+ "paper": { "color": "#2ca02c", "size": 3 },
61
+ "author": { "color": "#9467bd", "size": 5 },
62
+ "organization": { "color": "#1f77b4", "size": 4 },
63
+ "unknown": { "color": "#ff7f0e", "size": 3 }
64
+ },
65
+ "colorPalette": [
66
+ "#9467bd", "#2ca02c", "#1f77b4", "#17becf",
67
+ "#ff7f0e", "#d62728", "#8c564b", "#e377c2",
68
+ "#bcbd22", "#7f7f7f"
69
+ ]
70
+ }
css/style.css ADDED
@@ -0,0 +1,489 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * {
2
+ padding: 0px;
3
+ margin: 0px;
4
+ }
5
+
6
+ .cf:before, .cf:after {
7
+ content: "";
8
+ display: table;
9
+ }
10
+
11
+ .cf:after {
12
+ clear: both;
13
+ }
14
+
15
+ .cf {
16
+ zoom: 1;
17
+ }
18
+
19
+ html, body {
20
+ width: 100%;
21
+ height: 100%;
22
+ margin: 0px;
23
+ padding: 0px;
24
+ font-size: 12px;
25
+ font-family: Helvetica, Arial, sans-serif;
26
+ line-height: 1.25em;
27
+ }
28
+
29
+ .sigma-parent {
30
+ position: relative;
31
+ height: 100%;
32
+ }
33
+
34
+ .sigma-expand {
35
+ position: absolute;
36
+ width: 100%;
37
+ height: 100%;
38
+ top: 0;
39
+ left: 0;
40
+ background-color: #fff;
41
+ background-position: center center;
42
+ background-repeat: no-repeat;
43
+ -webkit-touch-callout: none;
44
+ -webkit-user-select: none;
45
+ -khtml-user-select: none;
46
+ -moz-user-select: none;
47
+ -ms-user-select: none;
48
+ user-select: none;
49
+ }
50
+
51
+ canvas#sigma_bg_1 {
52
+ display: none;
53
+ }
54
+
55
+ #attributepane {
56
+ display: none;
57
+ position: absolute;
58
+ height: auto;
59
+ right: 0;
60
+ top: 0;
61
+ bottom: 0;
62
+ width: 240px;
63
+ background-color: #fff;
64
+ margin: 0;
65
+ z-index: 1;
66
+ border-left: 1px solid #ccc;
67
+ overflow: auto;
68
+ padding: 0px;
69
+ padding-bottom: 60px;
70
+ box-shadow: -3px 0px 8px rgba(0, 0, 0, 0.15);
71
+ }
72
+
73
+ #attributepane .text {
74
+ padding: 10px;
75
+ }
76
+
77
+ #attributepane .headertext {
78
+ color: #fff;
79
+ background-color: #444;
80
+ margin: 0;
81
+ padding: 10px;
82
+ font-weight: bold;
83
+ position: sticky;
84
+ top: 0;
85
+ z-index: 2;
86
+ }
87
+
88
+ #attributepane .returntext {
89
+ color: #1f77b4;
90
+ cursor: pointer;
91
+ margin-top: 10px;
92
+ padding: 5px;
93
+ border-top: 1px solid #ddd;
94
+ font-weight: bold;
95
+ }
96
+
97
+ #attributepane .returntext:hover {
98
+ text-decoration: underline;
99
+ }
100
+
101
+ .nodeattributes {
102
+ padding: 5px 0;
103
+ }
104
+
105
+ .nodeattributes .name {
106
+ font-size: 16px;
107
+ font-weight: bold;
108
+ margin-bottom: 10px;
109
+ }
110
+
111
+ .nodeattributes .data div {
112
+ margin-bottom: 5px;
113
+ }
114
+
115
+ .nodeattributes .p {
116
+ font-weight: bold;
117
+ margin: 10px 0 5px 0;
118
+ }
119
+
120
+ .nodeattributes .link ul {
121
+ list-style: none;
122
+ margin: 0;
123
+ padding: 0;
124
+ }
125
+
126
+ .nodeattributes .link ul li {
127
+ margin-bottom: 5px;
128
+ }
129
+
130
+ .nodeattributes .link ul li a {
131
+ color: #1f77b4;
132
+ text-decoration: none;
133
+ }
134
+
135
+ .nodeattributes .link ul li a:hover {
136
+ text-decoration: underline;
137
+ }
138
+
139
+ #attributepane .close {
140
+ padding-left: 14px;
141
+ margin-top: 10px;
142
+ }
143
+
144
+ #attributepane .close .c {
145
+ border-top: 2px solid #999;
146
+ padding: 10px 0 14px 0;
147
+ }
148
+
149
+ #attributepane .nodeattributes {
150
+ display: block;
151
+ height: 85%;
152
+ overflow-y: auto;
153
+ overflow-x: hidden;
154
+ border-bottom: 1px solid #999;
155
+ }
156
+
157
+ #attributepane .name {
158
+ font-size: 14px;
159
+ cursor: default;
160
+ padding-bottom: 10px;
161
+ padding-top: 18px;
162
+ font-weight: bold;
163
+ }
164
+
165
+ #attributepane .data {
166
+ margin-bottom: 10px;
167
+ }
168
+
169
+ #attributepane .link {
170
+ padding: 0 0 0 4px;
171
+ }
172
+
173
+ #attributepane .link li {
174
+ padding-top: 2px;
175
+ cursor: pointer;
176
+ list-style: none;
177
+ }
178
+
179
+ #attributepane .p {
180
+ padding-top: 10px;
181
+ font-weight: bold;
182
+ font-size: 14px;
183
+ }
184
+
185
+ .left-close {
186
+ cursor: pointer;
187
+ padding-left: 31px;
188
+ line-height: 36px;
189
+ background-repeat: no-repeat;
190
+ margin-bottom: 25px;
191
+ font-weight: bold;
192
+ font-size: 14px;
193
+ }
194
+
195
+ #developercontainer {
196
+ position: fixed;
197
+ bottom: 0;
198
+ left: 0;
199
+ width: 100%;
200
+ text-align: left;
201
+ font-size: 11px;
202
+ color: #777;
203
+ background: rgba(255, 255, 255, 0.8);
204
+ z-index: 20;
205
+ padding: 5px 10px;
206
+ border-top: 1px solid #ddd;
207
+ }
208
+
209
+ #maintitle {
210
+ width: 100%;
211
+ text-align: center;
212
+ margin-top: 10px;
213
+ }
214
+
215
+ #maintitle h1 {
216
+ font-size: 20px;
217
+ }
218
+
219
+ #mainpanel {
220
+ position: absolute;
221
+ top: 0;
222
+ left: 0;
223
+ bottom: auto;
224
+ width: 240px;
225
+ height: auto;
226
+ z-index: 20;
227
+ background: rgba(255, 255, 255, 0.95);
228
+ border-right: 1px solid #ccc;
229
+ box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.2);
230
+ overflow: auto;
231
+ padding: 0;
232
+ }
233
+
234
+ #mainpanel .col {
235
+ padding: 15px;
236
+ border-bottom: 1px solid #eee;
237
+ }
238
+
239
+ #title {
240
+ font-weight: bold;
241
+ text-align: left;
242
+ font-size: 18px;
243
+ margin-bottom: 5px;
244
+ }
245
+
246
+ #titletext {
247
+ padding: 5px 0;
248
+ }
249
+
250
+ .info {
251
+ padding: 5px 0;
252
+ }
253
+
254
+ .info a {
255
+ color: #0000CC;
256
+ text-decoration: none;
257
+ }
258
+
259
+ .info a:hover {
260
+ text-decoration: underline;
261
+ }
262
+
263
+ #legend {
264
+ padding: 10px 0;
265
+ }
266
+
267
+ .legenditem {
268
+ display: flex;
269
+ align-items: center;
270
+ margin-bottom: 5px;
271
+ }
272
+
273
+ .legendcolor {
274
+ display: inline-block;
275
+ width: 12px;
276
+ height: 12px;
277
+ margin-right: 8px;
278
+ border-radius: 50%;
279
+ }
280
+
281
+ .box {
282
+ margin-bottom: 10px;
283
+ }
284
+
285
+ h2 {
286
+ font-size: 14px;
287
+ font-weight: bold;
288
+ margin-bottom: 10px;
289
+ }
290
+
291
+ #search {
292
+ margin-top: 10px;
293
+ }
294
+
295
+ #search input {
296
+ border: 1px solid #ccc;
297
+ padding: 5px 7px;
298
+ width: 80%;
299
+ box-sizing: border-box;
300
+ margin-bottom: 5px;
301
+ }
302
+
303
+ #search-button {
304
+ display: inline-block;
305
+ background: #f5f5f5;
306
+ border: 1px solid #ccc;
307
+ padding: 3px 8px;
308
+ cursor: pointer;
309
+ margin-left: 5px;
310
+ vertical-align: middle;
311
+ }
312
+
313
+ .state {
314
+ display: inline-block;
315
+ width: 14px;
316
+ height: 14px;
317
+ background-position: -131px -13px;
318
+ margin-right: 5px;
319
+ cursor: pointer;
320
+ }
321
+
322
+ .results {
323
+ margin-top: 10px;
324
+ border: 1px solid #eee;
325
+ max-height: 200px;
326
+ overflow-y: auto;
327
+ }
328
+
329
+ .results a {
330
+ display: block;
331
+ padding: 5px;
332
+ text-decoration: none;
333
+ color: #000;
334
+ border-bottom: 1px solid #eee;
335
+ }
336
+
337
+ .results a:hover {
338
+ background: #f5f5f5;
339
+ }
340
+
341
+ .select-wrapper {
342
+ position: relative;
343
+ margin-bottom: 10px;
344
+ }
345
+
346
+ .select-wrapper select {
347
+ width: 100%;
348
+ padding: 5px;
349
+ border: 1px solid #ccc;
350
+ background-color: #fff;
351
+ appearance: none;
352
+ -webkit-appearance: none;
353
+ -moz-appearance: none;
354
+ cursor: pointer;
355
+ }
356
+
357
+ .select-wrapper:after {
358
+ content: "";
359
+ position: absolute;
360
+ right: 10px;
361
+ top: 50%;
362
+ transform: translateY(-50%);
363
+ border-style: solid;
364
+ border-width: 5px 5px 0 5px;
365
+ border-color: #999 transparent transparent transparent;
366
+ pointer-events: none;
367
+ }
368
+
369
+ #attributeselect, #coloringselect, #atlasselect {
370
+ margin-top: 15px;
371
+ padding-bottom: 10px;
372
+ }
373
+
374
+ .select {
375
+ border: 1px solid #ccc;
376
+ padding: 5px 7px;
377
+ width: 100%;
378
+ box-sizing: border-box;
379
+ margin-bottom: 5px;
380
+ cursor: pointer;
381
+ background: #f5f5f5;
382
+ position: relative;
383
+ }
384
+
385
+ .list {
386
+ margin-top: 5px;
387
+ max-height: 200px;
388
+ overflow-y: auto;
389
+ display: none;
390
+ border: 1px solid #eee;
391
+ background: #fff;
392
+ }
393
+
394
+ .list a {
395
+ display: block;
396
+ padding: 5px;
397
+ text-decoration: none;
398
+ color: #000;
399
+ border-bottom: 1px solid #eee;
400
+ }
401
+
402
+ .list a:hover {
403
+ background: #f5f5f5;
404
+ }
405
+
406
+ #colorLegend {
407
+ margin-top: 10px;
408
+ }
409
+
410
+ #colorLegend div {
411
+ display: flex;
412
+ align-items: center;
413
+ margin-bottom: 5px;
414
+ }
415
+
416
+ #colorLegend span {
417
+ display: inline-block;
418
+ width: 12px;
419
+ height: 12px;
420
+ border-radius: 50%;
421
+ margin-right: 8px;
422
+ }
423
+
424
+ #zoom {
425
+ position: absolute;
426
+ bottom: 40px;
427
+ right: 10px;
428
+ z-index: 20;
429
+ background: rgba(255, 255, 255, 0.8);
430
+ border: 1px solid #ccc;
431
+ border-radius: 3px;
432
+ padding: 5px;
433
+ }
434
+
435
+ #zoom .z {
436
+ display: inline-block;
437
+ width: 24px;
438
+ height: 24px;
439
+ border: 1px solid #ccc;
440
+ margin: 0 2px;
441
+ text-align: center;
442
+ cursor: pointer;
443
+ font-weight: bold;
444
+ font-size: 18px;
445
+ line-height: 24px;
446
+ border-radius: 3px;
447
+ background: #fff;
448
+ }
449
+
450
+ #zoom .z:hover {
451
+ background: #f5f5f5;
452
+ }
453
+
454
+ #zoom .z[rel="in"]:before {
455
+ content: "+";
456
+ }
457
+
458
+ #zoom .z[rel="out"]:before {
459
+ content: "-";
460
+ }
461
+
462
+ #zoom .z[rel="center"]:before {
463
+ content: "⌂";
464
+ }
465
+
466
+ #key-features {
467
+ padding-top: 10px;
468
+ }
469
+
470
+ #key-features ul {
471
+ margin-left: 20px;
472
+ margin-top: 5px;
473
+ }
474
+
475
+ #key-features li {
476
+ margin-bottom: 5px;
477
+ }
478
+
479
+ #node-info {
480
+ position: absolute;
481
+ background: rgba(255, 255, 255, 0.8);
482
+ padding: 5px 10px;
483
+ border: 1px solid #ccc;
484
+ border-radius: 3px;
485
+ font-size: 12px;
486
+ z-index: 10;
487
+ display: none;
488
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
489
+ }
css/tablet.css ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ #mainpanel {
2
+ width: 200px;
3
+ }
graphml_to_json.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ import json
3
+ import gzip
4
+ import xml.etree.ElementTree as ET
5
+ import sys
6
+ import os
7
+
8
+ def graphml_to_json(graphml_file, output_json, compressed_output=None):
9
+ """
10
+ Convert a GraphML file to SigmaJS-compatible JSON format
11
+ """
12
+ # Parse the GraphML file
13
+ print(f"Parsing GraphML file: {graphml_file}")
14
+ tree = ET.parse(graphml_file)
15
+ root = tree.getroot()
16
+
17
+ # Define the namespace
18
+ ns = {'graphml': 'http://graphml.graphdrawing.org/xmlns'}
19
+
20
+ # Extract the graph from the GraphML
21
+ graph = root.find('graphml:graph', ns)
22
+
23
+ if graph is None:
24
+ # Try without namespace
25
+ graph = root.find('graph')
26
+ if graph is None:
27
+ raise ValueError("Could not find graph element in GraphML file")
28
+
29
+ # Prepare the JSON structure
30
+ sigma_data = {
31
+ 'nodes': [],
32
+ 'edges': []
33
+ }
34
+
35
+ print("Processing nodes...")
36
+ node_count = 0
37
+ # Process nodes
38
+ for node in graph.findall('graphml:node', ns) or graph.findall('node'):
39
+ node_id = node.get('id')
40
+ node_data = {'id': node_id, 'attr': {'colors': {}}}
41
+
42
+ # Process node attributes
43
+ for data in node.findall('graphml:data', ns) or node.findall('data'):
44
+ key = data.get('key')
45
+ if key == 'label':
46
+ node_data['label'] = data.text
47
+ elif key == 'x':
48
+ node_data['x'] = float(data.text)
49
+ elif key == 'y':
50
+ node_data['y'] = float(data.text)
51
+ elif key == 'size':
52
+ node_data['size'] = float(data.text)
53
+ elif key == 'r':
54
+ # Find g and b values
55
+ g_elem = node.find(f'graphml:data[@key="g"]', ns) or node.find(f'data[@key="g"]')
56
+ b_elem = node.find(f'graphml:data[@key="b"]', ns) or node.find(f'data[@key="b"]')
57
+
58
+ if g_elem is not None and b_elem is not None:
59
+ node_data['color'] = f"rgb({data.text},{g_elem.text},{b_elem.text})"
60
+ elif key == 'type':
61
+ node_data['attr']['colors']['type'] = data.text
62
+ # Set a default color based on node type
63
+ if data.text == 'author':
64
+ node_data['color'] = 'rgb(154,150,229)'
65
+ elif data.text == 'paper':
66
+ node_data['color'] = 'rgb(229,150,154)'
67
+ else:
68
+ node_data['color'] = 'rgb(150,229,154)'
69
+
70
+ sigma_data['nodes'].append(node_data)
71
+ node_count += 1
72
+
73
+ print(f"Processed {node_count} nodes")
74
+
75
+ print("Processing edges...")
76
+ edge_count = 0
77
+ # Process edges
78
+ for edge in graph.findall('graphml:edge', ns) or graph.findall('edge'):
79
+ source = edge.get('source')
80
+ target = edge.get('target')
81
+
82
+ edge_data = {
83
+ 'id': f"e{edge_count}",
84
+ 'source': source,
85
+ 'target': target
86
+ }
87
+ edge_count += 1
88
+
89
+ # Process edge attributes
90
+ for data in edge.findall('graphml:data', ns) or edge.findall('data'):
91
+ key = data.get('key')
92
+ if key == 'weight':
93
+ edge_data['weight'] = float(data.text)
94
+ elif key == 'edgelabel':
95
+ edge_data['label'] = data.text
96
+
97
+ sigma_data['edges'].append(edge_data)
98
+
99
+ print(f"Processed {edge_count} edges")
100
+
101
+ # Write the JSON file
102
+ print(f"Writing JSON to {output_json}")
103
+ with open(output_json, 'w') as f:
104
+ json.dump(sigma_data, f)
105
+
106
+ # If compressed output is requested, create a gzipped version
107
+ if compressed_output:
108
+ print(f"Creating compressed file: {compressed_output}")
109
+ with open(output_json, 'rb') as f_in:
110
+ data = f_in.read()
111
+ # Write gzipped data with proper headers for web
112
+ with gzip.open(compressed_output, 'wb', compresslevel=9) as f_out:
113
+ f_out.write(data)
114
+
115
+ if __name__ == '__main__':
116
+ if len(sys.argv) < 3:
117
+ print("Usage: python graphml_to_json.py <input_graphml> <output_json> [compressed_output]")
118
+ sys.exit(1)
119
+
120
+ input_file = sys.argv[1]
121
+ output_file = sys.argv[2]
122
+ compressed_file = sys.argv[3] if len(sys.argv) > 3 else None
123
+
124
+ try:
125
+ graphml_to_json(input_file, output_file, compressed_file)
126
+ print(f"Conversion completed. JSON saved to {output_file}")
127
+ if compressed_file:
128
+ print(f"Compressed version saved to {compressed_file}")
129
+ except Exception as e:
130
+ print(f"Error during conversion: {e}")
131
+ sys.exit(1)
index.html ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-gb" lang="en" xmlns:og="http://opengraphprotocol.org/schema/" xmlns:fb="http://www.facebook.com/2008/fbml" itemscope itemtype="http://schema.org/Map">
3
+
4
+ <head>
5
+ <title>Daily Paper Atlas</title>
6
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
7
+ <meta charset="utf-8">
8
+ <meta name="viewport" content="width=device-width,height=device-height,initial-scale=1,user-scalable=no" />
9
+ <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
10
+ <meta name="description" content="A network visualization of research papers and authors">
11
+
12
+ <!-- Load jQuery first -->
13
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" type="text/javascript"></script>
14
+
15
+ <!-- Load Sigma.js (exact SigmaJS version used in Model-Atlas) -->
16
+ <script src="js/sigma/sigma.min.js" type="text/javascript"></script>
17
+ <script src="js/sigma/sigma.parseJson.js" type="text/javascript"></script>
18
+ <script src="js/sigma/plugins/sigma.plugins.filter.min.js" type="text/javascript"></script>
19
+
20
+ <!-- Load fancybox for popups -->
21
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/fancybox/3.5.7/jquery.fancybox.min.js" type="text/javascript"></script>
22
+
23
+ <!-- Load pako for decompression -->
24
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/pako/2.0.4/pako.min.js" type="text/javascript"></script>
25
+
26
+ <!-- CSS files -->
27
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fancybox/3.5.7/jquery.fancybox.min.css"/>
28
+ <link rel="stylesheet" href="css/style.css" type="text/css" media="screen" />
29
+ <link rel="stylesheet" media="screen and (max-height: 770px)" href="css/tablet.css" />
30
+
31
+ <!-- Our main JavaScript file - load this after the libraries -->
32
+ <script src="js/main.js" type="text/javascript"></script>
33
+
34
+ </head>
35
+
36
+ <body>
37
+ <div class="sigma-parent">
38
+ <div class="sigma-expand" id="sigma-canvas"></div>
39
+ </div>
40
+ <div id="mainpanel">
41
+ <div class="col">
42
+ <div id="title">Daily Paper Atlas</div>
43
+ <div id="titletext">A visualization of research papers and authors network. This interactive map shows relationships between papers and the researchers who created them.</div>
44
+ </div>
45
+
46
+ <div class="col">
47
+ <div id="key-features">
48
+ <h2>Key Features</h2>
49
+ <ul>
50
+ <li><strong>Search:</strong> Find papers or authors by name.</li>
51
+ <li><strong>Color By:</strong> Customize node colors based on attributes.</li>
52
+ <li><strong>Filter:</strong> Filter the network by node type.</li>
53
+ <li><strong>Interactive Nodes:</strong> Click nodes to view details.</li>
54
+ </ul>
55
+ </div>
56
+
57
+ <div id="legend">
58
+ <h2>Legend:</h2>
59
+ <div id="colorLegend"></div>
60
+ </div>
61
+ </div>
62
+
63
+ <div class="col">
64
+ <div id="search">
65
+ <h2>Search:</h2>
66
+ <div class="search-wrapper">
67
+ <input type="text" id="search-input" placeholder="Search by name">
68
+ <div id="search-button">→</div>
69
+ </div>
70
+ <div class="results"></div>
71
+ </div>
72
+ </div>
73
+
74
+ <div class="col">
75
+ <div id="coloringselect">
76
+ <h2>Color By:</h2>
77
+ <div class="select-wrapper">
78
+ <select id="color-attribute">
79
+ <option value="type">type</option>
80
+ <option value="year">year</option>
81
+ <option value="category">category</option>
82
+ </select>
83
+ </div>
84
+ </div>
85
+ </div>
86
+
87
+ <div class="col">
88
+ <div id="filteroptions">
89
+ <h2>Filter:</h2>
90
+ <div class="select-wrapper">
91
+ <select id="filter-select">
92
+ <option value="all">Show All</option>
93
+ <option value="papers">Papers Only</option>
94
+ <option value="authors">Authors Only</option>
95
+ </select>
96
+ </div>
97
+ </div>
98
+ </div>
99
+ </div>
100
+
101
+ <div id="attributepane">
102
+ <div class="text">
103
+ <div class="headertext">Information Pane</div>
104
+ <div class="nodeattributes">
105
+ <div class="name">Select a node to see details</div>
106
+ <div class="data"></div>
107
+ <div class="p">Connections:</div>
108
+ <div class="link">
109
+ <ul></ul>
110
+ </div>
111
+ </div>
112
+ <div class="returntext">Return to the full network</div>
113
+ </div>
114
+ </div>
115
+
116
+ <div id="zoom">
117
+ <div class="z" rel="in"></div>
118
+ <div class="z" rel="out"></div>
119
+ <div class="z" rel="center"></div>
120
+ </div>
121
+
122
+ <div id="developercontainer">
123
+ Web rendering powered by <a href="https://sigmajs.org/" target="_blank">SigmaJS</a>. Developed by Daily Paper Atlas Team.
124
+ </div>
125
+ </body>
126
+ </html>
js/main.js ADDED
@@ -0,0 +1,647 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Global variables
2
+ let sigmaInstance;
3
+ let graph;
4
+ let filter;
5
+ let config = {};
6
+ let greyColor = '#ccc';
7
+ let activeState = { activeNodes: [], activeEdges: [] };
8
+ let selectedNode = null;
9
+ let colorAttributes = [];
10
+ let colors = [
11
+ '#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd',
12
+ '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf'
13
+ ];
14
+ let nodeTypes = {
15
+ 'paper': { color: '#2ca02c', size: 3 },
16
+ 'author': { color: '#9467bd', size: 5 },
17
+ 'organization': { color: '#1f77b4', size: 4 },
18
+ 'unknown': { color: '#ff7f0e', size: 3 }
19
+ };
20
+
21
+ // Initialize when document is ready
22
+ $(document).ready(function() {
23
+ console.log("Document ready, initializing Daily Paper Atlas");
24
+
25
+ // Initialize attribute pane
26
+ $('#attributepane').css('display', 'none');
27
+
28
+ // Load configuration
29
+ $.getJSON('config.json', function(data) {
30
+ console.log("Configuration loaded:", data);
31
+ config = data;
32
+ document.title = config.text.title || 'Daily Paper Atlas';
33
+ $('#title').text(config.text.title || 'Daily Paper Atlas');
34
+ $('#titletext').text(config.text.intro || '');
35
+ loadGraph();
36
+ }).fail(function(jqXHR, textStatus, errorThrown) {
37
+ console.error("Failed to load config:", textStatus, errorThrown);
38
+ });
39
+
40
+ // Set up search functionality
41
+ $('#search-input').keyup(function(e) {
42
+ let searchTerm = $(this).val();
43
+ if (searchTerm.length > 2) {
44
+ searchNodes(searchTerm);
45
+ } else {
46
+ $('.results').empty();
47
+ }
48
+ });
49
+
50
+ $('#search-button').click(function() {
51
+ let searchTerm = $('#search-input').val();
52
+ if (searchTerm.length > 2) {
53
+ searchNodes(searchTerm);
54
+ }
55
+ });
56
+
57
+ // Set up zoom buttons
58
+ $('#zoom .z[rel="in"]').click(function() {
59
+ if (sigmaInstance) {
60
+ let a = sigmaInstance._core;
61
+ sigmaInstance.zoomTo(a.domElements.nodes.width / 2, a.domElements.nodes.height / 2, a.mousecaptor.ratio * 1.5);
62
+ }
63
+ });
64
+
65
+ $('#zoom .z[rel="out"]').click(function() {
66
+ if (sigmaInstance) {
67
+ let a = sigmaInstance._core;
68
+ sigmaInstance.zoomTo(a.domElements.nodes.width / 2, a.domElements.nodes.height / 2, a.mousecaptor.ratio * 0.5);
69
+ }
70
+ });
71
+
72
+ $('#zoom .z[rel="center"]').click(function() {
73
+ if (sigmaInstance) {
74
+ sigmaInstance.position(0, 0, 1).draw();
75
+ }
76
+ });
77
+
78
+ // Set up attribute pane functionality
79
+ $('.returntext').click(function() {
80
+ nodeNormal();
81
+ });
82
+
83
+ // Set up color selector
84
+ $('#color-attribute').change(function() {
85
+ let attr = $(this).val();
86
+ colorNodesByAttribute(attr);
87
+ });
88
+
89
+ // Set up filter selector
90
+ $('#filter-select').change(function() {
91
+ let filterValue = $(this).val();
92
+ filterByNodeType(filterValue);
93
+ });
94
+ });
95
+
96
+ // Load graph data
97
+ function loadGraph() {
98
+ console.log("Loading graph data from:", config.data);
99
+
100
+ // Check if data is a .gz file and needs decompression
101
+ if (config.data && config.data.endsWith('.gz')) {
102
+ console.log("Compressed data detected, loading via fetch and pako");
103
+
104
+ fetch(config.data)
105
+ .then(response => response.arrayBuffer())
106
+ .then(arrayBuffer => {
107
+ try {
108
+ // Decompress the gzipped data
109
+ const uint8Array = new Uint8Array(arrayBuffer);
110
+ const decompressed = pako.inflate(uint8Array, { to: 'string' });
111
+
112
+ // Parse the JSON data
113
+ const data = JSON.parse(decompressed);
114
+ console.log("Graph data decompressed and parsed successfully");
115
+ initializeGraph(data);
116
+ } catch (error) {
117
+ console.error("Error decompressing data:", error);
118
+ }
119
+ })
120
+ .catch(error => {
121
+ console.error("Error fetching compressed data:", error);
122
+ });
123
+ } else {
124
+ // Load uncompressed JSON directly
125
+ $.getJSON(config.data, function(data) {
126
+ console.log("Graph data loaded successfully");
127
+ initializeGraph(data);
128
+ }).fail(function(jqXHR, textStatus, errorThrown) {
129
+ console.error("Failed to load graph data:", textStatus, errorThrown);
130
+ alert('Failed to load graph data. Please check the console for more details.');
131
+ });
132
+ }
133
+ }
134
+
135
+ // Initialize the graph with the loaded data
136
+ function initializeGraph(data) {
137
+ graph = data;
138
+ console.log("Initializing graph with nodes:", graph.nodes.length, "edges:", graph.edges.length);
139
+
140
+ try {
141
+ // Initialize Sigma instance using the older sigma.init pattern
142
+ sigmaInstance = sigma.init(document.getElementById('sigma-canvas'));
143
+
144
+ // Configure mouse properties to ensure events work
145
+ sigmaInstance.mouseProperties({
146
+ maxRatio: 32,
147
+ minRatio: 0.5,
148
+ mouseEnabled: true,
149
+ mouseInertia: 0.8
150
+ });
151
+
152
+ console.log("Sigma mouse properties configured");
153
+
154
+ // Add nodes and edges to sigma
155
+ for (let i = 0; i < graph.nodes.length; i++) {
156
+ let node = graph.nodes[i];
157
+ sigmaInstance.addNode(node.id, {
158
+ label: node.label || node.id,
159
+ x: node.x || Math.random() * 100,
160
+ y: node.y || Math.random() * 100,
161
+ size: node.size || 1,
162
+ color: node.color || (node.type && config.nodeTypes && config.nodeTypes[node.type] ?
163
+ config.nodeTypes[node.type].color : nodeTypes[node.type]?.color || '#666'),
164
+ type: node.type
165
+ });
166
+ }
167
+
168
+ for (let i = 0; i < graph.edges.length; i++) {
169
+ let edge = graph.edges[i];
170
+ sigmaInstance.addEdge(edge.id, edge.source, edge.target, {
171
+ size: edge.size || 1,
172
+ color: edge.color || '#aaa'
173
+ });
174
+ }
175
+
176
+ // Configure drawing properties
177
+ sigmaInstance.drawingProperties({
178
+ labelThreshold: config.sigma?.drawingProperties?.labelThreshold || 8,
179
+ defaultLabelColor: config.sigma?.drawingProperties?.defaultLabelColor || '#000',
180
+ defaultLabelSize: config.sigma?.drawingProperties?.defaultLabelSize || 14,
181
+ defaultEdgeType: config.sigma?.drawingProperties?.defaultEdgeType || 'curve',
182
+ defaultHoverLabelBGColor: config.sigma?.drawingProperties?.defaultHoverLabelBGColor || '#002147',
183
+ defaultLabelHoverColor: config.sigma?.drawingProperties?.defaultLabelHoverColor || '#fff',
184
+ borderSize: 2,
185
+ nodeBorderColor: '#fff',
186
+ defaultNodeBorderColor: '#fff',
187
+ defaultNodeHoverColor: '#fff'
188
+ });
189
+
190
+ // Configure graph properties
191
+ sigmaInstance.graphProperties({
192
+ minNodeSize: config.sigma?.graphProperties?.minNodeSize || 1,
193
+ maxNodeSize: config.sigma?.graphProperties?.maxNodeSize || 8,
194
+ minEdgeSize: config.sigma?.graphProperties?.minEdgeSize || 0.5,
195
+ maxEdgeSize: config.sigma?.graphProperties?.maxEdgeSize || 2,
196
+ sideMargin: 50
197
+ });
198
+
199
+ // Force redraw and refresh
200
+ sigmaInstance.draw(2, 2, 2, 2);
201
+ sigmaInstance.refresh();
202
+
203
+ console.log("Sigma instance created and configured:", sigmaInstance);
204
+
205
+ // Initialize ForceAtlas2 layout if configured
206
+ if (config.features && config.features.forceAtlas2) {
207
+ console.log("Starting ForceAtlas2 layout...");
208
+ sigmaInstance.startForceAtlas2();
209
+
210
+ setTimeout(function() {
211
+ sigmaInstance.stopForceAtlas2();
212
+ console.log("ForceAtlas2 layout completed");
213
+ sigmaInstance.refresh();
214
+
215
+ // Initialize node colors and sizes by type
216
+ applyNodeStyles();
217
+
218
+ // Initialize filtering
219
+ initFilters();
220
+
221
+ // If a default color attribute is set, apply it
222
+ if (config.features && config.features.defaultColorAttribute) {
223
+ $('#color-attribute').val(config.features.defaultColorAttribute);
224
+ colorNodesByAttribute(config.features.defaultColorAttribute);
225
+ } else {
226
+ updateColorLegend(nodeTypes);
227
+ }
228
+
229
+ // Bind events
230
+ bindEvents();
231
+ }, config.features?.forceAtlas2Time || 5000);
232
+ } else {
233
+ // Initialize node colors and sizes by type
234
+ applyNodeStyles();
235
+
236
+ // Initialize filtering
237
+ initFilters();
238
+
239
+ // If a default color attribute is set, apply it
240
+ if (config.features && config.features.defaultColorAttribute) {
241
+ $('#color-attribute').val(config.features.defaultColorAttribute);
242
+ colorNodesByAttribute(config.features.defaultColorAttribute);
243
+ } else {
244
+ updateColorLegend(nodeTypes);
245
+ }
246
+
247
+ // Bind events
248
+ bindEvents();
249
+ }
250
+ } catch (e) {
251
+ console.error("Error initializing sigma instance:", e);
252
+ }
253
+ }
254
+
255
+ // Apply node styles based on node type
256
+ function applyNodeStyles() {
257
+ if (!sigmaInstance) return;
258
+ try {
259
+ sigmaInstance.iterNodes(function(node) {
260
+ if (node.type && config.nodeTypes && config.nodeTypes[node.type]) {
261
+ node.color = config.nodeTypes[node.type].color;
262
+ node.size = config.nodeTypes[node.type].size;
263
+ } else if (node.type && nodeTypes[node.type]) {
264
+ node.color = nodeTypes[node.type].color;
265
+ node.size = nodeTypes[node.type].size;
266
+ }
267
+ });
268
+ sigmaInstance.refresh();
269
+ } catch (e) {
270
+ console.error("Error applying node styles:", e);
271
+ }
272
+ }
273
+
274
+ // Initialize filters
275
+ function initFilters() {
276
+ try {
277
+ if (sigma.plugins && sigma.plugins.filter) {
278
+ filter = new sigma.plugins.filter(sigmaInstance);
279
+ console.log("Filter plugin initialized");
280
+ } else {
281
+ console.warn("Sigma filter plugin not available");
282
+ }
283
+ } catch (e) {
284
+ console.error("Error initializing filter plugin:", e);
285
+ }
286
+ }
287
+
288
+ // Filter nodes by type
289
+ function filterByNodeType(filterValue) {
290
+ if (!filter) return;
291
+ try {
292
+ filter.undo('node-type');
293
+
294
+ if (filterValue === 'papers') {
295
+ filter.nodesBy(function(n) {
296
+ return n.type === 'paper';
297
+ }, 'node-type');
298
+ } else if (filterValue === 'authors') {
299
+ filter.nodesBy(function(n) {
300
+ return n.type === 'author';
301
+ }, 'node-type');
302
+ }
303
+
304
+ filter.apply();
305
+ sigmaInstance.refresh();
306
+ } catch (e) {
307
+ console.error("Error filtering nodes:", e);
308
+ }
309
+ }
310
+
311
+ // Bind events
312
+ function bindEvents() {
313
+ if (!sigmaInstance) {
314
+ console.error("Sigma instance not found when binding events");
315
+ return;
316
+ }
317
+
318
+ console.log("Binding sigma events to instance:", sigmaInstance);
319
+
320
+ // Add a direct click handler to the sigma canvas
321
+ document.getElementById('sigma-canvas').addEventListener('click', function(evt) {
322
+ console.log("Canvas clicked, checking if it's on a node");
323
+ // The event happened on canvas, now check if it was on a node
324
+ var x = evt.offsetX || evt.layerX;
325
+ var y = evt.offsetY || evt.layerY;
326
+
327
+ var nodeFound = false;
328
+ sigmaInstance.iterNodes(function(n) {
329
+ if (!nodeFound && n.displayX && n.displayY && n.displaySize) {
330
+ var dx = n.displayX - x;
331
+ var dy = n.displayY - y;
332
+ var distance = Math.sqrt(dx * dx + dy * dy);
333
+
334
+ if (distance < n.displaySize) {
335
+ console.log("Node found under click:", n.id);
336
+ nodeFound = true;
337
+ nodeActive(n.id);
338
+ }
339
+ }
340
+ });
341
+
342
+ if (!nodeFound) {
343
+ console.log("No node found under click, closing node panel");
344
+ nodeNormal();
345
+ }
346
+ });
347
+
348
+ // Still try to use sigma's events
349
+ try {
350
+ // When a node is clicked, display its details
351
+ sigmaInstance.bind('clickNode', function(e) {
352
+ var node = e.target || e.data.node || e.data;
353
+ console.log("Official clickNode event received:", e);
354
+ var nodeId = node.id || node;
355
+ console.log("Node clicked via official event:", nodeId);
356
+ nodeActive(nodeId);
357
+ });
358
+
359
+ // When stage is clicked, close the attribute pane
360
+ sigmaInstance.bind('clickStage', function() {
361
+ console.log("Official clickStage event received");
362
+ nodeNormal();
363
+ });
364
+
365
+ // Highlight connected nodes on hover
366
+ sigmaInstance.bind('overNode', function(e) {
367
+ var node = e.target || e.data.node || e.data;
368
+ var nodeId = node.id || node;
369
+ console.log("Node hover enter:", nodeId);
370
+
371
+ // First identify neighbors
372
+ var neighbors = {};
373
+ sigmaInstance.iterEdges(function(edge) {
374
+ if (edge.source == nodeId || edge.target == nodeId) {
375
+ neighbors[edge.source == nodeId ? edge.target : edge.source] = true;
376
+ }
377
+ });
378
+
379
+ // Then update node and edge colors
380
+ sigmaInstance.iterNodes(function(node) {
381
+ if (node.id == nodeId || neighbors[node.id]) {
382
+ node.originalColor = node.color;
383
+ } else {
384
+ node.originalColor = node.originalColor || node.color;
385
+ node.color = greyColor;
386
+ }
387
+ });
388
+
389
+ sigmaInstance.iterEdges(function(edge) {
390
+ if (edge.source == nodeId || edge.target == nodeId) {
391
+ edge.originalColor = edge.color;
392
+ } else {
393
+ edge.originalColor = edge.originalColor || edge.color;
394
+ edge.color = greyColor;
395
+ }
396
+ });
397
+
398
+ sigmaInstance.refresh();
399
+ });
400
+
401
+ sigmaInstance.bind('outNode', function(e) {
402
+ var node = e.target || e.data.node || e.data;
403
+ var nodeId = node.id || node;
404
+ console.log("Node hover leave:", nodeId);
405
+
406
+ if (!sigmaInstance.detail) {
407
+ sigmaInstance.iterNodes(function(n) {
408
+ n.color = n.originalColor || n.color;
409
+ });
410
+
411
+ sigmaInstance.iterEdges(function(e) {
412
+ e.color = e.originalColor || e.color;
413
+ });
414
+
415
+ sigmaInstance.refresh();
416
+ }
417
+ });
418
+ console.log("Sigma events bound successfully");
419
+ } catch (e) {
420
+ console.error("Error binding sigma events:", e);
421
+ }
422
+ }
423
+
424
+ // Display node details (used when a node is clicked)
425
+ function nodeActive(nodeId) {
426
+ console.log("nodeActive called with id:", nodeId);
427
+
428
+ // Find the node
429
+ var node = null;
430
+ sigmaInstance.iterNodes(function(n) {
431
+ if (n.id == nodeId) {
432
+ node = n;
433
+ }
434
+ });
435
+
436
+ if (!node) {
437
+ console.error("Node not found:", nodeId);
438
+ return;
439
+ }
440
+
441
+ console.log("Node found:", node);
442
+ sigmaInstance.detail = true;
443
+ selectedNode = node;
444
+
445
+ // Find neighbors
446
+ var neighbors = {};
447
+ sigmaInstance.iterEdges(function(e) {
448
+ if (e.source == nodeId || e.target == nodeId) {
449
+ neighbors[e.source == nodeId ? e.target : e.source] = {
450
+ name: e.label || "",
451
+ color: e.color
452
+ };
453
+ }
454
+ });
455
+
456
+ console.log("Neighbors found:", Object.keys(neighbors).length);
457
+
458
+ // Update node appearance
459
+ sigmaInstance.iterNodes(function(n) {
460
+ if (n.id == nodeId) {
461
+ n.color = n.originalColor || n.color;
462
+ n.size = n.size * 1.5; // Emphasize selected node
463
+ } else if (neighbors[n.id]) {
464
+ n.color = n.originalColor || n.color;
465
+ } else {
466
+ n.originalColor = n.originalColor || n.color;
467
+ n.color = greyColor;
468
+ }
469
+ });
470
+
471
+ // Refresh display
472
+ sigmaInstance.refresh();
473
+
474
+ // Populate connection list
475
+ var connectionList = [];
476
+ for (var id in neighbors) {
477
+ var neighbor = null;
478
+ sigmaInstance.iterNodes(function(n) {
479
+ if (n.id == id) {
480
+ neighbor = n;
481
+ }
482
+ });
483
+
484
+ if (neighbor) {
485
+ connectionList.push('<li><a href="#" data-node-id="' + id + '">' + (neighbor.label || id) + '</a></li>');
486
+ }
487
+ }
488
+
489
+ // Show node details panel
490
+ try {
491
+ console.log("Displaying attribute pane");
492
+ // Make absolutely sure the panel is visible with both CSS approaches
493
+ $('#attributepane').show().css('display', 'block');
494
+
495
+ // Update panel content
496
+ $('.nodeattributes .name').text(node.label || node.id);
497
+
498
+ let dataHTML = '';
499
+ for (let attr in node) {
500
+ if (attr !== 'id' && attr !== 'x' && attr !== 'y' && attr !== 'size' && attr !== 'color' &&
501
+ attr !== 'label' && attr !== 'originalColor' && attr !== 'hidden' &&
502
+ typeof node[attr] !== 'function' && attr !== 'displayX' && attr !== 'displayY' &&
503
+ attr !== 'displaySize') {
504
+ dataHTML += '<div><strong>' + attr + ':</strong> ' + node[attr] + '</div>';
505
+ }
506
+ }
507
+
508
+ if (dataHTML === '') {
509
+ dataHTML = '<div>No additional attributes</div>';
510
+ }
511
+
512
+ $('.nodeattributes .data').html(dataHTML);
513
+ $('.nodeattributes .link ul').html(connectionList.length ? connectionList.join('') : '<li>No connections</li>');
514
+
515
+ // Set up click event for neighbor nodes
516
+ $('.nodeattributes .link ul li a').click(function(e) {
517
+ e.preventDefault();
518
+ var id = $(this).data('node-id');
519
+ nodeActive(id);
520
+ });
521
+
522
+ console.log("Attribute pane updated with node details");
523
+ } catch (e) {
524
+ console.error("Error updating attribute pane:", e);
525
+ }
526
+ }
527
+
528
+ // Reset display (used when clicking outside nodes or closing the panel)
529
+ function nodeNormal() {
530
+ console.log("nodeNormal called");
531
+ if (sigmaInstance) {
532
+ sigmaInstance.detail = false;
533
+ selectedNode = null;
534
+
535
+ // Reset node appearance
536
+ sigmaInstance.iterNodes(function(node) {
537
+ node.color = node.originalColor || node.color;
538
+ // Reset size to original
539
+ if (node.type && config.nodeTypes && config.nodeTypes[node.type]) {
540
+ node.size = config.nodeTypes[node.type].size;
541
+ } else if (node.type && nodeTypes[node.type]) {
542
+ node.size = nodeTypes[node.type].size;
543
+ }
544
+ });
545
+
546
+ // Reset edge appearance
547
+ sigmaInstance.iterEdges(function(edge) {
548
+ edge.color = edge.originalColor || edge.color;
549
+ });
550
+
551
+ // Hide panel and refresh display
552
+ $('#attributepane').css('display', 'none');
553
+ sigmaInstance.refresh();
554
+ }
555
+ }
556
+
557
+ // Color nodes by attribute
558
+ function colorNodesByAttribute(attribute) {
559
+ if (!sigmaInstance) return;
560
+
561
+ console.log("Coloring nodes by attribute:", attribute);
562
+ // Get all unique values for the attribute
563
+ let values = {};
564
+ let valueCount = 0;
565
+
566
+ sigmaInstance.iterNodes(function(n) {
567
+ let value = n[attribute] || 'unknown';
568
+ if (!values[value]) {
569
+ values[value] = true;
570
+ valueCount++;
571
+ }
572
+ });
573
+
574
+ // Assign colors to values
575
+ let valueColors = {};
576
+ let i = 0;
577
+ let palette = config.colorPalette || colors;
578
+
579
+ for (let value in values) {
580
+ valueColors[value] = palette[i % palette.length];
581
+ i++;
582
+ }
583
+
584
+ // Update node colors
585
+ sigmaInstance.iterNodes(function(n) {
586
+ let value = n[attribute] || 'unknown';
587
+ n.originalColor = valueColors[value];
588
+ n.color = valueColors[value];
589
+ });
590
+
591
+ sigmaInstance.refresh();
592
+
593
+ // Update color legend
594
+ updateColorLegend(valueColors);
595
+ }
596
+
597
+ // Update color legend
598
+ function updateColorLegend(valueColors) {
599
+ let legendHTML = '';
600
+
601
+ for (let value in valueColors) {
602
+ let color = valueColors[value];
603
+ if (typeof color === 'object') {
604
+ color = color.color;
605
+ }
606
+ legendHTML += '<div class="legenditem"><span class="legendcolor" style="background-color: ' + color + '"></span>' + value + '</div>';
607
+ }
608
+
609
+ $('#colorLegend').html(legendHTML);
610
+ }
611
+
612
+ // Search nodes by term
613
+ function searchNodes(term) {
614
+ if (!sigmaInstance) return;
615
+
616
+ let results = [];
617
+ let lowerTerm = term.toLowerCase();
618
+
619
+ sigmaInstance.iterNodes(function(n) {
620
+ if ((n.label && n.label.toLowerCase().indexOf(lowerTerm) >= 0) ||
621
+ (n.id && n.id.toLowerCase().indexOf(lowerTerm) >= 0)) {
622
+ results.push(n);
623
+ }
624
+ });
625
+
626
+ // Limit to top 10 results
627
+ results = results.slice(0, 10);
628
+
629
+ // Display results
630
+ let resultsHTML = '';
631
+ if (results.length > 0) {
632
+ results.forEach(function(n) {
633
+ resultsHTML += '<a href="#" data-node-id="' + n.id + '">' + (n.label || n.id) + '</a>';
634
+ });
635
+ } else {
636
+ resultsHTML = '<div>No results found</div>';
637
+ }
638
+
639
+ $('.results').html(resultsHTML);
640
+
641
+ // Set up click event for results
642
+ $('.results a').click(function(e) {
643
+ e.preventDefault();
644
+ let nodeId = $(this).data('node-id');
645
+ nodeActive(nodeId);
646
+ });
647
+ }
js/paper-atlas.js ADDED
@@ -0,0 +1,433 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Global variables
2
+ let sigmaInstance;
3
+ let graph;
4
+ let filter;
5
+ let config = {};
6
+ let greyColor = '#666';
7
+ let activeState = { activeNodes: [], activeEdges: [] };
8
+ let selectedNode = null;
9
+ let colorAttributes = [];
10
+ let colors = [
11
+ '#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd',
12
+ '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf'
13
+ ];
14
+ let nodeTypes = {
15
+ 'paper': { color: '#2ca02c', size: 3 },
16
+ 'author': { color: '#9467bd', size: 5 },
17
+ 'organization': { color: '#1f77b4', size: 4 },
18
+ 'unknown': { color: '#ff7f0e', size: 3 }
19
+ };
20
+
21
+ // Initialize when document is ready
22
+ $(document).ready(function() {
23
+ // Load configuration
24
+ $.getJSON('config.json', function(data) {
25
+ config = data;
26
+ document.title = config.title || 'Daily Paper Atlas';
27
+ $('#title').text(config.title || 'Daily Paper Atlas');
28
+ loadGraph();
29
+ });
30
+
31
+ // Set up search functionality
32
+ $('#search-input').keyup(function(e) {
33
+ let searchTerm = $(this).val();
34
+ if (searchTerm.length > 2) {
35
+ searchNodes(searchTerm);
36
+ } else {
37
+ $('.results').empty();
38
+ }
39
+ });
40
+
41
+ $('#search-button').click(function() {
42
+ let searchTerm = $('#search-input').val();
43
+ if (searchTerm.length > 2) {
44
+ searchNodes(searchTerm);
45
+ }
46
+ });
47
+
48
+ // Set up zoom buttons
49
+ $('#zoom .z[rel="in"]').click(function() {
50
+ if (sigmaInstance) sigmaInstance.camera.goTo({ratio: sigmaInstance.camera.ratio / 1.5});
51
+ });
52
+
53
+ $('#zoom .z[rel="out"]').click(function() {
54
+ if (sigmaInstance) sigmaInstance.camera.goTo({ratio: sigmaInstance.camera.ratio * 1.5});
55
+ });
56
+
57
+ $('#zoom .z[rel="center"]').click(function() {
58
+ if (sigmaInstance) sigmaInstance.camera.goTo({x: 0, y: 0, ratio: 1});
59
+ });
60
+
61
+ // Set up attribute pane functionality
62
+ $('.returntext').click(function() {
63
+ closeAttributePane();
64
+ });
65
+
66
+ // Set up color selector
67
+ $('#color-attribute').change(function() {
68
+ let attr = $(this).val();
69
+ colorNodesByAttribute(attr);
70
+ });
71
+
72
+ // Set up filter selector
73
+ $('#filter-select').change(function() {
74
+ let filterValue = $(this).val();
75
+ filterByNodeType(filterValue);
76
+ });
77
+ });
78
+
79
+ // Load graph data
80
+ function loadGraph() {
81
+ $.getJSON(config.data, function(data) {
82
+ graph = data;
83
+
84
+ // Initialize Sigma instance
85
+ sigmaInstance = new sigma({
86
+ graph: graph,
87
+ container: 'sigma-canvas',
88
+ settings: {
89
+ labelThreshold: config.labelThreshold || 7,
90
+ minEdgeSize: config.minEdgeSize || 0.5,
91
+ maxEdgeSize: config.maxEdgeSize || 2,
92
+ minNodeSize: config.minNodeSize || 1,
93
+ maxNodeSize: config.maxNodeSize || 8,
94
+ drawLabels: config.drawLabels !== false,
95
+ defaultLabelColor: config.defaultLabelColor || '#000',
96
+ defaultEdgeColor: config.defaultEdgeColor || '#999',
97
+ edgeColor: 'default',
98
+ defaultNodeColor: config.defaultNodeColor || '#666',
99
+ defaultNodeBorderColor: config.defaultNodeBorderColor || '#fff',
100
+ borderSize: config.borderSize || 1,
101
+ nodeHoverColor: 'default',
102
+ defaultNodeHoverColor: config.defaultNodeHoverColor || '#000',
103
+ defaultHoverLabelBGColor: config.defaultHoverLabelBGColor || '#fff',
104
+ defaultLabelHoverColor: config.defaultLabelHoverColor || '#000',
105
+ enableEdgeHovering: config.enableEdgeHovering !== false,
106
+ edgeHoverColor: 'edge',
107
+ edgeHoverSizeRatio: config.edgeHoverSizeRatio || 1.5,
108
+ defaultEdgeHoverColor: config.defaultEdgeHoverColor || '#000',
109
+ edgeHoverExtremities: config.edgeHoverExtremities !== false,
110
+ batchEdgesDrawing: config.batchEdgesDrawing !== false,
111
+ hideEdgesOnMove: config.hideEdgesOnMove !== false,
112
+ canvasEdgesBatchSize: config.canvasEdgesBatchSize || 500,
113
+ animationsTime: config.animationsTime || 1500
114
+ }
115
+ });
116
+
117
+ // Initialize ForceAtlas2 layout
118
+ if (config.forceAtlas2) {
119
+ console.log("Starting ForceAtlas2 layout...");
120
+ sigmaInstance.startForceAtlas2({
121
+ worker: true,
122
+ barnesHutOptimize: true,
123
+ gravity: 1,
124
+ scalingRatio: 2,
125
+ slowDown: 10
126
+ });
127
+
128
+ setTimeout(function() {
129
+ sigmaInstance.stopForceAtlas2();
130
+ console.log("ForceAtlas2 layout completed");
131
+ sigmaInstance.refresh();
132
+
133
+ // Initialize node colors and sizes by type
134
+ applyNodeStyles();
135
+
136
+ // Initialize filtering
137
+ initFilters();
138
+
139
+ // If a default color attribute is set, apply it
140
+ if (config.defaultColorAttribute) {
141
+ $('#color-attribute').val(config.defaultColorAttribute);
142
+ colorNodesByAttribute(config.defaultColorAttribute);
143
+ } else {
144
+ updateColorLegend(nodeTypes);
145
+ }
146
+
147
+ // Bind events
148
+ bindEvents();
149
+
150
+ }, config.forceAtlas2Time || 5000);
151
+ } else {
152
+ // Initialize node colors and sizes by type
153
+ applyNodeStyles();
154
+
155
+ // Initialize filtering
156
+ initFilters();
157
+
158
+ // If a default color attribute is set, apply it
159
+ if (config.defaultColorAttribute) {
160
+ $('#color-attribute').val(config.defaultColorAttribute);
161
+ colorNodesByAttribute(config.defaultColorAttribute);
162
+ } else {
163
+ updateColorLegend(nodeTypes);
164
+ }
165
+
166
+ // Bind events
167
+ bindEvents();
168
+ }
169
+ }).fail(function(jqXHR, textStatus, errorThrown) {
170
+ console.error("Failed to load graph data:", textStatus, errorThrown);
171
+ alert('Failed to load graph data. Please check the console for more details.');
172
+ });
173
+ }
174
+
175
+ // Apply node styles based on node type
176
+ function applyNodeStyles() {
177
+ sigmaInstance.graph.nodes().forEach(function(node) {
178
+ if (node.type && config.nodeTypes && config.nodeTypes[node.type]) {
179
+ node.color = config.nodeTypes[node.type].color;
180
+ node.size = config.nodeTypes[node.type].size;
181
+ } else if (node.type && nodeTypes[node.type]) {
182
+ node.color = nodeTypes[node.type].color;
183
+ node.size = nodeTypes[node.type].size;
184
+ }
185
+ });
186
+ sigmaInstance.refresh();
187
+ }
188
+
189
+ // Initialize filters
190
+ function initFilters() {
191
+ try {
192
+ filter = new sigma.plugins.filter(sigmaInstance);
193
+ console.log("Filter plugin initialized");
194
+ } catch (e) {
195
+ console.error("Error initializing filter plugin:", e);
196
+ }
197
+ }
198
+
199
+ // Filter nodes by type
200
+ function filterByNodeType(filterValue) {
201
+ if (!filter) return;
202
+
203
+ filter.undo('node-type');
204
+
205
+ if (filterValue !== 'all') {
206
+ filter.nodesBy(function(n) {
207
+ return n.type === filterValue;
208
+ }, 'node-type');
209
+ }
210
+
211
+ filter.apply();
212
+ sigmaInstance.refresh();
213
+ }
214
+
215
+ // Bind events
216
+ function bindEvents() {
217
+ // When a node is clicked, display its details
218
+ sigmaInstance.bind('clickNode', function(e) {
219
+ let node = e.data.node;
220
+ selectedNode = node;
221
+ displayNodeDetails(node);
222
+ });
223
+
224
+ // When stage is clicked, close the attribute pane
225
+ sigmaInstance.bind('clickStage', function() {
226
+ closeAttributePane();
227
+ });
228
+
229
+ // Highlight connected nodes on hover
230
+ sigmaInstance.bind('hovers', function(e) {
231
+ if (!e.data.enter.nodes.length) return;
232
+
233
+ let nodeId = e.data.enter.nodes[0];
234
+ let toKeep = sigmaInstance.graph.neighbors(nodeId);
235
+ toKeep[nodeId] = e.data.enter.nodes[0];
236
+
237
+ activeState.activeNodes = Object.keys(toKeep);
238
+
239
+ sigmaInstance.graph.nodes().forEach(function(n) {
240
+ if (toKeep[n.id]) {
241
+ n.originalColor = n.color;
242
+ n.color = n.color;
243
+ } else {
244
+ n.originalColor = n.originalColor || n.color;
245
+ n.color = greyColor;
246
+ }
247
+ });
248
+
249
+ sigmaInstance.graph.edges().forEach(function(e) {
250
+ if (toKeep[e.source] && toKeep[e.target]) {
251
+ e.originalColor = e.color;
252
+ e.color = e.color;
253
+ } else {
254
+ e.originalColor = e.originalColor || e.color;
255
+ e.color = greyColor;
256
+ }
257
+ });
258
+
259
+ sigmaInstance.refresh();
260
+ });
261
+
262
+ sigmaInstance.bind('hovers', function(e) {
263
+ if (e.data.leave.nodes.length && !selectedNode) {
264
+ sigmaInstance.graph.nodes().forEach(function(n) {
265
+ n.color = n.originalColor || n.color;
266
+ });
267
+
268
+ sigmaInstance.graph.edges().forEach(function(e) {
269
+ e.color = e.originalColor || e.color;
270
+ });
271
+
272
+ sigmaInstance.refresh();
273
+ }
274
+ });
275
+ }
276
+
277
+ // Display node details in the attribute pane
278
+ function displayNodeDetails(node) {
279
+ $('#attributepane').show();
280
+ $('.nodeattributes .name').text(node.label || node.id);
281
+
282
+ let dataHTML = '';
283
+ for (let attr in node) {
284
+ if (attr !== 'id' && attr !== 'x' && attr !== 'y' && attr !== 'size' && attr !== 'color' &&
285
+ attr !== 'label' && attr !== 'originalColor' && attr !== 'hidden') {
286
+ dataHTML += '<div><strong>' + attr + ':</strong> ' + node[attr] + '</div>';
287
+ }
288
+ }
289
+ $('.nodeattributes .data').html(dataHTML);
290
+
291
+ // Display connected nodes
292
+ let neighbors = sigmaInstance.graph.neighbors(node.id);
293
+ let linksHTML = '';
294
+ for (let id in neighbors) {
295
+ let neighbor = sigmaInstance.graph.nodes(id);
296
+ if (neighbor) {
297
+ linksHTML += '<li><a href="#" data-node-id="' + id + '">' + (neighbor.label || id) + '</a></li>';
298
+ }
299
+ }
300
+ $('.nodeattributes .link ul').html(linksHTML);
301
+
302
+ // Set up click event for connected nodes
303
+ $('.nodeattributes .link ul li a').click(function(e) {
304
+ e.preventDefault();
305
+ let nodeId = $(this).data('node-id');
306
+ let node = sigmaInstance.graph.nodes(nodeId);
307
+ if (node) {
308
+ selectedNode = node;
309
+ displayNodeDetails(node);
310
+ sigmaInstance.renderers[0].dispatchEvent('clickNode', {
311
+ node: node
312
+ });
313
+ }
314
+ });
315
+ }
316
+
317
+ // Close the attribute pane
318
+ function closeAttributePane() {
319
+ $('#attributepane').hide();
320
+ selectedNode = null;
321
+
322
+ // Reset colors
323
+ sigmaInstance.graph.nodes().forEach(function(n) {
324
+ n.color = n.originalColor || n.color;
325
+ });
326
+
327
+ sigmaInstance.graph.edges().forEach(function(e) {
328
+ e.color = e.originalColor || e.color;
329
+ });
330
+
331
+ sigmaInstance.refresh();
332
+ }
333
+
334
+ // Color nodes by attribute
335
+ function colorNodesByAttribute(attribute) {
336
+ // Get all unique values for the attribute
337
+ let values = {};
338
+ let valueCount = 0;
339
+
340
+ sigmaInstance.graph.nodes().forEach(function(n) {
341
+ let value = n[attribute] || 'unknown';
342
+ if (!values[value]) {
343
+ values[value] = true;
344
+ valueCount++;
345
+ }
346
+ });
347
+
348
+ // Assign colors to values
349
+ let valueColors = {};
350
+ let i = 0;
351
+ let palette = config.colorPalette || colors;
352
+
353
+ for (let value in values) {
354
+ valueColors[value] = palette[i % palette.length];
355
+ i++;
356
+ }
357
+
358
+ // Update node colors
359
+ sigmaInstance.graph.nodes().forEach(function(n) {
360
+ let value = n[attribute] || 'unknown';
361
+ n.originalColor = valueColors[value];
362
+ if (!selectedNode) {
363
+ n.color = valueColors[value];
364
+ }
365
+ });
366
+
367
+ sigmaInstance.refresh();
368
+
369
+ // Update color legend
370
+ updateColorLegend(valueColors);
371
+ }
372
+
373
+ // Update color legend
374
+ function updateColorLegend(valueColors) {
375
+ let legendHTML = '';
376
+
377
+ for (let value in valueColors) {
378
+ let color = valueColors[value];
379
+ if (typeof color === 'object') {
380
+ color = color.color;
381
+ }
382
+ legendHTML += '<div class="legenditem"><span class="legendcolor" style="background-color: ' + color + '"></span>' + value + '</div>';
383
+ }
384
+
385
+ $('#colorLegend').html(legendHTML);
386
+ }
387
+
388
+ // Search nodes by term
389
+ function searchNodes(term) {
390
+ let results = [];
391
+ let lowerTerm = term.toLowerCase();
392
+
393
+ sigmaInstance.graph.nodes().forEach(function(n) {
394
+ if ((n.label && n.label.toLowerCase().indexOf(lowerTerm) >= 0) ||
395
+ (n.id && n.id.toLowerCase().indexOf(lowerTerm) >= 0)) {
396
+ results.push(n);
397
+ }
398
+ });
399
+
400
+ // Limit to top 10 results
401
+ results = results.slice(0, 10);
402
+
403
+ // Display results
404
+ let resultsHTML = '';
405
+ if (results.length > 0) {
406
+ results.forEach(function(n) {
407
+ resultsHTML += '<a href="#" data-node-id="' + n.id + '">' + (n.label || n.id) + '</a>';
408
+ });
409
+ } else {
410
+ resultsHTML = '<div>No results found</div>';
411
+ }
412
+
413
+ $('.results').html(resultsHTML);
414
+
415
+ // Set up click event for results
416
+ $('.results a').click(function(e) {
417
+ e.preventDefault();
418
+ let nodeId = $(this).data('node-id');
419
+ let node = sigmaInstance.graph.nodes(nodeId);
420
+ if (node) {
421
+ // Center the camera on the node
422
+ sigmaInstance.camera.goTo({
423
+ x: node[sigmaInstance.camera.readPrefix + 'x'],
424
+ y: node[sigmaInstance.camera.readPrefix + 'y'],
425
+ ratio: 0.5
426
+ });
427
+
428
+ // Select the node
429
+ selectedNode = node;
430
+ displayNodeDetails(node);
431
+ }
432
+ });
433
+ }
js/sigma/plugins/sigma.plugins.filter.min.js ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Sigma.js Filter Plugin - v1.0.0
3
+ */
4
+ (function() {
5
+ 'use strict';
6
+
7
+ if (typeof sigma === 'undefined')
8
+ throw new Error('sigma not in scope.');
9
+
10
+ // Add pkg utility if it doesn't exist
11
+ sigma.utils = sigma.utils || {};
12
+ sigma.utils.pkg = sigma.utils.pkg || function(pkg) {
13
+ return pkg.split('.').reduce(function(context, current) {
14
+ return (context[current] = context[current] || {});
15
+ }, window);
16
+ };
17
+
18
+ // Initialize package:
19
+ sigma.utils.pkg('sigma.plugins');
20
+
21
+ sigma.plugins.filter = function(s) {
22
+ var _s = s,
23
+ _g = s.graph,
24
+ _filters = {};
25
+
26
+ this.undo = function(filterType) {
27
+ if (filterType) {
28
+ delete _filters[filterType];
29
+ } else {
30
+ _filters = {};
31
+ }
32
+ return this;
33
+ };
34
+
35
+ this.nodesBy = function(fn, filterType) {
36
+ if (!filterType)
37
+ throw new Error('filterType is required.');
38
+
39
+ _filters[filterType] = {
40
+ nodePredicate: fn
41
+ };
42
+ return this;
43
+ };
44
+
45
+ this.edgesBy = function(fn, filterType) {
46
+ if (!filterType)
47
+ throw new Error('filterType is required.');
48
+
49
+ _filters[filterType] = {
50
+ edgePredicate: fn
51
+ };
52
+ return this;
53
+ };
54
+
55
+ this.apply = function() {
56
+ var n, e, nid, eid, filter, edge, srcNode, tgtNode;
57
+ var g = _g;
58
+ var nodes = g.nodes();
59
+ var edges = g.edges();
60
+
61
+ // Restore all nodes:
62
+ for (nid in g.nodesIndex) {
63
+ g.nodesIndex[nid].hidden = false;
64
+ }
65
+
66
+ // Restore all edges:
67
+ for (eid in g.edgesIndex) {
68
+ g.edgesIndex[eid].hidden = false;
69
+ }
70
+
71
+ // Apply filters to nodes:
72
+ for (n in _filters) {
73
+ filter = _filters[n];
74
+ if (filter.nodePredicate) {
75
+ for (nid in g.nodesIndex) {
76
+ if (!filter.nodePredicate(g.nodesIndex[nid])) {
77
+ g.nodesIndex[nid].hidden = true;
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ // Apply filters to edges:
84
+ for (e in _filters) {
85
+ filter = _filters[e];
86
+ if (filter.edgePredicate) {
87
+ for (eid in g.edgesIndex) {
88
+ if (!filter.edgePredicate(g.edgesIndex[eid])) {
89
+ g.edgesIndex[eid].hidden = true;
90
+ }
91
+ }
92
+ }
93
+ }
94
+
95
+ // Hide edges connected to hidden nodes:
96
+ for (eid in g.edgesIndex) {
97
+ edge = g.edgesIndex[eid];
98
+ srcNode = g.nodesIndex[edge.source];
99
+ tgtNode = g.nodesIndex[edge.target];
100
+ if (srcNode.hidden || tgtNode.hidden) {
101
+ edge.hidden = true;
102
+ }
103
+ }
104
+
105
+ // Refresh visualization:
106
+ _s.refresh();
107
+ return this;
108
+ };
109
+ };
110
+ }).call(this);
js/sigma/sigma.min.js ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* sigmajs.org - an open-source light-weight JavaScript graph drawing library - Version: 0.1 - Author: Alexis Jacomy - License: MIT */
2
+ var sigma={tools:{},classes:{},instances:{}};
3
+ (function(){Array.prototype.some||(Array.prototype.some=function(g,i){var k=this.length;if("function"!=typeof g)throw new TypeError;for(var m=0;m<k;m++)if(m in this&&g.call(i,this[m],m,this))return!0;return!1});Array.prototype.forEach||(Array.prototype.forEach=function(g,i){var k=this.length;if(typeof g!="function")throw new TypeError;for(var m=0;m<k;m++)m in this&&g.call(i,this[m],m,this)});Array.prototype.map||(Array.prototype.map=function(g,i){var k=this.length;if(typeof g!="function")throw new TypeError;
4
+ for(var m=Array(k),b=0;b<k;b++)b in this&&(m[b]=g.call(i,this[b],b,this));return m});Array.prototype.filter||(Array.prototype.filter=function(g,i){var k=this.length;if(typeof g!="function")throw new TypeError;for(var m=[],b=0;b<k;b++)if(b in this){var j=this[b];g.call(i,j,b,this)&&m.push(j)}return m});if(!Object.keys){var i=Object,q=Object.prototype.hasOwnProperty,g=!{toString:null}.propertyIsEnumerable("toString"),p="toString toLocaleString valueOf hasOwnProperty isPrototypeOf propertyIsEnumerable constructor".split(" "),
5
+ z=p.length;i.keys=function(i){if(typeof i!=="object"&&typeof i!=="function"||i===null)throw new TypeError("Object.keys called on non-object");var x=[],k;for(k in i)q.call(i,k)&&x.push(k);if(g)for(k=0;k<z;k++)q.call(i,p[k])&&x.push(p[k]);return x}}})();sigma.classes.Cascade=function(){this.p={};this.config=function(i,q){if("string"==typeof i&&void 0==q)return this.p[i];var g="object"==typeof i&&void 0==q?i:{};"string"==typeof i&&(g[i]=q);for(var p in g)void 0!=this.p[p]&&(this.p[p]=g[p]);return this}};
6
+ sigma.classes.EventDispatcher=function(){var i={},q=this;this.one=function(g,p){if(!p||!g)return q;("string"==typeof g?g.split(" "):g).forEach(function(g){i[g]||(i[g]=[]);i[g].push({h:p,one:!0})});return q};this.bind=function(g,p){if(!p||!g)return q;("string"==typeof g?g.split(" "):g).forEach(function(g){i[g]||(i[g]=[]);i[g].push({h:p,one:!1})});return q};this.unbind=function(g,p){g||(i={});var z="string"==typeof g?g.split(" "):g;p?z.forEach(function(g){i[g]&&(i[g]=i[g].filter(function(g){return g.h!=
7
+ p}));i[g]&&0==i[g].length&&delete i[g]}):z.forEach(function(g){delete i[g]});return q};this.dispatch=function(g,p){i[g]&&(i[g].forEach(function(i){i.h({type:g,content:p,target:q})}),i[g]=i[g].filter(function(g){return!g.one}));return q}};
8
+ (function(){function i(){function b(a){return{x:a.x,y:a.y,size:a.size,degree:a.degree,inDegree:a.inDegree,outDegree:a.outDegree,displayX:a.displayX,displayY:a.displayY,displaySize:a.displaySize,label:a.label,id:a.id,color:a.color,fixed:a.fixed,active:a.active,hidden:a.hidden,forceLabel:a.forceLabel,attr:a.attr}}function j(a){return{source:a.source.id,target:a.target.id,size:a.size,type:a.type,weight:a.weight,displaySize:a.displaySize,label:a.label,hidden:a.hidden,id:a.id,attr:a.attr,color:a.color}}
9
+ function f(){c.nodes=[];c.nodesIndex={};c.edges=[];c.edgesIndex={};return c}sigma.classes.Cascade.call(this);sigma.classes.EventDispatcher.call(this);var c=this;this.p={minNodeSize:0,maxNodeSize:0,minEdgeSize:0,maxEdgeSize:0,scalingMode:"inside",nodesPowRatio:0.5,edgesPowRatio:0};this.borders={};f();this.addNode=function(a,b){if(c.nodesIndex[a])throw Error('Node "'+a+'" already exists.');var b=b||{},d={x:0,y:0,size:1,degree:0,inDegree:0,outDegree:0,fixed:!1,active:!1,hidden:!1,forceLabel:!1,label:a.toString(),
10
+ id:a.toString(),attr:{}},f;for(f in b)switch(f){case "id":break;case "x":case "y":case "size":d[f]=+b[f];break;case "fixed":case "active":case "hidden":case "forceLabel":d[f]=!!b[f];break;case "color":case "label":d[f]=b[f];break;default:d.attr[f]=b[f]}c.nodes.push(d);c.nodesIndex[a.toString()]=d;return c};this.addEdge=function(a,b,d,f){if(c.edgesIndex[a])throw Error('Edge "'+a+'" already exists.');if(!c.nodesIndex[b])throw Error("Edge's source \""+b+'" does not exist yet.');if(!c.nodesIndex[d])throw Error("Edge's target \""+
11
+ d+'" does not exist yet.');f=f||{};b={source:c.nodesIndex[b],target:c.nodesIndex[d],size:1,weight:1,displaySize:0.5,label:a.toString(),id:a.toString(),hidden:!1,attr:{}};b.source.degree++;b.source.outDegree++;b.target.degree++;b.target.inDegree++;for(var o in f)switch(o){case "id":case "source":case "target":break;case "hidden":b[o]=!!f[o];break;case "size":case "weight":b[o]=+f[o];break;case "color":b[o]=f[o].toString();break;case "type":b[o]=f[o].toString();break;case "label":b[o]=f[o];break;default:b.attr[o]=
12
+ f[o]}c.edges.push(b);c.edgesIndex[a.toString()]=b;return c};this.dropNode=function(a){((a instanceof Array?a:[a])||[]).forEach(function(a){if(c.nodesIndex[a]){var b=null;c.nodes.some(function(c,f){return c.id==a?(b=f,!0):!1});null!=b&&c.nodes.splice(b,1);delete c.nodesIndex[a];c.edges=c.edges.filter(function(b){return b.source.id==a?(delete c.edgesIndex[b.id],b.target.degree--,b.target.inDegree--,!1):b.target.id==a?(delete c.edgesIndex[b.id],b.source.degree--,b.source.outDegree--,!1):!0})}else sigma.log('Node "'+
13
+ a+'" does not exist.')});return c};this.dropEdge=function(a){((a instanceof Array?a:[a])||[]).forEach(function(a){if(c.edgesIndex[a]){c.edgesIndex[a].source.degree--;c.edgesIndex[a].source.outDegree--;c.edgesIndex[a].target.degree--;c.edgesIndex[a].target.inDegree--;var b=null;c.edges.some(function(c,f){return c.id==a?(b=f,!0):!1});null!=b&&c.edges.splice(b,1);delete c.edgesIndex[a]}else sigma.log('Edge "'+a+'" does not exist.')});return c};this.iterEdges=function(a,b){var d=b?b.map(function(a){return c.edgesIndex[a]}):
14
+ c.edges,f=d.map(j);f.forEach(a);d.forEach(function(a,b){var d=f[b],l;for(l in d)switch(l){case "id":case "displaySize":break;case "weight":case "size":a[l]=+d[l];break;case "source":case "target":a[l]=c.nodesIndex[l]||a[l];break;case "hidden":a[l]=!!d[l];break;case "color":case "label":case "type":a[l]=(d[l]||"").toString();break;default:a.attr[l]=d[l]}});return c};this.iterNodes=function(a,f){var d=f?f.map(function(a){return c.nodesIndex[a]}):c.nodes,j=d.map(b);j.forEach(a);d.forEach(function(a,
15
+ b){var d=j[b],c;for(c in d)switch(c){case "id":case "attr":case "degree":case "inDegree":case "outDegree":case "displayX":case "displayY":case "displaySize":break;case "x":case "y":case "size":a[c]=+d[c];break;case "fixed":case "active":case "hidden":case "forceLabel":a[c]=!!d[c];break;case "color":case "label":a[c]=(d[c]||"").toString();break;default:a.attr[c]=d[c]}});return c};this.getEdges=function(a){var b=((a instanceof Array?a:[a])||[]).map(function(a){return j(c.edgesIndex[a])});return a instanceof
16
+ Array?b:b[0]};this.getNodes=function(a){var f=((a instanceof Array?a:[a])||[]).map(function(a){return b(c.nodesIndex[a])});return a instanceof Array?f:f[0]};this.empty=f;this.rescale=function(a,b,d,f){var j=0,h=0;d&&c.nodes.forEach(function(a){h=Math.max(a.size,h)});f&&c.edges.forEach(function(a){j=Math.max(a.size,j)});var h=h||1,j=j||1,g,l,s,t;d&&c.nodes.forEach(function(a){l=Math.max(a.x,l||a.x);g=Math.min(a.x,g||a.x);t=Math.max(a.y,t||a.y);s=Math.min(a.y,s||a.y)});var A="outside"==c.p.scalingMode?
17
+ Math.max(a/Math.max(l-g,1),b/Math.max(t-s,1)):Math.min(a/Math.max(l-g,1),b/Math.max(t-s,1)),i=(c.p.maxNodeSize||h)/A;l+=i;g-=i;t+=i;s-=i;var A="outside"==c.p.scalingMode?Math.max(a/Math.max(l-g,1),b/Math.max(t-s,1)):Math.min(a/Math.max(l-g,1),b/Math.max(t-s,1)),u,k;!c.p.maxNodeSize&&!c.p.minNodeSize?(u=1,k=0):c.p.maxNodeSize==c.p.minNodeSize?(u=0,k=c.p.maxNodeSize):(u=(c.p.maxNodeSize-c.p.minNodeSize)/h,k=c.p.minNodeSize);var B,E;!c.p.maxEdgeSize&&!c.p.minEdgeSize?(B=1,E=0):(B=c.p.maxEdgeSize==c.p.minEdgeSize?
18
+ 0:(c.p.maxEdgeSize-c.p.minEdgeSize)/j,E=c.p.minEdgeSize);d&&c.nodes.forEach(function(c){c.displaySize=c.size*u+k;if(!c.fixed){c.displayX=(c.x-(l+g)/2)*A+a/2;c.displayY=(c.y-(t+s)/2)*A+b/2}});f&&c.edges.forEach(function(a){a.displaySize=a.size*B+E});return c};this.translate=function(a,b,d,f,j){var h=Math.pow(d,c.p.nodesPowRatio);f&&c.nodes.forEach(function(c){c.fixed||(c.displayX=c.displayX*d+a,c.displayY=c.displayY*d+b);c.displaySize*=h});h=Math.pow(d,c.p.edgesPowRatio);j&&c.edges.forEach(function(a){a.displaySize*=
19
+ h});return c};this.setBorders=function(){c.borders={};c.nodes.forEach(function(a){c.borders.minX=Math.min(void 0==c.borders.minX?a.displayX-a.displaySize:c.borders.minX,a.displayX-a.displaySize);c.borders.maxX=Math.max(void 0==c.borders.maxX?a.displayX+a.displaySize:c.borders.maxX,a.displayX+a.displaySize);c.borders.minY=Math.min(void 0==c.borders.minY?a.displayY-a.displaySize:c.borders.minY,a.displayY-a.displaySize);c.borders.maxY=Math.max(void 0==c.borders.maxY?a.displayY-a.displaySize:c.borders.maxY,
20
+ a.displayY-a.displaySize)})};this.checkHover=function(a,b){var d,f,j,h=[],g=[];c.nodes.forEach(function(c){if(c.hidden)c.hover=!1;else{d=Math.abs(c.displayX-a);f=Math.abs(c.displayY-b);j=c.displaySize;var s=c.hover,t=d<j&&f<j&&Math.sqrt(d*d+f*f)<j;s&&!t?(c.hover=!1,g.push(c.id)):t&&!s&&(c.hover=!0,h.push(c.id))}});h.length&&c.dispatch("overnodes",h);g.length&&c.dispatch("outnodes",g);return c}}function q(b,j){function f(){var a;a="<p>GLOBAL :</p>";for(var b in c.p.globalProbes)a+="<p>"+b+" : "+c.p.globalProbes[b]()+
21
+ "</p>";a+="<br><p>LOCAL :</p>";for(b in c.p.localProbes)a+="<p>"+b+" : "+c.p.localProbes[b]()+"</p>";c.p.dom.innerHTML=a;return c}sigma.classes.Cascade.call(this);var c=this;this.instance=b;this.monitoring=!1;this.p={fps:40,dom:j,globalProbes:{"Time (ms)":sigma.chronos.getExecutionTime,Queue:sigma.chronos.getQueuedTasksCount,Tasks:sigma.chronos.getTasksCount,FPS:sigma.chronos.getFPS},localProbes:{"Nodes count":function(){return c.instance.graph.nodes.length},"Edges count":function(){return c.instance.graph.edges.length}}};
22
+ this.activate=function(){c.monitoring||(c.monitoring=window.setInterval(f,1E3/c.p.fps));return c};this.desactivate=function(){c.monitoring&&(window.clearInterval(c.monitoring),c.monitoring=null,c.p.dom.innerHTML="");return c}}function g(b){var j=b.changedTouches[0],f="";switch(b.type){case "touchstart":f="mousedown";break;case "touchmove":f="mousemove";break;case "touchend":f="mouseup";break;default:return}var c=document.createEvent("MouseEvent");c.initMouseEvent(f,!0,!0,window,1,j.posX,j.posY,j.clientX,
23
+ j.clientY,!1,!1,!1,!1,0,null);j.target.dispatchEvent(c);b.preventDefault()}function p(b){function j(b){a.p.mouseEnabled&&(f(a.mouseX,a.mouseY,a.ratio*(0<(void 0!=b.wheelDelta&&b.wheelDelta||void 0!=b.detail&&-b.detail)?a.p.zoomMultiply:1/a.p.zoomMultiply)),a.p.blockScroll&&(b.preventDefault?b.preventDefault():b.returnValue=!1))}function f(b,d,f){if(!a.isMouseDown&&(window.clearInterval(a.interpolationID),m=void 0!=f,w=a.stageX,n=b,o=a.stageY,l=d,h=f||a.ratio,h=Math.min(Math.max(h,a.p.minRatio),a.p.maxRatio),
24
+ u=a.p.directZooming?1-(m?a.p.zoomDelta:a.p.dragDelta):0,a.ratio!=h||a.stageX!=n||a.stageY!=l))c(),a.interpolationID=window.setInterval(c,50),a.dispatch("startinterpolate")}function c(){u+=m?a.p.zoomDelta:a.p.dragDelta;u=Math.min(u,1);var b=sigma.easing.quadratic.easeout(u),c=a.ratio;a.ratio=c*(1-b)+h*b;m?(a.stageX=n+(a.stageX-n)*a.ratio/c,a.stageY=l+(a.stageY-l)*a.ratio/c):(a.stageX=w*(1-b)+n*b,a.stageY=o*(1-b)+l*b);a.dispatch("interpolate");1<=u&&(window.clearInterval(a.interpolationID),b=a.ratio,
25
+ m?(a.ratio=h,a.stageX=n+(a.stageX-n)*a.ratio/b,a.stageY=l+(a.stageY-l)*a.ratio/b):(a.stageX=n,a.stageY=l),a.dispatch("stopinterpolate"))}sigma.classes.Cascade.call(this);sigma.classes.EventDispatcher.call(this);var a=this;this.p={minRatio:1,maxRatio:32,marginRatio:1,zoomDelta:0.1,dragDelta:0.3,zoomMultiply:2,directZooming:!1,blockScroll:!0,inertia:1.1,mouseEnabled:!0,touchEnabled:!0};var i=0,d=0,w=0,o=0,h=1,n=0,l=0,s=0,t=0,A=0,k=0,u=0,m=!1;this.stageY=this.stageX=0;this.ratio=1;this.mouseY=this.mouseX=
26
+ 0;this.isTouchDown=this.isMouseDown=!1;b.addEventListener("DOMMouseScroll",j,!0);b.addEventListener("mousewheel",j,!0);b.addEventListener("mousemove",function(b){a.mouseX=void 0!=b.offsetX&&b.offsetX||void 0!=b.layerX&&b.layerX||void 0!=b.clientX&&b.clientX;a.mouseY=void 0!=b.offsetY&&b.offsetY||void 0!=b.layerY&&b.layerY||void 0!=b.clientY&&b.clientY;if(a.isMouseDown){var c=a.mouseX-i+w,h=a.mouseY-d+o;if(c!=a.stageX||h!=a.stageY)t=s,k=A,s=c,A=h,a.stageX=c,a.stageY=h,a.dispatch("drag")}a.dispatch("move");
27
+ b.preventDefault?b.preventDefault():b.returnValue=!1},!0);b.addEventListener("mousedown",function(b){a.p.mouseEnabled&&(a.isMouseDown=!0,a.dispatch("mousedown"),w=a.stageX,o=a.stageY,i=a.mouseX,d=a.mouseY,t=s=a.stageX,k=A=a.stageY,a.dispatch("startdrag"),b.preventDefault?b.preventDefault():b.returnValue=!1)},!0);document.addEventListener("mouseup",function(b){a.p.mouseEnabled&&a.isMouseDown&&(a.isMouseDown=!1,a.dispatch("mouseup"),(w!=a.stageX||o!=a.stageY)&&f(a.stageX+a.p.inertia*(a.stageX-t),a.stageY+
28
+ a.p.inertia*(a.stageY-k)),b.preventDefault?b.preventDefault():b.returnValue=!1)},!0);b.addEventListener("touchstart",g,!0);b.addEventListener("touchmove",g,!0);document.addEventListener("touchend",g,!0);b.addEventListener("touchcancel",g,!0);this.checkBorders=function(){return a};this.interpolate=f}function z(b,j,f,c,a,g,d){function i(a){var b=c,d="fixed"==h.p.labelSize?h.p.defaultLabelSize:h.p.labelSizeRatio*a.displaySize;b.font=(h.p.hoverFontStyle||h.p.fontStyle||"")+" "+d+"px "+(h.p.hoverFont||
29
+ h.p.font||"");b.fillStyle="node"==h.p.labelHoverBGColor?a.color||h.p.defaultNodeColor:h.p.defaultHoverLabelBGColor;b.beginPath();h.p.labelHoverShadow&&(b.shadowOffsetX=0,b.shadowOffsetY=0,b.shadowBlur=4,b.shadowColor=h.p.labelHoverShadowColor);sigma.tools.drawRoundRect(b,Math.round(a.displayX-d/2-2),Math.round(a.displayY-d/2-2),Math.round(b.measureText(a.label).width+1.5*a.displaySize+d/2+4),Math.round(d+4),Math.round(d/2+2),"left");b.closePath();b.fill();b.shadowOffsetX=0;b.shadowOffsetY=0;b.shadowBlur=
30
+ 0;b.beginPath();b.fillStyle="node"==h.p.nodeBorderColor?a.color||h.p.defaultNodeColor:h.p.defaultNodeBorderColor;b.arc(Math.round(a.displayX),Math.round(a.displayY),a.displaySize+h.p.borderSize,0,2*Math.PI,!0);b.closePath();b.fill();b.beginPath();b.fillStyle="node"==h.p.nodeHoverColor?a.color||h.p.defaultNodeColor:h.p.defaultNodeHoverColor;b.arc(Math.round(a.displayX),Math.round(a.displayY),a.displaySize,0,2*Math.PI,!0);b.closePath();b.fill();b.fillStyle="node"==h.p.labelHoverColor?a.color||h.p.defaultNodeColor:
31
+ h.p.defaultLabelHoverColor;b.fillText(a.label,Math.round(a.displayX+1.5*a.displaySize),Math.round(a.displayY+d/2-3));return h}function o(a){if(isNaN(a.x)||isNaN(a.y))throw Error("A node's coordinate is not a number (id: "+a.id+")");return!a.hidden&&a.displayX+a.displaySize>-n/3&&a.displayX-a.displaySize<4*n/3&&a.displayY+a.displaySize>-l/3&&a.displayY-a.displaySize<4*l/3}sigma.classes.Cascade.call(this);var h=this;this.p={labelColor:"default",defaultLabelColor:"#000",labelHoverBGColor:"default",defaultHoverLabelBGColor:"#fff",
32
+ labelHoverShadow:!0,labelHoverShadowColor:"#000",labelHoverColor:"default",defaultLabelHoverColor:"#000",labelActiveBGColor:"default",defaultActiveLabelBGColor:"#fff",labelActiveShadow:!0,labelActiveShadowColor:"#000",labelActiveColor:"default",defaultLabelActiveColor:"#000",labelSize:"fixed",defaultLabelSize:12,labelSizeRatio:2,labelThreshold:6,font:"Arial",hoverFont:"",activeFont:"",fontStyle:"",hoverFontStyle:"",activeFontStyle:"",edgeColor:"source",defaultEdgeColor:"#aaa",defaultEdgeType:"line",
33
+ defaultNodeColor:"#aaa",nodeHoverColor:"node",defaultNodeHoverColor:"#fff",nodeActiveColor:"node",defaultNodeActiveColor:"#fff",borderSize:0,nodeBorderColor:"node",defaultNodeBorderColor:"#fff",edgesSpeed:200,nodesSpeed:200,labelsSpeed:200};var n=g,l=d;this.currentLabelIndex=this.currentNodeIndex=this.currentEdgeIndex=0;this.task_drawLabel=function(){for(var b=a.nodes.length,c=0;c++<h.p.labelsSpeed&&h.currentLabelIndex<b;)if(h.isOnScreen(a.nodes[h.currentLabelIndex])){var d=a.nodes[h.currentLabelIndex++],
34
+ j=f;if(d.displaySize>=h.p.labelThreshold||d.forceLabel){var g="fixed"==h.p.labelSize?h.p.defaultLabelSize:h.p.labelSizeRatio*d.displaySize;j.font=h.p.fontStyle+g+"px "+h.p.font;j.fillStyle="node"==h.p.labelColor?d.color||h.p.defaultNodeColor:h.p.defaultLabelColor;j.fillText(d.label,Math.round(d.displayX+1.5*d.displaySize),Math.round(d.displayY+g/2-3))}}else h.currentLabelIndex++;return h.currentLabelIndex<b};this.task_drawEdge=function(){for(var b=a.edges.length,c,d,f=0;f++<h.p.edgesSpeed&&h.currentEdgeIndex<
35
+ b;)if(e=a.edges[h.currentEdgeIndex],c=e.source,d=e.target,e.hidden||c.hidden||d.hidden||!h.isOnScreen(c)&&!h.isOnScreen(d))h.currentEdgeIndex++;else{c=a.edges[h.currentEdgeIndex++];d=c.source.displayX;var g=c.source.displayY,o=c.target.displayX,i=c.target.displayY,l=c.color;if(!l)switch(h.p.edgeColor){case "source":l=c.source.color||h.p.defaultNodeColor;break;case "target":l=c.target.color||h.p.defaultNodeColor;break;default:l=h.p.defaultEdgeColor}var n=j;switch(c.type||h.p.defaultEdgeType){case "curve":n.strokeStyle=
36
+ l;n.lineWidth=c.displaySize/3;n.beginPath();n.moveTo(d,g);n.quadraticCurveTo((d+o)/2+(i-g)/4,(g+i)/2+(d-o)/4,o,i);n.stroke();break;default:n.strokeStyle=l,n.lineWidth=c.displaySize/3,n.beginPath(),n.moveTo(d,g),n.lineTo(o,i),n.stroke()}}return h.currentEdgeIndex<b};this.task_drawNode=function(){for(var c=a.nodes.length,d=0;d++<h.p.nodesSpeed&&h.currentNodeIndex<c;)if(h.isOnScreen(a.nodes[h.currentNodeIndex])){var f=a.nodes[h.currentNodeIndex++],j=Math.round(10*f.displaySize)/10,g=b;g.fillStyle=f.color;
37
+ g.beginPath();g.arc(f.displayX,f.displayY,j,0,2*Math.PI,!0);g.closePath();g.fill();f.hover&&i(f)}else h.currentNodeIndex++;return h.currentNodeIndex<c};this.drawActiveNode=function(a){var b=c;if(!o(a))return h;var d="fixed"==h.p.labelSize?h.p.defaultLabelSize:h.p.labelSizeRatio*a.displaySize;b.font=(h.p.activeFontStyle||h.p.fontStyle||"")+" "+d+"px "+(h.p.activeFont||h.p.font||"");b.fillStyle="node"==h.p.labelHoverBGColor?a.color||h.p.defaultNodeColor:h.p.defaultActiveLabelBGColor;b.beginPath();h.p.labelActiveShadow&&
38
+ (b.shadowOffsetX=0,b.shadowOffsetY=0,b.shadowBlur=4,b.shadowColor=h.p.labelActiveShadowColor);sigma.tools.drawRoundRect(b,Math.round(a.displayX-d/2-2),Math.round(a.displayY-d/2-2),Math.round(b.measureText(a.label).width+1.5*a.displaySize+d/2+4),Math.round(d+4),Math.round(d/2+2),"left");b.closePath();b.fill();b.shadowOffsetX=0;b.shadowOffsetY=0;b.shadowBlur=0;b.beginPath();b.fillStyle="node"==h.p.nodeBorderColor?a.color||h.p.defaultNodeColor:h.p.defaultNodeBorderColor;b.arc(Math.round(a.displayX),
39
+ Math.round(a.displayY),a.displaySize+h.p.borderSize,0,2*Math.PI,!0);b.closePath();b.fill();b.beginPath();b.fillStyle="node"==h.p.nodeActiveColor?a.color||h.p.defaultNodeColor:h.p.defaultNodeActiveColor;b.arc(Math.round(a.displayX),Math.round(a.displayY),a.displaySize,0,2*Math.PI,!0);b.closePath();b.fill();b.fillStyle="node"==h.p.labelActiveColor?a.color||h.p.defaultNodeColor:h.p.defaultLabelActiveColor;b.fillText(a.label,Math.round(a.displayX+1.5*a.displaySize),Math.round(a.displayY+d/2-3));return h};
40
+ this.drawHoverNode=i;this.isOnScreen=o;this.resize=function(a,b){n=a;l=b;return h}}function F(b,g){function f(){sigma.chronos.removeTask("node_"+d.id,2).removeTask("edge_"+d.id,2).removeTask("label_"+d.id,2).stopTasks();return d}function c(a,b){d.domElements[a]=document.createElement(b);d.domElements[a].style.position="absolute";d.domElements[a].setAttribute("id","sigma_"+a+"_"+d.id);d.domElements[a].setAttribute("class","sigma_"+a+"_"+b);d.domElements[a].setAttribute("width",d.width+"px");d.domElements[a].setAttribute("height",
41
+ d.height+"px");d.domRoot.appendChild(d.domElements[a]);return d}function a(){d.p.drawHoverNodes&&(d.graph.checkHover(d.mousecaptor.mouseX,d.mousecaptor.mouseY),d.graph.nodes.forEach(function(a){a.hover&&!a.active&&d.plotter.drawHoverNode(a)}));return d}function D(){d.p.drawActiveNodes&&d.graph.nodes.forEach(function(a){a.active&&d.plotter.drawActiveNode(a)});return d}sigma.classes.Cascade.call(this);sigma.classes.EventDispatcher.call(this);var d=this;this.id=g.toString();this.p={auto:!0,drawNodes:2,
42
+ drawEdges:1,drawLabels:2,lastNodes:2,lastEdges:0,lastLabels:2,drawHoverNodes:!0,drawActiveNodes:!0};this.domRoot=b;this.width=this.domRoot.offsetWidth;this.height=this.domRoot.offsetHeight;this.graph=new i;this.domElements={};c("edges","canvas");c("nodes","canvas");c("labels","canvas");c("hover","canvas");c("monitor","div");c("mouse","canvas");this.plotter=new z(this.domElements.nodes.getContext("2d"),this.domElements.edges.getContext("2d"),this.domElements.labels.getContext("2d"),this.domElements.hover.getContext("2d"),
43
+ this.graph,this.width,this.height);this.monitor=new q(this,this.domElements.monitor);this.mousecaptor=new p(this.domElements.mouse,this.id);this.mousecaptor.bind("drag interpolate",function(){d.draw(d.p.auto?2:d.p.drawNodes,d.p.auto?0:d.p.drawEdges,d.p.auto?2:d.p.drawLabels,!0)}).bind("stopdrag stopinterpolate",function(){d.draw(d.p.auto?2:d.p.drawNodes,d.p.auto?1:d.p.drawEdges,d.p.auto?2:d.p.drawLabels,!0)}).bind("mousedown mouseup",function(a){var b=d.graph.nodes.filter(function(a){return!!a.hover}).map(function(a){return a.id});
44
+ d.dispatch("mousedown"==a.type?"downgraph":"upgraph");b.length&&d.dispatch("mousedown"==a.type?"downnodes":"upnodes",b)}).bind("move",function(){d.domElements.hover.getContext("2d").clearRect(0,0,d.domElements.hover.width,d.domElements.hover.height);a();D()});sigma.chronos.bind("startgenerators",function(){sigma.chronos.getGeneratorsIDs().some(function(a){return!!a.match(RegExp("_ext_"+d.id+"$",""))})&&d.draw(d.p.auto?2:d.p.drawNodes,d.p.auto?0:d.p.drawEdges,d.p.auto?2:d.p.drawLabels)}).bind("stopgenerators",
45
+ function(){d.draw()});for(var w=0;w<m.plugins.length;w++)m.plugins[w](this);this.draw=function(a,b,c,g){if(g&&sigma.chronos.getGeneratorsIDs().some(function(a){return!!a.match(RegExp("_ext_"+d.id+"$",""))}))return d;a=void 0==a?d.p.drawNodes:a;b=void 0==b?d.p.drawEdges:b;c=void 0==c?d.p.drawLabels:c;g={nodes:a,edges:b,labels:c};d.p.lastNodes=a;d.p.lastEdges=b;d.p.lastLabels=c;f();d.graph.rescale(d.width,d.height,0<a,0<b).setBorders();d.mousecaptor.checkBorders(d.graph.borders,d.width,d.height);d.graph.translate(d.mousecaptor.stageX,
46
+ d.mousecaptor.stageY,d.mousecaptor.ratio,0<a,0<b);d.dispatch("graphscaled");for(var j in d.domElements)"canvas"==d.domElements[j].nodeName.toLowerCase()&&(void 0==g[j]||0<=g[j])&&d.domElements[j].getContext("2d").clearRect(0,0,d.domElements[j].width,d.domElements[j].height);d.plotter.currentEdgeIndex=0;d.plotter.currentNodeIndex=0;d.plotter.currentLabelIndex=0;j=null;g=!1;if(a)if(1<a)for(;d.plotter.task_drawNode(););else sigma.chronos.addTask(d.plotter.task_drawNode,"node_"+d.id,!1),g=!0,j="node_"+
47
+ d.id;if(c)if(1<c)for(;d.plotter.task_drawLabel(););else j?sigma.chronos.queueTask(d.plotter.task_drawLabel,"label_"+d.id,j):sigma.chronos.addTask(d.plotter.task_drawLabel,"label_"+d.id,!1),g=!0,j="label_"+d.id;if(b)if(1<b)for(;d.plotter.task_drawEdge(););else j?sigma.chronos.queueTask(d.plotter.task_drawEdge,"edge_"+d.id,j):sigma.chronos.addTask(d.plotter.task_drawEdge,"edge_"+d.id,!1),g=!0,j="edge_"+d.id;d.dispatch("draw");d.refresh();g&&sigma.chronos.runTasks();return d};this.resize=function(a,
48
+ b){var c=d.width,f=d.height;void 0!=a&&void 0!=b?(d.width=a,d.height=b):(d.width=d.domRoot.offsetWidth,d.height=d.domRoot.offsetHeight);if(c!=d.width||f!=d.height){for(var j in d.domElements)d.domElements[j].setAttribute("width",d.width+"px"),d.domElements[j].setAttribute("height",d.height+"px");d.plotter.resize(d.width,d.height);d.draw(d.p.lastNodes,d.p.lastEdges,d.p.lastLabels,!0)}return d};this.refresh=function(){d.domElements.hover.getContext("2d").clearRect(0,0,d.domElements.hover.width,d.domElements.hover.height);
49
+ a();D();return d};this.drawHover=a;this.drawActive=D;this.clearSchedule=f;window.addEventListener("resize",function(){d.resize()})}function x(b){var j=this;sigma.classes.EventDispatcher.call(this);this._core=b;this.kill=function(){};this.getID=function(){return b.id};this.configProperties=function(f,c){var a=b.config(f,c);return a==b?j:a};this.drawingProperties=function(f,c){var a=b.plotter.config(f,c);return a==b.plotter?j:a};this.mouseProperties=function(f,c){var a=b.mousecaptor.config(f,c);return a==
50
+ b.mousecaptor?j:a};this.graphProperties=function(f,c){var a=b.graph.config(f,c);return a==b.graph?j:a};this.getMouse=function(){return{mouseX:b.mousecaptor.mouseX,mouseY:b.mousecaptor.mouseY,down:b.mousecaptor.isMouseDown}};this.position=function(f,c,a){if(0==arguments.length)return{stageX:b.mousecaptor.stageX,stageY:b.mousecaptor.stageY,ratio:b.mousecaptor.ratio};b.mousecaptor.stageX=void 0!=f?f:b.mousecaptor.stageX;b.mousecaptor.stageY=void 0!=c?c:b.mousecaptor.stageY;b.mousecaptor.ratio=void 0!=
51
+ a?a:b.mousecaptor.ratio;return j};this.goTo=function(f,c,a){b.mousecaptor.interpolate(f,c,a);return j};this.zoomTo=function(f,c,a){a=Math.min(Math.max(b.mousecaptor.config("minRatio"),a),b.mousecaptor.config("maxRatio"));a==b.mousecaptor.ratio?b.mousecaptor.interpolate(f-b.width/2+b.mousecaptor.stageX,c-b.height/2+b.mousecaptor.stageY):b.mousecaptor.interpolate((a*f-b.mousecaptor.ratio*b.width/2)/(a-b.mousecaptor.ratio),(a*c-b.mousecaptor.ratio*b.height/2)/(a-b.mousecaptor.ratio),a);return j};this.resize=
52
+ function(f,c){b.resize(f,c);return j};this.draw=function(f,c,a,g){b.draw(f,c,a,g);return j};this.refresh=function(){b.refresh();return j};this.addGenerator=function(f,c,a){sigma.chronos.addGenerator(f+"_ext_"+b.id,c,a);return j};this.removeGenerator=function(f){sigma.chronos.removeGenerator(f+"_ext_"+b.id);return j};this.addNode=function(f,c){b.graph.addNode(f,c);return j};this.addEdge=function(f,c,a,g){b.graph.addEdge(f,c,a,g);return j};this.dropNode=function(f){b.graph.dropNode(f);return j};this.dropEdge=
53
+ function(f){b.graph.dropEdge(f);return j};this.pushGraph=function(f,c){f.nodes&&f.nodes.forEach(function(a){a.id&&(!c||!b.graph.nodesIndex[a.id])&&j.addNode(a.id,a)});f.edges&&f.edges.forEach(function(a){(validID=a.source&&a.target&&a.id)&&(!c||!b.graph.edgesIndex[a.id])&&j.addNode(a.id,a.source,a.target,a)});return j};this.emptyGraph=function(){b.graph.empty();return j};this.getNodesCount=function(){return b.graph.nodes.length};this.getEdgesCount=function(){return b.graph.edges.length};this.iterNodes=
54
+ function(f,c){b.graph.iterNodes(f,c);return j};this.iterEdges=function(f,c){b.graph.iterEdges(f,c);return j};this.getNodes=function(f){return b.graph.getNodes(f)};this.getEdges=function(f){return b.graph.getEdges(f)};this.activateMonitoring=function(){return b.monitor.activate()};this.desactivateMonitoring=function(){return b.monitor.desactivate()};b.bind("downnodes upnodes downgraph upgraph",function(b){j.dispatch(b.type,b.content)});b.graph.bind("overnodes outnodes",function(b){j.dispatch(b.type,
55
+ b.content)})}var k=0,m={plugins:[]};sigma.init=function(b){b=new F(b,(++k).toString());sigma.instances[k]=new x(b);return sigma.instances[k]};sigma.addPlugin=function(b,g,f){x.prototype[b]=g;m.plugins.push(f)};sigma.chronos=new function(){function b(a){window.setTimeout(a,0);return h}function g(){for(h.dispatch("frameinserted");n&&v.length&&f(););!n||!v.length?a():(B=(new Date).getTime(),m++,z=u-p,q=p-z,h.dispatch("insertframe"),b(g))}function f(){C%=v.length;if(!v[C].task()){var a=v[C].taskName;
56
+ y=y.filter(function(b){b.taskParent==a&&v.push({taskName:b.taskName,task:b.task});return b.taskParent!=a});h.dispatch("killed",v.splice(C--,1)[0])}C++;u=(new Date).getTime()-B;return u<=q}function c(){n=!0;m=C=0;x=B=(new Date).getTime();h.dispatch("start");h.dispatch("insertframe");b(g);return h}function a(){h.dispatch("stop");n=!1;return h}function i(a,b,d){if("function"!=typeof a)throw Error('Task "'+b+'" is not a function');v.push({taskName:b,task:a});n=!(!n&&!(d&&c()||1));return h}function d(a){return a?
57
+ Object.keys(r).filter(function(a){return!!r[a].on}).length:Object.keys(r).length}function w(){Object.keys(r).length?(h.dispatch("startgenerators"),h.unbind("killed",o),b(function(){for(var a in r)r[a].on=!0,i(r[a].task,a,!1)}),h.bind("killed",o).runTasks()):h.dispatch("stopgenerators");return h}function o(a){void 0!=r[a.content.taskName]&&(r[a.content.taskName].del||!r[a.content.taskName].condition()?delete r[a.content.taskName]:r[a.content.taskName].on=!1,0==d(!0)&&w())}sigma.classes.EventDispatcher.call(this);
58
+ var h=this,n=!1,l=80,k=0,m=0,p=1E3/l,q=p,u=0,x=0,B=0,z=0,r={},v=[],y=[],C=0;this.frequency=function(a){return void 0!=a?(l=Math.abs(1*a),p=1E3/l,m=0,h):l};this.runTasks=c;this.stopTasks=a;this.insertFrame=b;this.addTask=i;this.queueTask=function(a,b,c){if("function"!=typeof a)throw Error('Task "'+b+'" is not a function');if(!v.concat(y).some(function(a){return a.taskName==c}))throw Error('Parent task "'+c+'" of "'+b+'" is not attached.');y.push({taskParent:c,taskName:b,task:a});return h};this.removeTask=
59
+ function(b,c){if(void 0==b)v=[],1==c?y=[]:2==c&&(v=y,y=[]),a();else{var d="string"==typeof b?b:"";v=v.filter(function(a){return("string"==typeof b?a.taskName==b:a.task==b)?(d=a.taskName,!1):!0});0<c&&(y=y.filter(function(a){1==c&&a.taskParent==d&&v.push(a);return a.taskParent!=d}))}n=!(v.length&&(!a()||1));return h};this.addGenerator=function(a,b,c){if(void 0!=r[a])return h;r[a]={task:b,condition:c};0==d(!0)&&w();return h};this.removeGenerator=function(a){r[a]&&(r[a].on=!1,r[a].del=!0);return h};
60
+ this.startGenerators=w;this.getGeneratorsIDs=function(){return Object.keys(r)};this.getFPS=function(){n&&(k=Math.round(1E4*(m/((new Date).getTime()-x)))/10);return k};this.getTasksCount=function(){return v.length};this.getQueuedTasksCount=function(){return y.length};this.getExecutionTime=function(){return B-x};return this};sigma.debugMode=0;sigma.log=function(){if(1==sigma.debugMode)for(var b in arguments)console.log(arguments[b]);else if(1<sigma.debugMode)for(b in arguments)throw Error(arguments[b]);
61
+ return sigma};sigma.easing={linear:{},quadratic:{}};sigma.easing.linear.easenone=function(b){return b};sigma.easing.quadratic.easein=function(b){return b*b};sigma.easing.quadratic.easeout=function(b){return-b*(b-2)};sigma.easing.quadratic.easeinout=function(b){return 1>(b*=2)?0.5*b*b:-0.5*(--b*(b-2)-1)};sigma.tools.drawRoundRect=function(b,g,f,c,a,i,d){var i=i?i:0,k=d?d:[],k="string"==typeof k?k.split(" "):k,d=i&&(0<=k.indexOf("topleft")||0<=k.indexOf("top")||0<=k.indexOf("left")),m=i&&(0<=k.indexOf("topright")||
62
+ 0<=k.indexOf("top")||0<=k.indexOf("right")),h=i&&(0<=k.indexOf("bottomleft")||0<=k.indexOf("bottom")||0<=k.indexOf("left")),k=i&&(0<=k.indexOf("bottomright")||0<=k.indexOf("bottom")||0<=k.indexOf("right"));b.moveTo(g,f+i);d?b.arcTo(g,f,g+i,f,i):b.lineTo(g,f);m?(b.lineTo(g+c-i,f),b.arcTo(g+c,f,g+c,f+i,i)):b.lineTo(g+c,f);k?(b.lineTo(g+c,f+a-i),b.arcTo(g+c,f+a,g+c-i,f+a,i)):b.lineTo(g+c,f+a);h?(b.lineTo(g+i,f+a),b.arcTo(g,f+a,g,f+a-i,i)):b.lineTo(g,f+a);b.lineTo(g,f+i)};sigma.tools.getRGB=function(b,
63
+ g){var b=b.toString(),f={r:0,g:0,b:0};if(3<=b.length&&"#"==b.charAt(0)){var c=b.length-1;6==c?f={r:parseInt(b.charAt(1)+b.charAt(2),16),g:parseInt(b.charAt(3)+b.charAt(4),16),b:parseInt(b.charAt(5)+b.charAt(5),16)}:3==c&&(f={r:parseInt(b.charAt(1)+b.charAt(1),16),g:parseInt(b.charAt(2)+b.charAt(2),16),b:parseInt(b.charAt(3)+b.charAt(3),16)})}g&&(f=[f.r,f.g,f.b]);return f};sigma.tools.rgbToHex=function(b,g,f){return sigma.tools.toHex(b)+sigma.tools.toHex(g)+sigma.tools.toHex(f)};sigma.tools.toHex=
64
+ function(b){b=parseInt(b,10);if(isNaN(b))return"00";b=Math.max(0,Math.min(b,255));return"0123456789ABCDEF".charAt((b-b%16)/16)+"0123456789ABCDEF".charAt(b%16)};sigma.publicPrototype=x.prototype})();
js/sigma/sigma.parseJson.js ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // // Scott Hale (Oxford Internet Institute)
2
+ // // Requires sigma.js and jquery to be loaded
3
+ // // based on parseGexf from Mathieu Jacomy @ Sciences Po M�dialab & WebAtlas
4
+
5
+
6
+ var isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
7
+
8
+ // Regular implementation for Chrome and other browsers
9
+ sigma.publicPrototype.parseJson = function(gzippedJsonPath, callback) {
10
+ var sigmaInstance = this;
11
+
12
+ // Use XMLHttpRequest for binary data
13
+ var xhr = new XMLHttpRequest();
14
+ xhr.open('GET', gzippedJsonPath, true);
15
+ xhr.responseType = 'arraybuffer';
16
+
17
+ xhr.onload = function() {
18
+ if (xhr.status === 200) {
19
+ try {
20
+ // Decompress the gzipped data using pako
21
+ var inflatedData = pako.inflate(new Uint8Array(xhr.response));
22
+
23
+ // Convert binary data to string
24
+ var jsonString = new TextDecoder('utf-8').decode(inflatedData);
25
+
26
+ // Parse the JSON
27
+ var jsonData = JSON.parse(jsonString);
28
+
29
+ // Process nodes
30
+ for (var i = 0; i < jsonData.nodes.length; i++) {
31
+ var id = jsonData.nodes[i].id;
32
+ sigmaInstance.addNode(id, jsonData.nodes[i]);
33
+ }
34
+
35
+ // Process edges
36
+ for (var j = 0; j < jsonData.edges.length; j++) {
37
+ var edgeNode = jsonData.edges[j];
38
+ var source = edgeNode.source;
39
+ var target = edgeNode.target;
40
+ var label = edgeNode.label;
41
+ var eid = edgeNode.id;
42
+
43
+ sigmaInstance.addEdge(eid, source, target, edgeNode);
44
+ }
45
+
46
+ // Call the callback function if provided
47
+ if (callback) {
48
+ callback.call(sigmaInstance);
49
+ }
50
+ } catch (error) {
51
+ console.error("Error processing gzipped JSON:", error);
52
+ }
53
+ } else {
54
+ console.error("Error fetching gzipped JSON. Status:", xhr.status);
55
+ }
56
+ };
57
+
58
+ xhr.onerror = function() {
59
+ console.error("Network error while fetching gzipped JSON");
60
+ };
61
+
62
+ xhr.send();
63
+ };
64
+
65
+
66
+ // Create a new function specifically for loading gzipped data safely
67
+ // This avoids sigma initialization issues by loading data first
68
+ var loadGzippedGraphData = function(gzippedJsonPath, callback) {
69
+ // Use XMLHttpRequest for binary data
70
+ var xhr = new XMLHttpRequest();
71
+ xhr.open('GET', gzippedJsonPath, true);
72
+ xhr.responseType = 'arraybuffer';
73
+
74
+ xhr.onload = function() {
75
+ if (xhr.status === 200) {
76
+ try {
77
+ // Decompress the gzipped data using pako
78
+ var inflatedData = pako.inflate(new Uint8Array(xhr.response));
79
+
80
+ // Convert binary data to string
81
+ var jsonString;
82
+ try {
83
+ jsonString = new TextDecoder('utf-8').decode(inflatedData);
84
+ } catch (e) {
85
+ // Fallback for older browsers
86
+ jsonString = "";
87
+ var array = inflatedData;
88
+ var i = 0, len = array.length;
89
+ var c, char2, char3;
90
+
91
+ while (i < len) {
92
+ c = array[i++];
93
+ switch (c >> 4) {
94
+ case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
95
+ // 0xxxxxxx
96
+ jsonString += String.fromCharCode(c);
97
+ break;
98
+ case 12: case 13:
99
+ // 110x xxxx 10xx xxxx
100
+ char2 = array[i++];
101
+ jsonString += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
102
+ break;
103
+ case 14:
104
+ // 1110 xxxx 10xx xxxx 10xx xxxx
105
+ char2 = array[i++];
106
+ char3 = array[i++];
107
+ jsonString += String.fromCharCode(((c & 0x0F) << 12) |
108
+ ((char2 & 0x3F) << 6) |
109
+ ((char3 & 0x3F) << 0));
110
+ break;
111
+ }
112
+ }
113
+ }
114
+
115
+ // Parse the JSON
116
+ var jsonData = JSON.parse(jsonString);
117
+
118
+ // Return the parsed data to the callback
119
+ if (callback) {
120
+ callback(jsonData);
121
+ }
122
+ } catch (error) {
123
+ console.error("Error processing gzipped JSON:", error);
124
+ if (callback) {
125
+ callback(null, error);
126
+ }
127
+ }
128
+ } else {
129
+ console.error("Error fetching gzipped JSON. Status:", xhr.status);
130
+ if (callback) {
131
+ callback(null, new Error("HTTP status: " + xhr.status));
132
+ }
133
+ }
134
+ };
135
+
136
+ xhr.onerror = function() {
137
+ console.error("Network error while fetching gzipped JSON");
138
+ if (callback) {
139
+ callback(null, new Error("Network error"));
140
+ }
141
+ };
142
+
143
+ xhr.send();
144
+ };
145
+
146
+
147
+
148
+
149
+ // Safe initialization for Safari
150
+ function initSigmaWithGzippedData(containerId, gzippedJsonPath, options, callbackFn) {
151
+ // Make options parameter optional
152
+ if (typeof options === 'function') {
153
+ callbackFn = options;
154
+ options = {};
155
+ }
156
+
157
+ options = options || {};
158
+
159
+ // For Safari, use a completely different approach
160
+ if (isSafari) {
161
+ // First, load the data
162
+ loadGzippedGraphData(gzippedJsonPath, function(data, error) {
163
+ if (error || !data) {
164
+ console.error("Failed to load graph data:", error);
165
+ return;
166
+ }
167
+
168
+ // Wait for DOM to be completely ready
169
+ jQuery(document).ready(function() {
170
+ // Make sure container is ready with dimensions
171
+ var container = document.getElementById(containerId);
172
+ if (!container) {
173
+ console.error("Container not found:", containerId);
174
+ return;
175
+ }
176
+
177
+ // Ensure container has dimensions
178
+ if (!container.offsetWidth || !container.offsetHeight) {
179
+ container.style.width = container.style.width || "100%";
180
+ container.style.height = container.style.height || "500px";
181
+ container.style.display = "block";
182
+ }
183
+
184
+ // Wait for next animation frame to ensure DOM updates
185
+ requestAnimationFrame(function() {
186
+ // Create settings with explicit container reference
187
+ var sigmaSettings = Object.assign({}, options);
188
+
189
+ // Wait a bit more for Safari
190
+ setTimeout(function() {
191
+ try {
192
+ // Initialize sigma with empty graph first
193
+ var sigmaInstance = new sigma(containerId);
194
+
195
+ // Add nodes and edges manually
196
+ for (var i = 0; i < data.nodes.length; i++) {
197
+ var id = data.nodes[i].id;
198
+ sigmaInstance.addNode(id, data.nodes[i]);
199
+ }
200
+
201
+ for (var j = 0; j < data.edges.length; j++) {
202
+ var edgeNode = data.edges[j];
203
+ var source = edgeNode.source;
204
+ var target = edgeNode.target;
205
+ var label = edgeNode.label || "";
206
+ var eid = edgeNode.id;
207
+
208
+ sigmaInstance.addEdge(eid, source, target, edgeNode);
209
+ }
210
+
211
+ // Refresh the graph
212
+ sigmaInstance.refresh();
213
+
214
+ // Call user callback if provided
215
+ if (callbackFn) {
216
+ callbackFn(sigmaInstance);
217
+ }
218
+ } catch (e) {
219
+ console.error("Error initializing sigma:", e);
220
+ }
221
+ }, 300); // Longer delay for Safari
222
+ });
223
+ });
224
+ });
225
+ } else {
226
+ // For Chrome and others, use a more standard approach
227
+ jQuery(document).ready(function() {
228
+ try {
229
+ var sigmaInstance = new sigma(containerId);
230
+ sigmaInstance.parseGzippedJson(gzippedJsonPath, function() {
231
+ this.refresh();
232
+ if (callbackFn) callbackFn(this);
233
+ });
234
+ } catch (e) {
235
+ console.error("Error initializing sigma:", e);
236
+ }
237
+ });
238
+ }
239
+ }
paper_atlas_data.json ADDED
The diff for this file is too large to render. See raw diff