-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathindex.html
More file actions
316 lines (294 loc) · 12.1 KB
/
index.html
File metadata and controls
316 lines (294 loc) · 12.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
<!DOCTYPE html>
<html>
<head>
<script src=""></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.2/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/4.1.2/papaparse.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vis/4.16.1/vis.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vis/4.16.1/vis.min.css"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.5.8/es5-shim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.35.1/es6-shim.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/themes/blitzer/jquery-ui.min.css">
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-77691127-4', 'auto');
ga('send', 'pageview');
</script>
<script>
// helper function
function getLabel(node) {
label = node.name.replace(/\s(\S{3})/g,"\n$1");
if (node.type == "s") label += "\n(" + node.distance/2 + ")";
return label;
}
// helper function
function getGroup(node, toNode, fromNode) {
if (node == toNode) return "To";
if (node == fromNode) return "From";
return node.type;
}
// helper function
function getUrlVars()
{
var vars = [], hash;
var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
for(var i = 0; i < hashes.length; i++)
{
hash = hashes[i].split('=');
vars.push(hash[0]);
vars[hash[0]] = hash[1].replace("%20", " ");
}
return vars;
}
// Global vars
var quartets = {}; // dictionary
var singers = {}; // dictionary
var timeOuts = [];
// Read the CSV file and build a network graph model
Papa.parse("https://raw.githubusercontent.com/liwenyip/6-degrees-of-nooj/gh-pages/BABS%20Quartet%20Singers.csv", {
download: true,
complete: function(results) {
// compile unique lists of quartets, singers, and relationships
$.each(results.data, function(i, row) {
q = row[0] + " (" + row[3] + ")"; // name (affiliation)
s = row[1];
// create the quartet and singer if they don't exist
if (!quartets.hasOwnProperty(q)) quartets[q] = {name:q, type:"q", links:[]};
if (!singers.hasOwnProperty(s)) singers[s] = {name:s, type:"s", links:[]};
// create links between quartet and singer (but don't create duplicates)
if ($.inArray(singers[s], quartets[q].links) == -1) quartets[q].links.push(singers[s]);
if ($.inArray(quartets[q], singers[s].links) == -1) singers[s].links.push(quartets[q]);
});
// Set autocomplete on inputs
$( "#from, #to" ).autocomplete({
source: Object.keys(singers)
});
// read to/from from URL string if available
vars = getUrlVars();
if (vars["to"]) $("#to").val(vars["to"]);
if (vars["from"]) $("#from").val(vars["from"]);
updateTitles();
// debug
console.log(quartets);
console.log(singers);
}
});
function updateTitles() {
for (i = 1; i <= 6; i++) {
$("#" + i + "deg").html(i + " degrees of " + $("#from").val());
$("#paths").html("Shortest paths between " + $("#from").val() + " and " + $("#to").val());
$("h1, title").html("Six Degrees of " + $("#to").val());
}
}
$(function() {
// update dropdown when fromNode changes
$("#from, #to").focusout(function() {updateTitles()});
// Make a graph
$( "#go" ).click(function() {
// create the new graph
console.log("Creating graph object...");
var container = document.getElementById('network');
var data = {/* nodes: graphNodes, edges: graphLinks */};
/*
// Development mode options
var options = { configure: {
filter:function (option, path) {
if (path.indexOf('physics') !== -1) {
return true;
}
if (path.indexOf('smooth') !== -1 || option === 'smooth') {
return true;
}
return false;
},
container: document.getElementById('config')
}
};
*/
var options = {}; // override previous options
var network = new vis.Network(container, data, options);
// Clear any unexecuted timeout events
while (timeOuts.length > 0) clearTimeout(timeOuts.pop());
// Traverse the graph model
console.log("Traversing model...");
// initialise root node
fromNode = singers[$("#from").val()];
fromNode.distance = 0;
// initialise Nooj
toNode = singers[$("#to").val()];
toNode.distance = 0;
// initialise lists
toVisit = [fromNode]; // queue of nodes to be visited
visited = [] // list of nodes that have been visited
visitedLinks = [] // list of links, use for graphing web
// Create a new radial model, by doing a breadth-first search starting at fromNode
furtherestNode = fromNode;
while (toVisit.length > 0) {
// get the next node to visit, mark it as visited, check for max distance
node = toVisit.shift()
visited.push(node);
if (node.distance > furtherestNode.distance) furtherestNode = node;
// Loop through linked nodes, create radial links
node.radialLinks = []; // clear any previous links
$.each(node.links, function(i, node2) {
if ($.inArray(node2, visited) != -1) return true; // skip linked nodes we've already visited
if ($.inArray(node2, toVisit) != -1) return true; // skip linked nodes we're already planning vo visit (prevents non-radial links)
node.radialLinks.push(node2); // create radial link
// visitedLinks.push({from:node.name, to:node2.name}); // add link (edge) to the graph
toVisit.push(node2); // add linked node to list to visit
node2.distance = node.distance + 1; // calculate distance from fromNode
});
}
console.log("Furtherest node from " + fromNode.name + " is " + furtherestNode.name + " who has distance of " + furtherestNode.distance/2);
// Check if we reached the target and calculate shortest path
console.log("Calculating shortest path...");
msg = "<p>";
if (toNode != fromNode && toNode.distance == 0) {
msg += fromNode.name + " is not connected to " + toNode.name;
} else {
// calculate distance
msg += fromNode.name + " has a " + toNode.name + " number of " + toNode.distance /2 + ": ";
permalink = "http://liwenyip.github.io/6-degrees-of-nooj/?from=" + fromNode.name + "&to=" + toNode.name;
msg += " (Permalink: <a href=\"" + permalink + "\">" + permalink + "</a>)";
// find ONE shortest path from fromNode to toNode
next = toNode;
path = [toNode]; // list of nodes along shortest path
while (path[0] != fromNode) {
// find the linked node that is next-closest to fromNode
$.each(path[0].links, function(j, link) {
if (link.distance < next.distance) next = link;
});
path.unshift(next);
}
msg += "</p><p>" + path.map(getLabel).join(' -> ') + "</p>";
}
$("#result").html(msg); // display result
// initialise network
graphLinks = new vis.DataSet([]);
graphNodes = new vis.DataSet([]);
network.setData({edges:graphLinks, nodes:graphNodes});
// Make some pretty graphs
if ( $("#graphtype").val() == "paths") {
// find and graph shortest multiple paths
console.log("Graphing paths...");
// start at toNode and work way back towards fromNode
pathToVisit = [toNode];
pathVisited = []; // list of nodes that lie along shortest path(s)
while (pathToVisit.length > 0) {
// get the next node to visit, mark it as visited, and add it to graph
node = pathToVisit.shift()
pathVisited.push(node);
if (node == fromNode) break; // if we've reached fromNode then we're done
$.each(node.links, function(i, node2) {
if ($.inArray(node2, pathVisited) != -1) return true; // skip linked nodes we've already visited
if (node2.distance >= node.distance) return true; // skip linked nodes that aren't any closer to fromNode
if ($.inArray(node2, pathToVisit) == -1) pathToVisit.push(node2); // add linked node to list to visit
graphLinks.add({from:node.name, to:node2.name}); // add link to graph
});
}
// Add nodes to graph at 100ms intervals
$.each(pathVisited, function(i, node) {
timeOuts.push(setTimeout( function() {
graphNodes.add({
id:node.name,
label:getLabel(node),
group:getGroup(node, toNode, fromNode),
level:node.distance})
}, 100 * i));
});
} else {
// graph a radial network
console.log("Graphing web...");
// traverse the radial network with depth-first search
radialToVisit = [fromNode];
radialVisited = []; // list of nodes that lie along shortest path(s)
while (radialToVisit.length > 0) {
// get the next node to visit, mark it as visited, and add it to graph
node = radialToVisit.shift();
radialVisited.push(node);
$.each(node.radialLinks, function(i, node2) {
if ($.inArray(node2, radialVisited) != -1) return true; // skip linked nodes we've already visited
if ($.inArray(node2, radialToVisit) != -1) return true; // skip linked nodes we're already planning to visit
if (node2.distance > $("#graphtype").val()*2) return true; // skip nodes that are more than 6 quartets away from fromNode
graphLinks.add({from:node.name, to:node2.name}); // add link to graph
radialToVisit.push(node2); // add linked node to list to visit
});
}
// Add nodes to graph at 100ms intervals
$.each(radialVisited, function(i, node) {
timeOuts.push(setTimeout( function() {
graphNodes.add({
id:node.name,
label:getLabel(node),
group:getGroup(node, toNode, fromNode),
level:node.distance});
}, 100 * i));
});
}
// Do the visualisation stuff
console.log("Done.");
});
});
</script>
<style type="text/css">
#network {
float: left;
width: 100%;
height: 600px;
border: 1px solid lightgray;
}
#config {
float: right;
width: 40%;
height: 600px;
border: 1px solid lightgray;
}
#inputs, #result, #header {
width: 100%;
padding: 10px;
}
#header h1, #header p {
margin: 2px;
}
#result p {
margin: 5px;
}
</style>
<title>Six Degrees of Nooj</title>
<meta name="description" content="Find out how you're connected to other barbershop quartet singers">
<meta name="keywords" content="Barbershop,Quartet,Six,Degrees,Separation,Links,BABS,LABBS,UK,BinH,Mixed,Singers,Linked,Network,Nooj,Number">
<meta name="author" content="Li-Wen Yip"></head>
<body>
<div class="ui-widget">
<div id="header" class="ui-widget-header">
<h1>Six Degrees of Nooj</h1>
<p>Find out how you're connected to other barbershop quartet singers</p>
</div>
<div id="inputs" class="ui-widget">
<label for="from">From: </label>
<input id="from">
<label for="to">To: </label>
<input id="to" value="Nooj">
<label for="graphtype">Show me: </label>
<select id="graphtype">
<option id="paths" value="paths">Shortest paths between them</option>
<option id="1deg" value=1>1 degrees of x</option>
<option id="2deg" value=2>2 degrees of x</option>
<option id="3deg" value=3>3 degrees of x</option>
<option id="4deg" value=4>4 degrees of x</option>
<option id="5deg" value=5>5 degrees of x</option>
<option id="6deg" value=6>6 degrees of x</option>
</select>
<button id="go">Go!</button>
<a href="about.html">About</a>
</div>
<div id="result" class="ui-widget-container"><p> </p></div>
<div id="network" class="ui-widget-container"></div>
</div>
<!--<div id="config"></div>-->
</body>
</html>