A Python library using Altair for declarative, data-driven network visualisation.
Tidy, legible graph visualisations can be elusive. Alph helps by bringing together effective styling, layout and pruning options from across the Python ecosystem.
- Get your data into a NetworkX Graph
- Pick a network layout function, or bring your own node coordinates
- Define node and edge style attributes
- Plot using a simple function call
Best bet is probably to dive straight into the examples, and come back to the API documentation below as needed.
- plot any NetworkX Graph
- support for any layout expressed as a NetworkX
pos
structure (a dict like{node_id: (x,y), ...}
) - several readily accessible and tuneable layout functions (see example)
- Altair-style data driven node and edge styling - size, colour, stroke, opacity, scales, conditionals and more
- convenience args for node labels, halos
- experimental 1-level "combo" node support
pip install alph
-
for graphviz layout support, install graphviz on your platform - e.g.
brew install graphviz
on Mac,sudo apt install libgraphviz-dev graphviz
on Colab, Debian, Ubuntu etc - or download the installer -
Install alph with graphviz support:
pip install "alph[graphviz]"
-
Install the ForceAtlas2 layout library from our fork, along with cython for speedup
pip install cython "git+https://github.com/connectedcompany/forceatlas2.git"
ForceAtlas2 is a classic, user feedback led layout algorithm from the Gephi team, and the ForceAtlas2 library implementation is an excellent, performant Python port. There are two things to be aware of:
GPL licence - the ForceAtlas2 library, and some of the works it is derived from, are GPL licensed - hence care is needed when distributing and linking to it. We thus intend to keep its install optional long term. Since alph uses a plugin design for layout providers, this is straightforward for us to accommodate, and maintain explicit separation for use cases where GPL is an issue.
Our fork - recently, releases of the library have been sporadic - though there is stated intent for regular maintenance to resume. In the meantime, we've created a temporary fork to be able to roll in changes. Currently, our fork incorporates a simple change that enables deterministic layouts.
Simply call the alph
function with desired options.
Minimally, given a weighted network G:
from alph import alph
G = ...
alph(G, weight_attr="weight")
See examples
. Here's an overview:
-
Some of the supported layouts (from the layouts gallery example):
-
Use of geolocation coordinates for graph layout, (from the flight routes example):
-
Basic styling (from the styling example):
-
A styled interaction network (from the dolphins example):
-
"Combo"-style layout (experimental) - category-driven node grouping with edge weight aggregation, from the Les Mis example:
- NetworkX layouts: Spring, Fruchterman-Reingold, etc
- NetworkX-wrapped graphviz layouts:
dot, neato etc. You can use
args
for specific layouts - for example, for neato:args="-Goverlap=scale -Gstart=123"
- Gephi ForceAtlas2 based on the forceatlas2 Python implementation - see layout.py for configuration options, and this paper for more detail
- ForceAtlas implementation within scikit-network
- Any other that returns a NetworkX-style node positions dictionary
arg | type(s) | default | description |
---|---|---|---|
G | Networkx Graph | graph to visualise | |
weight_attr | str | edge weight attribute, for weighted graphs | |
layout_fn | function | ForceAtlas2 | Function that, given a graph, returns a layout |
node args | dict | See below | |
edge args | dict | See below | |
combo_group_by | str or list | Attribute to use to create grouped combo nodes | |
combo_layout_fn | function | Fruchterman-Reingold | Layout function for combo nodes |
combo_node_args | dict | See below | |
combo_edge_args | dict | See below | |
combo_edge_weight_agg_attr | dict | Attribute to use to weigh combo edges; if set, overrides weight_attr. Can use values given via combo_edge_agg_attrs. If not set and weight_attr not given, falls back to simple edge count. | |
combo_edge_agg_attrs | dict | Pandas groupby-style dict, describing how to aggregate edge attributes that span nodes - for example {"combo_edge_attr_name": ("edge_attr_name", "sum")} |
|
combo_edge_weight_threshold | dict | Drop edges below this weight | |
include_edgeless_combo_nodes | dict | Whether or not to incorporate disconnected combo nodes | |
combo_node_additional_attrs | dict | Attributes to add to combo nodes | |
edge_node_additional_attrs | dict | Attributes to add to combo node edges, like {"edge_attr_name": agg_fn} ; agg fn is applied across all attr values for edges that link grouped nodes |
|
combo_empty_attr_action | drop, group or promote | drop |
What to do with nodes that have an empty value for the combo_group_by attribute |
combo_size_scale_domain | 2-item list or tuple | [0, 25] |
Lower/upper bound of num nodes to apply to combo node size range |
combo_size_scale_range | 2-item list or tuple | [6**2, 180**2] |
Combo node size range |
combo_inner_graph_scale_factor | float | 0.6 |
Scale down inner graph to fit inside combo nodes by this factor - normally <1 |
non_serializable_datetime_format | str | %d %b %Y |
Format string for non-serialisable date / time types that otherwise break Altair |
width | int | 800 |
Figure width (px) |
height | int | 600 |
Figure height (px) |
prop_kwargs | dict | Optional properties such as title | |
padding | int | Padding inside figure edges. No node centres will be placed outside this boundary. | |
nodes_layer_params | selection or other | Altair params to be added to the nodes layer via .add_params() - typically a selection |
arg | type(s) | default |
---|---|---|
size | int, alt.* | 400 |
tooltip_attrs | list | |
fill | str, alt.* | #000 |
opacity | float, alt.* | 1 |
stroke | str, alt.* | #565656 |
strokeWidth | int, alt.* | 0 |
halo_offset | int | |
halo_opacity | float, alt.* | 1 |
halo_stroke | str, alt.* | #343434 |
halo_strokeWidth | int, alt.* | 2 |
label_attr | str | |
label_offset | int | 6 |
label_size | int | 10 |
label_color | str | black |
arg | type(s) | default |
---|---|---|
weight_attr | str | weight |
color | str, alt.* | #606060 |
opacity | float , alt.* | alt.Size(weight_attr, scale=alt.Scale(range=[0.3, 1]) if weighted, 1 otherwise |
strokeWidth | int, alt.* | alt.Size(weight_attr, scale=alt.Scale(range=[0.1, 5]) if weighted, 2 otherwise |
- Get to know your layout algos - especially the 2-3 most used ones. They can have a dramatic effect on the results. A combination of FA2, Spring and Fruchterman is extremely versatile if set up right.
- Pass
seed
to layout functions where possible, for repeatable layouts - Set padding to ensure node elements stay within figure boundaries
-
Node
size
attribute does not support all Altair options - currently onlyalt.value
andalt.Size
with lineardomain
andrange
scales. More can be added as needed.This is a design choice, made to not burden the user with calculating things like label and halo positions when node sizes vary. Will review this tradeoff based on in-use experience.
-
One combo level currently supported
- nx-altair is a nice project that takes a slightly different approach to combining NetworkX and Altair for network viz.