Skip to content

FigTree compatibility

FigTree compatibility is load-bearing: phylo can open a FigTree/BEAST NEXUS file, apply its FIGTREE styling block, and write it back so that FigTree itself re-opens the result without complaint. The trickiest part is preserving int-vs-double in numeric annotations — FigTree casts settings to specific Java types in its Swing controllers, and a 5.0 where a 5 is expected raises a ClassCastException at paint time, which hangs FigTree with no error dialog.

Three modules form the seam: figtree-apply.ts (settings → style), figtree-write.ts (style → settings), and figtree-text-style.ts (settings → label text styling).

A NEXUS document’s figtreeSettings (from parseNexus) is a raw Map<string, string>. recognizeFigtreeSettings turns a typed settings bag into a structured RecognizedFigtreeSettings — the shared intermediate both translators read.

import { recognizeFigtreeSettings } from "insomni-phylo";
const recognized = recognizeFigtreeSettings(settingsLike);
recognized.branchColorAttribute; // string?
recognized.branchLineWidth; // number?
recognized.foregroundColour; // Color?

The input shape, FigTreeSettingsLike, wraps values: ReadonlyMap<string, FigTreeValueLike> where each value is a tagged union that distinguishes integers from doubles:

type FigTreeValueLike =
| { kind: "boolean"; value: boolean }
| { kind: "integer"; value: number }
| { kind: "double"; value: number }
| …; // string / color

Carrying "integer" vs "double" through recognition is what makes the round-trip safe — the type tag survives so the writer can re-emit 5 as 5, not 5.0.

Two translators share that recognition layer:

import {
applyFigtreeSettings, // → Partial<PhyloChartConfig>
applyFigtreeSettingsLegacy, // → Partial<PhyloStyle>
} from "insomni-phylo";
// Drive the legacy PhyloScene renderer:
const patch = applyFigtreeSettingsLegacy(settingsLike, attributes);
handle.setStyle(patch);
FunctionOutputUse
applyFigtreeSettings(settings, attributes, options?)Partial<PhyloChartConfig>The config shape; options may fold in buffers / layout / annotations to produce text(id) accessors and per-tip !font styling.
applyFigtreeSettingsLegacy(settings, attributes)Partial<PhyloStyle>Patch for the PhyloScene renderer (what phylon uses).

Both take the attribute catalog (AttributeInfo[]) so a branchColorAttribute naming a non-numeric column is silently dropped instead of producing a broken scheme. ApplyFigtreeSettingsOptions fields: clades, buffers, layout, annotations.

FigTree’s layout-affecting keys (ladderize order, branch transform, tip alignment) are read separately by applyFigtreeLayoutSettings, which returns a PhyloLayoutOptions (ladderize?, transform?, …):

import { applyFigtreeLayoutSettings } from "insomni-phylo";
const { ladderize, transform } = applyFigtreeLayoutSettings(settingsLike);

figtree-text-style.ts translates recognized settings into per-section TextStyleOptions the labels consume — folding in font size, the master appearance.foregroundColour, and per-tip !font annotation overrides.

FunctionSection
figtreeTipLabelStyle(r, buffers, annotations)Tip labels (incl. !font).
figtreeBranchLabelStyle(r, buffers, annotations)Branch labels.
figtreeInternalLabelStyle(r, buffers, annotations)Internal-node labels.
figtreeLabelText(buffers, layout, annotations, displayAttribute, sigDigits)A text(id) accessor resolving displayAttribute (wraps resolveLabelText).

phyloStyleToFigtreeSettings(style) is the inverse of applyFigtreeSettingsLegacy: it produces a Map<string, string> of raw value strings ready for set key=value; emission. Merge this into the parsed document’s figtreeSettings map before calling writeNexus.

import { phyloStyleToFigtreeSettings, writeNexus } from "insomni-phylo";
const patch = phyloStyleToFigtreeSettings(handle.style);
for (const [k, v] of patch) doc.figtreeSettings.set(k, v);
const nexus = writeNexus(doc);

The int-vs-double discipline is enforced here. Numeric keys are emitted with the explicit formatters from the writer module:

HelperEmits
formatJavaInteger(n)Integer.toString — e.g. 12 (range-checked, throws on non-int).
formatJavaDouble(n)Double.toString — e.g. 5.0, 1.0E-7.
encodeFigtreeColor(r, g, b, a?)#rrggbb or #aarrggbb.

phyloStyleToFigtreeSettings chooses formatJavaInteger for keys FigTree casts to int (font sizes, significant digits, …) and formatJavaDouble for keys it casts to double (line widths, …) — matching the cast each FigTree controller performs.

The same int-vs-double concern applies to per-node annotations, not just the FIGTREE block. The AnnotationColumn.rawValues channel keeps the verbatim source slice of every cell ("0.5455417729722768", "{8,8.88E-16,#-26966}"). When writeNewick re-emits annotations it prefers the raw slice, so scientific-notation casing, integer-typed positional list elements, and #-prefixed vs bare-decimal color encodings all survive a round-trip byte-for-byte. The color parser behind extractHilights accepts #rrggbb, #aarrggbb, and Java’s signed-decimal ARGB form (-16776961) that BEAST and FigTree emit for !color / !hilight.