Skip to content

Commit 9caa020

Browse files
Merge pull request #228 from hyanwong/hover-viz
Add examples of summarising clades into a single node
2 parents b3deaf5 + 86feb60 commit 9caa020

1 file changed

Lines changed: 168 additions & 7 deletions

File tree

viz.md

Lines changed: 168 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -444,15 +444,15 @@ and so on.
444444

445445
The classes above make it easy to target specific nodes or edges in one or multiple
446446
trees. For example, we can colour branches that are shared between trees
447-
(identified here as ones that have the same parent and child):
447+
(identified below as ones that have the same parent and child):
448448

449449
```{code-cell} ipython3
450450
css_string = ".a15.n9 > .edge {stroke: cyan; stroke-width: 2px}" # branches from 15->9
451451
ts_small.draw_svg(time_scale="rank", size=wide_fmt, style=css_string)
452452
```
453453

454454
By generating the css string programatically, you can target all the edges present in a
455-
particular tree, and see how they gradually disappear from adjacent trees. Here, for
455+
particular tree, and see how they gradually disappear from adjacent trees. Below, for
456456
example the branches in the central tree have been coloured red, as have the identical
457457
branches in adjacent trees. The central tree represents a location in the genome
458458
that has seen a selective sweep, and therefore has short branch lengths: adjacent trees
@@ -572,9 +572,9 @@ Using the transformations discussed in the next section, it is also possible to
572572
SVG images, as shown in the {ref}`sec_tskit_viz_SVG_examples_animation` code within the
573573
{ref}`sec_tskit_viz_SVG_examples` section near the end of this tutorial.
574574

575-
(sec_tskit_viz_styling_moving_and_transforming)=
575+
(sec_tskit_viz_styling_transforming_and_masking)=
576576

577-
#### Moving and transforming elements
577+
#### Transforming and masking elements
578578

579579
We can also use styles to transform elements of the drawing, shifting them into different
580580
locations or changing their orientation. For example,
@@ -585,8 +585,8 @@ following CSS string to rotate leaf labels:
585585
.leaf > .lab {text-anchor: start; transform: rotate(90deg) translate(6px)}
586586
```
587587

588-
Transformations not only allow us to shift e.g. labels about, but also change the size
589-
of symbols, which can create rather different formatting styles:
588+
Transformations not only allow us to shift elements about, but also resize and skew them.
589+
When applied to both symbols and labels this can create rather different formatting styles:
590590

591591
```{code-cell} ipython3
592592
css_string = (
@@ -639,6 +639,43 @@ but otherwise you may need to use the `chromium` workaround documented
639639
{ref}`here <sec_tskit_viz_converting_note>`.
640640
:::
641641

642+
Although it is hard to change the style of a node symbol, the visible area of the symbol
643+
can be modified using the `clip-path` CSS property. This can be useful to show, for
644+
instance, a triangle to summarise the descendants of a MRCA.
645+
646+
```{code-cell} ipython3
647+
# Check that MRCA of 2 & 3 is node 4 in all trees, assumed later
648+
assert all([4 == tree.mrca(2, 3) for tree in ts_tiny.trees()])
649+
650+
styles = [
651+
# Set all node labels to be rotated and small
652+
".node > .lab {text-anchor: start; transform: rotate(90deg) translate(6px); font-size: 8px}",
653+
654+
# Hide all nodes descending from node 4. We then treat node 4 as a summary node
655+
".n4 > .node {display: none}",
656+
657+
# Use clipping & scaling to change the symbol for node 4 into a summary triangle
658+
".n4 > .sym {clip-path: polygon(50% 50%, 75% 75%, 25% 75%); transform: scale(8.0, 8.0)}",
659+
660+
# Make the font bigger for this summary node label
661+
".n4 > .lab {transform: rotate(90deg) translate(14px); font-size: 16px}"
662+
]
663+
664+
node_labels = {0: "Nd. 0", 1: "Nd. 1", 4: "Two samples"}
665+
666+
ts_tiny.draw_svg(
667+
size=(800, 300),
668+
x_scale="treewise",
669+
time_scale="log_time",
670+
style="".join(styles),
671+
node_labels=node_labels,
672+
)
673+
```
674+
In the example above we simply hid the descendant topology for each "summary MRCA",
675+
meaning more horizontal space was taken up than expected. For a more sophisticated
676+
example, see {ref}`sec_tskit_viz_SVG_examples_larger_plots` in which some
677+
descendant samples are actually removed from the tree sequence entirely, and their MRCA
678+
is changed into a sample node instead.
642679

643680
#### Styling and SVG structure
644681

@@ -1128,12 +1165,136 @@ ts.draw_svg(
11281165
)
11291166
```
11301167

1168+
(sec_tskit_viz_SVG_examples_larger_plots)=
1169+
1170+
#### Simplifying larger plots
1171+
1172+
It is common to want to visualise a tree sequence with many samples and trees. If there
1173+
are many trees, the `max_num_trees` parameter can be used to just show those at the start
1174+
and end of the genome. To reduce the size of each tree, multiple samples can be clustered
1175+
into a single representative clade. If that clade has the same set of descendant samples
1176+
throughout the tree sequence, {ref}`sec_simplification` can be used to to turn the MRCA
1177+
of these samples into a sample node itself, while removing the original descendants.
1178+
By using the scaling and masking method described in
1179+
{ref}`sec_tskit_viz_styling_transforming_and_masking` this summary MRCA can be shown
1180+
as a large triangle, of size proportional to the number of samples underneath it. The
1181+
example below shows how a tree sequence of 40 sample nodes can be visualised relatively
1182+
compactly using these techniques:
1183+
1184+
```{code-cell} ipython3
1185+
:"tags": ["hide-input"]
1186+
import msprime
1187+
import numpy as np
1188+
1189+
def clonal_mrcas(ts, most_recent=True):
1190+
"""
1191+
Identify the nodes in a tree sequence which define clonal subtrees (i.e. in which the
1192+
samples descending from that node are identical and show identical relationships
1193+
to each other over the entire tree sequence). This includes, at its limit, nodes
1194+
with only a single descendant sample.
1195+
1196+
:param bool most_recent: If True, and the clonal node is a unary node, return IDs of
1197+
the most recent node that defines the clonal subtree. In this case, the returned
1198+
IDs represent cases where the node is either a tip or a coalescent point.
1199+
:return: a list of nodes defining constant subtrees over the entire tree sequence
1200+
:rtype: list
1201+
"""
1202+
for interval, edges_out, edges_in in ts.edge_diffs():
1203+
if interval.left==0:
1204+
is_full_length_clonal = np.ones(ts.num_nodes, dtype=bool) # nodes start clonal
1205+
else:
1206+
for e in edges_in:
1207+
is_full_length_clonal[e.parent] = False
1208+
for e in edges_out:
1209+
is_full_length_clonal[e.parent] = False
1210+
clonal_nodes = np.where(is_full_length_clonal)[0]
1211+
1212+
tables = ts.dump_tables()
1213+
edges = tables.edges
1214+
# only keep edges where both the child and the parent are full-length clonal nodes
1215+
keep_edge = np.logical_and(
1216+
np.isin(edges.child, clonal_nodes),
1217+
np.isin(edges.parent, clonal_nodes),
1218+
)
1219+
tables.edges.set_columns(
1220+
left = tables.edges.left[keep_edge],
1221+
right=tables.edges.right[keep_edge],
1222+
parent=tables.edges.parent[keep_edge],
1223+
child=tables.edges.child[keep_edge],
1224+
)
1225+
clonal_ts = tables.tree_sequence()
1226+
assert clonal_ts.num_trees == 1
1227+
1228+
# Also remove all the edges ascending from removed edges
1229+
tree = clonal_ts.first()
1230+
non_clonal_ancestors = set()
1231+
deleted_edges = np.logical_not(keep_edge)
1232+
for u in np.unique(ts.edges_parent[deleted_edges]):
1233+
while u != tskit.NULL and u not in non_clonal_ancestors:
1234+
non_clonal_ancestors.add(u)
1235+
u = tree.parent(u)
1236+
non_clonal_ancestors = np.array(list(non_clonal_ancestors))
1237+
tables = clonal_ts.dump_tables()
1238+
remove_edge = np.isin(tables.edges.parent, non_clonal_ancestors)
1239+
tables.edges.replace_with(tables.edges[np.logical_not(remove_edge)])
1240+
clonal_ts = tables.tree_sequence()
1241+
1242+
tree = ts.first(sample_lists=True)
1243+
clonal_tree = clonal_ts.first(sample_lists=True)
1244+
clonal_nodes = []
1245+
for root in clonal_tree.roots:
1246+
# Clonal trees should subtend the same set of samples as in the original tree
1247+
assert set(tree.samples(root)) == set(clonal_tree.samples(root))
1248+
u = root
1249+
if most_recent:
1250+
# decend to the first coalescent node (i.e. MRCA)
1251+
while clonal_tree.num_children(u) == 1:
1252+
u = clonal_tree.children(u)[0]
1253+
clonal_nodes.append(u)
1254+
return clonal_nodes
1255+
1256+
ts = msprime.sim_ancestry(
1257+
20, population_size=1e2, sequence_length=1e4, recombination_rate=1e-6, random_seed=83)
1258+
clones = clonal_mrcas(ts)
1259+
# Simplify but keep the same node IDs using filter_nodes=False
1260+
ts_simp = ts.simplify(clones, filter_nodes=False)
1261+
1262+
styles = [
1263+
".node > .lab {font-size: 9px}",
1264+
".leaf > .lab {text-anchor: start; transform: rotate(90deg) translate(5px); font-size: 12px}",
1265+
1266+
]
1267+
1268+
node_labels = {u: u for u in range(ts.num_nodes)}
1269+
for u in ts.samples():
1270+
node_labels[u] = f"S{u}"
1271+
1272+
tree = ts.first()
1273+
for u in clones:
1274+
num_samples = tree.num_samples(u)
1275+
if num_samples > 1:
1276+
node_labels[u] = f"{num_samples} samples"
1277+
styles.append(
1278+
f".n{u} > .sym {{clip-path: polygon(50% 50%, 100% 100%, 0% 100%);"+
1279+
f"transform: scale({(num_samples-1)/5 + 1}, 4.0)}}" +
1280+
f".n{u} > .lab {{transform: rotate(90deg) translate(15px); font-size: 13px}}"
1281+
)
1282+
ts_simp.draw_svg(
1283+
size=(1000, 400),
1284+
style="".join(styles),
1285+
node_labels=node_labels,
1286+
time_scale="log_time",
1287+
y_axis=True,
1288+
y_ticks=[0, 1, 10, 100],
1289+
max_num_trees=4)
1290+
```
1291+
11311292
(sec_tskit_viz_SVG_examples_3D)=
11321293

11331294
#### 3D effects
11341295

11351296
We can use various CSS transforms, as
1136-
{ref}`discussed previously<sec_tskit_viz_styling_moving_and_transforming>`,
1297+
{ref}`discussed previously<sec_tskit_viz_styling_transforming_and_masking>`,
11371298
to skew the trees and stagger them. With a bit of trigonometry,
11381299
this can create flexible and tolerably good 3D effects for presentations, etc.
11391300

0 commit comments

Comments
 (0)