archaeopteryx 3.24.0 → 3.26.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 +74 -32
- package/archaeopteryx.d.ts +15 -4
- package/archaeopteryx.js +179 -166
- package/forester.js +8 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -64,23 +64,32 @@ Archaeopteryx.js has the following dependencies:
|
|
|
64
64
|
* d3.js (version 7): https://www.npmjs.com/package/d3
|
|
65
65
|
* sax.js (1.6.1): https://www.npmjs.com/package/sax/v/1.6.1
|
|
66
66
|
|
|
67
|
-
For **raster PNG export** (optional — the PNG entry
|
|
68
|
-
|
|
69
|
-
* canvg (4.x): https://www.npmjs.com/package/canvg
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
67
|
+
For **raster PNG export** (optional — the PNG entry, the Copy PNG button and
|
|
68
|
+
the PNG-scale row appear only when `window.Canvg` is present):
|
|
69
|
+
* canvg (4.x): https://www.npmjs.com/package/canvg. **Load it before
|
|
70
|
+
`launch()` is called:** the Download menu looks for it at click time, but
|
|
71
|
+
the Copy PNG button and the PNG-scale row are built with the control
|
|
72
|
+
panel at launch, so a page that provides it later gets a PNG entry and
|
|
73
|
+
nothing else. canvg publishes ES modules only (no classic-script global
|
|
74
|
+
build); this repository ships two ready-made files:
|
|
75
|
+
* `docs/lib/canvg.global.js` — a classic-script build for a plain
|
|
76
|
+
`<script src="canvg.global.js"></script>`, or for one more file in a
|
|
77
|
+
site's concatenated bundle; it defines `window.Canvg` itself. This is
|
|
78
|
+
what the demo pages use. (Generated from `canvg.js` by
|
|
79
|
+
`test/make_canvg_global.js`; a unit test keeps the two in step.)
|
|
80
|
+
* `docs/lib/canvg.js` — the self-contained ES module build (npm's
|
|
81
|
+
`canvg@4.0.3` bundled into one file via `esm.sh/canvg@4?bundle`, with
|
|
82
|
+
its one unnecessary Node-environment shim import removed — see the
|
|
83
|
+
file's own header), for a page that prefers a module bridge of its own:
|
|
84
|
+
```html
|
|
85
|
+
<script type="module">
|
|
86
|
+
import {Canvg} from 'http://path/to/canvg.js';
|
|
87
|
+
window.Canvg = Canvg;
|
|
88
|
+
</script>
|
|
89
|
+
```
|
|
90
|
+
A module script runs after the document is parsed, so a page that calls
|
|
91
|
+
`launch()` from an inline script during parsing must use the classic
|
|
92
|
+
build instead.
|
|
84
93
|
|
|
85
94
|
For **vector PDF export** (optional — the PDF entry appears in the Download
|
|
86
95
|
menu only when both are loaded before archaeopteryx.js):
|
|
@@ -246,10 +255,14 @@ program's.
|
|
|
246
255
|
|
|
247
256
|
A phylogram carries a **scale bar** at the bottom left: a round number of
|
|
248
257
|
branch-length units (1, 2 or 5 × 10ᵏ, whichever makes the bar about 100 px)
|
|
249
|
-
with its length written above it
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
the
|
|
258
|
+
with its length written above it, followed by the unit the tree states
|
|
259
|
+
(phyloXML's `branch_length_unit`, e.g. `0.05 subs/site`) and by nothing when
|
|
260
|
+
it states none: a tree that does not say what its lengths measure gets no
|
|
261
|
+
guess. It is drawn with the tree, so it zooms and exports with it and its
|
|
262
|
+
label always holds. A cladogram has nothing to measure and shows none, and a
|
|
263
|
+
tree under a scale axis leaves the measuring to the axis. **Scale** under
|
|
264
|
+
Display Data > Options hides it (on by default; config `showScaleBar`, view
|
|
265
|
+
key `scaleBar`; the desktop's switch has the same name and defaults to off).
|
|
253
266
|
|
|
254
267
|
**Scale Axis** under Display Data (off by default, as on the desktop) draws a
|
|
255
268
|
labelled ruler under a phylogram, from 0 at the root out to the deepest tip, in
|
|
@@ -1056,8 +1069,8 @@ Both entry points take **exactly** the arguments shown — a call with the old
|
|
|
1056
1069
|
trailing arguments (the separate settings bag, `nodeVisualizations`,
|
|
1057
1070
|
`nodeLabels`, `specialVisualizations`, or the positional Newick parse
|
|
1058
1071
|
options) **throws** with a message saying where each one went: everything now
|
|
1059
|
-
lives in the **one config object** (`
|
|
1060
|
-
`
|
|
1072
|
+
lives in the **one config object** (`internalNumericLabels` is a config key;
|
|
1073
|
+
`nodeLabels` is retired, see the deprecated keys below). `config` itself is optional — `archaeopteryx.launch('#phylogram1',
|
|
1061
1074
|
tree)` works.
|
|
1062
1075
|
|
|
1063
1076
|
`container` is a **CSS selector or the DOM element itself** (frameworks hand
|
|
@@ -1543,6 +1556,7 @@ copy-pastable JSON.
|
|
|
1543
1556
|
| `geneFamily` | `'auto'` | The annotation namespace that colours the genes and anchors the rows: `'auto'` (the one most genes carry) or a prefix such as `'pgfam'`. |
|
|
1544
1557
|
| `geneLabels` | `'genes'` | Where gene symbols go: `'genes'` (on the arrows), `'legend'` (a card of the families), or `'none'`. |
|
|
1545
1558
|
| `showScaleAxis` | tree-derived | Open with the scale axis shown: the time axis on a time tree whose branches show time, a distance axis on any other phylogram. Default: on when the tree carries `<date>` elements and opens shown in time, off otherwise (a dated tree that opens in divergence is a divergence tree on screen) — an explicit `true`/`false` overrides that. |
|
|
1559
|
+
| `showScaleBar` | `true` | Open with the scale bar at the bottom left of a phylogram (a round length in the tree's branch-length unit). It gives way while the scale axis is shown either way. |
|
|
1546
1560
|
| `showScaleGrid` | `false` | Open with the Scale Grid lines on (only meaningful — and only offered as a checkbox — while the scale axis itself is shown). |
|
|
1547
1561
|
| `showSupportDots` | `false` | Open with the Support Dots marks on (the checkbox appears whenever the tree has confidences). |
|
|
1548
1562
|
| `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. |
|
|
@@ -1551,14 +1565,13 @@ copy-pastable JSON.
|
|
|
1551
1565
|
| `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. |
|
|
1552
1566
|
| `searchBinitialValue` | `null` | Prefill search box B. |
|
|
1553
1567
|
| `enableVisualizations` | `true` | Offer the Color / Shape visualizations (which fields they cover is decided from the tree). |
|
|
1554
|
-
| `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. |
|
|
1568
|
+
| `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. A name the tree *can* honour is always honoured, but when it is a field the automatic choice would not have opened with -- more than 20 values, or a value on fewer than two thirds of the tips -- a console warning says so and names the field the viewer would have chosen. Default: Archaeopteryx.js picks the most informative field itself. |
|
|
1555
1569
|
| `visualizationsLegendXpos` | `254` | Legend position, x. |
|
|
1556
1570
|
| `visualizationsLegendYpos` | `30` | Legend position, y. |
|
|
1557
1571
|
| `enableDownloads` | `true` | Offer the download buttons. |
|
|
1558
1572
|
| `pngExportScale` | `4` | The PNG resolution the Download section starts on: 2, 4 or 8 (any other value is snapped to the nearest, with a console warning). |
|
|
1559
1573
|
| `nhExportWriteConfidences` | `true` | Write confidences into exported Newick. |
|
|
1560
1574
|
| `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'`). |
|
|
1561
|
-
| `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.) |
|
|
1562
1575
|
| `enableSubtreeDeletion` | `true` | Offer node / subtree deletion in the node menu. |
|
|
1563
1576
|
| `enableAccessToDatabases` | `true` | Offer the “Access DB” link in the node menu. |
|
|
1564
1577
|
| `enableManualNodeSelection` | `false` | Add the Select/Deselect entries to the node menu. |
|
|
@@ -1581,7 +1594,7 @@ whatever it names.
|
|
|
1581
1594
|
|
|
1582
1595
|
### Accepted, with a warning
|
|
1583
1596
|
|
|
1584
|
-
|
|
1597
|
+
Five keys are neither current nor removed: they are accepted so an existing
|
|
1585
1598
|
embed keeps working, and warn on the console.
|
|
1586
1599
|
|
|
1587
1600
|
| Key | What happens |
|
|
@@ -1590,6 +1603,7 @@ embed keeps working, and warn on the console.
|
|
|
1590
1603
|
| `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. |
|
|
1591
1604
|
| `showTimeAxis` | Translated to `showScaleAxis`, value unchanged: the switch now also draws a distance axis on a tree that is not in time. An explicit `showScaleAxis` always wins. |
|
|
1592
1605
|
| `timeAxisGrid` | Translated to `showScaleGrid`, value unchanged. |
|
|
1606
|
+
| `nodeLabels` | Retired in 3.25.0: ignored, with a warning. The custom label checkboxes it declared predate the Metadata checkbox and its "Metadata fields…" chooser, which offer exactly the fields the tree carries; a fixed site-wide list put checkboxes for absent fields on every tree. `labelProperties` sets the starting fields where the automatic choice is not wanted. Removed in a later release. |
|
|
1593
1607
|
|
|
1594
1608
|
### What replaced the rest
|
|
1595
1609
|
|
|
@@ -1734,6 +1748,21 @@ All 118 of them, alphabetically:
|
|
|
1734
1748
|
| `visualizationsLegendXposOrig` | Internal bookkeeping; set visualizationsLegendXpos. |
|
|
1735
1749
|
| `visualizationsLegendYposOrig` | Internal bookkeeping; set visualizationsLegendYpos. |
|
|
1736
1750
|
|
|
1751
|
+
### Removed from `forester` — calling these throws `TypeError`
|
|
1752
|
+
|
|
1753
|
+
3.0.0 also dropped twelve exports of `forester.js` that the viewer no longer
|
|
1754
|
+
used. They are gone, not stubbed, so a 2.x embedding that calls one fails with
|
|
1755
|
+
`TypeError: forester.<name> is not a function` — inside its own parse step, if
|
|
1756
|
+
that is where it called it.
|
|
1757
|
+
|
|
1758
|
+
| Function | Why, and what to do instead |
|
|
1759
|
+
| --- | --- |
|
|
1760
|
+
| `collectPropertyRefs` | Nothing to call. The viewer derives the Color-by candidates (`forester.visualizationCandidates(tree)`, below) and the label-field chooser's list from the tree itself, and substitutes a readable name property (`genome_name`) for identifier-like tip names on its own. A 2.x embedding that built `nodeLabels` from this function's result should delete that block together with the `nodeLabels` key; `labelProperties` sets the starting label fields where the automatic choice is not wanted. |
|
|
1761
|
+
| `collapse`, `unCollapse` | The subtree-collapse feature was removed; they were its only writers. |
|
|
1762
|
+
| `getChildren` | Only meant something while collapse existed; read `node.children`. |
|
|
1763
|
+
| `findByTaxonomyCode`, `findByTaxonomyScientificName` | Superseded by the search (the panel's search fields). |
|
|
1764
|
+
| `calcAverageTreeHeight`, `calcMaxDepth`, `calcBranchLengthSimpleStatistics`, `removeMaxBranchLength`, `isHasNodeData`, `getOneDistinctTaxonomy` | Never used by the viewer; nothing replaces them. |
|
|
1765
|
+
|
|
1737
1766
|
## The automatic visualization system (developer spec)
|
|
1738
1767
|
|
|
1739
1768
|
This section, together with **Value grouping** below, is written to be
|
|
@@ -1895,17 +1924,29 @@ exists, and clears the per-legend chip states.
|
|
|
1895
1924
|
Highest first, at each paint:
|
|
1896
1925
|
|
|
1897
1926
|
* **Label colour**: search/selection highlight → active Color visualization
|
|
1898
|
-
|
|
1927
|
+
(its colour; the theme ink for a node without a value) → `style:font_color`
|
|
1928
|
+
→ phyloXML branch colour → theme ink.
|
|
1899
1929
|
* **Node fill**: highlight → duplication/speciation event colour → active
|
|
1900
|
-
Color visualization
|
|
1901
|
-
|
|
1930
|
+
Color visualization (its colour; a node without a value draws no dot,
|
|
1931
|
+
or a hollow one while Node events are shown) → `style:node_color` (else
|
|
1932
|
+
`style:font_color`) → background.
|
|
1902
1933
|
* **Node outline**: darkened highlight → event colour → visualization fill
|
|
1903
|
-
→ style colour
|
|
1934
|
+
→ style colour (not while a Color visualization is active) → branch colour
|
|
1935
|
+
→ branch default.
|
|
1904
1936
|
* **Node shape path**: suppressed for highlighted/event nodes; chosen Shape
|
|
1905
1937
|
visualization → `style:node_shape`. A node earns its default dot when a
|
|
1906
1938
|
Color visualization is active (and no shape was drawn), or when it
|
|
1907
1939
|
carries `style:node_color` — `font_color` alone paints only the label.
|
|
1908
1940
|
|
|
1941
|
+
While a Color visualization is active the style colours are **not consulted
|
|
1942
|
+
for any node**, valued or not: not for the label, the dot, its outline, the
|
|
1943
|
+
hover glow or the clock plot's points. The visualization owns the colours and
|
|
1944
|
+
its legend explains them; a node without a value takes the default label
|
|
1945
|
+
colour and draws no dot. That is the desktop's rule (its TreePanel.setColor).
|
|
1946
|
+
Clearing the Color menu brings the file's colours back. Branch colours,
|
|
1947
|
+
shapes, font sizes and styles are not colours of the visualization's kind
|
|
1948
|
+
and stay.
|
|
1949
|
+
|
|
1909
1950
|
### Legend anatomy
|
|
1910
1951
|
|
|
1911
1952
|
One draggable SVG card per active visualization (colour, then shape,
|
|
@@ -2102,8 +2143,9 @@ honoured: `style:font_color`, `style:node_color`, `style:node_shape`
|
|
|
2102
2143
|
The **Visual Styles** checkbox — the desktop's "Visual Styles/Branch Colors",
|
|
2103
2144
|
shown when the tree carries either, on by default — turns them off and on,
|
|
2104
2145
|
and gates phyloXML `<color>` branch colours too, exactly as on the desktop.
|
|
2105
|
-
An active Color visualization outranks
|
|
2106
|
-
|
|
2146
|
+
An active Color visualization outranks the style colours for every node,
|
|
2147
|
+
valued or not, on labels and node dots alike, as on the desktop: set the
|
|
2148
|
+
Color menu to default to see the tree as its file styled it.
|
|
2107
2149
|
`style:` never appears in the Color / Shape menus — it is rendering, not data.
|
|
2108
2150
|
|
|
2109
2151
|
### Moving the legends
|
package/archaeopteryx.d.ts
CHANGED
|
@@ -29,9 +29,13 @@ export interface Phylogeny extends PhylogenyNode {
|
|
|
29
29
|
|
|
30
30
|
export type Layout = 'rectangular' | 'circular' | 'unrooted';
|
|
31
31
|
|
|
32
|
-
/**
|
|
32
|
+
/** @deprecated Retired in 3.25.0 with `nodeLabels`: accepted with a console
|
|
33
|
+
* warning, no effect; removed in a later release. The Metadata checkbox and
|
|
34
|
+
* its "Metadata fields…" chooser offer the tree's own fields.
|
|
33
35
|
*
|
|
34
|
-
*
|
|
36
|
+
* One custom label-field entry (the config's nodeLabels values).
|
|
37
|
+
*
|
|
38
|
+
* Two modes (3.0.0 to 3.24.0):
|
|
35
39
|
* - a PANEL CHECKBOX needs label, description AND propertyRef set, plus
|
|
36
40
|
* showButton: true -- omit showButton (or any of the three) and no
|
|
37
41
|
* checkbox appears;
|
|
@@ -85,6 +89,10 @@ export interface ArchaeopteryxConfig {
|
|
|
85
89
|
nhConfidenceValuesAsInternalNames?: boolean;
|
|
86
90
|
nhConfidenceValuesInBrackets?: boolean;
|
|
87
91
|
nhExportWriteConfidences?: boolean;
|
|
92
|
+
/** @deprecated Retired in 3.25.0: accepted with a console warning, no
|
|
93
|
+
* effect; removed in a later release. The Metadata checkbox and its
|
|
94
|
+
* "Metadata fields…" chooser offer the tree's own fields; labelProperties
|
|
95
|
+
* sets the starting fields. */
|
|
88
96
|
nodeLabels?: Record<string, NodeLabelSpec> | null;
|
|
89
97
|
/** Called once per settled redraw when the view changed: the state as
|
|
90
98
|
* getViewState() returns it, and its hash-ready string. */
|
|
@@ -130,6 +138,8 @@ export interface ArchaeopteryxConfig {
|
|
|
130
138
|
* branches show time, a distance axis on any other. Default: on for a
|
|
131
139
|
* time tree, off otherwise. */
|
|
132
140
|
showScaleAxis?: boolean;
|
|
141
|
+
/** The scale bar at the bottom left of a phylogram; on unless false. */
|
|
142
|
+
showScaleBar?: boolean;
|
|
133
143
|
/** Grid lines (rings, in circular) at the scale axis's ticks. */
|
|
134
144
|
showScaleGrid?: boolean;
|
|
135
145
|
showSupportDots?: boolean;
|
|
@@ -176,8 +186,8 @@ export interface ViewState {
|
|
|
176
186
|
shapeBy?: string;
|
|
177
187
|
/** The panel's checked boxes: name, taxonomy, sequence, confidence,
|
|
178
188
|
* madValues, branchLength, external, internal, nodeEvents, branchEvents,
|
|
179
|
-
* supportDots, shortNames, autoHide, visualizations, visualStyles
|
|
180
|
-
* custom:<key>
|
|
189
|
+
* supportDots, shortNames, autoHide, visualizations, visualStyles. (A
|
|
190
|
+
* custom:<key> flag from a 3.24.0 hash is read and ignored.) */
|
|
181
191
|
show?: string[];
|
|
182
192
|
font?: number;
|
|
183
193
|
node?: number;
|
|
@@ -205,6 +215,7 @@ export interface ViewState {
|
|
|
205
215
|
geneLabels?: 'none' | 'genes' | 'legend';
|
|
206
216
|
scaleAxis?: boolean;
|
|
207
217
|
scaleGrid?: boolean;
|
|
218
|
+
scaleBar?: boolean;
|
|
208
219
|
/** @deprecated the name of scaleAxis in older links; still read. */
|
|
209
220
|
timeAxis?: boolean;
|
|
210
221
|
/** @deprecated the name of scaleGrid in older links; still read. */
|
package/archaeopteryx.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
// v 3.
|
|
23
|
+
// v 3.26.0
|
|
24
24
|
// 2026-09-17
|
|
25
25
|
//
|
|
26
26
|
// Archaeopteryx.js is a software tool for the visualization and
|
|
@@ -103,7 +103,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
103
103
|
// IIFE's own function name -- a plain object says what it is.)
|
|
104
104
|
let archaeopteryx = {};
|
|
105
105
|
|
|
106
|
-
const VERSION = '3.
|
|
106
|
+
const VERSION = '3.26.0';
|
|
107
107
|
const WEBSITE = 'https://cmzmasek.github.io/archaeopteryx-js/';
|
|
108
108
|
const DESKTOP_WEBSITE = 'https://cmzmasek.github.io/archaeopteryx/';
|
|
109
109
|
const SOURCE_WEBSITE = 'https://github.com/cmzmasek/archaeopteryx-js';
|
|
@@ -390,6 +390,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
390
390
|
const GENE_LABELS_SELECT = 'gene_labels';
|
|
391
391
|
const SCALE_AXIS_CB = 'scaleaxis_cb';
|
|
392
392
|
const SCALE_GRID_CB = 'scalegrid_cb';
|
|
393
|
+
const SCALE_BAR_CB = 'scalebar_cb';
|
|
393
394
|
const MSA_SCROLL_ID = 'aptxmsascroll';
|
|
394
395
|
const FONT_SIZE_SLIDER = 'fs_sl';
|
|
395
396
|
const EXTERNAL_LABEL_CB = 'extl_cb';
|
|
@@ -539,7 +540,6 @@ function (root, d3, forester, phyloXml) {
|
|
|
539
540
|
let _container = null; // the resolved container ELEMENT (never a selector string)
|
|
540
541
|
let _intervalId = 0;
|
|
541
542
|
let _maxLabelLength = 0;
|
|
542
|
-
let _nodeLabels = null;
|
|
543
543
|
let _state = null; // live display state: what the control panel writes to
|
|
544
544
|
let _root = null;
|
|
545
545
|
let _root_const = null;
|
|
@@ -606,6 +606,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
606
606
|
const DOMAIN_SCALE_HEADROOM = 0.9; // drawn length over reserved length
|
|
607
607
|
const DOMAIN_BOX_MIN_H = 6;
|
|
608
608
|
const DOMAIN_BOX_MAX_H = 16;
|
|
609
|
+
const LEGEND_MAX_ROWS = 20; // value rows a legend lists before "n more"; the rest fold
|
|
609
610
|
const DOMAIN_BACKBONE_COLOR = 'rgb(100,100,100)'; // the same in both themes and in exports
|
|
610
611
|
const DOMAIN_SHADOW_COLOR = 'rgb(8,18,21)';
|
|
611
612
|
const DOMAIN_SHADOWS = [[0.4, 0.7, 40], [0.9, 1.5, 28], [1.6, 2.5, 17]]; // dx, dy, alpha of 255, in drawing order
|
|
@@ -1447,8 +1448,9 @@ function (root, d3, forester, phyloXml) {
|
|
|
1447
1448
|
// a node coloured by Host Group glows in its host colour -- and the UI
|
|
1448
1449
|
// accent otherwise. As on the desktop (TreePanel.hoverGlowColor). The
|
|
1449
1450
|
// precedence is the one the node itself is painted by: the Color-by
|
|
1450
|
-
// value, then a per-node visual style
|
|
1451
|
-
//
|
|
1451
|
+
// value, then a per-node visual style (not while a Color-by is active,
|
|
1452
|
+
// see nodeStyleColor), then an event colour, then a colourised clade's
|
|
1453
|
+
// branch colour. NEVER the found/search colour: a
|
|
1452
1454
|
// focus ring must not read as a selection state.
|
|
1453
1455
|
//
|
|
1454
1456
|
// The glow is a 13-23% alpha wash, so a dark or a pale colour would
|
|
@@ -1462,10 +1464,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
1462
1464
|
c = vis;
|
|
1463
1465
|
}
|
|
1464
1466
|
if (!c) {
|
|
1465
|
-
|
|
1466
|
-
if (style && (style.nodeColor || style.fontColor)) {
|
|
1467
|
-
c = style.nodeColor || style.fontColor;
|
|
1468
|
-
}
|
|
1467
|
+
c = nodeStyleColor(d);
|
|
1469
1468
|
}
|
|
1470
1469
|
if (!c && _state.showNodeEvents && d.events && d.children) {
|
|
1471
1470
|
c = makeNodeEventsDependentColor(d.events);
|
|
@@ -1948,7 +1947,32 @@ function (root, d3, forester, phyloXml) {
|
|
|
1948
1947
|
// an explicit request is honoured even for a wide field (21+
|
|
1949
1948
|
// values) the automatic path would only offer, never impose;
|
|
1950
1949
|
// autoColorId too, so it survives subtree views like the
|
|
1951
|
-
// automatic pick does
|
|
1950
|
+
// automatic pick does.
|
|
1951
|
+
//
|
|
1952
|
+
// Honoured, but not in silence. A request for a field the
|
|
1953
|
+
// automatic choice would not open with -- wide, or sparse (on
|
|
1954
|
+
// fewer than 2/3 of the tips; it opens only when nothing denser
|
|
1955
|
+
// exists) -- says so in the console and names the field the
|
|
1956
|
+
// viewer would have chosen. BV-BRC's virus trees carried a
|
|
1957
|
+
// site-wide 'Host' from the influenza days: family trees opened
|
|
1958
|
+
// coloured by 39 to 168 hosts while Genus (4 to 15 values) sat
|
|
1959
|
+
// in the file, and nothing anywhere said the viewer had been
|
|
1960
|
+
// overruled (Christian, 2026-10-08: a console warning when a
|
|
1961
|
+
// sub-optimal visualization is forced is needed). A request for
|
|
1962
|
+
// a clean field that merely ranks below the automatic pick is a
|
|
1963
|
+
// site's taste and warns nothing; nor does a sparse field that
|
|
1964
|
+
// is the automatic pick itself.
|
|
1965
|
+
let auto = forester.openingVisualization(_vis.candidates);
|
|
1966
|
+
if ((requested.wide || requested.sparse) && !(auto && auto.id === requested.id)) {
|
|
1967
|
+
let why = requested.wide
|
|
1968
|
+
? requested.values.length + ' distinct values on this tree, more than the '
|
|
1969
|
+
+ LEGEND_MAX_ROWS + ' the legend lists'
|
|
1970
|
+
: 'a value on ' + requested.coverage + ' of the tree\'s ' + requested.total + ' tips';
|
|
1971
|
+
console.warn(WARNING + ': initialVisualization "' + _settings.initialVisualization
|
|
1972
|
+
+ '" names a field with ' + why + '; left to itself the viewer would '
|
|
1973
|
+
+ (auto ? 'open with "' + auto.label + '" (' + auto.values.length + ' values)'
|
|
1974
|
+
: 'open uncoloured'));
|
|
1975
|
+
}
|
|
1952
1976
|
_vis.autoColorId = requested.id;
|
|
1953
1977
|
_vis.colorId = requested.id;
|
|
1954
1978
|
_state.showVisualizations = true;
|
|
@@ -2144,6 +2168,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
2144
2168
|
}
|
|
2145
2169
|
dialog.addEventListener('close', close);
|
|
2146
2170
|
|
|
2171
|
+
let placed = false; // set by place() once the picker has been put somewhere
|
|
2147
2172
|
render(false);
|
|
2148
2173
|
dialog.show();
|
|
2149
2174
|
place();
|
|
@@ -2151,40 +2176,67 @@ function (root, d3, forester, phyloXml) {
|
|
|
2151
2176
|
// Beside the legend it edits, never on top of it -- a picker that hides
|
|
2152
2177
|
// the swatch it is changing defeats the live preview.
|
|
2153
2178
|
//
|
|
2154
|
-
//
|
|
2155
|
-
//
|
|
2156
|
-
//
|
|
2157
|
-
//
|
|
2158
|
-
//
|
|
2159
|
-
//
|
|
2160
|
-
//
|
|
2179
|
+
// Placed OUTRIGHT the first time, viewport-fixed. A non-modal <dialog>
|
|
2180
|
+
// appended to the body sits at its static position, the end of the
|
|
2181
|
+
// document: on the demo page that is the top of the window (the
|
|
2182
|
+
// container is out of flow), on a page that flows, such as BV-BRC's,
|
|
2183
|
+
// it is below everything on it. Until 2026-10-08 the picker moved
|
|
2184
|
+
// only when that spot overlapped the card, so on BV-BRC it opened
|
|
2185
|
+
// under the site's own bars, off the bottom of the window
|
|
2186
|
+
// (Christian's screenshot). Fixed positioning also keeps a scrolling
|
|
2187
|
+
// page from carrying it away.
|
|
2188
|
+
//
|
|
2189
|
+
// Re-measured rather than remembered afterwards, for two reasons:
|
|
2190
|
+
// every redraw DESTROYS and rebuilds the legend group, so a held
|
|
2191
|
+
// reference goes stale and measures zero; and the card grows when the
|
|
2192
|
+
// [reset colors] chip appears on the first pick, which would leave a
|
|
2193
|
+
// positioned-once picker sitting over the counts. Once placed it only
|
|
2194
|
+
// MOVES when it would otherwise overlap, so the dialog does not chase
|
|
2195
|
+
// the legend around while the user drags.
|
|
2161
2196
|
function place() {
|
|
2162
2197
|
let el = anchorClass ? document.querySelector('g.' + anchorClass) : null;
|
|
2163
2198
|
let box = el ? el.getBoundingClientRect() : null;
|
|
2164
|
-
if (
|
|
2165
|
-
|
|
2199
|
+
if (box && !box.width) {
|
|
2200
|
+
box = null;
|
|
2166
2201
|
}
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2202
|
+
if (placed) {
|
|
2203
|
+
if (!box) {
|
|
2204
|
+
return;
|
|
2205
|
+
}
|
|
2206
|
+
let now = dialog.getBoundingClientRect();
|
|
2207
|
+
let overlaps = now.width > 0
|
|
2208
|
+
&& !(now.left >= box.right || now.right <= box.left
|
|
2209
|
+
|| now.top >= box.bottom || now.bottom <= box.top);
|
|
2210
|
+
if (!overlaps) {
|
|
2211
|
+
return;
|
|
2212
|
+
}
|
|
2173
2213
|
}
|
|
2174
2214
|
let w = dialog.offsetWidth;
|
|
2175
2215
|
let h = dialog.offsetHeight;
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2216
|
+
let left;
|
|
2217
|
+
let top;
|
|
2218
|
+
if (box) {
|
|
2219
|
+
// to the right of the card, or to its left when that would
|
|
2220
|
+
// leave the viewport
|
|
2221
|
+
left = box.right + 10;
|
|
2222
|
+
if (left + w > window.innerWidth - 6) {
|
|
2223
|
+
left = box.left - w - 10;
|
|
2224
|
+
}
|
|
2225
|
+
top = box.top;
|
|
2226
|
+
} else {
|
|
2227
|
+
// no card to sit beside (the view has no swatch for the value):
|
|
2228
|
+
// the tree area's top left corner, inside the window
|
|
2229
|
+
let area = _container ? _container.getBoundingClientRect() : null;
|
|
2230
|
+
left = area ? area.left + 14 : 10;
|
|
2231
|
+
top = area ? Math.max(area.top, 0) + 14 : 10;
|
|
2181
2232
|
}
|
|
2182
2233
|
setStyles(dialog, {
|
|
2183
2234
|
'position': 'fixed',
|
|
2184
2235
|
'margin': '0',
|
|
2185
2236
|
'left': Math.max(6, Math.min(window.innerWidth - w - 6, left)) + 'px',
|
|
2186
|
-
'top': Math.max(6, Math.min(window.innerHeight - h - 6,
|
|
2237
|
+
'top': Math.max(6, Math.min(window.innerHeight - h - 6, top)) + 'px'
|
|
2187
2238
|
});
|
|
2239
|
+
placed = true;
|
|
2188
2240
|
}
|
|
2189
2241
|
setTimeout(function () {
|
|
2190
2242
|
document.addEventListener('pointerdown', onOutside, true);
|
|
@@ -2377,6 +2429,36 @@ function (root, d3, forester, phyloXml) {
|
|
|
2377
2429
|
return ((_vis && _vis.colorId) ? _vis.byId[_vis.colorId] : null) || null;
|
|
2378
2430
|
}
|
|
2379
2431
|
|
|
2432
|
+
// A Color visualization is on screen: it owns every node's colour, and the
|
|
2433
|
+
// file's style colours wait until the Color menu is cleared (the desktop's
|
|
2434
|
+
// rule; see makeLabelColor and makeNodeFillColor).
|
|
2435
|
+
function colorVisActive() {
|
|
2436
|
+
return !!_state.showVisualizations && !!currentColorVis();
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
// The colour a node's style gives it, or null: style:node_color (else
|
|
2440
|
+
// style:font_color) for the node's mark, style:font_color for its label.
|
|
2441
|
+
// Null while a Color visualization is active -- the desktop's rule
|
|
2442
|
+
// (TreePanel.setColor): the visualization owns every node's colour and
|
|
2443
|
+
// its legend explains them; a node without a value takes the default ink
|
|
2444
|
+
// and no dot rather than the file's colour. Until 2026-10-08 the valueless
|
|
2445
|
+
// nodes fell through to the file's colour on their dots, so BV-BRC's
|
|
2446
|
+
// trees -- style:font_color on every tip, following Genus -- showed two
|
|
2447
|
+
// palettes under Color by Host (Christian: adopt the desktop's rule).
|
|
2448
|
+
// EVERY reader of a style colour goes through here, so none can forget
|
|
2449
|
+
// the rule: the label, the dot, its outline, the hover glow, the clock
|
|
2450
|
+
// plot's points (which a first version of the rule missed).
|
|
2451
|
+
function nodeStyleColor(node, forLabel) {
|
|
2452
|
+
if (colorVisActive()) {
|
|
2453
|
+
return null;
|
|
2454
|
+
}
|
|
2455
|
+
let style = nodeStyle(node);
|
|
2456
|
+
if (!style) {
|
|
2457
|
+
return null;
|
|
2458
|
+
}
|
|
2459
|
+
return (forLabel ? style.fontColor : (style.nodeColor || style.fontColor)) || null;
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2380
2462
|
function currentShapeVis() {
|
|
2381
2463
|
return ((_vis && _vis.shapeId) ? _vis.byId[_vis.shapeId] : null) || null;
|
|
2382
2464
|
}
|
|
@@ -2521,7 +2603,6 @@ function (root, d3, forester, phyloXml) {
|
|
|
2521
2603
|
// is then just its "no value" row
|
|
2522
2604
|
const isRange = kind === 'color' && colorModeOf(vis) === 'range' && vis.values.length > 0;
|
|
2523
2605
|
|
|
2524
|
-
const LEGEND_MAX_ROWS = 20;
|
|
2525
2606
|
let rows = [];
|
|
2526
2607
|
let hidden = 0;
|
|
2527
2608
|
if (!isRange) {
|
|
@@ -3624,11 +3705,9 @@ function (root, d3, forester, phyloXml) {
|
|
|
3624
3705
|
if (visColor !== _state.backgroundColorDefault) {
|
|
3625
3706
|
return visColor;
|
|
3626
3707
|
}
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
}
|
|
3631
|
-
return visColor;
|
|
3708
|
+
// the style colour, or the background -- which, under an active Color
|
|
3709
|
+
// visualization, drawTreeGeometry turns into no dot at all
|
|
3710
|
+
return nodeStyleColor(phynode) || visColor;
|
|
3632
3711
|
};
|
|
3633
3712
|
|
|
3634
3713
|
// A darker shade of a node's found/selected highlight color, used as a thin
|
|
@@ -3661,10 +3740,11 @@ function (root, d3, forester, phyloXml) {
|
|
|
3661
3740
|
return v;
|
|
3662
3741
|
}
|
|
3663
3742
|
}
|
|
3664
|
-
let
|
|
3665
|
-
if (
|
|
3666
|
-
return
|
|
3743
|
+
let styleColor = nodeStyleColor(phynode);
|
|
3744
|
+
if (styleColor) {
|
|
3745
|
+
return styleColor;
|
|
3667
3746
|
}
|
|
3747
|
+
// the branch colour, unlike a style colour, holds under a Color-by (desktop)
|
|
3668
3748
|
if (_state.useVisualStyles && phynode.color) {
|
|
3669
3749
|
let c = phynode.color;
|
|
3670
3750
|
return "rgb(" + c.red + "," + c.green + "," + c.blue + ")";
|
|
@@ -3677,17 +3757,15 @@ function (root, d3, forester, phyloXml) {
|
|
|
3677
3757
|
if (foundColor) {
|
|
3678
3758
|
return foundColor;
|
|
3679
3759
|
}
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3760
|
+
// an active Color visualization owns the colours: its colour where
|
|
3761
|
+
// the node has a value, the default ink where it has none (and
|
|
3762
|
+
// nodeStyleColor is null meanwhile, see there)
|
|
3763
|
+
if (colorVisActive()) {
|
|
3764
|
+
return makeVisLabelColor(phynode);
|
|
3685
3765
|
}
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
if (style && style.fontColor) {
|
|
3690
|
-
return style.fontColor;
|
|
3766
|
+
let fontColor = nodeStyleColor(phynode, true);
|
|
3767
|
+
if (fontColor) {
|
|
3768
|
+
return fontColor;
|
|
3691
3769
|
}
|
|
3692
3770
|
if (_state.useVisualStyles && phynode.color) {
|
|
3693
3771
|
let c = phynode.color;
|
|
@@ -4725,25 +4803,6 @@ function (root, d3, forester, phyloXml) {
|
|
|
4725
4803
|
l = append(l, labelPropertiesOf(phynode));
|
|
4726
4804
|
}
|
|
4727
4805
|
|
|
4728
|
-
if (_nodeLabels && phynode.properties) {
|
|
4729
|
-
const props_length = phynode.properties.length;
|
|
4730
|
-
if (props_length > 0) {
|
|
4731
|
-
for (const value of Object.values(_nodeLabels)) {
|
|
4732
|
-
if (value.selected === true && value.propertyRef) {
|
|
4733
|
-
let prop_text = '';
|
|
4734
|
-
for (let pm = 0; pm < props_length; ++pm) {
|
|
4735
|
-
if (phynode.properties[pm].ref === value.propertyRef && phynode.properties[pm].datatype === 'xsd:string' && forester.isNodeScopedProperty(phynode.properties[pm])) {
|
|
4736
|
-
if (prop_text.length > 0) {
|
|
4737
|
-
prop_text += ', '
|
|
4738
|
-
}
|
|
4739
|
-
prop_text += phynode.properties[pm].value;
|
|
4740
|
-
}
|
|
4741
|
-
}
|
|
4742
|
-
l = append(l, prop_text);
|
|
4743
|
-
}
|
|
4744
|
-
}
|
|
4745
|
-
}
|
|
4746
|
-
}
|
|
4747
4806
|
|
|
4748
4807
|
|
|
4749
4808
|
if (_state.showDistributions && phynode.distributions && phynode.distributions.length > 0) {
|
|
@@ -5522,6 +5581,21 @@ function (root, d3, forester, phyloXml) {
|
|
|
5522
5581
|
return v;
|
|
5523
5582
|
},
|
|
5524
5583
|
note: 'the grid follows the scale axis, time or distance'
|
|
5584
|
+
},
|
|
5585
|
+
// Retired in 3.25.0 (Christian, 2026-10-08: "go with the deprecation").
|
|
5586
|
+
// The custom label checkboxes it declared -- a site's fixed list of
|
|
5587
|
+
// property refs, one panel checkbox each, labelling nodes with the
|
|
5588
|
+
// property's value -- predate the Metadata checkbox and its
|
|
5589
|
+
// "Metadata fields…" chooser, which offer exactly the fields the tree
|
|
5590
|
+
// carries and replace identifier-like tip names with a name property
|
|
5591
|
+
// on their own. On BV-BRC the list, written for influenza, put eight
|
|
5592
|
+
// checkboxes on every virus tree, six of them for fields the tree
|
|
5593
|
+
// did not have. The checkbox code went with the key (the label
|
|
5594
|
+
// text, the panel rows, the view state's custom:<key> flags, which
|
|
5595
|
+
// an old hash may still carry and the reader now ignores). Accepted
|
|
5596
|
+
// with this warning for one release cycle, then REMOVED_CONFIG.
|
|
5597
|
+
nodeLabels: {
|
|
5598
|
+
note: 'the Metadata checkbox and its "Metadata fields…" chooser offer the tree\'s own fields; labelProperties sets the starting fields'
|
|
5525
5599
|
}
|
|
5526
5600
|
};
|
|
5527
5601
|
|
|
@@ -5699,6 +5773,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
5699
5773
|
'geneFamily',
|
|
5700
5774
|
'geneLabels',
|
|
5701
5775
|
'showScaleAxis',
|
|
5776
|
+
'showScaleBar',
|
|
5702
5777
|
'showScaleGrid',
|
|
5703
5778
|
'showSupportDots',
|
|
5704
5779
|
'searchAinitialValue',
|
|
@@ -5721,7 +5796,6 @@ function (root, d3, forester, phyloXml) {
|
|
|
5721
5796
|
'internalNumericLabels',
|
|
5722
5797
|
'ladderizeTree',
|
|
5723
5798
|
'nhExportWriteConfidences',
|
|
5724
|
-
'nodeLabels',
|
|
5725
5799
|
'onViewChange',
|
|
5726
5800
|
'panelDensity',
|
|
5727
5801
|
'pngExportScale',
|
|
@@ -6066,6 +6140,8 @@ function (root, d3, forester, phyloXml) {
|
|
|
6066
6140
|
_state.showScaleAxis = !!(_timeInfo && _timeInfo.type) && !branchesShowDivergence();
|
|
6067
6141
|
}
|
|
6068
6142
|
_state.showScaleGrid = _state.showScaleGrid === true; // desktop default: off
|
|
6143
|
+
// the scale bar is on unless the caller says no; it gives way to the axis anyway
|
|
6144
|
+
_state.showScaleBar = _state.showScaleBar !== false;
|
|
6069
6145
|
_state.showSupportDots = _state.showSupportDots === true;
|
|
6070
6146
|
_state.showNodeEvents = _basicTreeProperties.nodeEvents === true;
|
|
6071
6147
|
_state.showBranchEvents = _basicTreeProperties.branchEvents === true;
|
|
@@ -6321,19 +6397,6 @@ function (root, d3, forester, phyloXml) {
|
|
|
6321
6397
|
} else if (typeof _settings.onViewChange !== 'function') {
|
|
6322
6398
|
throw new Error(ERROR + '"onViewChange" must be a function (state, encoded) or null');
|
|
6323
6399
|
}
|
|
6324
|
-
if (_settings.nodeLabels === undefined) {
|
|
6325
|
-
_settings.nodeLabels = null;
|
|
6326
|
-
} else if (_settings.nodeLabels !== null && typeof _settings.nodeLabels !== 'object') {
|
|
6327
|
-
throw new Error('nodeLabels must be an object (see the README) or null');
|
|
6328
|
-
}
|
|
6329
|
-
if (_settings.nodeLabels) {
|
|
6330
|
-
// a DEEP copy: the viewer writes runtime state into these specs
|
|
6331
|
-
// (the checkbox element id, the live checked state), and the
|
|
6332
|
-
// caller's config object must stay declarative -- an embedder
|
|
6333
|
-
// reusing one nodeLabels constant across launches must not
|
|
6334
|
-
// inherit the previous session's checkbox state
|
|
6335
|
-
_settings.nodeLabels = JSON.parse(JSON.stringify(_settings.nodeLabels));
|
|
6336
|
-
}
|
|
6337
6400
|
if (_settings.pngExportScale === undefined) {
|
|
6338
6401
|
_settings.pngExportScale = 4;
|
|
6339
6402
|
} else if (typeof _settings.pngExportScale !== 'number'
|
|
@@ -6593,7 +6656,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
6593
6656
|
+ ' trailing arguments were removed. The separate settings bag merged into the one'
|
|
6594
6657
|
+ ' config object; "nodeVisualizations" and "specialVisualizations" are gone for good'
|
|
6595
6658
|
+ ' (visualizations are derived automatically from the tree itself); "nodeLabels"'
|
|
6596
|
-
+ '
|
|
6659
|
+
+ ' is retired (the Metadata checkbox offers the tree\'s own fields).');
|
|
6597
6660
|
}
|
|
6598
6661
|
if (phylo === undefined || phylo === null) {
|
|
6599
6662
|
throw new Error(ERROR + 'input tree is undefined or null');
|
|
@@ -6684,9 +6747,8 @@ function (root, d3, forester, phyloXml) {
|
|
|
6684
6747
|
.on('zoom', zoom);
|
|
6685
6748
|
_basicTreeProperties = forester.collectBasicTreeProperties(_treeData);
|
|
6686
6749
|
|
|
6687
|
-
// Every launch starts from a clean slate: _vis is rebuilt below
|
|
6688
|
-
//
|
|
6689
|
-
// the same page.
|
|
6750
|
+
// Every launch starts from a clean slate: _vis is rebuilt below, never
|
|
6751
|
+
// inherited from a previous launch in the same page.
|
|
6690
6752
|
_radialRotation = 0;
|
|
6691
6753
|
_radialLabelsHorizontal = false;
|
|
6692
6754
|
_msaColOffset = 0;
|
|
@@ -6704,7 +6766,6 @@ function (root, d3, forester, phyloXml) {
|
|
|
6704
6766
|
// candidate ranking over 13k tips before the card could even paint.
|
|
6705
6767
|
checkLayoutValue(cfg.state ? cfg.state.layout : undefined);
|
|
6706
6768
|
initializeSettings(cfg.settings);
|
|
6707
|
-
_nodeLabels = _settings.nodeLabels || null;
|
|
6708
6769
|
|
|
6709
6770
|
// whatever a previous launch left in this container (or in the
|
|
6710
6771
|
// container before it) goes now, so the old tree does not sit under
|
|
@@ -9896,20 +9957,6 @@ function (root, d3, forester, phyloXml) {
|
|
|
9896
9957
|
return found;
|
|
9897
9958
|
}
|
|
9898
9959
|
|
|
9899
|
-
// The config's custom label checkboxes (nodeLabels) that are offered
|
|
9900
|
-
function customLabelEntries() {
|
|
9901
|
-
let out = [];
|
|
9902
|
-
if (_nodeLabels) {
|
|
9903
|
-
Object.keys(_nodeLabels).forEach(function (key) {
|
|
9904
|
-
let v = _nodeLabels[key];
|
|
9905
|
-
if (v && v.label && v.showButton === true && v.propertyRef && v.description) {
|
|
9906
|
-
out.push({key: key, entry: v});
|
|
9907
|
-
}
|
|
9908
|
-
});
|
|
9909
|
-
}
|
|
9910
|
-
return out;
|
|
9911
|
-
}
|
|
9912
|
-
|
|
9913
9960
|
function searchStateOf(idx) {
|
|
9914
9961
|
let spec = currentSearchSpec(idx);
|
|
9915
9962
|
let value = (spec.value === null || spec.value === undefined) ? '' : String(spec.value);
|
|
@@ -9957,11 +10004,6 @@ function (root, d3, forester, phyloXml) {
|
|
|
9957
10004
|
}).map(function (f) {
|
|
9958
10005
|
return f[0];
|
|
9959
10006
|
});
|
|
9960
|
-
customLabelEntries().forEach(function (c) {
|
|
9961
|
-
if (c.entry.selected === true) {
|
|
9962
|
-
s.show.push('custom:' + c.key);
|
|
9963
|
-
}
|
|
9964
|
-
});
|
|
9965
10007
|
s.font = _state.externalNodeFontSize;
|
|
9966
10008
|
s.node = _state.nodeSizeDefault;
|
|
9967
10009
|
s.branch = _state.branchWidthDefault;
|
|
@@ -10005,6 +10047,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
10005
10047
|
if (_basicTreeProperties.branchLengths === true) {
|
|
10006
10048
|
s.scaleAxis = _state.showScaleAxis === true;
|
|
10007
10049
|
s.scaleGrid = _state.showScaleGrid === true;
|
|
10050
|
+
s.scaleBar = _state.showScaleBar === true;
|
|
10008
10051
|
}
|
|
10009
10052
|
if (_branchScaleAvailable) {
|
|
10010
10053
|
s.scale = _state.branchScale; // time or divergence: which layout the branches show
|
|
@@ -10107,9 +10150,6 @@ function (root, d3, forester, phyloXml) {
|
|
|
10107
10150
|
VIEW_SHOW_FLAGS.forEach(function (f) {
|
|
10108
10151
|
_state[f[2]] = on.has(f[0]);
|
|
10109
10152
|
});
|
|
10110
|
-
customLabelEntries().forEach(function (c) {
|
|
10111
|
-
c.entry.selected = on.has('custom:' + c.key);
|
|
10112
|
-
});
|
|
10113
10153
|
}
|
|
10114
10154
|
if (isFinite(s.font)) {
|
|
10115
10155
|
setFontSizes(Number(s.font));
|
|
@@ -10189,6 +10229,9 @@ function (root, d3, forester, phyloXml) {
|
|
|
10189
10229
|
if (typeof grid === 'boolean') {
|
|
10190
10230
|
_state.showScaleGrid = grid;
|
|
10191
10231
|
}
|
|
10232
|
+
if (typeof s.scaleBar === 'boolean') {
|
|
10233
|
+
_state.showScaleBar = s.scaleBar;
|
|
10234
|
+
}
|
|
10192
10235
|
if (!radialDisplay()) {
|
|
10193
10236
|
_radialLabelsHorizontal = false;
|
|
10194
10237
|
} else if ((_state.showDomainArchitectures && _basicTreeProperties.domainArchitectures)
|
|
@@ -10259,17 +10302,13 @@ function (root, d3, forester, phyloXml) {
|
|
|
10259
10302
|
VIEW_SHOW_FLAGS.forEach(function (f) {
|
|
10260
10303
|
setCheckboxValue(f[1], _state[f[2]] === true);
|
|
10261
10304
|
});
|
|
10262
|
-
customLabelEntries().forEach(function (c) {
|
|
10263
|
-
if (c.entry.cb_id) {
|
|
10264
|
-
setCheckboxValue(c.entry.cb_id, c.entry.selected === true);
|
|
10265
|
-
}
|
|
10266
|
-
});
|
|
10267
10305
|
setCheckboxValue(MSA_CB, _state.showMsa === true);
|
|
10268
10306
|
setCheckboxValue(MSA_LOGO_CB, _state.showMsaLogo === true);
|
|
10269
10307
|
setCheckboxValue(HEATMAP_CB, _state.showHeatmap === true);
|
|
10270
10308
|
syncHeatmapControls();
|
|
10271
10309
|
setCheckboxValue(SCALE_AXIS_CB, _state.showScaleAxis === true);
|
|
10272
10310
|
setCheckboxValue(SCALE_GRID_CB, _state.showScaleGrid === true);
|
|
10311
|
+
setCheckboxValue(SCALE_BAR_CB, _state.showScaleBar === true);
|
|
10273
10312
|
setCheckboxValue(DOMAIN_GLOW_CB, _state.domainGlow === true);
|
|
10274
10313
|
setValue(DOMAIN_LABELS_SELECT, _state.domainLabels);
|
|
10275
10314
|
syncDomainControls();
|
|
@@ -10350,7 +10389,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
10350
10389
|
const VIEW_INT_KEYS = ['tree', 'subtree', 'rotation', 'domainEvalue'];
|
|
10351
10390
|
const VIEW_NUMBER_KEYS = ['font', 'node', 'branch'];
|
|
10352
10391
|
const VIEW_BOOL_KEYS = ['horizontalLabels', 'msa', 'msaLogo', 'heatmap', 'domains', 'domainGlow', 'genes',
|
|
10353
|
-
'scaleAxis', 'scaleGrid', 'timeAxis', 'timeGrid', 'matchCase', 'inverse'];
|
|
10392
|
+
'scaleAxis', 'scaleGrid', 'scaleBar', 'timeAxis', 'timeGrid', 'matchCase', 'inverse'];
|
|
10354
10393
|
// Lists of strings, comma-joined. A property ref carries ':' but never a
|
|
10355
10394
|
// comma, and encodeViewValue leaves both readable in a hash.
|
|
10356
10395
|
const VIEW_LIST_KEYS = ['heatmapManual', 'labelFields'];
|
|
@@ -11345,9 +11384,9 @@ function (root, d3, forester, phyloXml) {
|
|
|
11345
11384
|
if (vis) {
|
|
11346
11385
|
return vis;
|
|
11347
11386
|
}
|
|
11348
|
-
let
|
|
11349
|
-
if (
|
|
11350
|
-
return
|
|
11387
|
+
let styleColor = nodeStyleColor(node); // null under an active Color-by, as in the tree
|
|
11388
|
+
if (styleColor) {
|
|
11389
|
+
return styleColor;
|
|
11351
11390
|
}
|
|
11352
11391
|
if (_state.useVisualStyles && node.color) {
|
|
11353
11392
|
return 'rgb(' + node.color.red + ',' + node.color.green + ',' + node.color.blue + ')';
|
|
@@ -13496,7 +13535,8 @@ function (root, d3, forester, phyloXml) {
|
|
|
13496
13535
|
const SCALE_BAR_RESERVE = 30;
|
|
13497
13536
|
|
|
13498
13537
|
function scaleBarShown() {
|
|
13499
|
-
return _state.phylogram === true && _basicTreeProperties.branchLengths === true && !scaleAxisShown()
|
|
13538
|
+
return _state.phylogram === true && _basicTreeProperties.branchLengths === true && !scaleAxisShown()
|
|
13539
|
+
&& _state.showScaleBar !== false;
|
|
13500
13540
|
}
|
|
13501
13541
|
|
|
13502
13542
|
// pixels per branch-length unit in the current layout's own coordinates
|
|
@@ -13522,7 +13562,9 @@ function (root, d3, forester, phyloXml) {
|
|
|
13522
13562
|
if (!scaleBarShown()) {
|
|
13523
13563
|
return;
|
|
13524
13564
|
}
|
|
13525
|
-
|
|
13565
|
+
// the unit the file states, as the distance axis prints it; none when it states none
|
|
13566
|
+
let unit = _treeData && typeof _treeData.branch_length_unit === 'string' ? _treeData.branch_length_unit.trim() : '';
|
|
13567
|
+
let bar = forester.scaleBarLength(branchLengthPixelsPerUnit(), SCALE_BAR_TARGET_PX, unit);
|
|
13526
13568
|
if (!bar) {
|
|
13527
13569
|
return;
|
|
13528
13570
|
}
|
|
@@ -14156,6 +14198,11 @@ function (root, d3, forester, phyloXml) {
|
|
|
14156
14198
|
scheduleUpdate(null, 0);
|
|
14157
14199
|
}
|
|
14158
14200
|
|
|
14201
|
+
function scaleBarCbClicked() {
|
|
14202
|
+
_state.showScaleBar = getCheckboxValue(SCALE_BAR_CB);
|
|
14203
|
+
scheduleUpdate(null, 0);
|
|
14204
|
+
}
|
|
14205
|
+
|
|
14159
14206
|
// Fit the circular tree into the viewport, centred. The root is at the
|
|
14160
14207
|
// group origin (0,0), so we place that at the viewport centre and scale so
|
|
14161
14208
|
// the outer label ring fits. Computed from the known radial extent rather
|
|
@@ -15314,6 +15361,12 @@ function (root, d3, forester, phyloXml) {
|
|
|
15314
15361
|
gridCb.disabled = _state.unrootedDisplay === true || _state.phylogram !== true
|
|
15315
15362
|
|| _state.showScaleAxis !== true;
|
|
15316
15363
|
}
|
|
15364
|
+
let barCb = byId(SCALE_BAR_CB);
|
|
15365
|
+
if (barCb) {
|
|
15366
|
+
// a cladogram has nothing to measure, and under the axis the bar
|
|
15367
|
+
// has given way; every layout can carry it otherwise
|
|
15368
|
+
barCb.disabled = _state.phylogram !== true || scaleAxisShown();
|
|
15369
|
+
}
|
|
15317
15370
|
let minus = byId(ZOOM_OUT_X);
|
|
15318
15371
|
let plus = byId(ZOOM_IN_X);
|
|
15319
15372
|
let expandV = byId(ZOOM_TO_EXPAND_Y);
|
|
@@ -15380,22 +15433,6 @@ function (root, d3, forester, phyloXml) {
|
|
|
15380
15433
|
}
|
|
15381
15434
|
|
|
15382
15435
|
|
|
15383
|
-
function customCbClicked(cb_id) {
|
|
15384
|
-
if (_nodeLabels) {
|
|
15385
|
-
const cb_value = getCheckboxValue(cb_id);
|
|
15386
|
-
for (const value of Object.values(_nodeLabels)) {
|
|
15387
|
-
if (value.label && value.showButton === true && value.propertyRef && value.description) {
|
|
15388
|
-
if (value.cb_id === cb_id) {
|
|
15389
|
-
value.selected = cb_value;
|
|
15390
|
-
}
|
|
15391
|
-
}
|
|
15392
|
-
}
|
|
15393
|
-
search0();
|
|
15394
|
-
search1();
|
|
15395
|
-
update();
|
|
15396
|
-
}
|
|
15397
|
-
}
|
|
15398
|
-
|
|
15399
15436
|
function taxonomyCbClicked() {
|
|
15400
15437
|
_state.showTaxonomy = getCheckboxValue(TAXONOMY_CB);
|
|
15401
15438
|
if (_state.showTaxonomy) {
|
|
@@ -18418,6 +18455,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
18418
18455
|
on(GENE_ANCHOR_SELECT, 'change', geneAnchorChanged);
|
|
18419
18456
|
on(GENE_FAMILY_SELECT, 'change', geneFamilyChanged);
|
|
18420
18457
|
on(GENE_LABELS_SELECT, 'change', geneLabelsChanged);
|
|
18458
|
+
on(SCALE_BAR_CB, 'click', scaleBarCbClicked);
|
|
18421
18459
|
on(SCALE_AXIS_CB, 'click', scaleAxisCbClicked);
|
|
18422
18460
|
on(SCALE_GRID_CB, 'click', scaleGridCbClicked);
|
|
18423
18461
|
|
|
@@ -18431,20 +18469,6 @@ function (root, d3, forester, phyloXml) {
|
|
|
18431
18469
|
|
|
18432
18470
|
on(SHORTEN_NODE_NAME_CB, 'click', shortenCbClicked);
|
|
18433
18471
|
|
|
18434
|
-
if (_nodeLabels) {
|
|
18435
|
-
for (const [key, value] of Object.entries(_nodeLabels)) {
|
|
18436
|
-
if (value.label && value.showButton === true && value.propertyRef && value.description) {
|
|
18437
|
-
const cb_id = makeIdForCustomCheckboxButton(key);
|
|
18438
|
-
on(cb_id, 'click', function () {
|
|
18439
|
-
customCbClicked(cb_id);
|
|
18440
|
-
});
|
|
18441
|
-
if (value.selected === true) {
|
|
18442
|
-
setCheckboxValue(cb_id, true);
|
|
18443
|
-
}
|
|
18444
|
-
}
|
|
18445
|
-
}
|
|
18446
|
-
}
|
|
18447
|
-
|
|
18448
18472
|
on(LABEL_COLOR_SELECT_MENU, 'change', function () {
|
|
18449
18473
|
let v = this.value;
|
|
18450
18474
|
if (v && v !== DEFAULT && _vis && _vis.byId[v]) {
|
|
@@ -18885,10 +18909,6 @@ function (root, d3, forester, phyloXml) {
|
|
|
18885
18909
|
return '<label class="aptx-seg" title="' + tooltip + '"><input type="radio" name="' + radioGroup + '" id="' + id + '">' + content + '</label>';
|
|
18886
18910
|
}
|
|
18887
18911
|
|
|
18888
|
-
function makeIdForCustomCheckboxButton(key) {
|
|
18889
|
-
return key + '__cb';
|
|
18890
|
-
}
|
|
18891
|
-
|
|
18892
18912
|
function makeDisplayControl() {
|
|
18893
18913
|
let labels = [];
|
|
18894
18914
|
let nodes = [];
|
|
@@ -18910,17 +18930,6 @@ function (root, d3, forester, phyloXml) {
|
|
|
18910
18930
|
labels.push(makeCheckboxItem('Metadata', PROPERTIES_CB, 'to show/hide the nodes\' metadata (phyloXML'
|
|
18911
18931
|
+ ' properties: species, host, …) in their labels; Metadata fields… chooses which, and in what order'));
|
|
18912
18932
|
}
|
|
18913
|
-
if (_nodeLabels) {
|
|
18914
|
-
for (const [key, value] of Object.entries(_nodeLabels)) {
|
|
18915
|
-
if (value.label && value.propertyRef && value.description) {
|
|
18916
|
-
const cb_id = makeIdForCustomCheckboxButton(key);
|
|
18917
|
-
if (value.showButton === true) {
|
|
18918
|
-
labels.push(makeCheckboxItem(value.label, cb_id, value.description));
|
|
18919
|
-
}
|
|
18920
|
-
value.cb_id = cb_id;
|
|
18921
|
-
}
|
|
18922
|
-
}
|
|
18923
|
-
}
|
|
18924
18933
|
if (_basicTreeProperties.confidences) {
|
|
18925
18934
|
labels.push(makeCheckboxItem('Confidence', CONFIDENCE_VALUES_CB, 'to show/hide confidence values'));
|
|
18926
18935
|
}
|
|
@@ -18984,6 +18993,9 @@ function (root, d3, forester, phyloXml) {
|
|
|
18984
18993
|
let timeAxis = _timeInfo && _timeInfo.type
|
|
18985
18994
|
? (_timeInfo.type === 'geologic' ? 'the geologic (ICS) time axis' : 'the calendar time axis')
|
|
18986
18995
|
: null;
|
|
18996
|
+
// "Scale", the desktop's label for the same switch (its Settings > Overlays > Scale & Grid)
|
|
18997
|
+
opts.push(makeCheckboxItem('Scale', SCALE_BAR_CB, 'to show/hide the scale bar at the bottom left of a'
|
|
18998
|
+
+ ' phylogram: a round length in the tree\'s branch-length unit (it gives way while the scale axis is on)'));
|
|
18987
18999
|
opts.push(makeCheckboxItem('Scale Axis', SCALE_AXIS_CB, 'to show/hide '
|
|
18988
19000
|
+ (timeAxis ? timeAxis + ' and node-age bars (a distance axis while the branches show divergence)'
|
|
18989
19001
|
: 'a labeled distance axis from the root to the deepest tip')
|
|
@@ -19399,6 +19411,7 @@ function (root, d3, forester, phyloXml) {
|
|
|
19399
19411
|
syncHeatmapControls();
|
|
19400
19412
|
setCheckboxValue(SCALE_AXIS_CB, _state.showScaleAxis);
|
|
19401
19413
|
setCheckboxValue(SCALE_GRID_CB, _state.showScaleGrid);
|
|
19414
|
+
setCheckboxValue(SCALE_BAR_CB, _state.showScaleBar);
|
|
19402
19415
|
setCheckboxValue(SHORTEN_NODE_NAME_CB, _state.shortenNodeNames);
|
|
19403
19416
|
populateVisualizationMenus();
|
|
19404
19417
|
initializeSearchOptions();
|
package/forester.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
// v 3.
|
|
23
|
+
// v 3.26.0
|
|
24
24
|
// 2026-09-17
|
|
25
25
|
//
|
|
26
26
|
// forester.js is a general suite for dealing with phylogenetic trees.
|
|
@@ -8784,7 +8784,11 @@
|
|
|
8784
8784
|
// -- 1, 2 or 5 times a power of ten -- chosen so the bar comes out about
|
|
8785
8785
|
// targetPx long at pxPerUnit pixels per unit. Returns {length, label,
|
|
8786
8786
|
// px}, or null when the scale is unusable (zero, negative or infinite).
|
|
8787
|
-
|
|
8787
|
+
// The label carries the tree's stated unit after the number ("0.05
|
|
8788
|
+
// subs/site"), as the distance axis does at its end, and nothing when
|
|
8789
|
+
// the file states none: a tree that does not say what its lengths
|
|
8790
|
+
// measure gets no guess (a BV-BRC user asked for units, 2026-10-08).
|
|
8791
|
+
forester.scaleBarLength = function (pxPerUnit, targetPx, unit) {
|
|
8788
8792
|
if (!(pxPerUnit > 0) || !isFinite(pxPerUnit)) {
|
|
8789
8793
|
return null;
|
|
8790
8794
|
}
|
|
@@ -8793,7 +8797,8 @@
|
|
|
8793
8797
|
let base = raw / Math.pow(10, k);
|
|
8794
8798
|
let nice = base < 1.5 ? 1 : (base < 3.5 ? 2 : (base < 7.5 ? 5 : 10));
|
|
8795
8799
|
let length = Number((nice * Math.pow(10, k)).toPrecision(2));
|
|
8796
|
-
|
|
8800
|
+
let u = typeof unit === 'string' ? unit.trim() : '';
|
|
8801
|
+
return {length: length, label: String(length) + (u ? ' ' + u : ''), px: length * pxPerUnit};
|
|
8797
8802
|
};
|
|
8798
8803
|
|
|
8799
8804
|
// --------------------------------------------------------------
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "archaeopteryx",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.26.0",
|
|
4
4
|
"description": "Archaeopteryx.js is a software tool for the visualization and analysis of highly annotated phylogenetic trees.",
|
|
5
5
|
"main": "archaeopteryx.js",
|
|
6
6
|
"types": "archaeopteryx.d.ts",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"LICENSE"
|
|
21
21
|
],
|
|
22
22
|
"scripts": {
|
|
23
|
-
"test": "node test/forester_test.js && node test/search_test.js && node test/visualization_test.js && node test/domain_test.js && node test/gene_test.js && node test/clock_test.js && node test/metadata_test.js && node test/heatmap_test.js && node test/clustering_test.js",
|
|
23
|
+
"test": "node test/forester_test.js && node test/search_test.js && node test/visualization_test.js && node test/domain_test.js && node test/gene_test.js && node test/clock_test.js && node test/metadata_test.js && node test/heatmap_test.js && node test/clustering_test.js && node test/canvg_global_test.js",
|
|
24
24
|
"lint": "eslint .",
|
|
25
25
|
"lint:fix": "eslint . --fix",
|
|
26
26
|
"format": "prettier --write .",
|