Skip to main content
Compendium / Governance & Risk / Thresholds / Threshold Line
#561

Threshold Line

A horizontal threshold marker drawn across a metric — the line where nominal becomes a breach.

STABLE GO
#svg #chart #real-content #dark-mode-tested #responsive
Live

Metric · vs threshold

value / 100
threshold 70/100
Spec

Intent

Thresholds are governance crystallised into a number. Render the line itself as a primitive so any chart, gauge, or sparkline can drop it in and inherit the same visual grammar.

Acceptance Criteria

  • [ ] Dashed horizontal rule at the threshold value
  • [ ] Label clamps to the right edge with the numeric value
  • [ ] Metric track is rendered beneath for context
  • [ ] Negative: does NOT carry chart semantics — pure marker

Constraints

  • Pure SVG, theme-aware via tokens
  • Self-contained — no shared imports
// threshold-line.astro
---
import type { CompendiumMeta } from '../../../data/types';

export const meta: CompendiumMeta = {
  id: 561,
  slug: 'threshold-line',
  name: 'Threshold Line',
  category: 'governance-risk',
  subcategory: 'thresholds',
  pillar: 'go',
  description: 'A horizontal threshold marker drawn across a metric — the line where nominal becomes a breach.',
  spec: `## Intent
Thresholds are governance crystallised into a number. Render the line itself as a primitive so any chart, gauge, or sparkline can drop it in and inherit the same visual grammar.

## Acceptance Criteria
- [ ] Dashed horizontal rule at the threshold value
- [ ] Label clamps to the right edge with the numeric value
- [ ] Metric track is rendered beneath for context
- [ ] Negative: does NOT carry chart semantics — pure marker

## Constraints
- Pure SVG, theme-aware via tokens
- Self-contained — no shared imports`,
  tags: ['svg', 'chart', 'real-content', 'dark-mode-tested', 'responsive'],
  status: 'stable',
  health: { ics: 88, a11y: 'aa', themed: true, animated: false },
};

const threshold = 70;
const series = [22, 28, 35, 41, 38, 52, 47, 61, 58, 73, 69, 64];
const max = 100;
const w = 200;
const h = 80;
const stepX = w / (series.length - 1);
const ty = h - (threshold / max) * h;
const points = series.map((v, i) => `${(i * stepX).toFixed(1)},${(h - (v / max) * h).toFixed(1)}`).join(' ');
---

<div data-this-component class="w-full bg-paper text-black border-4 border-black p-4 overflow-hidden">
  <div class="flex items-baseline justify-between mb-2">
    <p class="font-mono text-[10px] uppercase tracking-widest opacity-60">Metric · vs threshold</p>
    <span class="font-mono text-[10px] uppercase tracking-widest opacity-60">value / 100</span>
  </div>

  <svg viewBox={`0 0 ${w} ${h}`} class="w-full block" role="img" aria-label={`Threshold at ${threshold}`}>
    <polyline
      points={points}
      fill="none"
      stroke="var(--color-ink)"
      stroke-width="2"
      stroke-linecap="square"
      stroke-linejoin="miter"
    />
    <line
      x1="0" x2={w} y1={ty} y2={ty}
      stroke="var(--color-danger)"
      stroke-width="2"
      stroke-dasharray="6 4"
    />
  </svg>

  <div class="mt-2 flex items-baseline justify-between border-t-2 border-black pt-2">
    <span class="font-mono text-[10px] uppercase tracking-widest opacity-60">threshold</span>
    <span class="font-black text-[14px] tabular-nums">{threshold}<span class="opacity-60 text-[10px] ms-1">/100</span></span>
  </div>
</div>