1 GitHub star = 1 new component.

Star the repo
Charts & Stats chartsvgzero-dependencygaugekpigoalprogress

Gauge Chart

Semicircular progress gauge for a single KPI against a goal with the number always shown as text.

Live Preview

Monthly sales goal

November

$73.4k 73% of $100k goal
View data
MetricValueGoal% of goal
Monthly sales goal$73.4k$100k73.4%

Usage

Copy and paste this code into your Astro page:

astro
---
import { GaugeChart } from 'astro-component-kit/charts';
---

<GaugeChart title="Sales goal" value={73400} max={100000} prefix="$" label="of $100k goal" />
--- import { GaugeChart } from 'astro-component-kit/charts'; --- <GaugeChart title="Sales goal" value={73400} max={100000} prefix="$" label="of $100k goal" />

Manual Installation

If you are not using the npm package, create a new file and paste the following code: src/components/lib/GaugeChart.astro:

astro
---
/**
 * GaugeChart — Semicircular progress gauge for a single KPI against a goal,
 * with the number and % of goal always shown as text.
 *
 * @param {number} value - Current value.
 * @param {number} max - Goal / maximum.
 * @param {string} label - Caption under the number.
 * @param {string} prefix - Value prefix.
 * @param {string} suffix - Value suffix.
 * @param {string} color - Arc color.
 * @param {string} variant - Card style (glass|solid|ghost).
 * @param {string} size - Density (sm|md|lg).
 */
import ChartFrame from './ChartFrame.astro';
import { arcStroke, formatValue } from './chart-utils';

interface Props {
  title?: string;
  subtitle?: string;
  value?: number;
  max?: number;
  label?: string;
  prefix?: string;
  suffix?: string;
  color?: string;
  variant?: 'glass' | 'solid' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
  className?: string;
}

const {
  title = 'Monthly sales goal',
  subtitle = 'November',
  value = 73400,
  max = 100000,
  label = 'of $100k goal',
  prefix = '$',
  suffix = '',
  color = 'var(--chart-1, #3987e5)',
  variant = 'glass',
  size = 'md',
  className = '',
} = Astro.props;

const W = 260;
const H = 150;
const cx = W / 2;
const cy = 132;
const r = 108;
const pct = Math.max(0, Math.min(1, value / (max || 1)));
const a0 = -Math.PI / 2;
const a1 = Math.PI / 2;
const fmt = (v: number) => formatValue(v, prefix, suffix);
---

<ChartFrame
  title={title}
  subtitle={subtitle}
  variant={variant}
  size={size}
  className={className}
  table={{ headers: ['Metric', 'Value', 'Goal', '% of goal'], rows: [[title, fmt(value), fmt(max), `${(pct * 100).toFixed(1)}%`]] }}
>
  <div class="gc" role="meter" aria-valuemin={0} aria-valuemax={max} aria-valuenow={value} aria-label={`${title}: ${fmt(value)} ${label}, ${(pct * 100).toFixed(0)}%`}>
    <svg viewBox={`0 0 ${W} ${H}`} aria-hidden="true">
      <path d={arcStroke(cx, cy, r, a0, a1)} fill="none" stroke="rgb(var(--ink-rgb, 255 255 255) / 0.07)" stroke-width="18" stroke-linecap="round" />
      {pct > 0 && <path class="gc__arc" d={arcStroke(cx, cy, r, a0, a0 + Math.PI * pct)} fill="none" stroke={color} stroke-width="18" stroke-linecap="round" />}
      <text class="cf-label" x={cx - r} y={H - 2} text-anchor="middle">0</text>
      <text class="cf-label" x={cx + r} y={H - 2} text-anchor="middle">{fmt(max)}</text>
    </svg>
    <div class="gc__center">
      <span class="gc__value">{fmt(value)}</span>
      <span class="gc__label">{(pct * 100).toFixed(0)}% {label}</span>
    </div>
  </div>
</ChartFrame>

<style>
  .gc {
    position: relative;
    max-width: 340px;
    margin: 0 auto;
  }
  .gc__arc {
    stroke-dasharray: 400;
    stroke-dashoffset: 400;
    animation: gc-draw 1.1s cubic-bezier(0.4, 0, 0.2, 1) forwards;
  }
  @keyframes gc-draw { to { stroke-dashoffset: 0; } }
  .gc__center {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 6%;
    display: flex;
    flex-direction: column;
    align-items: center;
  }
  .gc__value {
    font-size: clamp(1.5rem, 5vw, 2rem);
    font-weight: 750;
    color: var(--c-text-1, #fff);
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
  }
  .gc__label { font-size: 0.75rem; color: var(--c-text-2, #a3a3a3); }
  @media (prefers-reduced-motion: reduce) {
    .gc__arc { animation: none; stroke-dashoffset: 0; }
  }
</style>
--- /** * GaugeChart — Semicircular progress gauge for a single KPI against a goal, * with the number and % of goal always shown as text. * * @param {number} value - Current value. * @param {number} max - Goal / maximum. * @param {string} label - Caption under the number. * @param {string} prefix - Value prefix. * @param {string} suffix - Value suffix. * @param {string} color - Arc color. * @param {string} variant - Card style (glass|solid|ghost). * @param {string} size - Density (sm|md|lg). */ import ChartFrame from './ChartFrame.astro'; import { arcStroke, formatValue } from './chart-utils'; interface Props { title?: string; subtitle?: string; value?: number; max?: number; label?: string; prefix?: string; suffix?: string; color?: string; variant?: 'glass' | 'solid' | 'ghost'; size?: 'sm' | 'md' | 'lg'; className?: string; } const { title = 'Monthly sales goal', subtitle = 'November', value = 73400, max = 100000, label = 'of $100k goal', prefix = '$', suffix = '', color = 'var(--chart-1, #3987e5)', variant = 'glass', size = 'md', className = '', } = Astro.props; const W = 260; const H = 150; const cx = W / 2; const cy = 132; const r = 108; const pct = Math.max(0, Math.min(1, value / (max || 1))); const a0 = -Math.PI / 2; const a1 = Math.PI / 2; const fmt = (v: number) => formatValue(v, prefix, suffix); --- <ChartFrame title={title} subtitle={subtitle} variant={variant} size={size} className={className} table={{ headers: ['Metric', 'Value', 'Goal', '% of goal'], rows: [[title, fmt(value), fmt(max), `${(pct * 100).toFixed(1)}%`]] }} > <div class="gc" role="meter" aria-valuemin={0} aria-valuemax={max} aria-valuenow={value} aria-label={`${title}: ${fmt(value)} ${label}, ${(pct * 100).toFixed(0)}%`}> <svg viewBox={`0 0 ${W} ${H}`} aria-hidden="true"> <path d={arcStroke(cx, cy, r, a0, a1)} fill="none" stroke="rgb(var(--ink-rgb, 255 255 255) / 0.07)" stroke-width="18" stroke-linecap="round" /> {pct > 0 && <path class="gc__arc" d={arcStroke(cx, cy, r, a0, a0 + Math.PI * pct)} fill="none" stroke={color} stroke-width="18" stroke-linecap="round" />} <text class="cf-label" x={cx - r} y={H - 2} text-anchor="middle">0</text> <text class="cf-label" x={cx + r} y={H - 2} text-anchor="middle">{fmt(max)}</text> </svg> <div class="gc__center"> <span class="gc__value">{fmt(value)}</span> <span class="gc__label">{(pct * 100).toFixed(0)}% {label}</span> </div> </div> </ChartFrame> <style> .gc { position: relative; max-width: 340px; margin: 0 auto; } .gc__arc { stroke-dasharray: 400; stroke-dashoffset: 400; animation: gc-draw 1.1s cubic-bezier(0.4, 0, 0.2, 1) forwards; } @keyframes gc-draw { to { stroke-dashoffset: 0; } } .gc__center { position: absolute; left: 0; right: 0; bottom: 6%; display: flex; flex-direction: column; align-items: center; } .gc__value { font-size: clamp(1.5rem, 5vw, 2rem); font-weight: 750; color: var(--c-text-1, #fff); font-variant-numeric: tabular-nums; line-height: 1.1; } .gc__label { font-size: 0.75rem; color: var(--c-text-2, #a3a3a3); } @media (prefers-reduced-motion: reduce) { .gc__arc { animation: none; stroke-dashoffset: 0; } } </style>

Quick Info

Category
Charts & Stats
Filename
GaugeChart.astro
Dependencies
apexcharts (peer)
Tags
chartsvgzero-dependencygaugekpigoalprogress