Chart

Data Visualization

선언적 ChartSpec 을 렌더하는 차트.

Usage

런타임 ChartSpec으로 차트 유형과 인코딩을 선언해 동적으로 렌더할 때 사용한다.

import

import

import { Chart } from '@mildang/design-system/Chart';

예제를 복사해 쓸 때 필요한 준비

@mildang/styled-system 은 이 저장소에서 Panda 가 생성하는 산출물이다. 저장소 안에서는 turbo run ship 이후 쓸 수 있고, 패키지 소비자는 자기 Panda 산출물이나 다른 레이아웃 수단으로 바꿔야 한다.

API Reference

Chart Props

Prop

Type

Default

spec
필수

ChartSpec

지정 안 함

className

string

지정 안 함

height

string | number

320

width

string | number

100%

Card

공개 Props 없음

ChartAxis

Prop

Type

Default

position
필수

"bottom" | "left" | "right" | "top"

지정 안 함

scale
필수

any

지정 안 함

hideAxisLine

boolean

지정 안 함

hideTicks

boolean

지정 안 함

label

string

지정 안 함

numTicks

number

지정 안 함

tickFormat

(value: unknown) => string

지정 안 함

ChartContainer

Prop

Type

Default

children
필수

(dims: { innerWidth: number; innerHeight: number; }) => ReactNode

지정 안 함

className

string

지정 안 함

margin

Partial<ChartMargin>

지정 안 함

ChartGrid

Prop

Type

Default

height
필수

number

지정 안 함

width
필수

number

지정 안 함

columns

boolean

false

numTicksColumns

number

지정 안 함

numTicksRows

number

지정 안 함

rows

boolean

true

xScale

any

지정 안 함

yScale

any

지정 안 함

ChartLegend

Prop

Type

Default

items
필수

LegendItem[]

지정 안 함

className

string

지정 안 함

direction

"column" | "row"

row

ChartTooltip

Prop

Type

Default

tooltipOpen
필수

boolean

지정 안 함

renderContent

(data: TDatum) => ReactNode

지정 안 함

tooltipData

TDatum

지정 안 함

tooltipLeft

number

지정 안 함

tooltipTop

number

지정 안 함

ChartTooltipContent

Prop

Type

Default

items
필수

ChartTooltipItem[]

지정 안 함

formatter

(value: string | number) => ReactNode

지정 안 함

hideIndicator

boolean

false

hideLabel

boolean

false

indicator

"line" | "dot"

dot

label

string

지정 안 함

예제

모든 차트

import type { ComponentProps } from 'react';
import { CHART_COLOR_PRESET, Chart, type ChartSpec } from '@mildang/design-system/Chart';
import { Box, Grid, Stack } from '@mildang/styled-system/jsx';

type Story = { args?: ComponentProps<typeof Chart> };

/* ─── 공용 샘플 데이터 (밀당 도메인, 전부 결정적) ─── */

const subjectScores = [
  { 과목: '국어', 평균점수: 82 },
  { 과목: '영어', 평균점수: 76 },
  { 과목: '수학', 평균점수: 68 },
  { 과목: '과학', 평균점수: 88 },
  { 과목: '사회', 평균점수: 74 },
];

const subjectClassScores = [
  { 과목: '국어', 반: '1반', 점수: 84 },
  { 과목: '국어', 반: '2반', 점수: 78 },
  { 과목: '영어', 반: '1반', 점수: 72 },
  { 과목: '영어', 반: '2반', 점수: 81 },
  { 과목: '수학', 반: '1반', 점수: 66 },
  { 과목: '수학', 반: '2반', 점수: 74 },
];

const monthlyMinutes = [
  { 월: '1월', 학습시간: 620 },
  { 월: '2월', 학습시간: 540 },
  { 월: '3월', 학습시간: 710 },
  { 월: '4월', 학습시간: 680 },
  { 월: '5월', 학습시간: 820 },
  { 월: '6월', 학습시간: 760 },
];

/** 월×과목 학습 시간 long 데이터 — 결정적 공식으로 생성 */
const monthlySubjectMinutes = ['1월', '2월', '3월', '4월', '5월', '6월'].flatMap((, mi) =>
  ['국어', '영어', '수학'].map((과목, si) => ({
    월,
    과목,
    학습시간: 120 + ((mi * 53 + si * 91) % 180),
  })),
);

/** 반별 점수 분포 long 데이터 — 분포 계열(Box/Violin/Density/ECDF/Strip/Histogram) 공용 */
const scoreDistRows = ['1반', '2반', '3반'].flatMap((, ci) =>
  Array.from({ length: 24 }, (_, i) => ({
    반,
    점수: 45 + ci * 5 + ((i * 7) % 29) + (i % 4) * 2,
  })),
);

const monthlyClassScores = [
  { 월: '3월', 반: '1반', 평균점수: 72 },
  { 월: '3월', 반: '2반', 평균점수: 68 },
  { 월: '4월', 반: '1반', 평균점수: 75 },
  { 월: '4월', 반: '2반', 평균점수: 71 },
  { 월: '5월', 반: '1반', 평균점수: 78 },
  { 월: '5월', 반: '2반', 평균점수: 76 },
  { 월: '6월', 반: '1반', 평균점수: 81 },
  { 월: '6월', 반: '2반', 평균점수: 74 },
];

/* ─── 막대 계열 ─── */

export const BarChart: Story = {
  args: {
    spec: {
      chartType: 'Bar Chart',
      data: { values: subjectScores },
      semanticTypes: { 평균점수: 'Score' },
      encodings: { x: '과목', y: { field: '평균점수', label: '평균 점수' } },
    },
  },
};

export const GroupedBarChart: Story = {
  args: {
    spec: {
      chartType: 'Grouped Bar Chart',
      data: { values: subjectClassScores },
      semanticTypes: { 점수: 'Score' },
      encodings: { x: '과목', y: '점수', color: '반' },
    },
  },
};

export const StackedBarChart: Story = {
  args: {
    spec: {
      chartType: 'Stacked Bar Chart',
      data: {
        values: [
          { 월: '3월', 유형: '객관식', 풀이수: 320 },
          { 월: '3월', 유형: '주관식', 풀이수: 180 },
          { 월: '3월', 유형: '서술형', 풀이수: 60 },
          { 월: '4월', 유형: '객관식', 풀이수: 410 },
          { 월: '4월', 유형: '주관식', 풀이수: 210 },
          { 월: '4월', 유형: '서술형', 풀이수: 90 },
          { 월: '5월', 유형: '객관식', 풀이수: 380 },
          { 월: '5월', 유형: '주관식', 풀이수: 240 },
          { 월: '5월', 유형: '서술형', 풀이수: 110 },
        ],
      },
      semanticTypes: { 월: 'Month', 풀이수: 'Count' },
      encodings: { x: '월', y: '풀이수', color: '유형' },
    },
  },
};

/* ─── 영역 계열 ─── */

export const AreaChart: Story = {
  args: {
    spec: {
      chartType: 'Area Chart',
      data: { values: monthlyMinutes },
      semanticTypes: { 월: 'Month', 학습시간: 'DurationMinutes' },
      encodings: { x: '월', y: { field: '학습시간', label: '학습 시간' } },
    },
  },
};

export const StackedAreaChart: Story = {
  args: {
    spec: {
      chartType: 'Stacked Area Chart',
      data: { values: monthlySubjectMinutes },
      semanticTypes: { 월: 'Month', 학습시간: 'DurationMinutes' },
      encodings: { x: '월', y: '학습시간', color: '과목' },
    },
  },
};

export const Streamgraph: Story = {
  args: {
    spec: {
      chartType: 'Streamgraph',
      data: { values: monthlySubjectMinutes },
      semanticTypes: { 월: 'Month', 학습시간: 'DurationMinutes' },
      encodings: { x: '월', y: '학습시간', color: '과목' },
    },
  },
};

export const RangeAreaChart: Story = {
  args: {
    spec: {
      chartType: 'Range Area Chart',
      data: {
        values: [
          { 월: '1월', 최저: 52, 최고: 94, 평균: 71 },
          { 월: '2월', 최저: 48, 최고: 90, 평균: 68 },
          { 월: '3월', 최저: 55, 최고: 96, 평균: 74 },
          { 월: '4월', 최저: 58, 최고: 93, 평균: 76 },
          { 월: '5월', 최저: 61, 최고: 98, 평균: 79 },
          { 월: '6월', 최저: 57, 최고: 95, 평균: 77 },
        ],
      },
      semanticTypes: { 월: 'Month', 최저: 'Score', 최고: 'Score', 평균: 'Score' },
      encodings: {
        x: '월',
        y0: { field: '최저', label: '최저 점수' },
        y1: { field: '최고', label: '최고 점수' },
        mid: { field: '평균', label: '평균 점수' },
      },
    },
  },
};

/* ─── 격자·달력 ─── */

export const Heatmap: Story = {
  args: {
    spec: {
      chartType: 'Heatmap',
      data: {
        values: ['1반', '2반', '3반'].flatMap((, ci) =>
          ['1단원', '2단원', '3단원', '4단원', '5단원'].map((단원, ui) => ({
            반,
            단원,
            정답률: 55 + ((ci * 17 + ui * 13) % 40),
          })),
        ),
      },
      semanticTypes: { 정답률: 'Percent' },
      encodings: { x: '단원', y: '반', value: { field: '정답률', label: '정답률' } },
      chartProperties: { showValues: true, showLegend: true },
    },
  },
};

export const CalendarHeatmap: Story = {
  args: {
    spec: {
      chartType: 'Calendar Heatmap',
      data: {
        values: Array.from({ length: 112 }, (_, i) => ({
          날짜: new Date(Date.UTC(2026, 0, 5 + i)).toISOString().slice(0, 10),
          풀이수: i % 6 === 5 ? 0 : ((i * 7) % 19) + (i % 4),
        })),
      },
      semanticTypes: { 날짜: 'Date', 풀이수: 'Count' },
      encodings: { date: '날짜', value: { field: '풀이수', label: '풀이 수' } },
      chartProperties: { showLegend: true },
    },
  },
};

/* ─── 분포 계열 ─── */

export const Histogram: Story = {
  args: {
    spec: {
      chartType: 'Histogram',
      data: { values: scoreDistRows },
      semanticTypes: { 점수: 'Score' },
      encodings: { x: { field: '점수', label: '점수' } },
      chartProperties: { binCount: 8 },
    },
  },
};

export const BoxPlot: Story = {
  args: {
    spec: {
      chartType: 'Box Plot',
      data: { values: scoreDistRows },
      semanticTypes: { 점수: 'Score' },
      encodings: { y: { field: '점수', label: '점수' }, x: '반' },
    },
  },
};

export const ViolinPlot: Story = {
  args: {
    spec: {
      chartType: 'Violin Plot',
      data: { values: scoreDistRows },
      semanticTypes: { 점수: 'Score' },
      encodings: { y: { field: '점수', label: '점수' }, x: '반' },
    },
  },
};

export const DensityPlot: Story = {
  args: {
    spec: {
      chartType: 'Density Plot',
      data: { values: scoreDistRows },
      semanticTypes: { 점수: 'Score' },
      encodings: { x: { field: '점수', label: '점수' }, color: '반' },
    },
  },
};

export const EcdfPlot: Story = {
  args: {
    spec: {
      chartType: 'ECDF Plot',
      data: { values: scoreDistRows },
      semanticTypes: { 점수: 'Score' },
      encodings: { x: { field: '점수', label: '점수' }, color: '반' },
    },
  },
};

export const StripPlot: Story = {
  args: {
    spec: {
      chartType: 'Strip Plot',
      data: { values: scoreDistRows },
      semanticTypes: { 점수: 'Score' },
      encodings: { x: '반', y: { field: '점수', label: '점수' } },
    },
  },
};

/* ─── 카테고리 비교 ─── */

export const LollipopChart: Story = {
  args: {
    spec: {
      chartType: 'Lollipop Chart',
      data: { values: subjectScores },
      semanticTypes: { 평균점수: 'Score' },
      encodings: { x: '과목', y: { field: '평균점수', label: '평균 점수' } },
      chartProperties: { showValueText: true },
    },
  },
};

export const WaterfallChart: Story = {
  args: {
    spec: {
      chartType: 'Waterfall Chart',
      data: {
        values: [
          { 월: '3월', 순증감: 42 },
          { 월: '4월', 순증감: 18 },
          { 월: '5월', 순증감: -12 },
          { 월: '6월', 순증감: 25 },
          { 월: '7월', 순증감: -8 },
        ],
      },
      semanticTypes: { 월: 'Month', 순증감: 'Count' },
      encodings: { x: '월', y: { field: '순증감', label: '수강생 순증감' } },
      chartProperties: { totalLabel: '순증 합계' },
    },
  },
};

export const PyramidChart: Story = {
  args: {
    spec: {
      chartType: 'Pyramid Chart',
      data: {
        values: [
          { 학년: '중1', 남학생: 120, 여학생: 132 },
          { 학년: '중2', 남학생: 145, 여학생: 138 },
          { 학년: '중3', 남학생: 160, 여학생: 155 },
          { 학년: '고1', 남학생: 130, 여학생: 148 },
          { 학년: '고2', 남학생: 110, 여학생: 118 },
          { 학년: '고3', 남학생: 88, 여학생: 95 },
        ],
      },
      semanticTypes: { 학년: 'Grade', 남학생: 'Count', 여학생: 'Count' },
      encodings: { y: '학년', left: '남학생', right: '여학생' },
    },
  },
};

export const BulletChart: Story = {
  args: {
    spec: {
      chartType: 'Bullet Chart',
      data: {
        values: [
          { 과목: '국어', 진도율: 72, 목표: 80 },
          { 과목: '영어', 진도율: 91, 목표: 85 },
          { 과목: '수학', 진도율: 58, 목표: 75 },
          { 과목: '과학', 진도율: 83, 목표: 80 },
        ],
      },
      semanticTypes: { 진도율: 'Percent', 목표: 'Percent' },
      encodings: {
        y: '과목',
        value: { field: '진도율', label: '진도율' },
        target: { field: '목표', label: '목표' },
      },
      chartProperties: { ranges: [50, 75, 100] },
    },
  },
};

/* ─── 시간·흐름 ─── */

export const GanttChart: Story = {
  args: {
    spec: {
      chartType: 'Gantt Chart',
      data: {
        values: [
          { 작업: '교재 기획', 시작: '2026-03-02', 종료: '2026-03-13', 팀: '콘텐츠' },
          { 작업: '문항 집필', 시작: '2026-03-09', 종료: '2026-04-03', 팀: '콘텐츠' },
          { 작업: '검수', 시작: '2026-03-30', 종료: '2026-04-10', 팀: '편집' },
          { 작업: '조판·디자인', 시작: '2026-04-06', 종료: '2026-04-24', 팀: '편집' },
          { 작업: '베타 수업', 시작: '2026-04-20', 종료: '2026-05-08', 팀: '운영' },
        ],
      },
      semanticTypes: { 시작: 'Date', 종료: 'Date' },
      encodings: { y: '작업', start: '시작', end: '종료', color: '팀' },
    },
  },
};

export const FunnelChart: Story = {
  args: {
    spec: {
      chartType: 'Funnel Chart',
      data: {
        values: [
          { 단계: '상담 신청', 인원: 1200 },
          { 단계: '레벨 테스트', 인원: 860 },
          { 단계: '체험 수업', 인원: 540 },
          { 단계: '정규 등록', 인원: 310 },
        ],
      },
      semanticTypes: { 인원: 'Count' },
      encodings: { x: '단계', y: { field: '인원', label: '인원' } },
    },
  },
};

export const ConnectedScatter: Story = {
  args: {
    spec: {
      chartType: 'Connected Scatter',
      data: {
        values: [
          { 월: '1월', 학습시간: 480, 평균점수: 62 },
          { 월: '2월', 학습시간: 540, 평균점수: 66 },
          { 월: '3월', 학습시간: 620, 평균점수: 71 },
          { 월: '4월', 학습시간: 590, 평균점수: 74 },
          { 월: '5월', 학습시간: 720, 평균점수: 79 },
          { 월: '6월', 학습시간: 680, 평균점수: 83 },
        ],
      },
      semanticTypes: { 학습시간: 'DurationMinutes', 평균점수: 'Score' },
      encodings: {
        x: { field: '학습시간', label: '월 학습 시간' },
        y: { field: '평균점수', label: '평균 점수' },
        detail: '월',
      },
    },
  },
};

export const RangedDotPlot: Story = {
  args: {
    spec: {
      chartType: 'Ranged Dot Plot',
      data: {
        values: [
          { 과목: '국어', 중간: 74, 기말: 82 },
          { 과목: '영어', 중간: 68, 기말: 79 },
          { 과목: '수학', 중간: 61, 기말: 73 },
          { 과목: '과학', 중간: 85, 기말: 81 },
          { 과목: '사회', 중간: 70, 기말: 76 },
        ],
      },
      semanticTypes: { 중간: 'Score', 기말: 'Score' },
      encodings: {
        y: '과목',
        start: { field: '중간', label: '중간고사' },
        end: { field: '기말', label: '기말고사' },
      },
    },
  },
};

/* ─── 계층·관계 ─── */

const studyTimeByUnit = [
  { 과목군: '언어', 과목: '국어', 학습시간: 320 },
  { 과목군: '언어', 과목: '영어', 학습시간: 410 },
  { 과목군: '수리', 과목: '수학', 학습시간: 520 },
  { 과목군: '수리', 과목: '과학', 학습시간: 180 },
  { 과목군: '탐구', 과목: '사회', 학습시간: 150 },
  { 과목군: '탐구', 과목: '한국사', 학습시간: 120 },
];

export const Treemap: Story = {
  args: {
    spec: {
      chartType: 'Treemap',
      data: { values: studyTimeByUnit },
      semanticTypes: { 학습시간: 'DurationMinutes' },
      encodings: {
        category: '과목',
        value: { field: '학습시간', label: '학습 시간' },
        group: '과목군',
      },
      chartProperties: { showLegend: true },
    },
  },
};

export const Sunburst: Story = {
  args: {
    spec: {
      chartType: 'Sunburst',
      data: { values: studyTimeByUnit },
      semanticTypes: { 학습시간: 'DurationMinutes' },
      encodings: {
        category: '과목',
        value: { field: '학습시간', label: '학습 시간' },
        group: '과목군',
      },
      chartProperties: { innerRadius: 40 },
    },
  },
};

export const Tree: Story = {
  args: {
    spec: {
      chartType: 'Tree',
      data: {
        values: [
          { 이름: '중등 수학', 상위: null },
          { 이름: '수와 연산', 상위: '중등 수학' },
          { 이름: '문자와 식', 상위: '중등 수학' },
          { 이름: '함수', 상위: '중등 수학' },
          { 이름: '소인수분해', 상위: '수와 연산' },
          { 이름: '정수와 유리수', 상위: '수와 연산' },
          { 이름: '일차방정식', 상위: '문자와 식' },
          { 이름: '좌표평면과 그래프', 상위: '함수' },
        ],
      },
      encodings: { category: '이름', parent: '상위' },
    },
  },
};

export const Sankey: Story = {
  args: {
    spec: {
      chartType: 'Sankey',
      data: {
        values: [
          { 출발: '중3', 도착: '고등 내신반', 인원: 120 },
          { 출발: '중3', 도착: '고등 수능반', 인원: 80 },
          { 출발: '고등 내신반', 도착: '이과', 인원: 70 },
          { 출발: '고등 내신반', 도착: '문과', 인원: 50 },
          { 출발: '고등 수능반', 도착: '이과', 인원: 45 },
          { 출발: '고등 수능반', 도착: '문과', 인원: 35 },
        ],
      },
      semanticTypes: { 인원: 'Count' },
      encodings: { source: '출발', target: '도착', value: { field: '인원', label: '인원' } },
    },
  },
};

export const Graph: Story = {
  args: {
    spec: {
      chartType: 'Graph',
      data: {
        values: [
          { 개념A: '일차방정식', 개념B: '연립방정식', 연관도: 5 },
          { 개념A: '일차방정식', 개념B: '일차함수', 연관도: 4 },
          { 개념A: '일차함수', 개념B: '이차함수', 연관도: 3 },
          { 개념A: '연립방정식', 개념B: '일차함수', 연관도: 2 },
          { 개념A: '이차함수', 개념B: '이차방정식', 연관도: 4 },
          { 개념A: '이차방정식', 개념B: '인수분해', 연관도: 5 },
        ],
      },
      encodings: { source: '개념A', target: '개념B', value: '연관도' },
      chartProperties: { sizeByDegree: true },
    },
  },
};

/* ─── 극좌표·게이지 ─── */

export const RoseChart: Story = {
  args: {
    spec: {
      chartType: 'Rose Chart',
      data: {
        values: [
          { 요일: '월', 학습시간: 95 },
          { 요일: '화', 학습시간: 110 },
          { 요일: '수', 학습시간: 85 },
          { 요일: '목', 학습시간: 120 },
          { 요일: '금', 학습시간: 70 },
          { 요일: '토', 학습시간: 140 },
          { 요일: '일', 학습시간: 60 },
        ],
      },
      semanticTypes: { 학습시간: 'DurationMinutes' },
      encodings: {
        category: { field: '요일', type: 'ordinal' },
        value: { field: '학습시간', label: '학습 시간' },
      },
    },
  },
};

export const Gauge: Story = {
  args: {
    spec: {
      chartType: 'Gauge',
      data: { values: [{ 진도율: 72 }] },
      semanticTypes: { 진도율: 'Percent' },
      encodings: { value: { field: '진도율', label: '전체 진도율' } },
    },
  },
};

/* ─── 변동·순위 ─── */

export const CandlestickChart: Story = {
  args: {
    spec: {
      chartType: 'Candlestick Chart',
      data: {
        values: [
          { 주차: '1주', 시가: 68, 고가: 75, 저가: 64, 종가: 72 },
          { 주차: '2주', 시가: 72, 고가: 78, 저가: 69, 종가: 70 },
          { 주차: '3주', 시가: 70, 고가: 82, 저가: 70, 종가: 80 },
          { 주차: '4주', 시가: 80, 고가: 85, 저가: 74, 종가: 76 },
          { 주차: '5주', 시가: 76, 고가: 88, 저가: 75, 종가: 86 },
        ],
      },
      semanticTypes: { 시가: 'Score', 고가: 'Score', 저가: 'Score', 종가: 'Score' },
      encodings: {
        x: { field: '주차', type: 'ordinal' },
        open: '시가',
        high: '고가',
        low: '저가',
        close: '종가',
      },
    },
  },
};

export const BumpChart: Story = {
  args: {
    spec: {
      chartType: 'Bump Chart',
      data: {
        values: [
          { 월: '3월', 학생: '김민준', 순위: 1 },
          { 월: '3월', 학생: '이서연', 순위: 2 },
          { 월: '3월', 학생: '박지호', 순위: 3 },
          { 월: '4월', 학생: '김민준', 순위: 2 },
          { 월: '4월', 학생: '이서연', 순위: 1 },
          { 월: '4월', 학생: '박지호', 순위: 3 },
          { 월: '5월', 학생: '김민준', 순위: 3 },
          { 월: '5월', 학생: '이서연', 순위: 1 },
          { 월: '5월', 학생: '박지호', 순위: 2 },
          { 월: '6월', 학생: '김민준', 순위: 2 },
          { 월: '6월', 학생: '이서연', 순위: 3 },
          { 월: '6월', 학생: '박지호', 순위: 1 },
        ],
      },
      semanticTypes: { 월: 'Month', 순위: 'Rank' },
      encodings: { x: '월', y: '순위', color: '학생' },
    },
  },
};

export const SlopeChart: Story = {
  args: {
    spec: {
      chartType: 'Slope Chart',
      data: {
        values: [
          { 시험: '중간고사', 학생: '김민준', 점수: 72 },
          { 시험: '기말고사', 학생: '김민준', 점수: 84 },
          { 시험: '중간고사', 학생: '이서연', 점수: 88 },
          { 시험: '기말고사', 학생: '이서연', 점수: 82 },
          { 시험: '중간고사', 학생: '박지호', 점수: 65 },
          { 시험: '기말고사', 학생: '박지호', 점수: 78 },
        ],
      },
      semanticTypes: { 점수: 'Score' },
      encodings: { x: '시험', y: '점수', color: '학생' },
    },
  },
};

/* ─── KPI·미니 차트 ─── */

export const KpiCard: Story = {
  args: {
    spec: {
      chartType: 'KPI Card',
      data: {
        values: [
          { 월: '2026-01-01', 활성학습자: 1180 },
          { 월: '2026-02-01', 활성학습자: 1240 },
          { 월: '2026-03-01', 활성학습자: 1315 },
          { 월: '2026-04-01', 활성학습자: 1290 },
          { 월: '2026-05-01', 활성학습자: 1402 },
          { 월: '2026-06-01', 활성학습자: 1476 },
        ],
      },
      semanticTypes: { 월: 'Date', 활성학습자: 'Count' },
      encodings: { value: { field: '활성학습자', label: '월간 활성 학습자' }, x: '월' },
      chartProperties: { deltaLabel: '전월 대비', unit: '명' },
    },
  },
};

export const Sparkline: Story = {
  args: {
    spec: {
      chartType: 'Sparkline',
      data: { values: monthlyMinutes },
      semanticTypes: { 학습시간: 'DurationMinutes' },
      encodings: { y: { field: '학습시간', label: '학습 시간' } },
      chartProperties: { variant: 'area' },
    },
    height: 60,
  },
};

/* ─── 다차원 ─── */

export const ParallelCoordinates: Story = {
  args: {
    spec: {
      chartType: 'Parallel Coordinates',
      data: {
        values: [
          { 학생: '김민준', 반: '1반', 국어: 82, 영어: 74, 수학: 91, 과학: 68 },
          { 학생: '이서연', 반: '1반', 국어: 76, 영어: 88, 수학: 72, 과학: 80 },
          { 학생: '박지호', 반: '2반', 국어: 64, 영어: 70, 수학: 85, 과학: 77 },
          { 학생: '최수아', 반: '2반', 국어: 90, 영어: 82, 수학: 66, 과학: 85 },
          { 학생: '정도윤', 반: '3반', 국어: 71, 영어: 65, 수학: 78, 과학: 72 },
          { 학생: '한지우', 반: '3반', 국어: 85, 영어: 79, 수학: 88, 과학: 90 },
        ],
      },
      encodings: { color: '반' },
      chartProperties: { dimensions: ['국어', '영어', '수학', '과학'] },
    },
  },
};

/* ─── 기본 차트 ─── */

export const LineChart: Story = {
  args: {
    spec: {
      chartType: 'Line Chart',
      data: { values: monthlyClassScores },
      semanticTypes: { 월: 'Month', 평균점수: 'Score' },
      encodings: { x: '월', y: '평균점수', color: '반' },
    },
  },
};

const questionTypes = [
  { 유형: '객관식', 문항수: 420 },
  { 유형: '주관식', 문항수: 260 },
  { 유형: '서술형', 문항수: 120 },
  { 유형: 'OX', 문항수: 90 },
];

export const PieChart: Story = {
  args: {
    spec: {
      chartType: 'Pie Chart',
      data: { values: questionTypes },
      semanticTypes: { 문항수: 'Count' },
      encodings: { category: '유형', value: { field: '문항수', label: '문항 수' } },
    },
  },
};

export const DonutChart: Story = {
  args: {
    spec: {
      chartType: 'Donut Chart',
      data: { values: questionTypes },
      semanticTypes: { 문항수: 'Count' },
      encodings: { category: '유형', value: { field: '문항수', label: '문항 수' } },
    },
  },
};

export const ScatterPlot: Story = {
  args: {
    spec: {
      chartType: 'Scatter Plot',
      data: {
        values: Array.from({ length: 30 }, (_, i) => ({
          반: ['1반', '2반', '3반'][i % 3],
          학습시간: 120 + ((i * 37) % 480),
          점수: 40 + ((i * 23) % 55),
          풀이수: 20 + ((i * 11) % 80),
        })),
      },
      semanticTypes: { 학습시간: 'DurationMinutes', 점수: 'Score', 풀이수: 'Count' },
      encodings: {
        x: { field: '학습시간', label: '주간 학습 시간' },
        y: { field: '점수', label: '점수' },
        color: '반',
        size: '풀이수',
      },
    },
  },
};

export const RadarChart: Story = {
  args: {
    spec: {
      chartType: 'Radar Chart',
      data: {
        values: [
          { 영역: '어휘', 학생: '김민준', 점수: 78 },
          { 영역: '문법', 학생: '김민준', 점수: 65 },
          { 영역: '독해', 학생: '김민준', 점수: 88 },
          { 영역: '듣기', 학생: '김민준', 점수: 72 },
          { 영역: '쓰기', 학생: '김민준', 점수: 60 },
          { 영역: '어휘', 학생: '이서연', 점수: 85 },
          { 영역: '문법', 학생: '이서연', 점수: 80 },
          { 영역: '독해', 학생: '이서연', 점수: 70 },
          { 영역: '듣기', 학생: '이서연', 점수: 90 },
          { 영역: '쓰기', 학생: '이서연', 점수: 76 },
        ],
      },
      semanticTypes: { 점수: 'Score' },
      encodings: { x: '영역', y: '점수', color: '학생' },
    },
  },
};

const chartCatalog = [
  { title: 'Bar Chart', story: BarChart },
  { title: 'Grouped Bar Chart', story: GroupedBarChart },
  { title: 'Stacked Bar Chart', story: StackedBarChart },
  { title: 'Area Chart', story: AreaChart },
  { title: 'Stacked Area Chart', story: StackedAreaChart },
  { title: 'Streamgraph', story: Streamgraph },
  { title: 'Range Area Chart', story: RangeAreaChart },
  { title: 'Heatmap', story: Heatmap },
  { title: 'Calendar Heatmap', story: CalendarHeatmap },
  { title: 'Histogram', story: Histogram },
  { title: 'Box Plot', story: BoxPlot },
  { title: 'Violin Plot', story: ViolinPlot },
  { title: 'Density Plot', story: DensityPlot },
  { title: 'ECDF Plot', story: EcdfPlot },
  { title: 'Strip Plot', story: StripPlot },
  { title: 'Lollipop Chart', story: LollipopChart },
  { title: 'Waterfall Chart', story: WaterfallChart },
  { title: 'Pyramid Chart', story: PyramidChart },
  { title: 'Bullet Chart', story: BulletChart },
  { title: 'Gantt Chart', story: GanttChart },
  { title: 'Funnel Chart', story: FunnelChart },
  { title: 'Connected Scatter', story: ConnectedScatter },
  { title: 'Ranged Dot Plot', story: RangedDotPlot },
  { title: 'Treemap', story: Treemap },
  { title: 'Sunburst', story: Sunburst },
  { title: 'Tree', story: Tree },
  { title: 'Sankey', story: Sankey },
  { title: 'Graph', story: Graph },
  { title: 'Rose Chart', story: RoseChart },
  { title: 'Gauge', story: Gauge, height: 220 },
  { title: 'Candlestick Chart', story: CandlestickChart },
  { title: 'Bump Chart', story: BumpChart },
  { title: 'Slope Chart', story: SlopeChart },
  { title: 'KPI Card', story: KpiCard, height: 180 },
  { title: 'Sparkline', story: Sparkline, height: 96 },
  { title: 'Parallel Coordinates', story: ParallelCoordinates },
  { title: 'Line Chart', story: LineChart },
  { title: 'Pie Chart', story: PieChart },
  { title: 'Donut Chart', story: DonutChart },
  { title: 'Scatter Plot', story: ScatterPlot },
  { title: 'Radar Chart', story: RadarChart },
] satisfies Array<{ title: string; story: Story; height?: number }>;

function CatalogGrid({
  heading,
  description,
  mapSpec = (spec) => spec,
}: {
  heading: string;
  description: string;
  mapSpec?: (spec: ChartSpec) => ChartSpec;
}) {
  return (
    <Stack gap="24" padding="24" width="100%">
      <Box>
        <Box as="h2" textStyle="title-2xl" color="neutral.text.base">
          {heading}
        </Box>
        <Box mt="4" textStyle="body-sm" color="neutral.text.low">
          {description}
        </Box>
      </Box>

      <Grid gridTemplateColumns={{ base: '1fr', xl: 'repeat(2, minmax(0, 1fr))' }} gap="24">
        {chartCatalog.map(({ title, story, height }) => {
          const spec = story.args?.spec as ChartSpec | undefined;

          if (!spec) {
            return null;
          }

          return (
            <Stack
              key={title}
              as="article"
              gap="12"
              padding="16"
              borderWidth="1px"
              borderColor="neutral.border.low"
              borderRadius="12"
              bg="neutral.surface.low"
            >
              <Box>
                <Box as="h3" textStyle="title-md" color="neutral.text.base">
                  {title}
                </Box>
                <Box mt="2" textStyle="caption-lg" color="neutral.text.low">
                  chartType: {spec.chartType}
                </Box>
              </Box>

              <Chart spec={mapSpec(spec)} height={height ?? 280} />
            </Stack>
          );
        })}
      </Grid>
    </Stack>
  );
}

const ChartAllChartsExample = () => (
    <CatalogGrid
      heading="Chart Catalog"
      description={`ChartSpec 샘플 ${chartCatalog.length}개를 한 번에 렌더링합니다.`}
    />
);

export const ChartAllChartsTremorExample = () => (
  <CatalogGrid
    heading="Chart Catalog — Tremor"
    description={`chartProperties.colors에 TREMOR_9를 주입해 ${chartCatalog.length}개 차트를 렌더링합니다. colors prop이 없는 차트는 기본 색을 유지합니다.`}
    mapSpec={(spec) => ({
      ...spec,
      chartProperties: { ...spec.chartProperties, colors: CHART_COLOR_PRESET },
    })}
  />
);

export default ChartAllChartsExample;

BarChart

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

/* ─── 공용 샘플 데이터 (밀당 도메인, 전부 결정적) ─── */

const subjectScores = [
  { 과목: '국어', 평균점수: 82 },
  { 과목: '영어', 평균점수: 76 },
  { 과목: '수학', 평균점수: 68 },
  { 과목: '과학', 평균점수: 88 },
  { 과목: '사회', 평균점수: 74 },
];

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Bar Chart',
      data: { values: subjectScores },
      semanticTypes: { 평균점수: 'Score' },
      encodings: { x: '과목', y: { field: '평균점수', label: '평균 점수' } },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartBarChartExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

GroupedBarChart

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const subjectClassScores = [
  { 과목: '국어', 반: '1반', 점수: 84 },
  { 과목: '국어', 반: '2반', 점수: 78 },
  { 과목: '영어', 반: '1반', 점수: 72 },
  { 과목: '영어', 반: '2반', 점수: 81 },
  { 과목: '수학', 반: '1반', 점수: 66 },
  { 과목: '수학', 반: '2반', 점수: 74 },
];

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Grouped Bar Chart',
      data: { values: subjectClassScores },
      semanticTypes: { 점수: 'Score' },
      encodings: { x: '과목', y: '점수', color: '반' },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartGroupedBarChartExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

StackedBarChart

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Stacked Bar Chart',
      data: {
        values: [
          { 월: '3월', 유형: '객관식', 풀이수: 320 },
          { 월: '3월', 유형: '주관식', 풀이수: 180 },
          { 월: '3월', 유형: '서술형', 풀이수: 60 },
          { 월: '4월', 유형: '객관식', 풀이수: 410 },
          { 월: '4월', 유형: '주관식', 풀이수: 210 },
          { 월: '4월', 유형: '서술형', 풀이수: 90 },
          { 월: '5월', 유형: '객관식', 풀이수: 380 },
          { 월: '5월', 유형: '주관식', 풀이수: 240 },
          { 월: '5월', 유형: '서술형', 풀이수: 110 },
        ],
      },
      semanticTypes: { 월: 'Month', 풀이수: 'Count' },
      encodings: { x: '월', y: '풀이수', color: '유형' },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartStackedBarChartExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

AreaChart

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const monthlyMinutes = [
  { 월: '1월', 학습시간: 620 },
  { 월: '2월', 학습시간: 540 },
  { 월: '3월', 학습시간: 710 },
  { 월: '4월', 학습시간: 680 },
  { 월: '5월', 학습시간: 820 },
  { 월: '6월', 학습시간: 760 },
];

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Area Chart',
      data: { values: monthlyMinutes },
      semanticTypes: { 월: 'Month', 학습시간: 'DurationMinutes' },
      encodings: { x: '월', y: { field: '학습시간', label: '학습 시간' } },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartAreaChartExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

StackedAreaChart

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

/** 월×과목 학습 시간 long 데이터 — 결정적 공식으로 생성 */
const monthlySubjectMinutes = ['1월', '2월', '3월', '4월', '5월', '6월'].flatMap((, mi) =>
  ['국어', '영어', '수학'].map((과목, si) => ({
    월,
    과목,
    학습시간: 120 + ((mi * 53 + si * 91) % 180),
  })),
);

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Stacked Area Chart',
      data: { values: monthlySubjectMinutes },
      semanticTypes: { 월: 'Month', 학습시간: 'DurationMinutes' },
      encodings: { x: '월', y: '학습시간', color: '과목' },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartStackedAreaChartExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

Streamgraph

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

/** 월×과목 학습 시간 long 데이터 — 결정적 공식으로 생성 */
const monthlySubjectMinutes = ['1월', '2월', '3월', '4월', '5월', '6월'].flatMap((, mi) =>
  ['국어', '영어', '수학'].map((과목, si) => ({
    월,
    과목,
    학습시간: 120 + ((mi * 53 + si * 91) % 180),
  })),
);

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Streamgraph',
      data: { values: monthlySubjectMinutes },
      semanticTypes: { 월: 'Month', 학습시간: 'DurationMinutes' },
      encodings: { x: '월', y: '학습시간', color: '과목' },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartStreamgraphExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

RangeAreaChart

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Range Area Chart',
      data: {
        values: [
          { 월: '1월', 최저: 52, 최고: 94, 평균: 71 },
          { 월: '2월', 최저: 48, 최고: 90, 평균: 68 },
          { 월: '3월', 최저: 55, 최고: 96, 평균: 74 },
          { 월: '4월', 최저: 58, 최고: 93, 평균: 76 },
          { 월: '5월', 최저: 61, 최고: 98, 평균: 79 },
          { 월: '6월', 최저: 57, 최고: 95, 평균: 77 },
        ],
      },
      semanticTypes: { 월: 'Month', 최저: 'Score', 최고: 'Score', 평균: 'Score' },
      encodings: {
        x: '월',
        y0: { field: '최저', label: '최저 점수' },
        y1: { field: '최고', label: '최고 점수' },
        mid: { field: '평균', label: '평균 점수' },
      },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartRangeAreaChartExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

Heatmap

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Heatmap',
      data: {
        values: ['1반', '2반', '3반'].flatMap((, ci) =>
          ['1단원', '2단원', '3단원', '4단원', '5단원'].map((단원, ui) => ({
            반,
            단원,
            정답률: 55 + ((ci * 17 + ui * 13) % 40),
          })),
        ),
      },
      semanticTypes: { 정답률: 'Percent' },
      encodings: { x: '단원', y: '반', value: { field: '정답률', label: '정답률' } },
      chartProperties: { showValues: true, showLegend: true },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartHeatmapExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

CalendarHeatmap

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Calendar Heatmap',
      data: {
        values: Array.from({ length: 112 }, (_, i) => ({
          날짜: new Date(Date.UTC(2026, 0, 5 + i)).toISOString().slice(0, 10),
          풀이수: i % 6 === 5 ? 0 : ((i * 7) % 19) + (i % 4),
        })),
      },
      semanticTypes: { 날짜: 'Date', 풀이수: 'Count' },
      encodings: { date: '날짜', value: { field: '풀이수', label: '풀이 수' } },
      chartProperties: { showLegend: true },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartCalendarHeatmapExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

Histogram

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

/** 반별 점수 분포 long 데이터 — 분포 계열(Box/Violin/Density/ECDF/Strip/Histogram) 공용 */
const scoreDistRows = ['1반', '2반', '3반'].flatMap((, ci) =>
  Array.from({ length: 24 }, (_, i) => ({
    반,
    점수: 45 + ci * 5 + ((i * 7) % 29) + (i % 4) * 2,
  })),
);

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Histogram',
      data: { values: scoreDistRows },
      semanticTypes: { 점수: 'Score' },
      encodings: { x: { field: '점수', label: '점수' } },
      chartProperties: { binCount: 8 },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartHistogramExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

BoxPlot

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

/** 반별 점수 분포 long 데이터 — 분포 계열(Box/Violin/Density/ECDF/Strip/Histogram) 공용 */
const scoreDistRows = ['1반', '2반', '3반'].flatMap((, ci) =>
  Array.from({ length: 24 }, (_, i) => ({
    반,
    점수: 45 + ci * 5 + ((i * 7) % 29) + (i % 4) * 2,
  })),
);

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Box Plot',
      data: { values: scoreDistRows },
      semanticTypes: { 점수: 'Score' },
      encodings: { y: { field: '점수', label: '점수' }, x: '반' },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartBoxPlotExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

ViolinPlot

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

/** 반별 점수 분포 long 데이터 — 분포 계열(Box/Violin/Density/ECDF/Strip/Histogram) 공용 */
const scoreDistRows = ['1반', '2반', '3반'].flatMap((, ci) =>
  Array.from({ length: 24 }, (_, i) => ({
    반,
    점수: 45 + ci * 5 + ((i * 7) % 29) + (i % 4) * 2,
  })),
);

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Violin Plot',
      data: { values: scoreDistRows },
      semanticTypes: { 점수: 'Score' },
      encodings: { y: { field: '점수', label: '점수' }, x: '반' },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartViolinPlotExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

DensityPlot

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

/** 반별 점수 분포 long 데이터 — 분포 계열(Box/Violin/Density/ECDF/Strip/Histogram) 공용 */
const scoreDistRows = ['1반', '2반', '3반'].flatMap((, ci) =>
  Array.from({ length: 24 }, (_, i) => ({
    반,
    점수: 45 + ci * 5 + ((i * 7) % 29) + (i % 4) * 2,
  })),
);

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Density Plot',
      data: { values: scoreDistRows },
      semanticTypes: { 점수: 'Score' },
      encodings: { x: { field: '점수', label: '점수' }, color: '반' },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartDensityPlotExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

EcdfPlot

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

/** 반별 점수 분포 long 데이터 — 분포 계열(Box/Violin/Density/ECDF/Strip/Histogram) 공용 */
const scoreDistRows = ['1반', '2반', '3반'].flatMap((, ci) =>
  Array.from({ length: 24 }, (_, i) => ({
    반,
    점수: 45 + ci * 5 + ((i * 7) % 29) + (i % 4) * 2,
  })),
);

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'ECDF Plot',
      data: { values: scoreDistRows },
      semanticTypes: { 점수: 'Score' },
      encodings: { x: { field: '점수', label: '점수' }, color: '반' },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartEcdfPlotExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

StripPlot

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

/** 반별 점수 분포 long 데이터 — 분포 계열(Box/Violin/Density/ECDF/Strip/Histogram) 공용 */
const scoreDistRows = ['1반', '2반', '3반'].flatMap((, ci) =>
  Array.from({ length: 24 }, (_, i) => ({
    반,
    점수: 45 + ci * 5 + ((i * 7) % 29) + (i % 4) * 2,
  })),
);

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Strip Plot',
      data: { values: scoreDistRows },
      semanticTypes: { 점수: 'Score' },
      encodings: { x: '반', y: { field: '점수', label: '점수' } },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartStripPlotExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

LollipopChart

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

/* ─── 공용 샘플 데이터 (밀당 도메인, 전부 결정적) ─── */

const subjectScores = [
  { 과목: '국어', 평균점수: 82 },
  { 과목: '영어', 평균점수: 76 },
  { 과목: '수학', 평균점수: 68 },
  { 과목: '과학', 평균점수: 88 },
  { 과목: '사회', 평균점수: 74 },
];

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Lollipop Chart',
      data: { values: subjectScores },
      semanticTypes: { 평균점수: 'Score' },
      encodings: { x: '과목', y: { field: '평균점수', label: '평균 점수' } },
      chartProperties: { showValueText: true },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartLollipopChartExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

WaterfallChart

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Waterfall Chart',
      data: {
        values: [
          { 월: '3월', 순증감: 42 },
          { 월: '4월', 순증감: 18 },
          { 월: '5월', 순증감: -12 },
          { 월: '6월', 순증감: 25 },
          { 월: '7월', 순증감: -8 },
        ],
      },
      semanticTypes: { 월: 'Month', 순증감: 'Count' },
      encodings: { x: '월', y: { field: '순증감', label: '수강생 순증감' } },
      chartProperties: { totalLabel: '순증 합계' },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartWaterfallChartExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

PyramidChart

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Pyramid Chart',
      data: {
        values: [
          { 학년: '중1', 남학생: 120, 여학생: 132 },
          { 학년: '중2', 남학생: 145, 여학생: 138 },
          { 학년: '중3', 남학생: 160, 여학생: 155 },
          { 학년: '고1', 남학생: 130, 여학생: 148 },
          { 학년: '고2', 남학생: 110, 여학생: 118 },
          { 학년: '고3', 남학생: 88, 여학생: 95 },
        ],
      },
      semanticTypes: { 학년: 'Grade', 남학생: 'Count', 여학생: 'Count' },
      encodings: { y: '학년', left: '남학생', right: '여학생' },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartPyramidChartExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

BulletChart

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Bullet Chart',
      data: {
        values: [
          { 과목: '국어', 진도율: 72, 목표: 80 },
          { 과목: '영어', 진도율: 91, 목표: 85 },
          { 과목: '수학', 진도율: 58, 목표: 75 },
          { 과목: '과학', 진도율: 83, 목표: 80 },
        ],
      },
      semanticTypes: { 진도율: 'Percent', 목표: 'Percent' },
      encodings: {
        y: '과목',
        value: { field: '진도율', label: '진도율' },
        target: { field: '목표', label: '목표' },
      },
      chartProperties: { ranges: [50, 75, 100] },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartBulletChartExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

GanttChart

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Gantt Chart',
      data: {
        values: [
          { 작업: '교재 기획', 시작: '2026-03-02', 종료: '2026-03-13', 팀: '콘텐츠' },
          { 작업: '문항 집필', 시작: '2026-03-09', 종료: '2026-04-03', 팀: '콘텐츠' },
          { 작업: '검수', 시작: '2026-03-30', 종료: '2026-04-10', 팀: '편집' },
          { 작업: '조판·디자인', 시작: '2026-04-06', 종료: '2026-04-24', 팀: '편집' },
          { 작업: '베타 수업', 시작: '2026-04-20', 종료: '2026-05-08', 팀: '운영' },
        ],
      },
      semanticTypes: { 시작: 'Date', 종료: 'Date' },
      encodings: { y: '작업', start: '시작', end: '종료', color: '팀' },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartGanttChartExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

FunnelChart

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Funnel Chart',
      data: {
        values: [
          { 단계: '상담 신청', 인원: 1200 },
          { 단계: '레벨 테스트', 인원: 860 },
          { 단계: '체험 수업', 인원: 540 },
          { 단계: '정규 등록', 인원: 310 },
        ],
      },
      semanticTypes: { 인원: 'Count' },
      encodings: { x: '단계', y: { field: '인원', label: '인원' } },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartFunnelChartExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

ConnectedScatter

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Connected Scatter',
      data: {
        values: [
          { 월: '1월', 학습시간: 480, 평균점수: 62 },
          { 월: '2월', 학습시간: 540, 평균점수: 66 },
          { 월: '3월', 학습시간: 620, 평균점수: 71 },
          { 월: '4월', 학습시간: 590, 평균점수: 74 },
          { 월: '5월', 학습시간: 720, 평균점수: 79 },
          { 월: '6월', 학습시간: 680, 평균점수: 83 },
        ],
      },
      semanticTypes: { 학습시간: 'DurationMinutes', 평균점수: 'Score' },
      encodings: {
        x: { field: '학습시간', label: '월 학습 시간' },
        y: { field: '평균점수', label: '평균 점수' },
        detail: '월',
      },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartConnectedScatterExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

RangedDotPlot

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Ranged Dot Plot',
      data: {
        values: [
          { 과목: '국어', 중간: 74, 기말: 82 },
          { 과목: '영어', 중간: 68, 기말: 79 },
          { 과목: '수학', 중간: 61, 기말: 73 },
          { 과목: '과학', 중간: 85, 기말: 81 },
          { 과목: '사회', 중간: 70, 기말: 76 },
        ],
      },
      semanticTypes: { 중간: 'Score', 기말: 'Score' },
      encodings: {
        y: '과목',
        start: { field: '중간', label: '중간고사' },
        end: { field: '기말', label: '기말고사' },
      },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartRangedDotPlotExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

Treemap

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

/* ─── 계층·관계 ─── */

const studyTimeByUnit = [
  { 과목군: '언어', 과목: '국어', 학습시간: 320 },
  { 과목군: '언어', 과목: '영어', 학습시간: 410 },
  { 과목군: '수리', 과목: '수학', 학습시간: 520 },
  { 과목군: '수리', 과목: '과학', 학습시간: 180 },
  { 과목군: '탐구', 과목: '사회', 학습시간: 150 },
  { 과목군: '탐구', 과목: '한국사', 학습시간: 120 },
];

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Treemap',
      data: { values: studyTimeByUnit },
      semanticTypes: { 학습시간: 'DurationMinutes' },
      encodings: {
        category: '과목',
        value: { field: '학습시간', label: '학습 시간' },
        group: '과목군',
      },
      chartProperties: { showLegend: true },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartTreemapExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

Sunburst

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

/* ─── 계층·관계 ─── */

const studyTimeByUnit = [
  { 과목군: '언어', 과목: '국어', 학습시간: 320 },
  { 과목군: '언어', 과목: '영어', 학습시간: 410 },
  { 과목군: '수리', 과목: '수학', 학습시간: 520 },
  { 과목군: '수리', 과목: '과학', 학습시간: 180 },
  { 과목군: '탐구', 과목: '사회', 학습시간: 150 },
  { 과목군: '탐구', 과목: '한국사', 학습시간: 120 },
];

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Sunburst',
      data: { values: studyTimeByUnit },
      semanticTypes: { 학습시간: 'DurationMinutes' },
      encodings: {
        category: '과목',
        value: { field: '학습시간', label: '학습 시간' },
        group: '과목군',
      },
      chartProperties: { innerRadius: 40 },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartSunburstExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

Tree

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Tree',
      data: {
        values: [
          { 이름: '중등 수학', 상위: null },
          { 이름: '수와 연산', 상위: '중등 수학' },
          { 이름: '문자와 식', 상위: '중등 수학' },
          { 이름: '함수', 상위: '중등 수학' },
          { 이름: '소인수분해', 상위: '수와 연산' },
          { 이름: '정수와 유리수', 상위: '수와 연산' },
          { 이름: '일차방정식', 상위: '문자와 식' },
          { 이름: '좌표평면과 그래프', 상위: '함수' },
        ],
      },
      encodings: { category: '이름', parent: '상위' },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartTreeExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

Sankey

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Sankey',
      data: {
        values: [
          { 출발: '중3', 도착: '고등 내신반', 인원: 120 },
          { 출발: '중3', 도착: '고등 수능반', 인원: 80 },
          { 출발: '고등 내신반', 도착: '이과', 인원: 70 },
          { 출발: '고등 내신반', 도착: '문과', 인원: 50 },
          { 출발: '고등 수능반', 도착: '이과', 인원: 45 },
          { 출발: '고등 수능반', 도착: '문과', 인원: 35 },
        ],
      },
      semanticTypes: { 인원: 'Count' },
      encodings: { source: '출발', target: '도착', value: { field: '인원', label: '인원' } },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartSankeyExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

Graph

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Graph',
      data: {
        values: [
          { 개념A: '일차방정식', 개념B: '연립방정식', 연관도: 5 },
          { 개념A: '일차방정식', 개념B: '일차함수', 연관도: 4 },
          { 개념A: '일차함수', 개념B: '이차함수', 연관도: 3 },
          { 개념A: '연립방정식', 개념B: '일차함수', 연관도: 2 },
          { 개념A: '이차함수', 개념B: '이차방정식', 연관도: 4 },
          { 개념A: '이차방정식', 개념B: '인수분해', 연관도: 5 },
        ],
      },
      encodings: { source: '개념A', target: '개념B', value: '연관도' },
      chartProperties: { sizeByDegree: true },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartGraphExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

RoseChart

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Rose Chart',
      data: {
        values: [
          { 요일: '월', 학습시간: 95 },
          { 요일: '화', 학습시간: 110 },
          { 요일: '수', 학습시간: 85 },
          { 요일: '목', 학습시간: 120 },
          { 요일: '금', 학습시간: 70 },
          { 요일: '토', 학습시간: 140 },
          { 요일: '일', 학습시간: 60 },
        ],
      },
      semanticTypes: { 학습시간: 'DurationMinutes' },
      encodings: {
        category: { field: '요일', type: 'ordinal' },
        value: { field: '학습시간', label: '학습 시간' },
      },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartRoseChartExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

Gauge

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Gauge',
      data: { values: [{ 진도율: 72 }] },
      semanticTypes: { 진도율: 'Percent' },
      encodings: { value: { field: '진도율', label: '전체 진도율' } },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartGaugeExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

CandlestickChart

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Candlestick Chart',
      data: {
        values: [
          { 주차: '1주', 시가: 68, 고가: 75, 저가: 64, 종가: 72 },
          { 주차: '2주', 시가: 72, 고가: 78, 저가: 69, 종가: 70 },
          { 주차: '3주', 시가: 70, 고가: 82, 저가: 70, 종가: 80 },
          { 주차: '4주', 시가: 80, 고가: 85, 저가: 74, 종가: 76 },
          { 주차: '5주', 시가: 76, 고가: 88, 저가: 75, 종가: 86 },
        ],
      },
      semanticTypes: { 시가: 'Score', 고가: 'Score', 저가: 'Score', 종가: 'Score' },
      encodings: {
        x: { field: '주차', type: 'ordinal' },
        open: '시가',
        high: '고가',
        low: '저가',
        close: '종가',
      },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartCandlestickChartExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

BumpChart

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Bump Chart',
      data: {
        values: [
          { 월: '3월', 학생: '김민준', 순위: 1 },
          { 월: '3월', 학생: '이서연', 순위: 2 },
          { 월: '3월', 학생: '박지호', 순위: 3 },
          { 월: '4월', 학생: '김민준', 순위: 2 },
          { 월: '4월', 학생: '이서연', 순위: 1 },
          { 월: '4월', 학생: '박지호', 순위: 3 },
          { 월: '5월', 학생: '김민준', 순위: 3 },
          { 월: '5월', 학생: '이서연', 순위: 1 },
          { 월: '5월', 학생: '박지호', 순위: 2 },
          { 월: '6월', 학생: '김민준', 순위: 2 },
          { 월: '6월', 학생: '이서연', 순위: 3 },
          { 월: '6월', 학생: '박지호', 순위: 1 },
        ],
      },
      semanticTypes: { 월: 'Month', 순위: 'Rank' },
      encodings: { x: '월', y: '순위', color: '학생' },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartBumpChartExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

SlopeChart

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Slope Chart',
      data: {
        values: [
          { 시험: '중간고사', 학생: '김민준', 점수: 72 },
          { 시험: '기말고사', 학생: '김민준', 점수: 84 },
          { 시험: '중간고사', 학생: '이서연', 점수: 88 },
          { 시험: '기말고사', 학생: '이서연', 점수: 82 },
          { 시험: '중간고사', 학생: '박지호', 점수: 65 },
          { 시험: '기말고사', 학생: '박지호', 점수: 78 },
        ],
      },
      semanticTypes: { 점수: 'Score' },
      encodings: { x: '시험', y: '점수', color: '학생' },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartSlopeChartExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

KpiCard

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'KPI Card',
      data: {
        values: [
          { 월: '2026-01-01', 활성학습자: 1180 },
          { 월: '2026-02-01', 활성학습자: 1240 },
          { 월: '2026-03-01', 활성학습자: 1315 },
          { 월: '2026-04-01', 활성학습자: 1290 },
          { 월: '2026-05-01', 활성학습자: 1402 },
          { 월: '2026-06-01', 활성학습자: 1476 },
        ],
      },
      semanticTypes: { 월: 'Date', 활성학습자: 'Count' },
      encodings: { value: { field: '활성학습자', label: '월간 활성 학습자' }, x: '월' },
      chartProperties: { deltaLabel: '전월 대비', unit: '명' },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartKpiCardExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

Sparkline

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const monthlyMinutes = [
  { 월: '1월', 학습시간: 620 },
  { 월: '2월', 학습시간: 540 },
  { 월: '3월', 학습시간: 710 },
  { 월: '4월', 학습시간: 680 },
  { 월: '5월', 학습시간: 820 },
  { 월: '6월', 학습시간: 760 },
];

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Sparkline',
      data: { values: monthlyMinutes },
      semanticTypes: { 학습시간: 'DurationMinutes' },
      encodings: { y: { field: '학습시간', label: '학습 시간' } },
      chartProperties: { variant: 'area' },
    },
    height: 60,
  }) } as ComponentProps<typeof Chart>;

export default function ChartSparklineExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

ParallelCoordinates

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Parallel Coordinates',
      data: {
        values: [
          { 학생: '김민준', 반: '1반', 국어: 82, 영어: 74, 수학: 91, 과학: 68 },
          { 학생: '이서연', 반: '1반', 국어: 76, 영어: 88, 수학: 72, 과학: 80 },
          { 학생: '박지호', 반: '2반', 국어: 64, 영어: 70, 수학: 85, 과학: 77 },
          { 학생: '최수아', 반: '2반', 국어: 90, 영어: 82, 수학: 66, 과학: 85 },
          { 학생: '정도윤', 반: '3반', 국어: 71, 영어: 65, 수학: 78, 과학: 72 },
          { 학생: '한지우', 반: '3반', 국어: 85, 영어: 79, 수학: 88, 과학: 90 },
        ],
      },
      encodings: { color: '반' },
      chartProperties: { dimensions: ['국어', '영어', '수학', '과학'] },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartParallelCoordinatesExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

LineChart

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const monthlyClassScores = [
  { 월: '3월', 반: '1반', 평균점수: 72 },
  { 월: '3월', 반: '2반', 평균점수: 68 },
  { 월: '4월', 반: '1반', 평균점수: 75 },
  { 월: '4월', 반: '2반', 평균점수: 71 },
  { 월: '5월', 반: '1반', 평균점수: 78 },
  { 월: '5월', 반: '2반', 평균점수: 76 },
  { 월: '6월', 반: '1반', 평균점수: 81 },
  { 월: '6월', 반: '2반', 평균점수: 74 },
];

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Line Chart',
      data: { values: monthlyClassScores },
      semanticTypes: { 월: 'Month', 평균점수: 'Score' },
      encodings: { x: '월', y: '평균점수', color: '반' },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartLineChartExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

PieChart

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const questionTypes = [
  { 유형: '객관식', 문항수: 420 },
  { 유형: '주관식', 문항수: 260 },
  { 유형: '서술형', 문항수: 120 },
  { 유형: 'OX', 문항수: 90 },
];

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Pie Chart',
      data: { values: questionTypes },
      semanticTypes: { 문항수: 'Count' },
      encodings: { category: '유형', value: { field: '문항수', label: '문항 수' } },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartPieChartExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

DonutChart

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const questionTypes = [
  { 유형: '객관식', 문항수: 420 },
  { 유형: '주관식', 문항수: 260 },
  { 유형: '서술형', 문항수: 120 },
  { 유형: 'OX', 문항수: 90 },
];

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Donut Chart',
      data: { values: questionTypes },
      semanticTypes: { 문항수: 'Count' },
      encodings: { category: '유형', value: { field: '문항수', label: '문항 수' } },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartDonutChartExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

ScatterPlot

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Scatter Plot',
      data: {
        values: Array.from({ length: 30 }, (_, i) => ({
          반: ['1반', '2반', '3반'][i % 3],
          학습시간: 120 + ((i * 37) % 480),
          점수: 40 + ((i * 23) % 55),
          풀이수: 20 + ((i * 11) % 80),
        })),
      },
      semanticTypes: { 학습시간: 'DurationMinutes', 점수: 'Score', 풀이수: 'Count' },
      encodings: {
        x: { field: '학습시간', label: '주간 학습 시간' },
        y: { field: '점수', label: '점수' },
        color: '반',
        size: '풀이수',
      },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartScatterPlotExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

RadarChart

import type { ComponentProps } from 'react';
import { Chart } from '@mildang/design-system/Chart';

const STORY_DEFAULT_ARGS = { ...({}), ...({
    spec: {
      chartType: 'Radar Chart',
      data: {
        values: [
          { 영역: '어휘', 학생: '김민준', 점수: 78 },
          { 영역: '문법', 학생: '김민준', 점수: 65 },
          { 영역: '독해', 학생: '김민준', 점수: 88 },
          { 영역: '듣기', 학생: '김민준', 점수: 72 },
          { 영역: '쓰기', 학생: '김민준', 점수: 60 },
          { 영역: '어휘', 학생: '이서연', 점수: 85 },
          { 영역: '문법', 학생: '이서연', 점수: 80 },
          { 영역: '독해', 학생: '이서연', 점수: 70 },
          { 영역: '듣기', 학생: '이서연', 점수: 90 },
          { 영역: '쓰기', 학생: '이서연', 점수: 76 },
        ],
      },
      semanticTypes: { 점수: 'Score' },
      encodings: { x: '영역', y: '점수', color: '학생' },
    },
  }) } as ComponentProps<typeof Chart>;

export default function ChartRadarChartExample(props: Partial<ComponentProps<typeof Chart>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof Chart>;
  return <Chart {...mergedProps} />;
}

Basic

import { Card } from '@mildang/design-system/Chart';
import { Text } from '@mildang/design-system/Text';

const CardBasicExample = () => (
    <div style={{ width: 320 }}>
      <Card>
        <Text variant="caption-md" color="neutral.text.low">
          이번 달 신규 가입
        </Text>
        <Text variant="title-lg">1,284명</Text>
      </Card>
    </div>
  );

export default CardBasicExample;

영역 차트 + 요약 리스트

import { Card } from '@mildang/design-system/Chart';
import { Box, HStack, Stack } from '@mildang/styled-system/jsx';
import { AreaChart } from '@mildang/design-system/Chart';
import { getChartColor } from '@mildang/design-system/Chart';
import { css } from '@mildang/styled-system/css';

/** Card 상단 제목 + 설명 — Tremor 블록의 헤더 관례 */
function CardHeading({ title, description, action }: { title: string; description?: string; action?: React.ReactNode }) {
  return (
    <HStack justifyContent="space-between" alignItems="flex-start" gap="16">
      <Box>
        <Box as="h3" textStyle="title-md" color="primary.text.base">
          {title}
        </Box>
        {description ? (
          <Box as="p" mt="2" textStyle="caption-lg" color="primary.text.low">
            {description}
          </Box>
        ) : null}
      </Box>
      {action}
    </HStack>
  );
}

/** 색 점 + 라벨 + 값 + 비중 행 — 차트 하단/우측 요약 리스트 공용 */
function SummaryRow({ color, label, value, percent }: { color: string; label: string; value: string; percent: string }) {
  return (
    <div className={summaryRowClass}>
      <Box flexShrink={0} width="8px" height="8px" borderRadius="full" style={{ backgroundColor: color }} />
      <Box textStyle="caption-lg" color="primary.text.low" flexGrow={1}>
        {label}
      </Box>
      <Box textStyle="caption-lg-medium" color="primary.text.base" fontVariantNumeric="tabular-nums">
        {value}
      </Box>
      <Box textStyle="caption-lg" color="primary.text.lowest" fontVariantNumeric="tabular-nums" width="44px" textAlign="right">
        {percent}
      </Box>
    </div>
  );
}

const summaryRowClass = css({
  display: 'flex',
  alignItems: 'center',
  gap: '8',
  py: '10',
  borderTopWidth: '1',
  borderTopStyle: 'solid',
  borderTopColor: 'primary.border.low',
});

/* ─── 공용 데이터 (밀당 도메인, 결정적) ─── */

const monthlyStudy = [
  { 월: '1월', 자기주도: 4120, 수업: 2280 },
  { 월: '2월', 자기주도: 3860, 수업: 2140 },
  { 월: '3월', 자기주도: 4510, 수업: 2620 },
  { 월: '4월', 자기주도: 4380, 수업: 2540 },
  { 월: '5월', 자기주도: 5230, 수업: 2860 },
  { 월: '6월', 자기주도: 5680, 수업: 3120 },
];

/* ─── 블록 구현 (스토리와 대시보드 조합에서 재사용) ─── */

function AreaSummaryBlock() {
  const total = monthlyStudy.reduce(
    (acc, d) => ({ 자기주도: acc.자기주도 + d.자기주도, 수업: acc.수업 + d.수업 }),
    { 자기주도: 0, 수업: 0 },
  );
  const sum = total.자기주도 + total.수업;
  const series = [
    { key: '자기주도' as const, label: '자기주도 학습' },
    { key: '수업' as const, label: '수업' },
  ];

  return (
    <Card>
      <Stack gap="20">
        <CardHeading title="학습 시간 구성" description="최근 6개월 · 분 단위" />
        <Box height="240px">
          <AreaChart
            data={monthlyStudy}
            xKey="월"
            yKeys={['자기주도', '수업']}
            stackMode="stacked"
            labels={{ 자기주도: '자기주도 학습', 수업: '수업' }}
            showTooltip
            margin={{ left: 60 }}
          />
        </Box>
        <Box>
          {series.map(({ key, label }, i) => (
            <SummaryRow
              key={key}
              color={getChartColor(i)}
              label={label}
              value={`${total[key].toLocaleString('ko-KR')}분`}
              percent={`${((total[key] / sum) * 100).toFixed(1)}%`}
            />
          ))}
        </Box>
      </Stack>
    </Card>
  );
}

const ChartBlocksAreaWithSummaryListExample = () => <AreaSummaryBlock />;

export default ChartBlocksAreaWithSummaryListExample;

KPI 헤더 + 차트

import { Card } from '@mildang/design-system/Chart';
import { Box, HStack, Stack } from '@mildang/styled-system/jsx';
import { AreaChart } from '@mildang/design-system/Chart';

/* ─── 공용 헬퍼 ─── */

/** 증감 배지 pill — 양수 success, 음수 error */
function DeltaBadge({ delta, suffix = '%' }: { delta: number; suffix?: string }) {
  const up = delta >= 0;
  return (
    <Box
      as="span"
      display="inline-flex"
      alignItems="center"
      px="8"
      py="2"
      borderRadius="6"
      textStyle="caption-lg-medium"
      bg={up ? 'success.bg.low' : 'error.bg.low'}
      color={up ? 'success.text.base' : 'error.text.base'}
      fontVariantNumeric="tabular-nums"
    >
      {up ? '▲' : '▼'} {Math.abs(delta).toLocaleString('ko-KR')}
      {suffix}
    </Box>
  );
}

/** Card 상단 제목 + 설명 — Tremor 블록의 헤더 관례 */
function CardHeading({ title, description, action }: { title: string; description?: string; action?: React.ReactNode }) {
  return (
    <HStack justifyContent="space-between" alignItems="flex-start" gap="16">
      <Box>
        <Box as="h3" textStyle="title-md" color="primary.text.base">
          {title}
        </Box>
        {description ? (
          <Box as="p" mt="2" textStyle="caption-lg" color="primary.text.low">
            {description}
          </Box>
        ) : null}
      </Box>
      {action}
    </HStack>
  );
}

const weeklyActive = [
  { 주: '1주', 학생: 1820 },
  { 주: '2주', 학생: 1760 },
  { 주: '3주', 학생: 1930 },
  { 주: '4주', 학생: 2050 },
  { 주: '5주', 학생: 1990 },
  { 주: '6주', 학생: 2180 },
  { 주: '7주', 학생: 2340 },
  { 주: '8주', 학생: 2410 },
];

function KpiHeaderChartBlock() {
  return (
    <Card>
      <Stack gap="20">
        <CardHeading title="주간 활성 학생" description="최근 8주" action={<DeltaBadge delta={12.4} />} />
        <HStack gap="4" alignItems="baseline">
          <Box textStyle="headline-sm" color="primary.text.base" fontVariantNumeric="tabular-nums">
            2,410
          </Box>
          <Box textStyle="contents-lg" color="primary.text.lowest">

          </Box>
        </HStack>
        <Box height="180px">
          <AreaChart data={weeklyActive} xKey="주" yKeys={['학생']} labels={{ 학생: '활성 학생' }} showTooltip showYAxis={false} />
        </Box>
        <Box pt="16" borderTopWidth="1" borderTopStyle="solid" borderTopColor="primary.border.low" textStyle="caption-lg" color="primary.text.low">
          매주 월요일 00시 기준 집계
        </Box>
      </Stack>
    </Card>
  );
}

const ChartBlocksKpiHeaderChartExample = () => <KpiHeaderChartBlock />;

export default ChartBlocksKpiHeaderChartExample;

지표 전환 + 순위 리스트

import { useState } from 'react';
import { Card } from '@mildang/design-system/Chart';
import { Box, HStack, Stack } from '@mildang/styled-system/jsx';
import { SegmentedControl } from '@mildang/design-system/SegmentedControl';
import { AreaChart } from '@mildang/design-system/Chart';
import { BarList } from '@mildang/design-system/Chart';

/* ─── 공용 헬퍼 ─── */

/** 증감 배지 pill — 양수 success, 음수 error */
function DeltaBadge({ delta, suffix = '%' }: { delta: number; suffix?: string }) {
  const up = delta >= 0;
  return (
    <Box
      as="span"
      display="inline-flex"
      alignItems="center"
      px="8"
      py="2"
      borderRadius="6"
      textStyle="caption-lg-medium"
      bg={up ? 'success.bg.low' : 'error.bg.low'}
      color={up ? 'success.text.base' : 'error.text.base'}
      fontVariantNumeric="tabular-nums"
    >
      {up ? '▲' : '▼'} {Math.abs(delta).toLocaleString('ko-KR')}
      {suffix}
    </Box>
  );
}

/** Card 상단 제목 + 설명 — Tremor 블록의 헤더 관례 */
function CardHeading({ title, description, action }: { title: string; description?: string; action?: React.ReactNode }) {
  return (
    <HStack justifyContent="space-between" alignItems="flex-start" gap="16">
      <Box>
        <Box as="h3" textStyle="title-md" color="primary.text.base">
          {title}
        </Box>
        {description ? (
          <Box as="p" mt="2" textStyle="caption-lg" color="primary.text.low">
            {description}
          </Box>
        ) : null}
      </Box>
      {action}
    </HStack>
  );
}

/* ─── 공용 데이터 (밀당 도메인, 결정적) ─── */

const monthlyStudy = [
  { 월: '1월', 자기주도: 4120, 수업: 2280 },
  { 월: '2월', 자기주도: 3860, 수업: 2140 },
  { 월: '3월', 자기주도: 4510, 수업: 2620 },
  { 월: '4월', 자기주도: 4380, 수업: 2540 },
  { 월: '5월', 자기주도: 5230, 수업: 2860 },
  { 월: '6월', 자기주도: 5680, 수업: 3120 },
];

const metricData = {
  학습시간: {
    unit: '분',
    total: 43340,
    delta: 8.6,
    trend: monthlyStudy.map(({ , 자기주도, 수업 }) => ({ 월, 값: 자기주도 + 수업 })),
    ranking: [
      { label: '영어', value: 14200 },
      { label: '수학', value: 12800 },
      { label: '국어', value: 9340 },
      { label: '과학', value: 7000 },
    ],
  },
  문제풀이: {
    unit: '문항',
    total: 128400,
    delta: -3.2,
    trend: monthlyStudy.map(({  }, i) => ({ 월, 값: 18200 + ((i * 1730) % 5200) })),
    ranking: [
      { label: '수학', value: 48200 },
      { label: '영어', value: 39600 },
      { label: '과학', value: 22400 },
      { label: '국어', value: 18200 },
    ],
  },
};

function MetricSwitcherBlock() {
  const [metric, setMetric] = useState<keyof typeof metricData>('학습시간');
  const current = metricData[metric];

  return (
    <Card>
      <Stack gap="20">
        <CardHeading
          title="학습 지표"
          description="최근 6개월 누적"
          action={
            <SegmentedControl size="sm" value={metric} onValueChange={(v) => setMetric(v as keyof typeof metricData)} aria-label="지표 선택">
              <SegmentedControl.Item value="학습시간">학습 시간</SegmentedControl.Item>
              <SegmentedControl.Item value="문제풀이">문제 풀이</SegmentedControl.Item>
            </SegmentedControl>
          }
        />
        <HStack gap="8" alignItems="baseline">
          <Box textStyle="headline-sm" color="primary.text.base" fontVariantNumeric="tabular-nums">
            {current.total.toLocaleString('ko-KR')}
          </Box>
          <Box textStyle="contents-lg" color="primary.text.lowest">
            {current.unit}
          </Box>
          <DeltaBadge delta={current.delta} />
        </HStack>
        <Box height="200px">
          <AreaChart data={current.trend} xKey="월" yKeys={['값']} labels={{ 값: metric }} showTooltip showYAxis={false} />
        </Box>
        <Box>
          <Box textStyle="caption-lg-medium" color="primary.text.low" mb="8">
            과목별 순위
          </Box>
          <BarList data={current.ranking} ariaLabel={`${metric} 과목별 순위`} valueFormatter={(v) => `${v.toLocaleString('ko-KR')}${current.unit}`} />
        </Box>
      </Stack>
    </Card>
  );
}

const ChartBlocksMetricSwitcherExample = () => <MetricSwitcherBlock />;

export default ChartBlocksMetricSwitcherExample;

모니터링 + 기간 선택

import { useState } from 'react';
import { Card } from '@mildang/design-system/Chart';
import { Box, HStack, Stack } from '@mildang/styled-system/jsx';
import { SegmentedControl } from '@mildang/design-system/SegmentedControl';
import { AreaChart } from '@mildang/design-system/Chart';

/** Card 상단 제목 + 설명 — Tremor 블록의 헤더 관례 */
function CardHeading({ title, description, action }: { title: string; description?: string; action?: React.ReactNode }) {
  return (
    <HStack justifyContent="space-between" alignItems="flex-start" gap="16">
      <Box>
        <Box as="h3" textStyle="title-md" color="primary.text.base">
          {title}
        </Box>
        {description ? (
          <Box as="p" mt="2" textStyle="caption-lg" color="primary.text.low">
            {description}
          </Box>
        ) : null}
      </Box>
      {action}
    </HStack>
  );
}

const monitoringByPeriod = {
  '1주': Array.from({ length: 7 }, (_, i) => ({ 구간: `${i + 1}일`, 요청: 4200 + ((i * 811) % 1900) })),
  '4주': Array.from({ length: 4 }, (_, i) => ({ 구간: `${i + 1}주`, 요청: 30200 + ((i * 3391) % 8200) })),
  '12주': Array.from({ length: 12 }, (_, i) => ({ 구간: `${i + 1}주`, 요청: 27800 + ((i * 2113) % 9600) })),
};

function MonitoringBlock() {
  const [period, setPeriod] = useState<keyof typeof monitoringByPeriod>('4주');

  return (
    <Card>
      <Stack gap="20">
        <CardHeading title="AI 튜터 응답 모니터링" description="요청 수 · 성공률 · 평균 응답 시간" />
        <HStack gap="24" flexWrap="wrap">
          {[
            { label: '총 요청', value: '132,400건' },
            { label: '성공률', value: '99.2%' },
            { label: '평균 응답', value: '1.4초' },
          ].map(({ label, value }) => (
            <Box key={label}>
              <Box textStyle="caption-lg" color="primary.text.low">
                {label}
              </Box>
              <Box textStyle="body-xl-semibold" color="primary.text.base" fontVariantNumeric="tabular-nums">
                {value}
              </Box>
            </Box>
          ))}
        </HStack>
        <SegmentedControl
          size="sm"
          value={period}
          onValueChange={(v) => setPeriod(v as keyof typeof monitoringByPeriod)}
          aria-label="기간 선택"
        >
          <SegmentedControl.Item value="1주">1주</SegmentedControl.Item>
          <SegmentedControl.Item value="4주">4주</SegmentedControl.Item>
          <SegmentedControl.Item value="12주">12주</SegmentedControl.Item>
        </SegmentedControl>
        <Box height="200px">
          <AreaChart data={monitoringByPeriod[period]} xKey="구간" yKeys={['요청']} labels={{ 요청: '요청 수' }} showTooltip margin={{ left: 60 }} />
        </Box>
      </Stack>
    </Card>
  );
}

const ChartBlocksMonitoringWithPeriodExample = () => <MonitoringBlock />;

export default ChartBlocksMonitoringWithPeriodExample;

KPI 그리드

import { Grid } from '@mildang/styled-system/jsx';
import { KpiCard } from '@mildang/design-system/Chart';

const weeklyActive = [
  { 주: '1주', 학생: 1820 },
  { 주: '2주', 학생: 1760 },
  { 주: '3주', 학생: 1930 },
  { 주: '4주', 학생: 2050 },
  { 주: '5주', 학생: 1990 },
  { 주: '6주', 학생: 2180 },
  { 주: '7주', 학생: 2340 },
  { 주: '8주', 학생: 2410 },
];

function KpiCardsBlock() {
  return (
    <Grid gridTemplateColumns={{ base: '1fr', sm: 'repeat(2, minmax(0, 1fr))', lg: 'repeat(4, minmax(0, 1fr))' }} gap="16">
      <KpiCard label="활성 학생" value={2410} unit="명" delta={264} deltaLabel="전주 대비" sparklineData={weeklyActive.map((d) => d.학생)} />
      <KpiCard label="평균 정답률" value={78.4} unit="%" delta={2.1} deltaLabel="전주 대비" format={(v) => v.toFixed(1)} />
      <KpiCard label="평균 학습 시간" value={94} unit="분/일" delta={-6} deltaLabel="전주 대비" />
      <KpiCard label="완료된 수업" value={1832} unit="회" delta={118} deltaLabel="전주 대비" />
    </Grid>
  );
}

const ChartBlocksKpiCardsGridExample = () => <KpiCardsBlock />;

export default ChartBlocksKpiCardsGridExample;

KPI + 진행률

import { Box, Grid, HStack, Stack } from '@mildang/styled-system/jsx';
import { Card } from '@mildang/design-system/Chart';
import { ProgressBar } from '@mildang/design-system/Progress';

const progressKpis = [
  { label: '이번 달 목표 수업', current: 1832, goal: 2400 },
  { label: '신규 가입 목표', current: 428, goal: 500 },
  { label: '학부모 상담 처리', current: 96, goal: 120 },
];

function KpiProgressBlock() {
  return (
    <Grid gridTemplateColumns={{ base: '1fr', md: 'repeat(3, minmax(0, 1fr))' }} gap="16">
      {progressKpis.map(({ label, current, goal }) => {
        const percent = Math.round((current / goal) * 100);
        return (
          <Card key={label} p="16">
            <Stack gap="12">
              <Box textStyle="caption-lg-medium" color="primary.text.low">
                {label}
              </Box>
              <HStack gap="4" alignItems="baseline">
                <Box textStyle="title-2xl" color="primary.text.base" fontVariantNumeric="tabular-nums">
                  {current.toLocaleString('ko-KR')}
                </Box>
                <Box textStyle="caption-lg" color="primary.text.lowest" fontVariantNumeric="tabular-nums">
                  / {goal.toLocaleString('ko-KR')}
                </Box>
              </HStack>
              <HStack gap="8" alignItems="center">
                <Box flexGrow={1}>
                  <ProgressBar value={percent} type="conditional" />
                </Box>
                <Box textStyle="caption-lg-medium" color="primary.text.base" fontVariantNumeric="tabular-nums" width="36px" textAlign="right">
                  {percent}%
                </Box>
              </HStack>
            </Stack>
          </Card>
        );
      })}
    </Grid>
  );
}

const ChartBlocksKpiWithProgressExample = () => <KpiProgressBlock />;

export default ChartBlocksKpiWithProgressExample;

도넛 + 범례 리스트

import { Card } from '@mildang/design-system/Chart';
import { Box, Grid, HStack, Stack } from '@mildang/styled-system/jsx';
import { PieChart } from '@mildang/design-system/Chart';
import { getChartColor } from '@mildang/design-system/Chart';
import { css } from '@mildang/styled-system/css';

/** Card 상단 제목 + 설명 — Tremor 블록의 헤더 관례 */
function CardHeading({ title, description, action }: { title: string; description?: string; action?: React.ReactNode }) {
  return (
    <HStack justifyContent="space-between" alignItems="flex-start" gap="16">
      <Box>
        <Box as="h3" textStyle="title-md" color="primary.text.base">
          {title}
        </Box>
        {description ? (
          <Box as="p" mt="2" textStyle="caption-lg" color="primary.text.low">
            {description}
          </Box>
        ) : null}
      </Box>
      {action}
    </HStack>
  );
}

/** 색 점 + 라벨 + 값 + 비중 행 — 차트 하단/우측 요약 리스트 공용 */
function SummaryRow({ color, label, value, percent }: { color: string; label: string; value: string; percent: string }) {
  return (
    <div className={summaryRowClass}>
      <Box flexShrink={0} width="8px" height="8px" borderRadius="full" style={{ backgroundColor: color }} />
      <Box textStyle="caption-lg" color="primary.text.low" flexGrow={1}>
        {label}
      </Box>
      <Box textStyle="caption-lg-medium" color="primary.text.base" fontVariantNumeric="tabular-nums">
        {value}
      </Box>
      <Box textStyle="caption-lg" color="primary.text.lowest" fontVariantNumeric="tabular-nums" width="44px" textAlign="right">
        {percent}
      </Box>
    </div>
  );
}

const summaryRowClass = css({
  display: 'flex',
  alignItems: 'center',
  gap: '8',
  py: '10',
  borderTopWidth: '1',
  borderTopStyle: 'solid',
  borderTopColor: 'primary.border.low',
});

const channelData = [
  { 채널: '앱', 학생수: 1240 },
  { 채널: '웹', 학생수: 680 },
  { 채널: '태블릿', 학생수: 380 },
  { 채널: '기타', 학생수: 110 },
];

function DonutLegendBlock() {
  const sum = channelData.reduce((acc, d) => acc + d.학생수, 0);
  return (
    <Card>
      <Stack gap="20">
        <CardHeading title="접속 채널 분포" description="최근 30일 활성 학생 기준" />
        <Grid gridTemplateColumns={{ base: '1fr', md: '200px minmax(0, 1fr)' }} gap="20" alignItems="center">
          <Box height="200px">
            <PieChart data={channelData} valueKey="학생수" labelKey="채널" innerRadius="donut" />
          </Box>
          <Box>
            {channelData.map((d, i) => (
              <SummaryRow
                key={d.채널}
                color={getChartColor(i)}
                label={d.채널}
                value={`${d.학생수.toLocaleString('ko-KR')}명`}
                percent={`${((d.학생수 / sum) * 100).toFixed(1)}%`}
              />
            ))}
          </Box>
        </Grid>
      </Stack>
    </Card>
  );
}

const ChartBlocksDonutWithLegendListExample = () => <DonutLegendBlock />;

export default ChartBlocksDonutWithLegendListExample;

대시보드 조합

import { Box, Grid, HStack, Stack } from '@mildang/styled-system/jsx';
import { useState } from 'react';
import { Card } from '@mildang/design-system/Chart';
import { SegmentedControl } from '@mildang/design-system/SegmentedControl';
import { AreaChart } from '@mildang/design-system/Chart';
import { BarList } from '@mildang/design-system/Chart';
import { PieChart } from '@mildang/design-system/Chart';
import { getChartColor } from '@mildang/design-system/Chart';
import { css } from '@mildang/styled-system/css';
import { ProgressBar } from '@mildang/design-system/Progress';
import { KpiCard } from '@mildang/design-system/Chart';

/* ─── 공용 헬퍼 ─── */

/** 증감 배지 pill — 양수 success, 음수 error */
function DeltaBadge({ delta, suffix = '%' }: { delta: number; suffix?: string }) {
  const up = delta >= 0;
  return (
    <Box
      as="span"
      display="inline-flex"
      alignItems="center"
      px="8"
      py="2"
      borderRadius="6"
      textStyle="caption-lg-medium"
      bg={up ? 'success.bg.low' : 'error.bg.low'}
      color={up ? 'success.text.base' : 'error.text.base'}
      fontVariantNumeric="tabular-nums"
    >
      {up ? '▲' : '▼'} {Math.abs(delta).toLocaleString('ko-KR')}
      {suffix}
    </Box>
  );
}

/** Card 상단 제목 + 설명 — Tremor 블록의 헤더 관례 */
function CardHeading({ title, description, action }: { title: string; description?: string; action?: React.ReactNode }) {
  return (
    <HStack justifyContent="space-between" alignItems="flex-start" gap="16">
      <Box>
        <Box as="h3" textStyle="title-md" color="primary.text.base">
          {title}
        </Box>
        {description ? (
          <Box as="p" mt="2" textStyle="caption-lg" color="primary.text.low">
            {description}
          </Box>
        ) : null}
      </Box>
      {action}
    </HStack>
  );
}

/** 색 점 + 라벨 + 값 + 비중 행 — 차트 하단/우측 요약 리스트 공용 */
function SummaryRow({ color, label, value, percent }: { color: string; label: string; value: string; percent: string }) {
  return (
    <div className={summaryRowClass}>
      <Box flexShrink={0} width="8px" height="8px" borderRadius="full" style={{ backgroundColor: color }} />
      <Box textStyle="caption-lg" color="primary.text.low" flexGrow={1}>
        {label}
      </Box>
      <Box textStyle="caption-lg-medium" color="primary.text.base" fontVariantNumeric="tabular-nums">
        {value}
      </Box>
      <Box textStyle="caption-lg" color="primary.text.lowest" fontVariantNumeric="tabular-nums" width="44px" textAlign="right">
        {percent}
      </Box>
    </div>
  );
}

const summaryRowClass = css({
  display: 'flex',
  alignItems: 'center',
  gap: '8',
  py: '10',
  borderTopWidth: '1',
  borderTopStyle: 'solid',
  borderTopColor: 'primary.border.low',
});

/* ─── 공용 데이터 (밀당 도메인, 결정적) ─── */

const monthlyStudy = [
  { 월: '1월', 자기주도: 4120, 수업: 2280 },
  { 월: '2월', 자기주도: 3860, 수업: 2140 },
  { 월: '3월', 자기주도: 4510, 수업: 2620 },
  { 월: '4월', 자기주도: 4380, 수업: 2540 },
  { 월: '5월', 자기주도: 5230, 수업: 2860 },
  { 월: '6월', 자기주도: 5680, 수업: 3120 },
];

const weeklyActive = [
  { 주: '1주', 학생: 1820 },
  { 주: '2주', 학생: 1760 },
  { 주: '3주', 학생: 1930 },
  { 주: '4주', 학생: 2050 },
  { 주: '5주', 학생: 1990 },
  { 주: '6주', 학생: 2180 },
  { 주: '7주', 학생: 2340 },
  { 주: '8주', 학생: 2410 },
];

/* ─── 블록 구현 (스토리와 대시보드 조합에서 재사용) ─── */

function AreaSummaryBlock() {
  const total = monthlyStudy.reduce(
    (acc, d) => ({ 자기주도: acc.자기주도 + d.자기주도, 수업: acc.수업 + d.수업 }),
    { 자기주도: 0, 수업: 0 },
  );
  const sum = total.자기주도 + total.수업;
  const series = [
    { key: '자기주도' as const, label: '자기주도 학습' },
    { key: '수업' as const, label: '수업' },
  ];

  return (
    <Card>
      <Stack gap="20">
        <CardHeading title="학습 시간 구성" description="최근 6개월 · 분 단위" />
        <Box height="240px">
          <AreaChart
            data={monthlyStudy}
            xKey="월"
            yKeys={['자기주도', '수업']}
            stackMode="stacked"
            labels={{ 자기주도: '자기주도 학습', 수업: '수업' }}
            showTooltip
            margin={{ left: 60 }}
          />
        </Box>
        <Box>
          {series.map(({ key, label }, i) => (
            <SummaryRow
              key={key}
              color={getChartColor(i)}
              label={label}
              value={`${total[key].toLocaleString('ko-KR')}분`}
              percent={`${((total[key] / sum) * 100).toFixed(1)}%`}
            />
          ))}
        </Box>
      </Stack>
    </Card>
  );
}

const metricData = {
  학습시간: {
    unit: '분',
    total: 43340,
    delta: 8.6,
    trend: monthlyStudy.map(({ , 자기주도, 수업 }) => ({ 월, 값: 자기주도 + 수업 })),
    ranking: [
      { label: '영어', value: 14200 },
      { label: '수학', value: 12800 },
      { label: '국어', value: 9340 },
      { label: '과학', value: 7000 },
    ],
  },
  문제풀이: {
    unit: '문항',
    total: 128400,
    delta: -3.2,
    trend: monthlyStudy.map(({  }, i) => ({ 월, 값: 18200 + ((i * 1730) % 5200) })),
    ranking: [
      { label: '수학', value: 48200 },
      { label: '영어', value: 39600 },
      { label: '과학', value: 22400 },
      { label: '국어', value: 18200 },
    ],
  },
};

function MetricSwitcherBlock() {
  const [metric, setMetric] = useState<keyof typeof metricData>('학습시간');
  const current = metricData[metric];

  return (
    <Card>
      <Stack gap="20">
        <CardHeading
          title="학습 지표"
          description="최근 6개월 누적"
          action={
            <SegmentedControl size="sm" value={metric} onValueChange={(v) => setMetric(v as keyof typeof metricData)} aria-label="지표 선택">
              <SegmentedControl.Item value="학습시간">학습 시간</SegmentedControl.Item>
              <SegmentedControl.Item value="문제풀이">문제 풀이</SegmentedControl.Item>
            </SegmentedControl>
          }
        />
        <HStack gap="8" alignItems="baseline">
          <Box textStyle="headline-sm" color="primary.text.base" fontVariantNumeric="tabular-nums">
            {current.total.toLocaleString('ko-KR')}
          </Box>
          <Box textStyle="contents-lg" color="primary.text.lowest">
            {current.unit}
          </Box>
          <DeltaBadge delta={current.delta} />
        </HStack>
        <Box height="200px">
          <AreaChart data={current.trend} xKey="월" yKeys={['값']} labels={{ 값: metric }} showTooltip showYAxis={false} />
        </Box>
        <Box>
          <Box textStyle="caption-lg-medium" color="primary.text.low" mb="8">
            과목별 순위
          </Box>
          <BarList data={current.ranking} ariaLabel={`${metric} 과목별 순위`} valueFormatter={(v) => `${v.toLocaleString('ko-KR')}${current.unit}`} />
        </Box>
      </Stack>
    </Card>
  );
}

const monitoringByPeriod = {
  '1주': Array.from({ length: 7 }, (_, i) => ({ 구간: `${i + 1}일`, 요청: 4200 + ((i * 811) % 1900) })),
  '4주': Array.from({ length: 4 }, (_, i) => ({ 구간: `${i + 1}주`, 요청: 30200 + ((i * 3391) % 8200) })),
  '12주': Array.from({ length: 12 }, (_, i) => ({ 구간: `${i + 1}주`, 요청: 27800 + ((i * 2113) % 9600) })),
};

function MonitoringBlock() {
  const [period, setPeriod] = useState<keyof typeof monitoringByPeriod>('4주');

  return (
    <Card>
      <Stack gap="20">
        <CardHeading title="AI 튜터 응답 모니터링" description="요청 수 · 성공률 · 평균 응답 시간" />
        <HStack gap="24" flexWrap="wrap">
          {[
            { label: '총 요청', value: '132,400건' },
            { label: '성공률', value: '99.2%' },
            { label: '평균 응답', value: '1.4초' },
          ].map(({ label, value }) => (
            <Box key={label}>
              <Box textStyle="caption-lg" color="primary.text.low">
                {label}
              </Box>
              <Box textStyle="body-xl-semibold" color="primary.text.base" fontVariantNumeric="tabular-nums">
                {value}
              </Box>
            </Box>
          ))}
        </HStack>
        <SegmentedControl
          size="sm"
          value={period}
          onValueChange={(v) => setPeriod(v as keyof typeof monitoringByPeriod)}
          aria-label="기간 선택"
        >
          <SegmentedControl.Item value="1주">1주</SegmentedControl.Item>
          <SegmentedControl.Item value="4주">4주</SegmentedControl.Item>
          <SegmentedControl.Item value="12주">12주</SegmentedControl.Item>
        </SegmentedControl>
        <Box height="200px">
          <AreaChart data={monitoringByPeriod[period]} xKey="구간" yKeys={['요청']} labels={{ 요청: '요청 수' }} showTooltip margin={{ left: 60 }} />
        </Box>
      </Stack>
    </Card>
  );
}

function KpiCardsBlock() {
  return (
    <Grid gridTemplateColumns={{ base: '1fr', sm: 'repeat(2, minmax(0, 1fr))', lg: 'repeat(4, minmax(0, 1fr))' }} gap="16">
      <KpiCard label="활성 학생" value={2410} unit="명" delta={264} deltaLabel="전주 대비" sparklineData={weeklyActive.map((d) => d.학생)} />
      <KpiCard label="평균 정답률" value={78.4} unit="%" delta={2.1} deltaLabel="전주 대비" format={(v) => v.toFixed(1)} />
      <KpiCard label="평균 학습 시간" value={94} unit="분/일" delta={-6} deltaLabel="전주 대비" />
      <KpiCard label="완료된 수업" value={1832} unit="회" delta={118} deltaLabel="전주 대비" />
    </Grid>
  );
}

const progressKpis = [
  { label: '이번 달 목표 수업', current: 1832, goal: 2400 },
  { label: '신규 가입 목표', current: 428, goal: 500 },
  { label: '학부모 상담 처리', current: 96, goal: 120 },
];

function KpiProgressBlock() {
  return (
    <Grid gridTemplateColumns={{ base: '1fr', md: 'repeat(3, minmax(0, 1fr))' }} gap="16">
      {progressKpis.map(({ label, current, goal }) => {
        const percent = Math.round((current / goal) * 100);
        return (
          <Card key={label} p="16">
            <Stack gap="12">
              <Box textStyle="caption-lg-medium" color="primary.text.low">
                {label}
              </Box>
              <HStack gap="4" alignItems="baseline">
                <Box textStyle="title-2xl" color="primary.text.base" fontVariantNumeric="tabular-nums">
                  {current.toLocaleString('ko-KR')}
                </Box>
                <Box textStyle="caption-lg" color="primary.text.lowest" fontVariantNumeric="tabular-nums">
                  / {goal.toLocaleString('ko-KR')}
                </Box>
              </HStack>
              <HStack gap="8" alignItems="center">
                <Box flexGrow={1}>
                  <ProgressBar value={percent} type="conditional" />
                </Box>
                <Box textStyle="caption-lg-medium" color="primary.text.base" fontVariantNumeric="tabular-nums" width="36px" textAlign="right">
                  {percent}%
                </Box>
              </HStack>
            </Stack>
          </Card>
        );
      })}
    </Grid>
  );
}

const channelData = [
  { 채널: '앱', 학생수: 1240 },
  { 채널: '웹', 학생수: 680 },
  { 채널: '태블릿', 학생수: 380 },
  { 채널: '기타', 학생수: 110 },
];

function DonutLegendBlock() {
  const sum = channelData.reduce((acc, d) => acc + d.학생수, 0);
  return (
    <Card>
      <Stack gap="20">
        <CardHeading title="접속 채널 분포" description="최근 30일 활성 학생 기준" />
        <Grid gridTemplateColumns={{ base: '1fr', md: '200px minmax(0, 1fr)' }} gap="20" alignItems="center">
          <Box height="200px">
            <PieChart data={channelData} valueKey="학생수" labelKey="채널" innerRadius="donut" />
          </Box>
          <Box>
            {channelData.map((d, i) => (
              <SummaryRow
                key={d.채널}
                color={getChartColor(i)}
                label={d.채널}
                value={`${d.학생수.toLocaleString('ko-KR')}명`}
                percent={`${((d.학생수 / sum) * 100).toFixed(1)}%`}
              />
            ))}
          </Box>
        </Grid>
      </Stack>
    </Card>
  );
}

const ChartBlocksDashboardExample = () => (
    <Stack gap="20">
      <KpiCardsBlock />
      <KpiProgressBlock />
      <Grid gridTemplateColumns={{ base: '1fr', lg: 'repeat(2, minmax(0, 1fr))' }} gap="20">
        <AreaSummaryBlock />
        <DonutLegendBlock />
      </Grid>
      <Grid gridTemplateColumns={{ base: '1fr', lg: 'repeat(2, minmax(0, 1fr))' }} gap="20">
        <MetricSwitcherBlock />
        <MonitoringBlock />
      </Grid>
    </Stack>
  );

export default ChartBlocksDashboardExample;

색상 프리셋

import { CHART_COLOR_PRESET } from '@mildang/design-system/Chart';

const CHART_STROKE_PRESET: string[] = ['#2563eb', '#059669', '#7c3aed', '#d97706', '#4b5563', '#0891b2', '#db2777', '#65a30d', '#c026d3'];

const CHART_TEXT_COLOR_PRESET: string[] = [
  '#ffffff',
  '#ffffff',
  '#ffffff',
  '#111827',
  '#ffffff',
  '#ffffff',
  '#ffffff',
  '#111827',
  '#ffffff',
];

const TREMOR_NAMES = ['blue', 'emerald', 'violet', 'amber', 'gray', 'cyan', 'pink', 'lime', 'fuchsia'];

const ChartColorsPaletteExample = () => (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
      {CHART_COLOR_PRESET.map((fill, i) => (
        <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          {/* Fill */}
          <div
            style={{
              width: 32,
              height: 32,
              borderRadius: 6,
              backgroundColor: fill,
              border: `2px solid ${CHART_STROKE_PRESET[i]}`,
              flexShrink: 0,
            }}
          />
          {/* Text color sample */}
          <div
            style={{
              width: 32,
              height: 32,
              borderRadius: 6,
              backgroundColor: fill,
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              flexShrink: 0,
            }}
          >
            <span style={{ color: CHART_TEXT_COLOR_PRESET[i], fontSize: 11, fontWeight: 700 }}>Aa</span>
          </div>
          {/* Stroke swatch */}
          <div
            style={{
              width: 12,
              height: 32,
              borderRadius: 3,
              backgroundColor: CHART_STROKE_PRESET[i],
              flexShrink: 0,
            }}
          />
          {/* Token name */}
          <span style={{ fontFamily: 'monospace', fontSize: 12, color: '#666' }}>
            {TREMOR_NAMES[i]}
            <span style={{ color: '#bbb' }}> (fill / stroke / text)</span>
          </span>
        </div>
      ))}
    </div>
  );

export default ChartColorsPaletteExample;

기본 색상 (프리셋)

import { PieChart } from '@mildang/design-system/Chart';
import { BarChart } from '@mildang/design-system/Chart';

const sampleData = [
  { subject: '수학', score: 35 },
  { subject: '영어', score: 25 },
  { subject: '국어', score: 20 },
  { subject: '과학', score: 12 },
  { subject: '사회', score: 8 },
];

const barData = [
  { month: '1월', a: 420, b: 280 },
  { month: '2월', a: 380, b: 250 },
  { month: '3월', a: 510, b: 320 },
  { month: '4월', a: 460, b: 300 },
  { month: '5월', a: 580, b: 350 },
];

const ChartColorsDefaultColorsExample = () => (
    <div style={{ display: 'flex', gap: 32, alignItems: 'flex-start' }}>
      <div>
        <div style={{ marginBottom: 8, fontSize: 12, color: '#888' }}>Donut</div>
        <div style={{ width: 280, height: 280 }}>
          <PieChart data={sampleData} valueKey="score" labelKey="subject" innerRadius="donut" showLegend />
        </div>
      </div>
      <div>
        <div style={{ marginBottom: 8, fontSize: 12, color: '#888' }}>Bar</div>
        <div style={{ width: 400, height: 280 }}>
          <BarChart data={barData} xKey="month" yKeys={['a', 'b']} groupMode="grouped" labels={{ a: '계획', b: '실적' }} showLegend />
        </div>
      </div>
    </div>
  );

export default ChartColorsDefaultColorsExample;

Tableau 20 팔레트

const TABLEAU_20: string[] = ['#4E79A7', '#A0CBE8', '#F28E2B', '#FFBE7D', '#59A14F', '#8CD17D', '#B6992D', '#F1CE63', '#499894', '#86BCB6', '#E15759', '#FF9D9A', '#79706E', '#BAB0AC', '#D37295', '#FABFD2', '#B07AA1', '#D4A6C8', '#9D7660', '#D7B5A6'];

const ChartColorsTableau20PaletteExample = () => (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
      <div style={{ marginBottom: 8, fontFamily: 'monospace', fontSize: 11, color: '#888' }}>
        Tableau 20 — 진한색(짝수) + 연한색(홀수) 10쌍
      </div>
      {Array.from({ length: 10 }, (_, i) => {
        const dark = TABLEAU_20[i * 2];
        const light = TABLEAU_20[i * 2 + 1];
        return (
          <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <div style={{ width: 32, height: 24, borderRadius: 4, backgroundColor: dark, flexShrink: 0 }} />
            <div style={{ width: 32, height: 24, borderRadius: 4, backgroundColor: light, flexShrink: 0 }} />
            <span style={{ fontFamily: 'monospace', fontSize: 11, color: '#666' }}>
              {dark} · {light}
            </span>
          </div>
        );
      })}
    </div>
  );

export default ChartColorsTableau20PaletteExample;

Tableau 20 — 차트 적용

import { PieChart } from '@mildang/design-system/Chart';
import { BarChart } from '@mildang/design-system/Chart';

const TABLEAU_20: string[] = ['#4E79A7', '#A0CBE8', '#F28E2B', '#FFBE7D', '#59A14F', '#8CD17D', '#B6992D', '#F1CE63', '#499894', '#86BCB6', '#E15759', '#FF9D9A', '#79706E', '#BAB0AC', '#D37295', '#FABFD2', '#B07AA1', '#D4A6C8', '#9D7660', '#D7B5A6'];

const sampleData = [
  { subject: '수학', score: 35 },
  { subject: '영어', score: 25 },
  { subject: '국어', score: 20 },
  { subject: '과학', score: 12 },
  { subject: '사회', score: 8 },
];

const barData = [
  { month: '1월', a: 420, b: 280 },
  { month: '2월', a: 380, b: 250 },
  { month: '3월', a: 510, b: 320 },
  { month: '4월', a: 460, b: 300 },
  { month: '5월', a: 580, b: 350 },
];

const ChartColorsTableau20ChartsExample = () => (
    <div style={{ display: 'flex', gap: 32, alignItems: 'flex-start' }}>
      <div>
        <div style={{ marginBottom: 8, fontSize: 12, color: '#888' }}>Donut — 진한색 5개</div>
        <div style={{ width: 280, height: 280 }}>
          <PieChart
            data={sampleData}
            valueKey="score"
            labelKey="subject"
            innerRadius="donut"
            showLegend
            colors={[0, 2, 4, 6, 8].map((i) => TABLEAU_20[i])}
          />
        </div>
      </div>
      <div>
        <div style={{ marginBottom: 8, fontSize: 12, color: '#888' }}>Bar — 진한색 + 연한색 쌍</div>
        <div style={{ width: 400, height: 280 }}>
          <BarChart
            data={barData}
            xKey="month"
            yKeys={['a', 'b']}
            groupMode="grouped"
            labels={{ a: '계획', b: '실적' }}
            showLegend
            colors={[TABLEAU_20[0], TABLEAU_20[1]]}
          />
        </div>
      </div>
    </div>
  );

export default ChartColorsTableau20ChartsExample;

Tremor 팔레트

const TREMOR_9: string[] = ['#3b82f6', '#10b981', '#8b5cf6', '#f59e0b', '#6b7280', '#06b6d4', '#ec4899', '#84cc16', '#d946ef'];

const TREMOR_NAMES = ['blue', 'emerald', 'violet', 'amber', 'gray', 'cyan', 'pink', 'lime', 'fuchsia'];

const ChartColorsTremor9PaletteExample = () => (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
      <div style={{ marginBottom: 8, fontFamily: 'monospace', fontSize: 11, color: '#888' }}>
        Tremor AvailableChartColors — Tailwind 500 계열 9색
      </div>
      {TREMOR_9.map((color, i) => (
        <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          <div style={{ width: 32, height: 24, borderRadius: 4, backgroundColor: color, flexShrink: 0 }} />
          <span style={{ fontFamily: 'monospace', fontSize: 11, color: '#666' }}>
            {color} · {TREMOR_NAMES[i]}
          </span>
        </div>
      ))}
    </div>
  );

export default ChartColorsTremor9PaletteExample;

Tremor — 차트 적용

import { PieChart } from '@mildang/design-system/Chart';
import { BarChart } from '@mildang/design-system/Chart';

const TREMOR_9: string[] = ['#3b82f6', '#10b981', '#8b5cf6', '#f59e0b', '#6b7280', '#06b6d4', '#ec4899', '#84cc16', '#d946ef'];

const sampleData = [
  { subject: '수학', score: 35 },
  { subject: '영어', score: 25 },
  { subject: '국어', score: 20 },
  { subject: '과학', score: 12 },
  { subject: '사회', score: 8 },
];

const barData = [
  { month: '1월', a: 420, b: 280 },
  { month: '2월', a: 380, b: 250 },
  { month: '3월', a: 510, b: 320 },
  { month: '4월', a: 460, b: 300 },
  { month: '5월', a: 580, b: 350 },
];

const ChartColorsTremor9ChartsExample = () => (
    <div style={{ display: 'flex', gap: 32, alignItems: 'flex-start' }}>
      <div>
        <div style={{ marginBottom: 8, fontSize: 12, color: '#888' }}>Donut</div>
        <div style={{ width: 280, height: 280 }}>
          <PieChart
            data={sampleData}
            valueKey="score"
            labelKey="subject"
            innerRadius="donut"
            showLegend
            colors={TREMOR_9}
          />
        </div>
      </div>
      <div>
        <div style={{ marginBottom: 8, fontSize: 12, color: '#888' }}>Bar</div>
        <div style={{ width: 400, height: 280 }}>
          <BarChart
            data={barData}
            xKey="month"
            yKeys={['a', 'b']}
            groupMode="grouped"
            labels={{ a: '계획', b: '실적' }}
            showLegend
            colors={[TREMOR_9[0], TREMOR_9[5]]}
          />
        </div>
      </div>
    </div>
  );

export default ChartColorsTremor9ChartsExample;

커스텀 색상

import { PieChart } from '@mildang/design-system/Chart';
import { BarChart } from '@mildang/design-system/Chart';
import { token } from '@mildang/styled-system/tokens';

const sampleData = [
  { subject: '수학', score: 35 },
  { subject: '영어', score: 25 },
  { subject: '국어', score: 20 },
  { subject: '과학', score: 12 },
  { subject: '사회', score: 8 },
];

const barData = [
  { month: '1월', a: 420, b: 280 },
  { month: '2월', a: 380, b: 250 },
  { month: '3월', a: 510, b: 320 },
  { month: '4월', a: 460, b: 300 },
  { month: '5월', a: 580, b: 350 },
];

// 커스텀 팔레트 예시 — goes 계열 5색
const customColors = [
  token('colors.goes.violet.light.500'),
  token('colors.goes.green.light.500'),
  token('colors.goes.orange.light.500'),
  token('colors.goes.red.light.500'),
  token('colors.goes.light.500'),
];

const customBarColors = [
  token('colors.goes.violet.light.500'),
  token('colors.goes.green.light.500'),
];

const ChartColorsCustomColorsExample = () => (
    <div style={{ display: 'flex', gap: 32, alignItems: 'flex-start' }}>
      <div>
        <div style={{ marginBottom: 8, fontSize: 12, color: '#888' }}>Donut</div>
        <div style={{ width: 280, height: 280 }}>
          <PieChart
            data={sampleData}
            valueKey="score"
            labelKey="subject"
            innerRadius="donut"
            showLegend
            colors={customColors}
          />
        </div>
      </div>
      <div>
        <div style={{ marginBottom: 8, fontSize: 12, color: '#888' }}>Bar</div>
        <div style={{ width: 400, height: 280 }}>
          <BarChart
            data={barData}
            xKey="month"
            yKeys={['a', 'b']}
            groupMode="grouped"
            labels={{ a: '계획', b: '실적' }}
            showLegend
            colors={customBarColors}
          />
        </div>
      </div>
    </div>
  );

export default ChartColorsCustomColorsExample;

행 방향

import type { ComponentProps } from 'react';
import { token } from '@mildang/styled-system/tokens';
import { ChartLegend } from '@mildang/design-system/Chart';

const legendItems = [
  { key: 'math', label: '수학', color: token('colors.info.border.high') },
  { key: 'english', label: '영어', color: token('colors.warning.border.high') },
  { key: 'korean', label: '국어', color: token('colors.positive.border.high') },
];

const STORY_DEFAULT_ARGS = { ...({}), ...({
    items: legendItems,
  }) } as ComponentProps<typeof ChartLegend>;

export default function ChartLegendRowExample(props: Partial<ComponentProps<typeof ChartLegend>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof ChartLegend>;
  return <ChartLegend {...mergedProps} />;
}

열 방향

import type { ComponentProps } from 'react';
import { token } from '@mildang/styled-system/tokens';
import { ChartLegend } from '@mildang/design-system/Chart';

const legendItems = [
  { key: 'math', label: '수학', color: token('colors.info.border.high') },
  { key: 'english', label: '영어', color: token('colors.warning.border.high') },
  { key: 'korean', label: '국어', color: token('colors.positive.border.high') },
];

const STORY_DEFAULT_ARGS = { ...({}), ...({
    items: legendItems,
    direction: 'column',
  }) } as ComponentProps<typeof ChartLegend>;

export default function ChartLegendColumnExample(props: Partial<ComponentProps<typeof ChartLegend>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof ChartLegend>;
  return <ChartLegend {...mergedProps} />;
}

값 포함

import type { ComponentProps } from 'react';
import { token } from '@mildang/styled-system/tokens';
import { ChartLegend } from '@mildang/design-system/Chart';

const valueLegendItems = [
  { key: 'math', label: '수학', value: '35명', color: token('colors.info.border.high') },
  { key: 'english', label: '영어', value: '25명', color: token('colors.warning.border.high') },
  { key: 'korean', label: '국어', value: '20명', color: token('colors.positive.border.high') },
];

const STORY_DEFAULT_ARGS = { ...({}), ...({
    items: valueLegendItems,
    direction: 'column',
  }) } as ComponentProps<typeof ChartLegend>;

export default function ChartLegendWithValuesExample(props: Partial<ComponentProps<typeof ChartLegend>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof ChartLegend>;
  return <ChartLegend {...mergedProps} />;
}

패턴 swatch

import type { ComponentProps } from 'react';
import { token } from '@mildang/styled-system/tokens';
import { createElement } from 'react';
import { ChartLegend } from '@mildang/design-system/Chart';

const patternId = 'chart-legend-story-pattern';

const patternLegendItems = [
  {
    key: 'revenue',
    label: '매출',
    value: '4,200',
    color: token('colors.info.surface.base'),
    strokeColor: token('colors.info.border.highest'),
    patternId,
    patternDefinition: createElement(
      'pattern',
      {
        id: patternId,
        width: 8,
        height: 8,
        patternUnits: 'userSpaceOnUse',
      },
      createElement('rect', {
        width: 8,
        height: 8,
        fill: token('colors.info.surface.base'),
      }),
      createElement('path', {
        d: 'M-2,2 l4,-4 M0,8 l8,-8 M6,10 l4,-4',
        stroke: token('colors.info.border.highest'),
        strokeWidth: 1.5,
      })
    ),
  },
  {
    key: 'cost',
    label: '비용',
    value: '2,800',
    color: token('colors.warning.surface.high'),
    strokeColor: token('colors.warning.border.highest'),
  },
];

const STORY_DEFAULT_ARGS = { ...({}), ...({
    items: patternLegendItems,
    direction: 'column',
  }) } as ComponentProps<typeof ChartLegend>;

export default function ChartLegendPatternSwatchExample(props: Partial<ComponentProps<typeof ChartLegend>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof ChartLegend>;
  return <ChartLegend {...mergedProps} />;
}

방향 비교

import { css } from '@mildang/styled-system/css';
import { ChartLegend } from '@mildang/design-system/Chart';
import { token } from '@mildang/styled-system/tokens';

const legendItems = [
  { key: 'math', label: '수학', color: token('colors.info.border.high') },
  { key: 'english', label: '영어', color: token('colors.warning.border.high') },
  { key: 'korean', label: '국어', color: token('colors.positive.border.high') },
];

const valueLegendItems = [
  { key: 'math', label: '수학', value: '35명', color: token('colors.info.border.high') },
  { key: 'english', label: '영어', value: '25명', color: token('colors.warning.border.high') },
  { key: 'korean', label: '국어', value: '20명', color: token('colors.positive.border.high') },
];

const ChartLegendDirectionComparisonExample = () => (
    <div
      className={css({
        display: 'flex',
        flexDirection: 'column',
        gap: '24',
      })}
    >
      <ChartLegend items={legendItems} />
      <ChartLegend items={valueLegendItems} direction="column" />
    </div>
  );

export default ChartLegendDirectionComparisonExample;

Basic

import { ChartTooltip, ChartTooltipContent, getChartColor } from '@mildang/design-system/Chart';

const ChartTooltipBasicExample = () => (
    <div style={{ position: 'relative', width: 280, height: 120 }}>
      <ChartTooltip
        tooltipOpen
        tooltipLeft={24}
        tooltipTop={16}
        tooltipData={{ label: '3월' }}
        renderContent={(data) => (
          <ChartTooltipContent
            label={data.label}
            items={[
              { name: '신규', value: 1284, color: getChartColor(0) },
              { name: '재방문', value: 842, color: getChartColor(1) },
            ]}
          />
        )}
      />
    </div>
  );

export default ChartTooltipBasicExample;

Indicator — dot (기본)

import type { ComponentProps } from 'react';
import { ChartTooltipContent } from '@mildang/design-system/Chart';

const multiItems = [
  { color: '#4f86f7', name: '매출', value: 4200 },
  { color: '#f7954f', name: '비용', value: 2800 },
];

const STORY_DEFAULT_ARGS = { ...({}), ...({
    label: '1월',
    items: multiItems,
    indicator: 'dot',
  }) } as ComponentProps<typeof ChartTooltipContent>;

export default function ChartTooltipContentDotExample(props: Partial<ComponentProps<typeof ChartTooltipContent>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof ChartTooltipContent>;
  return <ChartTooltipContent {...mergedProps} />;
}

Indicator — line

import type { ComponentProps } from 'react';
import { ChartTooltipContent } from '@mildang/design-system/Chart';

const multiItems = [
  { color: '#4f86f7', name: '매출', value: 4200 },
  { color: '#f7954f', name: '비용', value: 2800 },
];

const STORY_DEFAULT_ARGS = { ...({}), ...({
    label: '1월',
    items: multiItems,
    indicator: 'line',
  }) } as ComponentProps<typeof ChartTooltipContent>;

export default function ChartTooltipContentLineExample(props: Partial<ComponentProps<typeof ChartTooltipContent>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof ChartTooltipContent>;
  return <ChartTooltipContent {...mergedProps} />;
}

Indicator 비교

import { ChartTooltipContent } from '@mildang/design-system/Chart';

const multiItems = [
  { color: '#4f86f7', name: '매출', value: 4200 },
  { color: '#f7954f', name: '비용', value: 2800 },
];

const ChartTooltipContentAllIndicatorsExample = () => (
    <div style={{ display: 'flex', gap: 16 }}>
      <ChartTooltipContent label="dot" items={multiItems} indicator="dot" />
      <ChartTooltipContent label="line" items={multiItems} indicator="line" />
    </div>
  );

export default ChartTooltipContentAllIndicatorsExample;

단일 아이템 (파이 차트 스타일)

import type { ComponentProps } from 'react';
import { ChartTooltipContent } from '@mildang/design-system/Chart';

const singleItem = [
  { color: '#4f86f7', name: 'Chrome', value: '275 (34.1%)' },
];

const STORY_DEFAULT_ARGS = { ...({}), ...({
    items: singleItem,
  }) } as ComponentProps<typeof ChartTooltipContent>;

export default function ChartTooltipContentSingleItemExample(props: Partial<ComponentProps<typeof ChartTooltipContent>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof ChartTooltipContent>;
  return <ChartTooltipContent {...mergedProps} />;
}

다중 시리즈

import type { ComponentProps } from 'react';
import { ChartTooltipContent } from '@mildang/design-system/Chart';

const manyItems = [
  { color: '#4f86f7', name: 'Desktop',  value: 186 },
  { color: '#f7954f', name: 'Mobile',   value: 80  },
  { color: '#6fcf97', name: 'Tablet',   value: 44  },
  { color: '#bb6bd9', name: 'Smart TV', value: 12  },
];

const STORY_DEFAULT_ARGS = { ...({}), ...({
    label: 'January',
    items: manyItems,
  }) } as ComponentProps<typeof ChartTooltipContent>;

export default function ChartTooltipContentManyItemsExample(props: Partial<ComponentProps<typeof ChartTooltipContent>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof ChartTooltipContent>;
  return <ChartTooltipContent {...mergedProps} />;
}

값 없음 (이름만)

import type { ComponentProps } from 'react';
import { ChartTooltipContent } from '@mildang/design-system/Chart';

const multiItems = [
  { color: '#4f86f7', name: '매출', value: 4200 },
  { color: '#f7954f', name: '비용', value: 2800 },
];

const STORY_DEFAULT_ARGS = { ...({}), ...({
    label: '범주',
    items: multiItems.map(({ color, name }) => ({ color, name })),
  }) } as ComponentProps<typeof ChartTooltipContent>;

export default function ChartTooltipContentNoValueExample(props: Partial<ComponentProps<typeof ChartTooltipContent>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof ChartTooltipContent>;
  return <ChartTooltipContent {...mergedProps} />;
}

hideIndicator

import type { ComponentProps } from 'react';
import { ChartTooltipContent } from '@mildang/design-system/Chart';

const multiItems = [
  { color: '#4f86f7', name: '매출', value: 4200 },
  { color: '#f7954f', name: '비용', value: 2800 },
];

const STORY_DEFAULT_ARGS = { ...({}), ...({
    label: '1월',
    items: multiItems,
    hideIndicator: true,
  }) } as ComponentProps<typeof ChartTooltipContent>;

export default function ChartTooltipContentHideIndicatorExample(props: Partial<ComponentProps<typeof ChartTooltipContent>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof ChartTooltipContent>;
  return <ChartTooltipContent {...mergedProps} />;
}

hideLabel

import type { ComponentProps } from 'react';
import { ChartTooltipContent } from '@mildang/design-system/Chart';

const multiItems = [
  { color: '#4f86f7', name: '매출', value: 4200 },
  { color: '#f7954f', name: '비용', value: 2800 },
];

const STORY_DEFAULT_ARGS = { ...({}), ...({
    label: '1월',
    items: multiItems,
    hideLabel: true,
  }) } as ComponentProps<typeof ChartTooltipContent>;

export default function ChartTooltipContentHideLabelExample(props: Partial<ComponentProps<typeof ChartTooltipContent>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof ChartTooltipContent>;
  return <ChartTooltipContent {...mergedProps} />;
}

Playground

import type { ComponentProps } from 'react';
import { ChartTooltipContent } from '@mildang/design-system/Chart';

const multiItems = [
  { color: '#4f86f7', name: '매출', value: 4200 },
  { color: '#f7954f', name: '비용', value: 2800 },
];

const STORY_DEFAULT_ARGS = { ...({}), ...({
    label: '1월',
    items: multiItems,
    indicator: 'dot',
    hideLabel: false,
    hideIndicator: false,
  }) } as ComponentProps<typeof ChartTooltipContent>;

export default function ChartTooltipContentPlaygroundExample(props: Partial<ComponentProps<typeof ChartTooltipContent>> = {}) {
  const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ComponentProps<typeof ChartTooltipContent>;
  return <ChartTooltipContent {...mergedProps} />;
}