Tag Archives: gephi

#NoDAPL Twitter Analysis


Map By Carl Sack


My Visualization of the #NODAPL network

Preliminary Network Analysis:












Further Observations:




Next Steps:



Detailed Procedure:



















































2 of N: Gephi, D3.js, and maps: Success!

A working, geographically accurate map using Gephi, D3.js, and Leaflet. NOTE: Link subject to change.

In my previous post I outlined how I used D3.js to display a “raw” JSON output from Gephi. After some hacking around, I am now able to display my Gephi data on an interactive leaflet map!

This is a departure from other work on the subject for a few reasons:

  1. Not all of my data has geographic information – indeed in many cases a specific longitude / latitude combination is inappropriate and would lend a false sense of permanence to anyone looking at the map. In my case I have names of Greek garrison commanders which have some relation to a place, but it is unclear in some instances if they are actually at a specific place, have dominion over the location, or are mentioned in an inscription for some other reason. Therefore, I need to locate data that has a fuzzy relation to a location (ancient people who may originate, reside, work, and be mentioned in different and / or unknown locations) and locations that may themselves have fuzzy or unknown geography. This is a problem for just about every ancient to pre-modern project, as we do not have a wealth of location information, or even a clear idea of where some people are at any particular moment.
  2. I want to show how social networks form around specific geographic points which are known, and have those social networks remain “reactive” on zooms, changing map states, etc. This can be expanded to encompass epistolary networks, knowledge maps, etc – basically anything that links people together who may not be locatable themselves.
  3. Gephi does not output in GeoJSON, and the remaining export options that are geographically oriented require that *all* nodes have geographic information. As this is not my case (see above), the standard export options will not work for me. Also, as part of my work on BAM, I want to create a framework that is as “plug and play” as possible, so that we can simply take Gephi files and drop them into the system to make new modules. Therefore this work has to be reproducible with a minimum of tweaking.

So, let us get to the code!

First things first: You need to make your html, bring in your javascript,and style some elements. I put the css in the file for testing – it will be split off later.

&lt;!DOCTYPE html&gt;

&lt;meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no' /&gt;
&lt;!-- Mapbox includes below --&gt;
&lt;script src='https://api.mapbox.com/mapbox.js/v2.2.2/mapbox.js'&gt;&lt;/script&gt;
&lt;link href='https://api.mapbox.com/mapbox.js/v2.2.2/mapbox.css' rel='stylesheet' /&gt;
&lt;script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"&gt;&lt;/script&gt;
&lt;script src="http://d3js.org/d3.v3.js"&gt;&lt;/script&gt;
&lt;meta charset="utf-8"&gt;
&lt;!-- Will split off css when done with testing --&gt;

.node circle {
stroke: grey;
stroke-width: 10px;

.link {
stroke: black;
stroke-width: 1px;
opacity: .2;

.label {
font-family: Arial;
font-size: 12px;

#map {
height: 98vh;

#attributepane {
display: block;
display: none;
position: absolute;
height: auto;
bottom: 20%;
top: 20%;
right: 0;
width: 240px;
background-color: #fff;
margin: 0;
border-left: 1px solid #ccc;
padding: 18px 18px 18px 18px;
z-index: 8998;
overflow: scroll;


&lt;div id='attributepane'&gt;&lt;/div&gt;

&lt;div id='map'&gt;

Next, make a map.

var map = L.mapbox.map('map', 'yourmap', {
accessToken: 'yourtoken'

//set the initial view. This is pretty standard for most of the ancient med. projects
map.setView([40.58058, 36.29883], 4);

Pretty basic so far. Next we follow some of the examples that are already in the wild to initiate D3 goodness:

var force = d3.layout.force()

/* Initialize the SVG layer */

/* We simply pick up the SVG from the map object */
var svg = d3.select("#map").select("svg"),
g = svg.append("g");

Next, we bring in our json file from Gephi. Again, this is pretty standard:

d3.json("graph.json", function(error, json) {

if (error) throw error;

Now we get into the actual modifications to make the json, D3, and leaflet all talk to each other. The first thing to do is to modify the colors (from http://stackoverflow.com/questions/13070054/convert-rgb-strings-to-hex-in-javascript) so that D3 displays what we have in Gephi:

//fix up the data so it is what we want for d3
json.nodes.forEach(function(d) {
//convert the rgb colors to hex for d3
a = a.split(",");

var b = a.map(function(x) { //For each array element
b = "#" + b.join("");
d.color = b;

Next, we need to put in “dummy” coordinates for locations that do not have geography. This is messy and could probably be removed with some more efficient coding later. For the nodes that do have geography, the map.latLngToLayerPoint will translate the values into map units, which places them where they need to go. These are simply lat lon attributes in the Gephi file. I also set nodes that are fixed / not fixed, based on the presence of lat/lon data.

if (!("lng" in d.attributes) == true) {
//if there is no geography, then allow the node to float around
d.LatLng = new L.LatLng(0, 0);
d.fixed = false;
} else //there is geography, so place the node where it goes
d.LatLng = new L.LatLng(d.attributes.lat, d.attributes.lng);
d.fixed = true;
d.x = map.latLngToLayerPoint(d.LatLng).x;
d.y = map.latLngToLayerPoint(d.LatLng).y;

Now to setup the links. As we are keyed on attributes and not an index value, we need to follow this fix:

var edges = [];
json.edges.forEach(function(e) {
var sourceNode = json.nodes.filter(function(n) {
return n.id === e.source;
targetNode = json.nodes.filter(function(n) {
return n.id === e.target;

source: sourceNode,
target: targetNode,
value: e.Value

var link = svg.selectAll(".link")
.attr("class", "link");

Now to setup the nodes. I wanted to do a popup on a mouseclick event, but for some reason this is not firing (mousedown and mouseover do work, however). The following code builds the nodes, with radii, fill, and other information pulled from the JSON file. It also toggles a div that is populated with attribute information from the JSON. There is still some work to do at this part: the .css needs to be cleaned up, images need to be resized, and the attribute information for the nodes should be a configurable option when importing the JSON.

var node = svg.selectAll(".node")
//display nodes and information when a node is clicked on
//for some reason the click event is not registering, but mousedown and mouseover are.
.on("mouseover", function(d) {

//put in blank values if there are no attributes
var titleForBox, imageForBox, descriptionForBox = '';
titleForBox = '
&lt;h1&gt;' + d.label + '&lt;/h1&gt;


if (typeof d.attributes.Description != "undefined") {
descriptionForBox = d.attributes.Description;
} else {
descriptionForBox = '';

if (typeof d.attributes.image != "undefined") {
imageForBox = '&lt;img src="' + d.attributes.image + '" align="left"&gt;';
} else {
imageForBox = '';

var htmlForBox = imageForBox + ' ' + titleForBox + descriptionForBox;
document.getElementById("attributepane").innerHTML = htmlForBox;
.style("stroke", "black")
.style("opacity", .6)
.attr("r", function(d) {
return d.size * 2;
.style("fill", function(d) {
return d.color;

Now for the transformations when the map state changes. The idea is to keep the fixed nodes in the correct place, but to redraw the “floating” nodes when the map is zoomed in and out. The nodes that need to be transformed are dealt with first, then the links are rebuilt with the new (or fixed) x / y data.

//for when the map changes viewpoint
map.on("viewreset", update);

function update() {

function(d) {
if (d.fixed == true) {
d.x = map.latLngToLayerPoint(d.LatLng).x;
d.y = map.latLngToLayerPoint(d.LatLng).y;
return "translate(" +
map.latLngToLayerPoint(d.LatLng).x + "," +
map.latLngToLayerPoint(d.LatLng).y + ")";

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("cx", function(d) {
if (d.fixed == false) {
return d.x;
.attr("cy", function(d) {
if (d.fixed == false) {
return d.y;

//this kickstarts the simulation, so the nodes will realign to a zoomed state

Next, time to start the simulation for the first time and close out the d3 json block:

force.on("tick", update);

}); //end

Finally, time to put a function in to toggle the visibility of the div (from here) and close out our file:

function toggle_visibility(id) {
var e = document.getElementById(id);
if (e.style.display == 'block')
e.style.display = 'none';
e.style.display = 'block';

There you have it- a nice, interactive map with a mix of geographic information and social networks. While I am pleased with the result, there are still some things to fix / address:

  1. The click even not working. This is a real puzzler.
  2. Tweaking the distances of the simulation – I do not want nodes to be placed half a world away from their connections. This may have to be map zoom level dependent.
  3. Style the links according to Gephi and provide popups where applicable. This should be easy enough to do, but simply hasn’t been done in this code.
  4. Tweak the visibility of the connections and nodes. While retaining an option to show the entire network at once, my idea is to have a map that starts out with JUST the locations, and then makes the nodes that are connected to that location visible when you click on it (which would also apply to the unlocated nodes – i.e. you see what they are connected to when you click on them).
  5. Connected to the above point, the implementation of a slider to show nodes in a particular timeframe. As my data spans a period from the 600s BCE to the 200s CE, this would provide a better snapshot of a particular network at a particular time.
  6. Implement a URI based system – you will be able to go to address/someEntityName and that entity will be selected with its information pane and connected neighbors displayed. This will result in an RDF file that will be sent to the Pelagios Project.
  7. Fix up the .css for the information pane.

I will detail further steps in a later post.

1 of N: Gephi, D3.js, and maps


After finding no real way to use background maps with SigmaJs, I stumbled on this example of combining leaflet with D3.jshttp://bost.ocks.org/mike/leaflet/. The example is more closely aligned with what I want to achieve, which is using a display library to show a social network that respects / interacts with underlying geography. This would be a very valuable visualization for both TBib/BAM and my own work on garrisons, and completing it will allow me to get back to other tasks, like pounding out Greek inscriptions.

For this work I am not tied to Gephi, but I do like its interface and low learning curve, which is valuable for pedagogical and collaborative use. So, my first order of business is getting a Gephi project to talk nicely with D3.js. There is, of course, a nice example already in the wild: http://bl.ocks.org/susielu/9526340. However, this presented some serious problems, which I will outline to (hopefully!) help others who may be going down this path. So, refer back to http://bl.ocks.org/susielu/9526340 for the code template – what follows below are additions / modifications.



 var links = json.edges.map(function(d){
 return {
 'source': parseInt(d.source),
 'target': parseInt(d.target)


var links = json.edges.map(function(d){
 return {
 'source': d.source,
 'target': d.target


It seems that linking nodes by attribute instead of index is a somewhat common problem in D3.js, with a good solution here: http://stackoverflow.com/questions/23986466/d3-force-layout-linking-nodes-by-name-instead-of-index. Following this example, I modified my code by adding the following:

var edges = [];
links.forEach(function(e) {
// Get the source and target nodes
var sourceNode = nodes.filter(function(n) { return n.id === e.source; })[0],
targetNode = nodes.filter(function(n) { return n.id === e.target; })[0];

// Add the edge to the array


var force = d3.layout.force()


var link = svg.selectAll(".link")





did the trick. I also wanted to add colors from Gephi – the following code does so (with a conversion from RGB to hex provided by http://stackoverflow.com/questions/13070054/convert-rgb-strings-to-hex-in-javascript) :

a = a.split(",");


b = "#"+b.join("");

 return {
 'id' : d.id,
 'x' : d.x,
 'y' : d.y,
 'fixed': true,
 'label' : d.label,
 'size' : d.size,
 'color' : b,



added to


onemoreproblemThis produces a graph that looks correct except for one MAJOR problem: It seems the Y axis is inverted from the original! This is obviously not acceptable if I am trying to capture actual coordinates for a map. All is not lost: I do remember this being a problem in the SigmaJS exporter. A fix is provided here: https://github.com/oxfordinternetinstitute/gephi-plugins/issues/5#issuecomment-22291683. For me, this was as simple as adding the following code:

finalY = -d.y;
return {
'id' : d.id,
'x' : d.x,
'y' : finalY,
'fixed': true,
'label' : d.label,
'size' : d.size,
'color' : b,


to the

  var nodes = json.nodes.map(function(d)



Quick and Dirty Footnotes For Gephi / SigmaJS

Before I begin, I once again want to recognize the excellent SigmaJS Exporter plugin for Gephi. This really does mitigate a lot of the grunt work involved in quickly making a usable, interactive social network graph. However, sometimes you just want another feature or some further refinement – in my case adding workable footnotes to information on each node.

For those of us in the humanities, citations are sine qua non for scholarship. However, there are few good was to maintain linkable citations on the web that are not hardcoded beforehand, or reliant on javascript trickery. What I wanted to do was find a tool or a method to easily move text and citations contained in my dissertation to a description field in a Gephi-based application without manually entering footnotes, footnote numbers, or linking them myself, as I have over 2,000 footnotes to deal with.

What I found is a bit of a hack, and certainly can be improved, but it works. First, you are going to want to have your document in a format that is readable by OpenOffice / LibreOffice / etc. What you need to do is select the bit of text you are interested in, dump it into a new file (making sure to include your footnotes!) and then export that file as XHTML.

export-textOnce this is complete, you will have a lovely, fully encapsulated xml file of your text – including all formatting, footnotes, etc. However, we want to eliminate some of the elements produced by this process. Open this file in your favorite text editor. You will notice that you have code similar to the following at the top of the document:

<!--This file was converted to xhtml by LibreOffice - see http://cgit.freedesktop.org/libreoffice/core/tree/filter/source/xslt for the code.-->
<head profile="http://dublincore.org/documents/dcmi-terms/">
<title xml:lang="en-US">- no title specified</title>
<meta name="DCTERMS.title" content="" xml:lang="en-US"/>
<meta name="DCTERMS.language" content="en-US" scheme="DCTERMS.RFC4646"/>
<meta name="DCTERMS.source" content="http://xml.openoffice.org/odf2xhtml"/>
<meta name="DCTERMS.issued" content="2015-09-26T21:09:50.283345000" scheme="DCTERMS.W3CDTF"/>
<meta name="DCTERMS.modified" content="2015-09-26T21:23:01.904161000" scheme="DCTERMS.W3CDTF"/>
<meta name="DCTERMS.provenance" content="" xml:lang="en-US"/>
<meta name="DCTERMS.subject" content="," xml:lang="en-US"/>
<link rel="schema.DC" href="http://purl.org/dc/elements/1.1/" hreflang="en"/>
<link rel="schema.DCTERMS" href="http://purl.org/dc/terms/" hreflang="en"/>
<link rel="schema.DCTYPE" href="http://purl.org/dc/dcmitype/" hreflang="en"/>
<link rel="schema.DCAM" href="http://purl.org/dc/dcam/" hreflang="en"/>

This can all be eliminated. Make sure you retain the

<style type="text/css">

tag at the end of the line.

Likewise, delete this from the start of the last line:

</head><body dir="ltr" style="max-width:8.5in;margin-top:0.7874in; margin-bottom:0.7874in; margin-left:0.7874in; margin-right:0.7874in; writing-mode:lr-tb; ">

and this from the end of the last line:


Now, simply paste what is left into a field in your Gephi data.

Gephi with footnotes

Export as usual, and viola! you have your clickable, interactive footnotes.


Now, this is good for a quick and dirty solution, but it would require a modification of the json datafile if you ever make a change or wish to add more information (or, even worse, a re-export of your entire network). As such, this solution will not be used for BAM, as we are seeking a more flexible and modifiable code base.

Networks, Geography, and Gephi: Lots of Promise, but Lots of Work to be Done

This post will outline some of my efforts to bring social networks into dialog with geography. Although I have found some interesting plugins and hacks, the results still leave something to be desired.

Screen Shot 2015-10-07 at 1.40.46 PMTo provide some background: From my dissertation I have a nice, interactive map of all garrisons (phrourai, in orange), and garrison commanders (phrourarchoi, in white) from all of Greek sources up to the mid second century C.E. This is all nicely georeferenced, linked to other projects such as Pleiades and Pelagios, and serves its purpose pretty well. However, this provides the location and frequency of garrisons and commanders, and does not really show the social network that developed between commanders, monarchs, and communities. I could perhaps use a clustering strategy to create dynamic markers around specific points, but that seems to be a very unwieldy solution.

Strictly speaking, by modeling people (phrourarchoi, monarchs) with places and abstract communities I am moving beyond a social network and instead looking at an information network, as I am interested in a number of different connections (social, geographic, ideological) that are not traditionally associated with social network analysis.

The first step to get all of my data into Gephi, assign different “types” to my nodes (in my case people, offices, places, phrourarchoi). I then created a network map, ran statistics, assigned the node size based on degree, and ran a force atlas layout. At the same time I also color coded the network based on type. This is all pretty basic Gephi use so far, and produced a perfectly serviceable network graph.

First graph. Pretty basic and serviceable.

Now it was time to experiment with different types of ranking. Betweenness centrality, or the measure of a node’s influence, led to an interesting difference in graphs:

Betweenness Graph. Note the increased importance of individuals.

However, this result is somewhat meaningless, as my graph covers a period from the 400s BCE to the 100s CE. Despite any of his wishes to the contrary, Ptolemy VIII did not live forever, yet he is the unquestioned central authority of this graph. All of the other Egyptian monarchs also score highly, underlining their importance in the communications and relationships between different phrourarchoi. This is an interesting yet hardly unsurprising finding – a good portion of the surviving data on phroruarchoi originates from Ptolemaic Egypt, which may inflate the relative importance of the dynasty in this kind of analysis. What this map does show is the enormous influence of individuals – most of whom were not phrourarchoi themselves.

However, I am interested in garrisons as a sustained phenomena across several centuries, so I want to get back to the importance of location and geography on garrisons. In other words: Where are the most important locations for phrourarchoi, and how do those relate to one another?

Running an Eigenvector Centrality measurement produces a graph that somewhat mimics my original map, with physical locations, not people as the most significant authorities. This gives a better impression of what I am looking for – the centrality of a node relative to the whole network, which in my case privileges locations, which often serve as a bridge between different populations of nodes.

Eigenvector Centrality

To me this is an interesting graph: It shows the importance of locations, while still highlighting important individuals. Now that I have this graph, I would love to place it on a map. I actually have coordinates for all of the locations, so a simple use of the Gephi GeoLayout plugin puts all of my identified places in a rough geographic layout.

Screen Shot 2015-10-07 at 12.48.40 PM

From here I simply fixed the location of the places, then ran some other layouts to try and make a coherent graph of people and offices that did not have a specific geographic value.The results were generally less than satisfying. The individuals in my dataset are not assigned coordinates because it would make little sense to do so – some phroruarchoi served in multiple locations, and almost all imperial phrourarchoi served outside their place of origin, were buried somewhere else, possibly lived in yet another location, etc.

Force Atlas combined with GeoLayout
Force atlas and Fruchterman-Reingold
Adjusting the size of the nodes and running force atlas eventually produced  a result that looks more comprehensible, if a bit small.

From this step, I thought I would try out some Gephi plugins to push my data into a format I could drop onto a map. Only a very small percentage of my nodes actually contain geographic information, so the ExportToEarth plugin was not going to help. My first attempt at pushing out a shapefile using Export to SHP initially looked like a success in QGIS:

Screen Shot 2015-10-07 at 1.43.16 PM
This looks promising…

So, I decided to throw in some background, and that is when the trouble started. QGIS does a good job of transforming coordinates, but this was just messy (and not to mention wrong – there certainly were no phrourarchoi in Antarctica!)

Screen Shot 2015-10-07 at 1.35.59 PM
Note how the nodes are now literally all over the map.

So, what happened? If you do not have coordinates already explicitly assigned to your data, Export to SHP actually does not use “geographic” coordinates, and instead uses, in the words of the plugin, “fake geography – that is the current position of the nodes in the Gephi layout”. My thought that this position would line up with correct coordinates fromGeoLayout were false –Export to SHP treats the middle of the map as an origin point (instead of using whatever geographic data is present), and as such it does not match with any projection in QGIS.

This is a bit of a let down. It seems that all of mapping plugins in Gephi need for *ALL* of the nodes to have geographic information already baked in, or they will not export a geographically accurate map. This does make some sense, but it would be nice if you could use GeoLayout to place nodes with actual geographic data, then use force atlas or some other layout to produce a graph, and finally use the location of those nodes as coordinates. In other words, the location of nodes on the graph that have no actual geographic data of their own are located relative to nodes that do have geographic data. I tried the Sigmajs exporter, but the json object also does not use real coordinates, as seen in the fragment below ( lng and lat are the real-world coordinates, while x and are used by SigmaJS):


So, is there a way around this?

Short of writing a new plugin to do so, it looks like Gephi is simply missing the functionality of assigning geographic points to nodes that do not already have that information, then exporting that graph in a way that makes sense to mapping software. I could export an image and georeference that, but that will not provide the functionality I am looking for either.

What I would like is for a graph produced by Gephi to use coordinates for nodes that have them, and make real world coordinates for nodes that do not. This map could then be placed on Leaflet / OpenLayers / whatever map, providing a level of interaction beyond a static image. As it is impracticable to duplicate the functionality (especially the statistical tools and layouts) of Gephi in a mapping application, this strikes me as something that would be very valuable to visualization and study.

My next idea is to see if R has something close to what I want, which I will detail in a future post.

Code for BAM: Part 1 of N. Gephi and Maps




For a working demonstration of the final result, see http://awmc.unc.edu/awmc/applications/bam/luke/. All of the code mentioned in this post, and created for BAM, is available at: https://github.com/Big-Ancient-Mediterranean/BAM.

Step 1: Get your data in order!










<div class="sigma-parent">



<div class="text">


<div class="c cf">
<span>Return to the full network</span>


<div class="nodeattributes">

<div class="name"></div>

<div class="data"></div>


<div class="link">











(function() {
//first copy the old function in the new one
 var old_nodeActive = nodeActive;

 nodeActive = function() {

// this is a separate function that will be explained below 
 tBibPersonConnections(arguments[0], tBibPeoplelayer);
 activePerson = arguments[0];

// Calls the original function\
 var result = old_nodeActive.apply(this, arguments);

// now return the result
 return result;


function tBibPersonConnections(personNameChoice, tBibPeoplelayer)
 tBibfeaturesOnMap =[];

 dataType: "json",
 success:function(dataJson) {
 var untransformed_feature = geojson_format.read(dataJson, "FeatureCollection");
 if (tBibfeaturesOnMap.indexOf(untransformed_feature[j].attributes.pid) &amp;amp;amp;amp;amp;lt; 0){
 error: function (xhr, ajaxOptions, thrownError) {




tBibfeaturesOnMap =[];



$query = "select
pplaces.title, count(pplaces.title), max (pplaces.id) as pleaides_id,
from pplaces
pplaces.id = tbib_pleiades.pleiades_id
tbib_pleiades.verse = tbib_network.reference



//make a geojson object
while($row =pg_fetch_assoc($qry_result)){
//resize for map

//arrange for map
$arr[] = array(
"type" => "Feature",
"properties" => array(
 "title" =>$row[title],
 "count" =>$sizeForMap,
 "pid" => $row[pleaides_id]
//encode into geojson
echo $geojson;



 success:function(dataJson) {
 var untransformed_feature = geojson_format.read(dataJson, "FeatureCollection");
 if (tBibfeaturesOnMap.indexOf(untransformed_feature[j].attributes.pid) < 0){


A user selected popup
A user selected popup




//this is the popup listner

//now to start stripping out to what we need
if (columnName == 'Reference')

ActiveRef = ActiveRef.replace('Lk ','');
var ActiveRefSpilt = ActiveRef.split(":");
activeChapter = ActiveRefSpilt[0];
activeVerse = ActiveRefSpilt[1];

var dataString = 'pid='+personNameChoice;



//from the sigma.js gephi instance



tBibPersonConnections(data2, tBibPeoplelayer);


