From 3c92c6256fd796f24db177caf813ba52e2389ff8 Mon Sep 17 00:00:00 2001 From: Samuel Fadel Date: Wed, 13 Apr 2016 12:20:01 -0300 Subject: Bundling now supports selection. Selection is implemented via main() updating the line plot using the current active selection. Beware of bug (issue #23). Also adds support for setting the line width, while changing the default line width to 2 pixels wide. ProjectionHistory: now reports changes to selections relative to the whole dataset, not only CPs/RPs. --- lineplot.cpp | 222 +++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 154 insertions(+), 68 deletions(-) (limited to 'lineplot.cpp') diff --git a/lineplot.cpp b/lineplot.cpp index 35654be..10fff9b 100644 --- a/lineplot.cpp +++ b/lineplot.cpp @@ -19,15 +19,19 @@ #include "geometry.h" #include "scatterplot.h" +static const float EPSILON = 1e-5f; static const int SAMPLES = 128; LinePlot::LinePlot(QQuickItem *parent) : QQuickFramebufferObject(parent) , m_sx(0, 1, 0, 1) , m_sy(0, 1, 0, 1) + , m_brushedItem(-1) + , m_anySelected(false) , m_linesChanged(false) , m_valuesChanged(false) , m_colorScaleChanged(false) + , m_updateOffsets(false) // Q_PROPERTY's , m_iterations(15) @@ -44,6 +48,7 @@ LinePlot::LinePlot(QQuickItem *parent) , m_advectionSpeed(0.5) , m_relaxation(0) , m_bundleGPU(true) + , m_lineWidth(2.0) { setFlag(QQuickItem::ItemHasContents); setTextureFollowsItemSize(false); @@ -66,6 +71,27 @@ void LinePlot::relax() setLinesChanged(true); } +void LinePlot::buildGraph() +{ + Graph g(m_Y.n_rows); + PointSet points; + + m_sx.setRange(0, width()); + m_sy.setRange(0, height()); + for (arma::uword i = 0; i < m_Y.n_rows; i++) { + points.push_back(Point2d(m_sx(m_Y(i, 0)), m_sy(m_Y(i, 1)))); + } + + for (arma::uword k = 0; k < m_indices.n_elem; k += 2) { + arma::uword i = m_indices[k + 0], + j = m_indices[k + 1]; + g(i, j) = g(j, i) = m_values[k]; + } + + m_gdPtr.reset(new GraphDrawing); + m_gdPtr.get()->build(&g, &points); +} + void LinePlot::bundle() { m_gdBundle = *m_gdPtr.get(); @@ -91,6 +117,7 @@ void LinePlot::bundle() } else { bundling.bundleCPU(); } + m_updateOffsets = true; relax(); } @@ -101,45 +128,33 @@ void LinePlot::setLines(const arma::uvec &indices, const arma::mat &Y) return; } - 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)))); - } + m_indices = indices; + emit indicesChanged(m_indices); - for (arma::uword k = 0; k < m_values.size(); k++) { - arma::uword i = indices(2*k + 0), - j = indices(2*k + 1); + m_Y = Y; + emit pointsChanged(m_Y); - g(i, j) = g(j, i) = m_values[k]; - } + // Clear current selection + m_anySelected = false; + m_selection.assign(m_Y.n_rows, false); + emit selectionChanged(m_selection); - m_gdPtr.reset(new GraphDrawing); - m_gdPtr.get()->build(&g, &points); + // Build the line plot's internal representation: a graph where each + // endpoint of a line is a node, each line is a link... + buildGraph(); // ... then bundle the edges bundle(); - emit linesChanged(m_lines); update(); } void LinePlot::setValues(const arma::vec &values) { - if (m_lines.n_rows > 0 - && (values.n_elem > 0 && values.n_elem != m_lines.n_rows)) { - return; - } - m_values.resize(values.n_elem); - std::copy(values.begin(), values.end(), m_values.begin()); + std::transform(values.begin(), values.end(), m_values.begin(), [](float v) { + return std::max(v, EPSILON); + }); emit valuesChanged(values); setValuesChanged(true); @@ -155,6 +170,27 @@ void LinePlot::setScale(const LinearScale &sx, update(); } +void LinePlot::brushItem(int item) +{ + m_brushedItem = item; + m_updateOffsets = true; + update(); +} + +void LinePlot::setSelection(const std::vector &selection) +{ + m_selection = selection; + m_anySelected = std::any_of(m_selection.cbegin(), + m_selection.cend(), + [](bool b) { return b; }); + emit selectionChanged(m_selection); + + // XXX: *possibly* needed; doesn't seem to make much of a difference + //bundle(); + m_updateOffsets = true; + update(); +} + // Q_PROPERTY's void LinePlot::setIterations(int iterations) { if (m_iterations == iterations) { @@ -311,6 +347,18 @@ void LinePlot::setBundleGPU(bool bundleGPU) update(); } +void LinePlot::setLineWidth(float lineWidth) +{ + if (m_lineWidth == lineWidth) { + return; + } + + m_lineWidth = lineWidth; + emit lineWidthChanged(m_lineWidth); + + update(); +} + // ---------------------------------------------------------------------------- class LinePlotRenderer @@ -334,11 +382,15 @@ private: void updateValues(); void updateColormap(); - void copyPolylines(const GraphDrawing *gd); + void copyPolylines(const LinePlot *plot); + void computeOffsets(const LinePlot *plot); + void computeBrushOffsets(const LinePlot *plot); + void computeAllOffsets(const LinePlot *plot); QSize m_size; std::vector m_points; const std::vector *m_values, *m_cmap; + float m_lineWidth; std::vector m_offsets; QQuickWindow *m_window; // used to reset OpenGL state (as per docs) @@ -360,6 +412,9 @@ LinePlotRenderer::LinePlotRenderer() : gl(QOpenGLContext::currentContext()) , m_sx(0.0f, 1.0f, 0.0f, 1.0f) , m_sy(0.0f, 1.0f, 0.0f, 1.0f) + , m_pointsChanged(false) + , m_valuesChanged(false) + , m_colormapChanged(false) { std::fill(&m_transform[0][0], &m_transform[0][0] + 16, 0.0f); m_transform[3][3] = 1.0f; @@ -471,10 +526,6 @@ QOpenGLFramebufferObject *LinePlotRenderer::createFramebufferObject(const QSize void LinePlotRenderer::render() { - if (!m_pointsChanged && !m_valuesChanged && !m_colormapChanged) { - return; - } - // Update OpenGL buffers and textures as needed if (m_pointsChanged) { updatePoints(); @@ -486,11 +537,6 @@ void LinePlotRenderer::render() updateColormap(); } - if (m_offsets.size() < 2) { - // Nothing to draw - return; - } - m_program->bind(); gl.glActiveTexture(GL_TEXTURE0); @@ -506,8 +552,9 @@ void LinePlotRenderer::render() 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.glLineWidth(m_lineWidth); + for (int i = 0; i < m_offsets.size(); i += 2) { + gl.glDrawArrays(GL_LINE_STRIP, m_offsets[i], m_offsets[i + 1]); } gl.glDisable(GL_LINE_SMOOTH); gl.glDisable(GL_BLEND); @@ -517,23 +564,83 @@ void LinePlotRenderer::render() m_window->resetOpenGLState(); } -void LinePlotRenderer::copyPolylines(const GraphDrawing *gd) +void LinePlotRenderer::synchronize(QQuickFramebufferObject *item) { - if (!gd || gd->draw_order.empty()) { - return; + LinePlot *plot = static_cast(item); + + m_pointsChanged = plot->m_linesChanged; + m_valuesChanged = plot->m_valuesChanged; + m_colormapChanged = plot->m_colorScaleChanged; + + m_values = &(plot->values()); + m_cmap = &(plot->colorScale()); + m_lineWidth = plot->m_lineWidth; + m_window = plot->window(); + + if (plot->m_updateOffsets) { + plot->m_updateOffsets = false; + computeOffsets(plot); + } + + if (m_pointsChanged) { + copyPolylines(plot); + } + + plot->m_linesChanged = false; + plot->m_valuesChanged = false; + plot->m_colorScaleChanged = false; +} + +void LinePlotRenderer::computeOffsets(const LinePlot *plot) +{ + if (plot->m_brushedItem >= 0) { + computeBrushOffsets(plot); + } else { + computeAllOffsets(plot); } +} - int pointsNum = 0; +void LinePlotRenderer::computeBrushOffsets(const LinePlot *plot) +{ + int offset = 0; m_offsets.clear(); - m_offsets.reserve(gd->draw_order.size() + 1); - m_offsets.push_back(0); - for (auto &p : gd->draw_order) { - pointsNum += p.second->size(); + for (auto &p : plot->graphDrawing()->draw_order) { + int pointsNum = p.second->size(); + //int item1 = plot->m_indices[2*i]; + //int item2 = plot->m_indices[2*i + 1]; + //if (item1 == plot->m_brushedItem + // || item2 == plot->m_brushedItem) { + m_offsets.push_back(offset); + m_offsets.push_back(pointsNum); + //} + + offset += pointsNum; + } +} + +void LinePlotRenderer::computeAllOffsets(const LinePlot *plot) +{ + int offset = 0; + m_offsets.clear(); + m_offsets.reserve(2 * plot->graphDrawing()->draw_order.size()); + for (auto &p : plot->graphDrawing()->draw_order) { + int pointsNum = p.second->size(); + m_offsets.push_back(offset); m_offsets.push_back(pointsNum); + + offset += pointsNum; + } +} + +void LinePlotRenderer::copyPolylines(const LinePlot *plot) +{ + const GraphDrawing *gd = plot->graphDrawing(); + if (!gd || gd->draw_order.empty()) { + return; } m_points.clear(); - m_points.reserve(2 * pointsNum); + //m_points.reserve(2 * totalPoints); for (auto &p : gd->draw_order) { for (auto &point : *p.second) { m_points.push_back(point.x); @@ -542,27 +649,6 @@ void LinePlotRenderer::copyPolylines(const GraphDrawing *gd) } } -void LinePlotRenderer::synchronize(QQuickFramebufferObject *item) -{ - LinePlot *plot = static_cast(item); - - m_pointsChanged = plot->linesChanged(); - m_valuesChanged = plot->valuesChanged(); - m_colormapChanged = plot->colorScaleChanged(); - - if (m_pointsChanged) { - copyPolylines(plot->graphDrawing()); - } - m_values = &(plot->values()); - m_cmap = &(plot->colorScale()); - m_window = plot->window(); - - // Reset so that we have the correct values by the next synchronize() - plot->setLinesChanged(false); - plot->setValuesChanged(false); - plot->setColorScaleChanged(false); -} - void LinePlotRenderer::updatePoints() { gl.glBindBuffer(GL_ARRAY_BUFFER, m_VBOs[0]); -- cgit v1.2.3