aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--main.cpp59
-rw-r--r--main_view.qml86
-rw-r--r--scatterplot.cpp150
-rw-r--r--scatterplot.h13
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 <cmath>
+#include <QSurfaceFormat>
#include <QGuiApplication>
-#include <QtQuick/QQuickView>
+#include <QQmlApplicationEngine>
#include "mp.h"
#include "scatterplot.h"
-arma::uvec getSample(arma::uword n)
-{
- return arma::randi<arma::uvec>((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<Scatterplot>("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<Scatterplot *>("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<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);
}
- 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<QSGGeometryNode *>(childNode)->geometry();
+ QSGGeometry *geometry = static_cast<QSGGeometryNode *>(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<QSGGeometryNode *>(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;
};