A 3D graph representing the network of coappearances of characters in Victor Hugo's novel Les Miserables

We define our graph as an igraph.Graph object. Python igraph is a library for high-performance graph generation and analysis.

In [1]:
import igraph as ig

Read graph data from a json file:

In [2]:
import json

data = []
with open('miserables.json') as f: 
    for line in f:
        data.append(json.loads(line))        
In [3]:
data=data[0]
data
Out[3]:
{u'links': [{u'source': 1, u'target': 0, u'value': 1},
  {u'source': 2, u'target': 0, u'value': 8},
  {u'source': 3, u'target': 0, u'value': 10},
  {u'source': 3, u'target': 2, u'value': 6},
  {u'source': 4, u'target': 0, u'value': 1},
  {u'source': 5, u'target': 0, u'value': 1},
  {u'source': 6, u'target': 0, u'value': 1},
  {u'source': 7, u'target': 0, u'value': 1},
  {u'source': 8, u'target': 0, u'value': 2},
  {u'source': 9, u'target': 0, u'value': 1},
  {u'source': 11, u'target': 10, u'value': 1},
  {u'source': 11, u'target': 3, u'value': 3},
  {u'source': 11, u'target': 2, u'value': 3},
  {u'source': 11, u'target': 0, u'value': 5},
  {u'source': 12, u'target': 11, u'value': 1},
  {u'source': 13, u'target': 11, u'value': 1},
  {u'source': 14, u'target': 11, u'value': 1},
  {u'source': 15, u'target': 11, u'value': 1},
  {u'source': 17, u'target': 16, u'value': 4},
  {u'source': 18, u'target': 16, u'value': 4},
  {u'source': 18, u'target': 17, u'value': 4},
  {u'source': 19, u'target': 16, u'value': 4},
  {u'source': 19, u'target': 17, u'value': 4},
  {u'source': 19, u'target': 18, u'value': 4},
  {u'source': 20, u'target': 16, u'value': 3},
  {u'source': 20, u'target': 17, u'value': 3},
  {u'source': 20, u'target': 18, u'value': 3},
  {u'source': 20, u'target': 19, u'value': 4},
  {u'source': 21, u'target': 16, u'value': 3},
  {u'source': 21, u'target': 17, u'value': 3},
  {u'source': 21, u'target': 18, u'value': 3},
  {u'source': 21, u'target': 19, u'value': 3},
  {u'source': 21, u'target': 20, u'value': 5},
  {u'source': 22, u'target': 16, u'value': 3},
  {u'source': 22, u'target': 17, u'value': 3},
  {u'source': 22, u'target': 18, u'value': 3},
  {u'source': 22, u'target': 19, u'value': 3},
  {u'source': 22, u'target': 20, u'value': 4},
  {u'source': 22, u'target': 21, u'value': 4},
  {u'source': 23, u'target': 16, u'value': 3},
  {u'source': 23, u'target': 17, u'value': 3},
  {u'source': 23, u'target': 18, u'value': 3},
  {u'source': 23, u'target': 19, u'value': 3},
  {u'source': 23, u'target': 20, u'value': 4},
  {u'source': 23, u'target': 21, u'value': 4},
  {u'source': 23, u'target': 22, u'value': 4},
  {u'source': 23, u'target': 12, u'value': 2},
  {u'source': 23, u'target': 11, u'value': 9},
  {u'source': 24, u'target': 23, u'value': 2},
  {u'source': 24, u'target': 11, u'value': 7},
  {u'source': 25, u'target': 24, u'value': 13},
  {u'source': 25, u'target': 23, u'value': 1},
  {u'source': 25, u'target': 11, u'value': 12},
  {u'source': 26, u'target': 24, u'value': 4},
  {u'source': 26, u'target': 11, u'value': 31},
  {u'source': 26, u'target': 16, u'value': 1},
  {u'source': 26, u'target': 25, u'value': 1},
  {u'source': 27, u'target': 11, u'value': 17},
  {u'source': 27, u'target': 23, u'value': 5},
  {u'source': 27, u'target': 25, u'value': 5},
  {u'source': 27, u'target': 24, u'value': 1},
  {u'source': 27, u'target': 26, u'value': 1},
  {u'source': 28, u'target': 11, u'value': 8},
  {u'source': 28, u'target': 27, u'value': 1},
  {u'source': 29, u'target': 23, u'value': 1},
  {u'source': 29, u'target': 27, u'value': 1},
  {u'source': 29, u'target': 11, u'value': 2},
  {u'source': 30, u'target': 23, u'value': 1},
  {u'source': 31, u'target': 30, u'value': 2},
  {u'source': 31, u'target': 11, u'value': 3},
  {u'source': 31, u'target': 23, u'value': 2},
  {u'source': 31, u'target': 27, u'value': 1},
  {u'source': 32, u'target': 11, u'value': 1},
  {u'source': 33, u'target': 11, u'value': 2},
  {u'source': 33, u'target': 27, u'value': 1},
  {u'source': 34, u'target': 11, u'value': 3},
  {u'source': 34, u'target': 29, u'value': 2},
  {u'source': 35, u'target': 11, u'value': 3},
  {u'source': 35, u'target': 34, u'value': 3},
  {u'source': 35, u'target': 29, u'value': 2},
  {u'source': 36, u'target': 34, u'value': 2},
  {u'source': 36, u'target': 35, u'value': 2},
  {u'source': 36, u'target': 11, u'value': 2},
  {u'source': 36, u'target': 29, u'value': 1},
  {u'source': 37, u'target': 34, u'value': 2},
  {u'source': 37, u'target': 35, u'value': 2},
  {u'source': 37, u'target': 36, u'value': 2},
  {u'source': 37, u'target': 11, u'value': 2},
  {u'source': 37, u'target': 29, u'value': 1},
  {u'source': 38, u'target': 34, u'value': 2},
  {u'source': 38, u'target': 35, u'value': 2},
  {u'source': 38, u'target': 36, u'value': 2},
  {u'source': 38, u'target': 37, u'value': 2},
  {u'source': 38, u'target': 11, u'value': 2},
  {u'source': 38, u'target': 29, u'value': 1},
  {u'source': 39, u'target': 25, u'value': 1},
  {u'source': 40, u'target': 25, u'value': 1},
  {u'source': 41, u'target': 24, u'value': 2},
  {u'source': 41, u'target': 25, u'value': 3},
  {u'source': 42, u'target': 41, u'value': 2},
  {u'source': 42, u'target': 25, u'value': 2},
  {u'source': 42, u'target': 24, u'value': 1},
  {u'source': 43, u'target': 11, u'value': 3},
  {u'source': 43, u'target': 26, u'value': 1},
  {u'source': 43, u'target': 27, u'value': 1},
  {u'source': 44, u'target': 28, u'value': 3},
  {u'source': 44, u'target': 11, u'value': 1},
  {u'source': 45, u'target': 28, u'value': 2},
  {u'source': 47, u'target': 46, u'value': 1},
  {u'source': 48, u'target': 47, u'value': 2},
  {u'source': 48, u'target': 25, u'value': 1},
  {u'source': 48, u'target': 27, u'value': 1},
  {u'source': 48, u'target': 11, u'value': 1},
  {u'source': 49, u'target': 26, u'value': 3},
  {u'source': 49, u'target': 11, u'value': 2},
  {u'source': 50, u'target': 49, u'value': 1},
  {u'source': 50, u'target': 24, u'value': 1},
  {u'source': 51, u'target': 49, u'value': 9},
  {u'source': 51, u'target': 26, u'value': 2},
  {u'source': 51, u'target': 11, u'value': 2},
  {u'source': 52, u'target': 51, u'value': 1},
  {u'source': 52, u'target': 39, u'value': 1},
  {u'source': 53, u'target': 51, u'value': 1},
  {u'source': 54, u'target': 51, u'value': 2},
  {u'source': 54, u'target': 49, u'value': 1},
  {u'source': 54, u'target': 26, u'value': 1},
  {u'source': 55, u'target': 51, u'value': 6},
  {u'source': 55, u'target': 49, u'value': 12},
  {u'source': 55, u'target': 39, u'value': 1},
  {u'source': 55, u'target': 54, u'value': 1},
  {u'source': 55, u'target': 26, u'value': 21},
  {u'source': 55, u'target': 11, u'value': 19},
  {u'source': 55, u'target': 16, u'value': 1},
  {u'source': 55, u'target': 25, u'value': 2},
  {u'source': 55, u'target': 41, u'value': 5},
  {u'source': 55, u'target': 48, u'value': 4},
  {u'source': 56, u'target': 49, u'value': 1},
  {u'source': 56, u'target': 55, u'value': 1},
  {u'source': 57, u'target': 55, u'value': 1},
  {u'source': 57, u'target': 41, u'value': 1},
  {u'source': 57, u'target': 48, u'value': 1},
  {u'source': 58, u'target': 55, u'value': 7},
  {u'source': 58, u'target': 48, u'value': 7},
  {u'source': 58, u'target': 27, u'value': 6},
  {u'source': 58, u'target': 57, u'value': 1},
  {u'source': 58, u'target': 11, u'value': 4},
  {u'source': 59, u'target': 58, u'value': 15},
  {u'source': 59, u'target': 55, u'value': 5},
  {u'source': 59, u'target': 48, u'value': 6},
  {u'source': 59, u'target': 57, u'value': 2},
  {u'source': 60, u'target': 48, u'value': 1},
  {u'source': 60, u'target': 58, u'value': 4},
  {u'source': 60, u'target': 59, u'value': 2},
  {u'source': 61, u'target': 48, u'value': 2},
  {u'source': 61, u'target': 58, u'value': 6},
  {u'source': 61, u'target': 60, u'value': 2},
  {u'source': 61, u'target': 59, u'value': 5},
  {u'source': 61, u'target': 57, u'value': 1},
  {u'source': 61, u'target': 55, u'value': 1},
  {u'source': 62, u'target': 55, u'value': 9},
  {u'source': 62, u'target': 58, u'value': 17},
  {u'source': 62, u'target': 59, u'value': 13},
  {u'source': 62, u'target': 48, u'value': 7},
  {u'source': 62, u'target': 57, u'value': 2},
  {u'source': 62, u'target': 41, u'value': 1},
  {u'source': 62, u'target': 61, u'value': 6},
  {u'source': 62, u'target': 60, u'value': 3},
  {u'source': 63, u'target': 59, u'value': 5},
  {u'source': 63, u'target': 48, u'value': 5},
  {u'source': 63, u'target': 62, u'value': 6},
  {u'source': 63, u'target': 57, u'value': 2},
  {u'source': 63, u'target': 58, u'value': 4},
  {u'source': 63, u'target': 61, u'value': 3},
  {u'source': 63, u'target': 60, u'value': 2},
  {u'source': 63, u'target': 55, u'value': 1},
  {u'source': 64, u'target': 55, u'value': 5},
  {u'source': 64, u'target': 62, u'value': 12},
  {u'source': 64, u'target': 48, u'value': 5},
  {u'source': 64, u'target': 63, u'value': 4},
  {u'source': 64, u'target': 58, u'value': 10},
  {u'source': 64, u'target': 61, u'value': 6},
  {u'source': 64, u'target': 60, u'value': 2},
  {u'source': 64, u'target': 59, u'value': 9},
  {u'source': 64, u'target': 57, u'value': 1},
  {u'source': 64, u'target': 11, u'value': 1},
  {u'source': 65, u'target': 63, u'value': 5},
  {u'source': 65, u'target': 64, u'value': 7},
  {u'source': 65, u'target': 48, u'value': 3},
  {u'source': 65, u'target': 62, u'value': 5},
  {u'source': 65, u'target': 58, u'value': 5},
  {u'source': 65, u'target': 61, u'value': 5},
  {u'source': 65, u'target': 60, u'value': 2},
  {u'source': 65, u'target': 59, u'value': 5},
  {u'source': 65, u'target': 57, u'value': 1},
  {u'source': 65, u'target': 55, u'value': 2},
  {u'source': 66, u'target': 64, u'value': 3},
  {u'source': 66, u'target': 58, u'value': 3},
  {u'source': 66, u'target': 59, u'value': 1},
  {u'source': 66, u'target': 62, u'value': 2},
  {u'source': 66, u'target': 65, u'value': 2},
  {u'source': 66, u'target': 48, u'value': 1},
  {u'source': 66, u'target': 63, u'value': 1},
  {u'source': 66, u'target': 61, u'value': 1},
  {u'source': 66, u'target': 60, u'value': 1},
  {u'source': 67, u'target': 57, u'value': 3},
  {u'source': 68, u'target': 25, u'value': 5},
  {u'source': 68, u'target': 11, u'value': 1},
  {u'source': 68, u'target': 24, u'value': 1},
  {u'source': 68, u'target': 27, u'value': 1},
  {u'source': 68, u'target': 48, u'value': 1},
  {u'source': 68, u'target': 41, u'value': 1},
  {u'source': 69, u'target': 25, u'value': 6},
  {u'source': 69, u'target': 68, u'value': 6},
  {u'source': 69, u'target': 11, u'value': 1},
  {u'source': 69, u'target': 24, u'value': 1},
  {u'source': 69, u'target': 27, u'value': 2},
  {u'source': 69, u'target': 48, u'value': 1},
  {u'source': 69, u'target': 41, u'value': 1},
  {u'source': 70, u'target': 25, u'value': 4},
  {u'source': 70, u'target': 69, u'value': 4},
  {u'source': 70, u'target': 68, u'value': 4},
  {u'source': 70, u'target': 11, u'value': 1},
  {u'source': 70, u'target': 24, u'value': 1},
  {u'source': 70, u'target': 27, u'value': 1},
  {u'source': 70, u'target': 41, u'value': 1},
  {u'source': 70, u'target': 58, u'value': 1},
  {u'source': 71, u'target': 27, u'value': 1},
  {u'source': 71, u'target': 69, u'value': 2},
  {u'source': 71, u'target': 68, u'value': 2},
  {u'source': 71, u'target': 70, u'value': 2},
  {u'source': 71, u'target': 11, u'value': 1},
  {u'source': 71, u'target': 48, u'value': 1},
  {u'source': 71, u'target': 41, u'value': 1},
  {u'source': 71, u'target': 25, u'value': 1},
  {u'source': 72, u'target': 26, u'value': 2},
  {u'source': 72, u'target': 27, u'value': 1},
  {u'source': 72, u'target': 11, u'value': 1},
  {u'source': 73, u'target': 48, u'value': 2},
  {u'source': 74, u'target': 48, u'value': 2},
  {u'source': 74, u'target': 73, u'value': 3},
  {u'source': 75, u'target': 69, u'value': 3},
  {u'source': 75, u'target': 68, u'value': 3},
  {u'source': 75, u'target': 25, u'value': 3},
  {u'source': 75, u'target': 48, u'value': 1},
  {u'source': 75, u'target': 41, u'value': 1},
  {u'source': 75, u'target': 70, u'value': 1},
  {u'source': 75, u'target': 71, u'value': 1},
  {u'source': 76, u'target': 64, u'value': 1},
  {u'source': 76, u'target': 65, u'value': 1},
  {u'source': 76, u'target': 66, u'value': 1},
  {u'source': 76, u'target': 63, u'value': 1},
  {u'source': 76, u'target': 62, u'value': 1},
  {u'source': 76, u'target': 48, u'value': 1},
  {u'source': 76, u'target': 58, u'value': 1}],
 u'nodes': [{u'group': 1, u'name': u'Myriel'},
  {u'group': 1, u'name': u'Napoleon'},
  {u'group': 1, u'name': u'Mlle.Baptistine'},
  {u'group': 1, u'name': u'Mme.Magloire'},
  {u'group': 1, u'name': u'CountessdeLo'},
  {u'group': 1, u'name': u'Geborand'},
  {u'group': 1, u'name': u'Champtercier'},
  {u'group': 1, u'name': u'Cravatte'},
  {u'group': 1, u'name': u'Count'},
  {u'group': 1, u'name': u'OldMan'},
  {u'group': 2, u'name': u'Labarre'},
  {u'group': 2, u'name': u'Valjean'},
  {u'group': 3, u'name': u'Marguerite'},
  {u'group': 2, u'name': u'Mme.deR'},
  {u'group': 2, u'name': u'Isabeau'},
  {u'group': 2, u'name': u'Gervais'},
  {u'group': 3, u'name': u'Tholomyes'},
  {u'group': 3, u'name': u'Listolier'},
  {u'group': 3, u'name': u'Fameuil'},
  {u'group': 3, u'name': u'Blacheville'},
  {u'group': 3, u'name': u'Favourite'},
  {u'group': 3, u'name': u'Dahlia'},
  {u'group': 3, u'name': u'Zephine'},
  {u'group': 3, u'name': u'Fantine'},
  {u'group': 4, u'name': u'Mme.Thenardier'},
  {u'group': 4, u'name': u'Thenardier'},
  {u'group': 5, u'name': u'Cosette'},
  {u'group': 4, u'name': u'Javert'},
  {u'group': 0, u'name': u'Fauchelevent'},
  {u'group': 2, u'name': u'Bamatabois'},
  {u'group': 3, u'name': u'Perpetue'},
  {u'group': 2, u'name': u'Simplice'},
  {u'group': 2, u'name': u'Scaufflaire'},
  {u'group': 2, u'name': u'Woman1'},
  {u'group': 2, u'name': u'Judge'},
  {u'group': 2, u'name': u'Champmathieu'},
  {u'group': 2, u'name': u'Brevet'},
  {u'group': 2, u'name': u'Chenildieu'},
  {u'group': 2, u'name': u'Cochepaille'},
  {u'group': 4, u'name': u'Pontmercy'},
  {u'group': 6, u'name': u'Boulatruelle'},
  {u'group': 4, u'name': u'Eponine'},
  {u'group': 4, u'name': u'Anzelma'},
  {u'group': 5, u'name': u'Woman2'},
  {u'group': 0, u'name': u'MotherInnocent'},
  {u'group': 0, u'name': u'Gribier'},
  {u'group': 7, u'name': u'Jondrette'},
  {u'group': 7, u'name': u'Mme.Burgon'},
  {u'group': 8, u'name': u'Gavroche'},
  {u'group': 5, u'name': u'Gillenormand'},
  {u'group': 5, u'name': u'Magnon'},
  {u'group': 5, u'name': u'Mlle.Gillenormand'},
  {u'group': 5, u'name': u'Mme.Pontmercy'},
  {u'group': 5, u'name': u'Mlle.Vaubois'},
  {u'group': 5, u'name': u'Lt.Gillenormand'},
  {u'group': 8, u'name': u'Marius'},
  {u'group': 5, u'name': u'BaronessT'},
  {u'group': 8, u'name': u'Mabeuf'},
  {u'group': 8, u'name': u'Enjolras'},
  {u'group': 8, u'name': u'Combeferre'},
  {u'group': 8, u'name': u'Prouvaire'},
  {u'group': 8, u'name': u'Feuilly'},
  {u'group': 8, u'name': u'Courfeyrac'},
  {u'group': 8, u'name': u'Bahorel'},
  {u'group': 8, u'name': u'Bossuet'},
  {u'group': 8, u'name': u'Joly'},
  {u'group': 8, u'name': u'Grantaire'},
  {u'group': 9, u'name': u'MotherPlutarch'},
  {u'group': 4, u'name': u'Gueulemer'},
  {u'group': 4, u'name': u'Babet'},
  {u'group': 4, u'name': u'Claquesous'},
  {u'group': 4, u'name': u'Montparnasse'},
  {u'group': 5, u'name': u'Toussaint'},
  {u'group': 10, u'name': u'Child1'},
  {u'group': 10, u'name': u'Child2'},
  {u'group': 4, u'name': u'Brujon'},
  {u'group': 8, u'name': u'Mme.Hucheloup'}]}
In [4]:
print data.keys()
[u'nodes', u'links']

Get the number of nodes:

In [5]:
N=len(data['nodes'])
N
Out[5]:
77

Define the list of edges:

In [6]:
L=len(data['links'])
Edges=[(data['links'][k]['source'], data['links'][k]['target']) for k in range(L)]

Define the Graph object from Edges:

In [7]:
G=ig.Graph(Edges, directed=False)

Extract the node attributes, 'group', and 'name':

In [8]:
data['nodes'][0]
Out[8]:
{u'group': 1, u'name': u'Myriel'}
In [9]:
labels=[]
group=[]
for node in data['nodes']:
    labels.append(node['name'])
    group.append(node['group'])

Get the node positions, set by the Kamada-Kawai layout for 3D graphs:

In [28]:
layt=G.layout('kk', dim=3) 

layt is a list of three elements lists (the coordinates of nodes):

In [29]:
layt[5]
Out[29]:
[-2.165298835959627, -3.688710574002034, -1.6020681541017792]

Set data for the Plotly plot of the graph:

In [30]:
Xn=[layt[k][0] for k in range(N)]# x-coordinates of nodes
Yn=[layt[k][1] for k in range(N)]# y-coordinates
Zn=[layt[k][2] for k in range(N)]# z-coordinates
Xe=[]
Ye=[]
Ze=[]
for e in Edges:
    Xe+=[layt[e[0]][0],layt[e[1]][0], None]# x-coordinates of edge ends
    Ye+=[layt[e[0]][1],layt[e[1]][1], None]  
    Ze+=[layt[e[0]][2],layt[e[1]][2], None]  
In [31]:
import plotly.plotly as py
from plotly.graph_objs import *
In [32]:
trace1=Scatter3d(x=Xe,
               y=Ye,
               z=Ze,
               mode='lines',
               line=Line(color='rgb(125,125,125)', width=1),
               hoverinfo='none'
               )
trace2=Scatter3d(x=Xn,
               y=Yn,
               z=Zn,  
               mode='markers',
               name='actors',
               marker=Marker(symbol='dot',
                             size=6, 
                             color=group, 
                             colorscale='Viridis',
                             line=Line(color='rgb(50,50,50)', width=0.5)
                             ),
               text=labels,
               hoverinfo='text'
               )
In [33]:
axis=dict(showbackground=False,
          showline=False,  
          zeroline=False,
          showgrid=False,
          showticklabels=False,
          title='' 
          )
In [34]:
layout = Layout(
         title="Network of coappearances of characters in Victor Hugo's novel<br> Les Miserables (3D visualization)", 
         width=1000,
         height=1000,
         showlegend=False,
         scene=Scene(  
         xaxis=XAxis(axis),
         yaxis=YAxis(axis), 
         zaxis=ZAxis(axis), 
        ),
     margin=Margin(
        t=100
    ),
    hovermode='closest',
    annotations=Annotations([
           Annotation(
           showarrow=False, 
            text="Data source: <a href='http://bost.ocks.org/mike/miserables/miserables.json'>[1]</a>",
            xref='paper',     
            yref='paper',     
            x=0,  
            y=0.1,  
            xanchor='left',   
            yanchor='bottom',  
            font=Font(
            size=14 
            )     
            )
        ]),    )
In [35]:
data=Data([trace1, trace2])
py.sign_in('empet', 'jkxft90od0')
fig=Figure(data=data, layout=layout)

py.plot(fig, filename='Les-Miserables')
Out[35]:
u'https://plot.ly/~empet/9059'
Network of coappearances of characters in Victor Hugo's novel<br> Les Miserables (3D visualization)
In [36]:
from IPython.core.display import HTML
def  css_styling():
    styles = open("./custom.css", "r").read()
    return HTML(styles)
css_styling()
Out[36]: