Overview
SimulationBank provides real-time visualization of queue operations, teller status, and performance metrics. Visualizations update dynamically as the simulation progresses.Visualization Components
Queue Visualization
QueueVisualizer displays customers waiting in a visual queue:function QueueVisualizer({ customers }) {
return (
<div className="queue-container">
<div className="queue-header">
<h3>Waiting Queue</h3>
<span className="queue-count">{customers.length} customers</span>
</div>
<div className="queue-line flex gap-2 overflow-x-auto py-4">
{customers.map((customer, idx) => (
<QueueNode
key={customer.id}
customer={customer}
position={idx + 1}
/>
))}
</div>
{customers.length === 0 && (
<div className="empty-queue text-gray-400 text-center py-8">
No customers waiting
</div>
)}
</div>
);
}
Priority Color Coding
function QueueNode({ customer, position }) {
const getPriorityColor = (priority) => {
switch(priority) {
case 1: return {
bg: 'bg-red-500',
border: 'border-red-600',
text: 'text-red-900'
};
case 2: return {
bg: 'bg-yellow-500',
border: 'border-yellow-600',
text: 'text-yellow-900'
};
case 3: return {
bg: 'bg-green-500',
border: 'border-green-600',
text: 'text-green-900'
};
}
};
const colors = getPriorityColor(customer.priority);
const waitTime = (Date.now() - customer.arrival_time * 1000) / 1000;
return (
<div className={`queue-node ${colors.bg} ${colors.border} border-2 rounded-lg p-3 min-w-[120px]`}>
<div className="font-mono text-sm font-bold">{customer.id}</div>
<div className="text-xs">Position: #{position}</div>
<div className="text-xs">Wait: {waitTime.toFixed(1)}s</div>
<div className="text-xs truncate">{customer.transaction_type}</div>
</div>
);
}
Priority Legend
function PriorityLegend() {
return (
<div className="flex gap-4 mb-4">
<div className="flex items-center gap-2">
<div className="w-4 h-4 bg-red-500 rounded"></div>
<span className="text-sm">High Priority</span>
</div>
<div className="flex items-center gap-2">
<div className="w-4 h-4 bg-yellow-500 rounded"></div>
<span className="text-sm">Medium Priority</span>
</div>
<div className="flex items-center gap-2">
<div className="w-4 h-4 bg-green-500 rounded"></div>
<span className="text-sm">Low Priority</span>
</div>
</div>
);
}
Teller Visualization
Teller Grid
function TellerGrid({ tellers }) {
return (
<div className="teller-grid">
<h3 className="text-xl font-semibold mb-4">Teller Windows</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{Object.values(tellers).map(teller => (
<TellerCard key={teller.id} teller={teller} />
))}
</div>
</div>
);
}
Teller Status Indicator
function TellerCard({ teller }) {
const getStatusStyle = (status) => {
switch(status) {
case 'IDLE':
return {
bg: 'bg-green-100',
border: 'border-green-500',
badge: 'bg-green-500',
text: 'Available'
};
case 'BUSY':
return {
bg: 'bg-blue-100',
border: 'border-blue-500',
badge: 'bg-blue-500',
text: 'Busy'
};
case 'BROKEN':
return {
bg: 'bg-red-100',
border: 'border-red-500',
badge: 'bg-red-500',
text: 'Broken'
};
}
};
const style = getStatusStyle(teller.status);
return (
<div className={`teller-card ${style.bg} ${style.border} border-2 rounded-lg p-4`}>
<div className="flex justify-between items-center mb-3">
<h4 className="text-lg font-semibold">{teller.id}</h4>
<span className={`${style.badge} text-white px-2 py-1 rounded text-xs`}>
{style.text}
</span>
</div>
{teller.current_customer ? (
<div className="customer-serving bg-white bg-opacity-70 rounded p-2 mb-2">
<div className="text-sm font-medium">Serving:</div>
<div className="font-mono text-sm">{teller.current_customer.id}</div>
<div className="text-xs text-gray-600">
{teller.current_customer.transaction_type}
</div>
</div>
) : (
<div className="text-sm text-gray-500 italic mb-2">
No customer
</div>
)}
<div className="text-sm text-gray-700">
Sessions served: <span className="font-semibold">{teller.sessions_served}</span>
</div>
</div>
);
}
Metrics Charts
Charts are implemented using custom SVG or could use libraries like Recharts/Chart.js.Wait Time Chart
function WaitTimeChart({ data }) {
// data = [{ time: 0, wait_time: 0 }, { time: 10, wait_time: 5.2 }, ...]
const maxWait = Math.max(...data.map(d => d.wait_time), 1);
const maxTime = Math.max(...data.map(d => d.time), 1);
return (
<div className="chart-container">
<h4 className="text-lg font-semibold mb-2">Wait Time Over Time</h4>
<svg width="100%" height="200" viewBox="0 0 500 200">
<g className="grid">
{[0, 50, 100, 150, 200].map(y => (
<line
key={y}
x1="0"
y1={y}
x2="500"
y2={y}
stroke="#e5e7eb"
strokeWidth="1"
/>
))}
</g>
<polyline
fill="none"
stroke="#2563eb"
strokeWidth="2"
points={data.map((d, i) =>
`${(d.time / maxTime) * 500},${200 - (d.wait_time / maxWait) * 200}`
).join(' ')}
/>
</svg>
<div className="text-sm text-gray-600 mt-2">
X: Simulation time | Y: Wait time (seconds)
</div>
</div>
);
}
Throughput Chart (Bar Chart)
function ThroughputChart({ data }) {
// data = [{ interval: '0-60s', count: 12 }, { interval: '60-120s', count: 15 }, ...]
const maxCount = Math.max(...data.map(d => d.count), 1);
const barWidth = 500 / data.length;
return (
<div className="chart-container">
<h4 className="text-lg font-semibold mb-2">Throughput</h4>
<svg width="100%" height="200" viewBox="0 0 500 200">
{data.map((d, i) => (
<g key={i}>
<rect
x={i * barWidth}
y={200 - (d.count / maxCount) * 180}
width={barWidth * 0.8}
height={(d.count / maxCount) * 180}
fill="#3b82f6"
/>
<text
x={i * barWidth + (barWidth * 0.4)}
y="195"
fontSize="10"
textAnchor="middle"
>
{d.interval}
</text>
</g>
))}
</svg>
<div className="text-sm text-gray-600 mt-2">
Customers served per interval
</div>
</div>
);
}
Queue Length Chart (Area Chart)
function QueueLengthChart({ data }) {
// data = [{ time: 0, length: 0 }, { time: 5, length: 3 }, ...]
const maxLength = Math.max(...data.map(d => d.length), 1);
const maxTime = Math.max(...data.map(d => d.time), 1);
const pathData = data.map((d, i) =>
`${i === 0 ? 'M' : 'L'} ${(d.time / maxTime) * 500},${200 - (d.length / maxLength) * 180}`
).join(' ');
const closedPath = `${pathData} L 500,200 L 0,200 Z`;
return (
<div className="chart-container">
<h4 className="text-lg font-semibold mb-2">Queue Length</h4>
<svg width="100%" height="200" viewBox="0 0 500 200">
<path
d={closedPath}
fill="rgba(30, 64, 175, 0.3)"
stroke="#1e40af"
strokeWidth="2"
/>
</svg>
<div className="text-sm text-gray-600 mt-2">
X: Simulation time | Y: Number of customers waiting
</div>
</div>
);
}
Real-Time Updates
Polling and Updating Charts
import { useState, useEffect } from 'react';
function MetricsDashboard() {
const [waitTimeData, setWaitTimeData] = useState([]);
const [queueLengthData, setQueueLengthData] = useState([]);
useEffect(() => {
const interval = setInterval(async () => {
const response = await fetch('/api/metrics/live');
const metrics = await response.json();
// Append new data points
setWaitTimeData(prev => [
...prev,
{ time: metrics.current_time, wait_time: metrics.avg_wait_time }
]);
setQueueLengthData(prev => [
...prev,
{ time: metrics.current_time, length: metrics.customers_waiting }
]);
}, 2000);
return () => clearInterval(interval);
}, []);
return (
<div className="grid grid-cols-2 gap-4">
<WaitTimeChart data={waitTimeData} />
<QueueLengthChart data={queueLengthData} />
</div>
);
}
Data Point Limiting
Prevent unbounded array growth:setWaitTimeData(prev => {
const newData = [...prev, newPoint];
// Keep only last 100 points
return newData.slice(-100);
});
Saturation Report
function SaturationReport({ metrics }) {
const utilization = metrics.teller_utilization || 0;
const saturation = utilization > 0.85 ? 'High' : utilization > 0.5 ? 'Normal' : 'Low';
const statusColor = saturation === 'High' ? 'text-red-600' : saturation === 'Normal' ? 'text-yellow-600' : 'text-green-600';
return (
<div className="saturation-report bg-white rounded-lg p-4 shadow">
<h4 className="text-lg font-semibold mb-3">System Status</h4>
<div className="grid grid-cols-2 gap-4">
<div>
<div className="text-sm text-gray-600">Utilization</div>
<div className="text-2xl font-bold">{(utilization * 100).toFixed(1)}%</div>
</div>
<div>
<div className="text-sm text-gray-600">Saturation</div>
<div className={`text-2xl font-bold ${statusColor}`}>{saturation}</div>
</div>
<div>
<div className="text-sm text-gray-600">Avg Wait Time</div>
<div className="text-2xl font-bold">{metrics.avg_wait_time?.toFixed(1) || 0}s</div>
</div>
<div>
<div className="text-sm text-gray-600">Throughput</div>
<div className="text-2xl font-bold">{metrics.customers_per_hour?.toFixed(1) || 0}/hr</div>
</div>
</div>
</div>
);
}
Animation and Transitions
Customer Movement Animation
.queue-node {
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
Status Change Animation
.teller-card {
transition: all 0.3s ease;
}
.teller-card.busy {
box-shadow: 0 0 20px rgba(37, 99, 235, 0.5);
}
Responsive Design
function MetricsDashboard() {
return (
<div className="metrics-dashboard">
{/* Stack on mobile, 2 columns on tablet, 2 columns on desktop */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<WaitTimeChart />
<ThroughputChart />
<QueueLengthChart />
<SaturationReport />
</div>
</div>
);
}
Performance Considerations
Memoization
import { useMemo } from 'react';
function WaitTimeChart({ data }) {
const chartPath = useMemo(() => {
// Expensive calculation
return computePath(data);
}, [data]);
return <path d={chartPath} />;
}
Throttling Updates
import { useRef, useEffect } from 'react';
function useThrottledState(initialValue, delay = 500) {
const [value, setValue] = useState(initialValue);
const timeoutRef = useRef(null);
const setThrottledValue = (newValue) => {
if (timeoutRef.current) return;
setValue(newValue);
timeoutRef.current = setTimeout(() => {
timeoutRef.current = null;
}, delay);
};
return [value, setThrottledValue];
}
Next Steps
Component Structure
Complete component documentation
State Management
How data flows through components
Queue Visualization
User guide for queue display features
Metrics Dashboard
Understanding the metrics display