aboutsummaryrefslogtreecommitdiff
path: root/historygraph.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'historygraph.cpp')
-rw-r--r--historygraph.cpp260
1 files changed, 257 insertions, 3 deletions
diff --git a/historygraph.cpp b/historygraph.cpp
index 1a4532a..997d079 100644
--- a/historygraph.cpp
+++ b/historygraph.cpp
@@ -1,14 +1,268 @@
#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 GLYPH_SIZE = 4.0f;
+static const float PADDING = 0.05f;
+
+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; }
+ 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;
+};
+
+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);
}
-void HistoryGraph::addHistoryItem(const int &item)
-{}
+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->appendChildNode(glyphOutlineNode);
+ 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);
+
+ 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 *)
{
- return oldNode;
+ 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;
}