The 1-dimensional version of Borsuk-Ulam says: any continuous function f from the circle S1 to R must satisfy f(x)=f(−x) for some x. Here you can see it happen.
<!-- Borsuk-Ulam 1D demo: draggable point on circle, shows f(theta) vs f(theta+pi) -->
<div class="bu-wrap">
<p class="hint">Drag anywhere on the circle. The blue dot is your point; the orange dot is its antipode. When the two curves meet, f(x) = f(-x) — an antipodal match. The theorem guarantees at least one always exists.</p>
<canvas id="bu-canvas" width="340" height="340" aria-label="Interactive circle showing the Borsuk-Ulam theorem"></canvas>
<div class="info-row">
<span class="dot blue"></span><span class="label-text">f(θ) — value at your point</span>
<span class="dot orange"></span><span class="label-text">f(θ+π) — value at antipodal point</span>
</div>
<div class="status" id="bu-status">Drag the blue dot to explore.</div>
<div class="btns">
<button id="bu-reset" type="button" class="ghost">Reset</button>
<button id="bu-randomize" type="button">New function</button>
</div>
</div>
/* Base reset and layout */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: 14px; }
.bu-wrap { display: flex; flex-direction: column; align-items: center; gap: .5rem; padding: .6rem; }
.hint { font-size: .85rem; color: #444; margin: 0; line-height: 1.45; max-width: 340px; text-align: center; }
canvas { cursor: crosshair; border-radius: 50%; border: 1.5px solid #cdd9e3; touch-action: none; }
.info-row { display: flex; gap: 1rem; align-items: center; flex-wrap: wrap; justify-content: center; font-size: .82rem; color: #444; }
.dot { display: inline-block; width: 11px; height: 11px; border-radius: 50%; }
.dot.blue { background: #1d6fbf; }
.dot.orange { background: #e07020; }
.label-text { margin-right: .3rem; }
.status { font-size: .95rem; font-weight: 600; min-height: 1.4em; text-align: center; color: #1d3557; }
.status.match { color: #0a7d33; }
.status.nomatch { color: #555; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; justify-content: center; }
button { font: 600 13px system-ui, sans-serif; padding: .4rem .85rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Borsuk-Ulam (1D): for any continuous f: S^1 -> R there exists theta where
// f(theta) = f(theta + pi). We visualise f as a displacement along the outward
// normal of the circle. Blue = f(theta), orange = f(theta+pi); they must cross.
const canvas = document.getElementById('bu-canvas');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
const CX = W / 2, CY = H / 2;
const R = 130; // circle radius in pixels
// Fourier-like coefficients that define the current random function
let coeffs = newCoeffs();
function newCoeffs() {
// random mix of sin/cos harmonics — always continuous on the circle
return [
(Math.random() - 0.5) * 2,
(Math.random() - 0.5) * 1.5,
(Math.random() - 0.5) * 1,
(Math.random() - 0.5) * 0.8
];
}
// Evaluate f at angle theta
function fVal(theta) {
const [a, b, c, d] = coeffs;
return a * Math.sin(theta) + b * Math.cos(2 * theta) + c * Math.sin(3 * theta) + d * Math.cos(theta);
}
let dragAngle = 0; // current angle of the draggable blue point
let isDragging = false;
function angleFromEvent(e) {
const rect = canvas.getBoundingClientRect();
const scaleX = W / rect.width, scaleY = H / rect.height;
const clientX = e.touches ? e.touches[0].clientX : e.clientX;
const clientY = e.touches ? e.touches[0].clientY : e.clientY;
const x = (clientX - rect.left) * scaleX - CX;
const y = (clientY - rect.top) * scaleY - CY;
return Math.atan2(y, x);
}
canvas.addEventListener('mousedown', e => { isDragging = true; dragAngle = angleFromEvent(e); draw(); });
canvas.addEventListener('mousemove', e => { if (!isDragging) return; dragAngle = angleFromEvent(e); draw(); });
canvas.addEventListener('mouseup', () => { isDragging = false; });
canvas.addEventListener('touchstart', e => { e.preventDefault(); isDragging = true; dragAngle = angleFromEvent(e); draw(); }, { passive: false });
canvas.addEventListener('touchmove', e => { e.preventDefault(); if (!isDragging) return; dragAngle = angleFromEvent(e); draw(); }, { passive: false });
canvas.addEventListener('touchend', () => { isDragging = false; });
function draw() {
ctx.clearRect(0, 0, W, H);
// draw the base circle
ctx.beginPath();
ctx.arc(CX, CY, R, 0, 2 * Math.PI);
ctx.strokeStyle = '#cdd9e3';
ctx.lineWidth = 1.5;
ctx.stroke();
const STEPS = 200;
const scaleY = 55; // amplitude scaling: maps f-values to pixel displacement
// draw f(theta) displaced outward along the circle normal
ctx.beginPath();
for (let i = 0; i <= STEPS; i++) {
const theta = (i / STEPS) * 2 * Math.PI;
const px = CX + R * Math.cos(theta);
const py = CY + R * Math.sin(theta);
const nx = -Math.sin(theta), ny = Math.cos(theta); // outward normal direction at this point
const val = fVal(theta) * scaleY;
// displace outward by f(theta)
const ox = px + ny * val, oy = py - nx * val;
if (i === 0) ctx.moveTo(ox, oy); else ctx.lineTo(ox, oy);
}
ctx.strokeStyle = '#1d6fbf';
ctx.lineWidth = 2;
ctx.stroke();
// draw f(theta+pi) displaced outward — the orange antipodal curve
ctx.beginPath();
for (let i = 0; i <= STEPS; i++) {
const theta = (i / STEPS) * 2 * Math.PI;
const px = CX + R * Math.cos(theta);
const py = CY + R * Math.sin(theta);
const nx = -Math.sin(theta), ny = Math.cos(theta);
const val = fVal(theta + Math.PI) * scaleY;
const ox = px + ny * val, oy = py - nx * val;
if (i === 0) ctx.moveTo(ox, oy); else ctx.lineTo(ox, oy);
}
ctx.strokeStyle = '#e07020';
ctx.lineWidth = 2;
ctx.stroke();
// draw the draggable blue point
const px = CX + R * Math.cos(dragAngle);
const py = CY + R * Math.sin(dragAngle);
ctx.beginPath(); ctx.arc(px, py, 7, 0, 2 * Math.PI);
ctx.fillStyle = '#1d6fbf'; ctx.fill();
// draw the corresponding orange antipodal point
const apx = CX + R * Math.cos(dragAngle + Math.PI);
const apy = CY + R * Math.sin(dragAngle + Math.PI);
ctx.beginPath(); ctx.arc(apx, apy, 7, 0, 2 * Math.PI);
ctx.fillStyle = '#e07020'; ctx.fill();
// check whether blue and orange values are close enough to count as a match
const vBlue = fVal(dragAngle);
const vOrange = fVal(dragAngle + Math.PI);
const diff = Math.abs(vBlue - vOrange);
const MATCH_TOL = 0.08; // within this tolerance we call it a match
const statusEl = document.getElementById('bu-status');
if (diff < MATCH_TOL) {
statusEl.textContent = '✓ Antipodal match! f(x) = f(-x) here.';
statusEl.className = 'status match';
} else {
statusEl.textContent = fmt('f(x) = %vBlue% | f(-x) = %vOrange%', { vBlue: vBlue.toFixed(2), vOrange: vOrange.toFixed(2) });
statusEl.className = 'status nomatch';
}
}
document.getElementById('bu-reset').addEventListener('click', () => {
dragAngle = 0;
draw();
});
document.getElementById('bu-randomize').addEventListener('click', () => {
coeffs = newCoeffs();
dragAngle = 0;
draw();
});
draw(); // initial render
<!-- Borsuk-Ulam 1D demo: draggable point on circle, shows f(theta) vs f(theta+pi) -->
<div class="bu-wrap">
<p class="hint">Drag anywhere on the circle. The blue dot is your point; the orange dot is its antipode. When the two curves meet, f(x) = f(-x) — an antipodal match. The theorem guarantees at least one always exists.</p>
<canvas id="bu-canvas" width="340" height="340" aria-label="Interactive circle showing the Borsuk-Ulam theorem"></canvas>
<div class="info-row">
<span class="dot blue"></span><span class="label-text">f(θ) — value at your point</span>
<span class="dot orange"></span><span class="label-text">f(θ+π) — value at antipodal point</span>
</div>
<div class="status" id="bu-status">Drag the blue dot to explore.</div>
<div class="btns">
<button id="bu-reset" type="button" class="ghost">Reset</button>
<button id="bu-randomize" type="button">New function</button>
</div>
</div>
/* Base reset and layout */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: 14px; }
.bu-wrap { display: flex; flex-direction: column; align-items: center; gap: .5rem; padding: .6rem; }
.hint { font-size: .85rem; color: #444; margin: 0; line-height: 1.45; max-width: 340px; text-align: center; }
canvas { cursor: crosshair; border-radius: 50%; border: 1.5px solid #cdd9e3; touch-action: none; }
.info-row { display: flex; gap: 1rem; align-items: center; flex-wrap: wrap; justify-content: center; font-size: .82rem; color: #444; }
.dot { display: inline-block; width: 11px; height: 11px; border-radius: 50%; }
.dot.blue { background: #1d6fbf; }
.dot.orange { background: #e07020; }
.label-text { margin-right: .3rem; }
.status { font-size: .95rem; font-weight: 600; min-height: 1.4em; text-align: center; color: #1d3557; }
.status.match { color: #0a7d33; }
.status.nomatch { color: #555; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; justify-content: center; }
button { font: 600 13px system-ui, sans-serif; padding: .4rem .85rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Borsuk-Ulam (1D): for any continuous f: S^1 -> R there exists theta where
// f(theta) = f(theta + pi). We visualise f as a displacement along the outward
// normal of the circle. Blue = f(theta), orange = f(theta+pi); they must cross.
const canvas = document.getElementById('bu-canvas');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
const CX = W / 2, CY = H / 2;
const R = 130; // circle radius in pixels
// Fourier-like coefficients that define the current random function
let coeffs = newCoeffs();
function newCoeffs() {
// random mix of sin/cos harmonics — always continuous on the circle
return [
(Math.random() - 0.5) * 2,
(Math.random() - 0.5) * 1.5,
(Math.random() - 0.5) * 1,
(Math.random() - 0.5) * 0.8
];
}
// Evaluate f at angle theta
function fVal(theta) {
const [a, b, c, d] = coeffs;
return a * Math.sin(theta) + b * Math.cos(2 * theta) + c * Math.sin(3 * theta) + d * Math.cos(theta);
}
let dragAngle = 0; // current angle of the draggable blue point
let isDragging = false;
function angleFromEvent(e) {
const rect = canvas.getBoundingClientRect();
const scaleX = W / rect.width, scaleY = H / rect.height;
const clientX = e.touches ? e.touches[0].clientX : e.clientX;
const clientY = e.touches ? e.touches[0].clientY : e.clientY;
const x = (clientX - rect.left) * scaleX - CX;
const y = (clientY - rect.top) * scaleY - CY;
return Math.atan2(y, x);
}
canvas.addEventListener('mousedown', e => { isDragging = true; dragAngle = angleFromEvent(e); draw(); });
canvas.addEventListener('mousemove', e => { if (!isDragging) return; dragAngle = angleFromEvent(e); draw(); });
canvas.addEventListener('mouseup', () => { isDragging = false; });
canvas.addEventListener('touchstart', e => { e.preventDefault(); isDragging = true; dragAngle = angleFromEvent(e); draw(); }, { passive: false });
canvas.addEventListener('touchmove', e => { e.preventDefault(); if (!isDragging) return; dragAngle = angleFromEvent(e); draw(); }, { passive: false });
canvas.addEventListener('touchend', () => { isDragging = false; });
function draw() {
ctx.clearRect(0, 0, W, H);
// draw the base circle
ctx.beginPath();
ctx.arc(CX, CY, R, 0, 2 * Math.PI);
ctx.strokeStyle = '#cdd9e3';
ctx.lineWidth = 1.5;
ctx.stroke();
const STEPS = 200;
const scaleY = 55; // amplitude scaling: maps f-values to pixel displacement
// draw f(theta) displaced outward along the circle normal
ctx.beginPath();
for (let i = 0; i <= STEPS; i++) {
const theta = (i / STEPS) * 2 * Math.PI;
const px = CX + R * Math.cos(theta);
const py = CY + R * Math.sin(theta);
const nx = -Math.sin(theta), ny = Math.cos(theta); // outward normal direction at this point
const val = fVal(theta) * scaleY;
// displace outward by f(theta)
const ox = px + ny * val, oy = py - nx * val;
if (i === 0) ctx.moveTo(ox, oy); else ctx.lineTo(ox, oy);
}
ctx.strokeStyle = '#1d6fbf';
ctx.lineWidth = 2;
ctx.stroke();
// draw f(theta+pi) displaced outward — the orange antipodal curve
ctx.beginPath();
for (let i = 0; i <= STEPS; i++) {
const theta = (i / STEPS) * 2 * Math.PI;
const px = CX + R * Math.cos(theta);
const py = CY + R * Math.sin(theta);
const nx = -Math.sin(theta), ny = Math.cos(theta);
const val = fVal(theta + Math.PI) * scaleY;
const ox = px + ny * val, oy = py - nx * val;
if (i === 0) ctx.moveTo(ox, oy); else ctx.lineTo(ox, oy);
}
ctx.strokeStyle = '#e07020';
ctx.lineWidth = 2;
ctx.stroke();
// draw the draggable blue point
const px = CX + R * Math.cos(dragAngle);
const py = CY + R * Math.sin(dragAngle);
ctx.beginPath(); ctx.arc(px, py, 7, 0, 2 * Math.PI);
ctx.fillStyle = '#1d6fbf'; ctx.fill();
// draw the corresponding orange antipodal point
const apx = CX + R * Math.cos(dragAngle + Math.PI);
const apy = CY + R * Math.sin(dragAngle + Math.PI);
ctx.beginPath(); ctx.arc(apx, apy, 7, 0, 2 * Math.PI);
ctx.fillStyle = '#e07020'; ctx.fill();
// check whether blue and orange values are close enough to count as a match
const vBlue = fVal(dragAngle);
const vOrange = fVal(dragAngle + Math.PI);
const diff = Math.abs(vBlue - vOrange);
const MATCH_TOL = 0.08; // within this tolerance we call it a match
const statusEl = document.getElementById('bu-status');
if (diff < MATCH_TOL) {
statusEl.textContent = '✓ Antipodal match! f(x) = f(-x) here.';
statusEl.className = 'status match';
} else {
statusEl.textContent = fmt('f(x) = %vBlue% | f(-x) = %vOrange%', { vBlue: vBlue.toFixed(2), vOrange: vOrange.toFixed(2) });
statusEl.className = 'status nomatch';
}
}
document.getElementById('bu-reset').addEventListener('click', () => {
dragAngle = 0;
draw();
});
document.getElementById('bu-randomize').addEventListener('click', () => {
coeffs = newCoeffs();
dragAngle = 0;
draw();
});
draw(); // initial render
The blue curve shows the value of f(θ) (a wiggly function on the circle), and the orange curve shows f(θ+π) — the value at the antipodal point. Because both curves are continuous and the circle wraps around, they must cross. The crossing point is exactly the antipodal pair that agrees. You cannot draw a continuous function on a circle where the two antipodal curves never meet.
Comments
Loading comments...