source: orange/orange/OrangeWidgets/Visualize Qt/OWScatterPlot3D.py @ 8811:1b6ff57d37a3

Revision 8811:1b6ff57d37a3, 29.7 KB checked in by matejd <matejd@…>, 21 months ago (diff)

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

Line 
1'''
2<name>Scatterplot 3D</name>
3<priority>2001</priority>
4'''
5
6from OWWidget import *
7from plot.owplot3d import *
8from plot.owplotgui import OWPlotGUI
9from plot.owplot import OWPlot
10
11import orange
12Discrete = orange.VarTypes.Discrete
13Continuous = orange.VarTypes.Continuous
14
15from Orange.preprocess.scaling import get_variable_values_sorted
16
17import OWGUI
18import OWToolbars
19import orngVizRank
20from OWkNNOptimization import *
21from orngScaleScatterPlotData import *
22
23import numpy
24
25TooltipKind = enum('NONE', 'VISIBLE', 'ALL')
26
27# TODO: move themes to owtheme.py
28class ScatterPlotTheme(PlotTheme):
29    def __init__(self):
30        super(ScatterPlotTheme, self).__init__()
31        self.grid_color = QColor(200, 200, 200, 255)
32
33class LightTheme(ScatterPlotTheme):
34    pass
35
36class DarkTheme(ScatterPlotTheme):
37    def __init__(self):
38        super(DarkTheme, self).__init__()
39        self.grid_color = QColor(80, 80, 80, 255)
40        self.labels_color = QColor(230, 230, 230, 255)
41        self.helpers_color = QColor(230, 230, 230, 255)
42        self.axis_values_color = QColor(180, 180, 180, 255)
43        self.axis_color = QColor(200, 200, 200, 255)
44        self.background_color = QColor(0, 0, 0, 255)
45
46class ScatterPlot(OWPlot3D, orngScaleScatterPlotData):
47    def __init__(self, parent=None):
48        OWPlot3D.__init__(self, parent)
49        orngScaleScatterPlotData.__init__(self)
50
51        self.disc_palette = ColorPaletteGenerator()
52        self._theme = LightTheme()
53        self.show_grid = True
54        self.show_chassis = True
55
56    def activate_zooming(self):
57        print('activate_zooming')
58
59    def set_data(self, data, subset_data=None, **args):
60        if data == None:
61            return
62        args['skipIfSame'] = False
63        orngScaleScatterPlotData.set_data(self, data, subset_data, **args)
64        OWPlot3D.set_plot_data(self, self.scaled_data, self.scaled_subset_data)
65
66    def update_data(self, x_attr, y_attr, z_attr,
67                    color_attr, symbol_attr, size_attr, label_attr):
68        if self.data == None:
69            return
70        self.before_draw_callback = self.before_draw
71
72        color_discrete = symbol_discrete = size_discrete = False
73
74        color_index = -1
75        if color_attr != '' and color_attr != '(Same color)':
76            color_index = self.attribute_name_index[color_attr]
77            if self.data_domain[color_attr].varType == Discrete:
78                color_discrete = True
79                self.disc_palette.setNumberOfColors(len(self.data_domain[color_attr].values))
80
81        symbol_index = -1
82        num_symbols_used = -1
83        if symbol_attr != '' and symbol_attr != 'Same symbol)' and\
84           len(self.data_domain[symbol_attr].values) < len(Symbol):
85            symbol_index = self.attribute_name_index[symbol_attr]
86            if self.data_domain[symbol_attr].varType == Discrete:
87                symbol_discrete = True
88                num_symbols_used = len(self.data_domain[symbol_attr].values)
89
90        size_index = -1
91        if size_attr != '' and size_attr != '(Same size)':
92            size_index = self.attribute_name_index[size_attr]
93            if self.data_domain[size_attr].varType == Discrete:
94                size_discrete = True
95
96        label_index = -1
97        if label_attr != '' and label_attr != '(No labels)':
98            label_index = self.attribute_name_index[label_attr]
99
100        x_index = self.attribute_name_index[x_attr]
101        y_index = self.attribute_name_index[y_attr]
102        z_index = self.attribute_name_index[z_attr]
103
104        x_discrete = self.data_domain[x_attr].varType == Discrete
105        y_discrete = self.data_domain[y_attr].varType == Discrete
106        z_discrete = self.data_domain[z_attr].varType == Discrete
107
108        colors = []
109        if color_discrete:
110            for i in range(len(self.data_domain[color_attr].values)):
111                c = self.disc_palette[i]
112                colors.append(c)
113
114        data_scale = [self.attr_values[x_attr][1] - self.attr_values[x_attr][0],
115                      self.attr_values[y_attr][1] - self.attr_values[y_attr][0],
116                      self.attr_values[z_attr][1] - self.attr_values[z_attr][0]]
117        data_translation = [self.attr_values[x_attr][0],
118                            self.attr_values[y_attr][0],
119                            self.attr_values[z_attr][0]]
120        data_scale = 1. / numpy.array(data_scale)
121        if x_discrete:
122            data_scale[0] = 0.5 / float(len(self.data_domain[x_attr].values))
123            data_translation[0] = 1.
124        if y_discrete:
125            data_scale[1] = 0.5 / float(len(self.data_domain[y_attr].values))
126            data_translation[1] = 1.
127        if z_discrete:
128            data_scale[2] = 0.5 / float(len(self.data_domain[z_attr].values))
129            data_translation[2] = 1.
130
131        self.clear()
132        self.set_shown_attributes_indices(x_index, y_index, z_index,
133            color_index, symbol_index, size_index, label_index,
134            colors, num_symbols_used,
135            x_discrete, y_discrete, z_discrete,
136            data_scale, data_translation)
137
138        if self.show_legend:
139            legend_keys = {}
140            color_index = color_index if color_index != -1 and color_discrete else -1
141            size_index = size_index if size_index != -1 and size_discrete else -1
142            symbol_index = symbol_index if symbol_index != -1 and symbol_discrete else -1
143
144            single_legend = [color_index, size_index, symbol_index].count(-1) == 2
145            if single_legend:
146                legend_join = lambda name, val: val
147            else:
148                legend_join = lambda name, val: name + '=' + val
149
150            color_attr = self.data_domain[color_attr] if color_index != -1 else None
151            symbol_attr = self.data_domain[symbol_attr] if symbol_index != -1 else None
152            size_attr = self.data_domain[size_attr] if size_index != -1 else None
153
154            if color_index != -1:
155                num = len(color_attr.values)
156                val = [[], [], [1.]*num, [Symbol.RECT]*num]
157                var_values = get_variable_values_sorted(color_attr)
158                for i in range(num):
159                    val[0].append(legend_join(color_attr.name, var_values[i]))
160                    c = self.disc_palette[i]
161                    val[1].append([c.red()/255., c.green()/255., c.blue()/255., 1.])
162                legend_keys[color_attr] = val
163
164            if symbol_index != -1:
165                num = len(symbol_attr.values)
166                if legend_keys.has_key(symbol_attr):
167                    val = legend_keys[symbol_attr]
168                else:
169                    val = [[], [(0, 0, 0, 1)]*num, [1.]*num, []]
170                var_values = get_variable_values_sorted(symbol_attr)
171                val[3] = []
172                val[0] = []
173                for i in range(num):
174                    val[3].append(i)
175                    val[0].append(legend_join(symbol_attr.name, var_values[i]))
176                legend_keys[symbol_attr] = val
177
178            if size_index != -1:
179                num = len(size_attr.values)
180                if legend_keys.has_key(size_attr):
181                    val = legend_keys[size_attr]
182                else:
183                    val = [[], [(0, 0, 0, 1)]*num, [], [Symbol.RECT]*num]
184                val[2] = []
185                val[0] = []
186                var_values = get_variable_values_sorted(size_attr)
187                for i in range(num):
188                    val[0].append(legend_join(size_attr.name, var_values[i]))
189                    val[2].append(0.1 + float(i) / len(var_values))
190                legend_keys[size_attr] = val
191
192            for val in legend_keys.values():
193                for i in range(len(val[1])):
194                    self.legend.add_item(val[3][i], val[1][i], val[2][i], val[0][i])
195
196        self.set_axis_title(Axis.X, x_attr)
197        self.set_axis_title(Axis.Y, y_attr)
198        self.set_axis_title(Axis.Z, z_attr)
199
200        if x_discrete:
201            self.set_axis_labels(Axis.X, get_variable_values_sorted(self.data_domain[x_attr]))
202        if y_discrete:
203            self.set_axis_labels(Axis.Y, get_variable_values_sorted(self.data_domain[y_attr]))
204        if z_discrete:
205            self.set_axis_labels(Axis.Z, get_variable_values_sorted(self.data_domain[z_attr]))
206
207        self.updateGL()
208
209    def before_draw(self):
210        glMatrixMode(GL_PROJECTION)
211        glLoadIdentity()
212        glMultMatrixd(numpy.array(self.projection.data(), dtype=float))
213        glMatrixMode(GL_MODELVIEW)
214        glLoadIdentity()
215        glMultMatrixd(numpy.array(self.modelview.data(), dtype=float))
216
217        if self.show_grid:
218            self.draw_grid()
219        if self.show_chassis:
220            self.draw_chassis()
221
222    def draw_chassis(self):
223        self.qglColor(self._theme.axis_values_color)
224        glEnable(GL_LINE_STIPPLE)
225        glLineStipple(1, 0x00FF)
226        glDisable(GL_DEPTH_TEST)
227        glLineWidth(1)
228        glEnable(GL_BLEND)
229        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
230        edges = [self.x_axis, self.y_axis, self.z_axis,
231                 self.x_axis+self.unit_z, self.x_axis+self.unit_y,
232                 self.x_axis+self.unit_z+self.unit_y,
233                 self.y_axis+self.unit_x, self.y_axis+self.unit_z,
234                 self.y_axis+self.unit_x+self.unit_z,
235                 self.z_axis+self.unit_x, self.z_axis+self.unit_y,
236                 self.z_axis+self.unit_x+self.unit_y]
237        glBegin(GL_LINES)
238        for edge in edges:
239            start, end = edge
240            glVertex3f(*start)
241            glVertex3f(*end)
242        glEnd()
243        glDisable(GL_LINE_STIPPLE)
244        glEnable(GL_DEPTH_TEST)
245        glDisable(GL_BLEND)
246
247    def draw_grid(self):
248        cam_in_space = numpy.array([
249          self.camera[0]*self.camera_distance,
250          self.camera[1]*self.camera_distance,
251          self.camera[2]*self.camera_distance
252        ])
253
254        def _draw_grid(axis0, axis1, normal0, normal1, i, j):
255            self.qglColor(self._theme.grid_color)
256            for axis, normal, coord_index in zip([axis0, axis1], [normal0, normal1], [i, j]):
257                start, end = axis.copy()
258                start_value = self.map_to_data(start.copy())[coord_index]
259                end_value = self.map_to_data(end.copy())[coord_index]
260                values, _ = loose_label(start_value, end_value, 7)
261                for value in values:
262                    if not (start_value <= value <= end_value):
263                        continue
264                    position = start + (end-start)*((value-start_value) / float(end_value-start_value))
265                    glBegin(GL_LINES)
266                    glVertex3f(*position)
267                    glVertex3f(*(position-normal*1.))
268                    glEnd()
269
270        glDisable(GL_DEPTH_TEST)
271        glLineWidth(1)
272        glEnable(GL_BLEND)
273        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
274
275        planes = [self.axis_plane_xy, self.axis_plane_yz,
276                  self.axis_plane_xy_back, self.axis_plane_yz_right]
277        axes = [[self.x_axis, self.y_axis],
278                [self.y_axis, self.z_axis],
279                [self.x_axis+self.unit_z, self.y_axis+self.unit_z],
280                [self.z_axis+self.unit_x, self.y_axis+self.unit_x]]
281        normals = [[numpy.array([0,-1, 0]), numpy.array([-1, 0, 0])],
282                   [numpy.array([0, 0,-1]), numpy.array([ 0,-1, 0])],
283                   [numpy.array([0,-1, 0]), numpy.array([-1, 0, 0])],
284                   [numpy.array([0,-1, 0]), numpy.array([ 0, 0,-1])]]
285        coords = [[0, 1],
286                  [1, 2],
287                  [0, 1],
288                  [2, 1]]
289        visible_planes = [plane_visible(plane, cam_in_space) for plane in planes]
290        xz_visible = not plane_visible(self.axis_plane_xz, cam_in_space)
291        if xz_visible:
292            _draw_grid(self.x_axis, self.z_axis, numpy.array([0,0,-1]), numpy.array([-1,0,0]), 0, 2)
293        for visible, (axis0, axis1), (normal0, normal1), (i, j) in\
294             zip(visible_planes, axes, normals, coords):
295            if not visible:
296                _draw_grid(axis0, axis1, normal0, normal1, i, j)
297
298        glEnable(GL_DEPTH_TEST)
299        glDisable(GL_BLEND)
300
301class OWScatterPlot3D(OWWidget):
302    settingsList = ['plot.show_legend', 'plot.symbol_size', 'plot.show_x_axis_title', 'plot.show_y_axis_title',
303                    'plot.show_z_axis_title', 'plot.show_legend', 'plot.use_2d_symbols',
304                    'plot.alpha_value', 'plot.show_grid', 'plot.pitch', 'plot.yaw', 'plot.use_ortho',
305                    'plot.show_chassis', 'plot.show_axes',
306                    'auto_send_selection', 'auto_send_selection_update',
307                    'plot.jitter_size', 'plot.jitter_continuous']
308    contextHandlers = {'': DomainContextHandler('', ['x_attr', 'y_attr', 'z_attr'])}
309    jitter_sizes = [0.0, 0.1, 0.5, 1, 2, 3, 4, 5, 7, 10, 15, 20, 30, 40, 50]
310
311    def __init__(self, parent=None, signalManager=None, name='Scatter Plot 3D'):
312        OWWidget.__init__(self, parent, signalManager, name, True)
313
314        self.inputs = [('Examples', ExampleTable, self.set_data, Default), ('Subset Examples', ExampleTable, self.set_subset_data)]
315        self.outputs = [('Selected Examples', ExampleTable), ('Unselected Examples', ExampleTable)]
316
317        self.x_attr = ''
318        self.y_attr = ''
319        self.z_attr = ''
320
321        self.x_attr_discrete = False
322        self.y_attr_discrete = False
323        self.z_attr_discrete = False
324
325        self.color_attr = ''
326        self.size_attr = ''
327        self.symbol_attr = ''
328        self.label_attr = ''
329
330        self.tabs = OWGUI.tabWidget(self.controlArea)
331        self.main_tab = OWGUI.createTabPage(self.tabs, 'Main')
332        self.settings_tab = OWGUI.createTabPage(self.tabs, 'Settings', canScroll=True)
333
334        self.x_attr_cb = OWGUI.comboBox(self.main_tab, self, 'x_attr', box='X-axis attribute',
335            tooltip='Attribute to plot on X axis.',
336            callback=self.on_axis_change,
337            sendSelectedValue=1,
338            valueType=str)
339
340        self.y_attr_cb = OWGUI.comboBox(self.main_tab, self, 'y_attr', box='Y-axis attribute',
341            tooltip='Attribute to plot on Y axis.',
342            callback=self.on_axis_change,
343            sendSelectedValue=1,
344            valueType=str)
345
346        self.z_attr_cb = OWGUI.comboBox(self.main_tab, self, 'z_attr', box='Z-axis attribute',
347            tooltip='Attribute to plot on Z axis.',
348            callback=self.on_axis_change,
349            sendSelectedValue=1,
350            valueType=str)
351
352        self.color_attr_cb = OWGUI.comboBox(self.main_tab, self, 'color_attr', box='Point color',
353            tooltip='Attribute to use for point color',
354            callback=self.on_axis_change,
355            sendSelectedValue=1,
356            valueType=str)
357
358        # Additional point properties (labels, size, symbol).
359        additional_box = OWGUI.widgetBox(self.main_tab, 'Additional Point Properties')
360        self.size_attr_cb = OWGUI.comboBox(additional_box, self, 'size_attr', label='Point size:',
361            tooltip='Attribute to use for point size',
362            callback=self.on_axis_change,
363            indent=10,
364            emptyString='(Same size)',
365            sendSelectedValue=1,
366            valueType=str)
367
368        self.symbol_attr_cb = OWGUI.comboBox(additional_box, self, 'symbol_attr', label='Point symbol:',
369            tooltip='Attribute to use for point symbol',
370            callback=self.on_axis_change,
371            indent=10,
372            emptyString='(Same symbol)',
373            sendSelectedValue=1,
374            valueType=str)
375
376        self.label_attr_cb = OWGUI.comboBox(additional_box, self, 'label_attr', label='Point label:',
377            tooltip='Attribute to use for pointLabel',
378            callback=self.on_axis_change,
379            indent=10,
380            emptyString='(No labels)',
381            sendSelectedValue=1,
382            valueType=str)
383
384        self.plot = ScatterPlot(self)
385        self.vizrank = OWVizRank(self, self.signalManager, self.plot, orngVizRank.SCATTERPLOT3D, 'ScatterPlot3D')
386        self.optimization_dlg = self.vizrank
387
388        self.optimization_buttons = OWGUI.widgetBox(self.main_tab, 'Optimization dialogs', orientation='horizontal')
389        OWGUI.button(self.optimization_buttons, self, 'VizRank', callback=self.vizrank.reshow,
390            tooltip='Opens VizRank dialog, where you can search for interesting projections with different subsets of attributes',
391            debuggingEnabled=0)
392
393        box = OWGUI.widgetBox(self.settings_tab, 'Point properties')
394        ss = OWGUI.hSlider(box, self, 'plot.symbol_scale', label='Symbol scale',
395            minValue=1, maxValue=20,
396            tooltip='Scale symbol size',
397            callback=self.on_checkbox_update)
398        ss.setValue(4)
399
400        OWGUI.hSlider(box, self, 'plot.alpha_value', label='Transparency',
401            minValue=10, maxValue=255,
402            tooltip='Point transparency value',
403            callback=self.on_checkbox_update)
404        OWGUI.rubber(box)
405
406        box = OWGUI.widgetBox(self.settings_tab, 'Jittering Options')
407        self.jitter_size_combo = OWGUI.comboBox(box, self, 'plot.jitter_size', label='Jittering size (% of size)'+'  ',
408            orientation='horizontal',
409            callback=self.handleNewSignals,
410            items=self.jitter_sizes,
411            sendSelectedValue=1,
412            valueType=float)
413        OWGUI.checkBox(box, self, 'plot.jitter_continuous', 'Jitter continuous attributes',
414            callback=self.handleNewSignals,
415            tooltip='Does jittering apply also on continuous attributes?')
416
417        self.dark_theme = False
418
419        box = OWGUI.widgetBox(self.settings_tab, 'General settings')
420        OWGUI.checkBox(box, self, 'plot.show_x_axis_title',   'X axis title',   callback=self.on_checkbox_update)
421        OWGUI.checkBox(box, self, 'plot.show_y_axis_title',   'Y axis title',   callback=self.on_checkbox_update)
422        OWGUI.checkBox(box, self, 'plot.show_z_axis_title',   'Z axis title',   callback=self.on_checkbox_update)
423        OWGUI.checkBox(box, self, 'plot.show_legend',         'Show legend',    callback=self.on_checkbox_update)
424        OWGUI.checkBox(box, self, 'plot.use_ortho',           'Use ortho',      callback=self.on_checkbox_update)
425        OWGUI.checkBox(box, self, 'plot.use_2d_symbols',      '2D symbols',     callback=self.update_plot)
426        OWGUI.checkBox(box, self, 'dark_theme',               'Dark theme',     callback=self.on_theme_change)
427        OWGUI.checkBox(box, self, 'plot.show_grid',           'Show grid',      callback=self.on_checkbox_update)
428        OWGUI.checkBox(box, self, 'plot.show_axes',           'Show axes',      callback=self.on_checkbox_update)
429        OWGUI.checkBox(box, self, 'plot.show_chassis',        'Show chassis',   callback=self.on_checkbox_update)
430        OWGUI.checkBox(box, self, 'plot.hide_outside',        'Hide outside',   callback=self.on_checkbox_update)
431        OWGUI.rubber(box)
432
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)
441
442        self.tooltip_kind = TooltipKind.NONE
443        box = OWGUI.widgetBox(self.settings_tab, 'Tooltips Settings')
444        OWGUI.comboBox(box, self, 'tooltip_kind', items = [
445            'Don\'t Show Tooltips', 'Show Visible Attributes', 'Show All Attributes'])
446
447        self.plot.mouseover_callback = self.mouseover_callback
448
449        self.main_tab.layout().addStretch(100)
450        self.settings_tab.layout().addStretch(100)
451
452        self.mainArea.layout().addWidget(self.plot)
453        self.connect(self.graphButton, SIGNAL('clicked()'), self.plot.save_to_file)
454
455        self.loadSettings()
456        self.plot.update_camera()
457
458        self._set_behavior_replace()
459
460        self.data = None
461        self.subset_data = None
462        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)
476
477    def mouseover_callback(self, index):
478        if self.tooltip_kind == TooltipKind.VISIBLE:
479            self.plot.show_tooltip(self.get_example_tooltip(self.data[index], self.shown_attr_indices))
480        elif self.tooltip_kind == TooltipKind.ALL:
481            self.plot.show_tooltip(self.get_example_tooltip(self.data[index]))
482
483    def get_example_tooltip(self, example, indices=None, max_indices=20):
484        if indices and type(indices[0]) == str:
485            indices = [self.plot.attribute_name_index[i] for i in indices]
486        if not indices:
487            indices = range(len(self.data.domain.attributes))
488
489        if example.domain.classVar:
490            classIndex = self.plot.attribute_name_index[example.domain.classVar.name]
491            while classIndex in indices:
492                indices.remove(classIndex)
493
494        text = '<b>Attributes:</b><br>'
495        for index in indices[:max_indices]:
496            attr = self.plot.data_domain[index].name
497            if attr not in example.domain:  text += '&nbsp;'*4 + '%s = ?<br>' % (attr)
498            elif example[attr].isSpecial(): text += '&nbsp;'*4 + '%s = ?<br>' % (attr)
499            else:                           text += '&nbsp;'*4 + '%s = %s<br>' % (attr, str(example[attr]))
500
501        if len(indices) > max_indices:
502            text += '&nbsp;'*4 + ' ... <br>'
503
504        if example.domain.classVar:
505            text = text[:-4]
506            text += '<hr><b>Class:</b><br>'
507            if example.getclass().isSpecial(): text += '&nbsp;'*4 + '%s = ?<br>' % (example.domain.classVar.name)
508            else:                              text += '&nbsp;'*4 + '%s = %s<br>' % (example.domain.classVar.name, str(example.getclass()))
509
510        if len(example.domain.getmetas()) != 0:
511            text = text[:-4]
512            text += '<hr><b>Meta attributes:</b><br>'
513            for key in example.domain.getmetas():
514                try: text += '&nbsp;'*4 + '%s = %s<br>' % (example.domain[key].name, str(example[key]))
515                except: pass
516        return text[:-4]
517
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)
553
554    def set_data(self, data=None):
555        self.closeContext()
556        self.vizrank.clearResults()
557        same_domain = self.data and data and\
558            data.domain.checksum() == self.data.domain.checksum()
559        self.data = data
560        if not same_domain:
561            self.init_attr_values()
562        self.openContext('', data)
563
564    def init_attr_values(self):
565        self.x_attr_cb.clear()
566        self.y_attr_cb.clear()
567        self.z_attr_cb.clear()
568        self.color_attr_cb.clear()
569        self.size_attr_cb.clear()
570        self.symbol_attr_cb.clear()
571        self.label_attr_cb.clear()
572
573        self.discrete_attrs = {}
574
575        if not self.data:
576            return
577
578        self.color_attr_cb.addItem('(Same color)')
579        self.label_attr_cb.addItem('(No labels)')
580        self.symbol_attr_cb.addItem('(Same symbol)')
581        self.size_attr_cb.addItem('(Same size)')
582
583        icons = OWGUI.getAttributeIcons() 
584        for metavar in [self.data.domain.getmeta(mykey) for mykey in self.data.domain.getmetas().keys()]:
585            self.label_attr_cb.addItem(icons[metavar.varType], metavar.name)
586
587        for attr in self.data.domain:
588            if attr.varType in [Discrete, Continuous]:
589                self.x_attr_cb.addItem(icons[attr.varType], attr.name)
590                self.y_attr_cb.addItem(icons[attr.varType], attr.name)
591                self.z_attr_cb.addItem(icons[attr.varType], attr.name)
592                self.color_attr_cb.addItem(icons[attr.varType], attr.name)
593                self.size_attr_cb.addItem(icons[attr.varType], attr.name)
594            if attr.varType == Discrete: 
595                self.symbol_attr_cb.addItem(icons[attr.varType], attr.name)
596            self.label_attr_cb.addItem(icons[attr.varType], attr.name)
597
598        self.x_attr = str(self.x_attr_cb.itemText(0))
599        if self.y_attr_cb.count() > 1:
600            self.y_attr = str(self.y_attr_cb.itemText(1))
601        else:
602            self.y_attr = str(self.y_attr_cb.itemText(0))
603
604        if self.z_attr_cb.count() > 2:
605            self.z_attr = str(self.z_attr_cb.itemText(2))
606        else:
607            self.z_attr = str(self.z_attr_cb.itemText(0))
608
609        if self.data.domain.classVar and self.data.domain.classVar.varType in [Discrete, Continuous]:
610            self.color_attr = self.data.domain.classVar.name
611        else:
612            self.color_attr = ''
613
614        self.symbol_attr = self.size_attr = self.label_attr = ''
615        self.shown_attr_indices = [self.x_attr, self.y_attr, self.z_attr, self.color_attr]
616
617    def set_subset_data(self, data=None):
618        self.subset_data = data
619
620    def handleNewSignals(self):
621        self.vizrank.resetDialog()
622        self.plot.set_data(self.data, self.subset_data)
623        self.update_plot()
624        self.send_selection()
625
626    def saveSettings(self):
627        OWWidget.saveSettings(self)
628
629    def sendReport(self):
630        self.startReport('%s [%s - %s - %s]' % (self.windowTitle(), self.x_attr, self.y_attr, self.z_attr))
631        self.reportSettings('Visualized attributes',
632                            [('X', self.x_attr),
633                             ('Y', self.y_attr),
634                             ('Z', self.z_attr),
635                             self.color_attr and ('Color', self.color_attr),
636                             self.label_attr and ('Label', self.label_attr),
637                             self.symbol_attr and ('Symbol', self.symbol_attr),
638                             self.size_attr  and ('Size', self.size_attr)])
639        self.reportSettings('Settings',
640                            [('Symbol size', self.plot.symbol_scale),
641                             ('Transparency', self.plot.alpha_value),
642                             ('Jittering', self.jitter_size),
643                             ('Jitter continuous attributes', OWGUI.YesNo[self.jitter_continuous])
644                             ])
645        self.reportSection('Plot')
646        self.reportImage(self.plot.save_to_file_direct, QSize(400, 400))
647
648    def send_selection(self):
649        if self.data == None:
650            return
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))
655        self.send('Selected Examples', selected)
656        self.send('Unselected Examples', unselected)
657
658    def on_axis_change(self):
659        if self.data is not None:
660            self.update_plot()
661
662    def on_theme_change(self):
663        if self.dark_theme:
664            self.plot.theme = DarkTheme()
665        else:
666            self.plot.theme = LightTheme()
667
668    def on_checkbox_update(self):
669        self.plot.updateGL()
670
671    def update_plot(self):
672        if self.data is None:
673            return
674
675        self.plot.update_data(self.x_attr, self.y_attr, self.z_attr,
676                              self.color_attr, self.symbol_attr, self.size_attr,
677                              self.label_attr)
678
679    def showSelectedAttributes(self):
680        val = self.vizrank.getSelectedProjection()
681        if not val: return
682        if self.data.domain.classVar:
683            self.attr_color = self.data.domain.classVar.name
684        if not self.plot.have_data:
685            return
686        attr_list = val[3]
687        if attr_list and len(attr_list) == 3:
688            self.x_attr = attr_list[0]
689            self.y_attr = attr_list[1]
690            self.z_attr = attr_list[2]
691
692        self.update_plot()
693
694if __name__ == '__main__':
695    app = QApplication(sys.argv)
696    w = OWScatterPlot3D()
697    data = orange.ExampleTable('../../doc/datasets/iris')
698    w.set_data(data)
699    w.handleNewSignals()
700    w.show()
701    app.exec_()
Note: See TracBrowser for help on using the repository browser.