aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSamuel Fadel <samuelfadel@gmail.com>2016-03-15 16:37:07 -0300
committerSamuel Fadel <samuelfadel@gmail.com>2016-03-15 16:37:07 -0300
commit7cb2b5a84adfb1726bb061dfe985a68199118763 (patch)
treed50c73265ed4bf37f45e4b2cd00d32a5c8a01cb1
parent85af9a3daf3a9197a21dbf7d7bd4285755d3d037 (diff)
Initial CUBu support & LinePlot (with bundling) component.
-rw-r--r--CMakeLists.txt22
-rw-r--r--lineplot.cpp383
-rw-r--r--lineplot.h83
-rw-r--r--main.cpp4
-rw-r--r--main_view.qml11
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
diff --git a/main.cpp b/main.cpp
index 0208db4..c7a6f5c 100644
--- a/main.cpp
+++ b/main.cpp
@@ -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
}
}