Skip to content

Commit b5bc74c

Browse files
feat: add updateVisibility method to Edge class (#209)
closes #199 closes #200 closes #208 --------- Co-authored-by: Tomás Grüner <[email protected]>
1 parent 2bbf25d commit b5bc74c

File tree

5 files changed

+152
-4
lines changed

5 files changed

+152
-4
lines changed

src/graphics/renderables/device_info.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@ export class DeviceInfo extends BaseInfo {
2626

2727
protected addCommonInfoFields(): void {
2828
const { id, mac } = this.device;
29-
const connections = this.device.viewgraph
30-
.getConnections(id)
31-
.map((edge) => edge.otherEnd(id));
29+
const connections = this.device.viewgraph.getVisibleConnectedDeviceIds(id);
3230

3331
this.information.addField(TOOLTIP_KEYS.ID, id.toString(), TOOLTIP_KEYS.ID);
3432
this.information.addListField(

src/types/data-devices/dSwitch.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ export class DataSwitch extends DataDevice {
3131
};
3232
}
3333

34-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
3534
private forwardFrame(
3635
// eslint-disable-next-line @typescript-eslint/no-unused-vars
3736
frame: EthernetFrame,

src/types/edge.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,44 @@ export class Edge extends Graphics {
121121
return this.data;
122122
}
123123

124+
makeVisible() {
125+
this.visible = true;
126+
}
127+
128+
updateVisibility(): void {
129+
const device1 = this.viewgraph.getDevice(this.data.from.id);
130+
const device2 = this.viewgraph.getDevice(this.data.to.id);
131+
132+
if (!device1 || !device2) {
133+
console.warn(
134+
`One or both devices for edge ${this.data.from.id}${this.data.to.id} are missing.`,
135+
);
136+
this.visible = false;
137+
return;
138+
}
139+
140+
// Get visible devices reachable from each device
141+
const device1CanReachVisibleDevice = this.viewgraph.canReachVisibleDevice(
142+
device1.id,
143+
device2.id,
144+
);
145+
const device2CanReachVisibleDevice = this.viewgraph.canReachVisibleDevice(
146+
device2.id,
147+
device1.id,
148+
);
149+
150+
// Update the visibility of the edge
151+
this.visible = device1CanReachVisibleDevice && device2CanReachVisibleDevice;
152+
}
153+
154+
/**
155+
* Updates the position of an edge connecting two devices, taking into account their visibility
156+
* and dimensions. If a device is visible, the edge will leave a space around the device's center
157+
* to account for its size. Otherwise, the edge will connect directly to the device's position.
158+
*
159+
* @param device1 - The first device (starting point of the edge).
160+
* @param device2 - The second device (ending point of the edge).
161+
*/
124162
private updatePosition(device1: ViewDevice, device2: ViewDevice) {
125163
const dx = device2.x - device1.x;
126164
const dy = device2.y - device1.y;

src/types/graphs/graph.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
export type VertexId = number;
22

3+
interface DfsOptions<Vertex, Edge> {
4+
vertexFilter?: (vertex: Vertex, vertexId: VertexId) => boolean;
5+
edgeFilter?: (edge: Edge, id1: VertexId, id2: VertexId) => boolean;
6+
}
7+
38
export interface RemovedVertexData<Vertex, Edge> {
49
id: VertexId;
510
vertex: Vertex;
@@ -106,6 +111,50 @@ export class Graph<Vertex, Edge> {
106111
this.vertices.clear();
107112
this.edges.clear();
108113
}
114+
115+
/**
116+
* Travels the graph in a Depth-First Search manner.
117+
* @param startId ID of the vertex to start from
118+
* @param opts Options for filtering vertices or edges during traversal.
119+
* If the function returns true, the vertex's neighbors are visited or the edge is traversed.
120+
*/
121+
dfs(startId: VertexId, opts: DfsOptions<Vertex, Edge>): void {
122+
opts.edgeFilter = opts.edgeFilter || (() => true);
123+
opts.vertexFilter = opts.vertexFilter || (() => true);
124+
this.recursiveDfs(startId, opts, new Set<VertexId>());
125+
}
126+
127+
private recursiveDfs(
128+
currentId: VertexId,
129+
opts: DfsOptions<Vertex, Edge>,
130+
visited: Set<VertexId>,
131+
) {
132+
if (visited.has(currentId)) {
133+
return; // Avoid cycles
134+
}
135+
visited.add(currentId);
136+
137+
const currentDevice = this.getVertex(currentId);
138+
if (!currentDevice) {
139+
console.warn(`Device not found: ${currentId}`);
140+
return; // If the device doesn't exist, stop
141+
}
142+
if (!opts.vertexFilter(currentDevice, currentId)) {
143+
return; // If the filter returns false, stop
144+
}
145+
146+
// Explore neighbors recursively
147+
for (const [neighborId, edge] of this.getEdges(currentId)) {
148+
// If the neighbor hasn't been visited, we check the edge filter
149+
// and visit the neighbor if the filter returns true
150+
if (
151+
!visited.has(neighborId) &&
152+
opts.edgeFilter(edge, currentId, neighborId)
153+
) {
154+
this.recursiveDfs(neighborId, opts, visited);
155+
}
156+
}
157+
}
109158
}
110159

111160
function* edgeIterator<Edge>(

src/types/graphs/viewgraph.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ export class ViewGraph {
104104
);
105105
return null;
106106
}
107+
107108
if (this.graph.hasEdge(device1.id, device2.id)) {
108109
console.warn(`Edge with ID ${device1.id},${device2.id} already exists.`);
109110
return this.graph.getEdge(device1.id, device2.id);
@@ -197,8 +198,25 @@ export class ViewGraph {
197198
device.updateVisibility();
198199
}
199200

201+
// Refresh and make all edges visible again
200202
for (const [, , edge] of this.graph.getAllEdges()) {
201203
edge.refresh();
204+
edge.makeVisible();
205+
}
206+
207+
// Iterate until no changes are made
208+
for (let i = 0; i < this.graph.getVertexCount(); i++) {
209+
let hadChanges = false;
210+
// For each iteration, update visibility of all edges.
211+
// This takes into account currently visible edges and devices.
212+
for (const [, , edge] of this.graph.getAllEdges()) {
213+
const previousVisibility = edge.visible;
214+
edge.updateVisibility();
215+
hadChanges ||= previousVisibility !== edge.visible;
216+
}
217+
if (!hadChanges) {
218+
break;
219+
}
202220
}
203221

204222
// warn Packet Manager that the layer has been changed
@@ -223,6 +241,52 @@ export class ViewGraph {
223241
return Array.from(edges).map(([, edge]) => edge);
224242
}
225243

244+
getVisibleConnectedDeviceIds(deviceId: DeviceId): DeviceId[] {
245+
const visibleDevices: DeviceId[] = []; // Stores visible connected device IDs
246+
247+
const vertexFilter = (device: ViewDevice, id: DeviceId): boolean => {
248+
// If device is visible, add it to the set and stop traversal
249+
if (device.visible && id !== deviceId) {
250+
visibleDevices.push(id);
251+
return false;
252+
}
253+
return true;
254+
};
255+
256+
this.graph.dfs(deviceId, { vertexFilter });
257+
258+
return visibleDevices; // Return the list of visible connected device IDs
259+
}
260+
261+
/**
262+
* Checks if a device can reach any visible device, excluding the specified device from traversal.
263+
* @param startId ID of the device to check for
264+
* @param excludeId ID of a device to be excluded from traversal
265+
* @returns True if the device can reach any visible device, otherwise false.
266+
*/
267+
canReachVisibleDevice(startId: DeviceId, excludeId: DeviceId): boolean {
268+
const visibleDevices = new Set<DeviceId>();
269+
270+
const vertexFilter = (device: ViewDevice, id: DeviceId): boolean => {
271+
// If the device is excluded, skip it and stop traversal
272+
if (id === excludeId) {
273+
return false;
274+
}
275+
// If device is visible, add it to the set and stop traversal
276+
if (device.visible) {
277+
visibleDevices.add(id);
278+
return false;
279+
}
280+
return true;
281+
};
282+
283+
// Avoid invisible edges
284+
const edgeFilter = (edge: Edge) => edge.visible;
285+
286+
this.graph.dfs(startId, { vertexFilter, edgeFilter });
287+
return visibleDevices.size > 0;
288+
}
289+
226290
getAllConnections(): Edge[] {
227291
return Array.from(this.graph.getAllEdges()).map(([, , edge]) => edge);
228292
}

0 commit comments

Comments
 (0)