diff options
author | Samuel Fadel <samuelfadel@gmail.com> | 2016-03-15 16:37:07 -0300 |
---|---|---|
committer | Samuel Fadel <samuelfadel@gmail.com> | 2016-03-15 16:37:07 -0300 |
commit | 7cb2b5a84adfb1726bb061dfe985a68199118763 (patch) | |
tree | d50c73265ed4bf37f45e4b2cd00d32a5c8a01cb1 | |
parent | 85af9a3daf3a9197a21dbf7d7bd4285755d3d037 (diff) |
Initial CUBu support & LinePlot (with bundling) component.
-rw-r--r-- | CMakeLists.txt | 22 | ||||
-rw-r--r-- | lineplot.cpp | 383 | ||||
-rw-r--r-- | lineplot.h | 83 | ||||
-rw-r--r-- | main.cpp | 4 | ||||
-rw-r--r-- | main_view.qml | 11 |
5 files changed, 498 insertions, 5 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index ccc7e5e..69d2179 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,9 @@ -cmake_minimum_required (VERSION 2.8.12) +cmake_minimum_required(VERSION 2.8.12) -project (pm) +project(pm) + +# Change accordingly +get_filename_component(CUBU "../cubu" ABSOLUTE) if ((${CMAKE_CXX_COMPILER_ID} MATCHES "GNU") OR (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")) set(CMAKE_CXX_FLAGS "-std=c++11") @@ -29,6 +32,13 @@ if (OPENMP_FOUND) set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") endif() +find_library(CUBU_LIB + NAMES cubu + PATHS ${CUBU}) +if (CUBU_LIB) + message("Found CUBU: ${CUBU_LIB}") +endif() + # This is the only one tested, might work with other configs set(CUDA_NVCC_FLAGS -arch=compute_30) @@ -49,6 +59,7 @@ cuda_add_executable(pm historygraph.cpp knn.cpp lamp.cpp + lineplot.cpp manipulationhandler.cpp mapscalehandler.cpp measures.cpp @@ -63,10 +74,15 @@ cuda_add_executable(pm voronoisplat.cpp ${RESOURCES}) +include_directories( + ${CUBU} + ${CUBU}/include) + target_link_libraries(pm ${ARMADILLO_LIBRARIES} Qt5::Widgets Qt5::Qml - Qt5::Quick) + Qt5::Quick + ${CUBU_LIB}) install(TARGETS pm RUNTIME DESTINATION ${CMAKE_SOURCE_DIR}) diff --git a/lineplot.cpp b/lineplot.cpp new file mode 100644 index 0000000..fb622ee --- /dev/null +++ b/lineplot.cpp @@ -0,0 +1,383 @@ +#include "lineplot.h" + +#include <algorithm> +#include <cmath> +#include <limits> + +#include <QQuickWindow> +#include <QOpenGLFunctions> +#include <QOpenGLShaderProgram> +#include <QOpenGLVertexArrayObject> +#include <QSGGeometryNode> +#include <QSGSimpleRectNode> + +// From CUBu +#include "cpubundling.h" +#include "graph.h" + +#include "continuouscolorscale.h" +#include "geometry.h" +#include "scatterplot.h" + +LinePlot::LinePlot(QQuickItem *parent) + : QQuickFramebufferObject(parent) + , m_sx(0, 1, 0, 1) + , m_sy(0, 1, 0, 1) + , m_xyChanged(false) + , m_valuesChanged(false) + , m_colorScaleChanged(false) + , m_bundleGPU(true) +{ + setTextureFollowsItemSize(true); +} + +void LinePlot::setColorScale(const ColorScale *colorScale) +{ + m_colorScale = colorScale; + if (m_values.size() > 0) { + // FIXME + m_colorScaleChanged = true; + update(); + } +} + +void LinePlot::bundle() +{ + Graph g(m_xy.n_rows); + PointSet points; + + 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])); + + if (i > 0) { + g(i - 1, i) = m_values[i]; + g(i, i - 1) = m_values[i]; + } + } + + m_gdPtr.reset(new GraphDrawing); + m_gdPtr.get()->build(&g, &points); + + CPUBundling bundling(std::min(width(), height())); + bundling.setInput(m_gdPtr.get()); + if (m_bundleGPU) { + bundling.bundleGPU(); + } else { + bundling.bundleCPU(); + } +} + +void LinePlot::setXY(const arma::mat &xy) +{ + if (xy.n_cols != 2) { + return; + } + + m_xy = xy; + m_xyChanged = true; + emit xyChanged(m_xy); + + // 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 + bundle(); + + 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)) { + 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); + emit valuesChanged(values); + + m_valuesChanged = true; + update(); +} + +void LinePlot::setScale(const LinearScale<float> &sx, + const LinearScale<float> &sy) +{ + m_sx = sx; + m_sy = sy; + emit scaleChanged(m_sx, m_sy); + update(); +} + +// ---------------------------------------------------------------------------- + +class LinePlotRenderer + : public QQuickFramebufferObject::Renderer +{ +public: + LinePlotRenderer(); + virtual ~LinePlotRenderer(); + +protected: + QOpenGLFramebufferObject *createFramebufferObject(const QSize &size); + void render(); + void synchronize(QQuickFramebufferObject *item); + +private: + void setupShaders(); + void setupVAOs(); + void setupTextures(); + + 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; + 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; + bool m_pointsChanged, m_valuesChanged, m_colormapChanged; +}; + +QQuickFramebufferObject::Renderer *LinePlot::createRenderer() const +{ + return new LinePlotRenderer; +} + +LinePlotRenderer::LinePlotRenderer() + : m_sx(0, 1, 0, 1) + , m_sy(0, 1, 0, 1) + , gl(QOpenGLContext::currentContext()) +{ + std::fill(&m_transform[0][0], &m_transform[0][0] + 16, 0.0f); + m_transform[3][3] = 1.0f; + + setupShaders(); + setupVAOs(); + setupTextures(); +} + +void LinePlotRenderer::setupShaders() +{ + m_program = new QOpenGLShaderProgram; + m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, +R"EOF(#version 440 + +uniform mat4 transform; + +in vec2 vert; +in float scalar; + +out float value; + +void main() { + gl_Position = transform * vec4(vert, 0.0, 1.0); + value = scalar; +} +)EOF"); + m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, +R"EOF(#version 440 + +uniform sampler2D colormap; +in float value; + +layout (location = 0) out vec4 fragColor; + +vec3 getRGB(float value) { + return texture(colormap, vec2(mix(0.005, 0.995, value), 0)).rgb; +} + +void main() { + fragColor = vec4(getRGB(value), 1.0); +} +)EOF"); + m_program->link(); +} + +void LinePlotRenderer::setupVAOs() +{ + gl.glGenBuffers(2, m_VBOs); + + m_VAO.create(); + m_VAO.bind(); + gl.glBindBuffer(GL_ARRAY_BUFFER, m_VBOs[0]); + int vertAttrib = m_program->attributeLocation("vert"); + gl.glVertexAttribPointer(vertAttrib, 2, GL_FLOAT, GL_FALSE, 0, 0); + gl.glEnableVertexAttribArray(vertAttrib); + + gl.glBindBuffer(GL_ARRAY_BUFFER, m_VBOs[1]); + int valueAttrib = m_program->attributeLocation("scalar"); + gl.glVertexAttribPointer(valueAttrib, 1, GL_FLOAT, GL_FALSE, 0, 0); + gl.glEnableVertexAttribArray(valueAttrib); + m_VAO.release(); +} + +void LinePlotRenderer::setupTextures() +{ + gl.glGenTextures(1, &m_colormapTex); + gl.glBindTexture(GL_TEXTURE_2D, m_colormapTex); + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +} + +LinePlotRenderer::~LinePlotRenderer() +{ + delete m_program; +} + +QOpenGLFramebufferObject *LinePlotRenderer::createFramebufferObject(const QSize &size) +{ + m_size = size; + return QQuickFramebufferObject::Renderer::createFramebufferObject(m_size); +} + +void LinePlotRenderer::render() +{ + if (!m_pointsChanged || !m_valuesChanged || !m_colormapChanged) { + return; + } + + // Update OpenGL buffers and textures as needed + if (m_pointsChanged) { + updatePoints(); + } + if (m_valuesChanged) { + updateValues(); + } + if (m_colormapChanged) { + updateColormap(); + } + + 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); + + 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]); + } + m_VAO.release(); + + m_program->release(); + m_window->resetOpenGLState(); +} + +void LinePlotRenderer::copyPolylines(const GraphDrawing *gd) +{ + if (!gd || gd->draw_order.empty()) { + return; + } + + int pointsNum = 0; + m_offsets.clear(); + m_offsets.push_back(0); + for (std::pair<float, const GraphDrawing::Polyline *> 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; + } + + i += p.second->size(); + k++; + } +} + +void LinePlotRenderer::synchronize(QQuickFramebufferObject *item) +{ + LinePlot *plot = static_cast<LinePlot *>(item); + + m_pointsChanged = plot->xyChanged(); + m_valuesChanged = plot->valuesChanged(); + m_colormapChanged = plot->colorScaleChanged(); + + copyPolylines(plot->graphDrawing()); + m_values = &(plot->values()); + m_sx = plot->scaleX(); + m_sy = plot->scaleY(); + m_window = plot->window(); + + // Reset so that we have the correct values by the next synchronize() + plot->setXYChanged(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; +} + +void LinePlotRenderer::updateValues() +{ + gl.glBindBuffer(GL_ARRAY_BUFFER, m_VBOs[1]); + gl.glBufferData(GL_ARRAY_BUFFER, m_values->size() * sizeof(float), + m_values->data(), GL_DYNAMIC_DRAW); + + m_valuesChanged = false; +} + +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()); + + m_colormapChanged = false; +} diff --git a/lineplot.h b/lineplot.h new file mode 100644 index 0000000..060171d --- /dev/null +++ b/lineplot.h @@ -0,0 +1,83 @@ +#ifndef LINEPLOT_H +#define LINEPLOT_H + +#include <memory> +#include <vector> + +#include <QQuickFramebufferObject> + +#include <armadillo> + +// From CUBu +#include "gdrawing.h" + +#include "colorscale.h" +#include "scale.h" + +class LinePlot + : public QQuickFramebufferObject +{ + Q_OBJECT +public: + static const int PADDING = 20; + + LinePlot(QQuickItem *parent = 0); + + void setColorScale(const ColorScale *colorScale); + void setAutoScale(bool autoScale); + + const GraphDrawing *graphDrawing() const { return m_gdPtr.get(); } + const std::vector<float> &values() const { return m_values; } + 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 colorScaleChanged() const { return m_colorScaleChanged; } + + void setXYChanged(bool xyChanged) { + m_xyChanged = xyChanged; + } + void setValuesChanged(bool valuesChanged) { + m_valuesChanged = valuesChanged; + } + void setColorScaleChanged(bool colorScaleChanged) { + m_colorScaleChanged = colorScaleChanged; + } + +signals: + void xyChanged(const arma::mat &xy); + void valuesChanged(const arma::vec &values) const; + void scaleChanged(const LinearScale<float> &sx, + const LinearScale<float> &sy) const; + +public slots: + void setXY(const arma::mat &xy); + void setValues(const arma::vec &values); + void setScale(const LinearScale<float> &sx, + const LinearScale<float> &sy); + +private: + void bundle(); + + // Data + arma::mat m_xy; + std::vector<float> m_values; + + // Visuals + const ColorScale *m_colorScale; + + void autoScale(); + bool m_autoScale; + LinearScale<float> m_sx, m_sy; + + std::unique_ptr<GraphDrawing> m_gdPtr; + + // Internal state + bool m_xyChanged, m_valuesChanged, m_colorScaleChanged; + bool m_bundleGPU; +}; + +#endif // LINEPLOT_H @@ -14,6 +14,7 @@ #include "continuouscolorscale.h" #include "scatterplot.h" #include "voronoisplat.h" +#include "lineplot.h" #include "barchart.h" #include "colormap.h" #include "transitioncontrol.h" @@ -23,7 +24,7 @@ #include "selectionhandler.h" #include "brushinghandler.h" -static const int RNG_SEED = 96123; +static const int RNG_SEED = 123; static QObject *mainProvider(QQmlEngine *engine, QJSEngine *scriptEngine) { @@ -143,6 +144,7 @@ int main(int argc, char **argv) qmlRegisterType<Scatterplot>("PM", 1, 0, "Scatterplot"); qmlRegisterType<BarChart>("PM", 1, 0, "BarChart"); qmlRegisterType<VoronoiSplat>("PM", 1, 0, "VoronoiSplat"); + qmlRegisterType<LinePlot>("PM", 1, 0, "LinePlot"); qmlRegisterType<Colormap>("PM", 1, 0, "Colormap"); qmlRegisterType<TransitionControl>("PM", 1, 0, "TransitionControl"); qmlRegisterSingletonType<Main>("PM", 1, 0, "Main", mainProvider); diff --git a/main_view.qml b/main_view.qml index 900969e..bce7005 100644 --- a/main_view.qml +++ b/main_view.qml @@ -97,12 +97,21 @@ ApplicationWindow { anchors.fill: parent } + LinePlot { + id: linePlot + objectName: "linePlot" + x: parent.x + y: parent.y + z: 3 + anchors.fill: parent + } + TransitionControl { id: plotTC objectName: "plotTC" x: parent.x y: parent.y - z: 3 + z: 4 anchors.fill: parent } } |