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