ModsCode / Gallery / Designs / Histogram
Histogram
Tool call latency in 30 bins with a long right tail; dashed p50, p95 and p99 lines carry their values, p99 in Claude orange.


hooks/designs/histogram.mjs
// histogram: how long 2,000 tool calls took, in candle's look. The latencies are log-normal (median
// 380 ms, sigma 0.55), the usual shape of a service time: a hump near the median and a long right tail.
// 30 bins of 60 ms cover 0 to 1,800 ms; bars take candle's 62% slot at 55% fill. Calls past 1,800 ms
// are left out of the bins rather than piled into the last one (that bin would lie) and counted in the
// header instead. p50, p95 and p99 are dashed 35% lines like candle's last value, with p99 as the one
// Claude orange, and the tallest bin carries its count at its tip. Every number in the picture (the
// percentiles, the peak, the count past the scale end) is computed from the sample in draw(), never
// typed; with this seed they land on p50 378, p95 893, p99 1,336, a peak of 293 and 4 calls past 1,800 ms.
// Normal draws use the Box-Muller transform (Box and Muller, 1958); percentiles interpolate linearly
// between order statistics, the same rule as d3-array's quantile (ISC), written out here. Sample data only.
const W = 380
const H = 200
const STAGE = '#FAF9F5'
const INK = '#141413'
const MUTED = '#73726C'
const CLAUDE = '#D77757'
const FONT = "'Inter Tight','Segoe UI',system-ui,sans-serif"
const PLOT = { left: 36, right: 368, top: 52, bottom: 160 }
const N = 2000
const BINS = 30
const BIN_MS = 60
const END = BINS * BIN_MS // 1,800 ms, the scale end
const f = n => n.toFixed(1)
// A seeded walk, so the picture is the same on every render
function rng(seed) {
return () => (seed = (Math.imul(seed, 1103515245) + 12345) & 0x7fffffff) / 0x7fffffff
}
const text = (x, y, s, { size = 9, fill = MUTED, anchor = 'start' } = {}) =>
`<text x="${f(x)}" y="${f(y)}"${anchor === 'start' ? '' : ` text-anchor="${anchor}"`} font-size="${size}" font-family="${FONT}" fill="${fill}">${s}</text>`
const comma = n => String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ',')
// Log-normal latencies in ms: exp of a normal draw, scaled to a 380 ms median
function sample() {
const rnd = rng(20261036)
const out = []
for (let i = 0; i < N; i++) {
const u = 1 - rnd() // (0, 1], so the log stays finite
const z = Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * rnd())
out.push(380 * Math.exp(0.55 * z))
}
return out.sort((a, b) => a - b)
}
const quantile = (sorted, p) => {
const i = (sorted.length - 1) * p
const lo = Math.floor(i)
return sorted[lo] + (sorted[Math.min(lo + 1, sorted.length - 1)] - sorted[lo]) * (i - lo)
}
function draw() {
const ms = sample()
const bins = new Array(BINS).fill(0)
let over = 0
for (const v of ms) v >= END ? over++ : bins[Math.floor(v / BIN_MS)]++
const peakAt = bins.indexOf(Math.max(...bins))
const peak = bins[peakAt]
const top = Math.ceil(peak / 100) * 100 // the count scale ends on a whole hundred just past the peak
const slot = (PLOT.right - PLOT.left) / BINS
const bw = slot * 0.62
const x = v => PLOT.left + ((PLOT.right - PLOT.left) * v) / END
const y = c => PLOT.bottom - ((PLOT.bottom - PLOT.top) * c) / top
const parts = [`<rect width="${W}" height="${H}" rx="4" fill="${STAGE}"/>`]
parts.push(text(12, 18, `${comma(N)} TOOL CALLS`))
if (over) parts.push(text(W - 12, 18, `${over} OVER ${comma(END)} MS`, { anchor: 'end' }))
// Count grid: hairlines on whole hundreds, labelled on the left
for (let c = 0; c <= top; c += 100) {
const gy = Math.round(y(c)) + 0.5
parts.push(`<line x1="${PLOT.left}" x2="${PLOT.right}" y1="${f(gy)}" y2="${f(gy)}" stroke="${INK}" stroke-opacity="0.08" stroke-width="1"/>`)
parts.push(text(28, gy + 3, String(c), { anchor: 'end' }))
}
bins.forEach((c, i) => {
if (!c) return
const bx = PLOT.left + slot * i + (slot - bw) / 2
const by = y(c)
parts.push(`<rect x="${f(bx)}" y="${f(by)}" width="${f(bw)}" height="${f(Math.max(1, PLOT.bottom - by))}" fill="${INK}" fill-opacity="0.55"/>`)
})
parts.push(text(PLOT.left + slot * (peakAt + 0.5), y(peak) - 4, String(peak), { fill: INK, anchor: 'middle' }))
// Time ticks: 4px stubs under the plot, the unit at the right end of the row
for (let t = 0; t < END; t += 500) {
const tx = Math.round(x(t)) + 0.5
parts.push(`<line x1="${f(tx)}" x2="${f(tx)}" y1="${PLOT.bottom + 2}" y2="${PLOT.bottom + 6}" stroke="${MUTED}" stroke-width="1"/>`)
parts.push(text(tx, PLOT.bottom + 16, comma(t), { anchor: 'middle' }))
}
parts.push(text(W - 12, PLOT.bottom + 16, 'MS', { anchor: 'end' }))
// Percentiles: a dashed line from the label row to the plot floor, the label just right of its top.
// A label that would run into the next one, or past W - 12, moves to the left side of its line.
const marks = [['P50', 0.5, INK], ['P95', 0.95, INK], ['P99', 0.99, CLAUDE]].map(([name, p, color]) => ({ name, color, v: Math.round(quantile(ms, p)) }))
marks.forEach((m, i) => {
const mx = Math.round(x(m.v)) + 0.5
const accent = m.color === CLAUDE
parts.push(`<line x1="${f(mx)}" x2="${f(mx)}" y1="30" y2="${PLOT.bottom}" stroke="${m.color}"${accent ? '' : ' stroke-opacity="0.35"'} stroke-width="1" stroke-dasharray="3 3"/>`)
const label = `${m.name} ${comma(m.v)}`
const wide = label.length * 5 * 1.1
const next = marks[i + 1] ? Math.round(x(marks[i + 1].v)) : W - 12
const right = mx + 4 + wide < next - 4
const name = `<tspan fill="${accent ? CLAUDE : MUTED}">${m.name}</tspan>`
parts.push(`<text x="${f(right ? mx + 4 : mx - 4)}" y="37"${right ? '' : ' text-anchor="end"'} font-size="9" font-family="${FONT}" fill="${m.color}">${name} ${comma(m.v)}</text>`)
})
parts.push(text(12, H - 8, 'SAMPLE TOOL CALL LATENCY'))
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}">${parts.join('')}</svg>`
}
let source // drawn on first use and kept: the pane loads every design, so none pays at load time
export default {
id: 'histogram',
title: 'Histogram',
category: 'spread',
description: 'Tool call latency in 30 bins with a long right tail; dashed p50, p95 and p99 lines carry their values, p99 in Claude orange.',
element: ui => ui.Svg({ source: (source ??= draw()), alt: `Histogram of ${comma(N)} tool call latencies in ${BINS} bins with p50, p95 and p99 marked, sample data`, width: W, height: H }),
}
To use it in a mod: copy the file to your mod's hooks/designs/ and return its element(ui) from a pane's ui.render. How a pane is built.
Code: PolyForm Noncommercial License 1.0.0: noncommercial use only. Required Notice: Copyright 2026 Haruki Nakada (https://modscode.com).