archaeopteryx 3.9.0 → 3.11.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 +280 -4
- package/archaeopteryx.d.ts +17 -0
- package/archaeopteryx.js +1474 -30
- package/forester.js +1017 -15
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -33,6 +33,7 @@ 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)
|
|
@@ -220,7 +221,7 @@ Subtree** opens everything below a node; the tool row's uncollapse-all button
|
|
|
220
221
|
(the desktop's glyph, lit only while something is collapsed) opens the whole
|
|
221
222
|
tree, and so does **Esc**. The wedge has its apex at the node, one edge
|
|
222
223
|
reaching the clade's nearest tip and the other its farthest, so the shape
|
|
223
|
-
shows how uneven the clade's branch lengths are
|
|
224
|
+
shows how uneven the clade's branch lengths are (one depth
|
|
224
225
|
step in a cladogram). Its label stands where a tip's would: on the label
|
|
225
226
|
column in the aligned phylogram and on the outer ring in circular, with the
|
|
226
227
|
same guide line; it is filled in the colour most of its tips wear under the current
|
|
@@ -370,8 +371,9 @@ Date" comes back as `Collection Date` in every menu; a header that already
|
|
|
370
371
|
reads as `namespace:name` is kept as it is). From there nothing is special:
|
|
371
372
|
the columns are offered for **Color-by** and **Shape** by the same rules as
|
|
372
373
|
any property, with the same legends; they are **search** fields, typed
|
|
373
|
-
numeric when every filled cell is a number;
|
|
374
|
-
|
|
374
|
+
numeric when every filled cell is a number; the numeric ones become **heat
|
|
375
|
+
map** columns; they appear in the **node data**; and they are written into a
|
|
376
|
+
phyloXML export, so a saved tree keeps them.
|
|
375
377
|
Tip names are matched exactly, then case-insensitively; empty cells add
|
|
376
378
|
nothing; a column the tree already carries under the same ref is replaced by
|
|
377
379
|
the table's values. Quoted cells, `#` comment lines and Windows line ends are
|
|
@@ -533,6 +535,88 @@ elements, or in a **Nexus** file whose characters matrix accompanies its tree.
|
|
|
533
535
|
The **Nexus** entry in the Download menu writes the current tree *and* its
|
|
534
536
|
alignment back into one Nexus file (Taxa, Characters and Trees blocks).
|
|
535
537
|
|
|
538
|
+
## Heat maps
|
|
539
|
+
|
|
540
|
+
A tree whose tips carry numeric fields shows them as a **heat map** beside the
|
|
541
|
+
tree: one row per tip, one column per field, each cell coloured by its value
|
|
542
|
+
(the columns stand on the tips' common edge; in the **circular** layout that
|
|
543
|
+
edge is a ring, so each column becomes a concentric ring past the labels — the
|
|
544
|
+
unrooted layout has no such edge, and so no heat map). Turn
|
|
545
|
+
it on with the **Heat Map** checkbox under
|
|
546
|
+
Display Data; it is offered whenever the tree has two or more numeric per-tip
|
|
547
|
+
fields.
|
|
548
|
+
|
|
549
|
+
Every column is painted on **one shared scale**, so a colour means the same
|
|
550
|
+
number wherever it appears — that is what makes a block of related columns
|
|
551
|
+
readable as a block, and it is the point of a heat map rather than a row of
|
|
552
|
+
independent stripes. The scale spans the whole tree, so entering a subtree
|
|
553
|
+
narrows the rows and leaves the colours where they were. The columns that
|
|
554
|
+
appear are exactly the numeric fields the **Color by** menu offers, so the two
|
|
555
|
+
agree about what the tree holds; by default their left-to-right order follows
|
|
556
|
+
the order the file lists them in, which keeps a producer's grouping (core genes,
|
|
557
|
+
then resistance, then prophages) intact even where some tips are missing a
|
|
558
|
+
field — see **Order columns** below for the alternatives.
|
|
559
|
+
|
|
560
|
+
A cell **nobody filled in** is drawn as an outlined empty box, never as the
|
|
561
|
+
scale's low end: on a presence/absence matrix, reading a missing field as zero
|
|
562
|
+
states the opposite of what the file says. The key beside the scale names it.
|
|
563
|
+
|
|
564
|
+
**Hover any cell** for its tip, the column and its value — or `not assessed` —
|
|
565
|
+
and the scale it was coloured against. The column names stand under the matrix,
|
|
566
|
+
turned; a matrix with more columns than will fit shows a window and says so
|
|
567
|
+
(`Columns 1–240 of 400`), which the mouse wheel over the matrix scrolls.
|
|
568
|
+
|
|
569
|
+
**Order columns** (in the Heat Map section of the panel) decides where the
|
|
570
|
+
columns go. The two **clustered** orders put columns that behave alike side by
|
|
571
|
+
side and draw the clustering itself as a **dendrogram above the matrix** — a
|
|
572
|
+
clustergram — and one of them is what a heat map opens on, because reading block
|
|
573
|
+
structure is what a matrix beside a tree is for. *As in the input* is there when
|
|
574
|
+
you want the producer's own grouping back — and, because phyloXML gives every
|
|
575
|
+
node its own property list, its tooltip says whether that really is the input's
|
|
576
|
+
order or only the order most tips agree on. Both clustered orders are
|
|
577
|
+
complete-linkage hierarchical clustering, written to give the same answer as
|
|
578
|
+
R's `hclust(dist(t(m)), method = "complete")`; they differ in what "alike" means:
|
|
579
|
+
|
|
580
|
+
* **Clustered (co-occurrence)** uses Euclidean distance, the default of R's
|
|
581
|
+
`pheatmap`, `heatmap.2` and `ComplexHeatmap`.
|
|
582
|
+
* **Clustered (ignoring shared absence)** uses the **Bray–Curtis** dissimilarity
|
|
583
|
+
(R `vegan`'s `vegdist` default). Euclidean distance has the *double-zero
|
|
584
|
+
problem*: two genes both **absent** from the same strains are counted as
|
|
585
|
+
agreeing there, so on a sparse pan-genome the rare genes cluster together
|
|
586
|
+
merely for being rare. Bray–Curtis drops a tip where both columns are 0
|
|
587
|
+
instead of scoring it as agreement. On 0/1 data it is exactly the
|
|
588
|
+
Sørensen–Dice dissimilarity.
|
|
589
|
+
|
|
590
|
+
Which of the two a tree opens on is decided by its values: **Bray–Curtis where
|
|
591
|
+
the matrix has zeros to ignore and nothing negative, Euclidean otherwise**. A
|
|
592
|
+
zero is precisely the precondition for a double zero to exist, and Bray–Curtis is
|
|
593
|
+
meant for values 0 or more — on a matrix of years or coordinates it reads
|
|
594
|
+
magnitude instead of pattern, and negative values leave some pairs with no
|
|
595
|
+
distance at all. Set `heatmapColumnOrder` to override; an explicit choice is
|
|
596
|
+
never re-derived.
|
|
597
|
+
|
|
598
|
+
*Alphabetical* and *Frequency* (highest mean value first, over the tips that
|
|
599
|
+
have a value) are there too, and **Reorder columns…** opens a list you can drag
|
|
600
|
+
(or move with the arrow keys) into any order you like. Doing so sets **Manual**,
|
|
601
|
+
and nothing re-sorts a manual order — a sorting mode that quietly undid your
|
|
602
|
+
arrangement would make the arrangement pointless. *Automatic* in that dialog
|
|
603
|
+
hands the order back to the data. A manual order rides in a shared view, so a
|
|
604
|
+
link reproduces the figure exactly; a column it does not name — one added by an
|
|
605
|
+
edit, or a table joined since — follows the ones it does.
|
|
606
|
+
|
|
607
|
+
Whatever the mode, a blank is never read as 0, and
|
|
608
|
+
the dendrogram is drawn only when it describes the columns actually on screen —
|
|
609
|
+
never over a scrolled window, never over an order that did not come from a
|
|
610
|
+
clustering, and never over the rings, where it would have to bend.
|
|
611
|
+
|
|
612
|
+
Sørensen, T. (1948) *Biol. Skr.* 5, 1–34 · Bray, J.R., Curtis, J.T. (1957)
|
|
613
|
+
*Ecol. Monogr.* 27, 325–349 · Eisen, M.B. *et al.* (1998) *PNAS* 95, 14863–8.
|
|
614
|
+
|
|
615
|
+
Heat-map data arrives with the tree, as phyloXML `<property>` elements on the
|
|
616
|
+
tips (`<property ref="meta:recA" datatype="xsd:integer" applies_to="node">2
|
|
617
|
+
</property>`), or from a **metadata table** joined to it on the open page — a
|
|
618
|
+
table's numeric columns become heat-map columns like any others.
|
|
619
|
+
|
|
536
620
|
## Time trees
|
|
537
621
|
|
|
538
622
|
A tree whose nodes carry phyloXML `<date>` elements is drawn against time.
|
|
@@ -957,6 +1041,9 @@ copy-pastable JSON.
|
|
|
957
1041
|
| `layout` | `'rectangular'` | The starting layout: `'rectangular'`, `'circular'`, or `'unrooted'`. |
|
|
958
1042
|
| `ladderizeTree` | `true` | Ladderize the tree on load: at each node, the larger clade first (any number of children, so a polytomy sorts too). |
|
|
959
1043
|
| `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. |
|
|
1044
|
+
| `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. |
|
|
1045
|
+
| `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. |
|
|
1046
|
+
| `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
1047
|
| `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
1048
|
| `domainLabels` | `'domains'` | Where domain names go: `'domains'` (on the boxes), `'legend'` (a card), or `'none'`. |
|
|
962
1049
|
| `domainGlow` | `false` | Open with the glow around each domain box on. |
|
|
@@ -1505,7 +1592,7 @@ Legend fieldset (Show / Dir / four arrows / R) is gone; so is the shift- or
|
|
|
1505
1592
|
alt-click placement it documented. `visualizationsLegendXpos` and
|
|
1506
1593
|
`visualizationsLegendYpos` still set where they start out.
|
|
1507
1594
|
|
|
1508
|
-
## The layouts,
|
|
1595
|
+
## The layouts, tracks and time axes (developer spec)
|
|
1509
1596
|
|
|
1510
1597
|
The 2026 additions beyond the visualization system, specified tightly enough
|
|
1511
1598
|
to rebuild. All pure logic lives in forester.js under `npm test`; the viewer
|
|
@@ -1632,6 +1719,195 @@ tip's row, so a row reads back to its sequence without counting.
|
|
|
1632
1719
|
The conservation bar, consensus row and column ruler are a **floating strip**
|
|
1633
1720
|
(see the time axes below); the residue rows stay with their tips.
|
|
1634
1721
|
|
|
1722
|
+
### The heat map
|
|
1723
|
+
|
|
1724
|
+
Model (`forester.heatmapColumns(tree)` → `{refs:[{ref,label}], min, max}`,
|
|
1725
|
+
pure, in `test/heatmap_test.js`): a column is any candidate that
|
|
1726
|
+
`forester.visualizationCandidates` already calls a **numeric property** — so
|
|
1727
|
+
candidacy is not re-invented here and the refusal rules are inherited — carried
|
|
1728
|
+
by at least one **tip**; an internal node has no row, so a ref only internal
|
|
1729
|
+
nodes hold is not a column and its values never reach the scale. `min`/`max`
|
|
1730
|
+
span every drawn cell of the whole tree. `forester.heatmapValue(node, ref)`
|
|
1731
|
+
returns the number or **null**: null for absent, blank and non-numeric alike,
|
|
1732
|
+
and for a ref a node carries twice, the first.
|
|
1733
|
+
|
|
1734
|
+
Column order is a **consensus** (`forester.heatmapConsensusOrder`), because
|
|
1735
|
+
phyloXML gives every node its own property list: tips can list the same columns
|
|
1736
|
+
in different orders, and on `docs/data/influenza.xml` 2 of 6 do. Each tip votes
|
|
1737
|
+
on the pairs it lists next to each other, the majority direction of each pair
|
|
1738
|
+
becomes an edge, and the columns are read off a topological sort of that graph.
|
|
1739
|
+
|
|
1740
|
+
Two rules this replaced, both wrong in ways that showed:
|
|
1741
|
+
|
|
1742
|
+
* **First appearance** put a core gene at column 40 of 40 — 7 of 100 tips in the
|
|
1743
|
+
pan-genome demo carry no `dnaK` and the first tip is one of them. (Worse, the
|
|
1744
|
+
traversal reaches a flat tree's tips *last*-first, so "first" meant the file's
|
|
1745
|
+
last tip.)
|
|
1746
|
+
* **Mean position** fixed that, and then interleaved blocks no tip carries
|
|
1747
|
+
together: tips holding `A,B` and tips holding `X,Y` put A and X both at
|
|
1748
|
+
position 0, giving `X A Y B` instead of `A B X Y`. No tie-break can mend it —
|
|
1749
|
+
the average is what is wrong, because it compares positions measured on tips
|
|
1750
|
+
that share no frame of reference.
|
|
1751
|
+
|
|
1752
|
+
Only **adjacent** pairs vote, which keeps this linear in the data; every pair
|
|
1753
|
+
would be quadratic in the column count, which is exactly where a wide matrix
|
|
1754
|
+
hurts. Transitivity comes from the sort instead. The cost is that adjacency can
|
|
1755
|
+
manufacture a cycle where all pairs would not (tips `A,B,C` and `C,A` give
|
|
1756
|
+
A→B→C→A), so cycles are broken at the column fewest others wait on rather than
|
|
1757
|
+
assumed away — a column is never dropped because the votes disagreed. Where the
|
|
1758
|
+
graph is silent the old rule still decides: mean position, then the
|
|
1759
|
+
better-attested column, then the ref. None of it depends on the order the tips
|
|
1760
|
+
were reached in, which matters because that order follows the tree's current
|
|
1761
|
+
child arrangement and `ladderizeTree` rewrites it — measured, one file once gave
|
|
1762
|
+
`B A` ladderized and `A B` not.
|
|
1763
|
+
|
|
1764
|
+
`'manual'` is the reader's own order (`forester.heatmapManualOrder`), and the
|
|
1765
|
+
only mode that is **not** normalised first — being left alone is the whole point
|
|
1766
|
+
of it. It is set by the **Reorder columns…** dialog, which drags or arrow-keys a
|
|
1767
|
+
list of the columns as drawn; applying it writes `heatmapManualOrder` and sets
|
|
1768
|
+
the mode, exactly as the desktop's Annotation Fields arrows switch a tab to
|
|
1769
|
+
Manual. (Theirs also chooses which fields are columns and of what type; ours
|
|
1770
|
+
takes every numeric field automatically, so the dialog is about order alone.)
|
|
1771
|
+
Choosing Manual from the menu with nothing arranged yet freezes the order on
|
|
1772
|
+
screen — an empty list would mean "no opinion" and silently re-derive the very
|
|
1773
|
+
order the reader asked to keep. A manual order offers no dendrogram, because
|
|
1774
|
+
nothing clustered it.
|
|
1775
|
+
|
|
1776
|
+
The mode a tree opens on, when the caller did not say, is
|
|
1777
|
+
`forester.heatmapDefaultOrder`: `'clustered-presence'` when some value is 0 and
|
|
1778
|
+
none is negative, `'clustered'` otherwise, `'document'` when there are no values
|
|
1779
|
+
at all. Measured, which is why the rule is not simply "always Bray–Curtis": on a
|
|
1780
|
+
latitude/longitude matrix 2 of 6 pairs come out `+Infinity` (no distance, so
|
|
1781
|
+
they join last arbitrarily), and a year column among small ones sits at 0.9995
|
|
1782
|
+
from every one of them — maximally distant for being large rather than for any
|
|
1783
|
+
pattern, while *within* a block of comparable magnitude Bray–Curtis is exactly
|
|
1784
|
+
right. The desktop fixes its default at Euclidean instead, which is consistent
|
|
1785
|
+
with its columns being hand-picked where ours are every numeric field the tree
|
|
1786
|
+
carries. The resolved choice is cached on the model, never written back into the
|
|
1787
|
+
state, so the next tree in a multi-tree file does not inherit this one's answer.
|
|
1788
|
+
|
|
1789
|
+
Gate (`heatmapShown`): `showHeatmap` state (default **false**) AND **not** the
|
|
1790
|
+
unrooted layout AND at least `HEATMAP_MIN_COLUMNS` (2) columns with values —
|
|
1791
|
+
one column is a stripe, not a matrix. Rectangular draws the track, circular the
|
|
1792
|
+
rings (`heatmapCircular`).
|
|
1793
|
+
|
|
1794
|
+
Geometry: the matrix reserves `HEATMAP_TRACK_GAP(8) + band` from `_w`, where
|
|
1795
|
+
`band = min(columns × 14 px, clamp(viewportWidth × 0.45, 60 px, whatever leaves
|
|
1796
|
+
the tree ≥ 220 px))`. It is budgeted **before** the alignment, and the
|
|
1797
|
+
alignment's own band then yields to it: the matrix wants a fixed, finite width
|
|
1798
|
+
while the alignment's band is a window that scrolls and so loses nothing by
|
|
1799
|
+
giving way. (Budgeted the other way round, an 18-column matrix got a 60 px
|
|
1800
|
+
sliver.) It sits between the domain tracks and the alignment, its right edge at
|
|
1801
|
+
`displayWidth − rootOffset − msaReserve`. Rows tile the cluster height by the
|
|
1802
|
+
same once-derived midpoints the alignment uses. Column width is
|
|
1803
|
+
`clamp(band / columns, 3, 14)`; past `band / 3` columns the matrix shows a
|
|
1804
|
+
window, scrolled by the wheel and stated in the caption.
|
|
1805
|
+
|
|
1806
|
+
Colour: `d3.scaleLinear().range(VIS_COLOR_RAMP).domain([min, mid, max])` — the
|
|
1807
|
+
same 3-stop viridis the numeric visualizations use — built once and kept with
|
|
1808
|
+
the model, so every redraw and every view paints the same colours. A degenerate
|
|
1809
|
+
range (one value everywhere) takes the ramp's middle stop rather than mapping a
|
|
1810
|
+
zero-width domain. Same-colour runs merge into single rects.
|
|
1811
|
+
|
|
1812
|
+
A blank cell is the background **with an outline**, and a run of them stays a
|
|
1813
|
+
run of cells wherever a column is at least 7 px wide. Leaving a blank bare is
|
|
1814
|
+
not enough: measured on the sparse demo, a bare blank stands at a contrast
|
|
1815
|
+
ratio of 13.4 against the scale's low end in the light theme but **1.08** in
|
|
1816
|
+
the dark one — that is to say, in the dark theme "not assessed" and "zero" were
|
|
1817
|
+
the same picture.
|
|
1818
|
+
|
|
1819
|
+
**Column order** (`forester.heatmapOrder(tree, columns, mode)` →
|
|
1820
|
+
`{columns, dendrogram}`, pure, in `test/clustering_test.js`): the desktop's
|
|
1821
|
+
*View → Order Matrix Columns*, minus its Manual mode, which means "the order you
|
|
1822
|
+
dragged the rows into" and there is no drag-to-reorder dialog here. Every
|
|
1823
|
+
data-driven mode first normalises to **document order**
|
|
1824
|
+
(`forester.heatmapInDocumentOrder`, the same consensus over exactly the columns
|
|
1825
|
+
given, with columns no tip carries at the end) so a result cannot depend on the
|
|
1826
|
+
order the columns happened to be in. Measured: without it, the 6×6 linkage
|
|
1827
|
+
fixture fed in reverse clustered to `h6,h5,h3,h4,h2,h1` instead of R's
|
|
1828
|
+
`h6,h3,h5,h4,h1,h2` — the index order drives the tie-break and the leaf order,
|
|
1829
|
+
so the incoming order really does leak through. The normaliser deliberately does
|
|
1830
|
+
**not** go through `heatmapColumns`: that applies candidacy rules (a constant
|
|
1831
|
+
column is refused), and ordering must not move with them.
|
|
1832
|
+
|
|
1833
|
+
Distances are `forester.heatmapEuclideanDistances` (R's `dist` convention for
|
|
1834
|
+
missing values: pairwise deletion, the squared sum scaled up by `tips / used`;
|
|
1835
|
+
a pair sharing no assessed tip is `+Infinity`, where R returns `NA` and its
|
|
1836
|
+
`hclust` then refuses) and `forester.heatmapBrayCurtisDistances`
|
|
1837
|
+
(`sum|x−y| / sum(x+y)` over the jointly assessed tips — being a ratio it needs
|
|
1838
|
+
no scale-up; three cases `vegdist` cannot answer are decided so clustering never
|
|
1839
|
+
sees a `NaN`: no shared tip → `+Infinity`, 0 at every shared tip → 0, a
|
|
1840
|
+
non-positive total with columns that differ → `+Infinity`).
|
|
1841
|
+
`forester.heatmapCompleteLinkage` returns R's `hclust` object — `$merge` node
|
|
1842
|
+
ids (negative = singleton, positive = the cluster formed at that stage), the
|
|
1843
|
+
`$height` each merge happened at, and `$order` — reproducing R's tie-break
|
|
1844
|
+
(first pair the `(i, j)` scan finds) and `hcass2` leaf order.
|
|
1845
|
+
|
|
1846
|
+
**The expectations are R's own output**, R 4.5.3 and vegan 2.7-2, carried over
|
|
1847
|
+
from the desktop's `MatrixColumnOrderTest`, which generated them. Pinning to R
|
|
1848
|
+
pins the JS and the desktop to each other, which is the point: the two must
|
|
1849
|
+
cluster a matrix identically.
|
|
1850
|
+
|
|
1851
|
+
The dendrogram is drawn above the grid in the tree's own group, so it zooms,
|
|
1852
|
+
pans and exports with the cells. Heights are **linear in the merge distance**,
|
|
1853
|
+
so a block that joins low really is drawn tighter than one that joins high;
|
|
1854
|
+
spacing merges evenly by rank would read more clearly and would say something
|
|
1855
|
+
false. A merge with no finite height goes to the top of the band, above every
|
|
1856
|
+
measurable merge, which is where it belongs: it joined last. It is drawn only
|
|
1857
|
+
when the dendrogram's leaves ARE the columns on screen — never over a scrolled
|
|
1858
|
+
**window**, whose merges would reach columns the reader cannot see.
|
|
1859
|
+
|
|
1860
|
+
Its band is reserved **inside** the layout (the rows are laid out in a span
|
|
1861
|
+
shorter by the band and every node moves down by it), not taken off the canvas
|
|
1862
|
+
and made up for by the fit: a fit happens once, and switching the order is a
|
|
1863
|
+
redraw, so the fit route left the dendrogram 34 px above the top of the window.
|
|
1864
|
+
|
|
1865
|
+
**Circular** (`drawHeatmapRings`) is the same matrix in polar form: each column
|
|
1866
|
+
a concentric **ring** starting at `maxRad + tipLabelSpace + 7`, each cell an arc
|
|
1867
|
+
over that tip's own angular slice, with the slice boundaries derived once
|
|
1868
|
+
between neighbours exactly as the rows are. Ring thickness is
|
|
1869
|
+
`clamp(maxRad × 1.2 / columns, 1, 14)` — capped against the tree's own radius,
|
|
1870
|
+
so a wide matrix cannot turn the tree into a dot at the middle of a dartboard —
|
|
1871
|
+
and `fitRadialExtent` allows for the stack. Same-colour neighbours merge into
|
|
1872
|
+
one sector, which is what keeps a big fan drawable (281 paths for 18 × 50
|
|
1873
|
+
cells on the sparse demo).
|
|
1874
|
+
|
|
1875
|
+
Each ring carries its name **tangentially at the fan's seam**, the gap the
|
|
1876
|
+
layout already leaves between the last tip and the first: the one place a label
|
|
1877
|
+
can sit without covering a cell. Tangentially means rotated by the angle
|
|
1878
|
+
itself — `polarXY` puts angle *a* at screen direction *a* − 90°, so rotating by
|
|
1879
|
+
*a* − 90 instead lays every name out along the seam, where the rings are one
|
|
1880
|
+
ring-width apart and the names several times that. Measured before the fix: 44
|
|
1881
|
+
overlapping pairs out of 18 names.
|
|
1882
|
+
|
|
1883
|
+
The scale becomes a **corner card** (`drawHeatmapRingLegend`, bottom-left,
|
|
1884
|
+
screen coordinates so it neither turns nor scales with the fan), because a ring
|
|
1885
|
+
has no "under" to hang a strip from. There is no dendrogram: it would have to
|
|
1886
|
+
bend, and the desktop makes the same call. **Unrooted gets no heat map at all** —
|
|
1887
|
+
every tip there ends at its own radius, so a column has no ring to be. Hover is
|
|
1888
|
+
the same two questions in polar form: which ring, and which slice, the slice
|
|
1889
|
+
compared modulo a full turn because the fan rotates.
|
|
1890
|
+
|
|
1891
|
+
**Where the readout lands** is one function, `placeHoverReadout(el, event)`,
|
|
1892
|
+
shared by the heat map, the alignment track and the node tooltip. It sets the
|
|
1893
|
+
text, then measures the element — `.aptx-tip` is `width:max-content`, so its
|
|
1894
|
+
size is whatever it is currently saying — and offers it at `pageX + 14`,
|
|
1895
|
+
`pageY + 14`, flipping to the other side of the pointer on either axis only
|
|
1896
|
+
where that would leave the window (clamped to the edge if neither side fits).
|
|
1897
|
+
The heat map and the alignment both used to guess instead, shifting left by a
|
|
1898
|
+
hard-coded 280 px against a readout 181 px wide: since both tracks hug the right
|
|
1899
|
+
edge, every cell flipped and every readout floated ~95 px clear of the cell it
|
|
1900
|
+
described. `test_trees/heatmap_tip.html` pins it, in both layouts, and fails
|
|
1901
|
+
unless all four placements were exercised.
|
|
1902
|
+
|
|
1903
|
+
In the rectangular layout the turned column names, the gradient, its two
|
|
1904
|
+
numbers, the blank key and the caption are a **floating strip**; its reserve adds `MSA_NAV_RESERVE` while the
|
|
1905
|
+
alignment is shown, or the alignment's navigation bar (fixed at the viewport
|
|
1906
|
+
bottom) covers the scale.
|
|
1907
|
+
|
|
1908
|
+
Everything is plain rects, lines, text and one `<linearGradient>`, so the SVG,
|
|
1909
|
+
PDF and PNG exports match the screen.
|
|
1910
|
+
|
|
1635
1911
|
### The time axes
|
|
1636
1912
|
|
|
1637
1913
|
Data model: per-node `date = {unit, desc, value, minimum, maximum}` —
|
package/archaeopteryx.d.ts
CHANGED
|
@@ -83,6 +83,20 @@ 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;
|
|
87
101
|
showSupportDots?: boolean;
|
|
88
102
|
showTimeAxis?: boolean;
|
|
@@ -136,6 +150,9 @@ export interface ViewState {
|
|
|
136
150
|
rotation?: number;
|
|
137
151
|
horizontalLabels?: boolean;
|
|
138
152
|
msa?: boolean;
|
|
153
|
+
heatmap?: boolean;
|
|
154
|
+
heatmapOrder?: 'document' | 'clustered' | 'clustered-presence' | 'alphabetical' | 'frequency' | 'manual';
|
|
155
|
+
heatmapManual?: string[];
|
|
139
156
|
domains?: boolean;
|
|
140
157
|
domainLabels?: 'none' | 'domains' | 'legend';
|
|
141
158
|
domainGlow?: boolean;
|