aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSamuel Fadel <samuelfadel@gmail.com>2015-09-29 14:59:59 -0300
committerSamuel Fadel <samuelfadel@gmail.com>2015-09-29 14:59:59 -0300
commit30f327b2fd25104916bffe6fe76823f0dbe35a72 (patch)
tree3ceb449cbd3b6e8965e891efe3ea6b36183a6b10
parent56c9ebb2e41bd0487199ed95838cd9e1c1d9dd8d (diff)
Inital history graph implementation and using linear scales where applicable.
- geometry.h for geometry calculation functions - scale.h for implementations of scales (currently only the linear scale) - updated main_view.qml for the new HistoryGraph component - HistoryGraph displays each subsample used as a mini scatterplot (no colors currently) - Scatterplot now uses scale.h for transformations - Code cleanup and some bug fixes
-rw-r--r--geometry.cpp39
-rw-r--r--geometry.h13
-rw-r--r--historygraph.cpp260
-rw-r--r--historygraph.h16
-rw-r--r--main.cpp33
-rw-r--r--main_view.qml4
-rw-r--r--pm.pro3
-rw-r--r--scale.h81
-rw-r--r--scatterplot.cpp133
-rw-r--r--scatterplot.h7
10 files changed, 495 insertions, 94 deletions
diff --git a/geometry.cpp b/geometry.cpp
new file mode 100644
index 0000000..3a4a861
--- /dev/null
+++ b/geometry.cpp
@@ -0,0 +1,39 @@
+#include "geometry.h"
+
+static const float PI = 3.1415f;
+
+int calculateCircleVertexCount(float radius)
+{
+ // 10 * sqrt(r) \approx 2*pi / acos(1 - 1 / (4*r))
+ return int(10.0 * sqrt(radius));
+}
+
+void updateCircleGeometry(QSGGeometry *geometry, float radius, float cx, float cy)
+{
+ int vertexCount = geometry->vertexCount();
+
+ float theta = 2 * PI / float(vertexCount);
+ float c = cosf(theta);
+ float s = sinf(theta);
+ float x = radius;
+ float y = 0;
+
+ QSGGeometry::Point2D *vertexData = geometry->vertexDataAsPoint2D();
+ for (int i = 0; i < vertexCount; i++) {
+ vertexData[i].set(x + cx, y + cy);
+
+ float t = x;
+ x = c*x - s*y;
+ y = s*t + c*y;
+ }
+}
+
+void updateRectGeometry(QSGGeometry *geometry, float x, float y, float w, float h)
+{
+ QSGGeometry::Point2D *vertexData = geometry->vertexDataAsPoint2D();
+
+ vertexData[0].set(x, y);
+ vertexData[1].set(x + w, y);
+ vertexData[2].set(x + w, y + h);
+ vertexData[3].set(x, y + h);
+}
diff --git a/geometry.h b/geometry.h
new file mode 100644
index 0000000..b29e41f
--- /dev/null
+++ b/geometry.h
@@ -0,0 +1,13 @@
+#ifndef GEOMETRY_H
+#define GEOMETRY_H
+
+#include <QSGGeometry>
+
+// Circle
+int calculateCircleVertexCount(float radius);
+void updateCircleGeometry(QSGGeometry *geometry, float radius, float cx, float cy);
+
+// Rect
+void updateRectGeometry(QSGGeometry *geometry, float x, float y, float w, float h);
+
+#endif // GEOMETRY_H
diff --git a/historygraph.cpp b/historygraph.cpp
index 1a4532a..997d079 100644
--- a/historygraph.cpp
+++ b/historygraph.cpp
@@ -1,14 +1,268 @@
#include "historygraph.h"
+#include <algorithm>
+#include <functional>
+#include <numeric>
+#include <utility>
+
+#include "geometry.h"
+#include "scale.h"
+
+static const float ASPECT = 4.f / 3.f;
+static const float MARGIN = 0.1f;
+static const float GLYPH_SIZE = 4.0f;
+static const float PADDING = 0.05f;
+
+class HistoryGraph::HistoryItemNode
+{
+public:
+ HistoryItemNode(const arma::mat &item);
+ ~HistoryItemNode();
+
+ void append(HistoryItemNode *node);
+ const QList<HistoryItemNode *> &children() const { return m_next; }
+ const arma::mat &item() const;
+ int depth() const { return m_depth; }
+ int breadth() const { return m_breadth; }
+ void updateDepth();
+ void updateBreadth();
+
+ static int treeBreadth(HistoryItemNode *node, int level);
+
+private:
+ arma::mat m_item;
+ HistoryItemNode *m_prev;
+ QList<HistoryItemNode *> m_next;
+ int m_depth, m_breadth;
+};
+
+HistoryGraph::HistoryItemNode::HistoryItemNode(const arma::mat &item)
+ : m_item(item)
+ , m_prev(0)
+ , m_depth(1)
+ , m_breadth(1)
+{
+}
+
+HistoryGraph::HistoryItemNode::~HistoryItemNode()
+{
+ m_prev = 0;
+
+ while (!m_next.isEmpty()) {
+ delete m_next.takeLast();
+ }
+}
+
+void HistoryGraph::HistoryItemNode::updateDepth()
+{
+ HistoryGraph::HistoryItemNode *node = *std::max_element(
+ m_next.cbegin(), m_next.cend(),
+ [](const HistoryGraph::HistoryItemNode *n1, const HistoryGraph::HistoryItemNode *n2) {
+ return n1->depth() < n2->depth();
+ });
+
+ m_depth = 1 + node->depth();
+
+ if (m_prev) {
+ m_prev->updateDepth();
+ }
+}
+
+int HistoryGraph::HistoryItemNode::treeBreadth(HistoryGraph::HistoryItemNode *node, int level) {
+ if (!node || level < 1) {
+ return 0;
+ }
+
+ if (level == 1) {
+ return 1;
+ }
+
+ return std::accumulate(node->m_next.cbegin(), node->m_next.cend(), 0, [](int b, HistoryGraph::HistoryItemNode *n) {
+ n->m_breadth = treeBreadth(n, n->depth());
+ return b + n->m_breadth;
+ });
+}
+
+void HistoryGraph::HistoryItemNode::updateBreadth()
+{
+ HistoryItemNode *node = this;
+ while (node->m_prev) {
+ node = node->m_prev;
+ }
+
+ node->m_breadth = treeBreadth(node, node->m_depth);
+}
+
+void HistoryGraph::HistoryItemNode::append(HistoryItemNode *node)
+{
+ if (!node) {
+ return;
+ }
+
+ m_next.append(node);
+ if (node->depth() + 1 > m_depth) {
+ updateDepth();
+ }
+
+ updateBreadth();
+
+ node->m_prev = this;
+}
+
+const arma::mat &HistoryGraph::HistoryItemNode::item() const
+{
+ return m_item;
+}
+
HistoryGraph::HistoryGraph(QQuickItem *parent)
: QQuickItem(parent)
+ , m_firstNode(0)
+ , m_currentNode(0)
+ , m_needsUpdate(false)
{
+ setClip(true);
+ setFlag(QQuickItem::ItemHasContents);
}
-void HistoryGraph::addHistoryItem(const int &item)
-{}
+HistoryGraph::~HistoryGraph()
+{
+ delete m_firstNode;
+ m_firstNode = 0;
+}
+
+void HistoryGraph::addHistoryItem(const arma::mat &item)
+{
+ HistoryItemNode *newNode = new HistoryItemNode(item);
+ if (m_currentNode) {
+ m_currentNode->append(newNode);
+ } else {
+ m_firstNode = newNode;
+ }
+
+ m_currentNode = newNode;
+ m_needsUpdate = true;
+ update();
+}
+
+void HistoryGraph::addScatterplot(QSGNode *node, const HistoryGraph::HistoryItemNode *historyItemNode, float x, float y, float w, float h)
+{
+ const arma::mat &xy = historyItemNode->item();
+ int vertexCount = calculateCircleVertexCount(GLYPH_SIZE / 2);
+
+ LinearScale sx(xy.col(0).min(), xy.col(0).max(), x, x + w);
+ LinearScale sy(xy.col(1).min(), xy.col(1).max(), y + h, y); // reverse on purpose
+
+ for (arma::uword i = 0; i < xy.n_rows; i++) {
+ const arma::rowvec &row = xy.row(i);
+ QSGGeometryNode *glyphNode = new QSGGeometryNode;
+
+ QSGGeometry *glyphGeometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), vertexCount);
+ glyphGeometry->setDrawingMode(GL_POLYGON);
+ updateCircleGeometry(glyphGeometry, GLYPH_SIZE / 2 - 0.5, sx(row[0]), sy(row[1]));
+ glyphNode->setGeometry(glyphGeometry);
+ glyphNode->setFlag(QSGNode::OwnsGeometry);
+
+ QSGFlatColorMaterial *material = new QSGFlatColorMaterial;
+ material->setColor(QColor());
+ glyphNode->setMaterial(material);
+ glyphNode->setFlag(QSGNode::OwnsMaterial);
+
+ // Place the glyph geometry node under an opacity node
+ QSGOpacityNode *glyphOpacityNode = new QSGOpacityNode;
+ //glyphOpacityNode->appendChildNode(glyphOutlineNode);
+ glyphOpacityNode->appendChildNode(glyphNode);
+ node->appendChildNode(glyphOpacityNode);
+ }
+}
+
+QSGNode *HistoryGraph::createNodeTree()
+{
+ if (!m_firstNode) {
+ return 0;
+ }
+
+ //int breadth = m_firstNode->breadth();
+ //int depth = m_firstNode->depth();
+
+ QSGNode *sceneGraphRoot = new QSGNode;
+ HistoryItemNode *histNode = m_firstNode;
+ float margin = height()*MARGIN;
+ float padding = height()*PADDING;
+ float h = height() - 2.f*margin;
+ float w = ASPECT * h;
+ float x = margin;
+
+ QMatrix4x4 mat;
+ do {
+ QSGOpacityNode *opacityNode = new QSGOpacityNode;
+ opacityNode->setOpacity(histNode == m_currentNode ? 1.0f : 0.4f);
+
+ QSGGeometryNode *histItemGeomNode = new QSGGeometryNode;
+ QSGGeometry *histItemGeom = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 4);
+ updateRectGeometry(histItemGeom, x, margin, w, h);
+ histItemGeom->setDrawingMode(GL_LINE_LOOP);
+ histItemGeomNode->setGeometry(histItemGeom);
+ histItemGeomNode->setFlag(QSGNode::OwnsGeometry);
+
+ QSGFlatColorMaterial *material = new QSGFlatColorMaterial;
+ material->setColor(QColor());
+ histItemGeomNode->setMaterial(material);
+ histItemGeomNode->setFlag(QSGNode::OwnsMaterial);
+
+ addScatterplot(histItemGeomNode, histNode, x + padding, margin + padding, w - 2*padding, h - 2*padding);
+ opacityNode->appendChildNode(histItemGeomNode);
+ sceneGraphRoot->appendChildNode(opacityNode);
+
+ x += w + 2.f*margin;
+
+ QList<HistoryItemNode *> children = histNode->children();
+ if (children.isEmpty()) {
+ break;
+ }
+
+ // FIXME: add children
+ histNode = children[0];
+ } while (true);
+
+ // setWidth(xPos + radius + margin);
+
+ return sceneGraphRoot;
+}
+
+void HistoryGraph::updateNodeTree(QSGNode *root)
+{
+ if (!m_firstNode) {
+ return;
+ }
+
+ // FIXME: (really) lame update algorithm
+
+ QSGNode *child = root->firstChild();
+ if (child) {
+ root->removeChildNode(child);
+ delete child;
+ }
+
+ root->appendChildNode(createNodeTree());
+}
QSGNode *HistoryGraph::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
- return oldNode;
+ QSGNode *root = 0;
+ if (!oldNode) {
+ root = new QSGNode;
+ QSGNode *node = createNodeTree();
+ if (node) {
+ root->appendChildNode(node);
+ }
+ } else {
+ root = oldNode;
+ }
+
+ if (m_needsUpdate) {
+ m_needsUpdate = false;
+ updateNodeTree(root);
+ }
+
+ return root;
}
diff --git a/historygraph.h b/historygraph.h
index b854d1e..564845c 100644
--- a/historygraph.h
+++ b/historygraph.h
@@ -1,20 +1,32 @@
#ifndef HISTORYGRAPH_H
#define HISTORYGRAPH_H
-#include <armadillo>
#include <QtQuick>
+#include <armadillo>
class HistoryGraph : public QQuickItem
{
Q_OBJECT
public:
HistoryGraph(QQuickItem *parent = 0);
+ ~HistoryGraph();
public slots:
- void addHistoryItem(const int &item);
+ void addHistoryItem(const arma::mat &item);
protected:
QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *);
+
+private:
+ class HistoryItemNode;
+
+ QSGNode *createNodeTree();
+ void updateNodeTree(QSGNode *root);
+ void addScatterplot(QSGNode *node, const HistoryItemNode *historyItemNode, float x, float y, float w, float h);
+
+
+ HistoryItemNode *m_firstNode, *m_currentNode;
+ bool m_needsUpdate;
};
#endif // HISTORYGRAPH_H
diff --git a/main.cpp b/main.cpp
index 58578ac..e8a826e 100644
--- a/main.cpp
+++ b/main.cpp
@@ -8,6 +8,7 @@
#include "mp.h"
#include "continuouscolorscale.h"
#include "scatterplot.h"
+#include "historygraph.h"
#include "interactionhandler.h"
#include "selectionhandler.h"
#include "effectivenessobserver.h"
@@ -19,6 +20,7 @@ int main(int argc, char **argv)
QApplication app(argc, argv);
qmlRegisterType<Scatterplot>("PM", 1, 0, "Scatterplot");
+ qmlRegisterType<HistoryGraph>("PM", 1, 0, "HistoryGraph");
// Set up multisampling
QSurfaceFormat fmt;
@@ -42,7 +44,6 @@ int main(int argc, char **argv)
arma::mat Ys(subsampleSize, 2, arma::fill::randn);
Ys = mp::forceScheme(mp::dist(X.rows(sampleIndices)), Ys);
- /*
ColorScale colorScale{
QColor("#1f77b4"),
QColor("#ff7f0e"),
@@ -55,14 +56,13 @@ int main(int argc, char **argv)
QColor("#7f7f7f"),
};
colorScale.setExtents(labels.min(), labels.max());
- */
- ContinuousColorScale colorScale = ContinuousColorScale::builtin(ContinuousColorScale::RED_GRAY_BLUE);
- colorScale.setExtents(-1, 1);
+ //ContinuousColorScale colorScale = ContinuousColorScale::builtin(ContinuousColorScale::RED_GRAY_BLUE);
+ //colorScale.setExtents(-1, 1);
Scatterplot *subsamplePlot = engine.rootObjects()[0]->findChild<Scatterplot *>("subsamplePlot");
+ HistoryGraph *history = engine.rootObjects()[0]->findChild<HistoryGraph *>("history");
subsamplePlot->setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton | Qt::RightButton);
- subsamplePlot->setXY(Ys);
- subsamplePlot->setColorData(arma::zeros<arma::vec>(subsampleSize));
+ // subsamplePlot->setColorData(arma::zeros<arma::vec>(subsampleSize));
subsamplePlot->setColorScale(&colorScale);
Scatterplot *plot = engine.rootObjects()[0]->findChild<Scatterplot *>("plot");
@@ -72,13 +72,16 @@ int main(int argc, char **argv)
&interactionHandler, SLOT(setSubsample(const arma::mat &)));
QObject::connect(&interactionHandler, SIGNAL(subsampleChanged(const arma::mat &)),
plot, SLOT(setXY(const arma::mat &)));
+ QObject::connect(subsamplePlot, SIGNAL(xyChanged(const arma::mat &)),
+ history, SLOT(addHistoryItem(const arma::mat &)));
SelectionHandler selectionHandler(sampleIndices);
- QObject::connect(subsamplePlot, SIGNAL(selectionChanged(const arma::uvec &)),
- &selectionHandler, SLOT(setSelection(const arma::uvec &)));
- QObject::connect(&selectionHandler, SIGNAL(selectionChanged(const arma::uvec &)),
- plot, SLOT(setSelection(const arma::uvec &)));
+ QObject::connect(subsamplePlot, SIGNAL(selectionChanged(const QSet<int> &)),
+ &selectionHandler, SLOT(setSelection(const QSet<int> &)));
+ QObject::connect(&selectionHandler, SIGNAL(selectionChanged(const QSet<int> &)),
+ plot, SLOT(setSelection(const QSet<int> &)));
+ /*
DistortionObserver distortionObs(X, sampleIndices);
std::unique_ptr<DistortionMeasure> distortionMeasure(new NPDistortion());
distortionObs.setMeasure(distortionMeasure.get());
@@ -94,11 +97,19 @@ int main(int argc, char **argv)
&enforcer, SLOT(setMeasureDifference(const arma::vec &)));
QObject::connect(&enforcer, SIGNAL(effectivenessChanged(const arma::vec &)),
subsamplePlot, SLOT(setColorData(const arma::vec &)));
+ */
+ /*
ContinuousColorScale ccolorScale = ContinuousColorScale::builtin(ContinuousColorScale::RED_GRAY_BLUE);
ccolorScale.setExtents(-1, 1);
plot->setColorScale(&ccolorScale);
- interactionHandler.setSubsample(Ys);
+ */
+ plot->setColorScale(&colorScale);
+ plot->setColorData(labels);
+
+ //interactionHandler.setSubsample(Ys);
+ subsamplePlot->setXY(Ys);
+ subsamplePlot->setColorData(labels(sampleIndices));
return app.exec();
}
diff --git a/main_view.qml b/main_view.qml
index 4c349f9..49448cb 100644
--- a/main_view.qml
+++ b/main_view.qml
@@ -77,9 +77,11 @@ ApplicationWindow {
}
}
- ScrollView {
+ Rectangle {
Layout.fillWidth: true
Layout.minimumHeight: 150
+ border.width: 1
+ border.color: "#cccccc"
HistoryGraph {
id: history
diff --git a/pm.pro b/pm.pro
index 6d67c88..92b13e7 100644
--- a/pm.pro
+++ b/pm.pro
@@ -4,6 +4,8 @@ QMAKE_CXXFLAGS += -std=c++11 -fopenmp
QMAKE_LIBS += -larmadillo -fopenmp
HEADERS += colorscale.h \
continuouscolorscale.h \
+ geometry.h \
+ scale.h \
scatterplot.h \
historygraph.h \
interactionhandler.h \
@@ -16,6 +18,7 @@ HEADERS += colorscale.h \
SOURCES += main.cpp \
colorscale.cpp \
continuouscolorscale.cpp \
+ geometry.cpp \
scatterplot.cpp \
historygraph.cpp \
interactionhandler.cpp \
diff --git a/scale.h b/scale.h
new file mode 100644
index 0000000..bfe18ae
--- /dev/null
+++ b/scale.h
@@ -0,0 +1,81 @@
+#ifndef SCALE_H
+#define SCALE_H
+
+#include <algorithm>
+
+class Scale
+{
+public:
+ Scale(float domainMin, float domainMax, float rangeMin, float rangeMax)
+ : m_domainMin(domainMin)
+ , m_domainMax(domainMax)
+ , m_rangeMin(rangeMin)
+ , m_rangeMax(rangeMax)
+ {
+ valuesUpdated();
+ }
+
+ virtual ~Scale() {}
+
+ void setRangeMin(float rangeMin) { m_rangeMin = rangeMin; valuesUpdated(); }
+ void setRangeMax(float rangeMax) { m_rangeMax = rangeMax; valuesUpdated(); }
+ void setDomainMin(float domainMin) { m_domainMin = domainMin; valuesUpdated(); }
+ void setDomainMax(float domainMax) { m_domainMax = domainMax; valuesUpdated(); }
+
+ void setRange(float rangeMin, float rangeMax)
+ {
+ m_rangeMin = rangeMin;
+ m_rangeMax = rangeMax;
+ valuesUpdated();
+ }
+
+ void setDomain(float domainMin, float domainMax)
+ {
+ m_domainMin = domainMin;
+ m_domainMax = domainMax;
+ valuesUpdated();
+ }
+
+ void reverse()
+ {
+ std::swap(m_rangeMin, m_domainMin);
+ std::swap(m_rangeMax, m_domainMax);
+ valuesUpdated();
+ }
+
+ virtual float operator()(float value) const = 0;
+
+protected:
+ // Called when internal values change
+ virtual void valuesUpdated() {}
+
+ float m_domainMin, m_domainMax;
+ float m_rangeMin, m_rangeMax;
+};
+
+class LinearScale
+ : public Scale
+{
+public:
+ LinearScale(float domainMin, float domainMax, float rangeMin, float rangeMax)
+ : Scale(domainMin, domainMax, rangeMin, rangeMax)
+ {
+ valuesUpdated();
+ }
+
+ virtual float operator()(float value) const
+ {
+ return (value - m_domainMin) * m_transformSlope + m_rangeMin;
+ }
+
+protected:
+ virtual void valuesUpdated()
+ {
+ m_transformSlope = (m_rangeMax - m_rangeMin) / (m_domainMax - m_domainMin);
+ }
+
+private:
+ float m_transformSlope;
+};
+
+#endif // SCALE_H
diff --git a/scatterplot.cpp b/scatterplot.cpp
index c5348f2..81a55b9 100644
--- a/scatterplot.cpp
+++ b/scatterplot.cpp
@@ -1,5 +1,6 @@
#include "scatterplot.h"
+#include "geometry.h"
#include <cmath>
static const qreal GLYPH_OPACITY = 0.4;
@@ -8,12 +9,13 @@ static const qreal GLYPH_OPACITY_SELECTED = 1.0;
static const QColor OUTLINE_COLOR(0, 0, 0);
static const QColor SELECTION_COLOR(128, 128, 128, 96);
-static const int GLYPH_SIZE = 8;
-static const float PADDING = 10;
-static const float PI = 3.1415f;
+static const int GLYPH_SIZE = 8.f;
+static const float PADDING = 10.f;
Scatterplot::Scatterplot(QQuickItem *parent)
: QQuickItem(parent)
+ , m_sx(0, 1, 0, 1)
+ , m_sy(0, 1, 0, 1)
, m_currentInteractionState(INTERACTION_NONE)
, m_shouldUpdateGeometry(false)
, m_shouldUpdateMaterials(false)
@@ -59,10 +61,8 @@ void Scatterplot::setXY(const arma::mat &xy)
}
m_xy = xy;
- m_xmin = xy.col(0).min();
- m_xmax = xy.col(0).max();
- m_ymin = xy.col(1).min();
- m_ymax = xy.col(1).max();
+ m_sx.setDomain(m_xy.col(0).min(), m_xy.col(0).max());
+ m_sy.setDomain(m_xy.col(1).min(), m_xy.col(1).max());
updateGeometry();
@@ -93,51 +93,19 @@ void Scatterplot::updateMaterials()
update();
}
-static int calculateCircleVertexCount(qreal radius)
-{
- // 10 * sqrt(r) \approx 2*pi / acos(1 - 1 / (4*r))
- return (int) (10.0 * sqrt(radius));
-}
-
-void updateCircleGeometry(QSGGeometry *geometry, float size, float cx, float cy)
+QSGNode *Scatterplot::createGlyphNodeTree()
{
- int vertexCount = geometry->vertexCount();
-
- float theta = 2 * PI / float(vertexCount);
- float c = cosf(theta);
- float s = sinf(theta);
- float x = size / 2;
- float y = 0;
-
- QSGGeometry::Point2D *vertexData = geometry->vertexDataAsPoint2D();
- for (int i = 0; i < vertexCount; i++) {
- vertexData[i].set(x + cx, y + cy);
-
- float t = x;
- x = c*x - s*y;
- y = s*t + c*y;
+ if (m_xy.n_rows < 1) {
+ return 0;
}
-}
-inline float Scatterplot::fromDataXToScreenX(float x) const
-{
- return PADDING + (x - m_xmin) / (m_xmax - m_xmin) * (width() - 2*PADDING);
-}
-
-inline float Scatterplot::fromDataYToScreenY(float y) const
-{
- return PADDING + (1 - (y - m_ymin) / (m_ymax - m_ymin)) * (height() - 2*PADDING);
-}
-
-QSGNode *Scatterplot::createGlyphNodeTree()
-{
QSGNode *node = new QSGNode;
int vertexCount = calculateCircleVertexCount(GLYPH_SIZE / 2);
for (arma::uword i = 0; i < m_xy.n_rows; i++) {
QSGGeometry *glyphOutlineGeometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), vertexCount);
glyphOutlineGeometry->setDrawingMode(GL_LINE_LOOP);
- updateCircleGeometry(glyphOutlineGeometry, GLYPH_SIZE, 0, 0);
+ updateCircleGeometry(glyphOutlineGeometry, GLYPH_SIZE / 2, 0, 0);
QSGGeometryNode *glyphOutlineNode = new QSGGeometryNode;
glyphOutlineNode->setGeometry(glyphOutlineGeometry);
glyphOutlineNode->setFlag(QSGNode::OwnsGeometry);
@@ -149,7 +117,7 @@ QSGNode *Scatterplot::createGlyphNodeTree()
QSGGeometry *glyphGeometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), vertexCount);
glyphGeometry->setDrawingMode(GL_POLYGON);
- updateCircleGeometry(glyphGeometry, GLYPH_SIZE - 1, 0, 0);
+ updateCircleGeometry(glyphGeometry, GLYPH_SIZE / 2 - 0.5, 0, 0);
QSGGeometryNode *glyphNode = new QSGGeometryNode;
glyphNode->setGeometry(glyphGeometry);
glyphNode->setFlag(QSGNode::OwnsGeometry);
@@ -171,20 +139,23 @@ QSGNode *Scatterplot::createGlyphNodeTree()
QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
- if (m_xy.n_rows < 1) {
- return 0;
- }
-
- qreal x, y, tx, ty, moveTranslationF;
-
QSGNode *root = 0;
if (!oldNode) {
root = new QSGNode;
- root->appendChildNode(createGlyphNodeTree());
+ QSGNode *glyphTreeRoot = createGlyphNodeTree();
+ if (glyphTreeRoot) {
+ root->appendChildNode(glyphTreeRoot);
+ }
} else {
root = oldNode;
}
+ if (m_xy.n_rows < 1) {
+ return root;
+ }
+
+ qreal x, y, tx, ty, moveTranslationF;
+
if (m_currentInteractionState == INTERACTION_MOVING) {
tx = m_dragCurrentPos.x() - m_dragOriginPos.x();
ty = m_dragCurrentPos.y() - m_dragOriginPos.y();
@@ -192,6 +163,9 @@ QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
tx = ty = 0;
}
+ m_sx.setRange(PADDING, width() - 2*PADDING);
+ m_sy.setRange(height() - 2*PADDING, PADDING);
+
QSGNode *node = root->firstChild()->firstChild();
for (arma::uword i = 0; i < m_xy.n_rows; i++) {
arma::rowvec row = m_xy.row(i);
@@ -204,15 +178,15 @@ QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
QSGGeometryNode *glyphNode = static_cast<QSGGeometryNode *>(node->firstChild()->nextSibling());
if (m_shouldUpdateGeometry) {
moveTranslationF = isSelected ? 1.0 : 0.0;
- x = fromDataXToScreenX(row[0]) + tx * moveTranslationF;
- y = fromDataYToScreenY(row[1]) + ty * moveTranslationF;
+ x = m_sx(row[0]) + tx * moveTranslationF;
+ y = m_sy(row[1]) + ty * moveTranslationF;
QSGGeometry *geometry = glyphOutlineNode->geometry();
- updateCircleGeometry(geometry, GLYPH_SIZE, x, y);
+ updateCircleGeometry(geometry, GLYPH_SIZE / 2, x, y);
glyphOutlineNode->markDirty(QSGNode::DirtyGeometry);
geometry = glyphNode->geometry();
- updateCircleGeometry(geometry, GLYPH_SIZE - 1, x, y);
+ updateCircleGeometry(geometry, GLYPH_SIZE / 2 - 0.5, x, y);
glyphNode->markDirty(QSGNode::DirtyGeometry);
}
if (m_shouldUpdateMaterials) {
@@ -263,7 +237,7 @@ void Scatterplot::mousePressEvent(QMouseEvent *event)
case INTERACTION_NONE:
case INTERACTION_SELECTED:
if (event->modifiers() == Qt::AltModifier) {
- m_currentInteractionState = INTERACTION_MOVING;
+ m_currentInteractionState = INTERACTION_BEGIN_MOVING;
} else {
m_currentInteractionState = INTERACTION_SELECTING;
}
@@ -271,6 +245,7 @@ void Scatterplot::mousePressEvent(QMouseEvent *event)
m_dragCurrentPos = m_dragOriginPos;
break;
case INTERACTION_SELECTING:
+ case INTERACTION_BEGIN_MOVING:
case INTERACTION_MOVING:
event->ignore();
return;
@@ -287,11 +262,11 @@ void Scatterplot::mouseMoveEvent(QMouseEvent *event)
m_dragCurrentPos = event->localPos();
update();
break;
+ case INTERACTION_BEGIN_MOVING:
+ m_currentInteractionState = INTERACTION_MOVING;
case INTERACTION_MOVING:
m_dragCurrentPos = event->localPos();
- applyManipulation();
updateGeometry();
- m_dragOriginPos = m_dragCurrentPos;
break;
case INTERACTION_SELECTED:
event->ignore();
@@ -310,11 +285,14 @@ void Scatterplot::mouseReleaseEvent(QMouseEvent *event)
: INTERACTION_NONE;
}
break;
-
+ case INTERACTION_BEGIN_MOVING:
+ m_currentInteractionState = INTERACTION_SELECTED;
+ break;
case INTERACTION_MOVING:
m_currentInteractionState = INTERACTION_SELECTED;
+ applyManipulation();
updateGeometry();
- emit xyChanged(m_xy);
+ m_dragOriginPos = m_dragCurrentPos;
break;
case INTERACTION_NONE:
case INTERACTION_SELECTED:
@@ -329,15 +307,21 @@ bool Scatterplot::updateSelection(bool mergeSelection)
selection.unite(m_selectedGlyphs);
}
- qreal originX = m_dragOriginPos.x() / width() * (m_xmax - m_xmin) + m_xmin;
- qreal originY = (1 - m_dragOriginPos.y() / height()) * (m_ymax - m_ymin) + m_ymin;
- qreal currentX = m_dragCurrentPos.x() / width() * (m_xmax - m_xmin) + m_xmin;
- qreal currentY = (1 - m_dragCurrentPos.y() / height()) * (m_ymax - m_ymin) + m_ymin;
+ m_sx.reverse();
+ m_sy.reverse();
+
+ float originX = m_sx(m_dragOriginPos.x());
+ float originY = m_sy(m_dragOriginPos.y());
+ float currentX = m_sx(m_dragCurrentPos.x());
+ float currentY = m_sy(m_dragCurrentPos.y());
+
+ m_sy.reverse();
+ m_sx.reverse();
QRectF selectionRect(QPointF(originX, originY), QPointF(currentX, currentY));
for (arma::uword i = 0; i < m_xy.n_rows; i++) {
- arma::rowvec row = m_xy.row(i);
+ const arma::rowvec &row = m_xy.row(i);
if (selectionRect.contains(row[0], row[1])) {
selection.insert(i);
@@ -358,19 +342,22 @@ void Scatterplot::setSelection(const QSet<int> &selection)
void Scatterplot::applyManipulation()
{
+ m_sx.reverse();
+ m_sy.reverse();
+ LinearScale rx = m_sx;
+ LinearScale ry = m_sy;
+ m_sy.reverse();
+ m_sx.reverse();
+
float tx = m_dragCurrentPos.x() - m_dragOriginPos.x();
float ty = m_dragCurrentPos.y() - m_dragOriginPos.y();
- tx /= (width() - PADDING);
- ty /= -(height() - PADDING);
-
- float x_extent = m_xmax - m_xmin;
- float y_extent = m_ymax - m_ymin;
-
for (auto it = m_selectedGlyphs.cbegin(); it != m_selectedGlyphs.cend(); it++) {
arma::rowvec row = m_xy.row(*it);
- row[0] = ((row[0] - m_xmin) / x_extent + tx) * x_extent + m_xmin;
- row[1] = ((row[1] - m_ymin) / y_extent + ty) * y_extent + m_ymin;
+ row[0] = rx(m_sx(row[0]) + tx);
+ row[1] = ry(m_sy(row[1]) + ty);
m_xy.row(*it) = row;
}
+
+ emit xyChanged(m_xy);
}
diff --git a/scatterplot.h b/scatterplot.h
index 3d7fa36..43bfc57 100644
--- a/scatterplot.h
+++ b/scatterplot.h
@@ -6,6 +6,7 @@
#include <QtQuick>
#include "colorscale.h"
+#include "scale.h"
class Scatterplot : public QQuickItem
{
@@ -37,21 +38,19 @@ private:
QSGNode *createGlyphNodeTree();
bool updateSelection(bool mergeSelection);
- float fromDataXToScreenX(float x) const;
- float fromDataYToScreenY(float y) const;
-
void applyManipulation();
void updateGeometry();
void updateMaterials();
arma::mat m_xy;
- float m_xmin, m_xmax, m_ymin, m_ymax;
+ LinearScale m_sx, m_sy;
enum InteractionState {
INTERACTION_NONE,
INTERACTION_SELECTING,
INTERACTION_SELECTED,
+ INTERACTION_BEGIN_MOVING,
INTERACTION_MOVING
} m_currentInteractionState;