From c644d9245e3bad68e569142dfe2ee8a183ac91a6 Mon Sep 17 00:00:00 2001 From: Bruce Allen <bdallen@nps.edu> Date: Fri, 9 Oct 2020 12:03:47 -0700 Subject: [PATCH] add bar chart --- python/graph_item.py | 2 +- python/mp_code_io_manager.py | 18 +++-- python/views/bar_chart.py | 127 +++++++++++++++++++++++++++++++++++ python/views/make_views.py | 5 +- 4 files changed, 140 insertions(+), 12 deletions(-) create mode 100644 python/views/bar_chart.py diff --git a/python/graph_item.py b/python/graph_item.py index 64babb7..7e8e092 100644 --- a/python/graph_item.py +++ b/python/graph_item.py @@ -44,7 +44,7 @@ class GraphItem(): if right_edge > x: x = right_edge for view in self.views: - right_edge = view.shape().boundingRect().right() + right_edge = view.x() + view.shape().boundingRect().right() if right_edge > x: x = right_edge return x diff --git a/python/mp_code_io_manager.py b/python/mp_code_io_manager.py index ce6759c..fa2607a 100644 --- a/python/mp_code_io_manager.py +++ b/python/mp_code_io_manager.py @@ -97,13 +97,13 @@ def read_generated_json(generated_json_text): pass # item 1 or 2: dict containing SAY key or view_object dict - for say_or_report_json in generated_json["GLOBAL"][1:]: - if "SAY" in say_or_report_json: + for say_or_generated_json_view in generated_json["GLOBAL"][1:]: + if "SAY" in say_or_generated_json_view: # SAY item - generated_global_views["SAY"] = say_or_report_json + generated_global_views["SAY"] = say_or_generated_json_view else: # view_object dict - generated_global_views.update(say_or_report_json) + generated_global_views.update(say_or_generated_json_view) # build graph item from the global view graph_index = 0 # sorts to top @@ -184,12 +184,10 @@ def read_generated_json(generated_json_text): elif isinstance(type_specific_element, dict) \ and len(type_specific_element) == 1 \ and "VIEWS" in type_specific_element: - if type_specific_element["VIEWS"]: - generated_views = type_specific_element["VIEWS"][0] - json_views = make_json_views(generated_views) - else: - # empty VIEWS dict - pass + generated_json_views = dict() + for generated_json_view in type_specific_element["VIEWS"]: + generated_json_views.update(generated_json_view) + json_views = make_json_views(generated_json_views) else: print("Unexpected input:", type_specific_element) diff --git a/python/views/bar_chart.py b/python/views/bar_chart.py new file mode 100644 index 0000000..6139512 --- /dev/null +++ b/python/views/bar_chart.py @@ -0,0 +1,127 @@ +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 + +MAX_BAR_H = 100 + +def _text_width(text): + fm = QFontMetrics(QFont()) # use default font since we do not set it + return fm.horizontalAdvance(text) + +class BarChart(QGraphicsItem): + Type = next_graphics_index() + + def __init__(self, json_data, default_x, default_y): + super(BarChart, 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, default_y)) + else: + self.setPos(QPoint(json_data["x"], json_data["y"])) + + # graph data + data = json_data["data"] + + # title, headers + self.title = data["title"] + self.headers = data["tabs"] + self.bar_header = self.headers[0] + self.value_header = self.headers[1] + + # header width + self.value_header_width = _text_width(self.value_header) + 4 + self.bar_header_width = _text_width(self.bar_header) + 4 + self.header_width = max([self.value_header_width, + self.bar_header_width]) + + # bar names, values, normalized heights, width, max height + bars = data["rows"] + self.bar_names = list() + self.bar_values = list() + self.bar_width = 0 + for bar in bars: + bar_name, bar_value = bar + self.bar_names.append(bar_name) + self.bar_values.append(bar_value) + w = _text_width(bar_name) + if w > self.bar_width: + self.bar_width = w + if self.bar_values: + self.bar_heights = list() + max_height = max(self.bar_values) + for bar_value in self.bar_values: + self.bar_heights.append(int(bar_value * MAX_BAR_H / max_height)) + + # bar chart width, height + self.bar_chart_w = self.header_width + len(bars) * self.bar_width + self.bar_chart_h = 60 + MAX_BAR_H + + # rectangle for mouse sense + self.bounding_path = QPainterPath() + self.bounding_path.addRect(QRectF(0, 0, + self.bar_chart_w, self.bar_chart_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 BarChart.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): + + # title + painter.drawText(QRectF(0, 5, self.bar_chart_w, 20), Qt.AlignCenter, + self.title) + + # headers + painter.drawText(QRectF(0, self.bar_chart_h - 20, + self.value_header_width, 20), + Qt.AlignCenter, self.bar_header) + painter.drawText(QRectF(0, self.bar_chart_h - 40, + self.value_header_width, 20), + Qt.AlignCenter, self.value_header) + + # bar names + for i in range(len(self.bar_names)): + painter.drawText(QRectF(self.header_width + self.bar_width * i, + self.bar_chart_h - 20, + self.bar_width, + 20), + Qt.AlignCenter, self.bar_names[i]) + + # bar value labels + for i in range(len(self.bar_values)): + painter.drawText(QRectF(self.header_width + self.bar_width * i, + self.bar_chart_h - 40, + self.bar_width, + 20), + Qt.AlignCenter, "%s"%self.bar_values[i]) + + # bars + for i in range(len(self.bar_heights)): + x = self.header_width + self.bar_width * i + y = MAX_BAR_H - self.bar_heights[i] + 40 + painter.drawRect(x, y, self.bar_width, self.bar_heights[i]) + diff --git a/python/views/make_views.py b/python/views/make_views.py index 6023fb1..1907dc6 100644 --- a/python/views/make_views.py +++ b/python/views/make_views.py @@ -9,8 +9,9 @@ from views.report import Report from views.table import Table from views.activity_diagram import ActivityDiagram from views.graph import Graph +from views.bar_chart import BarChart -VIEW_TYPES = {"REPORT", "TABLE", "AD", "GRAPH"} +VIEW_TYPES = {"REPORT", "TABLE", "AD", "GRAPH", "BAR_CHART"} def make_json_views(generated_views): """Convert trace-generator view data structures into structures with x,y positioning.""" @@ -42,6 +43,8 @@ def make_view(json_view, default_x, default_y): view = ActivityDiagram(json_view, default_x, default_y) elif json_view["type"] == "GRAPH": view = Graph(json_view, default_x, default_y) + elif json_view["type"] == "BAR_CHART": + view = BarChart(json_view, default_x, default_y) else: print("Unrecognized view type: %s"%view_type, json_view["type"]) -- GitLab