archaeopteryx 3.2.1 → 3.4.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
@@ -31,6 +31,7 @@ config key live and shows the exact config JSON to copy into your own
31
31
  * [Herpesviridae DNA polymerase (201 tips)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=herpes_dnapol)
32
32
  * [Caliciviridae (186 strains)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=caliciviridae_500)
33
33
  * [Adenoviridae (321 strains)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=adenoviridae)
34
+ * [Apaf-1 gene family (domain architectures)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=apaf)
34
35
  * [Nucleotide alignment (600 columns)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=alignment_nt)
35
36
  * [Genome alignment (150 × 30,000 columns)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=genome_alignment)
36
37
  * [Sequence alignment](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=alignment)
@@ -38,7 +39,6 @@ config key live and shows the exact config JSON to copy into your own
38
39
  * [Dinosaur time tree](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=dinosaur)
39
40
  * [Ammonite time tree (fossil ranges)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=ammonite)
40
41
  * [Late Cretaceous time tree (stages)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=late_cretaceous)
41
- * [Apaf-1 gene family](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=apaf)
42
42
  * [Bcl-2 family](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=bcl2)
43
43
  * [Confidence values](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=confidences)
44
44
  * [Branch events](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=branch_events)
@@ -177,15 +177,19 @@ and decides by itself what is worth showing. There is nothing to configure.
177
177
  two where both make sense.
178
178
  * The **legend** is a card you can **drag anywhere**. It shows a colour and
179
179
  a **count** per value, `[by count]` / `[A-Z]` toggles the order, a dashed
180
- **no value** row counts the nodes the field does not cover, and very long
180
+ **no value** row counts the nodes the field does not cover (a value that is
181
+ nothing but underscores or a `;`/`:` qualifier counts as no value), and very long
181
182
  legends show the top 20 with a `[+N more]` chip. Legends are part of PNG,
182
183
  PDF
183
184
  and SVG exports (exports always come out light).
184
- * **Switch into a subtree** (or delete part of the tree) and the menus,
185
- counts and legends are re-derived for what is on screen — a field with too
186
- many values on the full tree may become available inside a clade. Colours
187
- never change when you do this: a value keeps its colour for the whole
188
- session.
185
+ * **Switch into a subtree** and the legend re-describes what is on screen —
186
+ rows, counts, the **no value** row, and a numeric field's colours-or-gradient
187
+ band — while the menus and your Color / Shape choice stand exactly as they
188
+ were: colouring by Genus and entering a one-genus clade shows a one-row
189
+ legend, not a grey tree. A category value keeps its colour for the whole
190
+ session; a gradient re-spans the values on screen. **Deleting** part of the
191
+ tree re-derives the menus from what is left, and keeps your choice as long
192
+ as its field still has a value somewhere.
189
193
  * The **Visualizations** checkbox hides the chosen colours/shapes; the
190
194
  **Visual Styles** checkbox controls colours embedded in the tree file
191
195
  itself (and phyloXML branch colours). Search hits and selections always
@@ -198,6 +202,42 @@ panel: **rectangular** (root at left), **circular**, and **unrooted** — the
198
202
  desktop's equal-angle fan, where each subtree opens a wedge proportional to
199
203
  how many tips it holds.
200
204
 
205
+ In the rectangular layouts a rooted tree shows a short **stub** branch into
206
+ its root; a tree that declares itself unrooted (phyloXML `rooted="false"`, a
207
+ Nexus `[&U]` tree) shows none, and the circular layout never draws one. A
208
+ subtree view always shows the stub, since a clade has a definite root,
209
+ whatever the branch above it was. When the whole tree is a phylogram and the
210
+ file gives the root a branch length, that branch is drawn to scale instead
211
+ of the stub — except in the **unrooted** display, which draws no root branch
212
+ of any kind, real or stub: there the root is a point of the fan, not the end
213
+ of a branch from nowhere. The root itself wears a circle only for the reasons
214
+ any node does — an event, a search hit, a selection, a visualization.
215
+
216
+ **Collapsing a clade.** The node menu's **Collapse/Uncollapse** folds an
217
+ internal node's whole clade into a wedge and opens it again; **Uncollapse
218
+ Subtree** opens everything below a node; the tool row's uncollapse-all button
219
+ (the desktop's glyph, lit only while something is collapsed) opens the whole
220
+ tree, and so does **Esc**. The wedge is the desktop's triangle: its apex at
221
+ the node, its vertical base at the clade's average tip distance (one depth
222
+ step in a cladogram), so its depth stays readable; it is filled in the colour most of its tips wear under the current
223
+ Color-by, grows gently taller with its tip count, and is named — the node's
224
+ own name if it has one; else the one Color-by value nearly all its tips share,
225
+ so a clade reads "Bovine · 12 tips" while you look at hosts; else the tips'
226
+ common name prefix; always with the tip count, and with `[found/total]` while
227
+ a search hits inside it. Legends, alignment rows and domain tracks describe
228
+ the tips on screen, so a collapsed clade's tips leave them. Collapsing is
229
+ display state only: nothing is removed, exports and downloads carry every
230
+ tip, and the unrooted layout, which has no rows to fold, shows every clade
231
+ open. The controls are the desktop's; the drawing and naming are this
232
+ program's.
233
+
234
+ A phylogram carries a **scale bar** at the bottom left: a round number of
235
+ branch-length units (1, 2 or 5 × 10ᵏ, whichever makes the bar about 100 px)
236
+ with its length written above it. It is drawn with the tree, so it zooms and
237
+ exports with it and its label always holds. A cladogram has nothing to
238
+ measure and shows none, and a tree under a time axis leaves the measuring to
239
+ the axis.
240
+
201
241
  In the two radial layouts the zoom row changes meaning, exactly as on the
202
242
  desktop: **Y+ / Y− become the plain + / − zoom** (a circle has one diameter;
203
243
  the mouse wheel zooms too, and never rotates), **X− / X+ become rotate** (a
@@ -208,6 +248,36 @@ also resets rotation and label direction. Unrooted
208
248
  additionally greys out the aligned-phylogram option and Auto-hide Labels
209
249
  (there is no common label edge, and no even row spacing to hide against).
210
250
 
251
+ ## Metadata tables
252
+
253
+ A tree file rarely carries everything known about its tips. A **metadata
254
+ table** beside it does: TSV or CSV, a header row, the first column naming the
255
+ tip and every other column a piece of data. On the [open page](https://cmzmasek.github.io/archaeopteryx-js/open.html)
256
+ drop, choose or paste the table before or after the tree — a table pasted
257
+ first waits for the tree; one added while a tree is showing joins it and the
258
+ view relaunches — and the toolbar says how many columns joined how many tips,
259
+ with the rows that matched no tip and the tips without a row a hover away.
260
+
261
+ Each column becomes a node property (`meta:` plus the header, so "Collection
262
+ Date" comes back as `Collection Date` in every menu; a header that already
263
+ reads as `namespace:name` is kept as it is). From there nothing is special:
264
+ the columns are offered for **Color-by** and **Shape** by the same rules as
265
+ any property, with the same legends; they are **search** fields, typed
266
+ numeric when every filled cell is a number; they appear in the **node data**;
267
+ and they are written into a phyloXML export, so a saved tree keeps them.
268
+ Tip names are matched exactly, then case-insensitively; empty cells add
269
+ nothing; a column the tree already carries under the same ref is replaced by
270
+ the table's values. Quoted cells, `#` comment lines and Windows line ends are
271
+ fine.
272
+
273
+ Embedders do the same in two lines, before `launch()`:
274
+
275
+ ```js
276
+ const tree = archaeopteryx.parseTree(name, treeText);
277
+ const report = forester.joinMetadataTable(tree, tableText); // {columns, tips, matchedTips, unmatchedTips, unmatchedRows, properties}
278
+ archaeopteryx.launch('#tree', tree, config);
279
+ ```
280
+
211
281
  ## Searching
212
282
 
213
283
  Two search boxes (A and B), each with its own **field** menu (built from what
@@ -228,12 +298,93 @@ press centres the previous / next hit in the viewport, wrapping around.
228
298
 
229
299
  ## Keyboard
230
300
 
231
- Deliberately minimal: **Esc** or **Home** resets the view, **O** cycles the
232
- overview between corners, **PageUp / PageDown** change the font size — and
233
- the **mouse wheel** zooms (Shift: vertical only; Shift+Alt: horizontal;
234
- Ctrl+Shift: font size). Everything else is a button; the old Alt+letter
235
- combos are gone (macOS labels that key Option and types glyphs with it).
236
- Nothing fires while the cursor is in a text box.
301
+ The same actions on every platform; only the modifier differs: **⌘** on
302
+ macOS, **Ctrl** on Windows and Linux. **⌘/** (Ctrl+/) shows this list in
303
+ the viewer, drawn for the platform you are on; the About box links to it
304
+ too.
305
+
306
+ | macOS | Windows / Linux | Does |
307
+ |---|---|---|
308
+ | ⌘0 | Ctrl+0 | Fit the tree to the window |
309
+ | ⌘+ / ⌘− | Ctrl++ / Ctrl+− | Zoom in / out |
310
+ | ⌘⇧↑ / ⌘⇧↓ | Ctrl+Shift+↑ / ↓ | Zoom in / out vertically |
311
+ | ⌘⇧→ / ⌘⇧← | Ctrl+Shift+→ / ← | Zoom in / out horizontally (circular: rotate) |
312
+ | ⌘⇧E | Ctrl+Shift+E | Expand vertically until the labels fit |
313
+ | ⌘⇧L | Ctrl+Shift+L | Next layout: rectangular, circular, unrooted |
314
+ | ⌘⇧D | Ctrl+Shift+D | Next display type: phylogram, aligned, cladogram |
315
+ | ⌘⇧X | Ctrl+Shift+X | Time axis on / off |
316
+ | ⌘⇧O | Ctrl+Shift+O | Ladderize |
317
+ | ⌘⇧U | Ctrl+Shift+U | Uncollapse every clade |
318
+ | ⌘F | Ctrl+F | Go to the search box |
319
+ | ⌘G / ⌘⇧G | Ctrl+G / Ctrl+Shift+G | Next / previous search hit |
320
+ | ⌘⇧< / ⌘⇧> | Ctrl+Shift+< / > | Previous / next tree of a multi-tree file |
321
+ | ⌘/ | Ctrl+/ | The shortcut list |
322
+ | Esc or Home | Esc or Home | Back to the whole tree, the launch view |
323
+ | O | O | Move the overview to the next corner |
324
+ | Page Up / Page Down | PageUp / PageDown | Larger / smaller font |
325
+
326
+ Mouse wheel, also the same everywhere: plain zooms both axes, **Shift**
327
+ vertical only, **Shift+Alt** (macOS: Shift+Option) horizontal only,
328
+ **Ctrl+Shift** the font size. Inside a text box only the combos that cannot
329
+ interfere with typing fire (fit, zoom, search, the list); the plain keys
330
+ never do. The letters follow the desktop Archaeopteryx where it has the
331
+ action (its Alt+O, Alt+U, Alt+E and ⌘0, ⌘G). There is no key for
332
+ re-rooting, by decision. On Linux, Ctrl+Shift+U inside a text field is the
333
+ desktop's Unicode entry: it stays with the text field there, as it should.
334
+
335
+ ## Sharing a view
336
+
337
+ A view is what you made of a tree with the panel: the layout and display
338
+ type, which labels show, the colour and shape fields, both searches, the
339
+ clade you switched to, the clades you collapsed, the font, node and branch
340
+ sizes, the rotation, the tracks. On the demo pages it rides in the URL's
341
+ `#` hash and follows every change, so the address bar is always a link to
342
+ what is on screen: copy it (**Copy link to this view** in the toolbar) and
343
+ the recipient opens the same tree in the same view. Opening your own file
344
+ keeps the view in the hash too, so the same file reopened at that address
345
+ comes back as you left it. Zoom, pan, the legend's position and the node
346
+ selection are not part of a view; a shared view opens fitted.
347
+
348
+ Embedders get the same four pieces: the handle's `getViewState()` and
349
+ `applyViewState(state)`, the config's `view` (open straight into one) and
350
+ `onViewChange(state, encoded)` (called when it changes), and
351
+ `archaeopteryx.encodeViewState()` / `decodeViewState()` for the hash form,
352
+ which reads like
353
+ `layout=circular&colorBy=tax:common_name&show=name,external&font=9&collapsed=12,44&a=HUMAN&af=Any+Text&am=contains`.
354
+ Nodes are named by their launch-time preorder index, so a view belongs to
355
+ the tree it was made on; a key a view leaves out keeps its current value,
356
+ and anything that does not fit the tree is skipped.
357
+
358
+ ## Protein domain architectures
359
+
360
+ A tree whose tips carry `<domain_architecture>` elements (a protein's length
361
+ and its domains, each with a position and an E-value) draws them as **domain
362
+ tracks** beside the tips: a thin grey backbone, `L` residues long, with a
363
+ rounded box per domain, placed at its residues on one scale shared by the
364
+ whole tree so lengths compare across tips, coloured by domain name from the
365
+ Tableau palette, with the name written on the box when it fits. The tracks
366
+ appear from the start whenever a tree carries them; the **Domain
367
+ Architectures** checkbox under Display Data toggles them, and their own
368
+ section holds the controls:
369
+
370
+ * **Track width** `−` / `+` — the longest architecture's track starts at a
371
+ quarter of the window and scales by 0.8 / 1.2 per press (hold to repeat).
372
+ * **E-value ≤** `−` `10⁻³` `+` — only domains at or under the threshold are
373
+ drawn; each press moves it by a factor of ten, from `10⁻²⁰` to `10³`. The
374
+ colours are dealt again to the names that remain, in sorted order.
375
+ * **Labels** — `On domains` (the default), `Legend` (a card, at home in the
376
+ bottom-right corner, draggable, double-click to send it back: one row per
377
+ drawn name with its box count, in the order the names first appear down
378
+ the tree), or `None`.
379
+ * **Glow** — a soft glow in each domain's own colour around its box.
380
+
381
+ In the circular and unrooted layouts the tracks ride each tip's spoke
382
+ outward and carry no names (the legend still works); they need radial
383
+ labels, which switching layouts turns on. A malformed domain — a missing or
384
+ impossible position or E-value — is skipped and counted in a console
385
+ warning, never fatal. The tracks ride into the SVG, PDF and PNG exports.
386
+ This is the desktop's domain display, drawn to the same numbers
387
+ (`test/domain_test.js` holds them).
237
388
 
238
389
  ## Sequence alignments
239
390
 
@@ -365,6 +516,13 @@ const viewer = archaeopteryx.launchArchaeopteryx(container, fileName, data, conf
365
516
  const tree = archaeopteryx.parseTree(fileName, data);
366
517
  const viewer = archaeopteryx.launch(container, tree, config);
367
518
 
519
+ // every tree the file holds (a Nexus TREES block, a multi-tree Newick,
520
+ // several phylogenies): launch() takes the list, shows the first, and
521
+ // the panel gets a picker for the rest
522
+ const trees = archaeopteryx.parseTrees(fileName, data);
523
+ const viewer = archaeopteryx.launch(container, trees, config);
524
+ viewer.showTree(1); // also viewer.getTreeIndex(), viewer.getTreeCount()
525
+
368
526
  // later, e.g. when an SPA removes the view:
369
527
  viewer.destroy();
370
528
  ```
@@ -390,7 +548,7 @@ viewer.destroy(); // unmount COMPLETELY: the container DOM, the node
390
548
  // handler; a later launch() works normally
391
549
  ```
392
550
 
393
- **Big trees draw on the next frame.** Above 2,000 nodes, `launch()` does all
551
+ **Big trees draw on the next frame.** Above 3,000 nodes, `launch()` does all
394
552
  its validation, shows a "Drawing N nodes" card over the tree area, and
395
553
  returns within milliseconds — the label analysis, visualization candidates,
396
554
  control panel and the draw itself all run one frame later, so the browser
@@ -454,9 +612,13 @@ The parser is picked from the data and the `location`: content starting with
454
612
  `#NEXUS` (or a name ending in `.nex`/`.nexus`) is read as Nexus, JSON content
455
613
  (or a name ending in `.json`) as an **Auspice/Nextstrain v2** `dataset.json`,
456
614
  a name ending in `xml` as phyloXML, anything else as New Hampshire (Newick).
457
- A Nexus file shows its **first** tree; a protein/DNA/RNA characters matrix in
458
- the file (sequential or interleaved) lands on the tips as an aligned
459
- `mol_seq`, so the alignment track appears just as it does for phyloXML.
615
+ A file holding **several trees** — a Nexus TREES block, a Newick file with one
616
+ tree per `;`, a phyloXML with several phylogenies — opens on the first, and a
617
+ picker with previous / next buttons at the top of the control panel moves
618
+ between them; each tree opens fresh under the same config, the way a new tab
619
+ does on the desktop. A protein/DNA/RNA characters matrix in a Nexus file
620
+ (sequential or interleaved) lands on the tips as an aligned `mol_seq`, so the
621
+ alignment track appears just as it does for phyloXML.
460
622
 
461
623
  An Auspice dataset opens on the **time view** (branch lengths from `num_date`
462
624
  differences; a divergence-only build falls back to `div` differences): the
@@ -496,6 +658,7 @@ a popup any more, and nothing fails silently.
496
658
  | **Auspice / Nextstrain** `dataset.json` (v2) | in | Phylodynamic builds: sampling dates and their confidence, cumulative divergence, and discrete traits (country, clade, host, ...) with their posterior distributions. | [5] |
497
659
  | **BEAST** / BEAST 2 / TreeAnnotator annotations | in (embedded in Newick/Nexus) | `[&posterior=...,height_95%_HPD={lo,hi},rate=...]`-style blobs: posterior clade support, node-age confidence intervals, per-branch rates and other traits. FigTree's `!color` is read the same way. | [6, 7] |
498
660
  | **MrBayes** annotations | in (embedded in Newick/Nexus) | `prob=`/`prob.stddev=` blobs: posterior-probability clade support. | [8] |
661
+ | **Metadata table** (`.tsv`, `.csv`) | in (beside a tree) | A header row and one row per tip, the first column naming the tip: every other column is joined onto the tips as a property, so it is offered for Color-by and Shape, searched, shown in the node data and written into phyloXML exports. See [Metadata tables](#metadata-tables). | — |
499
662
  | **FASTA** | out | The molecular sequence(s) of the selected tip(s), or every sequence the tree carries. Offered in the Download menu only when the tree actually carries molecular sequences (aligned or not). | [9] |
500
663
  | SVG · PNG · vector PDF | out | A snapshot of the drawn tree for publication or further editing — vector (SVG, PDF) or raster (PNG). General-purpose graphics formats, not phylogenetic data, so no literature reference applies. | — |
501
664
 
@@ -588,7 +751,7 @@ shorter than the dot itself stays clean.
588
751
  One object, passed as the third argument. It is optional, and the best
589
752
  configuration is usually an empty one — almost everything that used to be
590
753
  configured is now read off the tree (see **Intelligent pre-sets** above). The
591
- twenty-eight keys below are the ones no tree can answer for you.
754
+ thirty keys below are the ones no tree can answer for you.
592
755
 
593
756
  There used to be two objects, `options` and `settings`, split by whether the
594
757
  user could also change the value from the control panel. That was a fact about
@@ -615,11 +778,17 @@ copy-pastable JSON.
615
778
  | `layout` | `'rectangular'` | The starting layout: `'rectangular'`, `'circular'`, or `'unrooted'`. |
616
779
  | `ladderizeTree` | `true` | Ladderize the tree on load: at each node, the larger clade first (any number of children, so a polytomy sorts too). |
617
780
  | `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. |
781
+ | `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. |
782
+ | `domainLabels` | `'domains'` | Where domain names go: `'domains'` (on the boxes), `'legend'` (a card), or `'none'`. |
783
+ | `domainGlow` | `false` | Open with the glow around each domain box on. |
784
+ | `domainEvalueExponent` | `-3` | The E-value threshold's exponent at launch, an integer from `-20` to `3`: domains with an E-value at or under `10^exponent` are drawn. |
618
785
  | `showTimeAxis` | tree-derived | Open with the time axis shown. Default: on when the tree carries `<date>` elements, off otherwise — an explicit `true`/`false` overrides that. |
619
786
  | `timeAxisGrid` | `false` | Open with the Time Grid vertical lines on (only meaningful — and only offered as a checkbox — while the time axis itself is shown). |
620
787
  | `showSupportDots` | `false` | Open with the Support Dots marks on (the checkbox appears whenever the tree has confidences). |
621
788
  | `supportDotMinimum` | `95` | Support Dots threshold, as a percentage. On a tree whose confidences top out at 1 (posterior probabilities) it is read on the 0–1 scale, so the default means ≥ 0.95 there and ≥ 95 on a bootstrap tree. |
622
789
  | `searchAinitialValue` | `null` | Prefill search box A. |
790
+ | `view` | `null` | Open straight into a saved view — the object `getViewState()` returns, or `decodeViewState()` reads from a URL hash: layout, display type, labels, colours, searches, the clade and the collapsed clades, sizes. See **Sharing a view**. |
791
+ | `onViewChange` | `null` | `function (state, encoded)`, called once per settled redraw when the view changed: `state` as `getViewState()` returns it, `encoded` its hash-ready string. The demo pages write it into the URL. |
623
792
  | `searchBinitialValue` | `null` | Prefill search box B. |
624
793
  | `enableVisualizations` | `true` | Offer the Color / Shape visualizations (which fields they cover is decided from the tree). |
625
794
  | `initialVisualization` | `null` | The visualization to open with, by its Color-menu name (e.g. `'Host'`; case-insensitive). A name the tree cannot honour logs a console warning and falls back to the automatic choice, so a site-wide value is safe on trees without that field. Default: Archaeopteryx.js picks the most informative field itself. |
@@ -1188,6 +1357,57 @@ minus π/2 in circular; `labelAngleDeg` rotates a label along its spoke and
1188
1357
  consumer (overview dots, hit navigator, node transforms). Unrooted disables
1189
1358
  aligned phylograms and label auto-hiding, as the desktop does.
1190
1359
 
1360
+ ### The domain tracks
1361
+
1362
+ Data model: per-tip `sequences[i].domain_architecture = {length, domains:
1363
+ [{name, from, to, confidence}]}` — the first sequence carrying one; `length`
1364
+ must be a positive integer or the architecture is not drawn. A domain is
1365
+ drawable when `from` and `to` are integers with `to > from` and `confidence`
1366
+ (its E-value) is a number; otherwise it is skipped and counted
1367
+ (`forester.domainArchitectureDomains`). Gate: `showDomainArchitectures`
1368
+ state (auto-on when `_basicTreeProperties.domainArchitectures`) AND external
1369
+ labels shown AND, in a radial layout, radial rather than upright labels.
1370
+
1371
+ Scale: one factor for the tree, `f = W_eff / Lmax × 0.9` px per residue.
1372
+ `W` (the track width) starts at `0.25 × viewport width`; `d+` / `d−` scale it
1373
+ by 1.2 / 0.8 and stop at 2000 / 20. `W_eff = W` in the rectangular layout,
1374
+ a width of the radial layouts' own, which starts at `min(W, 0.2 × radius)`
1375
+ and is then stepped by the same buttons (the desktop caps the drawn width at
1376
+ that fifth of the radius instead). `Lmax` is the longest architecture
1377
+ in the displayed tree, counting every domain whatever its E-value, so the
1378
+ threshold never rescales. The rectangular layout reserves `20 + W + 10` px
1379
+ from `_w` past the label reservation (`_domainReserve`, counted wherever `_w`
1380
+ is), so the tree compresses to make room; the radial fit adds
1381
+ `4 + W_eff + 10` to the ring. Placement: rectangular `start = _w +
1382
+ nodeLabelGap + labelSpace + 20` for every tip (one aligned column) with box
1383
+ height `clamp(round(tipPitch / 2), 6, 16)`; circular `r0 = maxRad +
1384
+ labelSpace + 4` under `rotate(spoke)`; unrooted `translate(tip)
1385
+ rotate(spoke)` with `start = labelSpace + 4`. A domain `from..to` covers
1386
+ `[start + (from − 1) f, start + to f]` — residue `r` is `[(r − 1) f, r f]`,
1387
+ decided jointly with the desktop on 2026-09-12.
1388
+
1389
+ Drawing, per box, in this order: three stepped shadow rects (`rgb(8,18,21)`
1390
+ at 40 / 28 / 17 of 255, offset 0.4/0.7, 0.9/1.5, 1.6/2.5), the optional two
1391
+ glow rects (the base colour at 20 then 34 of 255, grown by 3.2 then 1.6),
1392
+ the body (a vertical gradient `lighten(base, 0.12)` → `darken(base, 0.10)`,
1393
+ corner radius `min(2, min(w, h) / 2)`) with a 1 px `darken(base, 0.24)`
1394
+ border, and the name — rectangular only, in `min(external font, h − 2)` px
1395
+ when that is over 4 px and the text is at most `w − 4` wide, in near-black
1396
+ when the base luminance is over 0.55 and white otherwise. Colours: the drawn
1397
+ names over the whole tree, sorted by code unit, take Tableau 10 in order,
1398
+ then the same ten shifted toward white (odd cycles) or black (even cycles)
1399
+ by `min(0.55, 0.2 × cycle)`; an unnamed domain is `#808080`; dealt at load,
1400
+ after an edit and on every threshold change, and a name met later takes the
1401
+ next unused index. Legend (`'legend'` mode): title `Protein domains
1402
+ (E ≤ 1e<exp>)`, rows `NAME (count)` in first-appearance order over the tips
1403
+ in display order, clipped to 240 px; home bottom-right, inset 10; a drag
1404
+ keeps its place as a fraction of the view, a double-click sends it home.
1405
+ Everything is plain rects plus one `<linearGradient>` per colour in the
1406
+ track group's own `<defs>`, so exports match the screen. The acceptance
1407
+ numbers — apaf.xml: 31 tips, `Lmax` 2080, 202 domains; 9 names / 166 boxes
1408
+ at 1e−3; the palette; 22_MOUSE's box offsets at `W = 300` — are the
1409
+ desktop's, in `test/domain_test.js`.
1410
+
1191
1411
  ### The alignment track
1192
1412
 
1193
1413
  Data model: per-tip `sequences[0].mol_seq = {is_aligned, value}` (the gapped
@@ -73,6 +73,9 @@ export interface ArchaeopteryxConfig {
73
73
  nhConfidenceValuesInBrackets?: boolean;
74
74
  nhExportWriteConfidences?: boolean;
75
75
  nodeLabels?: Record<string, NodeLabelSpec> | null;
76
+ /** Called once per settled redraw when the view changed: the state as
77
+ * getViewState() returns it, and its hash-ready string. */
78
+ onViewChange?: ((state: ViewState, encoded: string) => void) | null;
76
79
  pngExportScale?: number;
77
80
  rootOffset?: number;
78
81
  searchAinitialValue?: string | null;
@@ -82,27 +85,103 @@ export interface ArchaeopteryxConfig {
82
85
  showTimeAxis?: boolean;
83
86
  supportDotMinimum?: number;
84
87
  timeAxisGrid?: boolean;
88
+ /** Open straight into a view (getViewState / decodeViewState). */
89
+ view?: ViewState | null;
85
90
  visualizationsLegendXpos?: number;
86
91
  visualizationsLegendYpos?: number;
87
92
  zoomToFitUponWindowResize?: boolean;
88
93
  }
89
94
 
95
+ /** One search box in a view: the field by its menu label, the mode, the
96
+ * value (and the range's second value). */
97
+ export interface ViewSearch {
98
+ field?: string;
99
+ mode?: string;
100
+ value: string;
101
+ value2?: string;
102
+ }
103
+
104
+ /** A view of a tree as the control panel left it: what getViewState()
105
+ * returns, what the config's view key and applyViewState() take, and what
106
+ * encodeViewState() / decodeViewState() turn into a URL-hash string and
107
+ * back. Every key is optional; a key left out keeps its current value,
108
+ * except searchA / searchB, which an absent key clears. Nodes (subtree,
109
+ * collapsed) are named by their launch-time preorder index. */
110
+ export interface ViewState {
111
+ /** Which tree of a multi-tree launch (0-based). */
112
+ tree?: number;
113
+ layout?: Layout;
114
+ display?: 'phylogram' | 'aligned' | 'cladogram';
115
+ /** The ladderize direction applied. */
116
+ order?: 'asc' | 'desc';
117
+ /** Midpoint re-rooted. */
118
+ root?: 'midpoint';
119
+ subtree?: number;
120
+ collapsed?: number[];
121
+ /** A visualization id (as the Color-by menu values them), or 'none'. */
122
+ colorBy?: string;
123
+ shapeBy?: string;
124
+ /** The panel's checked boxes: name, taxonomy, sequence, confidence,
125
+ * branchLength, external, internal, nodeEvents, branchEvents,
126
+ * supportDots, shortNames, autoHide, visualizations, visualStyles, and
127
+ * custom:<key> for a nodeLabels checkbox. */
128
+ show?: string[];
129
+ font?: number;
130
+ node?: number;
131
+ branch?: number;
132
+ /** Radial rotation in button presses (pi/32 each). */
133
+ rotation?: number;
134
+ horizontalLabels?: boolean;
135
+ msa?: boolean;
136
+ domains?: boolean;
137
+ domainLabels?: 'none' | 'domains' | 'legend';
138
+ domainGlow?: boolean;
139
+ domainEvalue?: number;
140
+ timeAxis?: boolean;
141
+ timeGrid?: boolean;
142
+ searchA?: ViewSearch;
143
+ searchB?: ViewSearch;
144
+ combine?: 'and' | 'or';
145
+ matchCase?: boolean;
146
+ inverse?: boolean;
147
+ }
148
+
90
149
  /** What launch() returns: the per-viewer surface an embedder needs after
91
150
  * launching. */
92
151
  export interface ViewerHandle {
93
152
  /** The nodes the user has selected via the node menu (when
94
153
  * enableManualNodeSelection is on). */
95
154
  getSelectedNodes(): PhylogenyNode[];
155
+ /** How many trees the launch holds: one, or every tree the file held
156
+ * (launch() with the array parseTrees returns). */
157
+ getTreeCount(): number;
158
+ /** Which of them is shown (0-based). */
159
+ getTreeIndex(): number;
160
+ /** Shows another tree of the launch in the same container under the
161
+ * same config; it opens fresh. Returns the handle for the new viewer. */
162
+ showTree(index: number): ViewerHandle;
163
+ /** The view as the panel left it (see ViewState). */
164
+ getViewState(): ViewState;
165
+ /** Opens a view on the running viewer; a view of another tree of the
166
+ * launch relaunches into that tree. */
167
+ applyViewState(state: ViewState): void;
96
168
  /** Unmounts the viewer completely: the DOM inside the container, the
97
169
  * body-level pieces, the window resize listener and every page-level
98
170
  * key/wheel handler. A later launch() works normally. */
99
171
  destroy(): void;
100
172
  }
101
173
 
174
+ /** How bare numeric internal labels of a Newick / Nexus tree are read:
175
+ * 'auto' decides per tree, 'confidence' takes every one as a support
176
+ * value, 'label' keeps them as names. */
177
+ export type InternalNumericLabels = 'auto' | 'confidence' | 'label';
178
+
102
179
  export interface Archaeopteryx {
103
180
  /** Launch the viewer into a container (a CSS selector or the element
104
- * itself; an unresolvable container throws). Exactly three arguments. */
105
- launch(container: string | Element, tree: Phylogeny, config?: ArchaeopteryxConfig): ViewerHandle;
181
+ * itself; an unresolvable container throws). Exactly three arguments.
182
+ * A tree, or every tree of a file (parseTrees): the first is shown and
183
+ * the control panel gets a picker for the others. */
184
+ launch(container: string | Element, tree: Phylogeny | Phylogeny[], config?: ArchaeopteryxConfig): ViewerHandle;
106
185
 
107
186
  /** Parse-and-launch in one step. Fetch the file content yourself; the
108
187
  * fileName picks the parser (extension; content is sniffed too).
@@ -112,23 +191,34 @@ export interface Archaeopteryx {
112
191
 
113
192
  /** Parse tree data, auto-detecting the format from content and fileName:
114
193
  * Nexus (#NEXUS / .nex / .nexus), Auspice/Nextstrain v2 JSON ({ / .json),
115
- * phyloXML (*xml), otherwise New Hampshire (Newick). */
194
+ * phyloXML (*xml), otherwise New Hampshire (Newick). The FIRST tree the
195
+ * data holds; parseTrees returns them all. */
116
196
  parseTree(fileName: string, data: string,
117
- nhConfidenceValuesInBrackets?: boolean,
118
- nhConfidenceValuesAsInternalNames?: boolean): Phylogeny;
197
+ internalNumericLabels?: InternalNumericLabels): Phylogeny;
198
+ /** Every tree the data holds, in file order: a Nexus TREES block, a
199
+ * Newick text with one tree per ';', a phyloXML with several
200
+ * phylogenies (an Auspice dataset is one tree). Hand the array to
201
+ * launch(). */
202
+ parseTrees(fileName: string, data: string,
203
+ internalNumericLabels?: InternalNumericLabels): Phylogeny[];
119
204
 
120
205
  parsePhyloXML(data: string): Phylogeny;
121
206
  parseNewHampshire(data: string,
122
- confidenceValuesInBrackets?: boolean,
123
- confidenceValuesAsInternalNames?: boolean): Phylogeny;
124
- /** A Nexus file can hold several trees; the FIRST is returned. */
207
+ internalNumericLabels?: InternalNumericLabels): Phylogeny;
208
+ /** A Nexus file can hold several trees; the FIRST is returned
209
+ * (parseTrees returns them all). */
125
210
  parseNexus(data: string,
126
- confidenceValuesInBrackets?: boolean,
127
- confidenceValuesAsInternalNames?: boolean): Phylogeny;
211
+ internalNumericLabels?: InternalNumericLabels): Phylogeny;
128
212
  parseAuspiceJson(data: string | object): Phylogeny;
129
213
 
130
214
  /** Module-level twin of the handle's getSelectedNodes. */
131
215
  getSelectedNodes(): PhylogenyNode[];
216
+
217
+ /** A view as a "key=value&..." string for a URL hash, and back. decode
218
+ * accepts a leading '#', ignores what it does not know, and returns
219
+ * null for nothing. */
220
+ encodeViewState(state: ViewState): string;
221
+ decodeViewState(text: string | null | undefined): ViewState | null;
132
222
  }
133
223
 
134
224
  export const archaeopteryx: Archaeopteryx;