Skip to content

Commit 024dbdd

Browse files
committed
feat(visualizer): improve default layout with dagrejs
Improve default layout with dagrejs
1 parent 9d56df5 commit 024dbdd

File tree

13 files changed

+72
-80
lines changed

13 files changed

+72
-80
lines changed

Diff for: packages/extension-shared/src/hooks/schema.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ export const useSchema = (): {
2323

2424
if (message.key !== schemaKey) {
2525
// update stores
26-
tableCoordsStore.switchTo(message.key, message.payload.tables);
26+
tableCoordsStore.switchTo(
27+
message.key,
28+
message.payload.tables,
29+
message.payload.refs,
30+
);
2731
stageStateStore.switchTo(message.key);
2832
detailLevelStore.switchTo(message.key);
2933

Diff for: packages/json-table-schema-visualizer/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"main": "index.js",
55
"license": "MIT",
66
"dependencies": {
7+
"@dagrejs/dagre": "^1.1.2",
78
"eventemitter3": "^5.0.1",
89
"konva": "^9.3.6",
910
"lucide-react": "^0.365.0",

Diff for: packages/json-table-schema-visualizer/src/components/DiagramViewer/DiagramViewer.stories.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export const DiagramViewerStory: Story = {
3333
},
3434
decorators: [
3535
(Story) => {
36-
tableCoordsStore.resetPositions(tables);
36+
tableCoordsStore.resetPositions(tables, exampleData.refs);
3737

3838
return <Story />;
3939
},

Diff for: packages/json-table-schema-visualizer/src/components/DiagramViewer/DiagramViewer.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const DiagramViewer = ({ refs, tables, enums }: DiagramViewerProps) => {
2727

2828
return (
2929
<TableLevelDetailProvider>
30-
<TablesPositionsProvider tables={tables}>
30+
<TablesPositionsProvider tables={tables} refs={refs}>
3131
<MainProviders tables={tables} enums={enums}>
3232
<DiagramWrapper>
3333
<RelationsConnections refs={refs} />

Diff for: packages/json-table-schema-visualizer/src/components/DiagramViewer/DiagramWrapper.stories.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const DiagramWrapperWrapper: Story = {
2424
},
2525
decorators: [
2626
(Story) => (
27-
<TablesPositionsProvider tables={[]}>
27+
<TablesPositionsProvider tables={[]} refs={[]}>
2828
<Story />
2929
</TablesPositionsProvider>
3030
),

Diff for: packages/json-table-schema-visualizer/src/components/RelationConnection/RelationConnection.stories.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ type Story = StoryObj<typeof RelationConnection>;
1919

2020
export const RelationConnectionStory: Story = {
2121
render: (props) => (
22-
<TablesPositionsProvider tables={exampleData.tables}>
22+
<TablesPositionsProvider
23+
tables={exampleData.tables}
24+
refs={exampleData.refs}
25+
>
2326
<MainProviders enums={exampleData.enums} tables={exampleData.tables}>
2427
<RelationConnection {...props} />
2528

Diff for: packages/json-table-schema-visualizer/src/components/Table.stories.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ type Story = StoryObj<typeof Table>;
1717

1818
export const TableStory: Story = {
1919
render: (props) => (
20-
<TablesPositionsProvider tables={[]}>
20+
<TablesPositionsProvider tables={[]} refs={[]}>
2121
<MainProviders enums={exampleData.enums} tables={exampleData.tables}>
2222
<Table {...props} />
2323
</MainProviders>

Diff for: packages/json-table-schema-visualizer/src/components/Toolbar/AutoArrage/AutoArrangeTables.stories.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const AutoArrangeTableButtonStory: Story = {
1717
render: () => <AutoArrangeTableButton />,
1818
decorators: [
1919
(Story) => (
20-
<TablesPositionsProvider tables={[]}>
20+
<TablesPositionsProvider tables={[]} refs={[]}>
2121
<Story />
2222
</TablesPositionsProvider>
2323
),

Diff for: packages/json-table-schema-visualizer/src/components/Toolbar/Toolbar.stories.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export const ToolbarStory: Story = {
1818
decorators: [
1919
(Story) => (
2020
<div className="py-32">
21-
<TablesPositionsProvider tables={[]}>
21+
<TablesPositionsProvider tables={[]} refs={[]}>
2222
<Story />
2323
</TablesPositionsProvider>
2424
</div>

Diff for: packages/json-table-schema-visualizer/src/providers/TablesPositionsProvider.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createContext, useMemo, type PropsWithChildren } from "react";
2-
import { type JSONTableTable } from "shared/types/tableSchema";
32

3+
import type { JSONTableRef, JSONTableTable } from "shared/types/tableSchema";
44
import type { TablesPositionsContextValue } from "@/types/dimension";
55

66
import { tableCoordsStore } from "@/stores/tableCoords";
@@ -10,14 +10,16 @@ export const TablesPositionsContext =
1010

1111
interface TablesPositionsProviderProps extends PropsWithChildren {
1212
tables: JSONTableTable[];
13+
refs: JSONTableRef[];
1314
}
1415

1516
const TablesPositionsProvider = ({
1617
tables,
18+
refs,
1719
children,
1820
}: TablesPositionsProviderProps) => {
1921
const resetPositions = () => {
20-
tableCoordsStore.resetPositions(tables);
22+
tableCoordsStore.resetPositions(tables, refs);
2123
};
2224

2325
const contextValue = useMemo(() => ({ resetPositions }), [resetPositions]);

Diff for: packages/json-table-schema-visualizer/src/stores/tableCoords.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import { type JSONTableTable } from "shared/types/tableSchema";
2-
31
import { PersistableStore } from "./PersitableStore";
42

3+
import type { JSONTableRef, JSONTableTable } from "shared/types/tableSchema";
54
import type { XYPosition } from "@/types/positions";
65

76
import computeTablesPositions from "@/utils/tablePositioning/computeTablesPositions";
@@ -35,8 +34,8 @@ class TableCoordsStore extends PersistableStore<Array<[string, XYPosition]>> {
3534
};
3635
}
3736

38-
public resetPositions(tables: JSONTableTable[]): void {
39-
const newTablesPos = computeTablesPositions(tables);
37+
public resetPositions(tables: JSONTableTable[], refs: JSONTableRef[]): void {
38+
const newTablesPos = computeTablesPositions(tables, refs);
4039
this.tableCoords = newTablesPos;
4140
eventEmitter.emit(TableCoordsStore.RESET_POS_EVENT_NAME, newTablesPos);
4241
}
@@ -52,15 +51,19 @@ class TableCoordsStore extends PersistableStore<Array<[string, XYPosition]>> {
5251
this.persist(this.currentStoreKey, storeValue);
5352
}
5453

55-
public switchTo(newStoreKey: string, newTables: JSONTableTable[]): void {
54+
public switchTo(
55+
newStoreKey: string,
56+
newTables: JSONTableTable[],
57+
refs: JSONTableRef[],
58+
): void {
5659
this.saveCurrentStore();
5760

5861
this.currentStoreKey = newStoreKey;
5962
const recoveredStore = this.retrieve(this.currentStoreKey) as Array<
6063
[string, XYPosition]
6164
>;
6265
if (recoveredStore === null || !Array.isArray(recoveredStore)) {
63-
this.resetPositions(newTables);
66+
this.resetPositions(newTables, refs);
6467
return;
6568
}
6669

Diff for: packages/json-table-schema-visualizer/src/utils/tablePositioning/computeTablesPositions.ts

+28-36
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,43 @@
1-
import { type JSONTableTable } from "shared/types/tableSchema";
1+
import dagre from "@dagrejs/dagre";
22

33
import { computeTableDimension } from "../computeTableDimension";
44

5-
import { getColsNumber } from "./getColsNumber";
5+
import type { JSONTableRef, JSONTableTable } from "shared/types/tableSchema";
66

77
import { TABLES_GAP_X, TABLES_GAP_Y } from "@/constants/sizing";
88
import { type XYPosition } from "@/types/positions";
99

1010
const computeTablesPositions = (
1111
tables: JSONTableTable[],
12+
refs: JSONTableRef[],
1213
): Map<string, XYPosition> => {
13-
const colNumber = getColsNumber(tables.length);
14-
15-
let nextColsY = 0;
16-
1714
const tablesPositions = new Map<string, XYPosition>();
18-
let nextTableX = 0;
19-
20-
for (let colIndex = 0; colIndex < colNumber; colIndex++) {
21-
let currentColMaxW = 0;
22-
23-
const currentColX = nextTableX;
24-
25-
for (
26-
let tableIndex = colIndex;
27-
tableIndex < tables.length;
28-
tableIndex += colNumber
29-
) {
30-
const table = tables[tableIndex];
31-
const colY = nextColsY ?? 0;
32-
33-
tablesPositions.set(table.name, { x: currentColX, y: colY });
34-
35-
const tableDimension = computeTableDimension(table);
36-
const isLastTableInCol = tableIndex + colNumber > tables.length - 1;
37-
nextColsY = isLastTableInCol
38-
? 0
39-
: colY + tableDimension.height + TABLES_GAP_Y;
40-
41-
currentColMaxW = Math.max(tableDimension.width, currentColMaxW);
42-
43-
nextTableX = isLastTableInCol
44-
? currentColMaxW + TABLES_GAP_X + currentColX
45-
: currentColX;
46-
}
47-
}
4815

16+
const graph = new dagre.graphlib.Graph();
17+
graph.setGraph({
18+
nodesep: TABLES_GAP_X * 3,
19+
ranksep: TABLES_GAP_Y * 3,
20+
rankdir: "LR",
21+
});
22+
graph.setDefaultEdgeLabel(function () {
23+
return {};
24+
});
25+
26+
tables.forEach((table) => {
27+
const { height, width } = computeTableDimension(table);
28+
graph.setNode(table.name, { width, height });
29+
});
30+
31+
refs.forEach((ref) => {
32+
graph.setEdge(ref.endpoints[0].tableName, ref.endpoints[1].tableName);
33+
});
34+
35+
dagre.layout(graph);
36+
37+
graph.nodes().forEach((node) => {
38+
const { x, y } = graph.node(node);
39+
tablesPositions.set(node, { x, y });
40+
});
4941
return tablesPositions;
5042
};
5143

Diff for: yarn.lock

+15-28
Original file line numberDiff line numberDiff line change
@@ -1094,6 +1094,18 @@
10941094
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
10951095
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
10961096

1097+
"@dagrejs/dagre@^1.1.2":
1098+
version "1.1.2"
1099+
resolved "https://registry.yarnpkg.com/@dagrejs/dagre/-/dagre-1.1.2.tgz#5ec339979447091f48d2144deed8c70dfadae374"
1100+
integrity sha512-F09dphqvHsbe/6C2t2unbmpr5q41BNPEfJCdn8Z7aEBpVSy/zFQ/b4SWsweQjWNsYMDvE2ffNUN8X0CeFsEGNw==
1101+
dependencies:
1102+
"@dagrejs/graphlib" "2.2.2"
1103+
1104+
"@dagrejs/[email protected]":
1105+
version "2.2.2"
1106+
resolved "https://registry.yarnpkg.com/@dagrejs/graphlib/-/graphlib-2.2.2.tgz#74154d5cb880a23b4fae71034a09b4b5aef06feb"
1107+
integrity sha512-CbyGpCDKsiTg/wuk79S7Muoj8mghDGAESWGxcSyhHX5jD35vYMBZochYVFzlHxynpE9unpu6O+4ZuhrLxASsOg==
1108+
10971109
"@dbml/core@^3.4.0":
10981110
version "3.4.0"
10991111
resolved "https://registry.yarnpkg.com/@dbml/core/-/core-3.4.0.tgz#d6358b03708231b78b9f59999d1a37807c30a43b"
@@ -10825,16 +10837,7 @@ string-length@^4.0.1:
1082510837
char-regex "^1.0.2"
1082610838
strip-ansi "^6.0.0"
1082710839

10828-
"string-width-cjs@npm:string-width@^4.2.0":
10829-
version "4.2.3"
10830-
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
10831-
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
10832-
dependencies:
10833-
emoji-regex "^8.0.0"
10834-
is-fullwidth-code-point "^3.0.0"
10835-
strip-ansi "^6.0.1"
10836-
10837-
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
10840+
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
1083810841
version "4.2.3"
1083910842
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
1084010843
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -10921,14 +10924,7 @@ string_decoder@~1.1.1:
1092110924
dependencies:
1092210925
safe-buffer "~5.1.0"
1092310926

10924-
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
10925-
version "6.0.1"
10926-
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
10927-
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
10928-
dependencies:
10929-
ansi-regex "^5.0.1"
10930-
10931-
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
10927+
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
1093210928
version "6.0.1"
1093310929
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
1093410930
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -11876,7 +11872,7 @@ [email protected]:
1187611872
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
1187711873
integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==
1187811874

11879-
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
11875+
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
1188011876
version "7.0.0"
1188111877
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
1188211878
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -11894,15 +11890,6 @@ wrap-ansi@^6.0.1:
1189411890
string-width "^4.1.0"
1189511891
strip-ansi "^6.0.0"
1189611892

11897-
wrap-ansi@^7.0.0:
11898-
version "7.0.0"
11899-
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
11900-
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
11901-
dependencies:
11902-
ansi-styles "^4.0.0"
11903-
string-width "^4.1.0"
11904-
strip-ansi "^6.0.0"
11905-
1190611893
wrap-ansi@^8.1.0:
1190711894
version "8.1.0"
1190811895
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"

0 commit comments

Comments
 (0)