import ipycytoscape
import json
import ipywidgets
What is a graph? Mathematical structures used to model pairwise relations between objects. Examples:
Nomenclature: The basic nomenclature consists of (based on an example of the train rail system):
Let's use ipycytoscape to dive into graphs.
One way to create an ipycytoscape graph is using a JSON input as follows: (We will be following the train-rail example)
Later on it might become clear that other ways to pass data to ipycytoscape are not only possible but probably desirable in many circumstances. For the moment we intend to create a really small graph to get up and running understanding graphs and ipycytoscape.
Moreover be aware that normally the data itself is in an external separate file, but if we would proceed reading the data from an external file we would not be able to see it in the notebook and it would not serve the teaching purpose.
# we create the graph that is an object of ipycytoscape
ipycytoscape_obj = ipycytoscape.CytoscapeWidget()
railnet= '''{
"nodes": [
{"data": { "id": "BER" }},
{"data": { "id": "MUN"}},
{"data": { "id": "FRA"}},
{"data": { "id": "HAM"}}
],
"edges": [
{"data": { "source": "BER", "target": "MUN" }},
{"data": { "source": "MUN", "target": "FRA" }},
{"data": { "source": "FRA", "target": "BER" }},
{"data": { "source": "BER", "target": "HAM" }}
]
}'''
print(type(railnet))
railnetJSON = json.loads(railnet)
<class 'str'>
Let's see our mini German rail system that joins the three main German cities BERlin, MUNich and FRAnkfurt
ipycytoscape_obj.graph.add_graph_from_json(railnetJSON)
ipycytoscape_obj
CytoscapeWidget(cytoscape_layout={'name': 'cola'}, cytoscape_style=[{'selector': 'node', 'css': {'background-c…
Some observations:
Lets try to solve those problems.
IMPORTANT NOTE: Multiple graphs are being created so it's possible for you to compare the results.
How would have been the JSON file if we dont want directionality? Compare the two graphs below, the first example is using directionality and the second isn't.
ipycytoscape_obj2 = ipycytoscape.CytoscapeWidget()
ipycytoscape_obj2.graph.add_graph_from_json(railnetJSON, directed=True) # I am telling I dont want directions
ipycytoscape_obj2
CytoscapeWidget(cytoscape_layout={'name': 'cola'}, cytoscape_style=[{'selector': 'node', 'css': {'background-c…
ipycytoscape_obj3 = ipycytoscape.CytoscapeWidget()
ipycytoscape_obj3.graph.add_graph_from_json(railnetJSON, directed=False) # I am telling I dont want directions
ipycytoscape_obj3
CytoscapeWidget(cytoscape_layout={'name': 'cola'}, cytoscape_style=[{'selector': 'node', 'css': {'background-c…
Lets say we want to see the names of the stations on top of the nodes. Those names are called labels. It's necessary to add the corresponding labels to all the nodes.
railnet= '''{
"nodes": [
{"data": { "id": "BER", "label":"HBf BER"}},
{"data": { "id": "MUN", "label":"HBf MUN"}},
{"data": { "id": "FRA", "label":"HBf FRA"}},
{"data": { "id": "HAM", "label":"HBf HAM"}}
],
"edges": [
{"data": { "source": "BER", "target": "MUN" }},
{"data": { "source": "MUN", "target": "FRA" }},
{"data": { "source": "FRA", "target": "BER" }},
{"data": { "source": "BER", "target": "HAM" }}
]
}'''
railnetJSON = json.loads(railnet)
ipycytoscape_obj4 = ipycytoscape.CytoscapeWidget()
ipycytoscape_obj4.graph.add_graph_from_json(railnetJSON, directed=False) # I am telling I dont want directions
ipycytoscape_obj4
CytoscapeWidget(cytoscape_layout={'name': 'cola'}, cytoscape_style=[{'selector': 'node', 'css': {'background-c…
mmmmmm, as you can see we did not achieve our objective of adding the name of the stations. The stations being the nodes of the graph. Be aware that in all examples of this notebook stations and nodes might be used interchangeably "node" being the graph technical term and rail station his representation of it in real life. (btw, HBf states for central main station in German). In order to affect and change the appearance of the graph we not only have to change the graph's data but also its style.
my_style = [
{'selector': 'node','style': {
'font-family': 'helvetica',
'font-size': '20px',
'label': 'data(label)'}},
]
What are we doing here?
We're writing the style of each one of the labels. With data(label) we specify that the property label of the attribute data of our graph should be printed with the font-size 20 and family helvetica.
Lets create a new graph with the labels
Here we're using CSS nomenclature.
You select one element and pass a style to that element.
ipycytoscape_obj5 = ipycytoscape.CytoscapeWidget()
ipycytoscape_obj5.graph.add_graph_from_json(railnetJSON, directed=False) # I am telling I dont want directions
ipycytoscape_obj5.set_style(my_style)
ipycytoscape_obj5
CytoscapeWidget(cytoscape_layout={'name': 'cola'}, cytoscape_style=[{'selector': 'node', 'style': {'font-famil…
Lets just play around and change the size of the font and the type of font. Now we want to change the style of an existing graph, namely the number 5.
ipycytoscape_obj6 = ipycytoscape.CytoscapeWidget()
ipycytoscape_obj6.graph.add_graph_from_json(railnetJSON, directed=False) # We're specifying that the graph should be undirected
ipycytoscape_obj6.set_style(my_style)
ipycytoscape_obj6 # which is the same as graph 5, but in the next cell we will try to change it
CytoscapeWidget(cytoscape_layout={'name': 'cola'}, cytoscape_style=[{'selector': 'node', 'style': {'font-famil…
my_style = [
{'selector': 'node','style': {
'font-family': 'arial',
'font-size': '10px',
'label': 'data(label)',
'background-color': 'red'}},
]
ipycytoscape_obj6 = ipycytoscape.CytoscapeWidget()
ipycytoscape_obj6.graph.add_graph_from_json(railnetJSON, directed=False) # We're specifying that the graph should be undirected
ipycytoscape_obj6.set_style(my_style)
ipycytoscape_obj6 # which is the same
CytoscapeWidget(cytoscape_layout={'name': 'cola'}, cytoscape_style=[{'selector': 'node', 'style': {'font-famil…
As you can see when running the previous cell the appearance of the graph changed: the font-family
is different and the font-size
matches the node size. And the circles are now red.
The first question that comes to mind is if one can change the attributes of only one node.
Let's see.
my_style = [
{'selector': 'node','style': {
'font-family': 'arial',
'font-size': '10px',
'label': 'data(label)',
'background-color': 'red'}},
{'selector': 'node[id = "BER"]','style': {
'font-family': 'arial',
'font-size': '10px',
'label': 'data(label)',
'background-color': 'green'}}
]
ipycytoscape_obj7 = ipycytoscape.CytoscapeWidget()
ipycytoscape_obj7.graph.add_graph_from_json(railnetJSON, directed=False) # I am telling I dont want directions
ipycytoscape_obj7.set_style(my_style)
ipycytoscape_obj7.set_style(my_style)
ipycytoscape_obj7
CytoscapeWidget(cytoscape_layout={'name': 'cola'}, cytoscape_style=[{'selector': 'node', 'style': {'font-famil…
We gave a particular style to ALL the nodes ('selector': 'node') and afterwards we gave the color green just to the berlin central station node: ('node[id = "BER"]')
As you can see the way to refer to the node is 'node[id = "BER"]'.
What else can we change in the apperance?
There is quite a few other attributes of the graph that we can change.
Let's assume that the train connections between the cities are as follows:
We can also add information to the edges. It is necessary to add labels to the edges and also to identify every edge.
railnet= '''{
"nodes": [
{"data": { "id": "BER", "label":"HBf BER"}},
{"data": { "id": "MUN", "label":"HBf MUN"}},
{"data": { "id": "FRA", "label":"HBf FRA"}},
{"data": { "id": "HAM", "label":"HBf HAM"}}
],
"edges": [
{"data": { "id": "line1", "source": "BER", "target": "MUN","label":"200km/h"}},
{"data": { "id": "line2", "source": "MUN", "target": "FRA","label":"200km/h"}},
{"data": { "id": "line3", "source": "FRA", "target": "BER","label":"250km/h" }},
{"data": { "id": "line4", "source": "BER", "target": "HAM","label":"300km/h" }}
]
}'''
my_style = [
{'selector': 'node','style': {
'font-family': 'arial',
'font-size': '10px',
'label': 'data(label)',
'background-color': 'red'}},
{'selector': 'node[id = "BER"]','style': {
'font-family': 'arial',
'font-size': '10px',
'label': 'data(label)',
'background-color': 'green'}},
{'selector': 'edge[id = "line1"]','style': {
'font-family': 'arial',
'font-size': '10px',
'label': 'data(label)',}},
{'selector': 'edge[id = "line2"]','style': {
'font-family': 'arial',
'font-size': '10px',
'label': 'data(label)',}},
{'selector': 'edge[id = "line3"]','style': {
'font-family': 'arial',
'font-size': '10px',
'label': 'data(label)',}},
{'selector': 'edge[id = "line4"]','style': {
'font-family': 'arial',
'font-size': '10px',
'label': 'data(label)',}}
]
railnetJSON = json.loads(railnet)
ipycytoscape_obj8 = ipycytoscape.CytoscapeWidget()
ipycytoscape_obj8.graph.add_graph_from_json(railnetJSON, directed=True) # We're specifying that the graph should be undirected
ipycytoscape_obj8.set_style(my_style)
ipycytoscape_obj8.set_style(my_style)
ipycytoscape_obj8
CytoscapeWidget(cytoscape_layout={'name': 'cola'}, cytoscape_style=[{'selector': 'node', 'style': {'font-famil…
Imagine we want to divide the rail net into two parts.
And that we also want to paint these nodes in one go with a particular color. Meaning that we don't want to paint node by node but "paint all the west cities blue and east cities green" at once.
We can use classes for that.
We add a class to each node.
Let's revisit how to do this using an example from the very beginning.
railnet= '''{
"nodes": [
{"data": { "id": "BER", "label":"HBf BER"}, "classes":"east"},
{"data": { "id": "MUN", "label":"HBf MUN"}, "classes":"west"},
{"data": { "id": "FRA", "label":"HBf FRA"}, "classes":"west"},
{"data": { "id": "HAM", "label":"HBf HAM"}, "classes":"west"},
{"data": { "id": "LEP", "label":"HBf LEP"}, "classes":"east"}
],
"edges": [
{"data": { "id": "line1", "source": "BER", "target": "MUN","label":"200km/h"}},
{"data": { "id": "line2", "source": "MUN", "target": "FRA","label":"200km/h"}},
{"data": { "id": "line3", "source": "FRA", "target": "BER","label":"250km/h" }},
{"data": { "id": "line4", "source": "BER", "target": "HAM","label":"300km/h" }},
{"data": { "id": "line5", "source": "BER", "target": "LEP","label":"300km/h" }}
]
}'''
my_style = [
{'selector': 'node','style': {
'font-family': 'arial',
'font-size': '10px',
'label': 'data(label)',}},
{'selector': 'node.east','style': {
'background-color': 'yellow'}},
{'selector': 'node.west','style': {
'background-color': 'blue'}},
{'selector': 'node[id = "BER"]','style': {
'font-family': 'arial',
'font-size': '10px',
'label': 'data(label)',
'background-color': 'green'}},
{'selector': 'edge[id = "line1"]','style': {
'font-family': 'arial',
'font-size': '10px',
'label': 'data(label)'}},
{'selector': 'edge[id = "line2"]','style': {
'font-family': 'arial',
'font-size': '10px',
'label': 'data(label)'}},
{'selector': 'edge[id = "line3"]','style': {
'font-family': 'arial',
'font-size': '10px',
'label': 'data(label)'}},
{'selector': 'edge[id = "line4"]','style': {
'font-family': 'arial',
'font-size': '10px',
'label': 'data(label)'}},
{'selector': 'edge[id = "line5"]','style': {
'font-family': 'arial',
'font-size': '10px',
'label': 'data(label)'}}
]
railnetJSON = json.loads(railnet)
ipycytoscape_obj9 = ipycytoscape.CytoscapeWidget()
ipycytoscape_obj9.graph.add_graph_from_json(railnetJSON, directed=True) # We're specifying that the graph should be undirected
ipycytoscape_obj9.set_style(my_style)
ipycytoscape_obj9
CytoscapeWidget(cytoscape_layout={'name': 'cola'}, cytoscape_style=[{'selector': 'node', 'style': {'font-famil…
What happended?
With
{'selector': 'node.east]',
'style': {'background-color': 'yellow'}},
We painted all east German cities yellow. BER as well, but BER color is overwritten by the green color of the BER node.
There is still a lot to uncover from ipycytoscape's functionalities:
stay tuned.