#include "barchart.h" #include <algorithm> #include <cmath> #include <numeric> #include "continuouscolorscale.h" #include "geometry.h" static const Color OUTLINE_COLOR(0, 0, 0); static const Color BRUSH_COLOR(0, 0, 0); static const Color BAR_COLOR(128, 128, 128); static const Color PRESELECTION_COLOR(128, 128, 128, 96); static const Color SELECTION_VISIBLE_COLOR(128, 128, 128, 96); static const Color SELECTION_INVISIBLE_COLOR(0, 0, 0, 0); static const float DEFAULT_OPACITY = 0.8f; template<typename T> static inline T clamp(T value, T min, T max) { return std::min(std::max(min, value), max); } static const char *shaderVertex = R"EOF(#version 330 precision mediump float; uniform int num; uniform float minimum; uniform float maximum; uniform sampler2D colormap; layout (location = 0) in float value; 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() { float v = (value - minimum) / (maximum - minimum); vs_out.color = vec4(getRGB(v), 1.0); // Position here is top-left of the rectangle (bar), the geometry shader does // the rest of the job float width = max(0.01, 2.0 / num); gl_Position = vec4(gl_VertexID * width - 1.0, mix(1.0, -1.0, v), 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 = 4) out; uniform int num; in VS_OUT { vec4 color; } gs_in[]; out vec4 color; void main() { float width = max(0.01, 2.0 / num); float x = gl_in[0].gl_Position.x; float y = gl_in[0].gl_Position.y; color = gs_in[0].color; // top-left gl_Position = vec4(x, y, 0.0, 1.0); EmitVertex(); // bottom-left gl_Position = vec4(x, 1.0, 0.0, 1.0); EmitVertex(); // top-right gl_Position = vec4(x + width, y, 0.0, 1.0); EmitVertex(); // bottom-right gl_Position = vec4(x + width, 1.0, 0.0, 1.0); EmitVertex(); EndPrimitive(); } )EOF"; BarChart::BarChart() : m_redraw(false) , m_shouldUpdateBars(false) , m_shouldUpdatePreSelection(false) , m_dragStartPos(-1.0f) , m_dragLastPos(-1.0f) , m_shouldUpdateSelection(false) , m_brushedItem(-1) , m_scale(0.0f, 1.0f, 0.0f, 1.0f) , m_width(0) , m_height(0) { glGenFramebuffers(1, &m_FBO); glGenTextures(1, &m_outTex); glGenBuffers(1, &m_VBO); glGenVertexArrays(1, &m_VAO); glBindVertexArray(m_VAO); glBindBuffer(GL_ARRAY_BUFFER, m_VBO); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, 0, nullptr); // glVertexAttribDivisor(0, 1); glBindVertexArray(0); m_shader = std::make_unique<Shader>( shaderVertex, shaderFragment, shaderGeometry); } void BarChart::update() { m_redraw = m_values.n_elem != 0; if (m_shouldUpdateBars) { // TODO updateBars(); } } void BarChart::updateValues(const arma::vec &values) { if (m_values.n_elem != values.n_elem) { return; } m_values = values; m_shouldUpdateBars = true; update(); } void BarChart::updateBars() { std::vector<float> sortedValues(m_values.n_elem); size_t i = 0; for (auto j : m_originalIndices) { sortedValues[i] = m_values[j]; i++; } glBindBuffer(GL_ARRAY_BUFFER, m_VBO); glBufferData(GL_ARRAY_BUFFER, sortedValues.size() * sizeof(float), sortedValues.data(), GL_DYNAMIC_DRAW); m_shouldUpdateBars = false; } void BarChart::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 BarChart::setValues(const arma::vec &values) { m_values = values; if (m_selection.size() != m_values.n_elem) { m_selection.assign(m_values.n_elem, false); } m_originalIndices.resize(m_values.n_elem); m_currentIndices.resize(m_values.n_elem); // setAcceptHoverEvents(m_values.n_elem > 0); if (m_values.n_elem > 0) { m_scale.setDomain(m_values.min(), m_values.max()); // m_originalIndices[i] == the original (datum) index of item i std::iota(m_originalIndices.begin(), m_originalIndices.end(), 0); std::sort(m_originalIndices.begin(), m_originalIndices.end(), [this](int i, int j) { return m_values[i] > m_values[j]; }); // m_currentIndices[i] == the displayed position of datum i int k = 0; for (auto i: m_originalIndices) { m_currentIndices[i] = k++; } } valuesChanged(values); m_shouldUpdateSelection = true; m_shouldUpdateBars = true; update(); } void BarChart::setColormap(GLuint texture) { m_colormapTex = texture; update(); } void BarChart::setSelection(const std::vector<bool> &selection) { m_selection = selection; selectionChanged(m_selection); // Our selection changed but we don't want to display it unless we have the // right number of values if (m_values.size() == m_selection.size()) { m_shouldUpdateSelection = true; update(); } } void BarChart::brushItem(int item) { if (item < 0) { m_brushedItem = item; itemBrushed(m_brushedItem, 0.0f); } else { // safe comparison: we just checked for negative values if (m_values.n_elem == 0 || item > m_values.n_elem - 1) { return; } m_brushedItem = m_currentIndices[item]; item = m_originalIndices[m_brushedItem]; itemBrushed(item, m_values[item]); } update(); } void BarChart::draw() { if (!m_redraw) { return; } m_redraw = false; 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("num", static_cast<GLint>(m_values.size())); m_shader->setUniform("minimum", static_cast<GLfloat>(m_values.min())); m_shader->setUniform("maximum", static_cast<GLfloat>(m_values.max())); 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); glBlendFunc(GL_ONE, GL_ZERO); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT); glBindVertexArray(m_VAO); glDrawArrays(GL_POINTS, 0, m_values.size()); m_shader->release(); glDisable(GL_BLEND); /* GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { checkGLError("draw():"); } */ glBindFramebuffer(GL_FRAMEBUFFER, originalFBO); } /* QSGNode *BarChart::newSceneGraph() const { // NOTE: scene graph structure is as follows: // root [ // barsNode [ // barNode ... // ] // selectedBarsNode [ // selectedBarNode ... // ] // preSelectionNode // brushNode // ] QSGTransformNode *root = new QSGTransformNode; // The node that has all bars as children root->appendChildNode(new QSGNode); // The node that has all selected bars as children root->appendChildNode(new QSGNode); // The node for the preselection QSGSimpleRectNode *preSelectionNode = new QSGSimpleRectNode; preSelectionNode->setColor(PRESELECTION_COLOR); root->appendChildNode(preSelectionNode); // The node for drawing the brush QSGGeometryNode *brushGeomNode = new QSGGeometryNode; QSGGeometry *brushGeom = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 4); brushGeom->setDrawingMode(GL_POLYGON); brushGeom->setVertexDataPattern(QSGGeometry::DynamicPattern); brushGeom->setLineWidth(1.0f); QSGFlatColorMaterial *brushMaterial = new QSGFlatColorMaterial; brushMaterial->setColor(BRUSH_COLOR); brushGeomNode->setGeometry(brushGeom); brushGeomNode->setMaterial(brushMaterial); brushGeomNode->setFlags(QSGNode::OwnsGeometry | QSGNode::OwnsMaterial); root->appendChildNode(brushGeomNode); return root; } */ /* QSGNode *BarChart::newBarNode() const { // A bar node is: // opacityNode [outlineGeomNode barGeomNode] //QSGGeometryNode *outlineGeomNode = new QSGGeometryNode; //QSGGeometry *outlineGeometry = // new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 4); //outlineGeometry->setDrawingMode(GL_LINE_LOOP); //outlineGeometry->setVertexDataPattern(QSGGeometry::DynamicPattern); //outlineGeomNode->setGeometry(outlineGeometry); //outlineGeomNode->setFlag(QSGNode::OwnsGeometry); //QSGFlatColorMaterial *material = new QSGFlatColorMaterial; //material->setColor(OUTLINE_COLOR); //outlineGeomNode->setMaterial(material); //outlineGeomNode->setFlag(QSGNode::OwnsMaterial); QSGGeometryNode *barGeomNode = new QSGGeometryNode; QSGGeometry *barGeometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 4); barGeometry->setDrawingMode(GL_POLYGON); barGeometry->setVertexDataPattern(QSGGeometry::DynamicPattern); barGeomNode->setGeometry(barGeometry); barGeomNode->setFlag(QSGNode::OwnsGeometry); QSGFlatColorMaterial *material = new QSGFlatColorMaterial; material->setColor(BAR_COLOR); barGeomNode->setMaterial(material); barGeomNode->setFlag(QSGNode::OwnsMaterial); QSGOpacityNode *opacityNode = new QSGOpacityNode; opacityNode->setOpacity(DEFAULT_OPACITY); opacityNode->appendChildNode(barGeomNode); //opacityNode->appendChildNode(outlineGeomNode); return opacityNode; } */ /* QSGNode *BarChart::newSelectionBarNode() const { QSGSimpleRectNode *node = new QSGSimpleRectNode; return node; } */ /* void BarChart::updateViewport(QSGNode *root) const { QSGTransformNode *viewportNode = static_cast<QSGTransformNode *>(root); QMatrix4x4 viewport; viewport.scale(width(), height()); viewportNode->setMatrix(viewport); } */ /* void BarChart::updateBarNodeGeom(QSGNode *barNode, float x, float barWidth, float barHeight) const { float y = 1.0f - barHeight; QSGGeometryNode *barGeomNode = static_cast<QSGGeometryNode *>(barNode->firstChild()); updateRectGeometry(barGeomNode->geometry(), x, y, barWidth, barHeight); barGeomNode->markDirty(QSGNode::DirtyGeometry); //QSGGeometryNode *outlineGeomNode = // static_cast<QSGGeometryNode *>(barGeomNode->nextSibling()); //updateRectGeometry(outlineGeomNode->geometry(), x, y, barWidth, barHeight); //outlineGeomNode->markDirty(QSGNode::DirtyGeometry); } */ /* void BarChart::updateBarNodeColor(QSGNode *barNode, const QColor &color) const { QSGGeometryNode *barGeomNode = static_cast<QSGGeometryNode *>(barNode->firstChild()); QSGFlatColorMaterial *material = static_cast<QSGFlatColorMaterial *>(barGeomNode->material()); material->setColor(color); barGeomNode->markDirty(QSGNode::DirtyMaterial); } void BarChart::updateBars(QSGNode *node) const { int numValues = (int) m_values.n_elem; float barWidth = 1.0f / numValues; // First, make sure we have the same number of values & bars while (numValues > node->childCount()) { QSGNode *barNode = newBarNode(); node->prependChildNode(barNode); } while (numValues < node->childCount()) { // NOTE: as stated in docs, QSGNode's children are stored in a // linked list. Hence, this operation should be as fast as expected node->removeChildNode(node->firstChild()); } // Then, update each bar to reflect the values node = node->firstChild(); float x = 0; for (auto it = m_originalIndices.cbegin(); it != m_originalIndices.cend(); it++) { updateBarNodeGeom(node, x, barWidth, m_scale(m_values[*it])); updateBarNodeColor(node, m_colorScale->color(m_values[*it])); x += barWidth; node = node->nextSibling(); } } */ /* void BarChart::updateSelectionBar(QSGNode *node, float x, float barWidth, const QColor &color) const { QSGSimpleRectNode *barNode = static_cast<QSGSimpleRectNode *>(node); barNode->setRect(x, 0, barWidth, 1.0f); barNode->setColor(color); } */ /* void BarChart::updateSelectionBars(QSGNode *node) const { int numValues = (int) m_values.n_elem; float barWidth = 1.0f / numValues; // First, make sure we have the same number of values & bars while (numValues > node->childCount()) { QSGNode *barNode = newSelectionBarNode(); node->prependChildNode(barNode); } while (numValues < node->childCount()) { // NOTE: as stated in docs, QSGNode's children are stored in a // linked list. Hence, this operation should be as fast as expected node->removeChildNode(node->firstChild()); } // Update each bar, displaying it (using a visible color) only if it is a // selected item float x = 0; node = node->firstChild(); for (auto it = m_originalIndices.cbegin(); it != m_originalIndices.cend(); it++) { updateSelectionBar(node, x, barWidth, m_selection[*it] ? SELECTION_VISIBLE_COLOR : SELECTION_INVISIBLE_COLOR); x += barWidth; node = node->nextSibling(); } } */ /* void BarChart::updatePreSelection(QSGNode *node) const { QSGSimpleRectNode *preSelectionNode = static_cast<QSGSimpleRectNode *>(node); preSelectionNode->setRect(std::min(m_dragStartPos, m_dragLastPos), 0.0f, fabs(m_dragLastPos - m_dragStartPos), 1.0f); } */ /* void BarChart::updateBrush(QSGNode *node) const { float barWidth = 1.0f / m_values.n_elem; QSGGeometryNode *brushGeomNode = static_cast<QSGGeometryNode *>(node); updateRectGeometry(brushGeomNode->geometry(), barWidth * m_brushedItem, 0.0f, barWidth, 1.0f); brushGeomNode->markDirty(QSGNode::DirtyGeometry); } */ /* QSGNode *BarChart::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) { QSGNode *root = oldNode ? oldNode : newSceneGraph(); updateViewport(root); QSGNode *node = root->firstChild(); if (m_shouldUpdateBars) { updateBars(node); m_shouldUpdateBars = false; } node = node->nextSibling(); if (m_shouldUpdateSelection) { updateSelectionBars(node); m_shouldUpdateSelection = false; } node = node->nextSibling(); if (m_shouldUpdatePreSelection) { updatePreSelection(node); m_shouldUpdatePreSelection = false; } node = node->nextSibling(); updateBrush(node); node = node->nextSibling(); return root; } */ int BarChart::itemAt(float x, bool includeSelectorWidth) const { int numValues = m_values.n_elem; float barWidth = 1.0f / numValues; if (includeSelectorWidth) { x += 1.0f / width(); } return clamp(int(x / barWidth), 0, numValues - 1); } void BarChart::interactiveSelection(float start, float end) { if (start > end) { std::swap(start, end); } m_selection.assign(m_selection.size(), false); if (start < 0.0f || end < 0.0f) { return; } // Bars are located in ranges: // [0..barWidth] + barIndex / barWidth int firstIndex = itemAt(start); int lastIndex = itemAt(end, true); for (int i = firstIndex; i <= lastIndex; i++) { m_selection[m_originalIndices[i]] = true; } selectionInteractivelyChanged(m_selection); } /* void BarChart::hoverEnterEvent(QHoverEvent *event) { m_brushedItem = itemAt(float(event->pos().x()) / width()); emit itemInteractivelyBrushed(m_originalIndices[m_brushedItem]); update(); } */ /* void BarChart::hoverMoveEvent(QHoverEvent *event) { m_brushedItem = itemAt(float(event->pos().x()) / width()); emit itemInteractivelyBrushed(m_originalIndices[m_brushedItem]); update(); } */ /* void BarChart::hoverLeaveEvent(QHoverEvent *event) { m_brushedItem = -1; emit itemInteractivelyBrushed(m_brushedItem); update(); } */ /* void BarChart::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::RightButton) { m_dragStartPos = -1.0f; m_dragLastPos = -1.0f; m_selection.assign(m_selection.size(), false); emit selectionInteractivelyChanged(m_selection); } else { QCursor dragCursor(Qt::SizeHorCursor); setCursor(dragCursor); float pos = float(event->pos().x()) / width(); m_dragStartPos = pos; m_dragLastPos = pos; } m_shouldUpdatePreSelection = true; update(); } */ /* void BarChart::mouseMoveEvent(QMouseEvent *event) { if (m_dragStartPos < 0.0f) { return; } float pos = float(event->pos().x()) / width(); m_dragLastPos = clamp(pos, 0.0f, 1.0f); m_shouldUpdatePreSelection = true; update(); } */ /* void BarChart::mouseReleaseEvent(QMouseEvent *event) { if (m_dragStartPos < 0.0f) { return; } unsetCursor(); float pos = float(event->pos().x()) / width(); m_dragLastPos = clamp(pos, 0.0f, 1.0f); if (m_values.n_elem > 0) { interactiveSelection(m_dragStartPos, m_dragLastPos); } m_dragStartPos = -1.0f; m_dragLastPos = -1.0f; m_shouldUpdatePreSelection = true; update(); } */