Skip to content

Commit

Permalink
Added recursive subgraph parsing, box3d shape, graphics items now imm…
Browse files Browse the repository at this point in the history
…ediately parented (#87)

* Added recursive subgraph parsing, box3d shape, graphics items now immediately parented

* Method signature fixes and formatting changes




Fix

* Removed semicolons; replaced tabs with spaces; added newline to end.

* Latest set of minor fixes.

* fix condition
  • Loading branch information
EliteMasterEric authored and dirk-thomas committed Nov 3, 2017
1 parent ee8f046 commit 363d837
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 22 deletions.
43 changes: 43 additions & 0 deletions qt_dotgraph/src/qt_dotgraph/dot_shapes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from python_qt_binding.QtCore import QRectF
from python_qt_binding.QtWidgets import QAbstractGraphicsShapeItem

class QGraphicsBox3dItem(QAbstractGraphicsShapeItem):
def __init__(self, bounding_box):
super(QGraphicsBox3dItem, self).__init__()
self._bounding_box = bounding_box

def boundingRect(self):
return self._bounding_box

def paint(self, painter, option, widget):
# Main rectangle
rectangle = QRectF(self._bounding_box.topLeft().x(),
self._bounding_box.topLeft().y() + self._bounding_box.height() * 0.1,
self._bounding_box.width() - self._bounding_box.height() * 0.1,
self._bounding_box.height() - self._bounding_box.height() * 0.1)
painter.drawRect(rectangle)
# Top line
painter.drawLine(rectangle.topLeft().x() + self._bounding_box.height() * 0.1,
self._bounding_box.topLeft().y(),
self._bounding_box.topRight().x(),
self._bounding_box.topRight().y())
# Top left corner
painter.drawLine(rectangle.topLeft().x() + self._bounding_box.height() * 0.1,
self._bounding_box.topLeft().y(),
self._bounding_box.topLeft().x() + 1,
rectangle.topLeft().y())
# Top right corner
painter.drawLine(self._bounding_box.topRight().x(),
self._bounding_box.topRight().y(),
rectangle.topRight().x(),
rectangle.topRight().y())
# Bottom right corner
painter.drawLine(rectangle.bottomRight().x() + 1,
rectangle.bottomRight().y() - 1,
self._bounding_box.bottomRight().x(),
rectangle.bottomRight().y() - self._bounding_box.height() * 0.1)
# Right line
painter.drawLine(self._bounding_box.topRight().x(),
self._bounding_box.topRight().y(),
self._bounding_box.topRight().x(),
self._bounding_box.bottomRight().y() - self._bounding_box.height() * 0.1)
45 changes: 28 additions & 17 deletions qt_dotgraph/src/qt_dotgraph/dot_to_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class DotToQtGenerator():
def __init__(self):
pass

def getNodeItemForSubgraph(self, subgraph, highlight_level):
def getNodeItemForSubgraph(self, subgraph, highlight_level, scene=None):
# let pydot imitate pygraphviz api
attr = {}
for name in subgraph.get_attributes().keys():
Expand Down Expand Up @@ -103,6 +103,7 @@ def getNodeItemForSubgraph(self, subgraph, highlight_level):
label=name,
shape='box',
color=color,
parent=scene.activePanel() if scene is not None else None,
label_pos=QPointF(float(label_pos[0]), -float(label_pos[1])))
bounding_box = QRectF(bounding_box)
# With clusters we have the problem that mouse hovers cannot
Expand All @@ -111,9 +112,12 @@ def getNodeItemForSubgraph(self, subgraph, highlight_level):
# border region would be even better (multiple RectF)
bounding_box.setHeight(LABEL_HEIGHT)
subgraph_nodeitem.set_hovershape(bounding_box)

if scene is not None:
scene.addItem(subgraph_nodeitem)
return subgraph_nodeitem

def getNodeItemForNode(self, node, highlight_level):
def getNodeItemForNode(self, node, highlight_level, scene=None):
"""
returns a pyqt NodeItem object, or None in case of error or invisible style
"""
Expand Down Expand Up @@ -152,13 +156,15 @@ def getNodeItemForNode(self, node, highlight_level):
label=name,
shape=node.attr.get('shape', 'ellipse'),
color=color,
tooltip=node.attr.get('tooltip')
#parent=None,
tooltip=node.attr.get('tooltip'),
parent=scene.activePanel() if scene is not None else None
#label_pos=None
)
if scene is not None:
scene.addItem(node_item)
return node_item

def addEdgeItem(self, edge, nodes, edges, highlight_level, same_label_siblings=False):
def addEdgeItem(self, edge, nodes, edges, highlight_level, same_label_siblings=False, scene=None):
"""
adds EdgeItem by data in edge to edges
:param same_label_siblings: if true, edges with same label will be considered siblings (collective highlighting)
Expand Down Expand Up @@ -208,6 +214,7 @@ def addEdgeItem(self, edge, nodes, edges, highlight_level, same_label_siblings=F
from_node=nodes[source_node],
to_node=nodes[destination_node],
penwidth=penwidth,
parent=scene.activePanel() if scene is not None else None,
edge_color=color,
style=style)

Expand All @@ -228,8 +235,10 @@ def addEdgeItem(self, edge, nodes, edges, highlight_level, same_label_siblings=F
if edge_name not in edges:
edges[edge_name] = []
edges[edge_name].append(edge_item)
if scene is not None:
edge_item.add_to_scene(scene)

def dotcode_to_qt_items(self, dotcode, highlight_level, same_label_siblings=False):
def dotcode_to_qt_items(self, dotcode, highlight_level, same_label_siblings=False, scene=None):
"""
takes dotcode, runs layout, and creates qt items based on the dot layout.
returns two dicts, one mapping node names to Node_Item, one mapping edge names to lists of Edge_Item
Expand All @@ -245,20 +254,20 @@ def dotcode_to_qt_items(self, dotcode, highlight_level, same_label_siblings=Fals
#graph = pygraphviz.AGraph(string=self._current_dotcode, strict=False, directed=True)
#graph.layout(prog='dot')

nodes = self.parse_nodes(graph, highlight_level)
edges = self.parse_edges(graph, nodes, highlight_level, same_label_siblings)
nodes = self.parse_nodes(graph, highlight_level, scene=scene)
edges = self.parse_edges(graph, nodes, highlight_level, same_label_siblings, scene=scene)
return nodes, edges

def parse_nodes(self, graph, highlight_level):
def parse_nodes(self, graph, highlight_level, scene=None):
"""Recursively searches all nodes inside the graph and all subgraphs."""
# let pydot imitate pygraphviz api
graph.nodes_iter = graph.get_node_list
graph.subgraphs_iter = graph.get_subgraph_list

nodes = {}
for subgraph in graph.subgraphs_iter():
subgraph_nodeitem = self.getNodeItemForSubgraph(subgraph, highlight_level)
nodes.update(self.parse_nodes(subgraph, highlight_level))
subgraph_nodeitem = self.getNodeItemForSubgraph(subgraph, highlight_level, scene=scene)
nodes.update(self.parse_nodes(subgraph, highlight_level, scene=scene))
# skip subgraphs with empty bounding boxes
if subgraph_nodeitem is None:
continue
Expand All @@ -269,15 +278,15 @@ def parse_nodes(self, graph, highlight_level):
# hack required by pydot
if node.get_name() in ('graph', 'node', 'empty'):
continue
nodes[node.get_name()] = self.getNodeItemForNode(node, highlight_level)
nodes[node.get_name()] = self.getNodeItemForNode(node, highlight_level, scene=scene)
for node in graph.nodes_iter():
# hack required by pydot
if node.get_name() in ('graph', 'node', 'empty'):
continue
nodes[node.get_name()] = self.getNodeItemForNode(node, highlight_level)
nodes[node.get_name()] = self.getNodeItemForNode(node, highlight_level, scene=scene)
return nodes

def parse_edges(self, graph, nodes, highlight_level, same_label_siblings):
def parse_edges(self, graph, nodes, highlight_level, same_label_siblings, scene=None):
"""Recursively searches all edges inside the graph and all subgraphs."""
# let pydot imitate pygraphviz api
graph.subgraphs_iter = graph.get_subgraph_list
Expand All @@ -286,15 +295,17 @@ def parse_edges(self, graph, nodes, highlight_level, same_label_siblings):
edges = {}
for subgraph in graph.subgraphs_iter():
subgraph.edges_iter = subgraph.get_edge_list
edges.update(self.parse_edges(subgraph, nodes, highlight_level, same_label_siblings))
edges.update(self.parse_edges(subgraph, nodes, highlight_level, same_label_siblings, scene=scene))
for edge in subgraph.edges_iter():
self.addEdgeItem(edge, nodes, edges,
highlight_level=highlight_level,
same_label_siblings=same_label_siblings)
same_label_siblings=same_label_siblings,
scene=scene)

for edge in graph.edges_iter():
self.addEdgeItem(edge, nodes, edges,
highlight_level=highlight_level,
same_label_siblings=same_label_siblings)
same_label_siblings=same_label_siblings,
scene=scene)

return edges
2 changes: 1 addition & 1 deletion qt_dotgraph/src/qt_dotgraph/edge_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ def __init__(self, highlight_level, spline, label_center, label, from_node, to_n
self._arrow.hoverLeaveEvent = self._handle_hoverLeaveEvent
self._arrow.setAcceptHoverEvents(True)

self._path = QGraphicsPathItem()
self._path = QGraphicsPathItem(parent)
self._path.setPath(path)
self.addToGroup(self._path)

Expand Down
21 changes: 17 additions & 4 deletions qt_dotgraph/src/qt_dotgraph/node_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,15 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

from __future__ import print_function

import sys

from python_qt_binding.QtCore import Qt
from python_qt_binding.QtGui import QBrush, QPainterPath, QPen
from python_qt_binding.QtWidgets import QGraphicsEllipseItem, QGraphicsRectItem, QGraphicsSimpleTextItem

from .dot_shapes import QGraphicsBox3dItem
from .graph_item import GraphItem


Expand All @@ -51,10 +56,7 @@ def __init__(self, highlight_level, bounding_box, label, shape, color=None, pare
self._incoming_edges = set()
self._outgoing_edges = set()

if shape == 'box':
self._graphics_item = QGraphicsRectItem(bounding_box)
else:
self._graphics_item = QGraphicsEllipseItem(bounding_box)
self.parse_shape(shape, bounding_box)
self.addToGroup(self._graphics_item)

self._label = QGraphicsSimpleTextItem(label)
Expand All @@ -75,6 +77,17 @@ def __init__(self, highlight_level, bounding_box, label, shape, color=None, pare

self.hovershape = None

def parse_shape(self, shape, bounding_box):
if shape in ('box', 'rect', 'rectangle'):
self._graphics_item = QGraphicsRectItem(bounding_box)
elif shape in ('ellipse', 'oval', 'circle'):
self._graphics_item = QGraphicsEllipseItem(bounding_box)
elif shape in ('box3d', ):
self._graphics_item = QGraphicsBox3dItem(bounding_box)
else:
print("Invalid shape '%s', defaulting to ellipse" % shape, file=sys.stderr)
self._graphics_item = QGraphicsEllipseItem(bounding_box)

def set_hovershape(self, newhovershape):
self.hovershape = newhovershape

Expand Down

0 comments on commit 363d837

Please sign in to comment.