Skip to content

Commit

Permalink
chore(Canvas): Extract TargetAnchor
Browse files Browse the repository at this point in the history
  • Loading branch information
lordrip committed Oct 17, 2024
1 parent a8a532d commit c085d05
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 51 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import {
AbstractAnchor,
AnchorEnd,
CollapsibleGroupProps,
GROUPS_LAYER,
Layer,
Node,
Point,
Rect,
WithContextMenuProps,
WithDndDropProps,
Expand All @@ -18,6 +16,7 @@ import { FunctionComponent, useRef } from 'react';
import { CollapseButton } from './CollapseButton';
import { ContextMenuButton } from './ContextMenuButton';
import { CustomGroupProps } from './Group.models';
import { TargetAnchor } from '../target-anchor';

type CustomGroupExpandedProps = CustomGroupProps &
CollapsibleGroupProps &
Expand All @@ -26,55 +25,6 @@ type CustomGroupExpandedProps = CustomGroupProps &
WithDndDropProps &
WithContextMenuProps;

class TargetAnchor extends AbstractAnchor {
getLocation(reference: Point): Point {
return this.closestPointOnRectangle(this.owner.getBounds(), reference);
}

getReferencePoint(): Point {
return super.getReferencePoint();
}

private closestPointOnRectangle(rect: Rect, point: Point): Point {
// Deconstruct the rectangle and point parameters
const { x: rx, y: ry, width, height } = rect;
const { x: px, y: py } = point;

// Calculate the projections on the edges
// For left edge
const leftX = rx;
const leftY = Math.max(ry, Math.min(py, ry + height));

// For right edge
const rightX = rx + width;
const rightY = Math.max(ry, Math.min(py, ry + height));

// For top edge
const topX = Math.max(rx, Math.min(px, rx + width));
const topY = ry;

// For bottom edge
const bottomX = Math.max(rx, Math.min(px, rx + width));
const bottomY = ry + height;

// Calculate distances to each edge projection
const distances = [
{ x: leftX, y: leftY, dist: Math.hypot(px - leftX, py - leftY) },
{ x: rightX, y: rightY, dist: Math.hypot(px - rightX, py - rightY) },
{ x: topX, y: topY, dist: Math.hypot(px - topX, py - topY) },
{ x: bottomX, y: bottomY, dist: Math.hypot(px - bottomX, py - bottomY) },
];

// Find the minimum distance
const closestPoint = distances.reduce((minPoint, currentPoint) =>
currentPoint.dist < minPoint.dist ? currentPoint : minPoint,
);

// Return the closest point
return new Point(closestPoint.x, closestPoint.y);
}
}

export const CustomGroupExpanded: FunctionComponent<CustomGroupExpandedProps> = observer(
({ className, element, onSelect, label: propsLabel, onContextMenu, onCollapseChange }) => {
const label = propsLabel || element.getLabel();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Node, Point, Rect } from '@patternfly/react-topology';
import { TargetAnchor } from './target-anchor';

describe('TargetAnchor', () => {
/**
* ______
* | | height: 50
* |____|
* width: 50
*
* (*) reference point
* (x: 100, y: 100)
*/
it('should return the closest point of a rectangle to a reference point below', () => {
const element = {
getBounds: () => new Rect(0, 0, 50, 50),
} as Node;
const targetAnchor = new TargetAnchor(element);

const reference = new Point(100, 100);
const result = targetAnchor.getLocation(reference);

expect(result).toEqual({ x: 50, y: 50 });
});

/**
*
* (*) reference point
* (x: 25, y: 25)
* ______
* | | height: 50
* |____|
* width: 50
* top left corner: (0, 100)
*/
it('should return the closest point of a rectangle to a reference point above', () => {
const reference = new Point(25, 25);

const element = {
getBounds: () => new Rect(0, 100, 50, 50),
} as Node;
const targetAnchor = new TargetAnchor(element);

const result = targetAnchor.getLocation(reference);

expect(result).toEqual({ x: 25, y: 100 });
});

it('should delegate to the super class to get the reference point', () => {
const superSpy = jest.spyOn(TargetAnchor.prototype, 'getReferencePoint');
const element = {
getBounds: () => new Rect(0, 0, 50, 50),
} as Node;

const targetAnchor = new TargetAnchor(element);
targetAnchor.getReferencePoint();

expect(superSpy).toHaveBeenCalled();
});
});
51 changes: 51 additions & 0 deletions packages/ui/src/components/Visualization/Custom/target-anchor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { AbstractAnchor, Point, Rect } from '@patternfly/react-topology';

export class TargetAnchor extends AbstractAnchor {
getLocation(reference: Point): Point {
return this.closestPointOnRectangle(this.owner.getBounds(), reference);
}

getReferencePoint(): Point {
return super.getReferencePoint();
}

private closestPointOnRectangle(rect: Rect, point: Point): Point {
// Deconstruct the rectangle and point parameters
const { x: rx, y: ry, width, height } = rect;
const { x: px, y: py } = point;

// Calculate the projections on the edges
// For left edge
const leftX = rx;
const leftY = Math.max(ry, Math.min(py, ry + height));

// For right edge
const rightX = rx + width;
const rightY = Math.max(ry, Math.min(py, ry + height));

// For top edge
const topX = Math.max(rx, Math.min(px, rx + width));
const topY = ry;

// For bottom edge
const bottomX = Math.max(rx, Math.min(px, rx + width));
const bottomY = ry + height;

// Calculate distances to each edge projection
const distances = [
{ x: leftX, y: leftY, dist: Math.hypot(px - leftX, py - leftY) },
{ x: rightX, y: rightY, dist: Math.hypot(px - rightX, py - rightY) },
{ x: topX, y: topY, dist: Math.hypot(px - topX, py - topY) },
{ x: bottomX, y: bottomY, dist: Math.hypot(px - bottomX, py - bottomY) },
];

// Find the minimum distance
const closestPoint = distances.reduce(
(minPoint, currentPoint) => (currentPoint.dist < minPoint.dist ? currentPoint : minPoint),
{ x: 0, y: 0, dist: Number.MAX_SAFE_INTEGER },
);

// Return the closest point
return new Point(closestPoint.x, closestPoint.y);
}
}

0 comments on commit c085d05

Please sign in to comment.