diff --git a/packages/view/src/components/Statistics/AuthorBarChart/AuthorBarChart.tsx b/packages/view/src/components/Statistics/AuthorBarChart/AuthorBarChart.tsx index b1aae6d9..8d2b9298 100644 --- a/packages/view/src/components/Statistics/AuthorBarChart/AuthorBarChart.tsx +++ b/packages/view/src/components/Statistics/AuthorBarChart/AuthorBarChart.tsx @@ -16,23 +16,35 @@ import "./AuthorBarChart.scss"; const AuthorBarChart = () => { const { data: totalData, filteredData, setSelectedData, setFilteredData } = useGlobalData(); - const rawData = useGetSelectedData(); + const rawData = useGetSelectedData(); const svgRef = useRef(null); const tooltipRef = useRef(null); const [metric, setMetric] = useState(METRIC_TYPE[0]); - const [prevData, setPrevData] = useState([]); - + const [prevData, setPrevData] = useState([]); + const [selectedAuthor, setSelectedAuthor] = useState(""); const authorData = getDataByAuthor(rawData as ClusterNode[]); + let data = authorData.sort((a, b) => { if (a[metric] === b[metric]) { return sortDataByName(a.name, b.name); } return b[metric] - a[metric]; }); + if (data.length > 10) { - data = data.slice(0, 10); + const topAuthors = data.slice(0, 9); + const otherAuthors = data.slice(9); + const reducedOtherAuthors = otherAuthors.reduce( + (acc, cur) => { + acc[metric] += cur[metric]; + acc.names?.push(cur.name); + return acc; + }, + { name: "others", commit: 0, insertion: 0, deletion: 0, names: [] } as AuthorDataType + ); + data = [...topAuthors, reducedOtherAuthors]; } useEffect(() => { @@ -82,7 +94,7 @@ const AuthorBarChart = () => { tooltip .style("display", "inline-block") .style("left", `${e.pageX - 70}px`) - .style("top", `${e.pageY - 90}px`) + .style("top", `${e.pageY - 120}px`) .html( `

${d.name}

${metric}: @@ -96,22 +108,44 @@ const AuthorBarChart = () => { }; const handleMouseMove = (e: MouseEvent) => { - tooltip.style("left", `${e.pageX - 70}px`).style("top", `${e.pageY - 90}px`); + tooltip.style("left", `${e.pageX - 70}px`).style("top", `${e.pageY - 120}px`); }; const handleMouseOut = () => { tooltip.style("display", "none"); }; + const handleClickBar = (_: MouseEvent, d: AuthorDataType) => { - const isAuthorSelected = !!prevData.length; + const isAuthorSelected = selectedAuthor === d.name && d.name !== "others"; + + const getNewFilteredData = (names: string[]) => { + return sortDataByAuthor(totalData, names); + }; if (isAuthorSelected) { - setFilteredData(prevData); - setPrevData([]); - } else { - setFilteredData(sortDataByAuthor(filteredData, d.name)); - setPrevData(filteredData); + // 현재 선택된 사용자를 다시 클릭하면 이전 데이터로 복원 + const newFilteredData = prevData.length > 0 ? prevData.pop() : filteredData; + setFilteredData(newFilteredData ?? filteredData); + setPrevData([...prevData]); + setSelectedAuthor(""); + setSelectedData([]); + tooltip.style("display", "none"); + return; + } + + if (d.name === "others") { + // "others" 바를 클릭할 때 + setPrevData([...prevData, filteredData]); + setFilteredData(getNewFilteredData(d.names || [])); + setSelectedAuthor(d.name); + setSelectedData([]); + tooltip.style("display", "none"); + return; } + // 특정 사용자를 클릭할 때 + setPrevData([...prevData, filteredData]); + setFilteredData(getNewFilteredData([d.name])); + setSelectedAuthor(d.name); setSelectedData([]); tooltip.style("display", "none"); }; @@ -160,7 +194,18 @@ const AuthorBarChart = () => { .attr("width", 14) .attr("height", 14); }); - }, [data, filteredData, metric, prevData, rawData, setFilteredData, setSelectedData, totalData]); + }, [ + data, + filteredData, + metric, + prevData, + rawData, + setFilteredData, + setSelectedData, + totalData, + selectedAuthor, + setSelectedAuthor, + ]); const handleChangeMetric = (e: ChangeEvent): void => { setMetric(e.target.value as MetricType); diff --git a/packages/view/src/components/Statistics/AuthorBarChart/AuthorBarChart.type.ts b/packages/view/src/components/Statistics/AuthorBarChart/AuthorBarChart.type.ts index ec0ba232..2dfd54b8 100644 --- a/packages/view/src/components/Statistics/AuthorBarChart/AuthorBarChart.type.ts +++ b/packages/view/src/components/Statistics/AuthorBarChart/AuthorBarChart.type.ts @@ -5,6 +5,7 @@ export type AuthorDataType = { commit: number; insertion: number; deletion: number; + names?: string[]; }; export type MetricType = (typeof METRIC_TYPE)[number]; diff --git a/packages/view/src/components/Statistics/AuthorBarChart/AuthorBarChart.util.ts b/packages/view/src/components/Statistics/AuthorBarChart/AuthorBarChart.util.ts index a371abb0..11bb3edc 100644 --- a/packages/view/src/components/Statistics/AuthorBarChart/AuthorBarChart.util.ts +++ b/packages/view/src/components/Statistics/AuthorBarChart/AuthorBarChart.util.ts @@ -51,12 +51,16 @@ export const convertNumberFormat = (d: number | { valueOf(): number }): string = return d3.format("~s")(d); }; -export const sortDataByAuthor = (data: ClusterNode[], author: string): ClusterNode[] => { +export const sortDataByAuthor = (data: ClusterNode[], names: string[]): ClusterNode[] => { return data.reduce((acc: ClusterNode[], cluster: ClusterNode) => { const checkedCluster = cluster.commitNodeList.filter((commitNode: CommitNode) => - commitNode.commit.author.names.includes(author) + names.some((name) => commitNode.commit.author.names.includes(name)) ); - if (!checkedCluster.length) return acc; - return [...acc, { nodeTypeName: "CLUSTER" as const, commitNodeList: checkedCluster }]; + + if (checkedCluster.length > 0) { + acc.push({ nodeTypeName: "CLUSTER", commitNodeList: checkedCluster }); + } + + return acc; }, []); };