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()