From 02e2ebf10c30ca278dc8a85649c6a7db87858cde Mon Sep 17 00:00:00 2001 From: Samuel Fadel Date: Tue, 19 May 2015 18:54:20 -0300 Subject: Initial selection implementation. --- main.cpp | 59 ++++++++++++---------- main_view.qml | 86 ++++++++++++++++++++++++++++++-- scatterplot.cpp | 150 ++++++++++++++++++++++++++++++++++++++++++++++---------- scatterplot.h | 13 +++++ 4 files changed, 251 insertions(+), 57 deletions(-) diff --git a/main.cpp b/main.cpp index a00d884..4fcff65 100644 --- a/main.cpp +++ b/main.cpp @@ -1,49 +1,54 @@ #include +#include #include -#include +#include #include "mp.h" #include "scatterplot.h" -arma::uvec getSample(arma::uword n) -{ - return arma::randi((arma::uword) 3*sqrt(n), arma::distr_param(0, n-1)); -} - -arma::mat getProjection(const arma::mat &X) -{ - arma::uword n = X.n_rows; - arma::uvec sampleIndices = getSample(n); - arma::mat Ys = arma::randn(sampleIndices.n_elem, 2); - Ys = mp::forceScheme(mp::dist(X.rows(sampleIndices)), Ys); - return mp::lamp(X, sampleIndices, Ys); -} - int main(int argc, char **argv) { QGuiApplication app(argc, argv); qmlRegisterType("PM", 1, 0, "Scatterplot"); - QQuickView view; + /*QQuickView view; QSurfaceFormat format = view.format(); format.setSamples(16); view.setFormat(format); view.setResizeMode(QQuickView::SizeRootObjectToView); - view.setSource(QUrl("qrc:///main_view.qml")); + view.setSource(QUrl("qrc:///main_view.qml"));*/ + QSurfaceFormat fmt; + fmt.setSamples(16); + QSurfaceFormat::setDefaultFormat(fmt); + QQmlApplicationEngine engine(QUrl("qrc:///main_view.qml")); if (argc > 1) { - arma::mat X; - X.load(argv[1], arma::raw_ascii); - - Scatterplot *plot = view.rootObject()->findChild("plot"); - arma::mat scatterData(X.n_rows, 3); - scatterData.cols(0, 1) = getProjection(X.cols(0, X.n_cols - 2)); - scatterData.col(2) = X.col(X.n_cols - 1); - plot->setData(scatterData); + arma::mat dataset; + dataset.load(argv[1], arma::raw_ascii); + arma::mat X = dataset.cols(0, dataset.n_cols - 2); + arma::vec labels = dataset.col(dataset.n_cols - 1); + + arma::uword n = dataset.n_rows; + arma::uword subsampleSize = (arma::uword) sqrt(n) * 3; + arma::uvec sampleIndices = arma::randi(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("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("plot"); + arma::mat reducedData(n, 3); + reducedData.cols(0, 1) = mp::lamp(X, sampleIndices, Ys); + reducedData.col(2) = labels; + plot->setData(reducedData); } - view.show(); - return app.exec(); } diff --git a/main_view.qml b/main_view.qml index a9ff37e..8389461 100644 --- a/main_view.qml +++ b/main_view.qml @@ -1,13 +1,89 @@ import QtQuick 2.0 +import QtQuick.Controls 1.3 import PM 1.0 -Item { - width: 480 +ApplicationWindow { + visible: true + width: 960 height: 480 - Scatterplot { - id: plot - objectName: "plot" + menuBar: MenuBar { + Menu { + title: "Application" + MenuItem { action: quitAction } + } + + Menu { + title: "File" + MenuItem { action: openAction } + MenuItem { action: savePlotAction } + MenuItem { action: saveDataAction } + } + } + + Item { anchors.fill: parent + + Rectangle { + anchors.fill: subsamplePlot + border.width: 1 + border.color: "#cccccc" + z: 0 + } + + Rectangle { + anchors.fill: plot + border.width: 1 + border.color: "#cccccc" + z: 0 + } + + Scatterplot { + id: subsamplePlot + objectName: "subsamplePlot" + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + width: 0.5 * parent.width + z: 1 + } + + Scatterplot { + id: plot + objectName: "plot" + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + width: 0.5 * parent.width + z: 1 + } + } + + Action { + id: quitAction + text: "&Quit" + shortcut: "Ctrl+Q" + onTriggered: Qt.quit() + } + + Action { + id: openAction + text: "&Open..." + shortcut: "Ctrl+O" + onTriggered: console.log("Open file") + } + + Action { + id: savePlotAction + text: "Save &plot" + shortcut: "Ctrl+S" + onTriggered: console.log("Save plot") + } + + Action { + id: saveDataAction + text: "Save &data" + shortcut: "Ctrl+D" + onTriggered: console.log("Save data") } } diff --git a/scatterplot.cpp b/scatterplot.cpp index a3a0dfc..414f46a 100644 --- a/scatterplot.cpp +++ b/scatterplot.cpp @@ -16,9 +16,22 @@ const float PI = 3.1415f; Scatterplot::Scatterplot(QQuickItem *parent) : QQuickItem(parent) - , m_colorScale{QColor("red"), QColor("green"), QColor("blue")} + , m_dragOriginPos(-1.0, -1.0) + , m_colorScale{ + QColor("#1f77b4"), + QColor("#ff7f0e"), + QColor("#2ca02c"), + QColor("#d62728"), + QColor("#9467bd"), + QColor("#8c564b"), + QColor("#e377c2"), + QColor("#7f7f7f"), + QColor("#bcbd22"), + } { + setClip(true); setFlag(QQuickItem::ItemHasContents); + setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton); } Scatterplot::~Scatterplot() @@ -71,51 +84,138 @@ void updateSquareGeometry(QSGGeometry *geometry, float size, float cx, float cy) vertexData[3].set(cx - r, cy + r); } +void updateSelectionGeometry(QSGGeometry *geometry, const QPointF &p1, const QPointF &p2) +{ + QSGGeometry::Point2D *vertexData = geometry->vertexDataAsPoint2D(); + vertexData[0].set(p1.x(), p1.y()); + vertexData[1].set(p2.x(), p1.y()); + vertexData[2].set(p2.x(), p2.y()); + vertexData[3].set(p1.x(), p2.y()); +} + +QSGNode *Scatterplot::newGlyphNodeTree() { + QSGNode *node = new QSGNode; + int vertexCount = calculateCircleVertexCount(GLYPH_SIZE / 2); + + for (arma::uword i = 0; i < m_data.n_rows; i++) { + QSGGeometryNode *glyphNode = new QSGGeometryNode; + + QSGGeometry *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), vertexCount); + geometry->setDrawingMode(GL_POLYGON); + glyphNode->setGeometry(geometry); + glyphNode->setFlag(QSGNode::OwnsGeometry); + + QSGFlatColorMaterial *material = new QSGFlatColorMaterial; + material->setColor(m_colorScale.color(m_data(i, 2))); + glyphNode->setMaterial(material); + glyphNode->setFlag(QSGNode::OwnsMaterial); + + node->appendChildNode(glyphNode); + } + + return node; +} + QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) { if (m_data.n_rows < 1) return 0; - QSGNode *node = 0; - int vertexCount = calculateCircleVertexCount(GLYPH_SIZE / 2); 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; + QSGNode *root = 0; if (!oldNode) { - node = new QSGNode; - for (arma::uword i = 0; i < m_data.n_rows; i++) { - QSGGeometryNode *childNode = new QSGGeometryNode; - - QSGGeometry *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), vertexCount); - geometry->setDrawingMode(GL_POLYGON); - childNode->setGeometry(geometry); - childNode->setFlag(QSGNode::OwnsGeometry); - - QSGFlatColorMaterial *material = new QSGFlatColorMaterial; - material->setColor(m_colorScale.color(m_data(i, 2))); - childNode->setMaterial(material); - childNode->setFlag(QSGNode::OwnsMaterial); - - node->appendChildNode(childNode); - } + root = new QSGNode; + root->appendChildNode(newGlyphNodeTree()); } else { - node = oldNode; + root = oldNode; } - QSGNode *childNode = node->firstChild(); + QSGNode *glyphNode = root->firstChild()->firstChild(); 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); - QSGGeometry *geometry = static_cast(childNode)->geometry(); + QSGGeometry *geometry = static_cast(glyphNode)->geometry(); updateCircleGeometry(geometry, GLYPH_SIZE, x, y); - childNode->markDirty(QSGNode::DirtyGeometry); - childNode = childNode->nextSibling(); + glyphNode->markDirty(QSGNode::DirtyGeometry); + glyphNode = glyphNode->nextSibling(); } - return node; + // Draw selection + if (m_currentState == INTERACTION_SELECTING) { + QSGGeometryNode *selectionNode = 0; + if (!root->firstChild()->nextSibling()) { + selectionNode = new QSGGeometryNode; + QSGGeometry *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 4); + geometry->setDrawingMode(GL_POLYGON); + selectionNode->setGeometry(geometry); + selectionNode->setFlag(QSGNode::OwnsGeometry); + + QSGFlatColorMaterial *material = new QSGFlatColorMaterial; + material->setColor(QColor(128, 128, 128, 128)); + selectionNode->setMaterial(material); + selectionNode->setFlag(QSGNode::OwnsMaterial); + + root->appendChildNode(selectionNode); + } else { + selectionNode = static_cast(root->firstChild()->nextSibling()); + } + + updateSelectionGeometry(selectionNode->geometry(), m_dragOriginPos, m_dragCurrentPos); + selectionNode->markDirty(QSGNode::DirtyGeometry); + } else if (m_currentState == INTERACTION_NONE) { + if (root->firstChild()->nextSibling()) { + root->firstChild()->nextSibling()->markDirty(QSGNode::DirtyGeometry); + root->removeChildNode(root->firstChild()->nextSibling()); + } + } + + return root; +} + +void Scatterplot::mousePressEvent(QMouseEvent *event) +{ + switch (m_currentState) { + case INTERACTION_NONE: + m_currentState = INTERACTION_SELECTING; + m_dragOriginPos = event->localPos(); + break; + case INTERACTION_SELECTED: + m_currentState = INTERACTION_MOVING; + break; // TODO + case INTERACTION_SELECTING: + case INTERACTION_MOVING: + return; // should not be reached + } +} + +void Scatterplot::mouseMoveEvent(QMouseEvent *event) +{ + if (m_currentState != INTERACTION_SELECTING) + return; + + m_dragCurrentPos = event->localPos(); + update(); +} + +void Scatterplot::mouseReleaseEvent(QMouseEvent *event) +{ + switch (m_currentState) { + case INTERACTION_SELECTING: + m_currentState = INTERACTION_NONE; + update(); + break; + + case INTERACTION_MOVING: + break; + case INTERACTION_NONE: + case INTERACTION_SELECTED: + return; // should not be reached + } } diff --git a/scatterplot.h b/scatterplot.h index 3f2fee6..58774a9 100644 --- a/scatterplot.h +++ b/scatterplot.h @@ -22,8 +22,21 @@ public slots: protected: QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *); + void mousePressEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); private: + QSGNode *newGlyphNodeTree(); + + enum InteractionState { + INTERACTION_NONE, + INTERACTION_SELECTING, + INTERACTION_SELECTED, + INTERACTION_MOVING + } m_currentState; + QPointF m_dragOriginPos, m_dragCurrentPos; + arma::mat m_data; ColorScale m_colorScale; }; -- cgit v1.2.3