Skip to content

Commit

Permalink
add support for top label position in groups
Browse files Browse the repository at this point in the history
  • Loading branch information
karthikjeeyar committed Feb 13, 2024
1 parent 2e52be2 commit 0144981
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,8 @@ const DefaultGroupCollapsed: React.FunctionComponent<DefaultGroupCollapsedProps>
{showLabel && (
<NodeLabel
className={styles.topologyGroupLabel}
x={labelPosition === LabelPosition.right ? collapsedWidth + 8 : collapsedWidth / 2}
y={labelPosition === LabelPosition.right ? collapsedHeight / 2 : collapsedHeight + 6}
x={collapsedWidth / 2}
y={labelPosition === LabelPosition.top ? collapsedHeight / 2 - collapsedHeight : collapsedHeight + 6}
paddingX={8}
paddingY={5}
dragRef={dragNodeRef ? dragLabelRef : undefined}
Expand Down
64 changes: 56 additions & 8 deletions packages/module/src/components/groups/DefaultGroupExpanded.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import NodeLabel from '../nodes/labels/NodeLabel';
import { Layer } from '../layers';
import { GROUPS_LAYER, TOP_LAYER } from '../../const';
import { hullPath, maxPadding, useCombineRefs, useHover } from '../../utils';
import { BadgeLocation, isGraph, Node, NodeShape, NodeStyle, PointTuple } from '../../types';
import { BadgeLocation, isGraph, LabelPosition, Node, NodeShape, NodeStyle, PointTuple } from '../../types';
import {
useDragNode,
useSvgAnchor,
Expand Down Expand Up @@ -41,17 +41,23 @@ type DefaultGroupExpandedProps = {
badgeLocation?: BadgeLocation;
labelIconClass?: string; // Icon to show in label
labelIcon?: string;
labelPosition?: LabelPosition;
labelIconPadding?: number;
hulledOutline?: boolean;
} & CollapsibleGroupProps & WithDragNodeProps & WithSelectionProps & WithDndDropProps & WithContextMenuProps;
} & CollapsibleGroupProps &
WithDragNodeProps &
WithSelectionProps &
WithDndDropProps &
WithContextMenuProps;

type PointWithSize = [number, number, number];

// Return the point whose Y is the largest value.
// Return the point whose Y is the largest or smallest based on the labelPosition value.
// If multiple points are found, compute the center X between them
// export for testing only
export function computeLabelLocation(points: PointWithSize[]): PointWithSize {
export function computeLabelLocation(points: PointWithSize[], labelPosition: LabelPosition): PointWithSize {
let lowPoints: PointWithSize[];
let highPoints: PointWithSize[];
const threshold = 5;

_.forEach(points, p => {
Expand All @@ -62,6 +68,36 @@ export function computeLabelLocation(points: PointWithSize[]): PointWithSize {
lowPoints.push(p);
}
});

points.forEach((p) => {
const delta = !highPoints ? -Infinity : Math.round(p[1]) - Math.round(highPoints[0][1]);
// If the difference is greater than the threshold, update the highest point
if (delta < -threshold) {
highPoints = [p];
} else if (Math.abs(delta) <= threshold) {
if (!highPoints) {
highPoints = [];
}
highPoints.push(p);
}
});

if (labelPosition === LabelPosition.top) {
// find min and max by x and y coordinates
const minX = highPoints.reduce((min, p) => Math.min(min, p[0]), Infinity);
const maxX = highPoints.reduce((max, p) => Math.max(max, p[0]), -Infinity);
const minY = highPoints.reduce((min, p) => Math.min(min, p[1]), Infinity);
// find max by size value
const maxSize = highPoints.reduce((max, p) => Math.max(max, p[2]), -Infinity);

return [
(minX + maxX) / 2,
minY,
// use the max size value
maxSize
];
}

return [
(_.minBy(lowPoints, p => p[0])[0] + _.maxBy(lowPoints, p => p[0])[0]) / 2,
lowPoints[0][1],
Expand Down Expand Up @@ -97,6 +133,7 @@ const DefaultGroupExpanded: React.FunctionComponent<DefaultGroupExpandedProps> =
badgeLocation,
labelIconClass,
labelIcon,
labelPosition,
labelIconPadding,
onCollapseChange,
hulledOutline = true,
Expand Down Expand Up @@ -157,10 +194,13 @@ const DefaultGroupExpanded: React.FunctionComponent<DefaultGroupExpandedProps> =
pathRef.current = hullPath(hullPoints as PointTuple[], hullPadding);

// Compute the location of the group label.
labelLocation.current = computeLabelLocation(hullPoints as PointWithSize[]);
labelLocation.current = computeLabelLocation(hullPoints as PointWithSize[], labelPosition);
} else {
boxRef.current = element.getBounds();
labelLocation.current = [boxRef.current.x + boxRef.current.width / 2, boxRef.current.y + boxRef.current.height, 0];
labelLocation.current =
labelPosition === LabelPosition.top
? [boxRef.current.x + boxRef.current.width / 2, boxRef.current.y, 0]
: [boxRef.current.x + boxRef.current.width / 2, boxRef.current.y + boxRef.current.height, 0];
}
}

Expand All @@ -183,6 +223,14 @@ const DefaultGroupExpanded: React.FunctionComponent<DefaultGroupExpandedProps> =
canDrop && dropTarget && 'pf-m-drop-target'
);

const outlinePadding = hulledOutline ? hullPadding(labelLocation.current) : 0;
const labelGap = 24;
const startX = labelLocation.current[0];
const startY =
labelPosition === LabelPosition.top
? labelLocation.current[1] - outlinePadding - labelGap * 2
: labelLocation.current[1] + outlinePadding + labelGap;

return (
<g ref={labelHoverRef} onContextMenu={onContextMenu} onClick={onSelect} className={groupClassName}>
<Layer id={GROUPS_LAYER}>
Expand All @@ -198,8 +246,8 @@ const DefaultGroupExpanded: React.FunctionComponent<DefaultGroupExpandedProps> =
<Layer id={isHover ? TOP_LAYER : undefined}>
<NodeLabel
className={styles.topologyGroupLabel}
x={labelLocation.current[0]}
y={labelLocation.current[1] + (hulledOutline ? hullPadding(labelLocation.current) : 0) + 24}
x={startX}
y={startY}
paddingX={8}
paddingY={5}
dragRef={dragNodeRef ? dragLabelRef : undefined}
Expand Down
19 changes: 17 additions & 2 deletions packages/module/src/components/nodes/labels/NodeLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,23 @@ const NodeLabel: React.FunctionComponent<NodeLabelProps> = ({
const primaryWidth = iconSpace + badgeSpace + paddingX + textSize.width + actionSpace + contextSpace + paddingX;
const secondaryWidth = secondaryLabel && secondaryTextSize ? secondaryTextSize.width + 2 * paddingX : 0;
const width = Math.max(primaryWidth, secondaryWidth);
const startX = position === LabelPosition.right ? x + iconSpace : x - width / 2 + iconSpace / 2;
const startY = position === LabelPosition.right ? y - height / 2 : y;

let startX: number;
let startY: number;

if (position === LabelPosition.top) {
startX = x - width / 2;
startY = -y + height + paddingY;
} else if (position === LabelPosition.right) {
startX = x + iconSpace;
startY = y - height / 2;
} else if (position === LabelPosition.left) {
startX = -x - width / 2;
startY = height + paddingY;
} else {
startX = x - width / 2 + iconSpace / 2;
startY = y;
}
const actionStartX = iconSpace + badgeSpace + paddingX + textSize.width + paddingX;
const contextStartX = actionStartX + actionSpace;
const backgroundHeight =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,15 @@ const DefaultTaskGroupInner: React.FunctionComponent<DefaultTaskGroupInnerProps>
return [0, 0];
}
switch (labelPosition) {
case LabelPosition.top:
return [minX + (maxX - minX) / 2, -minY + (padding + labelOffset) * 2];
case LabelPosition.right:
return [maxX + labelOffset, minY + (maxY - minY) / 2];
case LabelPosition.bottom:
default:
return [minX + (maxX - minX) / 2, maxY + labelOffset];
}
}, [element, label, labelOffset, labelPosition, maxX, maxY, minX, minY, showLabel]);
}, [element, label, labelOffset, labelPosition, maxX, maxY, minX, minY, showLabel, padding]);

if (children.length === 0) {
return null;
Expand Down
2 changes: 2 additions & 0 deletions packages/module/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ export enum EdgeTerminalType {
}

export enum LabelPosition {
top,
left,
right,
bottom
}
Expand Down

0 comments on commit 0144981

Please sign in to comment.