Пример #1
0
    def _build_anchor_grid(self):
        lines = []
        num_parts = 30
        anchors = numpy.array([a[:3] for a in self.anchor_data])
        for anchor in self.anchor_data:
            a0 = numpy.array(anchor[:3])
            neighbours = anchors.copy()
            neighbours = [(((n - a0)**2).sum(), n) for n in neighbours]
            neighbours.sort(key=lambda e: e[0])
            for i in range(1, min(len(anchors), 4)):
                difference = neighbours[i][1] - a0
                for j in range(num_parts):
                    lines.extend(
                        normalize(a0 + difference * j / float(num_parts)))
                    lines.extend(
                        normalize(a0 + difference *
                                  (j + 1) / float(num_parts)))

        self._grid_buffer = VertexBuffer(numpy.array(lines, numpy.float32),
                                         [(3, GL_FLOAT)])
Пример #2
0
    def _build_anchor_grid(self):
        lines = []
        num_parts = 30
        anchors = numpy.array([a[:3] for a in self.anchor_data])
        for anchor in self.anchor_data:
            a0 = numpy.array(anchor[:3])
            neighbours = anchors.copy()
            neighbours = [(((n-a0)**2).sum(), n)  for n in neighbours]
            neighbours.sort(key=lambda e: e[0])
            for i in range(1, min(len(anchors), 4)): 
                difference = neighbours[i][1]-a0
                for j in range(num_parts):
                    lines.extend(normalize(a0 + difference*j/float(num_parts)))
                    lines.extend(normalize(a0 + difference*(j+1)/float(num_parts)))

        self._grid_buffer = VertexBuffer(numpy.array(lines, numpy.float32), [(3, GL_FLOAT)])
Пример #3
0
    def setData(self, data, subset_data=None, **args):
        OWLinProj3DPlot.set_data(self, data, subset_data, **args)

        # No need to generate backgroud grid sphere geometry more than once
        if hasattr(self, '_sphere_buffer'):
            return

        self.makeCurrent()

        lines = []
        num_parts = 30
        num_horizontal_rings = 20
        num_vertical_rings = 24
        r = 1.

        for i in range(num_horizontal_rings):
            z_offset = float(i) * 2 / num_horizontal_rings - 1.
            r = (1. - z_offset**2)**0.5
            for j in range(num_parts):
                angle_z_0 = float(j) * 2 * pi / num_parts
                angle_z_1 = float(j+1) * 2 * pi / num_parts
                lines.extend([sin(angle_z_0)*r, z_offset, cos(angle_z_0)*r,
                              sin(angle_z_1)*r, z_offset, cos(angle_z_1)*r])

        for i in range(num_vertical_rings):
            r = 1.
            phi = 2 * i * pi / num_vertical_rings
            for j in range(num_parts):
                theta_0 = (j) * pi / num_parts
                theta_1 = (j+1) * pi / num_parts
                lines.extend([sin(theta_0)*cos(phi)*r, cos(theta_0)*r, sin(theta_0)*sin(phi)*r,
                              sin(theta_1)*cos(phi)*r, cos(theta_1)*r, sin(theta_1)*sin(phi)*r])

        self._sphere_buffer = VertexBuffer(numpy.array(lines, numpy.float32), [(3, GL_FLOAT)])

        self._sphere_shader = QtOpenGL.QGLShaderProgram()
        self._sphere_shader.addShaderFromSourceFile(QtOpenGL.QGLShader.Vertex,
            os.path.join(os.path.dirname(__file__), 'sphere.vs'))
        self._sphere_shader.addShaderFromSourceFile(QtOpenGL.QGLShader.Fragment,
            os.path.join(os.path.dirname(__file__), 'sphere.fs'))

        self._sphere_shader.bindAttributeLocation('position', 0)

        if not self._sphere_shader.link():
            print('Failed to link sphere shader!')

        ## Cones
        cone_data = parse_obj('cone_hq.obj')
        vertices = []
        for v0, v1, v2, n0, n1, n2 in cone_data:
            vertices.extend([v0[0],v0[1],v0[2], n0[0],n0[1],n0[2],
                             v1[0],v1[1],v1[2], n1[0],n1[1],n1[2],
                             v2[0],v2[1],v2[2], n2[0],n2[1],n2[2]])

        self._cone_buffer = VertexBuffer(numpy.array(vertices, numpy.float32),
            [(3, GL_FLOAT),
             (3, GL_FLOAT)])

        self._cone_shader = QtOpenGL.QGLShaderProgram()
        self._cone_shader.addShaderFromSourceFile(QtOpenGL.QGLShader.Vertex,
            os.path.join(os.path.dirname(__file__), 'cone.vs'))
        self._cone_shader.addShaderFromSourceFile(QtOpenGL.QGLShader.Fragment,
            os.path.join(os.path.dirname(__file__), 'cone.fs'))

        self._cone_shader.bindAttributeLocation('position', 0)
        self._cone_shader.bindAttributeLocation('normal', 1)

        if not self._cone_shader.link():
            print('Failed to link cone shader!')

        ## Another dummy shader (anchor grid)
        self._grid_shader = QtOpenGL.QGLShaderProgram()
        self._grid_shader.addShaderFromSourceFile(QtOpenGL.QGLShader.Vertex,
            os.path.join(os.path.dirname(__file__), 'grid.vs'))
        self._grid_shader.addShaderFromSourceFile(QtOpenGL.QGLShader.Fragment,
            os.path.join(os.path.dirname(__file__), 'grid.fs'))

        self._grid_shader.bindAttributeLocation('position', 0)

        if not self._grid_shader.link():
            print('Failed to link grid shader!')

        self.before_draw_callback = self.before_draw
Пример #4
0
class OWSphereviz3DPlot(OWLinProj3DPlot):
    def __init__(self, widget, parent=None, name='SpherevizPlot'):
        OWLinProj3DPlot.__init__(self, widget, parent, name)

        self.camera_angle = 90
        self.camera_type = 0 # Default, center, attribute
        self.show_anchor_grid = False

    def _build_anchor_grid(self):
        lines = []
        num_parts = 30
        anchors = numpy.array([a[:3] for a in self.anchor_data])
        for anchor in self.anchor_data:
            a0 = numpy.array(anchor[:3])
            neighbours = anchors.copy()
            neighbours = [(((n-a0)**2).sum(), n)  for n in neighbours]
            neighbours.sort(key=lambda e: e[0])
            for i in range(1, min(len(anchors), 4)): 
                difference = neighbours[i][1]-a0
                for j in range(num_parts):
                    lines.extend(normalize(a0 + difference*j/float(num_parts)))
                    lines.extend(normalize(a0 + difference*(j+1)/float(num_parts)))

        self._grid_buffer = VertexBuffer(numpy.array(lines, numpy.float32), [(3, GL_FLOAT)])

    def update_data(self, labels=None, setAnchors=0, **args):
        OWLinProj3DPlot.updateData(self, labels, setAnchors, **args)

        if self.anchor_data:
            self._build_anchor_grid()

        self.updateGL()

    updateData = update_data

    def setData(self, data, subset_data=None, **args):
        OWLinProj3DPlot.set_data(self, data, subset_data, **args)

        # No need to generate backgroud grid sphere geometry more than once
        if hasattr(self, '_sphere_buffer'):
            return

        self.makeCurrent()

        lines = []
        num_parts = 30
        num_horizontal_rings = 20
        num_vertical_rings = 24
        r = 1.

        for i in range(num_horizontal_rings):
            z_offset = float(i) * 2 / num_horizontal_rings - 1.
            r = (1. - z_offset**2)**0.5
            for j in range(num_parts):
                angle_z_0 = float(j) * 2 * pi / num_parts
                angle_z_1 = float(j+1) * 2 * pi / num_parts
                lines.extend([sin(angle_z_0)*r, z_offset, cos(angle_z_0)*r,
                              sin(angle_z_1)*r, z_offset, cos(angle_z_1)*r])

        for i in range(num_vertical_rings):
            r = 1.
            phi = 2 * i * pi / num_vertical_rings
            for j in range(num_parts):
                theta_0 = (j) * pi / num_parts
                theta_1 = (j+1) * pi / num_parts
                lines.extend([sin(theta_0)*cos(phi)*r, cos(theta_0)*r, sin(theta_0)*sin(phi)*r,
                              sin(theta_1)*cos(phi)*r, cos(theta_1)*r, sin(theta_1)*sin(phi)*r])

        self._sphere_buffer = VertexBuffer(numpy.array(lines, numpy.float32), [(3, GL_FLOAT)])

        self._sphere_shader = QtOpenGL.QGLShaderProgram()
        self._sphere_shader.addShaderFromSourceFile(QtOpenGL.QGLShader.Vertex,
            os.path.join(os.path.dirname(__file__), 'sphere.vs'))
        self._sphere_shader.addShaderFromSourceFile(QtOpenGL.QGLShader.Fragment,
            os.path.join(os.path.dirname(__file__), 'sphere.fs'))

        self._sphere_shader.bindAttributeLocation('position', 0)

        if not self._sphere_shader.link():
            print('Failed to link sphere shader!')

        ## Cones
        cone_data = parse_obj('cone_hq.obj')
        vertices = []
        for v0, v1, v2, n0, n1, n2 in cone_data:
            vertices.extend([v0[0],v0[1],v0[2], n0[0],n0[1],n0[2],
                             v1[0],v1[1],v1[2], n1[0],n1[1],n1[2],
                             v2[0],v2[1],v2[2], n2[0],n2[1],n2[2]])

        self._cone_buffer = VertexBuffer(numpy.array(vertices, numpy.float32),
            [(3, GL_FLOAT),
             (3, GL_FLOAT)])

        self._cone_shader = QtOpenGL.QGLShaderProgram()
        self._cone_shader.addShaderFromSourceFile(QtOpenGL.QGLShader.Vertex,
            os.path.join(os.path.dirname(__file__), 'cone.vs'))
        self._cone_shader.addShaderFromSourceFile(QtOpenGL.QGLShader.Fragment,
            os.path.join(os.path.dirname(__file__), 'cone.fs'))

        self._cone_shader.bindAttributeLocation('position', 0)
        self._cone_shader.bindAttributeLocation('normal', 1)

        if not self._cone_shader.link():
            print('Failed to link cone shader!')

        ## Another dummy shader (anchor grid)
        self._grid_shader = QtOpenGL.QGLShaderProgram()
        self._grid_shader.addShaderFromSourceFile(QtOpenGL.QGLShader.Vertex,
            os.path.join(os.path.dirname(__file__), 'grid.vs'))
        self._grid_shader.addShaderFromSourceFile(QtOpenGL.QGLShader.Fragment,
            os.path.join(os.path.dirname(__file__), 'grid.fs'))

        self._grid_shader.bindAttributeLocation('position', 0)

        if not self._grid_shader.link():
            print('Failed to link grid shader!')

        self.before_draw_callback = self.before_draw

    def update_camera_type(self):
        if self.camera_type == 2:
            self._random_anchor = choice(self.anchor_data)
        self.update()

    def before_draw(self):
        view = QMatrix4x4()
        if self.camera_type == 2:
            view.lookAt(
                QVector3D(self._random_anchor[0], self._random_anchor[1], self._random_anchor[2]),
                self.camera,
                QVector3D(0, 1, 0))
            projection = QMatrix4x4()
            projection.perspective(self.camera_angle, float(self.width()) / self.height(),
                                   0.01, 5.)
            self.projection = projection
        elif self.camera_type == 1:
            view.lookAt(
                QVector3D(0, 0, 0),
                self.camera * self.camera_distance,
                QVector3D(0, 1, 0))
            projection = QMatrix4x4()
            projection.perspective(self.camera_angle, float(self.width()) / self.height(),
                                   0.01, 5.)
            self.projection = projection
        else:
            view.lookAt(
                self.camera * self.camera_distance,
                QVector3D(0, 0, 0),
                QVector3D(0, 1, 0))
        self.view = view

        self._draw_sphere()

        # Qt text rendering classes still use deprecated OpenGL functionality
        glEnable(GL_DEPTH_TEST)
        glDisable(GL_BLEND)
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        glMultMatrixd(numpy.array(self.projection.data(), dtype=float))
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        glMultMatrixd(numpy.array(self.view.data(), dtype=float))
        glMultMatrixd(numpy.array(self.model.data(), dtype=float))

        if self.showAnchors:
            for anchor in self.anchor_data:
                x, y, z, label = anchor

                direction = QVector3D(x, y, z)
                up = QVector3D(0, 1, 0)
                right = QVector3D.crossProduct(direction, up).normalized()
                up = QVector3D.crossProduct(right, direction)
                rotation = QMatrix4x4()
                rotation.setColumn(0, QVector4D(right, 0))
                rotation.setColumn(1, QVector4D(up, 0))
                rotation.setColumn(2, QVector4D(direction, 0))

                model = QMatrix4x4()
                model.translate(x, y, z)
                model = model * rotation
                model.rotate(-90, 1, 0, 0)
                model.translate(0, -0.05, 0)
                model.scale(0.02, 0.02, 0.02)

                self._cone_shader.bind()
                self._cone_shader.setUniformValue('projection', self.projection)
                self._cone_shader.setUniformValue('modelview', self.view * model)
                self._cone_buffer.draw()
                self._cone_shader.release()

                self.qglColor(self._theme.axis_values_color)
                self.renderText(x*1.2, y*1.2, z*1.2, label)

            if self.anchor_data and not hasattr(self, '_grid_buffer'):
                self._build_anchor_grid()

            # Draw grid between anchors
            if self.show_anchor_grid:
                self._grid_shader.bind()
                self._grid_shader.setUniformValue('projection', self.projection)
                self._grid_shader.setUniformValue('modelview', self.view * self.model)
                self._grid_shader.setUniformValue('color', self._theme.axis_color)
                self._grid_buffer.draw(GL_LINES)
                self._grid_shader.release()

        self._draw_value_lines()

    def _draw_sphere(self):
        glDisable(GL_DEPTH_TEST)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

        self._sphere_shader.bind()
        self._sphere_shader.setUniformValue('projection', self.projection)
        self._sphere_shader.setUniformValue('modelview', self.view * self.model)
        self._sphere_shader.setUniformValue('cam_position', self.camera * self.camera_distance)
        self._sphere_shader.setUniformValue('use_transparency', self.camera_type == 0)
        self._sphere_buffer.draw(GL_LINES)
        self._sphere_shader.release()

    def mouseMoveEvent(self, event):
        self.invert_mouse_x = self.camera_type != 0
        OWLinProj3DPlot.mouseMoveEvent(self, event)
Пример #5
0
    def updateData(self, labels=None, setAnchors=0, **args):
        self.clear()
        self.clear_plot_transformations()

        if labels == None:
            labels = [anchor[3] for anchor in self.anchor_data]

        if not self.have_data or len(labels) < 3:
            self.anchor_data = []
            self.update()
            return

        if setAnchors or (args.has_key('XAnchors') and args.has_key('YAnchors')
                          and args.has_key('ZAnchors')):
            self.setAnchors(args.get('XAnchors'), args.get('YAnchors'),
                            args.get('ZAnchors'), labels)

        indices = [
            self.attribute_name_index[anchor[3]] for anchor in self.anchor_data
        ]
        valid_data = self.getValidList(indices)
        trans_proj_data = self.create_projection_as_numeric_array(
            indices,
            validData=valid_data,
            scaleFactor=1.0,
            normalize=self.normalize_examples,
            jitterSize=-1,
            useAnchorData=1,
            removeMissingData=0)
        if trans_proj_data == None:
            return

        proj_data = trans_proj_data.T
        proj_data[0:3] += 0.5
        if self.data_has_discrete_class:
            proj_data[3] = self.no_jittering_scaled_data[
                self.attribute_name_index[self.data_domain.classVar.name]]
        self.set_plot_data(proj_data, None)
        self.proj_data = proj_data
        self.symbol_scale = self.point_width * self._point_width_to_symbol_scale
        self.hide_outside = False
        self.fade_outside = False

        color_index = symbol_index = size_index = label_index = -1
        color_discrete = False
        x_discrete = self.data_domain[self.anchor_data[0]
                                      [3]].varType == Discrete
        y_discrete = self.data_domain[self.anchor_data[1]
                                      [3]].varType == Discrete
        z_discrete = self.data_domain[self.anchor_data[2]
                                      [3]].varType == Discrete

        if self.data_has_discrete_class:
            self.discrete_palette.setNumberOfColors(
                len(self.data_domain.classVar.values))

        use_different_symbols = self.useDifferentSymbols and self.data_has_discrete_class and\
            len(self.data_domain.classVar.values) <= len(Symbol)

        if use_different_symbols:
            symbol_index = 3
            num_symbols_used = len(self.data_domain.classVar.values)
        else:
            num_symbols_used = -1

        if self.useDifferentColors and self.data_has_discrete_class:
            color_discrete = True
            color_index = 3

        colors = []
        if color_discrete:
            for i in range(len(self.data_domain.classVar.values)):
                c = self.discrete_palette[i]
                colors.append(c)

        self.set_features(0, 1, 2, color_index, symbol_index, size_index,
                          label_index, colors, num_symbols_used, x_discrete,
                          y_discrete, z_discrete)

        def_color = QColor(150, 150, 150)
        def_symbol = 0
        def_size = 10

        if color_discrete:
            num = len(self.data_domain.classVar.values)
            values = get_variable_values_sorted(self.data_domain.classVar)
            for ind in range(num):
                symbol = ind if use_different_symbols else def_symbol
                self.legend().add_item(
                    self.data_domain.classVar.name, values[ind],
                    OWPoint(symbol, self.discrete_palette[ind], def_size))

        if use_different_symbols and not color_discrete:
            num = len(self.data_domain.classVar.values)
            values = get_variable_values_sorted(self.data_domain.classVar)
            for ind in range(num):
                self.legend().add_item(self.data_domain.classVar.name,
                                       values[ind],
                                       OWPoint(ind, def_color, def_size))

        self.legend().set_orientation(Qt.Vertical)
        self.legend().max_size = QSize(400, 400)
        if self.legend().pos().x() == 0:
            self.legend().setPos(QPointF(100, 100))
        self.legend().update_items()
        self.legend().setVisible(self.show_legend)

        x_positions = proj_data[0] - 0.5
        y_positions = proj_data[1] - 0.5
        z_positions = proj_data[2] - 0.5
        XAnchors = [anchor[0] for anchor in self.anchor_data]
        YAnchors = [anchor[1] for anchor in self.anchor_data]
        ZAnchors = [anchor[2] for anchor in self.anchor_data]
        data_size = len(self.raw_data)

        value_lines = []
        for i in range(data_size):
            if not valid_data[i]:
                continue
            if self.useDifferentColors and self.data_has_discrete_class:
                color = self.discrete_palette.getRGB(
                    self.original_data[self.data_class_index][i])
            else:
                color = (0, 0, 0)

            len_anchor_data = len(self.anchor_data)
            x = numpy.array([x_positions[i]] * len_anchor_data)
            y = numpy.array([y_positions[i]] * len_anchor_data)
            z = numpy.array([z_positions[i]] * len_anchor_data)
            dists = numpy.sqrt((XAnchors - x)**2 + (YAnchors - y)**2 +
                               (ZAnchors - z)**2)
            x_directions = 0.03 * (XAnchors - x) / dists
            y_directions = 0.03 * (YAnchors - y) / dists
            z_directions = 0.03 * (ZAnchors - z) / dists
            example_values = [
                self.no_jittering_scaled_data[attr_ind, i]
                for attr_ind in indices
            ]

            for j in range(len_anchor_data):
                value_lines.extend([
                    x_positions[i], y_positions[i], z_positions[i],
                    color[0] / 255., color[1] / 255., color[2] / 255., 0., 0.,
                    0., x_positions[i], y_positions[i], z_positions[i],
                    color[0] / 255., color[1] / 255., color[2] / 255.,
                    x_directions[j] * example_values[j],
                    y_directions[j] * example_values[j],
                    z_directions[j] * example_values[j]
                ])

        self._value_lines_buffer = VertexBuffer(
            numpy.array(value_lines, numpy.float32), [(3, GL_FLOAT),
                                                      (3, GL_FLOAT),
                                                      (3, GL_FLOAT)])

        self.update()
Пример #6
0
    def setData(self, data, subset_data=None, **args):
        OWLinProj3DPlot.set_data(self, data, subset_data, **args)

        # No need to generate backgroud grid sphere geometry more than once
        if hasattr(self, '_sphere_buffer'):
            return

        self.makeCurrent()

        lines = []
        num_parts = 30
        num_horizontal_rings = 20
        num_vertical_rings = 24
        r = 1.

        for i in range(num_horizontal_rings):
            z_offset = float(i) * 2 / num_horizontal_rings - 1.
            r = (1. - z_offset**2)**0.5
            for j in range(num_parts):
                angle_z_0 = float(j) * 2 * pi / num_parts
                angle_z_1 = float(j + 1) * 2 * pi / num_parts
                lines.extend([
                    sin(angle_z_0) * r, z_offset,
                    cos(angle_z_0) * r,
                    sin(angle_z_1) * r, z_offset,
                    cos(angle_z_1) * r
                ])

        for i in range(num_vertical_rings):
            r = 1.
            phi = 2 * i * pi / num_vertical_rings
            for j in range(num_parts):
                theta_0 = (j) * pi / num_parts
                theta_1 = (j + 1) * pi / num_parts
                lines.extend([
                    sin(theta_0) * cos(phi) * r,
                    cos(theta_0) * r,
                    sin(theta_0) * sin(phi) * r,
                    sin(theta_1) * cos(phi) * r,
                    cos(theta_1) * r,
                    sin(theta_1) * sin(phi) * r
                ])

        self._sphere_buffer = VertexBuffer(numpy.array(lines, numpy.float32),
                                           [(3, GL_FLOAT)])

        self._sphere_shader = QtOpenGL.QGLShaderProgram()
        self._sphere_shader.addShaderFromSourceFile(
            QtOpenGL.QGLShader.Vertex,
            os.path.join(os.path.dirname(__file__), 'sphere.vs'))
        self._sphere_shader.addShaderFromSourceFile(
            QtOpenGL.QGLShader.Fragment,
            os.path.join(os.path.dirname(__file__), 'sphere.fs'))

        self._sphere_shader.bindAttributeLocation('position', 0)

        if not self._sphere_shader.link():
            print('Failed to link sphere shader!')

        ## Cones
        cone_data = parse_obj('cone_hq.obj')
        vertices = []
        for v0, v1, v2, n0, n1, n2 in cone_data:
            vertices.extend([
                v0[0], v0[1], v0[2], n0[0], n0[1], n0[2], v1[0], v1[1], v1[2],
                n1[0], n1[1], n1[2], v2[0], v2[1], v2[2], n2[0], n2[1], n2[2]
            ])

        self._cone_buffer = VertexBuffer(numpy.array(vertices, numpy.float32),
                                         [(3, GL_FLOAT), (3, GL_FLOAT)])

        self._cone_shader = QtOpenGL.QGLShaderProgram()
        self._cone_shader.addShaderFromSourceFile(
            QtOpenGL.QGLShader.Vertex,
            os.path.join(os.path.dirname(__file__), 'cone.vs'))
        self._cone_shader.addShaderFromSourceFile(
            QtOpenGL.QGLShader.Fragment,
            os.path.join(os.path.dirname(__file__), 'cone.fs'))

        self._cone_shader.bindAttributeLocation('position', 0)
        self._cone_shader.bindAttributeLocation('normal', 1)

        if not self._cone_shader.link():
            print('Failed to link cone shader!')

        ## Another dummy shader (anchor grid)
        self._grid_shader = QtOpenGL.QGLShaderProgram()
        self._grid_shader.addShaderFromSourceFile(
            QtOpenGL.QGLShader.Vertex,
            os.path.join(os.path.dirname(__file__), 'grid.vs'))
        self._grid_shader.addShaderFromSourceFile(
            QtOpenGL.QGLShader.Fragment,
            os.path.join(os.path.dirname(__file__), 'grid.fs'))

        self._grid_shader.bindAttributeLocation('position', 0)

        if not self._grid_shader.link():
            print('Failed to link grid shader!')

        self.before_draw_callback = self.before_draw
Пример #7
0
class OWSphereviz3DPlot(OWLinProj3DPlot):
    def __init__(self, widget, parent=None, name='SpherevizPlot'):
        OWLinProj3DPlot.__init__(self, widget, parent, name)

        self.camera_angle = 90
        self.camera_type = 0  # Default, center, attribute
        self.show_anchor_grid = False

    def _build_anchor_grid(self):
        lines = []
        num_parts = 30
        anchors = numpy.array([a[:3] for a in self.anchor_data])
        for anchor in self.anchor_data:
            a0 = numpy.array(anchor[:3])
            neighbours = anchors.copy()
            neighbours = [(((n - a0)**2).sum(), n) for n in neighbours]
            neighbours.sort(key=lambda e: e[0])
            for i in range(1, min(len(anchors), 4)):
                difference = neighbours[i][1] - a0
                for j in range(num_parts):
                    lines.extend(
                        normalize(a0 + difference * j / float(num_parts)))
                    lines.extend(
                        normalize(a0 + difference *
                                  (j + 1) / float(num_parts)))

        self._grid_buffer = VertexBuffer(numpy.array(lines, numpy.float32),
                                         [(3, GL_FLOAT)])

    def update_data(self, labels=None, setAnchors=0, **args):
        OWLinProj3DPlot.updateData(self, labels, setAnchors, **args)

        if self.anchor_data:
            self._build_anchor_grid()

        self.updateGL()

    updateData = update_data

    def setData(self, data, subset_data=None, **args):
        OWLinProj3DPlot.set_data(self, data, subset_data, **args)

        # No need to generate backgroud grid sphere geometry more than once
        if hasattr(self, '_sphere_buffer'):
            return

        self.makeCurrent()

        lines = []
        num_parts = 30
        num_horizontal_rings = 20
        num_vertical_rings = 24
        r = 1.

        for i in range(num_horizontal_rings):
            z_offset = float(i) * 2 / num_horizontal_rings - 1.
            r = (1. - z_offset**2)**0.5
            for j in range(num_parts):
                angle_z_0 = float(j) * 2 * pi / num_parts
                angle_z_1 = float(j + 1) * 2 * pi / num_parts
                lines.extend([
                    sin(angle_z_0) * r, z_offset,
                    cos(angle_z_0) * r,
                    sin(angle_z_1) * r, z_offset,
                    cos(angle_z_1) * r
                ])

        for i in range(num_vertical_rings):
            r = 1.
            phi = 2 * i * pi / num_vertical_rings
            for j in range(num_parts):
                theta_0 = (j) * pi / num_parts
                theta_1 = (j + 1) * pi / num_parts
                lines.extend([
                    sin(theta_0) * cos(phi) * r,
                    cos(theta_0) * r,
                    sin(theta_0) * sin(phi) * r,
                    sin(theta_1) * cos(phi) * r,
                    cos(theta_1) * r,
                    sin(theta_1) * sin(phi) * r
                ])

        self._sphere_buffer = VertexBuffer(numpy.array(lines, numpy.float32),
                                           [(3, GL_FLOAT)])

        self._sphere_shader = QtOpenGL.QGLShaderProgram()
        self._sphere_shader.addShaderFromSourceFile(
            QtOpenGL.QGLShader.Vertex,
            os.path.join(os.path.dirname(__file__), 'sphere.vs'))
        self._sphere_shader.addShaderFromSourceFile(
            QtOpenGL.QGLShader.Fragment,
            os.path.join(os.path.dirname(__file__), 'sphere.fs'))

        self._sphere_shader.bindAttributeLocation('position', 0)

        if not self._sphere_shader.link():
            print('Failed to link sphere shader!')

        ## Cones
        cone_data = parse_obj('cone_hq.obj')
        vertices = []
        for v0, v1, v2, n0, n1, n2 in cone_data:
            vertices.extend([
                v0[0], v0[1], v0[2], n0[0], n0[1], n0[2], v1[0], v1[1], v1[2],
                n1[0], n1[1], n1[2], v2[0], v2[1], v2[2], n2[0], n2[1], n2[2]
            ])

        self._cone_buffer = VertexBuffer(numpy.array(vertices, numpy.float32),
                                         [(3, GL_FLOAT), (3, GL_FLOAT)])

        self._cone_shader = QtOpenGL.QGLShaderProgram()
        self._cone_shader.addShaderFromSourceFile(
            QtOpenGL.QGLShader.Vertex,
            os.path.join(os.path.dirname(__file__), 'cone.vs'))
        self._cone_shader.addShaderFromSourceFile(
            QtOpenGL.QGLShader.Fragment,
            os.path.join(os.path.dirname(__file__), 'cone.fs'))

        self._cone_shader.bindAttributeLocation('position', 0)
        self._cone_shader.bindAttributeLocation('normal', 1)

        if not self._cone_shader.link():
            print('Failed to link cone shader!')

        ## Another dummy shader (anchor grid)
        self._grid_shader = QtOpenGL.QGLShaderProgram()
        self._grid_shader.addShaderFromSourceFile(
            QtOpenGL.QGLShader.Vertex,
            os.path.join(os.path.dirname(__file__), 'grid.vs'))
        self._grid_shader.addShaderFromSourceFile(
            QtOpenGL.QGLShader.Fragment,
            os.path.join(os.path.dirname(__file__), 'grid.fs'))

        self._grid_shader.bindAttributeLocation('position', 0)

        if not self._grid_shader.link():
            print('Failed to link grid shader!')

        self.before_draw_callback = self.before_draw

    def update_camera_type(self):
        if self.camera_type == 2:
            self._random_anchor = choice(self.anchor_data)
        self.update()

    def before_draw(self):
        view = QMatrix4x4()
        if self.camera_type == 2:
            view.lookAt(
                QVector3D(self._random_anchor[0], self._random_anchor[1],
                          self._random_anchor[2]), self.camera,
                QVector3D(0, 1, 0))
            projection = QMatrix4x4()
            projection.perspective(self.camera_angle,
                                   float(self.width()) / self.height(), 0.01,
                                   5.)
            self.projection = projection
        elif self.camera_type == 1:
            view.lookAt(QVector3D(0, 0, 0), self.camera * self.camera_distance,
                        QVector3D(0, 1, 0))
            projection = QMatrix4x4()
            projection.perspective(self.camera_angle,
                                   float(self.width()) / self.height(), 0.01,
                                   5.)
            self.projection = projection
        else:
            view.lookAt(self.camera * self.camera_distance, QVector3D(0, 0, 0),
                        QVector3D(0, 1, 0))
        self.view = view

        self._draw_sphere()

        # Qt text rendering classes still use deprecated OpenGL functionality
        glEnable(GL_DEPTH_TEST)
        glDisable(GL_BLEND)
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        glMultMatrixd(numpy.array(self.projection.data(), dtype=float))
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        glMultMatrixd(numpy.array(self.view.data(), dtype=float))
        glMultMatrixd(numpy.array(self.model.data(), dtype=float))

        if self.showAnchors:
            for anchor in self.anchor_data:
                x, y, z, label = anchor

                direction = QVector3D(x, y, z)
                up = QVector3D(0, 1, 0)
                right = QVector3D.crossProduct(direction, up).normalized()
                up = QVector3D.crossProduct(right, direction)
                rotation = QMatrix4x4()
                rotation.setColumn(0, QVector4D(right, 0))
                rotation.setColumn(1, QVector4D(up, 0))
                rotation.setColumn(2, QVector4D(direction, 0))

                model = QMatrix4x4()
                model.translate(x, y, z)
                model = model * rotation
                model.rotate(-90, 1, 0, 0)
                model.translate(0, -0.05, 0)
                model.scale(0.02, 0.02, 0.02)

                self._cone_shader.bind()
                self._cone_shader.setUniformValue('projection',
                                                  self.projection)
                self._cone_shader.setUniformValue('modelview',
                                                  self.view * model)
                self._cone_buffer.draw()
                self._cone_shader.release()

                self.qglColor(self._theme.axis_values_color)
                self.renderText(x * 1.2, y * 1.2, z * 1.2, label)

            if self.anchor_data and not hasattr(self, '_grid_buffer'):
                self._build_anchor_grid()

            # Draw grid between anchors
            if self.show_anchor_grid:
                self._grid_shader.bind()
                self._grid_shader.setUniformValue('projection',
                                                  self.projection)
                self._grid_shader.setUniformValue('modelview',
                                                  self.view * self.model)
                self._grid_shader.setUniformValue('color',
                                                  self._theme.axis_color)
                self._grid_buffer.draw(GL_LINES)
                self._grid_shader.release()

        self._draw_value_lines()

    def _draw_sphere(self):
        glDisable(GL_DEPTH_TEST)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

        self._sphere_shader.bind()
        self._sphere_shader.setUniformValue('projection', self.projection)
        self._sphere_shader.setUniformValue('modelview',
                                            self.view * self.model)
        self._sphere_shader.setUniformValue('cam_position',
                                            self.camera * self.camera_distance)
        self._sphere_shader.setUniformValue('use_transparency',
                                            self.camera_type == 0)
        self._sphere_buffer.draw(GL_LINES)
        self._sphere_shader.release()

    def mouseMoveEvent(self, event):
        self.invert_mouse_x = self.camera_type != 0
        OWLinProj3DPlot.mouseMoveEvent(self, event)