Spaces:
Running
Running
Commit
·
35e5b82
1
Parent(s):
36ae7a7
initial app
Browse files- .gitignore +2 -1
- README.md +49 -0
- app.py +21 -0
- check_json.py +63 -0
- config.json +70 -0
- css/style.css +489 -0
- css/tablet.css +3 -0
- graphml_to_json.py +131 -0
- index.html +126 -0
- js/main.js +647 -0
- js/paper-atlas.js +433 -0
- js/sigma/plugins/sigma.plugins.filter.min.js +110 -0
- js/sigma/sigma.min.js +64 -0
- js/sigma/sigma.parseJson.js +239 -0
- paper_atlas_data.json +0 -0
.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
|
|