/* ============================================================ stats.js — Statistics 탭 (논문 + 단백질 카탈로그 통계) ============================================================ */ (function () { 'use strict'; let charts = {}, rendered = false; const C = ['#2f63c8', '#7c3aed', '#0e7490', '#1f8a5b', '#b07a12', '#c8401f', '#c2367f', '#c0561f', '#5b6470', '#9333ea']; function count(arr, key) { const m = {}; arr.forEach(x => { const k = (typeof key === 'function' ? key(x) : x[key]) || '기타'; m[k] = (m[k] || 0) + 1; }); return m; } function sorted(obj, n) { return Object.entries(obj).sort((a, b) => b[1] - a[1]).slice(0, n || 99); } function doughnut(id, obj, n) { const e = sorted(obj, n); mk(id, 'doughnut', { labels: e.map(x => x[0]), datasets: [{ data: e.map(x => x[1]), backgroundColor: C, borderColor: '#fbf9f4', borderWidth: 2 }] }, { plugins: { legend: { position: 'right', labels: { color: '#4a463d', font: { size: 11 }, boxWidth: 12 } } } }); } function bar(id, obj, n, horizontal) { const e = sorted(obj, n); mk(id, 'bar', { labels: e.map(x => x[0]), datasets: [{ data: e.map(x => x[1]), backgroundColor: C[0], borderRadius: 4 }] }, { indexAxis: horizontal ? 'y' : 'x', plugins: { legend: { display: false } }, scales: { x: { ticks: { color: '#6b655a', font: { size: 10 } }, grid: { color: 'rgba(0,0,0,.08)' } }, y: { ticks: { color: '#6b655a', font: { size: 10 } }, grid: { display: false } } } }); } function line(id, obj) { const e = Object.entries(obj).filter(([y]) => /^\d{4}$/.test(y)).sort(); mk(id, 'line', { labels: e.map(x => x[0]), datasets: [{ data: e.map(x => x[1]), borderColor: '#22d3ee', backgroundColor: 'rgba(34,211,238,.12)', fill: true, tension: .3, pointRadius: 2 }] }, { plugins: { legend: { display: false } }, scales: { x: { ticks: { color: '#6b655a' }, grid: { display: false } }, y: { ticks: { color: '#6b655a' }, grid: { color: 'rgba(0,0,0,.08)' } } } }); } function mk(id, type, data, opts) { const ctx = document.getElementById(id); if (!ctx) return; if (charts[id]) charts[id].destroy(); charts[id] = new Chart(ctx, { type, data, options: Object.assign({ responsive: true, maintainAspectRatio: false }, opts) }); } function render() { if (rendered) return; rendered = true; const papers = (Store.analysis && Store.analysis.papers) || []; const proteins = Store.catalog.proteins; doughnut('chart-disease', count(papers, 'disease'), 6); bar('chart-axis', count(proteins.filter(p => p.twin_node && p.twin_node !== '—'), 'twin_node'), 8, true); line('chart-trend', count(papers, 'pubYear')); bar('chart-pathway', count(proteins, 'pathway'), 10, true); doughnut('chart-modality', count(papers, p => p.modality), 6); } window.StatsTab = { render }; })();