aboutsummaryrefslogtreecommitdiff
path: root/historygraph.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'historygraph.cpp')
-rw-r--r--historygraph.cpp325
1 files changed, 325 insertions, 0 deletions
diff --git a/historygraph.cpp b/historygraph.cpp
new file mode 100644
index 0000000..0aeec20
--- /dev/null
+++ b/historygraph.cpp
@@ -0,0 +1,325 @@
+#include "historygraph.h"
+
+#include <algorithm>
+#include <functional>
+#include <numeric>
+#include <utility>
+
+#include "geometry.h"
+#include "scale.h"
+
+static const float ASPECT = 4.f / 3.f;
+static const float MARGIN = 0.1f;
+static const float PADDING = 0.05f;
+
+static const float GLYPH_SIZE = 4.0f;
+static const float GLYPH_OPACITY = 0.6f;
+
+class HistoryGraph::HistoryItemNode
+{
+public:
+ HistoryItemNode(const arma::mat &item);
+ ~HistoryItemNode();
+
+ void append(HistoryItemNode *node);
+ const QList<HistoryItemNode *> &children() const { return m_next; }
+
+ const arma::mat &item() const;
+ int depth() const { return m_depth; }
+ int breadth() const { return m_breadth; }
+ const QRectF &rect() const { return m_rect; }
+
+ void setRect(const QRectF &rect) { m_rect = rect; }
+
+ void updateDepth();
+ void updateBreadth();
+
+ static int treeBreadth(HistoryItemNode *node, int level);
+
+private:
+ arma::mat m_item;
+ HistoryItemNode *m_prev;
+ QList<HistoryItemNode *> m_next;
+ int m_depth, m_breadth;
+ QRectF m_rect;
+};
+
+HistoryGraph::HistoryItemNode::HistoryItemNode(const arma::mat &item)
+ : m_item(item)
+ , m_prev(0)
+ , m_depth(1)
+ , m_breadth(1)
+{
+}
+
+HistoryGraph::HistoryItemNode::~HistoryItemNode()
+{
+ m_prev = 0;
+
+ while (!m_next.isEmpty()) {
+ delete m_next.takeLast();
+ }
+}
+
+void HistoryGraph::HistoryItemNode::updateDepth()
+{
+ HistoryGraph::HistoryItemNode *node = *std::max_element(
+ m_next.cbegin(), m_next.cend(),
+ [](const HistoryGraph::HistoryItemNode *n1, const HistoryGraph::HistoryItemNode *n2) {
+ return n1->depth() < n2->depth();
+ });
+
+ m_depth = 1 + node->depth();
+
+ if (m_prev) {
+ m_prev->updateDepth();
+ }
+}
+
+int HistoryGraph::HistoryItemNode::treeBreadth(HistoryGraph::HistoryItemNode *node, int level) {
+ if (!node || level < 1) {
+ return 0;
+ }
+
+ if (level == 1) {
+ return 1;
+ }
+
+ return std::accumulate(node->m_next.cbegin(), node->m_next.cend(), 0, [](int b, HistoryGraph::HistoryItemNode *n) {
+ n->m_breadth = treeBreadth(n, n->depth());
+ return b + n->m_breadth;
+ });
+}
+
+void HistoryGraph::HistoryItemNode::updateBreadth()
+{
+ HistoryItemNode *node = this;
+ while (node->m_prev) {
+ node = node->m_prev;
+ }
+
+ node->m_breadth = treeBreadth(node, node->m_depth);
+}
+
+void HistoryGraph::HistoryItemNode::append(HistoryItemNode *node)
+{
+ if (!node) {
+ return;
+ }
+
+ m_next.append(node);
+ if (node->depth() + 1 > m_depth) {
+ updateDepth();
+ }
+
+ updateBreadth();
+
+ node->m_prev = this;
+}
+
+const arma::mat &HistoryGraph::HistoryItemNode::item() const
+{
+ return m_item;
+}
+
+HistoryGraph::HistoryGraph(QQuickItem *parent)
+ : QQuickItem(parent)
+ , m_firstNode(0)
+ , m_currentNode(0)
+ , m_needsUpdate(false)
+{
+ setClip(true);
+ setFlag(QQuickItem::ItemHasContents);
+ setAcceptedMouseButtons(Qt::LeftButton);
+}
+
+HistoryGraph::~HistoryGraph()
+{
+ delete m_firstNode;
+ m_firstNode = 0;
+}
+
+void HistoryGraph::addHistoryItem(const arma::mat &item)
+{
+ HistoryItemNode *newNode = new HistoryItemNode(item);
+ if (m_currentNode) {
+ m_currentNode->append(newNode);
+ } else {
+ m_firstNode = newNode;
+ }
+
+ m_currentNode = newNode;
+ m_needsUpdate = true;
+ update();
+}
+
+void HistoryGraph::addScatterplot(QSGNode *node, const HistoryGraph::HistoryItemNode *historyItemNode, float x, float y, float w, float h)
+{
+ const arma::mat &xy = historyItemNode->item();
+ int vertexCount = calculateCircleVertexCount(GLYPH_SIZE / 2);
+
+ LinearScale sx(xy.col(0).min(), xy.col(0).max(), x, x + w);
+ LinearScale sy(xy.col(1).min(), xy.col(1).max(), y + h, y); // reverse on purpose
+
+ for (arma::uword i = 0; i < xy.n_rows; i++) {
+ const arma::rowvec &row = xy.row(i);
+ QSGGeometryNode *glyphNode = new QSGGeometryNode;
+
+ QSGGeometry *glyphGeometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), vertexCount);
+ glyphGeometry->setDrawingMode(GL_POLYGON);
+ updateCircleGeometry(glyphGeometry, GLYPH_SIZE / 2 - 0.5, sx(row[0]), sy(row[1]));
+ glyphNode->setGeometry(glyphGeometry);
+ glyphNode->setFlag(QSGNode::OwnsGeometry);
+
+ QSGFlatColorMaterial *material = new QSGFlatColorMaterial;
+ material->setColor(QColor());
+ glyphNode->setMaterial(material);
+ glyphNode->setFlag(QSGNode::OwnsMaterial);
+
+ // Place the glyph geometry node under an opacity node
+ QSGOpacityNode *glyphOpacityNode = new QSGOpacityNode;
+ glyphOpacityNode->setOpacity(GLYPH_OPACITY);
+ glyphOpacityNode->appendChildNode(glyphNode);
+ node->appendChildNode(glyphOpacityNode);
+ }
+}
+
+QSGNode *HistoryGraph::createNodeTree()
+{
+ if (!m_firstNode) {
+ return 0;
+ }
+
+ //int breadth = m_firstNode->breadth();
+ //int depth = m_firstNode->depth();
+
+ QSGNode *sceneGraphRoot = new QSGNode;
+ HistoryItemNode *histNode = m_firstNode;
+ float margin = height()*MARGIN;
+ float padding = height()*PADDING;
+ float h = height() - 2.f*margin;
+ float w = ASPECT * h;
+ float x = margin;
+
+ QMatrix4x4 mat;
+ do {
+ QSGOpacityNode *opacityNode = new QSGOpacityNode;
+ opacityNode->setOpacity(histNode == m_currentNode ? 1.0f : 0.4f);
+
+ histNode->setRect(QRectF(x, margin, w, h));
+ QSGGeometryNode *histItemGeomNode = new QSGGeometryNode;
+ QSGGeometry *histItemGeom = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 4);
+ updateRectGeometry(histItemGeom, x, margin, w, h);
+ histItemGeom->setDrawingMode(GL_LINE_LOOP);
+ histItemGeomNode->setGeometry(histItemGeom);
+ histItemGeomNode->setFlag(QSGNode::OwnsGeometry);
+
+ QSGFlatColorMaterial *material = new QSGFlatColorMaterial;
+ material->setColor(QColor());
+ histItemGeomNode->setMaterial(material);
+ histItemGeomNode->setFlag(QSGNode::OwnsMaterial);
+
+ addScatterplot(histItemGeomNode, histNode, x + padding, margin + padding, w - 2*padding, h - 2*padding);
+ opacityNode->appendChildNode(histItemGeomNode);
+ sceneGraphRoot->appendChildNode(opacityNode);
+
+ x += w + 2.f*margin;
+
+ QList<HistoryItemNode *> children = histNode->children();
+ if (children.isEmpty()) {
+ break;
+ }
+
+ // FIXME: add children
+ histNode = children[0];
+ } while (true);
+
+ // setWidth(xPos + radius + margin);
+
+ return sceneGraphRoot;
+}
+
+void HistoryGraph::updateNodeTree(QSGNode *root)
+{
+ if (!m_firstNode) {
+ return;
+ }
+
+ // FIXME: (really) lame update algorithm
+
+ QSGNode *child = root->firstChild();
+ if (child) {
+ root->removeChildNode(child);
+ delete child;
+ }
+
+ root->appendChildNode(createNodeTree());
+}
+
+QSGNode *HistoryGraph::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
+{
+ QSGNode *root = 0;
+ if (!oldNode) {
+ root = new QSGNode;
+ QSGNode *node = createNodeTree();
+ if (node) {
+ root->appendChildNode(node);
+ }
+ } else {
+ root = oldNode;
+ }
+
+ if (m_needsUpdate) {
+ m_needsUpdate = false;
+ updateNodeTree(root);
+ }
+
+ return root;
+}
+
+HistoryGraph::HistoryItemNode *HistoryGraph::nodeAt(const QPointF &pos) const
+{
+ return nodeAt(pos, m_firstNode);
+}
+
+HistoryGraph::HistoryItemNode *HistoryGraph::nodeAt(const QPointF &pos, HistoryGraph::HistoryItemNode *node) const
+{
+ if (!node) {
+ return 0;
+ }
+
+ const QRectF &rect = node->rect();
+
+ if (pos.x() < rect.x()) {
+ return 0;
+ }
+
+ if (rect.contains(pos)) {
+ return node;
+ }
+
+ QList<HistoryGraph::HistoryItemNode *> children = node->children();
+ for (auto it = children.begin(); it != children.end(); it++) {
+ HistoryGraph::HistoryItemNode *tmp = nodeAt(pos, *it);
+ if (tmp) {
+ return tmp;
+ }
+ }
+
+ return 0;
+}
+
+void HistoryGraph::mousePressEvent(QMouseEvent *event)
+{
+ HistoryGraph::HistoryItemNode *node = nodeAt(event->localPos());
+ if (!node || node == m_currentNode) {
+ event->ignore();
+ return;
+ }
+
+ m_currentNode = node;
+ m_needsUpdate = true;
+ update();
+
+ emit currentItemChanged(m_currentNode->item());
+}