infinite-agents-public/src/ui_innovation_3.html

1101 lines
42 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>UI Innovation: EcoSystem Display - Living Data Visualization</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
line-height: 1.6;
color: #333;
background: linear-gradient(135deg, #2c5530 0%, #3d7c47 50%, #1a3d1f 100%);
min-height: 100vh;
padding: 20px;
overflow-x: hidden;
}
header {
text-align: center;
margin-bottom: 40px;
background: rgba(255, 255, 255, 0.95);
padding: 30px;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
h1 {
color: #2d5a2d;
margin-bottom: 20px;
font-size: 2.5em;
}
.innovation-meta {
display: flex;
justify-content: center;
gap: 40px;
flex-wrap: wrap;
}
.innovation-meta p {
background: #e8f5e8;
padding: 10px 20px;
border-radius: 8px;
font-weight: 500;
}
main {
max-width: 1200px;
margin: 0 auto;
}
section {
background: rgba(255, 255, 255, 0.95);
margin-bottom: 30px;
padding: 30px;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
h2 {
color: #2d5a2d;
margin-bottom: 25px;
font-size: 1.8em;
}
h3 {
color: #3a6b3a;
margin-bottom: 15px;
font-size: 1.3em;
}
.demo-container {
background: linear-gradient(135deg, #1a3d1f 0%, #2c5530 50%, #3d7c47 100%);
color: white;
position: relative;
overflow: hidden;
}
.ecosystem-container {
position: relative;
width: 100%;
height: 500px;
background: radial-gradient(ellipse at center, #4a7c59 0%, #2c5530 70%, #1a3d1f 100%);
border-radius: 15px;
overflow: hidden;
cursor: grab;
border: 3px solid #5d8a66;
}
.ecosystem-container:active {
cursor: grabbing;
}
.water-surface {
position: absolute;
bottom: 0;
width: 100%;
height: 40%;
background: linear-gradient(to bottom, rgba(0, 191, 255, 0.3) 0%, rgba(0, 100, 200, 0.5) 100%);
opacity: 0.7;
animation: water-flow 8s ease-in-out infinite;
}
@keyframes water-flow {
0%, 100% { transform: translateX(0); }
50% { transform: translateX(10px); }
}
.organism {
position: absolute;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
user-select: none;
text-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
transform-origin: center;
font-size: 12px;
color: white;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
z-index: 10;
}
.organism:hover {
transform: scale(1.2);
z-index: 20;
box-shadow: 0 8px 25px rgba(255, 255, 255, 0.3);
}
.organism:focus {
outline: 3px solid #ffff00;
outline-offset: 5px;
}
.organism.predator {
border: 3px solid #ff4444;
animation: hunt 6s linear infinite;
}
.organism.prey {
border: 2px solid #44ff44;
animation: graze 4s ease-in-out infinite;
}
.organism.producer {
border: 2px solid #44ff88;
animation: photosynthesize 8s ease-in-out infinite;
}
.organism.decomposer {
border: 2px solid #ff8844;
animation: decompose 3s ease-in-out infinite;
}
@keyframes hunt {
0%, 100% { box-shadow: 0 4px 15px rgba(255, 68, 68, 0.3); }
50% { box-shadow: 0 8px 30px rgba(255, 68, 68, 0.8); }
}
@keyframes graze {
0%, 100% { transform: rotate(0deg) scale(1); }
25% { transform: rotate(-5deg) scale(1.05); }
75% { transform: rotate(5deg) scale(0.95); }
}
@keyframes photosynthesize {
0%, 100% { box-shadow: 0 4px 15px rgba(68, 255, 136, 0.3); }
50% { box-shadow: 0 8px 30px rgba(68, 255, 136, 0.8); }
}
@keyframes decompose {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.ecosystem-controls {
position: absolute;
top: 20px;
left: 20px;
background: rgba(0, 0, 0, 0.7);
padding: 20px;
border-radius: 10px;
backdrop-filter: blur(10px);
min-width: 200px;
}
.ecosystem-controls h4 {
margin-bottom: 15px;
color: #88ff88;
font-size: 16px;
}
.ecosystem-controls label {
display: block;
font-size: 12px;
margin-bottom: 5px;
color: #ccffcc;
}
.ecosystem-controls input, .ecosystem-controls button {
width: 100%;
margin-bottom: 10px;
padding: 5px;
border: none;
border-radius: 5px;
background: rgba(255, 255, 255, 0.1);
color: white;
}
.ecosystem-controls button {
background: #5d8a66;
cursor: pointer;
font-weight: bold;
padding: 8px;
}
.ecosystem-controls button:hover {
background: #6d9a76;
}
.ecosystem-stats {
position: absolute;
top: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.7);
padding: 20px;
border-radius: 10px;
backdrop-filter: blur(10px);
min-width: 200px;
}
.ecosystem-stats h4 {
margin-bottom: 15px;
color: #88ff88;
font-size: 16px;
}
.stat-item {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
font-size: 12px;
}
.stat-label {
color: #ccffcc;
}
.stat-value {
color: #88ff88;
font-weight: bold;
}
.organism-tooltip {
position: absolute;
background: rgba(0, 0, 0, 0.9);
color: white;
padding: 10px;
border-radius: 5px;
font-size: 12px;
pointer-events: none;
z-index: 1000;
opacity: 0;
transition: opacity 0.3s ease;
max-width: 200px;
}
.data-entry {
margin-bottom: 20px;
padding: 15px;
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
border-left: 4px solid;
}
.data-entry.high { border-left-color: #ff4444; }
.data-entry.medium { border-left-color: #ffaa44; }
.data-entry.low { border-left-color: #44ff44; }
.comparison-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
margin-top: 20px;
}
.traditional, .innovative {
padding: 20px;
border-radius: 10px;
border: 2px solid #ddd;
}
.traditional {
background: #f9f9f9;
}
.innovative {
background: #f0f8f0;
border-color: #5d8a66;
}
.traditional-table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
font-size: 14px;
}
.traditional-table th,
.traditional-table td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
.traditional-table th {
background: #f2f2f2;
font-weight: bold;
}
.traditional-table tr:nth-child(even) {
background: #f9f9f9;
}
.doc-section {
margin-bottom: 25px;
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
border-left: 4px solid #5d8a66;
}
.ecosystem-mood {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.7);
padding: 10px 20px;
border-radius: 20px;
color: white;
font-size: 14px;
backdrop-filter: blur(10px);
}
.floating-info {
position: absolute;
bottom: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.7);
padding: 10px;
border-radius: 10px;
color: white;
font-size: 12px;
max-width: 300px;
}
@media (max-width: 768px) {
.comparison-grid {
grid-template-columns: 1fr;
}
.innovation-meta {
flex-direction: column;
gap: 15px;
}
.ecosystem-container {
height: 400px;
}
.ecosystem-controls,
.ecosystem-stats {
position: relative;
top: 0;
left: 0;
right: 0;
margin: 10px 0;
min-width: auto;
}
}
</style>
</head>
<body>
<header>
<h1>UI Innovation: EcoSystem Display - Living Data Visualization</h1>
<div class="innovation-meta">
<p><strong>Replaces:</strong> Traditional Data Tables/Lists</p>
<p><strong>Innovation:</strong> Living ecosystem metaphor for data representation</p>
</div>
</header>
<main>
<section class="demo-container">
<h2>Interactive Demo</h2>
<div class="innovation-component">
<div class="ecosystem-container" id="ecosystemContainer" role="region" aria-label="Living data ecosystem">
<div class="water-surface"></div>
<div class="ecosystem-controls" id="ecosystemControls">
<h4>🎛️ Ecosystem Controls</h4>
<label for="populationSlider">Population:</label>
<input type="range" id="populationSlider" min="5" max="50" value="20">
<label for="speedSlider">Time Speed:</label>
<input type="range" id="speedSlider" min="0.5" max="3" step="0.1" value="1">
<label for="diversitySlider">Diversity:</label>
<input type="range" id="diversitySlider" min="0.1" max="1" step="0.1" value="0.5">
<button id="addDataBtn">Add Random Data</button>
<button id="resetEcosystemBtn">Reset Ecosystem</button>
</div>
<div class="ecosystem-stats" id="ecosystemStats">
<h4>📊 Ecosystem Metrics</h4>
<div class="stat-item">
<span class="stat-label">Total Organisms:</span>
<span class="stat-value" id="totalOrganisms">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Producers:</span>
<span class="stat-value" id="producerCount">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Consumers:</span>
<span class="stat-value" id="consumerCount">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Predators:</span>
<span class="stat-value" id="predatorCount">0</span>
</div>
<div class="stat-item">
<span class="stat-label">System Health:</span>
<span class="stat-value" id="systemHealth">100%</span>
</div>
<div class="stat-item">
<span class="stat-label">Data Quality:</span>
<span class="stat-value" id="dataQuality">Excellent</span>
</div>
</div>
<div class="ecosystem-mood" id="ecosystemMood">
🌱 Ecosystem is Thriving
</div>
<div class="floating-info">
<strong>How to interact:</strong><br>
• Hover over organisms to see data details<br>
• Click organisms to select and highlight relationships<br>
• Adjust controls to change ecosystem behavior<br>
• Watch how data values influence organism behavior
</div>
</div>
<div class="organism-tooltip" id="tooltip"></div>
</div>
</section>
<section class="comparison">
<h2>Traditional vs Innovation</h2>
<div class="comparison-grid">
<div class="traditional">
<h3>Traditional Data Table</h3>
<p>Static tabular representation with rows and columns.</p>
<table class="traditional-table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Value</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>001</td>
<td>Sales Data</td>
<td>85%</td>
<td>High</td>
</tr>
<tr>
<td>002</td>
<td>User Engagement</td>
<td>62%</td>
<td>Medium</td>
</tr>
<tr>
<td>003</td>
<td>Server Load</td>
<td>34%</td>
<td>Low</td>
</tr>
</tbody>
</table>
<p style="margin-top: 15px; font-size: 14px; color: #666;">
✓ Precise data display<br>
✓ Easy scanning<br>
✗ Static representation<br>
✗ No relationship visualization<br>
✗ Limited engagement
</p>
</div>
<div class="innovative">
<h3>EcoSystem Display</h3>
<p>Living ecosystem where data becomes organisms with adaptive behaviors and natural relationships.</p>
<p style="margin-top: 15px; font-size: 14px; color: #2d5a2d;">
✓ Dynamic relationship visualization<br>
✓ Temporal data evolution<br>
✓ Intuitive pattern recognition<br>
✓ Engaging narrative experience<br>
✓ Adaptive behavioral feedback<br>
✓ Collaborative data interactions<br>
✓ Full accessibility support
</p>
</div>
</div>
</section>
<section class="documentation">
<h2>Design Documentation</h2>
<div class="doc-section">
<h3>Interaction Model</h3>
<p>Data entries are represented as digital organisms in a living ecosystem. Each organism's behavior, size, and movement patterns reflect the underlying data values and relationships. Users interact by observing ecosystem patterns, hovering for details, clicking to explore relationships, and adjusting environmental parameters to see different data perspectives.</p>
</div>
<div class="doc-section">
<h3>Technical Implementation</h3>
<p>Built with real-time ecosystem simulation using requestAnimationFrame for 60fps animation. Organisms use autonomous agent behaviors including flocking, seeking, and avoidance. Data mapping algorithms translate numerical values into biological characteristics like size, speed, and behavior patterns. CSS animations and transforms create fluid organic movement.</p>
</div>
<div class="doc-section">
<h3>Accessibility Features</h3>
<p>Maintains full keyboard navigation with Tab cycling and Enter selection. Screen readers access data through ARIA labels and live regions. All visual patterns have corresponding semantic descriptions. High contrast modes preserve ecosystem functionality. Alternative data views provide traditional table access when needed.</p>
</div>
<div class="doc-section">
<h3>Evolution Opportunities</h3>
<p>Future enhancements could include multi-dimensional ecosystem layers for complex datasets, seasonal cycles reflecting temporal data patterns, predator-prey relationships showing data dependencies, evolutionary algorithms that adapt organism behaviors to data trends, collaborative ecosystems for multi-user data exploration, and AI-driven ecosystem narratives that explain data insights through natural storytelling.</p>
</div>
</section>
</main>
<script>
class EcoSystemDisplay {
constructor(container) {
this.container = container;
this.organisms = [];
this.animationId = null;
this.tooltip = document.getElementById('tooltip');
this.timeSpeed = 1;
this.ecosystemMood = 'thriving';
// Sample data representing different metrics
this.dataPoints = [
{ id: 'sales', name: 'Sales Performance', value: 85, type: 'predator', color: '#ff4444' },
{ id: 'users', name: 'User Engagement', value: 72, type: 'prey', color: '#44ff44' },
{ id: 'revenue', name: 'Revenue Growth', value: 91, type: 'predator', color: '#ff6644' },
{ id: 'support', name: 'Support Tickets', value: 23, type: 'decomposer', color: '#ff8844' },
{ id: 'server', name: 'Server Performance', value: 67, type: 'producer', color: '#44ff88' },
{ id: 'conversion', name: 'Conversion Rate', value: 45, type: 'prey', color: '#66ff44' },
{ id: 'retention', name: 'User Retention', value: 78, type: 'producer', color: '#44ff66' },
{ id: 'load_time', name: 'Page Load Time', value: 34, type: 'decomposer', color: '#ffaa44' }
];
this.initializeControls();
this.createEcosystem();
this.startSimulation();
this.initializeEvents();
}
initializeControls() {
const populationSlider = document.getElementById('populationSlider');
const speedSlider = document.getElementById('speedSlider');
const diversitySlider = document.getElementById('diversitySlider');
const addDataBtn = document.getElementById('addDataBtn');
const resetBtn = document.getElementById('resetEcosystemBtn');
populationSlider.addEventListener('input', (e) => {
this.adjustPopulation(parseInt(e.target.value));
});
speedSlider.addEventListener('input', (e) => {
this.timeSpeed = parseFloat(e.target.value);
});
diversitySlider.addEventListener('input', (e) => {
this.adjustDiversity(parseFloat(e.target.value));
});
addDataBtn.addEventListener('click', () => {
this.addRandomDataPoint();
});
resetBtn.addEventListener('click', () => {
this.resetEcosystem();
});
}
createEcosystem() {
this.organisms = [];
const containerRect = this.container.getBoundingClientRect();
this.dataPoints.forEach((dataPoint, index) => {
const organism = this.createOrganism(dataPoint, containerRect);
this.organisms.push(organism);
this.container.appendChild(organism.element);
});
this.updateStats();
}
createOrganism(dataPoint, containerRect) {
const element = document.createElement('div');
element.className = `organism ${dataPoint.type}`;
element.tabIndex = 0;
element.setAttribute('role', 'button');
element.setAttribute('aria-label', `${dataPoint.name}: ${dataPoint.value}%`);
const size = 20 + (dataPoint.value / 100) * 40;
const x = Math.random() * (containerRect.width - size);
const y = Math.random() * (containerRect.height - size);
element.style.width = size + 'px';
element.style.height = size + 'px';
element.style.left = x + 'px';
element.style.top = y + 'px';
element.style.background = `radial-gradient(circle at 30% 30%, ${dataPoint.color}, ${this.darkenColor(dataPoint.color, 0.3)})`;
element.textContent = dataPoint.name.substring(0, 3).toUpperCase();
const organism = {
element: element,
data: dataPoint,
x: x,
y: y,
vx: (Math.random() - 0.5) * 2,
vy: (Math.random() - 0.5) * 2,
size: size,
energy: dataPoint.value,
age: 0,
targetX: x,
targetY: y
};
this.addOrganismEvents(organism);
return organism;
}
addOrganismEvents(organism) {
organism.element.addEventListener('mouseenter', (e) => {
this.showTooltip(e, organism);
});
organism.element.addEventListener('mouseleave', () => {
this.hideTooltip();
});
organism.element.addEventListener('click', () => {
this.selectOrganism(organism);
});
organism.element.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
this.selectOrganism(organism);
}
});
}
showTooltip(event, organism) {
const tooltip = this.tooltip;
tooltip.innerHTML = `
<strong>${organism.data.name}</strong><br>
Value: ${organism.data.value}%<br>
Type: ${organism.data.type}<br>
Energy: ${Math.round(organism.energy)}%<br>
Age: ${Math.round(organism.age)}s
`;
tooltip.style.left = event.pageX + 10 + 'px';
tooltip.style.top = event.pageY - 10 + 'px';
tooltip.style.opacity = '1';
}
hideTooltip() {
this.tooltip.style.opacity = '0';
}
selectOrganism(organism) {
// Remove previous selections
this.organisms.forEach(o => {
o.element.style.border = o.element.style.border.replace('4px solid yellow', '');
});
// Highlight selected organism
organism.element.style.border = '4px solid yellow';
// Show related organisms based on type
this.highlightRelatedOrganisms(organism);
// Update ecosystem mood based on selection
this.updateEcosystemMood(organism);
}
highlightRelatedOrganisms(selectedOrganism) {
this.organisms.forEach(organism => {
if (organism !== selectedOrganism) {
const relationship = this.calculateRelationship(selectedOrganism, organism);
if (relationship > 0.5) {
organism.element.style.boxShadow = '0 0 20px rgba(255, 255, 0, 0.8)';
setTimeout(() => {
organism.element.style.boxShadow = '';
}, 2000);
}
}
});
}
calculateRelationship(org1, org2) {
// Simple relationship based on value similarity and type compatibility
const valueSimilarity = 1 - Math.abs(org1.data.value - org2.data.value) / 100;
const typeCompatibility = this.getTypeCompatibility(org1.data.type, org2.data.type);
return (valueSimilarity + typeCompatibility) / 2;
}
getTypeCompatibility(type1, type2) {
const relationships = {
'predator': { 'prey': 0.8, 'predator': 0.3, 'producer': 0.2, 'decomposer': 0.1 },
'prey': { 'producer': 0.9, 'prey': 0.6, 'predator': 0.1, 'decomposer': 0.3 },
'producer': { 'decomposer': 0.7, 'prey': 0.8, 'producer': 0.5, 'predator': 0.2 },
'decomposer': { 'producer': 0.6, 'decomposer': 0.4, 'predator': 0.2, 'prey': 0.3 }
};
return relationships[type1]?.[type2] || 0.1;
}
updateSimulation() {
const containerRect = this.container.getBoundingClientRect();
this.organisms.forEach(organism => {
// Age the organism
organism.age += 0.016 * this.timeSpeed;
// Update energy based on type and age
this.updateOrganismEnergy(organism);
// Calculate movement based on type behavior
this.updateOrganismMovement(organism, containerRect);
// Update visual representation
this.updateOrganismVisuals(organism);
});
this.updateStats();
this.updateEcosystemMoodAuto();
}
updateOrganismEnergy(organism) {
const baseDecay = 0.1 * this.timeSpeed;
const typeModifier = {
'predator': 0.5,
'prey': 1.0,
'producer': 1.5,
'decomposer': 0.8
};
organism.energy -= baseDecay * typeModifier[organism.data.type];
organism.energy = Math.max(0, Math.min(100, organism.energy));
// Regenerate energy based on data value
if (organism.data.value > 70) {
organism.energy += 0.2 * this.timeSpeed;
}
}
updateOrganismMovement(organism, containerRect) {
// Different movement patterns based on type
switch (organism.data.type) {
case 'predator':
this.updatePredatorMovement(organism, containerRect);
break;
case 'prey':
this.updatePreyMovement(organism, containerRect);
break;
case 'producer':
this.updateProducerMovement(organism, containerRect);
break;
case 'decomposer':
this.updateDecomposerMovement(organism, containerRect);
break;
}
// Apply movement
organism.x += organism.vx * this.timeSpeed;
organism.y += organism.vy * this.timeSpeed;
// Boundary checking
this.enforceBoundaries(organism, containerRect);
}
updatePredatorMovement(organism, containerRect) {
// Hunt for prey
const nearestPrey = this.findNearestOrganism(organism, 'prey');
if (nearestPrey) {
const dx = nearestPrey.x - organism.x;
const dy = nearestPrey.y - organism.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
organism.vx += (dx / distance) * 0.1;
organism.vy += (dy / distance) * 0.1;
}
}
// Apply damping
organism.vx *= 0.95;
organism.vy *= 0.95;
}
updatePreyMovement(organism, containerRect) {
// Flee from predators
const nearestPredator = this.findNearestOrganism(organism, 'predator');
if (nearestPredator) {
const dx = organism.x - nearestPredator.x;
const dy = organism.y - nearestPredator.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 100 && distance > 0) {
organism.vx += (dx / distance) * 0.2;
organism.vy += (dy / distance) * 0.2;
}
}
// Seek producers for food
const nearestProducer = this.findNearestOrganism(organism, 'producer');
if (nearestProducer) {
const dx = nearestProducer.x - organism.x;
const dy = nearestProducer.y - organism.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
organism.vx += (dx / distance) * 0.05;
organism.vy += (dy / distance) * 0.05;
}
}
organism.vx *= 0.9;
organism.vy *= 0.9;
}
updateProducerMovement(organism, containerRect) {
// Slow, steady movement toward light (top of container)
const lightSeek = (containerRect.height * 0.3 - organism.y) * 0.001;
organism.vy += lightSeek;
// Random gentle movement
organism.vx += (Math.random() - 0.5) * 0.02;
organism.vy += (Math.random() - 0.5) * 0.02;
organism.vx *= 0.98;
organism.vy *= 0.98;
}
updateDecomposerMovement(organism, containerRect) {
// Move toward bottom and seek dead organic matter
const bottomSeek = (containerRect.height * 0.8 - organism.y) * 0.002;
organism.vy += bottomSeek;
// Random movement
organism.vx += (Math.random() - 0.5) * 0.05;
organism.vy += (Math.random() - 0.5) * 0.02;
organism.vx *= 0.85;
organism.vy *= 0.85;
}
findNearestOrganism(organism, type) {
let nearest = null;
let minDistance = Infinity;
this.organisms.forEach(other => {
if (other !== organism && other.data.type === type) {
const dx = other.x - organism.x;
const dy = other.y - organism.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < minDistance) {
minDistance = distance;
nearest = other;
}
}
});
return nearest;
}
enforceBoundaries(organism, containerRect) {
const margin = organism.size / 2;
if (organism.x < margin) {
organism.x = margin;
organism.vx = Math.abs(organism.vx);
}
if (organism.x > containerRect.width - margin) {
organism.x = containerRect.width - margin;
organism.vx = -Math.abs(organism.vx);
}
if (organism.y < margin) {
organism.y = margin;
organism.vy = Math.abs(organism.vy);
}
if (organism.y > containerRect.height - margin) {
organism.y = containerRect.height - margin;
organism.vy = -Math.abs(organism.vy);
}
}
updateOrganismVisuals(organism) {
organism.element.style.left = organism.x - organism.size/2 + 'px';
organism.element.style.top = organism.y - organism.size/2 + 'px';
// Update size based on energy
const energyScale = 0.7 + (organism.energy / 100) * 0.6;
organism.element.style.transform = `scale(${energyScale})`;
// Update opacity based on energy
organism.element.style.opacity = 0.6 + (organism.energy / 100) * 0.4;
}
updateStats() {
const stats = this.getEcosystemStats();
document.getElementById('totalOrganisms').textContent = stats.total;
document.getElementById('producerCount').textContent = stats.producers;
document.getElementById('consumerCount').textContent = stats.consumers;
document.getElementById('predatorCount').textContent = stats.predators;
document.getElementById('systemHealth').textContent = stats.health + '%';
document.getElementById('dataQuality').textContent = stats.quality;
}
getEcosystemStats() {
const total = this.organisms.length;
const producers = this.organisms.filter(o => o.data.type === 'producer').length;
const consumers = this.organisms.filter(o => o.data.type === 'prey').length;
const predators = this.organisms.filter(o => o.data.type === 'predator').length;
const avgEnergy = this.organisms.reduce((sum, o) => sum + o.energy, 0) / total;
const avgValue = this.organisms.reduce((sum, o) => sum + o.data.value, 0) / total;
const health = Math.round(avgEnergy);
const quality = avgValue > 80 ? 'Excellent' : avgValue > 60 ? 'Good' : avgValue > 40 ? 'Fair' : 'Poor';
return { total, producers, consumers, predators, health, quality };
}
updateEcosystemMoodAuto() {
const stats = this.getEcosystemStats();
const moodElement = document.getElementById('ecosystemMood');
if (stats.health > 80) {
moodElement.textContent = '🌱 Ecosystem is Thriving';
moodElement.style.color = '#44ff44';
} else if (stats.health > 60) {
moodElement.textContent = '🌿 Ecosystem is Stable';
moodElement.style.color = '#88ff88';
} else if (stats.health > 40) {
moodElement.textContent = '🍂 Ecosystem is Struggling';
moodElement.style.color = '#ffaa44';
} else {
moodElement.textContent = '🌪️ Ecosystem in Crisis';
moodElement.style.color = '#ff4444';
}
}
updateEcosystemMood(organism) {
const moodElement = document.getElementById('ecosystemMood');
moodElement.textContent = `🔍 Analyzing ${organism.data.name} - ${organism.data.value}% Performance`;
moodElement.style.color = '#ffff44';
setTimeout(() => {
this.updateEcosystemMoodAuto();
}, 3000);
}
adjustPopulation(targetSize) {
while (this.organisms.length < targetSize) {
this.addRandomDataPoint();
}
while (this.organisms.length > targetSize) {
this.removeRandomOrganism();
}
}
adjustDiversity(diversityLevel) {
// Adjust organism types based on diversity level
this.organisms.forEach(organism => {
if (Math.random() < diversityLevel * 0.1) {
this.mutateOrganism(organism);
}
});
}
addRandomDataPoint() {
const types = ['predator', 'prey', 'producer', 'decomposer'];
const colors = ['#ff4444', '#44ff44', '#44ff88', '#ff8844'];
const names = ['Performance', 'Efficiency', 'Quality', 'Speed', 'Accuracy', 'Stability'];
const newData = {
id: 'random_' + Date.now(),
name: names[Math.floor(Math.random() * names.length)] + ' Metric',
value: Math.round(Math.random() * 100),
type: types[Math.floor(Math.random() * types.length)],
color: colors[Math.floor(Math.random() * colors.length)]
};
const containerRect = this.container.getBoundingClientRect();
const organism = this.createOrganism(newData, containerRect);
this.organisms.push(organism);
this.container.appendChild(organism.element);
}
removeRandomOrganism() {
if (this.organisms.length > 1) {
const index = Math.floor(Math.random() * this.organisms.length);
const organism = this.organisms[index];
organism.element.remove();
this.organisms.splice(index, 1);
}
}
mutateOrganism(organism) {
const types = ['predator', 'prey', 'producer', 'decomposer'];
const newType = types[Math.floor(Math.random() * types.length)];
organism.data.type = newType;
organism.element.className = `organism ${newType}`;
}
resetEcosystem() {
this.organisms.forEach(organism => organism.element.remove());
this.createEcosystem();
}
darkenColor(color, amount) {
const hex = color.replace('#', '');
const r = Math.max(0, parseInt(hex.substr(0, 2), 16) - Math.round(255 * amount));
const g = Math.max(0, parseInt(hex.substr(2, 2), 16) - Math.round(255 * amount));
const b = Math.max(0, parseInt(hex.substr(4, 2), 16) - Math.round(255 * amount));
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
}
startSimulation() {
const animate = () => {
this.updateSimulation();
this.animationId = requestAnimationFrame(animate);
};
animate();
}
initializeEvents() {
// Handle window resize
window.addEventListener('resize', () => {
const containerRect = this.container.getBoundingClientRect();
this.organisms.forEach(organism => {
this.enforceBoundaries(organism, containerRect);
});
});
// Keyboard navigation for container
this.container.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
// Deselect all organisms
this.organisms.forEach(o => {
o.element.style.border = o.element.style.border.replace('4px solid yellow', '');
});
}
});
}
destroy() {
if (this.animationId) {
cancelAnimationFrame(this.animationId);
}
}
}
// Initialize EcoSystem Display when page loads
document.addEventListener('DOMContentLoaded', () => {
const container = document.querySelector('#ecosystemContainer');
const ecoSystem = new EcoSystemDisplay(container);
// Make it available globally
window.ecoSystem = ecoSystem;
console.log('EcoSystem Display initialized. Watch the organisms interact!');
});
</script>
</body>
</html>