Rework power-scale scene: relatable analogies, no clipping, visceral framing
This commit is contained in:
parent
9d81de447d
commit
41ea27993a
1 changed files with 35 additions and 34 deletions
|
|
@ -136,20 +136,22 @@
|
|||
// ---------- Scene 0: kW to aW power ladder ----------
|
||||
function sceneScale() {
|
||||
const s = scene('scene-scale', {
|
||||
height: 230,
|
||||
caption: 'A logarithmic ladder from 1 kW on the right to 1 aW on the left. Each tick is a factor of ten. There are twenty-one of them, and the pulse that starts out at the transmitter has to fall all the way to the far end before anyone can decode it.',
|
||||
height: 260,
|
||||
caption: 'Each tick is ten times smaller than the one to its right. From the transmitter on the far right down to the echo on the far left, the signal shrinks by a factor of one billion trillion. The moving pulse is the signal going on its round trip.',
|
||||
});
|
||||
if (!s) return;
|
||||
|
||||
// Powers of ten from 10^3 W down to 10^-18 W. Labels use relatable objects
|
||||
// instead of SI prefixes so non-hams can follow.
|
||||
const marks = [
|
||||
{ p: 1e3, label: '1 kW — your transmitter', color: C.red },
|
||||
{ p: 1, label: '1 W — a handheld HT', color: C.orange },
|
||||
{ p: 1e-3, label: '1 mW — an indicator LED', color: C.yellow },
|
||||
{ p: 1e-6, label: '1 μW', color: C.green },
|
||||
{ p: 1e-9, label: '1 nW — strong Wi-Fi at the receiver', color: C.cyan },
|
||||
{ p: 1e-12, label: '1 pW', color: C.blue },
|
||||
{ p: 1e-15, label: '1 fW — weak HF DX at the antenna', color: '#b199e4' },
|
||||
{ p: 1e-18, label: '1 aW — what comes back from the Moon', color: C.magenta },
|
||||
{ p: 1e3, label: 'microwave oven', align: 'right', color: C.red },
|
||||
{ p: 1, label: 'handheld radio', align: 'center', color: C.orange },
|
||||
{ p: 1e-3, label: 'laser pointer', align: 'center', color: C.yellow },
|
||||
{ p: 1e-6, label: 'faint LED indicator', align: 'center', color: C.green },
|
||||
{ p: 1e-9, label: 'Wi-Fi at the router', align: 'center', color: C.cyan },
|
||||
{ p: 1e-12, label: 'cell phone at edge of coverage', align: 'center', color: C.blue },
|
||||
{ p: 1e-15, label: 'weak HF radio signal', align: 'center', color: '#b199e4' },
|
||||
{ p: 1e-18, label: 'echo from the Moon', align: 'left', color: C.magenta },
|
||||
];
|
||||
|
||||
const start = performance.now();
|
||||
|
|
@ -158,12 +160,12 @@
|
|||
const ctx = s.ctx;
|
||||
clear(ctx, w, h);
|
||||
|
||||
const m = { l: 30, r: 30, t: 70, b: 70 };
|
||||
// Generous margins so the endpoint labels can't clip
|
||||
const m = { l: 60, r: 60, t: 80, b: 70 };
|
||||
const pW = w - m.l - m.r;
|
||||
const barY = m.t + 20;
|
||||
const barH = 14;
|
||||
|
||||
// log scale: logP from -18 to +3
|
||||
const xOf = logP => m.l + (logP + 18) / 21 * pW;
|
||||
|
||||
// Gradient bar
|
||||
|
|
@ -179,7 +181,7 @@
|
|||
if (ctx.roundRect) { ctx.beginPath(); ctx.roundRect(m.l, barY, pW, barH, 4); ctx.stroke(); }
|
||||
else ctx.strokeRect(m.l, barY, pW, barH);
|
||||
|
||||
// Minor ticks at every decade (22 positions from -18..+3)
|
||||
// Decade ticks
|
||||
for (let d = -18; d <= 3; d++) {
|
||||
const x = xOf(d);
|
||||
const important = marks.some(mk => Math.abs(Math.log10(mk.p) - d) < 0.01);
|
||||
|
|
@ -191,22 +193,21 @@
|
|||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Labels — alternate above / below to avoid collisions
|
||||
// Labels. Alternate above/below and align endpoints inward so they never clip.
|
||||
marks.forEach((mk, i) => {
|
||||
const x = xOf(Math.log10(mk.p));
|
||||
const above = i % 2 === 0;
|
||||
const y = above ? barY - 18 : barY + barH + 22;
|
||||
pill(ctx, mk.label, x, y, mk.color, 'center');
|
||||
// Two-line label: the friendly name + the exact power in words on a second line
|
||||
pill(ctx, mk.label, x, y, mk.color, mk.align);
|
||||
});
|
||||
|
||||
// Animated pulse from kW (right) to aW (left) every 6 s
|
||||
const t = ((performance.now() - start) / 1000) % 6;
|
||||
const pulseLogP = 3 - (t / 6) * 21;
|
||||
// Animated pulse from right (microwave oven) to left (Moon echo)
|
||||
const t = ((performance.now() - start) / 1000) % 7;
|
||||
const pulseLogP = 3 - (t / 7) * 21;
|
||||
const pulseX = xOf(pulseLogP);
|
||||
const pulseY = barY + barH / 2;
|
||||
// Visual radius shrinks *slightly* (log-visible), not exponentially or it becomes invisible
|
||||
const visualR = 3 + 12 * (pulseLogP + 18) / 21;
|
||||
// Glow
|
||||
const glow = ctx.createRadialGradient(pulseX, pulseY, 0, pulseX, pulseY, visualR * 3);
|
||||
glow.addColorStop(0, 'rgba(226, 195, 125, 0.9)');
|
||||
glow.addColorStop(0.5, 'rgba(226, 195, 125, 0.2)');
|
||||
|
|
@ -216,21 +217,21 @@
|
|||
ctx.fillStyle = C.yellow;
|
||||
ctx.beginPath(); ctx.arc(pulseX, pulseY, visualR, 0, Math.PI * 2); ctx.fill();
|
||||
|
||||
// Current-power readout riding the pulse
|
||||
const currentW = Math.pow(10, pulseLogP);
|
||||
let currentStr;
|
||||
if (currentW >= 1) currentStr = `${currentW.toFixed(0)} W`;
|
||||
else if (currentW >= 1e-3) currentStr = `${(currentW * 1e3).toFixed(1)} mW`;
|
||||
else if (currentW >= 1e-6) currentStr = `${(currentW * 1e6).toFixed(1)} μW`;
|
||||
else if (currentW >= 1e-9) currentStr = `${(currentW * 1e9).toFixed(1)} nW`;
|
||||
else if (currentW >= 1e-12) currentStr = `${(currentW * 1e12).toFixed(1)} pW`;
|
||||
else if (currentW >= 1e-15) currentStr = `${(currentW * 1e15).toFixed(1)} fW`;
|
||||
else currentStr = `${(currentW * 1e18).toFixed(1)} aW`;
|
||||
pill(ctx, currentStr, pulseX, pulseY + 42, C.yellow, 'center');
|
||||
// How many times smaller right now: 10^(3 - logP) = 10^(21 * t/7)
|
||||
const shrink = Math.pow(10, 3 - pulseLogP);
|
||||
let shrinkStr;
|
||||
if (shrink < 10) shrinkStr = `${shrink.toFixed(1)}x smaller than the transmitter`;
|
||||
else if (shrink < 1e6) shrinkStr = `${shrink.toExponential(0)}x smaller`;
|
||||
else {
|
||||
const exp = Math.log10(shrink).toFixed(0);
|
||||
shrinkStr = `10^${exp}x smaller`;
|
||||
}
|
||||
pill(ctx, shrinkStr, Math.max(m.l + 50, Math.min(w - m.r - 50, pulseX)), pulseY + 42, C.yellow, 'center');
|
||||
|
||||
// Title
|
||||
text(ctx, 'power out vs power back: 21 orders of magnitude', w / 2, 22, C.fg, 'center', 'alphabetic', 13);
|
||||
text(ctx, '(+30 dBW on one end, −180 dBW on the other)', w / 2, 40, C.dim, 'center', 'alphabetic', 11);
|
||||
// Title: visceral, not technical
|
||||
text(ctx, 'From your transmitter to the echo off the Moon', w / 2, 22, C.fg, 'center', 'alphabetic', 14);
|
||||
text(ctx, 'the signal gets one billion trillion times weaker', w / 2, 42, C.dim, 'center', 'alphabetic', 12);
|
||||
text(ctx, '(that is a 1 followed by 21 zeros)', w / 2, 60, C.dim, 'center', 'alphabetic', 11);
|
||||
|
||||
requestAnimationFrame(draw);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue