Charts

Gauge

A half-circle gauge with a target tick: the empty part of the arc shows how much is left to plan.

  • chart
  • gauge
  • kpi
  • target

Preview

1440pxHost theme

Use it with AI

  1. 1. Copy the link.
  2. 2. Write to your agent in your own words and drop the link into the sentence.
  3. 3. The agent opens the link and installs the component from the registry.

put this in the header: https://vibeui.ru/c/chart-007?lang=en

Sales plan
74%процентов от планаЦель: 85%
Show the full instructions

What the link says, spelled out. Use it when your agent cannot open links — paste this text instead.

# Install and place "chart-007" (Gauge) from VibeUI

## 1. Install first — do not skip, do not recreate
Run this exact command before writing any code:
npx shadcn@latest add https://vibeui.ru/r/chart-007.json

Registry item: https://vibeui.ru/r/chart-007.json
Installs to: components/vibeui/chart-007.tsx (the exact path follows this project's components.json aliases).
npm dependencies: none.
Registry dependencies: none.

Install it from the registry. Do not recreate it from the description,
do not substitute a similar component from another library, and do not
rewrite it to match the project's existing style.

## 2. What it is
A half-circle gauge with a target tick: the empty part of the arc shows how much is left to plan.

A half-circle gauge: a filled arc, a target tick, a large number and a unit line. Zero dependencies, one file, no client JS.

## 3. How to use it
import { Chart007 } from "@/components/vibeui/chart-007"

<Chart007 title="Sales plan" value={74} target={85} />

Read the installed file for the full prop list.

## 4. Where to place it
This is a small inline component. Put it exactly where the user asked,
inside the existing markup. Do not create a new page, section or
wrapper for it. If a similar control already sits in that spot,
replace it instead of adding a second one.

Placement: ___
(The user fills this line in. If it is still blank, ask where to put it
instead of guessing.)

## 5. Keep exactly as installed
- the local --vibeui-chart-007-* palette — do not swap it for your theme tokens
- the half circle where the value has a ceiling: it shows what remains
- the target tick: "74%" without a plan says nothing about good or bad
- the number beside the arc — an arc's length never names a percentage
- role="img" with the value and target on the svg
- stroke-dasharray from the arc length instead of hand-built coordinates

## 6. You may change
- value, max and target
- title and unit
- the arc thickness
- the accent through the accent prop

## 7. Rules
- One metric per gauge: two values on one arc do not read.
- The value is clamped to 0–max: overshooting the plan is invisible here, use another chart for that.
- Without a target the component loses half its point: with no plan a plain number suffices.
- Do not lift the CSS variables into globals.css: the component has to stay a single file.

## 8. Verify
- it renders with no console errors;
- it looks like the preview on the VibeUI page you copied this from;
- if it does not, you changed something listed in section 5 — put it back.

For developers

npx shadcn@latest add https://vibeui.ru/r/chart-007.json
https://vibeui.ru/r/chart-007.json

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-chart-007-*. Серверный: хуков нет. Дуга рисуется одним path и заполняется через stroke-dasharray от длины полуокружности. Риска цели строится тригонометрией из доли: точка на радиусе и точка чуть дальше. У svg есть role="img" со значением и целью.

Component source

The same file your agent installs. Here in case you would rather copy it by hand.

import type { ComponentPropsWithoutRef, CSSProperties } from "react"

export type Chart007Props = Omit<
  ComponentPropsWithoutRef<"figure">,
  "children" | "title"
> & {
  title?: string
  value?: number
  max?: number
  unit?: string
  target?: number
  accent?: string
}

// Идея компонента: полукруглая шкала для одного показателя. Полукруг честнее
// кольца там, где значение имеет предел: пустая часть дуги показывает, сколько
// осталось. Цель отмечена риской — без неё «74%» не отвечает, хорошо это или
// плохо, а сравнивать надо именно с планом.
const STYLES = `
:where([data-vibeui-block="chart-007"]){
--vibeui-chart-007-bg:oklch(1 0 0);
--vibeui-chart-007-fg:oklch(0.22 0.014 265);
--vibeui-chart-007-muted:oklch(0.56 0.014 265);
--vibeui-chart-007-border:oklch(0.91 0.006 265);
--vibeui-chart-007-track:oklch(0.93 0.005 265);
--vibeui-chart-007-accent:oklch(0.55 0.17 265);
--vibeui-chart-007-mark:oklch(0.45 0.02 265);
--vibeui-chart-007-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
[data-vibeui-block="chart-007"]{
display:flex;flex-direction:column;align-items:center;gap:0.5rem;
width:100%;max-width:16rem;box-sizing:border-box;margin:0;padding:0.875rem;
background:var(--vibeui-chart-007-bg);
border:1px solid var(--vibeui-chart-007-border);border-radius:0.875rem;
color:var(--vibeui-chart-007-fg);font-family:var(--vibeui-chart-007-font);
}
[data-vibeui-block="chart-007"] [data-part="title"]{margin:0;font-size:0.875rem;font-weight:650;align-self:flex-start}
[data-vibeui-block="chart-007"] svg{display:block;width:100%;height:auto}
[data-vibeui-block="chart-007"] [data-part="track"]{
fill:none;stroke:var(--vibeui-chart-007-track);stroke-width:14;stroke-linecap:round;
}
[data-vibeui-block="chart-007"] [data-part="value"]{
fill:none;stroke:var(--vibeui-chart-007-accent);stroke-width:14;stroke-linecap:round;
}
/* Риска цели: без неё процент не отвечает, хорошо это или плохо. */
[data-vibeui-block="chart-007"] [data-part="target"]{stroke:var(--vibeui-chart-007-mark);stroke-width:2}
[data-vibeui-block="chart-007"] [data-part="number"]{
margin-top:-2.25rem;font-size:1.5rem;font-weight:700;line-height:1;
font-variant-numeric:tabular-nums;
}
[data-vibeui-block="chart-007"] [data-part="unit"]{font-size:0.75rem;color:var(--vibeui-chart-007-muted)}
[data-vibeui-block="chart-007"] [data-part="legend"]{
display:flex;align-items:center;gap:0.375rem;font-size:0.75rem;color:var(--vibeui-chart-007-muted);
}
[data-vibeui-block="chart-007"] [data-part="tick"]{width:0.125rem;height:0.625rem;background:var(--vibeui-chart-007-mark)}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="chart-007"] *{animation:none!important;transition:none!important}}
`

// Полукруг радиусом 70 в системе 160×90: длина дуги нужна для штриховки.
const RADIUS = 70
const ARC = Math.PI * RADIUS

function pointAt(share: number) {
  const angle = Math.PI * (1 - share)
  return {
    x: 80 + Math.cos(angle) * RADIUS,
    y: 80 - Math.sin(angle) * RADIUS,
  }
}

/**
 * Полукруглая шкала показателя с риской цели.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Chart007({
  title = "План продаж",
  value = 74,
  max = 100,
  unit = "процентов от плана",
  target = 85,
  accent,
  className,
  style,
  ...props
}: Chart007Props) {
  const share = Math.max(0, Math.min(1, value / (max || 1)))
  const targetShare = Math.max(0, Math.min(1, target / (max || 1)))
  const inner = pointAt(targetShare)
  const outer = {
    x: 80 + Math.cos(Math.PI * (1 - targetShare)) * (RADIUS + 9),
    y: 80 - Math.sin(Math.PI * (1 - targetShare)) * (RADIUS + 9),
  }

  const palette = {
    ...(accent ? { "--vibeui-chart-007-accent": accent } : null),
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-chart-007" precedence="medium">
        {STYLES}
      </style>
      <figure
        {...props}
        data-vibeui-block="chart-007"
        className={className}
        style={palette}
      >
        <figcaption data-part="title">{title}</figcaption>
        <svg
          viewBox="0 0 160 92"
          role="img"
          aria-label={`${title}: ${value} из ${max}, цель ${target}`}
        >
          <path data-part="track" d="M10 80 A70 70 0 0 1 150 80" />
          <path
            data-part="value"
            d="M10 80 A70 70 0 0 1 150 80"
            strokeDasharray={`${ARC * share} ${ARC}`}
          />
          <line
            data-part="target"
            x1={inner.x}
            y1={inner.y}
            x2={outer.x}
            y2={outer.y}
          />
        </svg>
        <span data-part="number">{value}%</span>
        <span data-part="unit">{unit}</span>
        <span data-part="legend">
          <span data-part="tick" aria-hidden="true" />
          Цель: {target}%
        </span>
      </figure>
    </>
  )
}