Skip to content

Commit 3071cf3

Browse files
authored
Merge pull request #943 from githru/refactor/941
refactor(view) : 스토리라인 차트 디자인 변경
2 parents 147165c + b047540 commit 3071cf3

4 files changed

Lines changed: 169 additions & 128 deletions

File tree

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

Lines changed: 66 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -66,60 +66,75 @@ export const renderClusterVisualization = ({
6666
.domain([0, d3.max(contributorActivities, (d) => d.changes) || 1])
6767
.range([3, 12]);
6868

69-
const colorScale = d3.scaleOrdinal().domain(uniqueContributors).range(d3.schemeCategory10);
69+
const svgElement = svg.node();
70+
const parentElement = svgElement?.parentElement;
71+
const chartColors = Array.from({ length: 10 }, (_, i) =>
72+
getComputedStyle(parentElement || document.documentElement)
73+
.getPropertyValue(`--chart-color-${i + 1}`)
74+
.trim()
75+
);
76+
77+
const colorScale = d3.scaleOrdinal().domain(uniqueContributors).range(chartColors);
7078

7179
const mainGroup = svg.append("g");
7280

73-
// 폴더 레인 그리기
7481
mainGroup
75-
.selectAll(".folder-lane")
82+
.selectAll(".folder-label")
7683
.data(topFolders)
7784
.enter()
78-
.append("g")
79-
.attr("class", "folder-lane")
80-
.each(function (this: SVGGElement, d: FolderActivity) {
81-
const lane = d3.select(this);
82-
83-
lane
84-
.append("rect")
85-
.attr("class", "lane-background")
86-
.attr("x", DIMENSIONS.margin.left)
87-
.attr("y", yScale(d.folderPath) || 0)
88-
.attr("width", DIMENSIONS.width - DIMENSIONS.margin.left - DIMENSIONS.margin.right)
89-
.attr("height", yScale.bandwidth())
90-
.attr("fill", "#f8f9fa")
91-
.attr("stroke", "#dee2e6")
92-
.attr("stroke-width", 1);
93-
94-
lane
95-
.append("text")
96-
.attr("class", "folder-label clickable")
97-
.attr("x", DIMENSIONS.width - DIMENSIONS.margin.right + 10)
98-
.attr("y", (yScale(d.folderPath) || 0) + yScale.bandwidth() / 2)
99-
.attr("text-anchor", "start")
100-
.attr("dominant-baseline", "middle")
101-
.text(() => {
102-
if (d.folderPath === ".") return "root";
103-
104-
const fileName = d.folderPath.includes("/") ? d.folderPath.split("/").pop() : d.folderPath;
105-
106-
return fileName && fileName.length > 15 ? `${fileName.substring(0, 12)}...` : fileName || "unknown";
107-
})
108-
.style("font-size", "12px")
109-
.style("fill", "#495057")
110-
.style("font-weight", "500")
111-
.style("cursor", "pointer")
112-
.on("click", () => {
113-
if (d.folderPath !== ".") {
114-
onFolderClick(d.folderPath);
115-
}
116-
})
117-
.on("mouseover", function () {
118-
d3.select(this).style("fill", "#007bff");
119-
})
120-
.on("mouseout", function () {
121-
d3.select(this).style("fill", "#495057");
122-
});
85+
.append("text")
86+
.attr("class", (d: FolderActivity) => {
87+
const isFile = d.folderPath.includes(".");
88+
return isFile ? "folder-label" : "folder-label clickable";
89+
})
90+
.attr("x", DIMENSIONS.width - DIMENSIONS.margin.right + 10)
91+
.attr("y", (d: FolderActivity) => (yScale(d.folderPath) || 0) + yScale.bandwidth() / 2)
92+
.attr("text-anchor", "start")
93+
.attr("dominant-baseline", "middle")
94+
.text((d: FolderActivity) => {
95+
if (d.folderPath === ".") return "root";
96+
97+
const fileName = d.folderPath.includes("/") ? d.folderPath.split("/").pop() : d.folderPath;
98+
99+
return fileName && fileName.length > 15 ? `${fileName.substring(0, 15)}...` : fileName || "unknown";
100+
})
101+
.style("font-size", "12px")
102+
.style("fill", "#b4bac6")
103+
.style("font-weight", "500")
104+
.style("cursor", (d: FolderActivity) => {
105+
const isFile = d.folderPath.includes(".");
106+
return isFile ? "default" : "pointer";
107+
})
108+
.on("click", (_event: MouseEvent, d: FolderActivity) => {
109+
const isFile = d.folderPath.includes(".");
110+
if (!isFile && d.folderPath !== ".") {
111+
onFolderClick(d.folderPath);
112+
}
113+
})
114+
.on("mouseover", function (_event: MouseEvent, d: FolderActivity) {
115+
const isFile = d.folderPath.includes(".");
116+
if (!isFile) {
117+
d3.select(this).style("fill", "#e06091");
118+
}
119+
})
120+
.on("mouseout", function (_event: MouseEvent, d: FolderActivity) {
121+
const isFile = d.folderPath.includes(".");
122+
const element = d3.select(this);
123+
if (!isFile) {
124+
element.style("fill", "#b4bac6");
125+
}
126+
// 호버 끝나면 축약된 텍스트로 복원
127+
const fileName = d.folderPath.includes("/") ? d.folderPath.split("/").pop() : d.folderPath;
128+
const displayName = fileName && fileName.length > 15 ? `${fileName.substring(0, 15)}...` : fileName || "unknown";
129+
element.text(d.folderPath === "." ? "root" : displayName);
130+
});
131+
132+
// 호버 시 전체 이름 표시를 위한 추가 이벤트
133+
mainGroup
134+
.selectAll<SVGTextElement, FolderActivity>(".folder-label")
135+
.on("mouseover.showfull", function (_event, d) {
136+
const fileName = d.folderPath.includes("/") ? d.folderPath.split("/").pop() : d.folderPath;
137+
d3.select(this).text(d.folderPath === "." ? "root" : fileName || "unknown");
123138
});
124139

125140
// 클러스터 축
@@ -151,9 +166,7 @@ export const renderClusterVisualization = ({
151166
.attr("cy", (d: ContributorActivity) => (yScale(d.folderPath) || 0) + yScale.bandwidth() / 2)
152167
.attr("r", (d: ContributorActivity) => sizeScale(d.changes))
153168
.attr("fill", (d: ContributorActivity) => colorScale(d.contributorName) as string)
154-
.attr("fill-opacity", 0.8)
155-
.attr("stroke", "#fff")
156-
.attr("stroke-width", 1);
169+
.attr("fill-opacity", 0.8);
157170

158171
// 툴팁 이벤트
159172
dots
@@ -198,7 +211,7 @@ export const renderClusterVisualization = ({
198211
.attr("dominant-baseline", "bottom")
199212
.text((d: ContributorActivity) => d.contributorName)
200213
.style("font-size", "10px")
201-
.style("fill", "#495057")
214+
.style("fill", "#f7f7f7")
202215
.style("font-weight", "500")
203216
.style("pointer-events", "none");
204217

@@ -215,5 +228,5 @@ export const renderClusterVisualization = ({
215228
.attr("fill", "none")
216229
.attr("stroke", (d) => colorScale(d.contributorName) as string)
217230
.attr("stroke-width", 2)
218-
.attr("stroke-opacity", 0.4);
231+
.attr("stroke-opacity", 1);
219232
};

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

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
1+
@import "../../styles/colors";
2+
13
.folder-activity-flow {
4+
// Chart color palette for contributors
5+
--chart-color-1: #e06091; // githru-pri
6+
--chart-color-2: #8840bb; // githru-sec
7+
--chart-color-3: #ffd08a; // githru-ter
8+
--chart-color-4: #07bebe; // githru-suc
9+
--chart-color-5: #456cf7; // hacker-pri
10+
--chart-color-6: #0687a3; // aqua-sec
11+
--chart-color-7: #ffcccb; // cotton-pri
12+
--chart-color-8: #feffd1; // cotton-sec
13+
--chart-color-9: #3a4776; // mono-sec
14+
--chart-color-10: #aa4b72; // mono-fai
15+
16+
217
padding: 1rem;
318
margin-bottom: 2rem;
4-
background: #ffffff;
19+
background: $color-background;
520
border-radius: 8px;
621
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
722

@@ -16,13 +31,13 @@
1631
margin: 0 0 0.5rem 0;
1732
font-size: 1.2rem;
1833
font-weight: 600;
19-
color: #212529;
34+
color: $color-white;
2035
}
2136

2237
&__subtitle {
2338
margin: 0 0 0.5rem 0;
2439
font-size: 0.875rem;
25-
color: #6c757d;
40+
color: $color-light-gray;
2641
}
2742

2843
&__mode-toggle {
@@ -41,53 +56,54 @@
4156
&__breadcrumb {
4257
margin: 0 0 1rem 0;
4358
padding: 0.5rem;
44-
background: #f8f9fa;
59+
background: $color-dark-gray;
4560
border-radius: 4px;
4661
font-size: 0.875rem;
4762
display: flex;
4863
align-items: center;
4964
gap: 0.5rem;
5065

5166
.separator {
52-
color: #6c757d;
67+
color: $color-light-gray;
5368
}
5469

5570
.clickable {
56-
color: #007bff;
71+
color: #456cf7;
5772
cursor: pointer;
5873
text-decoration: underline;
59-
74+
6075
&:hover {
61-
color: #0056b3;
76+
color: #e06091;
6277
}
6378
}
6479

6580
.current {
66-
color: #212529;
81+
color: $color-white;
6782
font-weight: 600;
6883
}
6984
}
7085

7186
&__back-btn {
7287
margin-left: auto;
7388
padding: 0.25rem 0.5rem;
74-
background: #007bff;
89+
background: #456cf7;
7590
color: white;
7691
border: none;
7792
border-radius: 4px;
7893
font-size: 0.75rem;
7994
cursor: pointer;
80-
95+
8196
&:hover {
82-
background: #0056b3;
97+
background: #e06091;
8398
}
8499
}
85100

86101
&__chart {
87102
display: block;
88103
margin: 0 auto;
89104
cursor: grab;
90-
105+
background: $color-background;
106+
91107
&:active {
92108
cursor: grabbing;
93109
}
@@ -122,21 +138,21 @@
122138

123139
.lane-background {
124140
transition: fill 0.2s ease;
125-
141+
126142
&:hover {
127-
fill: #e9ecef;
143+
fill: #757880;
128144
}
129145
}
130146

131147
.folder-label {
132148
font-weight: 500;
133-
149+
134150
&.clickable {
135151
cursor: pointer;
136152
transition: fill 0.2s ease;
137-
153+
138154
&:hover {
139-
fill: #007bff !important;
155+
fill: #e06091 !important;
140156
}
141157
}
142158
}
@@ -158,11 +174,11 @@
158174
.x-axis {
159175
.domain,
160176
.tick line {
161-
stroke: #dee2e6;
177+
stroke: $color-medium-gray;
162178
}
163-
179+
164180
.tick text {
165-
fill: #6c757d;
181+
fill: $color-light-gray;
166182
font-size: 11px;
167183
}
168184
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ const FolderActivityFlow = () => {
144144
className={index === getBreadcrumbs().length - 1 ? "current" : "clickable"}
145145
onClick={() => navigateToBreadcrumb(index, getBreadcrumbs().length)}
146146
onKeyDown={(e) => {
147-
if (e.key === 'Enter' || e.key === ' ') {
147+
if (e.key === "Enter" || e.key === " ") {
148148
e.preventDefault();
149149
navigateToBreadcrumb(index, getBreadcrumbs().length);
150150
}

0 commit comments

Comments
 (0)