ModsCodeMERGEBLE LLC Search Claude Code Mods

ModsCode / Gallery / Designs / Dendrogram

Dendrogram

Agent skills clustered by shared sessions as a tree from root to leaves, with runs per skill and one cluster in Claude orange.

DendrogramDendrogram
Flows and links · 380×300 · sample data
hooks/designs/dendrogram.mjs
// dendrogram: twenty agent skills grouped by how often they are used in the same session, as a tree
// from a root at the left to the skills at the right, with each skill's runs in the last 30 days. The
// grouping is a hand-made sample (three families, seven clusters) and the layout is d3-hierarchy's
// cluster() (ISC, Mike Bostock), which puts every leaf in one column; separation is 1, so the twenty
// rows are evenly spaced (the default doubles the gap between cousins and left the rows uneven). Links
// are elbows at candle's 35% reference ink, drawn as one path so the shared trunks do not build up where
// they overlap. The document skills (pdf, docx, xlsx, pptx, csv-clean, 182 runs) are the one Claude orange
// branch: the cluster the week leaned on, and its label carries that total. Cluster names sit on the line
// into their joint, uppercase at 9px; leaf names are 10px and the run counts are mono, right-aligned at W - 12. Sample data only.
import { hierarchy, cluster } from '../vendor/d3.mjs'

const W = 380
const H = 300
const STAGE = '#FAF9F5'
const INK = '#141413'
const MUTED = '#73726C'
const CLAUDE = '#D77757'
const FONT = "'Inter Tight','Segoe UI',system-ui,sans-serif"
const MONO = "ui-monospace,'Cascadia Mono',Consolas,monospace"
const ROOT_X = 12
const LEAF_X = 236 // the longest name (15 characters, about 92px at 10px with slack) still clears the counts
const TOP = 34
const BOTTOM = 270
const NOTICE = 'Docs' // the one accent branch
const f = n => n.toFixed(1)

// Family > cluster > [skill, runs in the last 30 days]
const TREE = {
  name: 'Skills',
  children: [
    { name: 'Make', children: [
      { name: 'Docs', children: [['pdf', 64], ['docx', 41], ['xlsx', 37], ['pptx', 22], ['csv-clean', 18]] },
      { name: 'Visual', children: [['dataviz', 29], ['artifact-design', 33], ['diagram', 15]] },
    ] },
    { name: 'Code', children: [
      { name: 'Review', children: [['code-review', 128], ['simplify', 46], ['security-review', 24]] },
      { name: 'Fix', children: [['test-fix', 71], ['lint', 52]] },
      { name: 'Build', children: [['claude-api', 39], ['mcp-builder', 12], ['skill-creator', 9]] },
    ] },
    { name: 'Ops', children: [
      { name: 'Run', children: [['run', 88], ['deploy', 31]] },
      { name: 'Repeat', children: [['loop', 17], ['schedule', 11]] },
    ] },
  ],
}

// The alt counts from the data, so it stays true when the sample changes
const leafCount = n => (Array.isArray(n) ? 1 : n.children.reduce((s, c) => s + leafCount(c), 0))
const clusterCount = n => (Array.isArray(n) ? 0 : n.children.some(Array.isArray) ? 1 : n.children.reduce((s, c) => s + clusterCount(c), 0))

const esc = s => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
const text = (x, y, s, { size = 9, fill = MUTED, anchor = 'start', font = FONT } = {}) =>
  `<text x="${f(x)}" y="${f(y)}"${anchor === 'start' ? '' : ` text-anchor="${anchor}"`} font-size="${size}" font-family="${font}" fill="${fill}">${esc(s)}</text>`

function draw() {
  const root = hierarchy(TREE, d => (Array.isArray(d) ? null : d.children))
  // cluster() lays x along the rows and y along the depth; here y becomes the horizontal position
  cluster().size([BOTTOM - TOP, LEAF_X - ROOT_X]).separation(() => 1)(root)
  const px = n => ROOT_X + n.y
  const py = n => TOP + n.x
  const inNotice = n => n.ancestors().some(a => a.data.name === NOTICE)

  // Each internal node draws its own trunk and the arms to its children; the Claude orange branch goes to its own path
  let ink = ''
  let accent = ''
  for (const n of root.descendants()) {
    if (!n.children) continue
    const ys = n.children.map(py)
    const trunk = `M${f(px(n))} ${f(Math.min(...ys))}V${f(Math.max(...ys))}`
    if (inNotice(n)) accent += trunk
    else ink += trunk
    for (const c of n.children) {
      const arm = `M${f(px(n))} ${f(py(c))}H${f(px(c))}`
      if (inNotice(c)) accent += arm
      else ink += arm
    }
  }

  const parts = [`<rect width="${W}" height="${H}" rx="4" fill="${STAGE}"/>`]
  parts.push(text(LEAF_X + 6, 20, 'SKILL'))
  parts.push(text(W - 12, 20, 'RUNS 30D', { anchor: 'end' }))
  parts.push(`<path d="${ink}" fill="none" stroke="${INK}" stroke-opacity="0.35"/>`)
  parts.push(`<path d="${accent}" fill="none" stroke="${CLAUDE}" stroke-opacity="0.9"/>`)

  for (const leaf of root.leaves()) {
    const [name, runs] = leaf.data
    const y = py(leaf)
    const hot = inNotice(leaf)
    parts.push(`<circle cx="${f(LEAF_X)}" cy="${f(y)}" r="2" fill="${hot ? CLAUDE : INK}" fill-opacity="${hot ? 0.9 : 0.55}"/>`)
    parts.push(text(LEAF_X + 6, y + 3.5, name, { size: 10, fill: INK }))
    parts.push(text(W - 12, y + 3, runs, { font: MONO, anchor: 'end' }))
  }

  // Cluster and family names ride on the line into their joint, ending 4px before it
  for (const n of root.descendants()) {
    if (!n.children || !n.parent) continue
    const hot = n.data.name === NOTICE
    // The Claude orange cluster also carries its runs, the number the branch is there to show
    const label = hot ? `${n.data.name.toUpperCase()} ${n.leaves().reduce((s, l) => s + l.data[1], 0)}` : n.data.name.toUpperCase()
    parts.push(text(px(n) - 4, py(n) - 4, label, { anchor: 'end', fill: hot ? CLAUDE : MUTED }))
  }

  const runs = root.leaves().reduce((s, l) => s + l.data[1], 0)
  parts.push(text(W - 12, H - 8, `TOTAL ${runs}`, { anchor: 'end' }))
  parts.push(text(12, H - 8, `SAMPLE ${root.leaves().length} SKILLS BY SHARED SESSIONS`))
  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: 'dendrogram',
  title: 'Dendrogram',
  category: 'flow',
  description: 'Agent skills clustered by shared sessions as a tree from root to leaves, with runs per skill and one cluster in Claude orange.',
  element: ui => ui.Svg({ source: (source ??= draw()), alt: `Dendrogram of ${leafCount(TREE)} skills in ${clusterCount(TREE)} clusters with their runs in the last 30 days, the ${NOTICE.toLowerCase()} skills in Claude orange, sample data`, width: W, height: H }),
}

Imports d3-hierarchy 3.1.2 (ISC), bundled with its licence in look's hooks/vendor/.

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).