Skip to content

Commit

Permalink
[2721] Support outside labels during layout and arrangeAll
Browse files Browse the repository at this point in the history
Bug: #2721
Signed-off-by: Florian ROUËNÉ <florian.rouene@obeosoft.com>
  • Loading branch information
frouene committed Feb 1, 2024
1 parent f7f8946 commit 6156e00
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 45 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Expand Up @@ -38,6 +38,7 @@ As a result, all the APIs relying on this code have been deleted too such as the
- https://github.com/eclipse-sirius/sirius-web/issues/3004[#3004] [deck] Fix some of the console errors
- https://github.com/eclipse-sirius/sirius-web/issues/2799[#2799] [diagram] Remove the unwanted space between the header and first element in list node
- https://github.com/eclipse-sirius/sirius-web/issues/2822[#2822] [diagram] Prevent direct edit to be triggered on edge if there is no edit label tool corresponding
- https://github.com/eclipse-sirius/sirius-web/issues/2721[#2721] [diagram] Prevent labels to exceed their container

=== New Features

Expand Down
Expand Up @@ -98,6 +98,25 @@ export const prepareLayoutArea = (
});
labelElements.push(element);
}
if (hiddenContainer && node.data.outsideLabels.BOTTOM_MIDDLE) {
const outsideLabel = node.data.outsideLabels.BOTTOM_MIDDLE;
const children: JSX.Element[] = [
createElement(Label, {
diagramElementId: node.id,
label: outsideLabel,
faded: false,
transform: '',
key: outsideLabel.id,
}),
];
const element: JSX.Element = createElement('div', {
id: `${outsideLabel.id}-label`,
key: `${outsideLabel.id}-label`,
role: 'button', // role applied by react flow
children,
});
labelElements.push(element);
}
});

// The container used to render label is a flex container authorizing wrapping.
Expand Down
Expand Up @@ -13,21 +13,8 @@
import { Box, Node, Rect, XYPosition, boxToRect, rectToBox } from 'reactflow';
import { NodeData } from '../DiagramRenderer.types';
import { RawDiagram } from './layout.types';
import {
getBorderNodeExtent,
isEastBorderNode,
isNorthBorderNode,
isSouthBorderNode,
isWestBorderNode,
} from './layoutBorderNodes';
import {
borderNodeOffset,
defaultHeight,
defaultNodeMargin,
defaultWidth,
gap,
rectangularNodePadding,
} from './layoutParams';
import { getBorderNodeExtent, isEastBorderNode, isNorthBorderNode, isSouthBorderNode, isWestBorderNode, } from './layoutBorderNodes';
import { borderNodeOffset, defaultHeight, defaultNodeMargin, defaultWidth, gap, rectangularNodePadding, } from './layoutParams';

/**
* It requires that nodes are already positioned
Expand Down Expand Up @@ -155,11 +142,17 @@ const getNodeFootprint = (allVisibleNodes: Node<NodeData>[], node: Node<NodeData

const footPrint: Box = [node, ...borderNodes].reduce<Box>(
(currentFootPrint, child) => {
const outsideLabel = child.data.outsideLabels.BOTTOM_MIDDLE;
let outsideLabelHeightFootPrint: number = 0;
if (outsideLabel) {
const outsideLabelElement = document.getElementById(`${outsideLabel.id}-label`);
outsideLabelHeightFootPrint = outsideLabelElement?.getBoundingClientRect().height ?? 0;
}
const nodeBox = rectToBox({
x: node !== child && child.data.isBorderNode ? node.position.x + child.position.x : child.position.x,
y: node !== child && child.data.isBorderNode ? node.position.y + child.position.y : child.position.y,
width: child.width ?? 0,
height: child.height ?? 0,
height: (child.height ?? 0) + outsideLabelHeightFootPrint,
});

return getBoundsOfBoxes(currentFootPrint, nodeBox);
Expand Down
Expand Up @@ -20,3 +20,5 @@ export const borderNodeOffset = 5;
export const defaultWidth = 150;
export const defaultHeight = 70;
export const headerVerticalOffset = 25;
export const labelVerticalPadding = 8;
export const labelHorizontalPadding = 16;
Expand Up @@ -17,7 +17,7 @@ import { DiagramNodeType } from '../node/NodeTypes.types';
import { ListNodeData } from '../node/ListNode.types';
import ELK, { ElkLabel, ElkNode } from 'elkjs/lib/elk.bundled';
import { LayoutOptions } from 'elkjs/lib/elk-api';
import { headerVerticalOffset } from './layoutParams';
import { headerVerticalOffset, labelVerticalPadding, labelHorizontalPadding } from './layoutParams';
import { RawDiagram } from './layout.types';
import { useLayout } from './useLayout';
import { useSynchronizeLayoutData } from './useSynchronizeLayoutData';
Expand Down Expand Up @@ -50,15 +50,30 @@ const elkOptions = {
'elk.layered.spacing.edgeNodeBetweenLayers': '40',
};

const computeContainerLabel = (parentNode, viewportZoom): ElkLabel[] => {
const computeLabels = (node, viewportZoom: number): ElkLabel[] => {
const labels: ElkLabel[] = [];
if (parentNode && parentNode.data.insideLabel) {
const label = document.querySelector<HTMLDivElement>(`[data-id="${parentNode.data.insideLabel.id}-content"]`);
if (node && node.data.insideLabel) {
const label = document.querySelector<HTMLDivElement>(`[data-id="${node.data.insideLabel.id}-content"]`);
if (label) {
const elkLabel: ElkLabel = {
width: label.getBoundingClientRect().width / viewportZoom,
height: label.getBoundingClientRect().height / viewportZoom,
text: parentNode.data.insideLabel.text,
id: node.data.insideLabel.id,
width: label.getBoundingClientRect().width / viewportZoom + labelHorizontalPadding * 2,
height: label.getBoundingClientRect().height / viewportZoom + labelVerticalPadding * 2,
text: node.data.insideLabel.text,
};
labels.push(elkLabel);
}
}
if (node && node.data.outsideLabels.BOTTOM_MIDDLE) {
const label = document.querySelector<HTMLDivElement>(
`[data-id="${node.data.outsideLabels.BOTTOM_MIDDLE.id}-content"]`
);
if (label) {
const elkLabel: ElkLabel = {
id: node.data.outsideLabels.BOTTOM_MIDDLE.id,
width: label.getBoundingClientRect().width / viewportZoom + labelHorizontalPadding * 2,
height: label.getBoundingClientRect().height / viewportZoom + labelVerticalPadding * 2,
text: node.data.outsideLabels.BOTTOM_MIDDLE.text,
};
labels.push(elkLabel);
}
Expand All @@ -77,7 +92,6 @@ export const useArrangeAll = (refreshEventPayloadId: string): UseArrangeAllValue
const getELKLayout = async (
nodes,
edges,
labels: ElkLabel[],
options: LayoutOptions = {},
parentNodeId: string,
withHeader: boolean
Expand All @@ -86,7 +100,7 @@ export const useArrangeAll = (refreshEventPayloadId: string): UseArrangeAllValue
id: parentNodeId,
layoutOptions: options,
children: nodes.map((node) => ({
labels,
labels: computeLabels(node, viewport.zoom),
...node,
})),
edges,
Expand Down Expand Up @@ -140,27 +154,22 @@ export const useArrangeAll = (refreshEventPayloadId: string): UseArrangeAllValue
edge.source = parentNodeId;
}
});
await getELKLayout(
subGroupNodes,
subGroupEdges,
computeContainerLabel(parentNode, viewport.zoom),
elkOptions,
parentNodeId,
withHeader
).then(({ nodes: layoutedSubNodes, layoutReturn }) => {
const parentNode = allNodes.find((node) => node.id === parentNodeId);
if (parentNode) {
parentNode.width = layoutReturn.width;
parentNode.height = layoutReturn.height + (withHeader ? headerVerticalOffset : 0);
parentNode.style = { width: `${parentNode.width}px`, height: `${parentNode.height}px` };
parentNodeWithNewSize.push(parentNode);
await getELKLayout(subGroupNodes, subGroupEdges, elkOptions, parentNodeId, withHeader).then(
({ nodes: layoutedSubNodes, layoutReturn }) => {
const parentNode = allNodes.find((node) => node.id === parentNodeId);
if (parentNode) {
parentNode.width = layoutReturn.width;
parentNode.height = layoutReturn.height + (withHeader ? headerVerticalOffset : 0);
parentNode.style = { width: `${parentNode.width}px`, height: `${parentNode.height}px` };
parentNodeWithNewSize.push(parentNode);
}
layoutedAllNodes = [
...layoutedAllNodes,
...layoutedSubNodes,
...nodes.filter((node) => node.data.isBorderNode),
];
}
layoutedAllNodes = [
...layoutedAllNodes,
...layoutedSubNodes,
...nodes.filter((node) => node.data.isBorderNode),
];
});
);
}
return layoutedAllNodes;
};
Expand All @@ -170,6 +179,7 @@ export const useArrangeAll = (refreshEventPayloadId: string): UseArrangeAllValue
const subNodes: Map<string, Node<NodeData, string>[]> = reverseOrdreMap(getSubNodes(nodes));
applyElkOnSubNodes(subNodes, nodes).then((nodes: Node<NodeData, string>[]) => {
const laidOutNodesWithElk: Node<NodeData, string>[] = nodes.reverse();
laidOutNodesWithElk.forEach((node) => (node.data.resizedByUser = true)); // allows elk to resize nodes during layout

const diagramToLayout: RawDiagram = {
nodes: laidOutNodesWithElk,
Expand Down

0 comments on commit 6156e00

Please sign in to comment.