Skip to content

Instantly share code, notes, and snippets.

@emeeks
Forked from mbostock/.block
Last active March 25, 2024 07:56
Show Gist options
  • Save emeeks/4588962 to your computer and use it in GitHub Desktop.
Save emeeks/4588962 to your computer and use it in GitHub Desktop.
An online tool for interactive teaching of network visualization and representation principles.

The range sliders at the top change the values for the force-directed algorithm and the buttons load new graphs and apply various techniques. This will hopefully serve as a tool for teaching network analysis and visualization principles during my Gephi courses and general Networks in the Humanities presentations.

Notice this includes a pretty straightforward way to load CSV node and edge lists as exported from Gephi.

It also includes a pathfinding algorithm built for the standard data structure of force-directed networks in D3. This requires the addition of .id attributes for the nodes, however.

Now with Clustering Coefficients!

Also, it loads images for nodes but the images are not in the gist. The code also refers to different network types but the data files on Gist only refer to the transportation network.

Label sizes are based on the size of the circle element of a g, non-working hierarchical code included for no extra charge

###Original Instructions###

Click to add nodes! Nodes near the cursor will be linked to the new node.

<!DOCTYPE html>
<meta charset="utf-8">
<title>An Interactive Introduction to Network Analysis and Representation</title>
<style>
html, body {
height: 100%;
width: 100%;
color: #444;
font: 1em/1 "Hoefler Text", "Georgia", Georgia, serif, sans-serif;
padding: 10px;
}
ol {
list-style-type: none;
}
h1 a:visited {
text-decoration:none;
color:#404040;
font-weight:600;
}
h1 {font-weight:400;font-size:1.6em;line-height:0.9em;padding:0px 0 0;margin: 1em 0em .25em 0em;}
h1 a{font-weight:100;letter-spacing:-0.05em;position:relative;}
h2 {font-weight:50;font-size:1.2em;letter-spacing:-0.05em;position:relative; color:#888;margin: 0;}
h2 a {
text-decoration:none;
color:#404040;
font-weight:600;
}
h2 a:hover {
color:#C71585;
font-weight:600;
}
rect {
fill: none;
pointer-events: all;
}
.node {
fill: #000;
}
.cursor {
fill: none;
stroke: brown;
pointer-events: none;
}
.link {
stroke: #999;
}
#viz {
height: 100%;
width: 50%;
float: left;
}
#lesson {
height: 100%;
width: 45%;
float: left;
}
#definitionbox {
margin-top: 20px;
margin-right: 100px;
font-style: italic;
font-size: 1.1em;
color:#707070;
height: 0;
overflow: visible;
}
#citations {
margin-top: 20px;
}
#codetext {
font: 1em/1 "Courier New", Courier, monospace;
}
#footer {
margin: 20px;
height: 20px;
width: 1000px;
}
</style>
<script src="http://d3js.org/d3.v3.min.js"></script>
<body>
<a href="https://gist.github.com/emeeks/4588962"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub"></a>
<div id="viz">
<form>
Gravity <input id="gravitySlider" type="range" onchange="updateForce(); changeGravity()" min ="0" max="1" step =".01" value=".1" />
<input type="text" id="gravityInput" value=".1" />
</form>
<form>
Attraction (Links) <input id="attractionSlider" type="range" onchange="updateForce(); highlightEdges()" min ="-100" max="100" step ="1" value="50"/>
<input type="text" id="attractionInput" value="50" /><input type="checkbox" id="edgeCheckbox" onclick="updateForce(); highlightEdges()" /> Weight
</form>
<form>
Charge (Nodes) <input id="repulsionSlider" type="range" onchange="updateForce(); highlightNodes()" min ="-200" max="0" step ="1" value="-60" />
<input type="text" id="repulsionInput" value="-60" />
<input type="checkbox" id="nodeCheckbox" onclick="updateForce(); highlightNodes()" /> Size
</form>
<form>
Size by
<input id="degreeCheck" type="radio" name="resizeGroup" checked="true" onclick="resize('nothing')"> Nothing
<input id="degreeCheck" type="radio" name="resizeGroup" onclick="resize('degree')"> Degree
<input id="labelSlider" style="width: 50px" type="range" onchange="labelChange();" min ="0" max="20" step =".1" value="0"/> Labels
</form>
<form><input type="checkbox" id="directedCheck" onclick="changeDirectedness()"> Directed</form>
<form><input type="button" value="Delete some edges" disabled=true> <input type="button" value="Delete some nodes" onclick="deleteRandomNodes()">
<input type="button" id="addNodesButton" value="Add nodes" onclick="addNodes()"></form>
<div id="definitionbox" height="1px">
</div>
</div>
<div id="lesson">
<div id="lessontitle">
<h1>Introduction to Network Analysis and Representation</h1>
<h2><a href="https://dhs.stanford.edu">Elijah Meeks</a> and Maya Krishnan</h2>
</div>
<div>
<ol>
<li><input type="button" value="Introduction" onclick='updateText("introduction")'></li>
<li>Models</li>
<li><input type="button" value="Random" onclick='randomGraph(50,.025); updateText("randomgraph")'><input type="button" value="Dramatic" onclick='loadGraph("hamlet_nodes.csv","hamlet_edges.csv","person"); updateText("drama")'><input type="button" value="Transportation" onclick='loadGraph("rome_nodes.csv","rome_edges.csv","city"); updateText("transport")'></li>
<li><input type="button" value="Correspondence" disabled=true><input type="button" value="Genealogy" onclick='loadGraph("darwin_nodes.csv","darwin_edges.csv","person"); updateText("darwin")'><input type="button" value="Administration" onclick='loadGraph("dgsd_nodes.csv","dgsd_edges.csv","city"); updateText("dgsd")'></li>
<li>Layouts</li>
<li><input type="button" value="Force-Directed" onclick='updateForce(); updateText("forcealgo")'><input type="button" value="Plot" onclick='plotLayout(); updateText("plotlayout")'><input type="button" value="Hierarchical" disabled=true></li>
<li>Metrics</li>
<li><input type="button" value="Centrality" onclick='calculateCentrality(); updateText("centrality")' /><input type="button" value="Clustering Coefficient" onclick='clusteringCoefficient(); updateText("clustering")' /></li>
<li><input type="button" value="Community Detection" disabled=true></li>
<li>Exploration</li>
<li><input type="button" value="Pathfinding" onclick='selectPath(); updateText("pathfinding")'><input type="button" value="Ego Network" onclick='showEgoNetwork(); updateText("egonetwork")'><input type="button" value="Spatial Problem" onclick='spatialProblem(); updateText("spatialproblem")' ></li>
</ol>
</div>
<div id="lessontext">
</div>
<div id="citations">
</div>
<div id="codetext">
</div>
</div>
<div id="footer"></div>
</body>
<script>
currentSizing = "nothing";
currentEdge = "fixed";
pathSource = "";
var width = 500,
height = 600;
var fill = d3.scale.category20();
//Set the initial state
randomGraph(50,.025);
updateText("introduction");
function updateText(lessonType) {
switch(lessonType)
{
case "introduction":
d3.select("#codetext").html("<h3>Code</h3><p><a href='https://gist.github.com/emeeks/4588962'>You can see the code on Github here</a></p><p>This section is used to display relevant code for the actions that you've clicked on. All implementations of network functionality on this site are built in D3 using JavaScript, so they're not particularly high performance. Plus, they were coded by a humanist, and you know how that goes.</p>");
d3.select("#citations").html("<h3>Citations</h3><ol><li>1. BRUGHMANS, T. 2012. Thinking through networks: a review of formal network methods in archaeology. Journal of Archaeological Method and Theory.</li></ol>");
d3.select("#lessontext").html("<h2>Introduction</h2><p>Networks and network analysis has grown more prominent in both humanities scholarship and public discourse. In this context, networks--also known as graphs or node-link diagrams--are \"a set of vertices (also called points or nodes) which represent the entities of research interest, and a set of lines (or ties) between these vertices which represent their relationships.\" [1]</p><p>This interactive application is designed to provide an overview of various network analysis principles used for analysis and representation. It also provides a few examples of untraditional networks used in digital humanities scholarship. Finally, along with the various methods described interactively here are links to related scholarship.</p><p>Each network type is listed in the Models section, and can be paired with an analysis or representation method by simply clicking on a network type to load a new network, and then clicking on an analysis or visualization method.</p><p>Networks are represented using traditional force-directed techniques or by plotting along the xy axis based on numerical attributes of the nodes (longitude and latitude in the case of nodes that represent geographic entities). For the force directed layout, you can adjust the various force principles to see how this affects the representation of the network you're working with.</p><p>This implementation will likely remain a work-in-progress for some time, and if you notice any flaws or discrepencies, or have a suggestion, please contact <a href='mailto:emeeks@stanford.edu'>Elijah Meeks</a>.</p>");
break;
case "transport":
d3.select("#codetext").html("<h3>Code</h3><p>Loading a Transportation Network uses the same code to load all preset networks, so maybe this should focus on the CSV being loaded</p>");
d3.select("#citations").html("<h3>Citations</h3><ol><li><a href='http://journalofdigitalhumanities.org/1-3/modeling-networks-and-scholarship-with-orbis-by-elijah-meeks-and-karl-grossner/'>Meeks, E. & Grossner, G. \"Modeling Networks and Scholarship with ORBIS\" Journal of Digital Humanities 1.3 Summer 2012.</a></li><li>...</li></ol>");
d3.select("#lessontext").html("<h2>Transportation Networks</h2><p>This is a piece of the transportation network of the Roman World from <a href='http://orbis.stanford.edu'>ORBIS</a>. A transportation network typically uses edges to describe an annotated connection between two sites, though sometimes nodes do not represent sites in the traditional sense but rather any place where a switch can be made from one route to another, such as an intersection, on-ramp, or crossroads.</p><p>If plotted, the nodes are laid out by their geographic location</p><p><a href='http://thenounproject.com/noun/city/#icon-No1566'>City icon by Thibault Geffroy, from The Noun Project</a></p>");
break;
case "randomgraph":
d3.select("#codetext").html("<h3>Code</h3><p>Some code that produces a random graph, with random links and random spatial characteristics.</p>");
d3.select("#citations").html("<h3>Citations</h3><ol><li>Albert, R., and Barabasi, A-L. (2002). Statistical mechanics of complex networks. Reviews of modern physics 74 (January): 47-97.</li><li>...</li></ol>");
d3.select("#lessontext").html("<h2>Random Graph</h2><p>Random graphs are used as control sets to compare variation with the studied network. Random isn't the best word for these networks, as they can be generated by very specific and complicated rules, so as better to model the phenomena being studied.</p><p>If plotted, each node will be placed according to random xy values generated when the network is first created.</p><p>This graph is created with 50 nodes and a 2.5% for each node to be connected to each node (in both directions, so a 5% chance total if treated as an undirected graph). You can create a new random graph with different settings:</p><form>Nodes: <input type=\"text\" name=\"nrgNodeValue\" value=\"50\" /> Link Chance <input type=\"text\" name=\"nrgLinksValue\" value=\".025\" /><input type=\"button\" value=\"New Graph\" onclick='randomGraph(this.form.nrgNodeValue.value,this.form.nrgLinksValue.value);'></form>");
break;
case "drama":
d3.select("#codetext").html("<h3>Code</h3><p>Loading a Dramatic Network uses the same code to load all preset networks, so maybe this should focus on the CSV being loaded.</p>");
d3.select("#citations").html("<h3>Citations</h3><ol><li>Moretti, Franco. 'Network Theory, Plot Analysis', New Left Review 68 (March-April 2011)</li><li>...</li></ol>");
d3.select("#lessontext").html("<h2>Dramatic Network</h2><p>This network represents speech-intereactions in one of Shakespeare's most famous plays, Hamlet. An edge from one character to another indicates the former has spoken to the latter; the weight of the edge indicates the number of words spoken. This network was first constructed by hand by Franco Moretti in 'Network Theory, Plot Analysis', published in New Left Review 68 (March-April 2011). Since then, researchers in the Literary Lab have constructed networks from each of Shakespeare's plays, as well as the plays of Aeschylus, Euripides, Sophocles, Aristophanes, Seneca, Racine, Corneille, and Ibsen. For more information, see the Literary Lab's second pamphlet on this work, available <a href='http://litlab.stanford.edu/?page_id=255'>here</a>.</p><p>If plotted, the network is laid out according to number of speech events (y-axis) by amount of words spoken (x-axis).");
break;
case "darwin":
d3.select("#codetext").html("<h3>Code</h3><p>Loading a Genaological Network uses the same code to load all preset networks, so maybe this should focus on the CSV being loaded.</p>");
d3.select("#citations").html("<h3>Citations</h3><ol><li>...</li><li>...</li></ol>");
d3.select("#lessontext").html("<h2>Genealogical Network</h2><p>This network consists of individuals with the last name Darwin, from a genealogical network of British cultural elites. The network does not include any children or spouses with different surnames.</p><p>If plotted, nodes are laid out by birthdate (x-axis) and lifespan (y-axis)");
break;
case "dgsd":
d3.select("#codetext").html("<h3>Code</h3>");
d3.select("#citations").html("<h3>Citations</h3><ol><li>...</li><li>...</li></ol>");
d3.select("#lessontext").html("<h2>Political Administrative Network</h2><p>The Digital Gazetteer of the Song Dynasty describes the manner in which administrative units were organized during the Song Dynasty. Here we can see counties, prefectures, and towns in Fujian Circuit.</p><p>In a sense, this is an n-partite graph, meaning that the network consists of nodes of different types (n indicating the number of different types), since it represents distinct administrative entities the sets of which (counties, prefectures, towns, circuits) do not directly connect within each set. e.g. A county does not connect to a county but rather connects to a prefecture or town, which likewise do not ever connect to members of the same class but rather administrative units above or below them.</p><p>Networks that represent different classes or categories of nodes are more difficult to measure and represent than networks that represent single categories of nodes. Of course, if one's object of study is a broader category, such as 'administrative units' then it avoids this issue.</p><p>If plotted, the network is laid out geographically, with unknown locations inheriting the point location of their parent unit (except for the circuit itself, which is laid at the geographic center of the known points.</p><p><a href='http://thenounproject.com/noun/city/#icon-No1566'>City icon by Thibault Geffroy, from The Noun Project</a></p>");
break;
case "pathfinding":
d3.select("#codetext").html("<h3>Code</h3><p>This one is going to give you fits, because the same code is used for individual paths and aggregated statistics.</p>");
d3.select("#citations").html("<h3>Citations</h3><ol><li>...</li><li>...</li></ol>");
d3.select("#lessontext").html("<h2>Network Pathfinding</h2><p>Finding the shortest path between two nodes in a network provides the basis for several network statistics measures such as Betweenness, Network Diameter, and Closeness.</p> <p>Network pathfinding is accomplished using various pathfinding algorithms, the most common being Dijkstra's algorithm, which is suitable for small networks like this but not for the large networks typically analyzed with packages such as Gephi. For large networks, heuristics need to be employed to improve the efficiency of an algorithm. One such pathfinding algorithm that employs heuristics is <a href='http://en.wikipedia.org/wiki/A*_search_algorithm'>A*</a>.</p><p>Notice that the path between nodes in a network can change or cease to exist if the network is directed instead of undirected.</p>");
break;
case "centrality":
d3.select("#codetext").html("<h3>Code</h3><p>Part of this code is from Pathfinding.</p>");
d3.select("#citations").html("<h3>Citations</h3><ol><li>1. <a href='http://faculty.ucr.edu/~hanneman/nettext/C10_Centrality.html'>Hanneman, R. & Riddle, M. Introduction to Social Network Methods - Chapter 10. Centrality</a></li><li>...</li></ol>");
d3.select("#lessontext").html("<h2>Centrality</h2><p>Centrality is a general term indicating a measure of a node's location in a network relative to other nodes in the network.</p><p>One measure, shown first, is the average Least Cost Path to all the nodes in the network. This <input type='button' onclick='sizeByStats(\"Average Path Length\")' value='Average Path Length' /> is similar to (these should be buttons) Closeness or Farness centrality.</p><p>Another measure is <input type='button' onclick='sizeByStats(\"Betweenness\")' value='Betweenness Centrality' /> which tallies the number of times a node is crossed for every least cost path in the network.</p><p>There are many more centrality measures some of which are described in [1].</p>");
break;
case "clustering":
d3.select("#codetext").html("<h3>Code</h3><p>This code only does undirected</p>");
d3.select("#citations").html("<h3>Citations</h3><ol><li>...</li><li>...</li></ol>");
d3.select("#lessontext").html("<h2>Clustering</h2><p>The clustering coefficient of a node is determined by comparing the number of connections between connected nodes to the number of possible connections between connected nodes. A clustering coefficient of 1 indicates a clique, which is a set of nodes that are all connected to each other. The local clustering coefficient is the value for an individual node, while the global clustering coefficient is the average of this value for the entire network.</p>");
break;
case "egonetwork":
d3.select("#codetext").html("<h3>Code</h3><p>Ego network code.</p>");
d3.select("#citations").html("<h3>Citations</h3><ol><li>...</li><li>...</li></ol>");
d3.select("#lessontext").html("<h2>Ego Network</h2><p>Ego networks show the local area of a network around a node. In this case, it's fixed to any node within two steps of the node you click on.</p>");
break;
case "spatialproblem":
d3.select("#codetext").html("<h3>Code</h3><p>Part of this code is from showing ego networks.</p>");
d3.select("#citations").html("<h3>Citations</h3><ol><li>...</li><li>...</li></ol>");
d3.select("#lessontext").html("<h2>The Spatial Problem</h2><p>The spatial problem in network visualization is the misrepresentation of similarity using space. By placing nodes on a visual plane, the implication is that nodes that are near each other spatially are near each other topologically. Unfortunately, this is often not the case. Here we can see nodes that are near each other spatially but distant from each other topologically.</p><p>A weighted version of this problem would not use raw ego networks but rather a fixed cost, and can be used to highlight, especially in transportation networks, how topography influences topology.</p>");
break;
case "forcealgo":
d3.select("#codetext").html("<h3>Code</h3><p>This relies on the standard D3.js force-directed layout behavior.</p>");
d3.select("#citations").html("<h3>Citations</h3><ol><li>...</li><li>...</li></ol>");
d3.select("#lessontext").html("<h2>Force-Directed Layout</h2><p>A popular method for laying out networks is to assign repulsive and attractive forces to nodes and links so that the emergent behavior of the competing forces produces a network that is more legible than manually or hierarchically placing the nodes. These competing forces are typcially a repulsive force exerted by nodes (which can be based on a numerical attribute of the node or a fixed value), an attractive force exerted by shared links between nodes (which can be based on the strength of the length, typically known as \"weight\" or fixed) and a canvas gravity that draws nodes toward the center of the screen and prevents them from being pushed beyond the view of the user.</p><p>Force-directed layouts do not typically assign any value to a node being placed along the x- or y-axis beyond the confluence of forces acting upon it from nearby nodes and links. As a result, even a very stable and readable force-directed layout can be mirrored or rotated without otherwise changing. This has had the effect upon scholars of assuming that there was something wrong with a force-directed layout that placed a node on the 'top' or 'left' in one layout but on the 'bottom' or 'right' in another. Such behavior is part of the force-directed layout unless specifically designed otherwise.</p>");
break;
case "plotlayout":
d3.select("#codetext").html("<h3>Code</h3><p>The code first shuts down the force-directed algorithm, then transforms the location of the nodes and edges using typical transition() behavior, then updates the .x/.px/.y/.py attributes so that if the force-directed algorithm is reinitialized, it restarts with nodes and edges in the proper position.</p>");
d3.select("#citations").html("<h3>Citations</h3><ol><li>...</li><li>...</li></ol>");
d3.select("#lessontext").html("<h2>Plot Layouts</h2><p>Nodes are ultimately data points with lines drawn between them. As such, they can be plotted like any data points by setting their position based on such attributes. This is used for nodes that represent geographic entities by placing them using their geographic coordinates, and can be used in traditional scatterplot fashion using built-in functionality in network analysis and visualization packages such as Gephi.</p>");
break;
}
}
function initializeGraph(newGraph) {
newNodes = [];
newLinks = [];
//We need a hash to fit the link structure of D3's force-directed layout
nodeHash = {};
for ( x = 0; x < newGraph.nodes.length; x++ ) {
newNodes.push(newGraph.nodes[x]);
nodeHash[String(newGraph.nodes[x].id)] = x;
}
for ( x = 0; x < newGraph.links.length; x++ ) {
newLinks.push({id: x, source: newGraph.nodes[nodeHash[newGraph.links[x].source]], target: newGraph.nodes[nodeHash[newGraph.links[x].target]], "cost": newGraph.links[x].cost, "weight": newGraph.links[x].invcost });
}
force = d3.layout.force()
.size([width, height])
.nodes(newNodes) // initialize with a single node
.links(newLinks)
.linkDistance(60)
.charge(-60)
.on("tick", tick);
var svg = d3.select("#viz").append("svg")
.attr("width", width)
.attr("height", height)
.attr("id", "networkViz");
svg.append("rect")
.attr("width", width)
.attr("height", height)
.attr("id","backgroundRect")
.on("mousemove", mousemove);
nodes = force.nodes();
links = force.links();
node = svg.selectAll(".node");
link = svg.selectAll(".link");
arrowhead = svg.selectAll(".link");
cursor = svg.append("circle")
.attr("transform", "translate(-100,-100)")
.attr("class", "cursor")
.attr("r", 1)
.style("opacity", 0);
restart();
}
function mousemove() {
cursor.attr("transform", "translate(" + d3.mouse(this) + ")");
}
function mousedown() {
var point = d3.mouse(this),
node = {id: nodes.length, label: "Node" + nodes.length, lat: .5, long: .25, weight: 1, x: point[0], y: point[1]},
n = nodes.push(node);
// add links to any nearby nodes
nodes.forEach(function(target) {
var x = target.x - node.x,
y = target.y - node.y;
if (Math.sqrt(x * x + y * y) < 30) {
links.push({id: links.length, source: node, target: target, cost: .5});
}
});
restart();
}
function tick() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform",function(d) {return "translate(" + d.x + "," + d.y + ")"})
arrowhead
.attr("cx", function(d) {return ((d.target.x * .9) + (d.source.x * .1))})
.attr("cy", function(d) {return ((d.target.y * .9) + (d.source.y * .1))})
}
function addNodes() {
// id="addNodesButton" value="Add nodes"
if (d3.select("#addNodesButton").attr("value") == "Add nodes") {
d3.select("#addNodesButton").attr("value", "Stop adding nodes");
d3.select("#backgroundRect").on("mousedown", mousedown);
cursor.transition().duration(300).attr("r", 30).style("opacity", 1);
d3.select("#definitionbox").html("Clicking on the canvas will add a new node and connect it to nearby nodes");
}
else {
d3.select("#addNodesButton").attr("value", "Add nodes");
d3.select("#backgroundRect").on("mousedown", null);
cursor.transition().duration(300).attr("r", 1).style("opacity", 0);
d3.select("#definitionbox").html("Clicking on the canvas will not add a new node");
}
}
function changeGravity() {
d3.select("#definitionbox").html("Canvas gravity draws all nodes toward the center of the canvas, preventing them from flying out of view.");
}
function highlightNodes() {
d3.selectAll("circle.node").transition().duration(300).style("stroke-width", 5);
d3.selectAll("circle.node").transition().delay(300).duration(300).style("stroke-width", 0);
if (document.getElementById('nodeCheckbox').checked == true) {
document.getElementById("repulsionSlider").disabled=true;
d3.select("#definitionbox").html("Nodes exert a repulsion on other nodes based on their degree centrality.");
}
else {
document.getElementById("repulsionSlider").disabled=false;
d3.select("#definitionbox").html("Nodes exert a fixed value of repulsion set with the slider.");
}
}
function highlightEdges() {
d3.selectAll("line.link").transition().duration(300).style("stroke", "black");
d3.selectAll("line.link").transition().delay(300).duration(300).style("stroke", "#999999")
if (document.getElementById('edgeCheckbox').checked == true) {
document.getElementById("attractionSlider").disabled=true;
d3.select("#definitionbox").html("Nodes attract connected nodes based on the strength of the shared link.");
}
else {
document.getElementById("attractionSlider").disabled=false;
d3.select("#definitionbox").html("Nodes attract connected nodes based on the fixed value set on the slider.");
}
}
function changeDirectedness() {
if (document.getElementById('directedCheck').checked == true) {
d3.select("#definitionbox").html("The network is now directed, edges are only followed source to target");
}
else {
d3.select("#definitionbox").html("The network is now undirected, edges are bidirectional");
}
arrowhead.transition().duration(300).style("opacity", function() {return (document.getElementById('directedCheck').checked == true) ? 1: 0}).attr("r", 8);
arrowhead.transition().delay(300).duration(300).attr("r", 2);
}
function restart() {
link = link.data(links);
arrowhead = arrowhead.data(links);
node = node.data(nodes);
nodeg = node.enter().insert("g", ".cursor")
.attr("class", "node")
.call(force.drag);
nodeg.append("circle")
.attr("r", 1)
.attr("class", "node")
.style("stroke-width", 0)
.style("stroke", "#808080")
;
nodeg.append("text")
.attr("x", -5)
.attr("y", -5)
.attr("class", "node")
.text(function(d) {return d.label})
.style("display", "none")
;
nodeg.append("image")
.attr("class", "node")
.attr("xlink:href", function(d) { return ("dot.svg")});
node.exit().transition().duration(300).attr("r",1).remove();
link.enter().insert("line", ".node")
.attr("class", "link");
link.exit().remove();
arrowhead.enter().insert("circle", ".node")
.attr("class", "arrowhead")
.attr("r",2)
.style("opacity", function() {return (document.getElementById('directedCheck').checked == true) ? 1: 0});
arrowhead.exit().remove();
force.start();
//Apparently you have to delay the resizing to keep D3 from holding the elements that are supposed to be .removed()
resize(currentSizing);
}
function getBetweenness() {
//
updateText("Betweenness");
}
function updateForce() {
force.stop();
var newGravity = document.getElementById('gravitySlider').value;
var newCharge = document.getElementById('repulsionSlider').value;
var newLStrength = document.getElementById('attractionSlider').value;
document.getElementById('gravityInput').value = newGravity;
document.getElementById('repulsionInput').value = newCharge;
document.getElementById('attractionInput').value = newLStrength;
force
.charge(newCharge)
.linkDistance(newLStrength)
.gravity(newGravity)
;
if (document.getElementById('edgeCheckbox').checked == true) {
var minWeight = d3.min(links, function(d) {return parseFloat(d["cost"])});
var maxWeight = d3.max(links, function(d) {return parseFloat(d["cost"])});
//notice this is the reverse of the max/min ramp in the styling, because we're not determining link strength but the inverse: Link Distance
var edgeRamp = d3.scale.linear().domain([minWeight,maxWeight]).range([.5,3]).clamp(true);
force.linkDistance(function(d) {return (edgeRamp(parseFloat(d["cost"])) * 30)})
}
if (document.getElementById('nodeCheckbox').checked == true) {
var minSize = d3.min(nodes, function(d) {return parseFloat(d["weight"])});
var maxSize = d3.max(nodes, function(d) {return parseFloat(d["weight"])});
var sizingRamp = d3.scale.linear().domain([minSize,maxSize]).range([1,10]).clamp(true);
force.charge(function(d) {return (sizingRamp(parseFloat(d["weight"])) * -20)})
}
force.start();
}
function loadGraph(nodePath, edgePath, imagePath) {
var newGraphObj = {nodes: [], links: []};
d3.csv(nodePath, function(error, nodescsv){
d3.csv(edgePath, function(error, edgescsv){
newGraphObj.nodes = nodescsv;
newGraphObj.links = edgescsv;
d3.select("#networkViz").remove();
initializeGraph(newGraphObj);
changeImage(imagePath);
})
})
}
function establishLinks(inGraph) {
var outGraph = {nodes: [], links: []};
//We need a hash to fit the link structure of D3's force-directed layout
nodeHash = {};
for ( x = 0; x < inGraph.nodes.length; x++ ) {
outGraph.nodes.push(inGraph.nodes[x]);
nodeHash[String(inGraph.nodes[x].id)] = x;
}
for ( x = 0; x < inGraph.links.length; x++ ) {
outGraph.links.push({source: inGraph.nodes[nodeHash[inGraph.links[x].source]], target: inGraph.nodes[nodeHash[inGraph.links[x].target]], "cost": inGraph.links[x].cost, "weight": inGraph.links[x].invcost });
}
return outGraph;
}
function randomGraph(nodeNumber, linkChance) {
newGraphObj = {nodes: [], links: []};
var x=0;
while (x < nodeNumber) {
var randomLat = Math.random();
var randomLong = Math.random();
var newNodeObj = {label: "Node"+x, id: x, lat: randomLat, long: randomLong}
newGraphObj.nodes.push(newNodeObj);
var y=0;
while (y < nodeNumber) {
if (y != x && Math.random() < linkChance) {
var randomEdgeWeight = Math.random();
newLinkObj = {source: x, target: y, weight: randomEdgeWeight, cost: randomEdgeWeight}
newGraphObj.links.push(newLinkObj);
}
y++;
}
x++;
}
d3.select("#networkViz").remove();
initializeGraph(newGraphObj);
}
function plotLayout() {
force.stop();
minX = d3.min(nodes, function(d) {return parseFloat(d["long"])});
maxX = d3.max(nodes, function(d) {return parseFloat(d["long"])});
minY = d3.min(nodes, function(d) {return parseFloat(d["lat"])});
maxY = d3.max(nodes, function(d) {return parseFloat(d["lat"])});
heightRamp=d3.scale.linear().domain([minY,maxY]).range([550,50]).clamp(true);
widthRamp=d3.scale.linear().domain([minX,maxX]).range([50,450]).clamp(true);
d3.selectAll("g.node").transition().duration(1000).attr("transform",function(d) {return "translate(" + widthRamp(parseFloat(d["long"])) + "," + heightRamp(parseFloat(d["lat"])) + ")"})
for (x in nodes) {
nodes[x].x = widthRamp(parseFloat(nodes[x].long));
nodes[x].px = widthRamp(parseFloat(nodes[x].long));
nodes[x].y = heightRamp(parseFloat(nodes[x].lat));
nodes[x].py = heightRamp(parseFloat(nodes[x].lat));
}
link.transition().duration(1000).attr("x1", function(d) { return widthRamp(d.source.long); })
.attr("y1", function(d) { return heightRamp(d.source.lat); })
.attr("x2", function(d) { return widthRamp(d.target.long); })
.attr("y2", function(d) { return heightRamp(d.target.lat); });
arrowhead.transition().duration(1000)
.attr("cx", function(d) {return ((d.target.x * .9) + (d.source.x * .1))})
.attr("cy", function(d) {return ((d.target.y * .9) + (d.source.y * .1))})
}
function clearGraph() {
}
function deleteNodes(nodeArray) {
var y = links.length - 1;
while (y > -1) {
if (nodeArray.indexOf(links[y].source.id) + nodeArray.indexOf(links[y].target.id) > -2) {
links.splice(y,1);
}
y--;
}
var y = nodes.length - 1;
while (y > -1) {
if (nodeArray.indexOf(nodes[y].id) > -1) {
nodes.splice(y,1);
}
y--;
}
restart();
}
function deleteRandomNodes() {
var randomNodeArray = getRandomSubarray(nodes, 5);
var sampledIDValues = [];
for (x in randomNodeArray) {
sampledIDValues.push(randomNodeArray[x].id)
}
deleteNodes(sampledIDValues);
}
function getRandomSubarray(arr, size) {
var shuffled = arr.slice(0), i = arr.length, temp, index;
while (i--) {
index = Math.floor(i * Math.random());
temp = shuffled[index];
shuffled[index] = shuffled[i];
shuffled[i] = temp;
}
return shuffled.slice(0, size);
}
function resize(byValue) {
currentSizing = byValue;
var minSize = d3.min(nodes, function(d) {return parseFloat(d["weight"])});
var maxSize = d3.max(nodes, function(d) {return parseFloat(d["weight"])});
var minWeight = d3.min(links, function(d) {return parseFloat(d["cost"])});
var maxWeight = d3.max(links, function(d) {return parseFloat(d["cost"])});
var sizingRamp = d3.scale.linear().domain([minSize,maxSize]).range([1,10]).clamp(true);
var edgeRamp = d3.scale.linear().domain([maxWeight,minWeight]).range([.5,3]).clamp(true);
switch(byValue)
{
case "nothing":
d3.selectAll("circle.node").attr("r", 5)
d3.selectAll("image.node").attr("x", -2.5)
.attr("y", -2.5)
.attr("width", 5)
.attr("height", 5);
break;
case "degree":
d3.selectAll("circle.node").attr("r", function(d) {return sizingRamp(d["weight"])})
d3.selectAll("image.node").attr("x", function(d) { return -((sizingRamp(d["weight"]))/2)})
.attr("y", function(d) { return -((sizingRamp(d["weight"]))/2)})
.attr("width", function(d) { return (sizingRamp(d["weight"]))})
.attr("height", function(d) { return (sizingRamp(d["weight"]))});
break;
}
d3.selectAll("line.link").style("stroke-width", function(d) {return edgeRamp(d["cost"])})
}
function pathfinding(sourceID, targetID, directedSearch, searchType) {
var graphStats = {};
if (searchType == "aggregate") {
var aggregatedCosts = {};
for (xx in nodes) {
graphStats[nodes[xx].id] = {};
graphStats[nodes[xx].id]["Betweenness"] = 0;
aggregatedCosts[nodes[xx].id] = {};
for (yy in nodes) {
aggregatedCosts[nodes[xx].id][nodes[yy].id] = +Infinity;
}
}
}
else {
for (xx in nodes) {
graphStats[nodes[xx].id] = {};
graphStats[nodes[xx].id]["Betweenness"] = 0;
}
}
// Two possibilities, either we're running an individual search, in which case we stop when we find the path, or we're aggregating values for the entire graph, in which case we run the paths from every node to every node
var aggregatedSourceID = 0;
while ((searchType == "individual" && pathFound != true) || (searchType != "individual" && aggregatedSourceID < nodes.length)) {
// Set a fresh TargetID in this loop;
var aggregatedTargetID = 0;
//For aggregated statistics, we start at node 0 and calculate for all nodes
if (searchType == "aggregate") {sourceID = nodes[aggregatedSourceID].id;}
// we need to know for each source the vertices that have been calculated
var visitedVertices = [];
//but we store those vertices in an object array, this allows us to indexOf the previous array and jump to the specific cost for the calculated vertex(node)
vertexCost = {};
// set the cost to infinity so that we know what we've calculated and what we haven't
for (n in nodes) {
vertexCost[nodes[n].id] = +Infinity;
}
// the source node is 0
vertexCost[sourceID] = 0;
// and thus is calculated
calculatedVertices = [sourceID];
// parentVertex is used to walk back through the network once we've calculated the shortest path because the aggregate of shortest paths is the shortest path in a network
parentVertex = {};
// we need a signal to point out that we've found a path for the individual check
var pathFound = false;
//we set a high minimum value because we need to cycle through the links to pick the lowest cost link to calculate first
var minimumValue = 9999;
//these are variables for holding possible source/target pairs
var sourceValue = "";
var targetValue = "";
//we store the links in a separate object so that we can reference them with source/target pairs to highlight them later
var linkID = 0;
linkList = {};
//there are two possibilities: we're looking for one route, in which case we want to stop when we find the path, or we're looking for all routes, in which case we want to stop when we reach the end of the list of nodes
while ((searchType == "individual" && pathFound != true) || (searchType != "individual" && aggregatedTargetID < nodes.length)) {
//set the targetID to the current aggregate target node in the loop, if running the aggregate search
if (searchType == "aggregate") {targetID = nodes[aggregatedTargetID].id;}
while (pathFound == false ) {
minimumValue = 9999;
sourceValue = "";
targetValue = "";
linkID = 0;
for (z in links) {
var otherVertex = "none";
var startVertex = "none";
//find the next edge to check and make sure it's the lowest value
if (calculatedVertices.indexOf(links[z].source.id) > -1 && calculatedVertices.indexOf(links[z].target.id) == -1) {
startVertex = links[z].source.id;
otherVertex = links[z].target.id;
}
//undirected search looks at target->source as well
else if (directedSearch == false && calculatedVertices.indexOf(links[z].target.id) > -1 && calculatedVertices.indexOf(links[z].source.id) == -1) {
startVertex = links[z].target.id;
otherVertex = links[z].source.id;
}
if (otherVertex != "none") {
if ((parseFloat(links[z].cost) + vertexCost[startVertex]) < minimumValue) {
minimumValue = (parseFloat(links[z].cost) + vertexCost[startVertex]);
sourceValue = startVertex;
targetValue = otherVertex;
linkID = z;
}
}
}
// if the minimum value is still 9999, then that means no paths were discovered, and there is no path
if (minimumValue == 9999) {
// vertexCost[targetID] = +Infinity;
calculatedVertices.push(targetID);
pathFound = true;
}
else {
vertexCost[targetValue] = minimumValue;
parentVertex[targetValue] = sourceValue;
calculatedVertices.push(targetValue);
//For a multidimensional object, you have to check and add new objects on-the-fly
if (!linkList[sourceValue]) {
linkList[sourceValue] = {};
}
linkList[sourceValue][targetValue] = linkID;
if (targetValue == targetID) {
pathFound = true;
}
}
}
var computedPathArray = [];
computedEdgeArray = [];
// this is causing a strange error, so if the type is aggregate, just skip it
if (minimumValue != 9999 && parentVertex[targetID]) {
var reversePath = targetID;
computedPathArray = [];
while (reversePath != sourceID) {
computedPathArray.push(reversePath);
computedEdgeArray.push(linkList[parentVertex[reversePath]][reversePath]);
reversePath = parentVertex[reversePath];
graphStats[reversePath]["Betweenness"] += 1;
}
//we shouldn't increase the source or target value for any paths, so decrement the sourceID betweenness
graphStats[sourceID]["Betweenness"] -= 1;
computedPathArray.push(sourceID);
computedEdgeArray.push(linkList[sourceID][parentVertex[reversePath]]);
}
else if (searchType == "individual") {
computedPathArray = ["Path not found"];
computedEdgeArray = ["Path not found"];
}
aggregatedTargetID++;
var alreadyComputed = true;
if (aggregatedTargetID < nodes.length) {
while (alreadyComputed == true && (nodes[aggregatedTargetID]) ) {
//find out if we've already computed the new target as part of an earlier path
if (calculatedVertices.indexOf(nodes[aggregatedTargetID].id) > -1) {
aggregatedTargetID++;
}
else {
alreadyComputed = false;
}
}
}
}
var sumOfConnectedNodes = 0;
var totalOfConnectedDistance = 0;
for (x in vertexCost) {
if (vertexCost[x]) {
if (vertexCost[x] != +Infinity) {
sumOfConnectedNodes++;
totalOfConnectedDistance += vertexCost[x];
}
}
}
graphStats[sourceID]["Total Connectivity"] = sumOfConnectedNodes;
if(sumOfConnectedNodes == 0) {
graphStats[sourceID]["Average Path Length"] = 0;
}
else {
graphStats[sourceID]["Average Path Length"] = totalOfConnectedDistance / sumOfConnectedNodes;
}
aggregatedSourceID++;
}
if (searchType == "individual") {
return {nodes: computedPathArray, paths: computedEdgeArray};
}
else {
return graphStats;
}
}
function selectPath() {
d3.selectAll("g.node").on("click",setSource).style("cursor", "pointer")
d3.select("#definitionbox").html("Select a node to compute a path from");
}
function setSource(d) {
pathSource = d.id;
d3.selectAll("g.node").on("click",setTarget);
d3.select("#definitionbox").html(d.id + "selected - select a node to compute the path to");
}
function setTarget(d) {
var directedValue = document.getElementById('directedCheck').checked;
var computedPathArray = pathfinding(pathSource,d.id,directedValue,"individual");
if (computedPathArray.nodes[0] == "Path not found") {
d3.select("#definitionbox").html("No path");
d3.selectAll("circle.node").transition().duration(300).style("fill", function(p) {return [d.id,pathSource].indexOf(p.id) > -1 ? "blue" : "black"});
d3.selectAll("line.link").transition().duration(300).style("stroke","black");
}
else {
computedPathArray.paths.reverse();
computedPathArray.nodes.reverse();
d3.select("#definitionbox").html("<span style='color:blue;'>Path from " + pathSource + " to " + d.id +"</span>");
d3.selectAll("circle.node").transition().delay(function(d) {return computedPathArray.nodes.indexOf("" + d.id) > -1 ? (computedPathArray.nodes.indexOf("" + d.id) * 500) + 500 : 0}).duration(300).style("fill", function(d) {return computedPathArray.nodes.indexOf(d.id) > -1 ? "blue" : "pink"});
d3.selectAll("line.link").transition().delay(function(d) {return computedPathArray.paths.indexOf("" + d.id) > -1 ? (computedPathArray.paths.indexOf("" + d.id) * 500) + 500 : 0}).duration(300).style("stroke", function(d) {return computedPathArray.paths.indexOf("" + d.id) > -1 ? "blue" : "pink"});
}
d3.selectAll("g.node").on("click", null).style("cursor","auto");
pathSource = "";
}
function sizeByStats(statname) {
var minStat = 9999;
var maxStat = 0;
for (x in nodes) {
if (graphStats[nodes[x].id]["Total Connectivity"] != 0) {
minStat = Math.min(minStat, graphStats[nodes[x].id][statname]);
maxStat = Math.max(maxStat, graphStats[nodes[x].id][statname]);
}
}
d3.select("#definitionbox").html("Nodes are now sized by " + statname);
var sizeRamp = d3.scale.linear().domain([minStat,maxStat]).range([2,10]).clamp(true);
d3.selectAll("circle.node").transition().duration(300).attr("r", function(d,i) {return graphStats[nodes[i].id][statname] > 0 ? sizeRamp(graphStats[nodes[i].id][statname]) : 1});
d3.selectAll("image.node").transition().duration(300)
.attr("height", function(d,i) {return graphStats[nodes[i].id]["Total Connectivity"] > 0 ? sizeRamp(graphStats[nodes[i].id][statname]) : 1})
.attr("width", function(d,i) {return graphStats[nodes[i].id]["Total Connectivity"] > 0 ? sizeRamp(graphStats[nodes[i].id][statname]) : 1})
.attr("x", function(d,i) {return graphStats[nodes[i].id]["Total Connectivity"] > 0 ? -(sizeRamp(graphStats[nodes[i].id][statname]) / 2) : -.5})
.attr("y", function(d,i) {return graphStats[nodes[i].id]["Total Connectivity"] > 0 ? -(sizeRamp(graphStats[nodes[i].id][statname]) / 2) : -.5});
}
function calculateCentrality() {
var directedValue = document.getElementById('directedCheck').checked;
d3.select("#definitionbox").html("Calculating paths...");
//Here's closeness, at least
graphStats = pathfinding(0,0,directedValue,"aggregate");
sizeByStats("Average Path Length");
// var sizeRamp = d3.scale.linear().domain([minConnected,maxConnected]).range([1,10]).clamp(true);
// d3.selectAll("circle.node").attr("r", function(d,i) {return sizeRamp(graphStats[nodes[i].id]["Total Connectivity"])});
}
function findEgoNetwork(searchNode, egoNetworkDegree, isDirected, searchType) {
var egoNetwork = {};
for (x in nodes) {
if (nodes[x].id == searchNode || searchType == "aggregate") {
egoNetwork[nodes[x].id] = [nodes[x].id];
var z = 0;
while (z < egoNetworkDegree) {
var thisEgoRing = egoNetwork[nodes[x].id].slice(0);
for (y in links) {
if (thisEgoRing.indexOf(links[y].source.id) > -1 && thisEgoRing.indexOf(links[y].target.id) == -1) {
egoNetwork[nodes[x].id].push(links[y].target.id)
}
else if (isDirected == false && thisEgoRing.indexOf(links[y].source.id) == -1 && thisEgoRing.indexOf(links[y].target.id) > -1) {
egoNetwork[nodes[x].id].push(links[y].source.id)
}
}
z++;
}
}
}
if (searchType == "aggregate") {
//if it's checking the entire network, pass back the entire object of arrays
return egoNetwork;
}
else {
//Otherwise only give back the array that corresponds with the search node
return egoNetwork[searchNode];
}
}
function showEgoNetwork() {
d3.selectAll("g.node").on("click",findEgo).style("cursor", "pointer");
d3.select("#definitionbox").html("Select a node to see its 2-Degree Ego Network");
}
function findEgo(d) {
d3.select("#definitionbox").html("<span style='color:blue;'>All nodes within 2 steps of</span><span style='color:purple;'> " + d.label + "</span>");
d3.selectAll("g.node").on("click",null).style("cursor", "auto");
var directedValue = document.getElementById('directedCheck').checked;
var computedEgoArray = findEgoNetwork(d.id, 2, directedValue,"individual");
d3.selectAll("circle.node").style("fill", function(p) {return p.id == d.id ? "purple" : computedEgoArray.indexOf(p.id) > -1 ? "blue" : "pink"})
}
function spatialProblem() {
var closeNodes = [];
//We can decrement the ego network size
var egoNetworkSize = 5;
while (closeNodes.length == 0) {
egoNetworkSize--;
var computedEgoObject = findEgoNetwork(0, egoNetworkSize, false,"aggregate");
var problemDistance = 20;
//The while statement means run this until we get to a distance where there are some nodes that violate this principle
while (closeNodes.length == 0 && (problemDistance < 60 || egoNetworkSize == 2)) {
for (x in nodes) {
for (y in nodes) {
if (computedEgoObject[nodes[x].id].indexOf(nodes[y].id) == -1) {
if (Math.sqrt(Math.pow((nodes[x].x - nodes[y].x),2) + Math.pow((nodes[x].y - nodes[y].y),2)) < problemDistance) {
closeNodes.push(nodes[x].id);
closeNodes.push(nodes[y].id);
}
}
}
}
problemDistance += 20;
}
}
d3.select("#definitionbox").html("<span style='color:blue;'>Nodes separated by more than "+egoNetworkSize+" steps but closer than " + problemDistance + "px</span>");
d3.selectAll("circle.node").transition().duration(300).style("fill", function(d) {return closeNodes.indexOf(d.id) > -1 ? "blue" : "pink"}).style("stroke-width", function(d) {return closeNodes.indexOf(d.id) > -1 ? 5 : 0});
d3.selectAll("circle.node").transition().delay(300).duration(300).style("stroke-width", 0);
}
function labelChange() {
//This function now accounts for the size of the circle but bases that size off of the fixed childNode position of the circle in the g element (position [0] in this implementation)
var newLabelSize = document.getElementById('labelSlider').value / 5;
if (newLabelSize > 0) {
d3.selectAll("text.node").style("display", "block").style("font-size", function(d) {return parseFloat(newLabelSize) * d3.select(this.parentNode.childNodes[0]).attr("r");});
}
else {
d3.selectAll("text.node").style("display", "none")
}
}
function changeImage(newImageName) {
d3.selectAll("image.node").attr("xlink:href", function(d) { return ("" + newImageName + ".svg")});
}
function clusteringCoefficient() {
//this implementation is currently only undirected
//we'll store the value we compute in an object to use it to size the results
var localClusteringCoefficient = {};
var maxClustering = 0;
var minClustering = 99999;
for (x in nodes) {
localClusteringCoefficient[nodes[x].id] = 1;
var connectedNodes = [];
// find the nodes connected to this node and put them in an array
for (y in links) {
if (links[y].source.id == nodes[x].id) {
connectedNodes.push(links[y].target.id);
}
else if (links[y].target.id == nodes[x].id) {
connectedNodes.push(links[y].source.id);
}
}
//only update the local clustering coefficient if we find more than 1 link (needs to be updated for directed graphs)
if (connectedNodes.length > 1) {
// each node connected to the node could possibly be connected to every other node
var totalPossibleClustering = (connectedNodes.length - 1) * connectedNodes.length;
var discoveredLinks = 0;
for (y in links) {
if (connectedNodes.indexOf(links[y].source.id) > -1 && connectedNodes.indexOf(links[y].target.id) > -1) {
discoveredLinks++;
}
else if (connectedNodes.indexOf(links[y].target.id) > -1 && connectedNodes.indexOf(links[y].source.id) > -1) {
discoveredLinks++;
}
}
//Local clustering coefficient is equal to the number of connected nodes divided by the connectivity of those nodes between each other
localClusteringCoefficient[nodes[x].id] = discoveredLinks / totalPossibleClustering;
maxClustering = Math.max(localClusteringCoefficient[nodes[x].id], maxClustering);
minClustering = Math.min(localClusteringCoefficient[nodes[x].id], minClustering);
}
}
d3.selectAll("circle.node")
var sizeRamp = d3.scale.linear().domain([minClustering,maxClustering]).range([2,10]).clamp(true);
d3.selectAll("circle.node").transition().duration(300).attr("r", function(d,i) {return sizeRamp(localClusteringCoefficient[nodes[i].id])});
d3.selectAll("image.node").transition().duration(300)
.attr("height", function(d,i) {return sizeRamp(localClusteringCoefficient[nodes[i].id])})
.attr("width", function(d,i) {return sizeRamp(localClusteringCoefficient[nodes[i].id])})
.attr("x", function(d,i) {return -(sizeRamp(localClusteringCoefficient[nodes[i].id]) / 2)})
.attr("y", function(d,i) {return -(sizeRamp(localClusteringCoefficient[nodes[i].id]) / 2)});
}
function hierarchicalLayout() {
for ( y = 0; y < nodes.length; y++ ) {
nodes[y].fixed = false;
}
tick();
force.stop();
var orbitRank = 0;
orbitArray = {};
orbitArray[centerID] = 0;
var totalLinks = links.length;
var openLinks = 0;
orbitalRings = [1]
while (openLinks < totalLinks){
for ( z = 0; z < links.length; z++) {
if (links[z].target.fixed == true && links[z].target.fixed == true) {
openLinks++;
}
else if (links[z].target.fixed == true) {
assignedID = (links[z].source.id);
assignedValue = (orbitArray[links[z].target.id] + 1);
links[z].source.fixed = true;
orbitArray[assignedID] = assignedValue;
orbitalRings[orbitArray[links[z].target.id] + 1]++;
}
else if (links[z].source.fixed == true) {
assignedID = (links[z].target.id);
assignedValue = (orbitArray[links[z].source.id] + 1);
links[z].target.fixed = true;
orbitArray[assignedID] = assignedValue;
orbitalRings[orbitArray[links[z].source.id] + 1]++;
}
}
}
}
</script>
source target type id label weight cost invcost
50206 50448 Directed 177631 19.85 0.12 19.85
50206 50368 Directed 175427 16.38 2.57 16.38
50206 50334 Directed 179173 19.85 0.12 19.85
50206 50449 Directed 186878 19.67 0.24 19.67
50756 50448 Directed 182997 19.92 0.07 19.92
50756 50379 Directed 177632 19.84 0.13 19.84
50756 50486 Directed 177993 19.64 0.27 19.64
50448 50206 Directed 177650 19.87 0.11 19.87
50448 50756 Directed 177959 19.93 0.06 19.93
50448 50476 Directed 182996 19.91 0.08 19.91
50368 50206 Directed 176241 16.38 2.57 16.38
50368 50060 Directed 176250 14.23 4.09 14.23
50060 50368 Directed 175436 14.23 4.09 14.23
50060 50299 Directed 176153 10.29 6.87 10.29
50145 50379 Directed 179121 19.97 0.03 19.97
50065 50379 Directed 183014 19.95 0.05 19.95
50065 50167 Directed 178215 19.91 0.08 19.91
50476 50448 Directed 177482 19.92 0.07 19.92
50379 50756 Directed 177806 19.89 0.09 19.89
50379 50145 Directed 183012 19.97 0.03 19.97
50379 50065 Directed 178207 19.96 0.04 19.96
50379 50203 Directed 175121 15.78 3.0 15.78
50379 50487 Directed 178000 19.61 0.29 19.61
50483 50482 Directed 183064 19.95 0.05 19.95
50483 50329 Directed 179176 19.99 0.02 19.99
50483 50198 Directed 179178 19.94 0.05 19.94
50483 50107 Directed 179122 19.81 0.15 19.81
50483 50250 Directed 177608 19.48 0.38 19.48
50394 50362 Directed 175425 15.62 3.11 15.62
50394 50334 Directed 176246 18.74 0.91 18.74
50221 50148 Directed 176199 18.91 0.78 18.91
50221 50427 Directed 175433 17.97 1.45 17.97
50148 50221 Directed 175385 18.91 0.78 18.91
50148 50222 Directed 176381 16.57 2.44 16.57
50135 50362 Directed 176190 18.33 1.19 18.33
50135 50120 Directed 175366 15.53 3.17 15.53
50362 50394 Directed 176239 15.62 3.11 15.62
50362 50135 Directed 175376 18.33 1.19 18.33
50362 50299 Directed 176169 17.88 1.51 17.88
50482 50483 Directed 179175 19.96 0.04 19.96
50482 50334 Directed 183063 19.96 0.04 19.96
50482 50250 Directed 177607 19.5 0.37 19.5
50334 50206 Directed 183062 19.81 0.15 19.81
50334 50394 Directed 175432 18.74 0.91 18.74
50334 50482 Directed 179174 19.97 0.03 19.97
50334 50329 Directed 176245 10.01 7.07 10.01
50222 50148 Directed 175567 16.57 2.44 16.57
50222 50385 Directed 176327 19.04 0.69 19.04
50120 50135 Directed 176180 15.53 3.17 15.53
50120 50385 Directed 176367 14.82 3.67 14.82
50120 50329 Directed 175439 16.61 2.41 16.61
50299 50060 Directed 175339 10.29 6.87 10.29
50299 50362 Directed 175355 17.88 1.51 17.88
50299 50427 Directed 176248 18.55 1.04 18.55
50427 50221 Directed 176247 17.97 1.45 17.97
50427 50299 Directed 175434 18.55 1.04 18.55
50385 50222 Directed 175513 19.04 0.69 19.04
50385 50120 Directed 175553 14.82 3.67 14.82
50385 50390 Directed 176326 12.11 5.59 12.11
50329 50483 Directed 183065 19.98 0.03 19.98
50329 50334 Directed 175431 10.01 7.07 10.01
50329 50120 Directed 176253 16.61 2.41 16.61
50329 50198 Directed 179177 19.95 0.05 19.95
50203 50379 Directed 175935 15.78 3.0 15.78
50167 50065 Directed 183016 19.92 0.07 19.92
50167 50264 Directed 178267 19.92 0.07 19.92
50390 50385 Directed 175512 12.11 5.59 12.11
50390 50369 Directed 176238 14.98 3.56 14.98
50390 50021 Directed 175995 18.25 1.25 18.25
50390 50387 Directed 176242 16.66 2.37 16.66
50198 50483 Directed 183068 19.91 0.07 19.91
50198 50329 Directed 183067 19.93 0.06 19.93
50198 50384 Directed 176256 16.35 2.59 16.35
50198 50449 Directed 183069 19.96 0.04 19.96
50198 50391 Directed 175445 16.08 2.79 16.08
50384 50198 Directed 175442 16.35 2.59 16.35
50384 50352 Directed 175443 16.73 2.32 16.73
50486 50756 Directed 177613 19.54 0.34 19.54
50486 50451 Directed 183050 19.85 0.12 19.85
50486 50449 Directed 178003 19.88 0.1 19.88
50486 50485 Directed 177593 19.91 0.08 19.91
50486 50107 Directed 177814 19.86 0.11 19.86
50486 50505 Directed 177612 19.82 0.14 19.82
50486 50286 Directed 177997 19.76 0.18 19.76
50451 50486 Directed 179161 19.86 0.11 19.86
50451 50487 Directed 183049 19.95 0.05 19.95
50451 50286 Directed 177623 19.85 0.12 19.85
50716 50487 Directed 179157 19.94 0.05 19.94
50716 50489 Directed 183051 19.95 0.05 19.95
50716 50250 Directed 178002 19.71 0.22 19.71
50487 50379 Directed 177626 19.56 0.33 19.56
50487 50451 Directed 179158 19.96 0.05 19.96
50487 50716 Directed 183048 19.93 0.06 19.93
50487 50286 Directed 177624 19.83 0.13 19.83
50487 50264 Directed 177627 19.63 0.28 19.63
50369 50390 Directed 175424 14.98 3.56 14.98
50369 50188 Directed 176243 12.06 5.62 12.06
50449 50198 Directed 177485 19.95 0.05 19.95
50449 50486 Directed 177629 19.86 0.11 19.86
50449 50485 Directed 182999 19.97 0.03 19.97
50449 50107 Directed 179179 19.9 0.08 19.9
50449 50264 Directed 177606 19.34 0.48 19.34
50260 50021 Directed 175318 14.65 3.79 14.65
50260 50107 Directed 175356 13.9 4.32 13.9
50260 50352 Directed 176258 17.89 1.5 17.89
50021 50390 Directed 175181 18.25 1.25 18.25
50021 50260 Directed 176132 14.65 3.79 14.65
50087 50391 Directed 176155 18.59 1.01 18.59
50087 50107 Directed 175358 12.39 5.39 12.39
50391 50198 Directed 176259 16.08 2.79 16.08
50391 50087 Directed 175341 18.59 1.01 18.59
50485 50486 Directed 177617 19.89 0.09 19.89
50485 50449 Directed 178025 19.98 0.03 19.98
50387 50390 Directed 175428 16.66 2.37 16.66
50107 50483 Directed 183006 19.73 0.2 19.73
50107 50486 Directed 177619 19.81 0.15 19.81
50107 50449 Directed 183070 19.87 0.1 19.87
50107 50260 Directed 176170 13.9 4.32 13.9
50107 50087 Directed 176172 12.39 5.39 12.39
50107 50505 Directed 177620 19.89 0.09 19.89
50107 50286 Directed 177621 19.66 0.25 19.66
50107 50188 Directed 175447 12.75 5.14 12.75
50107 50493 Directed 179180 19.97 0.03 19.97
50352 50384 Directed 176257 16.73 2.32 16.73
50352 50260 Directed 175444 17.89 1.5 17.89
50317 50327 Directed 176998 19.89 0.09 19.89
50505 50486 Directed 177992 19.76 0.19 19.76
50505 50107 Directed 177861 19.87 0.1 19.87
50505 50501 Directed 183000 19.98 0.03 19.98
50505 50499 Directed 183001 19.95 0.05 19.95
50505 50714 Directed 178517 19.94 0.06 19.94
50505 50507 Directed 183076 19.98 0.03 19.98
50505 50512 Directed 177643 19.86 0.11 19.86
50505 50456 Directed 183192 19.85 0.12 19.85
50505 50516 Directed 183007 19.46 0.39 19.46
50501 50505 Directed 178032 19.99 0.02 19.99
50501 50286 Directed 177634 19.75 0.19 19.75
50279 50265 Directed 175524 19.36 0.47 19.36
50279 50327 Directed 176702 19.01 0.71 19.01
50279 50366 Directed 176049 17.53 1.76 17.53
50265 50279 Directed 176338 19.36 0.47 19.36
50265 50327 Directed 175193 16.32 2.61 16.32
50327 50317 Directed 176698 19.76 0.19 19.76
50327 50279 Directed 177002 19.52 0.36 19.52
50327 50265 Directed 176007 16.32 2.61 16.32
50327 50425 Directed 176025 15.03 3.53 15.03
50327 50134 Directed 176024 13.76 4.43 13.76
50327 50286 Directed 176697 19.74 0.2 19.74
50327 50378 Directed 176000 15.41 3.26 15.41
50327 50173 Directed 175999 16.53 2.47 16.53
50327 50393 Directed 175971 18.61 0.99 18.61
50327 50324 Directed 176006 16.47 2.51 16.47
50425 50327 Directed 175211 15.03 3.53 15.03
50425 50122 Directed 176037 17.87 1.52 17.87
50134 50327 Directed 175210 13.76 4.43 13.76
50134 50286 Directed 179135 19.94 0.06 19.94
50134 50761 Directed 183056 19.95 0.05 19.95
50134 50307 Directed 183025 19.89 0.09 19.89
50286 50486 Directed 177622 19.76 0.18 19.76
50286 50451 Directed 177998 19.86 0.11 19.86
50286 50487 Directed 177999 19.83 0.13 19.83
50286 50107 Directed 177908 19.7 0.23 19.7
50286 50501 Directed 178004 19.79 0.16 19.79
50286 50327 Directed 176997 19.88 0.1 19.88
50286 50134 Directed 183026 19.93 0.06 19.93
50286 50378 Directed 179136 19.94 0.06 19.94
50286 50322 Directed 179191 19.9 0.09 19.9
50286 50762 Directed 179164 19.84 0.13 19.84
50489 50716 Directed 179162 19.96 0.04 19.96
50489 50761 Directed 183052 19.98 0.03 19.98
50499 50505 Directed 178039 19.94 0.05 19.94
50499 50495 Directed 178178 19.95 0.05 19.95
50499 50512 Directed 178027 19.87 0.11 19.87
50498 50188 Directed 183088 19.94 0.06 19.94
50188 50369 Directed 175429 12.06 5.62 12.06
50188 50107 Directed 176261 12.75 5.14 12.75
50188 50498 Directed 179193 19.96 0.04 19.96
50188 50495 Directed 183087 19.94 0.05 19.94
50493 50107 Directed 183058 19.97 0.04 19.97
50493 50495 Directed 179181 19.99 0.02 19.99
50495 50499 Directed 183002 19.96 0.04 19.96
50495 50188 Directed 179192 19.95 0.05 19.95
50495 50493 Directed 178962 19.98 0.03 19.98
50264 50167 Directed 183017 19.95 0.05 19.95
50264 50487 Directed 178001 19.7 0.23 19.7
50264 50449 Directed 177986 19.56 0.32 19.56
50264 50398 Directed 175127 12.84 5.07 12.84
50264 50040 Directed 178284 19.94 0.06 19.94
50264 50250 Directed 179130 19.91 0.08 19.91
50264 50269 Directed 175293 13.35 4.71 13.35
50239 50398 Directed 175940 14.9 3.62 14.9
50015 50393 Directed 175212 16.86 2.23 16.86
50015 50132 Directed 176028 18.1 1.36 18.1
50255 50175 Directed 177334 20.0 0.02 20.0
50255 50357 Directed 179139 20.01 0.01 20.01
50255 50381 Directed 175206 18.77 0.89 18.77
50175 50255 Directed 179138 20.0 0.02 20.0
50175 50378 Directed 177331 19.99 0.02 19.99
50177 50173 Directed 175203 18.64 0.98 18.64
50177 50109 Directed 176019 18.6 1.01 18.6
50047 50324 Directed 175243 15.31 3.33 15.31
50047 50110 Directed 175932 18.63 0.98 18.63
50378 50327 Directed 175186 15.41 3.26 15.41
50378 50286 Directed 183015 19.94 0.06 19.94
50378 50175 Directed 179137 19.99 0.02 19.99
50378 50322 Directed 179188 19.96 0.05 19.96
50714 50505 Directed 183077 19.93 0.06 19.93
50714 50516 Directed 179183 19.52 0.35 19.52
50173 50327 Directed 175185 16.53 2.47 16.53
50173 50177 Directed 176017 18.64 0.98 18.64
50122 50425 Directed 175223 17.87 1.52 17.87
50122 50043 Directed 176038 17.41 1.84 17.41
50761 50134 Directed 179167 19.95 0.05 19.95
50761 50489 Directed 179163 19.98 0.03 19.98
50761 50307 Directed 179166 19.96 0.05 19.96
50393 50327 Directed 175157 18.61 0.99 18.61
50393 50015 Directed 176026 16.86 2.23 16.86
50324 50327 Directed 175192 16.47 2.51 16.47
50324 50047 Directed 176057 15.31 3.33 15.31
50366 50279 Directed 175235 17.53 1.76 17.53
50366 50171 Directed 176047 12.83 5.08 12.83
50507 50505 Directed 178501 19.97 0.04 19.97
50507 50715 Directed 183075 19.98 0.03 19.98
50715 50507 Directed 178500 19.97 0.04 19.97
50715 50513 Directed 183074 19.96 0.04 19.96
50398 50264 Directed 175941 12.84 5.07 12.84
50398 50239 Directed 175126 14.9 3.62 14.9
50040 50264 Directed 183018 19.92 0.07 19.92
50040 50031 Directed 175297 16.56 2.45 16.56
50040 50250 Directed 178291 19.98 0.03 19.98
50040 50269 Directed 176141 18.54 1.05 18.54
50040 50037 Directed 177025 19.66 0.26 19.66
50022 50110 Directed 183038 19.96 0.04 19.96
50022 50171 Directed 179147 19.97 0.03 19.97
50022 50201 Directed 179244 19.91 0.08 19.91
50081 50010 Directed 175200 18.8 0.86 18.8
50104 50357 Directed 175473 18.09 1.36 18.09
50104 50113 Directed 175973 18.26 1.24 18.26
50132 50015 Directed 175214 18.1 1.36 18.1
50132 50205 Directed 176029 19.13 0.63 19.13
50357 50255 Directed 177336 20.01 0.01 20.01
50357 50104 Directed 176287 18.09 1.36 18.09
50357 50136 Directed 179140 19.98 0.03 19.98
50010 50081 Directed 176014 18.8 0.86 18.8
50010 50205 Directed 175199 16.54 2.46 16.54
50110 50047 Directed 175118 18.63 0.98 18.63
50110 50022 Directed 179146 19.95 0.05 19.95
50110 50049 Directed 183037 19.98 0.03 19.98
50205 50132 Directed 175215 19.13 0.63 19.13
50205 50010 Directed 176013 16.54 2.46 16.54
50205 50049 Directed 176008 18.13 1.33 18.13
50136 50357 Directed 177350 19.98 0.03 19.98
50136 50256 Directed 177253 20.01 0.01 20.01
50136 50322 Directed 176022 19.7 0.22 19.7
50256 50136 Directed 177351 20.01 0.01 20.01
50256 50322 Directed 179141 20.01 0.01 20.01
50322 50286 Directed 183086 19.89 0.09 19.89
50322 50378 Directed 183084 19.95 0.05 19.95
50322 50136 Directed 175208 19.7 0.22 19.7
50322 50256 Directed 177352 20.01 0.01 20.01
50322 50268 Directed 179142 20.0 0.02 20.0
50322 50762 Directed 179189 19.95 0.05 19.95
50268 50322 Directed 177683 20.0 0.02 20.0
50268 50312 Directed 179143 20.0 0.01 20.0
50268 50278 Directed 175975 17.94 1.47 17.94
50312 50268 Directed 177699 20.0 0.01 20.0
50312 50335 Directed 178307 19.98 0.03 19.98
50109 50177 Directed 175205 18.6 1.01 18.6
50109 50381 Directed 176018 18.14 1.33 18.14
50049 50110 Directed 179145 19.98 0.03 19.98
50049 50205 Directed 175194 18.13 1.33 18.13
50049 50336 Directed 179243 19.87 0.11 19.87
50381 50255 Directed 176020 18.77 0.89 18.77
50381 50109 Directed 175204 18.14 1.33 18.14
50176 50031 Directed 176094 14.53 3.88 14.53
50176 50250 Directed 183021 19.91 0.08 19.91
50031 50040 Directed 176111 16.56 2.45 16.56
50031 50176 Directed 175280 14.53 3.88 14.53
50031 50250 Directed 176146 18.66 0.96 18.66
50250 50483 Directed 177988 19.62 0.28 19.62
50250 50482 Directed 177987 19.62 0.28 19.62
50250 50716 Directed 177628 19.74 0.2 19.74
50250 50264 Directed 183020 19.89 0.09 19.89
50250 50040 Directed 183019 19.97 0.04 19.97
50250 50176 Directed 179131 19.91 0.07 19.91
50250 50031 Directed 175332 18.66 0.96 18.66
50043 50122 Directed 175224 17.41 1.84 17.41
50043 50174 Directed 176039 16.66 2.37 16.66
50513 50715 Directed 178476 19.93 0.06 19.93
50513 50456 Directed 183073 19.97 0.04 19.97
50512 50505 Directed 178028 19.81 0.15 19.81
50512 50499 Directed 177642 19.82 0.14 19.82
50512 50456 Directed 183191 19.94 0.06 19.94
50269 50264 Directed 176107 13.35 4.71 13.35
50269 50040 Directed 175327 18.54 1.05 18.54
50171 50366 Directed 175233 12.83 5.08 12.83
50171 50022 Directed 183039 19.98 0.03 19.98
50171 50042 Directed 179148 19.98 0.03 19.98
50237 50308 Directed 175475 18.34 1.19 18.34
50237 50241 Directed 176011 17.38 1.87 17.38
50174 50043 Directed 175225 16.66 2.37 16.66
50174 50307 Directed 176988 19.6 0.3 19.6
50174 50308 Directed 175997 18.31 1.21 18.31
50278 50268 Directed 175161 17.94 1.47 17.94
50278 50335 Directed 175976 19.42 0.43 19.42
50113 50104 Directed 175159 18.26 1.24 18.26
50335 50312 Directed 182888 19.98 0.03 19.98
50335 50278 Directed 175162 19.42 0.43 19.42
50335 50417 Directed 176030 4.94 10.66 4.94
50762 50286 Directed 183053 19.82 0.14 19.82
50762 50322 Directed 183085 19.95 0.05 19.95
50762 50326 Directed 179165 19.52 0.36 19.52
50307 50134 Directed 179125 19.91 0.08 19.91
50307 50761 Directed 183055 19.96 0.04 19.96
50307 50174 Directed 176688 19.18 0.59 19.18
50307 50241 Directed 183024 19.98 0.03 19.98
50456 50505 Directed 179259 19.79 0.16 19.79
50456 50513 Directed 178470 19.96 0.04 19.96
50456 50512 Directed 179258 19.93 0.06 19.93
50456 50455 Directed 183072 19.98 0.03 19.98
50456 50516 Directed 179252 19.91 0.08 19.91
50455 50456 Directed 178463 19.98 0.03 19.98
50455 50516 Directed 183071 19.94 0.06 19.94
50516 50505 Directed 179123 19.43 0.42 19.43
50516 50714 Directed 183078 19.5 0.37 19.5
50516 50456 Directed 183193 19.92 0.07 19.92
50516 50455 Directed 179182 19.94 0.05 19.94
50516 50326 Directed 179187 20.01 0.01 20.01
50326 50762 Directed 183054 19.49 0.37 19.49
50326 50516 Directed 183082 20.01 0.01 20.01
50326 50521 Directed 178320 19.98 0.03 19.98
50326 50417 Directed 183027 19.59 0.3 19.59
50518 50336 Directed 183166 19.96 0.04 19.96
50518 50201 Directed 179239 19.96 0.04 19.96
50521 50326 Directed 183028 19.97 0.04 19.97
50521 50523 Directed 178321 19.99 0.02 19.99
50417 50335 Directed 175216 4.94 10.66 4.94
50417 50326 Directed 178314 19.59 0.3 19.59
50523 50521 Directed 183029 19.99 0.02 19.99
50336 50049 Directed 183172 19.88 0.1 19.88
50336 50518 Directed 179238 19.95 0.05 19.95
50336 50089 Directed 175346 16.0 2.84 16.0
50308 50237 Directed 176289 18.34 1.19 18.34
50308 50174 Directed 175183 18.31 1.21 18.31
50089 50336 Directed 176160 16.0 2.84 16.0
50089 50201 Directed 175504 16.8 2.27 16.8
50201 50022 Directed 183173 19.91 0.08 19.91
50201 50518 Directed 183167 19.97 0.04 19.97
50201 50089 Directed 176318 16.8 2.27 16.8
50042 50171 Directed 183040 19.98 0.03 19.98
50241 50237 Directed 175197 17.38 1.87 17.38
50241 50307 Directed 179134 19.99 0.02 19.99
50037 50040 Directed 176725 19.29 0.51 19.29
id label name long lat rank expense
50206 Iol Caesarea Iol Caesarea 2.2 36.61 90 0.81
50756 Palma Palma 2.65 39.57 80 0.63
50448 Ebusus Ebusus 1.42 38.91 80 0.71
50368 Sufasar Sufasar 2.51 36.19 80 3.38
50060 Auzia Auzia 3.68 36.15 80 7.47
50145 Dertosa Dertosa 0.52 40.81 80 0.55
50065 Barcino Barcino 2.16 41.36 90 0.57
50476 Colubraria Colubraria 0.69 39.9 60 0.79
50379 Tarraco Tarraco 1.23 41.12 100 0.52
50483 Metagonium Pr. Metagonium Pr. 6.47 37.09 60 0.58
50394 Tubusuctu Tubusuctu 4.83 36.67 80 1.59
50221 Lamasba Lamasba 5.91 35.6 80 8.69
50148 Diana Veteranorum Diana Veteranorum 5.89 35.79 80 9.48
50135 Cuicul Cuicul 5.73 36.32 80 5.89
50362 Sitifis Sitifis 5.39 36.19 90 4.7
50482 Igilgili Igilgili 5.77 36.82 80 0.64
50334 Saldae Saldae 5.07 36.76 80 0.68
50222 Lambaesis Lambaesis 6.26 35.49 90 7.34
50120 Cirta Cirta 6.59 36.36 90 2.98
50299 Perdices Perdices 5.37 35.84 70 6.21
50427 Zarai Zarai 5.68 35.8 80 7.25
50385 Thamugadi Thamugadi 6.47 35.5 80 6.65
50329 Rusicade Rusicade 6.91 36.88 80 0.56
50203 Ilerda Ilerda 0.62 41.61 80 3.52
50167 Emporiae Emporiae 3.1 42.15 80 0.54
50390 Theveste Theveste 8.14 35.41 80 9.69
50198 Hippo Regius Hippo Regius 7.75 36.89 90 0.5
50384 Thagaste Thagaste 7.98 36.29 80 3.09
50486 Caralis Caralis 9.11 39.21 80 0.3
50451 Olbia Olbia 9.5 40.93 80 0.21
50716 Aleria Aleria 9.53 42.12 80 0.28
50487 Gallicum Fretum Gallicum Fretum 9.16 41.39 60 0.23
50369 Sufetula Sufetula 9.13 35.26 80 6.12
50449 Thabraca Thabraca 8.75 36.95 80 0.44
50260 Musti Musti 9.14 36.34 80 4.69
50021 Ammaedara Ammaedara 8.46 35.57 80 8.48
50087 Bulla Regia Bulla Regia 8.74 36.56 80 4.29
50391 Thuburnica Thuburnica 8.46 36.53 80 3.28
50485 Galata Galata 8.93 37.53 60 0.41
50387 Thelepte Thelepte 8.6 34.99 80 8.0
50107 Carthago Carthago 10.31 36.85 100 0.37
50352 Sicca Veneria Sicca Veneria 8.71 36.18 80 5.41
50317 Portus Portus 12.26 41.78 90 0.09
50505 Lilybaeum Lilybaeum 12.44 37.8 80 0.34
50501 Maritima Maritima 12.06 37.97 60 0.31
50279 Ocriculum Ocriculum 12.47 42.41 80 0.71
50265 Narnia Narnia 12.52 42.52 80 1.18
50327 Roma Roma 12.49 41.89 100 0.0
50425 Volsinii Volsinii 11.99 42.64 80 3.53
50134 Cosa Cosa 11.29 42.41 80 0.16
50286 Ostia/Portus Ostia/Portus 12.29 41.75 90 0.1
50489 Ilva Ilva 10.24 42.74 70 0.24
50499 Cossyra Cossyra 11.94 36.83 70 0.4
50498 Caput Vada Caput Vada 11.16 35.23 60 0.57
50188 Hadrumetum Hadrumetum 10.64 35.84 90 0.5
50493 Mercurii Pr. Mercurii Pr. 11.01 37.06 60 0.41
50495 Clipea Clipea 11.11 36.84 70 0.44
50264 Narbo Narbo 3.0 43.2 90 0.48
50239 Lugdunum Convenarum Lugdunum Convenarum 0.56 43.04 80 7.57
50015 Alba Fucens Alba Fucens 13.41 42.08 80 3.23
50255 Minturnae Minturnae 13.77 41.24 90 0.21
50175 Formiae Formiae 13.61 41.26 80 0.2
50177 Fregellanum Fregellanum 13.51 41.55 60 3.44
50047 Asculum Asculum 13.57 42.85 90 1.96
50378 Tarracina Tarracina 13.25 41.29 90 0.17
50714 Panormus Panormus 13.32 38.12 90 0.41
50173 Ferentinum Ferentinum 13.25 41.69 80 2.47
50122 Clusium Clusium 11.95 43.02 90 4.75
50761 Populonium Populonium 10.5 43.0 80 0.21
50393 Tibur Tibur 12.8 41.96 80 0.99
50324 Reate Reate 12.86 42.4 80 2.51
50366 Spoletium Spoletium 12.74 42.74 90 2.47
50507 Selinus Selinus 12.83 37.58 90 0.39
50715 Agrigentum Agrigentum 13.58 37.31 80 0.43
50398 Tolosa Tolosa 1.43 43.62 80 3.95
50040 Arelate Arelate 4.63 43.69 90 0.53
50022 Ancona Ancona 13.52 43.62 90 0.96
50081 Bovianum Bovianum 14.47 41.49 80 5.6
50104 Capua Capua 14.24 41.09 90 1.59
50132 Corfinium Corfinium 13.84 42.12 80 2.91
50357 Sinuessa Sinuessa 13.83 41.16 80 0.22
50010 Aesernia Aesernia 14.23 41.59 80 4.74
50110 Castrum Truentinum Castrum Truentinum 13.9 42.9 80 0.98
50205 Interpromium Interpromium 13.93 42.24 70 2.28
50136 Cumae Cumae 14.04 40.85 80 0.23
50256 Misenum Misenum 14.08 40.78 80 0.23
50322 Puteoli Puteoli 14.11 40.83 90 0.22
50268 Neapolis Neapolis 14.23 40.84 90 0.24
50312 Pompeii Pompeii 14.48 40.75 80 0.25
50109 Casinum Casinum 13.83 41.49 90 2.43
50049 Aternum Aternum 14.21 42.46 80 0.95
50381 Teanum Teanum 14.06 41.25 80 1.1
50176 Forum Iulii Forum Iulii 6.73 43.44 80 0.43
50031 Aquae Sextiae Aquae Sextiae 5.44 43.53 80 1.45
50250 Massilia Massilia 5.36 43.31 80 0.49
50043 Arretium Arretium 11.86 43.47 90 2.91
50513 Camarina Camarina 14.45 36.85 80 0.52
50512 Melita Melita 14.4 35.88 80 0.54
50269 Nemausus Nemausus 4.36 43.84 80 1.57
50171 Fanum Fortunae Fanum Fortunae 13.02 43.84 80 0.99
50237 Luca Luca 10.5 43.85 90 2.14
50174 Florentia Florentia 11.26 43.77 80 0.54
50278 Nuceria Nuceria 14.67 40.74 80 0.71
50113 Caudium Caudium 14.64 41.06 80 2.83
50335 Salernum Salernum 14.77 40.68 80 0.28
50762 Palinurus Pr. Palinurus Pr. 14.93 40.26 60 0.27
50307 Pisae Pisae 10.4 43.72 90 0.25
50456 Portus Pachyni Portus Pachyni 15.14 36.68 70 0.54
50455 Syracusae Syracusae 15.29 37.07 90 0.52
50516 Messana Messana 15.56 38.19 90 0.44
50326 Regium Regium 15.64 38.11 80 0.43
50518 Titius (river) Titius (river) 15.86 43.72 60 0.99
50521 Heracleum Pr. Heracleum Pr. 16.06 37.93 60 0.48
50417 Vibo Valentia Vibo Valentia 16.1 38.67 80 0.49
50523 Lokroi Epizephyrioi Lokroi Epizephyrioi 16.26 38.24 80 0.5
50336 Salona Salona 16.5 43.53 90 0.94
50308 Pistoriae Pistoriae 10.89 43.93 80 1.75
50089 Burnum Burnum 16.0 44.01 80 3.3
50201 Iader Iader 15.23 44.11 80 1.02
50042 Ariminum Ariminum 12.56 44.06 80 1.02
50241 Luna Luna 10.03 44.07 90 0.27
50037 Arausio Arausio 4.81 44.14 80 1.04
@doaa-altarawy
Copy link

Why is the project called Readme instead of a name?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment