aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSamuel Fadel <samuelfadel@gmail.com>2015-05-20 18:53:07 -0300
committerSamuel Fadel <samuelfadel@gmail.com>2015-05-21 18:09:16 -0300
commitb3c7ac156e1c4ac5d7455ce61e89549291ac85b1 (patch)
tree024ee0050cf68534b2e977d48c266303a2c9bdc8
parent02e2ebf10c30ca278dc8a85649c6a7db87858cde (diff)
Nearly complete implementation of interaction.
Interaction still does not work due to signals not being correctly emitted.
-rw-r--r--interactionhandler.cpp31
-rw-r--r--interactionhandler.h33
-rw-r--r--main.cpp33
-rw-r--r--main_view.qml4
-rw-r--r--pm.pro2
-rw-r--r--scatterplot.cpp111
-rw-r--r--scatterplot.h9
7 files changed, 180 insertions, 43 deletions
diff --git a/interactionhandler.cpp b/interactionhandler.cpp
new file mode 100644
index 0000000..fab3c75
--- /dev/null
+++ b/interactionhandler.cpp
@@ -0,0 +1,31 @@
+#include "interactionhandler.h"
+
+#include "mp.h"
+
+InteractionHandler::InteractionHandler(const arma::mat &X,
+ const arma::vec &labels,
+ const arma::uvec &sampleIndices)
+ : m_X(X)
+ , m_labels(labels)
+ , m_sampleIndices(sampleIndices)
+ , m_technique(TECHNIQUE_LAMP)
+{
+}
+
+void InteractionHandler::setSubsample(const arma::mat &Ys)
+{
+ arma::mat embedding(m_X.n_rows, Ys.n_cols);
+ switch (m_technique) {
+ case TECHNIQUE_PLMP:
+ case TECHNIQUE_LSP:
+ case TECHNIQUE_LAMP:
+ embedding = mp::lamp(m_X, m_sampleIndices, Ys);
+ break;
+ }
+
+ arma::mat Y(embedding.n_rows, embedding.n_cols);
+ Y.cols(0, embedding.n_cols - 1) = embedding;
+ Y.col(Y.n_cols - 1) = m_labels;
+
+ emit subsampleChanged(Y);
+}
diff --git a/interactionhandler.h b/interactionhandler.h
new file mode 100644
index 0000000..3dbeb8a
--- /dev/null
+++ b/interactionhandler.h
@@ -0,0 +1,33 @@
+#ifndef INTERACTIONHANDLER_H
+#define INTERACTIONHANDLER_H
+
+#include <armadillo>
+
+#include "scatterplot.h"
+
+class InteractionHandler : public QObject
+{
+ Q_OBJECT
+public:
+ enum InteractiveTechnique {
+ TECHNIQUE_PLMP,
+ TECHNIQUE_LAMP,
+ TECHNIQUE_LSP
+ };
+
+ InteractionHandler(const arma::mat &X, const arma::vec &labels, const arma::uvec &sampleIndices);
+
+signals:
+ void subsampleChanged(const arma::mat &Y);
+
+public slots:
+ void setSubsample(const arma::mat &Ys);
+
+private:
+ arma::mat m_X;
+ arma::vec m_labels;
+ arma::uvec m_sampleIndices;
+ InteractiveTechnique m_technique;
+};
+
+#endif // INTERACTIONHANDLER_H
diff --git a/main.cpp b/main.cpp
index 4fcff65..3f2a85e 100644
--- a/main.cpp
+++ b/main.cpp
@@ -1,10 +1,12 @@
#include <cmath>
+#include <memory>
#include <QSurfaceFormat>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "mp.h"
#include "scatterplot.h"
+#include "interactionhandler.h"
int main(int argc, char **argv)
{
@@ -12,17 +14,12 @@ int main(int argc, char **argv)
qmlRegisterType<Scatterplot>("PM", 1, 0, "Scatterplot");
- /*QQuickView view;
- QSurfaceFormat format = view.format();
- format.setSamples(16);
- view.setFormat(format);
- view.setResizeMode(QQuickView::SizeRootObjectToView);
- view.setSource(QUrl("qrc:///main_view.qml"));*/
QSurfaceFormat fmt;
fmt.setSamples(16);
QSurfaceFormat::setDefaultFormat(fmt);
QQmlApplicationEngine engine(QUrl("qrc:///main_view.qml"));
+ std::unique_ptr<InteractionHandler> interactionHandler((InteractionHandler *) 0);
if (argc > 1) {
arma::mat dataset;
dataset.load(argv[1], arma::raw_ascii);
@@ -34,20 +31,22 @@ int main(int argc, char **argv)
arma::uvec sampleIndices = arma::randi<arma::uvec>(subsampleSize, arma::distr_param(0, n-1));
arma::mat Ys = arma::randn(subsampleSize, 2);
Ys = mp::forceScheme(mp::dist(X.rows(sampleIndices)), Ys);
-
- // Plot the subsample
- Scatterplot *plot = engine.rootObjects()[0]->findChild<Scatterplot *>("subsamplePlot");
arma::mat subsampleData(subsampleSize, 3);
subsampleData.cols(0, 1) = Ys;
subsampleData.col(2) = labels(sampleIndices);
- plot->setData(subsampleData);
-
- // Plot entire dataset
- plot = engine.rootObjects()[0]->findChild<Scatterplot *>("plot");
- arma::mat reducedData(n, 3);
- reducedData.cols(0, 1) = mp::lamp(X, sampleIndices, Ys);
- reducedData.col(2) = labels;
- plot->setData(reducedData);
+
+ Scatterplot *subsamplePlot = engine.rootObjects()[0]->findChild<Scatterplot *>("subsamplePlot");
+ subsamplePlot->setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton | Qt::RightButton);
+ subsamplePlot->setData(subsampleData);
+ Scatterplot *plot = engine.rootObjects()[0]->findChild<Scatterplot *>("plot");
+
+ // connect both plots through interaction handler
+ interactionHandler = std::unique_ptr<InteractionHandler>(new InteractionHandler(X, labels, sampleIndices));
+ QObject::connect(subsamplePlot, SIGNAL(dataChanged(const arma::mat &)),
+ interactionHandler.get(), SLOT(setSubsample(const arma::mat &)));
+ QObject::connect(interactionHandler.get(), SIGNAL(subsampleChanged(const arma::mat &)),
+ plot, SLOT(setData(const arma::mat &)));
+
}
return app.exec();
diff --git a/main_view.qml b/main_view.qml
index 8389461..4a093f2 100644
--- a/main_view.qml
+++ b/main_view.qml
@@ -4,8 +4,8 @@ import PM 1.0
ApplicationWindow {
visible: true
- width: 960
- height: 480
+ width: 1200
+ height: 600
menuBar: MenuBar {
Menu {
diff --git a/pm.pro b/pm.pro
index 1c6e26a..0d0f153 100644
--- a/pm.pro
+++ b/pm.pro
@@ -4,10 +4,12 @@ QMAKE_CXXFLAGS += -std=c++11
QMAKE_LIBS += -larmadillo
HEADERS += colorscale.h \
scatterplot.h \
+ interactionhandler.h \
mp.h
SOURCES += main.cpp \
colorscale.cpp \
scatterplot.cpp \
+ interactionhandler.cpp \
lamp.cpp \
forceScheme.cpp \
dist.cpp
diff --git a/scatterplot.cpp b/scatterplot.cpp
index 414f46a..dd8d378 100644
--- a/scatterplot.cpp
+++ b/scatterplot.cpp
@@ -1,16 +1,13 @@
#include "scatterplot.h"
-#include <iostream>
-
#include <cmath>
-#include <QSGNode>
#include <QSGGeometry>
#include <QSGGeometryNode>
#include <QSGMaterial>
#include <QSGFlatColorMaterial>
#include <QSGSimpleRectNode>
-const int GLYPH_SIZE = 5;
+const int GLYPH_SIZE = 8;
const float PADDING = 10;
const float PI = 3.1415f;
@@ -31,7 +28,6 @@ Scatterplot::Scatterplot(QQuickItem *parent)
{
setClip(true);
setFlag(QQuickItem::ItemHasContents);
- setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
}
Scatterplot::~Scatterplot()
@@ -44,7 +40,12 @@ void Scatterplot::setData(const arma::mat &data)
return;
m_data = data;
+
m_colorScale.setExtents(m_data.col(2).min(), m_data.col(2).max());
+ m_selectedGlyphs.clear();
+ for (arma::uword i = 0; i < m_data.n_rows; i++)
+ m_selectedGlyphs.append(false);
+
update();
}
@@ -101,7 +102,7 @@ QSGNode *Scatterplot::newGlyphNodeTree() {
QSGGeometryNode *glyphNode = new QSGGeometryNode;
QSGGeometry *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), vertexCount);
- geometry->setDrawingMode(GL_POLYGON);
+ geometry->setDrawingMode(GL_LINE_LOOP);
glyphNode->setGeometry(geometry);
glyphNode->setFlag(QSGNode::OwnsGeometry);
@@ -125,7 +126,7 @@ QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
xmax = m_data.col(0).max(),
ymin = m_data.col(1).min(),
ymax = m_data.col(1).max(),
- x, y;
+ x, y, xt, yt, selected;
QSGNode *root = 0;
if (!oldNode) {
@@ -136,12 +137,20 @@ QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
}
QSGNode *glyphNode = root->firstChild()->firstChild();
+ if (m_currentState != INTERACTION_MOVING)
+ xt = yt = 0;
+ else {
+ xt = m_dragCurrentPos.x() - m_dragOriginPos.x();
+ yt = m_dragCurrentPos.y() - m_dragOriginPos.y();
+ }
for (arma::uword i = 0; i < m_data.n_rows; i++) {
arma::rowvec row = m_data.row(i);
- x = PADDING + (row[0] - xmin) / (xmax - xmin) * (width() - 2*PADDING);
- y = PADDING + (row[1] - ymin) / (ymax - ymin) * (height() - 2*PADDING);
+ selected = m_selectedGlyphs[i] ? 1.0 : 0.0;
+ x = PADDING + (row[0] - xmin) / (xmax - xmin) * (width() - 2*PADDING) + xt * selected;
+ y = PADDING + (row[1] - ymin) / (ymax - ymin) * (height() - 2*PADDING) + yt * selected;
QSGGeometry *geometry = static_cast<QSGGeometryNode *>(glyphNode)->geometry();
+ geometry->setDrawingMode(m_selectedGlyphs[i] ? GL_POLYGON : GL_LINE_LOOP);
updateCircleGeometry(geometry, GLYPH_SIZE, x, y);
glyphNode->markDirty(QSGNode::DirtyGeometry);
glyphNode = glyphNode->nextSibling();
@@ -153,12 +162,12 @@ QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
if (!root->firstChild()->nextSibling()) {
selectionNode = new QSGGeometryNode;
QSGGeometry *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 4);
- geometry->setDrawingMode(GL_POLYGON);
+ geometry->setDrawingMode(GL_LINE_LOOP);
selectionNode->setGeometry(geometry);
selectionNode->setFlag(QSGNode::OwnsGeometry);
QSGFlatColorMaterial *material = new QSGFlatColorMaterial;
- material->setColor(QColor(128, 128, 128, 128));
+ material->setColor(QColor(0, 0, 0, 128));
selectionNode->setMaterial(material);
selectionNode->setFlag(QSGNode::OwnsMaterial);
@@ -169,7 +178,7 @@ QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
updateSelectionGeometry(selectionNode->geometry(), m_dragOriginPos, m_dragCurrentPos);
selectionNode->markDirty(QSGNode::DirtyGeometry);
- } else if (m_currentState == INTERACTION_NONE) {
+ } else {
if (root->firstChild()->nextSibling()) {
root->firstChild()->nextSibling()->markDirty(QSGNode::DirtyGeometry);
root->removeChildNode(root->firstChild()->nextSibling());
@@ -183,12 +192,12 @@ void Scatterplot::mousePressEvent(QMouseEvent *event)
{
switch (m_currentState) {
case INTERACTION_NONE:
- m_currentState = INTERACTION_SELECTING;
+ case INTERACTION_SELECTED:
+ m_currentState = (event->button() == Qt::MiddleButton) ? INTERACTION_MOVING
+ : INTERACTION_SELECTING;
m_dragOriginPos = event->localPos();
+ m_dragCurrentPos = m_dragOriginPos;
break;
- case INTERACTION_SELECTED:
- m_currentState = INTERACTION_MOVING;
- break; // TODO
case INTERACTION_SELECTING:
case INTERACTION_MOVING:
return; // should not be reached
@@ -197,25 +206,85 @@ void Scatterplot::mousePressEvent(QMouseEvent *event)
void Scatterplot::mouseMoveEvent(QMouseEvent *event)
{
- if (m_currentState != INTERACTION_SELECTING)
+ switch (m_currentState) {
+ case INTERACTION_NONE:
+ case INTERACTION_SELECTED:
return;
-
- m_dragCurrentPos = event->localPos();
- update();
+ case INTERACTION_SELECTING:
+ case INTERACTION_MOVING:
+ m_dragCurrentPos = event->localPos();
+ update();
+ }
}
void Scatterplot::mouseReleaseEvent(QMouseEvent *event)
{
+ bool mergeSelection;
+
switch (m_currentState) {
case INTERACTION_SELECTING:
- m_currentState = INTERACTION_NONE;
+ mergeSelection = (event->button() == Qt::RightButton);
+ m_currentState = selectGlyphs(mergeSelection) ? INTERACTION_SELECTED
+ : INTERACTION_NONE;
update();
break;
case INTERACTION_MOVING:
+ m_currentState = INTERACTION_SELECTED;
+ updateData();
+ update();
break;
case INTERACTION_NONE:
case INTERACTION_SELECTED:
return; // should not be reached
}
}
+
+bool Scatterplot::selectGlyphs(bool mergeSelection)
+{
+ qreal xmin = m_data.col(0).min(),
+ xmax = m_data.col(0).max(),
+ ymin = m_data.col(1).min(),
+ ymax = m_data.col(1).max(),
+ x, y;
+
+ QRectF selectionRect(m_dragOriginPos, m_dragCurrentPos);
+ bool anySelected = false;
+ for (arma::uword i = 0; i < m_data.n_rows; i++) {
+ arma::rowvec row = m_data.row(i);
+ x = PADDING + (row[0] - xmin) / (xmax - xmin) * (width() - 2*PADDING);
+ y = PADDING + (row[1] - ymin) / (ymax - ymin) * (height() - 2*PADDING);
+
+ bool contains = selectionRect.contains(x, y);
+ anySelected = anySelected || contains;
+ m_selectedGlyphs[i] = (mergeSelection && m_selectedGlyphs[i]) || contains;
+ }
+
+ return anySelected;
+}
+
+void Scatterplot::updateData()
+{
+ qreal xmin = m_data.col(0).min(),
+ xmax = m_data.col(0).max(),
+ ymin = m_data.col(1).min(),
+ ymax = m_data.col(1).max();
+
+ float xt = m_dragCurrentPos.x() - m_dragOriginPos.x();
+ float yt = m_dragCurrentPos.y() - m_dragOriginPos.y();
+
+ xt /= (width() - PADDING);
+ yt /= (height() - PADDING);
+ for (arma::uword i = 0; i < m_data.n_rows; i++) {
+ if (!m_selectedGlyphs[i])
+ continue;
+
+ arma::rowvec row = m_data.row(i);
+ row[0] = ((row[0] - xmin) / (xmax - xmin) + xt) * (xmax - xmin) + xmin;
+ row[1] = ((row[1] - ymin) / (ymax - ymin) + yt) * (ymax - ymin) + ymin;
+ m_data.row(i) = row;
+ }
+
+ // does not send last column (labels)
+ emit dataChanged(m_data.cols(0, m_data.n_cols - 2));
+}
diff --git a/scatterplot.h b/scatterplot.h
index 58774a9..5423a82 100644
--- a/scatterplot.h
+++ b/scatterplot.h
@@ -2,8 +2,8 @@
#define SCATTERPLOT_H
#include <armadillo>
-#include <vector>
#include <QQuickItem>
+#include <QSGNode>
#include "colorscale.h"
@@ -14,11 +14,11 @@ public:
Scatterplot(QQuickItem *parent = 0);
~Scatterplot();
- void setData(const arma::mat &data);
-
signals:
+ void dataChanged(const arma::mat &data);
public slots:
+ void setData(const arma::mat &data);
protected:
QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *);
@@ -28,6 +28,8 @@ protected:
private:
QSGNode *newGlyphNodeTree();
+ bool selectGlyphs(bool mergeSelection);
+ void updateData();
enum InteractionState {
INTERACTION_NONE,
@@ -36,6 +38,7 @@ private:
INTERACTION_MOVING
} m_currentState;
QPointF m_dragOriginPos, m_dragCurrentPos;
+ QList<bool> m_selectedGlyphs;
arma::mat m_data;
ColorScale m_colorScale;