Skip to content

Commit

Permalink
Adjust graph zoom based on selected task (#32792)
Browse files Browse the repository at this point in the history
* Center to node on task selection

* Use new fitView options from reactflow

* Maintain zoom, remove focusNode fn
  • Loading branch information
bbovenzi committed Jul 26, 2023
1 parent 579ce06 commit 3237cb3
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 118 deletions.
2 changes: 1 addition & 1 deletion airflow/www/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@
"react-syntax-highlighter": "^15.5.0",
"react-table": "^7.8.0",
"react-textarea-autosize": "^8.3.4",
"reactflow": "^11.4.0",
"reactflow": "^11.7.4",
"redoc": "^2.0.0-rc.72",
"remark-gfm": "^3.0.1",
"swagger-ui-dist": "4.1.3",
Expand Down
99 changes: 38 additions & 61 deletions airflow/www/static/js/dag/details/graph/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

import React, { useRef, useState, useEffect } from "react";
import React, { useRef, useState, useEffect, useMemo } from "react";
import { Box, useTheme, Select, Text } from "@chakra-ui/react";
import ReactFlow, {
ReactFlowProvider,
Expand All @@ -26,18 +26,13 @@ import ReactFlow, {
MiniMap,
Node as ReactFlowNode,
useReactFlow,
ControlButton,
Panel,
} from "reactflow";
import { RiFocus3Line } from "react-icons/ri";

import { useGraphData, useGridData } from "src/api";
import useSelection from "src/dag/useSelection";
import { useOffsetTop } from "src/utils";
import { useGraphLayout } from "src/utils/graph";
import Tooltip from "src/components/Tooltip";
import { useContainerRef } from "src/context/containerRef";
import useFilters from "src/dag/useFilters";
import Edge from "src/components/Graph/Edge";

import Node, { CustomNodeProps } from "./Node";
Expand All @@ -54,13 +49,9 @@ interface Props {

const Graph = ({ openGroupIds, onToggleGroups, hoveredTaskState }: Props) => {
const graphRef = useRef(null);
const containerRef = useContainerRef();
const { data } = useGraphData();
const [arrange, setArrange] = useState(data?.arrange || "LR");

const {
filters: { root, filterDownstream, filterUpstream },
} = useFilters();
const [hasRendered, setHasRendered] = useState(false);

useEffect(() => {
setArrange(data?.arrange || "LR");
Expand All @@ -77,45 +68,47 @@ const Graph = ({ openGroupIds, onToggleGroups, hoveredTaskState }: Props) => {
data: { dagRuns, groups },
} = useGridData();
const { colors } = useTheme();
const { setCenter, setViewport } = useReactFlow();
const { getZoom, fitView } = useReactFlow();
const latestDagRunId = dagRuns[dagRuns.length - 1]?.runId;

// Reset viewport when tasks are filtered
useEffect(() => {
setViewport({ x: 0, y: 0, zoom: 1 });
}, [root, filterDownstream, filterUpstream, setViewport]);

const offsetTop = useOffsetTop(graphRef);

let nodes: ReactFlowNode<CustomNodeProps>[] = [];

if (graphData?.children) {
nodes = flattenNodes({
children: graphData.children,
const nodes: ReactFlowNode<CustomNodeProps>[] = useMemo(
() =>
graphData?.children
? flattenNodes({
children: graphData.children,
selected,
openGroupIds,
onToggleGroups,
latestDagRunId,
groups,
hoveredTaskState,
})
: [],
[
graphData?.children,
selected,
openGroupIds,
onToggleGroups,
latestDagRunId,
groups,
hoveredTaskState,
});
}
]
);

const focusNode = () => {
if (selected.taskId) {
const node = nodes.find((n) => n.id === selected.taskId);
const x = node?.positionAbsolute?.x || node?.position.x;
const y = node?.positionAbsolute?.y || node?.position.y;
if (!x || !y) return;
setCenter(
x + (node.data.width || 0) / 2,
y + (node.data.height || 0) / 2,
{
duration: 1000,
}
);
// Zoom to/from nodes when changing selection, maintain zoom level when changing task selection
useEffect(() => {
if (hasRendered) {
const zoom = getZoom();
fitView({
duration: 750,
nodes: selected.taskId ? [{ id: selected.taskId }] : undefined,
minZoom: selected.taskId ? zoom : undefined,
maxZoom: selected.taskId ? zoom : undefined,
});
}
};
setHasRendered(true);
}, [fitView, hasRendered, selected.taskId, getZoom]);

const edges = buildEdges({
edges: graphData?.edges,
Expand All @@ -141,6 +134,11 @@ const Graph = ({ openGroupIds, onToggleGroups, hoveredTaskState }: Props) => {
maxZoom={1}
onlyRenderVisibleElements
defaultEdgeOptions={{ zIndex: 1 }}
// Fit view to selected task or the whole graph on render
fitView
fitViewOptions={{
nodes: selected.taskId ? [{ id: selected.taskId }] : undefined,
}}
>
<Panel position="top-right">
<Box bg="#ffffffdd" p={1}>
Expand All @@ -157,28 +155,7 @@ const Graph = ({ openGroupIds, onToggleGroups, hoveredTaskState }: Props) => {
</Box>
</Panel>
<Background />
<Controls showInteractive={false}>
<ControlButton onClick={focusNode} disabled={!selected.taskId}>
<Tooltip
portalProps={{ containerRef }}
label="Center selected task"
placement="right"
>
<Box>
<RiFocus3Line
size={16}
style={{
// override react-flow css
maxWidth: "16px",
maxHeight: "16px",
color: colors.gray[800],
}}
aria-label="Center selected task"
/>
</Box>
</Tooltip>
</ControlButton>
</Controls>
<Controls showInteractive={false} />
<MiniMap
nodeStrokeWidth={15}
nodeStrokeColor={(props) => nodeStrokeColor(props, colors)}
Expand Down
6 changes: 3 additions & 3 deletions airflow/www/static/js/dag/details/graph/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,14 @@ export const flattenNodes = ({
};

export const nodeColor = ({
data: { height, width, instance, childCount, isActive },
data: { height, width, instance, isActive, isOpen },
}: ReactFlowNode<CustomNodeProps>) => {
let opacity = "90";
let color = "#cccccc";
if (!height || !width) return "";
if (instance?.state && !childCount)
if (instance?.state && !isOpen)
color = Color(stateColors[instance.state]).hex();
if (childCount) opacity = "50";
if (isOpen) opacity = "50";
if (!isActive) opacity = "21";

return `${color}${opacity}`;
Expand Down
108 changes: 55 additions & 53 deletions airflow/www/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1354,13 +1354,6 @@
dependencies:
regenerator-runtime "^0.13.4"

"@babel/runtime@^7.18.9":
version "7.20.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.6.tgz#facf4879bfed9b5326326273a64220f099b0fce3"
integrity sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==
dependencies:
regenerator-runtime "^0.13.11"

"@babel/runtime@^7.3.1":
version "7.21.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200"
Expand Down Expand Up @@ -2762,29 +2755,28 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.2.tgz#830beaec4b4091a9e9398ac50f865ddea52186b9"
integrity sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==

"@reactflow/background@11.1.0":
version "11.1.0"
resolved "https://registry.yarnpkg.com/@reactflow/background/-/background-11.1.0.tgz#943c799d9f251340e9867ad8f4c6ac291163e401"
integrity sha512-EgDn3rhK+l8jKmE6KGUZvesRjdh7fOqsz5Hj7STUU5/uGsvgN9KFuudY/Ka8m+yCQxqNK8MAJcRMOZd0mvNFMQ==
"@reactflow/background@11.2.4":
version "11.2.4"
resolved "https://registry.yarnpkg.com/@reactflow/background/-/background-11.2.4.tgz#06cd4c9f222dbeb2d3ffb2a530b6d88bf130dd9c"
integrity sha512-SYQbCRCU0GuxT/40Tm7ZK+l5wByGnNJSLtZhbL9C/Hl7JhsJXV3UGXr0vrlhVZUBEtkWA7XhZM/5S9XEA5XSFA==
dependencies:
"@babel/runtime" "^7.18.9"
"@reactflow/core" "11.4.0"
"@reactflow/core" "11.7.4"
classcat "^5.0.3"
zustand "^4.1.1"
zustand "^4.3.1"

"@reactflow/controls@11.1.0":
version "11.1.0"
resolved "https://registry.yarnpkg.com/@reactflow/controls/-/controls-11.1.0.tgz#6d6f6dd6e53557579c6cfcea3c7376d2d00c2953"
integrity sha512-5nH1TQ9mkveUOnq7QgohzeAdGR4WwKQJMrWjb5u3Dnm5D5+oRxTE3eGBoaw6B6nYaK1rDrPCcMAuGmEPdEC+Mg==
"@reactflow/controls@11.1.15":
version "11.1.15"
resolved "https://registry.yarnpkg.com/@reactflow/controls/-/controls-11.1.15.tgz#6dc823eb67f38a50907fffcc21b6a20e4fc00e7c"
integrity sha512-//33XfBYu8vQ6brfmlZwKrDoh+8hh93xO2d88XiqfIbrPEEb32SYjsb9mS9VuHKNlSIW+eB27fBA1Gt00mEj5w==
dependencies:
"@babel/runtime" "^7.18.9"
"@reactflow/core" "11.4.0"
"@reactflow/core" "11.7.4"
classcat "^5.0.3"
zustand "^4.3.1"

"@reactflow/core@11.4.0":
version "11.4.0"
resolved "https://registry.yarnpkg.com/@reactflow/core/-/core-11.4.0.tgz#9af0c812eb9968b75cf55427c6be4a9205d0db48"
integrity sha512-AfFp685kmxWs2Iiq35TatG9Q8u5W+eftXECQ0ea55Oi37nrMe5jfWhjnGnnl3bSFcHqAe6avqNiFDwqugU6kzQ==
"@reactflow/core@11.7.4", "@reactflow/core@^11.6.0":
version "11.7.4"
resolved "https://registry.yarnpkg.com/@reactflow/core/-/core-11.7.4.tgz#1a7e4d6cabbd2ea888547133d507f1ab24896520"
integrity sha512-nt0T8ERp8TE7YCDQViaoEY9lb0StDPrWHVx3zBjhStFYET3wc88t8QRasZdf99xRTmyNtI3U3M40M5EBLNUpMw==
dependencies:
"@types/d3" "^7.4.0"
"@types/d3-drag" "^3.0.1"
Expand All @@ -2794,31 +2786,40 @@
d3-drag "^3.0.0"
d3-selection "^3.0.0"
d3-zoom "^3.0.0"
zustand "^4.1.1"
zustand "^4.3.1"

"@reactflow/minimap@11.3.0":
version "11.3.0"
resolved "https://registry.yarnpkg.com/@reactflow/minimap/-/minimap-11.3.0.tgz#2f89dbab4c10b754c452f70857172d959cca60aa"
integrity sha512-nvb4qmbsogjhrn7GWXpvLMtmAyE7mjs0BXvtbpcFVpKqQ3Lbf76zCa8c2krUMnBBqu+9yF0Ftkn7mMCTV2gPLQ==
"@reactflow/minimap@11.5.4":
version "11.5.4"
resolved "https://registry.yarnpkg.com/@reactflow/minimap/-/minimap-11.5.4.tgz#b072094f7d827660f0205796d5f22fbbf6e31cc3"
integrity sha512-1tDBj2zX2gxu2oHU6qvH5RGNrOWRfRxu8369KhDotuuBN5yJrGXJzWIKikwhzjsNsQJYOB+B0cS44yWAfwSwzw==
dependencies:
"@babel/runtime" "^7.18.9"
"@reactflow/core" "11.4.0"
"@reactflow/core" "11.7.4"
"@types/d3-selection" "^3.0.3"
"@types/d3-zoom" "^3.0.1"
classcat "^5.0.3"
d3-selection "^3.0.0"
d3-zoom "^3.0.0"
zustand "^4.1.1"
zustand "^4.3.1"

"@reactflow/node-toolbar@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@reactflow/node-toolbar/-/node-toolbar-1.1.0.tgz#c7de2fb5c7aef02a1e575ce12a35d23cd45c18bd"
integrity sha512-kibrTGGvwhFGndVSgwr9E6l9Uddr44csr06X+PJ7FJ0SXgeOHbSw4MaM/9dSFxkFoCi77fPXSdMONNTReSBnIg==
"@reactflow/node-resizer@2.1.1":
version "2.1.1"
resolved "https://registry.yarnpkg.com/@reactflow/node-resizer/-/node-resizer-2.1.1.tgz#8f9b4e362e572dcddb54d43a67b5c5919b25937f"
integrity sha512-5Q+IBmZfpp/bYsw3+KRVJB1nUbj6W3XAp5ycx4uNWH+K98vbssymyQsW0vvKkIhxEPg6tkiMzO4UWRWvwBwt1g==
dependencies:
"@babel/runtime" "^7.18.9"
"@reactflow/core" "11.4.0"
"@reactflow/core" "^11.6.0"
classcat "^5.0.4"
d3-drag "^3.0.0"
d3-selection "^3.0.0"
zustand "^4.3.1"

"@reactflow/node-toolbar@1.2.3":
version "1.2.3"
resolved "https://registry.yarnpkg.com/@reactflow/node-toolbar/-/node-toolbar-1.2.3.tgz#8ff8408dffee7920752479cd19e7ab7c9c47b4d2"
integrity sha512-uFQy9xpog92s0G1wsPLniwV9nyH4i/MmL7QoMsWdnKaOi7XMhd8SJcCzUdHC3imR21HltsuQITff/XQ51ApMbg==
dependencies:
"@reactflow/core" "11.7.4"
classcat "^5.0.3"
zustand "^4.1.1"
zustand "^4.3.1"

"@redocly/ajv@^8.6.4":
version "8.6.4"
Expand Down Expand Up @@ -4518,7 +4519,7 @@ cjs-module-lexer@^1.0.0:
resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40"
integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==

classcat@^5.0.3:
classcat@^5.0.3, classcat@^5.0.4:
version "5.0.4"
resolved "https://registry.yarnpkg.com/classcat/-/classcat-5.0.4.tgz#e12d1dfe6df6427f260f03b80dc63571a5107ba6"
integrity sha512-sbpkOw6z413p+HDGcBENe498WM9woqWHiJxCq7nvmxe9WmrUmqfAcxpIwAiMtM5Q3AhYkzXcNQHqsWq0mND51g==
Expand Down Expand Up @@ -9975,16 +9976,17 @@ react@^18.0.0:
dependencies:
loose-envify "^1.1.0"

reactflow@^11.4.0:
version "11.4.0"
resolved "https://registry.yarnpkg.com/reactflow/-/reactflow-11.4.0.tgz#aeb4b030ba93e8e656094f59226e55ec538f55b4"
integrity sha512-Y+LZ3XZX7UejW4vukeyLwDDfqNT0RxyNNSHD1FJOIu2IvyVMkj+wKTcbp3ehm7brBkMOOaPyugcEWlLwFXcrjg==
reactflow@^11.7.4:
version "11.7.4"
resolved "https://registry.yarnpkg.com/reactflow/-/reactflow-11.7.4.tgz#b00159c3471d007bc4865b23005c636b1f08ab26"
integrity sha512-QI6+oc1Ft6oFeLSdHlp+SmgymbI5Tm49wj5JyE84O4A54yN/ImfYaBhLit9Cmfzxn9Tz6tDqmGMGbk4bdtB8/w==
dependencies:
"@reactflow/background" "11.1.0"
"@reactflow/controls" "11.1.0"
"@reactflow/core" "11.4.0"
"@reactflow/minimap" "11.3.0"
"@reactflow/node-toolbar" "1.1.0"
"@reactflow/background" "11.2.4"
"@reactflow/controls" "11.1.15"
"@reactflow/core" "11.7.4"
"@reactflow/minimap" "11.5.4"
"@reactflow/node-resizer" "2.1.1"
"@reactflow/node-toolbar" "1.2.3"

read-pkg-up@^8.0.0:
version "8.0.0"
Expand Down Expand Up @@ -11863,10 +11865,10 @@ zrender@5.4.3:
dependencies:
tslib "2.3.0"

zustand@^4.1.1:
version "4.1.5"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.1.5.tgz#7402b511f5b23ccb0f9ba6d20ae01ec817e16eb6"
integrity sha512-PsdRT8Bvq22Yyh1tvpgdHNE7OAeFKqJXUxtJvj1Ixw2B9O2YZ1M34ImQ+xyZah4wZrR4lENMoDUutKPpyXCQ/Q==
zustand@^4.3.1:
version "4.3.9"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.3.9.tgz#a7d4332bbd75dfd25c6848180b3df1407217f2ad"
integrity sha512-Tat5r8jOMG1Vcsj8uldMyqYKC5IZvQif8zetmLHs9WoZlntTHmIoNM8TpLRY31ExncuUvUOXehd0kvahkuHjDw==
dependencies:
use-sync-external-store "1.2.0"

Expand Down

0 comments on commit 3237cb3

Please sign in to comment.