canloc / static /script.js
Biocoder09's picture
Update static/script.js
ebab832 verified
raw
history blame
12.2 kB
let barChart = null;
let radarChart = null;
let bigBarChartObj = null;
let bigRadarChartObj = null;
let fastaResults = [];
const API_BASE = ""; // same origin
//------------------ Safe Fetch ------------------
async function safeFetchJSON(url, options = {}) {
const res = await fetch(url, options);
const text = await res.text();
let data;
try {
data = JSON.parse(text);
} catch {
throw new Error(text || `HTTP ${res.status}`);
}
if (!res.ok) {
throw new Error(data.error || `HTTP ${res.status}`);
}
return data;
}
// ------------------ INIT ------------------
document.addEventListener("DOMContentLoaded", () => {
// Tabs
const tabSeq = document.getElementById("tab-seq");
const tabFasta = document.getElementById("tab-fasta");
const panelSeq = document.getElementById("panel-seq");
const panelFasta = document.getElementById("panel-fasta");
const downloadBtn = document.getElementById("downloadCsvBtn");
if (downloadBtn) {
downloadBtn.addEventListener("click", downloadCSV);
}
if (tabSeq && tabFasta && panelSeq && panelFasta) {
tabSeq.addEventListener("click", () => {
tabSeq.classList.add("active");
tabFasta.classList.remove("active");
panelSeq.classList.remove("hidden");
panelFasta.classList.add("hidden");
});
tabFasta.addEventListener("click", () => {
tabFasta.classList.add("active");
tabSeq.classList.remove("active");
panelFasta.classList.remove("hidden");
panelSeq.classList.add("hidden");
});
}
// Buttons
const predictBtn = document.getElementById("predictBtn");
if (predictBtn) predictBtn.addEventListener("click", predictSequence);
const predictFastaBtn = document.getElementById("predictFastaBtn");
if (predictFastaBtn) predictFastaBtn.addEventListener("click", predictFasta);
// FASTA modal close events
const fastaBackdrop = document.getElementById("fastaModalBackdrop");
const fastaClose = document.getElementById("fastaModalClose");
if (fastaBackdrop) fastaBackdrop.addEventListener("click", closeFastaModal);
if (fastaClose) fastaClose.addEventListener("click", closeFastaModal);
// Big chart click
const barCanvas = document.getElementById("barChart");
const radarCanvas = document.getElementById("radarChart");
if (barCanvas) barCanvas.addEventListener("click", openBigBar);
if (radarCanvas) radarCanvas.addEventListener("click", openBigRadar);
});
// ------------------ HELPERS ------------------
function validateSequence(seq) {
const s = seq.trim().toUpperCase();
if (!s) return [false, "Sequence is empty."];
const validAA = /^[ACDEFGHIKLMNPQRSTVWYUBZX*]+$/;
if (!validAA.test(s)) return [false, "Invalid characters in sequence."];
if (s.length < 15) return [false, "Sequence too short (min 15 AA)."];
return [true, null];
}
// Capitalize class names
function pretty(name) {
return name.charAt(0).toUpperCase() + name.slice(1);
}
// Probability severity
function getConfidenceMeta(maxProb) {
if (maxProb >= 0.75) return { color: "#4ade80", text: "High confidence" };
if (maxProb >= 0.6) return { color: "#facc15", text: "Medium confidence" };
return { color: "#f87171", text: "Low confidence – interpret cautiously" };
}
// Bar chart colors
function buildBarColors(values) {
const max = Math.max(...values);
return values.map(v =>
v === max ? "rgba(45,212,191,0.95)" : "rgba(166,184,184,0.9)"
);
}
// Probability list builder
function updateProbList(container, probs) {
if (!container) return;
container.innerHTML = "";
Object.entries(probs).forEach(([k, v]) => {
const div = document.createElement("div");
div.className = "prob-item";
const left = document.createElement("span");
left.textContent = pretty(k); // CAPITALIZED
const right = document.createElement("span");
right.textContent = (v * 100).toFixed(2) + "%"; // PERCENT
div.appendChild(left);
div.appendChild(right);
container.appendChild(div);
});
}
// ------------------ CHART DRAWING ------------------
function drawCharts(classLabels, rawValues) {
const barCanvas = document.getElementById("barChart");
const radarCanvas = document.getElementById("radarChart");
if (!barCanvas || !radarCanvas) return;
const labels = classLabels.map(pretty); // CAPITALIZE
const values = rawValues;
if (barChart) barChart.destroy();
if (radarChart) radarChart.destroy();
// Bar Chart
barChart = new Chart(barCanvas.getContext("2d"), {
type: "bar",
data: {
labels,
datasets: [
{
data: values,
backgroundColor: buildBarColors(values),
borderRadius: 6
}
]
},
options: {
responsive: true,
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
label: ctx => (ctx.parsed.y * 100).toFixed(2) + "%" // PERCENT
}
}
},
scales: {
y: { beginAtZero: true, max: 1 },
x: { ticks: { color: "#e5e7eb" } }
}
}
});
// Radar Chart
radarChart = new Chart(radarCanvas.getContext("2d"), {
type: "radar",
data: {
labels,
datasets: [
{
data: values,
backgroundColor: "rgba(45,212,191,0.18)",
borderColor: "rgba(45,212,191,0.9)",
borderWidth: 2
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
label: ctx => (ctx.parsed.r * 100).toFixed(2) + "%" // PERCENT
}
}
},
scales: {
r: {
beginAtZero: true,
max: 1,
grid: { color: "rgba(55,65,81,0.4)" },
angleLines: { color: "rgba(55,65,81,0.4)" },
pointLabels: { color: "#e5e7eb", font: { size: 16 } },
ticks: { display: false }
}
}
}
});
}
// ------------------ SINGLE SEQUENCE ------------------
async function predictSequence() {
const seqInput = document.getElementById("sequenceInput");
const seq = seqInput.value.trim();
const [ok, err] = validateSequence(seq);
if (!ok) return alert(err);
const loading = document.getElementById("loadingSeq");
const resultCard = document.getElementById("seqResultCard");
const resultHeader = document.getElementById("seqResultHeader");
const warningEl = document.getElementById("seqConfidenceWarning");
const probList = document.getElementById("seqProbList");
loading.classList.remove("hidden");
resultCard.classList.add("hidden");
try {
const res = await fetch(`${API_BASE}/api/predict_sequence`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ sequence: seq })
});
const data = await res.json();
loading.classList.add("hidden");
if (!res.ok || data.error) return alert(data.error || "Prediction failed.");
const label = pretty(data.prediction_label); // CAPITALIZED
const probs = data.probabilities || {};
const labels = Object.keys(probs);
const values = labels.map(k => probs[k]);
const maxProb = Math.max(...values);
const meta = getConfidenceMeta(maxProb);
resultHeader.innerHTML = `<span style="color:${meta.color}">Predicted Location: ${label}</span>`;
warningEl.textContent = meta.text;
warningEl.style.color = meta.color;
updateProbList(probList, probs);
drawCharts(labels, values);
resultCard.classList.remove("hidden");
} catch (e) {
loading.classList.add("hidden");
alert("Server error: " + e);
}
}
// ------------------ FASTA ------------------
async function predictFasta() {
const fileInput = document.getElementById("fastaFile");
const file = fileInput.files[0];
if (!file) return alert("Choose a FASTA file.");
const loading = document.getElementById("loadingFasta");
const wrapper = document.getElementById("fastaResultsWrapper");
const tbody = document.getElementById("fastaTableBody");
loading.classList.remove("hidden");
wrapper.classList.add("hidden");
tbody.innerHTML = "";
fastaResults = [];
try {
const form = new FormData();
form.append("file", file);
const res = await fetch(`${API_BASE}/api/predict_fasta`, {
method: "POST",
body: form
});
const data = await res.json();
loading.classList.add("hidden");
if (!res.ok || data.error) return alert(data.error || "FASTA read error.");
fastaResults = data.results;
renderFastaTable();
wrapper.classList.remove("hidden");
} catch (e) {
loading.classList.add("hidden");
alert("Server error: " + e);
}
}
function renderFastaTable() {
const tbody = document.getElementById("fastaTableBody");
tbody.innerHTML = "";
fastaResults.forEach((r, idx) => {
const probs = r.probabilities || {};
const maxProb = Math.max(...Object.values(probs));
const tr = document.createElement("tr");
tr.dataset.index = idx;
tr.innerHTML = `
<td>${idx + 1}</td>
<td>${r.sequence}</td>
<td>${r.length}</td>
<td>${pretty(r.prediction_label)}</td>
<td>${(maxProb * 100).toFixed(2)}%</td>
`;
tr.addEventListener("click", () => openFastaModal(idx));
tbody.appendChild(tr);
});
}
// FASTA modal (no charts)
function openFastaModal(i) {
const r = fastaResults[i];
if (!r) return;
document.getElementById("fastaModalTitle").textContent =
`Sequence: ${r.sequence}`;
document.getElementById("fastaModalMeta").textContent =
`Length: ${r.length} | Predicted: ${pretty(r.prediction_label)}`;
updateProbList(document.getElementById("fastaProbList"), r.probabilities);
document.getElementById("fastaModal").classList.remove("hidden");
}
function closeFastaModal() {
document.getElementById("fastaModal").classList.add("hidden");
}
// ------------------ BIG CHART MODALS ------------------
function openBigBar() {
if (!barChart) return;
const modal = document.getElementById("bigBarModal");
const ctx = document.getElementById("bigBarChart").getContext("2d");
if (bigBarChartObj) bigBarChartObj.destroy();
bigBarChartObj = new Chart(ctx, {
type: barChart.config.type,
data: barChart.config.data,
options: {
responsive: false,
animation: false,
scales: {
y: { beginAtZero: true, max: 1 },
x: { ticks: { color: "#e5e7eb" } }
}
}
});
modal.classList.remove("hidden");
}
function closeBigBar() {
document.getElementById("bigBarModal").classList.add("hidden");
}
function openBigRadar() {
if (!radarChart) return;
const modal = document.getElementById("bigRadarModal");
const ctx = document.getElementById("bigRadarChart").getContext("2d");
if (bigRadarChartObj) bigRadarChartObj.destroy();
bigRadarChartObj = new Chart(ctx, {
type: radarChart.config.type,
data: radarChart.config.data,
options: {
responsive: false,
animation: false,
scales: {
r: {
beginAtZero: true,
max: 1,
grid: { color: "rgba(55,65,81,0.4)" },
angleLines: { color: "rgba(55,65,81,0.4)" },
pointLabels: { color: "#e4e4e4ff", font: { size: 18 } },
ticks: { display: false }
}
}
}
});
modal.classList.remove("hidden");
}
function closeBigRadar() {
document.getElementById("bigRadarModal").classList.add("hidden");
}
// DOWNLOAD CSV
function downloadCSV() {
if (!fastaResults || fastaResults.length === 0) {
alert("No batch results available to download.");
return;
}
fetch("/api/download_csv", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(fastaResults)
})
.then(res => {
if (!res.ok) throw new Error("Failed to generate CSV");
return res.blob();
})
.then(blob => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "canloc_results.csv";
document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
})
.catch(err => {
alert("Download error: " + err.message);
});
}