ModsCodeMERGEBLE LLC Search Claude Code Mods

ModsCode / Gallery / Designs / Violin plot

Violin plot

Seconds per turn for four models as mirrored density shapes, with an interquartile bar and a median line; two of the shapes have two bulges.

Violin plotViolin plot
Spread · 380×240 · sample data
hooks/designs/violin.mjs
// violin: seconds per turn for four models, 300 turns each, in candle's look. Opus and Sonnet are
// mixtures of two log-normals, quick replies (median 9 s and 6 s) and tool-heavy turns (28 s and 19 s),
// so their shapes have two bulges: the thing a violin shows and a box plot hides. Haiku (3.5 s) and a
// local model (15 s) are single log-normals. Each shape is a kernel density estimate with the
// Epanechnikov kernel, 0.75 (1 - u^2), at Silverman's rule-of-thumb bandwidth (0.9 min(sd, IQR/1.34)
// n^-1/5) times 2.214, the factor that carries a Gaussian bandwidth over to this kernel (Marron and
// Nolan's canonical bandwidths). It is evaluated at 64 steps from one bandwidth below each model's
// fastest turn (never under 0 s) to one above its slowest, where this kernel reaches exactly zero, so
// every shape closes to a point, and mirrored as one path at 25% ink. Every shape is scaled
// to the same widest point, 62% of its slot (candle's bar share), since the models' spreads differ
// too much for one density scale. The interquartile range is a 4px bar at 55% ink and the median a
// 1px full-ink line across the shape, with the median printed under the name. Every number is
// computed in draw(); with this seed the medians land on 16.5, 8.1, 3.7 and 14.7 s. Normal draws use
// the Box-Muller transform (Box and Muller, 1958); quartiles interpolate linearly between order
// statistics, the rule of d3-array's quantile (ISC), written out here. Sample data only.
const W = 380
const H = 240
const STAGE = '#FAF9F5'
const INK = '#141413'
const MUTED = '#73726C'
const FONT = "'Inter Tight','Segoe UI',system-ui,sans-serif"
const PLOT = { left: 36, right: 368, top: 32, bottom: 186 }
const TURNS = 300
const STEPS = 64
const f = n => n.toFixed(1)

// Each model is a mixture of log-normals: [weight, median seconds, log sigma]
const MODELS = [
  ['Opus', [[0.55, 9, 0.35], [0.45, 28, 0.22]]],
  ['Sonnet', [[0.65, 6, 0.35], [0.35, 19, 0.25]]],
  ['Haiku', [[1, 3.5, 0.45]]],
  ['Local', [[1, 15, 0.33]]],
]

// 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 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 sample() {
  const rnd = rng(20261039)
  const normal = () => Math.sqrt(-2 * Math.log(1 - rnd())) * Math.cos(2 * Math.PI * rnd())
  return MODELS.map(([name, mix]) => {
    const s = []
    for (let i = 0; i < TURNS; i++) {
      let u = rnd()
      let k = 0
      while (k < mix.length - 1 && u > mix[k][0]) u -= mix[k++][0]
      s.push(mix[k][1] * Math.exp(mix[k][2] * normal()))
    }
    return { name, s: s.sort((a, b) => a - b) }
  })
}

// Density at STEPS points from one bandwidth below the fastest turn (never under 0 s) to one above the
// slowest: the kernel is exactly zero past one bandwidth, so the shape closes to a point, not a flat cut
function density(s) {
  const n = s.length
  const mean = s.reduce((a, v) => a + v, 0) / n
  const sd = Math.sqrt(s.reduce((a, v) => a + (v - mean) ** 2, 0) / (n - 1))
  const iqr = quantile(s, 0.75) - quantile(s, 0.25)
  const h = 2.214 * 0.9 * Math.min(sd, iqr / 1.34) * n ** -0.2
  const lo = Math.max(0, s[0] - h)
  const hi = s[n - 1] + h
  const pts = Array.from({ length: STEPS + 1 }, (_, i) => {
    const t = lo + ((hi - lo) * i) / STEPS
    let d = 0
    for (const v of s) {
      const u = (t - v) / h
      if (u > -1 && u < 1) d += 0.75 * (1 - u * u)
    }
    return [t, d / (n * h)]
  })
  return { pts, lo, hi }
}

function draw() {
  const models = sample()
  const top = Math.ceil(Math.max(...models.map(m => density(m.s).hi)) / 10) * 10 // a whole 10 s past the highest shape
  const y = v => PLOT.bottom - ((PLOT.bottom - PLOT.top) * v) / top
  const slot = (PLOT.right - PLOT.left) / models.length
  const half = (slot * 0.62) / 2
  const parts = [`<rect width="${W}" height="${H}" rx="4" fill="${STAGE}"/>`]

  parts.push(text(12, 18, 'SECONDS PER TURN'))
  parts.push(text(W - 12, 18, `${TURNS} TURNS EACH`, { anchor: 'end' }))
  for (let v = 0; v <= top; v += 20) {
    const gy = Math.round(y(v)) + 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(v), { anchor: 'end' }))
  }

  models.forEach((m, i) => {
    const cx = PLOT.left + slot * (i + 0.5)
    const { pts: dens, lo, hi } = density(m.s)
    const dmax = Math.max(...dens.map(d => d[1]))
    const w = d => (half * d) / dmax
    const right = dens.map(([t, d]) => `${f(cx + w(d))} ${f(y(t))}`)
    const left = dens.map(([t, d]) => `${f(cx - w(d))} ${f(y(t))}`).reverse()
    parts.push(`<path d="M${right.join('L')}L${left.join('L')}Z" fill="${INK}" fill-opacity="0.25"/>`)

    const q1 = quantile(m.s, 0.25)
    const q2 = quantile(m.s, 0.5)
    const q3 = quantile(m.s, 0.75)
    parts.push(`<rect x="${f(cx - 2)}" y="${f(y(q3))}" width="4" height="${f(y(q1) - y(q3))}" fill="${INK}" fill-opacity="0.55"/>`)
    // The median line spans the shape's width at the median, read off the nearest density step
    const step = Math.round(((q2 - lo) / (hi - lo)) * STEPS)
    const mw = Math.max(4, w(dens[step][1]))
    const my = Math.round(y(q2)) + 0.5
    parts.push(`<line x1="${f(cx - mw)}" x2="${f(cx + mw)}" y1="${f(my)}" y2="${f(my)}" stroke="${INK}" stroke-width="1"/>`)

    parts.push(text(cx, PLOT.bottom + 15, m.name, { size: 10, fill: INK, anchor: 'middle' }))
    parts.push(`<text x="${f(cx)}" y="${PLOT.bottom + 27}" text-anchor="middle" font-size="9" font-family="${FONT}" fill="${MUTED}">P50 <tspan fill="${INK}">${q2.toFixed(1)} S</tspan></text>`)
  })

  parts.push(text(12, H - 8, 'SAMPLE TURN TIMES BY MODEL'))
  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: 'violin',
  title: 'Violin plot',
  category: 'spread',
  description: 'Seconds per turn for four models as mirrored density shapes, with an interquartile bar and a median line; two of the shapes have two bulges.',
  element: ui => ui.Svg({ source: (source ??= draw()), alt: `Violin plot of seconds per turn for ${MODELS.length} models, ${TURNS} turns each, with medians and interquartile ranges, 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).