diff --git a/birdvisu/visualisation.py b/birdvisu/visualisation.py new file mode 100644 index 0000000..a50415f --- /dev/null +++ b/birdvisu/visualisation.py @@ -0,0 +1,133 @@ +from birdvisu.maps import Topology, TopologyDifference + +from PySide6 import QtCore, QtGui, QtWidgets + +from dataclasses import dataclass +from enum import Enum, auto + +class NodeType(Enum): + ROUTER = auto() + NETWORK = auto() + +class NodeStatus(Enum): + NORMAL = auto() + MISSING = auto() + EXTRA = auto() + DISCREPANCY = auto() + +@dataclass +class VisualisationNode: + position: tuple[float, float] + type: NodeType + status: NodeStatus + details: tuple[str, list] + + def draw_on_scene(self, scene): + # FIXME: Allow different styles + # TODO: not everything is a QRectF… + x, y = self.position + fill = QtGui.QBrush(QtGui.QColor( + { + # Poor man's switch + # Py 3.10 might be too new and this is probably more compact still. + NodeStatus.NORMAL: 'black', + NodeStatus.MISSING: 'red', + NodeStatus.EXTRA: 'green', + NodeStatus.DISCREPANCY: 'blue', + }[self.status] + )) + w, h = { + NodeType.ROUTER: (40, 40), + NodeType.NETWORK: (10, 10), + }[self.type] + return scene.addRect( + QtCore.QRectF(x, y, w, h), + brush = fill, + ) + + + +def nodes_from_topodiff(topodiff, visu_style='default') -> list[VisualisationNode]: + result = [] + + def get_node(dir_tuple, type): + n, nd = dir_tuple + visu_details = list(filter(lambda x: x[0] == ('visualisation '+visu_style), nd)) + if len(visu_details) > 1: + visu_details = visu_details[0][1] + det_dict = {} + for det in visu_details: + k, v = det.split(' ', maxxplit=1) + det_dict[k] = v + pos = det_dict['position'] + x, y = map(float, pos.strip('[]').split()) + else: + # Probably an extra node + # TODO: proper automatic placement + import random + x = random.randint(0, 2000) # FIXME: dimensions? + y = random.randint(0, 2000) + return VisualisationNode( + position = (x, y), + type = type, + status = NodeStatus.NORMAL, # Will change in a while + details = dir_tuple, + ) + + for r in topodiff.reference.routers: + node = get_node(r, type=NodeType.ROUTER) + if r[0] in topodiff.routers_missing: + node.status = NodeStatus.MISSING + result.append(node) + + for n in topodiff.reference.networks: + node = get_node(n, type=NodeType.NETWORK) + if n[0] in topodiff.networks_missing: + node.status = NodeStatus.MISSING + result.append(node) + + for r in topodiff.actual.routers: + if r[0] in topodiff.routers_extra: + node = get_node(r, type=NodeType.ROUTER) + node.status = NodeStatus.EXTRA + result.append(node) + + for n in topodiff.actual.networks: + if n[0] in topodiff.networks_extra: + node = get_node(n, type=NodeType.NETWORK) + node.status = NodeStatus.EXTRA + result.append(node) + + return result + + +class Visualisation(QtWidgets.QWidget): + def __init__(self, topodiff=None): + super().__init__() + + # guard: + self.scene = None + self.view = None + self.topodiff = None + self.items_nodes = None + self.items_edges = None + + if topodiff is not None: + self.set_topology_difference(topodiff) + + def set_topology_difference(self, topodiff): + self.topodiff = topodiff + self.draw_scene() + + def draw_scene(self): + self.scene = QtWidgets.QGraphicsScene(self) + self.view = QtWidgets.QGraphicsView(self.scene) + + if self.topodiff is not None: + nodes = nodes_from_topodiff(self.topodiff) + # TODO: filtering (dependent on UI) + if self.items_nodes is None: + self.items_nodes = {} # dict: ident -> QGraphicsItem + for n in nodes: + assert n.details[0] not in self.items_nodes, f'{n.details} already in dictionary' + self.items_nodes[n.details[0]] = n.draw_on_scene(self.scene)