← Files Biological Sequence & Alignment ViewerARCHIVED FILE
src/sequence/sequence-palette.ts
15.9 KB · Sep 30, 2026 · 23:01 UTC
import type { SequenceMoleculeKind } from "../biological-sequence-artifact-classifier";
import type { SequencePaletteId } from "./types";
export type SequencePaletteSwatch = {
backgroundColor: string;
label: string;
residues: string;
textColor?: string;
};
export type SequenceResidueStyle = {
backgroundColor: string;
color: string;
};
type SequenceResidueOptions = {
molecule: SequenceMoleculeKind;
paletteId: SequencePaletteId;
residue: string;
};
export type SequencePalette = {
description: string;
id: SequencePaletteId;
label: string;
moleculeKinds: Array<SequenceMoleculeKind>;
swatches: Array<SequencePaletteSwatch>;
};
const NEUTRAL_COLOR = "#f1f5f9";
const MONOCHROME_STYLE: SequenceResidueStyle = {
backgroundColor: "transparent",
color: "var(--bio-token-text-primary)",
};
const MUTED_NEUTRAL_STYLE: SequenceResidueStyle = {
backgroundColor: "var(--bio-sequence-neutral-surface)",
color: "var(--bio-sequence-neutral-foreground)",
};
const MUTED_NUCLEIC_ACID_STYLES: Record<string, SequenceResidueStyle> = {
A: {
backgroundColor: "var(--bio-sequence-a-surface)",
color: "var(--bio-sequence-a-foreground)",
},
C: {
backgroundColor: "var(--bio-sequence-c-surface)",
color: "var(--bio-sequence-c-foreground)",
},
G: {
backgroundColor: "var(--bio-sequence-g-surface)",
color: "var(--bio-sequence-g-foreground)",
},
T: {
backgroundColor: "var(--bio-sequence-t-surface)",
color: "var(--bio-sequence-t-foreground)",
},
};
const MUTED_AMINO_ACID_SWATCHES = [
{ label: "Hydrophobic", residues: "A F I L M V W", token: "hydrophobic" },
{ label: "Positive", residues: "K R", token: "positive" },
{ label: "Negative", residues: "D E", token: "negative" },
{ label: "Polar", residues: "N Q S T", token: "polar" },
{ label: "Cys", residues: "C", token: "cysteine" },
{ label: "Gly", residues: "G", token: "glycine" },
{ label: "Pro", residues: "P", token: "proline" },
{ label: "Aromatic", residues: "H Y", token: "aromatic" },
].map(({ label, residues, token }) => ({
backgroundColor: `var(--bio-protein-${token}-surface)`,
label,
residues,
textColor: `var(--bio-protein-${token}-foreground)`,
}));
const MUTED_AMINO_ACID_STYLES: Record<string, SequenceResidueStyle> =
Object.fromEntries(
MUTED_AMINO_ACID_SWATCHES.flatMap(({ backgroundColor, residues, textColor }) =>
residues.split(" ").map((residue) => [
residue,
{ backgroundColor, color: textColor },
]),
),
);
const RASMOL_COLORS: Record<string, string> = {
A: "#c8c8c8",
C: "#e6e600",
D: "#e60a0a",
E: "#e60a0a",
F: "#3232aa",
G: "#ebebeb",
H: "#8282d2",
I: "#0f820f",
K: "#145aff",
L: "#0f820f",
M: "#e6e600",
N: "#00dcdc",
P: "#dc9682",
Q: "#00dcdc",
R: "#145aff",
S: "#fa9600",
T: "#fa9600",
V: "#0f820f",
W: "#b45ab4",
Y: "#3232aa",
};
const NCBI_NUCLEIC_ACID_COLORS: Record<string, string> = {
A: "#ff0000",
C: "#ffff00",
G: "#0000ff",
T: "#008000",
U: "#008000",
};
const JALVIEW_NUCLEOTIDE_COLORS: Record<string, string> = {
A: "#64f73f",
C: "#ffb340",
G: "#eb413c",
T: "#3c88ee",
U: "#3c88ee",
};
const PURINE_PYRIMIDINE_COLORS: Record<string, string> = {
A: "#ff83fa",
C: "#40e0d0",
G: "#ff83fa",
R: "#ff83fa",
T: "#40e0d0",
U: "#40e0d0",
Y: "#40e0d0",
};
const NUCLEOTIDE_AMBIGUITY_COLORS: Record<string, string> = {
A: "#f0fff0",
B: "#8b4513",
C: "#f0fff0",
D: "#483d8b",
G: "#f0fff0",
H: "#808080",
I: "#ffffff",
K: "#9932cc",
M: "#9acd32",
N: "#2f4f4f",
R: "#cd5c5c",
S: "#ff8c00",
T: "#f0fff0",
U: "#f0fff0",
V: "#b8860b",
W: "#4682b4",
X: "#4f6f6f",
Y: "#008000",
};
const CLUSTAL_X_COLORS: Record<string, string> = {
A: "#80a0f0",
C: "#f08080",
D: "#c048c0",
E: "#c048c0",
F: "#80a0f0",
G: "#f09048",
H: "#15a4a4",
I: "#80a0f0",
K: "#f01505",
L: "#80a0f0",
M: "#80a0f0",
N: "#15c015",
P: "#c0c000",
Q: "#15c015",
R: "#f01505",
S: "#15c015",
T: "#15c015",
V: "#80a0f0",
W: "#80a0f0",
Y: "#15a4a4",
};
const ZAPPO_COLORS: Record<string, string> = {
A: "#ffafaf",
C: "#ffff00",
D: "#ff0000",
E: "#ff0000",
F: "#ffc800",
G: "#ff00ff",
H: "#6464ff",
I: "#ffafaf",
K: "#6464ff",
L: "#ffafaf",
M: "#ffafaf",
N: "#00ff00",
P: "#ff00ff",
Q: "#00ff00",
R: "#6464ff",
S: "#00ff00",
T: "#00ff00",
V: "#ffafaf",
W: "#ffc800",
Y: "#ffc800",
};
const HYDROPHOBICITY_COLORS: Record<string, string> = {
A: "#ad0052",
B: "#0c00f3",
C: "#c2003d",
D: "#0c00f3",
E: "#0c00f3",
F: "#cb0034",
G: "#6a0095",
H: "#1500ea",
I: "#ff0000",
K: "#0000ff",
L: "#ea0015",
M: "#b0004f",
N: "#0c00f3",
P: "#4600b9",
Q: "#0c00f3",
R: "#0000ff",
S: "#5e00a1",
T: "#61009e",
V: "#f60009",
W: "#5b00a4",
X: "#680097",
Y: "#4f00b0",
Z: "#0c00f3",
};
const PALETTES: Record<SequencePaletteId, SequencePalette> = {
"clustal-x": {
description:
"ClustalX-like amino-acid residue classes using the same canonical hues as the MSA viewer.",
id: "clustal-x",
label: "ClustalX",
moleculeKinds: ["protein"],
swatches: [
{ backgroundColor: CLUSTAL_X_COLORS.A, label: "Hydrophobic", residues: "A F I L M V W" },
{ backgroundColor: CLUSTAL_X_COLORS.K, label: "Positive", residues: "K R" },
{ backgroundColor: CLUSTAL_X_COLORS.D, label: "Negative", residues: "D E" },
{ backgroundColor: CLUSTAL_X_COLORS.N, label: "Polar", residues: "N Q S T" },
{ backgroundColor: CLUSTAL_X_COLORS.C, label: "Cys", residues: "C" },
{ backgroundColor: CLUSTAL_X_COLORS.G, label: "Gly", residues: "G" },
{ backgroundColor: CLUSTAL_X_COLORS.P, label: "Pro", residues: "P" },
{ backgroundColor: CLUSTAL_X_COLORS.H, label: "Aromatic", residues: "H Y" },
],
},
hydrophobicity: {
description:
"Hydrophobicity gradient from hydrophilic blue through intermediate purple to hydrophobic red.",
id: "hydrophobicity",
label: "Hydrophobicity",
moleculeKinds: ["protein"],
swatches: [
{ backgroundColor: HYDROPHOBICITY_COLORS.K, label: "Hydrophilic", residues: "K R D E N Q" },
{ backgroundColor: HYDROPHOBICITY_COLORS.G, label: "Intermediate", residues: "G P S T W Y" },
{ backgroundColor: HYDROPHOBICITY_COLORS.I, label: "Hydrophobic", residues: "I L V F A C M" },
],
},
"jalview-nucleotide": {
description: "Jalview nucleotide base colors with T/U sharing the blue pyrimidine color.",
id: "jalview-nucleotide",
label: "Jalview nucleotide",
moleculeKinds: ["dna", "nucleic-acid-ambiguous", "rna"],
swatches: [
{ backgroundColor: JALVIEW_NUCLEOTIDE_COLORS.A, label: "A", residues: "A" },
{ backgroundColor: JALVIEW_NUCLEOTIDE_COLORS.C, label: "C", residues: "C" },
{ backgroundColor: JALVIEW_NUCLEOTIDE_COLORS.G, label: "G", residues: "G" },
{ backgroundColor: JALVIEW_NUCLEOTIDE_COLORS.T, label: "T/U", residues: "T U" },
],
},
"muted-amino-acid": {
description:
"Soft, theme-aware, fixed ClustalX-inspired amino-acid groups, not alignment-conservation coloring. Ambiguous and rare residues, stops and gaps remain neutral.",
id: "muted-amino-acid",
label: "Soft amino acid",
moleculeKinds: ["protein"],
swatches: [
...MUTED_AMINO_ACID_SWATCHES,
{
backgroundColor: MUTED_NEUTRAL_STYLE.backgroundColor,
label: "X / * / -",
residues: "Ambiguous and rare residues, stops and gaps",
textColor: MUTED_NEUTRAL_STYLE.color,
},
],
},
"muted-nucleic-acid": {
description:
"Soft, theme-aware base colors: rose A, amber C, blue G and sage T/U. Ambiguity symbols and gaps remain neutral.",
id: "muted-nucleic-acid",
label: "Soft nucleotide",
moleculeKinds: ["dna", "nucleic-acid-ambiguous", "rna"],
swatches: [
...["A", "C", "G", "T"].map((residue) => ({
backgroundColor: MUTED_NUCLEIC_ACID_STYLES[residue].backgroundColor,
label: residue === "T" ? "T/U" : residue,
residues: residue === "T" ? "T U" : residue,
textColor: MUTED_NUCLEIC_ACID_STYLES[residue].color,
})),
{
backgroundColor: MUTED_NEUTRAL_STYLE.backgroundColor,
label: "N / -",
residues: "Ambiguity symbols and gaps",
textColor: MUTED_NEUTRAL_STYLE.color,
},
],
},
"ncbi-nucleic-acid": {
description:
"NCBI-style per-base residue colors with T/U sharing the pyrimidine color.",
id: "ncbi-nucleic-acid",
label: "NCBI nucleic acid",
moleculeKinds: ["dna", "nucleic-acid-ambiguous", "rna"],
swatches: [
{ backgroundColor: NCBI_NUCLEIC_ACID_COLORS.A, label: "A", residues: "A" },
{ backgroundColor: NCBI_NUCLEIC_ACID_COLORS.C, label: "C", residues: "C" },
{ backgroundColor: NCBI_NUCLEIC_ACID_COLORS.G, label: "G", residues: "G" },
{
backgroundColor: NCBI_NUCLEIC_ACID_COLORS.T,
label: "T/U",
residues: "T U",
},
],
},
neutral: {
description: "Theme-aware monochrome lettering without residue-specific color.",
id: "neutral",
label: "Monochrome",
moleculeKinds: ["dna", "nucleic-acid-ambiguous", "protein", "rna", "unknown"],
swatches: [
{
backgroundColor: MONOCHROME_STYLE.backgroundColor,
label: "Residue",
residues: "Any",
textColor: MONOCHROME_STYLE.color,
},
],
},
"nucleotide-ambiguity": {
description:
"IUPAC-aware nucleotide colors that make ambiguity symbols visually explicit.",
id: "nucleotide-ambiguity",
label: "Nucleotide ambiguity",
moleculeKinds: ["dna", "nucleic-acid-ambiguous", "rna"],
swatches: [
{ backgroundColor: NUCLEOTIDE_AMBIGUITY_COLORS.R, label: "R", residues: "R = A/G" },
{ backgroundColor: NUCLEOTIDE_AMBIGUITY_COLORS.Y, label: "Y", residues: "Y = C/T/U" },
{ backgroundColor: NUCLEOTIDE_AMBIGUITY_COLORS.W, label: "W", residues: "W = A/T/U" },
{ backgroundColor: NUCLEOTIDE_AMBIGUITY_COLORS.S, label: "S", residues: "S = C/G" },
{ backgroundColor: NUCLEOTIDE_AMBIGUITY_COLORS.N, label: "N", residues: "N = any" },
],
},
"purine-pyrimidine": {
description: "Groups purines A/G/R and pyrimidines C/T/U/Y.",
id: "purine-pyrimidine",
label: "Purine / Pyrimidine",
moleculeKinds: ["dna", "nucleic-acid-ambiguous", "rna"],
swatches: [
{
backgroundColor: PURINE_PYRIMIDINE_COLORS.A,
label: "Purine",
residues: "A G R",
},
{
backgroundColor: PURINE_PYRIMIDINE_COLORS.C,
label: "Pyrimidine",
residues: "C T U Y",
},
],
},
rasmol: {
description: "Traditional RasMol amino-acid residue colors.",
id: "rasmol",
label: "RasMol",
moleculeKinds: ["protein"],
swatches: [
{ backgroundColor: RASMOL_COLORS.K, label: "Basic", residues: "K R" },
{ backgroundColor: RASMOL_COLORS.D, label: "Acidic", residues: "D E" },
{ backgroundColor: RASMOL_COLORS.I, label: "Hydrophobic", residues: "I L V" },
{ backgroundColor: RASMOL_COLORS.F, label: "Aromatic", residues: "F Y" },
{ backgroundColor: RASMOL_COLORS.C, label: "Sulfur", residues: "C M" },
{ backgroundColor: RASMOL_COLORS.N, label: "Amide", residues: "N Q" },
{ backgroundColor: RASMOL_COLORS.S, label: "Hydroxyl", residues: "S T" },
{ backgroundColor: RASMOL_COLORS.G, label: "Small", residues: "A G P" },
],
},
zappo: {
description: "Zappo physicochemical amino-acid residue groups.",
id: "zappo",
label: "Zappo",
moleculeKinds: ["protein"],
swatches: [
{ backgroundColor: ZAPPO_COLORS.A, label: "Aliphatic", residues: "A I L M V" },
{ backgroundColor: ZAPPO_COLORS.F, label: "Aromatic", residues: "F W Y" },
{ backgroundColor: ZAPPO_COLORS.N, label: "Hydrophilic", residues: "N Q S T" },
{ backgroundColor: ZAPPO_COLORS.D, label: "Negative", residues: "D E" },
{ backgroundColor: ZAPPO_COLORS.K, label: "Positive", residues: "H K R" },
{ backgroundColor: ZAPPO_COLORS.G, label: "Special", residues: "G P" },
{ backgroundColor: ZAPPO_COLORS.C, label: "Cys", residues: "C" },
],
},
};
const PROTEIN_PALETTE_IDS: Array<SequencePaletteId> = [
"muted-amino-acid",
"neutral",
"rasmol",
"clustal-x",
"zappo",
"hydrophobicity",
];
const NUCLEIC_ACID_PALETTE_IDS: Array<SequencePaletteId> = [
"muted-nucleic-acid",
"neutral",
"ncbi-nucleic-acid",
"jalview-nucleotide",
"purine-pyrimidine",
"nucleotide-ambiguity",
];
export function getDefaultSequencePalette(
molecule: SequenceMoleculeKind,
): SequencePaletteId {
if (molecule === "protein") {
return "muted-amino-acid";
}
if (
molecule === "dna" ||
molecule === "rna" ||
molecule === "nucleic-acid-ambiguous"
) {
return "muted-nucleic-acid";
}
return "neutral";
}
export function getCompatibleSequencePalettes(
molecule: SequenceMoleculeKind,
): Array<SequencePalette> {
if (molecule === "protein") {
return PROTEIN_PALETTE_IDS.map((paletteId) => PALETTES[paletteId]);
}
if (
molecule === "dna" ||
molecule === "rna" ||
molecule === "nucleic-acid-ambiguous"
) {
return NUCLEIC_ACID_PALETTE_IDS.map((paletteId) => PALETTES[paletteId]);
}
return [PALETTES.neutral];
}
export function getSequencePalette(
paletteId: SequencePaletteId,
): SequencePalette {
return PALETTES[paletteId];
}
/** Theme-aware palettes must supply both colors; contrast cannot be inferred from a CSS token. */
export function getSequenceResidueStyle(
options: SequenceResidueOptions,
): SequenceResidueStyle {
if (options.paletteId === "neutral") {
return MONOCHROME_STYLE;
}
if (options.paletteId === "muted-amino-acid" && options.molecule === "protein") {
return (
MUTED_AMINO_ACID_STYLES[options.residue.toUpperCase()] ?? MUTED_NEUTRAL_STYLE
);
}
if (
options.paletteId === "muted-nucleic-acid" &&
(options.molecule === "dna" ||
options.molecule === "rna" ||
options.molecule === "nucleic-acid-ambiguous")
) {
const residue = options.residue.toUpperCase();
return (
MUTED_NUCLEIC_ACID_STYLES[residue === "U" ? "T" : residue] ??
MUTED_NEUTRAL_STYLE
);
}
const backgroundColor = getLegacySequenceResidueColor(options);
return {
backgroundColor,
color: getReadableResidueTextColor(backgroundColor),
};
}
export function getSequenceResidueColor(
options: SequenceResidueOptions,
): string {
return getSequenceResidueStyle(options).backgroundColor;
}
function getLegacySequenceResidueColor({
molecule,
paletteId,
residue,
}: SequenceResidueOptions): string {
const normalizedResidue = residue.toUpperCase();
if (paletteId === "rasmol" && molecule === "protein") {
return RASMOL_COLORS[normalizedResidue] ?? NEUTRAL_COLOR;
}
if (paletteId === "clustal-x" && molecule === "protein") {
return CLUSTAL_X_COLORS[normalizedResidue] ?? NEUTRAL_COLOR;
}
if (paletteId === "zappo" && molecule === "protein") {
return ZAPPO_COLORS[normalizedResidue] ?? NEUTRAL_COLOR;
}
if (paletteId === "hydrophobicity" && molecule === "protein") {
return HYDROPHOBICITY_COLORS[normalizedResidue] ?? NEUTRAL_COLOR;
}
if (paletteId === "jalview-nucleotide") {
return JALVIEW_NUCLEOTIDE_COLORS[normalizedResidue] ?? NEUTRAL_COLOR;
}
if (paletteId === "nucleotide-ambiguity") {
return NUCLEOTIDE_AMBIGUITY_COLORS[normalizedResidue] ?? NEUTRAL_COLOR;
}
if (paletteId === "purine-pyrimidine") {
return PURINE_PYRIMIDINE_COLORS[normalizedResidue] ?? NEUTRAL_COLOR;
}
if (paletteId === "ncbi-nucleic-acid") {
return NCBI_NUCLEIC_ACID_COLORS[normalizedResidue] ?? NEUTRAL_COLOR;
}
return NEUTRAL_COLOR;
}
export function getReadableResidueTextColor(backgroundColor: string): string {
const hex = backgroundColor.replace("#", "");
if (hex.length !== 6) {
return "#111827";
}
const red = Number.parseInt(hex.slice(0, 2), 16);
const green = Number.parseInt(hex.slice(2, 4), 16);
const blue = Number.parseInt(hex.slice(4, 6), 16);
const luminance = 0.2126 * red + 0.7152 * green + 0.0722 * blue;
return luminance < 145 ? "#ffffff" : "#111827";
}
SHA-256: fc88a257e64853b319b7d86f62311a8b936b91f7d10d8667856a1d2228d76086