Show description
Interactive Series Visualizer
Interactive Series Visualizer
Series Convergence Visualizer
Watch sums build to see convergence in action.
Converges
The infinite sum approaches a single, finite number. On the graph, the line will flatten out and approach a horizontal asymptote.
Diverges
The infinite sum does not settle on a single value. It might shoot off to infinity (∞ or -∞) or oscillate without approaching a limit.
Controls
Series Type
Geometric Series
p-Series
Alternating p-Series
Ratio Test (Factorial)
Ratio Test (Exponential)
Start
Reset
Series Info
Formula:
Terms Added (n): 0
Partial Sum (S_n): 0.00
Verdict: Press Start
Interactive Series Visualizer
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interactive Series Visualizer</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
background-color: #030712; /* gray-950 */
color: #d1d5db; /* gray-300 */
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #111827; /* gray-900 */
}
::-webkit-scrollbar-thumb {
background: #4b5563; /* gray-600 */
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #6b7280; /* gray-500 */
}
.control-panel, .info-card {
background-color: #111827; /* gray-900 */
border: 1px solid #374151; /* gray-700 */
border-radius: 0.75rem;
padding: 1.5rem;
}
.slider-container {
margin-top: 1rem;
}
input[type="range"] {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 6px;
background: #374151; /* gray-700 */
border-radius: 3px;
outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
background: #d1d5db; /* gray-300 */
cursor: pointer;
border-radius: 50%;
border: 2px solid #111827;
}
input[type="range"]::-moz-range-thumb {
width: 20px;
height: 20px;
background: #d1d5db; /* gray-300 */
cursor: pointer;
border-radius: 50%;
}
.btn {
background-color: #3b82f6;
color: white;
padding: 0.5rem 1.5rem;
border-radius: 0.5rem;
font-weight: 500;
transition: background-color 0.2s;
}
.btn:hover {
background-color: #2563eb;
}
.btn-secondary {
background-color: #4b5563;
}
.btn-secondary:hover {
background-color: #6b7280;
}
select {
background-color: #1f2937;
border: 1px solid #4b5563;
color: #d1d5db;
border-radius: 0.5rem;
padding: 0.5rem;
}
.definition-title {
color: #ffffff;
font-weight: 600;
}
</style>
</head>
<body class="p-4 md:p-8">
<div class="max-w-7xl mx-auto">
<header class="text-center mb-8">
<h1 class="text-4xl md:text-5xl font-bold text-gray-50">Series Convergence Visualizer</h1>
<p class="text-lg text-gray-400 mt-2">Watch sums build to see convergence in action.</p>
</header>
<!-- Definitions Section -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<div class="info-card">
<h3 class="text-xl definition-title">Converges</h3>
<p class="mt-2 text-gray-400">The infinite sum approaches a <strong class="text-gray-200">single, finite number</strong>. On the graph, the line will flatten out and approach a horizontal asymptote.</p>
</div>
<div class="info-card">
<h3 class="text-xl definition-title">Diverges</h3>
<p class="mt-2 text-gray-400">The infinite sum does not settle on a single value. It might shoot off to <strong class="text-gray-200">infinity (∞ or -∞)</strong> or oscillate without approaching a limit.</p>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Control Panel -->
<div class="lg:col-span-1 control-panel">
<h2 class="text-2xl font-bold text-white border-b border-gray-700 pb-2">Controls</h2>
<div class="mt-4">
<label for="series-type" class="block font-medium text-gray-300">Series Type</label>
<select id="series-type" class="w-full mt-1">
<option value="geometric">Geometric Series</option>
<option value="p-series">p-Series</option>
<option value="alternating-p">Alternating p-Series</option>
<option value="ratio-factorial">Ratio Test (Factorial)</option>
<option value="ratio-exponential">Ratio Test (Exponential)</option>
</select>
</div>
<!-- Parameter Sliders -->
<div id="params-container" class="mt-6"></div>
<div class="flex space-x-4 mt-6">
<button id="start-btn" class="btn flex-1">Start</button>
<button id="reset-btn" class="btn btn-secondary flex-1">Reset</button>
</div>
<!-- Info Display -->
<div class="mt-8 p-4 bg-gray-950 rounded-lg border border-gray-700">
<h3 class="font-semibold text-lg text-white">Series Info</h3>
<div class="mt-2 text-sm space-y-2">
<p><strong>Formula:</strong> <span id="formula-display"></span></p>
<p><strong>Terms Added (n):</strong> <span id="n-display">0</span></p>
<p><strong>Partial Sum (S_n):</strong> <span id="sum-display">0.00</span></p>
<p><strong>Verdict:</strong> <span id="verdict-display" class="font-bold">Press Start</span></p>
</div>
</div>
</div>
<!-- Chart -->
<div class="lg:col-span-2 control-panel">
<canvas id="seriesChart"></canvas>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
const seriesTypeSelect = document.getElementById('series-type');
const paramsContainer = document.getElementById('params-container');
const startBtn = document.getElementById('start-btn');
const resetBtn = document.getElementById('reset-btn');
const formulaDisplay = document.getElementById('formula-display');
const nDisplay = document.getElementById('n-display');
const sumDisplay = document.getElementById('sum-display');
const verdictDisplay = document.getElementById('verdict-display');
let chart;
let animationFrameId;
let state = {
n: 0,
sum: 0,
isRunning: false,
seriesType: 'geometric',
params: {}
};
// Factorial function for calculations
const factorials = [1];
function factorial(n) {
if (n < 0) return NaN;
if (n >= factorials.length) {
for (let i = factorials.length; i <= n; i++) {
factorials[i] = factorials[i - 1] * i;
}
}
return factorials[n];
}
const seriesConfig = {
'geometric': {
name: 'Geometric Series',
formula: (p) => `Σ a ⋅ rⁿ`,
params: {
a: { label: 'a (First Term)', min: -2, max: 2, step: 0.1, default: 1 },
r: { label: 'r (Ratio)', min: -1.5, max: 1.5, step: 0.01, default: 0.5 }
},
getTerm: (n, p) => p.a * Math.pow(p.r, n),
getVerdict: (p) => Math.abs(p.r) < 1 ? `Converges to ${ (p.a / (1 - p.r)).toFixed(4) }` : 'Diverges'
},
'p-series': {
name: 'p-Series',
formula: (p) => `Σ 1 / nᵖ`,
params: {
p: { label: 'p', min: 0, max: 3, step: 0.01, default: 2 }
},
getTerm: (n, p) => n > 0 ? 1 / Math.pow(n, p.p) : 0,
getVerdict: (p) => p.p > 1 ? 'Converges' : 'Diverges to ∞'
},
'alternating-p': {
name: 'Alternating p-Series',
formula: (p) => `Σ (-1)ⁿ⁻¹ / nᵖ`,
params: {
p: { label: 'p', min: 0, max: 3, step: 0.01, default: 1 }
},
getTerm: (n, p) => n > 0 ? Math.pow(-1, n - 1) / Math.pow(n, p.p) : 0,
getVerdict: (p) => p.p > 0 ? 'Converges' : 'Diverges'
},
'ratio-factorial': {
name: 'Ratio Test (Factorial)',
formula: (p) => `Σ xⁿ / n!`,
params: {
x: { label: 'x', min: -5, max: 5, step: 0.1, default: 1 }
},
getTerm: (n, p) => Math.pow(p.x, n) / factorial(n),
getVerdict: (p) => `Converges for all x (ROC = ∞). Sum is e^${p.x.toFixed(2)} ≈ ${Math.exp(p.x).toFixed(4)}`
},
'ratio-exponential': {
name: 'Ratio Test (Exponential)',
formula: (p) => `Σ n! ⋅ xⁿ`,
params: {
x: { label: 'x', min: -2, max: 2, step: 0.01, default: 0.5 }
},
getTerm: (n, p) => factorial(n) * Math.pow(p.x, n),
getVerdict: (p) => p.x === 0 ? 'Converges to 1 (at center x=0)' : 'Diverges for x ≠ 0 (ROC = 0)'
}
};
function createParamControls() {
const type = seriesTypeSelect.value;
const config = seriesConfig[type];
paramsContainer.innerHTML = '';
state.params = {};
for (const key in config.params) {
const param = config.params[key];
const container = document.createElement('div');
container.className = 'slider-container';
const label = document.createElement('label');
label.className = 'block font-medium text-gray-300';
label.innerText = `${param.label}: `;
const valueSpan = document.createElement('span');
valueSpan.id = `${key}-value`;
valueSpan.innerText = param.default;
label.appendChild(valueSpan);
const slider = document.createElement('input');
slider.type = 'range';
slider.id = `${key}-slider`;
slider.min = param.min;
slider.max = param.max;
slider.step = param.step;
slider.value = param.default;
slider.addEventListener('input', (e) => {
state.params[key] = parseFloat(e.target.value);
valueSpan.innerText = parseFloat(e.target.value).toFixed(2);
if (!state.isRunning) {
resetSimulation();
updateDisplay();
}
});
container.appendChild(label);
container.appendChild(slider);
paramsContainer.appendChild(container);
state.params[key] = param.default;
}
state.seriesType = type;
resetSimulation();
}
function initChart() {
const ctx = document.getElementById('seriesChart').getContext('2d');
if (chart) chart.destroy();
chart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Partial Sum (S_n)',
data: [],
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
borderWidth: 2,
pointRadius: 0,
tension: 0.1,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
title: { display: true, text: 'Number of Terms (n)', color: '#9ca3af' },
ticks: { color: '#9ca3af' },
grid: { color: 'rgba(255, 255, 255, 0.1)'}
},
y: {
title: { display: true, text: 'Sum', color: '#9ca3af' },
ticks: { color: '#9ca3af' },
grid: { color: 'rgba(255, 255, 255, 0.1)'}
}
},
plugins: {
legend: { labels: { color: '#9ca3af' } }
}
}
});
}
function updateDisplay() {
const config = seriesConfig[state.seriesType];
formulaDisplay.innerHTML = config.formula(state.params)
.replace(/Σ/g, '<span class="text-2xl text-blue-400">Σ</span>')
.replace(/\*/g, '⋅')
.replace(/\^/g, '<sup>')
.replace(/p/g, 'ᵖ')
.replace(/n/g, 'ⁿ');
nDisplay.textContent = state.n;
sumDisplay.textContent = state.sum.toFixed(4);
verdictDisplay.textContent = config.getVerdict(state.params);
}
function resetSimulation() {
state.isRunning = false;
startBtn.textContent = 'Start';
cancelAnimationFrame(animationFrameId);
state.n = 0;
state.sum = seriesConfig[state.seriesType].getTerm(0, state.params);
if (state.seriesType === 'p-series' || state.seriesType === 'alternating-p') {
state.n = 1;
state.sum = seriesConfig[state.seriesType].getTerm(1, state.params);
}
chart.data.labels = [state.n];
chart.data.datasets[0].data = [state.sum];
chart.update();
updateDisplay();
}
function simulationLoop() {
if (!state.isRunning) return;
const config = seriesConfig[state.seriesType];
for (let i = 0; i < 5; i++) {
if (state.n > 1000) { // Safety break
state.isRunning = false;
startBtn.textContent = 'Start';
break;
}
state.n++;
const term = config.getTerm(state.n, state.params);
if (isFinite(term)) {
state.sum += term;
} else {
state.isRunning = false;
startBtn.textContent = 'Start';
break;
}
if (state.n % 5 === 0) {
chart.data.labels.push(state.n);
chart.data.datasets[0].data.push(state.sum);
}
}
chart.update('none');
updateDisplay();
if (Math.abs(state.sum) > 1e6) {
verdictDisplay.textContent = 'Diverging to ∞';
state.isRunning = false;
startBtn.textContent = 'Start';
return;
}
animationFrameId = requestAnimationFrame(simulationLoop);
}
startBtn.addEventListener('click', () => {
state.isRunning = !state.isRunning;
if (state.isRunning) {
startBtn.textContent = 'Pause';
simulationLoop();
} else {
startBtn.textContent = 'Start';
cancelAnimationFrame(animationFrameId);
}
});
resetBtn.addEventListener('click', () => {
createParamControls();
});
seriesTypeSelect.addEventListener('change', () => {
createParamControls();
});
// Initial setup
initChart();
createParamControls();
});
</script>
</body>
</html>