#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 setNext(HistoryItemNode *node); HistoryItemNode *next() const { return m_next; } const arma::mat &item() const; const QRectF &rect() const { return m_rect; } void setRect(const QRectF &rect) { m_rect = rect; } int length() const { return m_length; } private: void updateLength(); arma::mat m_item; HistoryItemNode *m_prev, *m_next; int m_length; QRectF m_rect; }; HistoryGraph::HistoryItemNode::HistoryItemNode(const arma::mat &item) : m_item(item) , m_prev(0) , m_next(0) , m_length(1) { } HistoryGraph::HistoryItemNode::~HistoryItemNode() { m_prev = 0; if (m_next) { delete m_next; } } void HistoryGraph::HistoryItemNode::updateLength() { m_length = 1 + (m_next ? m_next->length() : 0); if (m_prev) { m_prev->updateLength(); } } void HistoryGraph::HistoryItemNode::setNext(HistoryItemNode *node) { if (!node) { return; } if (m_next) { delete m_next; } m_next = node; 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_currentWidth(0.0f) , m_needsUpdate(false) { setClip(true); setFlag(QQuickItem::ItemHasContents); setAcceptedMouseButtons(Qt::LeftButton); setAcceptHoverEvents(true); } HistoryGraph::~HistoryGraph() { delete m_firstNode; } void HistoryGraph::addHistoryItem(const arma::mat &item) { HistoryItemNode *newNode = new HistoryItemNode(item); if (m_currentNode) { m_currentNode->setNext(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<float> sx(xy.col(0).min(), xy.col(0).max(), x, x + w); LinearScale<float> 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(); QSGTransformNode *sceneGraphRoot = new QSGTransformNode; float margin = height()*MARGIN; float padding = height()*PADDING; float h = height() - 2.f*margin; float w = ASPECT * h; float x = margin; QMatrix4x4 mat; for (HistoryItemNode *histNode = m_firstNode; histNode; histNode = histNode->next()) { 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; } m_currentWidth = x - 2.0f*margin; if (m_currentWidth > width()) { const QRectF &rect = m_viewportTransform.mapRect(m_currentNode->rect()); if (rect.x() < 0) { m_viewportTransform.translate(rect.x(), 0); } else if (rect.x() + rect.width() > width()) { m_viewportTransform.translate(width() - (rect.x() + rect.width()) - margin, 0); } } else { m_viewportTransform.setToIdentity(); } 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); } static_cast<QSGTransformNode *>(root->firstChild())->setMatrix(m_viewportTransform); 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 = m_viewportTransform.mapRect(node->rect()); if (pos.x() < rect.x()) { return 0; } if (rect.contains(pos)) { return node; } HistoryGraph::HistoryItemNode *tmp = nodeAt(pos, node->next()); if (tmp) { return tmp; } return 0; } static inline float clamp(float x, float min, float max) { if (x < min) { return min; } else if (x > max) { return max; } return x; } void HistoryGraph::hoverMoveEvent(QHoverEvent *event) { if (m_currentWidth < width()) { return; } const QPointF &pos = event->posF(); float margin = MARGIN * height(); float prop = (pos.x() - 2*margin) / (width() - 4*margin); prop = clamp(prop, 0.0f, 1.0f); float displ = m_currentWidth - width() + margin; m_viewportTransform.setToIdentity(); m_viewportTransform.translate(-displ * prop, 0); update(); } 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()); }