archaeopteryx 3.10.0 → 3.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +331 -8
- package/archaeopteryx.d.ts +23 -0
- package/archaeopteryx.js +1597 -58
- package/forester.js +862 -9
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -33,9 +33,11 @@ config key live and shows the exact config JSON to copy into your own
|
|
|
33
33
|
* [Caliciviridae (186 strains)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=caliciviridae_500)
|
|
34
34
|
* [Adenoviridae (321 strains)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=adenoviridae)
|
|
35
35
|
* [Apaf-1 gene family (domain architectures)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=apaf)
|
|
36
|
+
* [Clustergram (heat map + column clustering)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=clustergram)
|
|
36
37
|
* [Nucleotide alignment (600 columns)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=alignment_nt)
|
|
37
38
|
* [Genome alignment (150 × 30,000 columns)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=genome_alignment)
|
|
38
39
|
* [Sequence alignment](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=alignment)
|
|
40
|
+
* [Sequence logo (the motif changes by clade)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=sequence_logo)
|
|
39
41
|
* [Influenza HA (annotated)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=influenza)
|
|
40
42
|
* [Dinosaur time tree](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=dinosaur)
|
|
41
43
|
* [Ammonite time tree (fossil ranges)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=ammonite)
|
|
@@ -220,7 +222,7 @@ Subtree** opens everything below a node; the tool row's uncollapse-all button
|
|
|
220
222
|
(the desktop's glyph, lit only while something is collapsed) opens the whole
|
|
221
223
|
tree, and so does **Esc**. The wedge has its apex at the node, one edge
|
|
222
224
|
reaching the clade's nearest tip and the other its farthest, so the shape
|
|
223
|
-
shows how uneven the clade's branch lengths are
|
|
225
|
+
shows how uneven the clade's branch lengths are (one depth
|
|
224
226
|
step in a cladogram). Its label stands where a tip's would: on the label
|
|
225
227
|
column in the aligned phylogram and on the outer ring in circular, with the
|
|
226
228
|
same guide line; it is filled in the colour most of its tips wear under the current
|
|
@@ -370,8 +372,9 @@ Date" comes back as `Collection Date` in every menu; a header that already
|
|
|
370
372
|
reads as `namespace:name` is kept as it is). From there nothing is special:
|
|
371
373
|
the columns are offered for **Color-by** and **Shape** by the same rules as
|
|
372
374
|
any property, with the same legends; they are **search** fields, typed
|
|
373
|
-
numeric when every filled cell is a number;
|
|
374
|
-
|
|
375
|
+
numeric when every filled cell is a number; the numeric ones become **heat
|
|
376
|
+
map** columns; they appear in the **node data**; and they are written into a
|
|
377
|
+
phyloXML export, so a saved tree keeps them.
|
|
375
378
|
Tip names are matched exactly, then case-insensitively; empty cells add
|
|
376
379
|
nothing; a column the tree already carries under the same ref is replaced by
|
|
377
380
|
the table's values. Quoted cells, `#` comment lines and Windows line ends are
|
|
@@ -453,7 +456,9 @@ desktop's Unicode entry: it stays with the text field there, as it should.
|
|
|
453
456
|
A view is what you made of a tree with the panel: the layout and display
|
|
454
457
|
type, which labels show, the colour and shape fields, both searches, the
|
|
455
458
|
clade you switched to, the clades you collapsed, the font, node and branch
|
|
456
|
-
sizes, the rotation, the tracks
|
|
459
|
+
sizes, the rotation, and the tracks — which track is shown, the heat map's
|
|
460
|
+
column order (including one you arranged by hand) and whether the alignment
|
|
461
|
+
is summarised as a logo. On the demo pages it rides in the URL's
|
|
457
462
|
`#` hash and follows every change, so the address bar is always a link to
|
|
458
463
|
what is on screen: copy it (**Copy link to this view** in the toolbar) and
|
|
459
464
|
the recipient opens the same tree in the same view. Opening your own file
|
|
@@ -470,7 +475,9 @@ as the page does.
|
|
|
470
475
|
Embedders get the same four pieces: the handle's `getViewState()` and
|
|
471
476
|
`applyViewState(state)`, the config's `view` (open straight into one) and
|
|
472
477
|
`onViewChange(state, encoded)` (called when it changes), and
|
|
473
|
-
`archaeopteryx.encodeViewState()` / `decodeViewState()` for the hash form
|
|
478
|
+
`archaeopteryx.encodeViewState()` / `decodeViewState()` for the hash form
|
|
479
|
+
(every key the state can hold survives the hash — a test reads `getViewState`
|
|
480
|
+
and fails on any key the codec does not know),
|
|
474
481
|
which reads like
|
|
475
482
|
`layout=circular&colorBy=tax:common_name&show=name,external&font=9&collapsed=12,44&a=HUMAN&af=Any+Text&am=contains`.
|
|
476
483
|
Nodes are named by their launch-time preorder index, so a view belongs to
|
|
@@ -528,11 +535,110 @@ checkbox under Display Data toggles the whole track.
|
|
|
528
535
|
To find a motif, pick **Molecular Sequence** in a search box: it matches the
|
|
529
536
|
residues as written, gap characters included, as the desktop does.
|
|
530
537
|
|
|
538
|
+
**Sequence Logo** (the checkbox under **Alignment**) replaces the conservation
|
|
539
|
+
bar with a **logo**: every column a stack of letters, as tall as that column's
|
|
540
|
+
information content in bits and shared out by residue frequency, most frequent
|
|
541
|
+
on top — the display the MEME Suite and WebLogo draw. A conserved column is one
|
|
542
|
+
tall letter, a variable one a short pile, and the caption gives the scale
|
|
543
|
+
(0 to 2 bits for nucleotides, 0 to 4.3 for amino acids).
|
|
544
|
+
|
|
545
|
+
It summarises **the tips currently on screen**, so entering a clade gives that
|
|
546
|
+
clade's motif rather than the file's, and the caption names how many tips that
|
|
547
|
+
is (`n = 12`). Two consequences worth knowing: gaps are not a letter —
|
|
548
|
+
frequencies are taken over the residues present, and the stack is then scaled
|
|
549
|
+
by the column's occupancy, so a column held up by two sequences out of fifty
|
|
550
|
+
draws short rather than perfectly conserved; and there is **no small-sample
|
|
551
|
+
correction**, because entering a three-tip clade is a normal thing to do and
|
|
552
|
+
Schneider's correction would subtract more than the maximum and leave the
|
|
553
|
+
column blank. Read `n` and judge.
|
|
554
|
+
|
|
531
555
|
Alignments arrive with the tree: as phyloXML `<mol_seq is_aligned="true">`
|
|
532
556
|
elements, or in a **Nexus** file whose characters matrix accompanies its tree.
|
|
533
557
|
The **Nexus** entry in the Download menu writes the current tree *and* its
|
|
534
558
|
alignment back into one Nexus file (Taxa, Characters and Trees blocks).
|
|
535
559
|
|
|
560
|
+
## Heat maps
|
|
561
|
+
|
|
562
|
+
A tree whose tips carry numeric fields shows them as a **heat map** beside the
|
|
563
|
+
tree: one row per tip, one column per field, each cell coloured by its value
|
|
564
|
+
(the columns stand on the tips' common edge; in the **circular** layout that
|
|
565
|
+
edge is a ring, so each column becomes a concentric ring past the labels — the
|
|
566
|
+
unrooted layout has no such edge, and so no heat map). Turn
|
|
567
|
+
it on with the **Heat Map** checkbox under
|
|
568
|
+
Display Data; it is offered whenever the tree has two or more numeric per-tip
|
|
569
|
+
fields.
|
|
570
|
+
|
|
571
|
+
Every column is painted on **one shared scale**, so a colour means the same
|
|
572
|
+
number wherever it appears — that is what makes a block of related columns
|
|
573
|
+
readable as a block, and it is the point of a heat map rather than a row of
|
|
574
|
+
independent stripes. The scale spans the whole tree, so entering a subtree
|
|
575
|
+
narrows the rows and leaves the colours where they were. The columns that
|
|
576
|
+
appear are exactly the numeric fields the **Color by** menu offers, so the two
|
|
577
|
+
agree about what the tree holds; by default their left-to-right order follows
|
|
578
|
+
the order the file lists them in, which keeps a producer's grouping (core genes,
|
|
579
|
+
then resistance, then prophages) intact even where some tips are missing a
|
|
580
|
+
field — see **Order columns** below for the alternatives.
|
|
581
|
+
|
|
582
|
+
A cell **nobody filled in** is drawn as an outlined empty box, never as the
|
|
583
|
+
scale's low end: on a presence/absence matrix, reading a missing field as zero
|
|
584
|
+
states the opposite of what the file says. The key beside the scale names it.
|
|
585
|
+
|
|
586
|
+
**Hover any cell** for its tip, the column and its value — or `not assessed` —
|
|
587
|
+
and the scale it was coloured against. The column names stand under the matrix,
|
|
588
|
+
turned; a matrix with more columns than will fit shows a window and says so
|
|
589
|
+
(`Columns 1–240 of 400`), which the mouse wheel over the matrix scrolls.
|
|
590
|
+
|
|
591
|
+
**Order columns** (in the Heat Map section of the panel) decides where the
|
|
592
|
+
columns go. The two **clustered** orders put columns that behave alike side by
|
|
593
|
+
side and draw the clustering itself as a **dendrogram above the matrix** — a
|
|
594
|
+
clustergram — and one of them is what a heat map opens on, because reading block
|
|
595
|
+
structure is what a matrix beside a tree is for. *As in the input* is there when
|
|
596
|
+
you want the producer's own grouping back — and, because phyloXML gives every
|
|
597
|
+
node its own property list, its tooltip says whether that really is the input's
|
|
598
|
+
order or only the order most tips agree on. Both clustered orders are
|
|
599
|
+
complete-linkage hierarchical clustering, written to give the same answer as
|
|
600
|
+
R's `hclust(dist(t(m)), method = "complete")`; they differ in what "alike" means:
|
|
601
|
+
|
|
602
|
+
* **Clustered (co-occurrence)** uses Euclidean distance, the default of R's
|
|
603
|
+
`pheatmap`, `heatmap.2` and `ComplexHeatmap`.
|
|
604
|
+
* **Clustered (ignoring shared absence)** uses the **Bray–Curtis** dissimilarity
|
|
605
|
+
(R `vegan`'s `vegdist` default). Euclidean distance has the *double-zero
|
|
606
|
+
problem*: two genes both **absent** from the same strains are counted as
|
|
607
|
+
agreeing there, so on a sparse pan-genome the rare genes cluster together
|
|
608
|
+
merely for being rare. Bray–Curtis drops a tip where both columns are 0
|
|
609
|
+
instead of scoring it as agreement. On 0/1 data it is exactly the
|
|
610
|
+
Sørensen–Dice dissimilarity.
|
|
611
|
+
|
|
612
|
+
Which of the two a tree opens on is decided by its values: **Bray–Curtis where
|
|
613
|
+
the matrix has zeros to ignore and nothing negative, Euclidean otherwise**. A
|
|
614
|
+
zero is precisely the precondition for a double zero to exist, and Bray–Curtis is
|
|
615
|
+
meant for values 0 or more — on a matrix of years or coordinates it reads
|
|
616
|
+
magnitude instead of pattern, and negative values leave some pairs with no
|
|
617
|
+
distance at all. Set `heatmapColumnOrder` to override; an explicit choice is
|
|
618
|
+
never re-derived.
|
|
619
|
+
|
|
620
|
+
*Alphabetical* and *Frequency* (highest mean value first, over the tips that
|
|
621
|
+
have a value) are there too, and **Reorder columns…** opens a list you can drag
|
|
622
|
+
(or move with the arrow keys) into any order you like. Doing so sets **Manual**,
|
|
623
|
+
and nothing re-sorts a manual order — a sorting mode that quietly undid your
|
|
624
|
+
arrangement would make the arrangement pointless. *Automatic* in that dialog
|
|
625
|
+
hands the order back to the data. A manual order rides in a shared view, so a
|
|
626
|
+
link reproduces the figure exactly; a column it does not name — one added by an
|
|
627
|
+
edit, or a table joined since — follows the ones it does.
|
|
628
|
+
|
|
629
|
+
Whatever the mode, a blank is never read as 0, and
|
|
630
|
+
the dendrogram is drawn only when it describes the columns actually on screen —
|
|
631
|
+
never over a scrolled window, never over an order that did not come from a
|
|
632
|
+
clustering, and never over the rings, where it would have to bend.
|
|
633
|
+
|
|
634
|
+
Sørensen, T. (1948) *Biol. Skr.* 5, 1–34 · Bray, J.R., Curtis, J.T. (1957)
|
|
635
|
+
*Ecol. Monogr.* 27, 325–349 · Eisen, M.B. *et al.* (1998) *PNAS* 95, 14863–8.
|
|
636
|
+
|
|
637
|
+
Heat-map data arrives with the tree, as phyloXML `<property>` elements on the
|
|
638
|
+
tips (`<property ref="meta:recA" datatype="xsd:integer" applies_to="node">2
|
|
639
|
+
</property>`), or from a **metadata table** joined to it on the open page — a
|
|
640
|
+
table's numeric columns become heat-map columns like any others.
|
|
641
|
+
|
|
536
642
|
## Time trees
|
|
537
643
|
|
|
538
644
|
A tree whose nodes carry phyloXML `<date>` elements is drawn against time.
|
|
@@ -957,6 +1063,10 @@ copy-pastable JSON.
|
|
|
957
1063
|
| `layout` | `'rectangular'` | The starting layout: `'rectangular'`, `'circular'`, or `'unrooted'`. |
|
|
958
1064
|
| `ladderizeTree` | `true` | Ladderize the tree on load: at each node, the larger clade first (any number of children, so a polytomy sorts too). |
|
|
959
1065
|
| `showMsa` | tree-derived | Open with the alignment track shown. Default: on when the tree carries an aligned `mol_seq`, off otherwise — an explicit `true`/`false` overrides that. |
|
|
1066
|
+
| `showMsaLogo` | `false` | Open with the alignment summarised as a sequence logo instead of a conservation bar: each column a stack of letters as tall as its information content, over the tips currently on screen. Only drawn while the alignment track is shown. |
|
|
1067
|
+
| `showHeatmap` | `false` | Open with the heat map shown. Offered whenever the tree carries two or more numeric per-tip fields, but off unless asked for: almost any annotated tree has such fields, so turning it on by itself would be an opinion about the tree rather than a service. |
|
|
1068
|
+
| `heatmapColumnOrder` | tree-derived | How the heat map's columns are ordered: `'document'` (as the file lists them), `'clustered'` (Euclidean), `'clustered-presence'` (Bray–Curtis), `'alphabetical'`, `'frequency'`. The clustered modes also draw the dendrogram. Default: a **clustered** order, with the distance chosen from the values — Bray–Curtis where the matrix has zeros to ignore and nothing negative, Euclidean otherwise. An explicit value always wins and is never re-derived. |
|
|
1069
|
+
| `heatmapManualOrder` | `null` | The heat map's columns in your own order, as an array of property refs (`['meta:recA', 'meta:gyrA', …]`). Only read while `heatmapColumnOrder` is `'manual'`. A ref the tree has not got is ignored, and a column the list does not name follows the ones it does. |
|
|
960
1070
|
| `showDomainArchitectures` | tree-derived | Open with the domain tracks shown. Default: on when any tip carries a `<domain_architecture>`, off otherwise — an explicit `true`/`false` overrides that. |
|
|
961
1071
|
| `domainLabels` | `'domains'` | Where domain names go: `'domains'` (on the boxes), `'legend'` (a card), or `'none'`. |
|
|
962
1072
|
| `domainGlow` | `false` | Open with the glow around each domain box on. |
|
|
@@ -1505,7 +1615,7 @@ Legend fieldset (Show / Dir / four arrows / R) is gone; so is the shift- or
|
|
|
1505
1615
|
alt-click placement it documented. `visualizationsLegendXpos` and
|
|
1506
1616
|
`visualizationsLegendYpos` still set where they start out.
|
|
1507
1617
|
|
|
1508
|
-
## The layouts,
|
|
1618
|
+
## The layouts, tracks and time axes (developer spec)
|
|
1509
1619
|
|
|
1510
1620
|
The 2026 additions beyond the visualization system, specified tightly enough
|
|
1511
1621
|
to rebuild. All pure logic lives in forester.js under `npm test`; the viewer
|
|
@@ -1629,8 +1739,221 @@ which clamps and redraws; the tree never moves. A faint dashed guide runs
|
|
|
1629
1739
|
from each tip's label (or its node, when labels are hidden) across to that
|
|
1630
1740
|
tip's row, so a row reads back to its sequence without counting.
|
|
1631
1741
|
|
|
1632
|
-
The
|
|
1633
|
-
|
|
1742
|
+
**The sequence logo** (`showMsaLogo`, `forester.msaLogo`) replaces the
|
|
1743
|
+
conservation bar and the consensus row rather than joining them: a stack's
|
|
1744
|
+
height *is* the column's conservation and its top letter *is* the consensus,
|
|
1745
|
+
so all three would say one thing three times. Per column the model returns
|
|
1746
|
+
`bits` = `log₂K − H` over the non-gap residues, `occupancy` = non-gap / rows,
|
|
1747
|
+
`height` = `bits × occupancy`, and the letters most frequent first (ties
|
|
1748
|
+
alphabetical, so a figure reproduces). Rows are the **displayed** tips over
|
|
1749
|
+
the visible window, which is what makes entering a clade re-read the summary.
|
|
1750
|
+
**No small-sample correction**: Schneider's `e_n = (K−1)/(2 ln2 · n)` is for a
|
|
1751
|
+
motif sampled from many sequences, and at n = 3 for protein it exceeds the
|
|
1752
|
+
4.32-bit maximum, so a perfectly conserved column of a small clade would draw
|
|
1753
|
+
nothing — the caption names `n` instead.
|
|
1754
|
+
|
|
1755
|
+
Each letter is scaled to fill its slice: `sy = hpx / (ascent + descent)` of
|
|
1756
|
+
**that glyph's** ink box, measured once per character off a canvas, with the
|
|
1757
|
+
baseline placed at `y − descent × sy` so the ink lands inside the slice.
|
|
1758
|
+
Measured off one glyph instead, Q and G hung their descenders through the
|
|
1759
|
+
ruler. Note that an SVG `<text>`'s `getBoundingClientRect` is the *font's*
|
|
1760
|
+
layout box, not the ink, so it cannot check this — `test_trees/msa_logo.html`
|
|
1761
|
+
compares letters and order through the DOM and the ink itself is measured from
|
|
1762
|
+
rendered pixels.
|
|
1763
|
+
|
|
1764
|
+
The conservation bar or logo, the consensus row and the column ruler are a
|
|
1765
|
+
**floating strip** (see the time axes below); the residue rows stay with their
|
|
1766
|
+
tips. The strip's height follows what it holds (`msaBottomReserve()`), and the
|
|
1767
|
+
bottom reserve the fit allows for follows that.
|
|
1768
|
+
|
|
1769
|
+
### The heat map
|
|
1770
|
+
|
|
1771
|
+
Model (`forester.heatmapColumns(tree)` → `{refs:[{ref,label}], min, max}`,
|
|
1772
|
+
pure, in `test/heatmap_test.js`): a column is any candidate that
|
|
1773
|
+
`forester.visualizationCandidates` already calls a **numeric property** — so
|
|
1774
|
+
candidacy is not re-invented here and the refusal rules are inherited — carried
|
|
1775
|
+
by at least one **tip**; an internal node has no row, so a ref only internal
|
|
1776
|
+
nodes hold is not a column and its values never reach the scale. `min`/`max`
|
|
1777
|
+
span every drawn cell of the whole tree. `forester.heatmapValue(node, ref)`
|
|
1778
|
+
returns the number or **null**: null for absent, blank and non-numeric alike,
|
|
1779
|
+
and for a ref a node carries twice, the first.
|
|
1780
|
+
|
|
1781
|
+
Column order is a **consensus** (`forester.heatmapConsensusOrder`), because
|
|
1782
|
+
phyloXML gives every node its own property list: tips can list the same columns
|
|
1783
|
+
in different orders, and on `docs/data/influenza.xml` 2 of 6 do. Each tip votes
|
|
1784
|
+
on the pairs it lists next to each other, the majority direction of each pair
|
|
1785
|
+
becomes an edge, and the columns are read off a topological sort of that graph.
|
|
1786
|
+
|
|
1787
|
+
Two rules this replaced, both wrong in ways that showed:
|
|
1788
|
+
|
|
1789
|
+
* **First appearance** put a core gene at column 40 of 40 — 7 of 100 tips in the
|
|
1790
|
+
pan-genome demo carry no `dnaK` and the first tip is one of them. (Worse, the
|
|
1791
|
+
traversal reaches a flat tree's tips *last*-first, so "first" meant the file's
|
|
1792
|
+
last tip.)
|
|
1793
|
+
* **Mean position** fixed that, and then interleaved blocks no tip carries
|
|
1794
|
+
together: tips holding `A,B` and tips holding `X,Y` put A and X both at
|
|
1795
|
+
position 0, giving `X A Y B` instead of `A B X Y`. No tie-break can mend it —
|
|
1796
|
+
the average is what is wrong, because it compares positions measured on tips
|
|
1797
|
+
that share no frame of reference.
|
|
1798
|
+
|
|
1799
|
+
Only **adjacent** pairs vote, which keeps this linear in the data; every pair
|
|
1800
|
+
would be quadratic in the column count, which is exactly where a wide matrix
|
|
1801
|
+
hurts. Transitivity comes from the sort instead. The cost is that adjacency can
|
|
1802
|
+
manufacture a cycle where all pairs would not (tips `A,B,C` and `C,A` give
|
|
1803
|
+
A→B→C→A), so cycles are broken at the column fewest others wait on rather than
|
|
1804
|
+
assumed away — a column is never dropped because the votes disagreed. Where the
|
|
1805
|
+
graph is silent the old rule still decides: mean position, then the
|
|
1806
|
+
better-attested column, then the ref. None of it depends on the order the tips
|
|
1807
|
+
were reached in, which matters because that order follows the tree's current
|
|
1808
|
+
child arrangement and `ladderizeTree` rewrites it — measured, one file once gave
|
|
1809
|
+
`B A` ladderized and `A B` not.
|
|
1810
|
+
|
|
1811
|
+
`'manual'` is the reader's own order (`forester.heatmapManualOrder`), and the
|
|
1812
|
+
only mode that is **not** normalised first — being left alone is the whole point
|
|
1813
|
+
of it. It is set by the **Reorder columns…** dialog, which drags or arrow-keys a
|
|
1814
|
+
list of the columns as drawn; applying it writes `heatmapManualOrder` and sets
|
|
1815
|
+
the mode, exactly as the desktop's Annotation Fields arrows switch a tab to
|
|
1816
|
+
Manual. (Theirs also chooses which fields are columns and of what type; ours
|
|
1817
|
+
takes every numeric field automatically, so the dialog is about order alone.)
|
|
1818
|
+
Choosing Manual from the menu with nothing arranged yet freezes the order on
|
|
1819
|
+
screen — an empty list would mean "no opinion" and silently re-derive the very
|
|
1820
|
+
order the reader asked to keep. A manual order offers no dendrogram, because
|
|
1821
|
+
nothing clustered it.
|
|
1822
|
+
|
|
1823
|
+
The mode a tree opens on, when the caller did not say, is
|
|
1824
|
+
`forester.heatmapDefaultOrder`: `'clustered-presence'` when some value is 0 and
|
|
1825
|
+
none is negative, `'clustered'` otherwise, `'document'` when there are no values
|
|
1826
|
+
at all. Measured, which is why the rule is not simply "always Bray–Curtis": on a
|
|
1827
|
+
latitude/longitude matrix 2 of 6 pairs come out `+Infinity` (no distance, so
|
|
1828
|
+
they join last arbitrarily), and a year column among small ones sits at 0.9995
|
|
1829
|
+
from every one of them — maximally distant for being large rather than for any
|
|
1830
|
+
pattern, while *within* a block of comparable magnitude Bray–Curtis is exactly
|
|
1831
|
+
right. The desktop fixes its default at Euclidean instead, which is consistent
|
|
1832
|
+
with its columns being hand-picked where ours are every numeric field the tree
|
|
1833
|
+
carries. The resolved choice is cached on the model, never written back into the
|
|
1834
|
+
state, so the next tree in a multi-tree file does not inherit this one's answer.
|
|
1835
|
+
|
|
1836
|
+
Gate (`heatmapShown`): `showHeatmap` state (default **false**) AND **not** the
|
|
1837
|
+
unrooted layout AND at least `HEATMAP_MIN_COLUMNS` (2) columns with values —
|
|
1838
|
+
one column is a stripe, not a matrix. Rectangular draws the track, circular the
|
|
1839
|
+
rings (`heatmapCircular`).
|
|
1840
|
+
|
|
1841
|
+
Geometry: the matrix reserves `HEATMAP_TRACK_GAP(8) + band` from `_w`, where
|
|
1842
|
+
`band = min(columns × 14 px, clamp(viewportWidth × 0.45, 60 px, whatever leaves
|
|
1843
|
+
the tree ≥ 220 px))`. It is budgeted **before** the alignment, and the
|
|
1844
|
+
alignment's own band then yields to it: the matrix wants a fixed, finite width
|
|
1845
|
+
while the alignment's band is a window that scrolls and so loses nothing by
|
|
1846
|
+
giving way. (Budgeted the other way round, an 18-column matrix got a 60 px
|
|
1847
|
+
sliver.) It sits between the domain tracks and the alignment, its right edge at
|
|
1848
|
+
`displayWidth − rootOffset − msaReserve`. Rows tile the cluster height by the
|
|
1849
|
+
same once-derived midpoints the alignment uses. Column width is
|
|
1850
|
+
`clamp(band / columns, 3, 14)`; past `band / 3` columns the matrix shows a
|
|
1851
|
+
window, scrolled by the wheel and stated in the caption.
|
|
1852
|
+
|
|
1853
|
+
Colour: `d3.scaleLinear().range(VIS_COLOR_RAMP).domain([min, mid, max])` — the
|
|
1854
|
+
same 3-stop viridis the numeric visualizations use — built once and kept with
|
|
1855
|
+
the model, so every redraw and every view paints the same colours. A degenerate
|
|
1856
|
+
range (one value everywhere) takes the ramp's middle stop rather than mapping a
|
|
1857
|
+
zero-width domain. Same-colour runs merge into single rects.
|
|
1858
|
+
|
|
1859
|
+
A blank cell is the background **with an outline**, and a run of them stays a
|
|
1860
|
+
run of cells wherever a column is at least 7 px wide. Leaving a blank bare is
|
|
1861
|
+
not enough: measured on the sparse demo, a bare blank stands at a contrast
|
|
1862
|
+
ratio of 13.4 against the scale's low end in the light theme but **1.08** in
|
|
1863
|
+
the dark one — that is to say, in the dark theme "not assessed" and "zero" were
|
|
1864
|
+
the same picture.
|
|
1865
|
+
|
|
1866
|
+
**Column order** (`forester.heatmapOrder(tree, columns, mode)` →
|
|
1867
|
+
`{columns, dendrogram}`, pure, in `test/clustering_test.js`): the desktop's
|
|
1868
|
+
*View → Order Matrix Columns*, minus its Manual mode, which means "the order you
|
|
1869
|
+
dragged the rows into" and there is no drag-to-reorder dialog here. Every
|
|
1870
|
+
data-driven mode first normalises to **document order**
|
|
1871
|
+
(`forester.heatmapInDocumentOrder`, the same consensus over exactly the columns
|
|
1872
|
+
given, with columns no tip carries at the end) so a result cannot depend on the
|
|
1873
|
+
order the columns happened to be in. Measured: without it, the 6×6 linkage
|
|
1874
|
+
fixture fed in reverse clustered to `h6,h5,h3,h4,h2,h1` instead of R's
|
|
1875
|
+
`h6,h3,h5,h4,h1,h2` — the index order drives the tie-break and the leaf order,
|
|
1876
|
+
so the incoming order really does leak through. The normaliser deliberately does
|
|
1877
|
+
**not** go through `heatmapColumns`: that applies candidacy rules (a constant
|
|
1878
|
+
column is refused), and ordering must not move with them.
|
|
1879
|
+
|
|
1880
|
+
Distances are `forester.heatmapEuclideanDistances` (R's `dist` convention for
|
|
1881
|
+
missing values: pairwise deletion, the squared sum scaled up by `tips / used`;
|
|
1882
|
+
a pair sharing no assessed tip is `+Infinity`, where R returns `NA` and its
|
|
1883
|
+
`hclust` then refuses) and `forester.heatmapBrayCurtisDistances`
|
|
1884
|
+
(`sum|x−y| / sum(x+y)` over the jointly assessed tips — being a ratio it needs
|
|
1885
|
+
no scale-up; three cases `vegdist` cannot answer are decided so clustering never
|
|
1886
|
+
sees a `NaN`: no shared tip → `+Infinity`, 0 at every shared tip → 0, a
|
|
1887
|
+
non-positive total with columns that differ → `+Infinity`).
|
|
1888
|
+
`forester.heatmapCompleteLinkage` returns R's `hclust` object — `$merge` node
|
|
1889
|
+
ids (negative = singleton, positive = the cluster formed at that stage), the
|
|
1890
|
+
`$height` each merge happened at, and `$order` — reproducing R's tie-break
|
|
1891
|
+
(first pair the `(i, j)` scan finds) and `hcass2` leaf order.
|
|
1892
|
+
|
|
1893
|
+
**The expectations are R's own output**, R 4.5.3 and vegan 2.7-2, carried over
|
|
1894
|
+
from the desktop's `MatrixColumnOrderTest`, which generated them. Pinning to R
|
|
1895
|
+
pins the JS and the desktop to each other, which is the point: the two must
|
|
1896
|
+
cluster a matrix identically.
|
|
1897
|
+
|
|
1898
|
+
The dendrogram is drawn above the grid in the tree's own group, so it zooms,
|
|
1899
|
+
pans and exports with the cells. Heights are **linear in the merge distance**,
|
|
1900
|
+
so a block that joins low really is drawn tighter than one that joins high;
|
|
1901
|
+
spacing merges evenly by rank would read more clearly and would say something
|
|
1902
|
+
false. A merge with no finite height goes to the top of the band, above every
|
|
1903
|
+
measurable merge, which is where it belongs: it joined last. It is drawn only
|
|
1904
|
+
when the dendrogram's leaves ARE the columns on screen — never over a scrolled
|
|
1905
|
+
**window**, whose merges would reach columns the reader cannot see.
|
|
1906
|
+
|
|
1907
|
+
Its band is reserved **inside** the layout (the rows are laid out in a span
|
|
1908
|
+
shorter by the band and every node moves down by it), not taken off the canvas
|
|
1909
|
+
and made up for by the fit: a fit happens once, and switching the order is a
|
|
1910
|
+
redraw, so the fit route left the dendrogram 34 px above the top of the window.
|
|
1911
|
+
|
|
1912
|
+
**Circular** (`drawHeatmapRings`) is the same matrix in polar form: each column
|
|
1913
|
+
a concentric **ring** starting at `maxRad + tipLabelSpace + 7`, each cell an arc
|
|
1914
|
+
over that tip's own angular slice, with the slice boundaries derived once
|
|
1915
|
+
between neighbours exactly as the rows are. Ring thickness is
|
|
1916
|
+
`clamp(maxRad × 1.2 / columns, 1, 14)` — capped against the tree's own radius,
|
|
1917
|
+
so a wide matrix cannot turn the tree into a dot at the middle of a dartboard —
|
|
1918
|
+
and `fitRadialExtent` allows for the stack. Same-colour neighbours merge into
|
|
1919
|
+
one sector, which is what keeps a big fan drawable (281 paths for 18 × 50
|
|
1920
|
+
cells on the sparse demo).
|
|
1921
|
+
|
|
1922
|
+
Each ring carries its name **tangentially at the fan's seam**, the gap the
|
|
1923
|
+
layout already leaves between the last tip and the first: the one place a label
|
|
1924
|
+
can sit without covering a cell. Tangentially means rotated by the angle
|
|
1925
|
+
itself — `polarXY` puts angle *a* at screen direction *a* − 90°, so rotating by
|
|
1926
|
+
*a* − 90 instead lays every name out along the seam, where the rings are one
|
|
1927
|
+
ring-width apart and the names several times that. Measured before the fix: 44
|
|
1928
|
+
overlapping pairs out of 18 names.
|
|
1929
|
+
|
|
1930
|
+
The scale becomes a **corner card** (`drawHeatmapRingLegend`, bottom-left,
|
|
1931
|
+
screen coordinates so it neither turns nor scales with the fan), because a ring
|
|
1932
|
+
has no "under" to hang a strip from. There is no dendrogram: it would have to
|
|
1933
|
+
bend, and the desktop makes the same call. **Unrooted gets no heat map at all** —
|
|
1934
|
+
every tip there ends at its own radius, so a column has no ring to be. Hover is
|
|
1935
|
+
the same two questions in polar form: which ring, and which slice, the slice
|
|
1936
|
+
compared modulo a full turn because the fan rotates.
|
|
1937
|
+
|
|
1938
|
+
**Where the readout lands** is one function, `placeHoverReadout(el, event)`,
|
|
1939
|
+
shared by the heat map, the alignment track and the node tooltip. It sets the
|
|
1940
|
+
text, then measures the element — `.aptx-tip` is `width:max-content`, so its
|
|
1941
|
+
size is whatever it is currently saying — and offers it at `pageX + 14`,
|
|
1942
|
+
`pageY + 14`, flipping to the other side of the pointer on either axis only
|
|
1943
|
+
where that would leave the window (clamped to the edge if neither side fits).
|
|
1944
|
+
The heat map and the alignment both used to guess instead, shifting left by a
|
|
1945
|
+
hard-coded 280 px against a readout 181 px wide: since both tracks hug the right
|
|
1946
|
+
edge, every cell flipped and every readout floated ~95 px clear of the cell it
|
|
1947
|
+
described. `test_trees/heatmap_tip.html` pins it, in both layouts, and fails
|
|
1948
|
+
unless all four placements were exercised.
|
|
1949
|
+
|
|
1950
|
+
In the rectangular layout the turned column names, the gradient, its two
|
|
1951
|
+
numbers, the blank key and the caption are a **floating strip**; its reserve adds `MSA_NAV_RESERVE` while the
|
|
1952
|
+
alignment is shown, or the alignment's navigation bar (fixed at the viewport
|
|
1953
|
+
bottom) covers the scale.
|
|
1954
|
+
|
|
1955
|
+
Everything is plain rects, lines, text and one `<linearGradient>`, so the SVG,
|
|
1956
|
+
PDF and PNG exports match the screen.
|
|
1634
1957
|
|
|
1635
1958
|
### The time axes
|
|
1636
1959
|
|
package/archaeopteryx.d.ts
CHANGED
|
@@ -83,7 +83,25 @@ export interface ArchaeopteryxConfig {
|
|
|
83
83
|
rootOffset?: number;
|
|
84
84
|
searchAinitialValue?: string | null;
|
|
85
85
|
searchBinitialValue?: string | null;
|
|
86
|
+
/** Open with the heat map shown. Default false: it is offered whenever
|
|
87
|
+
* the tree has two or more numeric per-tip fields, which is most annotated
|
|
88
|
+
* trees, so it waits to be asked for. */
|
|
89
|
+
/** How the heat map's columns are ordered. The clustered modes also draw
|
|
90
|
+
* the dendrogram above the matrix. Default: a clustered order with the
|
|
91
|
+
* distance chosen from the values — Bray–Curtis where the matrix has zeros
|
|
92
|
+
* to ignore and nothing negative, Euclidean otherwise. An explicit value
|
|
93
|
+
* always wins and is never re-derived. */
|
|
94
|
+
heatmapColumnOrder?: 'document' | 'clustered' | 'clustered-presence' | 'alphabetical' | 'frequency' | 'manual';
|
|
95
|
+
/** The heat map's columns in your own order, as property refs. Only read
|
|
96
|
+
* while heatmapColumnOrder is 'manual'. A ref the tree has not got is
|
|
97
|
+
* ignored; a column this does not name follows the ones it does. */
|
|
98
|
+
heatmapManualOrder?: string[] | null;
|
|
99
|
+
showHeatmap?: boolean;
|
|
86
100
|
showMsa?: boolean;
|
|
101
|
+
/** Summarize the alignment as a sequence logo instead of a conservation
|
|
102
|
+
* bar: each column a stack of letters as tall as its information content,
|
|
103
|
+
* over the tips currently on screen. Only drawn with the track shown. */
|
|
104
|
+
showMsaLogo?: boolean;
|
|
87
105
|
showSupportDots?: boolean;
|
|
88
106
|
showTimeAxis?: boolean;
|
|
89
107
|
supportDotMinimum?: number;
|
|
@@ -136,6 +154,11 @@ export interface ViewState {
|
|
|
136
154
|
rotation?: number;
|
|
137
155
|
horizontalLabels?: boolean;
|
|
138
156
|
msa?: boolean;
|
|
157
|
+
/** The alignment summarized as a sequence logo. */
|
|
158
|
+
msaLogo?: boolean;
|
|
159
|
+
heatmap?: boolean;
|
|
160
|
+
heatmapOrder?: 'document' | 'clustered' | 'clustered-presence' | 'alphabetical' | 'frequency' | 'manual';
|
|
161
|
+
heatmapManual?: string[];
|
|
139
162
|
domains?: boolean;
|
|
140
163
|
domainLabels?: 'none' | 'domains' | 'legend';
|
|
141
164
|
domainGlow?: boolean;
|