mirror of https://gitlab.com/pamhyr/pamhyr2
Merge branch 'multiple-edge-between-nodes' into v0.0.8
commit
f62b209ffb
|
|
@ -637,7 +637,7 @@ class AddNodeCommand(QUndoCommand):
|
||||||
self._node = node
|
self._node = node
|
||||||
|
|
||||||
def undo(self):
|
def undo(self):
|
||||||
self._graph.remove_node(self._node.name)
|
self._graph.remove_node(self._node)
|
||||||
|
|
||||||
def redo(self):
|
def redo(self):
|
||||||
self._graph.insert_node(self._node)
|
self._graph.insert_node(self._node)
|
||||||
|
|
|
||||||
|
|
@ -142,26 +142,21 @@ class Graph(object):
|
||||||
def insert_node(self, node):
|
def insert_node(self, node):
|
||||||
return self._add_node(node)
|
return self._add_node(node)
|
||||||
|
|
||||||
def remove_node(self, node_name: str):
|
def remove_node(self, node):
|
||||||
self._nodes = list(
|
self._nodes.remove(node)
|
||||||
filter(
|
self._remove_associated_edge(node)
|
||||||
lambda n: n.name != node_name,
|
|
||||||
self._nodes
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self._remove_associated_edge(node_name)
|
|
||||||
self._status.modified()
|
self._status.modified()
|
||||||
|
|
||||||
def _remove_associated_edge(self, node_name: str):
|
def _remove_associated_edge(self, node: str):
|
||||||
edges = list(
|
edges = list(
|
||||||
filter(
|
filter(
|
||||||
lambda e: (e.node1.name == node_name or
|
lambda e: (e.node1 == node or
|
||||||
e.node2.name == node_name),
|
e.node2 == node),
|
||||||
self._edges,
|
self._edges,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
for edge in edges:
|
for edge in edges:
|
||||||
self.remove_edge(edge.name)
|
self.remove_edge(edge)
|
||||||
|
|
||||||
def create_node(self, x: float = 0.0, y: float = 0.0):
|
def create_node(self, x: float = 0.0, y: float = 0.0):
|
||||||
node = self._create_node(x, y)
|
node = self._create_node(x, y)
|
||||||
|
|
@ -176,11 +171,11 @@ class Graph(object):
|
||||||
return edge
|
return edge
|
||||||
|
|
||||||
def _add_edge(self, edge):
|
def _add_edge(self, edge):
|
||||||
# This edge already exists ?
|
# # This edge already exists ?
|
||||||
if any(filter(lambda e: (e.node1 == edge.node1 and
|
# if any(filter(lambda e: (e.node1 == edge.node1 and
|
||||||
e.node2 == edge.node2),
|
# e.node2 == edge.node2),
|
||||||
self._edges)):
|
# self._edges)):
|
||||||
return None
|
# return None
|
||||||
|
|
||||||
self._edges.append(edge)
|
self._edges.append(edge)
|
||||||
|
|
||||||
|
|
@ -197,13 +192,8 @@ class Graph(object):
|
||||||
def create_edge(self, n1: Node, n2: Node):
|
def create_edge(self, n1: Node, n2: Node):
|
||||||
return self._create_edge(n1, n2)
|
return self._create_edge(n1, n2)
|
||||||
|
|
||||||
def remove_edge(self, edge_name: str):
|
def remove_edge(self, edge):
|
||||||
self._edges = list(
|
self._edges.remove(edge)
|
||||||
filter(
|
|
||||||
lambda e: e.name != edge_name,
|
|
||||||
self._edges
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self._status.modified()
|
self._status.modified()
|
||||||
|
|
||||||
def is_upstream_node(self, node):
|
def is_upstream_node(self, node):
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ class GraphTestCase(unittest.TestCase):
|
||||||
|
|
||||||
e0 = g.add_edge(n0, n1)
|
e0 = g.add_edge(n0, n1)
|
||||||
|
|
||||||
g.remove_edge(e0.name)
|
g.remove_edge(e0)
|
||||||
|
|
||||||
self.assertEqual(g.nodes_counts(), 2)
|
self.assertEqual(g.nodes_counts(), 2)
|
||||||
self.assertEqual(g.edges_counts(), 0)
|
self.assertEqual(g.edges_counts(), 0)
|
||||||
|
|
@ -73,7 +73,7 @@ class GraphTestCase(unittest.TestCase):
|
||||||
|
|
||||||
e0 = g.add_edge(n0, n1)
|
e0 = g.add_edge(n0, n1)
|
||||||
|
|
||||||
g.remove_node(n0.name)
|
g.remove_node(n0)
|
||||||
|
|
||||||
self.assertEqual(g.nodes_counts(), 1)
|
self.assertEqual(g.nodes_counts(), 1)
|
||||||
self.assertEqual(g.edges_counts(), 0)
|
self.assertEqual(g.edges_counts(), 0)
|
||||||
|
|
@ -88,7 +88,7 @@ class GraphTestCase(unittest.TestCase):
|
||||||
e0 = g.add_edge(n0, n1)
|
e0 = g.add_edge(n0, n1)
|
||||||
e1 = g.add_edge(n1, n2)
|
e1 = g.add_edge(n1, n2)
|
||||||
|
|
||||||
g.remove_node(n1.name)
|
g.remove_node(n1)
|
||||||
|
|
||||||
self.assertEqual(g.nodes_counts(), 2)
|
self.assertEqual(g.nodes_counts(), 2)
|
||||||
self.assertEqual(g.edges_counts(), 0)
|
self.assertEqual(g.edges_counts(), 0)
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,8 @@ class NodeItem(QGraphicsItem):
|
||||||
|
|
||||||
|
|
||||||
class EdgeItem(QGraphicsItem):
|
class EdgeItem(QGraphicsItem):
|
||||||
|
_arc_dist = 0.1
|
||||||
|
|
||||||
Type = QGraphicsItem.UserType + 2
|
Type = QGraphicsItem.UserType + 2
|
||||||
|
|
||||||
def __init__(self, src_node_item, dest_node_item,
|
def __init__(self, src_node_item, dest_node_item,
|
||||||
|
|
@ -131,6 +133,21 @@ class EdgeItem(QGraphicsItem):
|
||||||
self.edge = edge
|
self.edge = edge
|
||||||
|
|
||||||
self.graph = graph_widget
|
self.graph = graph_widget
|
||||||
|
self._ind = 1
|
||||||
|
|
||||||
|
geometry = self.compute_arc_two_point(
|
||||||
|
QPointF(
|
||||||
|
self.src_node.pos().x(),
|
||||||
|
self.src_node.pos().y()
|
||||||
|
),
|
||||||
|
QPointF(
|
||||||
|
self.dest_node.pos().x(),
|
||||||
|
self.dest_node.pos().y()
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
self._bound_rect = geometry['bound_rect']
|
||||||
|
self._arc = geometry['arc']
|
||||||
|
|
||||||
self.setAcceptedMouseButtons(Qt.NoButton)
|
self.setAcceptedMouseButtons(Qt.NoButton)
|
||||||
|
|
||||||
|
|
@ -140,32 +157,21 @@ class EdgeItem(QGraphicsItem):
|
||||||
def boundingRect(self):
|
def boundingRect(self):
|
||||||
# Rectangle of edge for display update
|
# Rectangle of edge for display update
|
||||||
pen_width = 2.0
|
pen_width = 2.0
|
||||||
extra = (pen_width + 5) / 2.0
|
extra = 0 #(pen_width + 5) / 2.0
|
||||||
|
|
||||||
return QRectF(
|
return self._bound_rect.normalized().adjusted(
|
||||||
self.src_node.pos(),
|
-extra, -extra, extra, extra
|
||||||
QSizeF(
|
|
||||||
self.dest_node.pos().x() - self.src_node.pos().x(),
|
|
||||||
self.dest_node.pos().y() - self.src_node.pos().y()
|
|
||||||
)
|
)
|
||||||
).normalized().adjusted(-extra, -extra, extra, extra)
|
|
||||||
|
|
||||||
def shape(self):
|
def shape(self):
|
||||||
# Shape around edge for mouse selection
|
return self._arc
|
||||||
vec = self.dest_node.pos() - self.src_node.pos()
|
|
||||||
vec = vec * (5 / math.sqrt(QPointF.dotProduct(vec, vec)))
|
|
||||||
extra = QPointF(vec.y(), -vec.x())
|
|
||||||
|
|
||||||
result = QPainterPath(self.src_node.pos() - vec + extra)
|
def paint(self, painter, option, widget):
|
||||||
result.lineTo(self.src_node.pos() - vec - extra)
|
#self.paint_line(painter, option, widget)
|
||||||
result.lineTo(self.dest_node.pos() + vec - extra)
|
self.paint_arc(painter, option, widget)
|
||||||
result.lineTo(self.dest_node.pos() + vec + extra)
|
|
||||||
result.closeSubpath()
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
@timer
|
@timer
|
||||||
def paint(self, painter, option, widget):
|
def paint_line(self, painter, option, widget):
|
||||||
# Draw shape of the edge
|
# Draw shape of the edge
|
||||||
# color = QColor(Qt.white)
|
# color = QColor(Qt.white)
|
||||||
# color.setAlpha(128)
|
# color.setAlpha(128)
|
||||||
|
|
@ -191,40 +197,169 @@ class EdgeItem(QGraphicsItem):
|
||||||
color, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin
|
color, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
# Draw the line
|
|
||||||
painter.drawLine(line)
|
painter.drawLine(line)
|
||||||
|
|
||||||
line_center = QPointF(
|
# Draw arrow
|
||||||
|
center = QPointF(
|
||||||
(self.src_node.pos().x() + self.dest_node.pos().x()) / 2,
|
(self.src_node.pos().x() + self.dest_node.pos().x()) / 2,
|
||||||
(self.src_node.pos().y() + self.dest_node.pos().y()) / 2,
|
(self.src_node.pos().y() + self.dest_node.pos().y()) / 2,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Draw the arrows
|
|
||||||
brush = QBrush()
|
|
||||||
brush.setColor(color)
|
|
||||||
brush.setStyle(Qt.SolidPattern)
|
|
||||||
|
|
||||||
angle = math.acos(line.dx() / line.length())
|
angle = math.acos(line.dx() / line.length())
|
||||||
if line.dy() >= 0:
|
if line.dy() >= 0:
|
||||||
angle = (math.pi * 2.0) - angle
|
angle = (math.pi * 2.0) - angle
|
||||||
|
|
||||||
|
self.paint_arrow(
|
||||||
|
painter, option, widget,
|
||||||
|
color, center, angle
|
||||||
|
)
|
||||||
|
|
||||||
|
@timer
|
||||||
|
def paint_arc(self, painter, option, widget):
|
||||||
|
line = QLineF(self.src_node.pos(), self.dest_node.pos())
|
||||||
|
if line.length() == 0.0:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Select color
|
||||||
|
# color = Qt.darkBlue
|
||||||
|
color = Qt.black
|
||||||
|
if self.graph.selected_item() == self:
|
||||||
|
color = Qt.red
|
||||||
|
elif self.graph.current_edge() == self:
|
||||||
|
color = Qt.blue
|
||||||
|
elif not self.graph.graph.is_enable_edge(self.edge):
|
||||||
|
color = Qt.darkGray
|
||||||
|
|
||||||
|
painter.setPen(
|
||||||
|
QPen(
|
||||||
|
color, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
geometry = self.compute_arc_two_point(
|
||||||
|
QPointF(
|
||||||
|
self.src_node.pos().x(),
|
||||||
|
self.src_node.pos().y()
|
||||||
|
),
|
||||||
|
QPointF(
|
||||||
|
self.dest_node.pos().x(),
|
||||||
|
self.dest_node.pos().y()
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
self._bound_rect = geometry['bound_rect']
|
||||||
|
self._arc = geometry['arc']
|
||||||
|
|
||||||
|
painter.drawPath(self._arc)
|
||||||
|
|
||||||
|
line = geometry['line']
|
||||||
|
angle = math.acos(line.dx() / line.length())
|
||||||
|
if line.dy() >= 0:
|
||||||
|
angle = (math.pi * 2.0) - angle
|
||||||
|
|
||||||
|
self.paint_arrow(
|
||||||
|
painter, option, widget,
|
||||||
|
color, geometry['c'], angle,
|
||||||
|
)
|
||||||
|
|
||||||
|
def compute_arc_two_point(self, a, b):
|
||||||
|
# This function is a black magic spell for invoking an arc
|
||||||
|
# path between two points in Qt! (In fact, it's just cryptic
|
||||||
|
# trigonometry functions from stackoverflow:
|
||||||
|
# https://stackoverflow.com/questions/26901540/arc-in-qgraphicsscene)
|
||||||
|
c = self.compute_arc_p3(a, b)
|
||||||
|
|
||||||
|
line_ac = QLineF(a, c)
|
||||||
|
line_bc = QLineF(b, c)
|
||||||
|
line_ab = QLineF(a, b)
|
||||||
|
|
||||||
|
rad = abs(
|
||||||
|
line_bc.length() / (
|
||||||
|
2 * math.sin(
|
||||||
|
math.radians(
|
||||||
|
line_ac.angleTo(line_ab)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
bisector_bc = QLineF(line_bc.pointAt(0.5), line_bc.p2())
|
||||||
|
bisector_bc.setAngle(line_bc.normalVector().angle())
|
||||||
|
|
||||||
|
bisector_ab = QLineF(line_ab.pointAt(0.5), line_ab.p2())
|
||||||
|
bisector_ab.setAngle(line_ab.normalVector().angle())
|
||||||
|
|
||||||
|
center = QPointF(0,0)
|
||||||
|
bisector_ab.intersect(bisector_bc, center)
|
||||||
|
|
||||||
|
circle = QRectF(
|
||||||
|
center.x() - rad,
|
||||||
|
center.y() - rad,
|
||||||
|
rad * 2, rad * 2
|
||||||
|
)
|
||||||
|
|
||||||
|
line_oa = QLineF(center, a)
|
||||||
|
line_ob = QLineF(center, b)
|
||||||
|
line_oc = QLineF(center, c)
|
||||||
|
|
||||||
|
start = line_ob
|
||||||
|
end = line_oa
|
||||||
|
|
||||||
|
w = 1
|
||||||
|
bounding_rect = circle.adjusted(
|
||||||
|
-w, -w, w, w
|
||||||
|
)
|
||||||
|
|
||||||
|
path = QPainterPath()
|
||||||
|
path.arcMoveTo(circle, start.angle())
|
||||||
|
path.arcTo(circle, start.angle(), start.angleTo(end))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'bound_rect': bounding_rect,
|
||||||
|
'arc': path,
|
||||||
|
'c': c,
|
||||||
|
'line': line_ab,
|
||||||
|
}
|
||||||
|
|
||||||
|
def compute_arc_p3(self, p1, p2):
|
||||||
|
dist_p3 = self._ind * self._arc_dist
|
||||||
|
|
||||||
|
center_p1_p2 = QPointF(
|
||||||
|
(p1.x() + p2.x()) / 2,
|
||||||
|
(p1.y() + p2.y()) / 2
|
||||||
|
)
|
||||||
|
|
||||||
|
# u = (p2.x() - p1.x(), p2.y() - p1.y())
|
||||||
|
v = (p2.y() - p1.y(), p1.x() - p2.x())
|
||||||
|
p3 = QPointF(
|
||||||
|
center_p1_p2.x() + dist_p3 * v[0],
|
||||||
|
center_p1_p2.y() + dist_p3 * v[1]
|
||||||
|
)
|
||||||
|
|
||||||
|
return p3
|
||||||
|
|
||||||
|
def paint_arrow(self, painter, option, widget,
|
||||||
|
color, center, angle):
|
||||||
|
brush = QBrush()
|
||||||
|
brush.setColor(color)
|
||||||
|
brush.setStyle(Qt.SolidPattern)
|
||||||
|
|
||||||
size = 10.0
|
size = 10.0
|
||||||
arrow_p1 = line_center + QPointF(
|
arrow_p1 = center + QPointF(
|
||||||
math.sin(angle - math.pi / 3) * size,
|
math.sin(angle - math.pi / 3) * size,
|
||||||
math.cos(angle - math.pi / 3) * size
|
math.cos(angle - math.pi / 3) * size
|
||||||
)
|
)
|
||||||
arrow_p2 = line_center + QPointF(
|
arrow_p2 = center + QPointF(
|
||||||
math.sin(angle - math.pi + math.pi / 3) * size,
|
math.sin(angle - math.pi + math.pi / 3) * size,
|
||||||
math.cos(angle - math.pi + math.pi / 3) * size
|
math.cos(angle - math.pi + math.pi / 3) * size
|
||||||
)
|
)
|
||||||
poly = QPolygonF([line_center, arrow_p1, arrow_p2])
|
poly = QPolygonF([center, arrow_p1, arrow_p2])
|
||||||
path = QPainterPath()
|
path = QPainterPath()
|
||||||
path.addPolygon(poly)
|
path.addPolygon(poly)
|
||||||
|
|
||||||
painter.drawPolygon(poly)
|
painter.drawPolygon(poly)
|
||||||
painter.fillPath(path, brush)
|
painter.fillPath(path, brush)
|
||||||
|
|
||||||
|
|
||||||
class NodeText(QGraphicsTextItem):
|
class NodeText(QGraphicsTextItem):
|
||||||
def __init__(self, node_item):
|
def __init__(self, node_item):
|
||||||
super(NodeText, self).__init__()
|
super(NodeText, self).__init__()
|
||||||
|
|
@ -321,9 +456,13 @@ class GraphWidget(QGraphicsView):
|
||||||
self.m_origin_y = 0.0
|
self.m_origin_y = 0.0
|
||||||
self.clicked = False
|
self.clicked = False
|
||||||
|
|
||||||
|
self.setup_scene(min_size, max_size, size)
|
||||||
|
|
||||||
|
def setup_scene(self, min_size, max_size, size):
|
||||||
scene = QGraphicsScene(self)
|
scene = QGraphicsScene(self)
|
||||||
scene.setItemIndexMethod(QGraphicsScene.NoIndex)
|
scene.setItemIndexMethod(QGraphicsScene.NoIndex)
|
||||||
scene.setSceneRect(0, 0, 2000, 2000)
|
scene.setSceneRect(0, 0, 2000, 2000)
|
||||||
|
|
||||||
self.setScene(scene)
|
self.setScene(scene)
|
||||||
self.setCacheMode(QGraphicsView.CacheBackground)
|
self.setCacheMode(QGraphicsView.CacheBackground)
|
||||||
self.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate)
|
self.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate)
|
||||||
|
|
@ -363,25 +502,40 @@ class GraphWidget(QGraphicsView):
|
||||||
|
|
||||||
curr_edge = self.graph.current_reach()
|
curr_edge = self.graph.current_reach()
|
||||||
|
|
||||||
|
iedges = []
|
||||||
|
multiple_edges = {}
|
||||||
for edge in self.graph.edges():
|
for edge in self.graph.edges():
|
||||||
n1 = list(
|
n1 = next(
|
||||||
filter(
|
filter(
|
||||||
lambda n: n.node.name == edge.node1.name,
|
lambda n: n.node.name == edge.node1.name,
|
||||||
self.node_items
|
self.node_items
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
n2 = list(
|
n2 = next(
|
||||||
filter(
|
filter(
|
||||||
lambda n: n.node.name == edge.node2.name,
|
lambda n: n.node.name == edge.node2.name,
|
||||||
self.node_items
|
self.node_items
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
iedge = EdgeItem(n1[0], n2[0], edge, self)
|
# Multiple edges counter
|
||||||
|
if (n1,n2) not in multiple_edges:
|
||||||
|
ind = 1
|
||||||
|
else:
|
||||||
|
ind = multiple_edges[(n1,n2)] + 1
|
||||||
|
|
||||||
|
multiple_edges[(n1,n2)] = ind
|
||||||
|
|
||||||
|
iedge = EdgeItem(n1, n2, edge, self)
|
||||||
|
iedge._ind = ind
|
||||||
|
|
||||||
if edge == curr_edge:
|
if edge == curr_edge:
|
||||||
self._current_edge = iedge
|
self._current_edge = iedge
|
||||||
|
|
||||||
|
iedges.append(iedge)
|
||||||
|
|
||||||
|
for iedge in reversed(iedges):
|
||||||
self.scene().addItem(iedge)
|
self.scene().addItem(iedge)
|
||||||
self.edge_items.append(iedge)
|
self.edge_items.append(iedge)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ class AddNodeCommand(QUndoCommand):
|
||||||
self._node = node
|
self._node = node
|
||||||
|
|
||||||
def undo(self):
|
def undo(self):
|
||||||
self._graph.remove_node(self._node.name)
|
self._graph.remove_node(self._node)
|
||||||
|
|
||||||
def redo(self):
|
def redo(self):
|
||||||
self._graph.insert_node(self._node)
|
self._graph.insert_node(self._node)
|
||||||
|
|
@ -85,9 +85,9 @@ class DelNodeCommand(QUndoCommand):
|
||||||
)
|
)
|
||||||
|
|
||||||
for edge in self._edges:
|
for edge in self._edges:
|
||||||
self._graph.remove_edge(edge.name)
|
self._graph.remove_edge(edge)
|
||||||
|
|
||||||
self._graph.remove_node(self._node.name)
|
self._graph.remove_node(self._node)
|
||||||
|
|
||||||
|
|
||||||
class AddEdgeCommand(QUndoCommand):
|
class AddEdgeCommand(QUndoCommand):
|
||||||
|
|
@ -98,7 +98,7 @@ class AddEdgeCommand(QUndoCommand):
|
||||||
self._edge = edge
|
self._edge = edge
|
||||||
|
|
||||||
def undo(self):
|
def undo(self):
|
||||||
self._graph.remove_edge(self._edge.name)
|
self._graph.remove_edge(self._edge)
|
||||||
|
|
||||||
def redo(self):
|
def redo(self):
|
||||||
self._graph.insert_edge(self._edge)
|
self._graph.insert_edge(self._edge)
|
||||||
|
|
@ -115,7 +115,7 @@ class DelEdgeCommand(QUndoCommand):
|
||||||
self._graph.insert_edge(self._edge)
|
self._graph.insert_edge(self._edge)
|
||||||
|
|
||||||
def redo(self):
|
def redo(self):
|
||||||
self._graph.remove_edge(self._edge.name)
|
self._graph.remove_edge(self._edge)
|
||||||
|
|
||||||
|
|
||||||
class SetCommand(QUndoCommand):
|
class SetCommand(QUndoCommand):
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue