diff --git a/packages/demo-app-ts/src/utils/styleUtils.ts b/packages/demo-app-ts/src/utils/styleUtils.ts index 2035ef5..cc0b33e 100644 --- a/packages/demo-app-ts/src/utils/styleUtils.ts +++ b/packages/demo-app-ts/src/utils/styleUtils.ts @@ -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; }; diff --git a/packages/module/src/components/groups/DefaultGroup.tsx b/packages/module/src/components/groups/DefaultGroup.tsx index 1beab61..b8515c8 100644 --- a/packages/module/src/components/groups/DefaultGroup.tsx +++ b/packages/module/src/components/groups/DefaultGroup.tsx @@ -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; diff --git a/packages/module/src/components/groups/DefaultGroupCollapsed.tsx b/packages/module/src/components/groups/DefaultGroupCollapsed.tsx index 0dcffbb..e226528 100644 --- a/packages/module/src/components/groups/DefaultGroupCollapsed.tsx +++ b/packages/module/src/components/groups/DefaultGroupCollapsed.tsx @@ -151,8 +151,8 @@ const DefaultGroupCollapsed: React.FunctionComponent {showLabel && ( { + 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) { @@ -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], @@ -97,6 +133,7 @@ const DefaultGroupExpanded: React.FunctionComponent = badgeLocation, labelIconClass, labelIcon, + labelPosition, labelIconPadding, onCollapseChange, hulledOutline = true, @@ -157,10 +194,13 @@ const DefaultGroupExpanded: React.FunctionComponent = 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]; } } @@ -183,6 +223,14 @@ const DefaultGroupExpanded: React.FunctionComponent = 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 ( @@ -198,8 +246,8 @@ const DefaultGroupExpanded: React.FunctionComponent = = 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 ( = observe = ({ 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 = diff --git a/packages/module/src/pipelines/components/groups/DefaultTaskGroup.tsx b/packages/module/src/pipelines/components/groups/DefaultTaskGroup.tsx index d711e26..70b8b6f 100644 --- a/packages/module/src/pipelines/components/groups/DefaultTaskGroup.tsx +++ b/packages/module/src/pipelines/components/groups/DefaultTaskGroup.tsx @@ -117,6 +117,8 @@ const DefaultTaskGroupInner: React.FunctionComponent 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: diff --git a/packages/module/src/types.ts b/packages/module/src/types.ts index 4385a6e..d488513 100644 --- a/packages/module/src/types.ts +++ b/packages/module/src/types.ts @@ -84,6 +84,8 @@ export enum EdgeTerminalType { } export enum LabelPosition { + top, + left, right, bottom }