Skip to content

Commit

Permalink
Merge pull request patternfly#141 from karthikjeeyar/group-label-posi…
Browse files Browse the repository at this point in the history
…tion

feat(label): add support for top label position in groups
  • Loading branch information
jeff-phillips-18 authored Feb 15, 2024
2 parents 7a13627 + a3c2452 commit d1b08ab
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 18 deletions.
9 changes: 6 additions & 3 deletions packages/demo-app-ts/src/utils/styleUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,15 +137,18 @@ export const createNode = (options: {
(options.marginX || 60) +
(options.x ??
(options.column - 1) *
(options.label && options.labelPosition === LabelPosition.right ? RIGHT_LABEL_COLUMN_WIDTH : COLUMN_WIDTH));
(options.label && [LabelPosition.right, LabelPosition.left].includes(options.labelPosition)
? RIGHT_LABEL_COLUMN_WIDTH
: COLUMN_WIDTH));
nodeModel.y =
20 +
(width - height) / 2 +
(options.y ??
(options.row - 1) *
(!options.label || options.labelPosition === LabelPosition.right ? ROW_HEIGHT : BOTTOM_LABEL_ROW_HEIGHT));
(!options.label || [LabelPosition.right, LabelPosition.left].includes(options.labelPosition)
? ROW_HEIGHT
: BOTTOM_LABEL_ROW_HEIGHT));
}

return nodeModel;
};

Expand Down
2 changes: 1 addition & 1 deletion packages/module/src/components/groups/DefaultGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ interface DefaultGroupProps {
secondaryLabel?: string;
/** Flag to show the label */
showLabel?: boolean; // Defaults to true
/** Position of the label, bottom or left. Defaults to element.getLabelPosition() or bottom */
/** Position of the label, top or bottom. Defaults to element.getLabelPosition() or bottom */
labelPosition?: LabelPosition;
/** The maximum length of the label before truncation */
truncateLength?: number;
Expand Down
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,19 +41,54 @@ 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;

if (labelPosition === LabelPosition.top) {
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);
}
});

// 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
];
}

_.forEach(points, p => {
const delta = !lowPoints ? Infinity : Math.round(p[1]) - Math.round(lowPoints[0][1]);
if (delta > threshold) {
Expand All @@ -62,6 +97,7 @@ export function computeLabelLocation(points: PointWithSize[]): PointWithSize {
lowPoints.push(p);
}
});

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
21 changes: 19 additions & 2 deletions packages/module/src/components/nodes/DefaultNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,23 @@ const DefaultNodeInner: React.FunctionComponent<DefaultNodeInnerProps> = observe
return { translateX, translateY };
}, [element, nodeScale, scaleNode]);

let labelX;
let labelY;
const labelPaddingX = 8;
const labelPaddingY = 4;
if(nodeLabelPosition === LabelPosition.right) {
labelX = (width + labelPaddingX) * labelPositionScale;
labelY = height / 2;
} else if (nodeLabelPosition === LabelPosition.left) {
labelX = 0;
labelY = height / 2 - labelPaddingY;
} else if(nodeLabelPosition === LabelPosition.top) {
labelX = width / 2;
labelY = labelPaddingY + labelPaddingY/2;
} else {
labelX = width / 2 * labelPositionScale;
labelY = height + labelPaddingY + labelPaddingY / 2 ;
}
return (
<g
className={groupClassName}
Expand All @@ -346,8 +363,8 @@ const DefaultNodeInner: React.FunctionComponent<DefaultNodeInnerProps> = observe
<g transform={`scale(${labelScale})`}>
<NodeLabel
className={css(styles.topologyNodeLabel, labelClassName)}
x={(nodeLabelPosition === LabelPosition.right ? width + 8 : width / 2) * labelPositionScale}
y={(nodeLabelPosition === LabelPosition.right ? height / 2 : height + 6) * labelPositionScale}
x={labelX}
y={labelY * labelPositionScale}
position={nodeLabelPosition}
paddingX={8}
paddingY={4}
Expand Down
18 changes: 16 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,22 @@ 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 = - width - paddingX;
startY = y - height / 2 + 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,6 +117,8 @@ const DefaultTaskGroupInner: React.FunctionComponent<DefaultTaskGroupInnerProps>
return [0, 0];
}
switch (labelPosition) {
case LabelPosition.top:
return [minX + (maxX - minX) / 2, -minY + labelOffset];
case LabelPosition.right:
return [maxX + labelOffset, minY + (maxY - minY) / 2];
case LabelPosition.bottom:
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 d1b08ab

Please sign in to comment.