Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 7 commits into from
Nov 3, 2017
44 changes: 44 additions & 0 deletions qt_dotgraph/src/qt_dotgraph/dot_shapes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from python_qt_binding.QtCore import QRectF
from python_qt_binding.QtGui import QColor
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import not used.

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use four space indentation and a newline at the end of the file to match the code style of the existing code.

46 changes: 29 additions & 17 deletions qt_dotgraph/src/qt_dotgraph/dot_to_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,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 @@ -101,6 +101,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 @@ -109,9 +110,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 @@ -149,13 +153,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, scene=None, same_label_siblings=False):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add the new keyword argument scene at the end of the signature. That will avoid breaking any code using the current signature without specifying the keyword explicitly.

Same below for dotcode_to_qt_items.

"""
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 @@ -205,6 +211,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 @@ -225,8 +232,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, scene=None, same_label_siblings=False):
"""
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 @@ -242,20 +251,21 @@ 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)
edges = self.parse_edges(graph, nodes, highlight_level, scene, same_label_siblings)
return nodes, edges

def parse_nodes(self, graph, highlight_level):
def parse_nodes(self, graph, highlight_level, scene):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the new argument be an optional keyword argument (for the same compatibility reason mentioned above).

All callers should pass the keyword argument by explicitly passing the keyword.

Same for parse_edges below.

"""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 = {}
edges = {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assigned but never used?

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))
# skip subgraphs with empty bounding boxes
if subgraph_nodeitem is None:
continue
Expand All @@ -266,15 +276,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, scene, same_label_siblings):
"""Recursively searches all edges inside the graph and all subgraphs."""
# let pydot imitate pygraphviz api
graph.subgraphs_iter = graph.get_subgraph_list
Expand All @@ -283,15 +293,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, scene, same_label_siblings))
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
1 change: 1 addition & 0 deletions qt_dotgraph/src/qt_dotgraph/edge_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ def __init__(self, highlight_level, spline, label_center, label, from_node, to_n
self._arrow.setAcceptHoverEvents(True)

self._path = QGraphicsPathItem()
self._path = QGraphicsPathItem(parent)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the line before be removed?

self._path.setPath(path)
self.addToGroup(self._path)

Expand Down
18 changes: 14 additions & 4 deletions qt_dotgraph/src/qt_dotgraph/node_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
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 +52,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 +73,18 @@ def __init__(self, highlight_level, bounding_box, label, shape, color=None, pare

self.hovershape = None

def parse_shape(self, shape, bounding_box):
if shape == 'box' or shape == 'rect' or shape == 'rectangle':
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead: if shape in ('box', 'rect', 'rectangle'):

Similar below.

self._graphics_item = QGraphicsRectItem(bounding_box)
elif shape == 'ellipse' or shape == 'oval' or shape == 'circle':
self._graphics_item = QGraphicsEllipseItem(bounding_box)
elif shape == 'box3d':
self._graphics_item = QGraphicsBox3dItem(bounding_box)
else:
#raise ValueError("Invalid shape '"+shape+"'")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the commented code line.

print("Invalid shape '"+shape+"', defaulting to ellipse")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use print("Invalid shape '%s', defaulting to ellipse" % shape).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also print the error message to stderr:

print('...', file=sys.stderr)

This will require the following at the top of the file:

from __future__ import print_function

import sys

self._graphics_item = QGraphicsEllipseItem(bounding_box)

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

Expand Down