#include "scatterplot.h" #include <algorithm> #include <cmath> #include "colormap.h" #include "continuouscolorscale.h" // Samples used in multisampling for rendering static const size_t NUM_SAMPLES = 8; // Glyphs settings static const Color DEFAULT_GLYPH_COLOR(255, 255, 255); static const float DEFAULT_GLYPH_SIZE = 8.0f; static const float GLYPH_OPACITY = 1.0f; static const float GLYPH_OPACITY_SELECTED = 1.0f; static const float GLYPH_OUTLINE_WIDTH = 2.0f; static const Color GLYPH_OUTLINE_COLOR(0, 0, 0); static const Color GLYPH_OUTLINE_COLOR_SELECTED(20, 255, 225); // Brush settings // BRUSHING_MAX_DIST in quadtree.cpp static const float CROSSHAIR_LENGTH = 8.0f; static const float CROSSHAIR_THICKNESS1 = 1.0f; static const float CROSSHAIR_THICKNESS2 = 0.5f; static const Color CROSSHAIR_COLOR1(255, 255, 255); static const Color CROSSHAIR_COLOR2(0, 0, 0); // Selection settings static const Color SELECTION_COLOR(128, 128, 128, 96); // Mouse buttons // 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); 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() { gl_FragDepth = gl_PrimitiveID / 3000; 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() { gl_FragDepth = gl_PrimitiveID / 2000; 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(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); glGenFramebuffers(1, &m_outFBO); 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_depthTex); glGenTextures(1, &m_tex); 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; } Scatterplot::~Scatterplot() { glDeleteFramebuffers(1, &m_FBO); } void Scatterplot::setSize(size_t width, size_t height) { m_width = width; m_height = height; glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, m_tex); glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, NUM_SAMPLES, GL_RGBA, m_width, m_height, GL_TRUE); // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0); glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, m_depthTex); glTexParameteri(GL_TEXTURE_2D_MULTISAMPLE, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D_MULTISAMPLE, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D_MULTISAMPLE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D_MULTISAMPLE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, NUM_SAMPLES, GL_DEPTH_COMPONENT, m_width, m_height, GL_TRUE); glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0); 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 { return m_xy; } void Scatterplot::setXY(const arma::mat &xy) { if (xy.n_cols != 2) { return; } m_xy = xy; xyChanged(m_xy); if (m_autoScale) { autoScale(); } updateQuadTree(); if (m_selection.size() != m_xy.n_rows) { m_selection.assign(m_xy.n_rows, false); } if (m_opacityData.n_elem != m_xy.n_rows) { // Reset opacity data m_opacityData.resize(xy.n_rows); m_opacityData.fill(GLYPH_OPACITY); opacityDataChanged(m_opacityData); } m_shouldUpdateGeometry = true; update(); } void Scatterplot::setColorData(const arma::vec &colorData) { if (m_xy.n_rows > 0 && (colorData.n_elem > 0 && colorData.n_elem != m_xy.n_rows)) { return; } m_colorData = colorData; colorDataChanged(m_colorData); m_shouldUpdateMaterials = true; update(); } void Scatterplot::setOpacityData(const arma::vec &opacityData) { if (m_xy.n_rows > 0 && opacityData.n_elem != m_xy.n_rows) { return; } m_opacityData = opacityData; opacityDataChanged(m_opacityData); update(); } void Scatterplot::setScale(const LinearScale<float> &sx, const LinearScale<float> &sy) { m_sx = sx; m_sy = sy; scaleChanged(m_sx, m_sy); updateQuadTree(); update(); } void Scatterplot::setAutoScale(bool autoScale) { m_autoScale = autoScale; if (autoScale) { this->autoScale(); } } void Scatterplot::autoScale() { m_sx.setDomain(m_xy.col(0).min(), m_xy.col(0).max()); m_sy.setDomain(m_xy.col(1).min(), m_xy.col(1).max()); scaleChanged(m_sx, m_sy); } void Scatterplot::setGlyphSize(float glyphSize) { if (m_glyphSize == glyphSize || glyphSize < 2.0f) { return; } m_glyphSize = glyphSize; glyphSizeChanged(m_glyphSize); 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; } m_redraw = false; int originalFBO; glGetIntegerv(GL_FRAMEBUFFER_BINDING, &originalFBO); glBindFramebuffer(GL_FRAMEBUFFER, m_FBO); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, m_tex, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D_MULTISAMPLE, m_depthTex, 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_DEPTH_TEST); 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 | GL_DEPTH_BUFFER_BIT); glBindVertexArray(m_VAO); glDrawArrays(GL_POINTS, 0, m_points.size()); m_shader->release(); // Swap out shaders to draw outline glLineWidth(GLYPH_OUTLINE_WIDTH); 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(); glLineWidth(1.0f); glDisable(GL_DEPTH_TEST); glBindFramebuffer(GL_FRAMEBUFFER, m_outFBO); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_outTex, 0); glBindFramebuffer(GL_READ_FRAMEBUFFER, m_FBO); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_outFBO); glBlitFramebuffer(0, 0, m_width, m_height, 0, 0, m_width, m_height, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST); /* GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { checkGLError("draw():"); } */ glBindFramebuffer(GL_FRAMEBUFFER, originalFBO); } /* QSGNode *Scatterplot::newSceneGraph() { // NOTE: // The hierarchy in the scene graph is as follows: // root [[splatNode] [glyphsRoot [glyph [...]]] [selectionNode]] QSGNode *root = new QSGNode; QSGNode *glyphTreeRoot = newGlyphTree(); if (glyphTreeRoot) { root->appendChildNode(glyphTreeRoot); } QSGSimpleRectNode *selectionRectNode = new QSGSimpleRectNode; selectionRectNode->setColor(SELECTION_COLOR); root->appendChildNode(selectionRectNode); QSGTransformNode *brushNode = new QSGTransformNode; QSGGeometryNode *whiteCrossHairNode = new QSGGeometryNode; QSGGeometry *whiteCrossHairGeom = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 12); whiteCrossHairGeom->setDrawingMode(GL_POLYGON); whiteCrossHairGeom->setVertexDataPattern(QSGGeometry::DynamicPattern); updateCrossHairGeometry(whiteCrossHairGeom, 0, 0, CROSSHAIR_THICKNESS1, CROSSHAIR_LENGTH); QSGFlatColorMaterial *whiteCrossHairMaterial = new QSGFlatColorMaterial; whiteCrossHairMaterial->setColor(CROSSHAIR_COLOR1); whiteCrossHairNode->setGeometry(whiteCrossHairGeom); whiteCrossHairNode->setMaterial(whiteCrossHairMaterial); whiteCrossHairNode->setFlags(QSGNode::OwnsGeometry | QSGNode::OwnsMaterial); brushNode->appendChildNode(whiteCrossHairNode); QSGGeometryNode *blackCrossHairNode = new QSGGeometryNode; QSGGeometry *blackCrossHairGeom = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 12); blackCrossHairGeom->setDrawingMode(GL_POLYGON); blackCrossHairGeom->setVertexDataPattern(QSGGeometry::DynamicPattern); updateCrossHairGeometry(blackCrossHairGeom, 0, 0, CROSSHAIR_THICKNESS2, CROSSHAIR_LENGTH); QSGFlatColorMaterial *blackCrossHairMaterial = new QSGFlatColorMaterial; blackCrossHairMaterial->setColor(CROSSHAIR_COLOR2); blackCrossHairNode->setGeometry(blackCrossHairGeom); blackCrossHairNode->setMaterial(blackCrossHairMaterial); blackCrossHairNode->setFlags(QSGNode::OwnsGeometry | QSGNode::OwnsMaterial); brushNode->appendChildNode(blackCrossHairNode); root->appendChildNode(brushNode); return root; } */ /* QSGNode *Scatterplot::newGlyphTree() { // NOTE: // The glyph graph is structured as: // root [opacityNode [outlineNode fillNode] ...] if (m_xy.n_rows < 1) { return 0; } QSGNode *node = new QSGNode; int vertexCount = calculateCircleVertexCount(m_glyphSize); for (arma::uword i = 0; i < m_xy.n_rows; i++) { QSGGeometry *glyphOutlineGeometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), vertexCount); glyphOutlineGeometry->setDrawingMode(GL_POLYGON); glyphOutlineGeometry->setVertexDataPattern(QSGGeometry::DynamicPattern); QSGGeometryNode *glyphOutlineNode = new QSGGeometryNode; glyphOutlineNode->setGeometry(glyphOutlineGeometry); glyphOutlineNode->setFlag(QSGNode::OwnsGeometry); QSGFlatColorMaterial *material = new QSGFlatColorMaterial; material->setColor(GLYPH_OUTLINE_COLOR); glyphOutlineNode->setMaterial(material); glyphOutlineNode->setFlag(QSGNode::OwnsMaterial); QSGGeometry *glyphGeometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), vertexCount); glyphGeometry->setDrawingMode(GL_POLYGON); glyphGeometry->setVertexDataPattern(QSGGeometry::DynamicPattern); QSGGeometryNode *glyphNode = new QSGGeometryNode; glyphNode->setGeometry(glyphGeometry); glyphNode->setFlag(QSGNode::OwnsGeometry); material = new QSGFlatColorMaterial; material->setColor(DEFAULT_GLYPH_COLOR); glyphNode->setMaterial(material); glyphNode->setFlag(QSGNode::OwnsMaterial); // Place the glyph geometry node under an opacity node QSGOpacityNode *glyphOpacityNode = new QSGOpacityNode; glyphOpacityNode->appendChildNode(glyphOutlineNode); glyphOpacityNode->appendChildNode(glyphNode); node->appendChildNode(glyphOpacityNode); } return node; } */ /* QSGNode *Scatterplot::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) { QSGNode *root = oldNode ? oldNode : newSceneGraph(); if (m_xy.n_rows < 1) { return root; } // This keeps track of where we are in the scene when updating QSGNode *node = root->firstChild(); updateGlyphs(node); node = node->nextSibling(); if (m_shouldUpdateGeometry) { m_shouldUpdateGeometry = false; } if (m_shouldUpdateMaterials) { m_shouldUpdateMaterials = false; } // Selection QSGSimpleRectNode *selectionNode = static_cast<QSGSimpleRectNode *>(node); if (m_interactionState == StateSelecting) { selectionNode->setRect(QRectF(m_dragOriginPos, m_dragCurrentPos)); selectionNode->markDirty(QSGNode::DirtyGeometry); } else { // Hide selection rect selectionNode->setRect(QRectF(-1, -1, 0, 0)); } node = node->nextSibling(); // Brushing updateBrush(node); node = node->nextSibling(); return root; } */ /* void Scatterplot::updateGlyphs(QSGNode *glyphsNode) { qreal x, y, tx, ty, moveTranslationF; if (!m_shouldUpdateGeometry && !m_shouldUpdateMaterials) { return; } if (m_interactionState == StateMoving) { tx = m_dragCurrentPos.x() - m_dragOriginPos.x(); ty = m_dragCurrentPos.y() - m_dragOriginPos.y(); } else { tx = ty = 0; } m_sx.setRange(PADDING, width() - PADDING); m_sy.setRange(height() - PADDING, PADDING); QSGNode *node = glyphsNode->firstChild(); for (arma::uword i = 0; i < m_xy.n_rows; i++) { const arma::rowvec &row = m_xy.row(i); bool isSelected = m_selection[i]; QSGOpacityNode *glyphOpacityNode = static_cast<QSGOpacityNode *>(node); glyphOpacityNode->setOpacity(m_opacityData[i]); QSGGeometryNode *glyphOutlineNode = static_cast<QSGGeometryNode *>(node->firstChild()); QSGGeometryNode *glyphNode = static_cast<QSGGeometryNode *>(node->firstChild()->nextSibling()); if (m_shouldUpdateGeometry) { moveTranslationF = isSelected ? 1.0 : 0.0; x = m_sx(row[0]) + tx * moveTranslationF; y = m_sy(row[1]) + ty * moveTranslationF; QSGGeometry *geometry = glyphOutlineNode->geometry(); updateCircleGeometry(geometry, m_glyphSize, x, y); glyphOutlineNode->markDirty(QSGNode::DirtyGeometry); geometry = glyphNode->geometry(); updateCircleGeometry(geometry, m_glyphSize - 2*GLYPH_OUTLINE_WIDTH, x, y); glyphNode->markDirty(QSGNode::DirtyGeometry); } if (m_shouldUpdateMaterials) { QSGFlatColorMaterial *material = static_cast<QSGFlatColorMaterial *>(glyphOutlineNode->material()); material->setColor(isSelected ? GLYPH_OUTLINE_COLOR_SELECTED : GLYPH_OUTLINE_COLOR); glyphOutlineNode->markDirty(QSGNode::DirtyMaterial); material = static_cast<QSGFlatColorMaterial *>(glyphNode->material()); if (m_colorData.n_elem > 0) { material->setColor(m_colorScale->color(m_colorData[i])); } else { material->setColor(DEFAULT_GLYPH_COLOR); } glyphNode->markDirty(QSGNode::DirtyMaterial); } node = node->nextSibling(); } // XXX: Beware: QSGNode::DirtyForceUpdate is undocumented // // This causes the scene graph to correctly update the materials of glyphs, // even though we individually mark dirty materials. Used to work in Qt 5.5, // though. glyphsNode->markDirty(QSGNode::DirtyForceUpdate); } */ /* void Scatterplot::updateBrush(QSGNode *node) { QMatrix4x4 transform; if (m_brushedItem < 0 || (m_interactionState != StateNone && m_interactionState != StateSelected)) { transform.translate(-width(), -height()); } else { const arma::rowvec &row = m_xy.row(m_brushedItem); transform.translate(m_sx(row[0]), m_sy(row[1])); } QSGTransformNode *brushNode = static_cast<QSGTransformNode *>(node); brushNode->setMatrix(transform); } */ /* void Scatterplot::mousePressEvent(QMouseEvent *event) { switch (m_interactionState) { case StateNone: case StateSelected: switch (event->button()) { case NORMAL_BUTTON: if (event->modifiers() == Qt::ShiftModifier && m_dragEnabled) { m_interactionState = StateMoving; m_dragOriginPos = event->localPos(); m_dragCurrentPos = m_dragOriginPos; } else { // We say 'brushing', but we mean 'selecting the current brushed // item' m_interactionState = StateBrushing; } break; case SPECIAL_BUTTON: m_interactionState = StateNone; m_selection.assign(m_selection.size(), false); selectionInteractivelyChanged(m_selection); m_shouldUpdateMaterials = true; update(); break; } break; case StateBrushing: case StateSelecting: case StateMoving: // Probably shouldn't reach these break; } } */ /* void Scatterplot::mouseMoveEvent(QMouseEvent *event) { switch (m_interactionState) { case StateBrushing: // Move while brushing becomes selecting, hence the 'fall through' m_interactionState = StateSelecting; m_dragOriginPos = event->localPos(); // fall through case StateSelecting: m_dragCurrentPos = event->localPos(); update(); break; case StateMoving: m_dragCurrentPos = event->localPos(); m_shouldUpdateGeometry = true; update(); break; case StateNone: case StateSelected: break; } } */ /* void Scatterplot::mouseReleaseEvent(QMouseEvent *event) { bool mergeSelection = (event->modifiers() == Qt::ControlModifier); switch (m_interactionState) { case StateBrushing: // Mouse clicked with brush target; set new selection or append to // current if (!mergeSelection) { m_selection.assign(m_selection.size(), false); } if (m_brushedItem == -1) { m_interactionState = StateNone; if (m_anySelected && !mergeSelection) { m_anySelected = false; selectionInteractivelyChanged(m_selection); m_shouldUpdateMaterials = true; update(); } } else { m_interactionState = StateSelected; m_selection[m_brushedItem] = !m_selection[m_brushedItem]; if (m_selection[m_brushedItem]) { m_anySelected = true; } selectionInteractivelyChanged(m_selection); m_shouldUpdateMaterials = true; update(); } break; case StateSelecting: { // Selecting points and mouse is now released; update selection and // brush interactiveSelection(mergeSelection); m_interactionState = m_anySelected ? StateSelected : StateNone; QPoint pos = event->pos(); m_brushedItem = m_quadtree->nearestTo(pos.x(), pos.y()); itemInteractivelyBrushed(m_brushedItem); m_shouldUpdateMaterials = true; update(); } break; case StateMoving: // Moving points and now stopped; apply manipulation m_interactionState = StateSelected; applyManipulation(); m_shouldUpdateGeometry = true; update(); m_dragOriginPos = m_dragCurrentPos; break; case StateNone: case StateSelected: break; } } */ /* void Scatterplot::hoverEnterEvent(QHoverEvent *event) { QPointF pos = event->posF(); m_brushedItem = m_quadtree->nearestTo(pos.x(), pos.y()); itemInteractivelyBrushed(m_brushedItem); update(); } */ /* void Scatterplot::hoverMoveEvent(QHoverEvent *event) { QPointF pos = event->posF(); m_brushedItem = m_quadtree->nearestTo(pos.x(), pos.y()); itemInteractivelyBrushed(m_brushedItem); update(); } */ /* void Scatterplot::hoverLeaveEvent(QHoverEvent *event) { m_brushedItem = -1; itemInteractivelyBrushed(m_brushedItem); update(); } */ void Scatterplot::interactiveSelection(bool mergeSelection) { if (!mergeSelection) { m_selection.assign(m_selection.size(), false); } std::vector<int> selected; m_quadtree->query(RectF(m_dragOriginPos, m_dragCurrentPos), selected); for (auto i: selected) { m_selection[i] = true; } for (auto isSelected: m_selection) { if (isSelected) { m_anySelected = true; break; } } selectionInteractivelyChanged(m_selection); } void Scatterplot::setSelection(const std::vector<bool> &selection) { if (m_selection.size() != selection.size()) { return; } m_selection = selection; selectionChanged(m_selection); m_shouldUpdateMaterials = true; update(); } void Scatterplot::brushItem(int item) { m_brushedItem = item; update(); } void Scatterplot::applyManipulation() { m_sx.inverse(); m_sy.inverse(); LinearScale<float> rx = m_sx; LinearScale<float> ry = m_sy; m_sy.inverse(); m_sx.inverse(); 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]) { arma::rowvec row = m_xy.row(i); row[0] = rx(m_sx(row[0]) + tx); row[1] = ry(m_sy(row[1]) + ty); m_xy.row(i) = row; } } updateQuadTree(); xyInteractivelyChanged(m_xy); } void Scatterplot::updateQuadTree() { m_sx.setRange(PADDING, m_width - PADDING); m_sy.setRange(m_height - PADDING, PADDING); 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); } }