import { QueryResult, ChartConfig, ChartData, ChartDataset } from "../types"; /** * 쿼리 결과를 차트 데이터로 변환 */ export function transformQueryResultToChartData(queryResult: QueryResult, config: ChartConfig): ChartData | null { if (!queryResult || !queryResult.rows.length || !config.xAxis) { return null; } let rows = queryResult.rows; // 그룹핑 처리 if (config.groupBy && config.groupBy !== "__none__") { rows = applyGrouping(rows, config.groupBy, config.aggregation, config.yAxis); } // X축 라벨 추출 const labels = rows.map((row) => String(row[config.xAxis!] || "")); // Y축 데이터 추출 const yAxisFields = Array.isArray(config.yAxis) ? config.yAxis : config.yAxis ? [config.yAxis] : []; // 집계 함수가 COUNT이고 Y축이 없으면 자동으로 count 필드 추가 if (config.aggregation === "count" && yAxisFields.length === 0) { const datasets: ChartDataset[] = [ { label: "개수", data: rows.map((row) => { const value = row["count"]; return typeof value === "number" ? value : parseFloat(String(value)) || 0; }), color: config.colors?.[0], }, ]; return { labels, datasets, }; } if (yAxisFields.length === 0) { return null; } // 각 Y축 필드에 대해 데이터셋 생성 const datasets: ChartDataset[] = yAxisFields.map((field, index) => { const data = rows.map((row) => { const value = row[field]; return typeof value === "number" ? value : parseFloat(String(value)) || 0; }); return { label: field, data, color: config.colors?.[index], }; }); return { labels, datasets, }; } /** * 그룹핑 및 집계 처리 */ function applyGrouping( rows: Record[], groupByField: string, aggregation?: "sum" | "avg" | "count" | "max" | "min", yAxis?: string | string[], ): Record[] { // 그룹별로 데이터 묶기 const groups = new Map[]>(); rows.forEach((row) => { const key = String(row[groupByField] || ""); if (!groups.has(key)) { groups.set(key, []); } groups.get(key)!.push(row); }); // 각 그룹에 대해 집계 수행 const aggregatedRows: Record[] = []; groups.forEach((groupRows, key) => { const aggregatedRow: Record = { [groupByField]: key, }; // Y축 필드에 대해 집계 const yAxisFields = Array.isArray(yAxis) ? yAxis : yAxis ? [yAxis] : []; if (aggregation === "count") { // COUNT: 그룹의 행 개수 aggregatedRow["count"] = groupRows.length; } else if (yAxisFields.length > 0) { yAxisFields.forEach((field) => { const values = groupRows.map((row) => { const value = row[field]; return typeof value === "number" ? value : parseFloat(String(value)) || 0; }); switch (aggregation) { case "sum": aggregatedRow[field] = values.reduce((a, b) => a + b, 0); break; case "avg": aggregatedRow[field] = values.reduce((a, b) => a + b, 0) / values.length; break; case "max": aggregatedRow[field] = Math.max(...values); break; case "min": aggregatedRow[field] = Math.min(...values); break; default: // 집계 없으면 첫 번째 값 사용 aggregatedRow[field] = values[0]; } }); } aggregatedRows.push(aggregatedRow); }); return aggregatedRows; } /** * API 응답을 차트 데이터로 변환 */ export function transformApiResponseToChartData( apiData: Record[], config: ChartConfig, ): ChartData | null { // API 응답을 QueryResult 형식으로 변환 if (!apiData || apiData.length === 0 || !config.xAxis) { return null; } const queryResult: QueryResult = { columns: Object.keys(apiData[0]), rows: apiData, totalRows: apiData.length, executionTime: 0, }; return transformQueryResultToChartData(queryResult, config); }