Visualisation graph is now separate.

Maybe other changes, idk
topo-mov
LEdoian 1 year ago
parent 09e654a016
commit 694c7386b4

@ -65,11 +65,11 @@ class PlaceVerticesFromFile(StyleAnnotator):
# TODO: cope with multiple presets in one file? # TODO: cope with multiple presets in one file?
for d, chl in positions: for d, chl in positions:
if d.startswith('visualisation '): if d.startswith('visualisation'):
self.add_positions(chl) self.add_positions(chl)
break break
return self.result return self.result
def add_positions(chl) -> None: def add_positions(self, chl) -> None:
# level-2 are standard directives like from BIRD. For networks, level-3 may contain details (dr / address, router for stubnets maybe?) # level-2 are standard directives like from BIRD. For networks, level-3 may contain details (dr / address, router for stubnets maybe?)
for directive, details in chl: for directive, details in chl:
tag, *det = directive.split() tag, *det = directive.split()
@ -77,7 +77,7 @@ class PlaceVerticesFromFile(StyleAnnotator):
elif tag in ['router', 'xrouter']: elif tag in ['router', 'xrouter']:
router_id = _parse_router_id(det[0]) router_id = _parse_router_id(det[0])
vtxid = VertexID(router_id=router_id, is_router=True, address=None, family=None, dr_id=None, discriminator=None) vtxid = VertexID(router_id=router_id, is_router=True, address=None, family=None, dr_id=None, discriminator=None)
annot = _parse_details(det) annot = _parse_details(details)
self.result.for_vertex[vtxid] = annot self.result.for_vertex[vtxid] = annot
elif tag == 'vlink': elif tag == 'vlink':
raise ValueError('Being a virtual link is not a vertex attribute.') raise ValueError('Being a virtual link is not a vertex attribute.')
@ -97,8 +97,8 @@ class PlaceVerticesFromFile(StyleAnnotator):
print(f'Bad number of candidates for {directive}') print(f'Bad number of candidates for {directive}')
continue # Place with other way continue # Place with other way
rid = cand.pop().router_id rid = cand.pop().router_id
vtxid = VertexID(router_id=rid, is_router=False, address=addr, family=family, dr_if=None, discriminator=None) vtxid = VertexID(router_id=rid, is_router=False, address=addr, family=family, dr_id=None, discriminator=None)
annot = _parse_details(det) annot = _parse_details(details)
self.result.for_vertex[vtxid] = annot self.result.for_vertex[vtxid] = annot
elif tag == 'network': elif tag == 'network':
# We do not know the OSPF version, which is sad. Let us guess # We do not know the OSPF version, which is sad. Let us guess
@ -136,7 +136,7 @@ class PlaceVerticesFromFile(StyleAnnotator):
continue # Place with other way continue # Place with other way
rid = cand.pop().dr_id rid = cand.pop().dr_id
vtxid = VertexID(dr_id=rid, discriminator=None, is_router=False, address=addr, family=AF_INET, router_id=None) vtxid = VertexID(dr_id=rid, discriminator=None, is_router=False, address=addr, family=AF_INET, router_id=None)
annot = _parse_details(det) annot = _parse_details(details)
self.result.for_vertex[vtxid] = annot self.result.for_vertex[vtxid] = annot
else: raise ValueError(f'Unknown directive: {directive}') else: raise ValueError(f'Unknown directive: {directive}')
@ -160,6 +160,11 @@ class PlaceUnplacedVertices(StyleAnnotator):
best = self.previous best = self.previous
already_placed: set[VertexID] = set(topo.annotations[best].for_vertex.keys()) if best is not None else set() already_placed: set[VertexID] = set(topo.annotations[best].for_vertex.keys()) if best is not None else set()
bad_vertices = set()
for v in already_placed:
if v not in topo.topology.vertices.keys():
bad_vertices.add(v)
already_placed -= bad_vertices
to_be_placed: set[VertexID] = topo.topology.vertices.keys() - already_placed to_be_placed: set[VertexID] = topo.topology.vertices.keys() - already_placed
# Let's say that a proximity is any distance shorter than 10% of the current diameter # Let's say that a proximity is any distance shorter than 10% of the current diameter
@ -172,7 +177,7 @@ class PlaceUnplacedVertices(StyleAnnotator):
result = Annotation() result = Annotation()
if best is not None: if len(already_placed) > 0:
for vtxid in already_placed: for vtxid in already_placed:
queue.append(( queue.append((
vtxid, vtxid,
@ -226,7 +231,7 @@ class PlaceUnplacedVertices(StyleAnnotator):
def _default_width_for_cost(cost): def _default_width_for_cost(cost):
# Maybe use exponential decay? # Maybe use exponential decay?
if cost > 0: return 10/cost if cost > 0: return 100/cost
# As thin as possible for no-weight edges (e.g. network → router) # As thin as possible for no-weight edges (e.g. network → router)
return 0 return 0
class EdgeWidthByCost(StyleAnnotator): class EdgeWidthByCost(StyleAnnotator):
@ -263,108 +268,25 @@ class HighlightTopoDiff(StyleAnnotator):
}[v]} }[v]}
return result return result
class HighlightCurrent(StyleAnnotator): class HighlightSPDAG(StyleAnnotator):
idempotent = False idempotent = True
def __init__(self, what): def __init__(self, vtxid):
self.what = what self.vtxid = vtxid
def annotate(self, topo): def annotate(self, topo):
spd_annot = AnnotatorID(ShortestPathTree, vtxid)
topo.run_annotator(spd_annot)
annotation = topo.annotations[spd_annot]
result = Annotation() result = Annotation()
if self.what is None: result.for_edge = {e: {'highlight_colour': (200, 200, 0, 128)} for e in annotation.for_edge.keys()}
# Not going to guess.
result.for_topology = False
return result
# We got an annotator, make sure it has run
topo.run_annotator(self.what)
current = topo.annotations[self.what]
if False: 'alignment'
elif self.what.annotator == analysis.ShortestPathTree:
# Highlight edges
result.for_edge = {e: {'highlight_colour': (200, 200, 0, 128)} for e in current.for_edge.keys()}
return result return result
# Add highlights for other tools here.
class MegaStyler(StyleAnnotator):
"""This Annotator summarizes various previous StyleAnnotators into styles of graphics items.
Either it gets a Sequence of annotators to consider as the parameter, or we
try to utilize most of the existing Annotations. The notable exception is
the ShortestPathTree, since that is only relevant when it is explicitly selected.
We depend on StyleAnnotators' Annotations being the correct dictionaries. class HighlightShortestPath(StyleAnnotator):
idempotent = True
Also, there are not enough Annotators currently shipped with Birdvisu.
Therefore, we do not implement any priority-based colouring, but rather
just apply styles in order, overriding the previous ones. While not
future-proof, it is simple enough to be implemented now and substituted in the
future.
It is almost like a MetaStyler, but sounds much cooler with a G!""" #lol
idempotent = False
def __init__(self, param): def __init__(self, param):
# The boolean determines whether we should run this annotator when it has not been run previously self.start, self.end = param
self.annotator_order: Sequence[tuple[StyleAnnotator, bool]] = [
(PlaceVerticesFromFile, False),
(PlaceUnplacedVertices, True),
(EdgeWidthByCost, True),
(HighlightTopoDiff, True),
(HighlightCurrent, True),
]
if param is not None:
self.detect = False
self.relevant_annotators = param
else:
self.detect = True
def annotate(self, topo): def annotate(self, topo):
# First, set some base styles spd_annot = AnnotatorID(ShortestPathTree, vtxid)
edge_style = defaultdict(lambda: { topo.run_annotator(spd_annot)
'width': 1, annotation = topo.annotations[spd_annot]
'colour': (0,0,0,255),
'highlight_colour': (0,0,0,0), # "None"
})
vertex_style = defaultdict(lambda: {
'position': (0,0), # Fallback to have consistent output, PLEASE OVERRIDE!
'highlight_colour': (0,0,0,0), # "None"
})
# Walk the annotators and collect the annotations
if not self.detect:
relevant_annotators = self.relevant_annotators
else:
# We will be iterating over this often, so this time it is not a set.
relevant_annotators = list(filter(
lambda ann_id: ann_id.annotator != analysis.ShortestPathTree,
topo.annotations.keys()))
for ann_cls, should_run in self.annotator_order:
# Could this code be better?
runs = list(filter(lambda ann_id: ann_id.annotator == ann_cls, relevant_annotators))
if len(runs) > 0:
annot = runs[0] # If there is not only one, we lack a hint what to choose.
elif len(runs) == 0 and should_run:
param = None
# This is sooo ugly: PlaceUnplacedVertices should get a
# parameter if a relevant placement annotator has been run.
# HighlightCurrent needs to know the right current tool.
if False: 'alignment'
elif ann_cls == PlaceUnplacedVertices:
param = next(filter(lambda ann_id: ann_id.annotator == PlaceVerticesFromFile, relevant_annotators), None)
elif ann_cls == HighlightCurrent:
# We hope that relevant_annotators contain a single ShortestPathTree:
param = next(filter(lambda ann_id: ann_id.annotator == analysis.ShortestPathTree, relevant_annotators), None)
annot = AnnotatorID(annotator=ann_cls, param=param)
topo.run_annotator(annot)
else: continue
data = topo.annotations[annot]
for k, v in data.for_vertex.items():
vertex_style[k] |= v
for k, v in data.for_edge.items():
edge_style[k] |= v
result = Annotation() result = Annotation()
for v in topo.topology.vertices.keys(): ...
result.for_vertex[v] = vertex_style[v]
for e in topo.topology.edges:
result.for_edge[e] = edge_style[e]
return result

@ -16,7 +16,10 @@ def _addrs_as_str(addrs):
# TODO: Do not duplicate so much code! # TODO: Do not duplicate so much code!
# TODO: tooltips? # TODO: tooltips?
class RouterGraphicsItem(QGraphicsItem): class RouterGraphicsItem(QGraphicsItem):
def __init__(self, vtxid, style, window, parent=None): default_style = {
'highlight_colour': (0,0,0,0),
}
def __init__(self, vtxid, window, parent=None):
super().__init__(parent) super().__init__(parent)
self.vertex_id = vtxid self.vertex_id = vtxid
self.window = window self.window = window
@ -40,17 +43,13 @@ class RouterGraphicsItem(QGraphicsItem):
p.setStyle(Qt.PenStyle.NoPen) p.setStyle(Qt.PenStyle.NoPen)
self.highlight.setPen(p) self.highlight.setPen(p)
self.setFlag(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable) self.setFlag(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable)
self.apply_position(style) self.apply_style({})
self.apply_style(style)
self.setZValue(200) self.setZValue(200)
def apply_position(self, style):
x, y = style['position']
self.setPos(x, y)
def apply_style(self, style): def apply_style(self, style):
full_style = self.default_style | style
# Highlight: # Highlight:
r,g,b,a = style['highlight_colour'] r,g,b,a = full_style['highlight_colour']
color = QColor(r,g,b,a) color = QColor(r,g,b,a)
br = self.highlight.brush() br = self.highlight.brush()
br.setStyle(Qt.BrushStyle.SolidPattern) br.setStyle(Qt.BrushStyle.SolidPattern)
@ -65,9 +64,10 @@ class RouterGraphicsItem(QGraphicsItem):
all_edges = set() all_edges = set()
for qgri in selected_items: for qgri in selected_items:
# It is a vertex. # It is a vertex.
vtxid = self.window.topologyitems[qgri] vtxid = qgri.vertex_id
vtx = self.window.annot_topo.topology.vertices[vtxid] neighs = self.window.visu_graph[0][vtxid]
all_edges |= vtx.incoming_edges | vtx.outgoing_edges neigh_edges = {tuple(sorted((vtxid, n))) for n in neighs}
all_edges |= neigh_edges
for edge in all_edges: for edge in all_edges:
self.window.graphicsitems[edge].update_line() self.window.graphicsitems[edge].update_line()
super().mouseMoveEvent(evt) super().mouseMoveEvent(evt)
@ -79,7 +79,10 @@ class RouterGraphicsItem(QGraphicsItem):
self.icon.paint(painter, option, widget) self.icon.paint(painter, option, widget)
class NetworkGraphicsItem(QGraphicsItem): class NetworkGraphicsItem(QGraphicsItem):
def __init__(self, vtxid, style, window, parent=None): default_style = {
'highlight_colour': (0,0,0,0),
}
def __init__(self, vtxid, window, parent=None):
super().__init__(parent) super().__init__(parent)
self.vertex_id = vtxid self.vertex_id = vtxid
self.window = window self.window = window
@ -104,17 +107,13 @@ class NetworkGraphicsItem(QGraphicsItem):
p.setStyle(Qt.PenStyle.NoPen) p.setStyle(Qt.PenStyle.NoPen)
self.highlight.setPen(p) self.highlight.setPen(p)
self.setFlag(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable) self.setFlag(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable)
self.apply_position(style) self.apply_style({})
self.apply_style(style)
self.setZValue(200) self.setZValue(200)
def apply_position(self, style):
x, y = style['position']
self.setPos(x, y)
def apply_style(self, style): def apply_style(self, style):
full_style = self.default_style | style
# Highlight: # Highlight:
r,g,b,a = style['highlight_colour'] r,g,b,a = full_style['highlight_colour']
color = QColor(r,g,b,a) color = QColor(r,g,b,a)
br = self.highlight.brush() br = self.highlight.brush()
br.setColor(color) br.setColor(color)
@ -129,9 +128,10 @@ class NetworkGraphicsItem(QGraphicsItem):
all_edges = set() all_edges = set()
for qgri in selected_items: for qgri in selected_items:
# It is a vertex. # It is a vertex.
vtxid = self.window.topologyitems[qgri] vtxid = qgri.vertex_id
vtx = self.window.annot_topo.topology.vertices[vtxid] neighs = self.window.visu_graph[0][vtxid]
all_edges |= vtx.incoming_edges | vtx.outgoing_edges neigh_edges = {tuple(sorted((vtxid, n))) for n in neighs}
all_edges |= neigh_edges
for edge in all_edges: for edge in all_edges:
self.window.graphicsitems[edge].update_line() self.window.graphicsitems[edge].update_line()
super().mouseMoveEvent(evt) super().mouseMoveEvent(evt)
@ -143,15 +143,20 @@ class NetworkGraphicsItem(QGraphicsItem):
self.icon.paint(painter, option, widget) self.icon.paint(painter, option, widget)
class EdgeGraphicsItem(QGraphicsItem): class EdgeGraphicsItem(QGraphicsItem):
def __init__(self, edge, style, window, parent=None): default_style = {
'highlight_colour': (0,0,0,0),
'width': 2,
'colour': (40,40,40,255),
}
def __init__(self, edge, window, parent=None):
super().__init__(parent) super().__init__(parent)
self.edge = edge self.edge = edge
self.window = window self.window = window
# Cache the two related objects. # Cache the two related objects.
# NOTE: this is only possible because the main window first creates vertices and then edges. # NOTE: this is only possible because the main window first creates vertices and then edges.
self.e_source = self.window.graphicsitems[edge.source] self.e_source = self.window.graphicsitems[edge[0]]
self.e_target = self.window.graphicsitems[edge.target] self.e_target = self.window.graphicsitems[edge[1]]
qlinef = QLineF(self.e_source.x(), self.e_source.y(), self.e_target.x(), self.e_target.y()) qlinef = QLineF(self.e_source.x(), self.e_source.y(), self.e_target.x(), self.e_target.y())
self.top_line = QGraphicsLineItem(qlinef, parent=self) self.top_line = QGraphicsLineItem(qlinef, parent=self)
@ -159,7 +164,7 @@ class EdgeGraphicsItem(QGraphicsItem):
self.bottom_line = QGraphicsLineItem(qlinef, parent=self.top_line) self.bottom_line = QGraphicsLineItem(qlinef, parent=self.top_line)
self.bottom_line.setZValue(90) self.bottom_line.setZValue(90)
self.apply_style(style) self.apply_style({})
self.setZValue(100) self.setZValue(100)
def update_line(self): def update_line(self):
@ -168,21 +173,21 @@ class EdgeGraphicsItem(QGraphicsItem):
self.bottom_line.setLine(qlinef) self.bottom_line.setLine(qlinef)
def apply_style(self, style): def apply_style(self, style):
if 'width' in style: full_style = self.default_style | style
p = self.top_line.pen() p = self.top_line.pen()
p.setWidth(style['width']) p.setWidth(full_style['width'])
self.top_line.setPen(p) self.top_line.setPen(p)
p = QPen(p) p = QPen(p)
p.setWidth(1.5*style['width']) p.setWidth(1.5*full_style['width'])
self.bottom_line.setPen(p) self.bottom_line.setPen(p)
if 'colour' in style:
r,g,b,a = style['colour'] r,g,b,a = full_style['colour']
col = QColor(r,g,b,a) col = QColor(r,g,b,a)
p = self.top_line.pen() p = self.top_line.pen()
p.setColor(col) p.setColor(col)
self.top_line.setPen(p) self.top_line.setPen(p)
if 'highlight_colour' in style:
r,g,b,a = style['highlight_colour'] r,g,b,a = full_style['highlight_colour']
col = QColor(r,g,b,a) col = QColor(r,g,b,a)
p = self.bottom_line.pen() p = self.bottom_line.pen()
p.setColor(col) p.setColor(col)

@ -9,6 +9,7 @@ from enum import IntEnum
from socket import AddressFamily from socket import AddressFamily
from collections import defaultdict from collections import defaultdict
from ipaddress import IPv4Network, IPv6Network from ipaddress import IPv4Network, IPv6Network
from functools import total_ordering
class TopologyV3: class TopologyV3:
"""This class represents the topology, that is the graph itself. """This class represents the topology, that is the graph itself.
@ -120,7 +121,8 @@ class VertexType(IntEnum):
ExtraAreaNet = 301 ExtraAreaNet = 301
StubNet = 302 StubNet = 302
@dataclass(frozen=True) @total_ordering
@dataclass(frozen=True, eq=True)
class VertexID: class VertexID:
"""An identifier of a particular vertex. """An identifier of a particular vertex.
@ -192,6 +194,34 @@ class VertexID:
@property @property
def is_network(self): return not self.is_router def is_network(self): return not self.is_router
# We need to be able to order vertices in visualisation. No particular
# order required, as long as its linear.
def __lt__(self, other):
if self.is_router and other.is_network: return True
if self.is_network and other.is_router: return False
if self.is_router:
return self.router_id < other.router_id
if self.dr_id is None and other.dr_id is not None: return True
if self.dr_id is not None and other.dr_id is None: return False
if self.dr_id is None:
#external, xnetwork or stubnet
return (self.address, self.router_id if self.router_id is not None else hash(None)) < (other.address, other.router_id if other.router_id is not None else hash(None))
# network
if self.dr_id != other.dr_id:
return self.dr_id < other.dr_id
if self.discriminator != other.discriminator:
return (self.discriminator or '') < (other.discrininator or '')
# Only tiebreaker: addresses.
if self.address is None and other.address is not None: return True
if self.address is not None and other.address is None: return False
if self.family != other.family:
return self.family < other.family
if isinstance(self.address, tuple) != isinstance(other.address, tuple):
return isinstance(self.address, tuple) < isinstance(other.address, tuple)
# Both tuples and IP addresses are comparable directly.
return self.address < other.address
# Phew!
@dataclass @dataclass
class Vertex: class Vertex:
"""Holds the details about a vertex that are specific to this topology. """Holds the details about a vertex that are specific to this topology.

@ -2,7 +2,7 @@
from birdvisu.annotations import AnnotatedTopology, AnnotatorID from birdvisu.annotations import AnnotatedTopology, AnnotatorID
from birdvisu.annotations.analysis import TopologyDifference, ShortestPathTree from birdvisu.annotations.analysis import TopologyDifference, ShortestPathTree
from birdvisu.annotations.layout import MegaStyler from birdvisu.annotations.layout import PlaceVerticesFromFile, PlaceUnplacedVertices, EdgeWidthByCost, HighlightTopoDiff, HighlightSPDAG, HighlightShortestPath
from birdvisu.ospfsock import BirdSocketConnection from birdvisu.ospfsock import BirdSocketConnection
from birdvisu.providers import BirdSocketTopologyProvider, OspfFileTopologyProvider, OspfFileParseError from birdvisu.providers import BirdSocketTopologyProvider, OspfFileTopologyProvider, OspfFileParseError
from birdvisu.topo_v3 import TopologyV3, VertexID from birdvisu.topo_v3 import TopologyV3, VertexID
@ -78,9 +78,11 @@ class BirdTopologyLoader(QtWidgets.QDialog):
class MainWindow(QtWidgets.QMainWindow): class MainWindow(QtWidgets.QMainWindow):
class Tool(Enum): class Mode(Enum):
MoveTopology = auto()
ShortestPath = auto() ShortestPath = auto()
ShortestPathDAG = auto()
TopologyDifference = auto()
EdgeWeight = auto()
def create_menus(self): def create_menus(self):
print('Creating menus…') print('Creating menus…')
@ -112,14 +114,21 @@ class MainWindow(QtWidgets.QMainWindow):
refresh_act.triggered.connect(self.refreshTopologies) refresh_act.triggered.connect(self.refreshTopologies)
topo_menu.addAction(refresh_act) topo_menu.addAction(refresh_act)
positions = self.menubar.addMenu('&Positions')
loadp = QtGui.QAction('Load from file', self)
loadp.triggered.connect(self.load_positions)
positions.addAction(loadp)
def __init__(self, *a, **kwa): def __init__(self, *a, **kwa):
super().__init__(*a, **kwa) super().__init__(*a, **kwa)
self.ref_topo_provider = None self.ref_topo_provider = None
self.cur_topo_provider = None self.cur_topo_provider = None
self.set_initial_annotators()
self.annot_topo = None self.annot_topo = None
# The actual graph to show: list of neighbours and of edges
self.visu_graph: tuple[dict[VertexID, set[VertexID]], set[VertexID, VertexID]] = None
self.highlighter = None self.highlighter = None
self.tool = self.Tool.MoveTopology self.mode = None
self.positions: dict[VertexID, tuple[float, float]] = dict()
self.scene = QtWidgets.QGraphicsScene() self.scene = QtWidgets.QGraphicsScene()
self.view = QtWidgets.QGraphicsView(self.scene) self.view = QtWidgets.QGraphicsView(self.scene)
self.view.setDragMode(self.view.DragMode.ScrollHandDrag) self.view.setDragMode(self.view.DragMode.ScrollHandDrag)
@ -127,6 +136,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.statusbar = self.statusBar() self.statusbar = self.statusBar()
self.statusbar.showMessage('Hello!') self.statusbar.showMessage('Hello!')
self.create_menus() self.create_menus()
self.edgeWeightMode()
self.autoLoad() self.autoLoad()
#Hack #Hack
@ -137,27 +147,76 @@ class MainWindow(QtWidgets.QMainWindow):
self.cur_topo_provider = BirdSocketTopologyProvider(instance='gennet4', area=1, version=2) self.cur_topo_provider = BirdSocketTopologyProvider(instance='gennet4', area=1, version=2)
self.refreshTopologies() self.refreshTopologies()
@Slot()
def load_positions(self):
filename = QtWidgets.QFileDialog.getOpenFileName(self, 'Open vertex positions', '.', 'OSPF files(*.visu);;All files(*)')[0]
if filename == '': return # Do nothing
self.positions_from_file(filename)
def positions_from_file(self, fn):
if self.visu_graph is None: return # nothing to do yet.
# Let's be frank: this used to be StyleAnnotators and it is not re-implemented.
pvff = AnnotatorID(PlaceVerticesFromFile, fn)
self.annot_topo.run_annotator(pvff)
placements = PlaceUnplacedVertices(pvff).annotate(self.annot_topo)
self.positions = {v: d['position'] for v, d in placements.for_vertex.items()}
# Apply the positions:
for v, pos in self.positions.items():
x, y = pos
self.graphicsitems[v].setPos(x, y)
# Fix all the edges probably
for e in self.visu_graph[1]:
self.graphicsitems[e].update_line()
@Slot()
def savePositions(self):
...
@Slot()
def apply_styles(self):
if self.visu_graph is None: return # First need graph.
styles_ant = self.highlighter.annotate(self.annot_topo)
for_vertex = styles_ant.for_vertex
for_edge_ant = styles_ant.for_edge
# We must resolve conflicts
for_edge: dict[tuple[VertexID, VertexID], tuple[Edge, dict]] = dict() # → edge, styling dict.
for e, sty in for_edge_ant.items():
a, b = tuple(sorted((e.source, e.target)))
if (a,b) not in for_edge:
for_edge[(a,b)] = (e, sty)
if e.cost == 0: continue # A collision that we do not care about
oe, _sty = for_edge[(a,b)]
if oe.cost == 0 or oe.cost > e.cost:
for_edge[(a,b)] = (e, sty)
# Actually apply the style:
for v, sty in for_vertex.items():
self.graphicsitems[v].apply_style(sty)
for e, tup in for_edge.items():
self.graphicsitems[e].apply_style(tup[1])
@Slot()
def dagMode(self):
self.mode = self.Mode.ShortestPathDAG
self.highlighter = HighlightSPDAG(...)
self.apply_styles()
@Slot()
def topoDiffMode(self):
self.mode = self.Mode.TopologyDifference
self.highlighter = HighlightTopoDiff(None)
self.apply_styles()
def set_initial_annotators(self): @Slot()
# We have three kinds of annotators: def edgeWeightMode(self):
# - The essential analytic ones (TopologyDifference) self.mode = self.Mode.EdgeWeight
# - The one that describes the current tool (when that is Annotator backed, like for ShortestPathTree) self.highlighter = EdgeWidthByCost(None)
# - The styling ones, that actually help visualise stuff (MegaStyler) self.apply_styles()
self.essential_annotators = [AnnotatorID(TopologyDifference)]
self.current_annotators = [AnnotatorID(ShortestPathTree, (VertexID(
family=None,
is_router=True,
address=None,
router_id=int(IPv4Address('172.23.100.10')),
dr_id=None,
discriminator=None
), 'current')),
]
self.styling_annotators = [AnnotatorID(MegaStyler, tuple(self.essential_annotators + self.current_annotators))]
@Slot() @Slot()
def shortestPathMode(self): def shortestPathMode(self):
self.tool = self.Tool.ShortestPath self.mode = self.Mode.ShortestPath
...
@Slot() @Slot()
def openRefTopology(self): def openRefTopology(self):
@ -226,40 +285,45 @@ class MainWindow(QtWidgets.QMainWindow):
combined_topology = TopologyV3.combine_topologies(reference=ref_topo, current=cur_topo) combined_topology = TopologyV3.combine_topologies(reference=ref_topo, current=cur_topo)
combined_topology.freeze() combined_topology.freeze()
self.annot_topo = AnnotatedTopology(combined_topology) self.annot_topo = AnnotatedTopology(combined_topology)
for ann_id in self.essential_annotators + self.current_annotators + self.styling_annotators: self.topo_to_graph()
self.annot_topo.run_annotator(ann_id)
# Draw it
self.draw_visu() self.draw_visu()
def topo_to_graph(self):
"""Converts an AnnotatedTopology to a graph"""
# We actually do not care about the annotations
topo = self.annot_topo.topology
neighbours = {v: set() for v in topo.vertices.keys()} # Neighbour lists
edges: set[tuple[VertexID, VertexID]] = set()
for e in topo.edges:
neighbours[e.source].add(e.target)
neighbours[e.target].add(e.source)
first = min([e.source, e.target])
second = max([e.source, e.target])
edges.add((first, second))
self.visu_graph = (neighbours, edges)
def draw_visu(self): def draw_visu(self):
# just take the result of the MegaStyler and create vertices according to the style. self.graphicsitems: dict[VertexID|tuple[VertexID, VertexID], QGraphicsItem] = dict()
megastyler = self.styling_annotators[-1] for vtxid in self.visu_graph[0].keys():
assert megastyler.annotator == MegaStyler gritem = self.create_vertex(vtxid)
msann = self.annot_topo.annotations[megastyler]
# These two dictionaries are used from outside, so that QGraphicsItems
# can resolve graph relations (e.g. to draw the line between the
# correct Vertices)
self.graphicsitems: dict[VertexID, QGraphicsItem] = dict()
self.topologyitems: dict[QGraphicsItem, VertexID|Edge] = dict()
for vtxid, style in msann.for_vertex.items():
gritem = self.create_vertex(vtxid, style)
self.graphicsitems[vtxid] = gritem self.graphicsitems[vtxid] = gritem
self.topologyitems[gritem] = vtxid
self.scene.addItem(gritem) self.scene.addItem(gritem)
for edge, style in msann.for_edge.items(): for edge in self.visu_graph[1]:
gritem = self.create_edge(edge, style) gritem = self.create_edge(edge)
self.graphicsitems[edge] = gritem self.graphicsitems[edge] = gritem
self.topologyitems[gritem] = edge
self.scene.addItem(gritem) self.scene.addItem(gritem)
self.positions_from_file(None)
self.apply_styles()
def create_vertex(self, vtxid, style): def create_vertex(self, vtxid):
if vtxid.is_router: if vtxid.is_router:
return RouterGraphicsItem(vtxid, style, self) return RouterGraphicsItem(vtxid, self)
else: else:
return NetworkGraphicsItem(vtxid, style, self) return NetworkGraphicsItem(vtxid, self)
def create_edge(self, edge):
return EdgeGraphicsItem(edge, self)
def create_edge(self, edge, style):
return EdgeGraphicsItem(edge, style, self)
main_window = MainWindow() main_window = MainWindow()
main_window.show() main_window.show()

Loading…
Cancel
Save