diff --git a/python/views/activity_diagram.py b/python/views/activity_diagram.py new file mode 100644 index 0000000000000000000000000000000000000000..806cb3853a412e58c516e47f91a336b5db89cd0f --- /dev/null +++ b/python/views/activity_diagram.py @@ -0,0 +1,167 @@ +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 +ACTIVITY_W = 127 +ACTIVITY_H = 17 +DECISION_W = 14 +DECISION_H = 14 +START_D = 10 +END_D = 10 +END_D2 = 3 +H_SPACING = 165 +V_SPACING = 55 + +def _xy(json_node): + # node center point + x = json_node[2] * H_SPACING + y = json_node[3] * V_SPACING + TITLE_SPACING + return x, y + +def _xy_in(json_node): + x, y = _xy(json_node) + if json_node[0] == "a": + return x, y - ACTIVITY_H/2 + elif json_node[0] == "d": + return x, y - DECISION_H/2 + elif json_node[0] == "s": + raise RuntimeError("bad") + elif json_node[0] == "e": + return x, y - END_D/2 + else: + raise RuntimeError("bad") + +def _xy_out(json_node): + x, y = _xy(json_node) + if json_node[0] == "a": + return x, y + ACTIVITY_H/2 + elif json_node[0] == "d": + return x, y + DECISION_H/2 + elif json_node[0] == "s": + return x, y + START_D/2 + elif json_node[0] == "e": + raise RuntimeError("bad") + else: + raise RuntimeError("bad") + +def _add_node_path(json_node, path): + x, y = _xy(json_node) + if json_node[0] == "a": + w = ACTIVITY_W + h = ACTIVITY_H + path.addRect(x - w/2, y - h/2, w, h) + elif json_node[0] == "d": + w = DECISION_W + h = DECISION_H + path.moveTo(x + w/2, y) + path.lineTo(x, y + h/2) + path.lineTo(x - w/2, y) + path.lineTo(x, y - h/2) + path.closeSubpath() + elif json_node[0] == "s": + d = START_D + path.addEllipse(x - d/2, y - d/2, d, d) + elif json_node[0] == "e": + d = END_D + d2 = END_D2 + path.addEllipse(x - d/2, y - d/2, d, d) + path.addEllipse(x - d2/2, y - d2/2, d2, d2) + else: + raise RuntimeError("bad") + +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) + +class ActivityDiagram(QGraphicsItem): + Type = next_graphics_index() + + def __init__(self, json_data, default_x, default_y): + super(ActivityDiagram, self).__init__() + + # 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 + ACTIVITY_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] + + # 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[1]] = json_node + + # edges + for json_edge in edges: + _add_edge_path(indexed_nodes[json_edge[0]], + indexed_nodes[json_edge[1]], + self.painter_path) + + # rectangle for mouse sense + self.bounding_path = QPainterPath() + self.bounding_path.addRect(self.painter_path.boundingRect()) + + def json_data(self): + self._json_data["x"] = self.x() + self._json_data["y"] = self.y() + return self._json_data + + def type(self): + return ActivityDiagram.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 = ACTIVITY_W + h = ACTIVITY_H + + # title + painter.drawText(QRectF(0, TITLE_Y, w, h), Qt.AlignCenter, + self.title) + + # painter_path of nodes and edges + painter.drawPath(self.painter_path) + + # any text + for node in self.nodes: + if node[0] == "a": # activity has text + x, y = _xy(node) + painter.drawText(QRectF(x - w/2, y - h/2, w, h), + Qt.AlignCenter, node[4]) + diff --git a/python/views/make_views.py b/python/views/make_views.py index cb8d94be1ccebe4945fe63bd395e9c86b04d4792..2f0b780713f795b91ab078f6fb815187583cf651 100644 --- a/python/views/make_views.py +++ b/python/views/make_views.py @@ -1,8 +1,9 @@ #from views.pyplot_report import pyplot_report from views.report import Report from views.table import Table +from views.activity_diagram import ActivityDiagram -VIEW_TYPES = {"REPORT", "TABLE"} +VIEW_TYPES = {"REPORT", "TABLE", "AD"} def make_json_views(generated_views): """Convert trace-generator view data structures into structures with x,y positioning.""" @@ -30,6 +31,8 @@ def make_view(json_view, default_x, default_y): view = Report(json_view, default_x, default_y) elif json_view["type"] == "TABLE": view = Table(json_view, default_x, default_y) + elif json_view["type"] == "AD": + view = ActivityDiagram(json_view, default_x, default_y) else: print("Unrecognized view type: %s"%view_type, json_view["type"]) diff --git a/python/views/table.py b/python/views/table.py index cab8e105e461be1958ded3ad9fac42717590d3e0..f8de0d27c9bfc9bd85c10451b5292e34cfc9c19f 100644 --- a/python/views/table.py +++ b/python/views/table.py @@ -63,10 +63,10 @@ def _make_grid(x_points, num_rows): grid.lineTo(x, y2) # painter path for mouse - painter_path = QPainterPath() - painter_path.addRect(0, 0, x2, y2) + mouse_path = QPainterPath() + mouse_path.addRect(0, 0, x2, y2) - return grid, painter_path + return grid, mouse_path # Table class Table(QGraphicsItem): @@ -111,7 +111,7 @@ class Table(QGraphicsItem): self.x_points = _x_points(self.columns) # grid and painter path - self.grid, self.painter_path = _make_grid(self.x_points, + self.grid, self.mouse_path = _make_grid(self.x_points, len(self.columns[0])) def json_data(self): @@ -124,11 +124,11 @@ class Table(QGraphicsItem): # draw inside this rectangle def boundingRect(self): - return self.painter_path.boundingRect().adjusted(-1, -1, 1, 1) + return self.mouse_path.boundingRect().adjusted(-1, -1, 1, 1) # mouse hovers when inside this rectangle def shape(self): - return self.painter_path + return self.mouse_path def paint(self, painter, _option, _widget): h = _cell_height()