Physics Lab

RC/LC CIRCUIT

Simulate RC charging, LC oscillations, RLC resonance with real-time voltage/current graphs and animated circuit diagrams.

Circuit Type

Circuit Diagram

Voltage & Current (t)

Source

10 V

Resistance & Inductance

100 Ω
100 mH

Capacitance

100 μF

Simulation

1.0×
Time Constant (τ)
10 ms
Resonant Frequency
1000 Hz
Q Factor
3.16
Current (A)
0 mA
Capacitor Energy
0 μJ
Inductor Energy
0 μJ
Developer Reference

Core Algorithm & Standalone Script

Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.

// Physics Simulation
    class CircuitSimulator {
      constructor() {
        // Parameters
        this.voltage = 10;
        this.resistance = 100;
        this.capacitance = 100e-6; // Convert μF to F
        this.inductance = 100e-3;  // Convert mH to H
        this.frequency = 100;
        this.isAC = false;
        this.switchClosed = false;
        this.time = 0;
        this.speed = 1;
        this.circuitType = 'rc'; // rc, rc-discharge, rl, lc, rlc

        // State
        this.voltageCapacitor = 0;
        this.voltageInductor = 0;
        this.current = 0;
        this.charge = 0;
        this.history = {
          time: [],
          voltage: [],
          current: [],
          voltageR: [],
          voltageC: [],
          voltageL: []
        };
        this.maxHistoryLength = 1000;
      }

      reset() {
        this.time = 0;
        this.voltageCapacitor = 0;
        this.voltageInductor = 0;
        this.current = 0;
        this.charge = 0;
        this.history = {
          time: [],
          voltage: [],
          current: [],
          voltageR: [],
          voltageC: [],
          voltageL: []
        };
      }

      step(dt) {
        if (!this.switchClosed) return;

        const V0 = this.isAC ? this.voltage * Math.sin(2 * Math.PI * this.frequency * this.time) : this.voltage;

        switch (this.circuitType) {
          case 'rc':
            this.stepRC(dt, V0, true);
            break;
          case 'rc-discharge':
            this.stepRC(dt, 0, false);
            break;
          case 'rl':
            this.stepRL(dt, V0);
            break;
          case 'lc':
            this.stepLC(dt, V0);
            break;
          case 'rlc':
            this.stepRLC(dt, V0);
            break;
        }

        this.time += dt;
        this.recordHistory();
      }

      stepRC(dt, V0, isCharging) {
        const tau = this.resistance * this.capacitance;

        if (isCharging) {
          // Charging: dV_c/dt = (V0 - V_c) / tau
          this.voltageCapacitor += ((V0 - this.voltageCapacitor) / tau) * dt;
        } else {
          // Discharging: dV_c/dt = -V_c / tau
          this.voltageCapacitor += (-this.voltageCapacitor / tau) * dt;
        }

        this.current = (V0 - this.voltageCapacitor) / this.resistance;
        this.charge = this.capacitance * this.voltageCapacitor;
      }

      stepRL(dt, V0) {
        const tau = this.inductance / this.resistance;
        // dI/dt = (V0 - I*R) / L
        this.current += ((V0 - this.current * this.resistance) / this.inductance) * dt;
        this.voltageInductor = this.inductance * (V0 - this.current * this.resistance) / this.inductance;
      }

      stepLC(dt, V0) {
        const omega0 = 1 / Math.sqrt(this.inductance * this.capacitance);
        // Using energy-conserving leap-frog
        const Q_max = this.capacitance * V0;
        const phase = omega0 * this.time;
        this.charge = Q_max * Math.cos(phase);
        this.current = -Q_max * omega0 * Math.sin(phase);
        this.voltageCapacitor = this.charge / this.capacitance;
      }

      stepRLC(dt, V0) {
        const omega0 = 1 / Math.sqrt(this.inductance * this.capacitance);
        const zeta = (this.resistance / 2) * Math.sqrt(this.capacitance / this.inductance);
        const omegad = omega0 * Math.sqrt(1 - zeta * zeta);

        const Q_max = this.capacitance * V0;
        const phase = omegad * this.time;
        const envelope = Math.exp(-zeta * omega0 * this.time);

        this.charge = Q_max * envelope * Math.cos(phase);
        this.voltageCapacitor = this.charge / this.capacitance;
        this.current = (Q_max / this.inductance) * envelope *
          (-zeta * omega0 * Math.cos(phase) - omegad * Math.sin(phase));
      }

      recordHistory() {
        if (this.history.time.length >= this.maxHistoryLength) {
          Object.keys(this.history).forEach(key => this.history[key].shift());
        }

        const voltageR = this.current * this.resistance;
        this.history.time.push(this.time);
        this.history.voltage.push(this.voltageCapacitor);
        this.history.current.push(this.current * 1000); // Convert to mA
        this.history.voltageR.push(voltageR);
        this.history.voltageC.push(this.voltageCapacitor);
        this.history.voltageL.push(this.voltageInductor);
      }

      getStats() {
        const tau = this.resistance * this.capacitance;
        const omega0 = 1 / Math.sqrt(this.inductance * this.capacitance);
        const f0 = omega0 / (2 * Math.PI);
        const Q = (1 / this.resistance) * Math.sqrt(this.inductance / this.capacitance);
        const energyC = 0.5 * this.capacitance * this.voltageCapacitor * this.voltageCapacitor * 1e6; // μJ
        const energyL = 0.5 * this.inductance * this.current * this.current * 1e6; // μJ

        return {
          tau: tau * 1000, // Convert to ms
          f0: f0,
          Q: Math.max(0, Q),
          current: this.current * 1000, // mA
          energyC,
          energyL
        };
      }
    }

    // Canvas Rendering
    class CircuitRenderer {
      constructor(schematicCanvas, graphCanvas, simulator) {
        this.schematicCtx = schematicCanvas.getContext('2d');
        this.graphCtx = graphCanvas.getContext('2d');
        this.simulator = simulator;
        this.schematicCanvas = schematicCanvas;
        this.graphCanvas = graphCanvas;

        this.resizeCanvases();
        window.addEventListener('resize', () => this.resizeCanvases());
      }

      resizeCanvases() {
        const schematicRect = this.schematicCanvas.parentElement.getBoundingClientRect();
        const graphRect = this.graphCanvas.parentElement.getBoundingClientRect();

        this.schematicCanvas.width = schematicRect.width;
        this.schematicCanvas.height = 400;
        this.graphCanvas.width = graphRect.width;
        this.graphCanvas.height = 400;
      }

      drawSchematic() {
        const ctx = this.schematicCtx;
        const w = this.schematicCanvas.width;
        const h = this.schematicCanvas.height;

        // Clear
        ctx.fillStyle = '#161616';
        ctx.fillRect(0, 0, w, h);

        const startX = w * 0.1;
        const startY = h * 0.3;
        const circuitW = w * 0.8;
        const circuitH = h * 0.4;

        ctx.strokeStyle = '#e8e0d5';
        ctx.lineWidth = 2;
        ctx.fillStyle = 'transparent';

        // Draw main circuit loop
        ctx.beginPath();
        ctx.moveTo(startX, startY);
        ctx.lineTo(startX + circuitW * 0.2, startY); // to switch
        ctx.lineTo(startX + circuitW * 0.2, startY); // switch
        ctx.lineTo(startX + circuitW * 0.4, startY); // to resistor

        // Resistor (zigzag)
        let x = startX + circuitW * 0.4;
        let y = startY;
        const zigWidth = 30;
        const zigHeight = 15;
        for (let i = 0; i < 4; i++) {
          ctx.lineTo(x + zigWidth/4, y + (i % 2 ? 1 : -1) * zigHeight);
          x += zigWidth/4;
        }

        ctx.lineTo(startX + circuitW * 0.6, startY);
        ctx.lineTo(startX + circuitW * 0.6, startY + circuitH);
        ctx.lineTo(startX, startY + circuitH);
        ctx.stroke();

        // Battery symbol
        ctx.strokeStyle = '#ff2200';
        ctx.lineWidth = 3;
        ctx.beginPath();
        ctx.moveTo(startX, startY);
        ctx.lineTo(startX, startY + circuitH);
        ctx.stroke();

        // Capacitor plates (right side)
        ctx.strokeStyle = '#e8e0d5';
        ctx.lineWidth = 2;
        ctx.beginPath();
        ctx.moveTo(startX + circuitW * 0.65, startY + circuitH * 0.3);
        ctx.lineTo(startX + circuitW * 0.65, startY + circuitH * 0.7);
        ctx.stroke();

        ctx.beginPath();
        ctx.moveTo(startX + circuitW * 0.72, startY + circuitH * 0.3);
        ctx.lineTo(startX + circuitW * 0.72, startY + circuitH * 0.7);
        ctx.stroke();

        // Charge visualization on capacitor
        if (this.simulator.voltageCapacitor > 0.1) {
          ctx.fillStyle = `rgba(255, 34, 0, ${this.simulator.voltageCapacitor / this.simulator.voltage * 0.6})`;
          ctx.fillRect(startX + circuitW * 0.65, startY + circuitH * 0.5 - 30, 7, 60);
        }

        // Animated current flow
        if (this.simulator.switchClosed && Math.abs(this.simulator.current) > 0.001) {
          const numDots = 5;
          const currentMag = Math.abs(this.simulator.current);
          const phase = (this.simulator.time * 5) % 1;

          ctx.fillStyle = `rgba(0, 200, 150, ${Math.min(1, currentMag / 0.1)})`;
          for (let i = 0; i < numDots; i++) {
            const t = (phase + i / numDots) % 1;
            const posX = startX + t * circuitW * 0.6;
            const posY = startY + (posX > startX + circuitW * 0.6 ? circuitH : 0);
            ctx.fillRect(posX - 3, posY - 3, 6, 6);
          }
        }

        // Labels
        ctx.fillStyle = '#555555';
        ctx.font = '12px DM Mono';
        ctx.textAlign = 'center';
        ctx.fillText(`R=${this.simulator.resistance.toFixed(0)}Ω`, startX + circuitW * 0.4, startY - 15);
        ctx.fillText(`C=${this.simulator.capacitance.toFixed(2)*1e6}μF`, startX + circuitW * 0.68, startY + circuitH + 25);
        ctx.fillText(`V=${this.simulator.voltage.toFixed(1)}V`, startX - 30, startY + circuitH * 0.5);

        // Switch status
        ctx.fillStyle = this.simulator.switchClosed ? '#00c896' : '#555555';
        ctx.font = 'bold 14px DM Mono';
        ctx.fillText(this.simulator.switchClosed ? '⊙' : '⊘', startX + circuitW * 0.2, startY - 10);
      }

      drawGraph() {
        const ctx = this.graphCtx;
        const w = this.graphCanvas.width;
        const h = this.graphCanvas.height;

        // Clear
        ctx.fillStyle = '#161616';
        ctx.fillRect(0, 0, w, h);

        const padding = 60;
        const graphW = w - 2 * padding;
        const graphH = h - 2 * padding;

        // Draw grid and axes
        ctx.strokeStyle = '#1e1e1e';
        ctx.lineWidth = 1;
        ctx.beginPath();
        for (let i = 0; i <= 10; i++) {
          const x = padding + (i / 10) * graphW;
          const y = padding + (i / 10) * graphH;
          ctx.moveTo(x, h - padding - 5);
          ctx.lineTo(x, h - padding);
          ctx.moveTo(padding - 5, h - padding - y);
          ctx.lineTo(padding, h - padding - y);
        }
        ctx.stroke();

        ctx.strokeStyle = '#2a2a2a';
        ctx.lineWidth = 0.5;
        ctx.beginPath();
        for (let i = 1; i < 10; i++) {
          const x = padding + (i / 10) * graphW;
          const y = h - padding - (i / 10) * graphH;
          ctx.moveTo(x, padding);
          ctx.lineTo(x, h - padding);
          ctx.moveTo(padding, y);
          ctx.lineTo(w - padding, y);
        }
        ctx.stroke();

        // Axes
        ctx.strokeStyle = '#e8e0d5';
        ctx.lineWidth = 2;
        ctx.beginPath();
        ctx.moveTo(padding, h - padding);
        ctx.lineTo(w - padding, h - padding);
        ctx.moveTo(padding, h - padding);
        ctx.lineTo(padding, padding);
        ctx.stroke();

        // Labels
        ctx.fillStyle = '#555555';
        ctx.font = '11px DM Mono';
        ctx.textAlign = 'center';
        ctx.fillText('Time (s)', w / 2, h - 10);
        ctx.save();
        ctx.translate(15, h / 2);
        ctx.rotate(-Math.PI / 2);
        ctx.fillText('Voltage (V) / Current (mA)', 0, 0);
        ctx.restore();

        // Plot data
        if (this.simulator.history.time.length > 1) {
          const timeData = this.simulator.history.time;
          const maxTime = Math.max(...timeData);
          const minTime = Math.min(...timeData);
          const timeRange = maxTime - minTime || 1;

          // Voltage
          ctx.strokeStyle = '#ff2200';
          ctx.lineWidth = 2;
          ctx.beginPath();
          for (let i = 0; i < timeData.length; i++) {
            const x = padding + ((timeData[i] - minTime) / timeRange) * graphW;
            const volt = this.simulator.history.voltageC[i];
            const maxVolt = Math.max(...this.simulator.history.voltageC) || 1;
            const y = h - padding - (volt / maxVolt * 0.8 * graphH);

            if (i === 0) ctx.moveTo(x, y);
            else ctx.lineTo(x, y);
          }
          ctx.stroke();

          // Current
          ctx.strokeStyle = '#00c896';
          ctx.lineWidth = 2;
          ctx.beginPath();
          for (let i = 0; i < timeData.length; i++) {
            const x = padding + ((timeData[i] - minTime) / timeRange) * graphW;
            const curr = this.simulator.history.current[i];
            const maxCurr = Math.max(...this.simulator.history.current.map(Math.abs)) || 1;
            const y = h - padding - (curr / maxCurr * 0.8 * graphH);

            if (i === 0) ctx.moveTo(x, y);
            else ctx.lineTo(x, y);
          }
          ctx.stroke();
        }

        // Legend
        ctx.font = '11px DM Mono';
        ctx.textAlign = 'left';
        ctx.fillStyle = '#ff2200';
        ctx.fillText('─ Voltage', w - padding - 120, padding + 15);
        ctx.fillStyle = '#00c896';
        ctx.fillText('─ Current', w - padding - 120, padding + 32);
      }
    }

    // Main Application
    const simulator = new CircuitSimulator();
    const schematicCanvas = document.getElementById('schematicCanvas');
    const graphCanvas = document.getElementById('graphCanvas');
    const renderer = new CircuitRenderer(schematicCanvas, graphCanvas, simulator);

    // UI Controls
    const voltageSlider = document.getElementById('voltageSlider');
    const resistanceSlider = document.getElementById('resistanceSlider');
    const capacitanceSlider = document.getElementById('capacitanceSlider');
    const inductanceSlider = document.getElementById('inductanceSlider');
    const frequencySlider = document.getElementById('frequencySlider');
    const speedSlider = document.getElementById('speedSlider');
    const acModeCheckbox = document.getElementById('acMode');
    const frequencyField = document.getElementById('frequencyField');
    const inductanceField = document.getElementById('inductanceField');
    const switchBtn = document.getElementById('switchBtn');
    const resetBtn = document.getElementById('resetBtn');
    const circuitTabs = document.querySelectorAll('.tab');

    function updateDisplays() {
      document.getElementById('voltageValue').textContent = `${simulator.voltage.toFixed(1)} V`;
      document.getElementById('resistanceValue').textContent = `${simulator.resistance.toFixed(0)} Ω`;
      document.getElementById('capacitanceValue').textContent = `${simulator.capacitance.toFixed(2) * 1e6} μF`;
      document.getElementById('inductanceValue').textContent = `${simulator.inductance.toFixed(0)} mH`;
      document.getElementById('frequencyValue').textContent = `${simulator.frequency.toFixed(0)} Hz`;
      document.getElementById('speedValue').textContent = `${simulator.speed.toFixed(1)}×`;

      const stats = simulator.getStats();
      document.getElementById('tauValue').textContent = `${stats.tau.toFixed(2)} ms`;
      document.getElementById('omegaValue').textContent = `${stats.f0.toFixed(1)} Hz`;
      document.getElementById('qValue').textContent = `${stats.Q.toFixed(2)}`;
      document.getElementById('currentValue').textContent = `${stats.current.toFixed(2)} mA`;
      document.getElementById('energyCapValue').textContent = `${stats.energyC.toFixed(2)} μJ`;
      document.getElementById('energyIndValue').textContent = `${stats.energyL.toFixed(2)} μJ`;
    }

    function updateControlVisibility() {
      const isLC = simulator.circuitType === 'lc' || simulator.circuitType === 'rlc';
      const isRL = simulator.circuitType === 'rl' || simulator.circuitType === 'rlc';
      inductanceField.style.display = isRL || isLC ? 'block' : 'none';
    }

    voltageSlider.addEventListener('input', (e) => {
      simulator.voltage = parseFloat(e.target.value);
      updateDisplays();
    });

    resistanceSlider.addEventListener('input', (e) => {
      simulator.resistance = parseFloat(e.target.value);
      updateDisplays();
    });

    capacitanceSlider.addEventListener('input', (e) => {
      simulator.capacitance = parseFloat(e.target.value) * 1e-6;
      updateDisplays();
    });

    inductanceSlider.addEventListener('input', (e) => {
      simulator.inductance = parseFloat(e.target.value) * 1e-3;
      updateDisplays();
    });

    frequencySlider.addEventListener('input', (e) => {
      simulator.frequency = parseFloat(e.target.value);
      updateDisplays();
    });

    speedSlider.addEventListener('input', (e) => {
      simulator.speed = parseFloat(e.target.value);
      updateDisplays();
    });

    acModeCheckbox.addEventListener('change', (e) => {
      simulator.isAC = e.target.checked;
      frequencyField.style.display = e.target.checked ? 'block' : 'none';
    });

    switchBtn.addEventListener('click', () => {
      simulator.switchClosed = !simulator.switchClosed;
      switchBtn.textContent = simulator.switchClosed ? 'Reset' : 'Close Switch';
      if (!simulator.switchClosed) {
        simulator.reset();
      }
    });

    resetBtn.addEventListener('click', () => {
      simulator.reset();
      simulator.switchClosed = false;
      switchBtn.textContent = 'Close Switch';
    });

    circuitTabs.forEach(tab => {
      tab.addEventListener('click', () => {
        circuitTabs.forEach(t => t.classList.remove('active'));
        tab.classList.add('active');
        simulator.circuitType = tab.dataset.type;
        simulator.reset();
        simulator.switchClosed = false;
        switchBtn.textContent = 'Close Switch';
        updateControlVisibility();
        updateDisplays();
      });
    });

    // Animation loop
    let lastTime = Date.now();
    function animate() {
      const now = Date.now();
      const deltaTime = (now - lastTime) / 1000; // seconds
      lastTime = now;

      simulator.step(deltaTime * simulator.speed * 0.01); // Scale down time step
      renderer.drawSchematic();
      renderer.drawGraph();
      updateDisplays();

      requestAnimationFrame(animate);
    }

    updateControlVisibility();
    updateDisplays();
    animate();