aboutsummaryrefslogtreecommitdiff
path: root/scatterplot.cpp
diff options
context:
space:
mode:
authorSamuel Fadel <samuel@nihil.ws>2023-06-04 13:02:14 +0200
committerSamuel Fadel <samuel@nihil.ws>2023-06-04 13:02:14 +0200
commitfb23c8d47f6dcef429423256d8dddcc0f7184fc4 (patch)
tree34e0032f28df5807e4abd90d6b1b4baad24d2991 /scatterplot.cpp
parent0f34fd437efb936ef29ac91186321aa7251fbfb1 (diff)
Further advances in nuklear port.
Rendering now looks similar to Qt version, needs a few tweaks: * Proper multisampling * Background Missing features: * Barcharts * Interactivity (e.g. brushing/linking in all objects) * History view of interactions
Diffstat (limited to 'scatterplot.cpp')
-rw-r--r--scatterplot.cpp303
1 files changed, 286 insertions, 17 deletions
diff --git a/scatterplot.cpp b/scatterplot.cpp
index fc31daa..a7194e8 100644
--- a/scatterplot.cpp
+++ b/scatterplot.cpp
@@ -3,8 +3,8 @@
#include <algorithm>
#include <cmath>
+#include "colormap.h"
#include "continuouscolorscale.h"
-#include "geometry.h"
// Glyphs settings
static const Color DEFAULT_GLYPH_COLOR(255, 255, 255);
@@ -31,28 +31,197 @@ static const Color SELECTION_COLOR(128, 128, 128, 96);
// static const Qt::MouseButton NORMAL_BUTTON = Qt::LeftButton;
// static const Qt::MouseButton SPECIAL_BUTTON = Qt::RightButton;
+static const char *shaderVertex = R"EOF(#version 330
+
+precision mediump float;
+
+uniform float colormask;
+uniform mat4 transform;
+uniform sampler2D colormap;
+
+layout (location = 0) in vec2 pos;
+layout (location = 1) in float value;
+// layout (location = 2) in float opacity;
+
+out VS_OUT {
+ vec4 color;
+} vs_out;
+
+vec3 getRGB(float value) {
+ return texture(colormap, vec2(mix(0.005, 0.995, value), 0)).rgb;
+}
+
+void main()
+{
+ vs_out.color = vec4(mix(vec3(1.0), getRGB(value), colormask), 1.0);
+ // vs_out.color = vec4(1.0);
+ gl_Position = transform * vec4(pos.xy, 0.0, 1.0);
+}
+)EOF";
+
+static const char *shaderFragment = R"EOF(#version 330
+in vec4 color;
+
+out vec4 FragColor;
+
+void main()
+{
+ FragColor = color;
+ // FragColor = vec4(1.0);
+}
+)EOF";
+
+static const char *shaderGeometry = R"EOF(#version 330
+layout (points) in;
+layout (triangle_strip, max_vertices = 16) out;
+
+uniform float size;
+
+in VS_OUT {
+ vec4 color;
+} gs_in[];
+
+out vec4 color;
+
+#define PI 3.1415926538
+
+void main() {
+ int num = 8;
+ float theta = 2 * PI / num;
+ float c = cos(theta);
+ float s = sin(theta);
+ vec4 p = vec4(size, 0.0, 0.0, 0.0);
+ // gs_in[0] as there is only one input vertex
+ color = gs_in[0].color;
+ for (int i = 0; i < num; i++) {
+ gl_Position = gl_in[0].gl_Position + p;
+ EmitVertex();
+
+ gl_Position = gl_in[0].gl_Position + vec4(p.x, -p.y, 0.0, 0.0);
+ EmitVertex();
+
+ p.xy = vec2(c*p.x - s*p.y, s*p.x + c*p.y);
+ }
+ EndPrimitive();
+}
+)EOF";
+
+static const char *shaderOutlineFragment = R"EOF(#version 330
+in vec4 color;
+
+out vec4 FragColor;
+
+void main()
+{
+ FragColor = vec4(0.0, 0.0, 0.0, 1.0);
+}
+)EOF";
+
+static const char *shaderOutlineGeometry = R"EOF(#version 330
+layout (points) in;
+layout (line_strip, max_vertices = 16) out;
+
+uniform float size;
+
+in VS_OUT {
+ vec4 color;
+} gs_in[];
+
+out vec4 color;
+
+#define PI 3.1415926538
+
+void main() {
+ int num = 15;
+ float theta = 2 * PI / num;
+ float c = cos(theta);
+ float s = sin(theta);
+ vec4 p = vec4(size, 0.0, 0.0, 0.0);
+ // gs_in[0] as there is only one input vertex
+ color = gs_in[0].color;
+ for (int i = 0; i < num; i++) {
+ gl_Position = gl_in[0].gl_Position + p;
+ EmitVertex();
+
+ p.xy = vec2(c*p.x - s*p.y, s*p.x + c*p.y);
+ }
+ gl_Position = gl_in[0].gl_Position + p;
+ EmitVertex();
+ EndPrimitive();
+}
+)EOF";
+
Scatterplot::Scatterplot()
: m_glyphSize(DEFAULT_GLYPH_SIZE)
, m_colorScale(0)
- , m_autoScale(true)
+ , m_autoScale(false)
, m_sx(0, 1, 0, 1)
, m_sy(0, 1, 0, 1)
, m_anySelected(false)
, m_brushedItem(-1)
, m_interactionState(StateNone)
, m_dragEnabled(false)
+ , m_redraw(false)
, m_shouldUpdateGeometry(false)
, m_shouldUpdateMaterials(false)
{
+ glGenFramebuffers(1, &m_FBO);
+
+ glGenBuffers(1, &m_pointsVBO);
+ glGenBuffers(1, &m_valuesVBO);
+ glGenVertexArrays(1, &m_VAO);
+ glBindVertexArray(m_VAO);
+ glBindBuffer(GL_ARRAY_BUFFER, m_pointsVBO);
+ glEnableVertexAttribArray(0);
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
+ // glVertexAttribDivisor(0, 1);
+
+ glBindBuffer(GL_ARRAY_BUFFER, m_valuesVBO);
+ glEnableVertexAttribArray(1);
+ glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 0, nullptr);
+ glVertexAttribDivisor(1, 1);
+ glBindVertexArray(0);
+
+ glGenTextures(1, &m_outTex);
+
+ m_shader = std::make_unique<Shader>(
+ shaderVertex,
+ shaderFragment,
+ shaderGeometry);
+
+ m_shaderOutline = std::make_unique<Shader>(
+ shaderVertex,
+ shaderOutlineFragment,
+ shaderOutlineGeometry);
+
+ std::fill(&m_transform[0][0], &m_transform[0][0] + 16, 0.0f);
+ m_transform[3][3] = 1.0f;
}
-void Scatterplot::setColorScale(std::shared_ptr<const ColorScale> colorScale)
+Scatterplot::~Scatterplot()
{
- m_colorScale = colorScale;
- if (m_colorData.n_elem > 0) {
- m_shouldUpdateMaterials = true;
- update();
- }
+ glDeleteFramebuffers(1, &m_FBO);
+}
+
+void Scatterplot::setSize(size_t width, size_t height)
+{
+ m_width = width;
+ m_height = height;
+
+ glBindTexture(GL_TEXTURE_2D, m_outTex);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, m_width, m_height, 0, GL_RGBA,
+ GL_FLOAT, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ update();
+}
+
+void Scatterplot::setColormap(GLuint texture)
+{
+ m_colormapTex = texture;
+ update();
}
arma::mat Scatterplot::XY() const
@@ -122,8 +291,6 @@ void Scatterplot::setScale(const LinearScale<float> &sx, const LinearScale<float
scaleChanged(m_sx, m_sy);
updateQuadTree();
-
- m_shouldUpdateGeometry = true;
update();
}
@@ -150,13 +317,115 @@ void Scatterplot::setGlyphSize(float glyphSize)
m_glyphSize = glyphSize;
glyphSizeChanged(m_glyphSize);
-
- m_shouldUpdateGeometry = true;
update();
}
void Scatterplot::update()
{
+ m_redraw = true;
+
+ if (m_shouldUpdateGeometry) {
+ updateGeometry();
+ }
+ if (m_shouldUpdateMaterials) {
+ updateMaterials();
+ }
+ updateTransform();
+}
+
+void Scatterplot::updateGeometry()
+{
+ if (m_xy.n_rows < 1 || m_xy.n_cols != 2) {
+ return;
+ }
+
+ if (m_colorData.n_elem > 0 && m_colorData.n_elem != m_xy.n_rows) {
+ // Old values are no longer valid, clean up
+ m_colorData.fill(0.0f);
+ }
+
+ // Copy 'points' to internal data structure(s)
+ m_points.resize(m_xy.n_rows);
+ const double *col_x = m_xy.colptr(0);
+ const double *col_y = m_xy.colptr(1);
+ for (unsigned i = 0; i < m_xy.n_rows; i++) {
+ m_points[i].set(col_x[i], col_y[i]);
+ }
+
+ glBindBuffer(GL_ARRAY_BUFFER, m_pointsVBO);
+ glBufferData(GL_ARRAY_BUFFER, m_points.size() * sizeof(vec2),
+ m_points.data(), GL_DYNAMIC_DRAW);
+
+ m_shouldUpdateGeometry = false;
+}
+
+void Scatterplot::updateMaterials()
+{
+ glBindBuffer(GL_ARRAY_BUFFER, m_valuesVBO);
+ glBufferData(GL_ARRAY_BUFFER, m_colorData.n_elem * sizeof(float),
+ m_colorData.memptr(), GL_DYNAMIC_DRAW);
+
+ m_shouldUpdateMaterials = false;
+}
+
+void Scatterplot::updateTransform()
+{
+ float padding = Scatterplot::PADDING;
+ float offsetX = padding / m_width, offsetY = padding / m_height;
+ updateTransform4x4(m_sx, m_sy, offsetX, offsetY, m_transform);
+}
+
+void Scatterplot::draw()
+{
+ if (!m_redraw) {
+ return;
+ }
+
+ int originalFBO;
+ glGetIntegerv(GL_FRAMEBUFFER_BINDING, &originalFBO);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, m_FBO);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_2D, m_outTex, 0);
+
+ glViewport(0, 0, m_width, m_height);
+ m_shader->use();
+ m_shader->setUniform("transform", m_transform);
+ m_shader->setUniform("size", m_glyphSize / m_width);
+ m_shader->setUniform("colormask", m_colorData.n_elem == 0 ? 0.0f : 1.0f);
+
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, m_colormapTex);
+ m_shader->setUniform("colormap", 0);
+
+ glEnable(GL_BLEND);
+ // glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glBindVertexArray(m_VAO);
+ glDrawArrays(GL_POINTS, 0, m_points.size());
+ m_shader->release();
+
+ // Swap out shaders to draw outline
+ m_shaderOutline->use();
+ m_shaderOutline->setUniform("transform", m_transform);
+ m_shaderOutline->setUniform("size", m_glyphSize / m_width);
+ m_shaderOutline->setUniform("colormap", 0);
+ glDrawArrays(GL_POINTS, 0, m_points.size());
+
+ glBindVertexArray(0);
+ m_shaderOutline->release();
+
+ /*
+ GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ if (status != GL_FRAMEBUFFER_COMPLETE) {
+ checkGLError("draw():");
+ }
+ */
+
+ glBindFramebuffer(GL_FRAMEBUFFER, originalFBO);
+ m_redraw = false;
}
/*
@@ -590,8 +859,8 @@ void Scatterplot::applyManipulation()
m_sy.inverse();
m_sx.inverse();
- float tx = m_dragCurrentPos.x() - m_dragOriginPos.x();
- float ty = m_dragCurrentPos.y() - m_dragOriginPos.y();
+ float tx = m_dragCurrentPos.x - m_dragOriginPos.x;
+ float ty = m_dragCurrentPos.y - m_dragOriginPos.y;
for (std::vector<bool>::size_type i = 0; i < m_selection.size(); i++) {
if (m_selection[i]) {
@@ -609,10 +878,10 @@ void Scatterplot::applyManipulation()
void Scatterplot::updateQuadTree()
{
- m_sx.setRange(PADDING, width() - PADDING);
- m_sy.setRange(height() - PADDING, PADDING);
+ m_sx.setRange(PADDING, m_width - PADDING);
+ m_sy.setRange(m_height - PADDING, PADDING);
- m_quadtree.reset(new QuadTree(RectF(x(), y(), width(), height())));
+ m_quadtree.reset(new QuadTree(RectF(0.0f, 0.0f, m_width, m_height)));
for (arma::uword i = 0; i < m_xy.n_rows; i++) {
const arma::rowvec &row = m_xy.row(i);
m_quadtree->insert(m_sx(row[0]), m_sy(row[1]), (int) i);