From a9236429e5691159f1ddc017b28ee0c060e0092d Mon Sep 17 00:00:00 2001 From: Samuel Fadel Date: Sun, 17 Jan 2016 16:09:51 +0100 Subject: Added a options panel. * Added screenshot action that saves two images: one of the main view (plot + splat) and one of the bottom view (bar chart) * Added methods/signals/slots to Scatterplot for handling glyph sizes * Added methods/signals/slots to VoronoiSplat for handling the alpha/beta parameters, which are now also no longer fixed * Options panel: - glyph sizes of both CPs and RPs - splat opacity - splat parameters (alpha & beta) - color scale combo box currently does nothing --- main.cpp | 34 ++++---- main_view.qml | 249 ++++++++++++++++++++++++++++++++++++++++--------------- scatterplot.cpp | 44 ++++++---- scatterplot.h | 26 +++--- voronoisplat.cpp | 26 +++++- voronoisplat.h | 18 +++- 6 files changed, 280 insertions(+), 117 deletions(-) diff --git a/main.cpp b/main.cpp index b79f6ff..402ecef 100644 --- a/main.cpp +++ b/main.cpp @@ -78,6 +78,8 @@ int main(int argc, char **argv) // cpIndices = relevanceSampling(X, cpSize); cpIndices = arma::randi(cpSize, arma::distr_param(0, n-1)); } + + arma::sort(cpIndices); } if (parser.isSet(cpFileOutputOption)) { const QString &cpFilename = parser.value(cpFileOutputOption); @@ -115,22 +117,22 @@ int main(int argc, char **argv) QQmlApplicationEngine engine(QUrl("qrc:///main_view.qml")); - ColorScale colorScale{ - QColor("#1f77b4"), - QColor("#ff7f0e"), - QColor("#2ca02c"), - QColor("#d62728"), - QColor("#9467bd"), - QColor("#8c564b"), - QColor("#e377c2"), - QColor("#17becf"), - QColor("#7f7f7f"), - }; - colorScale.setExtents(labels.min(), labels.max()); - - //ContinuousColorScale colorScale = ContinuousColorScale::builtin(ContinuousColorScale::RED_GRAY_BLUE); + //ColorScale colorScale{ + // QColor("#1f77b4"), + // QColor("#ff7f0e"), + // QColor("#2ca02c"), + // QColor("#d62728"), + // QColor("#9467bd"), + // QColor("#8c564b"), + // QColor("#e377c2"), + // QColor("#17becf"), + // QColor("#7f7f7f"), + //}; //colorScale.setExtents(labels.min(), labels.max()); + ContinuousColorScale colorScale = ContinuousColorScale::builtin(ContinuousColorScale::RAINBOW); + colorScale.setExtents(labels.min(), labels.max()); + Scatterplot *cpPlot = engine.rootObjects()[0]->findChild("cpPlot"); cpPlot->setAcceptedMouseButtons(Qt::LeftButton | Qt::MiddleButton | Qt::RightButton); // cpPlot->setColorData(arma::zeros(cpSize)); @@ -177,13 +179,13 @@ int main(int argc, char **argv) cpPlot, SLOT(setScale(const LinearScale &, const LinearScale &))); BarChart *barChart = engine.rootObjects()[0]->findChild("barChart"); - barChart->setValues(arma::randn(100)); + barChart->setValues(labels); //history->addHistoryItem(Ys); colormap->setColorScale(colorScale); plot->setColorScale(&colorScale); plot->setColorData(labels, false); - //splat->setColormap(colorScale); + splat->setColorScale(colorScale); splat->setValues(labels); cpPlot->setAutoScale(false); diff --git a/main_view.qml b/main_view.qml index 68d6fea..544f7dc 100644 --- a/main_view.qml +++ b/main_view.qml @@ -10,10 +10,8 @@ ApplicationWindow { id: mainWindow title: "Projection" visible: true - contentItem.minimumWidth: 532 + contentItem.minimumWidth: 800 contentItem.minimumHeight: 622 - contentItem.maximumWidth: contentItem.minimumWidth - contentItem.maximumHeight: contentItem.minimumHeight Component.onCompleted: { setX(Screen.width / 2 - width / 2); setY(Screen.height / 2 - height / 2); @@ -23,6 +21,7 @@ ApplicationWindow { Menu { title: "File" MenuItem { action: savePlotAction } + MenuItem { action: screenshotAction } MenuItem { action: quitAction } } @@ -89,78 +88,193 @@ ApplicationWindow { } } - ColumnLayout { - spacing: 10 + GridLayout { anchors.fill: parent - anchors.margins: this.spacing - - Rectangle { - //Layout.fillWidth: true - //Layout.fillHeight: true - width: 512 - height: 512 - border.width: 1 - border.color: "#cccccc" - - VoronoiSplat { - id: splat - objectName: "splat" - x: parent.x - y: parent.y - anchors.fill: parent - } + anchors.margins: 10 - Scatterplot { - id: plot - objectName: "plot" - x: parent.x - y: parent.y - anchors.fill: parent - } + // Main panel + ColumnLayout { + Rectangle { + width: 512 + height: 512 + border.width: 1 + border.color: "#cccccc" + + Item { + id: mainView + anchors.fill: parent + VoronoiSplat { + id: splat + objectName: "splat" + x: parent.x + y: parent.y + anchors.fill: parent + } + + Scatterplot { + id: plot + objectName: "plot" + x: parent.x + y: parent.y + anchors.fill: parent + } + + Scatterplot { + id: cpPlot + objectName: "cpPlot" + x: parent.x + y: parent.y + anchors.fill: parent + } - Scatterplot { - id: cpPlot - objectName: "cpPlot" - x: parent.x - y: parent.y - anchors.fill: parent + Colormap { + id: colormap + objectName: "colormap" + x: parent.x + 5 + y: parent.y + 5 + width: 128 + height: 10 + } + } } - Colormap { - id: colormap - objectName: "colormap" - x: parent.x + 5 - y: parent.y + 5 - width: 128 - height: 10 + Rectangle { + Layout.minimumHeight: 80 + width: mainView.width + border.width: 1 + border.color: "#cccccc" + + Item { + id: bottomView + anchors.fill: parent + BarChart { + id: barChart + objectName: "barChart" + anchors.fill: parent + } + + //HistoryGraph { + // id: history + // objectName: "history" + // anchors.fill: parent + //} + } } } - Rectangle { - Layout.fillWidth: true - Layout.minimumHeight: 80 - border.width: 1 - border.color: "#cccccc" + // Options panel + RowLayout { + anchors.margins: parent.anchors.margins + + // Left column + ColumnLayout { + GroupBox { + title: "Scatterplot" + + ColumnLayout { + GridLayout { + columns: 2 + + Label { text: "Colors:" } + ComboBox { + id: colormapCombo + model: [ "Categorical", "Continuous", "Divergent", "Rainbow" ] + } + } + + GroupBox { + flat: true + title: "Glyph size" + + GridLayout { + columns: 2 + Label { text: "Control points:" } + SpinBox { + id: cpGlyphSizeSpinBox + maximumValue: 50 + minimumValue: 8 + value: cpPlot.glyphSize() + decimals: 1 + stepSize: 1 + onValueChanged: cpPlot.setGlyphSize(this.value) + } + + Label { text: "Regular points:" } + SpinBox { + id: rpGlyphSizeSpinBox + maximumValue: 50 + minimumValue: 8 + value: plot.glyphSize() + decimals: 1 + stepSize: 1 + onValueChanged: plot.setGlyphSize(this.value) + } + } + } + } + } + + GroupBox { + title: "Splat" + + GridLayout { + columns: 2 + + Label { text: "Alpha:" } + SpinBox { + id: alphaSpinBox + maximumValue: 100 + minimumValue: 1 + value: splat.alpha() + decimals: 2 + stepSize: 1 + onValueChanged: splat.setAlpha(this.value) + } - BarChart { - id: barChart - objectName: "barChart" - anchors.fill: parent + Label { text: "Beta:" } + SpinBox { + id: betaSpinBox + maximumValue: 100 + minimumValue: 1 + value: splat.beta() + decimals: 2 + stepSize: 1 + onValueChanged: splat.setBeta(this.value) + } + + Label { text: "Opacity (%):" } + SpinBox { + id: splatOpacitySpinBox + maximumValue: 100 + minimumValue: 0 + value: 100 * splat.opacity + decimals: 0 + stepSize: 1 + onValueChanged: splat.opacity = this.value / 100 + } + } + } } - //HistoryGraph { - // id: history - // objectName: "history" - // anchors.fill: parent - //} + // Right column + ColumnLayout { + } } } Action { - id: quitAction - text: "&Quit" - shortcut: "Ctrl+Q" - onTriggered: Qt.quit() + id: screenshotAction + text: "Save screenshot" + shortcut: "Ctrl+Shift+S" + onTriggered: { + mainView.grabToImage(function(result) { + result.saveToFile("screenshot-main.png"); + }); + + bottomView.grabToImage(function(result) { + result.saveToFile("screenshot-bottom.png"); + }); + } } Action { @@ -173,6 +287,13 @@ ApplicationWindow { } } + Action { + id: quitAction + text: "&Quit" + shortcut: "Ctrl+Q" + onTriggered: Qt.quit() + } + ExclusiveGroup { id: techniqueGroup @@ -269,12 +390,4 @@ ApplicationWindow { onTriggered: console.log("stub: Silhouette") } } - - // TODO - //Window { - // title: "Options" - // minimumWidth: 500 - // minimumHeight: 300 - // visible: false - //} } diff --git a/scatterplot.cpp b/scatterplot.cpp index 8f08fee..5e67dc3 100644 --- a/scatterplot.cpp +++ b/scatterplot.cpp @@ -10,11 +10,12 @@ static const QColor GLYPH_OUTLINE_COLOR(255, 255, 255); static const QColor GLYPH_OUTLINE_COLOR_SELECTED(0, 0, 0); static const QColor SELECTION_COLOR(128, 128, 128, 96); -static const float GLYPH_SIZE = 8.0f; +static const float DEFAULT_GLYPH_SIZE = 8.0f; static const float GLYPH_OUTLINE_WIDTH = 2.0f; Scatterplot::Scatterplot(QQuickItem *parent) : QQuickItem(parent) + , m_glyphSize(DEFAULT_GLYPH_SIZE) , m_autoScale(true) , m_sx(0, 1, 0, 1) , m_sy(0, 1, 0, 1) @@ -76,7 +77,6 @@ void Scatterplot::setXY(const arma::mat &xy, bool updateView) } m_shouldUpdateGeometry = true; - if (updateView) { update(); } @@ -97,7 +97,6 @@ void Scatterplot::setColorData(const arma::vec &colorData, bool updateView) emit colorDataChanged(m_colorData); m_shouldUpdateMaterials = true; - if (updateView) { update(); } @@ -108,14 +107,6 @@ void Scatterplot::setColorData(const arma::vec &colorData) setColorData(colorData, true); } -void Scatterplot::setAutoScale(bool autoScale) -{ - m_autoScale = autoScale; - if (autoScale) { - this->autoScale(); - } -} - void Scatterplot::setOpacityData(const arma::vec &opacityData, bool updateView) { if (m_xy.n_rows > 0 && opacityData.n_elem != m_xy.n_rows) { @@ -142,7 +133,6 @@ void Scatterplot::setScale(const LinearScale &sx, const LinearScale &sx, const LinearScaleautoScale(); + } +} + void Scatterplot::autoScale() { m_sx.setDomain(m_xy.col(0).min(), m_xy.col(0).max()); @@ -161,6 +159,22 @@ void Scatterplot::autoScale() emit scaleChanged(m_sx, m_sy); } +void Scatterplot::setGlyphSize(float glyphSize, bool updateView) +{ + m_glyphSize = glyphSize; + emit glyphSizeChanged(m_glyphSize); + + m_shouldUpdateGeometry = true; + if (updateView) { + update(); + } +} + +void Scatterplot::setGlyphSize(float glyphSize) +{ + setGlyphSize(glyphSize, true); +} + QSGNode *Scatterplot::newGlyphTree() { // NOTE: @@ -171,7 +185,7 @@ QSGNode *Scatterplot::newGlyphTree() } QSGNode *node = new QSGNode; - int vertexCount = calculateCircleVertexCount(GLYPH_SIZE); + int vertexCount = calculateCircleVertexCount(m_glyphSize); for (arma::uword i = 0; i < m_xy.n_rows; i++) { QSGGeometry *glyphOutlineGeometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), vertexCount); @@ -293,11 +307,11 @@ void Scatterplot::updateGlyphs(QSGNode *glyphsNode) y = m_sy(row[1]) + ty * moveTranslationF; QSGGeometry *geometry = glyphOutlineNode->geometry(); - updateCircleGeometry(geometry, GLYPH_SIZE, x, y); + updateCircleGeometry(geometry, m_glyphSize, x, y); glyphOutlineNode->markDirty(QSGNode::DirtyGeometry); geometry = glyphNode->geometry(); - updateCircleGeometry(geometry, GLYPH_SIZE - 2*GLYPH_OUTLINE_WIDTH, x, y); + updateCircleGeometry(geometry, m_glyphSize - 2*GLYPH_OUTLINE_WIDTH, x, y); glyphNode->markDirty(QSGNode::DirtyGeometry); } if (m_shouldUpdateMaterials) { diff --git a/scatterplot.h b/scatterplot.h index 2997425..3c2d50b 100644 --- a/scatterplot.h +++ b/scatterplot.h @@ -22,9 +22,12 @@ public: void setColorData(const arma::vec &colorData, bool updateView); void setOpacityData(const arma::vec &opacityData, bool updateView); void setScale(const LinearScale &sx, const LinearScale &sy, bool updateView); + void setGlyphSize(float glyphSize, bool updateView); void setAutoScale(bool autoScale); Q_INVOKABLE bool saveToFile(const QUrl &url); + Q_INVOKABLE float glyphSize() const { return m_glyphSize; } + static const int PADDING = 10; signals: @@ -34,6 +37,7 @@ signals: void opacityDataChanged(const arma::vec &opacityData) const; void selectionChanged(const QSet &selection) const; void scaleChanged(const LinearScale &sx, const LinearScale &sy) const; + void glyphSizeChanged(float glyphSize) const; public slots: void setXY(const arma::mat &xy); @@ -41,6 +45,7 @@ public slots: void setOpacityData(const arma::vec &opacityData); void setSelection(const QSet &selection); void setScale(const LinearScale &sx, const LinearScale &sy); + Q_INVOKABLE void setGlyphSize(float glyphSize); protected: QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *); @@ -53,17 +58,25 @@ private: QSGNode *newGlyphTree(); void applyManipulation(); - void updateGlyphs(QSGNode *node); - bool updateSelection(bool mergeSelection); - + // Data arma::mat m_xy; + arma::vec m_colorData; + arma::vec m_opacityData; + + // Visuals + float m_glyphSize; + const ColorScale *m_colorScale; void autoScale(); bool m_autoScale; LinearScale m_sx, m_sy; + // Internal state + bool updateSelection(bool mergeSelection); + QSet m_selectedGlyphs; + enum InteractionState { INTERACTION_NONE, INTERACTION_SELECTING, @@ -74,14 +87,7 @@ private: QPointF m_dragOriginPos, m_dragCurrentPos; - QSet m_selectedGlyphs; - bool m_shouldUpdateGeometry, m_shouldUpdateMaterials; - - const ColorScale *m_colorScale; - - arma::vec m_colorData; - arma::vec m_opacityData; }; #endif // SCATTERPLOT_H diff --git a/voronoisplat.cpp b/voronoisplat.cpp index dcddc11..9154c5e 100644 --- a/voronoisplat.cpp +++ b/voronoisplat.cpp @@ -12,7 +12,8 @@ #include "scatterplot.h" #include "skelft.h" -static const float RAD_BLUR = 5.0f; +static const float DEFAULT_ALPHA = 5.0f; +static const float DEFAULT_BETA = 20.0f; static int nextPow2(int n) { @@ -27,6 +28,8 @@ static int nextPow2(int n) VoronoiSplat::VoronoiSplat(QQuickItem *parent) : QQuickFramebufferObject(parent) , m_cmap(3*Colormap::SAMPLES) + , m_alpha(DEFAULT_ALPHA) + , m_beta(DEFAULT_BETA) { setTextureFollowsItemSize(false); } @@ -87,6 +90,18 @@ void VoronoiSplat::setColorScale(const ColorScale &scale) update(); } +void VoronoiSplat::setAlpha(float alpha) +{ + m_alpha = alpha; + update(); +} + +void VoronoiSplat::setBeta(float beta) +{ + m_beta = beta; + update(); +} + // ---------------------------------------------------------------------------- class VoronoiSplatRenderer @@ -115,6 +130,7 @@ private: QSize m_size; const std::vector *m_sites, *m_values, *m_cmap; + float m_alpha, m_beta; QQuickWindow *m_window; // used to reset OpenGL state (as per docs) QOpenGLFunctions gl; @@ -336,8 +352,8 @@ void VoronoiSplatRenderer::render() // First pass m_program1->bind(); - m_program1->setUniformValue("rad_max", 20.0f); - m_program1->setUniformValue("rad_blur", RAD_BLUR); + m_program1->setUniformValue("rad_max", m_beta); + m_program1->setUniformValue("rad_blur", m_alpha); m_program1->setUniformValue("fb_size", float(m_size.width())); gl.glActiveTexture(GL_TEXTURE0); @@ -368,7 +384,7 @@ void VoronoiSplatRenderer::render() // Second pass m_program2->bind(); - m_program2->setUniformValue("rad_max", 20.0f); + m_program2->setUniformValue("rad_max", m_beta); gl.glActiveTexture(GL_TEXTURE0); gl.glBindTexture(GL_TEXTURE_2D, m_textures[0]); @@ -410,6 +426,8 @@ void VoronoiSplatRenderer::synchronize(QQuickFramebufferObject *item) splat->setValuesChanged(false); splat->setColorScaleChanged(false); + m_alpha = splat->alpha(); + m_beta = splat->beta(); m_sites = &(splat->sites()); m_values = &(splat->values()); m_cmap = &(splat->colorScale()); diff --git a/voronoisplat.h b/voronoisplat.h index f334261..6682fed 100644 --- a/voronoisplat.h +++ b/voronoisplat.h @@ -16,12 +16,14 @@ public: Renderer *createRenderer() const; - const std::vector &sites() const { return m_sites; } - const std::vector &values() const { return m_values; } + const std::vector &sites() const { return m_sites; } + const std::vector &values() const { return m_values; } const std::vector &colorScale() const { return m_cmap; } + Q_INVOKABLE float alpha() const { return m_alpha; } + Q_INVOKABLE float beta() const { return m_beta; } - bool sitesChanged() const { return m_sitesChanged; } - bool valuesChanged() const { return m_valuesChanged; } + bool sitesChanged() const { return m_sitesChanged; } + bool valuesChanged() const { return m_valuesChanged; } bool colorScaleChanged() const { return m_colorScaleChanged; } void setSitesChanged(bool sitesChanged) { @@ -38,6 +40,7 @@ signals: void sitesChanged(const arma::mat &sites) const; void valuesChanged(const arma::vec &values) const; void colorScaleChanged(const ColorScale &scale) const; + void alphaChanged(float alpha) const; public slots: // 'points' should be a 2D points matrix (each point in a row) @@ -49,8 +52,15 @@ public slots: // Set colorScale data based on the given color scale void setColorScale(const ColorScale &scale); + // Shepard blur radius + Q_INVOKABLE void setAlpha(float alpha); + + // Maximum blur radius + Q_INVOKABLE void setBeta(float beta); + private: std::vector m_sites, m_values, m_cmap; + float m_alpha, m_beta; bool m_sitesChanged, m_valuesChanged, m_colorScaleChanged; }; -- cgit v1.2.3