diff --git a/packages/module/src/elements/BaseGraph.ts b/packages/module/src/elements/BaseGraph.ts index 3ac4804f..6a94a4eb 100644 --- a/packages/module/src/elements/BaseGraph.ts +++ b/packages/module/src/elements/BaseGraph.ts @@ -267,16 +267,21 @@ export default class BaseGraph this.setPosition(new Point(x, y)); } - fit(padding = 0): void { + fit(padding = 0, node?: Node): void { let rect: Rect | undefined; - this.getNodes().forEach((c) => { - const b = c.getBounds(); - if (!rect) { - rect = b.clone(); - } else { - rect.union(b); - } - }); + if (node) { + rect = node.getBounds(); + } else { + this.getNodes().forEach((c) => { + const b = c.getBounds(); + if (!rect) { + rect = b.clone(); + } else { + rect.union(b); + } + }); + } + if (!rect) { return; } @@ -309,6 +314,22 @@ export default class BaseGraph this.setPosition(new Point(tx, ty)); } + centerInView = (nodeElement: Node): void => { + if (!nodeElement) { + return; + } + const { x: viewX, y: viewY, width: viewWidth, height: viewHeight } = this.getBounds(); + const boundingBox = nodeElement.getBounds().clone().scale(this.scale).translate(viewX, viewY); + const { x, y, width, height } = boundingBox; + + const newLocation = { + x: viewX - (x + width / 2) + viewWidth / 2, + y: viewY - (y + height / 2) + viewHeight / 2 + }; + + this.setBounds(new Rect(newLocation.x, newLocation.y, viewWidth, viewHeight)); + }; + panIntoView = ( nodeElement: Node, { offset = 0, minimumVisible = 0 }: { offset?: number; minimumVisible?: number } = {} diff --git a/packages/module/src/pipelines/components/groups/DefaultTaskGroup.tsx b/packages/module/src/pipelines/components/groups/DefaultTaskGroup.tsx index 69f5d0ea..41f323f9 100644 --- a/packages/module/src/pipelines/components/groups/DefaultTaskGroup.tsx +++ b/packages/module/src/pipelines/components/groups/DefaultTaskGroup.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { observer } from 'mobx-react'; import { OnSelect, WithDndDragProps, ConnectDragSource, ConnectDropTarget } from '../../../behavior'; import { ShapeProps } from '../../../components'; -import { Dimensions } from '../../../geom'; +import { Dimensions, Rect } from '../../../geom'; import { GraphElement, LabelPosition, BadgeLocation, isNode, Node } from '../../../types'; import { action } from '../../../mobx-exports'; import { getEdgesFromNodes, getSpacerNodes } from '../../utils'; @@ -170,6 +170,33 @@ const DefaultTaskGroupInner: React.FunctionComponent { + const b = c.getBounds(); + if (!rect) { + rect = b.clone(); + } else { + rect.union(b); + } + }); + + // If the required size is smaller, zoom in to fit the current graph + const graphBounds = graph.getBounds(); + if (rect.width < graphBounds.width || rect.height < graphBounds.height) { + graph.fit(80); + } + + // Center the graph on the group that was collapsed + graph.centerInView(group); + } + } + onCollapseChange && onCollapseChange(group, collapsed); }); diff --git a/packages/module/src/types.ts b/packages/module/src/types.ts index 68cf7ab6..a458180f 100644 --- a/packages/module/src/types.ts +++ b/packages/module/src/types.ts @@ -284,7 +284,8 @@ export interface Graph extends Graph // viewport operations reset(): void; scaleBy(scale: number, location?: Point): void; - fit(padding?: number): void; + fit(padding?: number, node?: Node): void; + centerInView(nodeElement: Node): void; panIntoView(element: Node, options?: { offset?: number; minimumVisible?: number }): void; isNodeInView(element: Node, options?: { padding: number }): boolean; expandAll(): void;