From d414b68cea008edc063e26133564d120a7430bd2 Mon Sep 17 00:00:00 2001 From: Samuel Fadel Date: Thu, 21 Jan 2016 19:36:14 +0100 Subject: Added selection linking. Needs more tests. --- barchart.cpp | 68 +++++++++++++++++++++++++++++++------------ barchart.h | 11 ++++--- main.cpp | 40 +++++++++++++------------ pm.pro | 2 ++ projectionobserver.cpp | 22 ++++++++++++-- projectionobserver.h | 9 +++--- scatterplot.cpp | 79 +++++++++++++++++++++++++------------------------- scatterplot.h | 12 ++++---- selectionhandler.cpp | 32 ++++++++++++++------ selectionhandler.h | 18 +++++++----- 10 files changed, 187 insertions(+), 106 deletions(-) diff --git a/barchart.cpp b/barchart.cpp index eb75dc8..8bc3ee9 100644 --- a/barchart.cpp +++ b/barchart.cpp @@ -48,11 +48,18 @@ void BarChart::setValues(const arma::vec &values) m_values = values; m_originalIndices.resize(m_values.n_elem); + + if (m_selection.size() != m_values.n_elem) { + m_selection.resize(m_values.n_elem); + m_selection.assign(m_selection.size(), false); + } + if (m_values.n_elem > 0) { m_scale.setDomain(m_values.min(), m_values.max()); m_colorScale.setExtents(m_values.min(), m_values.max()); - for (int i = 0; i < m_originalIndices.size(); i++) { + for (std::vector::size_type i = 0; + i < m_originalIndices.size(); i++) { m_originalIndices[i] = i; } @@ -77,6 +84,19 @@ void BarChart::setColorScale(const ColorScale &scale) update(); } +void BarChart::setSelection(const std::vector &selection) +{ + if (m_selection.size() != selection.size()) { + return; + } + + m_selection = selection; + emit selectionChanged(m_selection); + + m_shouldUpdateSelectionRect = true; + update(); +} + QSGNode *BarChart::newSceneGraph() const { // NOTE: scene graph structure is as follows: @@ -256,29 +276,37 @@ QSGNode *BarChart::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) return root; } -void BarChart::selectBarsInRange(float start, float end) +int BarChart::itemAt(float x, bool includeSelectorWidth) const +{ + int numValues = m_values.n_elem; + float barWidth = 1.0f / numValues; + if (includeSelectorWidth) { + x += 1.0f / width(); + } + + return clamp(int(x / barWidth), 0, numValues - 1); +} + +void BarChart::interactiveSelection(float start, float end) { if (start > end) { std::swap(start, end); } - m_selection.clear(); - if (start > 0 && end > 0) { - // Bars are located in ranges: - // [0..barWidth] + barIndex / barWidth - int numValues = m_values.n_elem; - float barWidth = 1.0f / numValues; - float selectorWidth = 1.0f / width(); - int firstIndex = int(start / barWidth); - int lastIndex = std::min(numValues - 1, int((end + selectorWidth) / barWidth)); - - for (int i = firstIndex; i <= lastIndex; i++) { - m_selection.insert(m_originalIndices[i]); - } + m_selection.assign(m_selection.size(), false); + if (start < 0.0f || end < 0.0f) { + return; } - emit selectionChanged(m_selection); - update(); + // Bars are located in ranges: + // [0..barWidth] + barIndex / barWidth + int firstIndex = itemAt(start); + int lastIndex = itemAt(end, true); + for (int i = firstIndex; i <= lastIndex; i++) { + m_selection[m_originalIndices[i]] = true; + } + + emit selectionInteractivelyChanged(m_selection); } void BarChart::hoverEnterEvent(QHoverEvent *event) @@ -328,7 +356,11 @@ void BarChart::mouseReleaseEvent(QMouseEvent *event) float pos = float(event->pos().x()) / width(); m_dragLastPos = clamp(pos, 0.0f, 1.0f); m_hoverPos = pos; - selectBarsInRange(m_dragStartPos, m_dragLastPos); + + if (m_values.n_elem > 0) { + interactiveSelection(m_dragStartPos, m_dragLastPos); + } + m_shouldUpdateSelectionRect = true; update(); } diff --git a/barchart.h b/barchart.h index d785aa9..d49c001 100644 --- a/barchart.h +++ b/barchart.h @@ -4,7 +4,6 @@ #include #include -#include #include @@ -22,11 +21,13 @@ public: signals: void valuesChanged(const arma::vec &values) const; void colorScaleChanged(const ColorScale &scale) const; - void selectionChanged(const QSet &selection) const; + void selectionChanged(const std::vector &selection) const; + void selectionInteractivelyChanged(const std::vector &selection) const; public slots: void setValues(const arma::vec &values); void setColorScale(const ColorScale &scale); + void setSelection(const std::vector &selection); protected: QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *); @@ -53,9 +54,11 @@ private: void updateSelectionRect(QSGNode *node); bool m_shouldUpdateSelectionRect; - void selectBarsInRange(float start, float end); + void interactiveSelection(float start, float end); float m_dragStartPos, m_dragLastPos; - QSet m_selection; + std::vector m_selection; + + int itemAt(float x, bool includeSelectorWidth = false) const; arma::vec m_values; ColorScale m_colorScale; diff --git a/main.cpp b/main.cpp index b9daab9..6af65b3 100644 --- a/main.cpp +++ b/main.cpp @@ -17,6 +17,7 @@ #include "barchart.h" #include "colormap.h" #include "interactionhandler.h" +#include "selectionhandler.h" #include "projectionobserver.h" #include "skelft.h" @@ -153,13 +154,6 @@ int main(int argc, char **argv) QObject::connect(&interactionHandler, SIGNAL(cpChanged(const arma::mat &)), m->splat, SLOT(setSites(const arma::mat &))); - // Linking between selections in cp plot and rp plot - //SelectionHandler selectionHandler(cpIndices); - //QObject::connect(cpPlot, SIGNAL(selectionChanged(const QSet &)), - // &selectionHandler, SLOT(setSelection(const QSet &))); - //QObject::connect(&selectionHandler, SIGNAL(selectionChanged(const QSet &)), - // plot, SLOT(setSelection(const QSet &))); - // Connections between history graph and cp plot //HistoryGraph *history = engine.rootObjects()[0]->findChild("history"); //QObject::connect(cpPlot, SIGNAL(xyInteractivelyChanged(const arma::mat &)), @@ -181,26 +175,36 @@ int main(int argc, char **argv) m->rpBarChart->setAcceptedMouseButtons(Qt::LeftButton); m->setRPBarChartColorScale(Main::ColorScaleContinuous); - QObject::connect(m->cpBarChart, SIGNAL(selectionChanged(const QSet &)), - m->cpPlot, SLOT(setSelection(const QSet &))); - - QObject::connect(m->rpBarChart, SIGNAL(selectionChanged(const QSet &)), - m->rpPlot, SLOT(setSelection(const QSet &))); + // Linking between selections + SelectionHandler cpSelectionHandler(cpIndices.n_elem); + QObject::connect(m->cpPlot, SIGNAL(selectionInteractivelyChanged(const std::vector &)), + &cpSelectionHandler, SLOT(setSelection(const std::vector &))); + QObject::connect(m->cpBarChart, SIGNAL(selectionInteractivelyChanged(const std::vector &)), + &cpSelectionHandler, SLOT(setSelection(const std::vector &))); + QObject::connect(&cpSelectionHandler, SIGNAL(selectionChanged(const std::vector &)), + m->cpPlot, SLOT(setSelection(const std::vector &))); + + //SelectionHandler rpSelectionHandler(X.n_rows - cpIndices.n_elem); + //QObject::connect(m->rpPlot, SIGNAL(selectionInteractivelyChanged(const std::vector &)), + // &rpSelectionHandler, SLOT(setSelection(const std::vector &))); + //QObject::connect(m->rpBarChart, SIGNAL(selectionInteractivelyChanged(const std::vector &)), + // &rpSelectionHandler, SLOT(setSelection(const std::vector &))); + //QObject::connect(&rpSelectionHandler, SIGNAL(selectionChanged(const std::vector &)), + // m->rpPlot, SLOT(setSelection(const std::vector &))); ProjectionObserver projectionObserver(X, cpIndices); m->projectionObserver = &projectionObserver; QObject::connect(&interactionHandler, SIGNAL(cpChanged(const arma::mat &)), m->projectionObserver, SLOT(setMap(const arma::mat &))); - QObject::connect(m->projectionObserver, SIGNAL(valuesChanged(const arma::vec &)), - m->rpPlot, SLOT(setColorData(const arma::vec &))); + //QObject::connect(m->projectionObserver, SIGNAL(valuesChanged(const arma::vec &)), + // m->rpPlot, SLOT(setColorData(const arma::vec &))); QObject::connect(m->projectionObserver, SIGNAL(valuesChanged(const arma::vec &)), m->splat, SLOT(setValues(const arma::vec &))); - QObject::connect(m->projectionObserver, SIGNAL(valuesChanged(const arma::vec &)), + QObject::connect(m->projectionObserver, SIGNAL(cpValuesChanged(const arma::vec &)), m->cpBarChart, SLOT(setValues(const arma::vec &))); - QObject::connect(m->projectionObserver, SIGNAL(valuesChanged(const arma::vec &)), - m->rpBarChart, SLOT(setValues(const arma::vec &))); + //QObject::connect(m->projectionObserver, SIGNAL(rpValuesChanged(const arma::vec &)), + // m->rpBarChart, SLOT(setValues(const arma::vec &))); - //history->addHistoryItem(Ys); m->setColormapColorScale(Main::ColorScaleContinuous); m->setCPPlotColorScale(Main::ColorScaleContinuous); m->setRPPlotColorScale(Main::ColorScaleContinuous); diff --git a/pm.pro b/pm.pro index 3f3332b..f864b96 100644 --- a/pm.pro +++ b/pm.pro @@ -15,6 +15,7 @@ HEADERS += main.h \ historygraph.h \ barchart.h \ interactionhandler.h \ + selectionhandler.h \ projectionobserver.h \ skelft.h \ skelftkernel.h \ @@ -29,6 +30,7 @@ SOURCES += main.cpp \ historygraph.cpp \ barchart.cpp \ interactionhandler.cpp \ + selectionhandler.cpp \ projectionobserver.cpp \ skelft_core.cpp \ lamp.cpp \ diff --git a/projectionobserver.cpp b/projectionobserver.cpp index 262332d..30f8325 100644 --- a/projectionobserver.cpp +++ b/projectionobserver.cpp @@ -1,5 +1,6 @@ #include "projectionobserver.h" +#include #include #include @@ -36,9 +37,18 @@ ProjectionObserver::ProjectionObserver(const arma::mat &X, : m_type(OBSERVER_CURRENT) , m_X(X) , m_cpIndices(cpIndices) + , m_rpIndices(X.n_rows - cpIndices.n_elem) { m_distX = mp::dist(m_X); m_values.set_size(m_X.n_rows); + + arma::uvec indices(m_X.n_rows); + for (arma::uword i = 0; i < indices.n_elem; i++) { + indices[i] = i; + } + std::set_difference(indices.cbegin(), indices.cend(), + m_cpIndices.cbegin(), m_cpIndices.cend(), + m_rpIndices.begin()); } bool ProjectionObserver::setType(int type) @@ -82,17 +92,25 @@ bool ProjectionObserver::emitValuesChanged() const { switch (m_type) { case OBSERVER_CURRENT: + emit cpValuesChanged(m_values(m_cpIndices)); + emit rpValuesChanged(m_values(m_rpIndices)); emit valuesChanged(m_values); return true; case OBSERVER_DIFF_PREVIOUS: if (m_prevValues.n_elem > 0) { - emit valuesChanged(m_values - m_prevValues); + arma::vec diff = m_values - m_prevValues; + emit cpValuesChanged(diff(m_cpIndices)); + emit rpValuesChanged(diff(m_rpIndices)); + emit valuesChanged(diff); return true; } return false; case OBSERVER_DIFF_ORIGINAL: if (m_origValues.n_elem > 0) { - emit valuesChanged(m_values - m_origValues); + arma::vec diff = m_values - m_origValues; + emit cpValuesChanged(diff(m_cpIndices)); + emit rpValuesChanged(diff(m_rpIndices)); + emit valuesChanged(diff); return true; } return false; diff --git a/projectionobserver.h b/projectionobserver.h index 1c305bd..c0ccd8a 100644 --- a/projectionobserver.h +++ b/projectionobserver.h @@ -4,9 +4,8 @@ #include #include -#include "distortionmeasure.h" - -class ProjectionObserver : public QObject +class ProjectionObserver + : public QObject { Q_OBJECT public: @@ -18,6 +17,8 @@ public: signals: void valuesChanged(const arma::vec &values) const; + void cpValuesChanged(const arma::vec &values) const; + void rpValuesChanged(const arma::vec &values) const; public slots: void setMap(const arma::mat &Y); @@ -29,7 +30,7 @@ private: int m_type; arma::mat m_X, m_Y, m_origY, m_prevY; arma::mat m_distX, m_distY, m_origDistY, m_prevDistY; - arma::uvec m_cpIndices; + arma::uvec m_cpIndices, m_rpIndices; // TODO: one per implemented measure arma::vec m_values, m_prevValues, m_origValues; diff --git a/scatterplot.cpp b/scatterplot.cpp index c5678ec..b547579 100644 --- a/scatterplot.cpp +++ b/scatterplot.cpp @@ -1,5 +1,6 @@ #include "scatterplot.h" +#include #include #include @@ -63,10 +64,6 @@ void Scatterplot::setXY(const arma::mat &xy, bool updateView) return; } - if (m_xy.n_elem != xy.n_elem) { - m_selectedGlyphs.clear(); - } - m_xy = xy; emit xyChanged(m_xy); @@ -74,6 +71,11 @@ void Scatterplot::setXY(const arma::mat &xy, bool updateView) autoScale(); } + if (m_selection.size() != m_xy.n_rows) { + m_selection.resize(m_xy.n_rows); + m_selection.assign(m_selection.size(), false); + } + if (m_opacityData.n_elem != m_xy.n_rows) { arma::vec opacityData(xy.n_rows); opacityData.fill(GLYPH_OPACITY); @@ -301,7 +303,7 @@ void Scatterplot::updateGlyphs(QSGNode *glyphsNode) QSGNode *node = glyphsNode->firstChild(); for (arma::uword i = 0; i < m_xy.n_rows; i++) { const arma::rowvec &row = m_xy.row(i); - bool isSelected = m_selectedGlyphs.contains(i); + bool isSelected = m_selection[i]; QSGOpacityNode *glyphOpacityNode = static_cast(node); glyphOpacityNode->setOpacity(m_opacityData[i]); @@ -359,9 +361,6 @@ 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(); @@ -373,6 +372,7 @@ void Scatterplot::mouseMoveEvent(QMouseEvent *event) m_shouldUpdateGeometry = true; update(); break; + case INTERACTION_NONE: case INTERACTION_SELECTED: event->ignore(); return; @@ -385,9 +385,11 @@ void Scatterplot::mouseReleaseEvent(QMouseEvent *event) case INTERACTION_SELECTING: { bool mergeSelection = (event->modifiers() == Qt::ControlModifier); - m_currentInteractionState = - updateSelection(mergeSelection) ? INTERACTION_SELECTED - : INTERACTION_NONE; + bool anySelected = interactiveSelection(mergeSelection); + m_currentInteractionState = anySelected ? INTERACTION_SELECTED + : INTERACTION_NONE; + m_shouldUpdateMaterials = true; + update(); } break; case INTERACTION_BEGIN_MOVING: @@ -402,53 +404,50 @@ void Scatterplot::mouseReleaseEvent(QMouseEvent *event) break; case INTERACTION_NONE: case INTERACTION_SELECTED: - return; // should not be reached + break; // should not be reached } } -bool Scatterplot::updateSelection(bool mergeSelection) +bool Scatterplot::interactiveSelection(bool mergeSelection) { - QSet selection; - if (mergeSelection) { - selection.unite(m_selectedGlyphs); + if (!mergeSelection) { + m_selection.assign(m_selection.size(), false); } m_sx.inverse(); m_sy.inverse(); - - 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()); - + QRectF selectionRect(QPointF(m_sx(m_dragOriginPos.x()), + m_sy(m_dragOriginPos.y())), + QPointF(m_sx(m_dragCurrentPos.x()), + m_sy(m_dragCurrentPos.y()))); m_sy.inverse(); m_sx.inverse(); - QRectF selectionRect(QPointF(originX, originY), QPointF(currentX, currentY)); - + bool anySelected = false; for (arma::uword i = 0; i < m_xy.n_rows; i++) { const arma::rowvec &row = m_xy.row(i); if (selectionRect.contains(row[0], row[1])) { - selection.insert(i); + m_selection[i] = true; + anySelected = true; } } - setSelection(selection); - return !selection.isEmpty(); + emit selectionInteractivelyChanged(m_selection); + return anySelected; } -void Scatterplot::setSelection(const QSet &selection) +void Scatterplot::setSelection(const std::vector &selection) { - m_selectedGlyphs = selection; - for (auto it = m_selectedGlyphs.cbegin(); - it != m_selectedGlyphs.cend(); it++) { - m_opacityData[*it] = GLYPH_OPACITY_SELECTED; + if (m_selection.size() != selection.size()) { + return; } + + m_selection = selection; + emit selectionChanged(m_selection); + m_shouldUpdateMaterials = true; update(); - - emit selectionChanged(selection); } void Scatterplot::applyManipulation() @@ -463,11 +462,13 @@ void Scatterplot::applyManipulation() float tx = m_dragCurrentPos.x() - m_dragOriginPos.x(); float ty = m_dragCurrentPos.y() - m_dragOriginPos.y(); - for (auto it = m_selectedGlyphs.cbegin(); it != m_selectedGlyphs.cend(); it++) { - arma::rowvec row = m_xy.row(*it); - row[0] = rx(m_sx(row[0]) + tx); - row[1] = ry(m_sy(row[1]) + ty); - m_xy.row(*it) = row; + for (std::vector::size_type i = 0; i < m_selection.size(); i++) { + if (m_selection[i]) { + arma::rowvec row = m_xy.row(i); + row[0] = rx(m_sx(row[0]) + tx); + row[1] = ry(m_sy(row[1]) + ty); + m_xy.row(i) = row; + } } emit xyInteractivelyChanged(m_xy); diff --git a/scatterplot.h b/scatterplot.h index c15215b..0fb12ba 100644 --- a/scatterplot.h +++ b/scatterplot.h @@ -1,8 +1,9 @@ #ifndef SCATTERPLOT_H #define SCATTERPLOT_H +#include + #include -#include #include @@ -35,7 +36,8 @@ signals: void xyInteractivelyChanged(const arma::mat &XY) const; void colorDataChanged(const arma::vec &colorData) const; void opacityDataChanged(const arma::vec &opacityData) const; - void selectionChanged(const QSet &selection) const; + void selectionChanged(const std::vector &selection) const; + void selectionInteractivelyChanged(const std::vector &selection) const; void scaleChanged(const LinearScale &sx, const LinearScale &sy) const; void glyphSizeChanged(float glyphSize) const; @@ -43,7 +45,7 @@ public slots: void setXY(const arma::mat &xy); void setColorData(const arma::vec &colorData); void setOpacityData(const arma::vec &opacityData); - void setSelection(const QSet &selection); + void setSelection(const std::vector &selection); void setScale(const LinearScale &sx, const LinearScale &sy); Q_INVOKABLE void setGlyphSize(float glyphSize); @@ -74,8 +76,8 @@ private: LinearScale m_sx, m_sy; // Internal state - bool updateSelection(bool mergeSelection); - QSet m_selectedGlyphs; + bool interactiveSelection(bool mergeSelection); + std::vector m_selection; enum InteractionState { INTERACTION_NONE, diff --git a/selectionhandler.cpp b/selectionhandler.cpp index 44eef28..190040d 100644 --- a/selectionhandler.cpp +++ b/selectionhandler.cpp @@ -1,19 +1,33 @@ #include "selectionhandler.h" -SelectionHandler::SelectionHandler(const arma::uvec &sampleIndices) - : m_sampleIndices(sampleIndices) +#include + +SelectionHandler::SelectionHandler(int numItems) + : m_selection(numItems, false) +{ +} + +void SelectionHandler::setSelection(const std::vector &selection) { + if (m_selection.size() != selection.size()) { + return; + } + + m_selection = selection; + emit selectionChanged(m_selection); } -void SelectionHandler::setSelection(const QSet &selection) +void SelectionHandler::setSelected(int item, bool selected) { - QSet newSelection; + m_selection[item] = selected; + emit selectionChanged(m_selection); +} - // The selecion happens over the sample indices. We use the original dataset - // indices in sampleIndices to translate indices. - for (auto it = selection.begin(); it != selection.end(); it++) { - newSelection.insert(m_sampleIndices[*it]); +void SelectionHandler::setSelected(const std::set &items, bool selected) +{ + for (auto it = items.cbegin(); it != items.cend(); it++) { + m_selection[*it] = selected; } - emit selectionChanged(newSelection); + emit selectionChanged(m_selection); } diff --git a/selectionhandler.h b/selectionhandler.h index e5b9686..dad59cc 100644 --- a/selectionhandler.h +++ b/selectionhandler.h @@ -1,24 +1,28 @@ #ifndef SELECTIONHANDLER_H #define SELECTIONHANDLER_H +#include +#include + #include -#include -#include -class SelectionHandler : public QObject +class SelectionHandler + : public QObject { Q_OBJECT public: - SelectionHandler(const arma::uvec &sampleIndices); + SelectionHandler(int numItems); signals: - void selectionChanged(const QSet &selection); + void selectionChanged(const std::vector &selection); public slots: - void setSelection(const QSet &selection); + void setSelection(const std::vector &selection); + void setSelected(int item, bool selected = true); + void setSelected(const std::set &items, bool selected = true); private: - arma::uvec m_sampleIndices; + std::vector m_selection; }; #endif // SELECTIONHANDLER_H -- cgit v1.2.3