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