Skip to content

Commit 7c3e180

Browse files
committed
feat: AuthorBarChart 렌더링 최적화 및 애니메이션 개선
1 parent 3583f7b commit 7c3e180

1 file changed

Lines changed: 84 additions & 36 deletions

File tree

packages/view/src/components/Statistics/AuthorBarChart/AuthorBarChart.tsx

Lines changed: 84 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,22 @@ const AuthorBarChart = () => {
5757
const svg = d3.select(svgRef.current).attr("width", DIMENSIONS.width).attr("height", DIMENSIONS.height);
5858
const tooltip = d3.select(tooltipRef.current);
5959

60-
svg.selectAll("*").remove();
61-
6260
const totalMetricValues = data.reduce((acc, item) => acc + item[metric], 0);
6361

6462
const xAxisGroup = svg
65-
.append("g")
63+
.selectAll(".x-axis")
64+
.data([null])
65+
.join("g")
6666
.attr("class", "author-bar-chart__axis x-axis")
6767
.style("transform", `translateY(${pxToRem(DIMENSIONS.height)})`);
68-
const yAxisGroup = svg.append("g").attr("class", "author-bar-chart__axis y-axis");
69-
const barGroup = svg.append("g").attr("class", "author-bar-chart__container");
68+
69+
const yAxisGroup = svg.selectAll(".y-axis").data([null]).join("g").attr("class", "author-bar-chart__axis y-axis");
70+
71+
const barGroup = svg
72+
.selectAll(".author-bar-chart__container")
73+
.data([null])
74+
.join("g")
75+
.attr("class", "author-bar-chart__container");
7076

7177
// Scales
7278
const xScale = d3
@@ -84,13 +90,15 @@ const AuthorBarChart = () => {
8490

8591
// Axis
8692
const xAxis = d3.axisBottom(xScale).ticks(0).tickSizeInner(0).tickSizeOuter(0);
87-
xAxisGroup.call(xAxis);
93+
xAxisGroup.call(xAxis as any);
8894

8995
const yAxis = d3.axisLeft(yScale).ticks(10).tickFormat(convertNumberFormat).tickSizeOuter(0);
90-
yAxisGroup.call(yAxis);
96+
yAxisGroup.call(yAxis as any);
9197

9298
xAxisGroup
93-
.append("text")
99+
.selectAll(".x-axis__label")
100+
.data([null])
101+
.join("text")
94102
.attr("class", "x-axis__label")
95103
.style("transform", `translate(${pxToRem(DIMENSIONS.width / 2)}, ${pxToRem(DIMENSIONS.margins - 10)})`)
96104
.text(`${metric} # / Total ${metric} # (%)`);
@@ -159,48 +167,88 @@ const AuthorBarChart = () => {
159167
};
160168

161169
// Draw bars
162-
barGroup
163-
.selectAll("rect")
164-
.data(data)
170+
const bars = barGroup
171+
.selectAll(".author-bar-chart__bar")
172+
.data(data, (d: any) => d?.name || "")
165173
.join(
166-
(enter) =>
167-
enter
168-
.append("g")
169-
.attr("class", "author-bar-chart__bar")
174+
(enter) => {
175+
const barElement = enter.append("g").attr("class", "author-bar-chart__bar");
176+
177+
// 각 바 그룹에 rect 추가
178+
barElement
170179
.append("rect")
171180
.attr("width", xScale.bandwidth())
172181
.attr("height", 0)
173-
.attr("x", (d) => xScale(d.name) || 0)
174-
.attr("y", DIMENSIONS.height),
175-
(update) => update,
176-
(exit) => exit.attr("height", 0).attr("y", 0).remove()
177-
)
178-
.on("mouseover", handleMouseOver)
179-
.on("mousemove", handleMouseMove)
180-
.on("mouseout", handleMouseOut)
181-
.on("click", handleClickBar)
182+
.attr("x", (d: any) => xScale(d?.name) || 0)
183+
.attr("y", DIMENSIONS.height)
184+
.on("mouseover", handleMouseOver)
185+
.on("mousemove", handleMouseMove)
186+
.on("mouseout", handleMouseOut)
187+
.on("click", handleClickBar);
188+
189+
return barElement;
190+
},
191+
(update) => {
192+
update
193+
.select("rect")
194+
.on("mouseover", handleMouseOver)
195+
.on("mousemove", handleMouseMove)
196+
.on("mouseout", handleMouseOut)
197+
.on("click", handleClickBar);
198+
199+
return update;
200+
},
201+
(exit) => {
202+
exit.select("rect").transition().duration(250).attr("height", 0).attr("y", DIMENSIONS.height);
203+
204+
return exit.transition().duration(250).remove();
205+
}
206+
);
207+
208+
bars
209+
.select("rect")
182210
.transition()
183211
.duration(500)
184212
.attr("width", xScale.bandwidth())
185-
.attr("height", (d: AuthorDataType) => DIMENSIONS.height - yScale(d[metric]))
186-
.attr("x", (d: AuthorDataType) => xScale(d.name) || 0)
187-
.attr("y", (d: AuthorDataType) => yScale(d[metric]));
213+
.attr("height", (d: any) => DIMENSIONS.height - yScale(d?.[metric] || 0))
214+
.attr("x", (d: any) => xScale(d?.name) || 0)
215+
.attr("y", (d: any) => yScale(d?.[metric] || 0));
188216

189217
// Draw author thumbnails
190-
const barElements = d3.selectAll(".author-bar-chart__bar").nodes();
191-
if (!barElements.length) return;
218+
bars.selectAll("image.author-bar-chart__profile-image").remove();
219+
220+
// 새로운 이미지들 추가 (비동기 로딩)
221+
const imagePromises = data.map(async (d: AuthorDataType) => {
222+
if (!d?.name) return null;
223+
224+
try {
225+
const profileImgSrc: string = await getAuthorProfileImgSrc(d.name).then((res: AuthorInfo) => res.src);
226+
return { name: d.name, src: profileImgSrc };
227+
} catch (error) {
228+
console.warn(`Failed to load profile image for ${d.name}:`, error);
229+
return null;
230+
}
231+
});
232+
233+
// 모든 이미지 로딩 완료 후 한번에 표시
234+
Promise.all(imagePromises).then((imageResults) => {
235+
const validImages = imageResults.filter((result) => result !== null);
192236

193-
barElements.forEach(async (barElement, i) => {
194-
const bar = d3.select(barElement).datum(data[i]);
195-
const profileImgSrc: string = await getAuthorProfileImgSrc(data[i].name).then((res: AuthorInfo) => res.src);
196-
bar
237+
bars
238+
.selectAll("image.author-bar-chart__profile-image")
239+
.data(validImages, (d: any) => d?.name || "")
240+
.enter()
197241
.append("image")
198242
.attr("class", "author-bar-chart__profile-image")
199-
.attr("xlink:href", profileImgSrc ?? "")
200-
.attr("x", (d: AuthorDataType) => (xScale(d.name) ?? 0) + xScale.bandwidth() / 2 - 7)
243+
.attr("x", (d: any) => (xScale(d?.name) ?? 0) + xScale.bandwidth() / 2 - 7)
201244
.attr("y", 204)
202245
.attr("width", 14)
203-
.attr("height", 14);
246+
.attr("height", 14)
247+
.attr("xlink:href", (d: any) => d?.src ?? "")
248+
.style("opacity", 0)
249+
.transition()
250+
.duration(300)
251+
.style("opacity", 1);
204252
});
205253
}, [
206254
data,

0 commit comments

Comments
 (0)