@@ -2,181 +2,13 @@ import type * as d3 from "d3";
22
33import type { ClusterNode } from "types" ;
44
5- import { extractFolderFromPath , type FolderActivity } from "./FolderActivityFlow.analyzer" ;
65import {
76 getTopFoldersByRelease ,
87 extractReleaseContributorActivities ,
98 type ReleaseGroup ,
109 type ReleaseFolderActivity ,
1110} from "./FolderActivityFlow.releaseAnalyzer" ;
12- import type {
13- ContributorActivity ,
14- FlowLineData ,
15- ReleaseContributorActivity ,
16- ReleaseFlowLineData ,
17- } from "./FolderActivityFlow.type" ;
18-
19- // 기여자 활동 데이터 추출
20- export function extractContributorActivities (
21- totalData : ClusterNode [ ] ,
22- topFolders : FolderActivity [ ] ,
23- currentPath : string
24- ) : ContributorActivity [ ] {
25- const contributorActivities : ContributorActivity [ ] = [ ] ;
26-
27- totalData . forEach ( ( cluster , clusterIndex ) => {
28- const clusterId = `cluster-${ clusterIndex } ` ;
29-
30- cluster . commitNodeList . forEach ( ( commitNode ) => {
31- if ( commitNode . commit . commitDate ) {
32- const { commit } = commitNode ;
33- const date = new Date ( commit . commitDate ) ;
34-
35- if ( commit . author ?. names ?. [ 0 ] && commit . author ?. emails ?. [ 0 ] && commit . diffStatistics ?. files ) {
36- const contributorName = commit . author . names [ 0 ] . trim ( ) ;
37- const contributorId = `${ contributorName } -${ commit . author . emails [ 0 ] } ` ;
38-
39- const folderChanges = new Map < string , { insertions : number ; deletions : number } > ( ) ;
40-
41- Object . entries ( commit . diffStatistics . files ) . forEach (
42- ( [ filePath , stats ] : [ string , { insertions : number ; deletions : number } ] ) => {
43- let folderPath : string ;
44-
45- if ( currentPath === "" ) {
46- folderPath = extractFolderFromPath ( filePath , 1 ) ;
47- } else if ( filePath . startsWith ( `${ currentPath } /` ) ) {
48- const relativePath = filePath . substring ( currentPath . length + 1 ) ;
49- const pathParts = relativePath . split ( "/" ) ;
50- folderPath = `${ currentPath } /${ pathParts [ 0 ] } ` ;
51- } else {
52- return ;
53- }
54-
55- if ( topFolders . some ( ( f ) => f . folderPath === folderPath ) ) {
56- if ( ! folderChanges . has ( folderPath ) ) {
57- folderChanges . set ( folderPath , { insertions : 0 , deletions : 0 } ) ;
58- }
59- const folder = folderChanges . get ( folderPath ) ;
60- if ( folder ) {
61- folder . insertions += stats . insertions ;
62- folder . deletions += stats . deletions ;
63- }
64- }
65- }
66- ) ;
67-
68- folderChanges . forEach ( ( stats , folderPath ) => {
69- contributorActivities . push ( {
70- contributorId,
71- contributorName,
72- date,
73- folderPath,
74- changes : stats . insertions + stats . deletions ,
75- insertions : stats . insertions ,
76- deletions : stats . deletions ,
77- clusterId,
78- clusterIndex,
79- } ) ;
80- } ) ;
81- }
82- }
83- } ) ;
84- } ) ;
85-
86- return contributorActivities . sort ( ( a , b ) => a . date . getTime ( ) - b . date . getTime ( ) ) ;
87- }
88-
89- // 플로우 라인 데이터 생성
90- export function generateFlowLineData ( contributorActivities : ContributorActivity [ ] ) : FlowLineData [ ] {
91- const activitiesByContributor = new Map < string , ContributorActivity [ ] > ( ) ;
92- contributorActivities . forEach ( ( activity ) => {
93- if ( ! activitiesByContributor . has ( activity . contributorId ) ) {
94- activitiesByContributor . set ( activity . contributorId , [ ] ) ;
95- }
96- const activities = activitiesByContributor . get ( activity . contributorId ) ;
97- if ( activities ) {
98- activities . push ( activity ) ;
99- }
100- } ) ;
101-
102- const flowLineData : FlowLineData [ ] = [ ] ;
103-
104- activitiesByContributor . forEach ( ( activities ) => {
105- activities . sort ( ( a , b ) => a . clusterIndex - b . clusterIndex || a . date . getTime ( ) - b . date . getTime ( ) ) ;
106-
107- for ( let i = 0 ; i < activities . length - 1 ; i += 1 ) {
108- const current = activities [ i ] ;
109- const next = activities [ i + 1 ] ;
110-
111- if ( current . clusterIndex !== next . clusterIndex ) {
112- flowLineData . push ( {
113- startClusterIndex : current . clusterIndex ,
114- startFolder : current . folderPath ,
115- endClusterIndex : next . clusterIndex ,
116- endFolder : next . folderPath ,
117- contributorName : current . contributorName ,
118- } ) ;
119- }
120- }
121- } ) ;
122-
123- return flowLineData ;
124- }
125-
126- // 클러스터 내 노드 위치 계산
127- export function calculateNodePosition (
128- activity : ContributorActivity ,
129- xScale : d3 . ScaleBand < string > ,
130- activitiesByCluster : Map < number , ContributorActivity [ ] >
131- ) : number {
132- const clusterX = ( xScale ( String ( activity . clusterIndex ) ) || 0 ) + xScale . bandwidth ( ) / 2 ;
133- const clusterActivities = activitiesByCluster . get ( activity . clusterIndex ) || [ ] ;
134- const activityIndex = clusterActivities . findIndex (
135- ( a ) =>
136- a . contributorId === activity . contributorId &&
137- a . folderPath === activity . folderPath &&
138- a . date . getTime ( ) === activity . date . getTime ( )
139- ) ;
140- const offsetRange = xScale . bandwidth ( ) * 0.8 ;
141- const offset =
142- ( activityIndex - ( clusterActivities . length - 1 ) / 2 ) * ( offsetRange / Math . max ( clusterActivities . length , 1 ) ) ;
143- return clusterX + offset ;
144- }
145-
146- // 첫 번째 기여자 노드 찾기
147- export function findFirstContributorNodes (
148- contributorActivities : ContributorActivity [ ]
149- ) : Map < string , ContributorActivity > {
150- const firstNodesByContributor = new Map < string , ContributorActivity > ( ) ;
151- const sortedActivities = [ ...contributorActivities ] . sort (
152- ( a , b ) => a . clusterIndex - b . clusterIndex || a . date . getTime ( ) - b . date . getTime ( )
153- ) ;
154-
155- sortedActivities . forEach ( ( activity ) => {
156- const key = activity . contributorId ;
157- if ( ! firstNodesByContributor . has ( key ) ) {
158- firstNodesByContributor . set ( key , activity ) ;
159- }
160- } ) ;
161-
162- return firstNodesByContributor ;
163- }
164-
165- // 플로우 라인 경로 생성
166- export function generateFlowLinePath (
167- d : FlowLineData ,
168- xScale : d3 . ScaleBand < string > ,
169- yScale : d3 . ScaleBand < string >
170- ) : string {
171- const x1 = ( xScale ( String ( d . startClusterIndex ) ) || 0 ) + xScale . bandwidth ( ) / 2 ;
172- const y1 = ( yScale ( d . startFolder ) || 0 ) + yScale . bandwidth ( ) / 2 ;
173- const x2 = ( xScale ( String ( d . endClusterIndex ) ) || 0 ) + xScale . bandwidth ( ) / 2 ;
174- const y2 = ( yScale ( d . endFolder ) || 0 ) + yScale . bandwidth ( ) / 2 ;
175- const midX = ( x1 + x2 ) / 2 ;
176- return `M ${ x1 } ,${ y1 } Q ${ midX } ,${ y1 } ${ midX } ,${ ( y1 + y2 ) / 2 } Q ${ midX } ,${ y2 } ${ x2 } ,${ y2 } ` ;
177- }
178-
179- // === ReleaseTags 기반 유틸리티 함수들 ===
11+ import type { ReleaseContributorActivity , ReleaseFlowLineData } from "./FolderActivityFlow.type" ;
18012
18113// 릴리즈 기반 상위 폴더 분석
18214export function analyzeReleaseBasedFolders (
0 commit comments