@@ -1503,22 +1503,135 @@ of which are outlined below.
15031503
15041504A tree sequence can be treated as a specific form of (directed)
15051505[ graph] ( https://en.wikipedia.org/wiki/Graph_(discrete_mathematics) ) consisting
1506- of nodes connected by edges. Standard graph visualization software, such as
1507- [ graphviz] ( https://graphviz.org ) can therefore be used to represent tree sequence
1508- topologies. This is a relatively common approach to visualizing the full
1509- "Ancestral Recombination Graph" or ARG (a structure in which some nodes are
1510- "recombination nodes", and which is possible to
1511- {ref} ` represent as a tree sequence <msprime:sec_ancestry_full_arg> ` ).
1506+ of nodes connected by edges. Standard graph visualization software,
1507+ such as [ graphviz] ( https://graphviz.org ) can therefore be used to depict tree sequence
1508+ topologies. Alternatively, the [ tskit_arg_visualizer ] ( https://github.com/kitchensjn/tskit_arg_visualizer )
1509+ project will draw a interactive ` tskit ` graph directly. Below is an example, which uses the
1510+ ` variable_edge_width ` option to highlight the spans of genome inherited through different routes.
1511+ Nodes can be dragged horizontally and embedded trees highlighted:
15121512
1513+ ``` {code-cell} ipython3
1514+ :"tags": ["hide-input"]
1515+ %%javascript
1516+ require.config({paths: {d3: 'https://d3js.org/d3.v7.min'}});
1517+ require(["d3"], function(d3) {window.d3 = d3;});
1518+ ```
15131519
1514- :::{todo}
1515- Link to the ARG tutorial,
1516- [ once it is created] ( https://github.com/tskit-dev/tutorials/issues/43 ) , and show a
1517- picture like this:
1520+ ``` {code-cell} ipython3
1521+ import msprime
1522+ import tskit_arg_visualizer
1523+ ts = msprime.sim_ancestry(4, sequence_length=1000, recombination_rate=0.001, random_seed=3)
1524+ d3arg = tskit_arg_visualizer.D3ARG.from_ts(ts=ts)
1525+ tip_order = [0, 6, 3, 1, 2, 7, 4, 5] # Found by trial and error for this seed
1526+ d3arg.draw(width=500, height=500, variable_edge_width=True, sample_order=tip_order)
1527+ ```
15181528
1519- ![ A tree sequence (ARG) as a graph] ( https://user-images.githubusercontent.com/36134434/109398193-2ec6b700-7933-11eb-9cbf-99fdfab46df0.png )
1520- (from [ here] ( https://github.com/tskit-dev/tutorials/issues/43#issuecomment-787124425 ) )
1521- :::
1529+ For an ` msprime ` "full ARG" tree sequence, ` edge_type="ortho" ` can be used to draw a
1530+ traditional "[ Ancestral Recombination Graph] ( sec_args ) " style plot (variable edge widths
1531+ turned off for clarity):
1532+
1533+ ``` {code-cell} ipython3
1534+ import msprime
1535+ import tskit_arg_visualizer
1536+ full_arg_ts = msprime.sim_ancestry(
1537+ 4, sequence_length=1000, recombination_rate=0.001, random_seed=3, record_full_arg=True)
1538+ d3arg = tskit_arg_visualizer.D3ARG.from_ts(ts=full_arg_ts)
1539+ d3arg.draw(width=500, height=500, edge_type="ortho", sample_order=tip_order)
1540+ ```
1541+
1542+ For more general graph plots, it can be helpful convert the tree sequence to a
1543+ [ networkx] ( https://networkx.org ) graph first, as described in the
1544+ {ref}` sec_args_other_analysis ` section of the {ref}` sec_args ` tutorial.
1545+ This provides interfaces to graph plotting software such as
1546+ [ graphviz] ( https://graphviz.org ) , which provides the ` dot ` layout engine for
1547+ directed graphs:
1548+
1549+ ``` {code-cell} ipython3
1550+ :"tags": ["hide-input"]
1551+ ## Networkx conversion code taken from the ARG tutorial
1552+
1553+ import networkx as nx
1554+ import pandas as pd
1555+ import tskit
1556+
1557+ def to_networkx_graph(ts, interval_lists=False):
1558+ """
1559+ Make an nx graph from a tree sequence. If `intervals_lists` is True, then
1560+ each graph edge will have an ``intervals`` attribute containing a *list*
1561+ of tskit.Intervals per parent/child combination. Otherwise each graph edge
1562+ will correspond to a tskit edge, with a ``left`` and ``right`` attribute.
1563+ """
1564+ D = dict(source=ts.edges_parent, target=ts.edges_child, left=ts.edges_left, right=ts.edges_right)
1565+ G = nx.from_pandas_edgelist(pd.DataFrame(D), edge_attr=True, create_using=nx.MultiDiGraph)
1566+ if interval_lists:
1567+ GG = nx.DiGraph() # Mave a new graph with one edge that can contai
1568+ for parent, children in G.adjacency():
1569+ for child, edict in children.items():
1570+ ilist = [tskit.Interval(v['left'], v['right']) for v in edict.values()]
1571+ GG.add_edge(parent, child, intervals=ilist)
1572+ G = GG
1573+ nx.set_node_attributes(G, {n.id: {'flags':n.flags, 'time': n.time} for n in ts.nodes()})
1574+ return G
1575+ ```
1576+
1577+ ``` {code-cell} ipython3
1578+ import networkx as nx
1579+ from IPython.display import SVG
1580+
1581+ def graphviz_svg(networkx_graph):
1582+ AG = nx.drawing.nx_agraph.to_agraph(networkx_graph) # Convert to graphviz "agraph"
1583+ nodes_at_time_0 = [k for k, v in networkx_graph.nodes(data=True) if v['time'] == 0]
1584+ AG.add_subgraph(nodes_at_time_0, rank='same') # put time=0 at same rank
1585+ return AG.draw(prog="dot", format="svg")
1586+
1587+ G = to_networkx_graph(ts, interval_lists=True) # Function from the ARG tutorial
1588+ print("Converted `ts` to a networkx graph named `G`")
1589+ print("Plotting using graphviz...")
1590+ SVG(graphviz_svg(G))
1591+ ```
1592+
1593+ Alternatively, you can read the ` graphviz ` positions back into ` networkx `
1594+ and use the ` networkx ` drawing functionality, which
1595+ relies upon the [ matplotlib] ( https://matplotlib.org ) library. This
1596+ allows modification of node colours and symbols, labels, rotations,
1597+ annotations, etc., as shown below:
1598+
1599+ ``` {code-cell} ipython3
1600+ :"tags": ["hide-input"]
1601+ from matplotlib import pyplot as plt
1602+ import string
1603+
1604+ def get_graphviz_positions(networkx_graph):
1605+ AG = nx.drawing.nx_agraph.to_agraph(networkx_graph) # Convert to graphviz "agraph"
1606+ nodes_at_time_0 = [k for k, v in networkx_graph.nodes(data=True) if v['time'] == 0]
1607+ AG.add_subgraph(nodes_at_time_0, rank='same') # put time=0 at same rank
1608+ AG.layout(prog="dot") # create the layout, storing positions in the "pos" attribute
1609+ return {n: [float(x) for x in AG.get_node(n).attr["pos"].split(",")] for n in G.nodes()}
1610+
1611+ pos=get_graphviz_positions(G)
1612+ edge_labels = {
1613+ edge[0:2]: "\n".join([f"[{int(i.left)},{int(i.right)})" for i in edge[2]["intervals"]])
1614+ for edge in G.edges(data=True)
1615+ }
1616+
1617+ samples = set(ts.samples())
1618+ nonsamples = set(range(ts.num_nodes)) - samples
1619+
1620+ plt.figure(figsize=(10, 4))
1621+ # Sample nodes as dark green squares with white text
1622+ nx.draw(G, pos, node_color="#007700", font_size=9, node_size=250, node_shape="s", nodelist=samples, edgelist=[])
1623+ nx.draw_networkx_labels(G, pos, font_size=9, labels={u: u for u in samples}, font_color="white")
1624+ # Others as blue circles with alphabetic labels
1625+ nx.draw(G, pos, node_color="#22CCFF", font_size=9, edgecolors="black", nodelist=nonsamples, edgelist=[])
1626+ nx.draw_networkx_labels(G, pos, font_size=9, labels={u: string.ascii_lowercase[u] for u in nonsamples})
1627+
1628+ nx.draw_networkx_edges(G, pos, edge_color="lightgrey", arrows=False, width=2);
1629+ nx.draw_networkx_edge_labels(G, pos, edge_labels, font_size=7, rotate=False);
1630+ ```
1631+
1632+ Note, however, that finding node and edge layout positions that avoid too much overlap
1633+ can be tricky, even for the graphviz layout engine, and there is no easy functionality
1634+ to place nodes at specific vertical (time) positions.
15221635
15231636(sec_tskit_viz_other_demographic)=
15241637
0 commit comments