Commit ·
aee68fd
1
Parent(s): 182233e
Update dataset statistics to MuSProt conformational metrics
Browse filesCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
frontend/src/components/datasets/protein/ProteinDashboard.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
-
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
| 2 |
-
import { FilterPanel } from './FilterPanel';
|
| 3 |
import { StaticDistributionPlots } from './StaticDistributionPlots';
|
| 4 |
import { DataTable } from './DataTable';
|
| 5 |
import { StatsPanel } from './StatsPanel';
|
|
@@ -10,7 +10,7 @@ import BindersPanel from './BindersPanel';
|
|
| 10 |
import FunctionPanel from './FunctionPanel';
|
| 11 |
import ErrorBoundary from './ErrorBoundary';
|
| 12 |
import { proteinApi } from '../../../api/protein';
|
| 13 |
-
import type { FilterParams, FilterOptions, DataResponse,
|
| 14 |
import { PageSEO } from '../../seo/PageSEO';
|
| 15 |
import { MuSProtJsonLd } from '../../seo/MuSProtJsonLd';
|
| 16 |
|
|
@@ -23,13 +23,12 @@ interface CacheEntry {
|
|
| 23 |
|
| 24 |
const NEGATIVE_CACHE_TTL = 10 * 60 * 1000; // 10 minutes
|
| 25 |
|
| 26 |
-
export function ProteinDashboard() {
|
| 27 |
// State management
|
| 28 |
const [filterOptions, setFilterOptions] = useState<FilterOptions | null>(null);
|
| 29 |
const [filters, setFilters] = useState<FilterParams>({});
|
| 30 |
const [dataResponse, setDataResponse] = useState<DataResponse | null>(null);
|
| 31 |
-
|
| 32 |
-
|
| 33 |
// Search input state (controlled)
|
| 34 |
const [searchInputValue, setSearchInputValue] = useState('');
|
| 35 |
const [searchStatus, setSearchStatus] = useState<'idle' | 'typing' | 'loading' | 'success' | 'not_found' | 'error'>('idle');
|
|
@@ -45,8 +44,7 @@ export function ProteinDashboard() {
|
|
| 45 |
|
| 46 |
const [, setLoadingFilters] = useState(true);
|
| 47 |
const [loadingData, setLoadingData] = useState(false);
|
| 48 |
-
|
| 49 |
-
|
| 50 |
const [error, setError] = useState<string | null>(null);
|
| 51 |
|
| 52 |
// Load filter options on mount
|
|
@@ -57,7 +55,7 @@ export function ProteinDashboard() {
|
|
| 57 |
const options = await proteinApi.getFilters();
|
| 58 |
setFilterOptions(options);
|
| 59 |
} catch (err) {
|
| 60 |
-
setError('Failed to load filter options. The MuSProt database may still be initializing.');
|
| 61 |
console.error('Error loading filters:', err);
|
| 62 |
} finally {
|
| 63 |
setLoadingFilters(false);
|
|
@@ -81,29 +79,16 @@ export function ProteinDashboard() {
|
|
| 81 |
}
|
| 82 |
}, [filters]);
|
| 83 |
|
| 84 |
-
const fetchStats = useCallback(async () => {
|
| 85 |
-
try {
|
| 86 |
-
setLoadingStats(true);
|
| 87 |
-
const stats = await proteinApi.getSummary(filters);
|
| 88 |
-
setSummaryStats(stats);
|
| 89 |
-
} catch (err) {
|
| 90 |
-
console.error('Error fetching stats:', err);
|
| 91 |
-
} finally {
|
| 92 |
-
setLoadingStats(false);
|
| 93 |
-
}
|
| 94 |
-
}, [filters]);
|
| 95 |
-
|
| 96 |
// Debounced data fetching — skip fetchData when there's no active chain search
|
| 97 |
useEffect(() => {
|
| 98 |
const timeoutId = setTimeout(() => {
|
| 99 |
if (filters.pdb_id && filters.auth_asym_id) {
|
| 100 |
fetchData();
|
| 101 |
}
|
| 102 |
-
fetchStats();
|
| 103 |
}, 500);
|
| 104 |
|
| 105 |
return () => clearTimeout(timeoutId);
|
| 106 |
-
}, [fetchData,
|
| 107 |
|
| 108 |
const handleFiltersChange = (newFilters: FilterParams) => {
|
| 109 |
setFilters(newFilters);
|
|
@@ -271,13 +256,13 @@ export function ProteinDashboard() {
|
|
| 271 |
return (
|
| 272 |
<div className="protein-dashboard min-h-screen bg-white">
|
| 273 |
<PageSEO
|
| 274 |
-
title="MuSProt: Multistate Protein Structure Database"
|
| 275 |
description="Explore 355,731 protein chains from 121,584 PDB entries with pairwise structural comparisons, energy scoring, and CATH functional annotations. Download the full SQLite database."
|
| 276 |
-
path="/"
|
| 277 |
-
/>
|
| 278 |
-
<MuSProtJsonLd />
|
| 279 |
-
|
| 280 |
-
{error && (
|
| 281 |
<div className="max-w-7xl mx-auto px-6 mt-4">
|
| 282 |
<div className="protein-error-banner">
|
| 283 |
<span>⚠️ {error}</span>
|
|
@@ -366,7 +351,7 @@ export function ProteinDashboard() {
|
|
| 366 |
|
| 367 |
{/* Default Dataset Summary View */}
|
| 368 |
<section className="border border-gray-200 rounded-2xl p-6 bg-white shadow-sm hover:border-slate-900 hover:shadow-lg hover:-translate-y-0.5 transition-all duration-300">
|
| 369 |
-
<StatsPanel
|
| 370 |
</section>
|
| 371 |
|
| 372 |
<section>
|
|
|
|
| 1 |
+
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
| 2 |
+
import { FilterPanel } from './FilterPanel';
|
| 3 |
import { StaticDistributionPlots } from './StaticDistributionPlots';
|
| 4 |
import { DataTable } from './DataTable';
|
| 5 |
import { StatsPanel } from './StatsPanel';
|
|
|
|
| 10 |
import FunctionPanel from './FunctionPanel';
|
| 11 |
import ErrorBoundary from './ErrorBoundary';
|
| 12 |
import { proteinApi } from '../../../api/protein';
|
| 13 |
+
import type { FilterParams, FilterOptions, DataResponse, ChainMetadata } from '../../../types/protein';
|
| 14 |
import { PageSEO } from '../../seo/PageSEO';
|
| 15 |
import { MuSProtJsonLd } from '../../seo/MuSProtJsonLd';
|
| 16 |
|
|
|
|
| 23 |
|
| 24 |
const NEGATIVE_CACHE_TTL = 10 * 60 * 1000; // 10 minutes
|
| 25 |
|
| 26 |
+
export function ProteinDashboard() {
|
| 27 |
// State management
|
| 28 |
const [filterOptions, setFilterOptions] = useState<FilterOptions | null>(null);
|
| 29 |
const [filters, setFilters] = useState<FilterParams>({});
|
| 30 |
const [dataResponse, setDataResponse] = useState<DataResponse | null>(null);
|
| 31 |
+
|
|
|
|
| 32 |
// Search input state (controlled)
|
| 33 |
const [searchInputValue, setSearchInputValue] = useState('');
|
| 34 |
const [searchStatus, setSearchStatus] = useState<'idle' | 'typing' | 'loading' | 'success' | 'not_found' | 'error'>('idle');
|
|
|
|
| 44 |
|
| 45 |
const [, setLoadingFilters] = useState(true);
|
| 46 |
const [loadingData, setLoadingData] = useState(false);
|
| 47 |
+
|
|
|
|
| 48 |
const [error, setError] = useState<string | null>(null);
|
| 49 |
|
| 50 |
// Load filter options on mount
|
|
|
|
| 55 |
const options = await proteinApi.getFilters();
|
| 56 |
setFilterOptions(options);
|
| 57 |
} catch (err) {
|
| 58 |
+
setError('Failed to load filter options. The MuSProt database may still be initializing.');
|
| 59 |
console.error('Error loading filters:', err);
|
| 60 |
} finally {
|
| 61 |
setLoadingFilters(false);
|
|
|
|
| 79 |
}
|
| 80 |
}, [filters]);
|
| 81 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
// Debounced data fetching — skip fetchData when there's no active chain search
|
| 83 |
useEffect(() => {
|
| 84 |
const timeoutId = setTimeout(() => {
|
| 85 |
if (filters.pdb_id && filters.auth_asym_id) {
|
| 86 |
fetchData();
|
| 87 |
}
|
|
|
|
| 88 |
}, 500);
|
| 89 |
|
| 90 |
return () => clearTimeout(timeoutId);
|
| 91 |
+
}, [fetchData, filters.pdb_id, filters.auth_asym_id]);
|
| 92 |
|
| 93 |
const handleFiltersChange = (newFilters: FilterParams) => {
|
| 94 |
setFilters(newFilters);
|
|
|
|
| 256 |
return (
|
| 257 |
<div className="protein-dashboard min-h-screen bg-white">
|
| 258 |
<PageSEO
|
| 259 |
+
title="MuSProt: Multistate Protein Structure Database"
|
| 260 |
description="Explore 355,731 protein chains from 121,584 PDB entries with pairwise structural comparisons, energy scoring, and CATH functional annotations. Download the full SQLite database."
|
| 261 |
+
path="/"
|
| 262 |
+
/>
|
| 263 |
+
<MuSProtJsonLd />
|
| 264 |
+
|
| 265 |
+
{error && (
|
| 266 |
<div className="max-w-7xl mx-auto px-6 mt-4">
|
| 267 |
<div className="protein-error-banner">
|
| 268 |
<span>⚠️ {error}</span>
|
|
|
|
| 351 |
|
| 352 |
{/* Default Dataset Summary View */}
|
| 353 |
<section className="border border-gray-200 rounded-2xl p-6 bg-white shadow-sm hover:border-slate-900 hover:shadow-lg hover:-translate-y-0.5 transition-all duration-300">
|
| 354 |
+
<StatsPanel />
|
| 355 |
</section>
|
| 356 |
|
| 357 |
<section>
|
frontend/src/components/datasets/protein/StatsPanel.tsx
CHANGED
|
@@ -1,42 +1,37 @@
|
|
| 1 |
import React from 'react';
|
| 2 |
-
import type { SummaryStats } from '../../../types/protein';
|
| 3 |
import './StatsPanel.css';
|
| 4 |
|
| 5 |
-
|
| 6 |
-
stats: SummaryStats | null;
|
| 7 |
-
loading: boolean;
|
| 8 |
-
}
|
| 9 |
-
|
| 10 |
-
export const StatsPanel: React.FC<StatsPanelProps> = ({ stats, loading }) => {
|
| 11 |
-
if (loading || !stats) {
|
| 12 |
-
return null;
|
| 13 |
-
}
|
| 14 |
-
|
| 15 |
return (
|
| 16 |
<div className="stats-panel">
|
| 17 |
<div className="stat-card">
|
| 18 |
-
<div className="stat-value">
|
| 19 |
-
<div className="stat-label">
|
| 20 |
</div>
|
| 21 |
-
|
| 22 |
<div className="stat-card">
|
| 23 |
-
<div className="stat-value">
|
| 24 |
-
<div className="stat-label">
|
| 25 |
</div>
|
| 26 |
-
|
| 27 |
<div className="stat-card">
|
| 28 |
-
<div className="stat-value">
|
| 29 |
-
<div className="stat-label">
|
| 30 |
</div>
|
| 31 |
-
|
| 32 |
<div className="stat-card">
|
| 33 |
-
<div className="stat-value">
|
| 34 |
-
<div className="stat-label">
|
| 35 |
</div>
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
<div className="stat-card">
|
| 38 |
-
<div className="stat-value">
|
| 39 |
-
<div className="stat-label">
|
| 40 |
</div>
|
| 41 |
</div>
|
| 42 |
);
|
|
|
|
| 1 |
import React from 'react';
|
|
|
|
| 2 |
import './StatsPanel.css';
|
| 3 |
|
| 4 |
+
export const StatsPanel: React.FC = () => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
return (
|
| 6 |
<div className="stats-panel">
|
| 7 |
<div className="stat-card">
|
| 8 |
+
<div className="stat-value">64,812</div>
|
| 9 |
+
<div className="stat-label">Sequences with multiple conformational observations</div>
|
| 10 |
</div>
|
| 11 |
+
|
| 12 |
<div className="stat-card">
|
| 13 |
+
<div className="stat-value">11,946</div>
|
| 14 |
+
<div className="stat-label">Sequences with multiple state clusters</div>
|
| 15 |
</div>
|
| 16 |
+
|
| 17 |
<div className="stat-card">
|
| 18 |
+
<div className="stat-value">355,731</div>
|
| 19 |
+
<div className="stat-label">Conformational observations</div>
|
| 20 |
</div>
|
| 21 |
+
|
| 22 |
<div className="stat-card">
|
| 23 |
+
<div className="stat-value">90,745</div>
|
| 24 |
+
<div className="stat-label">Unique state clusters</div>
|
| 25 |
</div>
|
| 26 |
+
|
| 27 |
+
<div className="stat-card">
|
| 28 |
+
<div className="stat-value">27.6 million</div>
|
| 29 |
+
<div className="stat-label">Transition triplets between conformational observations</div>
|
| 30 |
+
</div>
|
| 31 |
+
|
| 32 |
<div className="stat-card">
|
| 33 |
+
<div className="stat-value">5.7 million</div>
|
| 34 |
+
<div className="stat-label">Transition triplets between distinct states</div>
|
| 35 |
</div>
|
| 36 |
</div>
|
| 37 |
);
|