import React, { useState, useEffect } from "react"; import axios from "axios"; import { ResponsiveSankey } from "@nivo/sankey"; import { ResponsiveLine } from "@nivo/line"; import PointCloud from "../components/PointCloud"; import "../style.css"; const TemporalAnalysis: React.FC = () => { const [cellDataCS7, setCellDataCS7] = useState([]); const [cellDataCS8, setCellDataCS8] = useState([]); const [cellDataCS9, setCellDataCS9] = useState([]); const [loadingCS7, setLoadingCS7] = useState(false); const [loadingCS8, setLoadingCS8] = useState(false); const [loadingCS9, setLoadingCS9] = useState(false); const [errorCS7, setErrorCS7] = useState(""); const [errorCS8, setErrorCS8] = useState(""); const [errorCS9, setErrorCS9] = useState(""); const [sankeyData, setSankeyData] = useState({ nodes: [], links: [] }); const [sankeyLoading, setSankeyLoading] = useState(false); // 第三部分:基因时序分析相关状态 const [availableGenes, setAvailableGenes] = useState([]); const [selectedGene, setSelectedGene] = useState(""); const [temporalData, setTemporalData] = useState([]); const [temporalLoading, setTemporalLoading] = useState(false); const [temporalError, setTemporalError] = useState(""); // 生成细胞类型颜色映射 const generateCellTypeColors = (cellTypes: string[]) => { const colorMap = new Map(); const colors = [ '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD', '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E9', '#F8C471', '#82E0AA', '#F1948A', '#85CDCE', '#D7BDE2', '#A9DFBF', '#F9E79F', '#D5A6BD', '#AED6F1', '#E8DAEF' ]; cellTypes.forEach((type, index) => { colorMap.set(type, colors[index % colors.length]); }); return colorMap; }; // 获取可用基因列表(从CS7阶段获取) useEffect(() => { axios .get("http://localhost:5000/api/genes", { params: { stage: "CS7" }, }) .then((res) => setAvailableGenes(res.data)) .catch(() => setAvailableGenes([])); }, []); // 获取基因时序分析数据 const fetchTemporalAnalysis = async (gene: string) => { if (!gene) return; setTemporalLoading(true); setTemporalError(""); setTemporalData([]); try { const response = await axios.get("http://localhost:5000/api/gene_temporal_analysis", { params: { gene: gene.trim() } }); // 转换数据为线图格式 const stagesData = response.data.stages_data; const allCellTypes = new Set(); // 收集所有细胞类型 stagesData.forEach((stageData: any) => { Object.keys(stageData.cell_types).forEach(cellType => { allCellTypes.add(cellType); }); }); const cellTypesArray = Array.from(allCellTypes).sort(); const colorMap = generateCellTypeColors(cellTypesArray); // 构建线图数据 - 每个细胞类型一条线 const lineData = cellTypesArray.map(cellType => { const linePoints = stagesData.map((stageData: any) => { const cellTypeData = stageData.cell_types[cellType]; return { x: stageData.stage, y: cellTypeData ? cellTypeData.proportion : 0 }; }); return { id: cellType, color: colorMap.get(cellType) || '#CCCCCC', data: linePoints }; }); setTemporalData(lineData); console.log('Temporal analysis data:', lineData); } catch (err: any) { if (err.response && err.response.data?.error) { setTemporalError(err.response.data.error); } else { setTemporalError("Failed to fetch temporal analysis data."); } } finally { setTemporalLoading(false); } }; // 构建桑基图数据 const buildSankeyData = (dataCS7: any[], dataCS8: any[], dataCS9: any[]) => { setSankeyLoading(true); try { // 统计各阶段的细胞类型数量 const countCellTypes = (data: any[]) => { const counts: { [key: string]: number } = {}; data.forEach(cell => { const cellType = cell.value || 'Unknown'; // 修正:细胞类型存储在value字段中 counts[cellType] = (counts[cellType] || 0) + 1; }); return counts; }; const countsCS7 = countCellTypes(dataCS7); const countsCS8 = countCellTypes(dataCS8); const countsCS9 = countCellTypes(dataCS9); // 调试信息 console.log('Cell type counts CS7:', countsCS7); console.log('Cell type counts CS8:', countsCS8); console.log('Cell type counts CS9:', countsCS9); // 获取所有细胞类型 const allCellTypes = new Set([ ...Object.keys(countsCS7), ...Object.keys(countsCS8), ...Object.keys(countsCS9) ]); const cellTypeArray = Array.from(allCellTypes).sort(); const colorMap = generateCellTypeColors(cellTypeArray); // 计算总数 const totalCS7 = Object.values(countsCS7).reduce((sum, count) => sum + count, 0); const totalCS8 = Object.values(countsCS8).reduce((sum, count) => sum + count, 0); const totalCS9 = Object.values(countsCS9).reduce((sum, count) => sum + count, 0); // 构建节点 const nodes: any[] = []; cellTypeArray.forEach(cellType => { const color = colorMap.get(cellType) || '#CCCCCC'; // CS7 节点 if (countsCS7[cellType]) { const percentage = ((countsCS7[cellType] / totalCS7) * 100).toFixed(1); nodes.push({ id: `CS7_${cellType}`, nodeColor: color, label: `${cellType} (${percentage}%)` }); } // CS8 节点 if (countsCS8[cellType]) { const percentage = ((countsCS8[cellType] / totalCS8) * 100).toFixed(1); nodes.push({ id: `CS8_${cellType}`, nodeColor: color, label: `${cellType} (${percentage}%)` }); } // CS9 节点 if (countsCS9[cellType]) { const percentage = ((countsCS9[cellType] / totalCS9) * 100).toFixed(1); nodes.push({ id: `CS9_${cellType}`, nodeColor: color, label: `${cellType} (${percentage}%)` }); } }); // 构建连接:使用最小百分比作为同类连线,并将剩余部分匹配到有缺口的目标细胞类型,保证各阶段总量守恒 const buildBalancedLinks = ( sourceCounts: Record, sourceTotal: number, targetCounts: Record, targetTotal: number, sourceStage: string, targetStage: string ) => { const types = Array.from( new Set([ ...Object.keys(sourceCounts), ...Object.keys(targetCounts) ]) ); const sourcePct: Record = {}; const targetPct: Record = {}; types.forEach((t) => { sourcePct[t] = ((sourceCounts[t] || 0) / sourceTotal) * 100; targetPct[t] = ((targetCounts[t] || 0) / targetTotal) * 100; }); const flows: any[] = []; // 同类对齐:取最小值 const sourceResidual: Record = {}; const targetResidual: Record = {}; types.forEach((t) => { const direct = Math.min(sourcePct[t], targetPct[t]); if (direct > 0) { const color = (colorMap.get(t) || '#CCCCCC') + '80'; flows.push({ source: `${sourceStage}_${t}`, target: `${targetStage}_${t}`, value: direct, color }); } sourceResidual[t] = sourcePct[t] - direct; // >= 0 targetResidual[t] = targetPct[t] - direct; // >= 0 }); // 将源阶段的剩余按目标阶段的缺口比例分配 types.forEach((sType) => { let remaining = sourceResidual[sType]; if (remaining <= 0) return; // 计算当前目标缺口总和 let totalDeficit = types.reduce((sum, t) => sum + Math.max(targetResidual[t], 0), 0); if (totalDeficit <= 0) return; types.forEach((dType) => { if (targetResidual[dType] <= 0) return; const share = (remaining * targetResidual[dType]) / totalDeficit; if (share > 1e-6) { const color = (colorMap.get(sType) || '#CCCCCC') + '80'; flows.push({ source: `${sourceStage}_${sType}`, target: `${targetStage}_${dType}`, value: share, color }); targetResidual[dType] -= share; } }); }); return flows; }; const links: any[] = [ ...buildBalancedLinks(countsCS7, totalCS7, countsCS8, totalCS8, 'CS7', 'CS8'), ...buildBalancedLinks(countsCS8, totalCS8, countsCS9, totalCS9, 'CS8', 'CS9') ]; setSankeyData({ nodes, links }); } catch (error) { console.error('Error building Sankey data:', error); setSankeyData({ nodes: [], links: [] }); } finally { setSankeyLoading(false); } }; // 获取CS7阶段细胞类型数据 useEffect(() => { setLoadingCS7(true); setErrorCS7(""); setCellDataCS7([]); axios .get("http://localhost:5000/api/cell", { params: { stage: "CS7" }, }) .then((res) => { setCellDataCS7(res.data.cells); }) .catch((err) => { if (err.response && err.response.data?.error) { setErrorCS7(err.response.data.error); } else { setErrorCS7("Failed to fetch CS7 cell type data."); } }) .finally(() => { setLoadingCS7(false); }); }, []); // 获取CS8阶段细胞类型数据 useEffect(() => { setLoadingCS8(true); setErrorCS8(""); setCellDataCS8([]); axios .get("http://localhost:5000/api/cell", { params: { stage: "CS8" }, }) .then((res) => { setCellDataCS8(res.data.cells); }) .catch((err) => { if (err.response && err.response.data?.error) { setErrorCS8(err.response.data.error); } else { setErrorCS8("Failed to fetch CS8 cell type data."); } }) .finally(() => { setLoadingCS8(false); }); }, []); // 获取CS9阶段细胞类型数据 useEffect(() => { setLoadingCS9(true); setErrorCS9(""); setCellDataCS9([]); axios .get("http://localhost:5000/api/cell", { params: { stage: "CS9" }, }) .then((res) => { setCellDataCS9(res.data.cells); }) .catch((err) => { if (err.response && err.response.data?.error) { setErrorCS9(err.response.data.error); } else { setErrorCS9("Failed to fetch CS9 cell type data."); } }) .finally(() => { setLoadingCS9(false); }); }, []); // 当所有数据加载完成后构建桑基图数据 useEffect(() => { if (cellDataCS7.length > 0 && cellDataCS8.length > 0 && cellDataCS9.length > 0) { buildSankeyData(cellDataCS7, cellDataCS8, cellDataCS9); } }, [cellDataCS7, cellDataCS8, cellDataCS9]); return (

Temporal Analysis

Compare cell type distributions across stages to analyze changes and evolutionary patterns during embryonic development.

{/* 第一部分:三个发育阶段的细胞类型分布 */}

Stage comparison

The three 3D views below show cell type distributions for CS7, CS8, and CS9. Compare spatial distributions to observe changes over development.

{/* CS7阶段细胞类型分布 */}

CS7 - Early developmental stage

{errorCS7 &&
{errorCS7}
} {cellDataCS7.length > 0 ? ( ) : (
{loadingCS7 ? (
🔄

Loading CS7 data...

) : (
🧬

CS7 cell type distribution

Early developmental stage

)}
)}
{/* CS8阶段细胞类型分布 */}

CS8 - Mid developmental stage

{errorCS8 &&
{errorCS8}
} {cellDataCS8.length > 0 ? ( ) : (
{loadingCS8 ? (
🔄

Loading CS8 data...

) : (
🧬

CS8 cell type distribution

Mid developmental stage

)}
)}
{/* CS9阶段细胞类型分布 */}

CS9 - Late developmental stage

{errorCS9 &&
{errorCS9}
} {cellDataCS9.length > 0 ? ( ) : (
{loadingCS9 ? (
🔄

Loading CS9 data...

) : (
🧬

CS9 cell type distribution

Late developmental stage

)}
)}
{/* 第二部分和第三部分:并排显示 */}

Flow and gene timeline

The Sankey diagram (left) shows cell type flows across stages; the line chart (right) shows the proportion changes of the selected gene across cell types.

{/* 桑基图部分 */}

Cell type flow diagram

{sankeyData.nodes.length > 0 && sankeyData.links.length > 0 ? ( <> node.nodeColor} nodeOpacity={0.8} nodeHoverOpacity={1} nodeThickness={18} nodeSpacing={8} nodeBorderWidth={2} nodeBorderColor={{ from: 'color', modifiers: [['darker', 0.3]] }} linkOpacity={0.3} linkHoverOpacity={0.6} linkContract={4} enableLinkGradient={true} labelPosition="outside" labelOrientation="horizontal" labelPadding={12} labelTextColor={{ from: 'color', modifiers: [['darker', 1]] }} legends={[]} motionConfig="gentle" /> {/* Stage labels */}
CS7 - Early
CS8 - Mid
CS9 - Late
) : (
{sankeyLoading || loadingCS7 || loadingCS8 || loadingCS9 ? (
🔄

Building flow diagram...

) : (
📊

Waiting for data

)}
)}
{/* 基因时序分析部分 */}

Gene temporal analysis

{temporalLoading ? (
🔄

Loading temporal data...

) : temporalError ? (
{temporalError}
) : temporalData.length > 0 ? ( line.color} pointSize={8} pointColor={{ theme: 'background' }} pointBorderWidth={3} pointBorderColor={{ from: 'serieColor' }} pointLabelYOffset={-12} useMesh={true} enableGridX={false} enableGridY={true} lineWidth={3} legends={[ { anchor: 'top', direction: 'row', justify: false, translateX: 0, translateY: -30, itemsSpacing: 45, itemWidth: 70, itemHeight: 16, itemDirection: 'left-to-right', itemOpacity: 0.75, symbolSize: 8, symbolShape: 'circle', effects: [ { on: 'hover', style: { itemOpacity: 1 } } ] } ]} /> ) : (
📊

Please select a gene to view temporal changes

)}

User Guide

📅 Time-series comparison

The three 3D views for CS7, CS8, and CS9 allow intuitive observation of how cell types change over time.

🌊 Interpreting the Sankey diagram

Node size reflects proportions; links show continuity of the same cell type across stages. Colors are consistent to aid tracking.

📊 Gene temporal analysis

After selecting a gene, the line chart shows how its proportion changes across cell types, revealing expression dynamics during development.

🖱️ Interactions

Rotate with drag, zoom with scroll, click data points for details. Hover over the Sankey and line charts to see exact values.

🎨 Color encoding

Identical colors denote the same cell type across charts to make it easy to track changes and flows.

🔍 Developmental pattern identification

Compare stages and genes to identify emergence, disappearance, migration, and differentiation of cell types and their expression dynamics.

📈 Percentage analysis

Percentages indicate relative abundance of cell types per stage, helping interpret dynamic composition changes.

🧬 Gene selection strategy

Select development-related genes (e.g., SOX2, NANOG) to observe expression changes and cell-type specificity during embryogenesis.

); }; export default TemporalAnalysis;