feat(lineage): improve lineage re-focus experience (#2349)

This commit is contained in:
Gabe Lyons 2021-04-06 18:21:33 -07:00 committed by GitHub
parent fb559cba5e
commit f51340410f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 179 additions and 142 deletions

View File

@ -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(

View File

@ -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;

View File

@ -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>
);

View 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>
</>
);
}

View File

@ -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>
)}