source: orange/orange/OrangeWidgets/plot/owplot3d.py @ 9088:a3d02c4c389a

Revision 9088:a3d02c4c389a, 52.4 KB checked in by matejd <matejd@…>, 3 years ago (diff)

Added rotate button (in zoom-select toolbar) to 3d widgets

Line 
1'''
2
3#################
4Plot3D (``owplot3D``)
5#################
6
7.. autoclass:: OrangeWidgets.plot.OWPlot3D
8   
9'''
10
11import os
12import time
13from math import sin, cos, pi
14import struct
15
16from PyQt4.QtCore import *
17from PyQt4.QtGui import *
18from PyQt4 import QtOpenGL
19
20from OWDlgs import OWChooseImageSizeDlg
21from Orange.misc import deprecated_attribute, deprecated_members
22
23import orangeqt
24from owplotgui import OWPlotGUI
25from owtheme import PlotTheme
26from owplot import OWPlot
27from owlegend import OWLegend, OWLegendItem, OWLegendTitle, OWLegendGradient
28from owopenglrenderer import OWOpenGLRenderer
29from owconstants import ZOOMING, PANNING, ROTATING
30
31from OWColorPalette import ColorPaletteGenerator
32
33import OpenGL
34OpenGL.ERROR_CHECKING = False
35OpenGL.ERROR_LOGGING = False
36OpenGL.FULL_LOGGING = False
37OpenGL.ERROR_ON_COPY = False
38from OpenGL.GL import *
39from OpenGL.GL.ARB.vertex_array_object import *
40from OpenGL.GL.ARB.vertex_buffer_object import *
41from ctypes import c_void_p, c_char, c_char_p, POINTER
42
43import numpy
44
45try:
46    from itertools import chain
47    from itertools import izip as zip
48except:
49    pass
50
51def vec_div(v1, v2):
52    return QVector3D(v1.x() / v2.x(),
53                     v1.y() / v2.y(),
54                     v1.z() / v2.z())
55
56def lower_bound(value, vec):
57    if vec.x() < value:
58        vec.setX(value)
59    if vec.y() < value:
60        vec.setY(value)
61    if vec.z() < value:
62        vec.setZ(value)
63    return vec
64
65def enum(*sequential):
66    enums = dict(zip(sequential, range(len(sequential))))
67    enums['is_valid'] = lambda self, enum_value: enum_value < len(sequential)
68    enums['to_str'] = lambda self, enum_value: sequential[enum_value]
69    enums['__len__'] = lambda self: len(sequential)
70    return type('Enum', (), enums)()
71
72PlotState = enum('IDLE', 'DRAGGING_LEGEND', 'ROTATING', 'SCALING', 'SELECTING', 'PANNING')
73
74Symbol = enum('RECT', 'TRIANGLE', 'DTRIANGLE', 'CIRCLE', 'LTRIANGLE',
75              'DIAMOND', 'WEDGE', 'LWEDGE', 'CROSS', 'XCROSS')
76
77from plot.primitives import get_symbol_geometry, clamp, GeometryType
78
79class OWLegend3D(OWLegend):
80    def set_symbol_geometry(self, symbol, geometry):
81        if not hasattr(self, '_symbol_geometry'):
82            self._symbol_geometry = {}
83        self._symbol_geometry[symbol] = geometry
84
85    def _draw_item_background(self, pos, item, color):
86        rect = item.rect().normalized().adjusted(pos.x(), pos.y(), pos.x(), pos.y())
87        self.widget.renderer.draw_rectangle(
88            QVector3D(rect.left(), rect.top(), 0),
89            QVector3D(rect.left(), rect.bottom(), 0),
90            QVector3D(rect.right(), rect.bottom(), 0),
91            QVector3D(rect.right(), rect.top(), 0),
92            color=color)
93
94    def _draw_symbol(self, pos, symbol):
95        edges = self._symbol_geometry[symbol.symbol()]
96        color = symbol.color()
97        size = symbol.size() / 2
98        for v0, v1 in zip(edges[::2], edges[1::2]):
99            x0, y0 = v0.x(), v0.y()
100            x1, y1 = v1.x(), v1.y()
101            self.widget.renderer.draw_line(
102                QVector3D(x0*size + pos.x(), -y0*size + pos.y(), 0),
103                QVector3D(x1*size + pos.x(), -y1*size + pos.y(), 0),
104                color=color)
105
106    def _paint(self, widget):
107        self.widget = widget
108        glDisable(GL_DEPTH_TEST)
109        glDisable(GL_BLEND)
110        offset = QPointF(0, 15) # TODO
111
112        for category in self.items:
113            items = self.items[category]
114            for item in items:
115                if isinstance(item, OWLegendTitle):
116                    pos = self.pos() + item.pos()
117                    self._draw_item_background(pos, item.rect_item, widget._theme.background_color)
118
119                    widget.qglColor(widget._theme.labels_color)
120                    pos = self.pos() + item.pos() + item.text_item.pos() + offset
121                    widget.renderText(pos.x(), pos.y(), item.text_item.toPlainText(), item.text_item.font())
122                elif isinstance(item, OWLegendItem):
123                    pos = self.pos() + item.pos()
124                    self._draw_item_background(pos, item.rect_item, widget._theme.background_color)
125
126                    widget.qglColor(widget._theme.labels_color)
127                    pos = self.pos() + item.pos() + item.text_item.pos() + offset
128                    widget.renderText(pos.x(), pos.y(), item.text_item.toPlainText(), item.text_item.font())
129
130                    symbol = item.point_item
131                    pos = self.pos() + item.pos() + symbol.pos()
132                    self._draw_symbol(pos, symbol)
133                elif isinstance(item, OWLegendGradient):
134                    pos = self.pos() + item.pos()
135                    proxy = lambda: None
136                    proxy.rect = lambda: item.rect
137                    self._draw_item_background(pos, proxy, widget._theme.background_color)
138
139                    widget.qglColor(widget._theme.labels_color)
140                    for label in item.label_items:
141                        pos = self.pos() + item.pos() + label.pos() + offset + QPointF(5, 0)
142                        widget.renderText(pos.x(), pos.y(), label.toPlainText(), label.font())
143
144                    pos = self.pos() + item.pos() + item.gradient_item.pos()
145                    rect = item.gradient_item.rect().normalized().adjusted(pos.x(), pos.y(), pos.x(), pos.y())
146                    widget.renderer.draw_rectangle(
147                        QVector3D(rect.left(), rect.top(), 0),
148                        QVector3D(rect.left(), rect.bottom(), 0),
149                        QVector3D(rect.right(), rect.bottom(), 0),
150                        QVector3D(rect.right(), rect.top(), 0),
151                        QColor(0, 0, 0),
152                        QColor(0, 0, 255),
153                        QColor(0, 0, 255),
154                        QColor(0, 0, 0))
155
156name_map = {
157    "saveToFileDirect": "save_to_file_direct",
158    "saveToFile" : "save_to_file",
159}
160
161@deprecated_members(name_map, wrap_methods=name_map.keys())
162class OWPlot3D(orangeqt.Plot3D):
163    '''
164    The base class behind 3D plots in Orange. Uses OpenGL as its rendering platform.
165
166    **Settings**
167
168        .. attribute:: show_legend
169
170            A boolean controlling whether the legend is displayed or not
171
172        .. attribute:: gui
173
174            An :obj:`.OWPlotGUI` object associated with this graph
175
176    **Data**
177        This is the most important part of the Plot3D API. :meth:`set_plot_data` is
178        used to (not surprisingly) set the data to draw.
179        :meth:`set_features` tells Plot3D how to interpret the data (this method must
180        be called after :meth:`set_plot_data` and can be called multiple times).
181        :meth:`set_valid_data` optionally informs the plot which examples are invalid and
182        should not be drawn. It should be called after set_plot_data, but before set_features.
183        This separation permits certain optimizations, e.g. ScatterPlot3D sets data once only (at
184        the beginning), later on it calls solely set_features and set_valid_data.
185
186        .. automethod:: set_plot_data
187
188        .. automethod:: set_valid_data
189
190        .. automethod:: set_features
191
192    **Selections**
193        There are four possible selection behaviors used for selecting points in OWPlot3D.
194
195        .. data:: AddSelection
196
197            Points are added to the current selection, without affecting currently selected points.
198
199        .. data:: RemoveSelection
200
201            Points are removed from the current selection.
202
203        .. data:: ToggleSelection
204
205            The points' selection state is toggled.
206
207        .. data:: ReplaceSelection
208
209            The current selection is replaced with new one.
210
211        .. automethod:: select_points
212
213        .. automethod:: unselect_all_points
214
215        .. automethod:: get_selected_indices
216
217        .. automethod:: get_min_max_selected
218
219        .. automethod:: set_selection_behavior
220
221    **Callbacks**
222
223        Plot3D provides several callbacks which can be used to perform additional tasks (
224        such as drawing custom geometry before/after the data is drawn). For example usage
225        see ``OWScatterPlot3D``. Callbacks provided:
226
227            ==============               ==================================================
228            Callback                     Event
229            --------------               --------------------------------------------------
230            auto_send_selection_callback Selection changed.
231            mouseover_callback           Mouse cursor moved over a point. Also send example's index.
232            before_draw_callback         Right before drawing points (but after
233                                         current camera transformations have been computed,
234                                         so it's safe to use ``projection``, ``view`` and
235                                         ``model``).
236            after_draw_callback          Right after points have been drawn.
237            ==============               ==================================================
238
239    **Coordinate transformations**
240
241        Data set by ``set_plot_data`` is in what Plot3D calls
242        data coordinate system. Plot coordinate system takes into account plot translation
243        and scale (together effectively being zoom as well).
244
245        .. automethod:: map_to_plot
246
247        .. automethod:: map_to_data
248
249    **Themes**
250
251        Colors used for data points are specified with two palettes, one for continuous attributes, and one for
252        discrete ones.  Both are created by
253        :obj:`.OWColorPalette.ColorPaletteGenerator`
254
255        .. attribute:: continuous_palette
256
257            The palette used when point color represents a continuous attribute
258
259        .. attribute:: discrete_palette
260
261            The palette used when point color represents a discrete attribute
262    '''
263    def __init__(self, parent=None):
264        orangeqt.Plot3D.__init__(self, parent)
265
266        # Don't clear background when using QPainter
267        self.setAutoFillBackground(False)
268
269        self.camera_distance = 6.
270        self.scale_factor = 0.30
271        self.rotation_factor = 0.3
272        self.zoom_factor = 2000.
273        self.yaw = self.pitch = -pi / 4.
274        self.panning_factor = 0.8
275        self.perspective_near = 0.5
276        self.perspective_far = 10.
277        self.camera_fov = 14.
278        self.update_camera()
279
280        self.show_legend = True
281        self._legend = OWLegend3D(self, None)
282        self._legend_margin = QRectF(0, 0, 100, 0)
283        self._legend_moved = False
284        self._legend.set_floating(True)
285        self._legend.set_orientation(Qt.Vertical)
286
287        self.use_2d_symbols = False
288        self.symbol_scale = 1.
289        self.alpha_value = 255
290
291        self._state = PlotState.IDLE
292        self._selection = None
293        self.selection_behavior = OWPlot.AddSelection
294
295        ## Callbacks
296        self.auto_send_selection_callback = None
297        self.mouseover_callback = None
298        self.before_draw_callback = None
299        self.after_draw_callback = None
300
301        self.setMouseTracking(True)
302        self._mouse_position = QPoint(0, 0)
303        self.invert_mouse_x = False
304        self.mouse_sensitivity = 5
305
306        self.clear_plot_transformations()
307
308        self._theme = PlotTheme()
309        self._tooltip_fbo_dirty = True
310        self._tooltip_win_center = [0, 0]
311        self._use_fbos = True
312
313        # If True, do drawing using instancing + geometry shader processing,
314        # if False, build VBO every time set_plot_data is called.
315        self._use_opengl_3 = False
316        self.hide_outside = False
317        self.fade_outside = True
318
319        self.data = self.data_array = None
320        self.x_index = -1
321        self.y_index = -1
322        self.z_index = -1
323        self.label_index = -1
324        self.color_index = -1
325        self.symbol_index = -1
326        self.size_index = -1
327        self.colors = []
328        self.num_symbols_used = -1
329        self.x_discrete = False
330        self.y_discrete = False
331        self.z_discrete = False
332
333        self.continuous_palette = ColorPaletteGenerator(numberOfColors=-1)
334        self.discrete_palette = ColorPaletteGenerator()
335
336        self.gui = OWPlotGUI(self)
337    '''
338            An :obj:`.OWPlotGUI` object associated with this plot
339    '''
340
341    def legend(self):
342        '''
343            Returns the plot's legend, which is a :obj:`OrangeWidgets.plot.OWLegend`
344        '''
345        return self._legend
346
347    def initializeGL(self):
348        if hasattr(self, '_init_done'):
349            return
350        self.makeCurrent()
351        glClearColor(1.0, 1.0, 1.0, 1.0)
352        glClearDepth(1.0)
353        glDepthFunc(GL_LESS)
354        glEnable(GL_DEPTH_TEST)
355        glEnable(GL_LINE_SMOOTH) # TODO
356        glDisable(GL_CULL_FACE)
357        glEnable(GL_MULTISAMPLE)
358
359        # TODO: check hardware for OpenGL 3.x+ support
360
361        self.renderer = OWOpenGLRenderer()
362
363        if self._use_opengl_3:
364            self._feedback_generated = False
365
366            self.generating_program = QtOpenGL.QGLShaderProgram()
367            self.generating_program.addShaderFromSourceFile(QtOpenGL.QGLShader.Geometry,
368                os.path.join(os.path.dirname(__file__), 'generator.gs'))
369            self.generating_program.addShaderFromSourceFile(QtOpenGL.QGLShader.Vertex,
370                os.path.join(os.path.dirname(__file__), 'generator.vs'))
371            varyings = (c_char_p * 5)()
372            varyings[:] = ['out_position', 'out_offset', 'out_color', 'out_normal', 'out_index']
373            glTransformFeedbackVaryings(self.generating_program.programId(), 5, 
374                ctypes.cast(varyings, POINTER(POINTER(c_char))), GL_INTERLEAVED_ATTRIBS)
375
376            self.generating_program.bindAttributeLocation('index', 0)
377
378            if not self.generating_program.link():
379                print('Failed to link generating shader! Attribute changes may be slow.')
380            else:
381                print('Generating shader linked.')
382
383            # Upload all symbol geometry into a TBO (texture buffer object), so that generating
384            # geometry shader will have access to it. (TBO is easier to use than a texture in this use case).
385            geometry_data = []
386            symbols_indices = []
387            symbols_sizes = []
388            for symbol in range(len(Symbol)):
389                triangles = get_symbol_geometry(symbol, GeometryType.SOLID_3D)
390                symbols_indices.append(len(geometry_data) / 3)
391                symbols_sizes.append(len(triangles))
392                for tri in triangles:
393                    geometry_data.extend(chain(*tri))
394
395            for symbol in range(len(Symbol)):
396                triangles = get_symbol_geometry(symbol, GeometryType.SOLID_2D)
397                symbols_indices.append(len(geometry_data) / 3)
398                symbols_sizes.append(len(triangles))
399                for tri in triangles:
400                    geometry_data.extend(chain(*tri))
401
402            self.symbols_indices = symbols_indices
403            self.symbols_sizes = symbols_sizes
404
405            tbo = glGenBuffers(1)
406            glBindBuffer(GL_TEXTURE_BUFFER, tbo)
407            glBufferData(GL_TEXTURE_BUFFER, len(geometry_data)*4, numpy.array(geometry_data, 'f'), GL_STATIC_DRAW)
408            glBindBuffer(GL_TEXTURE_BUFFER, 0)
409            self.symbol_buffer = glGenTextures(1)
410            glBindTexture(GL_TEXTURE_BUFFER, self.symbol_buffer)
411            glTexBuffer(GL_TEXTURE_BUFFER, GL_RGB32F, tbo) # 3 floating-point components
412            glBindTexture(GL_TEXTURE_BUFFER, 0)
413
414            # Generate dummy vertex buffer (points which will be fed to the geometry shader).
415            self.dummy_vao = GLuint(0)
416            glGenVertexArrays(1, self.dummy_vao)
417            glBindVertexArray(self.dummy_vao)
418            vertex_buffer_id = glGenBuffers(1)
419            glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_id)
420            glBufferData(GL_ARRAY_BUFFER, numpy.arange(50*1000, dtype=numpy.float32), GL_STATIC_DRAW)
421            glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, 4, c_void_p(0))
422            glEnableVertexAttribArray(0)
423            glBindVertexArray(0)
424            glBindBuffer(GL_ARRAY_BUFFER, 0)
425
426            # Specify an output VBO (and VAO)
427            self.feedback_vao = feedback_vao = GLuint(0)
428            glGenVertexArrays(1, feedback_vao)
429            glBindVertexArray(feedback_vao)
430            self.feedback_bid = feedback_bid = glGenBuffers(1)
431            glBindBuffer(GL_ARRAY_BUFFER, feedback_bid)
432            vertex_size = (3+3+3+3+1)*4
433            glBufferData(GL_ARRAY_BUFFER, 20*1000*144*vertex_size, c_void_p(0), GL_STATIC_DRAW)
434            glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, vertex_size, c_void_p(0))
435            glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, vertex_size, c_void_p(3*4))
436            glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, vertex_size, c_void_p(6*4))
437            glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, vertex_size, c_void_p(9*4))
438            glVertexAttribPointer(4, 1, GL_FLOAT, GL_FALSE, vertex_size, c_void_p(12*4))
439            glEnableVertexAttribArray(0)
440            glEnableVertexAttribArray(1)
441            glEnableVertexAttribArray(2)
442            glEnableVertexAttribArray(3)
443            glEnableVertexAttribArray(4)
444            glBindVertexArray(0)
445            glBindBuffer(GL_ARRAY_BUFFER, 0)
446        else:
447            # Load symbol geometry and send it to the C++ parent.
448            geometry_data = []
449            for symbol in range(len(Symbol)):
450                triangles = get_symbol_geometry(symbol, GeometryType.SOLID_2D)
451                triangles = [QVector3D(*v) for triangle in triangles for v in triangle]
452                orangeqt.Plot3D.set_symbol_geometry(self, symbol, 0, triangles)
453
454                triangles = get_symbol_geometry(symbol, GeometryType.SOLID_3D)
455                triangles = [QVector3D(*v) for triangle in triangles for v in triangle]
456                orangeqt.Plot3D.set_symbol_geometry(self, symbol, 1, triangles)
457
458                edges = get_symbol_geometry(symbol, GeometryType.EDGE_2D)
459                edges = [QVector3D(*v) for edge in edges for v in edge]
460                orangeqt.Plot3D.set_symbol_geometry(self, symbol, 2, edges)
461                self._legend.set_symbol_geometry(symbol, edges)
462
463                edges = get_symbol_geometry(symbol, GeometryType.EDGE_3D)
464                edges = [QVector3D(*v) for edge in edges for v in edge]
465                orangeqt.Plot3D.set_symbol_geometry(self, symbol, 3, edges)
466
467        self.symbol_program = QtOpenGL.QGLShaderProgram()
468        self.symbol_program.addShaderFromSourceFile(QtOpenGL.QGLShader.Vertex,
469            os.path.join(os.path.dirname(__file__), 'symbol.vs'))
470        self.symbol_program.addShaderFromSourceFile(QtOpenGL.QGLShader.Fragment,
471            os.path.join(os.path.dirname(__file__), 'symbol.fs'))
472
473        self.symbol_program.bindAttributeLocation('position', 0)
474        self.symbol_program.bindAttributeLocation('offset',   1)
475        self.symbol_program.bindAttributeLocation('color',    2)
476        self.symbol_program.bindAttributeLocation('normal',   3)
477
478        if not self.symbol_program.link():
479            print('Failed to link symbol shader!')
480        else:
481            print('Symbol shader linked.')
482
483        self.symbol_program_use_2d_symbols = self.symbol_program.uniformLocation('use_2d_symbols')
484        self.symbol_program_symbol_scale   = self.symbol_program.uniformLocation('symbol_scale')
485        self.symbol_program_alpha_value    = self.symbol_program.uniformLocation('alpha_value')
486        self.symbol_program_scale          = self.symbol_program.uniformLocation('scale')
487        self.symbol_program_translation    = self.symbol_program.uniformLocation('translation')
488        self.symbol_program_hide_outside   = self.symbol_program.uniformLocation('hide_outside')
489        self.symbol_program_fade_outside   = self.symbol_program.uniformLocation('fade_outside')
490        self.symbol_program_force_color    = self.symbol_program.uniformLocation('force_color')
491        self.symbol_program_encode_color   = self.symbol_program.uniformLocation('encode_color')
492
493        format = QtOpenGL.QGLFramebufferObjectFormat()
494        format.setAttachment(QtOpenGL.QGLFramebufferObject.Depth)
495        self._tooltip_fbo = QtOpenGL.QGLFramebufferObject(256, 256, format)
496        if self._tooltip_fbo.isValid():
497            print('Tooltip FBO created.')
498        else:
499            print('Failed to create tooltip FBO! Tooltips disabled.')
500            self._use_fbos = False
501
502        self._init_done = True
503
504    def resizeGL(self, width, height):
505        pass
506
507    def update_camera(self):
508        self.pitch = clamp(self.pitch, -3., -0.1)
509        self.camera = QVector3D(
510            sin(self.pitch)*cos(self.yaw),
511            cos(self.pitch),
512            sin(self.pitch)*sin(self.yaw))
513
514    def get_mvp(self):
515        '''
516        Return current model, view and projection transforms.
517        '''
518        projection = QMatrix4x4()
519        width, height = self.width(), self.height()
520        aspect = float(width) / height if height != 0 else 1
521        projection.perspective(self.camera_fov, aspect, self.perspective_near, self.perspective_far)
522
523        view = QMatrix4x4()
524        view.lookAt(
525            self.camera * self.camera_distance,
526            QVector3D(0,-0.1, 0),
527            QVector3D(0, 1, 0))
528
529        model = QMatrix4x4()
530        return model, view, projection
531
532    def set_2D_mode(self):
533        '''
534        Sets ortho projection and identity modelview transform. A convenience method which
535        can be called before doing 2D drawing.
536        '''
537        glMatrixMode(GL_PROJECTION)
538        glLoadIdentity()
539        glOrtho(0, self.width(), self.height(), 0, -1, 1)
540        glMatrixMode(GL_MODELVIEW)
541        glLoadIdentity()
542
543        self.projection = QMatrix4x4()
544        self.projection.ortho(0, self.width(), self.height(), 0, -1, 1)
545        self.model = QMatrix4x4()
546        self.view = QMatrix4x4()
547
548    def paintEvent(self, event):
549        glViewport(0, 0, self.width(), self.height())
550        self.qglClearColor(self._theme.background_color)
551
552        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
553
554        model, view, projection = self.get_mvp()
555        self.model = model
556        self.view = view
557        self.projection = projection
558
559        if self.before_draw_callback:
560            self.before_draw_callback()
561
562        plot_scale = lower_bound(1e-5, self.plot_scale+self.additional_scale)
563
564        self.symbol_program.bind()
565        self.symbol_program.setUniformValue('modelview', self.view * self.model)
566        self.symbol_program.setUniformValue('projection', self.projection)
567        self.symbol_program.setUniformValue(self.symbol_program_use_2d_symbols, self.use_2d_symbols)
568        self.symbol_program.setUniformValue(self.symbol_program_fade_outside,   self.fade_outside)
569        self.symbol_program.setUniformValue(self.symbol_program_hide_outside,   self.hide_outside)
570        self.symbol_program.setUniformValue(self.symbol_program_encode_color,   False)
571        self.symbol_program.setUniformValue(self.symbol_program_symbol_scale,   self.symbol_scale, self.symbol_scale)
572        self.symbol_program.setUniformValue(self.symbol_program_alpha_value,    self.alpha_value / 255., self.alpha_value / 255.)
573        self.symbol_program.setUniformValue(self.symbol_program_scale,          plot_scale)
574        self.symbol_program.setUniformValue(self.symbol_program_translation,    self.plot_translation)
575        self.symbol_program.setUniformValue(self.symbol_program_force_color,    0., 0., 0., 0.)
576
577        if self._use_opengl_3 and self._feedback_generated:
578            glEnable(GL_DEPTH_TEST)
579            glEnable(GL_BLEND)
580            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
581
582            glBindVertexArray(self.feedback_vao)
583            glDrawArrays(GL_TRIANGLES, 0, self.num_primitives_generated*3)
584            glBindVertexArray(0)
585        elif not self._use_opengl_3:
586            glDisable(GL_CULL_FACE)
587            glEnable(GL_DEPTH_TEST)
588            glEnable(GL_BLEND)
589            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
590            orangeqt.Plot3D.draw_data(self, self.symbol_program.programId(), self.alpha_value / 255.)
591
592        self.symbol_program.release()
593
594        self._draw_labels()
595
596        if self.after_draw_callback:
597            self.after_draw_callback()
598
599        if self._tooltip_fbo_dirty:
600            self._tooltip_fbo.bind()
601            glClearColor(1, 1, 1, 1)
602            glClearDepth(1)
603            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
604            glDisable(GL_BLEND)
605            glEnable(GL_DEPTH_TEST)
606
607            glViewport(-self._mouse_position.x()+128, -(self.height()-self._mouse_position.y())+128, self.width(), self.height())
608            self._tooltip_win_center = [self._mouse_position.x(), self._mouse_position.y()]
609
610            self.symbol_program.bind()
611            self.symbol_program.setUniformValue(self.symbol_program_encode_color, True)
612
613            if self._use_opengl_3 and self._feedback_generated:
614                glBindVertexArray(self.feedback_vao)
615                glDrawArrays(GL_TRIANGLES, 0, self.num_primitives_generated*3)
616                glBindVertexArray(0)
617            elif not self._use_opengl_3:
618                orangeqt.Plot3D.draw_data_solid(self)
619            self.symbol_program.release()
620            self._tooltip_fbo.release()
621            self._tooltip_fbo_dirty = False
622            glViewport(0, 0, self.width(), self.height())
623
624        self._draw_helpers()
625
626        if self.show_legend:
627            glMatrixMode(GL_PROJECTION)
628            glLoadIdentity()
629            glOrtho(0, self.width(), self.height(), 0, -1, 1)
630            glMatrixMode(GL_MODELVIEW)
631            glLoadIdentity()
632            glDisable(GL_BLEND)
633
634            self._legend._paint(self)
635
636        self.swapBuffers()
637
638    def _draw_labels(self):
639        if self.label_index < 0:
640            return
641
642        glMatrixMode(GL_PROJECTION)
643        glLoadIdentity()
644        glMultMatrixd(numpy.array(self.projection.data(), dtype=float))
645        glMatrixMode(GL_MODELVIEW)
646        glLoadIdentity()
647        glMultMatrixd(numpy.array(self.view.data(), dtype=float))
648        glMultMatrixd(numpy.array(self.model.data(), dtype=float))
649
650        self.qglColor(self._theme.labels_color)
651        for example in self.data.transpose():
652            x = example[self.x_index]
653            y = example[self.y_index]
654            z = example[self.z_index]
655            label = example[self.label_index]
656            x, y, z = self.map_to_plot(QVector3D(x, y, z))
657            # TODO
658            #if isinstance(label, str):
659                #self.renderText(x,y,z, label, font=self._theme.labels_font)
660            #else:
661            self.renderText(x,y,z, ('%.3f' % label).rstrip('0').rstrip('.'),
662                            font=self._theme.labels_font)
663
664    def _draw_helpers(self):
665        glEnable(GL_BLEND)
666        glDisable(GL_DEPTH_TEST)
667
668        projection = QMatrix4x4()
669        projection.ortho(0, self.width(), self.height(), 0, -1, 1)
670        model = view = QMatrix4x4()
671
672        self.renderer.set_transform(model, view, projection)
673
674        if self._state == PlotState.SCALING:
675            x, y = self._mouse_position.x(), self._mouse_position.y()
676            self.renderer.draw_triangle(QVector3D(x-5, y-30, 0),
677                                        QVector3D(x+5, y-30, 0),
678                                        QVector3D(x, y-40, 0),
679                                        color=self._theme.helpers_color)
680            self.renderer.draw_line(QVector3D(x, y, 0),
681                                    QVector3D(x, y-30, 0),
682                                    color=self._theme.helpers_color)
683            self.renderer.draw_triangle(QVector3D(x-5, y-10, 0),
684                                        QVector3D(x+5, y-10, 0),
685                                        QVector3D(x, y, 0),
686                                        color=self._theme.helpers_color)
687
688            self.renderer.draw_triangle(QVector3D(x+10, y, 0),
689                                        QVector3D(x+20, y-5, 0),
690                                        QVector3D(x+20, y+5, 0),
691                                        color=self._theme.helpers_color)
692            self.renderer.draw_line(QVector3D(x+10, y, 0),
693                                    QVector3D(x+40, y, 0),
694                                    color=self._theme.helpers_color)
695            self.renderer.draw_triangle(QVector3D(x+50, y, 0),
696                                        QVector3D(x+40, y-5, 0),
697                                        QVector3D(x+40, y+5, 0),
698                                        color=self._theme.helpers_color)
699
700            glMatrixMode(GL_PROJECTION)
701            glLoadIdentity()
702            glOrtho(0, self.width(), self.height(), 0, -1, 1)
703            glMatrixMode(GL_MODELVIEW)
704            glLoadIdentity()
705
706            self.renderText(x, y-50, 'Scale y axis', font=self._theme.labels_font)
707            self.renderText(x+60, y+3, 'Scale x and z axes', font=self._theme.labels_font)
708        elif self._state == PlotState.SELECTING:
709            internal_color = QColor(168, 202, 236, 50)
710            self.renderer.draw_rectangle(QVector3D(self._selection.left(), self._selection.top(), 0),
711                                         QVector3D(self._selection.right(), self._selection.top(), 0),
712                                         QVector3D(self._selection.right(), self._selection.bottom(), 0),
713                                         QVector3D(self._selection.left(), self._selection.bottom(), 0),
714                                         color=internal_color)
715
716            border_color = QColor(51, 153, 255, 192)
717            self.renderer.draw_line(QVector3D(self._selection.left(), self._selection.top(), 0),
718                                    QVector3D(self._selection.right(), self._selection.top(), 0),
719                                    border_color, border_color)
720            self.renderer.draw_line(QVector3D(self._selection.right(), self._selection.top(), 0),
721                                    QVector3D(self._selection.right(), self._selection.bottom(), 0),
722                                    border_color, border_color)
723            self.renderer.draw_line(QVector3D(self._selection.right(), self._selection.bottom(), 0),
724                                    QVector3D(self._selection.left(), self._selection.bottom(), 0),
725                                    border_color, border_color)
726            self.renderer.draw_line(QVector3D(self._selection.left(), self._selection.bottom(), 0),
727                                    QVector3D(self._selection.left(), self._selection.top(), 0),
728                                    border_color, border_color)
729
730    def set_features(self,
731            x_index, y_index, z_index,
732            color_index, symbol_index, size_index, label_index,
733            colors, num_symbols_used,
734            x_discrete, y_discrete, z_discrete):
735        '''
736        Explains to the plot how to interpret the data set by :meth:`set_plot_data`. Its arguments
737        are indices (must be less than the size of an example) into the dataset (each one
738        specifies a column). Additionally, it accepts a list of colors (when color is a discrete
739        attribute), a value specifying how many different symbols are needed to display the data and
740        information whether or not positional data is discrete.
741
742        .. note:: This function does not add items to the legend automatically.
743                  You will have to add them yourself with :meth:`.OWLegend.add_item`.
744
745        :param x_index: Index (column) of the x coordinate.
746        :type int
747
748        :param y_index: Index (column) of the y coordinate.
749        :type int
750
751        :param z_index: Index (column) of the z coordinate.
752        :type int
753
754        :param color_index: Index (column) of the color attribute.
755        :type int
756
757        :param symbol_index: Index (column) of the symbol attribute.
758        :type int
759
760        :param size_index: Index (column) of the size attribute.
761        :type int
762
763        :param label_index: Index (column) of the label attribute.
764        :type int
765
766        :param colors: List of colors used for symbols. When color is a discrete attribute,
767            this list should be empty. You should make sure the number of colors in this list
768            equals the number of unique values in the color attribute.
769        :type list of QColor
770
771        :param num_symbols_used: Specifies the number of unique values in the symbol attribute.
772            Must be -1 if all points are to use the same symbol.
773        :type int
774
775        :param x_discrete: Specifies whether or not x coordinate is discrete.
776        :type bool
777
778        :param y_discrete: Specifies whether or not y coordinate is discrete.
779        :type bool
780
781        :param z_discrete: Specifies whether or not z coordinate is discrete.
782        :type bool
783        '''
784        if self.data == None:
785            print('Error: set_plot_data has not been called yet!')
786            return
787        start = time.time()
788        self.makeCurrent()
789        self.x_index = x_index
790        self.y_index = y_index
791        self.z_index = z_index
792        self.color_index = color_index
793        self.symbol_index = symbol_index
794        self.size_index = size_index
795        self.colors = colors
796        self.num_symbols_used = num_symbols_used
797        self.x_discrete = x_discrete
798        self.y_discrete = y_discrete
799        self.z_discrete = z_discrete
800        self.label_index = label_index
801
802        if self.num_examples > 10*1000:
803            self.use_2d_symbols = True
804
805        if self._use_opengl_3:
806            # Re-run generating program (geometry shader), store
807            # results through transform feedback into a VBO on the GPU.
808            self.generating_program.bind()
809            self.generating_program.setUniformValue('x_index', x_index)
810            self.generating_program.setUniformValue('y_index', y_index)
811            self.generating_program.setUniformValue('z_index', z_index)
812            self.generating_program.setUniformValue('x_discrete', x_discrete)
813            self.generating_program.setUniformValue('y_discrete', y_discrete)
814            self.generating_program.setUniformValue('z_discrete', z_discrete)
815            self.generating_program.setUniformValue('color_index', color_index)
816            self.generating_program.setUniformValue('symbol_index', symbol_index)
817            self.generating_program.setUniformValue('size_index', size_index)
818            self.generating_program.setUniformValue('use_2d_symbols', self.use_2d_symbols)
819            self.generating_program.setUniformValue('example_size', self.example_size)
820            self.generating_program.setUniformValue('num_colors', len(colors))
821            self.generating_program.setUniformValue('num_symbols_used', num_symbols_used)
822            # TODO: colors is list of QColor
823            glUniform3fv(glGetUniformLocation(self.generating_program.programId(), 'colors'),
824                len(colors), numpy.array(colors, 'f').ravel())
825            glUniform1iv(glGetUniformLocation(self.generating_program.programId(), 'symbols_sizes'),
826                len(Symbol)*2, numpy.array(self.symbols_sizes, dtype='i'))
827            glUniform1iv(glGetUniformLocation(self.generating_program.programId(), 'symbols_indices'),
828                len(Symbol)*2, numpy.array(self.symbols_indices, dtype='i'))
829
830            glActiveTexture(GL_TEXTURE0)
831            glBindTexture(GL_TEXTURE_BUFFER, self.symbol_buffer)
832            self.generating_program.setUniformValue('symbol_buffer', 0)
833            glActiveTexture(GL_TEXTURE1)
834            glBindTexture(GL_TEXTURE_BUFFER, self.data_buffer)
835            self.generating_program.setUniformValue('data_buffer', 1)
836
837            qid = glGenQueries(1)
838            glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, qid)
839            glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, self.feedback_bid)
840            glEnable(GL_RASTERIZER_DISCARD)
841            glBeginTransformFeedback(GL_TRIANGLES)
842
843            glBindVertexArray(self.dummy_vao)
844            glDrawArrays(GL_POINTS, 0, self.num_examples)
845
846            glEndTransformFeedback()
847            glDisable(GL_RASTERIZER_DISCARD)
848
849            glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN)
850            self.num_primitives_generated = glGetQueryObjectuiv(qid, GL_QUERY_RESULT)
851            glBindVertexArray(0)
852            self._feedback_generated = True
853            print('Num generated primitives: ' + str(self.num_primitives_generated))
854
855            self.generating_program.release()
856            glActiveTexture(GL_TEXTURE0)
857            print('Generation took ' + str(time.time()-start) + ' seconds')
858        else:
859            orangeqt.Plot3D.update_data(self, x_index, y_index, z_index,
860                color_index, symbol_index, size_index, label_index,
861                colors, num_symbols_used,
862                x_discrete, y_discrete, z_discrete, self.use_2d_symbols)
863            print('Data processing took ' + str(time.time() - start) + ' seconds')
864
865        self.update()
866
867    def set_plot_data(self, data, subset_data=None):
868        '''
869        Sets the data to be drawn. Data is expected to be scaled already (see ``OWScatterPlot3D`` for example).
870
871        :param data: Data
872        :type data: numpy array
873        '''
874        self.makeCurrent()
875        self.data = data
876        self.data_array = numpy.array(data.transpose().flatten(), dtype=numpy.float32)
877        self.example_size = len(data)
878        self.num_examples = len(data[0])
879
880        if self._use_opengl_3:
881            tbo = glGenBuffers(1)
882            glBindBuffer(GL_TEXTURE_BUFFER, tbo)
883            glBufferData(GL_TEXTURE_BUFFER, len(self.data_array)*4, self.data_array, GL_STATIC_DRAW)
884            glBindBuffer(GL_TEXTURE_BUFFER, 0)
885
886            self.data_buffer = glGenTextures(1)
887            glBindTexture(GL_TEXTURE_BUFFER, self.data_buffer)
888            GL_R32F = 0x822E
889            glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, tbo)
890            glBindTexture(GL_TEXTURE_BUFFER, 0)
891        else:
892            orangeqt.Plot3D.set_data(self, self.data_array,
893                                     self.num_examples,
894                                     self.example_size)
895
896    def set_valid_data(self, valid_data):
897        '''
898        Specifies which examples are invalid and should not be displayed.
899
900        :param valid_data: List of booleans: true for valid indices, false otherwise
901        :type valid_data: list of bool
902        '''
903        self.valid_data = numpy.array(valid_data, dtype=bool)
904        orangeqt.Plot3D.set_valid_data(self, self.valid_data)
905
906    def set_new_zoom(self, min, max):
907        '''
908        Specifies new zoom in data coordinates. Zoom is provided in form of plot translation
909        and scale, not camera transformation. This might not be what you want (``OWLinProj3D``
910        disables this behavior for example). Plot3D remembers translation and scale.
911        ``zoom_out`` can be use to restore the previous zoom level.
912
913        :param min: Lower left corner of the new zoom volume.
914        :type QVector3D
915
916        :param max: Upper right corner of the new zoom volume.
917        :type QVector3D
918        '''
919        self._zoom_stack.append((self.plot_scale, self.plot_translation))
920        center = (max + min) / 2.
921        new_translation = -center
922        self._zoomed_size = max-min + QVector3D(1e-5, 1e-5, 1e-5)
923        new_scale = vec_div(QVector3D(1, 1, 1), self._zoomed_size)
924        self._animate_new_scale_translation(new_scale, new_translation)
925
926    def zoom_out(self):
927        '''
928        Restores previous zoom level.
929        '''
930        if len(self._zoom_stack) < 1:
931            new_translation = QVector3D(-0.5, -0.5, -0.5)
932            new_scale = QVector3D(1, 1, 1)
933        else:
934            new_scale, new_translation = self._zoom_stack.pop()
935        self._animate_new_scale_translation(new_scale, new_translation)
936        self._zoomed_size = vec_div(QVector3D(1, 1, 1), new_scale)
937
938    def _animate_new_scale_translation(self, new_scale, new_translation, num_steps=10):
939        translation_step = (new_translation - self.plot_translation) / float(num_steps)
940        scale_step = (new_scale - self.plot_scale) / float(num_steps)
941        # Animate zooming: translate first for a number of steps,
942        # then scale. Make sure it doesn't take too long.
943        start = time.time()
944        for i in range(num_steps):
945            if time.time() - start > 1.:
946                self.plot_translation = new_translation
947                break
948            self.plot_translation = self.plot_translation + translation_step
949            self.repaint()
950        for i in range(num_steps):
951            if time.time() - start > 1.:
952                self.plot_scale = new_scale
953                break
954            self.plot_scale = self.plot_scale + scale_step
955            self.repaint()
956
957    def save_to_file(self, extraButtons=[]):
958        print('Save to file called!')
959        sizeDlg = OWChooseImageSizeDlg(self, extraButtons, parent=self)
960        sizeDlg.exec_()
961
962    def save_to_file_direct(self, fileName, size=None):
963        sizeDlg = OWChooseImageSizeDlg(self)
964        sizeDlg.saveImage(fileName, size)
965
966    def map_to_plot(self, point):
967        '''
968        Maps ``point`` to plot coordinates (applies current translation and scale).
969        ``point`` is assumed to be in data coordinates.
970
971        :param point: Location in space
972        :type QVector3D
973        '''
974        plot_scale = lower_bound(1e-5, self.plot_scale+self.additional_scale)
975        point = (point + self.plot_translation) * plot_scale
976        return point
977
978    def map_to_data(self, point):
979        '''
980        Maps ``point`` to data coordinates (applies inverse of the current translation and scale).
981        ``point`` is assumed to be in plot coordinates.
982
983        :param point: Location in space
984        :type QVector3D
985        '''
986        plot_scale = lower_bound(1e-5, self.plot_scale+self.additional_scale)
987        point = vec_div(point, plot_scale) - self.plot_translation
988        return point
989
990    def get_min_max_selected(self, area):
991        '''
992        Returns min/max x/y/z coordinate values of currently selected points.
993
994        :param area: Rectangular area.
995        :type QRect
996        '''
997        viewport = [0, 0, self.width(), self.height()]
998        area = [min(area.left(), area.right()), min(area.top(), area.bottom()), abs(area.width()), abs(area.height())]
999        min_max = orangeqt.Plot3D.get_min_max_selected(self, area, self.projection * self.view * self.model,
1000                                                       viewport, self.plot_scale, self.plot_translation)
1001        return min_max
1002
1003    def get_selected_indices(self):
1004        '''
1005        Returns indices of currently selected points (examples).
1006        '''
1007        return orangeqt.Plot3D.get_selected_indices(self)
1008
1009    def select_points(self, area, behavior):
1010        '''
1011        Selects all points inside volume specified by rectangular area and current camera transform
1012        using selection ``behavior``.
1013
1014        :param area: Rectangular area.
1015        :type QRect
1016
1017        :param behavior: :data:`AddSelection`, :data:`RemoveSelection`, :data:`ToggleSelection` or :data:`ReplaceSelection`
1018        :type behavior: int
1019        '''
1020        viewport = [0, 0, self.width(), self.height()]
1021        area = [min(area.left(), area.right()), min(area.top(), area.bottom()), abs(area.width()), abs(area.height())]
1022        orangeqt.Plot3D.select_points(self, area, self.projection * self.view * self.model,
1023                                      viewport, self.plot_scale, self.plot_translation,
1024                                      behavior)
1025        orangeqt.Plot3D.update_data(self, self.x_index, self.y_index, self.z_index,
1026                                    self.color_index, self.symbol_index, self.size_index, self.label_index,
1027                                    self.colors, self.num_symbols_used,
1028                                    self.x_discrete, self.y_discrete, self.z_discrete, self.use_2d_symbols)
1029
1030    def unselect_all_points(self):
1031        '''
1032        Unselects everything.
1033        '''
1034        orangeqt.Plot3D.unselect_all_points(self)
1035        orangeqt.Plot3D.update_data(self, self.x_index, self.y_index, self.z_index,
1036                                    self.color_index, self.symbol_index, self.size_index, self.label_index,
1037                                    self.colors, self.num_symbols_used,
1038                                    self.x_discrete, self.y_discrete, self.z_discrete, self.use_2d_symbols)
1039        self.update()
1040
1041    def set_selection_behavior(self, behavior):
1042        '''
1043        Sets selection behavior.
1044
1045        :param behavior: :data:`AddSelection`, :data:`RemoveSelection`, :data:`ToggleSelection` or :data:`ReplaceSelection`
1046        :type behavior: int
1047        '''
1048        self.selection_behavior = behavior
1049
1050    def send_selection(self):
1051        if self.auto_send_selection_callback:
1052            self.auto_send_selection_callback()
1053
1054    def mousePressEvent(self, event):
1055        pos = self._mouse_position = event.pos()
1056        buttons = event.buttons()
1057
1058        self._selection = None
1059
1060        if buttons & Qt.LeftButton:
1061            legend_pos = self._legend.pos()
1062            lx, ly = legend_pos.x(), legend_pos.y()
1063            if self.show_legend and self._legend.boundingRect().adjusted(lx, ly, lx, ly).contains(pos.x(), pos.y()):
1064                event.scenePos = lambda: QPointF(pos)
1065                self._legend.mousePressEvent(event)
1066                self.setCursor(Qt.ClosedHandCursor)
1067                self._state = PlotState.DRAGGING_LEGEND
1068            elif self.state == PANNING:
1069                self._state = PlotState.PANNING
1070            elif self.state == ROTATING or QApplication.keyboardModifiers() & Qt.ShiftModifier:
1071                self._state = PlotState.ROTATING
1072            else:
1073                self._state = PlotState.SELECTING
1074                self._selection = QRect(pos.x(), pos.y(), 0, 0)
1075        elif buttons & Qt.RightButton:
1076            if QApplication.keyboardModifiers() & Qt.ShiftModifier:
1077                self._state = PlotState.SCALING
1078                self.scaling_init_pos = self._mouse_position
1079                self.additional_scale = QVector3D(0, 0, 0)
1080            else:
1081                self.zoom_out()
1082            self.update()
1083        elif buttons & Qt.MiddleButton:
1084            if QApplication.keyboardModifiers() & Qt.ShiftModifier:
1085                self._state = PlotState.PANNING
1086            else:
1087                self._state = PlotState.ROTATING
1088
1089    def _check_mouseover(self, pos):
1090        if self.mouseover_callback != None and self._state == PlotState.IDLE:
1091            if abs(pos.x() - self._tooltip_win_center[0]) > 100 or\
1092               abs(pos.y() - self._tooltip_win_center[1]) > 100:
1093                self._tooltip_fbo_dirty = True
1094                self.update()
1095            # Use pixel-color-picking to read example index under mouse cursor (also called ID rendering).
1096            self._tooltip_fbo.bind()
1097            value = glReadPixels(pos.x() - self._tooltip_win_center[0] + 128,
1098                                 self._tooltip_win_center[1] - pos.y() + 128,
1099                                 1, 1,
1100                                 GL_RGBA,
1101                                 GL_UNSIGNED_BYTE)
1102            self._tooltip_fbo.release()
1103            value = struct.unpack('I', value)[0]
1104            # Check if value is less than 4294967295 (
1105            # the highest 32-bit unsigned integer) which
1106            # corresponds to white background in color-picking buffer.
1107            if value < 4294967295:
1108                self.mouseover_callback(value)
1109
1110    def mouseMoveEvent(self, event):
1111        pos = event.pos()
1112
1113        self._check_mouseover(pos)
1114
1115        dx = pos.x() - self._mouse_position.x()
1116        dy = pos.y() - self._mouse_position.y()
1117
1118        if self.invert_mouse_x:
1119            dx = -dx
1120
1121        if self._state == PlotState.SELECTING:
1122            self._selection.setBottomRight(pos)
1123        elif self._state == PlotState.DRAGGING_LEGEND:
1124            event.scenePos = lambda: QPointF(pos)
1125            self._legend.mouseMoveEvent(event)
1126        elif self._state == PlotState.ROTATING:
1127            self.yaw += (self.mouse_sensitivity / 5.) * dx / (self.rotation_factor*self.width())
1128            self.pitch += (self.mouse_sensitivity / 5.) * dy / (self.rotation_factor*self.height())
1129            self.update_camera()
1130        elif self._state == PlotState.PANNING:
1131            right_vec = QVector3D.crossProduct(self.camera, QVector3D(0, 1, 0)).normalized()
1132            up_vec = QVector3D.crossProduct(right_vec, self.camera).normalized()
1133            right_vec.setX(right_vec.x() * dx / (self.width() * self.plot_scale.x() * self.panning_factor))
1134            right_vec.setZ(right_vec.z() * dx / (self.width() * self.plot_scale.z() * self.panning_factor))
1135            right_vec.setX(right_vec.x() * (self.mouse_sensitivity / 5.))
1136            right_vec.setZ(right_vec.z() * (self.mouse_sensitivity / 5.))
1137            up_scale = self.height() * self.plot_scale.y() * self.panning_factor
1138            self.plot_translation -= right_vec + up_vec * (dy / up_scale) * (self.mouse_sensitivity / 5.)
1139        elif self._state == PlotState.SCALING:
1140            dx = pos.x() - self.scaling_init_pos.x()
1141            dy = pos.y() - self.scaling_init_pos.y()
1142            dx /= self.scale_factor * self.width()
1143            dy /= self.scale_factor * self.height()
1144            dy /= float(self._zoomed_size.y())
1145            dx *= self.mouse_sensitivity / 5.
1146            dy *= self.mouse_sensitivity / 5.
1147            right_vec = QVector3D.crossProduct(self.camera, QVector3D(0, 1, 0)).normalized()
1148            self.additional_scale = QVector3D(-dx * abs(right_vec.x()) / float(self._zoomed_size.x()),
1149                                               dy,
1150                                              -dx * abs(right_vec.z()) / float(self._zoomed_size.z()))
1151        elif self._state == PlotState.IDLE:
1152            legend_pos = self._legend.pos()
1153            lx, ly = legend_pos.x(), legend_pos.y()
1154            if self.show_legend and self._legend.boundingRect().adjusted(lx, ly, lx, ly).contains(pos.x(), pos.y()):
1155                self.setCursor(Qt.PointingHandCursor)
1156            else:
1157                self.unsetCursor()
1158
1159        self._mouse_position = pos
1160        self.update()
1161
1162    def mouseReleaseEvent(self, event):
1163        if self._state == PlotState.DRAGGING_LEGEND:
1164            self._legend.mouseReleaseEvent(event)
1165        if self._state == PlotState.SCALING:
1166            self.plot_scale = lower_bound(1e-5, self.plot_scale+self.additional_scale)
1167            self.additional_scale = QVector3D(0, 0, 0)
1168            self._state = PlotState.IDLE
1169        elif self._state == PlotState.SELECTING:
1170            self._selection.setBottomRight(event.pos())
1171            if self.state == ZOOMING: # self.state is actually set by OWPlotGUI (different from self._state!)
1172                min_max = self.get_min_max_selected(self._selection)
1173                x_min, x_max, y_min, y_max, z_min, z_max = min_max
1174                min, max = QVector3D(x_min, y_min, z_min), QVector3D(x_max, y_max, z_max)
1175                self.set_new_zoom(min, max)
1176            else:
1177                self.select_points(self._selection, self.selection_behavior)
1178                if self.auto_send_selection_callback:
1179                    self.auto_send_selection_callback()
1180
1181        self._tooltip_fbo_dirty = True
1182        self.unsetCursor()
1183        self._state = PlotState.IDLE
1184        self.update()
1185
1186    def wheelEvent(self, event):
1187        if event.orientation() == Qt.Vertical:
1188            delta = 1 + event.delta() / self.zoom_factor
1189            self.plot_scale *= delta
1190            self._tooltip_fbo_dirty = True
1191            self.update()
1192
1193    def notify_legend_moved(self, pos):
1194        self._legend.set_floating(True, pos)
1195        self._legend.set_orientation(Qt.Vertical)
1196
1197    def get_theme(self):
1198        return self._theme
1199
1200    def set_theme(self, value):
1201        self._theme = value
1202        self.update()
1203
1204    theme = pyqtProperty(PlotTheme, get_theme, set_theme)
1205
1206    def color(self, role, group = None):
1207        if group:
1208            return self.palette().color(group, role)
1209        else:
1210            return self.palette().color(role)
1211
1212    def set_palette(self, p):
1213        '''
1214        Sets the plot palette to ``p``.
1215
1216        :param p: The new color palette
1217        :type p: :obj:`.QPalette`
1218        '''
1219        self.setPalette(p)
1220        self.update()
1221
1222    def show_tooltip(self, text):
1223        x, y = self._mouse_position.x(), self._mouse_position.y()
1224        QToolTip.showText(self.mapToGlobal(QPoint(x, y)), text, self, QRect(x-3, y-3, 6, 6))
1225
1226    def clear(self):
1227        '''
1228        Clears the plot (legend) but remembers plot transformations (zoom, scale, translation).
1229        '''
1230        self._legend.clear()
1231        self._tooltip_fbo_dirty = True
1232        self._feedback_generated = False
1233
1234    def clear_plot_transformations(self):
1235        '''
1236        Forgets plot transformations (zoom, scale, translation).
1237        '''
1238        self._zoom_stack = []
1239        self._zoomed_size = QVector3D(1, 1, 1)
1240        self.plot_translation = QVector3D(-0.5, -0.5, -0.5)
1241        self.plot_scale = QVector3D(1, 1, 1)
1242        self.additional_scale = QVector3D(0, 0, 0)
1243
1244    contPalette = deprecated_attribute('contPalette', 'continuous_palette')
1245    discPalette = deprecated_attribute('discPalette', 'discrete_palette')
1246    showLegend = deprecated_attribute('showLegend', 'show_legend')
1247
1248if __name__ == "__main__":
1249    # TODO
1250    pass
Note: See TracBrowser for help on using the repository browser.