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();