aboutsummaryrefslogtreecommitdiff
#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();
}
*/