613 lines
18 KiB
TypeScript
613 lines
18 KiB
TypeScript
/*
|
|
* File: BrainPredictionsOverview.tsx
|
|
* Description: Component to display patient overview statistics based on different AI prediction scenarios for brain conditions
|
|
* Design & Developed by Tech4Biz Solutions
|
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
|
*/
|
|
|
|
import React from 'react';
|
|
import {
|
|
View,
|
|
Text,
|
|
StyleSheet,
|
|
Dimensions,
|
|
ScrollView,
|
|
} from 'react-native';
|
|
import { PieChart } from 'react-native-chart-kit';
|
|
import { theme } from '../../../theme/theme';
|
|
import { ERDashboard } from '../../../shared/types';
|
|
|
|
/**
|
|
* BrainPredictionsOverviewProps Interface
|
|
*
|
|
* Purpose: Defines the props required by the BrainPredictionsOverview component
|
|
*
|
|
* Props:
|
|
* - dashboard: ERDashboard object containing brain prediction statistics
|
|
*/
|
|
interface BrainPredictionsOverviewProps {
|
|
dashboard: ERDashboard;
|
|
}
|
|
|
|
/**
|
|
* BrainPredictionsOverview Component
|
|
*
|
|
* Purpose: Display patient overview statistics based on different AI prediction scenarios
|
|
*
|
|
* Features:
|
|
* 1. Shows distribution of patients by brain condition predictions
|
|
* 2. Displays AI confidence levels for each prediction type
|
|
* 3. Shows critical vs non-critical brain conditions
|
|
* 4. Provides summary statistics for quick overview
|
|
* 5. Visualizes data using react-native-chart-kit pie chart
|
|
*
|
|
* Prediction Categories:
|
|
* - Hemorrhagic Conditions (IPH, IVH, SAH, SDH, EDH)
|
|
* - Ischemic Conditions (Stroke)
|
|
* - Structural Changes (Mass effect, Midline shift)
|
|
* - Normal Findings
|
|
*/
|
|
export const BrainPredictionsOverview: React.FC<BrainPredictionsOverviewProps> = ({
|
|
dashboard,
|
|
}) => {
|
|
// ============================================================================
|
|
// MOCK PREDICTION DATA
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Mock prediction statistics based on brain conditions
|
|
* In a real app, this would come from the AI analysis API
|
|
*/
|
|
const predictionStats = {
|
|
// Hemorrhagic Conditions
|
|
intraparenchymal: { count: 2, confidence: 94, critical: true },
|
|
intraventricular: { count: 1, confidence: 87, critical: true },
|
|
subarachnoid: { count: 1, confidence: 87, critical: true },
|
|
subdural: { count: 1, confidence: 89, critical: true },
|
|
epidural: { count: 1, confidence: 96, critical: true },
|
|
|
|
// Ischemic Conditions
|
|
ischemic_stroke: { count: 1, confidence: 92, critical: true },
|
|
|
|
// Structural Changes
|
|
mass_effect: { count: 2, confidence: 91, critical: true },
|
|
midline_shift: { count: 1, confidence: 96, critical: true },
|
|
|
|
// Normal Findings
|
|
normal_brain: { count: 3, confidence: 98, critical: false },
|
|
|
|
// Pending Analysis
|
|
pending_analysis: { count: 6, confidence: 0, critical: false },
|
|
};
|
|
|
|
// ============================================================================
|
|
// HELPER FUNCTIONS
|
|
// ============================================================================
|
|
|
|
/**
|
|
* getPredictionLabel Function
|
|
*
|
|
* Purpose: Get human-readable label for prediction type
|
|
*
|
|
* @param predictionType - Type of brain prediction
|
|
* @returns Human-readable label
|
|
*/
|
|
const getPredictionLabel = (predictionType: string) => {
|
|
if (!predictionType) {
|
|
return 'Unknown'; // Default label for undefined types
|
|
}
|
|
|
|
const labels: { [key: string]: string } = {
|
|
intraparenchymal: 'IPH',
|
|
intraventricular: 'IVH',
|
|
subarachnoid: 'SAH',
|
|
subdural: 'SDH',
|
|
epidural: 'EDH',
|
|
ischemic_stroke: 'Stroke',
|
|
mass_effect: 'Mass Effect',
|
|
midline_shift: 'Midline Shift',
|
|
normal_brain: 'Normal',
|
|
pending_analysis: 'Pending',
|
|
};
|
|
|
|
return labels[predictionType] || predictionType;
|
|
};
|
|
|
|
/**
|
|
* getPredictionColor Function
|
|
*
|
|
* Purpose: Get color based on prediction type and criticality
|
|
*
|
|
* @param predictionType - Type of brain prediction
|
|
* @param isCritical - Whether the condition is critical
|
|
* @returns Color string for styling
|
|
*/
|
|
const getPredictionColor = (predictionType: string, isCritical: boolean) => {
|
|
if (!predictionType) {
|
|
return theme.colors.textSecondary; // Default color for undefined types
|
|
}
|
|
|
|
// Define distinct colors for each prediction class
|
|
const predictionColors: { [key: string]: string } = {
|
|
intraparenchymal: '#E53E3E', // Red for IPH
|
|
intraventricular: '#C53030', // Dark Red for IVH
|
|
subarachnoid: '#F56565', // Light Red for SAH
|
|
subdural: '#FC8181', // Pink Red for SDH
|
|
epidural: '#E53E3E', // Red for EDH
|
|
ischemic_stroke: '#3182CE', // Blue for Stroke
|
|
mass_effect: '#805AD5', // Purple for Mass Effect
|
|
midline_shift: '#553C9A', // Dark Purple for Midline Shift
|
|
normal_brain: '#38A169', // Green for Normal
|
|
pending_analysis: '#D69E2E', // Yellow for Pending
|
|
};
|
|
|
|
return predictionColors[predictionType] || theme.colors.textSecondary;
|
|
};
|
|
|
|
// ============================================================================
|
|
// COMPUTED VALUES
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Calculate total patients
|
|
*/
|
|
const totalPatients = Object.values(predictionStats).reduce((sum, stats) => sum + stats.count, 0);
|
|
|
|
/**
|
|
* Calculate total critical conditions
|
|
*/
|
|
const totalCritical = Object.values(predictionStats)
|
|
.filter(stats => stats.critical)
|
|
.reduce((sum, stats) => sum + stats.count, 0);
|
|
|
|
/**
|
|
* Calculate total normal findings
|
|
*/
|
|
const totalNormal = predictionStats.normal_brain.count;
|
|
|
|
/**
|
|
* Calculate total pending analysis
|
|
*/
|
|
const totalPending = predictionStats.pending_analysis.count;
|
|
|
|
/**
|
|
* Calculate average confidence for critical conditions
|
|
*/
|
|
const criticalConditions = Object.values(predictionStats).filter(stats => stats.critical && stats.confidence > 0);
|
|
const averageConfidence = criticalConditions.length > 0
|
|
? Math.round(criticalConditions.reduce((sum, stats) => sum + stats.confidence, 0) / criticalConditions.length)
|
|
: 0;
|
|
|
|
/**
|
|
* Get all prediction classes by count
|
|
*/
|
|
const allPredictions = Object.entries(predictionStats)
|
|
.filter(([_, stats]) => stats.count > 0)
|
|
.sort((a, b) => b[1].count - a[1].count);
|
|
|
|
// ============================================================================
|
|
// CHART DATA PREPARATION
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Prepare chart data for react-native-chart-kit PieChart
|
|
*/
|
|
const chartData = Object.entries(predictionStats)
|
|
.filter(([_, stats]) => stats && stats.count > 0) // Filter out undefined or zero count entries
|
|
.map(([predictionType, stats]) => ({
|
|
name: getPredictionLabel(predictionType),
|
|
population: stats.count, // react-native-chart-kit uses 'population' property
|
|
color: getPredictionColor(predictionType, stats.critical),
|
|
legendFontColor: theme.colors.textPrimary,
|
|
legendFontSize: 12,
|
|
confidence: stats.confidence,
|
|
isCritical: stats.critical,
|
|
}))
|
|
.filter(item => item.population > 0) // Additional filter to ensure no zero count items
|
|
.sort((a, b) => b.population - a.population); // Sort by count descending - removed limit to show all classes
|
|
|
|
/**
|
|
* Chart configuration for react-native-chart-kit
|
|
*/
|
|
const chartConfig = {
|
|
backgroundColor: theme.colors.background,
|
|
backgroundGradientFrom: theme.colors.background,
|
|
backgroundGradientTo: theme.colors.background,
|
|
decimalPlaces: 0,
|
|
color: (opacity = 1) => `rgba(33, 150, 243, ${opacity})`,
|
|
labelColor: (opacity = 1) => `rgba(33, 33, 33, ${opacity})`,
|
|
style: {
|
|
borderRadius: 16,
|
|
},
|
|
propsForDots: {
|
|
r: '6',
|
|
strokeWidth: '2',
|
|
stroke: theme.colors.primary,
|
|
},
|
|
useShadowColorFromDataset: false,
|
|
};
|
|
|
|
// ============================================================================
|
|
// RENDER FUNCTIONS
|
|
// ============================================================================
|
|
|
|
|
|
|
|
/**
|
|
* renderLegend Function
|
|
*
|
|
* Purpose: Render chart legend with confidence levels in grid layout
|
|
*
|
|
* @returns Legend component
|
|
*/
|
|
const renderLegend = () => (
|
|
<View style={styles.legendContainer}>
|
|
<Text style={styles.legendTitle}>Prediction Classes Breakdown</Text>
|
|
<View style={styles.legendGrid}>
|
|
{chartData.map((item, index) => (
|
|
<View key={index} style={styles.legendGridItem}>
|
|
<View style={styles.legendItemHeader}>
|
|
<View style={[styles.legendColor, { backgroundColor: item.color }]} />
|
|
<Text style={styles.legendLabel}>{item.name}</Text>
|
|
</View>
|
|
<Text style={styles.legendCount}>{item.population} patients</Text>
|
|
{item.confidence > 0 && (
|
|
<Text style={styles.legendConfidence}>{item.confidence}%</Text>
|
|
)}
|
|
</View>
|
|
))}
|
|
</View>
|
|
</View>
|
|
);
|
|
|
|
// ============================================================================
|
|
// MAIN RENDER
|
|
// ============================================================================
|
|
return (
|
|
<View style={styles.container}>
|
|
{/* Section Header */}
|
|
<View style={styles.header}>
|
|
<Text style={styles.title}>Brain AI Predictions Overview</Text>
|
|
<Text style={styles.subtitle}>
|
|
Patient distribution by AI-detected conditions
|
|
</Text>
|
|
</View>
|
|
|
|
{/* Pie Chart Section */}
|
|
<View style={styles.chartSection}>
|
|
|
|
<ScrollView
|
|
style={styles.chartScrollView}
|
|
showsVerticalScrollIndicator={false}
|
|
>
|
|
{chartData.length > 0 ? (
|
|
<View style={styles.chartContent}>
|
|
<View style={styles.pieChartContainer}>
|
|
{(() => {
|
|
try {
|
|
return (
|
|
<PieChart
|
|
data={chartData}
|
|
width={Dimensions.get('window').width}
|
|
height={250}
|
|
accessor="population"
|
|
backgroundColor="transparent"
|
|
paddingLeft="0"
|
|
absolute
|
|
hasLegend={false}
|
|
chartConfig={chartConfig}
|
|
center={[Dimensions.get('window').width/4, 0]}
|
|
/>
|
|
);
|
|
} catch (error) {
|
|
console.warn('PieChart error:', error);
|
|
return (
|
|
<View style={styles.errorContainer}>
|
|
<Text style={styles.errorText}>Chart temporarily unavailable</Text>
|
|
</View>
|
|
);
|
|
}
|
|
})()}
|
|
</View>
|
|
{renderLegend()}
|
|
</View>
|
|
) : (
|
|
<View style={styles.noDataContainer}>
|
|
<Text style={styles.noDataText}>No data available for chart</Text>
|
|
</View>
|
|
)}
|
|
</ScrollView>
|
|
</View>
|
|
|
|
{/* Summary Statistics */}
|
|
<View style={styles.summaryContainer}>
|
|
<View style={styles.summaryItem}>
|
|
<Text style={styles.summaryLabel}>Total Patients</Text>
|
|
<Text style={styles.summaryValue}>{totalPatients}</Text>
|
|
</View>
|
|
<View style={styles.summaryItem}>
|
|
<Text style={styles.summaryLabel}>Critical Cases</Text>
|
|
<Text style={[styles.summaryValue, { color: theme.colors.error }]}>
|
|
{totalCritical}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.summaryItem}>
|
|
<Text style={styles.summaryLabel}>Pending Analysis</Text>
|
|
<Text style={[styles.summaryValue, { color: theme.colors.warning }]}>
|
|
{totalPending}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.summaryItem}>
|
|
<Text style={styles.summaryLabel}>Avg Confidence</Text>
|
|
<Text style={[styles.summaryValue, { color: theme.colors.primary }]}>
|
|
{averageConfidence}%
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
// ============================================================================
|
|
// STYLES SECTION
|
|
// ============================================================================
|
|
|
|
const styles = StyleSheet.create({
|
|
// Main container
|
|
container: {
|
|
backgroundColor: theme.colors.background,
|
|
borderRadius: theme.borderRadius.large,
|
|
padding: theme.spacing.md,
|
|
marginVertical: theme.spacing.sm,
|
|
borderWidth: 1,
|
|
borderColor: theme.colors.border,
|
|
},
|
|
|
|
// Header section
|
|
header: {
|
|
// marginBottom: theme.spacing.lg,
|
|
},
|
|
|
|
// Main title
|
|
title: {
|
|
fontSize: theme.typography.fontSize.displaySmall,
|
|
fontFamily: theme.typography.fontFamily.bold,
|
|
color: theme.colors.textPrimary,
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// Subtitle
|
|
subtitle: {
|
|
fontSize: theme.typography.fontSize.bodyMedium,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textSecondary,
|
|
},
|
|
|
|
// Statistics grid
|
|
statsGrid: {
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
gap: theme.spacing.xs,
|
|
marginBottom: theme.spacing.lg,
|
|
},
|
|
|
|
// Individual stat card
|
|
statCard: {
|
|
flex: 1,
|
|
minWidth: '30%',
|
|
maxWidth: '32%',
|
|
backgroundColor: theme.colors.background,
|
|
borderRadius: theme.borderRadius.medium,
|
|
padding: theme.spacing.sm,
|
|
borderWidth: 1,
|
|
borderColor: theme.colors.border,
|
|
borderLeftWidth: 3,
|
|
alignItems: 'center',
|
|
},
|
|
|
|
// Stat value
|
|
statValue: {
|
|
fontSize: theme.typography.fontSize.displaySmall,
|
|
fontFamily: theme.typography.fontFamily.bold,
|
|
color: theme.colors.textPrimary,
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// Stat title
|
|
statTitle: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.semibold,
|
|
color: theme.colors.textPrimary,
|
|
textAlign: 'center',
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// Stat subtitle
|
|
statSubtitle: {
|
|
fontSize: theme.typography.fontSize.caption,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textSecondary,
|
|
textAlign: 'center',
|
|
},
|
|
|
|
// Chart section
|
|
chartSection: {
|
|
marginBottom: theme.spacing.lg,
|
|
},
|
|
|
|
// Chart title
|
|
chartTitle: {
|
|
fontSize: theme.typography.fontSize.bodyLarge,
|
|
fontFamily: theme.typography.fontFamily.semibold,
|
|
color: theme.colors.textPrimary,
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// Chart subtitle
|
|
chartSubtitle: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textSecondary,
|
|
marginBottom: theme.spacing.md,
|
|
},
|
|
|
|
// Chart scroll view
|
|
chartScrollView: {
|
|
// Removed maxHeight to allow full content to be visible
|
|
},
|
|
|
|
// Chart content
|
|
chartContent: {
|
|
alignItems: 'center',
|
|
paddingHorizontal: theme.spacing.sm,
|
|
},
|
|
|
|
// Pie chart container
|
|
pieChartContainer: {
|
|
alignItems: 'center',
|
|
backgroundColor: theme.colors.background,
|
|
borderRadius: theme.borderRadius.medium,
|
|
},
|
|
|
|
// Legend container
|
|
legendContainer: {
|
|
width: '100%',
|
|
paddingHorizontal: theme.spacing.sm,
|
|
backgroundColor: theme.colors.backgroundAlt,
|
|
borderRadius: theme.borderRadius.medium,
|
|
padding: theme.spacing.md,
|
|
},
|
|
|
|
// Legend title
|
|
legendTitle: {
|
|
fontSize: theme.typography.fontSize.bodyLarge,
|
|
fontFamily: theme.typography.fontFamily.semibold,
|
|
color: theme.colors.textPrimary,
|
|
marginBottom: theme.spacing.md,
|
|
textAlign: 'center',
|
|
},
|
|
|
|
// Legend grid container
|
|
legendGrid: {
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
gap: theme.spacing.sm,
|
|
},
|
|
|
|
// Legend grid item
|
|
legendGridItem: {
|
|
width: '48%',
|
|
backgroundColor: theme.colors.background,
|
|
borderRadius: theme.borderRadius.small,
|
|
padding: theme.spacing.sm,
|
|
borderWidth: 1,
|
|
borderColor: theme.colors.border,
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// Legend item header
|
|
legendItemHeader: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// Legend color
|
|
legendColor: {
|
|
width: 12,
|
|
height: 12,
|
|
borderRadius: 6,
|
|
marginRight: theme.spacing.xs,
|
|
},
|
|
|
|
// Legend label
|
|
legendLabel: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.semibold,
|
|
color: theme.colors.textPrimary,
|
|
flex: 1,
|
|
},
|
|
|
|
// Legend count
|
|
legendCount: {
|
|
fontSize: theme.typography.fontSize.caption,
|
|
fontFamily: theme.typography.fontFamily.medium,
|
|
color: theme.colors.textPrimary,
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// Legend confidence
|
|
legendConfidence: {
|
|
fontSize: theme.typography.fontSize.caption,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textSecondary,
|
|
},
|
|
|
|
// No data container
|
|
noDataContainer: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
padding: theme.spacing.md,
|
|
},
|
|
|
|
// No data text
|
|
noDataText: {
|
|
fontSize: theme.typography.fontSize.bodyMedium,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textSecondary,
|
|
},
|
|
|
|
// Summary container
|
|
summaryContainer: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-around',
|
|
paddingTop: theme.spacing.md,
|
|
borderTopWidth: 1,
|
|
borderTopColor: theme.colors.border,
|
|
},
|
|
|
|
// Summary item
|
|
summaryItem: {
|
|
alignItems: 'center',
|
|
},
|
|
|
|
// Summary label
|
|
summaryLabel: {
|
|
fontSize: theme.typography.fontSize.bodySmall,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textSecondary,
|
|
marginBottom: theme.spacing.xs,
|
|
},
|
|
|
|
// Summary value
|
|
summaryValue: {
|
|
fontSize: theme.typography.fontSize.displaySmall,
|
|
fontFamily: theme.typography.fontFamily.bold,
|
|
color: theme.colors.textPrimary,
|
|
},
|
|
|
|
// Error container
|
|
errorContainer: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
padding: theme.spacing.md,
|
|
backgroundColor: theme.colors.backgroundAlt,
|
|
borderRadius: theme.borderRadius.medium,
|
|
},
|
|
|
|
// Error text
|
|
errorText: {
|
|
fontSize: theme.typography.fontSize.bodyMedium,
|
|
fontFamily: theme.typography.fontFamily.regular,
|
|
color: theme.colors.textSecondary,
|
|
textAlign: 'center',
|
|
},
|
|
});
|
|
|
|
/*
|
|
* End of File: BrainPredictionsOverview.tsx
|
|
* Design & Developed by Tech4Biz Solutions
|
|
* Copyright (c) Spurrin Innovations. All rights reserved.
|
|
*/
|