From 0518b806d8ce25c69847ec8c403276193611b2e1 Mon Sep 17 00:00:00 2001 From: Samuel Fadel Date: Thu, 17 Sep 2015 12:20:24 -0300 Subject: Additional interactive functionalities. - Selection linking between subsample plot and main plot - Dumb "effectiveness" coloring --- effectivenessobserver.cpp | 42 ++++++++++++++++++++++++++++ effectivenessobserver.h | 26 +++++++++++++++++ main.cpp | 46 ++++++++++++++++++++---------- pm.pro | 4 +++ scatterplot.cpp | 71 +++++++++++++++++++++++++++++++++-------------- scatterplot.h | 4 ++- selectionhandler.cpp | 19 +++++++++++++ selectionhandler.h | 23 +++++++++++++++ 8 files changed, 199 insertions(+), 36 deletions(-) create mode 100644 effectivenessobserver.cpp create mode 100644 effectivenessobserver.h create mode 100644 selectionhandler.cpp create mode 100644 selectionhandler.h diff --git a/effectivenessobserver.cpp b/effectivenessobserver.cpp new file mode 100644 index 0000000..305b3f8 --- /dev/null +++ b/effectivenessobserver.cpp @@ -0,0 +1,42 @@ +#include "effectivenessobserver.h" + +EffectiveInteractionEnforcer::EffectiveInteractionEnforcer(const arma::uvec &sampleIndices) + : m_sampleIndices(sampleIndices) + , m_effectiveness(arma::zeros(sampleIndices.n_elem)) +{ +} + +void EffectiveInteractionEnforcer::setSelection(const arma::uvec &selection) +{ + m_selection = selection; +} + +void EffectiveInteractionEnforcer::setMeasureDifference(const arma::vec &measure) +{ + m_measure = measure; + + if (m_selection.n_elem == 0) { + return; + } + + arma::uvec selectionIndices(m_selection); + for (auto it = selectionIndices.begin(); it != selectionIndices.end(); it++) { + *it = m_sampleIndices[*it]; + } + + double diff = arma::mean(m_measure(selectionIndices)); + int effectiveness; + if (diff > 0) { + effectiveness = 1; + } else if (diff < 0) { + effectiveness = -1; + } else { + effectiveness = 0; + } + + for (auto it = m_selection.cbegin(); it != m_selection.cend(); it++) { + m_effectiveness[*it] = effectiveness; + } + + emit effectivenessChanged(m_effectiveness); +} diff --git a/effectivenessobserver.h b/effectivenessobserver.h new file mode 100644 index 0000000..8331916 --- /dev/null +++ b/effectivenessobserver.h @@ -0,0 +1,26 @@ +#ifndef EFFECTIVEINTERACTIONENFORCER_H +#define EFFECTIVEINTERACTIONENFORCER_H + +#include +#include + +class EffectiveInteractionEnforcer : public QObject +{ + Q_OBJECT +public: + EffectiveInteractionEnforcer(const arma::uvec &sampleIndices); + +signals: + void effectivenessChanged(const arma::vec &effectiveness); + +public slots: + void setSelection(const arma::uvec &selection); + void setMeasureDifference(const arma::vec &measure); + +private: + arma::mat m_effectiveness; + arma::uvec m_sampleIndices, m_selection; + arma::vec m_measure; +}; + +#endif // EFFECTIVEINTERACTIONENFORCER_H diff --git a/main.cpp b/main.cpp index 8bd0318..58578ac 100644 --- a/main.cpp +++ b/main.cpp @@ -9,6 +9,8 @@ #include "continuouscolorscale.h" #include "scatterplot.h" #include "interactionhandler.h" +#include "selectionhandler.h" +#include "effectivenessobserver.h" #include "distortionobserver.h" #include "npdistortion.h" @@ -40,6 +42,7 @@ 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"), @@ -52,10 +55,14 @@ 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); Scatterplot *subsamplePlot = engine.rootObjects()[0]->findChild("subsamplePlot"); subsamplePlot->setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton | Qt::RightButton); subsamplePlot->setXY(Ys); - subsamplePlot->setColorData(labels(sampleIndices)); + subsamplePlot->setColorData(arma::zeros(subsampleSize)); subsamplePlot->setColorScale(&colorScale); Scatterplot *plot = engine.rootObjects()[0]->findChild("plot"); @@ -66,21 +73,32 @@ int main(int argc, char **argv) QObject::connect(&interactionHandler, SIGNAL(subsampleChanged(const arma::mat &)), plot, SLOT(setXY(const arma::mat &))); - //DistortionObserver distortionObs(X, sampleIndices); - //std::unique_ptr distortionMeasure(new NPDistortion()); - //distortionObs.setMeasure(distortionMeasure.get()); - //QObject::connect(&interactionHandler, SIGNAL(subsampleChanged(const arma::mat &)), - // &distortionObs, SLOT(setMap(const arma::mat &))); - //QObject::connect(&distortionObs, SIGNAL(mapChanged(const arma::vec &)), - // plot, SLOT(setColorData(const arma::vec &))); + 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 &))); - //ContinuousColorScale ccolorScale = ContinuousColorScale::builtin(ContinuousColorScale::HEATED_OBJECTS); - //ccolorScale.setExtents(-1, 1); - plot->setColorScale(&colorScale); - interactionHandler.setSubsample(Ys); + DistortionObserver distortionObs(X, sampleIndices); + std::unique_ptr distortionMeasure(new NPDistortion()); + distortionObs.setMeasure(distortionMeasure.get()); + QObject::connect(&interactionHandler, SIGNAL(subsampleChanged(const arma::mat &)), + &distortionObs, SLOT(setMap(const arma::mat &))); + QObject::connect(&distortionObs, SIGNAL(mapChanged(const arma::vec &)), + plot, SLOT(setColorData(const arma::vec &))); - // TODO: remove when proper measure coloring is done - plot->setColorData(labels); + EffectiveInteractionEnforcer enforcer(sampleIndices); + QObject::connect(subsamplePlot, SIGNAL(selectionChanged(const arma::uvec &)), + &enforcer, SLOT(setSelection(const arma::uvec &))); + QObject::connect(plot, SIGNAL(colorDataChanged(const arma::vec &)), + &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); return app.exec(); } diff --git a/pm.pro b/pm.pro index de669cd..48c4d35 100644 --- a/pm.pro +++ b/pm.pro @@ -6,6 +6,8 @@ HEADERS += colorscale.h \ continuouscolorscale.h \ scatterplot.h \ interactionhandler.h \ + selectionhandler.h \ + effectivenessobserver.h \ distortionobserver.h \ distortionmeasure.h \ npdistortion.h \ @@ -15,6 +17,8 @@ SOURCES += main.cpp \ continuouscolorscale.cpp \ scatterplot.cpp \ interactionhandler.cpp \ + selectionhandler.cpp \ + effectivenessobserver.cpp \ distortionobserver.cpp \ npdistortion.cpp \ lamp.cpp \ diff --git a/scatterplot.cpp b/scatterplot.cpp index 36f6a38..9bbe09e 100644 --- a/scatterplot.cpp +++ b/scatterplot.cpp @@ -41,15 +41,19 @@ void Scatterplot::setXY(const arma::mat &xy) return; } + if (m_xy.n_elem != xy.n_elem) { + m_selectedGlyphs.clear(); + } + 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_selectedGlyphs.clear(); - emit xyChanged(m_xy); updateGeometry(); + + emit xyChanged(m_xy); } void Scatterplot::setColorData(const arma::vec &colorData) @@ -120,12 +124,12 @@ QSGNode *Scatterplot::createGlyphNodeTree() m_glyphGeometryPtr.reset(new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), vertexCount)); QSGGeometry *glyphGeometry = m_glyphGeometryPtr.get(); glyphGeometry->setDrawingMode(GL_POLYGON); - updateCircleGeometry(glyphGeometry, GLYPH_SIZE, 0, 0); + updateCircleGeometry(glyphGeometry, GLYPH_SIZE - 1, 0, 0); m_glyphOutlineGeometryPtr.reset(new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), vertexCount)); QSGGeometry *glyphOutlineGeometry = m_glyphOutlineGeometryPtr.get(); glyphOutlineGeometry->setDrawingMode(GL_LINE_LOOP); - updateCircleGeometry(glyphOutlineGeometry, GLYPH_SIZE + 1, 0, 0); + updateCircleGeometry(glyphOutlineGeometry, GLYPH_SIZE, 0, 0); for (arma::uword i = 0; i < m_xy.n_rows; i++) { QSGGeometryNode *glyphOutlineNode = new QSGGeometryNode; @@ -214,7 +218,7 @@ QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) m_shouldUpdateMaterials = false; } - // Draw selection + // Draw selection rect if (m_currentInteractionState == INTERACTION_SELECTING) { QSGSimpleRectNode *selectionNode = 0; if (!root->firstChild()->nextSibling()) { @@ -230,8 +234,9 @@ QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) } else { node = root->firstChild()->nextSibling(); if (node) { - node->markDirty(QSGNode::DirtyGeometry); + // node->markDirty(QSGNode::DirtyGeometry); root->removeChildNode(node); + delete node; } } @@ -261,6 +266,9 @@ void Scatterplot::mousePressEvent(QMouseEvent *event) void Scatterplot::mouseMoveEvent(QMouseEvent *event) { switch (m_currentInteractionState) { + case INTERACTION_NONE: + // event->localPos() + break; case INTERACTION_SELECTING: m_dragCurrentPos = event->localPos(); update(); @@ -271,7 +279,6 @@ void Scatterplot::mouseMoveEvent(QMouseEvent *event) updateGeometry(); m_dragOriginPos = m_dragCurrentPos; break; - case INTERACTION_NONE: case INTERACTION_SELECTED: event->ignore(); return; @@ -281,16 +288,18 @@ void Scatterplot::mouseMoveEvent(QMouseEvent *event) void Scatterplot::mouseReleaseEvent(QMouseEvent *event) { bool mergeSelection; + arma::uvec selection; switch (m_currentInteractionState) { case INTERACTION_SELECTING: mergeSelection = (event->modifiers() == Qt::ControlModifier); - if (selectGlyphs(mergeSelection)) { + selection = findSelection(mergeSelection); + if (selection.n_elem > 0) { + setSelection(selection); m_currentInteractionState = INTERACTION_SELECTED; } else { m_currentInteractionState = INTERACTION_NONE; } - update(); break; case INTERACTION_MOVING: @@ -304,27 +313,47 @@ void Scatterplot::mouseReleaseEvent(QMouseEvent *event) } } -bool Scatterplot::selectGlyphs(bool mergeSelection) +arma::uvec Scatterplot::findSelection(bool mergeSelection) { - if (!mergeSelection) - m_selectedGlyphs.clear(); + QSet selectedGlyphs(m_selectedGlyphs); + if (!mergeSelection) { + selectedGlyphs.clear(); + } - qreal x, y; + QPointF dragOrigin(m_dragOriginPos.x() / width() * (m_xmax - m_xmin) + m_xmin, + m_dragOriginPos.y() / height() * (m_ymax - m_ymin) + m_ymin); + QPointF dragCurrent(m_dragCurrentPos.x() / width() * (m_xmax - m_xmin) + m_xmin, + m_dragCurrentPos.y() / height() * (m_ymax - m_ymin) + m_ymin); + QRectF selectionRect(dragOrigin, dragCurrent); - QRectF selectionRect(m_dragOriginPos, m_dragCurrentPos); - bool anySelected = false; for (arma::uword i = 0; i < m_xy.n_rows; i++) { arma::rowvec row = m_xy.row(i); - x = fromDataXToScreenX(row[0]); - y = fromDataYToScreenY(row[1]); - if (selectionRect.contains(x, y)) { - m_selectedGlyphs.insert(i); - anySelected = true; + if (selectionRect.contains(row[0], row[1])) { + selectedGlyphs.insert(i); } } - return anySelected; + arma::uvec selection(selectedGlyphs.size()); + int i = 0; + for (auto it = selectedGlyphs.cbegin(); it != selectedGlyphs.cend(); it++, i++) { + selection[i] = *it; + } + + return selection; +} + +void Scatterplot::setSelection(const arma::uvec &selection) +{ + m_selectedGlyphs.clear(); + + for (auto it = selection.cbegin(); it != selection.cend(); it++) { + m_selectedGlyphs.insert(*it); + } + + update(); + + emit selectionChanged(selection); } void Scatterplot::applyManipulation() diff --git a/scatterplot.h b/scatterplot.h index 4275561..2d91a63 100644 --- a/scatterplot.h +++ b/scatterplot.h @@ -18,10 +18,12 @@ public: signals: void xyChanged(const arma::mat &XY); void colorDataChanged(const arma::vec &colorData); + void selectionChanged(const arma::uvec &selection); public slots: void setXY(const arma::mat &xy); void setColorData(const arma::vec &colorData); + void setSelection(const arma::uvec &selection); protected: QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *); @@ -31,7 +33,7 @@ protected: private: QSGNode *createGlyphNodeTree(); - bool selectGlyphs(bool mergeSelection); + arma::uvec findSelection(bool mergeSelection); float fromDataXToScreenX(float x); float fromDataYToScreenY(float y); diff --git a/selectionhandler.cpp b/selectionhandler.cpp new file mode 100644 index 0000000..4c488a0 --- /dev/null +++ b/selectionhandler.cpp @@ -0,0 +1,19 @@ +#include "selectionhandler.h" + +SelectionHandler::SelectionHandler(const arma::uvec &sampleIndices) + : m_sampleIndices(sampleIndices) +{ +} + +void SelectionHandler::setSelection(const arma::uvec &selection) +{ + arma::uvec newSelection(selection); + + // The selecion happens over the sample indices. We use the original dataset + // indices in sampleIndices to translate indices. + for (auto it = newSelection.begin(); it != newSelection.end(); it++) { + *it = m_sampleIndices[*it]; + } + + emit selectionChanged(newSelection); +} diff --git a/selectionhandler.h b/selectionhandler.h new file mode 100644 index 0000000..4c4a2ca --- /dev/null +++ b/selectionhandler.h @@ -0,0 +1,23 @@ +#ifndef SELECTIONHANDLER_H +#define SELECTIONHANDLER_H + +#include +#include + +class SelectionHandler : public QObject +{ + Q_OBJECT +public: + SelectionHandler(const arma::uvec &sampleIndices); + +signals: + void selectionChanged(const arma::uvec &selection); + +public slots: + void setSelection(const arma::uvec &selection); + +private: + arma::uvec m_sampleIndices; +}; + +#endif // SELECTIONHANDLER_H -- cgit v1.2.3