@@ -18,8 +18,10 @@ kernelspec:
1818
1919``` {code-cell} ipython3
2020:tags: [remove-cell]
21- import msprime
2221import io
22+ import string
23+
24+ import msprime
2325import tskit
2426
2527def viz_ts():
@@ -38,10 +40,10 @@ def viz_ts():
3840 }
3941 topologies = {tree.rank() for tree in ts_tiny.trees()}
4042 # Check we have picked a random seed that gives a nice plot of 7 trees
41- assert tip_orders == {first_4_nodes} and len(topologies) > 1 and ts .num_trees == 8
43+ assert tip_orders == {first_4_nodes} and len(topologies) > 1 and ts_tiny .num_trees == 8
4244 ts_tiny.dump("data/viz_ts_tiny.trees")
4345
44- eight_nodes = first_4_nodes + [ 40, 41, 80, 81] # Add nodes from individuals in B & C
46+ eight_nodes = first_4_nodes + ( 40, 41, 80, 81) # Add nodes from individuals in B & C
4547 ts_small = ts_full.simplify(eight_nodes) # a small 8-tip TS
4648 ts_small.dump("data/viz_ts_small.trees")
4749
@@ -58,13 +60,23 @@ def viz_selection():
5860 sequence_length = 5e4
5961 sweep_model = msprime.SweepGenicSelection(position=sequence_length/2,
6062 s=0.01, start_frequency=0.5e-4, end_frequency=0.99, dt=1e-6)
61- ts_selection = msprime.sim_ancestry(9,
63+ ts_selection = msprime.sim_ancestry(12,
64+ ploidy=1,
6265 model=[sweep_model, msprime.StandardCoalescent()],
6366 population_size=1e4,
64- recombination_rate=1e -8,
67+ recombination_rate=2e -8,
6568 sequence_length=sequence_length,
66- random_seed=9 ,
69+ random_seed=222 ,
6770 )
71+ tables = ts_selection.dump_tables()
72+ tables.nodes.clear()
73+ tables.nodes.metadata_schema = tskit.MetadataSchema.permissive_json()
74+ for node in ts_selection.nodes():
75+ metadata = {}
76+ if node.is_sample():
77+ metadata["name"] = f"Sample {string.ascii_uppercase[node.id]}"
78+ tables.nodes.add_row(node.replace(metadata=metadata))
79+ ts_selection = tables.tree_sequence()
6880 ts_selection.dump("data/viz_ts_selection.trees")
6981
7082
@@ -291,11 +303,15 @@ default these mutations are drawn in a slightly different shade (e.g. mutation 6
291303third_tree.draw_svg(size=(200, 300), all_edge_mutations=True)
292304```
293305
306+ (sec_tskit_viz_labelling)=
307+
294308### Labelling
295309
296310Although the default node and mutation labels show unique identifiers, they are't
297311terribly intuituive. The ` node_labels ` and ` mutation_labels ` parameters can be used
298312to set more meaningful labels (for example from the tree sequence {ref}` sec_metadata ` ).
313+ See {ref}` sec_tskit_viz_dynamic_effects ` if you want to dynamically hide and show such
314+ labels.
299315
300316``` {code-cell} ipython3
301317nd_labels = {} # An array of labels for the nodes
@@ -460,12 +476,16 @@ for node_id in focal_tree.nodes():
460476 if parent_id != tskit.NULL:
461477 css_edge_targets.append(f".a{parent_id}.n{node_id}>.edge")
462478css_string = ",".join(css_edge_targets) + "{stroke: red} .sym {display: none}"
463-
479+ css_string += ( # Rotate the position labels etc
480+ ".x-axis .ticks .lab {text-anchor: start; transform: translate(6px) rotate(90deg)}"
481+ ".x-axis .title .lab {text-anchor: start}"
482+ )
464483wide_tall_fmt = (1200, 400)
465484ts_selection.draw_svg(
466485 style=css_string,
467486 size=wide_tall_fmt,
468- x_lim=[1.74e4, 3.25e4],
487+ canvas_size=(wide_tall_fmt[0], wide_tall_fmt[1] + 30),
488+ x_lim=[1e4, 4e4],
469489 node_labels={},
470490)
471491```
@@ -477,6 +497,81 @@ in a tree sequence: for example, edges have the
477497additional constraint that they must belong to _ adjacent_ trees.
478498:::
479499
500+ (sec_tskit_viz_dynamic_effects)=
501+
502+ #### Dynamic effects
503+
504+ In the previous example, the large size of the plotted trees meant that, for clarity,
505+ the node and mutation labels were turned off (in that case by passing empty mappings
506+ to the ` node_labels ` and ` mutation_labels ` parameters). Nevertheless, it can be useful
507+ to identify nodes and mutations, and this can be done dynamically (on "mouseover") by
508+ setting the CSS [ display] ( https://www.w3.org/TR/SVG2/render.html#VisibilityControl )
509+ property to ` none ` vs ` initial ` , and combining it with the CSS ` :hover ` pseudoclass.
510+ Here's an example using a region from within the previous example:
511+
512+ ``` {code-cell} ipython3
513+ from IPython.display import HTML
514+ # add some mutations
515+ ts = msprime.sim_mutations(ts_selection, rate=2e-8, random_seed=1)
516+
517+ node_label_css = (
518+ # hide node labels by default
519+ "#hover_example .node > .sym ~ .lab {display: none}"
520+ # Unless the adjacent node or the label is hovered over
521+ "#hover_example .node > .sym:hover ~ .lab {display: inherit}"
522+ "#hover_example .node > .sym ~ .lab:hover {display: inherit}"
523+ )
524+
525+ mut_label_css = (
526+ # hide mutation labels by default
527+ "#hover_example .mut .sym ~ .lab {display: none}"
528+ # Unless the adjacent node or the label is hovered over
529+ "#hover_example .mut .sym:hover ~ .lab {display: inherit}"
530+ "#hover_example .mut .sym ~ .lab:hover {display: inherit}"
531+ )
532+
533+ optional_css = (
534+ # These are optional, but setting e.g. the node label text to bold with grey stroke
535+ # and a black fill, serves to make black text readable against a black tree
536+ "svg#hover_example {background-color: white}"
537+ "#hover_example .tree .plotbox .lab {stroke: #CCC; fill: black; font-weight: bold}"
538+ "#hover_example .tree .mut .lab {stroke: #FCC; fill: red; font-weight: bold}"
539+ )
540+
541+ HTML(ts.draw_svg(
542+ style=optional_css + node_label_css + mut_label_css,
543+ y_axis=True,
544+ y_ticks={0: "0", 500: "", 1000: "1000"},
545+ x_lim=[2.3e4, 2.7e4],
546+ root_svg_attributes={"id": "hover_example"},
547+ # Label node by name in metadata, if it exists, else node ID
548+ node_labels={u.id: u.metadata.get("name", f"NodeID={u.id}") for u in ts.nodes()},
549+ mutation_labels={
550+ # Label mutation by site position, prev state, and new state
551+ m.id: (
552+ f"pos {s.position:g}: " +
553+ (s.ancestral_state if m.parent<0 else ts.mutation(m.parent).derived_state) +
554+ f"→{m.derived_state}"
555+ )
556+ for s in ts.sites()
557+ for m in s.mutations
558+ },
559+ ))
560+ ```
561+
562+ :::{note}
563+ Above we have wrapped the svg in an IPython {class}` ~ipython:IPython.display.HTML `
564+ class, and given the SVG a unique
565+ id as described below in {ref}` sec_tskit_viz_styling_more_about ` . This forces the SVG
566+ plot to be rendered inline (rather than inside an ` <img> ` tag), allowing the hover
567+ functionality to work in all supported Jupyter notebook implementations. However,
568+ depending on your Jupyter setup, the ` HTML() ` wrapper may not be necessary.
569+ :::
570+
571+ Using the transformations discussed in the next section, it is also possible to animate
572+ SVG images, as shown in the {ref}` sec_tskit_viz_SVG_examples_animation ` code within the
573+ {ref}` sec_tskit_viz_SVG_examples ` section near the end of this tutorial.
574+
480575(sec_tskit_viz_styling_moving_and_transforming)=
481576
482577#### Moving and transforming elements
@@ -545,7 +640,6 @@ but otherwise you may need to use the `chromium` workaround documented
545640:::
546641
547642
548-
549643#### Styling and SVG structure
550644
551645To take full advantage of the SVG styling capabilities in tskit, it is worth knowing how
@@ -681,6 +775,8 @@ support it (note, however, as of v1.2 Inkscape does not appear to support this s
681775style_string = ".node:not(.leaf) > .sym, .node:not(.leaf) > .lab {display: none}"
682776```
683777
778+ (sec_tskit_viz_styling_more_about)=
779+
684780#### More about styling
685781
686782NOTE: if your SVG is embedded directly into an HTML page (a common way for jupyter
@@ -1090,6 +1186,7 @@ canvas_size = (width + y_step, height + ts.num_trees*y_step + math.tan(skew)*tre
10901186ts.draw_svg(size=size, x_scale="treewise", style=style, canvas_size=canvas_size)
10911187```
10921188
1189+ (sec_tskit_viz_SVG_examples_animation)=
10931190
10941191#### Animation
10951192
@@ -1113,9 +1210,12 @@ def make_full_arg_for_spr_animation():
11131210 random_seed=6787, model="smc_prime", record_full_arg=True)
11141211
11151212
1116- css_string = ".node:not(.sample) > .lab, .node:not(.sample) > .sym {display: none}"
1213+ css_string = (
1214+ "#anim_svg {background-color: white} "
1215+ "#anim_svg .node:not(.sample) > .lab, #anim_svg .node:not(.sample) > .sym {display: none}"
1216+ )
11171217html_string = r"""
1118- <div id="animated_svg_canvas">%s</div>
1218+ %s
11191219<script type="text/javascript" src="https://d3js.org/d3.v4.min.js"></script>
11201220<script type="text/javascript">
11211221function diff(A) {return A.slice(1).map((n, i) => { return n - A[i]; });};
@@ -1129,11 +1229,11 @@ function getRelativeXY(canvas, element, x, y) {
11291229};
11301230
11311231function animate_SPR(canvas, num_trees) {
1132- d3.selectAll(".tree").attr("opacity", 0);
1232+ d3.selectAll("#anim_svg .tree").attr("opacity", 0);
11331233 for(var i=0; i<num_trees - 1; i++)
11341234 {
1135- var source_tree = ".tree.t" + i;
1136- var target_tree = ".tree.t" + (i+1);
1235+ var source_tree = "#anim_svg .tree.t" + i;
1236+ var target_tree = "#anim_svg .tree.t" + (i+1);
11371237 var dur = 2000;
11381238 var delay = i * dur;
11391239 d3.select(source_tree)
@@ -1213,16 +1313,19 @@ function transform_tree(canvas, src_tree, target_tree, dur, delay) {
12131313 })
12141314};
12151315
1216- var svg_text = document.getElementById("animated_svg_canvas ").innerHTML;
1316+ var svg_text = document.getElementById("anim_svg ").innerHTML;
12171317
12181318</script>
12191319
1220- <button onclick='animate_SPR(d3.select("#animated_svg_canvas svg "), %s);'>Animate</button>
1221- <button onclick='document.getElementById("animated_svg_canvas ").innerHTML = svg_text;'>Reset</button>
1320+ <button onclick='animate_SPR(d3.select("#anim_svg "), %s);'>Animate</button>
1321+ <button onclick='document.getElementById("anim_svg ").innerHTML = svg_text;'>Reset</button>
12221322"""
12231323
12241324ts = make_full_arg_for_spr_animation()
1225- HTML(html_string % (ts.draw_svg(style=css_string), ts.num_trees))
1325+ HTML(html_string % (
1326+ ts.draw_svg(root_svg_attributes={"id": "anim_svg"}, style=css_string),
1327+ ts.num_trees,
1328+ ))
12261329```
12271330
12281331
0 commit comments