Skip to content
Permalink
gh-pages
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
316 lines (294 sloc) 12.1 KB
<!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>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="about.html">About</a>
</div>
<div id="result" class="ui-widget-container"><p>&nbsp;</p></div>
<div id="network" class="ui-widget-container"></div>
</div>
<!--<div id="config"></div>-->
</body>
</html>