← Files Biological Sequence & Alignment ViewerARCHIVED FILE
src/msa/exports.ts
2.79 KB · Sep 30, 2026 · 23:01 UTC
import { neutralizeSpreadsheetFormula } from "../spreadsheet-safety";
import type { MsaMotifSearchHit } from "./search";
import type { MsaSequenceRow } from "./types";
export function buildAlignedFasta(rows: Array<MsaSequenceRow>): string {
return rows.map((row) => `>${row.label}\n${row.alignedSequence}`).join("\n");
}
export function buildReferenceFasta({
label,
sequence,
}: {
label: string;
sequence: string;
}): string {
return `>${label}\n${sequence}`;
}
export function buildVisibleRangeText({
endColumn,
rows,
startColumn,
}: {
endColumn: number;
rows: Array<MsaSequenceRow>;
startColumn: number;
}): string {
return rows
.map(
(row) =>
`${escapeSpreadsheetTsvLabel(row.label)}\t${row.alignedSequence.slice(startColumn, endColumn)}`,
)
.join("\n");
}
export function buildSearchHitsTsv(hits: Array<MsaMotifSearchHit>): string {
const header = [
"row_label",
"orientation",
"alignment_start",
"alignment_end",
"ungapped_start",
"ungapped_end",
].join("\t");
const rows = hits.map((hit) =>
[
escapeSpreadsheetTsvLabel(hit.rowLabel),
hit.orientation,
hit.alignmentStartColumn + 1,
hit.alignmentEndColumn + 1,
hit.ungappedStartPosition,
hit.ungappedEndPosition,
].join("\t"),
);
return [header, ...rows].join("\n");
}
function escapeSpreadsheetTsvLabel(value: string): string {
return neutralizeSpreadsheetFormula(value).replace(/\r\n|[\r\n\t]/gu, " ");
}
export function buildVisibleRangeSvg({
endColumn,
rows,
startColumn,
}: {
endColumn: number;
rows: Array<MsaSequenceRow>;
startColumn: number;
}): string {
const rowHeight = 18;
const labelWidth = 180;
const cellWidth = 11;
const fontSize = 11;
const visibleColumnCount = Math.max(0, endColumn - startColumn);
const width = labelWidth + visibleColumnCount * cellWidth + 16;
const height = Math.max(rowHeight, rows.length * rowHeight + 18);
const body = rows
.map((row, rowIndex) => {
const y = 18 + rowIndex * rowHeight;
const sequence = row.alignedSequence.slice(startColumn, endColumn);
return [
`<text x="8" y="${y}" font-family="monospace" font-size="${fontSize}">${escapeXml(row.label)}</text>`,
`<text x="${labelWidth}" y="${y}" font-family="monospace" font-size="${fontSize}">${escapeXml(sequence)}</text>`,
].join("");
})
.join("");
return [
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">`,
'<rect width="100%" height="100%" fill="white"/>',
body,
"</svg>",
].join("");
}
function escapeXml(value: string): string {
return value
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
SHA-256: 6d0d1d7f1b42d724a3baa10a2cdcaad2451dfbfebe96f697c33f31d7d4785d9c