# v10: 차트 시각화 3 - 고급 KPI 위젯 ## 이번 단계에서 할 일 1. 스파크라인: 셀 내 미니 차트 2. 게이지 차트: 목표 달성률 3. 히트맵: 월별 지출 강도 4. 스코어카드: KPI 요약 박스 5. 진행바: 예산 소진율 ## 1. 위젯 개요 ### 생성할 KPI 위젯 | 위젯 | 구현 방식 | 용도 | |------|----------|------| | 스파크라인 | SPARKLINE 함수 | 셀 내 미니 추이 그래프 | | 게이지 | 텍스트 + 이모지 | 목표 대비 달성률 | | 히트맵 | 조건부 서식 (Gradient) | 값 크기별 색상 표현 | | 스코어카드 | 셀 레이아웃 + 이모지 | 핵심 KPI 하이라이트 | | 진행바 | REPT 함수 | 예산 소진율 시각화 | ### 위젯 배치 레이아웃 ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ │ │ AX1:BC10 📊 KPI 스코어카드 │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 💰 총 수입 ¥5,343,495 💸 총 지출 ¥4,224,666 │ │ │ │ 📈 순 저축 ¥1,118,829 📊 저축률 20.9% ✅ │ │ │ │ 🎯 지출 비율 79.1% 📆 월평균 저축 ¥93,236 │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ AX12:BC22 📊 예산 진행바 │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 카테고리 │ 실제 │ 예산 │ 달성률 │ 진행바 │ 상태 │ │ │ │ 식비 │ 77,000 │ 80,000 │ 96% │ ████████░░ │ ✅ │ │ │ │ 주거비 │ 93,000 │ 95,000 │ 98% │ █████████░ │ ✅ │ │ │ │ 쇼핑 │ 34,333 │ 30,000 │ 114% │ ██████████████ │ ⚠️ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ AX26:BL30 ⚡ 스파크라인 │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 지출 추이 │ ▁▂▃▅▃▂▁▂▃▅▇▅ (라인) │ │ │ │ 수입 추이 │ ▁▁▁▁▁▇▁▁▁▁▁▇ (라인 - 보너스 월 급등) │ │ │ │ 저축 추이 │ ▂▄▃▅▃▇▂▃▄▅▇█ (컬럼) │ │ │ │ 저축률 추이 │ ▁▃▂▄▂█▁▂▃▄█▇ (라인) │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ AX32:BC38 🎯 게이지 인디케이터 │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ 🎯 저축 목표 달성률 │ │ │ │ 목표: 20% 현재: 20.9% │ │ │ │ ▰▰▰▰▰▰▰▰▰▰▱ 104% │ │ │ │ 🎉 목표 달성! │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ``` ## 2. 스파크라인 구현 ### SPARKLINE 함수 사용 Google Sheets의 `SPARKLINE` 함수로 셀 내에 미니 차트를 생성합니다. ```typescript /** * 스파크라인 생성 */ private async createSparklines( spreadsheetId: string, sheetName: string, summaries: MonthlySummary[] ): Promise { // 월별 데이터 const monthlyExpenses = summaries.map(s => s.totalExpense); const monthlyIncomes = summaries.map(s => s.totalIncome); const monthlySavings = summaries.map(s => s.netSavings); // 스파크라인 수식 const sparklineFormulas = [ ['지출 추이', `=SPARKLINE(BA26:BL26, {"charttype","line";"color","red";"linewidth",2})`], ['수입 추이', `=SPARKLINE(BA27:BL27, {"charttype","line";"color","green";"linewidth",2})`], ['저축 추이', `=SPARKLINE(BA28:BL28, {"charttype","column";"color","blue"})`], ['저축률 추이', `=SPARKLINE(BA29:BL29, {"charttype","line";"color","purple";"linewidth",2})`], ]; // 데이터 영역 작성 await this.client.writeSheet(spreadsheetId, `${sheetName}!BA26`, [ monthlyExpenses, monthlyIncomes, monthlySavings, summaries.map(s => (s.netSavings / s.totalIncome) * 100), ]); // 수식 작성 await this.client.writeSheet(spreadsheetId, `${sheetName}!AX26`, sparklineFormulas); } ``` ### SPARKLINE 함수 옵션 | 옵션 | 값 | 설명 | |------|-----|------| | `charttype` | "line", "column", "bar", "winloss" | 차트 타입 | | `color` | "red", "green", "#4285f4" | 색상 | | `linewidth` | 1, 2, 3 | 라인 두께 | | `negcolor` | "red" | 음수 값 색상 | | `ymin`, `ymax` | 숫자 | Y축 범위 | ### 스파크라인 예시 ``` =SPARKLINE(A1:L1, {"charttype","line";"color","blue";"linewidth",2}) 결과: ▁▂▃▅▆▇█▆▅▃▂▁ (셀 내 미니 라인 차트) ``` ## 3. 스코어카드 구현 ### KPI 요약 박스 ```typescript /** * 스코어카드 생성 */ private async createScorecard( spreadsheetId: string, sheetName: string, stats: { totalIncome: number; totalExpense: number; netSavings: number; savingsRate: number } ): Promise { const savingsRateEmoji = stats.savingsRate >= 20 ? '✅' : stats.savingsRate >= 10 ? '⚠️' : '❌'; const scorecard = [ ['📊 KPI 스코어카드', '', '', '', '', ''], ['', '', '', '', '', ''], ['💰 총 수입', `¥${stats.totalIncome.toLocaleString()}`, '', '💸 총 지출', `¥${stats.totalExpense.toLocaleString()}`, ''], ['', '', '', '', '', ''], ['📈 순 저축', `¥${stats.netSavings.toLocaleString()}`, '', '📊 저축률', `${stats.savingsRate.toFixed(1)}%`, savingsRateEmoji], ['', '', '', '', '', ''], ['🎯 지출 비율', `${((stats.totalExpense / stats.totalIncome) * 100).toFixed(1)}%`, '', '📆 월평균 저축', `¥${Math.round(stats.netSavings / 12).toLocaleString()}`, ''], ]; await this.client.writeSheet(spreadsheetId, `${sheetName}!AX1`, scorecard); } ``` ### 결과 예시 ``` ┌─────────────────────────────────────────────────────────┐ │ 📊 KPI 스코어카드 │ ├─────────────────────────────────────────────────────────┤ │ 💰 총 수입 ¥5,343,495 💸 총 지출 ¥4,224,666 │ │ 📈 순 저축 ¥1,118,829 📊 저축률 20.9% ✅ │ │ 🎯 지출 비율 79.1% 📆 월평균 ¥93,236 │ └─────────────────────────────────────────────────────────┘ ``` ## 4. 진행바 구현 ### REPT 함수로 시각적 진행바 생성 ```typescript /** * 진행바 생성 */ private async createProgressBars( spreadsheetId: string, sheetName: string, breakdown: CategoryBreakdown[] ): Promise { const header = ['카테고리', '실제', '예산', '달성률', '진행바', '상태']; const rows = breakdown.slice(0, 8).map(b => { const budget = DEFAULT_BUDGETS[b.category] || 0; const monthlyActual = Math.round(b.amount / 12); const percentage = budget > 0 ? (monthlyActual / budget) * 100 : 0; // REPT 함수로 진행바 생성 const barFormula = `=REPT("█", MIN(10, ROUND(${percentage}/10)))&REPT("░", MAX(0, 10-ROUND(${percentage}/10)))`; const status = percentage <= 100 ? '✅' : percentage <= 120 ? '⚠️' : '🔴'; return [b.category, monthlyActual, budget, `${percentage.toFixed(0)}%`, barFormula, status]; }); await this.client.writeSheet(spreadsheetId, `${sheetName}!AX12`, [header, ...rows]); } ``` ### REPT 함수 진행바 예시 ``` =REPT("█", 8) & REPT("░", 2) 결과: ████████░░ (80%) =REPT("█", 10) & REPT("░", 0) 결과: ██████████ (100%+) ``` ### 진행바 결과 ``` 카테고리 │ 실제 │ 예산 │ 달성률 │ 진행바 │ 상태 ─────────┼────────┼───────┼───────┼───────────────┼───── 식비 │ 77,000 │ 80,000 │ 96% │ █████████░ │ ✅ 주거비 │ 93,000 │ 95,000 │ 98% │ █████████░ │ ✅ 쇼핑 │ 34,333 │ 30,000 │ 114% │ ██████████████ │ ⚠️ 교통비 │ 27,000 │ 20,000 │ 135% │ ██████████████ │ 🔴 ``` ## 5. 히트맵 구현 ### 조건부 서식 그라디언트 규칙 ```typescript /** * 히트맵 조건부 서식 생성 */ private async createHeatmapFormat( spreadsheetId: string, sheetId: number ): Promise { const requests = [ { addConditionalFormatRule: { rule: { ranges: [{ sheetId, startRowIndex: 9, endRowIndex: 21, startColumnIndex: 2, // 지출 열 endColumnIndex: 3, }], gradientRule: { minpoint: { color: { red: 0.85, green: 0.95, blue: 0.85 }, // 녹색 (낮음) type: 'MIN', }, midpoint: { color: { red: 1, green: 1, blue: 0.7 }, // 노란색 (중간) type: 'PERCENTILE', value: '50', }, maxpoint: { color: { red: 1, green: 0.7, blue: 0.7 }, // 빨간색 (높음) type: 'MAX', }, }, }, index: 0, }, }, ]; await this.client.batchUpdate(spreadsheetId, requests); } ``` ### 히트맵 색상 스케일 | 값 | 색상 | 의미 | |----|------|------| | MIN | 녹색 | 지출 낮음 (좋음) | | 50% | 노란색 | 중간 | | MAX | 빨간색 | 지출 높음 (주의) | ## 6. 게이지 인디케이터 구현 ### 텍스트 기반 게이지 ```typescript /** * 게이지 스타일 인디케이터 */ private async createGaugeIndicator( spreadsheetId: string, sheetName: string, stats: { savingsRate: number } ): Promise { const targetRate = 20; // 목표 저축률 const achievementRate = (stats.savingsRate / targetRate) * 100; // 게이지 바 생성 const gaugeLevel = Math.round(Math.min(achievementRate, 150) / 15); const gaugeBar = '▰'.repeat(Math.min(gaugeLevel, 10)) + '▱'.repeat(Math.max(0, 10 - gaugeLevel)); const message = achievementRate >= 100 ? '🎉 목표 달성!' : achievementRate >= 80 ? '💪 거의 달성!' : '📈 노력 필요'; const gaugeData = [ ['🎯 저축 목표 달성률', ''], ['', ''], [`목표: ${targetRate}%`, `현재: ${stats.savingsRate.toFixed(1)}%`], [gaugeBar, `${achievementRate.toFixed(0)}%`], ['', ''], [message, ''], ]; await this.client.writeSheet(spreadsheetId, `${sheetName}!AX32`, gaugeData); } ``` ### 게이지 결과 예시 ``` 🎯 저축 목표 달성률 목표: 20% 현재: 20.9% ▰▰▰▰▰▰▰▰▰▰▱ 104% 🎉 목표 달성! ``` ## 7. 위젯 요약 정리 | 위젯 | 함수/방식 | 주요 특징 | |------|----------|----------| | 스파크라인 | `SPARKLINE()` | 셀 내 미니 차트 | | 스코어카드 | 셀 + 이모지 | KPI 강조 표시 | | 진행바 | `REPT()` | 텍스트 기반 바 | | 히트맵 | 조건부 서식 | 그라디언트 색상 | | 게이지 | `▰▱` 문자 | 달성률 시각화 | ## 현재 프로젝트 구조 (완성) ``` project/src/ ├── sheets/ │ ├── client.ts ✅ Google Sheets API 클라이언트 │ ├── reader.ts ✅ 데이터 읽기 │ ├── writer.ts ✅ 데이터 쓰기 │ └── template.ts ✅ 시트 템플릿 ├── analysis/ │ ├── categories.ts ✅ 카테고리 설정 │ ├── calculator.ts ✅ 재무 계산 │ ├── aggregator.ts ✅ 데이터 집계 │ └── validator.ts ✅ 데이터 검증 ├── dashboard/ │ ├── creator.ts ✅ 대시보드 생성 │ └── formatter.ts ✅ 서식 관리 ├── charts/ │ ├── trend-charts.ts ✅ v8: 트렌드 차트 │ ├── ratio-charts.ts ✅ v9: 비율 차트 │ └── kpi-widgets.ts ✅ v10: KPI 위젯 ├── vertexai/ │ ├── client.ts ✅ Vertex AI 클라이언트 │ └── analyzer.ts ✅ AI 분석 ├── types/ │ └── index.ts ✅ 타입 정의 └── index.ts (v12에서 완성) ``` ## 차트 시각화 시리즈 완료! | 버전 | 차트 타입 | 개수 | |------|----------|------| | v8 | 트렌드 차트 | 4종 | | v9 | 비율 차트 | 4종 | | v10 | KPI 위젯 | 6종 | | **합계** | | **14종** | ## 다음 단계 v11에서는: - AI 인사이트를 시트에 기록 - 주요 발견사항 하이라이트 - 개선 제안 섹션 - 월별 목표 설정 v12에서는: - CLI 도구 완성 - 파라미터 처리 - 전체 프로젝트 마무리 --- **작성일**: 2025-12-01 **상태**: ✅ 완료 **다음**: v11 - AI 인사이트 섹션 추가