Skip to main content
Compendium / Metrics & Telemetry / Gauges / Dial Gauge
#703

Dial Gauge

Clock-face style dial with tick marks and a hard needle — analogue feel for analogue thinkers.

STABLE DE
#svg #gauge #real-content #responsive
Live
47 PSI
Spec

Intent

Some measurements are easier to reason about as a clock face — render a tick-ringed dial with a needle for that intuition.

Acceptance Criteria

  • [ ] 270-degree dial with tick marks at intervals
  • [ ] Hard, angular needle (no shadow, no curve)
  • [ ] Numeric readout centre
  • [ ] Negative: does NOT smooth needle motion

Constraints

  • Pure SVG
  • Theme-aware tokens
// dial-gauge.astro
---
import type { CompendiumMeta } from '../../../data/types';

export const meta: CompendiumMeta = {
  id: 703,
  slug: 'dial-gauge',
  name: 'Dial Gauge',
  category: 'metrics-telemetry',
  subcategory: 'gauges',
  pillar: 'de',
  description: 'Clock-face style dial with tick marks and a hard needle — analogue feel for analogue thinkers.',
  spec: `## Intent
Some measurements are easier to reason about as a clock face — render a tick-ringed dial with a needle for that intuition.

## Acceptance Criteria
- [ ] 270-degree dial with tick marks at intervals
- [ ] Hard, angular needle (no shadow, no curve)
- [ ] Numeric readout centre
- [ ] Negative: does NOT smooth needle motion

## Constraints
- Pure SVG
- Theme-aware tokens`,
  tags: ['svg', 'gauge', 'real-content', 'responsive'],
  status: 'stable',
  health: { ics: 87, a11y: 'aa', themed: true, animated: false },
};

const value = 47;
const max = 100;
const startAngle = 135;
const sweep = 270;
const angle = startAngle + (value / max) * sweep;
const rad = (angle * Math.PI) / 180;
const cx = 100;
const cy = 100;
const needleR = 62;
const tipX = cx + Math.cos(rad) * needleR;
const tipY = cy + Math.sin(rad) * needleR;

const ticks = Array.from({ length: 11 }, (_, i) => {
  const a = startAngle + (i / 10) * sweep;
  const r = (a * Math.PI) / 180;
  return {
    x1: cx + Math.cos(r) * 72,
    y1: cy + Math.sin(r) * 72,
    x2: cx + Math.cos(r) * 82,
    y2: cy + Math.sin(r) * 82,
  };
});
---

<div data-this-component class="w-full bg-paper text-black border-4 border-black p-4 flex items-center justify-center overflow-hidden">
  <svg viewBox="0 0 200 200" class="w-full max-w-[220px] block" aria-label={`Dial value ${value}`}>
    <circle cx={cx} cy={cy} r="84" fill="none" stroke="var(--color-ink)" stroke-width="3" />
    {ticks.map((t, i) => (
      <line x1={t.x1} y1={t.y1} x2={t.x2} y2={t.y2} stroke="var(--color-ink)" stroke-width={i % 5 === 0 ? 3 : 1.5} />
    ))}
    <line x1={cx} y1={cy} x2={tipX} y2={tipY} stroke="var(--color-de)" stroke-width="5" />
    <circle cx={cx} cy={cy} r="8" fill="var(--color-ink)" />
    <circle cx={cx} cy={cy} r="3" fill="var(--color-paper)" />
    <text x={cx} y="160" text-anchor="middle" font-family="Inter, sans-serif" font-weight="900" font-size="22" fill="var(--color-ink)">{value}</text>
    <text x={cx} y="178" text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="9" letter-spacing="2" fill="var(--color-ink)" opacity="0.6">PSI</text>
  </svg>
</div>