Skip to main content

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

Build docs developers (and LLMs) love