Shorten scale labels, add safe-area clamping, responsive title
This commit is contained in:
parent
41ea27993a
commit
5818b7e24d
1 changed files with 34 additions and 27 deletions
|
|
@ -137,21 +137,20 @@
|
||||||
function sceneScale() {
|
function sceneScale() {
|
||||||
const s = scene('scene-scale', {
|
const s = scene('scene-scale', {
|
||||||
height: 260,
|
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.',
|
caption: 'Power out is on the right, power back is on the left. Every tick is ten times smaller than the one to its right. There are twenty-one of them. The glow is the signal on its round trip, shrinking the whole way.',
|
||||||
});
|
});
|
||||||
if (!s) return;
|
if (!s) return;
|
||||||
|
|
||||||
// Powers of ten from 10^3 W down to 10^-18 W. Labels use relatable objects
|
// Fewer labels spaced every three decades so nothing collides on narrow viewports.
|
||||||
// instead of SI prefixes so non-hams can follow.
|
|
||||||
const marks = [
|
const marks = [
|
||||||
{ p: 1e3, label: 'microwave oven', align: 'right', color: C.red },
|
{ p: 1e3, label: 'microwave', color: C.red, anchor: 'right' },
|
||||||
{ p: 1, label: 'handheld radio', align: 'center', color: C.orange },
|
{ p: 1, label: 'handheld radio', color: C.orange, anchor: 'center' },
|
||||||
{ p: 1e-3, label: 'laser pointer', align: 'center', color: C.yellow },
|
{ p: 1e-3, label: 'laser pointer', color: C.yellow, anchor: 'center' },
|
||||||
{ p: 1e-6, label: 'faint LED indicator', align: 'center', color: C.green },
|
{ p: 1e-6, label: 'LED glow', color: C.green, anchor: 'center' },
|
||||||
{ p: 1e-9, label: 'Wi-Fi at the router', align: 'center', color: C.cyan },
|
{ p: 1e-9, label: 'Wi-Fi signal', color: C.cyan, anchor: 'center' },
|
||||||
{ p: 1e-12, label: 'cell phone at edge of coverage', align: 'center', color: C.blue },
|
{ p: 1e-12, label: 'faint radio', color: C.blue, anchor: 'center' },
|
||||||
{ p: 1e-15, label: 'weak HF radio signal', align: 'center', color: '#b199e4' },
|
{ p: 1e-15, label: 'weak DX', color: '#b199e4', anchor: 'center' },
|
||||||
{ p: 1e-18, label: 'echo from the Moon', align: 'left', color: C.magenta },
|
{ p: 1e-18, label: 'Moon echo', color: C.magenta, anchor: 'left' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const start = performance.now();
|
const start = performance.now();
|
||||||
|
|
@ -160,13 +159,14 @@
|
||||||
const ctx = s.ctx;
|
const ctx = s.ctx;
|
||||||
clear(ctx, w, h);
|
clear(ctx, w, h);
|
||||||
|
|
||||||
// Generous margins so the endpoint labels can't clip
|
// Generous margins on all sides.
|
||||||
const m = { l: 60, r: 60, t: 80, b: 70 };
|
const m = { l: 70, r: 70, t: 80, b: 70 };
|
||||||
const pW = w - m.l - m.r;
|
const pW = Math.max(120, w - m.l - m.r);
|
||||||
const barY = m.t + 20;
|
const barY = m.t + 20;
|
||||||
const barH = 14;
|
const barH = 14;
|
||||||
|
|
||||||
const xOf = logP => m.l + (logP + 18) / 21 * pW;
|
const xOf = logP => m.l + (logP + 18) / 21 * pW;
|
||||||
|
const clampX = x => Math.max(m.l + 2, Math.min(m.l + pW - 2, x));
|
||||||
|
|
||||||
// Gradient bar
|
// Gradient bar
|
||||||
const grad = ctx.createLinearGradient(m.l, 0, m.l + pW, 0);
|
const grad = ctx.createLinearGradient(m.l, 0, m.l + pW, 0);
|
||||||
|
|
@ -193,16 +193,15 @@
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Labels. Alternate above/below and align endpoints inward so they never clip.
|
// Labels alternating above/below the bar. Endpoint anchors pull them inward.
|
||||||
marks.forEach((mk, i) => {
|
marks.forEach((mk, i) => {
|
||||||
const x = xOf(Math.log10(mk.p));
|
const x = xOf(Math.log10(mk.p));
|
||||||
const above = i % 2 === 0;
|
const above = i % 2 === 0;
|
||||||
const y = above ? barY - 18 : barY + barH + 22;
|
const y = above ? barY - 18 : barY + barH + 22;
|
||||||
// Two-line label: the friendly name + the exact power in words on a second line
|
pill(ctx, mk.label, x, y, mk.color, mk.anchor);
|
||||||
pill(ctx, mk.label, x, y, mk.color, mk.align);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Animated pulse from right (microwave oven) to left (Moon echo)
|
// Animated pulse from right (microwave) to left (Moon echo)
|
||||||
const t = ((performance.now() - start) / 1000) % 7;
|
const t = ((performance.now() - start) / 1000) % 7;
|
||||||
const pulseLogP = 3 - (t / 7) * 21;
|
const pulseLogP = 3 - (t / 7) * 21;
|
||||||
const pulseX = xOf(pulseLogP);
|
const pulseX = xOf(pulseLogP);
|
||||||
|
|
@ -217,21 +216,29 @@
|
||||||
ctx.fillStyle = C.yellow;
|
ctx.fillStyle = C.yellow;
|
||||||
ctx.beginPath(); ctx.arc(pulseX, pulseY, visualR, 0, Math.PI * 2); ctx.fill();
|
ctx.beginPath(); ctx.arc(pulseX, pulseY, visualR, 0, Math.PI * 2); ctx.fill();
|
||||||
|
|
||||||
// How many times smaller right now: 10^(3 - logP) = 10^(21 * t/7)
|
// Readout under the pulse, clamped so it never runs off the canvas.
|
||||||
const shrink = Math.pow(10, 3 - pulseLogP);
|
const shrink = Math.pow(10, 3 - pulseLogP);
|
||||||
let shrinkStr;
|
let shrinkStr;
|
||||||
if (shrink < 10) shrinkStr = `${shrink.toFixed(1)}x smaller than the transmitter`;
|
if (shrink < 10) shrinkStr = `${shrink.toFixed(0)}x smaller`;
|
||||||
else if (shrink < 1e6) shrinkStr = `${shrink.toExponential(0)}x smaller`;
|
else if (shrink < 1000) shrinkStr = `${shrink.toFixed(0)}x smaller`;
|
||||||
else {
|
else {
|
||||||
const exp = Math.log10(shrink).toFixed(0);
|
const exp = Math.round(Math.log10(shrink));
|
||||||
shrinkStr = `10^${exp}x smaller`;
|
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');
|
// safe-area clamp: keep the label at least half its assumed width away from each edge
|
||||||
|
ctx.save();
|
||||||
|
ctx.font = '12px system-ui, sans-serif';
|
||||||
|
const assumed = ctx.measureText(shrinkStr).width / 2 + 10;
|
||||||
|
ctx.restore();
|
||||||
|
const labelX = Math.max(m.l + assumed, Math.min(m.l + pW - assumed, pulseX));
|
||||||
|
pill(ctx, shrinkStr, labelX, pulseY + 44, C.yellow, 'center');
|
||||||
|
|
||||||
// Title: visceral, not technical
|
// Title, sized to fit narrow viewports
|
||||||
text(ctx, 'From your transmitter to the echo off the Moon', w / 2, 22, C.fg, 'center', 'alphabetic', 14);
|
const titleSize = w < 460 ? 12 : 14;
|
||||||
text(ctx, 'the signal gets one billion trillion times weaker', w / 2, 42, C.dim, 'center', 'alphabetic', 12);
|
const subSize = w < 460 ? 10 : 12;
|
||||||
text(ctx, '(that is a 1 followed by 21 zeros)', w / 2, 60, C.dim, 'center', 'alphabetic', 11);
|
text(ctx, 'transmitter to Moon echo', w / 2, 24, C.fg, 'center', 'alphabetic', titleSize);
|
||||||
|
text(ctx, 'one billion trillion times weaker', w / 2, 24 + titleSize + 6, C.dim, 'center', 'alphabetic', subSize);
|
||||||
|
text(ctx, '(a 1 followed by 21 zeros)', w / 2, 24 + titleSize + subSize + 14, C.dim, 'center', 'alphabetic', subSize - 1);
|
||||||
|
|
||||||
requestAnimationFrame(draw);
|
requestAnimationFrame(draw);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue