Skip to content
Permalink
gh-pages
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
<!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>