'use client'; import React from 'react'; import { DashboardElement, QueryResult } from '../types'; import { BarChartComponent } from './BarChartComponent'; import { PieChartComponent } from './PieChartComponent'; import { LineChartComponent } from './LineChartComponent'; interface ChartRendererProps { element: DashboardElement; data?: QueryResult; width?: number; height?: number; } /** * 차트 렌더러 컴포넌트 * - 차트 타입에 따라 적절한 차트 컴포넌트를 렌더링 * - 데이터 변환 및 에러 처리 */ export function ChartRenderer({ element, data, width = 250, height = 200 }: ChartRendererProps) { // 데이터가 없거나 설정이 불완전한 경우 if (!data || !element.chartConfig?.xAxis || !element.chartConfig?.yAxis) { return (
📊
데이터를 설정해주세요
⚙️ 버튼을 클릭하여 설정
); } // 데이터 변환 const chartData = transformData(data, element.chartConfig); // 에러가 있는 경우 if (chartData.length === 0) { return (
⚠️
데이터를 불러올 수 없습니다
); } // 차트 공통 props const chartProps = { data: chartData, config: element.chartConfig, width: width - 20, // 패딩 고려 height: height - 60, // 헤더 높이 고려 }; // 차트 타입에 따른 렌더링 switch (element.subtype) { case 'bar': return ; case 'pie': return ; case 'line': return ; default: return (
지원하지 않는 차트 타입
); } } /** * 쿼리 결과를 차트 데이터로 변환 */ function transformData(queryResult: QueryResult, config: any) { try { const { xAxis, yAxis, groupBy, aggregation = 'sum' } = config; if (!queryResult.rows || queryResult.rows.length === 0) { return []; } // 그룹핑이 있는 경우 if (groupBy && groupBy !== xAxis) { const grouped = queryResult.rows.reduce((acc, row) => { const xValue = String(row[xAxis] || ''); const groupValue = String(row[groupBy] || ''); const yValue = Number(row[yAxis]) || 0; if (!acc[xValue]) { acc[xValue] = { [xAxis]: xValue }; } if (!acc[xValue][groupValue]) { acc[xValue][groupValue] = 0; } // 집계 함수 적용 switch (aggregation) { case 'sum': acc[xValue][groupValue] += yValue; break; case 'avg': // 평균 계산을 위해 임시로 배열 저장 if (!acc[xValue][`${groupValue}_values`]) { acc[xValue][`${groupValue}_values`] = []; } acc[xValue][`${groupValue}_values`].push(yValue); break; case 'count': acc[xValue][groupValue] += 1; break; case 'max': acc[xValue][groupValue] = Math.max(acc[xValue][groupValue], yValue); break; case 'min': acc[xValue][groupValue] = Math.min(acc[xValue][groupValue], yValue); break; } return acc; }, {} as any); // 평균 계산 후처리 if (aggregation === 'avg') { Object.keys(grouped).forEach(xValue => { Object.keys(grouped[xValue]).forEach(key => { if (key.endsWith('_values')) { const baseKey = key.replace('_values', ''); const values = grouped[xValue][key]; grouped[xValue][baseKey] = values.reduce((sum: number, val: number) => sum + val, 0) / values.length; delete grouped[xValue][key]; } }); }); } return Object.values(grouped); } // 단순 변환 (그룹핑 없음) const dataMap = new Map(); queryResult.rows.forEach(row => { const xValue = String(row[xAxis] || ''); const yValue = Number(row[yAxis]) || 0; if (!dataMap.has(xValue)) { dataMap.set(xValue, { [xAxis]: xValue, [yAxis]: 0, count: 0 }); } const existing = dataMap.get(xValue); switch (aggregation) { case 'sum': existing[yAxis] += yValue; break; case 'avg': existing[yAxis] += yValue; existing.count += 1; break; case 'count': existing[yAxis] += 1; break; case 'max': existing[yAxis] = Math.max(existing[yAxis], yValue); break; case 'min': existing[yAxis] = existing[yAxis] === 0 ? yValue : Math.min(existing[yAxis], yValue); break; } }); // 평균 계산 후처리 if (aggregation === 'avg') { dataMap.forEach(item => { if (item.count > 0) { item[yAxis] = item[yAxis] / item.count; } delete item.count; }); } return Array.from(dataMap.values()).slice(0, 50); // 최대 50개 데이터포인트 } catch (error) { console.error('데이터 변환 오류:', error); return []; } }