Skip to content
This repository was archived by the owner on Apr 7, 2024. It is now read-only.

Commit e0eb68f

Browse files
Undirected graphs support in the orbits strategy (#69)
* Improve disconnected graph placement for circular strategy * Start working on batch graph updates * Add graph batch updates * Fix orbits strategy * Fix replaceBatch * Improve batch updates types * Finish orbits placement strategy for diesconnected undirected graphs * Fix distances between vertices --------- Co-authored-by: kacperklusek <kacperklusek70@gmail.com>
1 parent 3630fee commit e0eb68f

File tree

16 files changed

+355
-225
lines changed

16 files changed

+355
-225
lines changed

src/App.tsx

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
import { useEffect } from 'react';
12
import { SafeAreaView, View } from 'react-native';
23
import { GestureHandlerRootView } from 'react-native-gesture-handler';
34

4-
import DirectedGraphComponent from '@/components/graphs/DirectedGraphComponent';
55
import DefaultEdgeLabelRenderer from '@/components/graphs/labels/renderers/DefaultEdgeLabelRenderer';
6-
import { DirectedGraph } from '@/models/graphs';
6+
import { UndirectedGraph } from '@/models/graphs';
77
import PannableScalableView from '@/views/PannableScalableView';
88

9+
import UndirectedGraphComponent from './components/graphs/UndirectedGraphComponent';
10+
911
const GRAPH1 = {
1012
vertices: [
1113
{ key: 'A', data: 'A' },
@@ -15,10 +17,10 @@ const GRAPH1 = {
1517
{ key: 'E', data: 'E' }
1618
],
1719
edges: [
18-
{ key: 'AB', from: 'A', to: 'B', data: 'AB' },
19-
{ key: 'AC', from: 'A', to: 'C', data: 'AC' },
20-
{ key: 'AD', from: 'A', to: 'D', data: 'AD' },
21-
{ key: 'AE', from: 'A', to: 'E', data: 'AE' }
20+
{ key: 'AB', vertices: ['A', 'B'], data: 'AB' },
21+
{ key: 'AC', vertices: ['A', 'C'], data: 'AC' },
22+
{ key: 'AD', vertices: ['A', 'D'], data: 'AD' },
23+
{ key: 'AE', vertices: ['A', 'E'], data: 'AE' }
2224
]
2325
};
2426

@@ -36,15 +38,15 @@ const GRAPH2 = {
3638
{ key: 'O', data: 'O' }
3739
],
3840
edges: [
39-
{ key: 'FG', from: 'F', to: 'G', data: 'FG' },
40-
{ key: 'FH', from: 'F', to: 'H', data: 'FH' },
41-
{ key: 'FI', from: 'F', to: 'I', data: 'FI' },
42-
{ key: 'GJ', from: 'G', to: 'J', data: 'GJ' },
43-
{ key: 'GK', from: 'G', to: 'K', data: 'GK' },
44-
{ key: 'GL', from: 'G', to: 'L', data: 'GL' },
45-
{ key: 'GM', from: 'G', to: 'M', data: 'GM' },
46-
{ key: 'GN', from: 'G', to: 'N', data: 'GN' },
47-
{ key: 'GO', from: 'G', to: 'O', data: 'GO' }
41+
{ key: 'FG', vertices: ['F', 'G'], data: 'FG' },
42+
{ key: 'FH', vertices: ['F', 'H'], data: 'FH' },
43+
{ key: 'FI', vertices: ['F', 'I'], data: 'FI' },
44+
{ key: 'GJ', vertices: ['G', 'J'], data: 'GJ' },
45+
{ key: 'GK', vertices: ['G', 'K'], data: 'GK' },
46+
{ key: 'GL', vertices: ['G', 'L'], data: 'GL' },
47+
{ key: 'GM', vertices: ['G', 'M'], data: 'GM' },
48+
{ key: 'GN', vertices: ['G', 'N'], data: 'GN' },
49+
{ key: 'GO', vertices: ['G', 'O'], data: 'GO' }
4850
]
4951
};
5052

@@ -54,8 +56,8 @@ const GRAPH3 = {
5456
{ key: 'Q', data: 'Q' }
5557
],
5658
edges: [
57-
{ key: 'PQ1', from: 'P', to: 'Q', data: 'PQ1' },
58-
{ key: 'PQ2', from: 'P', to: 'Q', data: 'PQ2' }
59+
{ key: 'PQ1', vertices: ['P', 'Q'], data: 'PQ1' },
60+
{ key: 'PQ2', vertices: ['P', 'Q'], data: 'PQ2' }
5961
]
6062
};
6163

@@ -64,23 +66,41 @@ const DISCONNECTED_GRAPH = {
6466
edges: [...GRAPH1.edges, ...GRAPH2.edges, ...GRAPH3.edges]
6567
};
6668

69+
let phase = 0;
70+
6771
export default function App() {
68-
const graph = DirectedGraph.fromData(
72+
const graph = UndirectedGraph.fromData(
6973
DISCONNECTED_GRAPH.vertices,
7074
DISCONNECTED_GRAPH.edges
7175
);
7276

77+
useEffect(() => {
78+
setInterval(() => {
79+
if (phase === 0) {
80+
graph.insertEdge('PC', '', 'P', 'C');
81+
} else if (phase === 1) {
82+
graph.insertEdge('CI', '', 'C', 'I');
83+
} else if (phase === 2) {
84+
graph.removeEdge('PC');
85+
} else if (phase === 3) {
86+
graph.removeEdge('CI');
87+
}
88+
phase = (phase + 1) % 4;
89+
}, 1000);
90+
}, [graph]);
91+
7392
return (
7493
<SafeAreaView className='grow'>
7594
<GestureHandlerRootView className='grow'>
7695
<View className='grow bg-black'>
7796
<PannableScalableView objectFit='contain' controls>
78-
<DirectedGraphComponent
97+
<UndirectedGraphComponent
7998
graph={graph}
8099
settings={{
81100
// TODO - fix orbits strategy padding
82101
placement: {
83-
strategy: 'circles',
102+
strategy: 'orbits',
103+
layerSizing: 'equal',
84104
minVertexSpacing: 100
85105
},
86106
components: {

src/components/graphs/GraphComponent.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,8 +389,22 @@ export default function GraphComponent<
389389
[verticesData]
390390
);
391391

392+
const containerWidth = useDerivedValue(
393+
() => boundingRect.right.value - boundingRect.left.value
394+
);
395+
const containerHeight = useDerivedValue(
396+
() => boundingRect.bottom.value - boundingRect.top.value
397+
);
398+
392399
return (
393400
<Group>
401+
<Rect
402+
x={boundingRect.left}
403+
y={boundingRect.top}
404+
width={containerWidth}
405+
height={containerHeight}
406+
color='#222'
407+
/>
394408
{renderEdges()}
395409
{renderVertices()}
396410
</Group>

src/components/graphs/edges/curved/DirectedCurvedEdgeComponent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
useSharedValue
66
} from 'react-native-reanimated';
77

8-
import { Vector, rotate } from '@shopify/react-native-skia';
8+
import { rotate } from '@shopify/react-native-skia';
99

1010
import { LABEL_COMPONENT_SETTINGS } from '@/constants/components';
1111
import { DirectedCurvedEdgeComponentProps } from '@/types/components/edges';

src/models/graphs/DirectedGraph.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ export default class DirectedGraph<V, E> extends Graph<
5151
targetKey: string,
5252
notifyObservers?: boolean
5353
): DirectedEdge<E, V> {
54-
const source = this.vertex(sourceKey);
55-
const target = this.vertex(targetKey);
54+
const source = this.getVertex(sourceKey);
55+
const target = this.getVertex(targetKey);
5656

5757
if (!source) {
5858
throw new Error(`Vertex ${sourceKey} does not exist`);
@@ -70,7 +70,7 @@ export default class DirectedGraph<V, E> extends Graph<
7070
}
7171

7272
override removeEdge(key: string, notifyObservers?: boolean): E {
73-
const edge = this.edge(key);
73+
const edge = this.getEdge(key);
7474

7575
if (!edge) {
7676
throw new Error(`Edge ${key} does not exist`);

src/models/graphs/Graph.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,12 @@ export default abstract class Graph<
154154
return !!this.edges$[key];
155155
}
156156

157-
vertex(key: string) {
158-
return this.vertices$[key];
157+
getVertex(key: string): GV | null {
158+
return this.vertices$[key] ?? null;
159159
}
160160

161-
edge(key: string) {
162-
return this.edges$[key];
161+
getEdge(key: string): GE | null {
162+
return this.edges$[key] ?? null;
163163
}
164164

165165
getEdgesBetween(vertex1key: string, vertex2key: string): Array<GE> {

src/models/graphs/UndirectedGraph.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ export default class UndirectedGraph<V, E> extends Graph<
4949
vertex2key: string,
5050
notifyObservers = true
5151
): UndirectedEdge<E, V> {
52-
const vertex1 = this.vertex(vertex1key);
53-
const vertex2 = this.vertex(vertex2key);
52+
const vertex1 = this.getVertex(vertex1key);
53+
const vertex2 = this.getVertex(vertex2key);
5454

5555
if (!vertex1) {
5656
throw new Error(`Vertex ${vertex1key} does not exist`);
@@ -71,7 +71,7 @@ export default class UndirectedGraph<V, E> extends Graph<
7171
}
7272

7373
override removeEdge(key: string, notifyObservers = true): E {
74-
const edge = this.edge(key);
74+
const edge = this.getEdge(key);
7575

7676
if (!edge) {
7777
throw new Error(`Edge ${key} does not exist`);

src/types/data/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ export type DirectedEdgeData<E> = {
1212

1313
export type UndirectedEdgeData<E> = {
1414
key: string;
15-
vertices: [string, string];
15+
vertices: string[];
1616
data: E;
1717
};

src/types/graphs/shared.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ export interface Graph<V, E> {
3636
hasVertex(key: string): boolean;
3737
hasEdge(key: string): boolean;
3838
getEdgesBetween(vertex1key: string, vertex2key: string): Array<Edge<E, V>>;
39-
vertex(key: string): Vertex<V, E> | undefined;
40-
edge(key: string): Edge<E, V> | undefined;
39+
getVertex(key: string): Vertex<V, E> | null;
40+
getEdge(key: string): Edge<E, V> | null;
4141
insertVertex(key: string, value: V, notifyObservers?: boolean): Vertex<V, E>;
4242
insertEdge(
4343
key: string,

src/types/settings/placement.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ export type DirectedGraphPlacementSettings<V, E> =
1717

1818
export type UndirectedGraphPlacementSettings<V, E> =
1919
| RandomPlacementSettings
20-
| CircularPlacementSettings<V, E>;
20+
| CircularPlacementSettings<V, E>
21+
| OrbitsPlacementSettings;
2122

2223
export type PlacementSettings<V, E> =
2324
| DirectedGraphPlacementSettings<V, E>
@@ -78,6 +79,7 @@ export type OrbitsLayerSizingSettings =
7879
// TODO - maybe add orbits vertices sorting
7980
export type OrbitsPlacementSettings = (SharedPlacementSettings & {
8081
strategy: 'orbits';
82+
roots?: Array<string>;
8183
}) &
8284
OrbitsLayerSizingSettings;
8385

src/utils/algorithms/graphs.ts

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
1+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
12
import Queue from '@/data/Queue';
2-
import { Graph, Vertex } from '@/types/graphs';
3+
import { Vertex } from '@/types/graphs';
34

4-
const bfs = <V, E>(
5-
graph: Graph<V, E>,
5+
export const bfs = <V, E>(
6+
startVertices: Array<Vertex<V, E>>,
67
callback: (data: {
78
vertex: Vertex<V, E>;
89
parent: Vertex<V, E> | null;
910
startVertex: Vertex<V, E>;
1011
depth: number;
11-
}) => boolean,
12-
startVertex?: string
12+
}) => boolean
1313
): Record<string, Vertex<V, E> | null> => {
1414
const parents: Record<string, Vertex<V, E> | null> = {};
15-
const startVertexNode = startVertex && graph.vertex(startVertex);
16-
const startVertices = startVertexNode ? [startVertexNode] : graph.vertices;
1715

1816
for (const sv of startVertices) {
1917
const queue = new Queue<{
@@ -53,11 +51,11 @@ const bfs = <V, E>(
5351
};
5452

5553
export const findGraphComponents = <V, E>(
56-
graph: Graph<V, E>
54+
vertices: Array<Vertex<V, E>>
5755
): Array<Array<Vertex<V, E>>> => {
5856
const components: Record<string, Array<Vertex<V, E>>> = {};
5957

60-
bfs(graph, ({ vertex, startVertex }) => {
58+
bfs(vertices, ({ vertex, startVertex }) => {
6159
if (!components[startVertex.key]) {
6260
components[startVertex.key] = [];
6361
}
@@ -67,3 +65,47 @@ export const findGraphComponents = <V, E>(
6765

6866
return Object.values(components);
6967
};
68+
69+
export const findGraphDiameter = <V, E>(
70+
graphComponent: Array<Vertex<V, E>>
71+
): { diameter: number; path: Array<Vertex<V, E>> } => {
72+
// Start from the last vertex in the component (it always will be
73+
// the farthest vertex from the vertex where BFS started as it
74+
// was inserted last)
75+
const startVertex = graphComponent[graphComponent.length - 1]!;
76+
77+
// Find the farthest vertex from the start vertex and the path
78+
// between them
79+
let farthestDistance = 0;
80+
let farthestVertex: Vertex<V, E> | null = null;
81+
82+
const parents = bfs([startVertex], ({ vertex, depth }) => {
83+
if (depth > farthestDistance) {
84+
farthestDistance = depth;
85+
farthestVertex = vertex;
86+
}
87+
return false;
88+
});
89+
90+
// Find the path between the start vertex and the farthest vertex
91+
const path: Array<Vertex<V, E>> = [];
92+
93+
let currentVertex = farthestVertex as Vertex<V, E> | null;
94+
95+
while (currentVertex) {
96+
path.push(currentVertex);
97+
currentVertex = parents[currentVertex.key] as Vertex<V, E> | null;
98+
}
99+
100+
return {
101+
diameter: farthestDistance,
102+
path
103+
};
104+
};
105+
106+
export const findGraphCenter = <V, E>(
107+
graphComponent: Array<Vertex<V, E>>
108+
): Vertex<V, E> => {
109+
const { path, diameter } = findGraphDiameter(graphComponent);
110+
return path[Math.floor(diameter / 2)]!;
111+
};

0 commit comments

Comments
 (0)