diff --git a/frontend/components/admin/dashboard/charts/AreaChart.tsx b/frontend/components/admin/dashboard/charts/AreaChart.tsx index 9304d406..cabc8f10 100644 --- a/frontend/components/admin/dashboard/charts/AreaChart.tsx +++ b/frontend/components/admin/dashboard/charts/AreaChart.tsx @@ -23,7 +23,7 @@ export function AreaChart({ data, config, width = 600, height = 400 }: AreaChart const svg = d3.select(svgRef.current); svg.selectAll("*").remove(); - const margin = { top: 40, right: 80, bottom: 60, left: 60 }; + const margin = { top: 40, right: 80, bottom: 80, left: 60 }; const chartWidth = width - margin.left - margin.right; const chartHeight = height - margin.top - margin.bottom; @@ -221,17 +221,23 @@ export function AreaChart({ data, config, width = 600, height = 400 }: AreaChart .text(config.yAxisLabel); } - // 범례 - if (config.showLegend !== false && data.datasets.length > 1) { + // 범례 (차트 하단 중앙) + if (config.showLegend !== false && data.datasets.length > 0) { + const legendItemWidth = 120; + const totalLegendWidth = data.datasets.length * legendItemWidth; + const legendStartX = (width - totalLegendWidth) / 2; + const legend = svg .append("g") .attr("class", "legend") - .attr("transform", `translate(${width - margin.right + 10}, ${margin.top})`); + .attr("transform", `translate(${legendStartX}, ${height - 20})`); data.datasets.forEach((dataset, i) => { - const legendRow = legend.append("g").attr("transform", `translate(0, ${i * 25})`); + const legendItem = legend + .append("g") + .attr("transform", `translate(${i * legendItemWidth}, 0)`); - legendRow + legendItem .append("rect") .attr("width", 15) .attr("height", 15) @@ -239,7 +245,7 @@ export function AreaChart({ data, config, width = 600, height = 400 }: AreaChart .attr("opacity", config.areaOpacity !== undefined ? config.areaOpacity : 0.3) .attr("rx", 3); - legendRow + legendItem .append("text") .attr("x", 20) .attr("y", 12) diff --git a/frontend/components/admin/dashboard/charts/BarChart.tsx b/frontend/components/admin/dashboard/charts/BarChart.tsx index 3f619d05..4026802d 100644 --- a/frontend/components/admin/dashboard/charts/BarChart.tsx +++ b/frontend/components/admin/dashboard/charts/BarChart.tsx @@ -23,7 +23,7 @@ export function BarChart({ data, config, width = 600, height = 400 }: BarChartPr const svg = d3.select(svgRef.current); svg.selectAll("*").remove(); - const margin = { top: 40, right: 80, bottom: 60, left: 60 }; + const margin = { top: 40, right: 80, bottom: 80, left: 60 }; const chartWidth = width - margin.left - margin.right; const chartHeight = height - margin.top - margin.bottom; @@ -196,24 +196,30 @@ export function BarChart({ data, config, width = 600, height = 400 }: BarChartPr .text(config.yAxisLabel); } - // 범례 - if (config.showLegend !== false && data.datasets.length > 1) { + // 범례 (차트 하단 중앙) + if (config.showLegend !== false && data.datasets.length > 0) { + const legendItemWidth = 120; // 각 범례 항목의 너비 + const totalLegendWidth = data.datasets.length * legendItemWidth; + const legendStartX = (width - totalLegendWidth) / 2; // 중앙 정렬 + const legend = svg .append("g") .attr("class", "legend") - .attr("transform", `translate(${width - margin.right + 10}, ${margin.top})`); + .attr("transform", `translate(${legendStartX}, ${height - 20})`); data.datasets.forEach((dataset, i) => { - const legendRow = legend.append("g").attr("transform", `translate(0, ${i * 25})`); + const legendItem = legend + .append("g") + .attr("transform", `translate(${i * legendItemWidth}, 0)`); - legendRow + legendItem .append("rect") .attr("width", 15) .attr("height", 15) .attr("fill", dataset.color || colors[i % colors.length]) .attr("rx", 3); - legendRow + legendItem .append("text") .attr("x", 20) .attr("y", 12) diff --git a/frontend/components/admin/dashboard/charts/ComboChart.tsx b/frontend/components/admin/dashboard/charts/ComboChart.tsx index ce373d71..e3763784 100644 --- a/frontend/components/admin/dashboard/charts/ComboChart.tsx +++ b/frontend/components/admin/dashboard/charts/ComboChart.tsx @@ -25,7 +25,7 @@ export function ComboChart({ data, config, width = 600, height = 400 }: ComboCha const svg = d3.select(svgRef.current); svg.selectAll("*").remove(); - const margin = { top: 40, right: 80, bottom: 60, left: 60 }; + const margin = { top: 40, right: 80, bottom: 80, left: 60 }; const chartWidth = width - margin.left - margin.right; const chartHeight = height - margin.top - margin.bottom; @@ -275,23 +275,32 @@ export function ComboChart({ data, config, width = 600, height = 400 }: ComboCha .text(config.yAxisLabel); } - // 범례 + // 범례 (차트 하단 중앙) if (config.showLegend !== false && data.datasets.length > 0) { - const legend = svg.append("g").attr("transform", `translate(${width - margin.right + 10}, ${margin.top})`); + const legendItemWidth = 120; + const totalLegendWidth = data.datasets.length * legendItemWidth; + const legendStartX = (width - totalLegendWidth) / 2; + + const legend = svg + .append("g") + .attr("class", "legend") + .attr("transform", `translate(${legendStartX}, ${height - 20})`); data.datasets.forEach((dataset, i) => { - const legendRow = legend.append("g").attr("transform", `translate(0, ${i * 25})`); + const legendItem = legend + .append("g") + .attr("transform", `translate(${i * legendItemWidth}, 0)`); // 범례 아이콘 (첫 번째는 사각형, 나머지는 라인) if (i === 0) { - legendRow + legendItem .append("rect") .attr("width", 15) .attr("height", 15) .attr("fill", dataset.color || colors[i % colors.length]) .attr("rx", 3); } else { - legendRow + legendItem .append("line") .attr("x1", 0) .attr("y1", 7) @@ -300,7 +309,7 @@ export function ComboChart({ data, config, width = 600, height = 400 }: ComboCha .attr("stroke", dataset.color || colors[i % colors.length]) .attr("stroke-width", 2); - legendRow + legendItem .append("circle") .attr("cx", 7.5) .attr("cy", 7) @@ -308,7 +317,7 @@ export function ComboChart({ data, config, width = 600, height = 400 }: ComboCha .attr("fill", dataset.color || colors[i % colors.length]); } - legendRow + legendItem .append("text") .attr("x", 20) .attr("y", 12) diff --git a/frontend/components/admin/dashboard/charts/ComboChartComponent.tsx b/frontend/components/admin/dashboard/charts/ComboChartComponent.tsx index 6f11d0fe..c9c40bb2 100644 --- a/frontend/components/admin/dashboard/charts/ComboChartComponent.tsx +++ b/frontend/components/admin/dashboard/charts/ComboChartComponent.tsx @@ -59,7 +59,7 @@ export function ComboChartComponent({ data, config, width = 250, height = 200 }: top: 5, right: 30, left: 20, - bottom: 5, + bottom: 25, }} > @@ -84,7 +84,7 @@ export function ComboChartComponent({ data, config, width = 250, height = 200 }: name ]} /> - {showLegend && yKeys.length > 1 && ( + {showLegend && ( diff --git a/frontend/components/admin/dashboard/charts/HorizontalBarChart.tsx b/frontend/components/admin/dashboard/charts/HorizontalBarChart.tsx index 60fcb666..955a02c9 100644 --- a/frontend/components/admin/dashboard/charts/HorizontalBarChart.tsx +++ b/frontend/components/admin/dashboard/charts/HorizontalBarChart.tsx @@ -23,7 +23,7 @@ export function HorizontalBarChart({ data, config, width = 600, height = 400 }: const svg = d3.select(svgRef.current); svg.selectAll("*").remove(); - const margin = { top: 40, right: 80, bottom: 60, left: 120 }; + const margin = { top: 40, right: 80, bottom: 80, left: 120 }; const chartWidth = width - margin.left - margin.right; const chartHeight = height - margin.top - margin.bottom; @@ -192,21 +192,30 @@ export function HorizontalBarChart({ data, config, width = 600, height = 400 }: .text(config.yAxisLabel); } - // 범례 - if (config.showLegend !== false && data.datasets.length > 1) { - const legend = svg.append("g").attr("transform", `translate(${width - margin.right + 10}, ${margin.top})`); + // 범례 (차트 하단 중앙) + if (config.showLegend !== false && data.datasets.length > 0) { + const legendItemWidth = 120; + const totalLegendWidth = data.datasets.length * legendItemWidth; + const legendStartX = (width - totalLegendWidth) / 2; + + const legend = svg + .append("g") + .attr("class", "legend") + .attr("transform", `translate(${legendStartX}, ${height - 20})`); data.datasets.forEach((dataset, i) => { - const legendRow = legend.append("g").attr("transform", `translate(0, ${i * 25})`); + const legendItem = legend + .append("g") + .attr("transform", `translate(${i * legendItemWidth}, 0)`); - legendRow + legendItem .append("rect") .attr("width", 15) .attr("height", 15) .attr("fill", dataset.color || colors[i % colors.length]) .attr("rx", 3); - legendRow + legendItem .append("text") .attr("x", 20) .attr("y", 12) diff --git a/frontend/components/admin/dashboard/charts/LineChart.tsx b/frontend/components/admin/dashboard/charts/LineChart.tsx index db4292a8..1d3c7a9a 100644 --- a/frontend/components/admin/dashboard/charts/LineChart.tsx +++ b/frontend/components/admin/dashboard/charts/LineChart.tsx @@ -23,7 +23,7 @@ export function LineChart({ data, config, width = 600, height = 400 }: LineChart const svg = d3.select(svgRef.current); svg.selectAll("*").remove(); - const margin = { top: 40, right: 80, bottom: 60, left: 60 }; + const margin = { top: 40, right: 80, bottom: 80, left: 60 }; const chartWidth = width - margin.left - margin.right; const chartHeight = height - margin.top - margin.bottom; @@ -208,17 +208,23 @@ export function LineChart({ data, config, width = 600, height = 400 }: LineChart .text(config.yAxisLabel); } - // 범례 - if (config.showLegend !== false && data.datasets.length > 1) { + // 범례 (차트 하단 중앙) + if (config.showLegend !== false && data.datasets.length > 0) { + const legendItemWidth = 120; + const totalLegendWidth = data.datasets.length * legendItemWidth; + const legendStartX = (width - totalLegendWidth) / 2; + const legend = svg .append("g") .attr("class", "legend") - .attr("transform", `translate(${width - margin.right + 10}, ${margin.top})`); + .attr("transform", `translate(${legendStartX}, ${height - 20})`); data.datasets.forEach((dataset, i) => { - const legendRow = legend.append("g").attr("transform", `translate(0, ${i * 25})`); + const legendItem = legend + .append("g") + .attr("transform", `translate(${i * legendItemWidth}, 0)`); - legendRow + legendItem .append("line") .attr("x1", 0) .attr("y1", 7) @@ -227,7 +233,7 @@ export function LineChart({ data, config, width = 600, height = 400 }: LineChart .attr("stroke", dataset.color || colors[i % colors.length]) .attr("stroke-width", 3); - legendRow + legendItem .append("circle") .attr("cx", 7.5) .attr("cy", 7) @@ -236,7 +242,7 @@ export function LineChart({ data, config, width = 600, height = 400 }: LineChart .attr("stroke", "white") .attr("stroke-width", 2); - legendRow + legendItem .append("text") .attr("x", 20) .attr("y", 12) diff --git a/frontend/components/admin/dashboard/charts/PieChart.tsx b/frontend/components/admin/dashboard/charts/PieChart.tsx index f9ab4810..8afcb4c0 100644 --- a/frontend/components/admin/dashboard/charts/PieChart.tsx +++ b/frontend/components/admin/dashboard/charts/PieChart.tsx @@ -24,7 +24,7 @@ export function PieChart({ data, config, width = 500, height = 500, isDonut = fa const svg = d3.select(svgRef.current); svg.selectAll("*").remove(); - const margin = { top: 40, right: 120, bottom: 40, left: 120 }; + const margin = { top: 40, right: 150, bottom: 40, left: 120 }; const chartWidth = width - margin.left - margin.right; const chartHeight = height - margin.top - margin.bottom; const radius = Math.min(chartWidth, chartHeight) / 2; @@ -136,28 +136,33 @@ export function PieChart({ data, config, width = 500, height = 500, isDonut = fa .text(config.title); } - // 범례 + // 범례 (차트 오른쪽, 세로 배치) if (config.showLegend !== false) { + const legendX = width / 2 + radius + 30; // 차트 오른쪽 + const legendY = (height - pieData.length * 25) / 2; // 세로 중앙 정렬 + const legend = svg .append("g") .attr("class", "legend") - .attr("transform", `translate(${width - margin.right + 10}, ${margin.top})`); + .attr("transform", `translate(${legendX}, ${legendY})`); pieData.forEach((d, i) => { - const legendRow = legend.append("g").attr("transform", `translate(0, ${i * 25})`); + const legendItem = legend + .append("g") + .attr("transform", `translate(0, ${i * 25})`); - legendRow + legendItem .append("rect") .attr("width", 15) .attr("height", 15) .attr("fill", colors[i % colors.length]) .attr("rx", 3); - legendRow + legendItem .append("text") .attr("x", 20) .attr("y", 12) - .style("font-size", "12px") + .style("font-size", "11px") .style("fill", "#333") .text(`${d.label} (${d.value})`); }); diff --git a/frontend/components/admin/dashboard/charts/StackedBarChart.tsx b/frontend/components/admin/dashboard/charts/StackedBarChart.tsx index 1a18c622..8aed840a 100644 --- a/frontend/components/admin/dashboard/charts/StackedBarChart.tsx +++ b/frontend/components/admin/dashboard/charts/StackedBarChart.tsx @@ -23,7 +23,7 @@ export function StackedBarChart({ data, config, width = 600, height = 400 }: Sta const svg = d3.select(svgRef.current); svg.selectAll("*").remove(); - const margin = { top: 40, right: 80, bottom: 60, left: 60 }; + const margin = { top: 40, right: 80, bottom: 80, left: 60 }; const chartWidth = width - margin.left - margin.right; const chartHeight = height - margin.top - margin.bottom; @@ -241,24 +241,30 @@ export function StackedBarChart({ data, config, width = 600, height = 400 }: Sta .text(config.yAxisLabel); } - // 범례 + // 범례 (차트 하단 중앙) if (config.showLegend !== false) { + const legendItemWidth = 120; + const totalLegendWidth = data.datasets.length * legendItemWidth; + const legendStartX = (width - totalLegendWidth) / 2; + const legend = svg .append("g") .attr("class", "legend") - .attr("transform", `translate(${width - margin.right + 10}, ${margin.top})`); + .attr("transform", `translate(${legendStartX}, ${height - 20})`); data.datasets.forEach((dataset, i) => { - const legendRow = legend.append("g").attr("transform", `translate(0, ${i * 25})`); + const legendItem = legend + .append("g") + .attr("transform", `translate(${i * legendItemWidth}, 0)`); - legendRow + legendItem .append("rect") .attr("width", 15) .attr("height", 15) .attr("fill", dataset.color || colors[i % colors.length]) .attr("rx", 3); - legendRow + legendItem .append("text") .attr("x", 20) .attr("y", 12) diff --git a/frontend/components/admin/dashboard/charts/StackedBarChartComponent.tsx b/frontend/components/admin/dashboard/charts/StackedBarChartComponent.tsx index a1115af5..5ddb7b85 100644 --- a/frontend/components/admin/dashboard/charts/StackedBarChartComponent.tsx +++ b/frontend/components/admin/dashboard/charts/StackedBarChartComponent.tsx @@ -54,7 +54,7 @@ export function StackedBarChartComponent({ data, config, width = 250, height = 2 top: 5, right: 30, left: 20, - bottom: 5, + bottom: 25, }} > @@ -79,7 +79,7 @@ export function StackedBarChartComponent({ data, config, width = 250, height = 2 name ]} /> - {showLegend && yKeys.length > 1 && ( + {showLegend && (