# @hm-design/chart
공식 shadcn/ui Chart(`recharts@3.8.0`) 기반의 HM 차트 패키지입니다. Bar(세로·가로·부호)·Line·Donut·Pie·Radar·Area·Radial 차트와 shadcn Chart 기본 부품을 제공하며, 색상·카드·글꼴은 `@hm-design/ui`의 HM Theme 토큰을 그대로 사용합니다. 색상값(HEX)은 이 패키지에 정의하지 않습니다.
> 이 패키지에는 별도 `ai/` 폴더가 없고 이 README가 AI 코딩 도구용 사용 지침을 겸합니다(공통 `AI_GUIDE.md`의 "기타" 유형). 아래 순서대로 따릅니다: **① 설치·연결 → ② 차트 고르기 → ③ 데이터 구성 → ④ 렌더링 → ⑤ 검증**.
## 1. 설치·연결
- 이 저장소를 Git Submodule로 포함한 프로젝트는 [외부 설치 가이드](../../docs/guidelines/external-install-submodule.md)의 절차를 따릅니다. **HMDesign 안에서 `npm ci` 후 `npm run build:ui`와 `npm run build:chart`(또는 `npm run build:packages`)를 먼저 실행**해야 `dist/`가 생깁니다. `dist/`는 Git에 없습니다.
- 소비 프로젝트의 npm Workspace에는 **`HMDesign/packages/ui`와 `HMDesign/packages/chart`를 함께** 추가합니다. `@hm-design/chart`는 `@hm-design/ui`(`0.1.0`), `react`·`react-dom`(`^19`), `tailwindcss`(`^4`)를 peer로 요구하며, 차트가 직접 쓰는 것은 `@hm-design/ui`의 **Theme CSS 토큰**(`theme.css`)뿐입니다(`@hm-design/ui`의 JS는 import하지 않음).
- 이 저장소 안에서는 루트 workspace(`packages/*`)로 이미 연결되어 있습니다.
### CSS 연결 (Tailwind v4 앱)
앱의 CSS 진입점(예: `src/index.css`)에서 `@import` 순서대로 연결합니다.
```css
@import "tailwindcss" source("./");
@import "@hm-design/ui/tailwind.css";
@import "@hm-design/chart/tailwind.css";
@import "@hm-design/ui/theme.css";
```
- `@hm-design/chart/tailwind.css`는 차트 마크업의 Tailwind 유틸리티를 소비 앱 빌드가 만들도록 하는 `@source` 한 줄입니다. 연결하지 않으면 그 유틸리티 class가 소비 앱 CSS에 생성되지 않습니다.
- `@hm-design/ui/theme.css`는 차트 색 별칭(`--hm-chart-*`)이 가리키는 `--hm-sem-*` 토큰과 Noto Sans KR 글꼴을 제공합니다.
- 차트 스타일(`@hm-design/chart/style.css`)은 JS 진입점(`@hm-design/chart`, `@hm-design/chart/examples`)이 자동으로 불러오므로 따로 import하지 않습니다.
- ``·`
`에 배경·글꼴을 정하는 것은 소비 앱의 몫입니다(차트는 전역 규칙을 넣지 않습니다).
## 2. 진입점과 차트 고르기
| Entry | 내용 |
| --- | --- |
| `@hm-design/chart` | 차트 컴포넌트(아래 표), shadcn Chart 부품(`ChartContainer`, `ChartTooltip(Content)`, `ChartLegend(Content)`, `ChartStyle`, `ChartConfig`), `chartColors`, 타입 |
| `@hm-design/chart/examples` | **예시 데이터**와 예제 카드(`ChartExamples` 및 `*Example` 컴포넌트, `sample*` 데이터, `SampleDataBadge`). 실제 업무 데이터가 아니며 운영 화면에 그대로 쓰지 않습니다. |
| `@hm-design/chart/tailwind.css` | Tailwind 유틸리티 스캔용 `@source` |
| `@hm-design/chart/style.css` | 차트 토큰·스타일(JS 진입점이 자동으로 불러옴) |
| 보여 줄 것 | 컴포넌트 | 데이터 모양 |
| --- | --- | --- |
| 카테고리별 값 비교(그룹·누적) | `HMBarChart` | `data` 행 + `categoryKey` + `series[]` |
| 항목명이 긴 순위·값 비교 | `HMHorizontalBarChart` | `data` 행 + `categoryKey` + `valueKey` + `valueLabel` |
| 증감(양수·음수) | `HMSignedBarChart` | `data` 행 + `categoryKey` + `valueKey` + `valueLabel` |
| 추이 | `HMLineChart` | `data` 행 + `categoryKey` + `series[]` |
| 시간 흐름의 양·구성비 | `HMAreaChart` | `data` 행 + `categoryKey` + `series[]` |
| 전체 대비 비중 | `HMDonutChart`, `HMPieChart` | `data: { id, label, value, color? }[]` |
| 두 구성의 동심원 비교 | `HMStackedPieChart` | `rings: [내부, 외부]` |
| 두께가 보이는 입체형 비중 | `HMSolidDonutChart`, `HMSolidPieChart` | 위 Donut 데이터 (`HMSolidDonutChart`는 `totalLabel` 선택) |
| 여러 지표의 균형 | `HMRadarChart` | `data` 행 + `categoryKey` + `series[]` |
| 목표 대비 진행률·항목별 막대 | `HMRadialBarChart`, `HMRadialMeterChart`, `HMRadialStackedChart` | 위 Donut 데이터 / `item` / `series[]`+`row` |
## 3. 데이터 구성 규칙
```ts
type ChartSeries = { key: string; label: string; color?: string; lightColor?: string }
type HMDonutDatum = { id: string; label: string; value: number; color?: string }
```
- 실제 데이터는 소비 앱이 props로 전달합니다. 이 패키지는 API·DB에 연결하지 않습니다.
- `series.key`와 `HMDonutDatum.id`는 CSS 변수 이름(`--color-`)으로 쓰이므로 **영문·숫자·`-`·`_`만** 사용합니다(한글·공백 금지). 화면에 보이는 이름은 `label`에 씁니다.
- 색상을 지정하지 않으면 `--hm-chart-1`~`5`(HM 그린·브라운·웜 뉴트럴 팔레트 별칭)를 순서대로 사용합니다. 다른 색은 `color`에 CSS 색상/토큰을 전달합니다.
- 범례 라벨은 짧게 유지합니다. Bar·Line·Radar·Area의 범례는 한 줄로 두며, 직접 `flex-wrap`으로 줄바꿈시키면 Recharts가 범례 높이를 과대 측정해 차트 아래에 빈 공간이 생길 수 있습니다. Donut·Pie·Stacked Pie는 컴포넌트 내부에서 줄바꿈 범례를 쓰도록 구성되어 있고, Radial 계열은 범례를 차트 박스 밖 아래에 따로 놓습니다.
- 모든 차트의 `label`은 필수 접근성 이름(`role="group"`의 `aria-label`)입니다.
- 차트는 부모 폭을 따라 줄어듭니다(`w-full`, 높이 `h-64` → `sm:h-80`). `className`으로 높이를 바꿀 수 있습니다. 부모에 `min-width: 0`이 없으면 flex·grid 안에서 넘칠 수 있습니다.
## 4. 렌더링
```tsx
import { HMBarChart, HMLineChart, HMDonutChart } from "@hm-design/chart"
const received = [{ month: "1월", received: 186, completed: 142 }]
```
가로형 라벨 막대(`HMHorizontalBarChart`)는 항목명을 막대 안쪽 왼쪽에, 값을 막대 끝 바깥에 표시합니다. 그라데이션은 왼쪽 30% → 오른쪽 100%이고(세로형은 상단 100% → 하단 30%), 막대에 들어가지 않는 긴 항목명은 `…`로 줄이며 막대가 너무 짧거나 0이면 값 뒤에 항목명을 보여 줍니다. 전체 항목명은 툴팁에서 확인합니다. 선택 props는 `color`, `valueFormatter`입니다. `HMSignedBarChart`는 양수는 0선 위로, 음수는 아래로 그립니다. Stacked 세로형 막대(`HMBarChart stacked`)는 눈금선이 비치지 않도록 각 세그먼트를 불투명한 두 색(시리즈 색 → 같은 계열의 한 단계 밝은 HM 팔레트 색, `--hm-chart-N-light`)으로 칠합니다. 사용자 지정 `color`는 `lightColor`를 함께 주지 않으면 단색 세그먼트가 됩니다.
```tsx
```
선 차트(`HMLineChart`) 옵션: `curve`(`"monotone"` 기본 | `"linear"` | `"step"`, 모든 시리즈에 적용), `dots`(기본 `true`; `false`면 기본 Dot 없이 선만 그리고 호버 시 활성 포인트·툴팁은 유지), `pointLabels`(큰 Dot + 값 라벨), `pointColorKey`(포인트별 색상 필드 — 문자열은 첫 시리즈, 객체는 `{ 시리즈키: 필드명 }`; 선·범례는 시리즈 색, Dot·라벨·툴팁 표시는 포인트 색). 시리즈가 둘 이상이면 같은 카테고리에서 값이 큰 쪽 라벨은 위, 작은 쪽은 아래에 두고, 선·Dot·다른 라벨·축 라벨과 닿으면 가까운 빈 자리(더 띄우기, 반대편, 좌우)로 옮깁니다. 점 간격이 라벨 폭보다 좁으면 라벨을 n개마다 표시하고 툴팁에는 모든 값이 나옵니다.
도넛 차트(`HMDonutChart`) 옵션: `totalLabel`(가운데에 전체 합계와 캡션), `activeId`(선택한 조각을 바깥쪽으로 확장하고 가운데에 그 이름·값 표시), `onActiveChange`(조각 클릭 시 id 전달; 상태는 소비 앱이 보관하며 호버로는 바뀌지 않음). 가운데 텍스트는 도넛 안쪽을 넘지 않도록 숫자를 줄이고(`1234.6억` 같은 축약) 이름은 `…`로 줄이며, 합계가 0이면 빈 링과 `0`을 표시합니다. 전체 이름은 범례와 툴팁에서 확인합니다.
파이(`HMPieChart`, `HMStackedPieChart`): `labels`(`"outside"` 조각 바깥 "이름 값"과 연결선, `"inside"` 조각 안 값)는 놓을 자리가 없으면 해당 Label만 생략하고 툴팁에는 전체 정보가 나옵니다. `HMStackedPieChart`는 `rings`(안쪽·바깥쪽 두 시리즈, 같은 `id`는 같은 색)로 동심원 링을 그립니다. 입체형 파이·도넛(`HMSolidPieChart`, `HMSolidDonutChart`): 기존 2D 차트와 같은 `data`(`id`·`label`·`value`·`color?`)·항목 순서·조각 각도·범례·툴팁을 쓰고, 조각을 약간 기울여 내려다본 두께 있는 원판(도넛은 바깥 옆면과 구멍 안쪽 벽)으로 그립니다. 회전 가능한 3D가 아니라 SVG로 두께와 깊이감만 표현하며 기존 2D 컴포넌트·Props는 바뀌지 않습니다. 윗면은 항목 색(`--color-`), 옆면은 같은 계열의 한 단계 어두운 HM 토큰(`--hm-chart-1-side` ~ `--hm-chart-5-side`, 공용 `--hm-chart-N` 토큰은 그대로)을 쓰며, HM 색이 아닌 `color`는 그 색을 어둡게 섞어 옆면에 씁니다. 앞쪽 옆면과 도넛 안쪽 뒤 벽은 서로·윗면과 겹치지 않는 영역만 그리므로 가려지는 면이 비치지 않고 조각 사이에 틈이 없습니다. 두께와 크기는 차트 영역 너비·높이에 맞춰 줄어듭니다(차트 높이 `h-72`/`sm:h-80`은 `className`으로 조정). `HMSolidDonutChart`의 `totalLabel`은 기울어진 구멍 안에 합계와 캡션으로 표시하고 없으면 가운데를 비웁니다. 제약: 조각 클릭·Active(`activeId`)·Label(`labels`)은 지원하지 않으며, 값 0인 항목은 그리지 않고(범례에는 남음), 합계가 0이면 빈 원판·링을 표시합니다. 툴팁은 윗면·옆면 어디에서나 같은 항목을 표시하고 포인터를 따라갑니다. 방사형(`HMRadarChart`): `filled`(기본 영역, `false`면 선만), `dots`, `customLabels`(축 이름을 두 줄로 줄이거나 `…`로 줄임), `maxValue`(공통 축 상한, 기본 100). 영역(`HMAreaChart`): `curve`(`monotone`·`linear`·`step`), `dots`, `pointLabels`·`pointColorKey`(Line과 같은 Dot·Label·충돌 처리), `stacked`(값 누적), `expanded`(각 시점 합계를 100%로 정규화, Y축 %, 툴팁에 원래 값과 비율; 합계 0이면 0%). 영역 채움은 시리즈 색의 세로 그라데이션(위 진하게, 아래 옅게)입니다.
래디얼(`HMRadialBarChart`, `HMRadialMeterChart`, `HMRadialStackedChart`)은 공식 shadcn/ui radial 예제(simple·label·grid·text·shape·stacked)의 각도·반지름·트랙·중앙 Text를 따릅니다(`labels`, `grid`, `startAngle`/`endAngle`, `innerRadius`/`outerRadius`, `trackRadii`, `cornerRadius`). 중앙 숫자는 `CenterText`가 구멍 폭에 맞춰 줄이거나 `1234.6억`처럼 축약합니다. 도넛·래디얼의 링·막대 두께는 공식 예제의 1.5배입니다(2026-11-06): 단일 막대(text·shape·stacked)는 바깥 반지름을 두고 안쪽 반지름을 줄였고, 여러 막대(simple·label·grid)는 `barSize`(18, grid 16.5)·`ringGap`(4, grid 3, 기존 간격)과 기존 중앙 구멍(`innerRadius` 30)을 유지해 `outerRadius`를 `innerRadius + 항목 수 × (barSize + ringGap)`(5개: 140, grid 127.5)로 계산합니다. 래디얼 차트 영역 높이는 300px로 같고 범례는 차트 영역 아래에 따로 놓이며, 폭이 모자라면(320px) simple·label·grid의 반지름·두께·간격이 비례해 줄어듭니다. `HMRadialMeterChart`의 `trackRadii`는 막대와 같은 두께로 맞춥니다. `labels`(항목명 Label)는 막대 안쪽 시작 부분에 호를 따라 그려지며(`insideStart`) 막대 호 길이에 들어가지 않는 긴 이름은 `…`로 줄입니다(전체 이름은 범례와 툴팁).
> 위 설명은 코드(`src/components/*.tsx`의 Props 타입·기본값)와 Storybook(`Chart` 그룹)에서 확인된 동작만 적었습니다. 아래 "예제 컴포넌트"의 `*Example` 이름은 `@hm-design/chart/examples`에서 확인합니다.
## 5. 검증
```sh
npm run build:ui && npm run build:chart # HMDesign 안: packages/ui/dist, packages/chart/dist
npm run typecheck -w @hm-design/chart
npm run build:chart-example # examples/chart (소비 예제 빌드)
npm run dev:chart-example # 소비 예제 실행
npm run storybook # Chart 그룹: Bar / Line / Donut / Pie / Radar / Area / Radial
```
- 소비 프로젝트에서는 앱의 `tsc --noEmit`·`vite build` 후 브라우저에서 차트가 렌더링되고(SVG 표시) 툴팁·범례가 보이는지, 320px에서 가로 스크롤이 없는지 확인합니다.
- 애니메이션이 끝난 뒤(약 1.5초) 화면을 확인합니다. 애니메이션 중에는 막대·Label이 일부만 그려집니다.
### 예제 컴포넌트
소비 예제(`examples/chart/main.tsx`)와 Storybook은 `@hm-design/chart/examples`의 예제 카드를 사용합니다: Bar(`BarChartExample`, `StackedBarChartExample`, `HorizontalBarChartExample`, `SignedBarChartExample`), Line(`LineChartExample` 외 Curve·Straight·Step 변형), Donut(`DonutChartExample` 외 Plain·Center·Active·Interactive·`DonutSolidExample`(입체형)), Pie(`PiePlainExample`, `PieLabelExample`, `PieLabelListExample`, `PieStackedExample`, `PieSolidExample`(입체형)), Radar(`RadarPlainExample`, `RadarDotExample`, `RadarLinesOnlyExample`, `RadarCustomLabelExample`), Area(`AreaCurveExample` 외 Straight·Step·Stacked·Expanded), Radial(`RadialSimpleExample`, `RadialLabelExample`, `RadialGridExample`, `RadialTextExample`, `RadialShapeExample`, `RadialStackedExample`), 묶음 `ChartExamples`. 예시 데이터(`sampleBar`, `samplePie`, `sampleRadial` 등)는 같은 진입점에서 export됩니다. 예제 카드는 `ExampleCard`(내부 컴포넌트)로 감싸며 실제 업무 데이터가 아님을 표시합니다.
## 공식 shadcn Chart와의 차이
`src/components/chart.tsx`는 `npx shadcn view chart`(radix-nova)의 공식 소스입니다. 차이는 세 가지뿐입니다: Dark 테마 항목 제거(HM은 Dark 없음), 툴팁·범례에 `data-slot` 추가, 툴팁 값의 `font-mono` 제거.
## 제한
- 차트 전용 HM 디자인 규격은 아직 없습니다. 색은 기존 HM 팔레트 Semantic 토큰의 별칭이며(`src/chart.css`), 차트 Token은 이 패키지 안에서만 정의합니다.
- `@hm-design/chart/examples`의 `ExampleCard`는 공개 Export가 아닙니다(예제 카드를 통해서만 사용).
- Dark 테마 없음. Runtime Theme(`@hm-design/ui/theme`)의 팔레트 override는 차트 별칭을 통해 반영됩니다.