declarative relay deployment on hetzner relay-eval.waow.tech
atproto relay
14
fork

Configure Feed

Select the types of activity you want to include in your feed.

relay-eval trend: rank-based Y-axis for better visual separation

replace linear density-window scaling with quantile/rank mapping.
values are spaced by their rank in the distribution, not their
absolute distance — the dense cluster near 100% gets most of the
canvas while outliers are clearly separated at the bottom.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

zzstoatzz 8afe2bf3 c6ab23bc

+25 -22
+2 -1
justfile
··· 1 1 # ATProto relay deployment 2 - # usage: just indigo <recipe> | just zlay <recipe> 2 + # usage: just indigo <recipe> | just zlay <recipe> | just relay-eval <recipe> 3 3 4 4 set dotenv-load := true 5 5 6 6 mod indigo 7 7 mod zlay 8 + mod relay-eval 8 9 9 10 # show available recipes 10 11 default:
+23 -21
relay-eval/src/static/index.html
··· 304 304 const pad = { t: 20, r: 20, b: 20, l: 20 }; 305 305 const cw = W - pad.l - pad.r, ch = H - pad.t - pad.b; 306 306 307 - // collect all values to find the dense cluster for Y-axis focus 308 - const allVals = []; 307 + // rank-based (quantile) Y mapping: space values by distribution rank, 308 + // not absolute distance. the dense cluster near 100% gets most of the 309 + // canvas; outliers are clearly separated at the bottom. 310 + const valSet = new Set(); 309 311 for (const host of hosts) { 310 - for (const v of series[host]) { if (v != null) allVals.push(v); } 311 - } 312 - allVals.sort((a, b) => a - b); 313 - const n = allVals.length; 314 - 315 - // find the tightest range containing >= 60% of data points 316 - // this isolates the dense cluster (e.g. 98-99.5%) from outliers (0%, 46%) 317 - const winSize = Math.max(2, Math.ceil(n * 0.6)); 318 - let minSpan = Infinity, bestI = 0; 319 - for (let i = 0; i <= n - winSize; i++) { 320 - const span = allVals[i + winSize - 1] - allVals[i]; 321 - if (span < minSpan) { minSpan = span; bestI = i; } 312 + for (const v of series[host]) { if (v != null) valSet.add(Math.round(v * 100) / 100); } 322 313 } 323 - let lo = Math.max(0, Math.floor(allVals[bestI] - 1)); 324 - let hi = Math.min(100, Math.ceil(allVals[bestI + winSize - 1] + 0.5)); 325 - if (hi - lo < 2) lo = Math.max(0, hi - 2); 326 - const yr = hi - lo || 1; 314 + const ranks = [...valSet].sort((a, b) => a - b); 327 315 328 316 const toX = i => pad.l + (i / (runs.length - 1)) * cw; 329 - // clamp values to [lo, hi] — outliers sit at the edge 330 - const toY = v => pad.t + ch - ((Math.max(Math.min(v, hi), lo) - lo) / yr) * ch; 331 - _tc.layout = { pad, cw, ch, lo, hi, yr, toX, toY, W, H }; 317 + const toY = v => { 318 + if (ranks.length <= 1) return pad.t + ch / 2; 319 + const vr = Math.round(v * 100) / 100; 320 + // binary search for position in sorted unique values 321 + let lo = 0, hi = ranks.length - 1; 322 + if (vr <= ranks[0]) return pad.t + ch; 323 + if (vr >= ranks[hi]) return pad.t; 324 + while (hi - lo > 1) { 325 + const mid = (lo + hi) >> 1; 326 + if (ranks[mid] <= vr) lo = mid; else hi = mid; 327 + } 328 + // interpolate between surrounding ranks for smooth positioning 329 + const frac = (ranks[hi] === ranks[lo]) ? 0.5 : (vr - ranks[lo]) / (ranks[hi] - ranks[lo]); 330 + const norm = (lo + frac) / (ranks.length - 1); 331 + return pad.t + ch - norm * ch; 332 + }; 333 + _tc.layout = { pad, cw, ch, toX, toY, W, H }; 332 334 333 335 const validPts = host => series[host].map((v, i) => [i, v]).filter(p => p[1] !== null); 334 336