mirror of
https://github.com/datahub-project/datahub.git
synced 2025-12-25 00:48:45 +00:00
feat(lineage): improve lineage re-focus experience (#2349)
This commit is contained in:
parent
fb559cba5e
commit
f51340410f
@ -2,6 +2,7 @@ import { HierarchyNode } from '@vx/hierarchy/lib/types';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import debounce from 'lodash.debounce';
|
||||
import { Tree } from '@vx/hierarchy';
|
||||
import { TransformMatrix } from '@vx/zoom/lib/types';
|
||||
|
||||
import { NodeData, Direction, EntitySelectParams, TreeProps } from './types';
|
||||
import LineageTreeNodeAndEdgeRenderer from './LineageTreeNodeAndEdgeRenderer';
|
||||
@ -9,14 +10,7 @@ import LineageTreeNodeAndEdgeRenderer from './LineageTreeNodeAndEdgeRenderer';
|
||||
type LineageTreeProps = {
|
||||
data: HierarchyNode<NodeData>;
|
||||
zoom: {
|
||||
transformMatrix: {
|
||||
scaleX: number;
|
||||
scaleY: number;
|
||||
translateX: number;
|
||||
translateY: number;
|
||||
skewX: number;
|
||||
skewY: number;
|
||||
};
|
||||
transformMatrix: TransformMatrix;
|
||||
};
|
||||
canvasHeight: number;
|
||||
canvasWidth: number;
|
||||
@ -41,6 +35,11 @@ export default function LineageTree({
|
||||
const [xCanvasScale, setXCanvasScale] = useState(1);
|
||||
const [yCanvasScale, setYCanvasScale] = useState(1);
|
||||
|
||||
useEffect(() => {
|
||||
setXCanvasScale(1);
|
||||
setYCanvasScale(1);
|
||||
}, [data.data.urn]);
|
||||
|
||||
// Need to disable exhaustive-deps because react has trouble introspecting the debounce call's dependencies
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const debouncedSetYCanvasScale = useCallback(
|
||||
|
||||
@ -2,6 +2,7 @@ import { HierarchyPointNode } from '@vx/hierarchy/lib/types';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Group } from '@vx/group';
|
||||
import { LinkHorizontal } from '@vx/shape';
|
||||
import { TransformMatrix } from '@vx/zoom/lib/types';
|
||||
|
||||
import { NodeData, Direction, EntitySelectParams, TreeProps } from './types';
|
||||
import LineageEntityNode from './LineageEntityNode';
|
||||
@ -10,14 +11,7 @@ import adjustVXTreeLayout from './utils/adjustVXTreeLayout';
|
||||
type Props = {
|
||||
tree: HierarchyPointNode<NodeData>;
|
||||
zoom: {
|
||||
transformMatrix: {
|
||||
scaleX: number;
|
||||
scaleY: number;
|
||||
translateX: number;
|
||||
translateY: number;
|
||||
skewX: number;
|
||||
skewY: number;
|
||||
};
|
||||
transformMatrix: TransformMatrix;
|
||||
};
|
||||
canvasHeight: number;
|
||||
onEntityClick: (EntitySelectParams) => void;
|
||||
|
||||
@ -1,33 +1,12 @@
|
||||
import { hierarchy } from '@vx/hierarchy';
|
||||
import React, { SVGProps, useMemo } from 'react';
|
||||
import React from 'react';
|
||||
import { useWindowSize } from '@react-hook/window-size';
|
||||
import { Zoom } from '@vx/zoom';
|
||||
import styled from 'styled-components';
|
||||
import { Button, Card } from 'antd';
|
||||
import { PlusOutlined, MinusOutlined } from '@ant-design/icons';
|
||||
|
||||
import { Direction, TreeProps } from './types';
|
||||
import constructTree from './utils/constructTree';
|
||||
import LineageTree from './LineageTree';
|
||||
import { TreeProps } from './types';
|
||||
import LineageVizInsideZoom from './LineageVizInsideZoom';
|
||||
|
||||
export const defaultMargin = { top: 10, left: 280, right: 280, bottom: 10 };
|
||||
|
||||
const ZoomCard = styled(Card)`
|
||||
position: absolute;
|
||||
box-shadow: 4px 4px 4px -1px grey;
|
||||
top: 145px;
|
||||
right: 20px;
|
||||
`;
|
||||
|
||||
const ZoomButton = styled(Button)`
|
||||
display: block;
|
||||
margin-bottom: 12px;
|
||||
`;
|
||||
|
||||
const RootSvg = styled.svg<{ isDragging: boolean } & SVGProps<SVGSVGElement>>`
|
||||
cursor: ${(props) => (props.isDragging ? 'grabbing' : 'grab')};
|
||||
`;
|
||||
|
||||
export default function LineageViz({
|
||||
margin = defaultMargin,
|
||||
dataset,
|
||||
@ -36,21 +15,10 @@ export default function LineageViz({
|
||||
onLineageExpand,
|
||||
selectedEntity,
|
||||
}: TreeProps) {
|
||||
const downstreamData = useMemo(() => hierarchy(constructTree(dataset, fetchedEntities, Direction.Downstream)), [
|
||||
dataset,
|
||||
fetchedEntities,
|
||||
]);
|
||||
const upstreamData = useMemo(() => hierarchy(constructTree(dataset, fetchedEntities, Direction.Upstream)), [
|
||||
dataset,
|
||||
fetchedEntities,
|
||||
]);
|
||||
|
||||
const [windowWidth, windowHeight] = useWindowSize();
|
||||
|
||||
const height = windowHeight - 133;
|
||||
const width = windowWidth;
|
||||
const yMax = height - margin.top - margin.bottom;
|
||||
const xMax = (width - margin.left - margin.right) / 2;
|
||||
const initialTransform = {
|
||||
scaleX: 2 / 3,
|
||||
scaleY: 2 / 3,
|
||||
@ -70,85 +38,17 @@ export default function LineageViz({
|
||||
transformMatrix={initialTransform}
|
||||
>
|
||||
{(zoom) => (
|
||||
<>
|
||||
<ZoomCard size="small">
|
||||
<ZoomButton onClick={() => zoom.scale({ scaleX: 1.2, scaleY: 1.2 })}>
|
||||
<PlusOutlined />
|
||||
</ZoomButton>
|
||||
<Button onClick={() => zoom.scale({ scaleX: 0.8, scaleY: 0.8 })}>
|
||||
<MinusOutlined />
|
||||
</Button>
|
||||
</ZoomCard>
|
||||
<RootSvg
|
||||
width={width}
|
||||
height={height}
|
||||
onMouseDown={zoom.dragStart}
|
||||
onMouseUp={zoom.dragEnd}
|
||||
onMouseMove={zoom.dragMove}
|
||||
onTouchStart={zoom.dragStart}
|
||||
onTouchMove={zoom.dragMove}
|
||||
onTouchEnd={zoom.dragEnd}
|
||||
isDragging={zoom.isDragging}
|
||||
>
|
||||
<defs>
|
||||
<marker
|
||||
id="triangle-downstream"
|
||||
viewBox="0 0 10 10"
|
||||
refX="10"
|
||||
refY="5"
|
||||
markerUnits="strokeWidth"
|
||||
markerWidth="10"
|
||||
markerHeight="10"
|
||||
orient="auto"
|
||||
>
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#000" />
|
||||
</marker>
|
||||
<marker
|
||||
id="triangle-upstream"
|
||||
viewBox="0 0 10 10"
|
||||
refX="0"
|
||||
refY="5"
|
||||
markerUnits="strokeWidth"
|
||||
markerWidth="10"
|
||||
markerHeight="10"
|
||||
orient="auto"
|
||||
>
|
||||
<path d="M 0 5 L 10 10 L 10 0 L 0 5 z" fill="#000" />
|
||||
</marker>
|
||||
<linearGradient id="gradient-Downstream" x1="1" x2="0" y1="0" y2="0">
|
||||
<stop offset="0%" stopColor="black" />
|
||||
<stop offset="100%" stopColor="black" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient id="gradient-Upstream" x1="0" x2="1" y1="0" y2="0">
|
||||
<stop offset="0%" stopColor="black" />
|
||||
<stop offset="100%" stopColor="black" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width={width} height={height} fill="#f6f8fa" />
|
||||
<LineageTree
|
||||
data={upstreamData}
|
||||
zoom={zoom}
|
||||
onEntityClick={onEntityClick}
|
||||
onLineageExpand={onLineageExpand}
|
||||
canvasHeight={yMax}
|
||||
canvasWidth={xMax}
|
||||
margin={margin}
|
||||
selectedEntity={selectedEntity}
|
||||
direction={Direction.Upstream}
|
||||
/>
|
||||
<LineageTree
|
||||
data={downstreamData}
|
||||
zoom={zoom}
|
||||
onEntityClick={onEntityClick}
|
||||
onLineageExpand={onLineageExpand}
|
||||
canvasHeight={yMax}
|
||||
canvasWidth={xMax}
|
||||
margin={margin}
|
||||
selectedEntity={selectedEntity}
|
||||
direction={Direction.Downstream}
|
||||
/>
|
||||
</RootSvg>
|
||||
</>
|
||||
<LineageVizInsideZoom
|
||||
dataset={dataset}
|
||||
width={width}
|
||||
height={height}
|
||||
margin={margin}
|
||||
onEntityClick={onEntityClick}
|
||||
onLineageExpand={onLineageExpand}
|
||||
selectedEntity={selectedEntity}
|
||||
zoom={zoom}
|
||||
fetchedEntities={fetchedEntities}
|
||||
/>
|
||||
)}
|
||||
</Zoom>
|
||||
);
|
||||
|
||||
152
datahub-web-react/src/app/lineage/LineageVizInsideZoom.tsx
Normal file
152
datahub-web-react/src/app/lineage/LineageVizInsideZoom.tsx
Normal file
@ -0,0 +1,152 @@
|
||||
import React, { SVGProps, useEffect, useMemo } from 'react';
|
||||
import { hierarchy } from '@vx/hierarchy';
|
||||
import { PlusOutlined, MinusOutlined } from '@ant-design/icons';
|
||||
import styled from 'styled-components';
|
||||
import { Button, Card } from 'antd';
|
||||
import { ProvidedZoom, TransformMatrix } from '@vx/zoom/lib/types';
|
||||
|
||||
import LineageTree from './LineageTree';
|
||||
import constructTree from './utils/constructTree';
|
||||
import { Direction, EntitySelectParams, FetchedEntity } from './types';
|
||||
import { GetDatasetQuery } from '../../graphql/dataset.generated';
|
||||
|
||||
const ZoomCard = styled(Card)`
|
||||
position: absolute;
|
||||
box-shadow: 4px 4px 4px -1px grey;
|
||||
top: 145px;
|
||||
right: 20px;
|
||||
`;
|
||||
|
||||
const ZoomButton = styled(Button)`
|
||||
display: block;
|
||||
margin-bottom: 12px;
|
||||
`;
|
||||
|
||||
const RootSvg = styled.svg<{ isDragging: boolean } & SVGProps<SVGSVGElement>>`
|
||||
cursor: ${(props) => (props.isDragging ? 'grabbing' : 'grab')};
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
margin: { top: number; right: number; bottom: number; left: number };
|
||||
dataset: GetDatasetQuery['dataset'];
|
||||
fetchedEntities: { [x: string]: FetchedEntity };
|
||||
onEntityClick: (EntitySelectParams) => void;
|
||||
onLineageExpand: (LineageExpandParams) => void;
|
||||
selectedEntity?: EntitySelectParams;
|
||||
zoom: ProvidedZoom & {
|
||||
transformMatrix: TransformMatrix;
|
||||
isDragging: boolean;
|
||||
};
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
export default function LineageVizInsideZoom({
|
||||
zoom,
|
||||
margin,
|
||||
dataset,
|
||||
fetchedEntities,
|
||||
onEntityClick,
|
||||
onLineageExpand,
|
||||
selectedEntity,
|
||||
width,
|
||||
height,
|
||||
}: Props) {
|
||||
const yMax = height - margin?.top - margin?.bottom;
|
||||
const xMax = (width - margin?.left - margin?.right) / 2;
|
||||
|
||||
const downstreamData = useMemo(() => hierarchy(constructTree(dataset, fetchedEntities, Direction.Downstream)), [
|
||||
dataset,
|
||||
fetchedEntities,
|
||||
]);
|
||||
const upstreamData = useMemo(() => hierarchy(constructTree(dataset, fetchedEntities, Direction.Upstream)), [
|
||||
dataset,
|
||||
fetchedEntities,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
zoom.setTransformMatrix({ ...zoom.transformMatrix, translateY: 0, translateX: width / 2 });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dataset?.urn]);
|
||||
return (
|
||||
<>
|
||||
<ZoomCard size="small">
|
||||
<ZoomButton onClick={() => zoom.scale({ scaleX: 1.2, scaleY: 1.2 })}>
|
||||
<PlusOutlined />
|
||||
</ZoomButton>
|
||||
<Button onClick={() => zoom.scale({ scaleX: 0.8, scaleY: 0.8 })}>
|
||||
<MinusOutlined />
|
||||
</Button>
|
||||
</ZoomCard>
|
||||
<RootSvg
|
||||
width={width}
|
||||
height={height}
|
||||
onMouseDown={zoom.dragStart}
|
||||
onMouseUp={zoom.dragEnd}
|
||||
onMouseMove={zoom.dragMove}
|
||||
onTouchStart={zoom.dragStart}
|
||||
onTouchMove={zoom.dragMove}
|
||||
onTouchEnd={zoom.dragEnd}
|
||||
isDragging={zoom.isDragging}
|
||||
>
|
||||
<defs>
|
||||
<marker
|
||||
id="triangle-downstream"
|
||||
viewBox="0 0 10 10"
|
||||
refX="10"
|
||||
refY="5"
|
||||
markerUnits="strokeWidth"
|
||||
markerWidth="10"
|
||||
markerHeight="10"
|
||||
orient="auto"
|
||||
>
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#000" />
|
||||
</marker>
|
||||
<marker
|
||||
id="triangle-upstream"
|
||||
viewBox="0 0 10 10"
|
||||
refX="0"
|
||||
refY="5"
|
||||
markerUnits="strokeWidth"
|
||||
markerWidth="10"
|
||||
markerHeight="10"
|
||||
orient="auto"
|
||||
>
|
||||
<path d="M 0 5 L 10 10 L 10 0 L 0 5 z" fill="#000" />
|
||||
</marker>
|
||||
<linearGradient id="gradient-Downstream" x1="1" x2="0" y1="0" y2="0">
|
||||
<stop offset="0%" stopColor="black" />
|
||||
<stop offset="100%" stopColor="black" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient id="gradient-Upstream" x1="0" x2="1" y1="0" y2="0">
|
||||
<stop offset="0%" stopColor="black" />
|
||||
<stop offset="100%" stopColor="black" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width={width} height={height} fill="#f6f8fa" />
|
||||
<LineageTree
|
||||
data={upstreamData}
|
||||
zoom={zoom}
|
||||
onEntityClick={onEntityClick}
|
||||
onLineageExpand={onLineageExpand}
|
||||
canvasHeight={yMax}
|
||||
canvasWidth={xMax}
|
||||
margin={margin}
|
||||
selectedEntity={selectedEntity}
|
||||
direction={Direction.Upstream}
|
||||
/>
|
||||
<LineageTree
|
||||
data={downstreamData}
|
||||
zoom={zoom}
|
||||
onEntityClick={onEntityClick}
|
||||
onLineageExpand={onLineageExpand}
|
||||
canvasHeight={yMax}
|
||||
canvasWidth={xMax}
|
||||
margin={margin}
|
||||
selectedEntity={selectedEntity}
|
||||
direction={Direction.Downstream}
|
||||
/>
|
||||
</RootSvg>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -3,6 +3,7 @@ import * as React from 'react';
|
||||
import { Col, Row, Divider, Layout, Card, Typography } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
import { TagOutlined } from '@ant-design/icons';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { RoutedTabs } from './RoutedTabs';
|
||||
import CompactContext from './CompactContext';
|
||||
@ -34,10 +35,6 @@ const TagIcon = styled(TagOutlined)`
|
||||
padding-right: 6px;
|
||||
`;
|
||||
|
||||
const ClickableTitle = styled.h1`
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
type LayoutProps = {
|
||||
isCompact: boolean;
|
||||
};
|
||||
@ -68,14 +65,9 @@ export const EntityProfile = ({ title, tags, header, tabs, titleLink }: EntityPr
|
||||
<Row style={{ padding: '20px 0px 10px 0px' }}>
|
||||
<Col span={24}>
|
||||
{titleLink ? (
|
||||
/* eslint-disable-next-line */
|
||||
<ClickableTitle
|
||||
onClick={() => {
|
||||
window.location.replace(titleLink);
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</ClickableTitle>
|
||||
<Link to={titleLink}>
|
||||
<h1>{title}</h1>
|
||||
</Link>
|
||||
) : (
|
||||
<h1>{title}</h1>
|
||||
)}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user