Tree model
The model layer represents a tree as flat typed arrays — a structure-of-arrays
design ported from figtree-web so that million-node trees stay cache-friendly
and never allocate a number[] per node.
TreeBuffers
Section titled “TreeBuffers”TreeBuffers is the central data structure. Child lists are not stored as
arrays; instead each node points at its firstChild, and children are walked
through a nextSibling linked list.
interface TreeBuffers { count: number; // valid node slots, ≤ capacity tipCount: number; // leaves (nodes with FLAG_TIP set) root: number; // index of the root node capacity: number; // max addressable nodeIdx (parsers pre-size) parent: Int32Array; firstChild: Int32Array; nextSibling: Int32Array; length: Float64Array; // branch length parent[i] → i; NaN when unspecified flags: Uint8Array; // per-node bit flags — currently just FLAG_TIP nameIdx: Int32Array; // index into `names`, or NO_NAME names: StringPool; // interned labels}Sentinels and flags are exported alongside it:
| Export | Meaning |
|---|---|
FLAG_TIP | Bit set on leaf nodes (flags[i] & FLAG_TIP). |
NO_NODE (-1) | Missing parent / child / sibling pointer. |
NO_NAME (-1) | nameIdx value for an unlabeled node. |
StringPool | Interns labels so identical tip names are stored once. |
allocTreeBuffers(capacity, names?) returns an empty buffer sized for
capacity nodes; parsers bump count as they write slots. Passing a shared
StringPool lets several trees intern identical labels together.
import { allocTreeBuffers, StringPool } from "insomni-phylo";
const pool = new StringPool();const buffers = allocTreeBuffers(1024, pool);Traversal
Section titled “Traversal”Three small helpers read the buffers. children is a generator over the
nextSibling chain and is safe to break out of early.
import { children, isTip, nodeName } from "insomni-phylo";
for (const c of children(tree, tree.root)) { if (isTip(tree, c)) console.log("leaf:", nodeName(tree, c));}| Function | Signature | Notes |
|---|---|---|
nodeName | (b, i) => string | null | Interned label, or null if unlabeled. |
isTip | (b, i) => boolean | True when FLAG_TIP is set. |
children | (b, i) => Generator<number> | Yields direct child node ids. |
Ordering — ladderize
Section titled “Ordering — ladderize”ladderize reorders sibling chains by descendant-tip count, in place. Only
firstChild / nextSibling pointers change — node indices, parents, lengths,
and any table keyed by nodeIdx stay aligned. It is a stable sort.
import { ladderize } from "insomni-phylo";
ladderize(tree, "increasing"); // smallest clade first (FigTree default)ladderize(tree, "decreasing"); // reverseLadderizeDirection is "increasing" | "decreasing".
Annotations
Section titled “Annotations”Per-node metadata parsed from [&k=v] / NHX / NEXUS lives in an
AnnotationTable — columnar dense storage with one cell per node.
import { AnnotationTable } from "insomni-phylo";
const table = new AnnotationTable();table.set(3, "posterior", 0.97, "0.97"); // value + optional verbatim raw texttable.get(3, "posterior"); // 0.97table.getRaw(3, "posterior"); // "0.97"Each AnnotationColumn carries values, rawValues (the verbatim source
slice), and an observed type ("number" | "string" | "boolean" | "list" | "mixed"). The rawValues channel is what makes byte-exact round-trips
possible — see FigTree compatibility.
| Method / function | Purpose |
|---|---|
set(i, key, value, raw?) | Write a cell; densifies columns lazily. |
get(i, key) / getRaw(i, key) | Read the value / verbatim source. |
column(key) / has(key) / keys() | Column access and enumeration. |
extractNumericColumn(table, key) | Dense Float64Array (NaN for missing). |
extractNumericColumnSized(table, key, n) | Same, padded to n nodes. |
getNumericStats(values) | { min, max, count } or null if all-NaN. |
getNumericPercentiles(values, fractions) | Linear-interpolated percentiles. |
extractHilights(table) | Parsed !hilight clade highlight colors. |
AnnotationValue is number | string | boolean | AnnotationValue[].
Attribute catalog
Section titled “Attribute catalog”buildAttributeCatalog runs one pass over an AnnotationTable and summarizes
every column into an AttributeInfo — the data behind “Colour by / Size by”
dropdowns. Keys starting with ! (FigTree “hot” annotations) are hidden unless
includeHot is set.
import { buildAttributeCatalog } from "insomni-phylo";
const catalog = buildAttributeCatalog(table, { buffers: tree });// → AttributeInfo[] sorted by keyEach AttributeInfo reports its kind (AttributeKind: "number",
"string", "boolean", "range", "list", "mixed", "temporal"), the
non-null count, numeric min/max, a capped distinct set (limit
DISTINCT_LIMIT = 64), plus auto-mapping hints — suggestedScale,
suggestedPalette, isDiverging, isSkewed, p5/p95, histogramBins, and
(when buffers is supplied) a phyloSignal clustering diagnostic.
joinByTip — external metadata
Section titled “joinByTip — external metadata”joinByTip merges a row table onto the tree by matching a row field against
tip names (ggtree’s %<+%). It is pure and returns a fresh AnnotationTable
plus warning diagnostics for unmatched or duplicate keys. It takes a TipAxis
(produced by a layout) for name → tip-id resolution.
import { joinByTip, layoutRectilinear } from "insomni-phylo";
const axis = layoutRectilinear(tree).tipAxis;const { table, diagnostics } = joinByTip( [ { id: "A", country: "DE" }, { id: "B", country: "FR" }, ], axis, { key: "id" },);groupClade — categorical clade column
Section titled “groupClade — categorical clade column”groupClade returns a fresh categorical AnnotationColumn labeling nodes
inside a clade vs. outside (ggtree’s groupClade / groupOTU). The clade is
specified either by nodeId or by the MRCA of named tips.
import { groupClade } from "insomni-phylo";
const col = groupClade( tree, { mrcaOf: ["A", "B"] }, { label: "ingroup", otherLabel: "outgroup", // pass null to leave outside nodes empty },);