archaeopteryx 2.3.2 → 3.1.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.
Files changed (6) hide show
  1. package/LICENSE +160 -499
  2. package/README.md +1205 -99
  3. package/archaeopteryx.d.ts +141 -0
  4. package/archaeopteryx.js +7984 -6469
  5. package/forester.js +3283 -535
  6. package/package.json +42 -12
package/README.md CHANGED
@@ -2,9 +2,6 @@
2
2
  Archaeopteryx.js is a software tool for the visualization and analysis of highly annotated phylogenetic trees.
3
3
 
4
4
 
5
- ### Website
6
- https://sites.google.com/view/archaeopteryxjs
7
-
8
5
  ### npm
9
6
  https://www.npmjs.com/package/archaeopteryx
10
7
 
@@ -12,130 +9,276 @@ https://www.npmjs.com/package/archaeopteryx
12
9
  https://github.com/cmzmasek/archaeopteryx-js
13
10
 
14
11
 
15
- ### Examples
16
-
17
- * [Bcl-2 gene family](http://www.phyloxml.org/archaeopteryx-js/bcl2_js.html)
18
- * Eukaryotic tree of life:
19
- * [collapsed with initial depth = 5](http://www.phyloxml.org/archaeopteryx-js/euk_tol_collapsed_js.html)
20
- * [uncollapsed](http://www.phyloxml.org/archaeopteryx-js/euk_tol_js.html)
21
- * [Influenza HA H3 collapsed by Country](http://www.phyloxml.org/archaeopteryx-js/influenza_collapsed.html)
22
- * [Amphibian phylogeny](http://www.phyloxml.org/archaeopteryx-js/amphi_frost_js.html)
23
- * Visualizations:
24
- * [Herpesviridae DNA polymerases](http://www.phyloxml.org/archaeopteryx-js/hg1001_js.html)
25
- * RAxML examples:
26
- * [bipartitions](http://www.phyloxml.org/archaeopteryx-js/raxml_bipartitions_bcl2_js.html)
27
- * [bipartitionsBranchLabels](http://www.phyloxml.org/archaeopteryx-js/raxml_bipartitions_branchlabels_bcl2_js.html)
28
- * MSA Residue Visualization:
29
- * [Bunyaviridae Glycoprotein](http://www.phyloxml.org/archaeopteryx-js/bunya_glycoprotein.html)
30
- * [Bcl-2 protein](http://www.phyloxml.org/archaeopteryx-js/bcl2_msa.html)
31
- * Preset search fields:
32
- * [H3N2](http://www.phyloxml.org/archaeopteryx-js/h3n2_search_js.html)
33
- * Grouping of species and years for visualization:
34
- * [Viral Strains](http://www.phyloxml.org/archaeopteryx-js/many_species_js.html)
35
- * SARS-CoV-2 with mutations and PANGO lineages:
36
- * [SARS-CoV-2](http://www.phyloxml.org/archaeopteryx-js/sars_cov_3.html)
12
+ ### Live demos
37
13
 
14
+ Self-contained demos, served from this repository (no external dependencies) —
15
+ they run entirely in your browser:
38
16
 
39
- ### Detailed developer documentation
40
- https://docs.google.com/document/d/1COVe0iYbKtcBQxGTP4_zuimpk2FH9iusOVOgd5xCJ3A/edit
17
+ **https://cmzmasek.github.io/archaeopteryx-js/**
18
+
19
+ **Try your own tree:**
20
+ [**cmzmasek.github.io/archaeopteryx-js/open.html**](https://cmzmasek.github.io/archaeopteryx-js/open.html)
21
+ — paste or open a Newick / NHX / Nexus / phyloXML / Auspice JSON file and
22
+ Archaeopteryx.js will visualize it. The tree is read locally in your browser;
23
+ nothing is uploaded. Its **Expert options** panel exercises every launch
24
+ config key live and shows the exact config JSON to copy into your own
25
+ `launch()` call.
41
26
 
42
- ### Version History
43
- https://github.com/cmzmasek/archaeopteryx-js/wiki/Archaeopteryx.js-Version-History
27
+ * [Auspice / Nextstrain JSON](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=auspice)
28
+ * [Swine H1 HA1 + alignment (Nexus)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=swh1)
29
+ * [BEAST annotations (Nexus)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=beast)
30
+ * [SARS-CoV-2 time tree (calendar)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=sarscov2)
31
+ * [Herpesviridae DNA polymerase (201 tips)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=herpes_dnapol)
32
+ * [Caliciviridae (186 strains)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=caliciviridae_500)
33
+ * [Adenoviridae (321 strains)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=adenoviridae)
34
+ * [Nucleotide alignment (600 columns)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=alignment_nt)
35
+ * [Genome alignment (150 × 30,000 columns)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=genome_alignment)
36
+ * [Sequence alignment](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=alignment)
37
+ * [Influenza HA (annotated)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=influenza)
38
+ * [Dinosaur time tree](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=dinosaur)
39
+ * [Ammonite time tree (fossil ranges)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=ammonite)
40
+ * [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
+ * [Bcl-2 family](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=bcl2)
43
+ * [Confidence values](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=confidences)
44
+ * [Branch events](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=branch_events)
45
+ * [Influenza A H5Nx (354 strains)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=flu_h5)
46
+ * [Start circular](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=circular)
47
+ * [Woese tree of life (start unrooted)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=woese)
48
+ * [Start with collapsed controls](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=collapsed)
49
+ * [H5N1 segment 3 (13,246 tips)](https://cmzmasek.github.io/archaeopteryx-js/demo.html?tree=flu_h5n1_seg3)
50
+
51
+
52
+ ### Detailed developer documentation
53
+ To be written. For now, the [For Developers](#for-developers) section below
54
+ covers the entry points, configuration, and the visualization system.
44
55
 
45
56
  ### Dependencies
46
57
  Archaeopteryx.js has the following dependencies:
47
58
  * forester.js: https://www.npmjs.com/package/archaeopteryx
48
59
  * phyloxml.js: https://www.npmjs.com/package/phyloxml
49
- * d3.js (version 3): https://www.npmjs.com/package/d3/v/3.5.17
50
- * jQuery (1.12.4): https://www.npmjs.com/package/jquery/v/1.12.4
51
- * jQuery UI (1.12.1): https://www.npmjs.com/package/jquery-ui/v/1.12.1
52
- * sax.js (1.2.4): https://www.npmjs.com/package/sax/v/1.2.4
60
+ * d3.js (version 7): https://www.npmjs.com/package/d3
61
+ * sax.js (1.6.1): https://www.npmjs.com/package/sax/v/1.6.1
53
62
 
54
- For file (Newick/New Hampshire, phyloXML) and graphics (PNG, SVG)
55
- download/export, the following five libraries are required as well:
56
- * canvg: https://www.npmjs.com/package/canvg
57
- * rgbcolor: https://www.npmjs.com/package/rgbcolor
58
- * Blob.js: https://github.com/eligrey/Blob.js
59
- * canvas-toBlob.js (needed in some versions of Internet Explorer and Opera): https://github.com/eligrey/canvas-toBlob.js
60
- * FileSaver.js: https://github.com/eligrey/FileSaver.js
61
-
62
- Additionally, Archaeopteryx.js also requires the following CSS:
63
- * jquery-ui.css: https://code.jquery.com/ui/1.12.0/themes/base/jquery-ui.css
63
+ For **raster PNG export** (optional — the PNG entry appears in the Download
64
+ menu only when `window.Canvg` is present):
65
+ * canvg (4.x): https://www.npmjs.com/package/canvg — publishes ES modules
66
+ only (no classic-script global build), so bridge it yourself:
67
+ ```html
68
+ <script type="module">
69
+ import {Canvg} from 'http://path/to/canvg.js'; // a self-contained build; see below
70
+ window.Canvg = Canvg;
71
+ </script>
72
+ ```
73
+ A module script is deferred regardless of where it sits in the page, and
74
+ PNG export only runs later from a Download click, so this can go anywhere
75
+ before the click — it does not need to precede archaeopteryx.js's own
76
+ `<script>` tag. `docs/lib/canvg.js` in this repository is one such
77
+ self-contained build (npm's `canvg@4.0.3` bundled into one file, e.g. via
78
+ `esm.sh/canvg@4?bundle`, with its one unnecessary Node-environment shim
79
+ import removed — see the file's own header comment).
80
+
81
+ For **vector PDF export** (optional — the PDF entry appears in the Download
82
+ menu only when both are loaded before archaeopteryx.js):
83
+ * jspdf (4.x): https://www.npmjs.com/package/jspdf
84
+ * svg2pdf.js (2.8.x): https://www.npmjs.com/package/svg2pdf.js
85
+
86
+ File (Newick/New Hampshire, Nexus, phyloXML, FASTA) and SVG download, as well as saving
87
+ the exported PNG, use native browser APIs (`Blob`, `canvas.toBlob()`, and an
88
+ `<a download>` link), so Blob.js, canvas-toBlob.js and FileSaver.js are no longer
89
+ required.
90
+
91
+ The user interface (control panel, sliders, dialogs) is built with native DOM
92
+ elements, so **jQuery and jQuery UI are no longer required** — neither their
93
+ JavaScript nor jQuery UI's CSS.
64
94
 
65
95
 
66
96
  ## Basic Example of HTML for launching Archaeopteryx.js
67
97
 
68
98
  Example of HTML page to launch a basic Archaeopteryx.js instance:
69
- ```
99
+ ```html
70
100
  <!DOCTYPE html>
71
- <meta charset="utf-8">
101
+ <html>
72
102
  <head>
103
+ <meta charset="utf-8">
73
104
  <title>Archaeopteryx.js Basic Demo</title>
74
105
 
75
- <!-- For MS IE/Edge compatibility:-->
76
- <meta http-equiv="X-UA-Compatible" content="IE=100">
77
-
78
- <!-- D3.js, jQuery, and jQuery UI:-->
79
- <script src="http://d3js.org/d3.v3.min.js"></script>
80
- <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
81
- <script src="https://code.jquery.com/ui/1.12.0/jquery-ui.js"></script>
82
-
83
- <!-- SAX XML parser:-->
84
- <script src="http://www.phyloxml.org/js/dependencies/sax.js"></script>
85
-
86
- <!-- Archaeopteryx.js requires forester.js and phyloxml.js:-->
87
- <script src="http://path/to/phyloxml.js"></script>
88
- <script src="http://path/to/forester.js"></script>
89
- <script src="http://path/to/archaeopteryx.js"></script>
90
-
91
- <!-- CSS for jQuery UI: -->
92
- <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.0/themes/base/jquery-ui.css">
93
-
94
- <script>
95
- function load() {
96
- var options = {};
97
- options.backgroundColorDefault = '#f0f0f0';
98
- var settings = {};
99
- var loc = 'https://raw.githubusercontent.com/cmzmasek/archaeopteryx-js/master/test/data/phyloxml_trees/apaf.xml';
100
-
101
- jQuery.get(loc,
102
- function (data) {
103
- var tree = null;
104
- try {
105
- tree = archaeopteryx.parseTree(loc, data, true, false);
106
- }
107
- catch (e) {
108
- alert("error while parsing tree: " + e);
109
- }
110
- if (tree) {
111
- try {
112
- archaeopteryx.launch('#phylogram1', tree, options, settings);
113
- }
114
- catch (e) {
115
- alert("error while launching archaeopteryx: " + e);
116
- }
117
- }
118
- },
119
- "text")
120
- .fail(function () {
121
- alert("error: failed to read tree(s) from \"" + loc + "\"");
122
- }
123
- );
124
- }
125
- </script>
106
+ <!-- D3.js (version 7): -->
107
+ <script src="https://d3js.org/d3.v7.min.js"></script>
108
+
109
+ <!-- SAX XML parser (needed by phyloxml.js): -->
110
+ <script src="https://path/to/sax.js"></script>
111
+
112
+ <!-- Archaeopteryx.js requires forester.js and phyloxml.js: -->
113
+ <script src="https://path/to/phyloxml.js"></script>
114
+ <script src="https://path/to/forester.js"></script>
115
+ <script src="https://path/to/archaeopteryx.js"></script>
126
116
  </head>
127
117
 
128
- <body onload="load()">
118
+ <body>
129
119
  <div>
130
120
  <h2>Archaeopteryx.js Basic Demo</h2>
131
- <div id='phylogram1'></div>
132
- <div id='controls0' class='ui-widget-content'></div>
121
+ <div id="phylogram1"></div>
133
122
  </div>
123
+
124
+ <script>
125
+ window.addEventListener('load', async () => {
126
+ const loc = 'https://path/to/apaf.xml';
127
+ const config = {}; // see "For Developers" below for what can go here
128
+
129
+ try {
130
+ const response = await fetch(loc);
131
+ if (!response.ok) {
132
+ throw new Error('HTTP ' + response.status + ' loading ' + loc);
133
+ }
134
+ const data = await response.text();
135
+
136
+ // launchArchaeopteryx returns a viewer handle:
137
+ // viewer.getSelectedNodes() -- the user's node selection
138
+ // viewer.destroy() -- unmount the viewer completely
139
+ // (call it when your view goes away;
140
+ // a later launch works normally)
141
+ const viewer = archaeopteryx.launchArchaeopteryx('#phylogram1', loc, data, config);
142
+ } catch (e) {
143
+ document.getElementById('phylogram1').textContent = 'Error: ' + e.message;
144
+ }
145
+ });
146
+ </script>
134
147
  </body>
148
+ </html>
135
149
  ```
136
150
 
151
+ Archaeopteryx.js reports problems by **throwing** — a failed download, an
152
+ unparsable tree, a container that does not resolve, and an unknown or removed
153
+ config key all land in the `catch` above with a message naming the problem.
154
+ See [For Developers](#for-developers) below for what goes into `config`.
155
+ Working copies of every file referenced above are vendored in this
156
+ repository's `docs/lib/` (they are what the [live demos](#live-demos) load),
157
+ and `docs/demo.html` is a real, running version of this page — including
158
+ using `viewer.destroy()` to switch trees in place.
159
+
160
+
161
+
162
+
163
+ ## Using the visualizations
137
164
 
165
+ A tree opens **already coloured** by its most informative field — the viewer
166
+ inspects what the tree carries (taxonomy, sequence fields, custom properties)
167
+ and decides by itself what is worth showing. There is nothing to configure.
138
168
 
169
+ * The **Color** and **Shape** menus (top of the control panel) list every
170
+ field worth visualizing, best first. Colouring paints the node label and
171
+ the node itself; shapes draw a symbol per value. Fields with many values
172
+ (hosts, species) sit at the end of the Color list — they are offered, but
173
+ never chosen for you.
174
+ * Numeric fields with few values are treated as **codes** (H5N1 vs H5N2)
175
+ and get individual colours; with many values they become a **gradient**
176
+ (years). The legend's `[colors]` / `[gradient]` chip switches between the
177
+ two where both make sense.
178
+ * The **legend** is a card you can **drag anywhere**. It shows a colour and
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
181
+ legends show the top 20 with a `[+N more]` chip. Legends are part of PNG,
182
+ PDF
183
+ 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.
189
+ * The **Visualizations** checkbox hides the chosen colours/shapes; the
190
+ **Visual Styles** checkbox controls colours embedded in the tree file
191
+ itself (and phyloXML branch colours). Search hits and selections always
192
+ outrank visualization colours. **Esc** returns to the opening state.
193
+
194
+ ## Layouts
195
+
196
+ Three layouts, switched with the second row of buttons at the top of the
197
+ panel: **rectangular** (root at left), **circular**, and **unrooted** — the
198
+ desktop's equal-angle fan, where each subtree opens a wedge proportional to
199
+ how many tips it holds.
200
+
201
+ In the two radial layouts the zoom row changes meaning, exactly as on the
202
+ desktop: **Y+ / Y− become the plain + / − zoom** (a circle has one diameter;
203
+ the mouse wheel zooms too, and never rotates), **X− / X+ become rotate** (a
204
+ 32nd of a turn per press), and the fit-width slot becomes the **node label
205
+ direction** flip — labels riding their spokes or standing upright — while
206
+ vertical expansion greys out. **Fit** centres and scales the fan; **Esc**
207
+ also resets rotation and label direction. Unrooted
208
+ additionally greys out the aligned-phylogram option and Auto-hide Labels
209
+ (there is no common label edge, and no even row spacing to hide against).
210
+
211
+ ## Searching
212
+
213
+ Two search boxes (A and B), each with its own **field** menu (built from what
214
+ the tree actually carries — names, taxonomy and sequence fields, every custom
215
+ property, branch lengths, confidences, structural values) and **match mode**
216
+ (contains / starts with / ends with / whole word / regex for text;
217
+ `= != < <= > >= range` for numbers). Inside one box, `,` means OR and `+` means AND (plain-text
218
+ modes only); **Combine A & B** intersects or unites the two. **Match case**
219
+ and **Inverse** apply to both.
220
+
221
+ Hits are hard to miss: their labels take the search colour **in bold**, a
222
+ translucent **pulsing halo** breathes behind each hit, and everything that is
223
+ *not* a hit fades — the desktop's "dim non-matches", engaged only while at
224
+ least one hit is actually visible, so a fruitless search never washes the
225
+ tree out. The **overview** miniature marks every hit as a dot in the same
226
+ colour, and a **◀ k / N ▶** navigator appears under the search boxes: each
227
+ press centres the previous / next hit in the viewport, wrapping around.
228
+
229
+ ## Keyboard
230
+
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.
237
+
238
+ ## Sequence alignments
239
+
240
+ A tree whose tips carry `<mol_seq is_aligned="true">` shows the alignment as
241
+ a residue track beside the tree (rectangular layout only — an alignment is
242
+ inherently horizontal). Amino acids use a physico-chemical colour scheme,
243
+ nucleotides one colour per base — decided from the residues themselves — with
244
+ gaps drawn as faint dashes that join into lines, so indel blocks read at a
245
+ glance.
246
+
247
+ A long alignment shows a scrollable **window** (at most ~60% of the display):
248
+ drag the slider at the window bottom, or roll the mouse wheel over the track;
249
+ the tree itself never moves. Under the rows sit a **conservation** bar per
250
+ column with the consensus residue beneath it (scored over the tips currently
251
+ displayed), and a 1-based **column ruler**. **Hover any residue** for its
252
+ alignment column, its position within that sequence's own ungapped residues,
253
+ its full name, class, and Kyte-Doolittle hydropathy. The **Alignment**
254
+ checkbox under Display Data toggles the whole track.
255
+
256
+ Alignments arrive with the tree: as phyloXML `<mol_seq is_aligned="true">`
257
+ elements, or in a **Nexus** file whose characters matrix accompanies its tree.
258
+ The **Nexus** entry in the Download menu writes the current tree *and* its
259
+ alignment back into one Nexus file (Taxa, Characters and Trees blocks).
260
+
261
+ ## Time trees
262
+
263
+ A tree whose nodes carry phyloXML `<date>` elements is drawn against time.
264
+ Ages (`unit="mya"` and friends, or values that look like ages) get the
265
+ **geologic axis**: two rows of ICS intervals in their official colours —
266
+ Period over Epoch for most trees, coarser pairs for Precambrian-deep ones —
267
+ plus a "Ma before present" ruler. Years (`unit="year"`, or values that look
268
+ like calendar years) get a labelled **calendar axis** instead. The tree's
269
+ layout itself never changes: time is an overlay calibrated by the dates, so
270
+ it also works for a fossil-only tree, where the axis simply stops at the
271
+ youngest tip and labels that age (the ammonite demo ends at the K-Pg, 66).
272
+
273
+ Nodes with a date **range** (`minimum`/`maximum` — minimum is the younger
274
+ bound) draw uncertainty bars: translucent blue **HPD age bars** on internal
275
+ nodes, sepia **fossil-range (FAD/LAD) bars with end caps** on tips. Node
276
+ tooltips show the date. The **Time Axis** checkbox under Display Data toggles
277
+ everything; the axis needs a phylogram (branch lengths carry the time) and
278
+ the rectangular layout. **Time Grid** (off by default, like the desktop's
279
+ "Time axis grid lines") adds faint vertical lines behind the tree at the
280
+ fine geologic-interval boundaries or the calendar year ticks, so a node's
281
+ position can be read against the axis.
139
282
 
140
283
  # forester.js
141
284
  forester.js is a general suite for dealing with phylogenetic trees.
@@ -204,3 +347,966 @@ In New Hampshire format:
204
347
 
205
348
 
206
349
 
350
+
351
+ # For Developers
352
+
353
+ This section describes the two configuration objects Archaeopteryx.js accepts.
354
+ It is generated against the current source; if something here disagrees with
355
+ the code, the code is right and this is a bug.
356
+
357
+ ## The entry points
358
+
359
+ ```js
360
+ // parse and launch in one step (what most callers want); fetch the file
361
+ // content yourself -- the library does no networking
362
+ const viewer = archaeopteryx.launchArchaeopteryx(container, fileName, data, config);
363
+
364
+ // or parse yourself, then launch
365
+ const tree = archaeopteryx.parseTree(fileName, data);
366
+ const viewer = archaeopteryx.launch(container, tree, config);
367
+
368
+ // later, e.g. when an SPA removes the view:
369
+ viewer.destroy();
370
+ ```
371
+
372
+ Both entry points take **exactly** the arguments shown — a call with the old
373
+ trailing arguments (the separate settings bag, `nodeVisualizations`,
374
+ `nodeLabels`, `specialVisualizations`, or the positional Newick parse
375
+ options) **throws** with a message saying where each one went: everything now
376
+ lives in the **one config object** (`nodeLabels` and
377
+ `internalNumericLabels` are config keys). `config` itself is optional — `archaeopteryx.launch('#phylogram1',
378
+ tree)` works.
379
+
380
+ `container` is a **CSS selector or the DOM element itself** (frameworks hand
381
+ you elements). A container that cannot be resolved **throws** — it used to
382
+ render nothing and say nothing. Both entry points return a **viewer handle**:
383
+
384
+ ```js
385
+ viewer.getSelectedNodes(); // the node-menu selections (enableManualNodeSelection)
386
+ viewer.ready; // a Promise: resolved once the tree is drawn
387
+ viewer.destroy(); // unmount COMPLETELY: the container DOM, the node
388
+ // menu / dialogs / alignment scroller, the window
389
+ // resize listener and every page-level key/wheel
390
+ // handler; a later launch() works normally
391
+ ```
392
+
393
+ **Big trees draw on the next frame.** Above 2,000 nodes, `launch()` does all
394
+ its validation, shows a "Drawing N nodes" card over the tree area, and
395
+ returns within milliseconds — the label analysis, visualization candidates,
396
+ control panel and the draw itself all run one frame later, so the browser
397
+ can paint the card instead of appearing frozen for the seconds a large tree
398
+ takes. Every error still throws synchronously from `launch()`
399
+ exactly as before; only the draw is deferred. `viewer.ready` resolves when it
400
+ has run (immediately for a small tree, which stays fully synchronous). Later
401
+ redraws on a big tree — a checkbox, a slider, a search — work the same way:
402
+ they show a "Redrawing" card and run on the next frame, and every redraw
403
+ requested in the same tick collapses into one. Wait on `ready` before reading
404
+ the tree's DOM after `launch()`:
405
+
406
+ ```js
407
+ const viewer = archaeopteryx.launch(container, tree, config);
408
+ await viewer.ready; // the SVG exists now
409
+ ```
410
+
411
+ The one part the library cannot defer for you is your own parse of a big
412
+ file before `launch()`. `archaeopteryx.busy()` shows the same card for that,
413
+ yields a frame so it paints, runs your work, and removes it:
414
+
415
+ ```js
416
+ archaeopteryx.busy(container, 'Reading ' + name, sizeMb + ' MB', function () {
417
+ const tree = archaeopteryx.parseTree(name, text);
418
+ viewer = archaeopteryx.launch(container, tree, config);
419
+ });
420
+ ```
421
+
422
+ Pass `document.body` as the container for a whole-page card; without the
423
+ work function it returns a remover and the yielding is up to you.
424
+
425
+ One viewer per page: the library keeps its display state in one place, so a
426
+ second launch — into any container — replaces the first. Launching into the
427
+ same container is the supported way to switch trees (the demo pages do
428
+ exactly that).
429
+
430
+ ### Loading the library
431
+
432
+ One file, loadable every way an embedder might want it:
433
+
434
+ * **`<script>` tags** (the classic path): load `d3` (v7), `sax`, `phyloxml`,
435
+ `forester`, then `archaeopteryx`; use `window.archaeopteryx`.
436
+ * **AMD** (Dojo, RequireJS): `require(['archaeopteryx'], ...)` — the module
437
+ reads its dependencies off the page's globals when first required (so load
438
+ the dependency scripts first), and also still sets `window.archaeopteryx`.
439
+ * **CommonJS / bundlers / Node**: `const {archaeopteryx} =
440
+ require('archaeopteryx')` — with **TypeScript definitions** included
441
+ (`archaeopteryx.d.ts` types the whole config object and the handle). In
442
+ plain Node, with no d3 at all, every **parser** works
443
+ (`archaeopteryx.parseNexus(...)` etc.); only `launch()` needs a browser
444
+ and d3, and says so by name. `forester` is importable as the extensionless
445
+ subpath `require('archaeopteryx/forester')` (the package's exports map
446
+ defines exactly that path).
447
+
448
+ Dependencies are checked when used, never at load time, and every failure
449
+ names exactly what is missing (including "the loaded d3 is not usable as d3
450
+ version 7"). The optional export libraries stay page-level globals in every
451
+ loading style: `window.Canvg` (PNG), `window.jspdf` + svg2pdf.js (PDF).
452
+
453
+ The parser is picked from the data and the `location`: content starting with
454
+ `#NEXUS` (or a name ending in `.nex`/`.nexus`) is read as Nexus, JSON content
455
+ (or a name ending in `.json`) as an **Auspice/Nextstrain v2** `dataset.json`,
456
+ 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.
460
+
461
+ An Auspice dataset opens on the **time view** (branch lengths from `num_date`
462
+ differences; a divergence-only build falls back to `div` differences): the
463
+ calendar time axis and node-age bars come from `num_date` and its confidence
464
+ interval, and every trait (country, host, clade, ...) becomes a
465
+ `nextstrain:<trait>` node property — so Color-by, search and the node dialog
466
+ pick them up. Both metrics are retained, and
467
+ `forester.applyTimeBranchLengths(phy)` /
468
+ `forester.applyDivergenceBranchLengths(phy)` /
469
+ `forester.hasTimeAndDivergence(phy)` are the plumbing for a future
470
+ time↔divergence display toggle.
471
+
472
+ **BEAST-style and NHX annotations** in Newick/Nexus input are always parsed
473
+ (they used to be discarded): in a `[&key=value,...]` blob — as written by
474
+ BEAST, BEAST 2, TreeAnnotator, FigTree and MrBayes — `posterior`, `prob`
475
+ (+`prob_stddev`) and `bootstrap` become confidences, node `height`
476
+ (median/mean) with its `95%_HPD` (or range) becomes the node date the age
477
+ bars draw, FigTree's `!color` becomes the branch colour, and every other
478
+ field (`rate`, traits, ...) becomes a `beast:<key>` node property for
479
+ Color-by and search. Classic `[&&NHX:...]` tags map to their phyloXML
480
+ equivalents (`S=` taxonomy, `T=` taxonomy id, `B=` support, `D=`
481
+ duplication/speciation event, `GN=`/`AC=` sequence name/accession). Plain
482
+ `[number]` brackets keep their old meaning (confidence values).
483
+
484
+ Both entry points **throw** on bad input — an undefined or empty tree, an
485
+ unparseable file, or a config key that no longer exists. Nothing is reported by
486
+ a popup any more, and nothing fails silently.
487
+
488
+ ## Supported file formats
489
+
490
+ | Format | I/O | What Archaeopteryx.js does with it | Ref. |
491
+ |---|---|---|---|
492
+ | **Newick** / New Hampshire (`.nwk`, `.nh`, `.tre`) | in / out | The base tree: topology, names, branch lengths, and bracketed confidence values. | [1] |
493
+ | **NHX** — New Hampshire eXtended | in | `[&&NHX:...]` tags riding on Newick: taxonomy (`S=`, `T=`), sequence (`GN=`, `AC=`), support (`B=`), duplication/speciation events (`D=`). | [2] |
494
+ | **Nexus** (`.nex`, `.nexus`) | in / out | One file for the tree(s) and, in a `CHARACTERS`/`DATA` block, an aligned protein/DNA/RNA matrix (sequential or interleaved) — the tree and its alignment together. | [3] |
495
+ | **phyloXML** (`.xml`) | in / out | The richest native format: taxonomy, sequences and alignments, dates, confidences, branch colours and arbitrary custom properties. | [4] |
496
+ | **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
+ | **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
+ | **MrBayes** annotations | in (embedded in Newick/Nexus) | `prob=`/`prob.stddev=` blobs: posterior-probability clade support. | [8] |
499
+ | **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
+ | 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
+
502
+ The parser for a given input is auto-detected (see **The entry points**
503
+ above); the Download menu offers whichever output formats the current tree
504
+ can carry.
505
+
506
+ Newick and Nexus files usually carry branch support as a bare internal label
507
+ (`)100:0.05`). Archaeopteryx.js recognises those automatically and treats them
508
+ as confidence values, so support-based features work without any setup. If your
509
+ internal labels are clade names rather than support, set
510
+ `internalNumericLabels: 'label'`. Bracketed values (`)[95]:0.05`) are
511
+ always read as confidences; a bracket that is not a number is a comment and is
512
+ ignored.
513
+
514
+ ### References
515
+
516
+ 1. Felsenstein, J. *PHYLIP (Phylogeny Inference Package)*. Department of
517
+ Genome Sciences, University of Washington, Seattle. The Newick tree
518
+ format itself has no single peer-reviewed citation — it was agreed at a
519
+ 1986 meeting of phylogenetics software authors at Newick's Lobster
520
+ House, Dover, New Hampshire, and has since been documented in the
521
+ PHYLIP distribution and in most subsequent tree-software manuals.
522
+ 2. Zmasek, C.M., Eddy, S.R. (2001). ATV: display and manipulation of
523
+ annotated phylogenetic trees. *Bioinformatics*, 17(4), 383–384.
524
+ 3. Maddison, D.R., Swofford, D.L., Maddison, W.P. (1997). NEXUS: an
525
+ extensible file format for systematic information. *Systematic
526
+ Biology*, 46(4), 590–621.
527
+ 4. Han, M.V., Zmasek, C.M. (2009). phyloXML: XML for evolutionary biology
528
+ and comparative genomics. *BMC Bioinformatics*, 10, 356.
529
+ 5. Hadfield, J., Megill, C., Bell, S.M., Huddleston, J., Potter, B.,
530
+ Callender, C., Sagulenko, P., Bedford, T., Neher, R.A. (2018).
531
+ Nextstrain: real-time tracking of pathogen evolution. *Bioinformatics*,
532
+ 34(23), 4121–4123.
533
+ 6. Drummond, A.J., Rambaut, A. (2007). BEAST: Bayesian evolutionary
534
+ analysis by sampling trees. *BMC Evolutionary Biology*, 7, 214.
535
+ 7. Bouckaert, R., Vaughan, T.G., Barido-Sottani, J., Duchêne, S., Fourment,
536
+ M., Gavryushkina, A., Heled, J., Jones, G., Kühnert, D., De Maio, N.,
537
+ Matschiner, M., Mendes, F.K., Müller, N.F., Ogilvie, H.A., du Plessis,
538
+ L., Popinga, A., Rambaut, A., Rasmussen, D., Siveroni, I., Suchard,
539
+ M.A., Wu, C.-H., Xie, D., Zhang, C., Stadler, T., Drummond, A.J. (2019).
540
+ BEAST 2.5: an advanced software platform for Bayesian evolutionary
541
+ analysis. *PLoS Computational Biology*, 15(4), e1006650.
542
+ 8. Ronquist, F., Teslenko, M., van der Mark, P., Ayres, D.L., Darling, A.,
543
+ Höhna, S., Larget, B., Liu, L., Suchard, M.A., Huelsenbeck, J.P. (2012).
544
+ MrBayes 3.2: efficient Bayesian phylogenetic inference and model choice
545
+ across a large model space. *Systematic Biology*, 61(3), 539–542.
546
+ 9. Pearson, W.R., Lipman, D.J. (1988). Improved tools for biological
547
+ sequence comparison. *Proceedings of the National Academy of Sciences
548
+ USA*, 85(8), 2444–2448.
549
+
550
+ ## Intelligent pre-sets
551
+
552
+ Almost everything that used to be configured is now read off the tree: a tree
553
+ with taxonomies shows taxonomies and offers a Taxonomy control, a tree without
554
+ them shows neither; a tree whose branches mostly carry lengths is drawn to
555
+ scale, one whose branches mostly do not is drawn as a cladogram.
556
+
557
+ So **the best configuration is usually an empty one**. What is left is the
558
+ handful of things no tree can answer for you: which layout, how the viewer is
559
+ sized, what the surrounding application allows, and what to prefill the search
560
+ boxes with.
561
+
562
+ Everything else is either derived, or a control the user can change once the
563
+ tree is on screen. Those controls still have defaults, and the defaults are
564
+ chosen per tree — they are simply no longer yours to set at launch.
565
+
566
+ That includes **which label checkboxes start checked**: instead of blindly
567
+ showing every field the tree carries, the viewer measures the actual label
568
+ text and unchecks fields that only repeat another field or that would make
569
+ the combined labels uselessly long (see *Initial label fields* in the
570
+ developer spec below). The checkboxes are still there to override it.
571
+
572
+ The same data-driven rule applies to the two big overlays: a tree whose tips
573
+ carry an aligned `mol_seq` opens with its **sequence alignment** showing, and
574
+ a tree with phyloXML `<date>` elements opens with its **time axis** drawn —
575
+ each with a checkbox under Display Data to turn it off.
576
+
577
+ Support and branch-length values draw **2 px smaller than the label font**
578
+ (never below 6 px), as on the desktop, so they annotate without competing.
579
+ And besides the numeric display there are **Support Dots**: a filled dot at
580
+ the midpoint of every branch whose support is at least 95% (`supportDotMinimum`;
581
+ posterior- and bootstrap-scaled trees are told apart automatically). The dot
582
+ is always a fixed amount wider than the branch itself, so it tracks the
583
+ Branch Width slider instead of sitting at one fixed size. A branch drawn
584
+ shorter than the dot itself stays clean.
585
+
586
+ ## Configuration
587
+
588
+ One object, passed as the third argument. It is optional, and the best
589
+ configuration is usually an empty one — almost everything that used to be
590
+ 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.
592
+
593
+ There used to be two objects, `options` and `settings`, split by whether the
594
+ user could also change the value from the control panel. That was a fact about
595
+ the internals, not something a caller could derive, and getting it wrong was
596
+ silent: the right name in the wrong object did nothing at all. There is now one
597
+ object. A fourth argument is still accepted and merged, so existing call sites
598
+ keep working; it logs a deprecation warning.
599
+
600
+ ### Still used
601
+
602
+ Every key below can be tried live in the
603
+ [open-your-own-tree page](https://cmzmasek.github.io/archaeopteryx-js/open.html)'s
604
+ **Expert options** panel, which also emits the resulting config as
605
+ copy-pastable JSON.
606
+
607
+ | Key | Default | What it does |
608
+ | --- | --- | --- |
609
+ | `collapseControlPanel` | `false` | Open with the control panel collapsed to just its header bar — the same state its own hide/show button toggles. |
610
+ | `enableDynamicSizing` | `true` | Size the tree to its container, and follow window resizes. |
611
+ | `displayWidth` | `800` | Width — only when dynamic sizing is off. |
612
+ | `displayHeight` | `600` | Height — only when dynamic sizing is off. |
613
+ | `zoomToFitUponWindowResize` | `true` | Re-fit the tree after a window resize. |
614
+ | `rootOffset` | `254` | Distance from the left edge to the root. The default clears the control panel: its inset plus its width plus a margin. |
615
+ | `layout` | `'rectangular'` | The starting layout: `'rectangular'`, `'circular'`, or `'unrooted'`. |
616
+ | `ladderizeTree` | `true` | Ladderize the tree on load: at each node, the larger clade first (any number of children, so a polytomy sorts too). |
617
+ | `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. |
618
+ | `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
+ | `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
+ | `showSupportDots` | `false` | Open with the Support Dots marks on (the checkbox appears whenever the tree has confidences). |
621
+ | `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
+ | `searchAinitialValue` | `null` | Prefill search box A. |
623
+ | `searchBinitialValue` | `null` | Prefill search box B. |
624
+ | `enableVisualizations` | `true` | Offer the Color / Shape visualizations (which fields they cover is decided from the tree). |
625
+ | `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. |
626
+ | `visualizationsLegendXpos` | `254` | Legend position, x. |
627
+ | `visualizationsLegendYpos` | `30` | Legend position, y. |
628
+ | `enableDownloads` | `true` | Offer the download buttons. |
629
+ | `pngExportScale` | `4` | PNG export resolution multiplier. |
630
+ | `nhExportWriteConfidences` | `true` | Write confidences into exported Newick. |
631
+ | `internalNumericLabels` | `'auto'` | Newick / Nexus parsing: how a bare numeric internal label (`)100:0.05`) is read. `'auto'` reads them as confidence values only when *every* internal label looks like support; `'confidence'` reads every numeric label as one, whatever its value; `'label'` keeps them as names. Replaces `nhConfidenceValuesAsInternalNames` (still accepted, with a warning; its `true` maps to `'confidence'`). |
632
+ | `nodeLabels` | `null` | Custom label-field checkboxes: `{key: {label, description, propertyRef, showButton, selected}}` — each adds a panel checkbox labelling nodes with the named property's value. (Was `launch()`'s sixth positional argument.) |
633
+ | `enableSubtreeDeletion` | `true` | Offer node / subtree deletion in the node menu. |
634
+ | `enableAccessToDatabases` | `true` | Offer the “Access DB” link in the node menu. |
635
+ | `enableManualNodeSelection` | `false` | Add the Select/Deselect entries to the node menu. |
636
+
637
+ ### Anything else throws
638
+
639
+ An unrecognised key is an error, whether it was removed in this modernization
640
+ or simply mistyped (the two deprecated keys below are the exception — they
641
+ warn rather than throw):
642
+
643
+ ```
644
+ ArchaeopteryxJS: ERROR: removed config key(s) passed to launch:
645
+ "circular" -- renamed to "layout": use layout: "circular"
646
+
647
+ ArchaeopteryxJS: ERROR: unknown config key(s) passed to launch: "enableDownlods"
648
+ ```
649
+
650
+ An ignored key looks like it worked. If you are upgrading, run once and fix
651
+ whatever it names.
652
+
653
+ ### Accepted, with a warning
654
+
655
+ Two keys are neither current nor removed: they are accepted so an existing
656
+ embed keeps working, and warn on the console. Both concern Newick support
657
+ values.
658
+
659
+ | Key | What happens |
660
+ |---|---|
661
+ | `nhConfidenceValuesAsInternalNames` | Translated to `internalNumericLabels`. **`true` becomes `'confidence'`, not `'auto'`** — `'auto'` is all-or-nothing and promotes nothing in a tree that mixes clade names with support, so a caller moved to it silently would lose promotions they had. An explicit `internalNumericLabels` always wins. |
662
+ | `nhConfidenceValuesInBrackets` | Retired: ignored. It gated whether `[95]` is read as a confidence, but setting it `false` never reinterpreted the bracket — it *discarded* it, so the option's only effect was to throw support values away. A bracket that is not a number is a Newick comment and was ignored either way, and NHX / BEAST blobs go through a different path. Bracketed values are now always read as confidences. |
663
+
664
+ ### What replaced the rest
665
+
666
+ A few of these are worth spelling out, because they are decisions rather than
667
+ constants:
668
+
669
+ * **Phylogram or cladogram** — the tree is drawn to scale when **more than half
670
+ its branches carry a positive length**. A tree where a handful of branches
671
+ have a length and the rest do not is not a phylogram with gaps; it is a
672
+ cladogram, and is now drawn as one.
673
+ * **Branch width** — `2` for a tree of 50 tips or fewer, `1` above that.
674
+ Hairlines suit a crowded tree; on a dozen branches they just look faint.
675
+ * **Labels** — node names, taxonomy, sequences, confidences and events are each
676
+ shown when the tree actually contains them.
677
+ * **Short names** — on from the start when the tree has names longer than 18
678
+ characters, off otherwise. The checkbox is always there either way.
679
+ * **Font** — one size (11) for every label, in whichever sans-serif the
680
+ reader's own system renders best.
681
+
682
+ ### Removed — passing these throws
683
+
684
+ All 118 of them, alphabetically:
685
+
686
+ | Key | Why, and what to do instead |
687
+ | --- | --- |
688
+ | `alignPhylogram` | Aligning the tips is a control, not a launch option. |
689
+ | `allowManualNodeSelection` | Renamed to `enableManualNodeSelection`. |
690
+ | `backgroundColorDefault` | The background is fixed. |
691
+ | `backgroundColorForPrintExportDefault` | The export background is fixed. |
692
+ | `border` | Style the tree's svg with CSS instead. |
693
+ | `branchColorDefault` | The default branch colour is fixed. |
694
+ | `branchDataFontSize` | Font size is fixed at launch (derived: 2px smaller than labels, floor 6px) and changed only via the in-panel Font slider — not a launch config key. |
695
+ | `branchWidthDefault` | Branch width follows the size of the tree. |
696
+ | `circular` | Renamed to `layout` (use `layout: 'circular'`). |
697
+ | `circularDisplay` | Replaced by `layout`: `'rectangular'` \| `'circular'` \| `'unrooted'`. |
698
+ | `collapsedLabelLength` | The collapse feature was removed. |
699
+ | `collapseLabelWidth` | The collapse feature was removed. |
700
+ | `controls0` | The control panel is created inside the tree's own container now. |
701
+ | `controls0Left` | The control panel is placed against the tree; drag it to move it. |
702
+ | `controls0Top` | The control panel is placed against the tree; drag it to move it. |
703
+ | `controls1` | The visualization menus moved into the main control panel. |
704
+ | `controls1Left` | The visualization menus moved into the main control panel. |
705
+ | `controls1Top` | The visualization menus moved into the main control panel. |
706
+ | `controls1Width` | The control panel sizes itself. |
707
+ | `controlsBackgroundColor` | The control panel follows the light / dark palette. |
708
+ | `controlsFont` | The legend uses the same sans-serif as the rest of the interface. |
709
+ | `controlsFontColor` | This never had any effect; the legend follows the tree's label colour. |
710
+ | `controlsFontSize` | The legend has one size. |
711
+ | `decimalsForLinearRangeMeanValue` | No longer configurable. |
712
+ | `defaultFont` | Labels use the sans-serif the reader's own system renders best. |
713
+ | `dynahide` | On by default; use the Auto-hide Labels checkbox. |
714
+ | `dynamicallyAddNodeVisualizations` | Visualizations are always derived automatically from the tree now. |
715
+ | `enableBranchVisualizations` | Merged into `enableVisualizations`. |
716
+ | `enableCollapseByBranchLenghts` | The collapse feature was removed. |
717
+ | `enableCollapseByFeature` | The collapse feature was removed. |
718
+ | `enableCollapseByTaxonomyRank` | The collapse feature was removed. |
719
+ | `enableMsaResidueVisualizations` | Colouring by aligned residue was removed. |
720
+ | `enableNodeVisualizations` | Merged into `enableVisualizations`. |
721
+ | `enableSpecialVisualizations2` | The special visualizations were removed. |
722
+ | `enableSpecialVisualizations3` | The special visualizations were removed. |
723
+ | `enableSpecialVisualizations4` | The special visualizations were removed. |
724
+ | `externalNodeFontSize` | Font size is fixed at launch (11px) and changed only via the in-panel Font slider — not a launch config key. |
725
+ | `filterValues` | Reshape the tree's properties yourself before calling launch. |
726
+ | `fontSize` | One default size for every label; the font-size slider changes it. |
727
+ | `found0and1ColorDefault` | The search / selection colours are fixed so they stay distinguishable. |
728
+ | `found0ColorDefault` | The search / selection colours are fixed so they stay distinguishable. |
729
+ | `found1ColorDefault` | The search / selection colours are fixed so they stay distinguishable. |
730
+ | `groupSpecies` | This setting was never read; it did nothing. |
731
+ | `groupYears` | This setting was never read; it did nothing. |
732
+ | `initialCollapseDepth` | The collapse feature was removed. |
733
+ | `initialCollapseFeature` | The collapse feature was removed. |
734
+ | `initialLabelColorVisualization` | Choose the visualization in the Visualizations panel. |
735
+ | `initialNodeFillColorVisualization` | Choose the visualization in the Visualizations panel. |
736
+ | `internalNodeFontSize` | Font size is fixed at launch (11px) and changed only via the in-panel Font slider — not a launch config key. |
737
+ | `labelColorDefault` | The default label colour is fixed. |
738
+ | `minBranchLengthValueToShow` | No longer configurable. |
739
+ | `minConfidenceValueToShow` | No longer configurable. |
740
+ | `nameForFastaDownload` | Download names follow `treeName`. |
741
+ | `nameForNhDownload` | Download names follow `treeName`. |
742
+ | `nameForPhyloXmlDownload` | Download names follow `treeName`. |
743
+ | `nameForPngDownload` | Download names follow `treeName`. |
744
+ | `nameForSvgDownload` | Download names follow `treeName`. |
745
+ | `nhExportReplaceIllegalChars` | Always on; Newick cannot carry those characters. |
746
+ | `nodeLabelGap` | The label gap is fixed. |
747
+ | `nodeSizeDefault` | Node size is fixed; the Node size slider changes it. |
748
+ | `nodeVisualizations` | The visualization-dictionary mechanism (with its per-visualization regex matching) was removed for good; visualizations are derived automatically from the tree itself. Throws as a config key — and any old positional argument after `config` is rejected by count. |
749
+ | `nodeVisualizationsOpacity` | No longer configurable. |
750
+ | `orderTree` | Renamed to `ladderizeTree`, to match the wording used everywhere else. |
751
+ | `phylogram` | The tree is drawn to scale when most of its branches have a length. |
752
+ | `propertiesToIgnoreForNodeVisualization` | Every property the tree carries is offered; choose what to show in the panel. |
753
+ | `searchFieldWidth` | The search boxes size themselves to the control panel. |
754
+ | `searchIsCaseSensitive` | Off by default; use the Match case checkbox. |
755
+ | `searchIsPartial` | Each search box picks its own match mode (contains / starts with / ends with / whole word / regex). |
756
+ | `searchNegateResult` | This is the state of the Inverse checkbox, not an input. |
757
+ | `searchProperties` | Choose the property in the search box's field menu instead. |
758
+ | `searchUsesRegex` | Choose the `regex` match mode in the search box instead. |
759
+ | `selectedColorDefault` | The search / selection colours are fixed so they stay distinguishable. |
760
+ | `shortenNodeNames` | On by default when the tree has long node names; use the Short Names checkbox. |
761
+ | `showBranchColors` | Merged into the Visual Styles checkbox, like the desktop's `Visual Styles/Branch Colors`. |
762
+ | `showBranchColorsButton` | The Visual Styles checkbox appears when the tree has branch colours or style properties. |
763
+ | `showBranchEvents` | Shown when the tree has branch events. |
764
+ | `showBranchLengthValues` | Off by default; use the Branch Length checkbox. |
765
+ | `showBranchVisualizations` | Node and branch visualizations are one switch now; use the Visualizations checkbox. |
766
+ | `showConfidenceValues` | Shown when the tree has confidences. |
767
+ | `showDistributions` | Off by default. |
768
+ | `showDynahideButton` | Shown automatically once the tree has enough tips to need it. |
769
+ | `showExternalLabels` | On by default; use the Ext. Labels checkbox. |
770
+ | `showExternalLabelsButton` | Always shown. |
771
+ | `showExternalNodes` | Node shapes now appear wherever a node visualization applies. |
772
+ | `showExternalNodesButton` | The Ext. Nodes switch no longer exists. |
773
+ | `showInternalLabels` | Off by default; use the Int. Labels checkbox. |
774
+ | `showInternalLabelsButton` | Shown automatically when the tree has internal node data. |
775
+ | `showInternalNodes` | Node shapes now appear wherever a node visualization applies. |
776
+ | `showInternalNodesButton` | The Int. Nodes switch no longer exists. |
777
+ | `showNodeEvents` | Shown when the tree has node events. |
778
+ | `showNodeName` | Shown when the tree has node names. |
779
+ | `showNodeNameButton` | Shown automatically when the tree has node names. |
780
+ | `showNodeVisualizations` | Node and branch visualizations are one switch now; use the Visualizations checkbox. |
781
+ | `showSearchPropertiesButton` | Properties are searched by choosing them in a search box's field menu. |
782
+ | `showSequence` | Shown when the tree has sequences. |
783
+ | `showSequenceAccession` | Sequence labelling follows what the tree contains. |
784
+ | `showSequenceButton` | Shown automatically when the tree has sequences. |
785
+ | `showSequenceGeneSymbol` | Sequence labelling follows what the tree contains. |
786
+ | `showSequenceName` | Sequence labelling follows what the tree contains. |
787
+ | `showSequenceSymbol` | Sequence labelling follows what the tree contains. |
788
+ | `showShortenNodeNamesButton` | Shown automatically when the tree has long node names. |
789
+ | `showTaxonomy` | Shown when the tree has taxonomies. |
790
+ | `showTaxonomyButton` | Shown automatically when the tree has taxonomies. |
791
+ | `showTaxonomyCode` | Taxonomy labelling follows what the tree contains. |
792
+ | `showTaxonomyCommonName` | Taxonomy labelling follows what the tree contains. |
793
+ | `showTaxonomyRank` | Taxonomy labelling follows what the tree contains. |
794
+ | `showTaxonomyScientificName` | Taxonomy labelling follows what the tree contains. |
795
+ | `showTaxonomySynonyms` | Taxonomy labelling follows what the tree contains. |
796
+ | `showVisualizations` | Off by default; use the Visualizations checkbox. |
797
+ | `specialVisualizations` | Removed along with the enableSpecialVisualizations2/3/4 settings. Throws as a config key — and any old positional argument after `config` is rejected by count. |
798
+ | `textFieldHeight` | The text fields size themselves to their content. |
799
+ | `treeName` | The name comes from the tree file. |
800
+ | `unrootedDisplay` | Replaced by `layout`; use `layout: 'unrooted'`. |
801
+ | `useVisualStyles` | On by default; use the Visual Styles checkbox. |
802
+ | `valuesToIgnoreForNodeVisualization` | Every value is shown; choose what to show in the panel. |
803
+ | `visualizationsLegendOrientation` | The legend orientation is fixed; the legend has its own control. |
804
+ | `visualizationsLegendXposOrig` | Internal bookkeeping; set visualizationsLegendXpos. |
805
+ | `visualizationsLegendYposOrig` | Internal bookkeeping; set visualizationsLegendYpos. |
806
+
807
+ ## The automatic visualization system (developer spec)
808
+
809
+ This section, together with **Value grouping** below, is written to be
810
+ sufficient for a skilled developer (or another Claude) to rebuild the
811
+ system. The division of labour is strict:
812
+
813
+ * **forester.js decides** — pure, DOM-free, Node-testable functions:
814
+ `visualizationCandidates(tree)` (what to offer and how),
815
+ `visualizationNodeValue(node, candidate)` (a node's folded value),
816
+ `nodeLabelProperty(tree)` (readable tip names),
817
+ `commonNamePrefix(tree, ref)` (shared-prefix for shortening),
818
+ `nodeVisualStyle(node)` (the `style:` namespace), and the `VIS_SYNONYMS`
819
+ dictionary.
820
+ * **archaeopteryx.js renders** — builds d3 scales from the descriptors,
821
+ owns one state object (`_vis`), the menus, the legends, and the
822
+ precedence chains below. It contains no classification logic.
823
+
824
+ The **Color** and **Shape** menus are filled **from the tree alone**. There is
825
+ nothing to configure and nothing to pass in: `launch()`'s old
826
+ `nodeVisualizations` argument and the `dynamicallyAddNodeVisualizations`
827
+ setting are gone, and passing either throws. What is worth offering is
828
+ decided by `forester.visualizationCandidates(tree)` and is under test
829
+ (`test/visualization_test.js`, 45 tests; the ten demo trees under
830
+ `docs/data/` are the fixtures and the executable spec — five of them real
831
+ ViPR / BV-BRC virus trees whose messy annotations the rules were tuned
832
+ against).
833
+
834
+ What gets offered, briefly:
835
+
836
+ * Candidates are taxonomy (code, scientific name, common name), sequence
837
+ (name, symbol, gene name), and node properties. The `style:` namespace is
838
+ never offered — the desktop reserves it for rendering instructions.
839
+ * A field must cover at least **⅔ of the external nodes** and have at least
840
+ 2 — and fewer than all — distinct values. Identifier-like fields
841
+ (accessions, genome ids) are refused.
842
+ * Up to **20** distinct values → **Color** (one fixed, colour-vision-aware
843
+ palette at every cardinality). Fields with **21+ values** (hosts, species)
844
+ are still offered — every value coloured, desktop-style, by extending the
845
+ palette with lightened / darkened cycles — provided their values genuinely
846
+ repeat (distinct ≤ 60% of covered nodes; near-unique fields like strains
847
+ stay out). Their legends show the 20 most frequent values with a
848
+ `[+N more]` chip to expand, and they are listed last and never
849
+ auto-applied. Numeric fields come in three bands: up to
850
+ **10** distinct values default to individual colours — numbers that few are
851
+ usually codes, like HA/NA subtypes — **11–20** default to a viridis
852
+ **colour ramp**, and both of those carry a `[colors]` / `[gradient]` switch
853
+ in their legend; above 20 it is a ramp with no switch. Legends list numeric
854
+ values in numeric order by default (words sort by count).
855
+ * Up to **7** distinct values → also **Shape** (the seven distinct d3 symbols).
856
+ * A node without a value keeps the default look, and the legend names the
857
+ field so partial coverage is visible.
858
+ * Property values are **grouped for colouring after normalization** —
859
+ spelling variants, host/country qualifiers, and a common-animal synonym
860
+ dictionary. The exact algorithm is specified under **Value grouping**
861
+ below; raw values remain untouched everywhere else — exports, search,
862
+ and the node-data dialog.
863
+
864
+ The menus are ordered **best first** — categorical fields ahead of numeric
865
+ ramps, then by coverage × balance (the normalized entropy of the value
866
+ distribution), so a field that reads "Nonhuman Mammal" on 92% of its nodes
867
+ sits below one that actually splits the tree. **The best candidate is applied
868
+ automatically on load**: a tree opens coloured by its most informative field
869
+ rather than grey with a menu to discover. Esc resets back to that state.
870
+
871
+ **Local candidacy, stable identity.** Candidates, bands, counts, menus and
872
+ legends always describe the **displayed** tree: switch into a subtree (or
873
+ delete part of the tree) and everything is re-derived, so a field that was
874
+ refused on the full tree — Species at 66 values, say — is offered inside a
875
+ clade where it has six. A value's **colour and shape, by contrast, are
876
+ identities**: assigned once per launch and remembered, so nothing recolours
877
+ when you dive in and out, and deleting a clade never shifts the colours of
878
+ what survives. Numeric ramps are the exception by design — a ramp's colour is
879
+ position in the view's range, so a six-year subtree gets a full-width
880
+ gradient. Your Color/Shape choice survives a view change when its field is
881
+ still a candidate there; otherwise the menu returns to default (and the tree
882
+ falls back to `style:` colours where the file carries them). Auto-apply
883
+ happens only at launch, and view changes never touch any checkbox. Choosing a
884
+ Color also switches the Visualizations checkbox on, since one colour paints
885
+ both the label and the node.
886
+
887
+ ### Candidate descriptors
888
+
889
+ `visualizationCandidates` returns an ordered array of plain objects:
890
+
891
+ ```js
892
+ { id: 'prop:vipr:Genus', // 'prop:'+ref | 'tax:code' | 'seq:name' | ...
893
+ kind: 'property', // 'property' | 'taxonomy' | 'sequence'
894
+ ref: 'vipr:Genus', // property ref; null for element slots
895
+ label: 'Genus', // prettified for menus and legend titles
896
+ numeric: false,
897
+ coverage: 307, total: 321, // nodes with a value / external nodes
898
+ values: ['Aviadenovirus', …], // one entry per GROUP (sorted; numeric fields numerically)
899
+ counts: {Aviadenovirus: 12, …},
900
+ canon: {aviadenovirus: 'Aviadenovirus', …}, // group key → display (property fields)
901
+ cut: null, // ';' | ':' | null (host/country qualifier)
902
+ score: 0.704, // coverage/total × normalized entropy
903
+ colorMode: 'category', // 'category' | 'range' (the DEFAULT mode)
904
+ switchable: false, // numeric ≤20 distinct: legend chip may flip the mode
905
+ wide: false, // categorical 21+ distinct (legend caps at 20)
906
+ shape: true } // ≤7 distinct: also offered in the Shape menu
907
+ ```
908
+
909
+ Labels are prettified from refs (underscores → spaces, camelCase split,
910
+ lowercase words capitalized, words with capitals kept: `PANGO_Lineage_L0` →
911
+ "PANGO Lineage L0"); a cross-namespace label collision falls back to the
912
+ verbatim refs.
913
+
914
+ ### Ranking and auto-apply
915
+
916
+ `score = (coverage / total) × (H / ln distinct)` where
917
+ `H = −Σ p·ln p` over the groups, `p = groupCount / coverage` — coverage
918
+ times the normalized entropy of the value distribution, so a field that
919
+ reads one value on 92% of nodes ranks low even at full coverage. Candidates
920
+ sort by **tier** (clean categorical → range → wide), then score descending,
921
+ ties by label case-insensitively then id. At launch the viewer applies
922
+ `candidates[0]` automatically — unless it is wide — and turns the
923
+ Visualizations checkbox on. Auto-apply happens **only** at launch.
924
+
925
+ ### Palettes, scales and shapes
926
+
927
+ * **Categorical**: `VIS_COLOR_PALETTE`, exactly 20 entries — Observable10
928
+ (`#4269d0 #efb118 #ff725c #6cc5b0 #3ca951 #ff8ab7 #a463f2 #97bbf5
929
+ #9c6b4e #9498a0`) followed by each darkened (× 0.7^0.9 per RGB channel).
930
+ Past 20 (wide fields), `extendedPaletteColor(i)` continues it: cycle
931
+ `k = ⌊i/20⌋` re-uses entry `i mod 20` blended `min(0.55, 0.2·k)` toward
932
+ white (odd cycles) or black (even), via d3.interpolateRgb.
933
+ * **Ranges**: 3-stop viridis `#440154 #21908C #FDE725` on a linear scale
934
+ with domain `[min, mean, max]` of the view's **distinct** numeric values
935
+ (mean of distinct values, not of nodes).
936
+ * **Shapes**: `['circle','square','diamond','triangle','cross','star','wye']`
937
+ — the 7 genuinely distinct d3 v7 fill symbols.
938
+
939
+ ### Views: local candidacy, stable identity
940
+
941
+ Candidates, bands, counts, menus and legends always describe the
942
+ **displayed** tree. On every view change — switch to subtree, return (whole
943
+ or by one), subtree deletion, Esc — `refreshVisualizations()` re-runs the
944
+ classifier on `displayedRoot()` (`_in_subtree ? _root : _treeData`). A field
945
+ refused on the full tree is offered inside a clade that earns it.
946
+
947
+ Colour and shape are **identities**, held in launch-lifetime memory maps
948
+ (`_vis.colorMemory` / `shapeMemory`, keyed `fieldId → normalizedValue →
949
+ colour`; values lowercased for property fields, verbatim for element slots).
950
+ First assignment wins forever; new values met in smaller views take the next
951
+ free palette slot (`colorNext`/`shapeNext` counters). The launch view
952
+ assigns its sorted domain 0,1,2,…, so first-view behaviour equals a plain
953
+ indexed palette. **Numeric ramps are the deliberate exception**: their
954
+ domain is recomputed per view (a ramp's colour is position in the view's
955
+ range, and a six-year subtree deserves a full-width gradient).
956
+
957
+ A selection survives a view change when its field is still a candidate;
958
+ otherwise the menu returns to `default` and the tree falls back down the
959
+ precedence chain (often to `style:` colours). View changes move no
960
+ checkbox. Esc re-applies the launch auto-choice only if its field still
961
+ exists, and clears the per-legend chip states.
962
+
963
+ ### Rendering precedence
964
+
965
+ Highest first, at each paint:
966
+
967
+ * **Label colour**: search/selection highlight → active Color visualization
968
+ → `style:font_color` → phyloXML branch colour → theme ink.
969
+ * **Node fill**: highlight → duplication/speciation event colour → active
970
+ Color visualization → `style:node_color` (else `style:font_color`) →
971
+ background.
972
+ * **Node outline**: darkened highlight → event colour → visualization fill
973
+ → style colour → branch colour → branch default.
974
+ * **Node shape path**: suppressed for highlighted/event nodes; chosen Shape
975
+ visualization → `style:node_shape`. A node earns its default dot when a
976
+ Color visualization is active (and no shape was drawn), or when it
977
+ carries `style:node_color` — `font_color` alone paints only the label.
978
+
979
+ ### Legend anatomy
980
+
981
+ One draggable SVG card per active visualization (colour, then shape,
982
+ stacked; both move together, positions from
983
+ `visualizationsLegendXpos/Ypos`). Card: background `backgroundColorDefault`
984
+ at 0.92 opacity, border `branchColorDefault` at 0.5, radius 5 — all theme
985
+ colours, so the always-light export rewrite handles them. Title row: the
986
+ candidate's label plus chips laid right-to-left, each shown only when
987
+ meaningful: sort (`[by count]` ⇄ `[A-Z]`/`[by value]`; numeric legends
988
+ default to value order, word legends to count order), mode
989
+ (`[colors]` ⇄ `[gradient]`, switchable numeric fields), expand
990
+ (`[+N more]` ⇄ `[fewer]`, wide fields; the cap keeps the 20 most frequent
991
+ under either sort order). Rows: 9px rounded swatch (or stroked shape
992
+ glyph), value text (ellipsized past 28 chars), count right-aligned at 0.55
993
+ opacity; a dashed **no value** row (total − coverage) pinned last. Gradient
994
+ legends: a 10px bar filled by an SVG linearGradient whose middle stop sits
995
+ at the mean's true position in `[min,max]`, min/max labels beneath, no
996
+ sort/expand chips. Text measured with a canvas 2D context in the legend's
997
+ font: the tree's font size floored at 11. Chip clicks stopPropagation on
998
+ mousedown so they do not start a drag; per-legend chip state lives in
999
+ `_vis.legendSortById / colorModeById / legendExpandedById` (keyed by field
1000
+ id, surviving view changes, cleared by Esc).
1001
+
1002
+ ### State and code map
1003
+
1004
+ All viewer state is one object, `_vis`, reset per launch:
1005
+ `candidates`, `byId`, `colorId`, `shapeId` (current choices),
1006
+ `autoColorId`, `labelRef`, `labelPrefix`, `hasStyles` (launch-frozen),
1007
+ `legendSortById`, `colorModeById`, `legendExpandedById` (per-legend chips),
1008
+ `colorMemory`, `colorNext`, `shapeMemory`, `shapeNext` (identity maps).
1009
+ Key viewer functions: `initializeVisualizations` (launch),
1010
+ `computeVisualizationCandidates(viewRoot)` (descriptors → scales),
1011
+ `refreshVisualizations` (view change), `visualizationColorFor` /
1012
+ `makeNodeVisShape` (per-node paint), `displayNodeName` (readable names),
1013
+ `drawLegendCard` / `addLegends`, `populateVisualizationMenus`.
1014
+
1015
+ ### Value grouping (normalization + synonym dictionary)
1016
+
1017
+ Applies to **node property values only**, and only for colouring/legends:
1018
+ taxonomy and sequence elements are used verbatim, and node names, exports,
1019
+ search, autocomplete and the node-data dialog always see the raw values.
1020
+ Grouping runs **before** classification, so distinct-value counts, the
1021
+ category/range/wide bands, and the entropy score are all computed on groups.
1022
+
1023
+ Each raw value maps to its **display form** by these steps, in order:
1024
+
1025
+ 1. **Qualifier cut** — only when the ref's local name (the part after the
1026
+ last `:` in the ref, compared case-insensitively) is exactly `host` or
1027
+ exactly `country`. For `host`, cut at the first `;`; for `country`, at
1028
+ the first `:`. No other refs are cut (`host_group`,
1029
+ `isolation_country` etc. keep their full values).
1030
+ 2. **Parenthesis repair** — if a cut left an unclosed `(`, truncate at the
1031
+ first unmatched `(`. (`Saimiri boliviensis (squirrel monkey; voucher:
1032
+ SBB04)` → cut at `;` → repair → `Saimiri boliviensis`.)
1033
+ 3. **Spelling fold** — trim; replace every `_` with a space; collapse each
1034
+ whitespace run to a single space; trim again (a leading/trailing `_`
1035
+ survives the underscore replacement as a bare space, so the fold is not
1036
+ done until this second trim). A value that becomes empty is dropped.
1037
+ 4. **Dictionary lookup** — lowercase the whole folded value and look it up
1038
+ in the synonym table below (each canonical name matches itself too). If
1039
+ there is no hit and the value ends in a parenthetical, retry once with
1040
+ one trailing `(...)` removed (`Bos taurus (cattle)` → `bos taurus`). On
1041
+ a hit, the display form is the canonical name. Matching is **whole-value
1042
+ only, never substring** — `ferret badger` and `42-day-old pig` keep
1043
+ their own groups.
1044
+
1045
+ The **group key** is the display form lowercased; values sharing a key are
1046
+ one group (one legend row, one colour, counts summed). The legend shows the
1047
+ group's **representative**: the canonical name for dictionary hits;
1048
+ otherwise the group's most frequent display spelling (ties broken by
1049
+ code-point order, ascending) with its first character uppercased.
1050
+
1051
+ The dictionary (`VIS_SYNONYMS` in forester.js — one constant, extend it
1052
+ there). Synonyms are matched lowercase. **Cross-implementation contract:**
1053
+ this dictionary, the element-slot ids (`tax:code`, `seq:name`, ...), and the
1054
+ verbatim-for-elements rule above are carried identically on the desktop —
1055
+ extend either side and the other, never just one.
1056
+
1057
+ | canonical | synonyms |
1058
+ | --- | --- |
1059
+ | Human | humans, homo sapiens, h. sapiens |
1060
+ | Cow | bovine, calf, cattle, bull, heifer, bos taurus, b. taurus |
1061
+ | Chicken | broiler chicken, broiler, hen, rooster, gallus gallus, g. gallus, gallus gallus domesticus |
1062
+ | Mouse | house mouse, murine, mus musculus, m. musculus |
1063
+ | Rat | brown rat, norway rat, black rat, rattus norvegicus, r. norvegicus, rattus rattus |
1064
+ | Ferret | domestic ferret, mustela putorius furo, mustela furo, m. putorius furo |
1065
+ | Guinea pig | cavy, domestic guinea pig, cavia porcellus, c. porcellus |
1066
+ | Rhesus monkey | rhesus macaque, macaca mulatta, m. mulatta |
1067
+ | Rabbit | european rabbit, oryctolagus cuniculus, o. cuniculus |
1068
+ | Dog | canine, canis familiaris, canis lupus familiaris, c. familiaris |
1069
+ | Cat | feline, domestic cat, felis catus, f. catus, felis silvestris catus |
1070
+ | Duck | mallard, mallard duck, domestic duck, anas platyrhynchos, a. platyrhynchos |
1071
+ | Pig | swine, porcine, hog, piglet, sus scrofa, s. scrofa, sus scrofa domesticus |
1072
+ | Horse | equine, mare, stallion, equus caballus, e. caballus |
1073
+ | Sheep | ovine, lamb, ewe, ovis aries, o. aries |
1074
+ | Goat | caprine, capra hircus, c. hircus |
1075
+ | Camel | dromedary, bactrian camel, camelus dromedarius, camelus bactrianus, c. dromedarius |
1076
+
1077
+ This is deliberately **display grouping, not data cleaning**: spelling and
1078
+ a short list of unambiguous synonyms, nothing semantic beyond it. It goes
1079
+ one step further than the desktop (which folds spellings and
1080
+ `human → Homo sapiens` only) — the dictionary, the parenthesis repair, and
1081
+ folding to capitalized common names (`Human`, not `Homo sapiens`) are this
1082
+ viewer's own choices.
1083
+
1084
+ ### Readable tip names
1085
+
1086
+ Database exports often name their tips with identifiers
1087
+ (`PATRIC.10334.249.FJ478159…`, `11320.305060`) while carrying the readable
1088
+ name in a property such as `BVBRC:genome_name`. When at least 80% of the tip
1089
+ names look like identifiers and a `…name` property is well-covered, mostly
1090
+ distinct and mostly wordy, that property is **displayed as the tip label**
1091
+ instead (`forester.nodeLabelProperty` makes the call, under test). Readable
1092
+ names are never overridden, exports and the node-data dialog keep the real
1093
+ name, and searching Node Name still searches the real name.
1094
+
1095
+ Shortened names drop the boring part first: when every tip shares a long
1096
+ prefix ("Influenza A virus …"), Short Names strips it before truncating, so
1097
+ what survives is the part that tells the tips apart ("A/duck/V..668/2017"
1098
+ rather than 300 identical "Influenz.." labels).
1099
+
1100
+ Both menus live in the single control panel, above Display Data, which is where
1101
+ the desktop puts them.
1102
+
1103
+ ### Initial label fields
1104
+
1105
+ Which of the three label checkboxes — Node Name, Taxonomy, Sequence — start
1106
+ checked is decided per tree by `forester.suggestLabelFields(root, extractors)`
1107
+ (pure, under test). The viewer hands it, per external node, exactly the text
1108
+ each field would print with the subfield cascade already applied (one good
1109
+ taxonomy identifier, one good sequence identifier, and the **displayed** node
1110
+ name — when the readable-tip-name substitution applies, the substituted name
1111
+ is what gets judged; only Short Names shortening is a later display nicety).
1112
+ Two rules, in order:
1113
+
1114
+ 1. **Redundancy.** For each ordered pair of fields (priority: name >
1115
+ taxonomy > sequence), if one field's text is *contained* in the other's —
1116
+ compared lowercased with spaces and underscores stripped — on **≥ 90%** of
1117
+ the nodes carrying both (and at least 2 such nodes), the contained field
1118
+ starts unchecked; on mutual containment the higher-priority field is kept.
1119
+ This is what removes ` | Feline calicivirus` from tips already named
1120
+ `Feline_calicivirus|CH-JL2|…`, and ` | MOUSE` from `22_MOUSE`.
1121
+ 2. **Length budget.** If the median length of the combined remaining label
1122
+ (fragments joined with `" | "`) still exceeds **50 characters**, only the
1123
+ single most *identifying* field stays: highest ratio of distinct
1124
+ (normalized) values to **all external nodes** — judged over every tip, not
1125
+ just the labelled ones, so a field carried by a handful of tips cannot win
1126
+ and leave the rest unlabelled. The top ratio is found first; every field
1127
+ within **0.05** of it then competes by the priority order, with one
1128
+ override: a lower-priority contender whose median length is under **60%**
1129
+ of the current pick's (substantially more economical) takes it.
1130
+
1131
+ A field with no printable values is never checked (apaf's sequences carry
1132
+ only domain architectures, so its Sequence box starts unchecked). The choice
1133
+ is logged to the console at launch, and the user can recheck anything — the
1134
+ rules only set the initial state.
1135
+
1136
+ ### Visual styles (the desktop's `style:` namespace)
1137
+
1138
+ phyloXML written by the desktop, ViPR or BV-BRC can carry per-node rendering
1139
+ instructions as properties in the reserved `style:` namespace. Five are
1140
+ honoured: `style:font_color`, `style:node_color`, `style:node_shape`
1141
+ (circle / rectangle / diamond), `style:font_size` and `style:font_style`
1142
+ (italic / bold / bold_italic). The rest of the vocabulary (font name,
1143
+ `node_size`, `node_transparency`, `node_fill_type`) is not, yet.
1144
+
1145
+ The **Visual Styles** checkbox — the desktop's "Visual Styles/Branch Colors",
1146
+ shown when the tree carries either, on by default — turns them off and on,
1147
+ and gates phyloXML `<color>` branch colours too, exactly as on the desktop.
1148
+ An active Color visualization outranks `style:font_color`, also as on the
1149
+ desktop: set the Color menu to default to see the tree as its file styled it.
1150
+ `style:` never appears in the Color / Shape menus — it is rendering, not data.
1151
+
1152
+ ### Moving the legends
1153
+
1154
+ The legends drawn over the tree are **dragged with the mouse** — grab one
1155
+ anywhere and both move together, keeping their stacked order. The old Vis
1156
+ Legend fieldset (Show / Dir / four arrows / R) is gone; so is the shift- or
1157
+ alt-click placement it documented. `visualizationsLegendXpos` and
1158
+ `visualizationsLegendYpos` still set where they start out.
1159
+
1160
+ ## The layouts, alignment track and time axes (developer spec)
1161
+
1162
+ The 2026 additions beyond the visualization system, specified tightly enough
1163
+ to rebuild. All pure logic lives in forester.js under `npm test`; the viewer
1164
+ draws.
1165
+
1166
+ ### The unrooted layout
1167
+
1168
+ `forester.equalAngleLayout(root, startAngle, lengthOf)` — Meacham's
1169
+ equal-angle rule, one pass, no daylight iterations. The root sits at (0,0)
1170
+ and owns `[startAngle, startAngle + 2π)`; each child receives a wedge of its
1171
+ parent's proportional to the external nodes it encloses, sits at its wedge's
1172
+ **mid-angle** at distance `lengthOf(child)`, and recurses into its own wedge.
1173
+ Angles are absolute screen radians (y down), inherited and subdivided — never
1174
+ re-referenced to the incoming branch, whose direction is implicitly the wedge
1175
+ midpoint. Writes `ux, uy, uangle` onto every node; returns `{maxRad}`.
1176
+ `startAngle` is `-π/2` (first wedge opens upward) plus the shared rotation
1177
+ offset. Spoke lengths: phylogram — `distToRoot` differences × a factor
1178
+ fitting the deepest tip into `0.42 × min(displayWidth, displayHeight)`;
1179
+ cladogram — one constant step per level (the desktop's integer-division bug
1180
+ here was fixed, not ported). Straight branch lines.
1181
+
1182
+ Circular and unrooted form the **radial family** (`radialDisplay()`): they
1183
+ share rotation, the label-direction flip, single-axis zoom, and the label
1184
+ mathematics — `spokeAngle(d)` is `uangle` in unrooted and the cluster angle
1185
+ minus π/2 in circular; `labelAngleDeg` rotates a label along its spoke and
1186
+ `labelFlip` adds 180° on the left half (`spokeAngle mod 2π ∈ (π/2, 3π/2)`).
1187
+ `layoutPointXY(d)` resolves a node's position in any layout for every
1188
+ consumer (overview dots, hit navigator, node transforms). Unrooted disables
1189
+ aligned phylograms and label auto-hiding, as the desktop does.
1190
+
1191
+ ### The alignment track
1192
+
1193
+ Data model: per-tip `sequences[0].mol_seq = {is_aligned, value}` (the gapped
1194
+ row); alignment length = the **max** row length (never assume rectangular —
1195
+ a short row's tail reads as gaps). Gate: `showMsa` state (auto-on when
1196
+ `alignedMolSeqs && maxMolSeqLength > 0`) AND rectangular layout.
1197
+
1198
+ Geometry: the track reserves `MSA_TRACK_GAP(8) + band` from `_w`, where
1199
+ `band = clamp(viewportWidth × 0.6, 120 px, whatever leaves the tree ≥ 220
1200
+ px)` — budgeted from the **viewport**, not the zoomed layout width. Its right
1201
+ edge lands exactly on the canvas edge (the fit translates the layout by
1202
+ `rootOffset`, so the track anchors at `displayWidth − rootOffset − band`).
1203
+ Rows tile the cluster height: each shared boundary is derived **once** as the
1204
+ midpoint between adjacent tip rows (per-row `y ± half` rounds a pixel apart
1205
+ and paints a seam); a cell's width is the *next* cell's rounded left edge
1206
+ minus its own, so cells abut at fractional widths. Column width is fixed
1207
+ (7 px); letters draw when rows are ≥ 8 px tall, in monospace with black or
1208
+ white ink by luminance (< 140 → white); when letters are off, same-colour
1209
+ runs merge into single rects and gap runs into single lines — that is what
1210
+ keeps big trees drawable. Only a true alignment edge gets a boundary line, so
1211
+ a scroll cutoff is distinguishable from the end.
1212
+
1213
+ Palettes (forester, frozen, byte-matched against the desktop): seven
1214
+ Zappo-style amino-acid classes, one colour per base (T ≡ U), grey ambiguity;
1215
+ amino-vs-nucleotide judged from the residues of the first non-empty row
1216
+ (> 90% ACGTUN of non-gaps → nucleotide). Gap characters: `- . ~ space`.
1217
+ Conservation (`forester.msaConservation`, scored over the **visible window**
1218
+ only): identity = most-common-residue count / rows (gaps stay in the
1219
+ denominator); information = `(log₂K − H)/log₂K × nonGapFraction`, K = 4 or
1220
+ 20; consensus = most common non-gap residue, ties alphabetical. The hover
1221
+ readout (`forester.msaResidueInfo`, `msaUngappedPosition`) names the residue
1222
+ (desktop vocabulary, selenocysteine and pyrrolysine included), its class
1223
+ (purine/pyrimidine for bases) and Kyte-Doolittle hydropathy.
1224
+
1225
+ Navigation: a lazily-created bar fixed at the viewport bottom — first / page
1226
+ back / slider / page forward / last, a jump-to-column box (1-based, matching
1227
+ the hover readout) and a live "column N – M of total" — plus wheel-over-track
1228
+ at a tenth of a screen per notch. Every route lands in one `msaScrollTo()`,
1229
+ which clamps and redraws; the tree never moves. A faint dashed guide runs
1230
+ from each tip's label (or its node, when labels are hidden) across to that
1231
+ tip's row, so a row reads back to its sequence without counting.
1232
+
1233
+ The conservation bar, consensus row and column ruler are a **floating strip**
1234
+ (see the time axes below); the residue rows stay with their tips.
1235
+
1236
+ ### The time axes
1237
+
1238
+ Data model: per-node `date = {unit, desc, value, minimum, maximum}` —
1239
+ **minimum is the younger bound, maximum the older**, everywhere. Detection
1240
+ (`forester.timeAxisInfo`): unit sets decide (`mya ma myr(s) my ga gya bya
1241
+ kya million/billion years` → geologic; `year(s) yr(s) cal ce ad calendar…` →
1242
+ calendar); unitless values fall back to magnitude — a strict majority in
1243
+ [1500, 2200] reads as years, `max > 10 && min ≤ 5% of max` as ages.
1244
+ Non-finite values are ignored (a `1e400` in a file once hung the tick
1245
+ loops). Calibration: the **largest** date value is the root age (geologic)
1246
+ or the present (calendar) — no unit conversion is done, so a Ga-valued tree
1247
+ is misbanded exactly as on the desktop.
1248
+
1249
+ Rendering (phylogram + rectangular only; the layout never changes): age→x is
1250
+ `anchorX + (anchorAge − age) × corr` with `corr` the branch-length scale's
1251
+ slope, anchored at the **deepest dated tip** rather than the root — the root
1252
+ and its direct children carry a synthetic half-average branch length, which
1253
+ would shift every band. The geologic axis draws
1254
+ `forester.geoBandRanks(rootAge)` — the finest of Period/Epoch, Era/Period,
1255
+ Eon/Era that still covers the range — as two 13 px rows of ICS intervals
1256
+ (official colours; ink by the same luminance rule), clipped to
1257
+ `[youngestTipAge, rootAge]`, then a ruler with ~8 ticks at 1/2/5 × 10ᵏ steps;
1258
+ a fossil-only tree's youngest age is labelled even when it is not a round
1259
+ tick. The calendar axis is a whole-year ruler over
1260
+ `[present − maxDistToRoot, present]`. Uncertainty bars use
1261
+ `x ± (bound − value) × s` where `s` is `+corr` for ages and `−corr` for
1262
+ years (so the earlier bound always lands left): internal → 7 px translucent
1263
+ blue `rgba(70,130,220,.35)`; tips → 5 px sepia `rgba(150,100,55,.86)` with
1264
+ ±4 px end caps. The MSA footer and the time axis occupy the same bottom
1265
+ strip side by side (track right, axis left), so their reserves take
1266
+ `max(56, 52 or 26)`, not the sum. The ICS table (`forester.geoIntervals`
1267
+ etc., 69 intervals, frozen) is byte-identical to the desktop's; reference:
1268
+ Cohen, K.M., Harper, D.A.T., Gibbard, P.L. & Car, N. (2025, updated),
1269
+ Episodes 48: 105-115; www.stratigraphy.org.
1270
+
1271
+ Banding ranks (`forester.geoBandRanks(youngMa, oldMa)`, shared with the
1272
+ desktop — change both or neither) take the span the tree actually occupies,
1273
+ youngest tip to root, not zero to root, so a fossil-only clade bands on its
1274
+ own window. A window overlapping one or two Series bands Series over **Stage**
1275
+ (the 101 ratified Phanerozoic stages plus the Pridoli, standing in for its own
1276
+ span as the printed ICS chart does); wider windows fall through the
1277
+ Period/Epoch → Era/Period → Eon/Era ladder, the finest pair that still fully
1278
+ covers the range. The overlap test is strict at both ends, so a window that
1279
+ merely touches a Series does not count it. A band label is drawn only where it
1280
+ fits its cell; a narrow stage keeps its colour and loses its name.
1281
+
1282
+ **Floating strips.** The axes and the alignment's conservation/consensus/ruler
1283
+ strip live on `_floatGroup`, a sibling of the zoomed tree group that is not
1284
+ itself transformed. Each is drawn in tree coordinates as usual and registered
1285
+ with `floatStripGroup(cls, top, height)`; `placeFloatingOverlays()` then gives
1286
+ it the tree's x and scale but a **sticky** y — `min(tree y, viewport bottom −
1287
+ strip)` — so it rides at the tree's bottom edge until that edge would leave
1288
+ the viewport and holds there instead. Each carries an opaque backdrop with a
1289
+ top rule, so tips panned underneath do not show through. Grid lines, per-node
1290
+ age bars and the alignment rows stay in the tree group, being bound to tips or
1291
+ spanning the tree's height. Exports re-anchor every strip to the tree, so a
1292
+ figure never carries an artefact of where the view happened to be scrolled.
1293
+ (The desktop pins its axes to the viewport bottom always; sticky is a
1294
+ deliberate difference.)
1295
+
1296
+ ## Node selection
1297
+
1298
+ With `enableManualNodeSelection` on, the node menu gains **Select/Deselect Node**
1299
+ and **Select/Deselect All Ext Nodes**, and the selection is readable from outside
1300
+ the viewer:
1301
+
1302
+ ```js
1303
+ var selected = archaeopteryx.getSelectedNodes(); // array of node objects
1304
+ ```
1305
+
1306
+ Selected nodes are drawn in the selection colour, which is fixed so that it
1307
+ stays distinguishable from the two search colours.
1308
+
1309
+ There is no push mechanism (no button or event that announces "the user is done
1310
+ selecting") — the embedding application reads the selection whenever it wants,
1311
+ typically from its own button. (Versions before 3.0 carried a dormant,
1312
+ never-rendered "Submit Selected" button in the source; it was removed.)