diff --git a/python/views/activity_diagram.py b/python/views/activity_diagram.py index 806cb3853a412e58c516e47f91a336b5db89cd0f..7bded9996d36e919368e9bd832b88d85ac158153 100644 --- a/python/views/activity_diagram.py +++ b/python/views/activity_diagram.py @@ -128,7 +128,9 @@ class ActivityDiagram(QGraphicsItem): # rectangle for mouse sense self.bounding_path = QPainterPath() - self.bounding_path.addRect(self.painter_path.boundingRect()) + bounding_rect = self.painter_path.boundingRect() + bounding_rect.setTop(0) + self.bounding_path.addRect(bounding_rect) def json_data(self): self._json_data["x"] = self.x() diff --git a/python/views/graph.py b/python/views/graph.py new file mode 100644 index 0000000000000000000000000000000000000000..8db3c7ea7adca2a31f4b849f91e6084f35363203 --- /dev/null +++ b/python/views/graph.py @@ -0,0 +1,159 @@ +import random +from PyQt5.QtCore import QPoint, QRectF, Qt +from PyQt5.QtGui import QPainterPath +from PyQt5.QtGui import QFontMetrics +from PyQt5.QtGui import QFont +from PyQt5.QtWidgets import QGraphicsItem +from next_graphics_index import next_graphics_index + +# see also typical settings_manager values +TITLE_Y = 15 +TITLE_SPACING = 25 +GRAPH_W = 400 +GRAPH_H = 400 +NODE_W = 127 +NODE_H = 17 +H_SPACING = 165 +V_SPACING = 55 + +def _xy(json_node): + # node center point + x = json_node[2] + y = json_node[3] + TITLE_SPACING + return x, y + +def _xy_in(json_node): + x, y = _xy(json_node) + return x, y - NODE_H/2 + +def _xy_out(json_node): + x, y = _xy(json_node) + return x, y + NODE_H/2 + +def _add_node_path(json_node, path): + x, y = _xy(json_node) + w = NODE_W + h = NODE_H + path.addRect(x - w/2, y - h/2, w, h) + +def _add_edge_path(from_json_node, to_json_node, path): + from_x, from_y = _xy_out(from_json_node) + to_x, to_y = _xy_in(to_json_node) + path.moveTo(from_x, from_y) + path.lineTo(to_x, to_y) + center_x = (from_x + to_x)/2 + center_y = (from_y + to_y)/2 + return center_x, center_y + + +def _random_xy(): + x = random.randrange(int(NODE_W/2), int(GRAPH_W - NODE_W / 2)) + y = random.randrange(int(NODE_H/2) + TITLE_SPACING, + int(GRAPH_H - NODE_H / 2)) + return x, y + +def _maybe_assign_xy(nodes): + for node in nodes: + if len(node) == 2: + x, y = _random_xy() + node.append(x) + node.append(y) + +class Graph(QGraphicsItem): + Type = next_graphics_index() + + def __init__(self, json_data, default_x, default_y): + super(Graph, self).__init__() + + # make graph deterministic + random.seed(a=1) + + # graphicsItem mode + self.setFlag(QGraphicsItem.ItemIsMovable) + self.setFlag(QGraphicsItem.ItemIsSelectable) + self.setZValue(2) + + # json_data + self._json_data = json_data # keep for export + + # x,y + if json_data["x"] == 0 and json_data["y"] == 0: + self.setPos(QPoint(default_x + NODE_W / 2, default_y)) + else: + self.setPos(QPoint(json_data["x"], json_data["y"])) + + # graph data + data = json_data["data"] + + # title, nodes, edges + self.title = data[0] + self.nodes = data[1] + edges = data[2] + _data3 = data[3] # ? + + # maybe assign coordinates to nodes + _maybe_assign_xy(self.nodes) + + # indexed nodes for edges + indexed_nodes = dict() + + # painter path + self.painter_path = QPainterPath() + + # nodes + for json_node in self.nodes: + + _add_node_path(json_node, self.painter_path) + indexed_nodes[json_node[0]] = json_node + + # edges and edge labels + self.edge_labels = list() + for json_edge in edges: + center_x, center_y = _add_edge_path(indexed_nodes[json_edge[0]], + indexed_nodes[json_edge[1]], + self.painter_path) + self.edge_labels.append((center_x, center_y, json_edge[2])) + + # rectangle for mouse sense + self.bounding_path = QPainterPath() + self.bounding_path.addRect(QRectF(0,0,GRAPH_W, GRAPH_H)) + + def json_data(self): + self._json_data["x"] = self.x() + self._json_data["y"] = self.y() + return self._json_data + + def type(self): + return Graph.Type + + # draw inside this rectangle + def boundingRect(self): + return self.bounding_path.boundingRect().adjusted(-1, -1, 1, 1) + + # mouse hovers when inside this rectangle + def shape(self): + return self.bounding_path + + def paint(self, painter, _option, _widget): + + w = NODE_W + h = NODE_H + + # title + painter.drawText(QRectF(0, TITLE_Y, GRAPH_W, h), Qt.AlignCenter, + self.title) + + # painter_path of nodes and edges + painter.drawPath(self.painter_path) + + # node text + for node in self.nodes: + x, y = _xy(node) + painter.drawText(QRectF(x - w/2, y - h/2, w, h), + Qt.AlignCenter, node[1]) + + # edge text + for x, y, label in self.edge_labels: + painter.drawText(QRectF(x-w/2, y-w/2, w, h), + Qt.AlignCenter, node[1]) + diff --git a/python/views/make_views.py b/python/views/make_views.py index 2f0b780713f795b91ab078f6fb815187583cf651..6023fb14426f54283c27cd8d0ce2c6e0142d4c6f 100644 --- a/python/views/make_views.py +++ b/python/views/make_views.py @@ -1,9 +1,16 @@ +"""To add a new view: + * Write the view in its own file, see other views for examples. + * Import the view in this file. + * Add the view name to VIEW_TYPES. + * Instantiate the view in function make_view. +""" #from views.pyplot_report import pyplot_report from views.report import Report from views.table import Table from views.activity_diagram import ActivityDiagram +from views.graph import Graph -VIEW_TYPES = {"REPORT", "TABLE", "AD"} +VIEW_TYPES = {"REPORT", "TABLE", "AD", "GRAPH"} def make_json_views(generated_views): """Convert trace-generator view data structures into structures with x,y positioning.""" @@ -33,6 +40,8 @@ def make_view(json_view, default_x, default_y): view = Table(json_view, default_x, default_y) elif json_view["type"] == "AD": view = ActivityDiagram(json_view, default_x, default_y) + elif json_view["type"] == "GRAPH": + view = Graph(json_view, default_x, default_y) else: print("Unrecognized view type: %s"%view_type, json_view["type"])