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 e19d58b
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -377,16 +377,16 @@ exports[`Canvas Catalog button should NOT be present if \`CatalogModalContext\`
>
<path
class="pf-topology__edge__background"
d="M440 277.5 L440 86.5"
d="M440 277.5 L11.841638549556876 7.4683061306864715"
/>
<path
class="pf-topology__edge__link pf-m-solid"
d="M440 277.5 L440 72.5"
d="M440 277.5 L0 0"
style="animation-duration: 0s;"
/>
<g
class="pf-topology-connector-arrow pf-topology__edge"
transform="translate(440, 86.5) rotate(270)"
transform="translate(11.841638549556876, 7.4683061306864715) rotate(212.23888483746998)"
>
<polygon
points=" 0,7 0,-7 14,0"
Expand Down Expand Up @@ -443,16 +443,16 @@ exports[`Canvas Catalog button should NOT be present if \`CatalogModalContext\`
>
<path
class="pf-topology__edge__background"
d="M440 700 L440 793.5"
d="M0 0 L433.3014040786489 795.2065540761567"
/>
<path
class="pf-topology__edge__link pf-m-solid"
d="M440 700 L440 807.5"
d="M0 0 L440 807.5"
style="animation-duration: 0s;"
/>
<g
class="pf-topology-connector-arrow pf-topology__edge"
transform="translate(440, 793.5) rotate(90)"
transform="translate(433.3014040786489, 795.2065540761567) rotate(61.41440289681057)"
>
<polygon
points=" 0,7 0,-7 14,0"
Expand Down Expand Up @@ -1136,16 +1136,16 @@ exports[`Canvas Catalog button should be present if \`CatalogModalContext\` is p
>
<path
class="pf-topology__edge__background"
d="M440 277.5 L440 86.5"
d="M440 277.5 L11.841638549556876 7.4683061306864715"
/>
<path
class="pf-topology__edge__link pf-m-solid"
d="M440 277.5 L440 72.5"
d="M440 277.5 L0 0"
style="animation-duration: 0s;"
/>
<g
class="pf-topology-connector-arrow pf-topology__edge"
transform="translate(440, 86.5) rotate(270)"
transform="translate(11.841638549556876, 7.4683061306864715) rotate(212.23888483746998)"
>
<polygon
points=" 0,7 0,-7 14,0"
Expand Down Expand Up @@ -1202,16 +1202,16 @@ exports[`Canvas Catalog button should be present if \`CatalogModalContext\` is p
>
<path
class="pf-topology__edge__background"
d="M440 700 L440 793.5"
d="M0 0 L433.3014040786489 795.2065540761567"
/>
<path
class="pf-topology__edge__link pf-m-solid"
d="M440 700 L440 807.5"
d="M0 0 L440 807.5"
style="animation-duration: 0s;"
/>
<g
class="pf-topology-connector-arrow pf-topology__edge"
transform="translate(440, 793.5) rotate(90)"
transform="translate(433.3014040786489, 795.2065540761567) rotate(61.41440289681057)"
>
<polygon
points=" 0,7 0,-7 14,0"
Expand Down Expand Up @@ -2707,16 +2707,16 @@ exports[`Canvas should render correctly 1`] = `
>
<path
class="pf-topology__edge__background"
d="M440 277.5 L440 86.5"
d="M440 277.5 L11.841638549556876 7.4683061306864715"
/>
<path
class="pf-topology__edge__link pf-m-solid"
d="M440 277.5 L440 72.5"
d="M440 277.5 L0 0"
style="animation-duration: 0s;"
/>
<g
class="pf-topology-connector-arrow pf-topology__edge"
transform="translate(440, 86.5) rotate(270)"
transform="translate(11.841638549556876, 7.4683061306864715) rotate(212.23888483746998)"
>
<polygon
points=" 0,7 0,-7 14,0"
Expand Down Expand Up @@ -2773,16 +2773,16 @@ exports[`Canvas should render correctly 1`] = `
>
<path
class="pf-topology__edge__background"
d="M440 700 L440 793.5"
d="M0 0 L433.3014040786489 795.2065540761567"
/>
<path
class="pf-topology__edge__link pf-m-solid"
d="M440 700 L440 807.5"
d="M0 0 L440 807.5"
style="animation-duration: 0s;"
/>
<g
class="pf-topology-connector-arrow pf-topology__edge"
transform="translate(440, 793.5) rotate(90)"
transform="translate(433.3014040786489, 795.2065540761567) rotate(61.41440289681057)"
>
<polygon
points=" 0,7 0,-7 14,0"
Expand Down Expand Up @@ -3466,16 +3466,16 @@ exports[`Canvas should render correctly with more routes 1`] = `
>
<path
class="pf-topology__edge__background"
d="M440 277.5 L440 86.5"
d="M440 277.5 L11.841638549556876 7.4683061306864715"
/>
<path
class="pf-topology__edge__link pf-m-solid"
d="M440 277.5 L440 72.5"
d="M440 277.5 L0 0"
style="animation-duration: 0s;"
/>
<g
class="pf-topology-connector-arrow pf-topology__edge"
transform="translate(440, 86.5) rotate(270)"
transform="translate(11.841638549556876, 7.4683061306864715) rotate(212.23888483746998)"
>
<polygon
points=" 0,7 0,-7 14,0"
Expand Down Expand Up @@ -3532,16 +3532,16 @@ exports[`Canvas should render correctly with more routes 1`] = `
>
<path
class="pf-topology__edge__background"
d="M440 700 L440 793.5"
d="M0 0 L433.3014040786489 795.2065540761567"
/>
<path
class="pf-topology__edge__link pf-m-solid"
d="M440 700 L440 807.5"
d="M0 0 L440 807.5"
style="animation-duration: 0s;"
/>
<g
class="pf-topology-connector-arrow pf-topology__edge"
transform="translate(440, 793.5) rotate(90)"
transform="translate(433.3014040786489, 795.2065540761567) rotate(61.41440289681057)"
>
<polygon
points=" 0,7 0,-7 14,0"
Expand Down
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: 0 },
);

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

0 comments on commit e19d58b

Please sign in to comment.