Changeset 8401:37bffdf7ecd6 in orange


Ignore:
Timestamp:
07/20/11 17:12:23 (3 years ago)
Author:
matejd <matejd@…>
Branch:
default
Convert:
e1de51e2165756567a50ed07212d3fbfb46c7d64
Message:

owplot3d/owscatterplot3d: polygon selection (plot states + gui elements)

Location:
orange/OrangeWidgets
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • orange/OrangeWidgets/Prototypes/OWScatterPlot3D.py

    r8398 r8401  
    66 
    77import OWGUI 
     8import OWToolbars 
    89import OWColorPalette 
    910 
     
    1718        OWWidget.__init__(self, parent, signalManager, name, True) 
    1819 
    19         self.inputs = [("Examples", ExampleTable, self.setData), ("Subset Examples", ExampleTable, self.setSubsetData)] 
     20        self.inputs = [("Examples", ExampleTable, self.set_data, Default), ("Subset Examples", ExampleTable, self.set_subset_data)] 
    2021        self.outputs = [("Selected Examples", ExampleTable), ("Unselected Examples", ExampleTable)] 
    2122 
     
    104105        OWGUI.rubber(box) 
    105106 
     107        self.auto_send_selection = True 
     108        self.auto_send_selection_update = False 
     109        self.plot.selection_change_callback = self.send_selections 
     110        box = OWGUI.widgetBox(self.settings_tab, 'Auto Send Selected Data When...') 
     111        OWGUI.checkBox(box, self, 'auto_send_selection', 'Adding/Removing selection areas', 
     112            callback = self.on_checkbox_update, tooltip = 'Send selected data whenever a selection area is added or removed') 
     113        OWGUI.checkBox(box, self, 'auto_send_selection_update', 'Moving/Resizing selection areas', 
     114            callback = self.on_checkbox_update, tooltip = 'Send selected data when a user moves or resizes an existing selection area') 
     115 
     116        self.zoom_select_toolbar = OWToolbars.ZoomSelectToolbar(self, self.main_tab, self.plot, self.auto_send_selection) 
     117        self.connect(self.zoom_select_toolbar.buttonSendSelections, SIGNAL('clicked()'), self.send_selections) 
     118        self.connect(self.zoom_select_toolbar.buttonSelectRect, SIGNAL('clicked()'), self.change_selection_type) 
     119        self.connect(self.zoom_select_toolbar.buttonSelectPoly, SIGNAL('clicked()'), self.change_selection_type) 
     120        self.connect(self.zoom_select_toolbar.buttonZoom, SIGNAL('clicked()'), self.change_selection_type) 
     121        self.toolbarSelection = None 
     122 
    106123        self.main_tab.layout().addStretch(100) 
    107124        self.settings_tab.layout().addStretch(100) 
     
    109126        self.mainArea.layout().addWidget(self.plot) 
    110127        self.connect(self.graphButton, SIGNAL("clicked()"), self.plot.save_to_file) 
    111  
    112         self.plot.auto_send_selection_callback = self.send_selections 
    113128 
    114129        self.data = None 
     
    116131        self.resize(1000, 600) 
    117132 
    118     def setData(self, data=None): 
     133    def change_selection_type(self): 
     134        if self.toolbarSelection < 3: 
     135            selection_type = [SelectionType.ZOOM, SelectionType.RECTANGLE, SelectionType.POLYGON][self.toolbarSelection] 
     136            self.plot.set_selection_type(selection_type) 
     137 
     138    def set_data(self, data=None): 
    119139        self.closeContext("") 
    120140        self.data = data 
     
    161181            self.openContext('', data) 
    162182 
    163     def setSubsetData(self, data=None): 
     183    def set_subset_data(self, data=None): 
    164184        self.subsetData = data 
    165185 
     
    277297    w = OWScatterPlot3D() 
    278298    data = orange.ExampleTable("../../doc/datasets/iris") 
    279     w.setData(data) 
     299    w.set_data(data) 
    280300    w.handleNewSignals() 
    281301    w.show() 
  • orange/OrangeWidgets/owplot3d.py

    r8399 r8401  
    3838""" 
    3939 
    40 __all__ = ['OWPlot3D', 'Symbol'] 
     40__all__ = ['OWPlot3D', 'Symbol', 'SelectionType'] 
    4141 
    4242from PyQt4.QtCore import * 
     
    140140#   scales data in y-coordinate, dragging it right and left scales data 
    141141#   in current horizontal coordinate (x or z, depends on rotation) 
    142 PlotState = enum('IDLE', 'DRAGGING_LEGEND', 'SCALING', 'SELECTING') 
     142PlotState = enum('IDLE', 'DRAGGING_LEGEND', 'ROTATING', 'SCALING', 'SELECTING', 'PANNING') 
    143143 
    144144# TODO: more symbols 
    145145Symbol = enum('TRIANGLE', 'RECTANGLE', 'PENTAGON', 'CIRCLE') 
     146 
     147SelectionType = enum('ZOOM', 'RECTANGLE', 'POLYGON') 
    146148 
    147149class Legend(object): 
     
    229231        self.position[1] += dy 
    230232 
     233class RectangleSelection(object): 
     234    def __init__(self, first_vertex): 
     235        self.first_vertex = first_vertex 
     236        self.current_vertex = first_vertex 
     237 
     238    def point_inside(self, x, y): 
     239        x1, x2 = sorted([self.first_vertex[0], self.current_vertex[0]]) 
     240        y1, y2 = sorted([self.first_vertex[1], self.current_vertex[1]]) 
     241        if x1 <= x <= x2 and\ 
     242           y1 <= y <= y2: 
     243            return True 
     244        return False 
     245 
     246    def draw(self): 
     247        v1, v2 = self.first_vertex, self.current_vertex 
     248        glLineWidth(1) 
     249        glColor4f(0, 0, 0, 1) 
     250        draw_line(v1[0], v1[1], v1[0], v2[1]) 
     251        draw_line(v1[0], v2[1], v2[0], v2[1]) 
     252        draw_line(v2[0], v2[1], v2[0], v1[1]) 
     253        draw_line(v2[0], v1[1], v1[0], v1[1]) 
     254 
     255    def valid(self): 
     256        return self.first_vertex != self.current_vertex 
     257        # TODO 
     258 
     259class PolygonSelection(object): 
     260    def __init__(self, first_vertex): 
     261        self.vertices = [first_vertex] 
     262        self.current_vertex = first_vertex 
     263        self.first_vertex = first_vertex 
     264 
     265    def add_current_vertex(self): 
     266        distance = (self.current_vertex[0]-self.first_vertex[0])**2 
     267        distance += (self.current_vertex[1]-self.first_vertex[1])**2 
     268        if distance < 10**2: 
     269            self.vertices.append(self.first_vertex) 
     270            return True 
     271        else: 
     272            self.vertices.append(self.current_vertex) 
     273            return False 
     274 
     275    def point_inside(self, x, y): 
     276        return False # TODO 
     277 
     278    def draw(self): 
     279        glLineWidth(1) 
     280        glColor4f(0, 0, 0, 1) 
     281        if len(self.vertices) == 1: 
     282            v1, v2 = self.vertices[0], self.current_vertex 
     283            draw_line(v1[0], v1[1], v2[0], v2[1]) 
     284            return 
     285        last_vertex = self.vertices[0] 
     286        for vertex in self.vertices[1:]: 
     287            v1, v2 = vertex, last_vertex 
     288            draw_line(v1[0], v1[1], v2[0], v2[1]) 
     289            last_vertex = vertex 
     290 
     291        v1, v2 = last_vertex, self.current_vertex 
     292        draw_line(v1[0], v1[1], v2[0], v2[1]) 
    231293 
    232294class OWPlot3D(QtOpenGL.QGLWidget): 
     
    294356        self.build_axes() 
    295357        self.selections = [] 
    296         self.auto_send_selection_callback = None 
     358        self.selection_change_callback = None 
     359        self.selection_type = SelectionType.RECTANGLE 
     360        self.new_selection = None 
     361 
     362        self.setMouseTracking(True) 
    297363 
    298364    def __del__(self): 
     
    508574                            'Scale {0} axis'.format(['z', 'x'][self.scale_x_axis]), 
    509575                            font=self.labels_font) 
    510         elif self.state == PlotState.SELECTING and self.new_selection[3] != None: 
    511             s = self.new_selection 
    512             glColor4f(0, 0, 0, 1) 
    513             draw_line(s[0], s[1], s[0], s[3]) 
    514             draw_line(s[0], s[3], s[2], s[3]) 
    515             draw_line(s[2], s[3], s[2], s[1]) 
    516             draw_line(s[2], s[1], s[0], s[1]) 
    517  
    518         for s in self.selections: 
    519             glColor4f(0, 0, 0, 1) 
    520             draw_line(s[0], s[1], s[0], s[3]) 
    521             draw_line(s[0], s[3], s[2], s[3]) 
    522             draw_line(s[2], s[3], s[2], s[1]) 
    523             draw_line(s[2], s[1], s[0], s[1]) 
     576        elif self.state == PlotState.SELECTING and self.new_selection != None: 
     577            self.new_selection.draw() 
     578 
     579        for selection in self.selections: 
     580            selection.draw() 
    524581 
    525582    def set_x_axis_title(self, title): 
     
    582639                glVertex3f(*(position+normal*0.2)) 
    583640                glEnd() 
    584                 value = position[coord_index] / (self.scale[coord_index] + self.additional_scale[coord_index]) 
     641                value = position[coord_index] /\ 
     642                    (max(0, self.scale[coord_index] + self.additional_scale[coord_index])) 
    585643                value /= self.initial_scale[coord_index] 
    586644                value += self.initial_center[coord_index] 
     
    653711            draw_axis(self.x_axis) 
    654712            draw_values(self.x_axis, 0, numpy.array([0, 0, -1])) 
    655             draw_axis_title(self.x_axis, self.x_axis_title, numpy.array([0, 0, -1])) 
     713            if self.show_x_axis_title: 
     714                draw_axis_title(self.x_axis, self.x_axis_title, numpy.array([0, 0, -1])) 
    656715        elif visible_planes[2]: 
    657716            draw_axis(self.x_axis + self.unit_z) 
    658717            draw_values(self.x_axis + self.unit_z, 0, numpy.array([0, 0, 1])) 
    659             draw_axis_title(self.x_axis + self.unit_z, 
    660                             self.x_axis_title, numpy.array([0, 0, 1])) 
     718            if self.show_x_axis_title: 
     719                draw_axis_title(self.x_axis + self.unit_z, 
     720                                self.x_axis_title, numpy.array([0, 0, 1])) 
    661721 
    662722        if visible_planes[1]: 
    663723            draw_axis(self.z_axis) 
    664724            draw_values(self.z_axis, 2, numpy.array([-1, 0, 0])) 
    665             draw_axis_title(self.z_axis, self.z_axis_title, numpy.array([-1, 0, 0])) 
     725            if self.show_z_axis_title: 
     726                draw_axis_title(self.z_axis, self.z_axis_title, numpy.array([-1, 0, 0])) 
    666727        elif visible_planes[3]: 
    667728            draw_axis(self.z_axis + self.unit_x) 
    668729            draw_values(self.z_axis + self.unit_x, 2, numpy.array([1, 0, 0])) 
    669             draw_axis_title(self.z_axis + self.unit_x, self.z_axis_title, numpy.array([1, 0, 0])) 
     730            if self.show_z_axis_title: 
     731                draw_axis_title(self.z_axis + self.unit_x, self.z_axis_title, numpy.array([1, 0, 0])) 
    670732 
    671733        try: 
     
    687749        draw_axis(axis) 
    688750        draw_values(axis, 1, normal) 
    689         draw_axis_title(axis, self.y_axis_title, normal) 
     751        if self.show_y_axis_title: 
     752            draw_axis_title(axis, self.y_axis_title, normal) 
    690753 
    691754        # Remember which axis to scale when dragging mouse horizontally. 
     
    890953            return winx, winy 
    891954 
    892         def inside_selection(x_win, y_win): 
    893             for selection in self.selections: 
    894                 x1, x2 = sorted([selection[0], selection[2]]) 
    895                 y1, y2 = sorted([selection[1], selection[3]]) 
    896                 if x1 <= x_win <= x2 and\ 
    897                    y1 <= y_win <= y2: 
    898                     return True 
    899             return False 
    900  
    901955        indices = [] 
    902956        for (cmd, params) in self.commands: 
     
    905959                for i, (x, y, z) in enumerate(zip(X, Y, Z)): 
    906960                    x_win, y_win = project(x, y, z) 
    907                     if inside_selection(x_win, y_win): 
    908                         indices.append(i) 
     961                    for selection in self.selections: 
     962                        if selection.point_inside(x_win, y_win): 
     963                            indices.append(i) 
     964                            break 
    909965 
    910966        return indices 
     967 
     968    def set_selection_type(self, type): 
     969        if SelectionType.is_valid(type): 
     970            self.selection_type = type 
    911971 
    912972    def mousePressEvent(self, event): 
    913973        pos = self.mouse_pos = event.pos() 
    914974        buttons = event.buttons() 
     975 
    915976        if buttons & Qt.LeftButton: 
    916977            if self.legend.point_inside(pos.x(), pos.y()): 
    917978                self.state = PlotState.DRAGGING_LEGEND 
     979                self.new_selection = None 
    918980            else: 
    919981                self.state = PlotState.SELECTING 
    920                 self.new_selection = [pos.x(), pos.y(), None, None] 
     982                if self.selection_type == SelectionType.RECTANGLE or\ 
     983                   self.selection_type == SelectionType.ZOOM: 
     984                    self.new_selection = RectangleSelection([pos.x(), pos.y()]) 
    921985        elif buttons & Qt.RightButton: 
    922986            self.selections = [] 
     987            self.new_selection = None 
    923988            self.state = PlotState.SCALING 
    924989            self.scaling_init_pos = self.mouse_pos 
    925990            self.additional_scale = [0, 0, 0] 
    926991            self.updateGL() 
     992        elif buttons & Qt.MiddleButton: 
     993            self.state = PlotState.ROTATING 
     994            self.selections = [] 
     995            self.new_selection = None 
    927996 
    928997    def mouseMoveEvent(self, event): 
    929998        pos = event.pos() 
     999 
     1000        if self.state == PlotState.IDLE: 
     1001            for selection in self.selections: 
     1002                if selection.point_inside(pos.x(), pos.y()): 
     1003                    self.setCursor(Qt.OpenHandCursor) 
     1004                    break 
     1005            else: 
     1006                self.setCursor(Qt.ArrowCursor) 
     1007            # Don't do anything else if idle 
     1008            return 
     1009 
    9301010        dx = pos.x() - self.mouse_pos.x() 
    9311011        dy = pos.y() - self.mouse_pos.y() 
    9321012 
    933         if event.buttons() & Qt.LeftButton: 
    934             if self.state == PlotState.DRAGGING_LEGEND: 
    935                 self.legend.move(dx, dy) 
    936             elif self.state == PlotState.SELECTING: 
    937                 self.new_selection[2:] = [pos.x(), pos.y()] 
    938         elif event.buttons() & Qt.MiddleButton: 
    939             self.selections = [] 
     1013        if self.state == PlotState.SELECTING: 
     1014            self.new_selection.current_vertex = [pos.x(), pos.y()] 
     1015        elif self.state == PlotState.DRAGGING_LEGEND: 
     1016            self.legend.move(dx, dy) 
     1017        elif self.state == PlotState.ROTATING: 
    9401018            if QApplication.keyboardModifiers() & Qt.ShiftModifier: 
    9411019                off_x = numpy.cross(self.camera, [0, 1, 0]) * (dx / self.move_factor) 
     
    9511029                    cos(self.pitch), 
    9521030                    sin(self.pitch)*sin(self.yaw)] 
    953  
    954         if self.state == PlotState.SCALING: 
     1031        elif self.state == PlotState.SCALING: 
    9551032            dx = pos.x() - self.scaling_init_pos.x() 
    9561033            dy = pos.y() - self.scaling_init_pos.y() 
     
    9621039 
    9631040    def mouseReleaseEvent(self, event): 
     1041        if self.state == PlotState.SELECTING and self.new_selection == None: 
     1042            self.new_selection = PolygonSelection([event.pos().x(), event.pos().y()]) 
     1043            return 
     1044 
    9641045        if self.state == PlotState.SCALING: 
    9651046            self.scale = numpy.maximum([0, 0, 0], self.scale + self.additional_scale) 
    9661047            self.additional_scale = [0, 0, 0] 
     1048            self.state = PlotState.IDLE 
    9671049        elif self.state == PlotState.SELECTING: 
    968             if self.new_selection[3] != None: 
    969                 self.selections.append(self.new_selection) 
    970                 self.auto_send_selection_callback() if self.auto_send_selection_callback else None 
    971  
    972         self.state = PlotState.IDLE 
     1050            if self.selection_type == SelectionType.POLYGON: 
     1051                last = self.new_selection.add_current_vertex() 
     1052                if last: 
     1053                    self.selections.append(self.new_selection) 
     1054                    self.selection_change_callback() if self.selection_change_callback else None 
     1055                    self.state = PlotState.IDLE 
     1056                    self.new_selection = None 
     1057            else: 
     1058                if self.new_selection.valid(): 
     1059                    self.selections.append(self.new_selection) 
     1060                    self.selection_change_callback() if self.selection_change_callback else None 
     1061        elif self.state == PlotState.ROTATING: 
     1062            self.state = PlotState.IDLE 
     1063 
     1064        if not (self.state == PlotState.SELECTING and self.selection_type == SelectionType.POLYGON): 
     1065            self.state = PlotState.IDLE 
     1066            self.new_selection = None 
     1067 
    9731068        self.updateGL() 
    9741069 
Note: See TracChangeset for help on using the changeset viewer.