diff --git a/frontend/components/admin/dashboard/CHART_SYSTEM_PLAN.md b/frontend/components/admin/dashboard/CHART_SYSTEM_PLAN.md new file mode 100644 index 00000000..b732dad2 --- /dev/null +++ b/frontend/components/admin/dashboard/CHART_SYSTEM_PLAN.md @@ -0,0 +1,661 @@ +# πŸ“Š 차트 μ‹œμŠ€ν…œ κ΅¬ν˜„ κ³„νš + +## κ°œμš” + +D3.js 기반의 κ°•λ ₯ν•œ 차트 μ‹œμŠ€ν…œμ„ κ΅¬μΆ•ν•©λ‹ˆλ‹€. μ‚¬μš©μžλŠ” 데이터λ₯Ό 두 κ°€μ§€ 방법(DB 쿼리 λ˜λŠ” REST API)으둜 가져와 λ‹€μ–‘ν•œ 차트둜 μ‹œκ°ν™”ν•  수 μžˆμŠ΅λ‹ˆλ‹€. + +--- + +## 🎯 핡심 μš”κ΅¬μ‚¬ν•­ + +### 1. 데이터 μ†ŒμŠ€ (2κ°€μ§€ 방식) + +#### A. λ°μ΄ν„°λ² μ΄μŠ€ 컀λ„₯μ…˜ + +- **ν˜„μž¬ DB**: μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ κΈ°λ³Έ PostgreSQL μ—°κ²° +- **μ™ΈλΆ€ DB**: κΈ°μ‘΄ "μ™ΈλΆ€ 컀λ„₯μ…˜ 관리" λ©”λ‰΄μ—μ„œ λ“±λ‘λœ 컀λ„₯μ…˜λ§Œ μ‚¬μš© + - μ‹ κ·œ 컀λ„₯μ…˜ 생성은 μ™ΈλΆ€ 컀λ„₯μ…˜ 관리 λ©”λ‰΄μ—μ„œλ§Œ κ°€λŠ₯ + - 차트 μ„€μ •μ—μ„œλŠ” λ“±λ‘λœ 컀λ„₯μ…˜ λͺ©λ‘μ—μ„œ μ„ νƒλ§Œ κ°€λŠ₯ +- **쿼리 μ œν•œ**: SELECT 문만 ν—ˆμš© (INSERT, UPDATE, DELETE, DROP λ“± κΈˆμ§€) +- **쿼리 검증**: μ„œλ²„ μΈ‘μ—μ„œ SQL Injection λ°©μ§€ 및 쿼리 νƒ€μž… 검증 + +#### B. REST API 호좜 + +- **HTTP Methods**: GET (ꢌμž₯) - 데이터 μ‘°νšŒμ— μΆ©λΆ„ +- **데이터 ν˜•μ‹**: JSON μ‘λ‹΅λ§Œ ν—ˆμš© +- **헀더 μ„€μ •**: Authorization, Content-Type λ“± μ»€μŠ€ν…€ 헀더 지원 +- **쿼리 νŒŒλΌλ―Έν„°**: URL νŒŒλΌλ―Έν„°λ‘œ 필터링 쑰건 전달 +- **응닡 νŒŒμ‹±**: JSON κ΅¬μ‘°μ—μ„œ 차트 데이터 μΆ”μΆœ +- **μ—λŸ¬ 처리**: HTTP μƒνƒœ μ½”λ“œ 및 νƒ€μž„μ•„μ›ƒ 처리 + +> **μ°Έκ³ **: POSTλŠ” ν–₯ν›„ ν™•μž₯ (GraphQL, λ³΅μž‘ν•œ 필터링)을 μœ„ν•΄ μ„ νƒμ μœΌλ‘œ 지원 κ°€λŠ₯ + +### 2. 차트 νƒ€μž… (D3.js 기반) + +ν˜„μž¬ 지원 μ˜ˆμ •: + +- **Bar Chart** (λ§‰λŒ€ 차트): μˆ˜ν‰/수직 λ§‰λŒ€ +- **Line Chart** (μ„  차트): 단일/닀쀑 μ‹œλ¦¬μ¦ˆ +- **Area Chart** (μ˜μ—­ 차트): λˆ„μ  μ˜μ—­ 지원 +- **Pie Chart** (원 차트): 도넛 차트 포함 +- **Stacked Bar** (λˆ„μ  λ§‰λŒ€): 닀쀑 μ‹œλ¦¬μ¦ˆ λˆ„μ  +- **Combo Chart** (ν˜Όν•© 차트): λ§‰λŒ€ + μ„  μ‘°ν•© + +### 3. μΆ• λ§€ν•‘ μ„€μ • + +- **XμΆ•**: μΉ΄ν…Œκ³ λ¦¬/μ‹œκ°„ 데이터 (λ¬Έμžμ—΄, λ‚ μ§œ) +- **YμΆ•**: 숫자 데이터 (단일 λ˜λŠ” 닀쀑 선택 κ°€λŠ₯) +- **닀쀑 YμΆ•**: μ—¬λŸ¬ μ‹œλ¦¬μ¦ˆλ₯Ό ν•œ μ°¨νŠΈμ— ν‘œμ‹œ (예: κ°€λŸ­μ‹œ vs 아이폰 맀좜) +- **μžλ™ 감지**: 데이터 νƒ€μž…μ— 따라 μΆ• μžλ™ μΆ”μ²œ +- **데이터 λ³€ν™˜**: λ¬Έμžμ—΄ λ‚ μ§œλ₯Ό Date 객체둜 μžλ™ λ³€ν™˜ + +### 4. 차트 μŠ€νƒ€μΌλ§ + +- **색상 νŒ”λ ˆνŠΈ**: 사전 μ •μ˜λœ 색상 μ„ΈνŠΈ 선택 +- **μ»€μŠ€ν…€ 색상**: μ‚¬μš©μž μ§€μ • 색상 μž…λ ₯ +- **λ²”λ‘€**: μœ„μΉ˜ μ„€μ • (상단, ν•˜λ‹¨, 쒌츑, 우츑, μˆ¨κΉ€) +- **μ• λ‹ˆλ©”μ΄μ…˜**: 차트 λ‘œλ“œ μ‹œ λΆ€λ“œλŸ¬μš΄ μ „ν™˜ 효과 +- **툴팁**: 데이터 포인트 ν˜Έλ²„ μ‹œ 상세 정보 ν‘œμ‹œ +- **κ·Έλ¦¬λ“œ**: X/YμΆ• κ·Έλ¦¬λ“œ 라인 ν‘œμ‹œ/μˆ¨κΉ€ + +--- + +## πŸ“ 파일 ꡬ쑰 + +``` +frontend/components/admin/dashboard/ +β”œβ”€β”€ CHART_SYSTEM_PLAN.md # 이 파일 +β”œβ”€β”€ types.ts # βœ… κΈ°μ‘΄ (νƒ€μž… ν™•μž₯ ν•„μš”) +β”œβ”€β”€ ElementConfigModal.tsx # βœ… κΈ°μ‘΄ (λ¦¬νŒ©ν† λ§ ν•„μš”) +β”‚ +β”œβ”€β”€ data-sources/ # πŸ†• 데이터 μ†ŒμŠ€ κ΄€λ ¨ +β”‚ β”œβ”€β”€ DataSourceSelector.tsx # 데이터 μ†ŒμŠ€ 선택 UI (DB vs API) +β”‚ β”œβ”€β”€ DatabaseConfig.tsx # DB 컀λ„₯μ…˜ μ„€μ • UI +β”‚ β”œβ”€β”€ ApiConfig.tsx # REST API μ„€μ • UI +β”‚ └── dataSourceUtils.ts # 데이터 μ†ŒμŠ€ μœ ν‹Έλ¦¬ν‹° +β”‚ +β”œβ”€β”€ chart-config/ # πŸ”„ 차트 μ„€μ • κ΄€λ ¨ (λ¦¬νŒ©ν† λ§) +β”‚ β”œβ”€β”€ QueryEditor.tsx # βœ… κΈ°μ‘΄ (ν™•μž₯ ν•„μš”) +β”‚ β”œβ”€β”€ ChartConfigPanel.tsx # βœ… κΈ°μ‘΄ (ν™•μž₯ ν•„μš”) +β”‚ β”œβ”€β”€ AxisMapper.tsx # πŸ†• μΆ• λ§€ν•‘ UI +β”‚ β”œβ”€β”€ StyleConfig.tsx # πŸ†• μŠ€νƒ€μΌ μ„€μ • UI +β”‚ └── ChartPreview.tsx # πŸ†• μ‹€μ‹œκ°„ 미리보기 +β”‚ +β”œβ”€β”€ charts/ # πŸ†• D3 차트 μ»΄ν¬λ„ŒνŠΈ +β”‚ β”œβ”€β”€ ChartRenderer.tsx # 차트 λ Œλ”λŸ¬ (메인) +β”‚ β”œβ”€β”€ BarChart.tsx # λ§‰λŒ€ 차트 +β”‚ β”œβ”€β”€ LineChart.tsx # μ„  차트 +β”‚ β”œβ”€β”€ AreaChart.tsx # μ˜μ—­ 차트 +β”‚ β”œβ”€β”€ PieChart.tsx # 원 차트 +β”‚ β”œβ”€β”€ StackedBarChart.tsx # λˆ„μ  λ§‰λŒ€ 차트 +β”‚ β”œβ”€β”€ ComboChart.tsx # ν˜Όν•© 차트 +β”‚ β”œβ”€β”€ chartUtils.ts # 차트 μœ ν‹Έλ¦¬ν‹° +β”‚ └── d3Helpers.ts # D3 헬퍼 ν•¨μˆ˜ +β”‚ +└── CanvasElement.tsx # βœ… κΈ°μ‘΄ (차트 λ Œλ”λ§ 톡합) +``` + +--- + +## πŸ”§ νƒ€μž… μ •μ˜ ν™•μž₯ + +### κΈ°μ‘΄ νƒ€μž… μ—…λ°μ΄νŠΈ + +```typescript +// types.ts + +// 데이터 μ†ŒμŠ€ νƒ€μž… ν™•μž₯ +export interface ChartDataSource { + type: "database" | "api"; // 'static' 제거 + + // DB 컀λ„₯μ…˜ κ΄€λ ¨ + connectionType?: "current" | "external"; // ν˜„μž¬ DB vs μ™ΈλΆ€ DB + externalConnectionId?: string; // μ™ΈλΆ€ DB 컀λ„₯μ…˜ ID + query?: string; // SQL 쿼리 (SELECT만) + + // API κ΄€λ ¨ + endpoint?: string; // API URL + method?: "GET"; // HTTP λ©”μ„œλ“œ (GET만 지원) + headers?: Record; // μ»€μŠ€ν…€ 헀더 + queryParams?: Record; // URL 쿼리 νŒŒλΌλ―Έν„° + jsonPath?: string; // JSON μ‘λ‹΅μ—μ„œ 데이터 μΆ”μΆœ 경둜 (예: "data.results") + + // 곡톡 + refreshInterval?: number; // μžλ™ μƒˆλ‘œκ³ μΉ¨ (초) + lastExecuted?: string; // λ§ˆμ§€λ§‰ μ‹€ν–‰ μ‹œκ°„ + lastError?: string; // λ§ˆμ§€λ§‰ 였λ₯˜ λ©”μ‹œμ§€ +} + +// μ™ΈλΆ€ DB 컀λ„₯μ…˜ 정보 (κΈ°μ‘΄ μ™ΈλΆ€ 컀λ„₯μ…˜ κ΄€λ¦¬μ—μ„œ κ°€μ Έμ˜΄) +export interface ExternalConnection { + id: string; + name: string; // μ‚¬μš©μž μ§€μ • 이름 (ν‘œμ‹œμš©) + type: "postgresql" | "mysql" | "mssql" | "oracle"; + // λ‚˜λ¨Έμ§€ μ •λ³΄λŠ” μ™ΈλΆ€ 컀λ„₯μ…˜ κ΄€λ¦¬μ—μ„œλ§Œ 관리 +} + +// 차트 μ„€μ • ν™•μž₯ +export interface ChartConfig { + // μΆ• λ§€ν•‘ + xAxis: string; // XμΆ• ν•„λ“œλͺ… + yAxis: string | string[]; // YμΆ• ν•„λ“œλͺ… (닀쀑 κ°€λŠ₯) + + // 데이터 처리 + groupBy?: string; // κ·Έλ£Ήν•‘ ν•„λ“œ + aggregation?: "sum" | "avg" | "count" | "max" | "min"; + sortBy?: string; // μ •λ ¬ κΈ°μ€€ ν•„λ“œ + sortOrder?: "asc" | "desc"; // μ •λ ¬ μˆœμ„œ + limit?: number; // 데이터 개수 μ œν•œ + + // μŠ€νƒ€μΌ + colors?: string[]; // 차트 색상 νŒ”λ ˆνŠΈ + title?: string; // 차트 제λͺ© + showLegend?: boolean; // λ²”λ‘€ ν‘œμ‹œ + legendPosition?: "top" | "bottom" | "left" | "right"; // λ²”λ‘€ μœ„μΉ˜ + + // μΆ• μ„€μ • + xAxisLabel?: string; // XμΆ• 라벨 + yAxisLabel?: string; // YμΆ• 라벨 + showGrid?: boolean; // κ·Έλ¦¬λ“œ ν‘œμ‹œ + + // μ• λ‹ˆλ©”μ΄μ…˜ + enableAnimation?: boolean; // μ• λ‹ˆλ©”μ΄μ…˜ ν™œμ„±ν™” + animationDuration?: number; // μ• λ‹ˆλ©”μ΄μ…˜ μ‹œκ°„ (ms) + + // 툴팁 + showTooltip?: boolean; // 툴팁 ν‘œμ‹œ + tooltipFormat?: string; // 툴팁 포맷 (ν…œν”Œλ¦Ώ) + + // μ°¨νŠΈλ³„ 특수 μ„€μ • + barOrientation?: "vertical" | "horizontal"; // λ§‰λŒ€ λ°©ν–₯ + lineStyle?: "smooth" | "straight"; // μ„  μŠ€νƒ€μΌ + areaOpacity?: number; // μ˜μ—­ 투λͺ…도 + pieInnerRadius?: number; // 도넛 차트 λ‚΄λΆ€ λ°˜μ§€λ¦„ (0-1) + stackMode?: "normal" | "percent"; // λˆ„μ  λͺ¨λ“œ +} + +// API 응닡 ꡬ쑰 +export interface ApiResponse { + success: boolean; + data: T; + message?: string; + error?: string; +} + +// 차트 데이터 (λ³€ν™˜ ν›„) +export interface ChartData { + labels: string[]; // XμΆ• λ ˆμ΄λΈ” + datasets: ChartDataset[]; // YμΆ• 데이터셋 (닀쀑 μ‹œλ¦¬μ¦ˆ) +} + +export interface ChartDataset { + label: string; // μ‹œλ¦¬μ¦ˆ 이름 + data: number[]; // 데이터 κ°’ + color?: string; // 색상 +} +``` + +--- + +## πŸ“ κ΅¬ν˜„ 단계 + +### Phase 1: 데이터 μ†ŒμŠ€ μ„€μ • UI (4-5μ‹œκ°„) + +#### Step 1.1: 데이터 μ†ŒμŠ€ 선택기 + +- [ ] `DataSourceSelector.tsx` 생성 +- [ ] DB vs API 선택 λΌλ””μ˜€ λ²„νŠΌ +- [ ] 선택에 따라 ν•˜μœ„ UI 동적 λ Œλ”λ§ +- [ ] μƒνƒœ 관리 (ν˜„μž¬ μ„ νƒλœ μ†ŒμŠ€ νƒ€μž…) + +#### Step 1.2: λ°μ΄ν„°λ² μ΄μŠ€ μ„€μ • + +- [ ] `DatabaseConfig.tsx` 생성 +- [ ] ν˜„μž¬ DB / μ™ΈλΆ€ DB 선택 λΌλ””μ˜€ λ²„νŠΌ +- [ ] μ™ΈλΆ€ DB 선택 μ‹œ: + - **κΈ°μ‘΄ μ™ΈλΆ€ 컀λ„₯μ…˜ κ΄€λ¦¬μ—μ„œ λ“±λ‘λœ 컀λ„₯μ…˜ λͺ©λ‘ 뢈러였기** + - λ“œλ‘­λ‹€μš΄μœΌλ‘œ 컀λ„₯μ…˜ 선택 (ID, 이름, νƒ€μž… ν‘œμ‹œ) + - "μ™ΈλΆ€ 컀λ„₯μ…˜ κ΄€λ¦¬λ‘œ 이동" 링크 제곡 + - μ„ νƒλœ 컀λ„₯μ…˜ 정보 ν‘œμ‹œ (읽기 μ „μš©) +- [ ] SQL 에디터 톡합 (κΈ°μ‘΄ `QueryEditor` μž¬μ‚¬μš©) +- [ ] 쿼리 ν…ŒμŠ€νŠΈ λ²„νŠΌ (μ„ νƒλœ 컀λ„₯μ…˜μœΌλ‘œ μ‹€ν–‰) + +#### Step 1.3: REST API μ„€μ • + +- [ ] `ApiConfig.tsx` 생성 +- [ ] API μ—”λ“œν¬μΈνŠΈ URL μž…λ ₯ +- [ ] HTTP λ©”μ„œλ“œ: GET κ³ μ • (UIμ—μ„œ ν‘œμ‹œλ§Œ) +- [ ] URL 쿼리 νŒŒλΌλ―Έν„° μΆ”κ°€ UI (ν‚€-κ°’ 쌍) + - 동적 νŒŒλΌλ―Έν„° μΆ”κ°€/제거 λ²„νŠΌ + - μ˜ˆμ‹œ: `?category=electronics&limit=10` +- [ ] 헀더 μΆ”κ°€ UI (ν‚€-κ°’ 쌍) + - Authorization 헀더 λΉ λ₯Έ μž…λ ₯ + - 일반적인 헀더 ν…œν”Œλ¦Ώ 제곡 +- [ ] JSON Path μ„€μ • (데이터 μΆ”μΆœ 경둜) + - μ˜ˆμ‹œ: `data.results`, `items`, `response.data` +- [ ] ν…ŒμŠ€νŠΈ μš”μ²­ λ²„νŠΌ +- [ ] 응닡 미리보기 (JSON ꡬ쑰 ν‘œμ‹œ) + +#### Step 1.4: 데이터 μ†ŒμŠ€ μœ ν‹Έλ¦¬ν‹° + +- [ ] `dataSourceUtils.ts` 생성 +- [ ] DB 컀λ„₯μ…˜ 검증 ν•¨μˆ˜ +- [ ] API μš”μ²­ μ‹€ν–‰ ν•¨μˆ˜ +- [ ] JSON Path νŒŒμ‹± ν•¨μˆ˜ +- [ ] 데이터 μ •κ·œν™” ν•¨μˆ˜ (DB/API κ²°κ³Όλ₯Ό ν†΅μΌλœ ν˜•μ‹μœΌλ‘œ) + +### Phase 2: μ„œλ²„ μΈ‘ API κ΅¬ν˜„ (2-3μ‹œκ°„) + +#### Step 2.1: μ™ΈλΆ€ 컀λ„₯μ…˜ λͺ©λ‘ 쑰회 API + +- [ ] `GET /api/external-connections` - κΈ°μ‘΄ μ™ΈλΆ€ 컀λ„₯μ…˜ κ΄€λ¦¬μ˜ 컀λ„₯μ…˜ λͺ©λ‘ 쑰회 +- [ ] 응닡: `{ id, name, type }` μ΅œμ†Œ μ •λ³΄λ§Œ λ°˜ν™˜ (λ³΄μ•ˆ) +- [ ] 인증된 μ‚¬μš©μžλ§Œ μ ‘κ·Ό κ°€λŠ₯ + +#### Step 2.2: 쿼리 μ‹€ν–‰ API (ν™•μž₯) + +- [ ] κΈ°μ‘΄ `POST /api/dashboards/execute-query` ν™•μž₯ +- [ ] μ™ΈλΆ€ DB μ—°κ²° 지원 +- [ ] SELECT 쿼리 검증 (μ •κ·œμ‹ + SQL νŒŒμ„œ) +- [ ] SQL Injection λ°©μ§€ +- [ ] 쿼리 νƒ€μž„μ•„μ›ƒ μ„€μ • +- [ ] κ²°κ³Ό ν–‰ 수 μ œν•œ (μ΅œλŒ€ 1000ν–‰) +- [ ] μ—λŸ¬ 핸듀링 및 λ‘œκΉ… + +#### Step 2.3: REST API ν”„λ‘μ‹œ + +- [ ] `GET /api/dashboards/fetch-api` - API 호좜 ν”„λ‘μ‹œ (GET ν”„λ‘μ‹œ) +- [ ] 쿼리 νŒŒλΌλ―Έν„°λ‘œ λŒ€μƒ URL, 헀더, JSON Path 전달 +- [ ] CORS 우회 +- [ ] μš”μ²­ 헀더 전달 (Authorization λ“±) +- [ ] 응닡 캐싱 (선택적, 5λΆ„) +- [ ] νƒ€μž„μ•„μ›ƒ μ„€μ • (30초) +- [ ] JSON Path 적용 (μ„œλ²„ μΈ‘μ—μ„œ 데이터 μΆ”μΆœ) +- [ ] μ—λŸ¬ 핸듀링 및 μƒνƒœ μ½”λ“œ λ³€ν™˜ + +### Phase 3: 차트 μ„€μ • UI κ°œμ„  (3-4μ‹œκ°„) + +#### Step 3.1: μΆ• 맀퍼 + +- [ ] `AxisMapper.tsx` 생성 +- [ ] XμΆ• ν•„λ“œ 선택 λ“œλ‘­λ‹€μš΄ +- [ ] YμΆ• ν•„λ“œ 닀쀑 선택 (μ²΄ν¬λ°•μŠ€) +- [ ] 데이터 νƒ€μž… μžλ™ 감지 및 ν‘œμ‹œ +- [ ] μƒ˜ν”Œ 데이터 미리보기 (첫 3ν–‰) +- [ ] μΆ• 라벨 μ»€μŠ€ν„°λ§ˆμ΄μ§• + +#### Step 3.2: μŠ€νƒ€μΌ μ„€μ • + +- [ ] `StyleConfig.tsx` 생성 +- [ ] 색상 νŒ”λ ˆνŠΈ 선택 (사전 μ •μ˜ + μ»€μŠ€ν…€) +- [ ] λ²”λ‘€ μœ„μΉ˜ 선택 +- [ ] κ·Έλ¦¬λ“œ ν‘œμ‹œ/μˆ¨κΉ€ +- [ ] μ• λ‹ˆλ©”μ΄μ…˜ μ„€μ • +- [ ] μ°¨νŠΈλ³„ 특수 μ˜΅μ…˜ + - λ§‰λŒ€ 차트: μˆ˜ν‰/수직 + - μ„  차트: λΆ€λ“œλŸ¬μ›€ 정도 + - 원 차트: 도넛 λͺ¨λ“œ + +#### Step 3.3: μ‹€μ‹œκ°„ 미리보기 + +- [ ] `ChartPreview.tsx` 생성 +- [ ] μΆ•μ†Œλœ 차트 미리보기 (300x200) +- [ ] μ„€μ • λ³€κ²½ μ‹œ μ‹€μ‹œκ°„ μ—…λ°μ΄νŠΈ +- [ ] λ‘œλ”© μƒνƒœ ν‘œμ‹œ +- [ ] μ—λŸ¬ ν‘œμ‹œ + +### Phase 4: D3 차트 μ»΄ν¬λ„ŒνŠΈ (6-8μ‹œκ°„) + +#### Step 4.1: 차트 λ Œλ”λŸ¬ (곡톡) + +- [ ] `ChartRenderer.tsx` 생성 +- [ ] 차트 νƒ€μž…μ— 따라 μ μ ˆν•œ μ»΄ν¬λ„ŒνŠΈ λ Œλ”λ§ +- [ ] 데이터 μ •κ·œν™” 및 λ³€ν™˜ +- [ ] 곡톡 λ ˆμ΄μ•„μ›ƒ (제λͺ©, λ²”λ‘€) +- [ ] λ°˜μ‘ν˜• 크기 쑰절 +- [ ] μ—λŸ¬ λ°”μš΄λ”λ¦¬ + +#### Step 4.2: λ§‰λŒ€ 차트 + +- [ ] `BarChart.tsx` 생성 +- [ ] D3 μŠ€μΌ€μΌ μ„€μ • (x: λ²”μ£Όν˜•, y: μ„ ν˜•) +- [ ] λ§‰λŒ€ λ Œλ”λ§ (rect μš”μ†Œ) +- [ ] μΆ• λ Œλ”λ§ (d3-axis) +- [ ] 툴팁 κ΅¬ν˜„ +- [ ] μ• λ‹ˆλ©”μ΄μ…˜ (높이 μ „ν™˜) +- [ ] μˆ˜ν‰/수직 λͺ¨λ“œ 지원 +- [ ] 닀쀑 μ‹œλ¦¬μ¦ˆ (κ·Έλ£Ήν™”) + +#### Step 4.3: μ„  차트 + +- [ ] `LineChart.tsx` 생성 +- [ ] D3 라인 μ œλ„ˆλ ˆμ΄ν„° (d3.line) +- [ ] λΆ€λ“œλŸ¬μš΄ 곑선 (d3.curveMonotoneX) +- [ ] 데이터 포인트 ν‘œμ‹œ (circle) +- [ ] 툴팁 κ΅¬ν˜„ +- [ ] μ• λ‹ˆλ©”μ΄μ…˜ (path 길이 μ „ν™˜) +- [ ] 닀쀑 μ‹œλ¦¬μ¦ˆ (μ—¬λŸ¬ μ„ ) +- [ ] λˆ„λ½ 데이터 처리 + +#### Step 4.4: μ˜μ—­ 차트 + +- [ ] `AreaChart.tsx` 생성 +- [ ] D3 μ˜μ—­ μ œλ„ˆλ ˆμ΄ν„° (d3.area) +- [ ] 투λͺ…도 μ„€μ • +- [ ] λˆ„μ  λͺ¨λ“œ 지원 (d3.stack) +- [ ] μ„  차트 κΈ°λŠ₯ μž¬μ‚¬μš© +- [ ] μ• λ‹ˆλ©”μ΄μ…˜ + +#### Step 4.5: 원 차트 + +- [ ] `PieChart.tsx` 생성 +- [ ] D3 파이 λ ˆμ΄μ•„μ›ƒ (d3.pie) +- [ ] 아크 μ œλ„ˆλ ˆμ΄ν„° (d3.arc) +- [ ] 도넛 λͺ¨λ“œ (innerRadius) +- [ ] 라벨 배치 (쀑심 λ˜λŠ” μ™ΈλΆ€) +- [ ] 툴팁 κ΅¬ν˜„ +- [ ] μ• λ‹ˆλ©”μ΄μ…˜ (νšŒμ „ μ „ν™˜) +- [ ] νΌμ„ΌνŠΈ ν‘œμ‹œ + +#### Step 4.6: λˆ„μ  λ§‰λŒ€ 차트 + +- [ ] `StackedBarChart.tsx` 생성 +- [ ] D3 μŠ€νƒ λ ˆμ΄μ•„μ›ƒ (d3.stack) +- [ ] 닀쀑 μ‹œλ¦¬μ¦ˆ λˆ„μ  +- [ ] 일반 λˆ„μ  vs νΌμ„ΌνŠΈ λͺ¨λ“œ +- [ ] λ§‰λŒ€ 차트 둜직 μž¬μ‚¬μš© +- [ ] λ²”λ‘€ 색상 λ§€ν•‘ + +#### Step 4.7: ν˜Όν•© 차트 + +- [ ] `ComboChart.tsx` 생성 +- [ ] λ§‰λŒ€ + μ„  μ‘°ν•© +- [ ] 이쀑 YμΆ• (쒌츑: λ§‰λŒ€, 우츑: μ„ ) +- [ ] μŠ€μΌ€μΌ 독립 μ„€μ • +- [ ] λ§‰λŒ€/μ„  차트 둜직 κ²°ν•© +- [ ] λ³΅μž‘ν•œ 툴팁 (두 데이터 ν‘œμ‹œ) + +#### Step 4.8: 차트 μœ ν‹Έλ¦¬ν‹° + +- [ ] `chartUtils.ts` 생성 +- [ ] 데이터 λ³€ν™˜ ν•¨μˆ˜ (QueryResult β†’ ChartData) +- [ ] λ‚ μ§œ νŒŒμ‹± 및 ν¬λ§·νŒ… +- [ ] 숫자 ν¬λ§·νŒ… (천 λ‹¨μœ„ 콀마, μ†Œμˆ˜μ ) +- [ ] 색상 νŒ”λ ˆνŠΈ μ •μ˜ +- [ ] λ°˜μ‘ν˜• 크기 계산 + +#### Step 4.9: D3 헬퍼 + +- [ ] `d3Helpers.ts` 생성 +- [ ] 곡톡 μŠ€μΌ€μΌ 생성 +- [ ] μΆ• 생성 및 μŠ€νƒ€μΌλ§ +- [ ] κ·Έλ¦¬λ“œ 라인 μΆ”κ°€ +- [ ] 툴팁 DOM 생성/제거 +- [ ] SVG λ§ˆμ§„ 계산 + +### Phase 5: 차트 톡합 및 λ Œλ”λ§ (2-3μ‹œκ°„) + +#### Step 5.1: CanvasElement 톡합 + +- [ ] `CanvasElement.tsx` μˆ˜μ • +- [ ] 차트 μš”μ†Œ 감지 (element.type === 'chart') +- [ ] `ChartRenderer` μ»΄ν¬λ„ŒνŠΈ μž„ν¬νŠΈ 및 λ Œλ”λ§ +- [ ] 데이터 λ‘œλ”© μƒνƒœ ν‘œμ‹œ +- [ ] μ—λŸ¬ μƒνƒœ ν‘œμ‹œ +- [ ] μžλ™ μƒˆλ‘œκ³ μΉ¨ 둜직 + +#### Step 5.2: 데이터 페칭 + +- [ ] 차트 마운트 μ‹œ 초기 데이터 λ‘œλ“œ +- [ ] μžλ™ μƒˆλ‘œκ³ μΉ¨ 타이머 μ„€μ • +- [ ] μˆ˜λ™ μƒˆλ‘œκ³ μΉ¨ λ²„νŠΌ +- [ ] λ‘œλ”©/μ—λŸ¬/성곡 μƒνƒœ 관리 +- [ ] 캐싱 (선택적) + +#### Step 5.3: ElementConfigModal λ¦¬νŒ©ν† λ§ + +- [ ] 데이터 μ†ŒμŠ€ 선택 UI 톡합 +- [ ] 3단계 ν”Œλ‘œμš° κ΅¬ν˜„ + 1. 데이터 μ†ŒμŠ€ 선택 및 μ„€μ • + 2. 데이터 κ°€μ Έμ˜€κΈ° 및 검증 + 3. μΆ• λ§€ν•‘ 및 μŠ€νƒ€μΌ μ„€μ • +- [ ] μ§„ν–‰ ν‘œμ‹œκΈ° (μŠ€ν… 인디케이터) +- [ ] λ’€λ‘œ/λ‹€μŒ λ²„νŠΌ + +### Phase 6: ν…ŒμŠ€νŠΈ 및 μ΅œμ ν™” (2-3μ‹œκ°„) + +#### Step 6.1: κΈ°λŠ₯ ν…ŒμŠ€νŠΈ + +- [ ] 각 차트 νƒ€μž… λ Œλ”λ§ 확인 +- [ ] DB 쿼리 μ‹€ν–‰ 및 차트 생성 +- [ ] API 호좜 및 차트 생성 +- [ ] 닀쀑 μ‹œλ¦¬μ¦ˆ 차트 확인 +- [ ] μžλ™ μƒˆλ‘œκ³ μΉ¨ λ™μž‘ 확인 +- [ ] μ—λŸ¬ 처리 확인 + +#### Step 6.2: UI/UX κ°œμ„  + +- [ ] λ‘œλ”© μŠ€ν”Όλ„ˆ μΆ”κ°€ +- [ ] 빈 데이터 μƒνƒœ UI +- [ ] μ—λŸ¬ λ©”μ‹œμ§€ κ°œμ„  +- [ ] 툴팁 μŠ€νƒ€μΌλ§ +- [ ] λ²”λ‘€ μŠ€νƒ€μΌλ§ +- [ ] λ°˜μ‘ν˜• λ ˆμ΄μ•„μ›ƒ 확인 + +#### Step 6.3: μ„±λŠ₯ μ΅œμ ν™” + +- [ ] D3 λ Œλ”λ§ μ΅œμ ν™” (λΆˆν•„μš”ν•œ μž¬λ Œλ”λ§ λ°©μ§€) +- [ ] λŒ€μš©λŸ‰ 데이터 처리 (μƒ˜ν”Œλ§, νŽ˜μ΄μ§•) +- [ ] λ©”λͺ¨μ΄μ œμ΄μ…˜ (useMemo, useCallback) +- [ ] SVG μ΅œμ ν™” +- [ ] 차트 데이터 캐싱 + +--- + +## πŸ”’ λ³΄μ•ˆ 고렀사항 + +### SQL Injection λ°©μ§€ + +- μ„œλ²„ μΈ‘μ—μ„œ 쿼리 νƒ€μž… 엄격 검증 (SELECT만 ν—ˆμš©) +- μ •κ·œμ‹ + SQL νŒŒμ„œ μ‚¬μš© +- Prepared Statement μ‚¬μš© (νŒŒλΌλ―Έν„° 바인딩) +- μœ„ν—˜ν•œ ν‚€μ›Œλ“œ 차단 (DROP, DELETE, UPDATE, INSERT, EXEC λ“±) + +### μ™ΈλΆ€ DB 컀λ„₯μ…˜ λ³΄μ•ˆ + +- κΈ°μ‘΄ "μ™ΈλΆ€ 컀λ„₯μ…˜ 관리"μ—μ„œ λ³΄μ•ˆ 처리됨 +- 차트 μ‹œμŠ€ν…œμ—μ„œλŠ” 컀λ„₯μ…˜ ID만 μ‚¬μš© +- 민감 정보(λΉ„λ°€λ²ˆν˜Έ, 호슀트 λ“±)λŠ” 차트 섀정에 λ…ΈμΆœν•˜μ§€ μ•ŠμŒ +- νƒ€μž„μ•„μ›ƒ μ„€μ • (30초) + +### API λ³΄μ•ˆ + +- CORS μ •μ±… 확인 +- λ―Όκ°ν•œ 헀더 λ‘œκΉ… λ°©μ§€ (Authorization λ“±) +- μš”μ²­ 크기 μ œν•œ +- Rate Limiting (API 호좜 λΉˆλ„ μ œν•œ) + +--- + +## 🎨 UI/UX κ°œμ„  사항 + +### μ„€μ • ν”Œλ‘œμš° + +1. **데이터 μ†ŒμŠ€ 선택** + - 큰 μ•„μ΄μ½˜κ³Ό μ„€λͺ…μœΌλ‘œ DB vs API 선택 + - 각 λ°©μ‹μ˜ μž₯단점 μ•ˆλ‚΄ + +2. **데이터 ꡬ성** + - DB: SQL 에디터 + μ‹€ν–‰ λ²„νŠΌ + - API: URL, λ©”μ„œλ“œ, 헀더, λ³Έλ¬Έ μž…λ ₯ + - ν…ŒμŠ€νŠΈ λ²„νŠΌμœΌλ‘œ μ¦‰μ‹œ 확인 + +3. **데이터 미리보기** + - 쿼리/API μ‹€ν–‰ κ²°κ³Όλ₯Ό ν…Œμ΄λΈ”λ‘œ ν‘œμ‹œ (μ΅œλŒ€ 10ν–‰) + - 컬럼λͺ…κ³Ό μƒ˜ν”Œ 데이터 ν‘œμ‹œ + +4. **차트 μ„€μ •** + - X/YμΆ• λ“œλž˜κ·Έ μ•€ λ“œλ‘­ λ§€ν•‘ + - μ‹€μ‹œκ°„ 미리보기 (μž‘μ€ 차트) + - μŠ€νƒ€μΌ 프리셋 선택 + +### ν”Όλ“œλ°± λ©”μ‹œμ§€ + +- βœ… 성곡: "데이터λ₯Ό μ„±κ³΅μ μœΌλ‘œ λΆˆλŸ¬μ™”μŠ΅λ‹ˆλ‹€ (45ν–‰)" +- ⚠️ κ²½κ³ : "쿼리 싀행이 였래 걸리고 μžˆμŠ΅λ‹ˆλ‹€" +- ❌ 였λ₯˜: "λ°μ΄ν„°λ² μ΄μŠ€ 연결에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€: 잘λͺ»λœ λΉ„λ°€λ²ˆν˜Έ" + +### λ‘œλ”© μƒνƒœ + +- μŠ€μΌˆλ ˆν†€ UI (차트 윀곽) +- μ§„ν–‰λ₯  ν‘œμ‹œ (λŒ€μš©λŸ‰ 데이터) +- μ·¨μ†Œ λ²„νŠΌ (μž₯μ‹œκ°„ μ‹€ν–‰ 쿼리) + +--- + +## πŸ“Š μƒ˜ν”Œ 데이터 및 μ‹œλ‚˜λ¦¬μ˜€ + +### μ‹œλ‚˜λ¦¬μ˜€ 1: 월별 맀좜 좔이 (DB 쿼리) + +```sql +SELECT + TO_CHAR(order_date, 'YYYY-MM') as month, + SUM(total_amount) as sales +FROM orders +WHERE order_date >= CURRENT_DATE - INTERVAL '12 months' +GROUP BY TO_CHAR(order_date, 'YYYY-MM') +ORDER BY month; +``` + +- **차트 νƒ€μž…**: Line Chart +- **XμΆ•**: month +- **YμΆ•**: sales + +### μ‹œλ‚˜λ¦¬μ˜€ 2: μ œν’ˆ 비ꡐ (닀쀑 μ‹œλ¦¬μ¦ˆ) + +```sql +SELECT + DATE_TRUNC('month', order_date) as month, + SUM(CASE WHEN product_category = 'κ°€λŸ­μ‹œ' THEN amount ELSE 0 END) as galaxy, + SUM(CASE WHEN product_category = '아이폰' THEN amount ELSE 0 END) as iphone +FROM orders +WHERE order_date >= CURRENT_DATE - INTERVAL '12 months' +GROUP BY DATE_TRUNC('month', order_date) +ORDER BY month; +``` + +- **차트 νƒ€μž…**: Combo Chart (Bar + Line) +- **XμΆ•**: month +- **YμΆ•**: [galaxy, iphone] (닀쀑) + +### μ‹œλ‚˜λ¦¬μ˜€ 3: μΉ΄ν…Œκ³ λ¦¬λ³„ 맀좜 (원 차트) + +```sql +SELECT + category, + SUM(amount) as total +FROM sales +WHERE sale_date >= CURRENT_DATE - INTERVAL '1 month' +GROUP BY category +ORDER BY total DESC +LIMIT 10; +``` + +- **차트 νƒ€μž…**: Pie Chart (Donut) +- **XμΆ•**: category +- **YμΆ•**: total + +### μ‹œλ‚˜λ¦¬μ˜€ 4: REST API (μ‹€μ‹œκ°„ ν™˜μœ¨) + +- **API**: `https://api.exchangerate-api.com/v4/latest/USD` +- **JSON Path**: `rates` +- **λ³€ν™˜**: Objectλ₯Ό λ°°μ—΄λ‘œ λ³€ν™˜ (톡화: ν™˜μœ¨) +- **차트 νƒ€μž…**: Bar Chart +- **XμΆ•**: 톡화 μ½”λ“œ (KRW, JPY, EUR λ“±) +- **YμΆ•**: ν™˜μœ¨ + +--- + +## βœ… μ™„λ£Œ κΈ°μ€€ + +### Phase 1: 데이터 μ†ŒμŠ€ μ„€μ • + +- [x] DB 컀λ„₯μ…˜ μ„€μ • UI μž‘λ™ +- [x] μ™ΈλΆ€ DB 컀λ„₯μ…˜ μ €μž₯ 및 뢈러였기 +- [x] API μ„€μ • UI μž‘λ™ +- [x] ν…ŒμŠ€νŠΈ λ²„νŠΌμœΌλ‘œ μ¦‰μ‹œ 확인 κ°€λŠ₯ + +### Phase 2: μ„œλ²„ API + +- [x] μ™ΈλΆ€ DB 컀λ„₯μ…˜ CRUD API μž‘λ™ +- [x] 쿼리 μ‹€ν–‰ API (ν˜„μž¬/μ™ΈλΆ€ DB) +- [x] SELECT 쿼리 검증 및 SQL Injection λ°©μ§€ +- [x] API ν”„λ‘μ‹œ μž‘λ™ + +### Phase 3: 차트 μ„€μ • UI + +- [x] μΆ• λ§€ν•‘ UI 직관적 +- [x] 닀쀑 YμΆ• 선택 κ°€λŠ₯ +- [x] μŠ€νƒ€μΌ μ„€μ • UI μž‘λ™ +- [x] μ‹€μ‹œκ°„ 미리보기 ν‘œμ‹œ + +### Phase 4: D3 차트 + +- [x] 6κ°€μ§€ 차트 νƒ€μž… λͺ¨λ‘ λ Œλ”λ§ +- [x] 툴팁 ν‘œμ‹œ +- [x] μ• λ‹ˆλ©”μ΄μ…˜ λΆ€λ“œλŸ¬μ›€ +- [x] λ°˜μ‘ν˜• 크기 쑰절 +- [x] 닀쀑 μ‹œλ¦¬μ¦ˆ 지원 + +### Phase 5: 톡합 + +- [x] μΊ”λ²„μŠ€μ—μ„œ 차트 ν‘œμ‹œ +- [x] μžλ™ μƒˆλ‘œκ³ μΉ¨ μž‘λ™ +- [x] μ„€μ • λͺ¨λ‹¬ 3단계 ν”Œλ‘œμš° μ™„λ£Œ +- [x] 데이터 λ‘œλ”©/μ—λŸ¬ μƒνƒœ ν‘œμ‹œ + +### Phase 6: ν…ŒμŠ€νŠΈ + +- [x] λͺ¨λ“  차트 νƒ€μž… 정상 μž‘λ™ +- [x] DB/API 데이터 μ†ŒμŠ€ λͺ¨λ‘ μž‘λ™ +- [x] μ—λŸ¬ 처리 적절 +- [x] μ„±λŠ₯ 이슈 μ—†μŒ (1000ν–‰ 데이터) + +--- + +## πŸš€ ν–₯ν›„ ν™•μž₯ κ³„νš + +- **μ‹€μ‹œκ°„ 슀트리밍**: WebSocket 데이터 μ†ŒμŠ€ μΆ”κ°€ +- **κ³ κΈ‰ 차트**: Scatter Plot, Heatmap, Radar Chart +- **데이터 λ³€ν™˜**: 필터링, μ •λ ¬, 계산 ν•„λ“œ μΆ”κ°€ +- **차트 μƒν˜Έμž‘μš©**: 클릭/λ“œλž˜κ·Έλ‘œ 데이터 필터링 +- **내보내기**: PNG, SVG, PDF μ €μž₯ +- **ν…œν”Œλ¦Ώ**: 사전 μ •μ˜λœ 차트 ν…œν”Œλ¦Ώ (업쒅별) + +--- + +## πŸ“… μ˜ˆμƒ 일정 + +- **Phase 1**: 1일 (데이터 μ†ŒμŠ€ UI) +- **Phase 2**: 0.5일 (μ„œλ²„ API) - κΈ°μ‘΄ μ™ΈλΆ€ 컀λ„₯μ…˜ 관리 ν™œμš©μœΌλ‘œ 단좕 +- **Phase 3**: 1일 (차트 μ„€μ • UI) +- **Phase 4**: 2일 (D3 차트 μ»΄ν¬λ„ŒνŠΈ) +- **Phase 5**: 0.5일 (톡합) +- **Phase 6**: 0.5일 (ν…ŒμŠ€νŠΈ) + +**총 μ˜ˆμƒ μ‹œκ°„**: 5.5일 (44μ‹œκ°„) + +--- + +**κ΅¬ν˜„ μ‹œμž‘μΌ**: 2025-10-14 +**λͺ©ν‘œ μ™„λ£ŒμΌ**: 2025-10-20 +**ν˜„μž¬ μ§„ν–‰λ₯ **: 0% (κ³„νš 수립 μ™„λ£Œ) + +--- + +## 🎯 λ‹€μŒ 단계 + +1. Phase 1 μ‹œμž‘: `DataSourceSelector.tsx` 생성 +2. νƒ€μž… μ •μ˜ ν™•μž₯: `types.ts` μ—…λ°μ΄νŠΈ +3. μ„œλ²„ API μ—”λ“œν¬μΈνŠΈ 섀계 및 κ΅¬ν˜„ +4. D3.js 라이브러리 μ„€μΉ˜ 및 κΈ°λ³Έ 차트 PoC