Advanced use¶
This Jupyter notebook demonstrates some advanced features of the Python package gravis. The .ipynb file can be found here.
[1]:
import os
import gravis as gv
import networkx as nx
Define a graph in all possible ways¶
The following gives an overview of all ways that are available for defining a graph that is recognized by gravis.
1) String in gJGF¶
[2]:
graph1 = """
{
"graph": {
"metadata": {"node_color": "blue"},
"nodes": [{"id": "0"}, {"id": "1"}, {"id": "2"}],
"edges": [{"source": 0, "target": 1}, {"source": 1, "target": 2}, {"source": 2, "target": 0}]
}
}
"""
gv.d3(graph1, graph_height=100)
[2]:
2) Dictionary in gJGF¶
The Python dictionary needs to be equivalent to a JSON string in gJGF. The following demonstrates two different ways a dictionary can be created in Python.
[3]:
graph2 = {
'graph': {
'metadata': {'node_color': 'orange'},
'nodes': [{'id': '0'}, {'id': '1'}, {'id': '2'}],
'edges': [{'source': 0, 'target': 1}, {'source': 1, 'target': 2}, {'source': 2, 'target': 0}],
}
}
gv.d3(graph2, graph_height=100)
[3]:
[4]:
graph3 = dict(graph=dict(
metadata=dict(node_color='red'),
nodes=[dict(id=0), dict(id=1), dict(id=2)],
edges=[dict(source=0, target=1), dict(source=1, target=2), dict(source=2, target=0)],
))
gv.d3(graph3, graph_height=100)
[4]:
3) Text file in gJGF¶
The text file needs to contain a string in gJGF.
[5]:
graph4 = os.path.join('data', 'graph_basic.gjgf')
gv.d3(graph4, graph_height=100)
[5]:
4) Graph object from external library¶
The following demonstrates two ways in which a graph can be defined in NetworkX. Other libraries are also supported an shown in other examples.
[6]:
graph5 = nx.Graph()
graph5.graph['node_color'] = 'green'
graph5.add_node(0)
graph5.add_node(1)
graph5.add_node(2)
graph5.add_edge(0, 1)
graph5.add_edge(1, 2)
graph5.add_edge(2, 0)
gv.d3(graph5, graph_height=100)
[6]:
[7]:
graph6 = nx.Graph()
graph6.graph['node_color'] = 'cyan'
graph6.add_edges_from([(0, 1), (1, 2), (2, 0)])
gv.d3(graph6, graph_height=100)
[7]:
Note that a graph object is internally converted into gJGF with a function that is also available to users
[8]:
dictionary = gv.convert.any_to_gjgf(graph6)
dictionary
[8]:
{'graph': {'nodes': {'0': {}, '1': {}, '2': {}},
'edges': [{'source': '0', 'target': '1'},
{'source': '0', 'target': '2'},
{'source': '1', 'target': '2'}],
'directed': False,
'metadata': {'node_color': 'cyan'}}}
[9]:
import json
json_string = json.dumps(dictionary, indent=2)
print(json_string)
{
"graph": {
"nodes": {
"0": {},
"1": {},
"2": {}
},
"edges": [
{
"source": "0",
"target": "1"
},
{
"source": "0",
"target": "2"
},
{
"source": "1",
"target": "2"
}
],
"directed": false,
"metadata": {
"node_color": "cyan"
}
}
}
5) List of any of the previous¶
Multiple graphs can be visualized in a single output cell by using the display
method of a Figure
object returned by a plotting function.
[10]:
graphs = [graph1, graph2, graph3, graph4, graph5, graph6]
for g in graphs:
print(type(g))
fig = gv.d3(g, graph_height=75)
fig.display(inline=True)
<class 'str'>
<class 'dict'>
<class 'dict'>
<class 'str'>
<class 'networkx.classes.graph.Graph'>
<class 'networkx.classes.graph.Graph'>
Multiple graph can also be made available in a single visualization. The user can switch between them in the Data selection tab.
[11]:
gv.d3(graphs, graph_height=200)
[11]:
Define a graph with all available annotations¶
1) Using a dictionary in gJGF¶
[12]:
graph_gjgf = {
'graph': {
'directed': True,
'metadata': {
'node_label_size': 14,
'node_label_color': 'green',
'edge_label_size': 10,
'edge_label_color': 'blue',
},
'nodes': [
{'id': '0', 'label': 'first node', 'metadata': {
'color': 'red',
'size': 15,
'shape': 'rectangle',
'opacity': 0.7,
'label_color': 'red',
'label_size': 20,
'border_color': 'black',
'border_size': 3,
}},
{'id': '1'},
{'id': '2'},
{'id': '3', 'metadata': {
'color': 'green',
'size': 15,
'shape': 'hexagon',
'opacity': 0.7,
'label_color': 'green',
'label_size': 10,
'border_color': 'blue',
'border_size': 3,
}},
{'id': '4'},
{'id': '5'},
{'id': '6', 'label': 'last node'},
],
'edges': [
{'source': 0, 'target': 1},
{'source': 1, 'target': 2, 'label': 'e2'},
{'source': 2, 'target': 3},
{'source': 3, 'target': 4},
{'source': 4, 'target': 5, 'label': 'e5', 'metadata': {
'color': 'orange',
'label_color': 'gray',
'label_size': 14,
'size': 4.0,
}},
{'source': 5, 'target': 6},
{'source': 6, 'target': 2, 'label': 'e2'},
]
}
}
[13]:
gv.d3(graph_gjgf, graph_height=200,
node_label_data_source='label',
show_edge_label=True, edge_label_data_source='label')
[13]:
2) Using NetworkX¶
[14]:
# Graph and graph attributes
graph = nx.DiGraph()
graph.graph['node_label_size'] = 14
graph.graph['node_label_color'] = 'green'
graph.graph['edge_label_size'] = 10
graph.graph['edge_label_color'] = 'blue'
# Nodes and node attributes
graph.add_node(0, label='first node', color='red', size=15, shape='rectangle', opacity=0.7,
label_color='red', label_size=20, border_color='black', border_size=3)
graph.add_node(3, color='green', size=15, shape='hexagon', opacity=0.7,
label_color='green', label_size=10, border_color='blue', border_size=3)
graph.add_node(6, label='last node')
# Edges and edge attributes
graph.add_edge(0, 1) # add_edge creates nodes if they don't exist yet
graph.add_edge(1, 2, label='e2')
graph.add_edge(2, 3)
graph.add_edge(3, 4)
graph.add_edge(4, 5, label='e5', color='orange', label_color='gray', label_size=14, size=4.0)
graph.add_edge(5, 6)
graph.add_edge(6, 2, label='e7')
[15]:
gv.d3(graph, graph_height=200,
node_label_data_source='label',
show_edge_label=True, edge_label_data_source='label')
[15]:
3) Using a text file in gJGF¶
[16]:
graph = os.path.join('data', 'graph_advanced.gjgf')
gv.d3(graph, graph_height=200, node_label_data_source='label',
show_edge_label=True, edge_label_data_source='label')
[16]:
Define a graph with multi-edges and self loops¶
This graph is a depiction of a state machine. Nodes represent states, edges represent state transitions and edge labels represent inputs that cause the corresponding state transition.
[17]:
graph_gjgf = {
'graph':{
'directed': True,
'metadata': {'node_size': 20},
'nodes': {
's0': {'metadata': {'x': -150, 'y': 20, 'size': 0}},
's1': {'label': 'State 1', 'metadata': {'x': -100, 'y': 20, 'color': 'red', 'label_color': 'red'}},
's2': {'label': 'State 2', 'metadata': {'x': 0, 'y': 20, 'color': 'green', 'label_color': 'green'}},
's3': {'label': 'State 3', 'metadata': {'x': 100, 'y': 20, 'color': 'blue', 'label_color': 'blue'}},
},
'edges': [
{'source': 's0', 'target': 's1'},
{'source': 's1', 'target': 's2', 'label': 'a'},
{'source': 's1', 'target': 's2', 'label': 'b'},
{'source': 's2', 'target': 's2', 'label': 'a'},
{'source': 's2', 'target': 's2', 'label': 'b'},
{'source': 's2', 'target': 's1', 'label': 'c'},
{'source': 's2', 'target': 's1', 'label': 'd'},
{'source': 's2', 'target': 's1', 'label': 'e'},
{'source': 's2', 'target': 's3', 'label': 'f'},
{'source': 's3', 'target': 's3', 'label': 'a'},
{'source': 's3', 'target': 's3', 'label': 'b'},
{'source': 's3', 'target': 's2', 'label': 'f'},
]
}
}
[18]:
gv.d3(
graph_gjgf,
show_node_label=True, node_label_data_source='label',
show_edge_label=True, edge_label_data_source='label', edge_curvature=0.4,
graph_height=250, zoom_factor=1.5)
[18]:
[19]:
# Caution: vis.js is not well suited for drawing self loops and fails to display multi edges!
# Please don't use it for graphs that include such edges, as the depictions can be misleading.
gv.vis(
graph_gjgf,
show_node_label=True, node_label_data_source='label',
show_edge_label=True, edge_label_data_source='label', edge_curvature=0.4,
graph_height=250, zoom_factor=1.5)
[19]:
[20]:
gv.three(
graph_gjgf,
show_node_label=True, node_label_data_source='label',
show_edge_label=True, edge_label_data_source='label', edge_curvature=0.4,
graph_height=250, zoom_factor=1.5)
[20]:
Calculate a fixed layout with external libraries¶
This example uses NetworkX to generate a graph and calculate a layout for it:
barabasi_albert_graph is used to generate a random graph with Barabási–Albert preferential attachment
spring_layout is used to calculate x and y positions for each node
[21]:
# Generate graph
graph = nx.barabasi_albert_graph(n=300, m=1)
# Calculate layout
pos = nx.drawing.layout.spring_layout(graph, scale=600)
# Add coordinates as node annotations that are recognized by gravis
for name, (x, y) in pos.items():
node = graph.nodes[name]
node['x'] = x
node['y'] = y
gv.d3(graph, zoom_factor=0.35, layout_algorithm_active=False)
[21]:
Plot a graph with all available arguments¶
[22]:
graph = nx.barabasi_albert_graph(n=80, m=1)
gv.d3(
data=graph,
graph_height=200,
details_height=100,
show_details=True,
show_details_toggle_button=True,
show_menu=True,
show_menu_toggle_button=True,
show_node=True,
node_size_factor=1.2,
node_size_data_source='size',
use_node_size_normalization=False,
node_size_normalization_min=10.0,
node_size_normalization_max=50.0,
node_drag_fix=True,
node_hover_neighborhood=True,
node_hover_tooltip=True,
show_node_image=True,
node_image_size_factor=1.0,
show_node_label=True,
show_node_label_border=False,
node_label_data_source='id',
node_label_size_factor=0.8,
node_label_rotation=45.0,
node_label_font='Arial',
show_edge=True,
edge_size_factor=1.0,
edge_size_data_source='size',
use_edge_size_normalization=False,
edge_size_normalization_min=0.2,
edge_size_normalization_max=5.0,
edge_curvature=0.0,
edge_hover_tooltip=True,
show_edge_label=True,
show_edge_label_border=False,
edge_label_data_source='id',
edge_label_size_factor=1.0,
edge_label_rotation=45.0,
edge_label_font='Arial',
zoom_factor=0.4,
large_graph_threshold=500,
layout_algorithm_active=True,
# specific for d3
use_many_body_force=True,
many_body_force_strength=- 70.0,
many_body_force_theta=0.9,
use_many_body_force_min_distance=False,
many_body_force_min_distance=10.0,
use_many_body_force_max_distance=False,
many_body_force_max_distance=1000.0,
use_links_force=True,
links_force_distance=50.0,
links_force_strength=0.5,
use_collision_force=True,
collision_force_radius=30.0,
collision_force_strength=0.9,
use_x_positioning_force=False,
x_positioning_force_strength=0.2,
use_y_positioning_force=True,
y_positioning_force_strength=0.5,
use_centering_force=True,
)
[22]: