ModsCodeMERGEBLE LLC Search Claude Code Mods

ModsCode / Gallery / Designs / Radar chart

Radar chart

Six axes with two series, the newer one filled, with a dot on each vertex so single values can be read.

Radar chartRadar chart
Compare · 380×320 · sample data
hooks/designs/radar.mjs
// radar: six axes in candle's look, with four 1px rings and six spokes at 12% ink. The newer series is an
// up-green fill at 25% with an up stroke; the older one is an ink line at 60%. Each vertex has a dot so a
// single value can be read off the chart. Axis names sit just outside the outer ring. Sample data only.
const W = 380
const H = 320
const CX = 190
const CY = 164
const R = 116
const AXES = ['Trend', 'Momentum', 'Volume', 'Volatility', 'Breadth', 'Value']
const UP = '#2C7A39'
const INK = '#141413'
const MUTED = '#73726C'
const STAGE = '#FAF9F5'
const FONT = "'Inter Tight','Segoe UI',system-ui,sans-serif"

// A seeded pair of readings per axis, as fractions of the outer ring
function sample() {
  let seed = 20261009
  const rnd = () => (seed = (Math.imul(seed, 1103515245) + 12345) & 0x7fffffff) / 0x7fffffff
  const now = AXES.map(() => 0.35 + rnd() * 0.6)
  const last = AXES.map(() => 0.25 + rnd() * 0.6)
  return { now, last }
}

// Axis 0 points up and the rest run clockwise, so the first name sits at twelve o'clock
const angle = k => -Math.PI / 2 + (k * Math.PI) / 3
const f = n => n.toFixed(1)
const at = (k, r) => [CX + r * Math.cos(angle(k)), CY + r * Math.sin(angle(k))]
const shape = radii => radii.map((r, k) => at(k, r).map(f).join(',')).join(' ')
const text = (x, y, s, anchor, size, fill) =>
  `<text x="${f(x)}" y="${f(y)}" text-anchor="${anchor}" font-size="${size}" font-family="${FONT}" fill="${fill}">${s}</text>`
const dot = (x, y, fill, opacity) => `<circle cx="${f(x)}" cy="${f(y)}" r="2" fill="${fill}" fill-opacity="${opacity}"/>`

function radarSource({ now, last }) {
  const parts = [`<rect width="${W}" height="${H}" rx="4" fill="${STAGE}"/>`]
  for (const share of [0.25, 0.5, 0.75, 1]) {
    parts.push(`<polygon points="${shape(AXES.map(() => R * share))}" fill="none" stroke="${INK}" stroke-opacity="0.12" stroke-width="1"/>`)
  }
  AXES.forEach((_, k) => {
    const [x, y] = at(k, R)
    parts.push(`<line x1="${CX}" y1="${CY}" x2="${f(x)}" y2="${f(y)}" stroke="${INK}" stroke-opacity="0.12" stroke-width="1"/>`)
  })
  const nowR = now.map(v => v * R)
  const lastR = last.map(v => v * R)
  parts.push(`<polygon points="${shape(nowR)}" fill="${UP}" fill-opacity="0.25" stroke="${UP}" stroke-width="1"/>`)
  parts.push(`<polygon points="${shape(lastR)}" fill="none" stroke="${INK}" stroke-opacity="0.6" stroke-width="1"/>`)
  nowR.forEach((r, k) => {
    const [x, y] = at(k, r)
    parts.push(dot(x, y, UP, 1))
  })
  lastR.forEach((r, k) => {
    const [x, y] = at(k, r)
    parts.push(dot(x, y, INK, 0.6))
  })
  AXES.forEach((name, k) => {
    const c = Math.cos(angle(k))
    const s = Math.sin(angle(k))
    const [x, y] = at(k, R + 12)
    // Names at twelve and six o'clock sit above or below their tip; the side names are centred on it
    const anchor = c > 0.2 ? 'start' : c < -0.2 ? 'end' : 'middle'
    const dy = s < -0.9 ? 0 : s > 0.9 ? 9 : 3.5
    parts.push(text(x, y + dy, name, anchor, 10, MUTED))
  })
  parts.push(text(8, 314, 'SAMPLE 6 AXES', 'start', 9, MUTED))
  parts.push(`<line x1="262" y1="310.5" x2="274" y2="310.5" stroke="${UP}" stroke-width="1"/>`)
  parts.push(text(278, 314, 'NOW', 'start', 9, UP))
  parts.push(`<line x1="304" y1="310.5" x2="316" y2="310.5" stroke="${INK}" stroke-opacity="0.6" stroke-width="1"/>`)
  parts.push(text(320, 314, 'LAST QTR', 'start', 9, MUTED))
  return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}">${parts.join('')}</svg>`
}

const SOURCE = radarSource(sample())

export default {
  id: 'radar',
  title: 'Radar chart',
  category: 'compare',
  description: 'Six axes with two series, the newer one filled, with a dot on each vertex so single values can be read.',
  element: ui => ui.Svg({ source: SOURCE, alt: 'Radar chart of six axes with two sample series', 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).