Skip to content

Commit ce868ce

Browse files
authored
[Merge/#46] Feat/#13/WordCloud
[fix]ν‚€μ›Œλ“œλ²„λΈ”μ†λ„κ΅¬ν˜„-μš”μ²­ν˜•μ‹μˆ˜μ •
2 parents a25ec25 + 17c5eac commit ce868ce

2 files changed

Lines changed: 61 additions & 48 deletions

File tree

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,74 @@
11
import React, { useEffect, useRef } from "react";
22
import * as d3 from "d3";
33

4-
const BubbleChart = ({ keywordCounts, onKeywordClick }) => {
5-
const svgRef = useRef(null);
4+
const BubbleChart = ({ keywordCounts }) => {
5+
const svgRef = useRef();
66

77
useEffect(() => {
88
if (!keywordCounts || Object.keys(keywordCounts).length === 0) return;
99

10-
const data = Object.entries(keywordCounts)
11-
.map(([keyword, count]) => ({ name: keyword, value: count }))
12-
.slice(0, 10); // μƒμœ„ 10개만 ν‘œμ‹œ
10+
d3.select(svgRef.current).selectAll("*").remove(); // 기쑴 차트 제거
1311

14-
const width = 800;
15-
const height = 600;
16-
const maxRadius = 150;
12+
const width = 600,
13+
height = 500;
1714

15+
// πŸ”Ή 데이터 μœ νš¨μ„± 검사 (NaN 제거)
16+
const validData = Object.entries(keywordCounts)
17+
.filter(([name, size]) => typeof size === "number" && !isNaN(size)) // NaN 제거
18+
.map(([name, size]) => ({ name, size }));
19+
20+
if (validData.length === 0) return; // μœ νš¨ν•œ 데이터 μ—†μœΌλ©΄ 차트 λ Œλ”λ§ X
21+
22+
// πŸ”Ή 색상 μŠ€μΌ€μΌ: 값이 클수둝 μ§„ν•œ νŒŒλž€μƒ‰ 적용
23+
const minSize = d3.min(validData, (d) => d.size);
24+
const maxSize = d3.max(validData, (d) => d.size);
1825
const colorScale = d3
1926
.scaleSequential(d3.interpolateBlues)
20-
.domain([
21-
Math.min(...data.map((d) => d.value)),
22-
Math.max(...data.map((d) => d.value)),
23-
]);
27+
.domain([minSize, maxSize]);
28+
29+
// D3 pack layout μ‚¬μš©
30+
const bubble = d3.pack().size([width, height]).padding(2);
31+
32+
const root = d3
33+
.hierarchy({ children: validData })
34+
.sum((d) => d.size)
35+
.sort((a, b) => b.value - a.value);
2436

25-
const bubble = d3.pack().size([width, height]).padding(5);
26-
const root = d3.hierarchy({ children: data }).sum((d) => d.value);
2737
bubble(root);
2838

2939
const svg = d3
3040
.select(svgRef.current)
31-
.attr("viewBox", `0 0 ${width} ${height}`)
3241
.attr("width", width)
3342
.attr("height", height)
3443
.attr("class", "bubble");
3544

3645
const nodes = svg
3746
.selectAll(".node")
3847
.data(root.children)
39-
.join("g")
48+
.enter()
49+
.append("g")
4050
.attr("class", "node")
41-
.attr("transform", (d) => `translate(${d.x}, ${d.y})`)
42-
.style("cursor", "pointer")
43-
.on("click", (event, d) => {
44-
if (onKeywordClick) {
45-
onKeywordClick(d.data.name);
46-
}
47-
});
51+
.attr("transform", (d) => `translate(${d.x || 0}, ${d.y || 0})`); // πŸ”Ή NaN λ°©μ§€
4852

4953
nodes
5054
.append("circle")
51-
.attr("r", (d) => Math.min(d.r, maxRadius))
52-
.attr("fill", (d) => colorScale(d.data.value));
55+
.attr("r", (d) => d.r || 10) // πŸ”Ή NaN λ°©μ§€: μ΅œμ†Œ 크기 μ§€μ •
56+
.style("fill", (d) => colorScale(d.data.size)) // βœ… 값이 클수둝 μ§„ν•œ νŒŒλž€μƒ‰ 적용
57+
.style("opacity", 0.8)
58+
.transition()
59+
.duration(1000)
60+
.attr("r", (d) => d.r || 10);
5361

5462
nodes
5563
.append("text")
5664
.attr("dy", ".3em")
5765
.attr("text-anchor", "middle")
58-
.text((d) => d.data.name)
59-
.attr("fill", "#fff")
60-
.style("font-size", (d) => `${Math.max(d.r / 5, 12)}px`)
61-
.style("font-weight", "bold");
62-
}, [keywordCounts, onKeywordClick]);
66+
.style("fill", "#fff")
67+
.style("font-size", (d) => Math.max(10, d.r / 4) + "px")
68+
.text((d) => d.data.name);
69+
}, [keywordCounts]);
6370

64-
return <svg ref={svgRef}></svg>;
71+
return <svg ref={svgRef} />;
6572
};
6673

6774
export default BubbleChart;

β€Žsrc/pages/Todaynews.jsxβ€Ž

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ function Todaynews() {
2626
const [keywords, setKeywords] = useState([]); // ν‚€μ›Œλ“œ λͺ©λ‘
2727
const [keywordCounts, setKeywordCounts] = useState({}); // 기사 개수
2828
const [isLoading, setIsLoading] = useState(true);
29-
const navigate = useNavigate(); // πŸ”Ή νŽ˜μ΄μ§€ 이동을 μœ„ν•œ navigate μΆ”κ°€
29+
const navigate = useNavigate();
3030

3131
useEffect(() => {
3232
const fetchKeywordData = async () => {
@@ -37,30 +37,36 @@ function Todaynews() {
3737
const keywordList = response.data.keywords.slice(0, 20); // μƒμœ„ 20개 ν‚€μ›Œλ“œ μ‚¬μš©
3838
setKeywords(keywordList);
3939

40-
// πŸ”Ή 각 ν‚€μ›Œλ“œλ³„ 기사 개수 κ°€μ Έμ˜€κΈ°
41-
const requests = keywordList.map((keyword) =>
42-
axios.get(`${apiUrl}/articles/count/${keyword}`).catch(() => null)
43-
);
40+
console.log("Keyword List:", keywordList);
4441

45-
const responses = await Promise.all(requests);
42+
const encodedKeywords = keywordList.map(encodeURIComponent).join(",");
4643

47-
const counts = {};
48-
responses.forEach((res, idx) => {
49-
if (res && res.data && typeof res.data.totalCount === "number") {
50-
counts[keywordList[idx]] = res.data.totalCount;
51-
} else {
52-
counts[keywordList[idx]] = 0; // μ‹€νŒ¨ μ‹œ κΈ°λ³Έκ°’ 0
53-
}
54-
});
44+
const countResponse = await axios.get(
45+
`${apiUrl}/articles/count/total?keywords=${encodedKeywords}`
46+
);
5547

56-
setKeywordCounts(counts);
48+
console.log("API Response:", countResponse.data);
49+
50+
// πŸ”Ή API 응닡 데이터 확인 및 λ³€ν™˜ (totalCount μΆ”μΆœ)
51+
if (countResponse.data) {
52+
const counts = {};
53+
Object.entries(countResponse.data).forEach(([keyword, data]) => {
54+
if (data !== null) {
55+
counts[keyword] = data; // βœ… null이 μ•„λ‹Œ κ²½μš°μ—λ§Œ μ €μž₯
56+
}
57+
});
58+
setKeywordCounts(counts);
59+
} else {
60+
console.error("API 응닡이 μ˜ˆμƒκ³Ό 닀름:", countResponse.data);
61+
setKeywordCounts({});
62+
}
5763
} else {
58-
console.error("❌ API 응닡 ν˜•μ‹μ΄ μ˜ˆμƒκ³Ό 닀름:", response.data);
64+
console.error("ν‚€μ›Œλ“œ API 응닡이 μ˜ˆμƒκ³Ό 닀름:", response.data);
5965
setKeywords([]);
6066
setKeywordCounts({});
6167
}
6268
} catch (error) {
63-
console.error("❌ ν‚€μ›Œλ“œ 데이터 κ°€μ Έμ˜€κΈ° μ‹€νŒ¨:", error);
69+
console.error("ν‚€μ›Œλ“œ 데이터 κ°€μ Έμ˜€κΈ° μ‹€νŒ¨:", error);
6470
setKeywords([]);
6571
setKeywordCounts({});
6672
}

0 commit comments

Comments
Β (0)