ERP-node/frontend/components/admin/dashboard/utils/chartDataTransform.ts

155 lines
4.1 KiB
TypeScript

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<string, any>[],
groupByField: string,
aggregation?: "sum" | "avg" | "count" | "max" | "min",
yAxis?: string | string[],
): Record<string, any>[] {
// 그룹별로 데이터 묶기
const groups = new Map<string, Record<string, any>[]>();
rows.forEach((row) => {
const key = String(row[groupByField] || "");
if (!groups.has(key)) {
groups.set(key, []);
}
groups.get(key)!.push(row);
});
// 각 그룹에 대해 집계 수행
const aggregatedRows: Record<string, any>[] = [];
groups.forEach((groupRows, key) => {
const aggregatedRow: Record<string, any> = {
[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<string, unknown>[],
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);
}