From 13fba60637c9a8a52ecfb76a9f1306c271ce0636 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 30 May 2025 19:53:20 -0400 Subject: [PATCH] Add comprehensive family tree feature expansion --- FAMILY_TREE_EXPANSION_GUIDE.md | 657 ++++++++++ .../static/css/family-tree-expansions.css | 1143 +++++++++++++++++ .../js/family-tree-analytics-complete.js | 863 +++++++++++++ .../templates/enhanced_family_tree.html | 702 ++++++++++ 4 files changed, 3365 insertions(+) create mode 100644 FAMILY_TREE_EXPANSION_GUIDE.md create mode 100644 kjvstudy_org/static/css/family-tree-expansions.css create mode 100644 kjvstudy_org/static/js/family-tree-analytics-complete.js create mode 100644 kjvstudy_org/templates/enhanced_family_tree.html diff --git a/FAMILY_TREE_EXPANSION_GUIDE.md b/FAMILY_TREE_EXPANSION_GUIDE.md new file mode 100644 index 0000000..30a99da --- /dev/null +++ b/FAMILY_TREE_EXPANSION_GUIDE.md @@ -0,0 +1,657 @@ +# Family Tree Expansion Implementation Guide + +This comprehensive guide details how to implement and integrate the advanced family tree features for the KJV Study application. + +## Overview + +The family tree expansion includes five major components: + +1. **Advanced Tree Layouts** - Multiple visualization algorithms +2. **Enhanced Search & Navigation** - Comprehensive search with highlighting +3. **Statistical Analytics** - Interactive charts and demographic insights +4. **Mobile Optimization** - Responsive design for all devices +5. **Export Capabilities** - Multiple output formats + +## ๐Ÿ“ File Structure + +``` +kjvstudy_org/ +โ”œโ”€โ”€ static/ +โ”‚ โ”œโ”€โ”€ js/ +โ”‚ โ”‚ โ”œโ”€โ”€ advanced-tree-layouts.js # Multiple layout algorithms +โ”‚ โ”‚ โ”œโ”€โ”€ family-tree-search.js # Search and navigation +โ”‚ โ”‚ โ”œโ”€โ”€ family-tree-analytics.js # Statistics and insights +โ”‚ โ”‚ โ””โ”€โ”€ family-tree-analytics-complete.js # Complete analytics +โ”‚ โ””โ”€โ”€ css/ +โ”‚ โ””โ”€โ”€ family-tree-expansions.css # All styling +โ”œโ”€โ”€ templates/ +โ”‚ โ”œโ”€โ”€ family_tree.html # Original template +โ”‚ โ””โ”€โ”€ enhanced_family_tree.html # New enhanced template +โ””โ”€โ”€ FAMILY_TREE_EXPANSION_GUIDE.md # This guide +``` + +## ๐Ÿš€ Quick Start Implementation + +### Step 1: Add Dependencies + +Add these dependencies to your existing `family_tree.html` template: + +```html + + + + + + + + + +``` + +### Step 2: Initialize Components + +Add this to your existing JavaScript section: + +```javascript +// Initialize advanced components after family data loads +let advancedLayouts, searchEngine, analyticsEngine; + +function initializeAdvancedFeatures() { + const svg = d3.select("#tree-svg"); + + // Initialize layout engine + advancedLayouts = new AdvancedTreeLayouts(svg.node(), familyData); + advancedLayouts.setSelectPersonCallback(selectPerson); + + // Initialize search + searchEngine = new FamilyTreeSearch(familyData, advancedLayouts); + + // Initialize analytics + analyticsEngine = new FamilyTreeAnalytics(familyData); +} + +// Call this after familyData is loaded +initializeAdvancedFeatures(); +``` + +### Step 3: Add Layout Selector + +Insert this HTML where you want the layout controls: + +```html +
+ + + + +
+``` + +## ๐ŸŽจ Advanced Tree Layouts + +### Available Layouts + +#### 1. Hierarchical Tree (Default) +- **Use Case**: Traditional family tree representation +- **Best For**: Clear parent-child relationships +- **Implementation**: +```javascript +// Uses existing D3 tree layout - no changes needed +updateD3Tree(person, personId); +``` + +#### 2. Radial Tree +- **Use Case**: Generations in concentric circles +- **Best For**: Showing generational patterns +- **Implementation**: +```javascript +advancedLayouts.renderRadialLayout(personId, maxGenerations); +``` + +#### 3. Force-Directed Layout +- **Use Case**: Dynamic relationship visualization +- **Best For**: Exploring complex relationships +- **Implementation**: +```javascript +advancedLayouts.renderForceDirectedLayout(personId, includeExtended); +``` + +#### 4. Timeline Layout +- **Use Case**: Chronological family history +- **Best For**: Historical context and lifespans +- **Implementation**: +```javascript +advancedLayouts.renderTimelineLayout(personId); +``` + +#### 5. Circular Pedigree +- **Use Case**: Traditional pedigree charts +- **Best For**: Ancestry focus +- **Implementation**: +```javascript +advancedLayouts.renderCircularPedigreeLayout(personId); +``` + +### Layout Switching Function + +```javascript +function switchLayout(layoutType) { + // Update UI + document.querySelectorAll('.layout-btn').forEach(btn => { + btn.classList.remove('active'); + }); + event.target.classList.add('active'); + + // Switch layout + switch(layoutType) { + case 'radial': + advancedLayouts.renderRadialLayout(currentPersonId); + break; + case 'force-directed': + advancedLayouts.renderForceDirectedLayout(currentPersonId); + break; + case 'timeline': + advancedLayouts.renderTimelineLayout(currentPersonId); + break; + case 'circular-pedigree': + advancedLayouts.renderCircularPedigreeLayout(currentPersonId); + break; + default: + updateD3Tree(familyData[currentPersonId], currentPersonId); + } +} +``` + +## ๐Ÿ” Enhanced Search & Navigation + +### Search Features + +#### Basic Search +- Real-time search with debouncing +- Multiple field search (name, title, description, verses) +- Gender and generation filtering + +#### Advanced Features +- Bookmarking system with localStorage +- Navigation breadcrumbs +- Search result highlighting in tree +- Export search results to CSV + +### Integration Example + +```javascript +// The search system auto-integrates when initialized +// Customize search behavior: +searchEngine.setTreeVisualization(advancedLayouts); + +// Handle search result selection +function handleSearchResult(personId) { + selectPerson(personId); + searchEngine.addToBreadcrumbs(personId); +} +``` + +### Bookmark Management + +```javascript +// Add current person to bookmarks +function addCurrentBookmark() { + searchEngine.addBookmark(currentPersonId); +} + +// Navigate to bookmarked person +function navigateToBookmark(personId) { + selectPerson(personId); +} +``` + +## ๐Ÿ“Š Statistical Analytics + +### Available Analytics + +#### Overview Statistics +- Total persons count +- Gender distribution +- Generation count +- Family metrics +- Average children per family + +#### Interactive Charts +1. **Demographics** - Gender distribution (pie/bar/doughnut) +2. **Generations** - Population by generation +3. **Relationships** - Family size distribution +4. **Timeline** - Biblical timeline analysis +5. **Longevity** - Lifespan distribution + +### Custom Analytics + +```javascript +// Add custom insight +analyticsEngine.addCustomInsight = function(title, value, description) { + const insightsList = document.getElementById('notable-stats'); + const li = document.createElement('li'); + li.innerHTML = `${title}: ${value} - ${description}`; + insightsList.appendChild(li); +}; + +// Example usage +analyticsEngine.addCustomInsight( + "Longest Lineage", + "10 generations", + "From Adam to Noah" +); +``` + +### Chart Customization + +```javascript +// Customize chart colors +const customColors = { + primary: '#007bff', + secondary: '#6c757d', + success: '#28a745', + danger: '#dc3545', + warning: '#ffc107', + info: '#17a2b8' +}; + +// Apply to charts +analyticsEngine.chartColors = customColors; +``` + +## ๐Ÿ“ฑ Mobile Optimization + +### Responsive Features + +The CSS includes comprehensive mobile optimizations: + +- **Touch-friendly controls** - Larger tap targets +- **Adaptive layouts** - Grid systems that stack on mobile +- **Optimized charts** - Reduced heights and simplified legends +- **Collapsible sections** - Save screen space +- **Swipe gestures** - For navigation (where supported) + +### Mobile-Specific CSS + +```css +@media (max-width: 768px) { + .family-search-container { + margin: 10px; + padding: 15px; + } + + .search-input-wrapper { + flex-direction: column; + } + + .chart-container { + height: 250px; /* Reduced from 300px */ + } + + .stats-overview { + grid-template-columns: 1fr; /* Single column on mobile */ + } +} +``` + +## ๐Ÿ’พ Export Capabilities + +### Available Export Formats + +#### SVG Export +```javascript +function exportSVG() { + const svg = document.getElementById('tree-svg'); + const svgData = new XMLSerializer().serializeToString(svg); + const blob = new Blob([svgData], {type: 'image/svg+xml'}); + const url = URL.createObjectURL(blob); + + const link = document.createElement('a'); + link.href = url; + link.download = 'family-tree.svg'; + link.click(); +} +``` + +#### PNG Export +```javascript +function exportPNG() { + const svg = document.getElementById('tree-svg'); + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + + const data = new XMLSerializer().serializeToString(svg); + const img = new Image(); + + img.onload = function() { + canvas.width = img.width; + canvas.height = img.height; + ctx.drawImage(img, 0, 0); + + const link = document.createElement('a'); + link.download = 'family-tree.png'; + link.href = canvas.toDataURL(); + link.click(); + }; + + img.src = 'data:image/svg+xml;base64,' + btoa(data); +} +``` + +#### Data Export +```javascript +function exportData() { + // Export search results as CSV + searchEngine.exportSearchResults(); + + // Export analytics report + analyticsEngine.exportAnalyticsReport(); +} +``` + +## ๐Ÿ”ง Server-Side Integration + +### Enhanced Route Handler + +Update your `server.py` to include additional endpoints: + +```python +@app.get("/family-tree-enhanced", response_class=HTMLResponse) +def enhanced_family_tree_page(request: Request): + """Enhanced family tree with advanced features""" + books = list(bible.iter_books()) + + # Load GEDCOM data (existing logic) + static_dir = Path(__file__).parent / "static" + gedcom_path = static_dir / "adameve.ged" + + if not gedcom_path.exists(): + raise HTTPException(status_code=404, detail="GEDCOM file not found") + + try: + family_tree_data = parse_gedcom_to_tree_data(gedcom_path) + + # Add enhanced metadata + enhanced_data = enhance_family_data(family_tree_data) + + except Exception as e: + raise HTTPException(status_code=500, detail=f"Failed to parse GEDCOM: {str(e)}") + + return templates.TemplateResponse( + "enhanced_family_tree.html", + { + "request": request, + "books": books, + "family_tree_data": enhanced_data, + "analytics_enabled": True, + "search_enabled": True + } + ) + +def enhance_family_data(family_data): + """Add enhanced metadata for analytics""" + for person_id, person in family_data.items(): + # Add generation calculation + person['generation'] = calculate_generation(person_id, family_data) + + # Add relationship metrics + person['relationship_count'] = len(person.get('children', [])) + len(person.get('parents', [])) + + # Add search keywords + person['search_keywords'] = generate_search_keywords(person) + + return family_data +``` + +### API Endpoints + +Add these endpoints for dynamic data: + +```python +@app.get("/api/family-tree/search") +def search_family_tree(q: str, filters: str = None): + """Search family tree data""" + # Implement server-side search + pass + +@app.get("/api/family-tree/analytics") +def get_family_analytics(): + """Get pre-computed analytics""" + # Return analytics data + pass + +@app.post("/api/family-tree/bookmark") +def save_bookmark(person_id: str, user_id: str = None): + """Save user bookmark""" + # Implement bookmark persistence + pass +``` + +## ๐ŸŽฏ Performance Optimization + +### Large Dataset Handling + +For families with 1000+ members: + +```javascript +// Implement pagination +const CHUNK_SIZE = 100; + +function loadDataInChunks(familyData) { + const chunks = Object.keys(familyData).reduce((acc, key, index) => { + const chunkIndex = Math.floor(index / CHUNK_SIZE); + if (!acc[chunkIndex]) acc[chunkIndex] = {}; + acc[chunkIndex][key] = familyData[key]; + return acc; + }, []); + + return chunks; +} + +// Lazy loading for search +searchEngine.enableLazyLoading = true; +searchEngine.chunkSize = CHUNK_SIZE; +``` + +### Memory Management + +```javascript +// Clean up on layout switch +function cleanupLayout() { + if (advancedLayouts.simulation) { + advancedLayouts.simulation.stop(); + } + + // Clear D3 selections + d3.select("#tree-svg").selectAll("*").remove(); + + // Clear analytics charts + Object.values(analyticsEngine.chartInstances).forEach(chart => { + if (chart) chart.destroy(); + }); +} +``` + +## ๐Ÿงช Testing + +### Unit Tests + +```javascript +// Test layout switching +describe('AdvancedTreeLayouts', () => { + let layouts; + + beforeEach(() => { + layouts = new AdvancedTreeLayouts(mockSvg, mockFamilyData); + }); + + test('should switch to radial layout', () => { + layouts.renderRadialLayout('adam'); + expect(layouts.getCurrentLayout()).toBe('radial'); + }); + + test('should center view', () => { + const spy = jest.spyOn(layouts, 'centerView'); + layouts.centerView(); + expect(spy).toHaveBeenCalled(); + }); +}); + +// Test search functionality +describe('FamilyTreeSearch', () => { + test('should find persons by name', () => { + const results = searchEngine.performSearch('adam'); + expect(results.length).toBeGreaterThan(0); + expect(results[0].name.toLowerCase()).toContain('adam'); + }); +}); +``` + +### Integration Tests + +```python +# Test enhanced family tree endpoint +def test_enhanced_family_tree_endpoint(client): + response = client.get("/family-tree-enhanced") + assert response.status_code == 200 + assert "Enhanced Family Tree" in response.text + assert "advanced-tree-layouts.js" in response.text +``` + +## ๐Ÿšจ Troubleshooting + +### Common Issues + +#### 1. Charts Not Rendering +```javascript +// Check if Chart.js is loaded +if (typeof Chart === 'undefined') { + console.error('Chart.js not loaded'); + // Load Chart.js dynamically + const script = document.createElement('script'); + script.src = 'https://cdn.jsdelivr.net/npm/chart.js'; + document.head.appendChild(script); +} +``` + +#### 2. D3.js Layout Issues +```javascript +// Check D3.js version compatibility +if (!d3.version || d3.version.split('.')[0] < '7') { + console.warn('D3.js v7+ required for full functionality'); +} + +// Handle missing data gracefully +function safeRenderLayout(personId) { + if (!familyData[personId]) { + console.warn('Person not found:', personId); + return; + } + + try { + advancedLayouts.renderRadialLayout(personId); + } catch (error) { + console.error('Layout render failed:', error); + // Fallback to hierarchical + updateD3Tree(familyData[personId], personId); + } +} +``` + +#### 3. Mobile Performance +```javascript +// Reduce complexity on mobile +function isMobile() { + return window.innerWidth < 768; +} + +if (isMobile()) { + // Disable complex animations + advancedLayouts.disableAnimations = true; + + // Reduce max nodes + advancedLayouts.maxNodes = 50; + + // Simplify force simulation + if (advancedLayouts.simulation) { + advancedLayouts.simulation.force("charge").strength(-100); + } +} +``` + +## ๐Ÿ“ˆ Future Enhancements + +### Planned Features + +1. **3D Visualization** - Three.js integration for 3D family trees +2. **Virtual Reality** - WebXR support for immersive exploration +3. **Collaborative Features** - Real-time collaboration on family trees +4. **AI Insights** - Machine learning for relationship discovery +5. **Blockchain Integration** - Immutable family records + +### Extension Points + +```javascript +// Plugin system for custom layouts +AdvancedTreeLayouts.registerPlugin = function(name, plugin) { + this.plugins[name] = plugin; +}; + +// Custom analytics modules +FamilyTreeAnalytics.addModule = function(name, module) { + this.modules[name] = module; +}; + +// Search providers +FamilyTreeSearch.addProvider = function(name, provider) { + this.providers[name] = provider; +}; +``` + +## ๐Ÿ“š Resources + +### Documentation +- [D3.js Documentation](https://d3js.org/) +- [Chart.js Documentation](https://www.chartjs.org/) +- [SVG Specification](https://www.w3.org/Graphics/SVG/) + +### Examples +- See `enhanced_family_tree.html` for complete integration example +- Check browser developer tools for debugging layout issues +- Use the demo controls to test all features + +### Support +- File issues in the project repository +- Check console for error messages +- Test in latest Chrome/Firefox for best compatibility + +--- + +## ๐ŸŽ‰ Conclusion + +This expansion transforms the basic family tree into a comprehensive genealogy exploration tool with: + +- **5 different layout algorithms** for varied perspectives +- **Advanced search capabilities** with real-time filtering +- **Statistical insights** through interactive charts +- **Mobile-optimized interface** for all devices +- **Export capabilities** for sharing and archiving + +The modular design allows for gradual implementation and easy customization for specific biblical genealogy needs. + +**Next Steps:** +1. Implement basic integration following the Quick Start guide +2. Customize styling to match your application theme +3. Add server-side endpoints for persistence +4. Test thoroughly across different devices +5. Consider performance optimizations for large datasets + +The enhanced family tree will significantly improve user engagement and provide valuable insights into biblical genealogies and family relationships. \ No newline at end of file diff --git a/kjvstudy_org/static/css/family-tree-expansions.css b/kjvstudy_org/static/css/family-tree-expansions.css new file mode 100644 index 0000000..a9a5d7a --- /dev/null +++ b/kjvstudy_org/static/css/family-tree-expansions.css @@ -0,0 +1,1143 @@ +/** + * Family Tree Expansions CSS + * Comprehensive styling for advanced family tree features including + * search, analytics, and multiple layout options + */ + +/* ===== SEARCH AND NAVIGATION STYLES ===== */ + +.family-search-container { + background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); + border: 1px solid #dee2e6; + border-radius: 12px; + padding: 25px; + margin-bottom: 25px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + transition: all 0.3s ease; +} + +.family-search-container:hover { + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15); +} + +.search-bar-container { + margin-bottom: 20px; +} + +.search-input-wrapper { + display: flex; + align-items: center; + margin-bottom: 15px; + position: relative; +} + +.search-input { + flex: 1; + padding: 12px 20px; + border: 2px solid #dee2e6; + border-radius: 30px; + font-size: 15px; + outline: none; + transition: all 0.3s ease; + background: white; + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.search-input:focus { + border-color: #007bff; + box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1); +} + +.search-clear-btn, .advanced-search-toggle { + margin-left: 12px; + padding: 12px; + border: none; + background: #6c757d; + color: white; + border-radius: 50%; + cursor: pointer; + transition: all 0.3s ease; + width: 44px; + height: 44px; + display: flex; + align-items: center; + justify-content: center; +} + +.search-clear-btn:hover, .advanced-search-toggle:hover { + background: #5a6268; + transform: scale(1.05); +} + +.search-filters { + display: none; + background: white; + padding: 20px; + border-radius: 12px; + border: 1px solid #dee2e6; + margin-bottom: 20px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + animation: slideDown 0.3s ease; +} + +.search-filters.visible { + display: block; +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.filter-group { + margin-bottom: 20px; + padding-bottom: 15px; + border-bottom: 1px solid #f1f3f4; +} + +.filter-group:last-child { + border-bottom: none; + margin-bottom: 0; +} + +.filter-group label { + margin-right: 20px; + font-size: 14px; + cursor: pointer; + display: inline-flex; + align-items: center; + transition: color 0.3s ease; +} + +.filter-group label:hover { + color: #007bff; +} + +.filter-group input[type="checkbox"], +.filter-group input[type="radio"] { + margin-right: 8px; + transform: scale(1.1); +} + +.search-results-container { + background: white; + border-radius: 12px; + border: 1px solid #dee2e6; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.search-results-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px; + border-bottom: 1px solid #dee2e6; + background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); +} + +.results-count { + font-weight: 600; + color: #495057; + font-size: 16px; +} + +.result-actions { + display: flex; + gap: 10px; +} + +.action-btn { + padding: 8px 16px; + border: none; + background: #007bff; + color: white; + border-radius: 6px; + cursor: pointer; + font-size: 13px; + transition: all 0.3s ease; + display: flex; + align-items: center; + gap: 6px; +} + +.action-btn:hover { + background: #0056b3; + transform: translateY(-1px); +} + +.search-results-list { + max-height: 350px; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: #dee2e6 transparent; +} + +.search-results-list::-webkit-scrollbar { + width: 8px; +} + +.search-results-list::-webkit-scrollbar-track { + background: transparent; +} + +.search-results-list::-webkit-scrollbar-thumb { + background: #dee2e6; + border-radius: 4px; +} + +.search-result-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 18px 20px; + border-bottom: 1px solid #f1f3f4; + transition: all 0.3s ease; +} + +.search-result-item:hover { + background: #f8f9fa; + transform: translateX(3px); +} + +.search-result-item:last-child { + border-bottom: none; +} + +.result-info { + flex: 1; +} + +.result-name { + font-weight: 600; + color: #007bff; + font-size: 16px; + margin-bottom: 4px; +} + +.result-title { + font-size: 13px; + color: #6c757d; + margin-bottom: 6px; +} + +.result-snippet { + font-size: 12px; + color: #6c757d; + line-height: 1.4; +} + +.result-actions { + display: flex; + gap: 6px; +} + +.result-actions button { + padding: 8px 10px; + border: none; + background: #6c757d; + color: white; + border-radius: 6px; + cursor: pointer; + font-size: 12px; + transition: all 0.3s ease; +} + +.result-actions button:hover { + background: #5a6268; + transform: scale(1.05); +} + +/* Navigation Styles */ + +.navigation-container { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 25px; + margin-top: 25px; +} + +.breadcrumb-navigation, .quick-navigation { + background: white; + border: 1px solid #dee2e6; + border-radius: 12px; + padding: 20px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.breadcrumb-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; + padding-bottom: 10px; + border-bottom: 1px solid #f1f3f4; +} + +.breadcrumb-header h4 { + margin: 0; + color: #495057; + font-size: 16px; +} + +.clear-btn { + padding: 6px 10px; + border: none; + background: #dc3545; + color: white; + border-radius: 4px; + cursor: pointer; + font-size: 11px; + transition: all 0.3s ease; +} + +.clear-btn:hover { + background: #c82333; + transform: scale(1.05); +} + +.breadcrumb-trail { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; +} + +.breadcrumb-item { + display: flex; + align-items: center; + padding: 6px 12px; + background: linear-gradient(135deg, #e9ecef 0%, #f8f9fa 100%); + border-radius: 20px; + font-size: 12px; + border: 1px solid #dee2e6; + transition: all 0.3s ease; +} + +.breadcrumb-item:hover { + background: linear-gradient(135deg, #007bff 0%, #0056b3 100%); + color: white; + transform: scale(1.02); +} + +.breadcrumb-name { + margin-right: 6px; +} + +.breadcrumb-select { + background: none; + border: none; + color: inherit; + cursor: pointer; + padding: 2px; + transition: transform 0.3s ease; +} + +.breadcrumb-select:hover { + transform: scale(1.2); +} + +.breadcrumb-separator { + color: #6c757d; + font-size: 14px; +} + +.nav-section { + margin-bottom: 20px; +} + +.nav-section:last-child { + margin-bottom: 0; +} + +.nav-section h4 { + margin: 0 0 12px 0; + color: #495057; + font-size: 16px; +} + +.quick-nav-buttons { + display: flex; + gap: 10px; + margin-bottom: 15px; +} + +.nav-btn, .bookmark-btn { + padding: 10px 14px; + border: 1px solid #dee2e6; + background: white; + border-radius: 6px; + cursor: pointer; + font-size: 12px; + transition: all 0.3s ease; + display: flex; + align-items: center; + gap: 6px; +} + +.nav-btn:hover, .bookmark-btn:hover { + background: #f8f9fa; + border-color: #007bff; + transform: translateY(-1px); +} + +.bookmark-controls { + display: flex; + gap: 10px; + margin-bottom: 15px; +} + +.bookmarks-list { + max-height: 220px; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: #dee2e6 transparent; +} + +.no-bookmarks { + text-align: center; + color: #6c757d; + font-style: italic; + padding: 20px; +} + +.bookmark-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px; + border-bottom: 1px solid #f1f3f4; + transition: background-color 0.3s ease; +} + +.bookmark-item:hover { + background: #f8f9fa; +} + +.bookmark-item:last-child { + border-bottom: none; +} + +.bookmark-info { + flex: 1; +} + +.bookmark-name { + font-weight: 600; + font-size: 13px; + color: #495057; +} + +.bookmark-title { + font-size: 11px; + color: #6c757d; +} + +.bookmark-actions { + display: flex; + gap: 6px; +} + +.bookmark-actions button { + padding: 6px 8px; + border: none; + background: #6c757d; + color: white; + border-radius: 4px; + cursor: pointer; + font-size: 10px; + transition: all 0.3s ease; +} + +.bookmark-actions button:hover { + background: #5a6268; + transform: scale(1.05); +} + +/* Notification Styles */ + +.search-notification { + position: fixed; + top: 20px; + right: 20px; + background: linear-gradient(135deg, #28a745 0%, #20c997 100%); + color: white; + padding: 12px 20px; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + z-index: 1000; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); + animation: slideInRight 0.3s ease; +} + +@keyframes slideInRight { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +/* ===== ANALYTICS STYLES ===== */ + +.family-analytics-container { + background: white; + border: 1px solid #dee2e6; + border-radius: 12px; + margin-top: 25px; + overflow: hidden; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +.analytics-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px; + background: linear-gradient(135deg, #007bff 0%, #0056b3 100%); + color: white; +} + +.analytics-header h3 { + margin: 0; + font-size: 18px; + display: flex; + align-items: center; + gap: 10px; +} + +.analytics-controls { + display: flex; + gap: 10px; +} + +.analytics-btn { + padding: 8px 12px; + border: 1px solid rgba(255, 255, 255, 0.3); + background: rgba(255, 255, 255, 0.1); + color: white; + border-radius: 6px; + cursor: pointer; + font-size: 12px; + transition: all 0.3s ease; + display: flex; + align-items: center; + gap: 6px; +} + +.analytics-btn:hover { + background: rgba(255, 255, 255, 0.2); + transform: translateY(-1px); +} + +.analytics-content { + padding: 25px; +} + +/* Stats Overview */ + +.stats-overview { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 20px; + margin-bottom: 30px; +} + +.stat-card { + display: flex; + align-items: center; + padding: 20px; + background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); + border-radius: 12px; + border: 1px solid #dee2e6; + transition: all 0.3s ease; +} + +.stat-card:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15); +} + +.stat-icon { + margin-right: 15px; + font-size: 24px; + color: #007bff; +} + +.stat-number { + font-size: 28px; + font-weight: 700; + color: #495057; + line-height: 1; +} + +.stat-label { + font-size: 12px; + color: #6c757d; + text-transform: uppercase; + letter-spacing: 0.5px; + margin-top: 4px; +} + +/* Chart Tabs */ + +.chart-tabs { + display: flex; + border-bottom: 2px solid #e9ecef; + margin-bottom: 25px; + overflow-x: auto; + scrollbar-width: none; +} + +.chart-tabs::-webkit-scrollbar { + display: none; +} + +.tab-btn { + padding: 12px 20px; + border: none; + background: none; + color: #6c757d; + cursor: pointer; + font-size: 14px; + font-weight: 500; + border-bottom: 3px solid transparent; + transition: all 0.3s ease; + white-space: nowrap; +} + +.tab-btn.active { + color: #007bff; + border-bottom-color: #007bff; +} + +.tab-btn:hover { + color: #007bff; + background: rgba(0, 123, 255, 0.05); +} + +/* Chart Panels */ + +.chart-panels { + position: relative; + min-height: 400px; +} + +.chart-panel { + display: none; + animation: fadeIn 0.3s ease; +} + +.chart-panel.active { + display: block; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +.panel-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + padding-bottom: 15px; + border-bottom: 1px solid #e9ecef; +} + +.panel-header h4 { + margin: 0; + color: #495057; + font-size: 18px; +} + +.chart-options select { + padding: 8px 12px; + border: 1px solid #dee2e6; + border-radius: 6px; + background: white; + font-size: 13px; + cursor: pointer; +} + +.chart-container { + position: relative; + height: 300px; + margin-bottom: 20px; + background: #fafafa; + border-radius: 8px; + padding: 15px; +} + +.timeline-container { + height: 250px; +} + +/* Chart Insights */ + +.chart-insights { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 15px; + padding: 15px; + background: #f8f9fa; + border-radius: 8px; +} + +.insight-item { + font-size: 14px; + color: #495057; +} + +.insight-item strong { + color: #007bff; +} + +/* Generation Details */ + +.generation-details { + margin-top: 20px; +} + +#generation-breakdown { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 15px; +} + +.generation-card { + padding: 15px; + background: #f8f9fa; + border-radius: 8px; + border: 1px solid #e9ecef; +} + +.generation-title { + font-weight: 600; + color: #495057; + margin-bottom: 10px; +} + +.generation-stats { + font-size: 13px; + color: #6c757d; +} + +/* Relationship Metrics */ + +.relationship-metrics { + margin-bottom: 20px; +} + +.metric-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 15px; +} + +.metric-item { + text-align: center; + padding: 15px; + background: #f8f9fa; + border-radius: 8px; + border: 1px solid #e9ecef; +} + +.metric-value { + font-size: 24px; + font-weight: 700; + color: #007bff; + margin-bottom: 5px; +} + +.metric-label { + font-size: 12px; + color: #6c757d; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +/* Timeline Insights */ + +.timeline-insights { + margin-top: 20px; +} + +.insight-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 15px; +} + +.insight-card { + padding: 15px; + background: #f8f9fa; + border-radius: 8px; + border: 1px solid #e9ecef; + text-align: center; +} + +.insight-card h5 { + margin: 0 0 10px 0; + color: #495057; + font-size: 14px; +} + +.insight-card div { + font-weight: 600; + color: #007bff; + font-size: 16px; +} + +/* Longevity Trends */ + +.longevity-trends { + margin-top: 20px; + padding: 15px; + background: #f8f9fa; + border-radius: 8px; +} + +/* Detailed Insights */ + +.detailed-insights { + margin-top: 30px; + padding-top: 25px; + border-top: 2px solid #e9ecef; +} + +.detailed-insights h4 { + margin-bottom: 20px; + color: #495057; + font-size: 18px; +} + +.insights-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 25px; +} + +.insight-section { + background: #f8f9fa; + padding: 20px; + border-radius: 8px; + border: 1px solid #e9ecef; +} + +.insight-section h5 { + margin: 0 0 15px 0; + color: #007bff; + font-size: 16px; + border-bottom: 1px solid #dee2e6; + padding-bottom: 8px; +} + +.insight-section ul { + list-style: none; + padding: 0; + margin: 0; +} + +.insight-section li { + padding: 8px 0; + border-bottom: 1px solid #e9ecef; + font-size: 14px; + color: #495057; +} + +.insight-section li:last-child { + border-bottom: none; +} + +/* ===== ADVANCED LAYOUT STYLES ===== */ + +/* Force-directed layout specific styles */ +.force-link { + stroke: #999; + stroke-opacity: 0.6; +} + +.force-link.marriage { + stroke: #4caf50; + stroke-width: 3px; + stroke-dasharray: 5,5; +} + +.force-node { + cursor: pointer; + transition: all 0.3s ease; +} + +.force-node:hover circle { + stroke-width: 3px; + r: 10; +} + +.force-node.current circle { + stroke: #007bff; + stroke-width: 4px; +} + +/* Radial layout specific styles */ +.radial-link { + stroke: #666; + stroke-width: 2; + fill: none; + opacity: 0.7; +} + +.generation-label { + font-size: 12px; + fill: #666; + text-anchor: middle; + font-weight: bold; +} + +/* Timeline layout specific styles */ +.timeline-axis { + stroke: #333; + stroke-width: 2; +} + +.timeline-tick { + stroke: #666; + stroke-width: 1; +} + +.timeline-tick-label { + font-size: 10px; + fill: #666; + text-anchor: middle; +} + +.timeline-connection { + stroke: #999; + stroke-width: 1; + stroke-dasharray: 2,2; + opacity: 0.5; +} + +/* Layout controls */ +.layout-controls { + display: flex; + gap: 10px; + margin-bottom: 15px; + padding: 15px; + background: #f8f9fa; + border-radius: 8px; + border: 1px solid #dee2e6; +} + +.layout-btn { + padding: 8px 16px; + border: 1px solid #dee2e6; + background: white; + color: #495057; + border-radius: 6px; + cursor: pointer; + font-size: 13px; + transition: all 0.3s ease; +} + +.layout-btn.active { + background: #007bff; + color: white; + border-color: #007bff; +} + +.layout-btn:hover { + border-color: #007bff; + color: #007bff; +} + +.layout-btn.active:hover { + background: #0056b3; + color: white; +} + +/* Tree highlighting styles */ +.tree-node.highlighted circle { + stroke: #ff9800; + stroke-width: 4px; + fill: #fff3e0; +} + +.tree-node.search-result circle { + stroke: #e91e63; + stroke-width: 3px; + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0% { stroke-opacity: 1; } + 50% { stroke-opacity: 0.5; } + 100% { stroke-opacity: 1; } +} + +/* Responsive Design */ + +@media (max-width: 1024px) { + .navigation-container { + grid-template-columns: 1fr; + } + + .stats-overview { + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + } + + .insights-grid { + grid-template-columns: 1fr; + } +} + +@media (max-width: 768px) { + .family-search-container, + .family-analytics-container { + margin: 15px; + padding: 15px; + } + + .search-input-wrapper { + flex-direction: column; + align-items: stretch; + } + + .search-clear-btn, + .advanced-search-toggle { + margin: 10px 0 0 0; + align-self: flex-end; + width: auto; + } + + .filter-group { + margin-bottom: 15px; + } + + .filter-group label { + display: block; + margin: 5px 0; + } + + .breadcrumb-trail { + flex-direction: column; + align-items: flex-start; + } + + .chart-tabs { + flex-wrap: wrap; + } + + .tab-btn { + flex: 1; + min-width: 120px; + } + + .metric-grid { + grid-template-columns: repeat(2, 1fr); + } + + .insight-grid { + grid-template-columns: 1fr; + } + + .analytics-header { + flex-direction: column; + gap: 15px; + text-align: center; + } + + .analytics-controls { + justify-content: center; + } +} + +@media (max-width: 480px) { + .stats-overview { + grid-template-columns: 1fr; + } + + .metric-grid { + grid-template-columns: 1fr; + } + + .search-results-header { + flex-direction: column; + gap: 10px; + text-align: center; + } + + .chart-container { + height: 250px; + } + + .timeline-container { + height: 200px; + } +} + +/* Dark mode support */ +@media (prefers-color-scheme: dark) { + .family-search-container, + .family-analytics-container { + background: #2d3436; + border-color: #636e72; + color: #ddd; + } + + .search-input { + background: #2d3436; + border-color: #636e72; + color: #ddd; + } + + .search-filters, + .breadcrumb-navigation, + .quick-navigation { + background: #2d3436; + border-color: #636e72; + } + + .stat-card, + .chart-insights, + .insight-card, + .metric-item { + background: #2d3436; + border-color: #636e72; + color: #ddd; + } + + .analytics-header { + background: linear-gradient(135deg, #0984e3 0%, #74b9ff 100%); + } +} + +/* Print styles */ +@media print { + .family-search-container, + .analytics-controls, + .chart-tabs { + display: none; + } + + .family-analytics-container { + break-inside: avoid; + box-shadow: none; + border: 1px solid #000; + } + + .chart-panel.active { + page-break-inside: avoid; + } +} \ No newline at end of file diff --git a/kjvstudy_org/static/js/family-tree-analytics-complete.js b/kjvstudy_org/static/js/family-tree-analytics-complete.js new file mode 100644 index 0000000..badad3b --- /dev/null +++ b/kjvstudy_org/static/js/family-tree-analytics-complete.js @@ -0,0 +1,863 @@ +/** + * Family Tree Analytics and Statistics System for KJV Study + * Provides comprehensive statistical analysis and insights with interactive charts + */ + +class FamilyTreeAnalytics { + constructor(familyData) { + this.familyData = familyData; + this.chartInstances = {}; + this.analysisCache = {}; + + this.initializeAnalytics(); + this.calculateStatistics(); + } + + initializeAnalytics() { + this.createAnalyticsInterface(); + this.setupEventListeners(); + } + + createAnalyticsInterface() { + const analyticsContainer = document.createElement('div'); + analyticsContainer.className = 'family-analytics-container'; + analyticsContainer.innerHTML = ` +
+

Family Tree Analytics

+
+ + + +
+
+ +
+ +
+
+
+
+
0
+
Total Persons
+
+
+ +
+
+
+
0
+
Male
+
+
+ +
+
+
+
0
+
Female
+
+
+ +
+
+
+
0
+
Generations
+
+
+ +
+
+
+
0
+
Families
+
+
+ +
+
+
+
0
+
Avg Children
+
+
+
+ + +
+ + + + + +
+ + +
+ +
+
+

Demographic Analysis

+
+ +
+
+
+ +
+
+
+ Gender Ratio: Loading... +
+
+ Most Common Names: Loading... +
+
+
+ + +
+
+

Generational Distribution

+
+ +
+
+
+ +
+
+
+
+
+ + +
+
+

Family Relationships

+
+
+
+
+
0
+
Married Couples
+
+
+
0
+
Single Parents
+
+
+
0
+
Childless Couples
+
+
+
0
+
Largest Family
+
+
+
+
+ +
+
+ + +
+
+

Biblical Timeline Analysis

+
+ +
+
+
+ +
+
+
+
+
Longest Lifespan
+
Loading...
+
+
+
Shortest Lifespan
+
Loading...
+
+
+
Average Lifespan
+
Loading...
+
+
+
+
+ + +
+
+

Longevity Analysis

+
+
+ +
+ +
+
+ + +
+

Detailed Insights

+
+
+
Family Patterns
+
    +
    +
    +
    Notable Statistics
    +
      +
      +
      +
      Genealogical Insights
      +
        +
        +
        +
        +
        + `; + + // Insert analytics container into the page + const familyViewer = document.querySelector('.family-viewer'); + if (familyViewer) { + familyViewer.appendChild(analyticsContainer); + } + } + + setupEventListeners() { + // Tab switching + document.querySelectorAll('.tab-btn').forEach(btn => { + btn.addEventListener('click', (e) => { + this.switchTab(e.target.dataset.tab); + }); + }); + + // Chart type changes + document.getElementById('demographic-chart-type')?.addEventListener('change', (e) => { + this.updateDemographicChart(e.target.value); + }); + + document.getElementById('generational-metric')?.addEventListener('change', (e) => { + this.updateGenerationalChart(e.target.value); + }); + + document.getElementById('timeline-view')?.addEventListener('change', (e) => { + this.updateTimelineChart(e.target.value); + }); + + // Control buttons + document.getElementById('refresh-analytics')?.addEventListener('click', () => { + this.refreshAnalytics(); + }); + + document.getElementById('export-analytics')?.addEventListener('click', () => { + this.exportAnalyticsReport(); + }); + + document.getElementById('toggle-analytics')?.addEventListener('click', () => { + this.toggleAnalyticsPanel(); + }); + } + + calculateStatistics() { + this.stats = { + totalPersons: Object.keys(this.familyData).length, + genderDistribution: this.calculateGenderDistribution(), + generationData: this.calculateGenerationData(), + familyStructure: this.calculateFamilyStructure(), + lifespanData: this.calculateLifespanData(), + relationshipMetrics: this.calculateRelationshipMetrics(), + nameAnalysis: this.calculateNameAnalysis(), + biblicalTimeline: this.calculateBiblicalTimeline() + }; + + this.updateOverviewStats(); + this.generateInsights(); + this.createCharts(); + } + + calculateGenderDistribution() { + const genders = { male: 0, female: 0, unknown: 0 }; + + Object.values(this.familyData).forEach(person => { + const gender = this.determineGender(person); + genders[gender]++; + }); + + return genders; + } + + calculateGenerationData() { + const generations = {}; + const visited = new Set(); + + const mapGeneration = (personId, generation = 0) => { + if (visited.has(personId)) return; + visited.add(personId); + + const person = this.familyData[personId]; + if (!person) return; + + if (!generations[generation]) { + generations[generation] = { + count: 0, + persons: [], + totalLifespan: 0, + lifespanCount: 0, + totalChildren: 0 + }; + } + + generations[generation].count++; + generations[generation].persons.push(personId); + generations[generation].totalChildren += (person.children?.length || 0); + + // Calculate lifespan if available + const lifespan = this.calculateLifespan(person); + if (lifespan > 0) { + generations[generation].totalLifespan += lifespan; + generations[generation].lifespanCount++; + } + + // Map children to next generation + if (person.children) { + person.children.forEach(childId => { + mapGeneration(childId, generation + 1); + }); + } + }; + + // Start with root figures (those without parents) + Object.entries(this.familyData).forEach(([id, person]) => { + if (!person.parents || person.parents.length === 0) { + mapGeneration(id, 0); + } + }); + + return generations; + } + + calculateFamilyStructure() { + let marriedCouples = 0; + let singleParents = 0; + let childlessCouples = 0; + let largestFamily = 0; + const familySizes = []; + + Object.values(this.familyData).forEach(person => { + const childrenCount = person.children?.length || 0; + + if (childrenCount > 0) { + familySizes.push(childrenCount); + largestFamily = Math.max(largestFamily, childrenCount); + + if (person.spouse) { + marriedCouples++; + } else { + singleParents++; + } + } else if (person.spouse) { + childlessCouples++; + } + }); + + return { + marriedCouples: Math.floor(marriedCouples / 2), // Avoid double counting + singleParents, + childlessCouples: Math.floor(childlessCouples / 2), + largestFamily, + familySizes, + averageChildren: familySizes.length > 0 ? + (familySizes.reduce((a, b) => a + b, 0) / familySizes.length).toFixed(1) : 0 + }; + } + + calculateLifespanData() { + const lifespans = []; + let totalLifespan = 0; + let lifespanCount = 0; + let longestLived = { name: '', years: 0 }; + let shortestLived = { name: '', years: Infinity }; + + Object.values(this.familyData).forEach(person => { + const lifespan = this.calculateLifespan(person); + if (lifespan > 0) { + lifespans.push({ name: person.name, years: lifespan }); + totalLifespan += lifespan; + lifespanCount++; + + if (lifespan > longestLived.years) { + longestLived = { name: person.name, years: lifespan }; + } + if (lifespan < shortestLived.years) { + shortestLived = { name: person.name, years: lifespan }; + } + } + }); + + return { + lifespans, + averageLifespan: lifespanCount > 0 ? (totalLifespan / lifespanCount).toFixed(1) : 0, + longestLived: longestLived.years > 0 ? longestLived : null, + shortestLived: shortestLived.years < Infinity ? shortestLived : null + }; + } + + calculateRelationshipMetrics() { + const relationships = { + parentChild: 0, + spouses: 0, + siblings: 0 + }; + + Object.values(this.familyData).forEach(person => { + relationships.parentChild += person.children?.length || 0; + if (person.spouse) relationships.spouses++; + }); + + relationships.spouses = Math.floor(relationships.spouses / 2); // Avoid double counting + + return relationships; + } + + calculateNameAnalysis() { + const nameFrequency = {}; + const nameComponents = {}; + + Object.values(this.familyData).forEach(person => { + const name = person.name.toLowerCase(); + nameFrequency[name] = (nameFrequency[name] || 0) + 1; + + // Analyze name components + const parts = name.split(' '); + parts.forEach(part => { + if (part.length > 2) { + nameComponents[part] = (nameComponents[part] || 0) + 1; + } + }); + }); + + const commonNames = Object.entries(nameFrequency) + .sort(([,a], [,b]) => b - a) + .slice(0, 5) + .map(([name, count]) => ({ name, count })); + + return { nameFrequency, nameComponents, commonNames }; + } + + calculateBiblicalTimeline() { + const timeline = []; + + Object.values(this.familyData).forEach(person => { + const birthYear = this.parseBiblicalYear(person.birth_year); + const deathYear = this.parseBiblicalYear(person.death_year); + + if (birthYear) { + timeline.push({ + name: person.name, + birthYear, + deathYear, + lifespan: deathYear ? deathYear - birthYear : null + }); + } + }); + + return timeline.sort((a, b) => (b.birthYear || 0) - (a.birthYear || 0)); + } + + updateOverviewStats() { + document.getElementById('total-persons').textContent = this.stats.totalPersons; + document.getElementById('male-count').textContent = this.stats.genderDistribution.male; + document.getElementById('female-count').textContent = this.stats.genderDistribution.female; + document.getElementById('generations-count').textContent = Object.keys(this.stats.generationData).length; + document.getElementById('families-count').textContent = this.stats.familyStructure.marriedCouples; + document.getElementById('avg-children').textContent = this.stats.familyStructure.averageChildren; + } + + createCharts() { + // Only create charts if Chart.js is available + if (typeof Chart !== 'undefined') { + this.createDemographicChart(); + this.createGenerationalChart(); + this.createRelationshipsChart(); + this.createTimelineChart(); + this.createLongevityChart(); + } + } + + createDemographicChart(type = 'pie') { + const ctx = document.getElementById('demographic-chart'); + if (!ctx || typeof Chart === 'undefined') return; + + if (this.chartInstances.demographic) { + this.chartInstances.demographic.destroy(); + } + + const data = { + labels: ['Male', 'Female', 'Unknown'], + datasets: [{ + data: [ + this.stats.genderDistribution.male, + this.stats.genderDistribution.female, + this.stats.genderDistribution.unknown + ], + backgroundColor: ['#2196F3', '#E91E63', '#9E9E9E'], + borderWidth: 2, + borderColor: '#fff' + }] + }; + + this.chartInstances.demographic = new Chart(ctx, { + type: type, + data: data, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + position: 'bottom' + }, + title: { + display: true, + text: 'Gender Distribution' + } + } + } + }); + + this.updateDemographicInsights(); + } + + createGenerationalChart(metric = 'count') { + const ctx = document.getElementById('generational-chart'); + if (!ctx || typeof Chart === 'undefined') return; + + if (this.chartInstances.generational) { + this.chartInstances.generational.destroy(); + } + + const generations = this.stats.generationData; + const labels = Object.keys(generations).map(gen => `Generation ${gen}`); + let dataValues = []; + let label = ''; + + switch(metric) { + case 'count': + dataValues = Object.values(generations).map(gen => gen.count); + label = 'Number of Persons'; + break; + case 'lifespan': + dataValues = Object.values(generations).map(gen => + gen.lifespanCount > 0 ? (gen.totalLifespan / gen.lifespanCount).toFixed(1) : 0 + ); + label = 'Average Lifespan (years)'; + break; + case 'children': + dataValues = Object.values(generations).map(gen => + gen.count > 0 ? (gen.totalChildren / gen.count).toFixed(1) : 0 + ); + label = 'Average Children per Person'; + break; + } + + this.chartInstances.generational = new Chart(ctx, { + type: 'bar', + data: { + labels: labels, + datasets: [{ + label: label, + data: dataValues, + backgroundColor: '#4CAF50', + borderColor: '#388E3C', + borderWidth: 1 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + scales: { + y: { + beginAtZero: true + } + }, + plugins: { + title: { + display: true, + text: `Generational Analysis - ${label}` + } + } + } + }); + + this.updateGenerationBreakdown(); + } + + createRelationshipsChart() { + const ctx = document.getElementById('relationships-chart'); + if (!ctx || typeof Chart === 'undefined') return; + + if (this.chartInstances.relationships) { + this.chartInstances.relationships.destroy(); + } + + const familySizes = this.stats.familyStructure.familySizes; + const distribution = {}; + + familySizes.forEach(size => { + const key = size > 10 ? '10+' : size.toString(); + distribution[key] = (distribution[key] || 0) + 1; + }); + + this.chartInstances.relationships = new Chart(ctx, { + type: 'bar', + data: { + labels: Object.keys(distribution).sort((a, b) => { + if (a === '10+') return 1; + if (b === '10+') return -1; + return parseInt(a) - parseInt(b); + }), + datasets: [{ + label: 'Number of Families', + data: Object.values(distribution), + backgroundColor: '#FF9800', + borderColor: '#F57C00', + borderWidth: 1 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + scales: { + x: { + title: { + display: true, + text: 'Number of Children' + } + }, + y: { + beginAtZero: true, + title: { + display: true, + text: 'Number of Families' + } + } + }, + plugins: { + title: { + display: true, + text: 'Family Size Distribution' + } + } + } + }); + + this.updateRelationshipMetrics(); + } + + createTimelineChart(view = 'births') { + const ctx = document.getElementById('timeline-chart'); + if (!ctx || typeof Chart === 'undefined') return; + + if (this.chartInstances.timeline) { + this.chartInstances.timeline.destroy(); + } + + const timeline = this.stats.biblicalTimeline.filter(person => person.birthYear); + + if (timeline.length === 0) { + ctx.getContext('2d').clearRect(0, 0, ctx.width, ctx.height); + return; + } + + // Simple timeline chart showing birth years + const labels = timeline.slice(0, 10).map(p => p.name); + const data = timeline.slice(0, 10).map(p => p.birthYear); + + this.chartInstances.timeline = new Chart(ctx, { + type: 'line', + data: { + labels: labels, + datasets: [{ + label: 'Birth Year', + data: data, + borderColor: '#9C27B0', + backgroundColor: 'rgba(156, 39, 176, 0.1)', + tension: 0.1 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + scales: { + y: { + reverse: true, // Earlier years at top + title: { + display: true, + text: 'Years Before Christ' + } + } + }, + plugins: { + title: { + display: true, + text: 'Biblical Timeline' + } + } + } + }); + + this.updateTimelineInsights(); + } + + createLongevityChart() { + const ctx = document.getElementById('longevity-chart'); + if (!ctx || typeof Chart === 'undefined') return; + + if (this.chartInstances.longevity) { + this.chartInstances.longevity.destroy(); + } + + const lifespans = this.stats.lifespanData.lifespans; + if (lifespans.length === 0) return; + + // Create age groups + const ageGroups = { + '0-100': 0, '101-200': 0, '201-300': 0, '301-400': 0, + '401-500': 0, '501-600': 0, '601-700': 0, '700+': 0 + }; + + lifespans.forEach(person => { + const age = person.years; + if (age <= 100) ageGroups['0-100']++; + else if (age <= 200) ageGroups['101-200']++; + else if (age <= 300) ageGroups['201-300']++; + else if (age <= 400) ageGroups['301-400']++; + else if (age <= 500) ageGroups['401-500']++; + else if (age <= 600) ageGroups['501-600']++; + else if (age <= 700) ageGroups['601-700']++; + else ageGroups['700+']++; + }); + + this.chartInstances.longevity = new Chart(ctx, { + type: 'doughnut', + data: { + labels: Object.keys(ageGroups), + datasets: [{ + data: Object.values(ageGroups), + backgroundColor: [ + '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', + '#FFEAA7', '#DDA0DD', '#98D8C8', '#F7DC6F' + ] + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + title: { + display: true, + text: 'Longevity Distribution (Years)' + }, + legend: { + position: 'bottom' + } + } + } + }); + + this.updateLongevityTrends(); + } + + // Helper Methods + + determineGender(person) { + const name = person.name.toLowerCase(); + const femaleNames = [ + 'eve', 'sarah', 'sarai', 'rebekah', 'rebecca', 'rachel', 'leah', 'dinah', + 'tamar', 'miriam', 'deborah', 'ruth', 'naomi', 'bathsheba', 'abigail', + 'esther', 'mary', 'elizabeth', 'anna', 'hannah', 'martha' + ]; + + if (femaleNames.some(femName => name.includes(femName))) { + return 'female'; + } + + // Check title/description for gender clues + const description = (person.description || '').toLowerCase(); + const title = (person.title || '').toLowerCase(); + + if (description.includes('wife') || title.includes('wife') || + description.includes('mother') || title.includes('mother')) { + return 'female'; + } + + return 'male'; // Default for biblical genealogies + } + + calculateLifespan(person) { + const birthYear = this.parseBiblicalYear(person.birth_year); + const deathYear = this.parseBiblicalYear(person.death_year); + + if (birthYear && deathYear && deathYear > birthYear) { + return deathYear - birthYear; + } + + // Try to extract from age_at_death + if (person.age_at_death && person.age_at_death !== "Unknown") { + const match = person.age_at_death.match(/(\d+)/); + if (match) { + return parseInt(match[1]); + } + } + + return 0; + } + + parseBiblicalYear(yearString) { + if (!yearString || yearString === "Unknown") return null; + + const match = yearString.match(/(\d+)/); + return match ? parseInt(match[1]) : null; + } + + // UI Update Methods + + switchTab(tabName) { + document.querySelectorAll('.tab-btn').forEach(btn => { + btn.classList.toggle('active', btn.dataset.tab === tabName); + }); + + document.querySelectorAll('.chart-panel').forEach(panel => { + panel.classList.toggle('active', panel.id === `${tabName}-panel`); + }); + } + + updateDemographicInsights() { + const total = this.stats.genderDistribution.male + this.stats.genderDistribution.female; + const ratio = total > diff --git a/kjvstudy_org/templates/enhanced_family_tree.html b/kjvstudy_org/templates/enhanced_family_tree.html new file mode 100644 index 0000000..f348317 --- /dev/null +++ b/kjvstudy_org/templates/enhanced_family_tree.html @@ -0,0 +1,702 @@ + + + + + + Enhanced Family Tree - KJV Study + + + + + + + + + + + + + + + + + +
        + +
        +

        Enhanced Family Tree Visualization

        +

        Advanced biblical genealogy exploration with multiple layouts, analytics, and interactive features

        +
        + + +
        + +
        +
        + Hierarchical +
        +
        + Radial +
        +
        + Force-Directed +
        +
        + Timeline +
        +
        + Circular Pedigree +
        +
        + + +
        + + + + + +
        + + +
        +
        +
        +
        Loading family tree data...
        +
        + +
        + + +
        + + +
        + + +
        +
        +
        +
        Advanced Search
        +
        + Comprehensive search capabilities with filtering by name, gender, generation, and biblical references. + Features real-time highlighting and bookmark management. +
        +
        + +
        +
        +
        Statistical Analytics
        +
        + Interactive charts and insights showing demographic patterns, generational trends, + family relationships, and biblical timeline analysis. +
        +
        + +
        +
        +
        Multiple Layouts
        +
        + Choose from hierarchical, radial, force-directed, timeline, and circular pedigree layouts. + Each optimized for different exploration patterns. +
        +
        + +
        +
        +
        Smart Navigation
        +
        + Breadcrumb trails, navigation history, bookmarking system, and quick access controls + for efficient family tree exploration. +
        +
        + +
        +
        +
        Responsive Design
        +
        + Fully responsive interface that adapts to desktop, tablet, and mobile devices + with touch-friendly controls and optimized layouts. +
        +
        + +
        +
        +
        Export Capabilities
        +
        + Export family trees as high-quality images, PDF documents, or structured data formats. + Perfect for sharing and printing. +
        +
        +
        +
        +
        + + + + + + + + + + \ No newline at end of file