Skip to content

Commit 5726ad8

Browse files
committed
Enhance co-attendance graph visualization
- Proportional node sizing and color coding by degree - Edge thickness scales with co-attendance frequency - Improved visual effects and interactivity - Updated description with visualization details
1 parent be750eb commit 5726ad8

File tree

2 files changed

+151
-16
lines changed

2 files changed

+151
-16
lines changed

Graph Analysis/unified_analysis.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,11 @@ def write_html_report(
414414
<p class="explanation">People are connected if they attend the same meeting; a person's degree is how many unique people they co-attended with.</p>
415415
416416
<h3>Interactive Network Visualization</h3>
417-
<p class="explanation">Visual representation of the co-attendance graph. Nodes represent people, edges represent co-attendance. Use mouse to zoom and pan. Hover over nodes to see details. Click and drag nodes to reposition.</p>
417+
<p class="explanation">
418+
Visual representation of the co-attendance graph. <strong>Nodes represent people</strong>, with size and color indicating degree (number of connections) - larger, darker nodes have more connections. <strong>Edges represent co-attendance</strong> - thicker, darker edges indicate more frequent co-attendance.
419+
<br><br>
420+
<strong>Interactions:</strong> Use mouse wheel to zoom, click and drag to pan, drag nodes to reposition. Hover over nodes or edges to see detailed information. Click on a node to highlight its connections.
421+
</p>
418422
<div id="coattendance-network"></div>
419423
420424
<h3>Top Nodes by Degree</h3>

docs/script.js

Lines changed: 146 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,94 @@ function initCoattendanceNetwork() {
1616
coattendanceNetwork.destroy();
1717
}
1818

19-
const nodes = new vis.DataSet(coattendanceGraphData.nodes);
20-
const edges = new vis.DataSet(coattendanceGraphData.edges);
19+
// Calculate min/max values for scaling
20+
const nodeValues = coattendanceGraphData.nodes.map(n => n.value || 1);
21+
const maxNodeValue = Math.max(...nodeValues);
22+
const minNodeValue = Math.min(...nodeValues);
23+
const nodeValueRange = maxNodeValue - minNodeValue;
24+
25+
const edgeValues = coattendanceGraphData.edges.map(e => e.value || 1);
26+
const maxEdgeValue = Math.max(...edgeValues);
27+
const minEdgeValue = Math.min(...edgeValues);
28+
const edgeValueRange = maxEdgeValue - minEdgeValue;
29+
30+
// Scale nodes: size based on degree (value)
31+
// Node size between 10 and 50 pixels
32+
const scaledNodes = coattendanceGraphData.nodes.map(node => {
33+
const degree = node.value || 1;
34+
// Scale size: 10 + (degree - min) / range * 40
35+
const size = nodeValueRange > 0
36+
? 10 + ((degree - minNodeValue) / nodeValueRange) * 40
37+
: 25;
38+
39+
// Color based on degree: blue gradient (darker/larger = higher degree)
40+
// Scale from light blue (0, 150, 255) to dark blue (0, 50, 150)
41+
const intensity = nodeValueRange > 0
42+
? (degree - minNodeValue) / nodeValueRange
43+
: 0.5;
44+
const r = Math.floor(0 + intensity * 0);
45+
const g = Math.floor(150 - intensity * 100);
46+
const b = Math.floor(255 - intensity * 105);
47+
const color = `rgb(${r}, ${g}, ${b})`;
48+
49+
return {
50+
...node,
51+
size: size,
52+
color: {
53+
background: color,
54+
border: '#0366d6',
55+
highlight: {
56+
background: '#0366d6',
57+
border: '#002155'
58+
},
59+
hover: {
60+
background: '#0366d6',
61+
border: '#002155'
62+
}
63+
},
64+
font: {
65+
size: Math.max(10, Math.min(16, size * 0.6)),
66+
color: '#24292e',
67+
face: 'Arial',
68+
bold: degree > maxNodeValue * 0.7
69+
},
70+
borderWidth: 2,
71+
borderWidthSelected: 4
72+
};
73+
});
74+
75+
// Scale edges: width based on co-attendance frequency (weight)
76+
const scaledEdges = coattendanceGraphData.edges.map(edge => {
77+
const weight = edge.value || 1;
78+
// Edge width between 1 and 5 pixels
79+
const width = edgeValueRange > 0
80+
? 1 + ((weight - minEdgeValue) / edgeValueRange) * 4
81+
: 2;
82+
83+
// Color based on weight: lighter gray for frequent, darker for rare
84+
const opacity = edgeValueRange > 0
85+
? 0.3 + ((weight - minEdgeValue) / edgeValueRange) * 0.5
86+
: 0.5;
87+
88+
return {
89+
...edge,
90+
width: width,
91+
color: {
92+
color: `rgba(3, 102, 214, ${opacity})`,
93+
highlight: '#0366d6',
94+
hover: '#0366d6'
95+
},
96+
smooth: {
97+
type: 'continuous',
98+
roundness: 0.5
99+
},
100+
selectionWidth: width * 2,
101+
hoverWidth: width * 1.5
102+
};
103+
});
104+
105+
const nodes = new vis.DataSet(scaledNodes);
106+
const edges = new vis.DataSet(scaledEdges);
21107

22108
const data = {
23109
nodes: nodes,
@@ -27,46 +113,91 @@ function initCoattendanceNetwork() {
27113
const options = {
28114
nodes: {
29115
shape: 'dot',
30-
size: 16,
31116
font: {
32117
size: 12,
33-
color: '#24292e'
118+
color: '#24292e',
119+
face: 'Arial'
34120
},
35121
borderWidth: 2,
36-
borderColor: '#0366d6'
122+
shadow: {
123+
enabled: true,
124+
color: 'rgba(0,0,0,0.2)',
125+
size: 5,
126+
x: 2,
127+
y: 2
128+
}
37129
},
38130
edges: {
39-
width: 2,
40-
color: {
41-
color: '#e1e4e8',
42-
highlight: '#0366d6'
43-
},
44131
smooth: {
45-
type: 'continuous'
132+
type: 'continuous',
133+
roundness: 0.5
134+
},
135+
arrows: {
136+
to: {
137+
enabled: false
138+
}
139+
},
140+
shadow: {
141+
enabled: true,
142+
color: 'rgba(0,0,0,0.1)',
143+
size: 3
46144
}
47145
},
48146
physics: {
49147
enabled: true,
50148
stabilization: {
51-
iterations: 200
149+
iterations: 200,
150+
updateInterval: 25,
151+
onlyDynamicEdges: false,
152+
fit: true
52153
},
53154
barnesHut: {
54155
gravitationalConstant: -2000,
55156
centralGravity: 0.3,
56-
springLength: 95,
157+
springLength: 100,
57158
springConstant: 0.04,
58-
damping: 0.09
159+
damping: 0.09,
160+
avoidOverlap: 0.5
59161
}
60162
},
61163
interaction: {
62164
hover: true,
63165
tooltipDelay: 100,
64166
zoomView: true,
65-
dragView: true
167+
dragView: true,
168+
dragNodes: true,
169+
selectConnectedEdges: true,
170+
hideEdgesOnDrag: false,
171+
hideEdgesOnZoom: false
172+
},
173+
layout: {
174+
improvedLayout: true,
175+
hierarchical: {
176+
enabled: false
177+
}
66178
}
67179
};
68180

69181
coattendanceNetwork = new vis.Network(container, data, options);
182+
183+
// Add event listeners for better interactivity
184+
coattendanceNetwork.on('click', function(params) {
185+
if (params.nodes.length > 0) {
186+
const nodeId = params.nodes[0];
187+
const node = coattendanceGraphData.nodes.find(n => n.id === nodeId);
188+
if (node) {
189+
console.log('Selected node:', node);
190+
}
191+
}
192+
});
193+
194+
coattendanceNetwork.on('hoverNode', function(params) {
195+
container.style.cursor = 'pointer';
196+
});
197+
198+
coattendanceNetwork.on('blurNode', function(params) {
199+
container.style.cursor = 'default';
200+
});
70201
}
71202

72203
// Tab switching functionality

0 commit comments

Comments
 (0)