"use client"; import { useRef } from "react"; import { exportSvgPng } from "@/lib/export-utils"; import { groupOf } from "@/lib/alignment-stats"; function columnConservation(column: string[]): number { const filtered = column.filter(c => c !== "-" && c !== "."); if (!filtered.length) return 0; const freqs = new Map(); for (const aa of filtered) freqs.set(aa, (freqs.get(aa) ?? 0) + 1); const maxFreq = Math.max(...Array.from(freqs.values())); const identityScore = maxFreq / filtered.length; const groups = new Set(filtered.map(groupOf)); const groupScore = Array.from(groups).length === 1 ? 0.15 : 0; return Math.min(1, identityScore + groupScore); } interface Props { alignedSeqs: string[]; } export function ConservationTrack({ alignedSeqs }: Props) { const svgRef = useRef(null); if (!alignedSeqs.length) return null; const L = alignedSeqs[0].length; const scores: number[] = []; for (let i = 0; i < L; i++) { scores.push(columnConservation(alignedSeqs.map(s => s[i] ?? "-"))); } const scoreColor = (s: number) => { if (s > 0.9) return "#4ADE80"; if (s > 0.7) return "#FBBF24"; if (s > 0.4) return "#7C3AED"; return "rgba(255,255,255,0.1)"; }; const avgConservation = (scores.reduce((a, b) => a + b, 0) / scores.length * 100).toFixed(1); const highlyConserved = scores.filter(s => s > 0.9).length; return (

Conservation Analysis

Avg: {avgConservation}% Fully conserved: {highlyConserved} positions
{scores.map((s, i) => ( ))}
1 {Math.floor(L / 2)} {L}
{[ { color: "#4ADE80", label: ">90% conserved" }, { color: "#FBBF24", label: ">70% conserved" }, { color: "#7C3AED", label: ">40% conserved" }, { color: "rgba(255,255,255,0.2)", label: "Variable" }, ].map(({ color, label }) => ( {label} ))}
); }