#21232: fix the lineage export image being cropped (#21765)

* fix the lineage export image being cropped

* fix the nodes children column not being able to trace well

* minor removal as not needed

(cherry picked from commit da25cd1a3968fc9a890f79f9badf3149702b8a95)
This commit is contained in:
Ashish Gupta 2025-06-14 17:09:08 +05:30
parent 9f0ec0fd04
commit abeed00935
3 changed files with 71 additions and 45 deletions

View File

@ -670,10 +670,10 @@ describe('Test EntityLineageUtils utility', () => {
const bounds = getNodesBoundsReactFlow(nodes);
expect(bounds).toEqual({
xMin: 100,
yMin: 200,
xMax: 150, // x + width
yMax: 230, // y + height
xMin: 80, // x - padding
yMin: 180, // y - padding
xMax: 170, // x + width + padding
yMax: 250, // y + height + padding
});
});
@ -705,10 +705,10 @@ describe('Test EntityLineageUtils utility', () => {
const bounds = getNodesBoundsReactFlow(nodes);
expect(bounds).toEqual({
xMin: 0,
yMin: 0,
xMax: 260, // rightmost x (200) + width (60)
yMax: 340, // bottom y (300) + height (40)
xMin: -20, // x - padding
yMin: -20, // y - padding
xMax: 280, // rightmost x (200) + width (60) + padding
yMax: 360, // bottom y (300) + height (40) + padding
});
});
@ -729,10 +729,10 @@ describe('Test EntityLineageUtils utility', () => {
const bounds = getNodesBoundsReactFlow(nodes);
expect(bounds).toEqual({
xMin: 100,
yMin: 200,
xMax: 200,
yMax: 300,
xMin: 80,
yMin: 180,
xMax: 220,
yMax: 320,
});
});
@ -770,10 +770,10 @@ describe('Test EntityLineageUtils utility', () => {
const bounds = getNodesBoundsReactFlow(nodes);
expect(bounds).toEqual({
xMin: -100,
yMin: -200,
xMax: 140, // rightmost x (100) + width (40)
yMax: 220, // bottom y (200) + height (20)
xMin: -120, // x - padding
yMin: -220, // y - padding
xMax: 160, // rightmost x (100) + width (40) + padding
yMax: 240, // bottom y (200) + height (20) + padding
});
});
});
@ -796,9 +796,9 @@ describe('Test EntityLineageUtils utility', () => {
);
expect(viewport).toEqual({
x: 0,
y: 0,
zoom: 2,
x: 20,
y: 20,
zoom: 1.1428571428571428,
});
});
@ -821,9 +821,9 @@ describe('Test EntityLineageUtils utility', () => {
);
expect(viewport).toEqual({
x: 100,
y: 100,
zoom: 1,
x: 110,
y: 97.5,
zoom: 0.75,
});
});
@ -844,9 +844,9 @@ describe('Test EntityLineageUtils utility', () => {
);
expect(viewport).toEqual({
x: 200,
y: 200,
zoom: 2,
x: 170,
y: 170,
zoom: 1.5,
});
});
@ -867,9 +867,9 @@ describe('Test EntityLineageUtils utility', () => {
);
expect(viewport).toEqual({
x: 0,
y: 50,
zoom: 0.5,
x: 20,
y: 56.36363636363636,
zoom: 0.36363636363636365,
});
});
@ -890,9 +890,9 @@ describe('Test EntityLineageUtils utility', () => {
);
expect(viewport).toEqual({
x: NaN,
y: NaN,
zoom: Infinity,
x: 20,
y: 20,
zoom: 4,
});
});
@ -915,9 +915,9 @@ describe('Test EntityLineageUtils utility', () => {
);
expect(viewport).toEqual({
x: -100,
y: -100,
zoom: 4,
x: -60,
y: -60,
zoom: 2.2857142857142856,
});
});
});

View File

@ -278,12 +278,15 @@ export const getELKLayoutedElements = async (
return {
...node,
position: { x: layoutedNode?.x ?? 0, y: layoutedNode?.y ?? 0 },
// layoutedNode contains the total height of the node including the children height
// Needed to calculate the bounds height of the nodes in the export
height: layoutedNode?.height ?? node.height,
hidden: false,
};
});
return { nodes: updatedNodes, edges: edges ?? [] };
} catch (error) {
} catch {
return { nodes: [], edges: [] };
}
};
@ -1725,10 +1728,16 @@ export const getNodesBoundsReactFlow = (nodes: Node[]) => {
nodes.forEach((node) => {
const { x, y } = node.position;
bounds.xMin = Math.min(bounds.xMin, x);
bounds.yMin = Math.min(bounds.yMin, y);
bounds.xMax = Math.max(bounds.xMax, x + (node.width ?? 0));
bounds.yMax = Math.max(bounds.yMax, y + (node.height ?? 0));
const width = node.width ?? 0;
const height = node.height ?? 0;
// Add padding to ensure nodes are fully visible
const padding = 20;
bounds.xMin = Math.min(bounds.xMin, x - padding);
bounds.yMin = Math.min(bounds.yMin, y - padding);
bounds.xMax = Math.max(bounds.xMax, x + width + padding);
bounds.yMax = Math.max(bounds.yMax, y + height + padding);
});
return bounds;
@ -1744,13 +1753,23 @@ export const getViewportForBoundsReactFlow = (
const width = bounds.xMax - bounds.xMin;
const height = bounds.yMax - bounds.yMin;
// Scale the image to fit the container
// Add extra padding to ensure content is fully visible
const padding = 20;
const paddedWidth = width + padding * 2;
const paddedHeight = height + padding * 2;
// Scale the image to fit the container while maintaining aspect ratio
const scale =
Math.min(imageWidth / width, imageHeight / height) * scaleFactor;
Math.min(
(imageWidth - padding * 2) / paddedWidth,
(imageHeight - padding * 2) / paddedHeight
) * scaleFactor;
// Calculate translation to center the flow
const translateX = (imageWidth - width * scale) / 2 - bounds.xMin * scale;
const translateY = (imageHeight - height * scale) / 2 - bounds.yMin * scale;
const translateX =
(imageWidth - paddedWidth * scale) / 2 - bounds.xMin * scale;
const translateY =
(imageHeight - paddedHeight * scale) / 2 - bounds.yMin * scale;
return { x: translateX, y: translateY, zoom: scale };
};
@ -1766,8 +1785,13 @@ export const getViewportForLineageExport = (
const nodesBounds = getNodesBoundsReactFlow(nodes);
// Calculate the viewport to fit all nodes
return getViewportForBoundsReactFlow(nodesBounds, imageWidth, imageHeight);
// Calculate the viewport to fit all nodes with padding
return getViewportForBoundsReactFlow(
nodesBounds,
imageWidth,
imageHeight,
0.9
); // Scale down slightly to ensure padding
};
export const getLineageEntityExclusionFilter = () => {

View File

@ -54,6 +54,8 @@ export const exportPNGImageFromElement = async (exportData: ExportData) => {
backgroundColor: '#ffffff',
width: imageWidth + padding * 2,
height: imageHeight + padding * 2,
pixelRatio: 3,
quality: 1.0,
style: {
width: imageWidth.toString(),
height: imageHeight.toString(),