데이터 모델
차트를 고르기 전에 데이터 shape를 먼저 고정합니다. 공통 Cartesian 모델, 특수 차트 모델, formatter, empty state를 구현 기준으로 분류합니다.
reopt designUpdated
1. 공통 Cartesian 모델
LineChart, BarChart, AreaChart는 같은 데이터 모델을 공유합니다. `dataKey`는 각 행의 numeric field를 가리키고, `name`은 tooltip과 legend에 표시되는 사람이 읽는 이름입니다.
export interface ChartDataPoint {
name: string;
[key: string]: string | number;
}
export interface ChartSeriesDef {
dataKey: string;
name: string;
color?: string;
type?: "line" | "bar" | "area";
stackId?: string;
}const data = [
{ name: "Enterprise", won: 42, lost: 8, pipeline: 19 },
{ name: "Mid-market", won: 31, lost: 11, pipeline: 27 },
{ name: "SMB", won: 24, lost: 14, pipeline: 32 },
];
const series = [
{ dataKey: "won", name: "Won", color: "hsl(142, 71%, 45%)" },
{ dataKey: "lost", name: "Lost", color: "hsl(349, 89%, 60%)" },
{ dataKey: "pipeline", name: "Pipeline", color: "hsl(221, 83%, 53%)" },
] satisfies ChartSeriesDef[];
<BarChart
data={data}
series={series}
stacked
showLegend
aria-label="세그먼트별 영업 상태"
/>;2. 특수 차트 모델
분석형 차트는 의미가 다르므로 공통 타입으로 억지로 맞추지 않습니다. 데이터가 이미 아래 shape 중 하나라면 해당 컴포넌트를 바로 선택합니다.
| Shape | Components | 설계 메모 |
|---|---|---|
| ChartDataPoint[] + ChartSeriesDef[] | LineChart, BarChart, AreaChart, ComposedChart, ComparisonChart, TrendChart | name이 기본 x축입니다. 시리즈의 dataKey는 숫자 필드를 가리키며 ComposedChart는 시리즈별 line/bar/area를 선택합니다. |
| PieChartDataPoint[] / RadialBarDataPoint[] | PieChart, RadialBarChart | name과 value로 구성비 또는 원형 진행률을 표현합니다. 진행률의 maxValue와 도넛 반경은 별도 옵션입니다. |
| ChartDataPoint[] + RadarSeriesDef[] | RadarChart | angleKey로 평가 항목을 지정하고 시리즈마다 숫자 값을 제공합니다. |
| ScatterSeriesDef[] / BubbleSeriesDef[] | ScatterChart, BubbleChart | 시리즈별 숫자 좌표 배열을 가지며 xKey, yKey, zKey/sizeKey를 지정합니다. |
| number[] / BoxPlotDatum[] | Histogram, BoxPlot | Histogram은 values 표본과 bins를 받습니다. BoxPlot은 원시 values 또는 min ≤ q1 ≤ median ≤ q3 ≤ max와 outliers를 받습니다. |
| number[][] + xLabels / yLabels | Heatmap | 행과 열의 방향을 축 라벨에 맞춥니다. 크기는 sequential, 0 같은 기준점 대비 양극성은 diverging을 사용합니다. |
| RetentionRow[] | RetentionHeatmap | cohort와 (number | null)[] values입니다. 0은 관측값이며 null은 아직 관측되지 않은 기간입니다. 기본 값 범위는 0–100입니다. |
| CalendarHeatmapDatum[] | CalendarHeatmap | YYYY-MM-DD date와 count입니다. startDate/endDate로 포함할 날짜 범위를 고정합니다. |
| TreemapDataNode[] / SunburstNode[] | Treemap, Sunburst | name/value/children의 재귀 계층입니다. 두 차트 모두 루트 노드 배열을 받습니다. |
| VennSet[] + VennIntersection[] | VennDiagram | 2–3개 집합과 교집합을 표현합니다. 교집합은 참조하는 집합의 크기를 넘을 수 없습니다. |
| SankeyNode[] + SankeyLink[] | SankeyChart | 고유 node id와 source/target/value 링크입니다. 링크는 label이 아닌 id를 참조합니다. Deck 어댑터는 미등록 노드와 순환을 거부합니다. |
| PathFlowData | PathFlowChart | steps의 위치별 rows와 인접 단계 edges입니다. otherCount, dropOffCount, entryCount로 집계 밖 경로도 표현합니다. |
| FunnelDataPoint[] | FunnelChart | name/value/color 단계입니다. list·bars·horizontal·steps를 고르며 steps는 첫 단계 대비 크기와 이전 단계 대비 전환율을 구분합니다. |
| WaterfallDataPoint[] | WaterfallChart | value는 누적액에 더하는 증감입니다. isTotal은 현재 누적액을 다시 표시하며 자기 value를 더하지 않습니다. 시작 잔액은 일반 행으로 넣습니다. |
| TimeSeriesPoint[] + TimeSeriesDef[] | TimeSeriesChart | t는 epoch ms, ISO 문자열 또는 Date입니다. from/to, granularity, timeZone을 명시합니다. Deck JSON에는 Date 객체 대신 epoch ms 또는 ISO 문자열을 넣습니다. |
| SmallMultipleSeries[] | SmallMultiples | 패널별 고유 id/name/data와 공통 dataKey를 사용합니다. sharedYAxis는 음수와 양수를 포함해 패널 간 척도를 맞춥니다. |
| GanttTask[] / TraceSpan[] | GanttChart, TraceWaterfall | Gantt는 숫자 start/end 구간입니다. TraceSpan은 id/parentId/startMs와 endMs 또는 durationMs로 부모·자식 실행 구간을 표현합니다. |
| Candle[] | CandlestickChart | name/open/high/low/close입니다. low ≤ open·close ≤ high인 OHLC 행을 제공합니다. |
| value + range / BulletDatum[] / number[] | GaugeChart, DataBar, BulletChart, Sparkline | Gauge와 DataBar는 스칼라 값과 범위, BulletChart는 value/target/오름차순 ranges, Sparkline은 숫자 표본을 사용합니다. |
| MetricsCardProps / EventSparklineProps / ReportWidgetConfig | MetricsCard, EventSparkline, ReportWidget | 지표·변화량·추세 표본 또는 type별 위젯 config입니다. ReportWidget은 9개 위젯 유형을 지원하며 35종 전체 슬라이드 선택기는 opt-deck 확장입니다. |
3. Formatter와 설명 텍스트
opt-charts는 데이터 단위를 추측하지 않습니다. 통화, 퍼센트, retention period, cohort 이름은 소비자 앱에서 formatter로 주입합니다. 복잡한 차트는 시각적 label만으로 끝내지 말고 `aria-describedby`에 요약 문장을 연결합니다.
<LineChart
data={data}
series={series}
aria-label="월별 매출"
aria-describedby="revenue-chart-summary"
tooltipOptions={{
labelFormatter: (label) => `${label} 실적`,
valueFormatter: (value, name) =>
`${name}: ${Number(value).toLocaleString("ko-KR")}만원`,
}}
legendOptions={{
formatter: (value) => <span className="font-medium">{value}</span>,
}}
/>;
<p id="revenue-chart-summary" className="sr-only">
1월부터 4월까지 매출과 비용이 모두 증가했고, 4월 매출이 가장 높습니다.
</p>4. Empty state 정책
차트는 빈 데이터와 재조회 상태를 위한 공통 계약을 제공합니다. 첫 로딩, 기존 데이터 재조회, 빈 결과, API 오류를 구분하세요. API 오류 메시지와 재시도 동작은 데이터를 가져오는 패널에서 처리합니다.
| 컴포넌트 | 현재 동작 | 권장 처리 |
|---|---|---|
| LineChart, BarChart, AreaChart, PieChart | 빈 데이터는 ChartEmptyState를 렌더링합니다. emptyState 또는 labels.empty로 내용을 바꿉니다. | 첫 로딩은 LoadingChart, 재조회는 이전 데이터와 loading을 함께 전달합니다. |
| FunnelChart, RetentionHeatmap, Heatmap | 빈 데이터에서도 ChartEmptyState를 보여줍니다. loading은 기존 데이터의 표시를 유지하며 aria-busy와 흐림 처리를 적용합니다. | retention의 관측값 0과 미관측 null을 구분하고, API 오류의 재시도 UI는 부모 패널에서 제공합니다. |
| ReportWidget | 지표·게이지 외 유형의 빈 데이터는 labels.emptyChart, 지연 로딩은 labels.loadingChart로 안내합니다. | 보고서 카드의 제목과 크기를 유지하면서 제품 언어로 labels를 지정합니다. |
import { AreaChart, LoadingChart } from "@reopt-ai/opt-charts";
// First load: reserve the plot's space. Refetch: keep the previous data visible.
function ChartPanel({ data, series, loading }: ChartPanelProps) {
if (loading && data.length === 0) {
return <LoadingChart variant="area" height={288} />;
}
return (
<AreaChart
data={data}
series={series}
height={288}
loading={loading}
labels={{ empty: "아직 표시할 데이터가 없습니다." }}
aria-label="매출 추세"
/>
);
}5. 색상 할당
명시적 color가 최우선입니다. 기본 계열 색상은 seriesColor(index)가 테마의 --opt-chart-1부터 --opt-chart-16까지 할당하며 이후 계열은 CHART_CHROME.other 중립색을 사용합니다. 첫 색을 반복하지 않습니다. 범주 구분에는 categorical, 크기에는 sequential, 기준점 양쪽에는 diverging 도메인을 사용합니다. CHART_COLOR_PALETTE는 기존 5색 토큰 배열이며 기본 차트 전체를 5색으로 제한하는 계약이 아닙니다.
6. 슬라이드 데이터와 React props
React 컴포넌트에는 formatter와 클릭 콜백을 전달할 수 있습니다. opt-deck 확장에는 type/title/props 형식의 직렬화 가능한 JSON만 저장합니다. 함수, React 요소, Date 객체는 포함하지 않으며, 그래프 참조·행렬 크기·구간 순서 등은 유형별 파서로 검증합니다. 전체 설정 JSON 입력란에는 props만 붙여넣습니다.
행렬·Sankey·Waterfall 슬라이드 예제 보기