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 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, as iTOL draws it (one depth
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; they appear in the **node data**;
374
- and they are written into a phyloXML export, so a saved tree keeps them.
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, alignment track and time axes (developer spec)
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}` —
@@ -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;