Files
Rpa_Skills/skills/rpa-dashboard-ai/templates/reference/web/components/Donut.tsx
T

96 lines
3.1 KiB
TypeScript

/* EXAMPLE ONLY: an extract from the Superbill Review prototype, kept as reference for porting. ECW/eClinicalWorks, Tebra, 'Example Client', SPB_* tables and patient/ticket wording are that project's examples. Don't copy them into a new project. */
/**
* A proportional ring.
*
* Earns its place only because the quantity it draws is genuinely a share of a
* whole — the split of every graded field across the confidence bands. A ring
* standing in for a single number would be decoration; this one is the only
* shape on the board that shows three parts against their total at once.
*
* It is `aria-hidden`, and that is deliberate rather than lazy: the legend
* beside it states every segment's name and count as text, so a screen reader
* gets the data in full and is not read a list of arc lengths. The chart is the
* fast path for someone looking; the legend is the actual record.
*/
export interface Slice {
key: string
label: string
value: number
color: string
}
export function Donut({
slices,
centerValue,
centerLabel,
size = 150,
thickness = 20,
/** Arc-length notch between segments, so two adjacent colours stay countable. */
gap = 3,
}: {
slices: Slice[]
centerValue: string
centerLabel: string
size?: number
thickness?: number
gap?: number
}) {
const r = (size - thickness) / 2
const c = 2 * Math.PI * r
const total = slices.reduce((n, s) => n + s.value, 0)
// Running start angle, in arc length. Accumulated rather than derived per
// index so a zero-value slice consumes nothing at all instead of leaving a
// gap where no data is.
let at = 0
const drawn = slices
.filter((s) => s.value > 0)
.map((s) => {
const len = (s.value / total) * c
const seg = { ...s, len: Math.max(len - gap, 0.5), offset: at }
at += len
return seg
})
return (
<div className="relative shrink-0" style={{ width: size, height: size }}>
<svg width={size} height={size} aria-hidden="true" className="-rotate-90">
{/* The track carries the empty case on its own, so a board with nothing
graded yet still draws a ring rather than blank space. */}
<circle
cx={size / 2}
cy={size / 2}
r={r}
fill="none"
strokeWidth={thickness}
stroke="var(--surface-2)"
/>
{drawn.map((s) => (
<circle
key={s.key}
cx={size / 2}
cy={size / 2}
r={r}
fill="none"
stroke={s.color}
strokeWidth={thickness}
strokeDasharray={`${s.len} ${c - s.len}`}
strokeDashoffset={-s.offset}
strokeLinecap="butt"
/>
))}
</svg>
<div className="absolute inset-0 flex flex-col items-center justify-center">
<span className="sb-display tnum text-[20px] font-bold leading-none tracking-tight text-ink">
{centerValue}
</span>
<span className="mt-1 text-[10px] font-medium uppercase tracking-[0.1em] text-muted">
{centerLabel}
</span>
</div>
</div>
)
}