aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lineplot.cpp382
-rw-r--r--lineplot.h119
-rw-r--r--main.cpp53
-rw-r--r--main.h4
-rw-r--r--main_view.qml627
5 files changed, 857 insertions, 328 deletions
diff --git a/lineplot.cpp b/lineplot.cpp
index b4d7412..83f6e12 100644
--- a/lineplot.cpp
+++ b/lineplot.cpp
@@ -19,86 +19,124 @@
#include "geometry.h"
#include "scatterplot.h"
+static const int SAMPLES = 128;
+
LinePlot::LinePlot(QQuickItem *parent)
: QQuickFramebufferObject(parent)
, m_sx(0, 1, 0, 1)
, m_sy(0, 1, 0, 1)
- , m_xyChanged(false)
+ , m_linesChanged(false)
, m_valuesChanged(false)
, m_colorScaleChanged(false)
+
+ // Q_PROPERTY's
+ , m_iterations(15)
+ , m_kernelSize(32)
+ , m_smoothingFactor(0.2)
+ , m_smoothingIterations(1)
+
+ , m_blockEndpoints(true)
+ , m_endsIterations(0)
+ , m_endsKernelSize(32)
+ , m_endsSmoothingFactor(0.5)
+
+ , m_edgeSampling(15)
+ , m_advectionSpeed(0.5)
+ , m_relaxation(0)
, m_bundleGPU(true)
{
+ setFlag(QQuickItem::ItemHasContents);
setTextureFollowsItemSize(false);
}
-void LinePlot::setColorScale(const ColorScale *colorScale)
+void LinePlot::setColorScale(const ColorScale *scale)
{
- m_colorScale = colorScale;
- if (m_values.size() > 0) {
- // FIXME
- m_colorScaleChanged = true;
- update();
- }
+ m_cmap.resize(SAMPLES * 3);
+ scale->sample(SAMPLES, m_cmap.begin());
+
+ setColorScaleChanged(true);
+ update();
}
void LinePlot::bundle()
{
- Graph g(m_xy.n_rows);
- PointSet points;
+ m_gdBundlePtr.reset(new GraphDrawing);
+ *m_gdBundlePtr.get() = *m_gdPtr.get();
- for (arma::uword i = 0; i < m_xy.n_rows; i++) {
- const arma::rowvec &row = m_xy.row(i);
- points.push_back(Point2d(row[0], row[1]));
+ CPUBundling bundling(std::min(width(), height()));
+ bundling.setInput(m_gdBundlePtr.get());
- if (i > 0) {
- g(i - 1, i) = m_values[i];
- g(i, i - 1) = m_values[i];
- }
- }
+ bundling.niter = m_iterations;
+ bundling.h = m_kernelSize;
+ bundling.lambda = m_smoothingFactor;
+ bundling.liter = m_smoothingIterations;
- m_gdPtr.reset(new GraphDrawing);
- m_gdPtr.get()->build(&g, &points);
+ bundling.block_endpoints = m_blockEndpoints;
+ bundling.niter_ms = m_endsIterations;
+ bundling.h_ms = m_endsKernelSize;
+ bundling.lambda_ends = m_endsSmoothingFactor;
+
+ bundling.spl = m_edgeSampling;
+ bundling.eps = m_advectionSpeed;
+ // TODO: use m_relaxation as lerp param towards original (without bundling)
- CPUBundling bundling(std::min(width(), height()));
- bundling.setInput(m_gdPtr.get());
if (m_bundleGPU) {
bundling.bundleGPU();
} else {
bundling.bundleCPU();
}
+
+ setLinesChanged(true);
}
-void LinePlot::setXY(const arma::mat &xy)
+void LinePlot::setLines(const arma::uvec &indices, const arma::mat &Y)
{
- if (xy.n_cols != 2) {
+ if (indices.n_elem % 2 != 0 || Y.n_cols != 2) {
return;
}
- m_xy = xy;
- m_xyChanged = true;
- emit xyChanged(m_xy);
+ m_lines = Y.rows(indices);
+
+ // Build the line plot's internal representation: a graph where each
+ // endpoint of a line is a node, each line is a link...
+ Graph g(Y.n_rows);
+ PointSet points;
+
+ m_sx.setRange(0, width());
+ m_sy.setRange(0, height());
+ for (arma::uword i = 0; i < Y.n_rows; i++) {
+ points.push_back(Point2d(m_sx(Y(i, 0)), m_sy(Y(i, 1))));
+ }
+
+ for (arma::uword k = 0; k < m_values.size(); k++) {
+ arma::uword i = indices(2*k + 0),
+ j = indices(2*k + 1);
- // Build the line plot's internal representation: graph where each endpoint
- // of a line is a node, each line is a link; and then bundle the graph's
- // edges
+ g(i, j) = g(j, i) = m_values[k];
+ }
+
+ m_gdPtr.reset(new GraphDrawing);
+ m_gdPtr.get()->build(&g, &points);
+
+ // ... then bundle the edges
bundle();
+ emit linesChanged(m_lines);
update();
}
void LinePlot::setValues(const arma::vec &values)
{
- if (m_xy.n_rows > 0
- && (values.n_elem > 0 && values.n_elem != m_xy.n_rows)) {
+ if (m_lines.n_rows > 0
+ && (values.n_elem > 0 && values.n_elem != m_lines.n_rows)) {
return;
}
m_values.resize(values.n_elem);
- LinearScale<float> scale(values.min(), values.max(), 0, 1.0f);
- std::transform(values.begin(), values.end(), m_values.begin(), scale);
+ std::copy(values.begin(), values.end(), m_values.begin());
emit valuesChanged(values);
- m_valuesChanged = true;
+ setValuesChanged(true);
update();
}
@@ -111,6 +149,162 @@ void LinePlot::setScale(const LinearScale<float> &sx,
update();
}
+// Q_PROPERTY's
+void LinePlot::setIterations(int iterations) {
+ if (m_iterations == iterations) {
+ return;
+ }
+
+ m_iterations = iterations;
+ emit iterationsChanged(m_iterations);
+
+ bundle();
+ update();
+}
+
+void LinePlot::setKernelSize(float kernelSize)
+{
+ if (m_kernelSize == kernelSize) {
+ return;
+ }
+
+ m_kernelSize = kernelSize;
+ emit kernelSizeChanged(m_kernelSize);
+
+ bundle();
+ update();
+}
+
+void LinePlot::setSmoothingFactor(float smoothingFactor)
+{
+ if (m_smoothingFactor == smoothingFactor) {
+ return;
+ }
+
+ m_smoothingFactor = smoothingFactor;
+ emit smoothingFactorChanged(m_smoothingFactor);
+
+ bundle();
+ update();
+}
+
+void LinePlot::setSmoothingIterations(int smoothingIterations)
+{
+ if (m_smoothingIterations == smoothingIterations) {
+ return;
+ }
+
+ m_smoothingIterations = smoothingIterations;
+ emit smoothingIterationsChanged(m_smoothingIterations);
+
+ bundle();
+ update();
+}
+
+void LinePlot::setBlockEndpoints(bool blockEndpoints)
+{
+ if (m_blockEndpoints == blockEndpoints) {
+ return;
+ }
+
+ m_blockEndpoints = blockEndpoints;
+ emit blockEndpointsChanged(m_blockEndpoints);
+
+ bundle();
+ update();
+}
+
+void LinePlot::setEndsIterations(int endsIterations)
+{
+ if (m_endsIterations == endsIterations) {
+ return;
+ }
+
+ m_endsIterations = endsIterations;
+ emit endsIterationsChanged(m_endsIterations);
+
+ bundle();
+ update();
+}
+
+void LinePlot::setEndsKernelSize(float endsKernelSize)
+{
+ if (m_endsKernelSize == endsKernelSize) {
+ return;
+ }
+
+ m_endsKernelSize = endsKernelSize;
+ emit endsKernelSizeChanged(m_endsKernelSize);
+
+ bundle();
+ update();
+}
+
+void LinePlot::setEndsSmoothingFactor(float endsSmoothingFactor)
+{
+ if (m_endsSmoothingFactor == endsSmoothingFactor) {
+ return;
+ }
+
+ m_endsSmoothingFactor = endsSmoothingFactor;
+ emit endsSmoothingFactorChanged(m_endsSmoothingFactor);
+
+ bundle();
+ update();
+}
+
+void LinePlot::setEdgeSampling(float edgeSampling)
+{
+ if (m_edgeSampling == edgeSampling) {
+ return;
+ }
+
+ m_edgeSampling = edgeSampling;
+ emit edgeSamplingChanged(m_edgeSampling);
+
+ bundle();
+ update();
+}
+
+void LinePlot::setAdvectionSpeed(float advectionSpeed)
+{
+ if (m_advectionSpeed == advectionSpeed) {
+ return;
+ }
+
+ m_advectionSpeed = advectionSpeed;
+ emit advectionSpeedChanged(m_advectionSpeed);
+
+ bundle();
+ update();
+}
+
+void LinePlot::setRelaxation(float relaxation)
+{
+ if (m_relaxation == relaxation) {
+ return;
+ }
+
+ m_relaxation = relaxation;
+ emit relaxationChanged(m_relaxation);
+
+ bundle();
+ update();
+}
+
+void LinePlot::setBundleGPU(bool bundleGPU)
+{
+ if (m_bundleGPU == bundleGPU) {
+ return;
+ }
+
+ m_bundleGPU = bundleGPU;
+ emit bundleGPUChanged(m_bundleGPU);
+
+ bundle();
+ update();
+}
+
// ----------------------------------------------------------------------------
class LinePlotRenderer
@@ -133,23 +327,21 @@ private:
void updatePoints();
void updateValues();
void updateColormap();
- void updateTransform();
void copyPolylines(const GraphDrawing *gd);
QSize m_size;
std::vector<float> m_points;
- const std::vector<float> *m_values;
+ const std::vector<float> *m_values, *m_cmap;
std::vector<int> m_offsets;
- float m_alpha, m_beta;
- GLfloat m_transform[4][4];
- LinearScale<float> m_sx, m_sy;
QQuickWindow *m_window; // used to reset OpenGL state (as per docs)
QOpenGLFunctions gl;
QOpenGLShaderProgram *m_program;
GLuint m_VBOs[2], m_colormapTex;
QOpenGLVertexArrayObject m_VAO;
+ GLfloat m_transform[4][4];
+ LinearScale<float> m_sx, m_sy;
bool m_pointsChanged, m_valuesChanged, m_colormapChanged;
};
@@ -159,13 +351,12 @@ QQuickFramebufferObject::Renderer *LinePlot::createRenderer() const
}
LinePlotRenderer::LinePlotRenderer()
- : m_sx(0, 1, 0, 1)
- , m_sy(0, 1, 0, 1)
- , gl(QOpenGLContext::currentContext())
+ : gl(QOpenGLContext::currentContext())
+ , m_sx(0.0f, 1.0f, 0.0f, 1.0f)
+ , m_sy(0.0f, 1.0f, 0.0f, 1.0f)
{
std::fill(&m_transform[0][0], &m_transform[0][0] + 16, 0.0f);
m_transform[3][3] = 1.0f;
-
setupShaders();
setupVAOs();
setupTextures();
@@ -236,12 +427,39 @@ void LinePlotRenderer::setupTextures()
LinePlotRenderer::~LinePlotRenderer()
{
+ gl.glDeleteBuffers(2, m_VBOs);
+ gl.glDeleteTextures(1, &m_colormapTex);
+
delete m_program;
}
QOpenGLFramebufferObject *LinePlotRenderer::createFramebufferObject(const QSize &size)
{
m_size = size;
+ GLfloat w = m_size.width(), h = m_size.height();
+
+ GLfloat rangeOffset = Scatterplot::PADDING / w;
+ m_sx.setDomain(0, w);
+ m_sx.setRange(rangeOffset, 1.0f - rangeOffset);
+ GLfloat sx = 2.0f * m_sx.slope();
+ GLfloat tx = 2.0f * m_sx.offset() - 1.0f;
+
+ rangeOffset = Scatterplot::PADDING / h;
+ m_sy.setDomain(0, h);
+ m_sy.setRange(1.0f - rangeOffset, rangeOffset); // inverted on purpose
+ GLfloat sy = 2.0f * m_sy.slope();
+ GLfloat ty = 2.0f * m_sy.offset() - 1.0f;
+
+ // The transform matrix should be this (but transposed -- column major):
+ // [ sx 0.0f 0.0f tx ]
+ // [ 0.0f sy 0.0f ty ]
+ // [ 0.0f 0.0f 0.0f 0.0f ]
+ // [ 0.0f 0.0f 0.0f 1.0f ]
+ m_transform[0][0] = sx;
+ m_transform[1][1] = sy;
+ m_transform[3][0] = tx;
+ m_transform[3][1] = ty;
+
return QQuickFramebufferObject::Renderer::createFramebufferObject(m_size);
}
@@ -262,20 +480,31 @@ void LinePlotRenderer::render()
updateColormap();
}
+ if (m_offsets.size() < 2) {
+ // Nothing to draw
+ return;
+ }
+
m_program->bind();
- m_program->setUniformValue("transform", m_transform);
gl.glActiveTexture(GL_TEXTURE0);
gl.glBindTexture(GL_TEXTURE_2D, m_colormapTex);
m_program->setUniformValue("colormap", 0);
+ m_program->setUniformValue("transform", m_transform);
gl.glClearColor(0, 0, 0, 0);
gl.glClear(GL_COLOR_BUFFER_BIT);
m_VAO.bind();
- for (int i = 0; i < m_offsets.size() - 1; i++) {
- gl.glDrawArrays(GL_LINES, m_offsets[i], m_offsets[i + 1]);
+ gl.glEnable(GL_LINE_SMOOTH);
+ gl.glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
+ gl.glEnable(GL_BLEND);
+ gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ for (int i = 1; i < m_offsets.size(); i++) {
+ gl.glDrawArrays(GL_LINE_STRIP, m_offsets[i - 1], m_offsets[i] - m_offsets[i - 1]);
}
+ gl.glDisable(GL_LINE_SMOOTH);
+ gl.glDisable(GL_BLEND);
m_VAO.release();
m_program->release();
@@ -290,22 +519,20 @@ void LinePlotRenderer::copyPolylines(const GraphDrawing *gd)
int pointsNum = 0;
m_offsets.clear();
+ m_offsets.reserve(gd->draw_order.size() + 1);
m_offsets.push_back(0);
- for (std::pair<float, const GraphDrawing::Polyline *> p : gd->draw_order) {
+ for (auto &p : gd->draw_order) {
pointsNum += p.second->size();
m_offsets.push_back(pointsNum);
}
- m_points.resize(2 * pointsNum);
- arma::uword i = 0, k = 0;
- for (std::pair<float, const GraphDrawing::Polyline *> p : gd->draw_order) {
- for (arma::uword j = 0; j < p.second->size(); j++) {
- m_points[i + j + 0] = (*p.second)[j].x;
- m_points[i + j + 1] = (*p.second)[j].y;
+ m_points.clear();
+ m_points.reserve(2 * pointsNum);
+ for (auto &p : gd->draw_order) {
+ for (auto &point : *p.second) {
+ m_points.push_back(point.x);
+ m_points.push_back(point.y);
}
-
- i += p.second->size();
- k++;
}
}
@@ -313,54 +540,29 @@ void LinePlotRenderer::synchronize(QQuickFramebufferObject *item)
{
LinePlot *plot = static_cast<LinePlot *>(item);
- m_pointsChanged = plot->xyChanged();
+ m_pointsChanged = plot->linesChanged();
m_valuesChanged = plot->valuesChanged();
m_colormapChanged = plot->colorScaleChanged();
- copyPolylines(plot->graphDrawing());
+ if (m_pointsChanged) {
+ copyPolylines(plot->bundleGraphDrawing());
+ }
m_values = &(plot->values());
- m_sx = plot->scaleX();
- m_sy = plot->scaleY();
+ m_cmap = &(plot->colorScale());
m_window = plot->window();
// Reset so that we have the correct values by the next synchronize()
- plot->setXYChanged(false);
+ plot->setLinesChanged(false);
plot->setValuesChanged(false);
plot->setColorScaleChanged(false);
}
-void LinePlotRenderer::updateTransform()
-{
- GLfloat w = m_size.width(), h = m_size.height();
-
- GLfloat rangeOffset = Scatterplot::PADDING / w;
- m_sx.setRange(rangeOffset, 1.0f - rangeOffset);
- GLfloat sx = 2.0f * m_sx.slope();
- GLfloat tx = 2.0f * m_sx.offset() - 1.0f;
-
- rangeOffset = Scatterplot::PADDING / h;
- m_sy.setRange(1.0f - rangeOffset, rangeOffset);
- GLfloat sy = 2.0f * m_sy.slope();
- GLfloat ty = 2.0f * m_sy.offset() - 1.0f;
-
- // The transform matrix should be this (but transposed -- column major):
- // [ sx 0.0f 0.0f -tx ]
- // [ 0.0f sy 0.0f -ty ]
- // [ 0.0f 0.0f 0.0f 0.0f ]
- // [ 0.0f 0.0f 0.0f 1.0f ]
- m_transform[0][0] = sx;
- m_transform[1][1] = sy;
- m_transform[3][0] = tx;
- m_transform[3][1] = ty;
-}
-
void LinePlotRenderer::updatePoints()
{
gl.glBindBuffer(GL_ARRAY_BUFFER, m_VBOs[0]);
gl.glBufferData(GL_ARRAY_BUFFER, m_points.size() * sizeof(float),
m_points.data(), GL_DYNAMIC_DRAW);
- updateTransform();
m_pointsChanged = false;
}
@@ -376,8 +578,8 @@ void LinePlotRenderer::updateValues()
void LinePlotRenderer::updateColormap()
{
gl.glBindTexture(GL_TEXTURE_2D, m_colormapTex);
- //gl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_cmap->size() / 3, 1, 0, GL_RGB,
- // GL_FLOAT, m_cmap->data());
+ gl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_cmap->size() / 3, 1, 0, GL_RGB,
+ GL_FLOAT, m_cmap->data());
m_colormapChanged = false;
}
diff --git a/lineplot.h b/lineplot.h
index 060171d..bc6340c 100644
--- a/lineplot.h
+++ b/lineplot.h
@@ -18,27 +18,46 @@ class LinePlot
: public QQuickFramebufferObject
{
Q_OBJECT
+
+ // Main bundling
+ Q_PROPERTY(int iterations READ iterations WRITE setIterations NOTIFY iterationsChanged)
+ Q_PROPERTY(float kernelSize READ kernelSize WRITE setKernelSize NOTIFY kernelSizeChanged)
+ Q_PROPERTY(float smoothingFactor READ smoothingFactor WRITE setSmoothingFactor NOTIFY smoothingFactorChanged)
+ Q_PROPERTY(int smoothingIterations READ smoothingIterations WRITE setSmoothingIterations NOTIFY smoothingIterationsChanged)
+
+ // Ends bundling
+ Q_PROPERTY(bool blockEndpoints READ blockEndpoints WRITE setBlockEndpoints NOTIFY blockEndpointsChanged)
+ Q_PROPERTY(int endsIterations READ endsIterations WRITE setEndsIterations NOTIFY endsIterationsChanged)
+ Q_PROPERTY(float endsKernelSize READ endsKernelSize WRITE setEndsKernelSize NOTIFY endsKernelSizeChanged)
+ Q_PROPERTY(float endsSmoothingFactor READ endsSmoothingFactor WRITE setEndsSmoothingFactor NOTIFY endsSmoothingFactorChanged)
+
+ // General bundling options
+ Q_PROPERTY(float edgeSampling READ edgeSampling WRITE setEdgeSampling NOTIFY edgeSamplingChanged)
+ Q_PROPERTY(float advectionSpeed READ advectionSpeed WRITE setAdvectionSpeed NOTIFY advectionSpeedChanged)
+ Q_PROPERTY(float relaxation READ relaxation WRITE setRelaxation NOTIFY relaxationChanged)
+ Q_PROPERTY(bool bundleGPU READ bundleGPU WRITE setBundleGPU NOTIFY bundleGPUChanged)
+
public:
static const int PADDING = 20;
LinePlot(QQuickItem *parent = 0);
- void setColorScale(const ColorScale *colorScale);
- void setAutoScale(bool autoScale);
+ void setColorScale(const ColorScale *scale);
- const GraphDrawing *graphDrawing() const { return m_gdPtr.get(); }
- const std::vector<float> &values() const { return m_values; }
+ const GraphDrawing *bundleGraphDrawing() const { return m_gdBundlePtr.get(); }
+ const std::vector<float> &values() const { return m_values; }
+ const std::vector<float> &colorScale() const { return m_cmap; }
LinearScale<float> scaleX() const { return m_sx; }
LinearScale<float> scaleY() const { return m_sy; }
Renderer *createRenderer() const;
- bool xyChanged() const { return m_xyChanged; }
- bool valuesChanged() const { return m_valuesChanged; }
+ bool linesChanged() const { return m_linesChanged; }
+ bool valuesChanged() const { return m_valuesChanged; }
bool colorScaleChanged() const { return m_colorScaleChanged; }
- void setXYChanged(bool xyChanged) {
- m_xyChanged = xyChanged;
+ void setLinesChanged(bool linesChanged) {
+ m_linesChanged = linesChanged;
}
void setValuesChanged(bool valuesChanged) {
m_valuesChanged = valuesChanged;
@@ -47,15 +66,66 @@ public:
m_colorScaleChanged = colorScaleChanged;
}
+ // Q_PROPERTY's
+ int iterations() const { return m_iterations; }
+ float kernelSize() const { return m_kernelSize; }
+ float smoothingFactor() const { return m_smoothingFactor; }
+ int smoothingIterations() const { return m_smoothingIterations; }
+
+ void setIterations(int iterations);
+ void setKernelSize(float kernelSize);
+ void setSmoothingFactor(float smoothingFactor);
+ void setSmoothingIterations(int smoothingIterations);
+
+ bool blockEndpoints() const { return m_blockEndpoints; }
+ int endsIterations() const { return m_endsIterations; }
+ float endsKernelSize() const { return m_endsKernelSize; }
+ float endsSmoothingFactor() const { return m_endsSmoothingFactor; }
+
+ void setBlockEndpoints(bool blockEndpoints);
+ void setEndsIterations(int endsIterations);
+ void setEndsKernelSize(float endsKernelSize);
+ void setEndsSmoothingFactor(float endsSmoothingFactor);
+
+ float edgeSampling() const { return m_edgeSampling; }
+ float advectionSpeed() const { return m_advectionSpeed; }
+ float relaxation() const { return m_relaxation; }
+ bool bundleGPU() const { return m_bundleGPU; }
+
+ void setEdgeSampling(float edgeSampling);
+ void setAdvectionSpeed(float advectionSpeed);
+ void setRelaxation(float relaxation);
+ void setBundleGPU(bool bundleGPU);
+
signals:
- void xyChanged(const arma::mat &xy);
+ void linesChanged(const arma::mat &xy);
void valuesChanged(const arma::vec &values) const;
void scaleChanged(const LinearScale<float> &sx,
const LinearScale<float> &sy) const;
+ // Q_PROPERTY's
+ void iterationsChanged(int iterations) const;
+ void kernelSizeChanged(float kernelSize) const;
+ void smoothingFactorChanged(float smoothingFactor) const;
+ void smoothingIterationsChanged(int smoothingIterations) const;
+
+ void blockEndpointsChanged(bool blockEndpoints) const;
+ void endsIterationsChanged(int endsIterations) const;
+ void endsKernelSizeChanged(float endsKernelSize) const;
+ void endsSmoothingFactorChanged(float endsSmoothingFactor) const;
+
+ void edgeSamplingChanged(float edgeSampling) const;
+ void advectionSpeedChanged(float advectionSpeed) const;
+ void relaxationChanged(float relaxation) const;
+ void bundleGPUChanged(bool bundleGPU) const;
+
public slots:
- void setXY(const arma::mat &xy);
+ // Lines are two consecutive elements in 'indices' (ref. points in 'Y')
+ void setLines(const arma::uvec &indices, const arma::mat &Y);
+
+ // One value for each line
void setValues(const arma::vec &values);
+
void setScale(const LinearScale<float> &sx,
const LinearScale<float> &sy);
@@ -63,21 +133,32 @@ private:
void bundle();
// Data
- arma::mat m_xy;
+ arma::mat m_lines;
std::vector<float> m_values;
// Visuals
- const ColorScale *m_colorScale;
-
- void autoScale();
- bool m_autoScale;
+ std::vector<float> m_cmap;
LinearScale<float> m_sx, m_sy;
-
- std::unique_ptr<GraphDrawing> m_gdPtr;
+ std::unique_ptr<GraphDrawing> m_gdPtr, m_gdBundlePtr;
// Internal state
- bool m_xyChanged, m_valuesChanged, m_colorScaleChanged;
- bool m_bundleGPU;
+ bool m_linesChanged, m_valuesChanged, m_colorScaleChanged;
+
+ // Q_PROPERTY's
+ int m_iterations;
+ float m_kernelSize;
+ float m_smoothingFactor;
+ int m_smoothingIterations;
+
+ bool m_blockEndpoints;
+ int m_endsIterations;
+ float m_endsKernelSize;
+ float m_endsSmoothingFactor;
+
+ float m_edgeSampling;
+ float m_advectionSpeed;
+ float m_relaxation;
+ bool m_bundleGPU;
};
#endif // LINEPLOT_H
diff --git a/main.cpp b/main.cpp
index c7a6f5c..bf9042a 100644
--- a/main.cpp
+++ b/main.cpp
@@ -153,9 +153,10 @@ int main(int argc, char **argv)
// Initialize pointers to visual components
m->cpPlot = engine.rootObjects()[0]->findChild<Scatterplot *>("cpPlot");
m->rpPlot = engine.rootObjects()[0]->findChild<Scatterplot *>("rpPlot");
+ m->splat = engine.rootObjects()[0]->findChild<VoronoiSplat *>("splat");
+ m->bundlePlot = engine.rootObjects()[0]->findChild<LinePlot *>("bundlePlot");
m->cpColormap = engine.rootObjects()[0]->findChild<Colormap *>("cpColormap");
m->rpColormap = engine.rootObjects()[0]->findChild<Colormap *>("rpColormap");
- m->splat = engine.rootObjects()[0]->findChild<VoronoiSplat *>("splat");
m->cpBarChart = engine.rootObjects()[0]->findChild<BarChart *>("cpBarChart");
m->rpBarChart = engine.rootObjects()[0]->findChild<BarChart *>("rpBarChart");
TransitionControl *plotTC = engine.rootObjects()[0]->findChild<TransitionControl *>("plotTC");
@@ -170,6 +171,20 @@ int main(int argc, char **argv)
QObject::connect(m->cpPlot, &Scatterplot::xyInteractivelyChanged,
m, &Main::setCP);
+ // Keep both scatterplots, the splat and line plot scaled equally and
+ // relative to the full plot
+ MapScaleHandler mapScaleHandler;
+ QObject::connect(&mapScaleHandler, &MapScaleHandler::scaleChanged,
+ m->cpPlot, &Scatterplot::setScale);
+ QObject::connect(&mapScaleHandler, &MapScaleHandler::scaleChanged,
+ m->rpPlot, &Scatterplot::setScale);
+ QObject::connect(&mapScaleHandler, &MapScaleHandler::scaleChanged,
+ m->splat, &VoronoiSplat::setScale);
+ QObject::connect(&mapScaleHandler, &MapScaleHandler::scaleChanged,
+ m->bundlePlot, &LinePlot::setScale);
+ QObject::connect(m->projectionHistory, &ProjectionHistory::currentMapChanged,
+ &mapScaleHandler, &MapScaleHandler::scaleToMap);
+
// Update projection as the cp are modified (either directly in the
// manipulationHandler object or interactively in cpPlot
ManipulationHandler manipulationHandler(X, cpIndices);
@@ -183,18 +198,32 @@ int main(int argc, char **argv)
// ... and update visual components whenever the history changes
QObject::connect(m->projectionHistory, &ProjectionHistory::currentMapChanged,
m, &Main::updateMap);
-
- // Keep both scatterplots and the splat scaled equally and relative to the
- // full plot
- MapScaleHandler mapScaleHandler;
- QObject::connect(&mapScaleHandler, &MapScaleHandler::scaleChanged,
- m->cpPlot, &Scatterplot::setScale);
- QObject::connect(&mapScaleHandler, &MapScaleHandler::scaleChanged,
- m->rpPlot, &Scatterplot::setScale);
- QObject::connect(&mapScaleHandler, &MapScaleHandler::scaleChanged,
- m->splat, &VoronoiSplat::setScale);
QObject::connect(m->projectionHistory, &ProjectionHistory::currentMapChanged,
- &mapScaleHandler, &MapScaleHandler::scaleToMap);
+ [m](const arma::mat &Y) {
+ // ... and bundling
+ const arma::mat &unreliability = m->projectionHistory->unreliability();
+ arma::uvec indicesLargest = arma::sort_index(unreliability, "descending");
+ auto numLargest = Y.n_rows * 0.1f;
+ indicesLargest = indicesLargest.subvec(0, numLargest-1);
+ m->bundlePlot->setValues(unreliability(indicesLargest));
+
+ const arma::uvec &cpIndices = m->projectionHistory->cpIndices();
+ arma::uvec CPs = cpIndices(indicesLargest / unreliability.n_rows);
+
+ const arma::uvec &rpIndices = m->projectionHistory->rpIndices();
+ arma::uvec RPs = indicesLargest;
+ RPs.transform([&unreliability](arma::uword v) {
+ return v % unreliability.n_rows;
+ });
+ RPs = rpIndices(RPs);
+
+ arma::uvec indices(CPs.n_elem + RPs.n_elem);
+ for (arma::uword i = 0; i < CPs.n_elem; i++) {
+ indices(2*i + 0) = CPs(i);
+ indices(2*i + 1) = RPs(i);
+ }
+ m->bundlePlot->setLines(indices, Y);
+ });
// Linking between selections
SelectionHandler cpSelectionHandler(cpIndices.n_elem);
diff --git a/main.h b/main.h
index 830c742..143d49e 100644
--- a/main.h
+++ b/main.h
@@ -12,6 +12,7 @@
#include "numericrange.h"
#include "barchart.h"
#include "colormap.h"
+#include "lineplot.h"
#include "scatterplot.h"
#include "voronoisplat.h"
@@ -96,6 +97,7 @@ public:
cpPlot->setColorScale(colorScaleCPs.get());
cpBarChart->setColorScale(colorScaleCPs.get());
cpColormap->setColorScale(colorScaleCPs.get());
+ bundlePlot->setColorScale(colorScaleCPs.get());
}
Q_INVOKABLE void setRPColorScale(ColorScaleType colorScaleType) {
@@ -122,6 +124,7 @@ public:
Colormap *cpColormap, *rpColormap;
Scatterplot *cpPlot, *rpPlot;
VoronoiSplat *splat;
+ LinePlot *bundlePlot;
// Color scales in use
std::unique_ptr<ColorScale> colorScaleCPs, colorScaleRPs;
@@ -188,6 +191,7 @@ private:
, cpPlot(0)
, rpPlot(0)
, splat(0)
+ , bundlePlot(0)
, projectionHistory(0)
{
}
diff --git a/main_view.qml b/main_view.qml
index bce7005..60a85aa 100644
--- a/main_view.qml
+++ b/main_view.qml
@@ -14,9 +14,6 @@ ApplicationWindow {
Component.onCompleted: {
setX(Screen.width / 2 - width / 2);
setY(Screen.height / 2 - height / 2);
-
- this.minimumWidth = width;
- this.minimumHeight = height;
}
menuBar: MenuBar {
@@ -38,6 +35,11 @@ ApplicationWindow {
MenuItem { action: selectRPsAction }
MenuItem { action: selectCPsAction }
}
+
+ Menu {
+ title: "View"
+ MenuItem { action: toggleOptionsAction }
+ }
}
statusBar: StatusBar {
@@ -78,28 +80,28 @@ ApplicationWindow {
anchors.fill: parent
}
- Scatterplot {
- id: rpPlot
- objectName: "rpPlot"
+ LinePlot {
+ id: bundlePlot
+ objectName: "bundlePlot"
x: parent.x
y: parent.y
z: 1
anchors.fill: parent
- glyphSize: 3.0
}
Scatterplot {
- id: cpPlot
- objectName: "cpPlot"
+ id: rpPlot
+ objectName: "rpPlot"
x: parent.x
y: parent.y
z: 2
anchors.fill: parent
+ glyphSize: 3.0
}
- LinePlot {
- id: linePlot
- objectName: "linePlot"
+ Scatterplot {
+ id: cpPlot
+ objectName: "cpPlot"
x: parent.x
y: parent.y
z: 3
@@ -237,234 +239,434 @@ ApplicationWindow {
}
// Options panel
- ColumnLayout {
- Layout.alignment: Qt.AlignTop | Qt.AlignLeft
-
- GroupBox {
- Layout.fillWidth: true
- title: "Control points"
- checkable: true
- __checkbox.onClicked: {
- cpPlot.visible = this.checked;
-
- if (this.checked) {
- cpPlot.z = 0;
- rpPlot.z = 0;
- } else {
- cpPlot.z = 0;
- rpPlot.z = 1;
- }
- }
-
- ColumnLayout {
- GroupBox {
- flat: true
- title: "Colors"
-
- GridLayout {
- columns: 2
+ RowLayout {
+ id: optionsPanel
- Label { text: "Map to:" }
- ComboBox {
- id: cpPlotMetricComboBox
- model: metricsModel
- }
+ ColumnLayout {
+ Layout.alignment: Qt.AlignTop | Qt.AlignLeft
- Label { text: "Color map:" }
- ComboBox {
- id: cpPlotColormapCombo
- model: colormapModel
- onActivated:
- Main.setCPColorScale(model.get(index).value);
- }
+ GroupBox {
+ Layout.fillWidth: true
+ title: "Control points"
+ checkable: true
+ __checkbox.onClicked: {
+ cpPlot.visible = this.checked;
+
+ if (this.checked) {
+ cpPlot.z = 0;
+ rpPlot.z = 0;
+ } else {
+ cpPlot.z = 0;
+ rpPlot.z = 1;
}
}
- GroupBox {
- flat: true
- title: "Glyphs"
-
- GridLayout {
- columns: 2
-
- Label { text: "Size:" }
- SpinBox {
- id: cpGlyphSizeSpinBox
- maximumValue: 100
- minimumValue: 6
- decimals: 1
- stepSize: 1
- value: cpPlot.glyphSize
- onValueChanged: cpPlot.glyphSize = this.value
+ ColumnLayout {
+ GroupBox {
+ flat: true
+ title: "Colors"
+
+ GridLayout {
+ columns: 2
+
+ Label { text: "Map to:" }
+ ComboBox {
+ id: cpPlotMetricComboBox
+ model: metricsModel
+ }
+
+ Label { text: "Color map:" }
+ ComboBox {
+ id: cpPlotColormapCombo
+ model: colormapModel
+ onActivated:
+ Main.setCPColorScale(model.get(index).value);
+ }
}
+ }
- Label { text: "Opacity:" }
- Slider {
- id: cpPlotOpacitySlider
- tickmarksEnabled: true
- stepSize: 0.1
- maximumValue: 1
- minimumValue: 0
- value: cpPlot.opacity
- onValueChanged: cpPlot.opacity = this.value
+ GroupBox {
+ flat: true
+ title: "Glyphs"
+
+ GridLayout {
+ columns: 2
+
+ Label { text: "Size:" }
+ SpinBox {
+ id: cpGlyphSizeSpinBox
+ maximumValue: 100
+ minimumValue: 6
+ decimals: 1
+ stepSize: 1
+ value: cpPlot.glyphSize
+ onValueChanged: cpPlot.glyphSize = this.value
+ }
+
+ Label { text: "Opacity:" }
+ Slider {
+ id: cpPlotOpacitySlider
+ tickmarksEnabled: true
+ stepSize: 0.1
+ maximumValue: 1
+ minimumValue: 0
+ value: cpPlot.opacity
+ onValueChanged: cpPlot.opacity = this.value
+ }
}
}
}
}
- }
-
- GroupBox {
- Layout.fillWidth: true
- title: "Regular points"
- checked: true
- checkable: true
- __checkbox.onClicked: {
- rpPlot.visible = this.checked;
- splat.visible = this.checked;
- }
-
- ColumnLayout {
- GroupBox {
- flat: true
- title: "Colors"
-
- GridLayout {
- columns: 2
- Label { text: "Map to:" }
- ComboBox {
- id: rpPlotMetricComboBox
- model: metricsModel
- }
-
- Label { text: "Color map:" }
- ComboBox {
- id: rpPlotColormapCombo
- model: colormapModel
- onActivated:
- Main.setRPColorScale(model.get(index).value);
- }
- }
+ GroupBox {
+ Layout.fillWidth: true
+ title: "Regular points"
+ checked: true
+ checkable: true
+ __checkbox.onClicked: {
+ rpPlot.visible = this.checked;
+ splat.visible = this.checked;
}
- GroupBox {
- flat: true
- title: "Splat"
-
- GridLayout {
- columns: 2
-
- Label { text: "Blur (α):" }
- SpinBox {
- id: alphaSpinBox
- maximumValue: 100
- minimumValue: 1
- value: splat.alpha
- decimals: 2
- stepSize: 1
- onValueChanged: splat.alpha = this.value
+ ColumnLayout {
+ GroupBox {
+ flat: true
+ title: "Colors"
+
+ GridLayout {
+ columns: 2
+
+ Label { text: "Map to:" }
+ ComboBox {
+ id: rpPlotMetricComboBox
+ model: metricsModel
+ }
+
+ Label { text: "Color map:" }
+ ComboBox {
+ id: rpPlotColormapCombo
+ model: colormapModel
+ onActivated:
+ Main.setRPColorScale(model.get(index).value);
+ }
}
+ }
- Label { text: "Radius (β):" }
- SpinBox {
- id: betaSpinBox
- maximumValue: 100
- minimumValue: 1
- value: splat.beta
- decimals: 2
- stepSize: 1
- onValueChanged: splat.beta = this.value
+ GroupBox {
+ flat: true
+ title: "Splat"
+
+ GridLayout {
+ columns: 2
+
+ Label { text: "Blur (α):" }
+ SpinBox {
+ id: alphaSpinBox
+ maximumValue: 100
+ minimumValue: 1
+ value: splat.alpha
+ decimals: 2
+ stepSize: 1
+ onValueChanged: splat.alpha = this.value
+ }
+
+ Label { text: "Radius (β):" }
+ SpinBox {
+ id: betaSpinBox
+ maximumValue: 100
+ minimumValue: 1
+ value: splat.beta
+ decimals: 2
+ stepSize: 1
+ onValueChanged: splat.beta = this.value
+ }
+
+ Label { text: "Opacity:" }
+ Slider {
+ id: splatOpacitySlider
+ tickmarksEnabled: true
+ stepSize: 0.1
+ maximumValue: 1
+ minimumValue: 0
+ value: splat.opacity
+ onValueChanged: splat.opacity = this.value
+ }
}
+ }
- Label { text: "Opacity:" }
- Slider {
- id: splatOpacitySlider
- tickmarksEnabled: true
- stepSize: 0.1
- maximumValue: 1
- minimumValue: 0
- value: splat.opacity
- onValueChanged: splat.opacity = this.value
+ GroupBox {
+ flat: true
+ title: "Glyphs"
+
+ GridLayout {
+ columns: 2
+
+ Label { text: "Size:" }
+ SpinBox {
+ id: rpGlyphSizeSpinBox
+ maximumValue: 100
+ minimumValue: 2
+ decimals: 1
+ stepSize: 1
+ value: rpPlot.glyphSize
+ onValueChanged: rpPlot.glyphSize = this.value
+ }
+
+ Label { text: "Opacity:" }
+ Slider {
+ id: rpPlotOpacitySlider
+ tickmarksEnabled: true
+ stepSize: 0.1
+ maximumValue: 1
+ minimumValue: 0
+ value: rpPlot.opacity
+ onValueChanged: rpPlot.opacity = this.value
+ }
}
}
}
+ }
- GroupBox {
- flat: true
- title: "Glyphs"
-
- GridLayout {
- columns: 2
-
- Label { text: "Size:" }
- SpinBox {
- id: rpGlyphSizeSpinBox
- maximumValue: 100
- minimumValue: 2
- decimals: 1
- stepSize: 1
- value: rpPlot.glyphSize
- onValueChanged: rpPlot.glyphSize = this.value
+ GroupBox {
+ Layout.fillWidth: true
+ id: metricsGroupBox
+ title: "Projection metrics"
+ property RadioButton current: currentMetricRadioButton
+
+ Column {
+ ExclusiveGroup { id: wrtMetricsGroup }
+
+ RadioButton {
+ id: currentMetricRadioButton
+ text: "Current"
+ exclusiveGroup: wrtMetricsGroup
+ checked: true
+ onClicked: {
+ if (!Main.setObserverType(Main.ObserverCurrent)) {
+ metricsGroupBox.current.checked = true;
+ } else {
+ metricsGroupBox.current = this;
+ }
}
-
- Label { text: "Opacity:" }
- Slider {
- id: rpPlotOpacitySlider
- tickmarksEnabled: true
- stepSize: 0.1
- maximumValue: 1
- minimumValue: 0
- value: rpPlot.opacity
- onValueChanged: rpPlot.opacity = this.value
+ }
+ RadioButton {
+ id: diffPreviousMetricRadioButton
+ text: "Diff. to previous"
+ exclusiveGroup: wrtMetricsGroup
+ onClicked: {
+ if (!Main.setObserverType(Main.ObserverDiffPrevious)) {
+ metricsGroupBox.current.checked = true;
+ } else {
+ metricsGroupBox.current = this;
+ }
+ }
+ }
+ RadioButton {
+ id: diffFirstMetricRadioButton
+ text: "Diff. to first"
+ exclusiveGroup: wrtMetricsGroup
+ onClicked: {
+ if (!Main.setObserverType(Main.ObserverDiffFirst)) {
+ metricsGroupBox.current.checked = true;
+ } else {
+ metricsGroupBox.current = this;
+ }
}
}
}
}
}
- GroupBox {
- Layout.fillWidth: true
- id: metricsGroupBox
- title: "Projection metrics"
- property RadioButton current: currentMetricRadioButton
-
- Column {
- ExclusiveGroup { id: wrtMetricsGroup }
-
- RadioButton {
- id: currentMetricRadioButton
- text: "Current"
- exclusiveGroup: wrtMetricsGroup
- checked: true
- onClicked: {
- if (!Main.setObserverType(Main.ObserverCurrent)) {
- metricsGroupBox.current.checked = true;
- } else {
- metricsGroupBox.current = this;
+ ColumnLayout {
+ Layout.alignment: Qt.AlignTop | Qt.AlignLeft
+
+ GroupBox {
+ Layout.fillWidth: true
+ id: bundlingGroupBox
+ title: "Bundling"
+ checked: true
+ checkable: true
+ __checkbox.onClicked: {
+ bundlePlot.visible = this.checked;
+ }
+
+ ColumnLayout {
+ GroupBox {
+ flat: true
+ title: "Main bundling"
+
+ GridLayout {
+ columns: 2
+
+ Label { text: "Iterations:" }
+ SpinBox {
+ maximumValue: 100
+ minimumValue: 0
+ decimals: 0
+ stepSize: 1
+ value: bundlePlot.iterations
+ onValueChanged: bundlePlot.iterations = this.value
+ }
+
+ Label { text: "Kernel size:" }
+ SpinBox {
+ maximumValue: 100
+ minimumValue: 3
+ decimals: 1
+ stepSize: 1
+ value: bundlePlot.kernelSize
+ onValueChanged: bundlePlot.kernelSize = this.value
+ }
+
+ Label { text: "Smoothing factor:" }
+ Slider {
+ tickmarksEnabled: true
+ stepSize: 0.1
+ maximumValue: 1
+ minimumValue: 0
+ value: bundlePlot.smoothingFactor
+ onValueChanged: bundlePlot.smoothingFactor = this.value
+ }
+
+ Label { text: "Smoothing iterations:" }
+ SpinBox {
+ maximumValue: 100
+ minimumValue: 0
+ decimals: 0
+ stepSize: 1
+ value: bundlePlot.smoothingIterations
+ onValueChanged: bundlePlot.smoothingIterations = this.value
+ }
}
}
- }
- RadioButton {
- id: diffPreviousMetricRadioButton
- text: "Diff. to previous"
- exclusiveGroup: wrtMetricsGroup
- onClicked: {
- if (!Main.setObserverType(Main.ObserverDiffPrevious)) {
- metricsGroupBox.current.checked = true;
- } else {
- metricsGroupBox.current = this;
+
+ GroupBox {
+ flat: true
+ title: "Ends bundling"
+
+ GridLayout {
+ columns: 2
+
+ CheckBox {
+ Layout.columnSpan: 2
+ text: "Block endpoints"
+ checked: bundlePlot.blockEndpoints
+ onClicked: bundlePlot.blockEndpoints = this.checked
+ }
+
+ Label { text: "Iterations:" }
+ SpinBox {
+ maximumValue: 100
+ minimumValue: 0
+ decimals: 0
+ stepSize: 1
+ value: bundlePlot.endsIterations
+ onValueChanged: bundlePlot.endsIterations = this.value
+ }
+
+ Label { text: "Kernel size:" }
+ SpinBox {
+ maximumValue: 100
+ minimumValue: 3
+ decimals: 1
+ stepSize: 1
+ value: bundlePlot.endsKernelSize
+ onValueChanged: bundlePlot.endsKernelSize = this.value
+ }
+
+ Label { text: "Smoothing factor:" }
+ Slider {
+ tickmarksEnabled: true
+ stepSize: 0.1
+ maximumValue: 1
+ minimumValue: 0
+ value: bundlePlot.endsSmoothingFactor
+ onValueChanged: bundlePlot.endsSmoothingFactor = this.value
+ }
}
}
- }
- RadioButton {
- id: diffFirstMetricRadioButton
- text: "Diff. to first"
- exclusiveGroup: wrtMetricsGroup
- onClicked: {
- if (!Main.setObserverType(Main.ObserverDiffFirst)) {
- metricsGroupBox.current.checked = true;
- } else {
- metricsGroupBox.current = this;
+
+ GroupBox {
+ flat: true
+ title: "General"
+
+ GridLayout {
+ columns: 2
+
+ Label { text: "Opacity:" }
+ Slider {
+ tickmarksEnabled: true
+ stepSize: 0.1
+ maximumValue: 1
+ minimumValue: 0
+ value: bundlePlot.opacity
+ onValueChanged: bundlePlot.opacity = this.value
+ }
+
+ Label { text: "Edge sampling:" }
+ SpinBox {
+ maximumValue: 100
+ minimumValue: 3
+ decimals: 1
+ stepSize: 1
+ value: bundlePlot.edgeSampling
+ onValueChanged: bundlePlot.edgeSampling = this.value
+ }
+
+ Label { text: "Advection speed:" }
+ Slider {
+ tickmarksEnabled: true
+ stepSize: 0.1
+ maximumValue: 1
+ minimumValue: 0
+ value: bundlePlot.advectionSpeed
+ onValueChanged: bundlePlot.advectionSpeed = this.value
+ }
+
+ Label { text: "Density estimation:" }
+ RowLayout {
+ ExclusiveGroup { id: densityEstimationGroup }
+ RadioButton {
+ text: "Exact"
+ exclusiveGroup: densityEstimationGroup
+ }
+ RadioButton {
+ text: "Fast"
+ exclusiveGroup: densityEstimationGroup
+ checked: true
+ }
+ }
+
+ Label { text: "Bundle shape:" }
+ RowLayout {
+ ExclusiveGroup { id: bundleShapeGroup }
+ RadioButton {
+ text: "FDEB"
+ exclusiveGroup: bundleShapeGroup
+ checked: true
+ }
+ RadioButton {
+ text: "HEB"
+ exclusiveGroup: bundleShapeGroup
+ }
+ }
+
+ Label { text: "Relaxation:" }
+ Slider {
+ tickmarksEnabled: true
+ stepSize: 0.1
+ maximumValue: 1
+ minimumValue: 0
+ value: bundlePlot.relaxation
+ onValueChanged: bundlePlot.relaxation = this.value
+ }
+
+ CheckBox {
+ Layout.columnSpan: 2
+ text: "Use GPU"
+ checked: bundlePlot.bundleGPU
+ onClicked: bundlePlot.bundleGPU = this.checked
+ }
}
}
}
@@ -562,6 +764,17 @@ ApplicationWindow {
}
Action {
+ id: toggleOptionsAction
+ text: "&Options"
+ shortcut: "Ctrl+O"
+ checkable: true
+ checked: true
+ onToggled: {
+ optionsPanel.visible = this.checked;
+ }
+ }
+
+ Action {
id: selectCPsAction
text: "&Control points"
shortcut: "C"