Skip to content

Commit a47ec93

Browse files
Merge pull request #225 from hyanwong/hover-viz
Add an example of dynamically showing labels
2 parents c861415 + 2d141e4 commit a47ec93

3 files changed

Lines changed: 122 additions & 18 deletions

File tree

_config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ sphinx:
4343
msprime: ["https://tskit.dev/msprime/docs/stable", null]
4444
pyslim: ["https://tskit.dev/pyslim/docs/stable", null]
4545
numpy: ["https://numpy.org/doc/stable/", null]
46+
ipython: ["https://ipython.readthedocs.io/en/stable/", null]
4647
myst_enable_extensions:
4748
- colon_fence
4849
- deflist

data/viz_ts_selection.trees

-2.2 KB
Binary file not shown.

viz.md

Lines changed: 121 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ kernelspec:
1818

1919
```{code-cell} ipython3
2020
:tags: [remove-cell]
21-
import msprime
2221
import io
22+
import string
23+
24+
import msprime
2325
import tskit
2426
2527
def 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
291303
third_tree.draw_svg(size=(200, 300), all_edge_mutations=True)
292304
```
293305

306+
(sec_tskit_viz_labelling)=
307+
294308
### Labelling
295309

296310
Although the default node and mutation labels show unique identifiers, they are't
297311
terribly intuituive. The `node_labels` and `mutation_labels` parameters can be used
298312
to 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
301317
nd_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")
462478
css_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+
)
464483
wide_tall_fmt = (1200, 400)
465484
ts_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
477497
additional 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

551645
To 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
681775
style_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

686782
NOTE: 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
10901186
ts.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+
)
11171217
html_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">
11211221
function 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
11311231
function 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
12241324
ts = 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

Comments
 (0)