Changeset 8820:5ee4e2f90e01 in orange


Ignore:
Timestamp:
08/29/11 00:32:17 (3 years ago)
Author:
matejd <matejd@…>
Branch:
default
Convert:
1d238d144439c19d0ef12c1762a19174e4cdbff6
Message:

Legends in 3d widgets now use Noughmads classes; value lines packed into VBO

Location:
orange/OrangeWidgets
Files:
6 edited

Legend:

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

    r8811 r8820  
    22from plot.owplotgui import * 
    33from plot.primitives import parse_obj 
    4  
    5 from Orange.preprocess.scaling import ScaleLinProjData3D 
     4from plot import OWPoint 
     5 
     6from Orange.preprocess.scaling import ScaleLinProjData3D, get_variable_values_sorted 
    67import orange 
    78Discrete = orange.VarTypes.Discrete 
     
    1415        ScaleLinProjData3D.__init__(self) 
    1516 
    16         self.camera_fov = 50. 
     17        self.camera_fov = 22. 
    1718        self.camera_in_center = False 
    1819        self.show_axes = self.show_chassis = self.show_grid = False 
     
    7475        self.cone_vao_id.num_vertices = len(vertices) / (vertex_size / 4) 
    7576 
    76         vertex_shader_source = '''#version 150 
     77        vertex_shader_source = ''' 
    7778            in vec3 position; 
    7879            in vec3 normal; 
     
    9394            ''' 
    9495 
    95         fragment_shader_source = '''#version 150 
     96        fragment_shader_source = ''' 
    9697            in vec4 color; 
    9798 
     
    111112        if not self.cone_shader.link(): 
    112113            print('Failed to link cone shader!') 
     114 
     115        ## Value lines shader 
     116        vertex_shader_source = ''' 
     117            in vec3 position; 
     118            in vec3 color; 
     119            in vec3 normal; 
     120 
     121            out vec4 var_color; 
     122 
     123            uniform mat4 projection; 
     124            uniform mat4 modelview; 
     125            uniform float value_line_length; 
     126            uniform vec3 plot_scale; 
     127 
     128            void main(void) 
     129            { 
     130                gl_Position = projection * modelview * vec4(position*plot_scale + normal*value_line_length, 1.); 
     131                var_color = vec4(color, 1.); 
     132            } 
     133            ''' 
     134 
     135        fragment_shader_source = ''' 
     136            in vec4 var_color; 
     137 
     138            void main(void) 
     139            { 
     140                gl_FragColor = var_color; 
     141            } 
     142            ''' 
     143 
     144        self.value_lines_shader = QtOpenGL.QGLShaderProgram() 
     145        self.value_lines_shader.addShaderFromSourceCode(QtOpenGL.QGLShader.Vertex, vertex_shader_source) 
     146        self.value_lines_shader.addShaderFromSourceCode(QtOpenGL.QGLShader.Fragment, fragment_shader_source) 
     147 
     148        self.value_lines_shader.bindAttributeLocation('position', 0) 
     149        self.value_lines_shader.bindAttributeLocation('color', 1) 
     150        self.value_lines_shader.bindAttributeLocation('normal', 2) 
     151 
     152        if not self.value_lines_shader.link(): 
     153            print('Failed to link value-lines shader!') 
    113154 
    114155    set_data = setData 
     
    193234                glLineWidth(1) 
    194235 
    195         self._draw_value_lines() 
    196  
    197     def _draw_value_lines(self): 
    198         # TODO: performance (VBO) 
    199236        if self.showValueLines: 
    200             for line in self.value_lines: 
    201                 x, y, z, xn, yn, zn, color = line 
    202                 x, y, z = self.plot_scale * [x, y, z] 
    203                 glColor3f(*color) 
    204                 glBegin(GL_LINES) 
    205                 glVertex3f(x, y, z) 
    206                 glVertex3f(x+self.valueLineLength*xn, 
    207                            y+self.valueLineLength*yn, 
    208                            z+self.valueLineLength*zn) 
    209                 glEnd() 
     237            self.value_lines_shader.bind() 
     238            self.value_lines_shader.setUniformValue('projection', self.projection) 
     239            self.value_lines_shader.setUniformValue('modelview', self.modelview) 
     240            self.value_lines_shader.setUniformValue('value_line_length', float(self.valueLineLength)) 
     241            self.value_lines_shader.setUniformValue('plot_scale', self.plot_scale[0], self.plot_scale[1], self.plot_scale[2]) 
     242 
     243            glBindVertexArray(self.value_lines_vao) 
     244            glDrawArrays(GL_LINES, 0, self.value_lines_vao.num_vertices) 
     245            glBindVertexArray(0) 
     246 
     247            self.value_lines_shader.release() 
    210248 
    211249    def updateData(self, labels=None, setAnchors=0, **args): 
    212250        self.clear() 
     251        self.clear_plot_transformations() 
    213252        self.value_lines = [] 
    214253 
    215254        if not self.have_data or (setAnchors and labels == None): 
    216255            self.anchor_data = [] 
    217             self.updateGL() 
     256            self.update() 
    218257            return 
    219258 
     
    230269 
    231270        proj_data = trans_proj_data.T 
    232         proj_data[0:3] += 0.5 # Geometry shader offsets positions by -0.5; leave class unmodified 
     271        proj_data[0:3] += 0.5 
    233272        if self.data_has_discrete_class: 
    234273            proj_data[3] = self.no_jittering_scaled_data[self.attribute_name_index[self.data_domain.classVar.name]] 
     
    266305                colors.append(c) 
    267306 
    268         self.set_shown_attributes_indices(0, 1, 2, color_index, symbol_index, size_index, label_index, 
    269                                           colors, num_symbols_used, 
    270                                           x_discrete, y_discrete, z_discrete, 
    271                                           numpy.array([1., 1., 1.]), numpy.array([0., 0., 0.])) 
     307        self.set_shown_attributes(0, 1, 2, color_index, symbol_index, size_index, label_index, 
     308                                  colors, num_symbols_used, 
     309                                  x_discrete, y_discrete, z_discrete, 
     310                                  numpy.array([1., 1., 1.]), numpy.array([0., 0., 0.])) 
     311 
     312        def_color = QColor(150, 150, 150) 
     313        def_symbol = 0 
     314        def_size = 10 
     315 
     316        if color_discrete: 
     317            num = len(self.data_domain.classVar.values) 
     318            values = get_variable_values_sorted(self.data_domain.classVar) 
     319            for ind in range(num): 
     320                symbol = ind if use_different_symbols else def_symbol 
     321                self.legend().add_item(self.data_domain.classVar.name, values[ind], OWPoint(symbol, self.discPalette[ind], def_size)) 
     322 
     323        if use_different_symbols and not color_discrete: 
     324            num = len(self.data_domain.classVar.values) 
     325            values = get_variable_values_sorted(self.data_domain.classVar) 
     326            for ind in range(num): 
     327                self.legend().add_item(self.data_domain.classVar.name, values[ind], OWPoint(ind, def_color, def_size)) 
     328 
     329        self.legend().set_orientation(Qt.Vertical) 
     330        self.legend().max_size = QSize(400, 400) 
     331        if self.legend().pos().x() == 0: 
     332            self.legend().setPos(QPointF(100, 100)) 
     333        self.legend().update_items() 
    272334 
    273335        x_positions = proj_data[0]-0.5 
     
    279341        data_size = len(self.raw_data) 
    280342 
    281         # TODO: build VBO out of this data 
    282343        for i in range(data_size): 
    283344            if not valid_data[i]: 
     
    299360 
    300361            for j in range(len_anchor_data): 
    301                 self.value_lines.append([x_positions[i], y_positions[i], z_positions[i], 
     362                self.value_lines.extend([x_positions[i], y_positions[i], z_positions[i], 
     363                                         color[0]/255., 
     364                                         color[1]/255., 
     365                                         color[2]/255., 
     366                                         0., 0., 0., 
     367                                         x_positions[i], y_positions[i], z_positions[i], 
     368                                         color[0]/255., 
     369                                         color[1]/255., 
     370                                         color[2]/255., 
    302371                                         x_directions[j]*example_values[j], 
    303372                                         y_directions[j]*example_values[j], 
    304                                          z_directions[j]*example_values[j], 
    305                                          color]) 
    306  
    307         self.updateGL() 
     373                                         z_directions[j]*example_values[j]]) 
     374 
     375        self.value_lines_vao = GLuint(0) 
     376        glGenVertexArrays(1, self.value_lines_vao) 
     377        glBindVertexArray(self.value_lines_vao) 
     378 
     379        vertex_buffer_id = glGenBuffers(1) 
     380        glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_id) 
     381        glBufferData(GL_ARRAY_BUFFER, numpy.array(self.value_lines, 'f'), GL_STATIC_DRAW) 
     382 
     383        vertex_size = (3+3+3)*4 
     384        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, vertex_size, c_void_p(0)) 
     385        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, vertex_size, c_void_p(3*4)) 
     386        glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, vertex_size, c_void_p(6*4)) 
     387        glEnableVertexAttribArray(0) 
     388        glEnableVertexAttribArray(1) 
     389        glEnableVertexAttribArray(2) 
     390 
     391        glBindVertexArray(0) 
     392        glBindBuffer(GL_ARRAY_BUFFER, 0) 
     393 
     394        self.value_lines_vao.num_vertices = len(self.value_lines) / (vertex_size / 4) 
     395 
     396        self.update() 
    308397 
    309398    def updateGraph(self, attrList=None, setAnchors=0, insideColors=None, **args): 
     
    320409 
    321410    def set_palette(self, palette): 
    322         self.updateGL() 
     411        self.update() 
    323412 
    324413    def getSelectionsAsExampleTables(self, attrList, useAnchorData=1, addProjectedPositions=0): 
     
    330419    def update_point_size(self): 
    331420        self.symbol_scale = self.point_width*self._point_width_to_symbol_scale 
    332         self.updateGL() 
     421        self.update() 
    333422 
    334423    def update_alpha_value(self): 
    335         self.updateGL() 
     424        self.update() 
    336425 
    337426    def replot(self): 
     
    357446        for x, y, z, attribute in self.anchor_data: 
    358447            value = example[self.attribute_name_index[attribute]] 
     448            if value < 0: 
     449                x = y = z = 0 
    359450            max_value = self.attr_values[attribute][1] 
    360451            factor = value / max_value 
     
    365456            self._arrow_lines.append([x*factor, y*factor, z*factor, value, factor, color]) 
    366457        self._mouseover_called = True 
    367         self.updateGL() 
     458        self.update() 
    368459 
    369460    def get_example_tooltip_text(self, example, indices=None, maxIndices=20): 
     
    372463        if not indices:  
    373464            indices = range(len(self.dataDomain.attributes)) 
    374          
     465 
    375466        # don't show the class value twice 
    376467        if example.domain.classVar: 
     
    378469            while classIndex in indices: 
    379470                indices.remove(classIndex)       
    380        
     471 
    381472        text = "<b>Attributes:</b><br>" 
    382473        for index in indices[:maxIndices]: 
     
    420511            self._arrow_lines = [] 
    421512            if before > 0: 
    422                 self.updateGL() 
     513                self.update() 
    423514 
    424515        OWPlot3D.mouseMoveEvent(self, event) 
  • orange/OrangeWidgets/Visualize Qt/OWLinProjQt.py

    r8811 r8820  
    219219        bbox = OWGUI.widgetBox(box, orientation = "horizontal") 
    220220        if "3d" in name_lower: 
    221             OWGUI.checkBox(bbox, self, 'graph.showValueLines', 'Show value lines  ', callback = self.graph.updateGL) 
    222             OWGUI.qwtHSlider(bbox, self, 'graph.valueLineLength', minValue=1, maxValue=10, step=1, callback = self.graph.updateGL, showValueLabel = 0) 
     221            OWGUI.checkBox(bbox, self, 'graph.showValueLines', 'Show value lines  ', callback = self.graph.update) 
     222            OWGUI.qwtHSlider(bbox, self, 'graph.valueLineLength', minValue=1, maxValue=10, step=1, callback = self.graph.update, showValueLabel = 0) 
    223223        else: 
    224224            OWGUI.checkBox(bbox, self, 'graph.showValueLines', 'Show value lines  ', callback = self.updateGraph) 
     
    250250 
    251251        box = OWGUI.widgetBox(self.SettingsTab, "Tooltips Settings") 
    252         callback = self.graph.updateGL if "3d" in name_lower else self.updateGraph 
     252        callback = self.graph.update if "3d" in name_lower else self.updateGraph 
    253253        OWGUI.comboBox(box, self, "graph.tooltipKind", items = ["Show line tooltips", "Show visible attributes", "Show all attributes"], callback = callback) 
    254254        OWGUI.comboBox(box, self, "graph.tooltipValue", items = ["Tooltips show data values", "Tooltips show spring values"], callback = callback, tooltip = "Do you wish that tooltips would show you original values of visualized attributes or the 'spring' values (values between 0 and 1). \nSpring values are scaled values that are used for determining the position of shown points. Observing these values will therefore enable you to \nunderstand why the points are placed where they are.") 
  • orange/OrangeWidgets/Visualize Qt/OWScatterPlot3D.py

    r8815 r8820  
    99from plot.owplotgui import OWPlotGUI 
    1010from plot.owplot import OWPlot 
     11from plot import OWPoint 
    1112 
    1213import orange 
     
    4748class ScatterPlot(OWPlot3D, orngScaleScatterPlotData): 
    4849    def __init__(self, parent=None): 
     50        self.parent = parent 
    4951        OWPlot3D.__init__(self, parent) 
    5052        orngScaleScatterPlotData.__init__(self) 
     
    5456        self.show_grid = True 
    5557        self.show_chassis = True 
     58         
     59        self.animate_plot = False 
    5660 
    5761    def activate_zooming(self): 
     
    6468        orngScaleScatterPlotData.set_data(self, data, subset_data, **args) 
    6569        OWPlot3D.set_plot_data(self, self.scaled_data, self.scaled_subset_data) 
     70        OWPlot3D.initializeGL(self) 
    6671 
    6772    def update_data(self, x_attr, y_attr, z_attr, 
     
    8388        num_symbols_used = -1 
    8489        if symbol_attr != '' and symbol_attr != 'Same symbol)' and\ 
    85            len(self.data_domain[symbol_attr].values) < len(Symbol): 
     90           len(self.data_domain[symbol_attr].values) < len(Symbol) and\ 
     91           self.data_domain[symbol_attr].varType == Discrete: 
    8692            symbol_index = self.attribute_name_index[symbol_attr] 
    87             if self.data_domain[symbol_attr].varType == Discrete: 
    88                 symbol_discrete = True 
    89                 num_symbols_used = len(self.data_domain[symbol_attr].values) 
     93            symbol_discrete = True 
     94            num_symbols_used = len(self.data_domain[symbol_attr].values) 
    9095 
    9196        size_index = -1 
     
    130135            data_translation[2] = 1. 
    131136 
     137        # TODO: valid_data! 
     138        #validData = self.getValidList(attrIndices)      # get examples that have valid data for each used attribute 
     139 
    132140        self.clear() 
    133         self.set_shown_attributes_indices(x_index, y_index, z_index, 
     141        self.set_shown_attributes(x_index, y_index, z_index, 
    134142            color_index, symbol_index, size_index, label_index, 
    135143            colors, num_symbols_used, 
     
    137145            data_scale, data_translation) 
    138146 
    139         if self.show_legend: 
    140             legend_keys = {} 
    141             color_index = color_index if color_index != -1 and color_discrete else -1 
    142             size_index = size_index if size_index != -1 and size_discrete else -1 
    143             symbol_index = symbol_index if symbol_index != -1 and symbol_discrete else -1 
    144  
    145             single_legend = [color_index, size_index, symbol_index].count(-1) == 2 
    146             if single_legend: 
    147                 legend_join = lambda name, val: val 
    148             else: 
    149                 legend_join = lambda name, val: name + '=' + val  
    150  
    151             color_attr = self.data_domain[color_attr] if color_index != -1 else None 
    152             symbol_attr = self.data_domain[symbol_attr] if symbol_index != -1 else None 
    153             size_attr = self.data_domain[size_attr] if size_index != -1 else None 
    154  
    155             if color_index != -1: 
    156                 num = len(color_attr.values) 
    157                 val = [[], [], [1.]*num, [Symbol.RECT]*num] 
    158                 var_values = get_variable_values_sorted(color_attr) 
    159                 for i in range(num): 
    160                     val[0].append(legend_join(color_attr.name, var_values[i])) 
    161                     c = self.disc_palette[i] 
    162                     val[1].append([c.red()/255., c.green()/255., c.blue()/255., 1.]) 
    163                 legend_keys[color_attr] = val 
    164  
    165             if symbol_index != -1: 
    166                 num = len(symbol_attr.values) 
    167                 if legend_keys.has_key(symbol_attr): 
    168                     val = legend_keys[symbol_attr] 
    169                 else: 
    170                     val = [[], [(0, 0, 0, 1)]*num, [1.]*num, []] 
    171                 var_values = get_variable_values_sorted(symbol_attr) 
    172                 val[3] = [] 
    173                 val[0] = [] 
    174                 for i in range(num): 
    175                     val[3].append(i) 
    176                     val[0].append(legend_join(symbol_attr.name, var_values[i])) 
    177                 legend_keys[symbol_attr] = val 
    178  
    179             if size_index != -1: 
    180                 num = len(size_attr.values) 
    181                 if legend_keys.has_key(size_attr): 
    182                     val = legend_keys[size_attr] 
    183                 else: 
    184                     val = [[], [(0, 0, 0, 1)]*num, [], [Symbol.RECT]*num] 
    185                 val[2] = [] 
    186                 val[0] = [] 
    187                 var_values = get_variable_values_sorted(size_attr) 
    188                 for i in range(num): 
    189                     val[0].append(legend_join(size_attr.name, var_values[i])) 
    190                     val[2].append(0.1 + float(i) / len(var_values)) 
    191                 legend_keys[size_attr] = val 
    192  
    193             for val in legend_keys.values(): 
    194                 for i in range(len(val[1])): 
    195                     self.legend.add_item(val[3][i], val[1][i], val[2][i], val[0][i]) 
     147        def_color = QColor(150, 150, 150) 
     148        def_symbol = 0 
     149        def_size = 10 
     150 
     151        if color_discrete: 
     152            num = len(self.data_domain[color_attr].values) 
     153            values = get_variable_values_sorted(self.data_domain[color_attr]) 
     154            for ind in range(num): 
     155                self.legend().add_item(color_attr, values[ind], OWPoint(def_symbol, self.disc_palette[ind], def_size)) 
     156 
     157        if symbol_index != -1: 
     158            num = len(self.data_domain[symbol_attr].values) 
     159            values = get_variable_values_sorted(self.data_domain[symbol_attr]) 
     160            for ind in range(num): 
     161                self.legend().add_item(symbol_attr, values[ind], OWPoint(ind, def_color, def_size)) 
     162 
     163        if size_discrete: 
     164            num = len(self.data_domain[size_attr].values) 
     165            values = get_variable_values_sorted(self.data_domain[size_attr]) 
     166            for ind in range(num): 
     167                self.legend().add_item(color_attr, values[ind], OWPoint(def_symbol, def_color, 6 + round(ind * 5 / len(values)))) 
     168 
     169        # Draw color scale for continuous coloring attribute 
     170        if color_index != -1 and self.data_domain[color_attr].varType == Continuous: 
     171            self.legend().add_color_gradient(color_attr, [("%%.%df" % self.data_domain[color_attr].numberOfDecimals % v) for v in self.attr_values[color_attr]]) 
     172 
     173        self.legend().set_orientation(Qt.Vertical) 
     174        self.legend().max_size = QSize(400, 400) 
     175        if self.legend().pos().x() == 0: 
     176            self.legend().setPos(QPointF(100, 100)) 
     177        self.legend().update_items() 
    196178 
    197179        self.set_axis_title(Axis.X, x_attr) 
     
    206188            self.set_axis_labels(Axis.Z, get_variable_values_sorted(self.data_domain[z_attr])) 
    207189 
    208         self.updateGL() 
     190        self.update() 
     191 
     192    # TODO 
     193    def color(self, role, group=None): 
     194        return QColor(200, 50, 50) 
     195        #if group: 
     196        #    return self.palette().color(group, role) 
     197        #else: 
     198        #    return self.palette().color(role) 
    209199 
    210200    def before_draw(self): 
     
    397387            tooltip='Scale symbol size', 
    398388            callback=self.on_checkbox_update) 
    399         ss.setValue(4) 
     389        ss.setValue(8) 
    400390 
    401391        OWGUI.hSlider(box, self, 'plot.alpha_value', label='Transparency', 
     
    557547                self.color_attr_cb.addItem(icons[attr.varType], attr.name) 
    558548                self.size_attr_cb.addItem(icons[attr.varType], attr.name) 
    559             if attr.varType == Discrete:  
     549            if attr.varType == Discrete and len(attr.values) < len(Symbol): 
    560550                self.symbol_attr_cb.addItem(icons[attr.varType], attr.name) 
    561551            self.label_attr_cb.addItem(icons[attr.varType], attr.name) 
     
    634624 
    635625    def on_checkbox_update(self): 
    636         self.plot.updateGL() 
     626        self.plot.update() 
    637627 
    638628    def update_plot(self): 
  • orange/OrangeWidgets/Visualize Qt/OWSphereviz3D.py

    r8810 r8820  
    119119            ''' 
    120120 
    121         fragment_shader_source = '''#version 150 
     121        fragment_shader_source = ''' 
    122122            in float transparency; 
    123123            uniform bool invert_transparency; 
     
    153153            ''' 
    154154 
    155         fragment_shader_source = '''#version 150 
     155        fragment_shader_source = ''' 
    156156            uniform vec4 color; 
    157157 
  • orange/OrangeWidgets/plot/owlegend.py

    r8735 r8820  
    202202            for i in lst: 
    203203                i.setParentItem(None) 
    204                 self.scene().removeItem(i) 
     204                if self.scene(): 
     205                    self.scene().removeItem(i) 
    205206        self.items = {} 
    206207        self.update_items() 
     
    248249        if category not in self.items: 
    249250            return 
    250         for item in self.items[category]: 
    251             self.scene().removeItem(item) 
     251        if self.scene(): 
     252            for item in self.items[category]: 
     253                self.scene().removeItem(item) 
    252254        del self.items[category] 
    253255         
  • orange/OrangeWidgets/plot/owplot3d.py

    r8811 r8820  
    2828from owtheme import PlotTheme 
    2929from owplot import OWPlot 
     30from owlegend import OWLegend, OWLegendItem, OWLegendTitle, OWLegendGradient 
    3031 
    3132import OpenGL 
     
    4950    pass 
    5051 
     52# TODO: move to owopenglrenderer 
    5153def draw_triangle(x0, y0, x1, y1, x2, y2): 
    5254    glBegin(GL_TRIANGLES) 
     
    109111              'DIAMOND', 'WEDGE', 'LWEDGE', 'CROSS', 'XCROSS') 
    110112 
     113# TODO: move to scatterplot 
    111114Axis = enum('X', 'Y', 'Z', 'CUSTOM') 
    112115 
    113116from plot.primitives import normal_from_points, get_symbol_geometry, clamp, normalize, GeometryType 
    114117 
    115 class Legend(object): 
    116     def __init__(self, plot): 
    117         self.border_color = [0.5, 0.5, 0.5, 1] 
    118         self.border_thickness = 2 
    119         self.position = [0, 0] 
    120         self.size = [0, 0] 
    121         self.items = [] 
    122         self.plot = plot 
    123         self.symbol_scale = 6 
    124         self.font = QFont('Helvetica', 9) 
    125         self.metrics = QFontMetrics(self.font) 
    126  
    127     def add_item(self, symbol, color, size, title): 
    128         '''Adds an item to the legend. 
    129            Symbol can be integer value or enum Symbol. 
    130            Color should be RGBA. Size should be between 0 and 1. 
    131         ''' 
    132         if not Symbol.is_valid(symbol): 
    133             print('Legend: invalid symbol') 
    134             return 
    135         self.items.append([symbol, color, size, title]) 
    136         self.size[0] = max(self.metrics.width(item[3]) for item in self.items) + 40 
    137         self.size[1] = len(self.items) * self.metrics.height() + 4 
    138  
    139     def clear(self): 
    140         self.items = [] 
    141  
    142     def draw(self): 
    143         if not self.items: 
    144             return 
    145  
    146         x, y = self.position 
    147         w, h = self.size 
    148         t = self.border_thickness 
    149  
    150         # Draw legend outline first. 
     118class OWLegend3D(OWLegend): 
     119    def set_symbol_geometry(self, symbol, geometry): 
     120        if not hasattr(self, '_symbol_geometry'): 
     121            self._symbol_geometry = {} 
     122        self._symbol_geometry[symbol] = geometry 
     123 
     124    def _draw_item_background(self, pos, item): 
     125        rect = item.rect().normalized().adjusted(pos.x(), pos.y(), pos.x(), pos.y()) 
     126        glBegin(GL_QUADS) 
     127        glVertex2f(rect.left(), rect.top()) 
     128        glVertex2f(rect.left(), rect.bottom()) 
     129        glVertex2f(rect.right(), rect.bottom()) 
     130        glVertex2f(rect.right(), rect.top()) 
     131        glEnd() 
     132 
     133    def _draw_symbol(self, pos, symbol): 
     134        edges = self._symbol_geometry[symbol.symbol()] 
     135        color = symbol.color() 
     136        size = symbol.size() / 2 
     137        glColor3f(color.red()/255., color.green()/255., color.blue()/255.) 
     138        for v0, v1 in zip(edges[::2], edges[1::2]): 
     139            x0, y0 = v0.x(), v0.y() 
     140            x1, y1 = v1.x(), v1.y() 
     141            glBegin(GL_LINES) 
     142            glVertex2f(x0*size + pos.x(), -y0*size + pos.y()) 
     143            glVertex2f(x1*size + pos.x(), -y1*size + pos.y()) 
     144            glEnd() 
     145 
     146    def _paint(self, widget): 
    151147        glDisable(GL_DEPTH_TEST) 
    152         glColor4f(*self.border_color) 
    153         glBegin(GL_QUADS) 
    154         glVertex2f(x,   y) 
    155         glVertex2f(x+w, y) 
    156         glVertex2f(x+w, y+h) 
    157         glVertex2f(x,   y+h) 
    158         glEnd() 
    159  
    160         glColor4f(1, 1, 1, 1) 
    161         glBegin(GL_QUADS) 
    162         glVertex2f(x+t,   y+t) 
    163         glVertex2f(x+w-t, y+t) 
    164         glVertex2f(x+w-t, y+h-t) 
    165         glVertex2f(x+t,   y+h-t) 
    166         glEnd() 
    167  
    168         item_pos_y = y + t + 13 
    169  
    170         for symbol, color, size, text in self.items: 
    171             glColor4f(*color) 
    172             triangles = get_symbol_geometry(symbol, GeometryType.SOLID_2D) 
    173             glBegin(GL_TRIANGLES) 
    174             for v0, v1, v2, _, _, _ in triangles: 
    175                 glVertex2f(x+v0[0]*self.symbol_scale*size+10, item_pos_y+v0[1]*self.symbol_scale*size-5) 
    176                 glVertex2f(x+v1[0]*self.symbol_scale*size+10, item_pos_y+v1[1]*self.symbol_scale*size-5) 
    177                 glVertex2f(x+v2[0]*self.symbol_scale*size+10, item_pos_y+v2[1]*self.symbol_scale*size-5) 
    178             glEnd() 
    179             self.plot.renderText(x+t+30, item_pos_y, text, font=self.font) 
    180             item_pos_y += self.metrics.height() 
    181  
    182     def contains(self, x, y): 
    183         return self.position[0] <= x <= self.position[0]+self.size[0] and\ 
    184                self.position[1] <= y <= self.position[1]+self.size[1] 
    185  
    186     def move(self, dx, dy): 
    187         self.position[0] += dx 
    188         self.position[1] += dy 
     148        glDisable(GL_BLEND) 
     149        offset = QPointF(0, 15) # TODO 
     150 
     151        for category in self.items: 
     152            items = self.items[category] 
     153            for item in items: 
     154                if isinstance(item, OWLegendTitle): 
     155                    widget.qglColor(widget._theme.background_color) 
     156                    pos = self.pos() + item.pos() 
     157                    self._draw_item_background(pos, item.rect_item) 
     158 
     159                    widget.qglColor(widget._theme.labels_color) 
     160                    pos = self.pos() + item.pos() + item.text_item.pos() + offset 
     161                    widget.renderText(pos.x(), pos.y(), item.text_item.toPlainText(), item.text_item.font()) 
     162                elif isinstance(item, OWLegendItem): 
     163                    widget.qglColor(widget._theme.background_color) 
     164                    pos = self.pos() + item.pos() 
     165                    self._draw_item_background(pos, item.rect_item) 
     166 
     167                    widget.qglColor(widget._theme.labels_color) 
     168                    pos = self.pos() + item.pos() + item.text_item.pos() + offset 
     169                    widget.renderText(pos.x(), pos.y(), item.text_item.toPlainText(), item.text_item.font()) 
     170 
     171                    symbol = item.point_item 
     172                    pos = self.pos() + item.pos() + symbol.pos() 
     173                    self._draw_symbol(pos, symbol) 
     174                # TODO: gradient 
    189175 
    190176class OWPlot3D(orangeqt.Plot3D): 
     
    192178        orangeqt.Plot3D.__init__(self, parent) 
    193179 
    194         self.camera_distance = 3. 
     180        # Don't clear background when using QPainter 
     181        self.setAutoFillBackground(False) 
     182 
     183        self.camera_distance = 6. 
    195184 
    196185        self.scale_factor = 0.05 
     
    205194        self.ortho_near = -1 
    206195        self.ortho_far = 2000 
    207         self.perspective_near = 0.01 
     196        self.perspective_near = 0.5 
    208197        self.perspective_far = 10. 
    209         self.camera_fov = 30. 
     198        self.camera_fov = 14. 
    210199 
    211200        self.use_ortho = False 
     201 
    212202        self.show_legend = True 
    213         self.legend = Legend(self) 
     203        self._legend = OWLegend3D(self, None) 
     204        self._legend_margin = QRectF(0, 0, 100, 0) 
     205        self._legend_moved = False 
     206        self._legend.set_floating(True) 
     207        self._legend.set_orientation(Qt.Vertical) 
     208 
     209        #self._legend.update_items() 
    214210 
    215211        self.use_2d_symbols = False 
     
    281277        #if hasattr(self, 'data_buffer'): 
    282278        #    glDeleteBuffers(1, self.data_buffer) 
     279 
     280    def legend(self): 
     281        return self._legend 
    283282 
    284283    def initializeGL(self): 
     
    394393                edges = [QVector3D(*v) for edge in edges for v in edge] 
    395394                orangeqt.Plot3D.set_symbol_geometry(self, symbol, 2, edges) 
     395                self._legend.set_symbol_geometry(symbol, edges) 
    396396 
    397397                edges = get_symbol_geometry(symbol, GeometryType.EDGE_3D) 
     
    469469        return modelview, projection 
    470470 
    471     def paintGL(self): 
     471    def paintEvent(self, event): 
    472472        glViewport(0, 0, self.width(), self.height()) 
    473473        self.qglClearColor(self._theme.background_color) 
     
    541541                glBindVertexArray(0) 
    542542            else: 
    543                 orangeqt.Plot3D.draw_data(self, self.symbol_program.programId(), self.alpha_value / 255.) 
     543                orangeqt.Plot3D.draw_data_solid(self) 
    544544            self.symbol_program.release() 
    545545            self.tooltip_fbo.release() 
     
    547547            glViewport(0, 0, self.width(), self.height()) 
    548548 
    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() 
    558  
    559549        self.draw_helpers() 
     550 
     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            glDisable(GL_BLEND) 
     558 
     559            self._legend._paint(self) 
     560 
     561        self.swapBuffers() 
    560562 
    561563    def draw_labels(self): 
     
    788790            draw_axis_title(axis, self.y_axis_title, normal) 
    789791 
    790     def set_shown_attributes_indices(self, 
     792    def set_shown_attributes(self, 
    791793            x_index, y_index, z_index, 
    792794            color_index, symbol_index, size_index, label_index, 
     
    877879            print('Data processing took ' + str(time.time() - start) + ' seconds') 
    878880 
    879         self.updateGL() 
     881        self.update() 
    880882 
    881883    def set_plot_data(self, data, subset_data=None): 
     
    902904                                     self.example_size) 
    903905 
     906    # TODO: to scatterplot 
    904907    def set_axis_labels(self, axis_id, labels): 
    905908        '''labels should be a list of strings''' 
     
    945948                break 
    946949            self.plot_translation = self.plot_translation + translation_step 
    947             self.updateGL() 
     950            self.update() 
    948951        for i in range(num_steps): 
    949952            if time.time() - start > 1.: 
     
    951954                break 
    952955            self.plot_scale = self.plot_scale + scale_step 
    953             self.updateGL() 
     956            self.update() 
    954957 
    955958    def zoom_out(self): 
     
    990993        return point 
    991994 
    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  
    1020995    def get_min_max_selected(self, area): 
    1021996        viewport = [0, 0, self.width(), self.height()] 
     
    10351010                                    self.colors, self.num_symbols_used, 
    10361011                                    self.x_discrete, self.y_discrete, self.z_discrete, self.use_2d_symbols) 
    1037         self.updateGL() 
     1012        self.update() 
    10381013 
    10391014    def set_selection_behavior(self, behavior): 
     
    10481023 
    10491024        if buttons & Qt.LeftButton: 
    1050             if self.show_legend and self.legend.contains(pos.x(), pos.y()): 
     1025            legend_pos = self._legend.pos() 
     1026            lx, ly = legend_pos.x(), legend_pos.y() 
     1027            if self._legend.boundingRect().adjusted(lx, ly, lx, ly).contains(pos.x(), pos.y()): 
     1028                event.scenePos = lambda: QPointF(pos) 
     1029                self._legend.mousePressEvent(event) 
     1030                self.setCursor(Qt.ClosedHandCursor) 
    10511031                self.state = PlotState.DRAGGING_LEGEND 
    10521032            else: 
     
    10601040            else: 
    10611041                self.zoom_out() 
    1062             self.updateGL() 
     1042            self.update() 
    10631043        elif buttons & Qt.MiddleButton: 
    10641044            if QApplication.keyboardModifiers() & Qt.ShiftModifier: 
     
    10681048 
    10691049    def _check_mouseover(self, pos): 
    1070         if self.mouseover_callback != None and self.state == PlotState.IDLE and\ 
    1071             (not self.show_legend or not self.legend.contains(pos.x(), pos.y())): 
     1050        if self.mouseover_callback != None and self.state == PlotState.IDLE:# and\ 
     1051            #(not self.show_legend or not self.legend.contains(pos.x(), pos.y())): 
    10721052            if abs(pos.x() - self.tooltip_win_center[0]) > 100 or\ 
    10731053               abs(pos.y() - self.tooltip_win_center[1]) > 100: 
    10741054                self.tooltip_fbo_dirty = True 
    1075                 self.updateGL() 
     1055                self.update() 
    10761056            # Use pixel-color-picking to read example index under mouse cursor (also called ID rendering). 
    10771057            self.tooltip_fbo.bind() 
     
    11031083            self._selection.setBottomRight(pos) 
    11041084        elif self.state == PlotState.DRAGGING_LEGEND: 
    1105             self.legend.move(dx, dy) 
     1085            event.scenePos = lambda: QPointF(pos) 
     1086            self._legend.mouseMoveEvent(event) 
    11061087        elif self.state == PlotState.ROTATING: 
    11071088            self.yaw += dx / (self.rotation_factor*self.width()) 
     
    11231104            dy /= self.scale_factor * self.height() 
    11241105            self.additional_scale = [dx, dy, 0] 
     1106        elif self.state == PlotState.IDLE: 
     1107            legend_pos = self._legend.pos() 
     1108            lx, ly = legend_pos.x(), legend_pos.y() 
     1109            if self._legend.boundingRect().adjusted(lx, ly, lx, ly).contains(pos.x(), pos.y()): 
     1110                self.setCursor(Qt.PointingHandCursor) 
     1111            else: 
     1112                self.unsetCursor() 
    11251113 
    11261114        self.mouse_position = pos 
    1127         self.updateGL() 
     1115        self.update() 
    11281116 
    11291117    def mouseReleaseEvent(self, event): 
     1118        if self.state == PlotState.DRAGGING_LEGEND: 
     1119            self._legend.mouseReleaseEvent(event) 
    11301120        if self.state == PlotState.SCALING: 
    11311121            self.plot_scale = numpy.maximum([1e-5, 1e-5, 1e-5], self.plot_scale+self.additional_scale) 
     
    11541144                    self.auto_send_selection_callback() 
    11551145 
     1146        self.unsetCursor() 
    11561147        self.state = PlotState.IDLE 
    1157         self.updateGL() 
     1148        self.update() 
    11581149 
    11591150    def wheelEvent(self, event): 
     
    11621153            self.plot_scale *= delta 
    11631154            self.tooltip_fbo_dirty = True 
    1164             self.updateGL() 
     1155            self.update() 
     1156 
     1157    def notify_legend_moved(self, pos): 
     1158        self._legend.set_floating(True, pos) 
     1159        self._legend.set_orientation(Qt.Vertical) 
    11651160 
    11661161    def get_theme(self): 
     
    11691164    def set_theme(self, value): 
    11701165        self._theme = value 
    1171         self.updateGL() 
     1166        self.update() 
    11721167 
    11731168    theme = pyqtProperty(PlotTheme, get_theme, set_theme) 
     
    11781173 
    11791174    def clear(self): 
    1180         self.legend.clear() 
    1181         self.zoom_stack = [] 
    1182         self.zoomed_size = [1., 1., 1.] 
    1183         self.plot_translation = -array([0.5, 0.5, 0.5]) 
    1184         self.plot_scale = array([1., 1., 1.]) 
    1185         self.additional_scale = array([0., 0., 0.]) 
     1175        self._legend.clear() 
    11861176        self.data_scale = array([1., 1., 1.]) 
    11871177        self.data_translation = array([0., 0., 0.]) 
     
    11921182        self.feedback_generated = False 
    11931183 
     1184    def clear_plot_transformations(self): 
     1185        self.zoom_stack = [] 
     1186        self.zoomed_size = [1., 1., 1.] 
     1187        self.plot_translation = -array([0.5, 0.5, 0.5]) 
     1188        self.plot_scale = array([1., 1., 1.]) 
     1189        self.additional_scale = array([0., 0., 0.]) 
     1190 
     1191 
    11941192if __name__ == "__main__": 
    11951193    # TODO 
Note: See TracChangeset for help on using the changeset viewer.