archaeopteryx 3.24.0 → 3.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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 appears in the Download
68
- menu only when `window.Canvg` is present):
69
- * canvg (4.x): https://www.npmjs.com/package/canvg — publishes ES modules
70
- only (no classic-script global build), so bridge it yourself:
71
- ```html
72
- <script type="module">
73
- import {Canvg} from 'http://path/to/canvg.js'; // a self-contained build; see below
74
- window.Canvg = Canvg;
75
- </script>
76
- ```
77
- A module script is deferred regardless of where it sits in the page, and
78
- PNG export only runs later from a Download click, so this can go anywhere
79
- before the click — it does not need to precede archaeopteryx.js's own
80
- `<script>` tag. `docs/lib/canvg.js` in this repository is one such
81
- self-contained build (npm's `canvg@4.0.3` bundled into one file, e.g. via
82
- `esm.sh/canvg@4?bundle`, with its one unnecessary Node-environment shim
83
- import removed — see the file's own header comment).
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):
@@ -1056,8 +1065,8 @@ Both entry points take **exactly** the arguments shown — a call with the old
1056
1065
  trailing arguments (the separate settings bag, `nodeVisualizations`,
1057
1066
  `nodeLabels`, `specialVisualizations`, or the positional Newick parse
1058
1067
  options) **throws** with a message saying where each one went: everything now
1059
- lives in the **one config object** (`nodeLabels` and
1060
- `internalNumericLabels` are config keys). `config` itself is optional — `archaeopteryx.launch('#phylogram1',
1068
+ lives in the **one config object** (`internalNumericLabels` is a config key;
1069
+ `nodeLabels` is retired, see the deprecated keys below). `config` itself is optional — `archaeopteryx.launch('#phylogram1',
1061
1070
  tree)` works.
1062
1071
 
1063
1072
  `container` is a **CSS selector or the DOM element itself** (frameworks hand
@@ -1551,14 +1560,13 @@ copy-pastable JSON.
1551
1560
  | `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
1561
  | `searchBinitialValue` | `null` | Prefill search box B. |
1553
1562
  | `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. |
1563
+ | `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
1564
  | `visualizationsLegendXpos` | `254` | Legend position, x. |
1556
1565
  | `visualizationsLegendYpos` | `30` | Legend position, y. |
1557
1566
  | `enableDownloads` | `true` | Offer the download buttons. |
1558
1567
  | `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
1568
  | `nhExportWriteConfidences` | `true` | Write confidences into exported Newick. |
1560
1569
  | `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
1570
  | `enableSubtreeDeletion` | `true` | Offer node / subtree deletion in the node menu. |
1563
1571
  | `enableAccessToDatabases` | `true` | Offer the “Access DB” link in the node menu. |
1564
1572
  | `enableManualNodeSelection` | `false` | Add the Select/Deselect entries to the node menu. |
@@ -1581,7 +1589,7 @@ whatever it names.
1581
1589
 
1582
1590
  ### Accepted, with a warning
1583
1591
 
1584
- Four keys are neither current nor removed: they are accepted so an existing
1592
+ Five keys are neither current nor removed: they are accepted so an existing
1585
1593
  embed keeps working, and warn on the console.
1586
1594
 
1587
1595
  | Key | What happens |
@@ -1590,6 +1598,7 @@ embed keeps working, and warn on the console.
1590
1598
  | `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
1599
  | `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
1600
  | `timeAxisGrid` | Translated to `showScaleGrid`, value unchanged. |
1601
+ | `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
1602
 
1594
1603
  ### What replaced the rest
1595
1604
 
@@ -1734,6 +1743,21 @@ All 118 of them, alphabetically:
1734
1743
  | `visualizationsLegendXposOrig` | Internal bookkeeping; set visualizationsLegendXpos. |
1735
1744
  | `visualizationsLegendYposOrig` | Internal bookkeeping; set visualizationsLegendYpos. |
1736
1745
 
1746
+ ### Removed from `forester` — calling these throws `TypeError`
1747
+
1748
+ 3.0.0 also dropped twelve exports of `forester.js` that the viewer no longer
1749
+ used. They are gone, not stubbed, so a 2.x embedding that calls one fails with
1750
+ `TypeError: forester.<name> is not a function` — inside its own parse step, if
1751
+ that is where it called it.
1752
+
1753
+ | Function | Why, and what to do instead |
1754
+ | --- | --- |
1755
+ | `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. |
1756
+ | `collapse`, `unCollapse` | The subtree-collapse feature was removed; they were its only writers. |
1757
+ | `getChildren` | Only meant something while collapse existed; read `node.children`. |
1758
+ | `findByTaxonomyCode`, `findByTaxonomyScientificName` | Superseded by the search (the panel's search fields). |
1759
+ | `calcAverageTreeHeight`, `calcMaxDepth`, `calcBranchLengthSimpleStatistics`, `removeMaxBranchLength`, `isHasNodeData`, `getOneDistinctTaxonomy` | Never used by the viewer; nothing replaces them. |
1760
+
1737
1761
  ## The automatic visualization system (developer spec)
1738
1762
 
1739
1763
  This section, together with **Value grouping** below, is written to be
@@ -29,9 +29,13 @@ export interface Phylogeny extends PhylogenyNode {
29
29
 
30
30
  export type Layout = 'rectangular' | 'circular' | 'unrooted';
31
31
 
32
- /** One custom label-field entry (the config's nodeLabels values).
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
- * Two modes:
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. */
@@ -176,8 +184,8 @@ export interface ViewState {
176
184
  shapeBy?: string;
177
185
  /** The panel's checked boxes: name, taxonomy, sequence, confidence,
178
186
  * madValues, branchLength, external, internal, nodeEvents, branchEvents,
179
- * supportDots, shortNames, autoHide, visualizations, visualStyles, and
180
- * custom:<key> for a nodeLabels checkbox. */
187
+ * supportDots, shortNames, autoHide, visualizations, visualStyles. (A
188
+ * custom:<key> flag from a 3.24.0 hash is read and ignored.) */
181
189
  show?: string[];
182
190
  font?: number;
183
191
  node?: number;
package/archaeopteryx.js CHANGED
@@ -20,7 +20,7 @@
20
20
  *
21
21
  */
22
22
 
23
- // v 3.24.0
23
+ // v 3.25.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.24.0';
106
+ const VERSION = '3.25.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';
@@ -539,7 +539,6 @@ function (root, d3, forester, phyloXml) {
539
539
  let _container = null; // the resolved container ELEMENT (never a selector string)
540
540
  let _intervalId = 0;
541
541
  let _maxLabelLength = 0;
542
- let _nodeLabels = null;
543
542
  let _state = null; // live display state: what the control panel writes to
544
543
  let _root = null;
545
544
  let _root_const = null;
@@ -606,6 +605,7 @@ function (root, d3, forester, phyloXml) {
606
605
  const DOMAIN_SCALE_HEADROOM = 0.9; // drawn length over reserved length
607
606
  const DOMAIN_BOX_MIN_H = 6;
608
607
  const DOMAIN_BOX_MAX_H = 16;
608
+ const LEGEND_MAX_ROWS = 20; // value rows a legend lists before "n more"; the rest fold
609
609
  const DOMAIN_BACKBONE_COLOR = 'rgb(100,100,100)'; // the same in both themes and in exports
610
610
  const DOMAIN_SHADOW_COLOR = 'rgb(8,18,21)';
611
611
  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
@@ -1948,7 +1948,32 @@ function (root, d3, forester, phyloXml) {
1948
1948
  // an explicit request is honoured even for a wide field (21+
1949
1949
  // values) the automatic path would only offer, never impose;
1950
1950
  // autoColorId too, so it survives subtree views like the
1951
- // automatic pick does
1951
+ // automatic pick does.
1952
+ //
1953
+ // Honoured, but not in silence. A request for a field the
1954
+ // automatic choice would not open with -- wide, or sparse (on
1955
+ // fewer than 2/3 of the tips; it opens only when nothing denser
1956
+ // exists) -- says so in the console and names the field the
1957
+ // viewer would have chosen. BV-BRC's virus trees carried a
1958
+ // site-wide 'Host' from the influenza days: family trees opened
1959
+ // coloured by 39 to 168 hosts while Genus (4 to 15 values) sat
1960
+ // in the file, and nothing anywhere said the viewer had been
1961
+ // overruled (Christian, 2026-10-08: a console warning when a
1962
+ // sub-optimal visualization is forced is needed). A request for
1963
+ // a clean field that merely ranks below the automatic pick is a
1964
+ // site's taste and warns nothing; nor does a sparse field that
1965
+ // is the automatic pick itself.
1966
+ let auto = forester.openingVisualization(_vis.candidates);
1967
+ if ((requested.wide || requested.sparse) && !(auto && auto.id === requested.id)) {
1968
+ let why = requested.wide
1969
+ ? requested.values.length + ' distinct values on this tree, more than the '
1970
+ + LEGEND_MAX_ROWS + ' the legend lists'
1971
+ : 'a value on ' + requested.coverage + ' of the tree\'s ' + requested.total + ' tips';
1972
+ console.warn(WARNING + ': initialVisualization "' + _settings.initialVisualization
1973
+ + '" names a field with ' + why + '; left to itself the viewer would '
1974
+ + (auto ? 'open with "' + auto.label + '" (' + auto.values.length + ' values)'
1975
+ : 'open uncoloured'));
1976
+ }
1952
1977
  _vis.autoColorId = requested.id;
1953
1978
  _vis.colorId = requested.id;
1954
1979
  _state.showVisualizations = true;
@@ -2144,6 +2169,7 @@ function (root, d3, forester, phyloXml) {
2144
2169
  }
2145
2170
  dialog.addEventListener('close', close);
2146
2171
 
2172
+ let placed = false; // set by place() once the picker has been put somewhere
2147
2173
  render(false);
2148
2174
  dialog.show();
2149
2175
  place();
@@ -2151,40 +2177,67 @@ function (root, d3, forester, phyloXml) {
2151
2177
  // Beside the legend it edits, never on top of it -- a picker that hides
2152
2178
  // the swatch it is changing defeats the live preview.
2153
2179
  //
2154
- // Re-measured rather than remembered, for two reasons: every redraw
2155
- // DESTROYS and rebuilds the legend group, so a held reference goes
2156
- // stale and measures zero; and the card grows when the [reset colors]
2157
- // chip appears on the first pick, which would leave a
2158
- // positioned-once picker sitting over the counts. It only MOVES when
2159
- // it would otherwise overlap, so the dialog does not chase the legend
2160
- // around while the user drags.
2180
+ // Placed OUTRIGHT the first time, viewport-fixed. A non-modal <dialog>
2181
+ // appended to the body sits at its static position, the end of the
2182
+ // document: on the demo page that is the top of the window (the
2183
+ // container is out of flow), on a page that flows, such as BV-BRC's,
2184
+ // it is below everything on it. Until 2026-10-08 the picker moved
2185
+ // only when that spot overlapped the card, so on BV-BRC it opened
2186
+ // under the site's own bars, off the bottom of the window
2187
+ // (Christian's screenshot). Fixed positioning also keeps a scrolling
2188
+ // page from carrying it away.
2189
+ //
2190
+ // Re-measured rather than remembered afterwards, for two reasons:
2191
+ // every redraw DESTROYS and rebuilds the legend group, so a held
2192
+ // reference goes stale and measures zero; and the card grows when the
2193
+ // [reset colors] chip appears on the first pick, which would leave a
2194
+ // positioned-once picker sitting over the counts. Once placed it only
2195
+ // MOVES when it would otherwise overlap, so the dialog does not chase
2196
+ // the legend around while the user drags.
2161
2197
  function place() {
2162
2198
  let el = anchorClass ? document.querySelector('g.' + anchorClass) : null;
2163
2199
  let box = el ? el.getBoundingClientRect() : null;
2164
- if (!box || !box.width) {
2165
- return;
2200
+ if (box && !box.width) {
2201
+ box = null;
2166
2202
  }
2167
- let now = dialog.getBoundingClientRect();
2168
- let overlaps = now.width > 0
2169
- && !(now.left >= box.right || now.right <= box.left
2170
- || now.top >= box.bottom || now.bottom <= box.top);
2171
- if (now.width > 0 && !overlaps) {
2172
- return;
2203
+ if (placed) {
2204
+ if (!box) {
2205
+ return;
2206
+ }
2207
+ let now = dialog.getBoundingClientRect();
2208
+ let overlaps = now.width > 0
2209
+ && !(now.left >= box.right || now.right <= box.left
2210
+ || now.top >= box.bottom || now.bottom <= box.top);
2211
+ if (!overlaps) {
2212
+ return;
2213
+ }
2173
2214
  }
2174
2215
  let w = dialog.offsetWidth;
2175
2216
  let h = dialog.offsetHeight;
2176
- // to the right of the card, or to its left when that would leave
2177
- // the viewport
2178
- let left = box.right + 10;
2179
- if (left + w > window.innerWidth - 6) {
2180
- left = box.left - w - 10;
2217
+ let left;
2218
+ let top;
2219
+ if (box) {
2220
+ // to the right of the card, or to its left when that would
2221
+ // leave the viewport
2222
+ left = box.right + 10;
2223
+ if (left + w > window.innerWidth - 6) {
2224
+ left = box.left - w - 10;
2225
+ }
2226
+ top = box.top;
2227
+ } else {
2228
+ // no card to sit beside (the view has no swatch for the value):
2229
+ // the tree area's top left corner, inside the window
2230
+ let area = _container ? _container.getBoundingClientRect() : null;
2231
+ left = area ? area.left + 14 : 10;
2232
+ top = area ? Math.max(area.top, 0) + 14 : 10;
2181
2233
  }
2182
2234
  setStyles(dialog, {
2183
2235
  'position': 'fixed',
2184
2236
  'margin': '0',
2185
2237
  'left': Math.max(6, Math.min(window.innerWidth - w - 6, left)) + 'px',
2186
- 'top': Math.max(6, Math.min(window.innerHeight - h - 6, box.top)) + 'px'
2238
+ 'top': Math.max(6, Math.min(window.innerHeight - h - 6, top)) + 'px'
2187
2239
  });
2240
+ placed = true;
2188
2241
  }
2189
2242
  setTimeout(function () {
2190
2243
  document.addEventListener('pointerdown', onOutside, true);
@@ -2521,7 +2574,6 @@ function (root, d3, forester, phyloXml) {
2521
2574
  // is then just its "no value" row
2522
2575
  const isRange = kind === 'color' && colorModeOf(vis) === 'range' && vis.values.length > 0;
2523
2576
 
2524
- const LEGEND_MAX_ROWS = 20;
2525
2577
  let rows = [];
2526
2578
  let hidden = 0;
2527
2579
  if (!isRange) {
@@ -4725,25 +4777,6 @@ function (root, d3, forester, phyloXml) {
4725
4777
  l = append(l, labelPropertiesOf(phynode));
4726
4778
  }
4727
4779
 
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
4780
 
4748
4781
 
4749
4782
  if (_state.showDistributions && phynode.distributions && phynode.distributions.length > 0) {
@@ -5522,6 +5555,21 @@ function (root, d3, forester, phyloXml) {
5522
5555
  return v;
5523
5556
  },
5524
5557
  note: 'the grid follows the scale axis, time or distance'
5558
+ },
5559
+ // Retired in 3.25.0 (Christian, 2026-10-08: "go with the deprecation").
5560
+ // The custom label checkboxes it declared -- a site's fixed list of
5561
+ // property refs, one panel checkbox each, labelling nodes with the
5562
+ // property's value -- predate the Metadata checkbox and its
5563
+ // "Metadata fields…" chooser, which offer exactly the fields the tree
5564
+ // carries and replace identifier-like tip names with a name property
5565
+ // on their own. On BV-BRC the list, written for influenza, put eight
5566
+ // checkboxes on every virus tree, six of them for fields the tree
5567
+ // did not have. The checkbox code went with the key (the label
5568
+ // text, the panel rows, the view state's custom:<key> flags, which
5569
+ // an old hash may still carry and the reader now ignores). Accepted
5570
+ // with this warning for one release cycle, then REMOVED_CONFIG.
5571
+ nodeLabels: {
5572
+ note: 'the Metadata checkbox and its "Metadata fields…" chooser offer the tree\'s own fields; labelProperties sets the starting fields'
5525
5573
  }
5526
5574
  };
5527
5575
 
@@ -5721,7 +5769,6 @@ function (root, d3, forester, phyloXml) {
5721
5769
  'internalNumericLabels',
5722
5770
  'ladderizeTree',
5723
5771
  'nhExportWriteConfidences',
5724
- 'nodeLabels',
5725
5772
  'onViewChange',
5726
5773
  'panelDensity',
5727
5774
  'pngExportScale',
@@ -6321,19 +6368,6 @@ function (root, d3, forester, phyloXml) {
6321
6368
  } else if (typeof _settings.onViewChange !== 'function') {
6322
6369
  throw new Error(ERROR + '"onViewChange" must be a function (state, encoded) or null');
6323
6370
  }
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
6371
  if (_settings.pngExportScale === undefined) {
6338
6372
  _settings.pngExportScale = 4;
6339
6373
  } else if (typeof _settings.pngExportScale !== 'number'
@@ -6593,7 +6627,7 @@ function (root, d3, forester, phyloXml) {
6593
6627
  + ' trailing arguments were removed. The separate settings bag merged into the one'
6594
6628
  + ' config object; "nodeVisualizations" and "specialVisualizations" are gone for good'
6595
6629
  + ' (visualizations are derived automatically from the tree itself); "nodeLabels"'
6596
- + ' moved into the config as its "nodeLabels" key.');
6630
+ + ' is retired (the Metadata checkbox offers the tree\'s own fields).');
6597
6631
  }
6598
6632
  if (phylo === undefined || phylo === null) {
6599
6633
  throw new Error(ERROR + 'input tree is undefined or null');
@@ -6684,9 +6718,8 @@ function (root, d3, forester, phyloXml) {
6684
6718
  .on('zoom', zoom);
6685
6719
  _basicTreeProperties = forester.collectBasicTreeProperties(_treeData);
6686
6720
 
6687
- // Every launch starts from a clean slate: _vis is rebuilt below and
6688
- // _nodeLabels reassigned, never inherited from a previous launch in
6689
- // the same page.
6721
+ // Every launch starts from a clean slate: _vis is rebuilt below, never
6722
+ // inherited from a previous launch in the same page.
6690
6723
  _radialRotation = 0;
6691
6724
  _radialLabelsHorizontal = false;
6692
6725
  _msaColOffset = 0;
@@ -6704,7 +6737,6 @@ function (root, d3, forester, phyloXml) {
6704
6737
  // candidate ranking over 13k tips before the card could even paint.
6705
6738
  checkLayoutValue(cfg.state ? cfg.state.layout : undefined);
6706
6739
  initializeSettings(cfg.settings);
6707
- _nodeLabels = _settings.nodeLabels || null;
6708
6740
 
6709
6741
  // whatever a previous launch left in this container (or in the
6710
6742
  // container before it) goes now, so the old tree does not sit under
@@ -9896,20 +9928,6 @@ function (root, d3, forester, phyloXml) {
9896
9928
  return found;
9897
9929
  }
9898
9930
 
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
9931
  function searchStateOf(idx) {
9914
9932
  let spec = currentSearchSpec(idx);
9915
9933
  let value = (spec.value === null || spec.value === undefined) ? '' : String(spec.value);
@@ -9957,11 +9975,6 @@ function (root, d3, forester, phyloXml) {
9957
9975
  }).map(function (f) {
9958
9976
  return f[0];
9959
9977
  });
9960
- customLabelEntries().forEach(function (c) {
9961
- if (c.entry.selected === true) {
9962
- s.show.push('custom:' + c.key);
9963
- }
9964
- });
9965
9978
  s.font = _state.externalNodeFontSize;
9966
9979
  s.node = _state.nodeSizeDefault;
9967
9980
  s.branch = _state.branchWidthDefault;
@@ -10107,9 +10120,6 @@ function (root, d3, forester, phyloXml) {
10107
10120
  VIEW_SHOW_FLAGS.forEach(function (f) {
10108
10121
  _state[f[2]] = on.has(f[0]);
10109
10122
  });
10110
- customLabelEntries().forEach(function (c) {
10111
- c.entry.selected = on.has('custom:' + c.key);
10112
- });
10113
10123
  }
10114
10124
  if (isFinite(s.font)) {
10115
10125
  setFontSizes(Number(s.font));
@@ -10259,11 +10269,6 @@ function (root, d3, forester, phyloXml) {
10259
10269
  VIEW_SHOW_FLAGS.forEach(function (f) {
10260
10270
  setCheckboxValue(f[1], _state[f[2]] === true);
10261
10271
  });
10262
- customLabelEntries().forEach(function (c) {
10263
- if (c.entry.cb_id) {
10264
- setCheckboxValue(c.entry.cb_id, c.entry.selected === true);
10265
- }
10266
- });
10267
10272
  setCheckboxValue(MSA_CB, _state.showMsa === true);
10268
10273
  setCheckboxValue(MSA_LOGO_CB, _state.showMsaLogo === true);
10269
10274
  setCheckboxValue(HEATMAP_CB, _state.showHeatmap === true);
@@ -15380,22 +15385,6 @@ function (root, d3, forester, phyloXml) {
15380
15385
  }
15381
15386
 
15382
15387
 
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
15388
  function taxonomyCbClicked() {
15400
15389
  _state.showTaxonomy = getCheckboxValue(TAXONOMY_CB);
15401
15390
  if (_state.showTaxonomy) {
@@ -18431,20 +18420,6 @@ function (root, d3, forester, phyloXml) {
18431
18420
 
18432
18421
  on(SHORTEN_NODE_NAME_CB, 'click', shortenCbClicked);
18433
18422
 
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
18423
  on(LABEL_COLOR_SELECT_MENU, 'change', function () {
18449
18424
  let v = this.value;
18450
18425
  if (v && v !== DEFAULT && _vis && _vis.byId[v]) {
@@ -18885,10 +18860,6 @@ function (root, d3, forester, phyloXml) {
18885
18860
  return '<label class="aptx-seg" title="' + tooltip + '"><input type="radio" name="' + radioGroup + '" id="' + id + '">' + content + '</label>';
18886
18861
  }
18887
18862
 
18888
- function makeIdForCustomCheckboxButton(key) {
18889
- return key + '__cb';
18890
- }
18891
-
18892
18863
  function makeDisplayControl() {
18893
18864
  let labels = [];
18894
18865
  let nodes = [];
@@ -18910,17 +18881,6 @@ function (root, d3, forester, phyloXml) {
18910
18881
  labels.push(makeCheckboxItem('Metadata', PROPERTIES_CB, 'to show/hide the nodes\' metadata (phyloXML'
18911
18882
  + ' properties: species, host, …) in their labels; Metadata fields… chooses which, and in what order'));
18912
18883
  }
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
18884
  if (_basicTreeProperties.confidences) {
18925
18885
  labels.push(makeCheckboxItem('Confidence', CONFIDENCE_VALUES_CB, 'to show/hide confidence values'));
18926
18886
  }
package/forester.js CHANGED
@@ -20,7 +20,7 @@
20
20
  *
21
21
  */
22
22
 
23
- // v 3.24.0
23
+ // v 3.25.0
24
24
  // 2026-09-17
25
25
  //
26
26
  // forester.js is a general suite for dealing with phylogenetic trees.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archaeopteryx",
3
- "version": "3.24.0",
3
+ "version": "3.25.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 .",