Add comprehensive family tree feature expansion

This commit is contained in:
2025-05-30 19:53:20 -04:00
parent 7c92e5ce18
commit 13fba60637
4 changed files with 3365 additions and 0 deletions
+657
View File
@@ -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
<!-- Add to <head> section -->
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<link href="/static/css/family-tree-expansions.css" rel="stylesheet">
<!-- Add before closing </body> tag -->
<script src="/static/js/advanced-tree-layouts.js"></script>
<script src="/static/js/family-tree-search.js"></script>
<script src="/static/js/family-tree-analytics.js"></script>
```
### 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
<div class="layout-controls">
<button onclick="switchLayout('hierarchical')" class="layout-btn active">
<i class="fas fa-sitemap"></i> Hierarchical
</button>
<button onclick="switchLayout('radial')" class="layout-btn">
<i class="fas fa-sun"></i> Radial
</button>
<button onclick="switchLayout('force-directed')" class="layout-btn">
<i class="fas fa-project-diagram"></i> Force-Directed
</button>
<button onclick="switchLayout('timeline')" class="layout-btn">
<i class="fas fa-timeline"></i> Timeline
</button>
</div>
```
## 🎨 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 = `<strong>${title}:</strong> ${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.
File diff suppressed because it is too large Load Diff
@@ -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 = `
<div class="analytics-header">
<h3><i class="fas fa-chart-bar"></i> Family Tree Analytics</h3>
<div class="analytics-controls">
<button id="refresh-analytics" class="analytics-btn" title="Refresh statistics">
<i class="fas fa-sync-alt"></i> Refresh
</button>
<button id="export-analytics" class="analytics-btn" title="Export analytics report">
<i class="fas fa-download"></i> Export Report
</button>
<button id="toggle-analytics" class="analytics-btn" title="Toggle analytics panel">
<i class="fas fa-eye"></i> Toggle
</button>
</div>
</div>
<div class="analytics-content">
<!-- Overview Statistics -->
<div class="stats-overview">
<div class="stat-card total-persons">
<div class="stat-icon"><i class="fas fa-users"></i></div>
<div class="stat-info">
<div class="stat-number" id="total-persons">0</div>
<div class="stat-label">Total Persons</div>
</div>
</div>
<div class="stat-card male-count">
<div class="stat-icon"><i class="fas fa-male"></i></div>
<div class="stat-info">
<div class="stat-number" id="male-count">0</div>
<div class="stat-label">Male</div>
</div>
</div>
<div class="stat-card female-count">
<div class="stat-icon"><i class="fas fa-female"></i></div>
<div class="stat-info">
<div class="stat-number" id="female-count">0</div>
<div class="stat-label">Female</div>
</div>
</div>
<div class="stat-card generations-count">
<div class="stat-icon"><i class="fas fa-layer-group"></i></div>
<div class="stat-info">
<div class="stat-number" id="generations-count">0</div>
<div class="stat-label">Generations</div>
</div>
</div>
<div class="stat-card families-count">
<div class="stat-icon"><i class="fas fa-home"></i></div>
<div class="stat-info">
<div class="stat-number" id="families-count">0</div>
<div class="stat-label">Families</div>
</div>
</div>
<div class="stat-card avg-children">
<div class="stat-icon"><i class="fas fa-baby"></i></div>
<div class="stat-info">
<div class="stat-number" id="avg-children">0</div>
<div class="stat-label">Avg Children</div>
</div>
</div>
</div>
<!-- Chart Tabs -->
<div class="chart-tabs">
<button class="tab-btn active" data-tab="demographic">Demographics</button>
<button class="tab-btn" data-tab="generational">Generations</button>
<button class="tab-btn" data-tab="relationships">Relationships</button>
<button class="tab-btn" data-tab="timeline">Timeline</button>
<button class="tab-btn" data-tab="longevity">Longevity</button>
</div>
<!-- Chart Panels -->
<div class="chart-panels">
<!-- Demographics Panel -->
<div class="chart-panel active" id="demographic-panel">
<div class="panel-header">
<h4>Demographic Analysis</h4>
<div class="chart-options">
<select id="demographic-chart-type">
<option value="pie">Pie Chart</option>
<option value="bar">Bar Chart</option>
<option value="doughnut">Doughnut Chart</option>
</select>
</div>
</div>
<div class="chart-container">
<canvas id="demographic-chart"></canvas>
</div>
<div class="chart-insights">
<div class="insight-item">
<strong>Gender Ratio:</strong> <span id="gender-ratio">Loading...</span>
</div>
<div class="insight-item">
<strong>Most Common Names:</strong> <span id="common-names">Loading...</span>
</div>
</div>
</div>
<!-- Generational Panel -->
<div class="chart-panel" id="generational-panel">
<div class="panel-header">
<h4>Generational Distribution</h4>
<div class="chart-options">
<select id="generational-metric">
<option value="count">Person Count</option>
<option value="lifespan">Average Lifespan</option>
<option value="children">Children per Generation</option>
</select>
</div>
</div>
<div class="chart-container">
<canvas id="generational-chart"></canvas>
</div>
<div class="generation-details">
<div id="generation-breakdown"></div>
</div>
</div>
<!-- Relationships Panel -->
<div class="chart-panel" id="relationships-panel">
<div class="panel-header">
<h4>Family Relationships</h4>
</div>
<div class="relationship-metrics">
<div class="metric-grid">
<div class="metric-item">
<div class="metric-value" id="married-couples">0</div>
<div class="metric-label">Married Couples</div>
</div>
<div class="metric-item">
<div class="metric-value" id="single-parents">0</div>
<div class="metric-label">Single Parents</div>
</div>
<div class="metric-item">
<div class="metric-value" id="childless-couples">0</div>
<div class="metric-label">Childless Couples</div>
</div>
<div class="metric-item">
<div class="metric-value" id="largest-family">0</div>
<div class="metric-label">Largest Family</div>
</div>
</div>
</div>
<div class="chart-container">
<canvas id="relationships-chart"></canvas>
</div>
</div>
<!-- Timeline Panel -->
<div class="chart-panel" id="timeline-panel">
<div class="panel-header">
<h4>Biblical Timeline Analysis</h4>
<div class="chart-options">
<select id="timeline-view">
<option value="births">Birth Timeline</option>
<option value="lifespans">Lifespan Overview</option>
<option value="generations">Generation Overlap</option>
</select>
</div>
</div>
<div class="chart-container timeline-container">
<canvas id="timeline-chart"></canvas>
</div>
<div class="timeline-insights">
<div class="insight-grid">
<div class="insight-card">
<h5>Longest Lifespan</h5>
<div id="longest-lived">Loading...</div>
</div>
<div class="insight-card">
<h5>Shortest Lifespan</h5>
<div id="shortest-lived">Loading...</div>
</div>
<div class="insight-card">
<h5>Average Lifespan</h5>
<div id="average-lifespan">Loading...</div>
</div>
</div>
</div>
</div>
<!-- Longevity Panel -->
<div class="chart-panel" id="longevity-panel">
<div class="panel-header">
<h4>Longevity Analysis</h4>
</div>
<div class="chart-container">
<canvas id="longevity-chart"></canvas>
</div>
<div class="longevity-trends">
<div id="longevity-trends-content"></div>
</div>
</div>
</div>
<!-- Detailed Insights Section -->
<div class="detailed-insights">
<h4>Detailed Insights</h4>
<div class="insights-grid">
<div class="insight-section">
<h5>Family Patterns</h5>
<ul id="family-patterns"></ul>
</div>
<div class="insight-section">
<h5>Notable Statistics</h5>
<ul id="notable-stats"></ul>
</div>
<div class="insight-section">
<h5>Genealogical Insights</h5>
<ul id="genealogical-insights"></ul>
</div>
</div>
</div>
</div>
`;
// 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 >
@@ -0,0 +1,702 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Enhanced Family Tree - KJV Study</title>
<!-- Core Styles -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<!-- D3.js for advanced visualizations -->
<script src="https://d3js.org/d3.v7.min.js"></script>
<!-- Chart.js for analytics -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<!-- Custom Styles -->
<link href="/static/css/family-tree-expansions.css" rel="stylesheet">
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
margin: 0;
padding: 20px;
}
.main-container {
max-width: 1400px;
margin: 0 auto;
background: white;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
overflow: hidden;
}
.header-section {
background: linear-gradient(135deg, #007bff 0%, #6610f2 100%);
color: white;
padding: 30px;
text-align: center;
}
.header-section h1 {
margin: 0;
font-size: 2.5rem;
font-weight: 300;
}
.header-section p {
margin: 10px 0 0 0;
opacity: 0.9;
font-size: 1.1rem;
}
.content-wrapper {
padding: 30px;
}
.layout-selector {
display: flex;
justify-content: center;
gap: 15px;
margin-bottom: 30px;
padding: 20px;
background: #f8f9fa;
border-radius: 10px;
}
.layout-option {
padding: 12px 24px;
border: 2px solid #dee2e6;
background: white;
color: #495057;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 500;
}
.layout-option.active {
background: #007bff;
color: white;
border-color: #007bff;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
}
.layout-option:hover {
border-color: #007bff;
transform: translateY(-1px);
}
.visualization-container {
background: white;
border: 1px solid #dee2e6;
border-radius: 12px;
min-height: 600px;
margin-bottom: 30px;
position: relative;
overflow: hidden;
}
.tree-svg {
width: 100%;
height: 600px;
}
.loading-indicator {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
color: #6c757d;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #007bff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 15px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.feature-showcase {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-top: 30px;
}
.feature-card {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 10px;
padding: 20px;
transition: all 0.3s ease;
}
.feature-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.feature-icon {
font-size: 2rem;
color: #007bff;
margin-bottom: 15px;
}
.feature-title {
font-size: 1.2rem;
font-weight: 600;
margin-bottom: 10px;
color: #495057;
}
.feature-description {
color: #6c757d;
line-height: 1.6;
}
.demo-controls {
display: flex;
justify-content: center;
gap: 15px;
margin: 20px 0;
flex-wrap: wrap;
}
.demo-btn {
padding: 10px 20px;
border: none;
background: #6c757d;
color: white;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 14px;
}
.demo-btn:hover {
background: #5a6268;
transform: translateY(-1px);
}
.demo-btn.primary {
background: #007bff;
}
.demo-btn.primary:hover {
background: #0056b3;
}
</style>
</head>
<body>
<div class="main-container">
<!-- Header Section -->
<div class="header-section">
<h1><i class="fas fa-sitemap"></i> Enhanced Family Tree Visualization</h1>
<p>Advanced biblical genealogy exploration with multiple layouts, analytics, and interactive features</p>
</div>
<!-- Content Wrapper -->
<div class="content-wrapper">
<!-- Layout Selector -->
<div class="layout-selector">
<div class="layout-option active" data-layout="hierarchical">
<i class="fas fa-sitemap"></i> Hierarchical
</div>
<div class="layout-option" data-layout="radial">
<i class="fas fa-sun"></i> Radial
</div>
<div class="layout-option" data-layout="force-directed">
<i class="fas fa-project-diagram"></i> Force-Directed
</div>
<div class="layout-option" data-layout="timeline">
<i class="fas fa-timeline"></i> Timeline
</div>
<div class="layout-option" data-layout="circular-pedigree">
<i class="fas fa-circle-notch"></i> Circular Pedigree
</div>
</div>
<!-- Demo Controls -->
<div class="demo-controls">
<button class="demo-btn primary" id="load-sample-data">
<i class="fas fa-database"></i> Load Sample Data
</button>
<button class="demo-btn" id="center-view">
<i class="fas fa-crosshairs"></i> Center View
</button>
<button class="demo-btn" id="export-view">
<i class="fas fa-download"></i> Export
</button>
<button class="demo-btn" id="toggle-analytics">
<i class="fas fa-chart-bar"></i> Analytics
</button>
<button class="demo-btn" id="toggle-search">
<i class="fas fa-search"></i> Search
</button>
</div>
<!-- Visualization Container -->
<div class="visualization-container" id="viz-container">
<div class="loading-indicator" id="loading-indicator">
<div class="loading-spinner"></div>
<div>Loading family tree data...</div>
</div>
<svg class="tree-svg" id="main-tree-svg"></svg>
</div>
<!-- Search Container (Will be dynamically inserted) -->
<div id="search-container"></div>
<!-- Analytics Container (Will be dynamically inserted) -->
<div id="analytics-container"></div>
<!-- Feature Showcase -->
<div class="feature-showcase">
<div class="feature-card">
<div class="feature-icon"><i class="fas fa-search-plus"></i></div>
<div class="feature-title">Advanced Search</div>
<div class="feature-description">
Comprehensive search capabilities with filtering by name, gender, generation, and biblical references.
Features real-time highlighting and bookmark management.
</div>
</div>
<div class="feature-card">
<div class="feature-icon"><i class="fas fa-chart-line"></i></div>
<div class="feature-title">Statistical Analytics</div>
<div class="feature-description">
Interactive charts and insights showing demographic patterns, generational trends,
family relationships, and biblical timeline analysis.
</div>
</div>
<div class="feature-card">
<div class="feature-icon"><i class="fas fa-project-diagram"></i></div>
<div class="feature-title">Multiple Layouts</div>
<div class="feature-description">
Choose from hierarchical, radial, force-directed, timeline, and circular pedigree layouts.
Each optimized for different exploration patterns.
</div>
</div>
<div class="feature-card">
<div class="feature-icon"><i class="fas fa-route"></i></div>
<div class="feature-title">Smart Navigation</div>
<div class="feature-description">
Breadcrumb trails, navigation history, bookmarking system, and quick access controls
for efficient family tree exploration.
</div>
</div>
<div class="feature-card">
<div class="feature-icon"><i class="fas fa-mobile-alt"></i></div>
<div class="feature-title">Responsive Design</div>
<div class="feature-description">
Fully responsive interface that adapts to desktop, tablet, and mobile devices
with touch-friendly controls and optimized layouts.
</div>
</div>
<div class="feature-card">
<div class="feature-icon"><i class="fas fa-download"></i></div>
<div class="feature-title">Export Capabilities</div>
<div class="feature-description">
Export family trees as high-quality images, PDF documents, or structured data formats.
Perfect for sharing and printing.
</div>
</div>
</div>
</div>
</div>
<!-- Core Scripts -->
<script src="/static/js/advanced-tree-layouts.js"></script>
<script src="/static/js/family-tree-search.js"></script>
<script src="/static/js/family-tree-analytics.js"></script>
<!-- Main Integration Script -->
<script>
// Enhanced Family Tree Integration
class EnhancedFamilyTree {
constructor() {
this.familyData = {};
this.currentLayout = 'hierarchical';
this.currentPerson = null;
this.layoutEngine = null;
this.searchEngine = null;
this.analyticsEngine = null;
this.initialize();
}
async initialize() {
// Initialize components
this.setupEventListeners();
await this.loadSampleData();
this.initializeComponents();
this.hideLoading();
}
setupEventListeners() {
// Layout selector
document.querySelectorAll('.layout-option').forEach(option => {
option.addEventListener('click', (e) => {
this.switchLayout(e.target.dataset.layout);
});
});
// Demo controls
document.getElementById('load-sample-data').addEventListener('click', () => {
this.loadSampleData();
});
document.getElementById('center-view').addEventListener('click', () => {
if (this.layoutEngine) {
this.layoutEngine.centerView();
}
});
document.getElementById('export-view').addEventListener('click', () => {
this.exportCurrentView();
});
document.getElementById('toggle-analytics').addEventListener('click', () => {
this.toggleAnalytics();
});
document.getElementById('toggle-search').addEventListener('click', () => {
this.toggleSearch();
});
}
async loadSampleData() {
// Sample biblical family data
this.familyData = {
'adam': {
name: 'Adam',
title: 'First Man',
description: 'The first human being created by God',
children: ['cain', 'abel', 'seth'],
parents: [],
spouse: 'Eve',
birth_year: '4004 BC',
death_year: '3074 BC',
age_at_death: '930 years',
verses: [
{ reference: 'Genesis 2:7', text: 'And the LORD God formed man of the dust of the ground...' },
{ reference: 'Genesis 5:5', text: 'And all the days that Adam lived were nine hundred and thirty years...' }
]
},
'eve': {
name: 'Eve',
title: 'First Woman',
description: 'The first woman, created from Adam\'s rib',
children: ['cain', 'abel', 'seth'],
parents: [],
spouse: 'Adam',
birth_year: '4004 BC',
death_year: 'Unknown',
age_at_death: 'Unknown',
verses: [
{ reference: 'Genesis 2:22', text: 'And the rib, which the LORD God had taken from man, made he a woman...' },
{ reference: 'Genesis 3:20', text: 'And Adam called his wife\'s name Eve; because she was the mother of all living.' }
]
},
'cain': {
name: 'Cain',
title: 'First Son',
description: 'First son of Adam and Eve, farmer and first murderer',
children: ['enoch'],
parents: ['adam', 'eve'],
spouse: null,
birth_year: '3874 BC',
death_year: 'Unknown',
age_at_death: 'Unknown',
verses: [
{ reference: 'Genesis 4:1', text: 'And Adam knew Eve his wife; and she conceived, and bare Cain...' }
]
},
'abel': {
name: 'Abel',
title: 'Second Son',
description: 'Second son of Adam and Eve, shepherd, killed by Cain',
children: [],
parents: ['adam', 'eve'],
spouse: null,
birth_year: '3871 BC',
death_year: '3796 BC',
age_at_death: '75 years',
verses: [
{ reference: 'Genesis 4:2', text: 'And she again bare his brother Abel. And Abel was a keeper of sheep...' }
]
},
'seth': {
name: 'Seth',
title: 'Third Son',
description: 'Third son of Adam and Eve, ancestor of Noah',
children: ['enos'],
parents: ['adam', 'eve'],
spouse: null,
birth_year: '3769 BC',
death_year: '2857 BC',
age_at_death: '912 years',
verses: [
{ reference: 'Genesis 4:25', text: 'And Adam knew his wife again; and she bare a son, and called his name Seth...' }
]
},
'enoch': {
name: 'Enoch',
title: 'Son of Cain',
description: 'Son of Cain, first city builder',
children: [],
parents: ['cain'],
spouse: null,
birth_year: '3700 BC',
death_year: 'Unknown',
age_at_death: 'Unknown',
verses: []
},
'enos': {
name: 'Enos',
title: 'Son of Seth',
description: 'Son of Seth, grandson of Adam',
children: ['cainan'],
parents: ['seth'],
spouse: null,
birth_year: '3679 BC',
death_year: '2769 BC',
age_at_death: '905 years',
verses: []
},
'cainan': {
name: 'Cainan',
title: 'Son of Enos',
description: 'Son of Enos, great-grandson of Adam',
children: [],
parents: ['enos'],
spouse: null,
birth_year: '3609 BC',
death_year: '2699 BC',
age_at_death: '910 years',
verses: []
}
};
// Simulate loading delay
await new Promise(resolve => setTimeout(resolve, 1000));
// Initialize with Adam as root
this.currentPerson = 'adam';
this.renderCurrentLayout();
}
initializeComponents() {
const svg = d3.select('#main-tree-svg');
// Initialize layout engine
this.layoutEngine = new AdvancedTreeLayouts(svg, this.familyData);
this.layoutEngine.setSelectPersonCallback((personId) => {
this.selectPerson(personId);
});
// Initialize search engine
this.searchEngine = new FamilyTreeSearch(this.familyData, this.layoutEngine);
// Initialize analytics engine
this.analyticsEngine = new FamilyTreeAnalytics(this.familyData);
// Initially hide search and analytics
this.hideSearch();
this.hideAnalytics();
}
switchLayout(layoutType) {
// Update UI
document.querySelectorAll('.layout-option').forEach(option => {
option.classList.toggle('active', option.dataset.layout === layoutType);
});
this.currentLayout = layoutType;
this.renderCurrentLayout();
}
renderCurrentLayout() {
if (!this.layoutEngine || !this.currentPerson) return;
this.showLoading();
// Small delay to show loading indicator
setTimeout(() => {
switch(this.currentLayout) {
case 'hierarchical':
// Use existing D3 tree implementation
this.renderHierarchicalLayout();
break;
case 'radial':
this.layoutEngine.renderRadialLayout(this.currentPerson);
break;
case 'force-directed':
this.layoutEngine.renderForceDirectedLayout(this.currentPerson);
break;
case 'timeline':
this.layoutEngine.renderTimelineLayout(this.currentPerson);
break;
case 'circular-pedigree':
this.layoutEngine.renderCircularPedigreeLayout(this.currentPerson);
break;
}
this.hideLoading();
}, 300);
}
renderHierarchicalLayout() {
// Use the existing hierarchical tree implementation
// This would integrate with the existing family tree code
const svg = d3.select('#main-tree-svg');
svg.selectAll('*').remove();
// Add sample hierarchical tree
const g = svg.append('g');
const nodes = [
{ name: 'Adam', x: 400, y: 100, id: 'adam' },
{ name: 'Eve', x: 500, y: 100, id: 'eve' },
{ name: 'Cain', x: 300, y: 200, id: 'cain' },
{ name: 'Abel', x: 400, y: 200, id: 'abel' },
{ name: 'Seth', x: 500, y: 200, id: 'seth' }
];
const nodeGroups = g.selectAll('.node')
.data(nodes)
.enter()
.append('g')
.attr('class', 'node')
.attr('transform', d => `translate(${d.x}, ${d.y})`)
.on('click', (event, d) => this.selectPerson(d.id));
nodeGroups.append('circle')
.attr('r', 8)
.attr('fill', d => d.id === this.currentPerson ? '#007bff' : '#6c757d');
nodeGroups.append('text')
.attr('dy', -15)
.attr('text-anchor', 'middle')
.attr('font-size', '12px')
.text(d => d.name);
}
selectPerson(personId) {
this.currentPerson = personId;
this.renderCurrentLayout();
// Update analytics if visible
if (this.analyticsEngine && !document.getElementById('analytics-container').style.display === 'none') {
this.analyticsEngine.calculateStatistics();
}
}
exportCurrentView() {
const svg = document.getElementById('main-tree-svg');
const svgData = new XMLSerializer().serializeToString(svg);
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
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-${new Date().getTime()}.png`;
link.href = canvas.toDataURL();
link.click();
};
img.src = 'data:image/svg+xml;base64,' + btoa(svgData);
}
toggleAnalytics() {
const container = document.getElementById('analytics-container');
if (container.style.display === 'none') {
this.showAnalytics();
} else {
this.hideAnalytics();
}
}
toggleSearch() {
const container = document.getElementById('search-container');
if (container.style.display === 'none') {
this.showSearch();
} else {
this.hideSearch();
}
}
showAnalytics() {
const container = document.getElementById('analytics-container');
container.style.display = 'block';
container.innerHTML = '';
if (this.analyticsEngine) {
// Re-initialize analytics in the container
const analyticsElement = this.analyticsEngine.createAnalyticsInterface();
container.appendChild(analyticsElement);
}
}
hideAnalytics() {
document.getElementById('analytics-container').style.display = 'none';
}
showSearch() {
const container = document.getElementById('search-container');
container.style.display = 'block';
}
hideSearch() {
document.getElementById('search-container').style.display = 'none';
}
showLoading() {
document.getElementById('loading-indicator').style.display = 'block';
}
hideLoading() {
document.getElementById('loading-indicator').style.display = 'none';
}
}
// Initialize the enhanced family tree when page loads
document.addEventListener('DOMContentLoaded', () => {
new EnhancedFamilyTree();
});
// Global helper functions for integration with existing code
window.selectPerson = function(personId) {
if (window.familyTreeInstance) {
window.familyTreeInstance.selectPerson(personId);
}
};
// Export for global access
window.EnhancedFamilyTree = EnhancedFamilyTree;
</script>
</body>
</html>