Skip to content

Commit dec6749

Browse files
authored
Merge pull request #985 from githru/refactor-chart-responsive/960
[refactor] (view): Storyline Chart 컴포넌트 반응형 구현
2 parents 421c640 + 0c7d56d commit dec6749

File tree

4 files changed

+58
-18
lines changed

4 files changed

+58
-18
lines changed
Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
1+
const DEFAULT_WIDTH = 800;
2+
export const LABEL_COLUMN_WIDTH = 140;
3+
14
export const DIMENSIONS = {
2-
width: 800,
5+
width: DEFAULT_WIDTH,
36
height: 500, // 고정 높이
4-
margin: { top: 20, right: 70, bottom: 20, left: 20 },
7+
margin: { top: 20, right: LABEL_COLUMN_WIDTH, bottom: 20, left: 30 },
8+
};
9+
10+
export const LABEL_COLUMN_PADDING = 12;
11+
12+
export const getResponsiveChartWidth = (containerWidth?: number): number => {
13+
const viewportWidth = typeof window !== "undefined" ? window.innerWidth : DEFAULT_WIDTH;
14+
const baseWidth = containerWidth ?? viewportWidth;
15+
16+
return Math.max(baseWidth, 0);
517
};

packages/view/src/components/FolderActivityFlow/FolderActivityFlow.scss

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,9 @@
2020

2121

2222
padding: 1rem;
23-
margin-bottom: 2rem;
2423
background: $color-background;
2524
border-radius: 8px;
26-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
25+
2726

2827
&__head {
2928
margin-top: 40px;
@@ -105,8 +104,6 @@
105104
&__chart {
106105
display: block;
107106
width: 100%;
108-
padding: 0 50px;
109-
box-sizing: border-box;
110107
cursor: grab;
111108
background: $color-background;
112109
overflow: visible;
@@ -150,12 +147,12 @@
150147
}
151148

152149
.insertions {
153-
color: #1fc3b5;
150+
color: var(--color-success);
154151
display: inline;
155152
}
156153

157154
.deletions {
158-
color: #e84b6b;
155+
color: var(--color-failed);
159156
display: inline;
160157
}
161158

@@ -177,7 +174,7 @@
177174
transition: fill 0.2s ease;
178175

179176
&:hover {
180-
fill: #757880;
177+
fill: $color-medium-gray;
181178
}
182179
}
183180

packages/view/src/components/FolderActivityFlow/FolderActivityFlow.tsx

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as d3 from "d3";
2-
import { useEffect, useRef, useMemo } from "react";
2+
import { useEffect, useRef, useMemo, useState } from "react";
33
import { useShallow } from "zustand/react/shallow";
44
import Breadcrumbs from "@mui/material/Breadcrumbs";
55
import Link from "@mui/material/Link";
@@ -9,7 +9,7 @@ import WorkspacePremiumRoundedIcon from "@mui/icons-material/WorkspacePremiumRou
99

1010
import { useDataStore } from "store";
1111

12-
import { DIMENSIONS } from "./FolderActivityFlow.const";
12+
import { DIMENSIONS, getResponsiveChartWidth } from "./FolderActivityFlow.const";
1313
import "./FolderActivityFlow.scss";
1414
import { extractReleaseBasedContributorActivities } from "./FolderActivityFlow.util";
1515
import { renderReleaseVisualization } from "./ReleaseVisualization";
@@ -21,6 +21,7 @@ const FolderActivityFlow = () => {
2121
const svgRef = useRef<SVGSVGElement>(null);
2222
const tooltipRef = useRef<HTMLDivElement>(null);
2323
const containerRef = useRef<HTMLDivElement>(null);
24+
const [chartWidth, setChartWidth] = useState(0);
2425

2526
const {
2627
currentPath,
@@ -38,6 +39,32 @@ const FolderActivityFlow = () => {
3839

3940
const breadcrumbs = useMemo(() => getBreadcrumbs(), [getBreadcrumbs]);
4041

42+
useEffect(() => {
43+
const updateWidth = () => {
44+
const containerWidth = containerRef.current?.clientWidth;
45+
setChartWidth(getResponsiveChartWidth(containerWidth));
46+
};
47+
48+
updateWidth();
49+
50+
if (typeof ResizeObserver !== "undefined") {
51+
const observer = new ResizeObserver(() => updateWidth());
52+
const containerElement = containerRef.current;
53+
if (containerElement) {
54+
observer.observe(containerElement);
55+
}
56+
57+
return () => {
58+
observer.disconnect();
59+
};
60+
}
61+
62+
window.addEventListener("resize", updateWidth);
63+
return () => {
64+
window.removeEventListener("resize", updateWidth);
65+
};
66+
}, []);
67+
4168
const { topContributorName, releaseRangeLabel } = useMemo(() => {
4269
if (!totalData || totalData.length === 0 || releaseTopFolderPaths.length === 0) {
4370
return {
@@ -109,9 +136,13 @@ const FolderActivityFlow = () => {
109136
return;
110137
}
111138

139+
if (!svgRef.current || chartWidth <= 0) {
140+
return;
141+
}
142+
112143
const svg = d3
113144
.select(svgRef.current)
114-
.attr("width", (containerRef.current?.clientWidth || DIMENSIONS.width) - 100)
145+
.attr("width", chartWidth)
115146
.attr("height", DIMENSIONS.height);
116147

117148
//activity가 있는 폴더 카운트
@@ -125,7 +156,6 @@ const FolderActivityFlow = () => {
125156
svg.selectAll("*").remove();
126157

127158
if (releaseContributorActivities.length === 0) {
128-
const chartWidth = (containerRef.current?.clientWidth || DIMENSIONS.width) - 100;
129159
svg
130160
.append("text")
131161
.attr("x", chartWidth / 2)
@@ -145,7 +175,7 @@ const FolderActivityFlow = () => {
145175
tooltipRef,
146176
onFolderClick: navigateToFolder,
147177
});
148-
}, [totalData, releaseGroups, releaseTopFolderPaths, navigateToFolder, currentPath]);
178+
}, [totalData, releaseGroups, releaseTopFolderPaths, navigateToFolder, currentPath, chartWidth]);
149179

150180
const topContributorLabel = topContributorName || "...";
151181

packages/view/src/components/FolderActivityFlow/ReleaseVisualization.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as d3 from "d3";
22
import type React from "react";
33

4-
import { DIMENSIONS } from "./FolderActivityFlow.const";
4+
import { DIMENSIONS, LABEL_COLUMN_PADDING } from "./FolderActivityFlow.const";
55
import type { ReleaseContributorActivity } from "./FolderActivityFlow.type";
66
import {
77
calculateReleaseNodePosition,
@@ -15,7 +15,7 @@ import {
1515
*/
1616
interface ReleaseVisualizationProps {
1717
/** D3 SVG selection to render into */
18-
svg: d3.Selection<SVGSVGElement | null, unknown, null, undefined>;
18+
svg: d3.Selection<SVGSVGElement, unknown, null, undefined>;
1919
/** Contributor activities grouped by release */
2020
releaseContributorActivities: ReleaseContributorActivity[];
2121
/** Top folder paths to display as lanes */
@@ -64,7 +64,8 @@ export const renderReleaseVisualization = ({
6464
.scaleBand()
6565
.domain(uniqueReleases.map(String))
6666
.range([DIMENSIONS.margin.left, chartWidth - DIMENSIONS.margin.right])
67-
.paddingInner(0.1);
67+
.paddingInner(0.1)
68+
.paddingOuter(0.05);
6869

6970
const yScale = d3
7071
.scaleBand()
@@ -98,7 +99,7 @@ export const renderReleaseVisualization = ({
9899
const isFile = folderPath.includes(".");
99100
return isFile ? "folder-label" : "folder-label clickable";
100101
})
101-
.attr("x", chartWidth - DIMENSIONS.margin.right + 10)
102+
.attr("x", chartWidth - DIMENSIONS.margin.right + LABEL_COLUMN_PADDING)
102103
.attr("y", (folderPath: string) => (yScale(folderPath) || 0) + yScale.bandwidth() / 2)
103104
.attr("text-anchor", "start")
104105
.attr("dominant-baseline", "middle")

0 commit comments

Comments
 (0)