From 7c4333eed407886114f33d803a3199c50556e4e3 Mon Sep 17 00:00:00 2001 From: Samuel Fadel Date: Mon, 18 May 2015 18:33:50 -0300 Subject: Updated UI. - Removed unnecessary UI elements from QML file; - Added the ColorScale class and implemented glyph color mapping from class labels; - Mark geometry nodes of individual glyphs as dirty when updating the schene graph. --- colorscale.cpp | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ colorscale.h | 24 +++++++++++++++++++ main.cpp | 9 +++++--- main_view.qml | 27 ---------------------- pm.pro | 5 +++- scatterplot.cpp | 45 ++++++++++++++++++------------------ scatterplot.h | 9 ++++++-- 7 files changed, 136 insertions(+), 55 deletions(-) create mode 100644 colorscale.cpp create mode 100644 colorscale.h diff --git a/colorscale.cpp b/colorscale.cpp new file mode 100644 index 0000000..50fedae --- /dev/null +++ b/colorscale.cpp @@ -0,0 +1,72 @@ +#include "colorscale.h" + +ColorScale::ColorScale(const QColor &firstColor, const QColor &lastColor) + : m_colors{firstColor, lastColor} +{ + setExtents(0, 1); +} + +ColorScale::ColorScale(std::initializer_list colors) + : m_colors(colors) +{ + setExtents(0, 1); +} + +ColorScale::ColorScale(const QList &colors) + : m_colors(colors) +{ + setExtents(0, 1); +} + +ColorScale::~ColorScale() +{ +} + +void ColorScale::setExtents(qreal min, qreal max) +{ + if (min >= max) + return; + + m_min = min; + m_max = max; +} + +static QColor lerp(const QColor &c1, const QColor &c2, qreal t) +{ + qreal r1, g1, b1, a1; + qreal r2, g2, b2, a2; + + c1.getRgbF(&r1, &g1, &b1, &a1); + c2.getRgbF(&r2, &g2, &b2, &a2); + QColor color; + color.setRgbF(r1 * (1 - t) + r2 * t, + g1 * (1 - t) + g2 * t, + b1 * (1 - t) + b2 * t, + a1 * (1 - t) + a2 * t); + return color; +} + +QColor ColorScale::color(qreal t) const +{ + if (t < m_min || t > m_max) + return QColor(); + + // normalize t + t = (t - m_min) / (m_max - m_min); + + // two colors, use a simpler solution + if (m_colors.size() == 2) + return lerp(m_colors.first(), m_colors.last(), t); + + // find which colors in the scale are adjacent to ours + qreal step = 1.0 / m_colors.size(); + int i = (int) (t / step); + int j = i + 1; + + if (i >= m_colors.size() - 1) + return QColor(m_colors.last()); + + // normalize t between the two colors + t = (t - i*step) / (j*step - i*step); + return lerp(m_colors[i], m_colors[j], t); +} diff --git a/colorscale.h b/colorscale.h new file mode 100644 index 0000000..6e1212d --- /dev/null +++ b/colorscale.h @@ -0,0 +1,24 @@ +#ifndef COLORSCALE_H +#define COLORSCALE_H + +#include +#include +#include + +class ColorScale +{ +public: + ColorScale(const QColor &firstColor, const QColor &lastColor); + ColorScale(std::initializer_list colors); + ColorScale(const QList &colors); + ~ColorScale(); + + void setExtents(qreal min, qreal max); + QColor color(qreal t) const; + +private: + qreal m_min, m_max; + QList m_colors; +}; + +#endif // COLORSCALE_H diff --git a/main.cpp b/main.cpp index fb6ab1e..a00d884 100644 --- a/main.cpp +++ b/main.cpp @@ -35,9 +35,12 @@ int main(int argc, char **argv) if (argc > 1) { arma::mat X; X.load(argv[1], arma::raw_ascii); - arma::mat projection = getProjection(X); - Scatterplot *plot = view.rootObject()->findChild("plot", Qt::FindDirectChildrenOnly); - plot->setData(projection); + + 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); } view.show(); diff --git a/main_view.qml b/main_view.qml index bf143fc..a9ff37e 100644 --- a/main_view.qml +++ b/main_view.qml @@ -10,31 +10,4 @@ Item { objectName: "plot" anchors.fill: parent } - - Item { - id: messageBox - anchors.right: parent.right - anchors.left: parent.left - anchors.bottom: parent.bottom - - Rectangle { - color: Qt.rgba(1, 1, 1, 0.7) - radius: 5 - border.width: 1 - border.color: "white" - anchors.fill: messageLabel - anchors.margins: -10 - } - - Text { - id: messageLabel - color: "black" - wrapMode: Text.WordWrap - text: "The background here is a squircle rendered with raw OpenGL using the 'beforeRender()' signal in QQuickWindow. This text label and its border is rendered using QML" - anchors.right: parent.right - anchors.left: parent.left - anchors.bottom: parent.bottom - anchors.margins: 20 - } - } } diff --git a/pm.pro b/pm.pro index 2ba534d..1c6e26a 100644 --- a/pm.pro +++ b/pm.pro @@ -1,9 +1,12 @@ QT += qml quick +QMAKE_CXXFLAGS += -std=c++11 QMAKE_LIBS += -larmadillo -HEADERS += scatterplot.h \ +HEADERS += colorscale.h \ + scatterplot.h \ mp.h SOURCES += main.cpp \ + colorscale.cpp \ scatterplot.cpp \ lamp.cpp \ forceScheme.cpp \ diff --git a/scatterplot.cpp b/scatterplot.cpp index b5e8261..a3a0dfc 100644 --- a/scatterplot.cpp +++ b/scatterplot.cpp @@ -1,18 +1,22 @@ #include "scatterplot.h" +#include + #include -#include #include #include #include #include #include +#include -const int GLYPH_SIZE = 4; -const float PADDING = 5; +const int GLYPH_SIZE = 5; +const float PADDING = 10; const float PI = 3.1415f; -Scatterplot::Scatterplot() +Scatterplot::Scatterplot(QQuickItem *parent) + : QQuickItem(parent) + , m_colorScale{QColor("red"), QColor("green"), QColor("blue")} { setFlag(QQuickItem::ItemHasContents); } @@ -23,10 +27,11 @@ Scatterplot::~Scatterplot() void Scatterplot::setData(const arma::mat &data) { - if (data.n_cols != 2) + if (data.n_cols != 3) return; m_data = data; + m_colorScale.setExtents(m_data.col(2).min(), m_data.col(2).max()); update(); } @@ -68,10 +73,10 @@ void updateSquareGeometry(QSGGeometry *geometry, float size, float cx, float cy) QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) { + if (m_data.n_rows < 1) + return 0; + QSGNode *node = 0; - QSGGeometryNode *childNode = 0; - QSGGeometry *geometry = 0; - QSGFlatColorMaterial *material = 0; int vertexCount = calculateCircleVertexCount(GLYPH_SIZE / 2); qreal xmin = m_data.col(0).min(), xmax = m_data.col(0).max(), @@ -82,19 +87,15 @@ QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) if (!oldNode) { node = new QSGNode; for (arma::uword i = 0; i < m_data.n_rows; i++) { - arma::rowvec row = m_data.row(i); - x = (row[0] - xmin) / (xmax - xmin) * width(); - y = (row[1] - ymin) / (ymax - ymin) * height(); - - childNode = new QSGGeometryNode; + QSGGeometryNode *childNode = new QSGGeometryNode; - geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), vertexCount); + QSGGeometry *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), vertexCount); geometry->setDrawingMode(GL_POLYGON); childNode->setGeometry(geometry); childNode->setFlag(QSGNode::OwnsGeometry); - material = new QSGFlatColorMaterial; - material->setColor(QColor()); + QSGFlatColorMaterial *material = new QSGFlatColorMaterial; + material->setColor(m_colorScale.color(m_data(i, 2))); childNode->setMaterial(material); childNode->setFlag(QSGNode::OwnsMaterial); @@ -104,17 +105,17 @@ QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) node = oldNode; } - childNode = static_cast(node->firstChild()); + QSGNode *childNode = node->firstChild(); for (arma::uword i = 0; i < m_data.n_rows; i++) { arma::rowvec row = m_data.row(i); - x = (row[0] - xmin) / (xmax - xmin) * width(); - y = (row[1] - ymin) / (ymax - ymin) * height(); + x = PADDING + (row[0] - xmin) / (xmax - xmin) * (width() - 2*PADDING); + y = PADDING + (row[1] - ymin) / (ymax - ymin) * (height() - 2*PADDING); - geometry = childNode->geometry(); + QSGGeometry *geometry = static_cast(childNode)->geometry(); updateCircleGeometry(geometry, GLYPH_SIZE, x, y); - childNode = static_cast(childNode->nextSibling()); + childNode->markDirty(QSGNode::DirtyGeometry); + childNode = childNode->nextSibling(); } - node->markDirty(QSGNode::DirtyGeometry); return node; } diff --git a/scatterplot.h b/scatterplot.h index f38ce78..3f2fee6 100644 --- a/scatterplot.h +++ b/scatterplot.h @@ -5,22 +5,27 @@ #include #include +#include "colorscale.h" + class Scatterplot : public QQuickItem { Q_OBJECT public: - Scatterplot(); + Scatterplot(QQuickItem *parent = 0); ~Scatterplot(); - QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *); void setData(const arma::mat &data); signals: public slots: +protected: + QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *); + private: arma::mat m_data; + ColorScale m_colorScale; }; #endif // SCATTERPLOT_H -- cgit v1.2.3 From ecc6ada9f64a9858f29b5da2e733fec0ec6f8bad 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 From 07eb2224a70e29683ec9fd5008662ede32c05639 Mon Sep 17 00:00:00 2001 From: Samuel Fadel Date: Wed, 20 May 2015 18:53:07 -0300 Subject: Nearly complete implementation of interaction. Interaction still does not work due to signals not being correctly emitted. --- interactionhandler.cpp | 31 ++++++++++++++ interactionhandler.h | 33 +++++++++++++++ main.cpp | 33 +++++++-------- main_view.qml | 4 +- pm.pro | 2 + scatterplot.cpp | 111 +++++++++++++++++++++++++++++++++++++++---------- scatterplot.h | 9 ++-- 7 files changed, 180 insertions(+), 43 deletions(-) create mode 100644 interactionhandler.cpp create mode 100644 interactionhandler.h 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 + +#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 +#include #include #include #include #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("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 *) 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(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); + + Scatterplot *subsamplePlot = engine.rootObjects()[0]->findChild("subsamplePlot"); + subsamplePlot->setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton | Qt::RightButton); + subsamplePlot->setData(subsampleData); + Scatterplot *plot = engine.rootObjects()[0]->findChild("plot"); + + // connect both plots through interaction handler + interactionHandler = std::unique_ptr(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 - #include -#include #include #include #include #include #include -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(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 -#include #include +#include #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 m_selectedGlyphs; arma::mat m_data; ColorScale m_colorScale; -- cgit v1.2.3 From e4d02b97c960162ef191f6eafadbdfc6b3668973 Mon Sep 17 00:00:00 2001 From: Samuel Fadel Date: Thu, 21 May 2015 12:55:35 -0300 Subject: Correct interaction handling. --- interactionhandler.cpp | 2 +- main.cpp | 3 ++- scatterplot.cpp | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/interactionhandler.cpp b/interactionhandler.cpp index fab3c75..217f4a5 100644 --- a/interactionhandler.cpp +++ b/interactionhandler.cpp @@ -23,7 +23,7 @@ void InteractionHandler::setSubsample(const arma::mat &Ys) break; } - arma::mat Y(embedding.n_rows, embedding.n_cols); + arma::mat Y(embedding.n_rows, embedding.n_cols + 1); Y.cols(0, embedding.n_cols - 1) = embedding; Y.col(Y.n_cols - 1) = m_labels; diff --git a/main.cpp b/main.cpp index 3f2a85e..380d311 100644 --- a/main.cpp +++ b/main.cpp @@ -14,6 +14,7 @@ int main(int argc, char **argv) qmlRegisterType("PM", 1, 0, "Scatterplot"); + // Set up multisampling QSurfaceFormat fmt; fmt.setSamples(16); QSurfaceFormat::setDefaultFormat(fmt); @@ -46,7 +47,7 @@ int main(int argc, char **argv) interactionHandler.get(), SLOT(setSubsample(const arma::mat &))); QObject::connect(interactionHandler.get(), SIGNAL(subsampleChanged(const arma::mat &)), plot, SLOT(setData(const arma::mat &))); - + interactionHandler.get()->setSubsample(Ys); } return app.exec(); diff --git a/scatterplot.cpp b/scatterplot.cpp index dd8d378..3335e9e 100644 --- a/scatterplot.cpp +++ b/scatterplot.cpp @@ -150,7 +150,7 @@ QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) y = PADDING + (row[1] - ymin) / (ymax - ymin) * (height() - 2*PADDING) + yt * selected; QSGGeometry *geometry = static_cast(glyphNode)->geometry(); - geometry->setDrawingMode(m_selectedGlyphs[i] ? GL_POLYGON : GL_LINE_LOOP); + geometry->setDrawingMode(!m_selectedGlyphs[i] ? GL_POLYGON : GL_LINE_LOOP); updateCircleGeometry(geometry, GLYPH_SIZE, x, y); glyphNode->markDirty(QSGNode::DirtyGeometry); glyphNode = glyphNode->nextSibling(); -- cgit v1.2.3 From e700acce78a80bcae5b8608caaab69d53e09bd5e Mon Sep 17 00:00:00 2001 From: Samuel Fadel Date: Thu, 21 May 2015 14:08:59 -0300 Subject: Added more state variables in Scatterplot to reduce computation time. --- scatterplot.cpp | 44 +++++++++++++++++++++++--------------------- scatterplot.h | 5 +++++ 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/scatterplot.cpp b/scatterplot.cpp index 3335e9e..2672efd 100644 --- a/scatterplot.cpp +++ b/scatterplot.cpp @@ -40,8 +40,13 @@ void Scatterplot::setData(const arma::mat &data) return; m_data = data; + m_xmin = data.col(0).min(); + m_xmax = data.col(0).max(); + m_ymin = data.col(1).min(); + m_ymax = data.col(1).max(); 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); @@ -94,6 +99,16 @@ void updateSelectionGeometry(QSGGeometry *geometry, const QPointF &p1, const QPo vertexData[3].set(p1.x(), p2.y()); } +float Scatterplot::fromDataXToScreenX(float x) +{ + return PADDING + (x - m_xmin) / (m_xmax - m_xmin) * (width() - 2*PADDING); +} + +float Scatterplot::fromDataYToScreenY(float y) +{ + return PADDING + (y - m_ymin) / (m_ymax - m_ymin) * (height() - 2*PADDING); +} + QSGNode *Scatterplot::newGlyphNodeTree() { QSGNode *node = new QSGNode; int vertexCount = calculateCircleVertexCount(GLYPH_SIZE / 2); @@ -122,11 +137,7 @@ QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) if (m_data.n_rows < 1) return 0; - 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, xt, yt, selected; + qreal x, y, xt, yt, selected; QSGNode *root = 0; if (!oldNode) { @@ -146,8 +157,8 @@ QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) for (arma::uword i = 0; i < m_data.n_rows; i++) { arma::rowvec row = m_data.row(i); 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; + x = fromDataXToScreenX(row[0]) + xt * selected; + y = fromDataYToScreenY(row[1]) + yt * selected; QSGGeometry *geometry = static_cast(glyphNode)->geometry(); geometry->setDrawingMode(!m_selectedGlyphs[i] ? GL_POLYGON : GL_LINE_LOOP); @@ -242,18 +253,14 @@ void Scatterplot::mouseReleaseEvent(QMouseEvent *event) 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; + qreal 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); + x = fromDataXToScreenX(row[0]); + y = fromDataYToScreenY(row[1]); bool contains = selectionRect.contains(x, y); anySelected = anySelected || contains; @@ -265,11 +272,6 @@ bool Scatterplot::selectGlyphs(bool mergeSelection) 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(); @@ -280,8 +282,8 @@ void Scatterplot::updateData() 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; + row[0] = ((row[0] - m_xmin) / (m_xmax - m_xmin) + xt) * (m_xmax - m_xmin) + m_xmin; + row[1] = ((row[1] - m_ymin) / (m_ymax - m_ymin) + yt) * (m_ymax - m_ymin) + m_ymin; m_data.row(i) = row; } diff --git a/scatterplot.h b/scatterplot.h index 5423a82..2b5aed8 100644 --- a/scatterplot.h +++ b/scatterplot.h @@ -31,6 +31,9 @@ private: bool selectGlyphs(bool mergeSelection); void updateData(); + float fromDataXToScreenX(float x); + float fromDataYToScreenY(float y); + enum InteractionState { INTERACTION_NONE, INTERACTION_SELECTING, @@ -41,6 +44,8 @@ private: QList m_selectedGlyphs; arma::mat m_data; + float m_xmin, m_xmax, m_ymin, m_ymax; + ColorScale m_colorScale; }; -- cgit v1.2.3 From 896c35b50e7a1a4ba40007fcca0982b70fc0d187 Mon Sep 17 00:00:00 2001 From: Samuel Fadel Date: Thu, 21 May 2015 15:12:36 -0300 Subject: Updated visual display. Points now have 0.5 opacity by default, while selected ones will have 1.0. --- scatterplot.cpp | 63 +++++++++++++++++++++++++-------------------------------- 1 file changed, 28 insertions(+), 35 deletions(-) diff --git a/scatterplot.cpp b/scatterplot.cpp index 2672efd..f5d0dbf 100644 --- a/scatterplot.cpp +++ b/scatterplot.cpp @@ -3,13 +3,17 @@ #include #include #include +#include #include #include #include -const int GLYPH_SIZE = 8; -const float PADDING = 10; -const float PI = 3.1415f; +static const qreal GLYPH_OPACITY = 0.3; +static const qreal GLYPH_OPACITY_SELECTED = 1.0; +static const QColor SELECTION_COLOR(QColor(128, 128, 128, 96)); +static const int GLYPH_SIZE = 8; +static const float PADDING = 10; +static const float PI = 3.1415f; Scatterplot::Scatterplot(QQuickItem *parent) : QQuickItem(parent) @@ -90,15 +94,6 @@ 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()); -} - float Scatterplot::fromDataXToScreenX(float x) { return PADDING + (x - m_xmin) / (m_xmax - m_xmin) * (width() - 2*PADDING); @@ -117,7 +112,7 @@ QSGNode *Scatterplot::newGlyphNodeTree() { QSGGeometryNode *glyphNode = new QSGGeometryNode; QSGGeometry *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), vertexCount); - geometry->setDrawingMode(GL_LINE_LOOP); + geometry->setDrawingMode(GL_POLYGON); glyphNode->setGeometry(geometry); glyphNode->setFlag(QSGNode::OwnsGeometry); @@ -126,7 +121,10 @@ QSGNode *Scatterplot::newGlyphNodeTree() { glyphNode->setMaterial(material); glyphNode->setFlag(QSGNode::OwnsMaterial); - node->appendChildNode(glyphNode); + // Place the glyph geometry node under a opacity node + QSGOpacityNode *glyphOpacityNode = new QSGOpacityNode; + glyphOpacityNode->appendChildNode(glyphNode); + node->appendChildNode(glyphOpacityNode); } return node; @@ -137,7 +135,7 @@ QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) if (m_data.n_rows < 1) return 0; - qreal x, y, xt, yt, selected; + qreal x, y, xt, yt, moveTranslationF; QSGNode *root = 0; if (!oldNode) { @@ -147,47 +145,42 @@ QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) root = oldNode; } - 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(); } + + QSGNode *glyphOpacityNode = root->firstChild()->firstChild(); for (arma::uword i = 0; i < m_data.n_rows; i++) { arma::rowvec row = m_data.row(i); - selected = m_selectedGlyphs[i] ? 1.0 : 0.0; - x = fromDataXToScreenX(row[0]) + xt * selected; - y = fromDataYToScreenY(row[1]) + yt * selected; + moveTranslationF = m_selectedGlyphs[i] ? 1.0 : 0.0; + x = fromDataXToScreenX(row[0]) + xt * moveTranslationF; + y = fromDataYToScreenY(row[1]) + yt * moveTranslationF; + QSGNode *glyphNode = glyphOpacityNode->firstChild(); QSGGeometry *geometry = static_cast(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(); + + static_cast(glyphOpacityNode)->setOpacity(m_selectedGlyphs[i] ? GLYPH_OPACITY_SELECTED : GLYPH_OPACITY); + + glyphOpacityNode = glyphOpacityNode->nextSibling(); } // Draw selection if (m_currentState == INTERACTION_SELECTING) { - QSGGeometryNode *selectionNode = 0; + QSGSimpleRectNode *selectionNode = 0; if (!root->firstChild()->nextSibling()) { - selectionNode = new QSGGeometryNode; - QSGGeometry *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 4); - geometry->setDrawingMode(GL_LINE_LOOP); - selectionNode->setGeometry(geometry); - selectionNode->setFlag(QSGNode::OwnsGeometry); - - QSGFlatColorMaterial *material = new QSGFlatColorMaterial; - material->setColor(QColor(0, 0, 0, 128)); - selectionNode->setMaterial(material); - selectionNode->setFlag(QSGNode::OwnsMaterial); - + selectionNode = new QSGSimpleRectNode; + selectionNode->setColor(SELECTION_COLOR); root->appendChildNode(selectionNode); } else { - selectionNode = static_cast(root->firstChild()->nextSibling()); + selectionNode = static_cast(root->firstChild()->nextSibling()); } - updateSelectionGeometry(selectionNode->geometry(), m_dragOriginPos, m_dragCurrentPos); + selectionNode->setRect(QRectF(m_dragOriginPos, m_dragCurrentPos)); selectionNode->markDirty(QSGNode::DirtyGeometry); } else { if (root->firstChild()->nextSibling()) { -- cgit v1.2.3 From 9706895943dcef136c67ebdaea4c01bfc81ce5d1 Mon Sep 17 00:00:00 2001 From: Samuel Fadel Date: Thu, 21 May 2015 17:52:26 -0300 Subject: Updated color scale with better distinguishable colors. --- scatterplot.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scatterplot.cpp b/scatterplot.cpp index f5d0dbf..b0c8034 100644 --- a/scatterplot.cpp +++ b/scatterplot.cpp @@ -17,7 +17,6 @@ static const float PI = 3.1415f; Scatterplot::Scatterplot(QQuickItem *parent) : QQuickItem(parent) - , m_dragOriginPos(-1.0, -1.0) , m_colorScale{ QColor("#1f77b4"), QColor("#ff7f0e"), @@ -27,7 +26,7 @@ Scatterplot::Scatterplot(QQuickItem *parent) QColor("#8c564b"), QColor("#e377c2"), QColor("#7f7f7f"), - QColor("#bcbd22"), + QColor("#17becf"), } { setClip(true); @@ -218,6 +217,7 @@ void Scatterplot::mouseMoveEvent(QMouseEvent *event) case INTERACTION_MOVING: m_dragCurrentPos = event->localPos(); update(); + break; } } -- cgit v1.2.3 From a12b317f441241c1eae3cac1e8b03d210859be90 Mon Sep 17 00:00:00 2001 From: Samuel Fadel Date: Thu, 21 May 2015 18:01:38 -0300 Subject: Added (non-functional) file dialog for opening files. --- main.cpp | 4 ++-- main_view.qml | 12 +++++++++++- pm.pro | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/main.cpp b/main.cpp index 380d311..0f7f8c8 100644 --- a/main.cpp +++ b/main.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include "mp.h" @@ -10,7 +10,7 @@ int main(int argc, char **argv) { - QGuiApplication app(argc, argv); + QApplication app(argc, argv); qmlRegisterType("PM", 1, 0, "Scatterplot"); diff --git a/main_view.qml b/main_view.qml index 4a093f2..5d86c36 100644 --- a/main_view.qml +++ b/main_view.qml @@ -1,8 +1,10 @@ import QtQuick 2.0 import QtQuick.Controls 1.3 +import QtQuick.Dialogs 1.2 import PM 1.0 ApplicationWindow { + title: "Projection Manipulation" visible: true width: 1200 height: 600 @@ -59,6 +61,14 @@ ApplicationWindow { } } + FileDialog { + id: fileDialog + title: "Choose a file..." + onAccepted: { + // datasetLoader.load(fileDialog.fileUrls) + } + } + Action { id: quitAction text: "&Quit" @@ -70,7 +80,7 @@ ApplicationWindow { id: openAction text: "&Open..." shortcut: "Ctrl+O" - onTriggered: console.log("Open file") + onTriggered: fileDialog.open() } Action { diff --git a/pm.pro b/pm.pro index 0d0f153..9608b89 100644 --- a/pm.pro +++ b/pm.pro @@ -1,4 +1,4 @@ -QT += qml quick +QT += qml quick widgets QMAKE_CXXFLAGS += -std=c++11 QMAKE_LIBS += -larmadillo -- cgit v1.2.3