Add basic / prototype annotators
parent
e60413bdf1
commit
105f00705c
@ -0,0 +1,185 @@
|
|||||||
|
"""Annotators for visualising.
|
||||||
|
|
||||||
|
Many of these should be somewhere else, because they make assumptions not
|
||||||
|
related to visualisation. Also, only the currently needed annotators were
|
||||||
|
written."""
|
||||||
|
|
||||||
|
from enum import Enum, auto
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import re
|
||||||
|
import random
|
||||||
|
from PySide6 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
|
# Classification
|
||||||
|
|
||||||
|
class DifferenceStatus:
|
||||||
|
"""Describes differences between two topologies"""
|
||||||
|
NORMAL = auto()
|
||||||
|
MISSING = auto()
|
||||||
|
EXTRA = auto()
|
||||||
|
DISCREPANCY = auto()
|
||||||
|
|
||||||
|
def difference_annotator(at, reference_src='reference', actual_src='actual'):
|
||||||
|
"""Adds DifferenceStatuses according to which sources provided which part
|
||||||
|
of topology"""
|
||||||
|
|
||||||
|
topo = at.topology
|
||||||
|
for data, annot in [
|
||||||
|
(topo.routers, at.router_annotations),
|
||||||
|
(topo.networks, at.network_annotations),
|
||||||
|
(topo.links, at.link_annotations),
|
||||||
|
]:
|
||||||
|
for k, v in data.items():
|
||||||
|
verdict = DifferenceStatus.NORMAL
|
||||||
|
if actual_src not in v.sources:
|
||||||
|
verdict = DifferenceStatus.MISSING
|
||||||
|
if reference_src not in v.sources:
|
||||||
|
verdict = DifferenceStatus.EXTRA
|
||||||
|
# Nodes currently cannot have discrepant information (provided the
|
||||||
|
# Topology.is_valid())
|
||||||
|
if data is topo.links:
|
||||||
|
if k.metric < 0:
|
||||||
|
verdict = DifferenceStatus.DISCREPANCY
|
||||||
|
|
||||||
|
# TODO: We should probably disallow mutating previous AnnotatedTopologies.
|
||||||
|
annot[k].append(verdict)
|
||||||
|
return at
|
||||||
|
|
||||||
|
# TODO: annotator for routing trees
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Layouting
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Position:
|
||||||
|
x: float
|
||||||
|
y: float
|
||||||
|
|
||||||
|
def extract_positions(at, directive='visualisation default'):
|
||||||
|
topo = at.topology
|
||||||
|
for data, annot in [
|
||||||
|
(topo.routers, at.router_annotations),
|
||||||
|
(topo.networks, at.network_annotations),
|
||||||
|
]:
|
||||||
|
for k, v in data.items():
|
||||||
|
visu_directive = None
|
||||||
|
for details in k.all_details:
|
||||||
|
visu_directives = list(filter(lambda x: x[0] == directive, details))
|
||||||
|
if len(visu_directives) > 0:
|
||||||
|
visu_directive = visu_directives[-1] # Use the last one found.
|
||||||
|
position = None
|
||||||
|
if visu_directive is not None:
|
||||||
|
for pos in visu_directive[1]:
|
||||||
|
m = re.match(r'position \[([0-9.]+) ([0-9.]+)\]', pos)
|
||||||
|
if pos is not None:
|
||||||
|
x, y = m.groups()
|
||||||
|
position = Position(x = float(x), y = float(y))
|
||||||
|
if position is not None:
|
||||||
|
annot[k].append(position)
|
||||||
|
return at
|
||||||
|
|
||||||
|
def random_posiiton(at):
|
||||||
|
# Fallback when no position could be extracted
|
||||||
|
# TODO: this should use some kind of heuristic or existing layout engine.
|
||||||
|
topo = at.topology
|
||||||
|
for data, annot in [
|
||||||
|
(topo.routers, at.router_annotations),
|
||||||
|
(topo.networks, at.network_annotations),
|
||||||
|
]:
|
||||||
|
for k, v in data.items():
|
||||||
|
if len(annot[k]) > 0 and not isinstance(annot[k][-1], Position):
|
||||||
|
annot[k].append(Position(
|
||||||
|
# This is really last resort, so expecting the canvas to be
|
||||||
|
# FullHD is as good assumption as any.
|
||||||
|
x = float(random.randint(0, 1920)),
|
||||||
|
y = float(random.randint(0, 1080)),
|
||||||
|
))
|
||||||
|
return at
|
||||||
|
|
||||||
|
|
||||||
|
# Rendering
|
||||||
|
|
||||||
|
def assign_brushes(at):
|
||||||
|
for annot in [
|
||||||
|
at.router_annotations,
|
||||||
|
at.network_annotations,
|
||||||
|
at.link_annotations,
|
||||||
|
]:
|
||||||
|
for tags in annot.values():
|
||||||
|
statuses = list(filter(lambda x: isinstance(x, DifferenceStatus), tags))
|
||||||
|
status = statuses[-1] if len(statuses) > 0 else DifferenceStatus.DISCREPANCY # should always have something.
|
||||||
|
color = {
|
||||||
|
DifferenceStatus.NORMAL: 'black',
|
||||||
|
DifferenceStatus.EXTRA: 'green',
|
||||||
|
DifferenceStatus.MISSING: 'red',
|
||||||
|
DifferenceStatus.DISCREPANCY: 'blue',
|
||||||
|
}[status]
|
||||||
|
tags.append(QtGui.QBrush(QtGui.QColor(color)))
|
||||||
|
return at
|
||||||
|
|
||||||
|
def create_qgritems(at):
|
||||||
|
# qgritem = QGraphicsItem
|
||||||
|
# TODO: reasonable visualisation, not just squares :-)
|
||||||
|
# - this probably involves creatin custom qgritems
|
||||||
|
# - We will need own qgritems anyway to handle interaction (e.g.
|
||||||
|
# resolving to Router objects)
|
||||||
|
topo = at.topology
|
||||||
|
for rk, r in topo.routers.items():
|
||||||
|
size = 30
|
||||||
|
brush = None
|
||||||
|
for tag in at.router_annotations[rk][-1::-1]:
|
||||||
|
if isinstance(tag, QtGui.QBrush):
|
||||||
|
brush = tag
|
||||||
|
break
|
||||||
|
pos = None
|
||||||
|
for tag in at.router_annotations[rk][-1::-1]:
|
||||||
|
if isinstance(tag, Position):
|
||||||
|
pos = tag
|
||||||
|
break
|
||||||
|
x = pos.x
|
||||||
|
y = pos.y
|
||||||
|
|
||||||
|
shape = QtWidgets.QGraphicsRectItem(x, y, size, size)
|
||||||
|
shape.setBrush(brush)
|
||||||
|
label = QtWidgets.QGraphicsSimpleTextItem(rk, parent=shape)
|
||||||
|
at.router_annotations[rk].append(shape)
|
||||||
|
|
||||||
|
for nk, n in topo.networks.items():
|
||||||
|
size = 10
|
||||||
|
brush = None
|
||||||
|
for tag in at.network_annotations[nk][-1::-1]:
|
||||||
|
if isinstance(tag, QtGui.QBrush):
|
||||||
|
brush = tag
|
||||||
|
break
|
||||||
|
pos = None
|
||||||
|
for tag in at.network_annotations[nk][-1::-1]:
|
||||||
|
if isinstance(tag, Position):
|
||||||
|
pos = tag
|
||||||
|
break
|
||||||
|
x = pos.x
|
||||||
|
y = pos.y
|
||||||
|
|
||||||
|
shape = QtWidgets.QGraphicsRectItem(x, y, size, size)
|
||||||
|
shape.setBrush(brush)
|
||||||
|
label = QtWidgets.QGraphicsSimpleTextItem(nk, parent=shape)
|
||||||
|
at.network_annotations[nk].append(shape)
|
||||||
|
|
||||||
|
for lk, l in topo.links.items():
|
||||||
|
rid = l.router.ident
|
||||||
|
nid = l.network.ident
|
||||||
|
|
||||||
|
rpos = None
|
||||||
|
for tag in at.router_annotations[rid][-1::-1]:
|
||||||
|
if isinstance(tag, Position):
|
||||||
|
rpos = tag
|
||||||
|
break
|
||||||
|
npos = None
|
||||||
|
for tag in at.network_annotations[nid][-1::-1]:
|
||||||
|
if isinstance(tag, Position):
|
||||||
|
npos = tag
|
||||||
|
break
|
||||||
|
|
||||||
|
line = QtWidgets.QGraphicsLineItem(rpos.x, rpos.y, npos.x, npos.y)
|
||||||
|
at.link_annotations[lk].append(line)
|
||||||
|
|
Loading…
Reference in New Issue