You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session.
vega / altair Public
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
I'm quite new to Vega(-Lite) and Altair, having only heard about them at Pycon 2017. I was wondering if it was possible to represent an interactive graph/network using Altair. My use case involves being able to view, zoom, pan, drag, drop, and edit the nodes and edge properties of a directed acyclic graph.
The Vega examples show something that could be modified to fit my use case (https://vega.github.io/vega/examples/airport-connections), but it seems that Altair specifically deals with Vega-Lite, and thus is mainly statistically-focused?
The text was updated successfully, but these errors were encountered:
luerhard commented Jun 6, 2017 ellisonbg added question vega-lite-related labels Sep 27, 2017 ellisonbg added this to the Reference milestone Sep 27, 2017 ellisonbg closed this as completed Sep 27, 2017 BrennanBarker commented Mar 25, 2021This doesn't directly address the OP's question, but for people who end up here looking for help visualizing networks in Altair, it's probably useful to point to the nx_altair package, which focuses on providing an API similar to NetworkX's, but with all the goodness of Altair charts' interactivity, etc.
For those interested in leaning into Altair's lovely Grammar of Graphics (GoG)-focused API, below is one way I've gone about it.
A GoG'y way to think about you're doing when you make a typical network visualization (the drawings with dots and lines between them) is you're laying out point marks (representing your nodes) spatially on an X/Y plane and drawing line marks between the nodes where edges exist. While your nodes and edges do have intrinsic properties (names, weights, ranks in a hierarchy, etc.), the trick is to realize that (by definition) your graph doesn't include information about how to position nodes spatially. You have to provide that information, either by specifying node positions arbitrarily or by using a layout algorithm. There are lots of layout algorithms ('forced-directed' being one category of layouts, there are others), and there are great packages that calculate graph layouts, but Altair (I think wisely) leaves it to those other packages rather than trying to replicate that functionality themselves.
Here's an example where I use NetworkX to build a graph and calculate some properties as well as layout information before feeding it into Altair to actually build the graphic:
from itertools import chain import pandas as pd import networkx as nx import altair as alt # Step 1: Prepare an example graph (this all happens outside of Altair) # example graph r,h = 3,3 G = nx.generators.classic.balanced_tree(r,h) # calculate rank of a given node and assign it as data for rank in range(0,h+1): nodes_in_rank = nx.descendants_at_distance(G, 0, rank) for node in nodes_in_rank: G.nodes[node]['rank'] = rank # calculate layout positions, for example using Graphviz's 'twopi' algorithm, calculated via networkx's API. pos = nx.drawing.nx_agraph.graphviz_layout(G, prog='twopi') # Step 2: Convert graph data from NetworkX's format to the pandas DataFrames expected by Altair pos_df = pd.DataFrame.from_records(dict(node_id=k, x=x, y=y) for k,(x,y) in pos.items()) node_df = pd.DataFrame.from_records(dict(data, **'node_id': n>) for n,data in G.nodes.data()) edge_data = ((dict(d, **'edge_id':i, 'end':'source', 'node_id':s>), dict(d, **'edge_id':i, 'end':'target', 'node_id':t>)) for i,(s,t,d) in enumerate(G.edges.data())) edge_df = pd.DataFrame.from_records(chain.from_iterable(edge_data)) # Step 3: Use Altair to encode the graph data as marks in a visualization x,y = alt.X('x:Q', axis=None), alt.Y('y:Q', axis=None) # use a lookup to tie position data to the other graph data node_position_lookup = < 'lookup': 'node_id', 'from_': alt.LookupData(data=pos_df, key='node_id', fields=['x', 'y']) > nodes = ( alt.Chart(node_df) .mark_circle(size=300, opacity=1) .encode(x=x, y=y, color=alt.Color('rank:N', legend=None)) .transform_lookup(**node_position_lookup) ) edges = ( alt.Chart(edge_df) .mark_line(color='gray') .encode(x=x, y=y, detail='edge_id:N') # `detail` gives one line per edge .transform_lookup(**node_position_lookup) ) chart = ( (edges+nodes) .properties(width=500, height=500,) .configure_view(strokeWidth=0) ) chart
The nice thing about keeping things in terms of Altair's API is that it's trivial at this point to add additional encodings, layers, interactivity, tooltips, etc.
All this said, there are a couple of things that Altair doesn't quite appear to support yet, although I'd be happy to be corrected: