From c89a9325f3bf1ccdcdbcd92a480511861a79b4b8 Mon Sep 17 00:00:00 2001 From: Samuel Fadel Date: Wed, 27 Jan 2016 14:30:23 +0100 Subject: Scatterplot & BarChart brushing functional. Scatterplot brushing needs some improvement, feels clumsy. --- barchart.cpp | 2 + main.cpp | 6 +- scatterplot.cpp | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- scatterplot.h | 9 +++ 4 files changed, 188 insertions(+), 8 deletions(-) diff --git a/barchart.cpp b/barchart.cpp index 22541d0..195b706 100644 --- a/barchart.cpp +++ b/barchart.cpp @@ -108,6 +108,8 @@ void BarChart::brushItem(int item) m_brushedItem = m_currentIndices[item]; emit itemBrushed(m_originalIndices[m_brushedItem]); } + + update(); } QSGNode *BarChart::newSceneGraph() const diff --git a/main.cpp b/main.cpp index a4f2bf0..924cb81 100644 --- a/main.cpp +++ b/main.cpp @@ -42,6 +42,7 @@ int main(int argc, char **argv) app.setApplicationVersion("1.0"); // app.setAttribute(Qt::AA_ShareOpenGLContexts); + // Command line parser QCommandLineParser parser; parser.setApplicationDescription("Interactive multidimensional projections."); parser.addHelpOption(); @@ -63,6 +64,7 @@ int main(int argc, char **argv) parser.showHelp(1); } + // Load dataset Main *m = Main::instance(); m->loadDataset(args[0].toStdString()); arma::mat X = m->X(); @@ -224,6 +226,7 @@ int main(int argc, char **argv) m->rpBarChart, SLOT(setValues(const arma::vec &))); // General component set up + m->cpPlot->setAcceptHoverEvents(true); m->cpPlot->setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton | Qt::RightButton); m->cpBarChart->setAcceptedMouseButtons(Qt::LeftButton); m->rpBarChart->setAcceptedMouseButtons(Qt::LeftButton); @@ -238,8 +241,9 @@ int main(int argc, char **argv) m->cpPlot->setAutoScale(false); m->rpPlot->setAutoScale(false); m->cpPlot->setColorData(labels(cpIndices), false); - //m->cpPlot->brushItem(0); + // This sets the initial CP configuration, triggering all the necessary + // signals to set up the helper objects and visual components manipulationHandler.setCP(Ys); return app.exec(); diff --git a/scatterplot.cpp b/scatterplot.cpp index 89edb2a..4cdeedf 100644 --- a/scatterplot.cpp +++ b/scatterplot.cpp @@ -9,15 +9,133 @@ #include "continuouscolorscale.h" #include "geometry.h" +// Glyphs settings +static const float DEFAULT_GLYPH_SIZE = 8.0f; static const qreal GLYPH_OPACITY = 1.0; static const qreal GLYPH_OPACITY_SELECTED = 1.0; +static const float GLYPH_OUTLINE_WIDTH = 2.0f; static const QColor GLYPH_OUTLINE_COLOR(0, 0, 0); static const QColor GLYPH_OUTLINE_COLOR_SELECTED(20, 255, 225); + +// Cosshair settings +static const float CROSSHAIR_LENGTH = 8.0f; +static const float CROSSHAIR_THICKNESS1 = 1.0f; +static const float CROSSHAIR_THICKNESS2 = 0.5f; +static const QColor CROSSHAIR_COLOR1(255, 255, 255); +static const QColor CROSSHAIR_COLOR2(0, 0, 0); + +// Selection settings static const QColor SELECTION_COLOR(128, 128, 128, 96); -static const float DEFAULT_GLYPH_SIZE = 8.0f; -static const float GLYPH_OUTLINE_WIDTH = 2.0f; +class QuadTree +{ +public: + QuadTree(const QRectF &bounds); + ~QuadTree(); + bool insert(float x, float y, int value); + int query(float x, float y) const; + + bool subdivide(); + + QRectF m_bounds; + float m_x, m_y; + int m_value; + QuadTree *m_nw, *m_ne, *m_sw, *m_se; +}; + +QuadTree::QuadTree(const QRectF &bounds) + : m_bounds(bounds) + , m_value(-1) + , m_nw(0), m_ne(0), m_sw(0), m_se(0) +{ +} + +QuadTree::~QuadTree() +{ + if (m_nw) { + delete m_nw; + delete m_ne; + delete m_sw; + delete m_se; + } +} + +bool QuadTree::subdivide() +{ + float halfWidth = m_bounds.width() / 2; + float halfHeight = m_bounds.height() / 2; + + m_nw = new QuadTree(QRectF(m_bounds.x(), + m_bounds.y(), + halfWidth, + halfHeight)); + m_ne = new QuadTree(QRectF(m_bounds.x() + halfWidth, + m_bounds.y(), + halfWidth, + halfHeight)); + m_sw = new QuadTree(QRectF(m_bounds.x(), + m_bounds.y() + halfHeight, + halfWidth, + halfHeight)); + m_se = new QuadTree(QRectF(m_bounds.x() + halfWidth, + m_bounds.y() + halfHeight, + halfWidth, + halfHeight)); + + int value = m_value; + m_value = -1; + return m_nw->insert(m_x, m_y, value) + || m_ne->insert(m_x, m_y, value) + || m_sw->insert(m_x, m_y, value) + || m_se->insert(m_x, m_y, value); +} + +bool QuadTree::insert(float x, float y, int value) +{ + if (!m_bounds.contains(x, y)) { + return false; + } + + if (m_nw) { + return m_nw->insert(x, y, value) + || m_ne->insert(x, y, value) + || m_sw->insert(x, y, value) + || m_se->insert(x, y, value); + } + + if (m_value >= 0) { + subdivide(); + return insert(x, y, value); + } + + m_x = x; + m_y = y; + m_value = value; + return true; +} + +int QuadTree::query(float x, float y) const +{ + if (!m_bounds.contains(x, y)) { + return -1; + } + + if (m_nw) { + int q; + + q = m_nw->query(x, y); + if (q >= 0) return q; + q = m_ne->query(x, y); + if (q >= 0) return q; + q = m_sw->query(x, y); + if (q >= 0) return q; + q = m_se->query(x, y); + if (q >= 0) return q; + } + + return m_value; +} Scatterplot::Scatterplot(QQuickItem *parent) : QQuickItem(parent) @@ -26,10 +144,11 @@ Scatterplot::Scatterplot(QQuickItem *parent) , m_autoScale(true) , m_sx(0, 1, 0, 1) , m_sy(0, 1, 0, 1) - , m_currentInteractionState(INTERACTION_NONE) , m_brushedItem(-1) + , m_currentInteractionState(INTERACTION_NONE) , m_shouldUpdateGeometry(false) , m_shouldUpdateMaterials(false) + , m_quadtree(0) { setClip(true); setFlag(QQuickItem::ItemHasContents); @@ -140,6 +259,8 @@ void Scatterplot::setScale(const LinearScale &sx, const LinearScalesetDrawingMode(GL_POLYGON); whiteCrossHairGeom->setVertexDataPattern(QSGGeometry::DynamicPattern); - updateCrossHairGeometry(whiteCrossHairGeom, 0, 0, 2, 8); + updateCrossHairGeometry(whiteCrossHairGeom, 0, 0, CROSSHAIR_THICKNESS1, CROSSHAIR_LENGTH); QSGFlatColorMaterial *whiteCrossHairMaterial = new QSGFlatColorMaterial; - whiteCrossHairMaterial->setColor(QColor(255, 255, 255)); + whiteCrossHairMaterial->setColor(CROSSHAIR_COLOR1); whiteCrossHairNode->setGeometry(whiteCrossHairGeom); whiteCrossHairNode->setMaterial(whiteCrossHairMaterial); whiteCrossHairNode->setFlags(QSGNode::OwnsGeometry | QSGNode::OwnsMaterial); @@ -216,9 +337,9 @@ QSGNode *Scatterplot::newSceneGraph() QSGGeometry *blackCrossHairGeom = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 12); blackCrossHairGeom->setDrawingMode(GL_POLYGON); blackCrossHairGeom->setVertexDataPattern(QSGGeometry::DynamicPattern); - updateCrossHairGeometry(blackCrossHairGeom, 0, 0, 1, 8); + updateCrossHairGeometry(blackCrossHairGeom, 0, 0, CROSSHAIR_THICKNESS2, CROSSHAIR_LENGTH); QSGFlatColorMaterial *blackCrossHairMaterial = new QSGFlatColorMaterial; - blackCrossHairMaterial->setColor(QColor(0, 0, 0)); + blackCrossHairMaterial->setColor(CROSSHAIR_COLOR2); blackCrossHairNode->setGeometry(blackCrossHairGeom); blackCrossHairNode->setMaterial(blackCrossHairMaterial); blackCrossHairNode->setFlags(QSGNode::OwnsGeometry | QSGNode::OwnsMaterial); @@ -455,6 +576,32 @@ void Scatterplot::mouseReleaseEvent(QMouseEvent *event) } } +void Scatterplot::hoverEnterEvent(QHoverEvent *event) +{ + QPointF pos = event->posF(); + m_brushedItem = m_quadtree->query(pos.x(), pos.y()); + emit itemInteractivelyBrushed(m_brushedItem); + + update(); +} + +void Scatterplot::hoverMoveEvent(QHoverEvent *event) +{ + QPointF pos = event->posF(); + m_brushedItem = m_quadtree->query(pos.x(), pos.y()); + emit itemInteractivelyBrushed(m_brushedItem); + + update(); +} + +void Scatterplot::hoverLeaveEvent(QHoverEvent *event) +{ + m_brushedItem = -1; + emit itemInteractivelyBrushed(m_brushedItem); + + update(); +} + bool Scatterplot::interactiveSelection(bool mergeSelection) { if (!mergeSelection) { @@ -524,5 +671,23 @@ void Scatterplot::applyManipulation() } } + updateQuadTree(); + emit xyInteractivelyChanged(m_xy); } + +void Scatterplot::updateQuadTree() +{ + m_sx.setRange(PADDING, width() - PADDING); + m_sy.setRange(height() - PADDING, PADDING); + + if (m_quadtree) { + delete m_quadtree; + } + + m_quadtree = new QuadTree(QRectF(x(), y(), width(), height())); + for (arma::uword i = 0; i < m_xy.n_rows; i++) { + const arma::rowvec &row = m_xy.row(i); + m_quadtree->insert(m_sx(row[0]), m_sy(row[1]), (int) i); + } +} diff --git a/scatterplot.h b/scatterplot.h index 040f272..c928439 100644 --- a/scatterplot.h +++ b/scatterplot.h @@ -10,6 +10,8 @@ #include "colorscale.h" #include "scale.h" +class QuadTree; + class Scatterplot : public QQuickItem { @@ -58,6 +60,10 @@ protected: void mouseMoveEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event); + void hoverEnterEvent(QHoverEvent *event); + void hoverMoveEvent(QHoverEvent *event); + void hoverLeaveEvent(QHoverEvent *event); + private: QSGNode *newSceneGraph(); QSGNode *newGlyphTree(); @@ -95,6 +101,9 @@ private: QPointF m_dragOriginPos, m_dragCurrentPos; bool m_shouldUpdateGeometry, m_shouldUpdateMaterials; + + void updateQuadTree(); + QuadTree *m_quadtree; }; #endif // SCATTERPLOT_H -- cgit v1.2.3