diff --git a/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.const.ts b/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.const.ts index 1f2990d1..287e25b7 100644 --- a/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.const.ts +++ b/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.const.ts @@ -1,5 +1,5 @@ export const NODE_GAP = 20; -export const COMMIT_HEIGHT = 50; +export const CLUSTER_HEIGHT = 50; export const GRAPH_WIDTH = 100; export const SVG_WIDTH = GRAPH_WIDTH + 4; export const DETAIL_HEIGHT = 300; diff --git a/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.scss b/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.scss index 56fd667f..e12d06a8 100644 --- a/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.scss +++ b/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.scss @@ -1,18 +1,18 @@ -.cluster-container { - .cluster-box { +.cluster-graph_container { + .cluster-graph_cluster { rx: 5; stroke-width: 1; stroke: rgb(13, 71, 161, 0.4); fill: transparent; } - .degree-box { + .cluster-graph_degree { rx: 5; fill: rgb(13, 71, 161, 0.4); } &:hover { - .cluster-box { + .cluster-graph_cluster { stroke-width: 3; } } diff --git a/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.tsx b/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.tsx index b134959e..d6467653 100644 --- a/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.tsx +++ b/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.tsx @@ -1,4 +1,4 @@ -import type { MouseEvent, RefObject } from "react"; +import type { RefObject } from "react"; import React, { useEffect, useRef } from "react"; import * as d3 from "d3"; @@ -8,13 +8,16 @@ import "./ClusterGraph.scss"; import { selectedDataUpdater } from "../VerticalClusterList.util"; -import { getGraphHeight, getClusterSizes } from "./ClusterGraph.util"; import { - COMMIT_HEIGHT, + getGraphHeight, + getClusterSizes, + getSelectedIndex, + getClusterPosition, +} from "./ClusterGraph.util"; +import { + CLUSTER_HEIGHT, DETAIL_HEIGHT, GRAPH_WIDTH, - NODE_GAP, - SVG_MARGIN, SVG_WIDTH, } from "./ClusterGraph.const"; import type { @@ -25,66 +28,43 @@ import type { const drawClusterBox = (container: SVGElementSelection) => { container .append("rect") - .attr("class", "cluster-box ") + .attr("class", "cluster-graph_cluster") .attr("width", GRAPH_WIDTH) - .attr("height", COMMIT_HEIGHT) - .attr("x", SVG_MARGIN.left) - .attr("y", (d, i, prev) => - i === 0 - ? SVG_MARGIN.top - : prev[i - 1].y.baseVal.value + - prev[i - 1].height.baseVal.value + - NODE_GAP + - (d.selected === d.cluster.commitNodeList[0].clusterId - ? DETAIL_HEIGHT - : 0) - ); + .attr("height", CLUSTER_HEIGHT); }; const drawDegreeBox = (container: SVGElementSelection) => { const widthScale = d3.scaleLinear().range([0, GRAPH_WIDTH]).domain([0, 10]); container .append("rect") - .attr("class", "degree-box") + .attr("class", "cluster-graph_degree") .attr("width", (d) => widthScale(Math.min(d.clusterSize, 10))) - .attr("height", COMMIT_HEIGHT) + .attr("height", CLUSTER_HEIGHT) .attr( "x", - (d) => - SVG_MARGIN.left + - GRAPH_WIDTH / 2 - - widthScale(Math.min(d.clusterSize, 10)) / 2 - ) - .attr("y", (d, i, prev) => - i === 0 - ? SVG_MARGIN.top - : prev[i - 1].y.baseVal.value + - prev[i - 1].height.baseVal.value + - NODE_GAP + - (d.selected === d.cluster.commitNodeList[0].clusterId - ? DETAIL_HEIGHT - : 0) + (d) => (GRAPH_WIDTH - widthScale(Math.min(d.clusterSize, 10))) / 2 ); }; const drawClusterGraph = ( svgRef: RefObject, data: ClusterGraphElement[], - onClickCluster: ( - this: SVGGElement, - event: MouseEvent, - d: ClusterGraphElement - ) => void + onClickCluster: (_: PointerEvent, d: ClusterGraphElement) => void ) => { - console.log(data); const group = d3 .select(svgRef.current) - .selectAll(".cluster-container") + .selectAll(".cluster-graph_container") .data(data) - .enter() - .append("g") - .attr("class", "cluster-container") - .on("click", onClickCluster); + .join("g") + .on("click", onClickCluster) + .attr("class", "cluster-graph_container") + .attr("transform", (d, i) => getClusterPosition(d, i, true)); + group + .transition() + .duration(300) + .ease(d3.easeLinear) + .attr("transform", (d, i) => getClusterPosition(d, i)); + drawClusterBox(group); drawDegreeBox(group); }; @@ -98,16 +78,6 @@ type ClusterGraphProps = { setSelectedData: React.Dispatch>; }; -const getSelectedNextId = ( - data: ClusterNode[], - selectedData: SelectedDataProps -) => { - const selectedId = selectedData?.commitNodeList[0].clusterId; - const selectedNextId = - data.findIndex((item) => item.commitNodeList[0].clusterId === selectedId) + - 1; - return data[selectedNextId]?.commitNodeList[0]?.clusterId; -}; const ClusterGraph = ({ data, selectedData, @@ -115,25 +85,27 @@ const ClusterGraph = ({ }: ClusterGraphProps) => { const svgRef = useRef(null); const clusterSizes = getClusterSizes(data); - const graphHeight = getGraphHeight(clusterSizes); - const selectedNextId = getSelectedNextId(data, selectedData); + const selectedIndex = getSelectedIndex(data, selectedData); + const graphHeight = + getGraphHeight(clusterSizes) + (selectedIndex < 0 ? 0 : DETAIL_HEIGHT); const clusterGraphElements = data.map((cluster, i) => ({ cluster, clusterSize: clusterSizes[i], - selected: selectedNextId, + selected: selectedIndex, })); useEffect(() => { - const handleClickCluster = (_: MouseEvent, d: ClusterGraphElement) => + const handleClickCluster = (_: PointerEvent, d: ClusterGraphElement) => { setSelectedData( selectedDataUpdater(d.cluster, d.cluster.commitNodeList[0].clusterId) ); + }; drawClusterGraph(svgRef, clusterGraphElements, handleClickCluster); return () => { destroyClusterGraph(svgRef); }; - }, [clusterGraphElements, setSelectedData]); + }, [clusterGraphElements, selectedIndex, setSelectedData]); return ; }; diff --git a/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.type.ts b/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.type.ts index 2e8b0455..88023561 100644 --- a/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.type.ts +++ b/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.type.ts @@ -9,7 +9,7 @@ export type ClusterGraphElement = { }; export type SVGElementSelection = Selection< - T, + T | BaseType, ClusterGraphElement, SVGSVGElement | null, unknown diff --git a/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.util.ts b/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.util.ts index e627eccd..815699c9 100644 --- a/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.util.ts +++ b/packages/view/src/components/VerticalClusterList/ClusterGraph/ClusterGraph.util.ts @@ -1,6 +1,12 @@ -import type { ClusterNode } from "types"; +import type { ClusterNode, SelectedDataProps } from "types"; -import { COMMIT_HEIGHT, NODE_GAP } from "./ClusterGraph.const"; +import { + CLUSTER_HEIGHT, + DETAIL_HEIGHT, + NODE_GAP, + SVG_MARGIN, +} from "./ClusterGraph.const"; +import type { ClusterGraphElement } from "./ClusterGraph.type"; export function getClusterSizes(data: ClusterNode[]) { return data.map((node) => node.commitNodeList.length); @@ -8,8 +14,32 @@ export function getClusterSizes(data: ClusterNode[]) { export function getGraphHeight(clusterSizes: number[]) { return ( - clusterSizes.length * COMMIT_HEIGHT + + clusterSizes.length * CLUSTER_HEIGHT + clusterSizes.length * NODE_GAP + NODE_GAP ); } + +export function getClusterPosition( + d: ClusterGraphElement, + i: number, + isPrev = false +) { + const curSelected = d.selected || Infinity; + const selected = isPrev ? Infinity : curSelected; + const margin = selected >= 0 && selected < i ? DETAIL_HEIGHT : 0; + const x = SVG_MARGIN.left; + const y = SVG_MARGIN.top + i * (CLUSTER_HEIGHT + NODE_GAP) + margin; + return `translate(${x}, ${y})`; +} + +export function getSelectedIndex( + data: ClusterNode[], + selectedData: SelectedDataProps +) { + const selectedId = selectedData?.commitNodeList[0].clusterId; + const selectedIndex = data.findIndex( + (item) => item.commitNodeList[0].clusterId === selectedId + ); + return selectedIndex; +} diff --git a/packages/view/src/components/VerticalClusterList/Summary/Summary.scss b/packages/view/src/components/VerticalClusterList/Summary/Summary.scss index cc247ad3..a087959f 100644 --- a/packages/view/src/components/VerticalClusterList/Summary/Summary.scss +++ b/packages/view/src/components/VerticalClusterList/Summary/Summary.scss @@ -18,6 +18,16 @@ border-radius: $border--radius; } +@mixin animate($animation, $duration, $method, $times) { + animation: $animation $duration $method $times; +} + +@mixin keyframes($name) { + @keyframes #{$name} { + @content; + } +} + .summary__entire { width: 85%; margin-left: 20px; @@ -158,9 +168,19 @@ .summary_detail_container { height: 280px; margin-top: 20px; - overflow: auto; + overflow: scroll; &::-webkit-scrollbar { display: none; /* Chrome, Safari, Opera*/ } + + @include keyframes(open_detail) { + 0% { + height: 0; + } + 100% { + height: 280px; + } + } + @include animate(open_detail, 0.3s, linear, 1); }