diff --git a/src/Model/Network/Graph.py b/src/Model/Network/Graph.py index df48f03da6932229d9365347c7f4abebe09903a1..9fb48fa0b647c67e389e8d57342ffcd52eeb6ca1 100644 --- a/src/Model/Network/Graph.py +++ b/src/Model/Network/Graph.py @@ -176,11 +176,11 @@ class Graph(object): return edge def _add_edge(self, edge): - # This edge already exists ? - if any(filter(lambda e: (e.node1 == edge.node1 and - e.node2 == edge.node2), - self._edges)): - return None + # # This edge already exists ? + # if any(filter(lambda e: (e.node1 == edge.node1 and + # e.node2 == edge.node2), + # self._edges)): + # return None self._edges.append(edge) diff --git a/src/View/Network/GraphWidget.py b/src/View/Network/GraphWidget.py index 2d96617be477a198cdaa3c7f9f7ff0063fcf89ee..41cee0215be6ca23929ca29ceddc2f298447c208 100644 --- a/src/View/Network/GraphWidget.py +++ b/src/View/Network/GraphWidget.py @@ -120,6 +120,8 @@ class NodeItem(QGraphicsItem): class EdgeItem(QGraphicsItem): + _arc_dist = 0.1 + Type = QGraphicsItem.UserType + 2 def __init__(self, src_node_item, dest_node_item, @@ -131,6 +133,21 @@ class EdgeItem(QGraphicsItem): self.edge = edge 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) @@ -142,30 +159,19 @@ class EdgeItem(QGraphicsItem): pen_width = 2.0 extra = (pen_width + 5) / 2.0 - return QRectF( - self.src_node.pos(), - 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) + return self._bound_rect.normalized().adjusted( + -extra, -extra, extra, extra + ) def shape(self): - # Shape around edge for mouse selection - vec = self.dest_node.pos() - self.src_node.pos() - vec = vec * (5 / math.sqrt(QPointF.dotProduct(vec, vec))) - extra = QPointF(vec.y(), -vec.x()) + return self._arc - result = QPainterPath(self.src_node.pos() - vec + extra) - result.lineTo(self.src_node.pos() - vec - extra) - result.lineTo(self.dest_node.pos() + vec - extra) - result.lineTo(self.dest_node.pos() + vec + extra) - result.closeSubpath() - - return result + def paint(self, painter, option, widget): + #self.paint_line(painter, option, widget) + self.paint_arc(painter, option, widget) @timer - def paint(self, painter, option, widget): + def paint_line(self, painter, option, widget): # Draw shape of the edge # color = QColor(Qt.white) # color.setAlpha(128) @@ -191,40 +197,169 @@ class EdgeItem(QGraphicsItem): color, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin ) ) - # Draw the 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().y() + self.dest_node.pos().y()) / 2, ) + angle = math.acos(line.dx() / line.length()) + if line.dy() >= 0: + angle = (math.pi * 2.0) - angle - # Draw the arrows - brush = QBrush() - brush.setColor(color) - brush.setStyle(Qt.SolidPattern) + 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 - arrow_p1 = line_center + QPointF( + arrow_p1 = center + QPointF( math.sin(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.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.addPolygon(poly) painter.drawPolygon(poly) painter.fillPath(path, brush) - class NodeText(QGraphicsTextItem): def __init__(self, node_item): super(NodeText, self).__init__() @@ -364,22 +499,33 @@ class GraphWidget(QGraphicsView): curr_edge = self.graph.current_reach() + multiple_edges = {} for edge in self.graph.edges(): - n1 = list( + n1 = next( filter( lambda n: n.node.name == edge.node1.name, self.node_items ) ) - n2 = list( + n2 = next( filter( lambda n: n.node.name == edge.node2.name, 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: self._current_edge = iedge