aboutsummaryrefslogtreecommitdiff
path: root/scatterplot.cpp
blob: a3a0dfc2e2e72b18bd3b4e14ba9db7cf38a331b1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#include "scatterplot.h"

#include <iostream>

#include <cmath>
#include <QSGNode>
#include <QSGGeometry>
#include <QSGGeometryNode>
#include <QSGMaterial>
#include <QSGFlatColorMaterial>
#include <QSGSimpleRectNode>

const int GLYPH_SIZE = 5;
const float PADDING = 10;
const float PI = 3.1415f;

Scatterplot::Scatterplot(QQuickItem *parent)
    : QQuickItem(parent)
    , m_colorScale{QColor("red"), QColor("green"), QColor("blue")}
{
    setFlag(QQuickItem::ItemHasContents);
}

Scatterplot::~Scatterplot()
{
}

void Scatterplot::setData(const arma::mat &data)
{
    if (data.n_cols != 3)
        return;

    m_data = data;
    m_colorScale.setExtents(m_data.col(2).min(), m_data.col(2).max());
    update();
}

int calculateCircleVertexCount(qreal radius)
{
    // 10 * sqrt(r) \approx 2*pi / acos(1 - 1 / (4*r))
    return (int) (10.0 * sqrt(radius));
}

void updateCircleGeometry(QSGGeometry *geometry, float size, float cx, float cy)
{
    int vertexCount = geometry->vertexCount();

    float theta = 2 * PI / float(vertexCount);
    float c = cosf(theta);
    float s = sinf(theta);
    float x = size / 2;
    float y = 0;

    QSGGeometry::Point2D *vertexData = geometry->vertexDataAsPoint2D();
    for (int i = 0; i < vertexCount; i++) {
        vertexData[i].set(x + cx, y + cy);

        float t = x;
        x = c*x - s*y;
        y = s*t + c*y;
    }
}

void updateSquareGeometry(QSGGeometry *geometry, float size, float cx, float cy)
{
    float r = size / 2;
    QSGGeometry::Point2D *vertexData = geometry->vertexDataAsPoint2D();
    vertexData[0].set(cx - r, cy - r);
    vertexData[1].set(cx + r, cy - r);
    vertexData[2].set(cx + r, cy + r);
    vertexData[3].set(cx - r, cy + r);
}

QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
    if (m_data.n_rows < 1)
        return 0;

    QSGNode *node = 0;
    int vertexCount = calculateCircleVertexCount(GLYPH_SIZE / 2);
    qreal xmin = m_data.col(0).min(),
          xmax = m_data.col(0).max(),
          ymin = m_data.col(1).min(),
          ymax = m_data.col(1).max(),
          x, y;

    if (!oldNode) {
        node = new QSGNode;
        for (arma::uword i = 0; i < m_data.n_rows; i++) {
            QSGGeometryNode *childNode = new QSGGeometryNode;

            QSGGeometry *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), vertexCount);
            geometry->setDrawingMode(GL_POLYGON);
            childNode->setGeometry(geometry);
            childNode->setFlag(QSGNode::OwnsGeometry);

            QSGFlatColorMaterial *material = new QSGFlatColorMaterial;
            material->setColor(m_colorScale.color(m_data(i, 2)));
            childNode->setMaterial(material);
            childNode->setFlag(QSGNode::OwnsMaterial);

            node->appendChildNode(childNode);
        }
    } else {
        node = oldNode;
    }

    QSGNode *childNode = node->firstChild();
    for (arma::uword i = 0; i < m_data.n_rows; i++) {
        arma::rowvec row = m_data.row(i);
        x = PADDING + (row[0] - xmin) / (xmax - xmin) * (width() - 2*PADDING);
        y = PADDING + (row[1] - ymin) / (ymax - ymin) * (height() - 2*PADDING);

        QSGGeometry *geometry = static_cast<QSGGeometryNode *>(childNode)->geometry();
        updateCircleGeometry(geometry, GLYPH_SIZE, x, y);
        childNode->markDirty(QSGNode::DirtyGeometry);
        childNode = childNode->nextSibling();
    }

    return node;
}