Changeset 8811:1b6ff57d37a3 in orange


Ignore:
Timestamp:
08/27/11 16:41:26 (3 years ago)
Author:
matejd <matejd@…>
Branch:
default
Convert:
b525bb0ac20915b1d5f8f55a8407c080a935578e
Message:

Selections on 3d widgets now work the same way as on the new (Qt) widgets

Location:
orange/OrangeWidgets
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • orange/OrangeWidgets/Visualize Qt/OWLinProj3DPlot.py

    r8810 r8811  
    269269                                          colors, num_symbols_used, 
    270270                                          x_discrete, y_discrete, z_discrete, 
    271                                           self.jitter_size, self.jitter_continuous, 
    272271                                          numpy.array([1., 1., 1.]), numpy.array([0., 0., 0.])) 
    273272 
     
    341340    def saveToFile(self): 
    342341        pass 
    343  
    344     # Mouse events overrides 
    345  
    346     def mousePressEvent(self, event): 
    347         pos = self.mouse_pos = event.pos() 
    348         buttons = event.buttons() 
    349  
    350         if buttons & Qt.LeftButton: 
    351             if self.show_legend and self.legend.contains(pos.x(), pos.y()): 
    352                 self.state = PlotState.DRAGGING_LEGEND 
    353                 self.new_selection = None 
    354             else: 
    355                 if self.state == PlotState.SELECTING: 
    356                     return 
    357                 for selection in self.selections: 
    358                     if selection.contains(pos.x(), pos.y()): 
    359                         self.state = PlotState.PANNING 
    360                         self.dragged_selection = selection 
    361                         return 
    362                 self.state = PlotState.SELECTING 
    363                 if self.selection_type == SelectionType.RECTANGLE or\ 
    364                    self.selection_type == SelectionType.ZOOM: 
    365                     self.new_selection = RectangleSelection(self, [pos.x(), pos.y()]) 
    366         elif buttons & Qt.RightButton: 
    367             self.zoom_out() 
    368             self.updateGL() 
    369         elif buttons & Qt.MiddleButton: 
    370             self.state = PlotState.ROTATING 
    371             self.selections = [] 
    372             self.new_selection = None 
    373342 
    374343    def _update_arrow_values(self, index): 
     
    434403        return text[:-4]        # remove the last <br> 
    435404 
     405    def mousePressEvent(self, event): 
     406        # Filter events (no panning or scaling) 
     407        if event.buttons() & Qt.LeftButton or\ 
     408           (event.buttons() & Qt.RightButton and not QApplication.keyboardModifiers() & Qt.ShiftModifier) or\ 
     409           (event.buttons() & Qt.MiddleButton and not QApplication.keyboardModifiers() & Qt.ShiftModifier): 
     410            OWPlot3D.mousePressEvent(self, event) 
     411 
    436412    def mouseMoveEvent(self, event): 
    437413        pos = event.pos() 
     
    446422                self.updateGL() 
    447423 
    448         if self.state == PlotState.IDLE: 
    449             if any(sel.contains(pos.x(), pos.y()) for sel in self.selections) or\ 
    450                (self.show_legend and self.legend.contains(pos.x(), pos.y())): 
    451                 self.setCursor(Qt.OpenHandCursor) 
    452             else: 
    453                 self.setCursor(Qt.ArrowCursor) 
    454             self.mouse_pos = pos 
    455             return 
    456  
    457         dx = pos.x() - self.mouse_pos.x() 
    458         dy = pos.y() - self.mouse_pos.y() 
    459  
    460         if self.invert_mouse_x: 
    461             dx = -dx 
    462  
    463         if self.state == PlotState.SELECTING and self.new_selection != None: 
    464             self.new_selection.current_vertex = [pos.x(), pos.y()] 
    465         elif self.state == PlotState.DRAGGING_LEGEND: 
    466             self.legend.move(dx, dy) 
    467         elif self.state == PlotState.ROTATING: 
    468             self.yaw += dx / (self.rotation_factor*self.width()) 
    469             self.pitch += dy / (self.rotation_factor*self.height()) 
    470             self.update_camera() 
    471         elif self.state == PlotState.PANNING: 
    472             self.dragged_selection.move(dx, dy) 
    473  
    474         self.mouse_pos = pos 
    475         self.updateGL() 
    476  
    477     def mouseReleaseEvent(self, event): 
    478         if self.state == PlotState.SELECTING and self.new_selection == None: 
    479             self.new_selection = PolygonSelection(self, [event.pos().x(), event.pos().y()]) 
    480             return 
    481  
    482         if self.state == PlotState.SELECTING: 
    483             if self.selection_type == SelectionType.POLYGON: 
    484                 last = self.new_selection.add_current_vertex() 
    485                 if last: 
    486                     self.selections.append(self.new_selection) 
    487                     self.selection_changed_callback() if self.selection_changed_callback else None 
    488                     self.state = PlotState.IDLE 
    489                     self.new_selection = None 
    490             else: 
    491                 if self.new_selection.valid(): 
    492                     self.selections.append(self.new_selection) 
    493                     self.updateGL() 
    494                     self.selection_changed_callback() if self.selection_changed_callback else None 
    495         elif self.state == PlotState.PANNING: 
    496             self.selection_updated_callback() if self.selection_updated_callback else None 
    497  
    498         if not (self.state == PlotState.SELECTING and self.selection_type == SelectionType.POLYGON): 
    499             self.state = PlotState.IDLE 
    500             self.tooltip_fbo_dirty = True 
    501             self.new_selection = None 
    502  
    503         self.updateGL() 
     424        OWPlot3D.mouseMoveEvent(self, event) 
  • orange/OrangeWidgets/Visualize Qt/OWLinProjQt.py

    r8810 r8811  
    170170        g = self.graph.gui 
    171171 
    172         # zooming / selection 
    173         self.zoomSelectToolbar = g.zoom_select_toolbar(self.GeneralTab, buttons = g.default_zoom_select_buttons + [g.Spacing, g.ShufflePoints]) 
    174         self.connect(self.zoomSelectToolbar.buttons[g.SendSelection], SIGNAL("clicked()"), self.sendSelections) 
     172        if "3d" in name_lower: 
     173            toolbar_buttons = [ 
     174                OWPlotGUI.StateButtonsBegin, 
     175                    OWPlotGUI.Select, 
     176                OWPlotGUI.StateButtonsEnd, 
     177                OWPlotGUI.Spacing, 
     178                OWPlotGUI.StateButtonsBegin, 
     179                    OWPlotGUI.SelectionOne, 
     180                    OWPlotGUI.SelectionAdd,  
     181                    OWPlotGUI.SelectionRemove, 
     182                OWPlotGUI.StateButtonsEnd, 
     183                OWPlotGUI.Spacing, 
     184                OWPlotGUI.SendSelection, 
     185                OWPlotGUI.ClearSelection 
     186            ] 
     187 
     188            self.zoomSelectToolbar = g.zoom_select_toolbar(self.GeneralTab, buttons=toolbar_buttons) 
     189            gui = g 
     190            self.connect(self.zoomSelectToolbar.buttons[gui.SelectionOne], SIGNAL("clicked()"), self._set_behavior_replace) 
     191            self.connect(self.zoomSelectToolbar.buttons[gui.SelectionAdd], SIGNAL("clicked()"), self._set_behavior_add) 
     192            self.connect(self.zoomSelectToolbar.buttons[gui.SelectionRemove], SIGNAL("clicked()"), self._set_behavior_remove) 
     193            self.connect(self.zoomSelectToolbar.buttons[gui.ClearSelection], SIGNAL("clicked()"), self.graph.unselect_all_points) 
     194            self._set_behavior_replace() 
     195        else: 
     196            # zooming / selection 
     197            self.zoomSelectToolbar = g.zoom_select_toolbar(self.GeneralTab, buttons = g.default_zoom_select_buttons + [g.Spacing, g.ShufflePoints]) 
     198            self.connect(self.zoomSelectToolbar.buttons[g.SendSelection], SIGNAL("clicked()"), self.sendSelections) 
    175199 
    176200        # #################################### 
     
    256280 
    257281        self.resize(900, 700) 
     282 
     283    def _set_behavior_add(self): 
     284        self.graph.set_selection_behavior(OWPlot.AddSelection) 
     285 
     286    def _set_behavior_replace(self): 
     287        self.graph.set_selection_behavior(OWPlot.ReplaceSelection) 
     288 
     289    def _set_behavior_remove(self): 
     290        self.graph.set_selection_behavior(OWPlot.RemoveSelection) 
    258291 
    259292    def saveToFile(self): 
  • orange/OrangeWidgets/Visualize Qt/OWScatterPlot3D.py

    r8810 r8811  
    66from OWWidget import * 
    77from plot.owplot3d import * 
     8from plot.owplotgui import OWPlotGUI 
     9from plot.owplot import OWPlot 
    810 
    911import orange 
     
    2325TooltipKind = enum('NONE', 'VISIBLE', 'ALL') 
    2426 
     27# TODO: move themes to owtheme.py 
    2528class ScatterPlotTheme(PlotTheme): 
    2629    def __init__(self): 
     
    4649        orngScaleScatterPlotData.__init__(self) 
    4750 
    48         null = lambda: None 
    49         self.activateZooming = null 
    50  
    5151        self.disc_palette = ColorPaletteGenerator() 
    5252        self._theme = LightTheme() 
    5353        self.show_grid = True 
    5454        self.show_chassis = True 
     55 
     56    def activate_zooming(self): 
     57        print('activate_zooming') 
    5558 
    5659    def set_data(self, data, subset_data=None, **args): 
     
    130133            color_index, symbol_index, size_index, label_index, 
    131134            colors, num_symbols_used, 
    132             x_discrete, y_discrete, z_discrete, self.jitter_size, self.jitter_continuous, 
     135            x_discrete, y_discrete, z_discrete, 
    133136            data_scale, data_translation) 
    134137 
     
    428431        OWGUI.rubber(box) 
    429432 
    430         self.auto_send_selection = True 
    431         self.auto_send_selection_update = False 
    432         self.plot.selection_changed_callback = self.selection_changed_callback 
    433         self.plot.selection_updated_callback = self.selection_updated_callback 
    434         box = OWGUI.widgetBox(self.settings_tab, 'Auto Send Selected Data When...') 
    435         OWGUI.checkBox(box, self, 'auto_send_selection', 'Adding/Removing selection areas', 
    436             callback = self.on_checkbox_update, tooltip='Send selected data whenever a selection area is added or removed') 
    437         OWGUI.checkBox(box, self, 'auto_send_selection_update', 'Moving selection areas', 
    438             callback = self.on_checkbox_update, tooltip='Send selected data when a user moves or resizes an existing selection area') 
    439  
    440         self.zoom_select_toolbar = OWToolbars.ZoomSelectToolbar(self, self.main_tab, self.plot, self.auto_send_selection, 
    441             buttons=(1, 4, 5, 0, 6, 7, 8)) 
    442         self.connect(self.zoom_select_toolbar.buttonSendSelections, SIGNAL('clicked()'), self.send_selections) 
    443         self.connect(self.zoom_select_toolbar.buttonSelectRect, SIGNAL('clicked()'), self.change_selection_type) 
    444         self.connect(self.zoom_select_toolbar.buttonSelectPoly, SIGNAL('clicked()'), self.change_selection_type) 
    445         self.connect(self.zoom_select_toolbar.buttonZoom, SIGNAL('clicked()'), self.change_selection_type) 
    446         self.connect(self.zoom_select_toolbar.buttonRemoveLastSelection, SIGNAL('clicked()'), self.plot.remove_last_selection) 
    447         self.connect(self.zoom_select_toolbar.buttonRemoveAllSelections, SIGNAL('clicked()'), self.plot.remove_all_selections) 
    448         self.toolbarSelection = None 
     433        self.gui = OWPlotGUI(self) 
     434        gui = self.gui 
     435        self.zoom_select_toolbar = gui.zoom_select_toolbar(self.main_tab, buttons=gui.default_zoom_select_buttons) 
     436        self.connect(self.zoom_select_toolbar.buttons[gui.SendSelection], SIGNAL("clicked()"), self.send_selection) 
     437        self.connect(self.zoom_select_toolbar.buttons[gui.Zoom], SIGNAL("clicked()"), self._set_behavior_zoom) 
     438        self.connect(self.zoom_select_toolbar.buttons[gui.SelectionOne], SIGNAL("clicked()"), self._set_behavior_replace) 
     439        self.connect(self.zoom_select_toolbar.buttons[gui.SelectionAdd], SIGNAL("clicked()"), self._set_behavior_add) 
     440        self.connect(self.zoom_select_toolbar.buttons[gui.SelectionRemove], SIGNAL("clicked()"), self._set_behavior_remove) 
    449441 
    450442        self.tooltip_kind = TooltipKind.NONE 
     
    464456        self.plot.update_camera() 
    465457 
     458        self._set_behavior_replace() 
     459 
    466460        self.data = None 
    467461        self.subset_data = None 
    468462        self.resize(1100, 600) 
     463 
     464    def _set_behavior_zoom(self): 
     465        self.plot.unselect_all_points() 
     466        self.plot.zoom_into_selection = True 
     467 
     468    def _set_behavior_add(self): 
     469        self.plot.set_selection_behavior(OWPlot.AddSelection) 
     470 
     471    def _set_behavior_replace(self): 
     472        self.plot.set_selection_behavior(OWPlot.ReplaceSelection) 
     473 
     474    def _set_behavior_remove(self): 
     475        self.plot.set_selection_behavior(OWPlot.RemoveSelection) 
    469476 
    470477    def mouseover_callback(self, index): 
     
    509516        return text[:-4] 
    510517 
    511     def selection_changed_callback(self): 
    512         if self.plot.selection_type == SelectionType.ZOOM: 
    513             indices = self.plot.get_selection_indices() 
    514             if len(indices) < 1: 
    515                 self.plot.remove_all_selections() 
    516                 return 
    517             selected_indices = [1 if i in indices else 0 
    518                                 for i in range(len(self.data))] 
    519             selected = self.plot.raw_data.selectref(selected_indices) 
    520             x_min = y_min = z_min = 1e100 
    521             x_max = y_max = z_max = -1e100 
    522             x_index = self.plot.attribute_name_index[self.x_attr] 
    523             y_index = self.plot.attribute_name_index[self.y_attr] 
    524             z_index = self.plot.attribute_name_index[self.z_attr] 
    525             # TODO: there has to be a faster way 
    526             for example in selected: 
    527                 x_min = min(example[x_index], x_min) 
    528                 y_min = min(example[y_index], y_min) 
    529                 z_min = min(example[z_index], z_min) 
    530                 x_max = max(example[x_index], x_max) 
    531                 y_max = max(example[y_index], y_max) 
    532                 z_max = max(example[z_index], z_max) 
    533             self.plot.set_new_zoom(x_min, x_max, y_min, y_max, z_min, z_max) 
    534         else: 
    535             if self.auto_send_selection: 
    536                 self.send_selections() 
    537  
    538     def selection_updated_callback(self): 
    539         if self.plot.selection_type != SelectionType.ZOOM and self.auto_send_selection_update: 
    540             self.send_selections() 
    541  
    542     def change_selection_type(self): 
    543         if self.toolbarSelection < 3: 
    544             selection_type = [SelectionType.ZOOM, SelectionType.RECTANGLE, SelectionType.POLYGON][self.toolbarSelection] 
    545             self.plot.set_selection_type(selection_type) 
     518    #def selection_changed_callback(self): 
     519    #    if self.plot.selection_type == SelectionType.ZOOM: 
     520    #        indices = self.plot.get_selection_indices() 
     521    #        if len(indices) < 1: 
     522    #            self.plot.remove_all_selections() 
     523    #            return 
     524    #        selected_indices = [1 if i in indices else 0 
     525    #                            for i in range(len(self.data))] 
     526    #        selected = self.plot.raw_data.selectref(selected_indices) 
     527    #        x_min = y_min = z_min = 1e100 
     528    #        x_max = y_max = z_max = -1e100 
     529    #        x_index = self.plot.attribute_name_index[self.x_attr] 
     530    #        y_index = self.plot.attribute_name_index[self.y_attr] 
     531    #        z_index = self.plot.attribute_name_index[self.z_attr] 
     532    #        # TODO: there has to be a faster way 
     533    #        for example in selected: 
     534    #            x_min = min(example[x_index], x_min) 
     535    #            y_min = min(example[y_index], y_min) 
     536    #            z_min = min(example[z_index], z_min) 
     537    #            x_max = max(example[x_index], x_max) 
     538    #            y_max = max(example[y_index], y_max) 
     539    #            z_max = max(example[z_index], z_max) 
     540    #        self.plot.set_new_zoom(x_min, x_max, y_min, y_max, z_min, z_max) 
     541    #    else: 
     542    #        if self.auto_send_selection: 
     543    #            self.send_selection() 
     544 
     545    #def selection_updated_callback(self): 
     546    #    if self.plot.selection_type != SelectionType.ZOOM and self.auto_send_selection_update: 
     547    #        self.send_selection() 
     548 
     549    #def change_selection_type(self): 
     550    #    if self.toolbarSelection < 3: 
     551    #        selection_type = [SelectionType.ZOOM, SelectionType.RECTANGLE, SelectionType.POLYGON][self.toolbarSelection] 
     552    #        self.plot.set_selection_type(selection_type) 
    546553 
    547554    def set_data(self, data=None): 
     
    615622        self.plot.set_data(self.data, self.subset_data) 
    616623        self.update_plot() 
    617         self.send_selections() 
     624        self.send_selection() 
    618625 
    619626    def saveSettings(self): 
     
    639646        self.reportImage(self.plot.save_to_file_direct, QSize(400, 400)) 
    640647 
    641     def send_selections(self): 
     648    def send_selection(self): 
    642649        if self.data == None: 
    643650            return 
    644         indices = self.plot.get_selection_indices() 
    645         selected = [1 if i in indices else 0 for i in range(len(self.data))] 
    646         unselected = map(lambda n: 1-n, selected) 
    647         selected = self.data.selectref(selected) 
    648         unselected = self.data.selectref(unselected) 
     651        selected = self.plot.get_selected_indices() 
     652        unselected = numpy.logical_not(selected) 
     653        selected = self.data.selectref(list(selected)) 
     654        unselected = self.data.selectref(list(unselected)) 
    649655        self.send('Selected Examples', selected) 
    650656        self.send('Unselected Examples', unselected) 
  • orange/OrangeWidgets/plot/owplot3d.py

    r8810 r8811  
    2727import orangeqt 
    2828from owtheme import PlotTheme 
     29from owplot import OWPlot 
    2930 
    3031import OpenGL 
     
    103104    return type('Enum', (), enums)() 
    104105 
    105 # States the plot can be in: 
    106 # * idle: mostly doing nothing, rotations are not considered special 
    107 # * dragging legend: user has pressed left mouse button and is now dragging legend 
    108 #   to a more suitable location 
    109 # * scaling: user has pressed right mouse button, dragging it up and down 
    110 #   scales data in y-coordinate, dragging it right and left scales data 
    111 #   in current horizontal coordinate (x or z, depends on rotation) 
    112106PlotState = enum('IDLE', 'DRAGGING_LEGEND', 'ROTATING', 'SCALING', 'SELECTING', 'PANNING') 
    113107 
    114108Symbol = enum('RECT', 'TRIANGLE', 'DTRIANGLE', 'CIRCLE', 'LTRIANGLE', 
    115109              'DIAMOND', 'WEDGE', 'LWEDGE', 'CROSS', 'XCROSS') 
    116  
    117 SelectionType = enum('ZOOM', 'RECTANGLE', 'POLYGON') 
    118110 
    119111Axis = enum('X', 'Y', 'Z', 'CUSTOM') 
     
    196188        self.position[1] += dy 
    197189 
    198 class RectangleSelection(object): 
    199     def __init__(self, plot, first_vertex): 
    200         self.plot = plot 
    201         self.first_vertex = first_vertex 
    202         self.current_vertex = first_vertex 
    203  
    204     def contains(self, x, y): 
    205         x1, x2 = sorted([self.first_vertex[0], self.current_vertex[0]]) 
    206         y1, y2 = sorted([self.first_vertex[1], self.current_vertex[1]]) 
    207         if x1 <= x <= x2 and\ 
    208            y1 <= y <= y2: 
    209             return True 
    210         return False 
    211  
    212     def move(self, dx, dy): 
    213         self.first_vertex[0] += dx 
    214         self.first_vertex[1] += dy 
    215         self.current_vertex[0] += dx 
    216         self.current_vertex[1] += dy 
    217  
    218     def draw(self): 
    219         v1, v2 = self.first_vertex, self.current_vertex 
    220         glLineWidth(1) 
    221         self.plot.qglColor(self.plot._theme.helpers_color) 
    222         draw_line(v1[0], v1[1], v1[0], v2[1]) 
    223         draw_line(v1[0], v2[1], v2[0], v2[1]) 
    224         draw_line(v2[0], v2[1], v2[0], v1[1]) 
    225         draw_line(v2[0], v1[1], v1[0], v1[1]) 
    226  
    227     def draw_mask(self): 
    228         v1, v2 = self.first_vertex, self.current_vertex 
    229         glBegin(GL_QUADS) 
    230         glVertex2f(v1[0], v1[1]) 
    231         glVertex2f(v1[0], v2[1]) 
    232         glVertex2f(v2[0], v2[1]) 
    233         glVertex2f(v2[0], v1[1]) 
    234         glEnd() 
    235  
    236     def valid(self): 
    237         return self.first_vertex != self.current_vertex 
    238         # TODO: drop if too small 
    239  
    240 class PolygonSelection(object): 
    241     def __init__(self, plot, first_vertex): 
    242         self.plot = plot 
    243         self.vertices = [first_vertex] 
    244         self.current_vertex = first_vertex 
    245         self.first_vertex = first_vertex 
    246         self.polygon = None 
    247  
    248     def add_current_vertex(self): 
    249         distance = (self.current_vertex[0]-self.first_vertex[0])**2 
    250         distance += (self.current_vertex[1]-self.first_vertex[1])**2 
    251         if distance < 10**2: 
    252             self.vertices.append(self.first_vertex) 
    253             self.polygon = QPolygon([QPoint(x, y) for (x, y) in self.vertices]) 
    254             return True 
    255         else: 
    256             if self.check_intersections(): 
    257                 return True 
    258             self.vertices.append(self.current_vertex) 
    259             return False 
    260  
    261     def check_intersections(self): 
    262         if len(self.vertices) < 3: 
    263             return False 
    264  
    265         current_line = QLineF(self.current_vertex[0], self.current_vertex[1], 
    266                               self.vertices[-1][0], self.vertices[-1][1]) 
    267         intersection = QPointF() 
    268         v1 = self.vertices[0] 
    269         for i, v2 in enumerate(self.vertices[1:-1]): 
    270             line = QLineF(v1[0], v1[1], 
    271                           v2[0], v2[1]) 
    272             if current_line.intersect(line, intersection) == QLineF.BoundedIntersection: 
    273                 self.current_vertex = [intersection.x(), intersection.y()] 
    274                 self.vertices = [self.current_vertex] + self.vertices[i+1:] 
    275                 self.vertices.append(self.current_vertex) 
    276                 self.polygon = QPolygon([QPoint(x, y) for (x, y) in self.vertices]) 
    277                 return True 
    278             v1 = v2 
    279         return False 
    280  
    281     def contains(self, x, y): 
    282         if self.polygon == None: 
    283             return False 
    284         return self.polygon.containsPoint(QPoint(x, y), Qt.OddEvenFill) 
    285  
    286     def move(self, dx, dy): 
    287         self.vertices = [[x+dx, y+dy] for x,y in self.vertices] 
    288         self.current_vertex[0] += dx 
    289         self.current_vertex[1] += dy 
    290         self.polygon.translate(dx, dy) 
    291  
    292     def draw(self): 
    293         glLineWidth(1) 
    294         self.plot.qglColor(self.plot._theme.helpers_color) 
    295         if len(self.vertices) == 1: 
    296             v1, v2 = self.vertices[0], self.current_vertex 
    297             draw_line(v1[0], v1[1], v2[0], v2[1]) 
    298             return 
    299         last_vertex = self.vertices[0] 
    300         for vertex in self.vertices[1:]: 
    301             v1, v2 = vertex, last_vertex 
    302             draw_line(v1[0], v1[1], v2[0], v2[1]) 
    303             last_vertex = vertex 
    304  
    305         v1, v2 = last_vertex, self.current_vertex 
    306         draw_line(v1[0], v1[1], v2[0], v2[1]) 
    307  
    308     def draw_mask(self): 
    309         if len(self.vertices) < 3: 
    310             return 
    311         v0 = self.vertices[0] 
    312         for i in range(1, len(self.vertices)-1): 
    313             vi = self.vertices[i] 
    314             vj = self.vertices[i+1] 
    315             draw_triangle(v0[0], v0[1], 
    316                           vi[0], vi[1], 
    317                           vj[0], vj[1]) 
    318  
    319190class OWPlot3D(orangeqt.Plot3D): 
    320191    def __init__(self, parent=None): 
    321192        orangeqt.Plot3D.__init__(self, parent) 
    322         #QtOpenGL.QGLWidget.__init__(self, QtOpenGL.QGLFormat(QtOpenGL.QGL.SampleBuffers), parent) 
    323193 
    324194        self.camera_distance = 3. 
    325195 
     196        self.scale_factor = 0.05 
     197        self.rotation_factor = 0.3 
     198        self.zoom_factor = 2000. 
     199 
    326200        self.yaw = self.pitch = -pi / 4. 
    327         self.rotation_factor = 0.3 
    328201        self.panning_factor = 0.4 
    329202        self.update_camera() 
     
    333206        self.ortho_far = 2000 
    334207        self.perspective_near = 0.01 
    335         self.perspective_far = 5. 
     208        self.perspective_far = 10. 
    336209        self.camera_fov = 30. 
    337         self.zoom_factor = 2000. 
    338210 
    339211        self.use_ortho = False 
     
    348220        self.state = PlotState.IDLE 
    349221 
    350         self.selections = [] 
    351         self.selection_changed_callback = None 
    352         self.selection_updated_callback = None 
    353         self.selection_type = SelectionType.ZOOM 
    354         self.new_selection = None 
    355  
    356         self.setMouseTracking(True) 
     222        self._selection = None 
     223        self._selection_behavior = OWPlot.AddSelection 
     224 
     225        ## Callbacks 
     226        self.auto_send_selection_callback = None 
    357227        self.mouseover_callback = None 
    358         self.mouse_pos = QPoint(0, 0) 
    359         self.invert_mouse_x = False 
    360228        self.before_draw_callback = None 
    361229        self.after_draw_callback = None 
    362230 
     231        self.setMouseTracking(True) 
     232        self.mouse_position = QPoint(0, 0) 
     233        self.invert_mouse_x = False 
     234 
     235        # TODO: these should be moved down to Scatterplot3D 
    363236        self.x_axis_labels = None 
    364237        self.y_axis_labels = None 
     
    371244        self.show_x_axis_title = self.show_y_axis_title = self.show_z_axis_title = True 
    372245 
    373         self.scale_factor = 0.05 
    374246        self.additional_scale = array([0., 0., 0.]) 
    375247        self.data_scale = array([1., 1., 1.]) 
     
    379251 
    380252        self.zoom_stack = [] 
     253        self.zoom_into_selection = True # If True, zoom is done after selection, else SelectionBehavior is considered 
    381254 
    382255        self._theme = PlotTheme() 
     
    385258        self.tooltip_fbo_dirty = True 
    386259        self.tooltip_win_center = [0, 0] 
    387         self.selection_fbo_dirty = True 
    388  
    389         self.use_fbos = True 
     260 
     261        self._use_fbos = True 
    390262 
    391263        # If True, do drawing using instancing + geometry shader processing, 
     
    398270 
    399271        self.build_axes() 
    400          
     272 
    401273        self.data = None 
    402274 
     
    554426        self.symbol_program_encode_color   = self.symbol_program.uniformLocation('encode_color') 
    555427 
    556         # Create two FBOs (framebuffer objects): 
    557  
    558         # - one will be used together with stencil mask to find out which 
    559         #   examples have been selected (in an efficient way) 
    560  
    561         # - the smaller one will be used for tooltips 
    562428        format = QtOpenGL.QGLFramebufferObjectFormat() 
    563         format.setAttachment(QtOpenGL.QGLFramebufferObject.CombinedDepthStencil) 
    564         self.selection_fbo = QtOpenGL.QGLFramebufferObject(1024, 1024, format) 
    565         if self.selection_fbo.isValid(): 
    566             print('Selection FBO created.') 
    567         else: 
    568             print('Failed to create selection FBO! Selections may be slow.') 
    569             self.use_fbos = False 
    570  
     429        format.setAttachment(QtOpenGL.QGLFramebufferObject.Depth) 
    571430        self.tooltip_fbo = QtOpenGL.QGLFramebufferObject(256, 256, format) 
    572431        if self.tooltip_fbo.isValid(): 
     
    574433        else: 
    575434            print('Failed to create tooltip FBO! Tooltips disabled.') 
    576             self.use_fbos = False 
     435            self._use_fbos = False 
    577436 
    578437    def resizeGL(self, width, height): 
     
    626485            self.draw_axes() 
    627486 
     487        plot_scale = numpy.maximum([1e-5, 1e-5, 1e-5], self.plot_scale+self.additional_scale) 
     488 
    628489        self.symbol_program.bind() 
    629490        self.symbol_program.setUniformValue('modelview', self.modelview) 
     
    633494        self.symbol_program.setUniformValue(self.symbol_program_hide_outside,   self.hide_outside) 
    634495        self.symbol_program.setUniformValue(self.symbol_program_encode_color,   False) 
    635         # Specifying float uniforms with vec2 because of a weird bug in PyQt 
    636496        self.symbol_program.setUniformValue(self.symbol_program_symbol_scale,   self.symbol_scale, self.symbol_scale) 
    637497        self.symbol_program.setUniformValue(self.symbol_program_alpha_value,    self.alpha_value / 255., self.alpha_value / 255.) 
    638         plot_scale = numpy.maximum([1e-5, 1e-5, 1e-5],                          self.plot_scale+self.additional_scale) 
    639498        self.symbol_program.setUniformValue(self.symbol_program_scale,          *plot_scale) 
    640499        self.symbol_program.setUniformValue(self.symbol_program_translation,    *self.plot_translation) 
     
    671530            glEnable(GL_DEPTH_TEST) 
    672531 
    673             glViewport(-self.mouse_pos.x()+128, -(self.height()-self.mouse_pos.y())+128, self.width(), self.height()) 
    674             self.tooltip_win_center = [self.mouse_pos.x(), self.mouse_pos.y()] 
     532            glViewport(-self.mouse_position.x()+128, -(self.height()-self.mouse_position.y())+128, self.width(), self.height()) 
     533            self.tooltip_win_center = [self.mouse_position.x(), self.mouse_position.y()] 
    675534 
    676535            self.symbol_program.bind() 
     
    688547            glViewport(0, 0, self.width(), self.height()) 
    689548 
    690         if self.selection_fbo_dirty: 
    691             # TODO: use transform feedback instead 
    692             self.selection_fbo.bind() 
    693             glClearColor(1, 1, 1, 1) 
    694             glClearStencil(0) 
    695             glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT) 
    696  
    697             self.symbol_program.bind() 
    698             self.symbol_program.setUniformValue(self.symbol_program_encode_color, True) 
    699             glDisable(GL_DEPTH_TEST) 
    700             glDisable(GL_BLEND) 
    701              
    702             if self._use_opengl_3: 
    703                 glBindVertexArray(self.feedback_vao) 
    704                 glDrawArrays(GL_TRIANGLES, 0, self.num_primitives_generated*3) 
    705                 glBindVertexArray(0) 
    706             else: 
    707                 orangeqt.Plot3D.draw_data(self, self.symbol_program.programId(), self.alpha_value / 255.) 
    708             self.symbol_program.release() 
    709  
    710             # Also draw stencil masks to the screen. No need to 
    711             # write color or depth information as well, so we 
    712             # disable those. 
    713             glMatrixMode(GL_PROJECTION) 
    714             glLoadIdentity() 
    715             glOrtho(0, self.width(), self.height(), 0, -1, 1) 
    716             glMatrixMode(GL_MODELVIEW) 
    717             glLoadIdentity() 
    718  
    719             glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE) 
    720             glDepthMask(GL_FALSE) 
    721             glStencilMask(0x01) 
    722             glStencilOp(GL_KEEP, GL_KEEP, GL_INVERT) 
    723             glStencilFunc(GL_ALWAYS, 0, ~0) 
    724             glEnable(GL_STENCIL_TEST) 
    725             for selection in self.selections: 
    726                 selection.draw_mask() 
    727             glDisable(GL_STENCIL_TEST) 
    728             glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE) 
    729             glDepthMask(GL_TRUE) 
    730             self.selection_fbo.release() 
    731             self.selection_fbo_dirty = False 
    732  
    733         glDisable(GL_DEPTH_TEST) 
    734         glDisable(GL_BLEND) 
    735         if self.show_legend: 
    736             glMatrixMode(GL_PROJECTION) 
    737             glLoadIdentity() 
    738             glOrtho(0, self.width(), self.height(), 0, -1, 1) 
    739             glMatrixMode(GL_MODELVIEW) 
    740             glLoadIdentity() 
    741             self.legend.draw() 
     549        #glDisable(GL_DEPTH_TEST) 
     550        #glDisable(GL_BLEND) 
     551        #if self.show_legend: 
     552        #    glMatrixMode(GL_PROJECTION) 
     553        #    glLoadIdentity() 
     554        #    glOrtho(0, self.width(), self.height(), 0, -1, 1) 
     555        #    glMatrixMode(GL_MODELVIEW) 
     556        #    glLoadIdentity() 
     557        #    self.legend.draw() 
    742558 
    743559        self.draw_helpers() 
     
    768584 
    769585    def draw_helpers(self): 
     586        # TODO: use owopenglrenderer 
    770587        glMatrixMode(GL_PROJECTION) 
    771588        glLoadIdentity() 
     
    773590        glMatrixMode(GL_MODELVIEW) 
    774591        glLoadIdentity() 
     592        glEnable(GL_BLEND) 
    775593 
    776594        if self.state == PlotState.SCALING: 
    777             x, y = self.mouse_pos.x(), self.mouse_pos.y() 
     595            x, y = self.mouse_position.x(), self.mouse_position.y() 
    778596            #TODO: replace with an image 
    779597            self.qglColor(self._theme.helpers_color) 
     
    788606            self.renderText(x, y-50, 'Scale y axis', font=self._theme.labels_font) 
    789607            self.renderText(x+60, y+3, 'Scale x and z axes', font=self._theme.labels_font) 
    790         elif self.state == PlotState.SELECTING and self.new_selection != None: 
    791             self.new_selection.draw() 
    792  
    793         for selection in self.selections: 
    794             selection.draw() 
     608        elif self.state == PlotState.SELECTING: 
     609            self.qglColor(QColor(168, 202, 236, 50)) 
     610            glBegin(GL_QUADS) 
     611            glVertex2f(self._selection.left(), self._selection.top()) 
     612            glVertex2f(self._selection.right(), self._selection.top()) 
     613            glVertex2f(self._selection.right(), self._selection.bottom()) 
     614            glVertex2f(self._selection.left(), self._selection.bottom()) 
     615            glEnd() 
     616 
     617            self.qglColor(QColor(51, 153, 255, 192)) 
     618            draw_line(self._selection.left(), self._selection.top(), 
     619                      self._selection.right(), self._selection.top()) 
     620            draw_line(self._selection.right(), self._selection.top(), 
     621                      self._selection.right(), self._selection.bottom()) 
     622            draw_line(self._selection.right(), self._selection.bottom(), 
     623                      self._selection.left(), self._selection.bottom()) 
     624            draw_line(self._selection.left(), self._selection.bottom(), 
     625                      self._selection.left(), self._selection.top()) 
    795626 
    796627    def build_axes(self): 
     
    957788            draw_axis_title(axis, self.y_axis_title, normal) 
    958789 
    959     def set_shown_attributes_indices(self, x_index, y_index, z_index, 
     790    def set_shown_attributes_indices(self, 
     791            x_index, y_index, z_index, 
    960792            color_index, symbol_index, size_index, label_index, 
    961793            colors, num_symbols_used, 
    962             x_discrete, y_discrete, z_discrete, jitter_size, jitter_continuous, 
    963             data_scale=array([1., 1., 1.]), data_translation=array([0., 0., 0.])): 
     794            x_discrete, y_discrete, z_discrete, 
     795            data_scale=array([1., 1., 1.]), 
     796            data_translation=array([0., 0., 0.])): 
    964797        start = time.time() 
    965798        self.makeCurrent() 
     
    969802        self.y_index = y_index 
    970803        self.z_index = z_index 
     804        self.color_index = color_index 
     805        self.symbol_index = symbol_index 
     806        self.size_index = size_index 
     807        self.colors = colors 
     808        self.num_symbols_used = num_symbols_used 
     809        self.x_discrete = x_discrete 
     810        self.y_discrete = y_discrete 
     811        self.z_discrete = z_discrete 
    971812        self.label_index = label_index 
    972813 
     
    980821            self.generating_program.setUniformValue('y_index', y_index) 
    981822            self.generating_program.setUniformValue('z_index', z_index) 
    982             self.generating_program.setUniformValue('jitter_size', jitter_size) 
    983             self.generating_program.setUniformValue('jitter_continuous', jitter_continuous) 
     823            #self.generating_program.setUniformValue('jitter_size', jitter_size) 
     824            #self.generating_program.setUniformValue('jitter_continuous', jitter_continuous) 
    984825            self.generating_program.setUniformValue('x_discrete', x_discrete) 
    985826            self.generating_program.setUniformValue('y_discrete', y_discrete) 
     
    1074915            setattr(self, 'show_' + Axis.to_str(axis_id).lower() + '_axis_title', show) 
    1075916 
    1076     def set_new_zoom(self, x_min, x_max, y_min, y_max, z_min, z_max): 
     917    def set_new_zoom(self, x_min, x_max, y_min, y_max, z_min, z_max, plot_coordinates=False): 
    1077918        '''Specifies new zoom in data coordinates.''' 
    1078         self.selections = [] 
    1079919        self.zoom_stack.append((self.plot_scale, self.plot_translation)) 
    1080920 
    1081921        max = array([x_max, y_max, z_max]).copy() 
    1082922        min = array([x_min, y_min, z_min]).copy() 
    1083         min -= self.data_translation 
    1084         min *= self.data_scale 
    1085         max -= self.data_translation 
    1086         max *= self.data_scale 
     923        if not plot_coordinates: 
     924            min -= self.data_translation 
     925            min *= self.data_scale 
     926            max -= self.data_translation 
     927            max *= self.data_scale 
    1087928        center = (max + min) / 2. 
    1088929        new_translation = -array(center) 
     
    1149990        return point 
    1150991 
    1151     def get_selection_indices(self): 
    1152         if len(self.selections) == 0: 
    1153             return [] 
    1154  
    1155         width, height = self.width(), self.height() 
    1156         if self.use_fbos and width <= 1024 and height <= 1024: 
    1157             self.selection_fbo_dirty = True 
    1158             self.updateGL() 
    1159  
    1160             self.selection_fbo.bind() 
    1161             color_pixels = glReadPixels(0, 0, 
    1162                                         width, height, 
    1163                                         GL_RGBA, 
    1164                                         GL_UNSIGNED_BYTE) 
    1165             stencil_pixels = glReadPixels(0, 0, 
    1166                                           width, height, 
    1167                                           GL_STENCIL_INDEX, 
    1168                                           GL_FLOAT) 
    1169             self.selection_fbo.release() 
    1170             stencils = struct.unpack('f'*width*height, stencil_pixels) 
    1171             colors = struct.unpack('I'*width*height, color_pixels) 
    1172             indices = set([]) 
    1173             for stencil, color in zip(stencils, colors): 
    1174                 if stencil > 0. and color < 4294967295: 
    1175                     indices.add(color) 
    1176  
    1177             return indices 
    1178         else: 
    1179             # Slower method (projects points manually and checks containments). 
    1180             modelview, projection = self.get_mvp() 
    1181             proj_model = projection * modelview 
    1182             viewport = [0, 0, width, height] 
    1183  
    1184             def project(x, y, z): 
    1185                 projected = proj_model * QVector4D(x, y, z, 1) 
    1186                 projected /= projected.z() 
    1187                 winx = viewport[0] + (1 + projected.x()) * viewport[2] / 2 
    1188                 winy = viewport[1] + (1 + projected.y()) * viewport[3] / 2 
    1189                 winy = height - winy 
    1190                 return winx, winy 
    1191  
    1192             indices = [] 
    1193             for i, example in enumerate(self.data.transpose()): 
    1194                 x = example[self.x_index] 
    1195                 y = example[self.y_index] 
    1196                 z = example[self.z_index] 
    1197                 x, y, z = self.map_to_plot(array([x,y,z]).copy(), original=False) 
    1198                 x_win, y_win = project(x, y, z) 
    1199                 if any(sel.contains(x_win, y_win) for sel in self.selections): 
    1200                     indices.append(i) 
    1201  
    1202             return indices 
    1203  
    1204     def set_selection_type(self, type): 
    1205         if SelectionType.is_valid(type): 
    1206             self.selection_type = type 
     992    def _get_frustum_planes(self, area): 
     993        '''Generates 4 frustum planes (no near or far plane) for an area (rectangular) on screen.''' 
     994        #http://forums.create.msdn.com/forums/p/6690/35401.aspx , by "The Friggm" 
     995        #http://ghoshehsoft.wordpress.com/2010/12/09/xna-picking-tutorial-part-ii/ 
     996        # 
     997        # Warning: work in progress 
     998        # 
     999        center = area.center() 
     1000        m = self.projection.data() 
     1001        m[0*4 + 0] /= float(area.width()) / self.width() 
     1002        m[1*4 + 1] /= float(area.height()) / self.height() 
     1003        m[2*4 + 0] = (center.x() - (self.width() / 2.)) / (self.width() / 2.)  
     1004        m[2*4 + 1] = -(center.y() - (self.height() / 2.)) / (self.height() / 2.) 
     1005        region_projection = QMatrix4x4(*m) 
     1006 
     1007        transform = region_projection * self.modelview# * region_projection 
     1008        self.tt = transform.transposed() 
     1009        data = self.tt.data() 
     1010 
     1011        planes = [] 
     1012        planes.append((QVector3D(data[12] - data[0], data[13] - data[1], data[14] - data[2]), data[15] - data[3])) # normal + offset 
     1013        planes.append((QVector3D(data[12] + data[0], data[13] + data[1], data[14] + data[2]), data[15] + data[3])) 
     1014        planes.append((QVector3D(data[12] - data[4], data[13] - data[5], data[14] - data[6]), data[15] - data[7])) 
     1015        planes.append((QVector3D(data[12] + data[4], data[13] + data[5], data[14] + data[6]), data[15] + data[7])) 
     1016 
     1017        planes = [(normal / normal.length(), offset / normal.length()) for normal, offset in planes] 
     1018        return planes 
     1019 
     1020    def get_min_max_selected(self, area): 
     1021        viewport = [0, 0, self.width(), self.height()] 
     1022        area = [min(area.left(), area.right()), min(area.top(), area.bottom()), abs(area.width()), abs(area.height())] 
     1023        min_max = orangeqt.Plot3D.get_min_max_selected(self, area, self.projection * self.modelview, 
     1024                                                       viewport, 
     1025                                                       QVector3D(*self.plot_scale), QVector3D(*self.plot_translation)) 
     1026        return min_max 
     1027 
     1028    def get_selected_indices(self): 
     1029        return orangeqt.Plot3D.get_selected_indices(self) 
     1030 
     1031    def unselect_all_points(self): 
     1032        orangeqt.Plot3D.unselect_all_points(self) 
     1033        orangeqt.Plot3D.update_data(self, self.x_index, self.y_index, self.z_index, 
     1034                                    self.color_index, self.symbol_index, self.size_index, self.label_index, 
     1035                                    self.colors, self.num_symbols_used, 
     1036                                    self.x_discrete, self.y_discrete, self.z_discrete, self.use_2d_symbols) 
     1037        self.updateGL() 
     1038 
     1039    def set_selection_behavior(self, behavior): 
     1040        self.zoom_into_selection = False 
     1041        self._selection_behavior = behavior 
    12071042 
    12081043    def mousePressEvent(self, event): 
    1209         pos = self.mouse_pos = event.pos() 
     1044        pos = self.mouse_position = event.pos() 
    12101045        buttons = event.buttons() 
     1046 
     1047        self._selection = None 
    12111048 
    12121049        if buttons & Qt.LeftButton: 
    12131050            if self.show_legend and self.legend.contains(pos.x(), pos.y()): 
    12141051                self.state = PlotState.DRAGGING_LEGEND 
    1215                 self.new_selection = None 
    12161052            else: 
    1217                 if self.state == PlotState.SELECTING: 
    1218                     return 
    1219                 for selection in self.selections: 
    1220                     if selection.contains(pos.x(), pos.y()): 
    1221                         self.state = PlotState.PANNING 
    1222                         self.dragged_selection = selection 
    1223                         return 
    12241053                self.state = PlotState.SELECTING 
    1225                 if self.selection_type == SelectionType.RECTANGLE or\ 
    1226                    self.selection_type == SelectionType.ZOOM: 
    1227                     self.new_selection = RectangleSelection(self, [pos.x(), pos.y()]) 
     1054                self._selection = QRect(pos.x(), pos.y(), 0, 0) 
    12281055        elif buttons & Qt.RightButton: 
    12291056            if QApplication.keyboardModifiers() & Qt.ShiftModifier: 
    1230                 self.selections = [] 
    1231                 self.new_selection = None 
    12321057                self.state = PlotState.SCALING 
    1233                 self.scaling_init_pos = self.mouse_pos 
     1058                self.scaling_init_pos = self.mouse_position 
    12341059                self.additional_scale = array([0., 0., 0.]) 
    12351060            else: 
     
    12371062            self.updateGL() 
    12381063        elif buttons & Qt.MiddleButton: 
    1239             self.state = PlotState.ROTATING 
    1240             self.selections = [] 
    1241             self.new_selection = None 
     1064            if QApplication.keyboardModifiers() & Qt.ShiftModifier: 
     1065                self.state = PlotState.PANNING 
     1066            else: 
     1067                self.state = PlotState.ROTATING 
    12421068 
    12431069    def _check_mouseover(self, pos): 
     
    12481074                self.tooltip_fbo_dirty = True 
    12491075                self.updateGL() 
    1250             # Use pixel-color-picking to read example index under mouse cursor. 
     1076            # Use pixel-color-picking to read example index under mouse cursor (also called ID rendering). 
    12511077            self.tooltip_fbo.bind() 
    12521078            value = glReadPixels(pos.x() - self.tooltip_win_center[0] + 128, 
     
    12681094        self._check_mouseover(pos) 
    12691095 
    1270         if self.state == PlotState.IDLE: 
    1271             if any(sel.contains(pos.x(), pos.y()) for sel in self.selections) or\ 
    1272                (self.show_legend and self.legend.contains(pos.x(), pos.y())): 
    1273                 self.setCursor(Qt.OpenHandCursor) 
    1274             else: 
    1275                 self.setCursor(Qt.ArrowCursor) 
    1276             self.mouse_pos = pos 
    1277             return 
    1278  
    1279         dx = pos.x() - self.mouse_pos.x() 
    1280         dy = pos.y() - self.mouse_pos.y() 
     1096        dx = pos.x() - self.mouse_position.x() 
     1097        dy = pos.y() - self.mouse_position.y() 
    12811098 
    12821099        if self.invert_mouse_x: 
    12831100            dx = -dx 
    12841101 
    1285         if self.state == PlotState.SELECTING and self.new_selection != None: 
    1286             self.new_selection.current_vertex = [pos.x(), pos.y()] 
     1102        if self.state == PlotState.SELECTING: 
     1103            self._selection.setBottomRight(pos) 
    12871104        elif self.state == PlotState.DRAGGING_LEGEND: 
    12881105            self.legend.move(dx, dy) 
    12891106        elif self.state == PlotState.ROTATING: 
    1290             if QApplication.keyboardModifiers() & Qt.ShiftModifier: 
    1291                 right_vec = normalize(numpy.cross(self.camera, [0, 1, 0])) 
    1292                 up_vec = normalize(numpy.cross(right_vec, self.camera)) 
    1293                 right_vec[0] *= dx / (self.width() * self.plot_scale[0] * self.panning_factor) 
    1294                 right_vec[2] *= dx / (self.width() * self.plot_scale[2] * self.panning_factor) 
    1295                 up_scale = self.height()*self.plot_scale[1]*self.panning_factor 
    1296                 self.plot_translation -= right_vec + up_vec*(dy / up_scale) 
    1297             else: 
    1298                 self.yaw += dx / (self.rotation_factor*self.width()) 
    1299                 self.pitch += dy / (self.rotation_factor*self.height()) 
    1300                 self.update_camera() 
     1107            self.yaw += dx / (self.rotation_factor*self.width()) 
     1108            self.pitch += dy / (self.rotation_factor*self.height()) 
     1109            self.update_camera() 
     1110        elif self.state == PlotState.PANNING: 
     1111            right_vec = normalize(numpy.cross(self.camera, [0, 1, 0])) 
     1112            up_vec = normalize(numpy.cross(right_vec, self.camera)) 
     1113            right_vec[0] *= dx / (self.width() * self.plot_scale[0] * self.panning_factor) 
     1114            right_vec[2] *= dx / (self.width() * self.plot_scale[2] * self.panning_factor) 
     1115            up_scale = self.height()*self.plot_scale[1]*self.panning_factor 
     1116            self.plot_translation -= right_vec + up_vec*(dy / up_scale) 
    13011117        elif self.state == PlotState.SCALING: 
    13021118            dx = pos.x() - self.scaling_init_pos.x() 
     
    13071123            dy /= self.scale_factor * self.height() 
    13081124            self.additional_scale = [dx, dy, 0] 
    1309         elif self.state == PlotState.PANNING: 
    1310             self.dragged_selection.move(dx, dy) 
    1311  
    1312         self.mouse_pos = pos 
     1125 
     1126        self.mouse_position = pos 
    13131127        self.updateGL() 
    13141128 
    13151129    def mouseReleaseEvent(self, event): 
    1316         if self.state == PlotState.SELECTING and self.new_selection == None: 
    1317             self.new_selection = PolygonSelection(self, [event.pos().x(), event.pos().y()]) 
    1318             return 
    1319  
    13201130        if self.state == PlotState.SCALING: 
    13211131            self.plot_scale = numpy.maximum([1e-5, 1e-5, 1e-5], self.plot_scale+self.additional_scale) 
     
    13231133            self.state = PlotState.IDLE 
    13241134        elif self.state == PlotState.SELECTING: 
    1325             if self.selection_type == SelectionType.POLYGON: 
    1326                 last = self.new_selection.add_current_vertex() 
    1327                 if last: 
    1328                     self.selections.append(self.new_selection) 
    1329                     self.selection_changed_callback() if self.selection_changed_callback else None 
    1330                     self.state = PlotState.IDLE 
    1331                     self.new_selection = None 
     1135            self._selection.setBottomRight(event.pos()) 
     1136            if self.zoom_into_selection: 
     1137                min_max = self.get_min_max_selected(self._selection) 
     1138                self.set_new_zoom(*min_max, plot_coordinates=True) 
    13321139            else: 
    1333                 if self.new_selection.valid(): 
    1334                     self.selections.append(self.new_selection) 
    1335                     self.updateGL() 
    1336                     self.selection_changed_callback() if self.selection_changed_callback else None 
    1337         elif self.state == PlotState.PANNING: 
    1338             self.selection_updated_callback() if self.selection_updated_callback else None 
    1339  
    1340         if not (self.state == PlotState.SELECTING and self.selection_type == SelectionType.POLYGON): 
    1341             self.state = PlotState.IDLE 
    1342             self.tooltip_fbo_dirty = True 
    1343             self.new_selection = None 
    1344  
     1140                area = self._selection 
     1141                viewport = [0, 0, self.width(), self.height()] 
     1142                area = [min(area.left(), area.right()), min(area.top(), area.bottom()), abs(area.width()), abs(area.height())] 
     1143                orangeqt.Plot3D.select_points(self, area, self.projection * self.modelview, 
     1144                                              viewport, 
     1145                                              QVector3D(*self.plot_scale), QVector3D(*self.plot_translation), 
     1146                                              self._selection_behavior) 
     1147                self.makeCurrent() 
     1148                orangeqt.Plot3D.update_data(self, self.x_index, self.y_index, self.z_index, 
     1149                                            self.color_index, self.symbol_index, self.size_index, self.label_index, 
     1150                                            self.colors, self.num_symbols_used, 
     1151                                            self.x_discrete, self.y_discrete, self.z_discrete, self.use_2d_symbols) 
     1152 
     1153                if self.auto_send_selection_callback: 
     1154                    self.auto_send_selection_callback() 
     1155 
     1156        self.state = PlotState.IDLE 
    13451157        self.updateGL() 
    13461158 
    13471159    def wheelEvent(self, event): 
    13481160        if event.orientation() == Qt.Vertical: 
    1349             self.selections = [] 
    13501161            delta = 1 + event.delta() / self.zoom_factor 
    13511162            self.plot_scale *= delta 
     
    13531164            self.updateGL() 
    13541165 
    1355     def remove_last_selection(self): 
    1356         if len(self.selections) > 0: 
    1357             self.selections.pop() 
    1358             self.updateGL() 
    1359             self.selection_changed_callback() if self.selection_changed_callback else None 
    1360  
    1361     def remove_all_selections(self): 
    1362         self.selections = [] 
    1363         if self.selection_changed_callback and self.selection_type != SelectionType.ZOOM: 
    1364             self.selection_changed_callback() 
     1166    def get_theme(self): 
     1167        return self._theme 
     1168 
     1169    def set_theme(self, value): 
     1170        self._theme = value 
    13651171        self.updateGL() 
    13661172 
    1367     @pyqtProperty(PlotTheme) 
    1368     def theme(self): 
    1369         return self._theme 
    1370  
    1371     @theme.setter 
    1372     def theme(self, theme): 
    1373         self._theme = theme 
    1374         self.updateGL() 
     1173    theme = pyqtProperty(PlotTheme, get_theme, set_theme) 
    13751174 
    13761175    def show_tooltip(self, text): 
    1377         x, y = self.mouse_pos.x(), self.mouse_pos.y() 
     1176        x, y = self.mouse_position.x(), self.mouse_position.y() 
    13781177        QToolTip.showText(self.mapToGlobal(QPoint(x, y)), text, self, QRect(x-3, y-3, 6, 6)) 
    13791178 
    13801179    def clear(self): 
    1381         self.selections = [] 
    13821180        self.legend.clear() 
    13831181        self.zoom_stack = [] 
     
    13921190        self.z_axis_labels = None 
    13931191        self.tooltip_fbo_dirty = True 
    1394         self.selection_fbo_dirty = True 
    13951192        self.feedback_generated = False 
    13961193 
    13971194if __name__ == "__main__": 
    1398     app = QApplication(sys.argv) 
    1399     w = OWPlot3D() 
    1400     w.show() 
    1401   
    1402     from random import random 
    1403     rand = lambda: random() - 0.5 
    1404     N = 100 
    1405     data = orange.ExampleTable("../doc/datasets/iris.tab") 
    1406     array, c, _ = data.toNumpyMA() 
    1407     import OWColorPalette 
    1408     palette = OWColorPalette.ColorPaletteHSV(len(data.domain.classVar.values)) 
    1409     x = array[:, 0] 
    1410     y = array[:, 1] 
    1411     z = array[:, 2] 
    1412     colors = [palette[int(ex.getclass())] for ex in data] 
    1413     colors = [[c.red()/255., c.green()/255., c.blue()/255., 0.8] for c in colors] 
    1414     w.scatter(x, y, z, colors=colors) 
    1415     app.exec_() 
     1195    # TODO 
     1196    pass 
Note: See TracChangeset for help on using the changeset viewer.