source: orange/Orange/OrangeWidgets/Visualize/OWParallelCoordinates.py @ 9671:a7b056375472

Revision 9671:a7b056375472, 31.6 KB checked in by anze <anze.staric@…>, 2 years ago (diff)

Moved orange to Orange (part 2)

Line 
1"""
2<name>Parallel Coordinates</name>
3<description>Parallel coordinates (multiattribute) visualization.</description>
4<contact>Gregor Leban (gregor.leban@fri.uni-lj.si)</contact>
5<icon>icons/ParallelCoordinates.png</icon>
6<priority>3200</priority>
7"""
8# ParallelCoordinates.py
9#
10# Show data using parallel coordinates visualization method
11#
12from OWVisWidget import *
13from OWParallelGraph import *
14import OWToolbars, OWGUI, OWColorPalette, orngVisFuncts
15from sys import getrecursionlimit, setrecursionlimit
16
17###########################################################################################
18##### WIDGET : Parallel coordinates visualization
19###########################################################################################
20class OWParallelCoordinates(OWVisWidget):
21    settingsList = ["graph.jitterSize", "graph.showDistributions",
22                    "graph.showAttrValues", "graph.useAntialiasing",
23                    "graph.useSplines", "graph.alphaValue", "graph.alphaValue2", "graph.enabledLegend", "autoSendSelection",
24                    "toolbarSelection", "graph.showStatistics", "colorSettings", "selectedSchemaIndex", "showAllAttributes"]
25    jitterSizeNums = [0, 2,  5,  10, 15, 20, 30]
26    contextHandlers = {"": DomainContextHandler("", [ContextField("shownAttributes", DomainContextHandler.RequiredList, selected="selectedShown", reservoir="hiddenAttributes")])}
27
28    def __init__(self,parent=None, signalManager = None):
29        OWWidget.__init__(self, parent, signalManager, "Parallel Coordinates", TRUE)
30
31        #add a graph widget
32        self.graph = OWParallelGraph(self, self.mainArea)
33        self.mainArea.layout().addWidget(self.graph)
34
35        self.showAllAttributes = 0
36
37        self.inputs = [("Data", ExampleTable, self.setData, Default), ("Data Subset", ExampleTable, self.setSubsetData), ("Features", AttributeList, self.setShownAttributes)]
38        self.outputs = [("Selected Data", ExampleTable), ("Other Data", ExampleTable), ("Features", AttributeList)]
39
40        #set default settings
41        self.data = None
42        self.subsetData = None
43        self.autoSendSelection = 1
44        self.attrDiscOrder = "Unordered"
45        self.attrContOrder = "Unordered"
46        self.projections = None
47        self.correlationDict = {}
48        self.middleLabels = "Correlations"
49        self.attributeSelectionList = None
50        self.toolbarSelection = 0
51        self.colorSettings = None
52        self.selectedSchemaIndex = 0
53
54        self.graph.jitterSize = 10
55        self.graph.showDistributions = 1
56        self.graph.showStatistics = 0
57        self.graph.showAttrValues = 1
58        self.graph.useSplines = 0
59        self.graph.enabledLegend = 1
60
61        #load settings
62        self.loadSettings()
63
64        #GUI
65        self.tabs = OWGUI.tabWidget(self.controlArea)
66        self.GeneralTab = OWGUI.createTabPage(self.tabs, "Main")
67        self.SettingsTab = OWGUI.createTabPage(self.tabs, "Settings")
68
69        self.createShowHiddenLists(self.GeneralTab, callback = self.updateGraph)
70        self.connect(self.shownAttribsLB, SIGNAL('itemDoubleClicked(QListWidgetItem*)'), self.flipAttribute)
71
72        self.optimizationDlg = ParallelOptimization(self, signalManager = self.signalManager)
73        self.optimizationDlgButton = OWGUI.button(self.GeneralTab, self, "Optimization Dialog", callback = self.optimizationDlg.reshow, debuggingEnabled = 0)
74
75        self.zoomSelectToolbar = OWToolbars.ZoomSelectToolbar(self, self.GeneralTab, self.graph, self.autoSendSelection, buttons = (1, 2, 0, 7, 8))
76        self.connect(self.zoomSelectToolbar.buttonSendSelections, SIGNAL("clicked()"), self.sendSelections)
77
78        #connect controls to appropriate functions
79        self.connect(self.graphButton, SIGNAL("clicked()"), self.graph.saveToFile)
80
81        # ####################################
82        # SETTINGS functionality
83        box = OWGUI.widgetBox(self.SettingsTab, "Transparency")
84        OWGUI.hSlider(box, self, 'graph.alphaValue', label = "Examples: ", minValue=0, maxValue=255, step=10, callback = self.updateGraph, tooltip = "Alpha value used for drawing example lines")
85        OWGUI.hSlider(box, self, 'graph.alphaValue2', label = "Rest:     ", minValue=0, maxValue=255, step=10, callback = self.updateGraph, tooltip = "Alpha value used to draw statistics, example subsets, ...")
86
87        box = OWGUI.widgetBox(self.SettingsTab, "Jittering Options")
88        OWGUI.comboBox(box, self, "graph.jitterSize", label = 'Jittering size (% of size):  ', orientation='horizontal', callback = self.setJitteringSize, items = self.jitterSizeNums, sendSelectedValue = 1, valueType = float)
89
90        # visual settings
91        box = OWGUI.widgetBox(self.SettingsTab, "Visual Settings")
92
93        OWGUI.checkBox(box, self, 'graph.showAttrValues', 'Show attribute values', callback = self.updateGraph)
94        OWGUI.checkBox(box, self, 'graph.useAntialiasing', 'Use antialiasing', callback = self.updateGraph)
95        OWGUI.checkBox(box, self, 'graph.useSplines', 'Show splines', callback = self.updateGraph, tooltip  = "Show lines using splines")
96        OWGUI.checkBox(box, self, 'graph.enabledLegend', 'Show legend', callback = self.updateGraph)
97
98        box = OWGUI.widgetBox(self.SettingsTab, "Axis Distance")
99        resizeColsBox = OWGUI.widgetBox(box, 0, "horizontal", 0)
100        OWGUI.label(resizeColsBox, self, "Increase/decrease distance: ")
101        b = OWGUI.toolButton(resizeColsBox, self, "+", callback=self.increaseAxesDistance, tooltip = "Increase the distance between the axes", width=30, height = 20)
102        b = OWGUI.toolButton(resizeColsBox, self, "-", callback=self.decreaseAxesDistance, tooltip = "Decrease the distance between the axes", width=30, height = 20)
103        OWGUI.rubber(resizeColsBox)
104        OWGUI.checkBox(box, self, "graph.autoUpdateAxes", "Auto scale X axis", tooltip = "Auto scale X axis to show all visualized attributes", callback = self.updateGraph)
105
106        box = OWGUI.widgetBox(self.SettingsTab, "Statistical Information")
107        OWGUI.comboBox(box, self, "graph.showStatistics", label = "Statistics: ", orientation = "horizontal", labelWidth=90, items = ["No statistics", "Means, deviations", "Median, quartiles"], callback = self.updateGraph, sendSelectedValue = 0, valueType = int)
108        OWGUI.comboBox(box, self, "middleLabels", label = "Middle labels: ", orientation="horizontal", labelWidth=90, items = ["No labels", "Correlations", "VizRank"], callback = self.updateGraph, tooltip = "The information do you wish to view on top in the middle of coordinate axes", sendSelectedValue = 1, valueType = str)
109        OWGUI.checkBox(box, self, 'graph.showDistributions', 'Show distributions', callback = self.updateGraph, tooltip = "Show bars with distribution of class values (only for discrete attributes)")
110
111        box = OWGUI.widgetBox(self.SettingsTab, "Colors", orientation = "horizontal")
112        OWGUI.button(box, self, "Set colors", self.setColors, tooltip = "Set the canvas background color and color palette for coloring continuous variables", debuggingEnabled = 0)
113
114        box = OWGUI.widgetBox(self.SettingsTab, "Auto Send Selected Data When...")
115        OWGUI.checkBox(box, self, 'autoSendSelection', 'Adding/Removing selection areas', callback = self.selectionChanged, tooltip = "Send selected data whenever a selection area is added or removed")
116        OWGUI.checkBox(box, self, 'graph.sendSelectionOnUpdate', 'Moving/Resizing selection areas', tooltip = "Send selected data when a user moves or resizes an existing selection area")
117        self.graph.autoSendSelectionCallback = self.selectionChanged
118
119        self.SettingsTab.layout().addStretch(100)
120        self.icons = self.createAttributeIconDict()
121
122        dlg = self.createColorDialog()
123        self.graph.contPalette = dlg.getContinuousPalette("contPalette")
124        self.graph.discPalette = dlg.getDiscretePalette("discPalette")
125        self.graph.setCanvasBackground(dlg.getColor("Canvas"))
126        apply([self.zoomSelectToolbar.actionZooming, self.zoomSelectToolbar.actionRectangleSelection, self.zoomSelectToolbar.actionPolygonSelection][self.toolbarSelection], [])
127        self.cbShowAllAttributes()
128
129        self.resize(900, 700)
130
131    def flipAttribute(self, item):
132        if self.graph.flipAttribute(str(item.text())):
133            self.updateGraph()
134            self.information(0)
135        else:
136            self.information(0, "Didn't flip the attribute. To flip a continuous attribute uncheck 'Global value scaling' checkbox.")
137
138    def updateGraph(self, *args):
139        attrs = self.getShownAttributeList()
140        self.graph.updateData(attrs, self.buildMidLabels(attrs))
141
142
143    def increaseAxesDistance(self):
144        m = self.graph.axisScaleDiv(QwtPlot.xBottom).interval().minValue()
145        M = self.graph.axisScaleDiv(QwtPlot.xBottom).interval().maxValue()
146        if (M-m) == 0:
147            return      # we have not yet updated the axes (self.graph.updateAxes())
148        self.graph.setAxisScale(QwtPlot.xBottom, m, M - (M-m)/10., 1)
149        self.graph.replot()
150
151    def decreaseAxesDistance(self):
152        m = self.graph.axisScaleDiv(QwtPlot.xBottom).interval().minValue()
153        M = self.graph.axisScaleDiv(QwtPlot.xBottom).interval().maxValue()
154        if (M-m) == 0:
155            return      # we have not yet updated the axes (self.graph.updateAxes())
156
157        self.graph.setAxisScale(QwtPlot.xBottom, m, min(len(self.graph.visualizedAttributes)-1, M + (M-m)/10.), 1)
158        self.graph.replot()
159
160
161    # build a list of strings that will be shown in the middle of the parallel axis
162    def buildMidLabels(self, attrs):
163        labels = []
164        if self.middleLabels == "No labels" or not self.graph.haveData: return None
165        elif self.middleLabels == "Correlations":
166            for i in range(len(attrs)-1):
167                corr = None
168                if self.correlationDict.has_key((attrs[i], attrs[i+1])):   corr = self.correlationDict[(attrs[i], attrs[i+1])]
169                elif self.correlationDict.has_key((attrs[i+1], attrs[i])): corr = self.correlationDict[(attrs[i+1], attrs[i])]
170                else:
171                    try:
172                        corr = orngVisFuncts.computeCorrelation(self.graph.rawData, attrs[i], attrs[i+1])
173                    except:
174                        corr = None
175                    self.correlationDict[(attrs[i], attrs[i+1])] = corr
176                if corr and (self.graph.attributeFlipInfo.get(attrs[i], 0) != self.graph.attributeFlipInfo.get(attrs[i+1], 0)): corr = -corr
177                if corr: labels.append("%2.3f" % (corr))
178                else: labels.append("")
179        elif self.middleLabels == "VizRank":
180            for i in range(len(attrs)-1):
181                val = self.optimizationDlg.getVizRankVal(attrs[i], attrs[i+1])
182                if val: labels.append("%2.2f%%" % (val))
183                else: labels.append("")
184        return labels
185
186
187    # ------------- SIGNALS --------------------------
188    # receive new data and update all fields
189    def setData(self, data):
190        if data and (len(data) == 0 or len(data.domain) == 0):
191            data = None
192        if self.data != None and data != None and self.data.checksum() == data.checksum():
193            return    # check if the new data set is the same as the old one
194
195        self.closeContext()
196        sameDomain = self.data and data and data.domain.checksum() == self.data.domain.checksum() # preserve attribute choice if the domain is the same
197        self.projections = None
198        self.correlationDict = {}
199        self.data = data
200        self.optimizationDlg.clearResults()
201        if not sameDomain:
202            self.setShownAttributeList(self.attributeSelectionList)
203        self.openContext("", self.data)
204        self.resetAttrManipulation()
205
206
207    def setSubsetData(self, subData):
208        self.subsetData = subData
209
210
211    # attribute selection signal - list of attributes to show
212    def setShownAttributes(self, attributeSelectionList):
213        self.attributeSelectionList = attributeSelectionList
214
215
216    # this is called by OWBaseWidget after setData and setSubsetData are called. this way the graph is updated only once
217    def handleNewSignals(self):
218        self.graph.setData(self.data, self.subsetData)
219        if self.attributeSelectionList and 0 not in [self.graph.attributeNameIndex.has_key(attr) for attr in self.attributeSelectionList]:
220            self.setShownAttributeList(self.attributeSelectionList)
221        else:
222            self.setShownAttributeList()
223        self.attributeSelectionList = None
224        self.updateGraph()
225        self.sendSelections()
226
227
228    def sendShownAttributes(self, attrList = None):
229        if attrList == None:
230            attrList = self.getShownAttributeList()
231        self.send("Features", attrList)
232
233    def selectionChanged(self):
234        self.zoomSelectToolbar.buttonSendSelections.setEnabled(not self.autoSendSelection)
235        if self.autoSendSelection:
236            self.sendSelections()
237
238    # send signals with selected and unselected examples as two datasets
239    def sendSelections(self):
240        (selected, unselected) = self.graph.getSelectionsAsExampleTables()
241        self.send("Selected Data", selected)
242        self.send("Other Data", unselected)
243
244
245    # jittering options
246    def setJitteringSize(self):
247        self.graph.rescaleData()
248        self.updateGraph()
249
250    def setColors(self):
251        dlg = self.createColorDialog()
252        if dlg.exec_():
253            self.colorSettings = dlg.getColorSchemas()
254            self.selectedSchemaIndex = dlg.selectedSchemaIndex
255            self.graph.contPalette = dlg.getContinuousPalette("contPalette")
256            self.graph.discPalette = dlg.getDiscretePalette("discPalette")
257            self.graph.setCanvasBackground(dlg.getColor("Canvas"))
258            self.updateGraph()
259
260    def createColorDialog(self):
261        c = OWColorPalette.ColorPaletteDlg(self, "Color Palette")
262        c.createDiscretePalette("discPalette", "Discrete Palette")
263        c.createContinuousPalette("contPalette", "Continuous Palette")
264        box = c.createBox("otherColors", "Other Colors")
265        c.createColorButton(box, "Canvas", "Canvas color", QColor(Qt.white))
266        c.setColorSchemas(self.colorSettings, self.selectedSchemaIndex)
267        return c
268
269    def saveSettings(self):
270        OWWidget.saveSettings(self)
271        self.optimizationDlg.saveSettings()
272
273    def closeEvent(self, ce):
274        self.optimizationDlg.hide()
275        OWWidget.closeEvent(self, ce)
276
277    def sendReport(self):
278        self.reportImage(self.graph.saveToFileDirect, QSize(500, 500))
279
280
281
282CORRELATION = 0
283VIZRANK = 1
284#
285# Find attribute subsets that are interesting to visualize using parallel coordinates
286class ParallelOptimization(OWWidget):
287    resultListList = [50, 100, 200, 500, 1000]
288    qualityMeasure =  ["Classification accuracy", "Average correct", "Brier score"]
289    testingMethod = ["Leave one out", "10-fold cross validation", "Test on learning set"]
290
291    settingsList = ["attributeCount", "fileBuffer", "lastSaveDirName", "optimizationMeasure",
292                    "numberOfAttributes", "orderAllAttributes", "optimizationMeasure"]
293
294    def __init__(self, parallelWidget, parent=None, signalManager = None):
295        OWWidget.__init__(self, parent, signalManager, "Parallel Optimization Dialog", FALSE)
296        self.setCaption("Parallel Optimization Dialog")
297        self.parallelWidget = parallelWidget
298
299        self.optimizationMeasure = 0
300        self.attributeCount = 5
301        self.numberOfAttributes = 6
302        self.fileName = ""
303        self.lastSaveDirName = os.getcwd() + "/"
304        self.fileBuffer = []
305        self.projections = []
306        self.allResults = []
307        self.canOptimize = 0
308        self.orderAllAttributes = 1 # do we wish to order all attributes or find just an interesting subset
309        self.worstVal = -1  # used in heuristics to stop the search in uninteresting parts of the graph
310
311        self.loadSettings()
312
313        self.measureBox = OWGUI.radioButtonsInBox(self.controlArea, self, "optimizationMeasure", ["Correlation", "VizRank"], box = "Select optimization measure", callback = self.updateGUI)
314        self.vizrankSettingsBox = OWGUI.widgetBox(self.controlArea, "VizRank settings")
315        self.optimizeBox = OWGUI.widgetBox(self.controlArea, "Optimize")
316        self.manageBox = OWGUI.widgetBox(self.controlArea, "Manage results")
317        self.resultsBox = OWGUI.widgetBox(self.mainArea, "Results")
318
319        self.resultList = OWGUI.listBox(self.resultsBox, self)
320        self.resultList.setMinimumSize(200,200)
321        self.connect(self.resultList, SIGNAL("itemSelectionChanged()"), self.showSelectedAttributes)
322
323        # remove non-existing files
324        names = []
325        for i in range(len(self.fileBuffer)-1, -1, -1):
326            (short, longName) = self.fileBuffer[i]
327            if not os.path.exists(longName):
328                self.fileBuffer.remove((short, longName))
329            else: names.append(short)
330        names.append("(None)")
331        self.fileName = "(None)"
332
333        self.hbox1 = OWGUI.widgetBox(self.vizrankSettingsBox, "VizRank projections file", orientation = "horizontal")
334        self.vizrankFileCombo = OWGUI.comboBox(self.hbox1, self, "fileName", items = names, tooltip = "File that contains information about interestingness of scatterplots \ngenerated by VizRank method in scatterplot widget", callback = self.changeProjectionFile, sendSelectedValue = 1, valueType = str)
335        self.browseButton = OWGUI.button(self.hbox1, self, "...", callback = self.loadProjections)
336        self.browseButton.setMaximumWidth(20)
337
338        self.resultsInfoBox = OWGUI.widgetBox(self.vizrankSettingsBox, "VizRank parameters")
339        self.kNeighborsLabel = OWGUI.widgetLabel(self.resultsInfoBox, "Number of neighbors (k):")
340        self.percentDataUsedLabel = OWGUI.widgetLabel(self.resultsInfoBox, "Percent of data used:")
341        self.testingMethodLabel = OWGUI.widgetLabel(self.resultsInfoBox, "Testing method used:")
342        self.qualityMeasureLabel = OWGUI.widgetLabel(self.resultsInfoBox, "Quality measure used:")
343
344        #self.numberOfAttributesCombo = OWGUI.comboBoxWithCaption(self.optimizeBox, self, "numberOfAttributes", "Number of visualized attributes: ", tooltip = "Projections with this number of attributes will be evaluated", items = [x for x in range(3, 12)], sendSelectedValue = 1, valueType = int)
345        self.allAttributesRadio = QRadioButton("Order all attributes", self.optimizeBox)
346        self.optimizeBox.layout().addWidget(self.allAttributesRadio)
347        self.connect(self.allAttributesRadio, SIGNAL("clicked()"), self.setAllAttributeRadio)
348        box = OWGUI.widgetBox(self.optimizeBox, orientation = "horizontal")
349        self.subsetAttributeRadio = QRadioButton("Find subsets of", box)
350#        self.optimizeBox.layout().addWidget(self.subsetAttributeRadio)
351        box.layout().addWidget(self.subsetAttributeRadio)
352        self.connect(self.subsetAttributeRadio, SIGNAL("clicked()"), self.setSubsetAttributeRadio)
353        self.subsetAttributeEdit = OWGUI.lineEdit(box, self, "numberOfAttributes", valueType = int)
354        self.subsetAttributeEdit.setMaximumWidth(30)
355        label  = OWGUI.widgetLabel(box, "attributes")
356
357        self.startOptimizationButton = OWGUI.button(self.optimizeBox, self, "Start Optimization", callback = self.startOptimization)
358        f = self.startOptimizationButton.font()
359        f.setBold(1)
360        self.startOptimizationButton.setFont(f)
361        self.stopOptimizationButton = OWGUI.button(self.optimizeBox, self, "Stop Evaluation", callback = self.stopOptimizationClick)
362        self.stopOptimizationButton.setFont(f)
363        self.stopOptimizationButton.hide()
364        self.connect(self.stopOptimizationButton , SIGNAL("clicked()"), self.stopOptimizationClick)
365
366
367        self.clearButton = OWGUI.button(self.manageBox, self, "Clear Results", self.clearResults)
368        self.loadButton  = OWGUI.button(self.manageBox, self, "Load", self.loadResults)
369        self.saveButton  = OWGUI.button(self.manageBox, self, "Save", self.saveResults)
370        self.closeButton = OWGUI.button(self.manageBox, self, "Close Dialog", self.hide)
371
372        self.changeProjectionFile()
373        self.updateGUI()
374        if self.orderAllAttributes: self.setAllAttributeRadio()
375        else:                       self.setSubsetAttributeRadio()
376
377    def updateGUI(self):
378        self.vizrankSettingsBox.setEnabled(self.optimizationMeasure)
379
380    # if user clicks new attribute list in optimization dialog, we update shown attributes
381    def showSelectedAttributes(self):
382        attrList = self.getSelectedAttributes()
383        if not attrList: return
384
385        self.parallelWidget.setShownAttributeList(attrList)
386        self.parallelWidget.graph.removeAllSelections()
387
388        self.parallelWidget.middleLabels = (self.optimizationMeasure == VIZRANK and "VizRank") or "Correlations"
389        self.parallelWidget.updateGraph()
390
391    def setAllAttributeRadio(self):
392        self.orderAllAttributes = 1
393        self.allAttributesRadio.setChecked(1)
394        self.subsetAttributeRadio.setChecked(0)
395        self.subsetAttributeEdit.setEnabled(0)
396
397    def setSubsetAttributeRadio(self):
398        self.orderAllAttributes = 0
399        self.allAttributesRadio.setChecked(0)
400        self.subsetAttributeRadio.setChecked(1)
401        self.subsetAttributeEdit.setEnabled(1)
402
403    # return list of selected attributes
404    def getSelectedAttributes(self):
405        if self.resultList.count() == 0 or self.allResults == []:
406            return None
407        return self.allResults[self.resultList.currentRow()][1]
408
409    # called when optimization is in progress
410    def canContinueOptimization(self):
411        return self.canOptimize
412
413    def getWorstVal(self):
414        return self.worstVal
415
416    def stopOptimizationClick(self):
417        self.canOptimize = 0
418
419    # get vizrank value for attributes attr1 and attr2
420    def getVizRankVal(self, attr1, attr2):
421        if not self.projections: return None
422        for (val, [a1, a2]) in self.projections:
423            if (attr1 == a1 and attr2 == a2) or (attr1 == a2 and attr2 == a1): return val
424        return None
425
426    def changeProjectionFile(self):
427        for (short, long) in self.fileBuffer:
428            if short == self.fileName:
429                self.loadProjections(long)
430                return
431
432    # load projections from a file
433    def loadProjections(self, name = None):
434        self.projections = []
435        self.kNeighborsLabel.setText("Number of neighbors (k): " )
436        self.percentDataUsedLabel.setText("Percent of data used:" )
437        self.testingMethodLabel.setText("Testing method used:" )
438        self.qualityMeasureLabel.setText("Quality measure used:" )
439
440        if name == None:
441            name = str(QFileDialog.getOpenFileName(self, "Open Projections",  self.lastSaveDirName, "Interesting projections (*.proj)"))
442            if name == "": return
443
444        dirName, shortFileName = os.path.split(name)
445        self.lastSaveDirName = dirName
446
447        file = open(name, "rt")
448        settings = eval(file.readline()[:-1])
449        if settings.has_key("parentName") and settings["parentName"].lower() != "scatterplot":
450            QMessageBox.critical( None, "Optimization Dialog", 'Unable to load projection file. Only projection file generated by scatterplot is compatible. \nThis file was created using %s method'%(settings["parentName"]), QMessageBox.Ok)
451            file.close()
452            return
453
454        if type(eval(file.readline()[:-1])) != list:    # second line must contain a list of classes that we tried to separate
455            QMessageBox.critical(None,'Old version of projection file','This file was saved with an older version of k-NN Optimization Dialog. The new version of dialog offers \nsome additional functionality and therefore you have to compute the projection quality again.',QMessageBox.Ok)
456            file.close()
457            return
458
459        try:
460            line = file.readline()[:-1]; ind = 0    # first line is a settings line
461            (acc, other_results, lenTable, attrList, tryIndex, strList) = eval(line)
462            if len(attrList) != 2:
463                QMessageBox.information(self, "Incorrect file", "File should contain projections with 2 attributes!", QMessageBox.Ok)
464                file.close()
465                return
466
467            while (line != ""):
468                (acc, other_results, lenTable, attrList, tryIndex, strList) = eval(line)
469                self.projections += [(acc, attrList)]
470                line = file.readline()[:-1]
471        except:
472            self.projections = []
473            file.close()
474            QMessageBox.information(self, "Incorrect file", "Incorrect file format!", QMessageBox.Ok)
475            return
476
477        file.close()
478
479        if (shortFileName, name) in self.fileBuffer:
480            self.fileBuffer.remove((shortFileName, name))
481
482        self.fileBuffer.insert(0, (shortFileName, name))
483
484
485        if len(self.fileBuffer) > 10:
486            self.fileBuffer.remove(self.fileBuffer[-1])
487
488        self.vizrankFileCombo.clear()
489        for i in range(len(self.fileBuffer)):
490            self.vizrankFileCombo.addItem(self.fileBuffer[i][0])
491        self.fileName = shortFileName
492
493        self.kNeighborsLabel.setText("Number of neighbors (k): " + str(settings["kValue"]))
494        self.percentDataUsedLabel.setText("Percent of data used: " + "%d %%" % (settings["percentDataUsed"]))
495        self.testingMethodLabel.setText("Testing method used: " + self.testingMethod[settings["testingMethod"]])
496        self.qualityMeasureLabel.setText("Quality measure used: " + self.qualityMeasure[settings["qualityMeasure"]])
497
498
499    def addProjection(self, val, attrList):
500        index = self.findTargetIndex(val)
501        self.allResults.insert(index, (val, attrList))
502        self.resultList.insertItem(index, "%.3f - %s" % (val, str(attrList)))
503
504
505    def findTargetIndex(self, accuracy):
506        # use bisection to find correct index
507        top = 0; bottom = len(self.allResults)
508
509        while (bottom-top) > 1:
510            mid  = (bottom + top)/2
511            if max(accuracy, self.allResults[mid][0]) == accuracy: bottom = mid
512            else: top = mid
513
514        if len(self.allResults) == 0: return 0
515        if max(accuracy, self.allResults[top][0]) == accuracy:
516            return top
517        else:
518            return bottom
519
520
521    def startOptimization(self):
522        self.clearResults()
523        if self.parallelWidget.data == None: return
524
525        if self.optimizationMeasure == VIZRANK and self.fileName == "":
526            QMessageBox.information(self, "No projection file", "If you wish to optimize using VizRank you first have to load a projection file \ncreated by VizRank using Scatterplot widget.", QMessageBox.Ok)
527            return
528        if self.parallelWidget.data == None:
529            QMessageBox.information(self, "Missing data set", "A data set has to be loaded in order to perform optimization.", QMessageBox.Ok)
530            return
531
532        attrInfo = []
533        self.progressBarInit()
534        if self.optimizationMeasure == CORRELATION:
535            attrList = [attr.name for attr in self.parallelWidget.data.domain.attributes]
536            self.startOptimizationButton.hide()
537            self.stopOptimizationButton.show()
538            self.canOptimize = 1
539            class StopOptimizationException(Exception):
540                pass
541            def progressSetWithStop(value):
542                if not self.canContinueOptimization():
543                    raise StopOptimizationException()
544                else:
545                    self.progressBarSet(value * 0.9)
546            try: 
547                attrInfo = orngVisFuncts.computeCorrelationBetweenAttributes(self.parallelWidget.data, attrList, progressCallback=progressSetWithStop)
548            except StopOptimizationException:
549                attrInfo = []
550                self.startOptimizationButton.show()
551                self.stopOptimizationButton.hide()
552               
553#            self.progressBarFinished()
554            #attrInfo = orngVisFuncts.computeCorrelationInsideClassesBetweenAttributes(self.parallelWidget.data, attrList)
555        elif self.optimizationMeasure == VIZRANK:
556            for (val, [a1, a2]) in self.projections:
557                attrInfo.append((val, a1, a2))
558
559            # check if all attributes in loaded projection file are actually present in this data set
560            attrs = [attr.name for attr in self.parallelWidget.data.domain.attributes]
561            for (v, a1, a2) in attrInfo:
562                if a1 not in attrs:
563                    print "attribute " + a1 + " was not found in the data set. You probably loaded wrong file with VizRank projections."
564                    return
565                if a2 not in attrs:
566                    print "attribute " + a2 + " was not found in the data set. You probably loaded wrong file with VizRank projections."
567                    return
568
569        if len(attrInfo) == 0:
570            print "len(attrInfo) == 0. No attribute pairs. Unable to optimize."; return
571
572        self.worstVal = -1
573        self.canOptimize = 1
574        self.startOptimizationButton.hide()
575        self.stopOptimizationButton.show()
576        #qApp.processEvents()        # allow processing of other events
577
578        if self.orderAllAttributes:
579            orngVisFuncts.optimizeAttributeOrder(attrInfo, len(self.parallelWidget.data.domain.attributes), self, qApp)
580        else:
581            orngVisFuncts.optimizeAttributeOrder(attrInfo, self.numberOfAttributes, self, qApp)
582
583        self.stopOptimizationButton.hide()
584        self.startOptimizationButton.show()
585       
586        self.progressBarFinished()
587
588
589    # ################################
590    # MANAGE RESULTS
591    def updateShownProjections(self, *args):
592        self.resultList.clear()
593        for i in range(len(self.allResults)):
594            self.resultList.addItem("%.2f - %s" % (self.allResults[i][0], str(self.allResults[i][1])), i)
595        if self.resultList.count() > 0: self.resultList.setCurrentRow(0)
596
597    def clearResults(self):
598        self.allResults = []
599        self.resultList.clear()
600
601
602    def saveResults(self, filename = None):
603        if filename == None:
604            filename = ""
605            datasetName = getattr(self.parallelWidget.graph.rawData, "name", "")
606            if datasetName != "":
607                filename = os.path.splitext(os.path.split(datasetName)[1])[0]
608            if self.optimizationMeasure == CORRELATION: filename += " - " + "correlation"
609            else:                                       filename += " - " + "vizrank"
610
611            name = str(QFileDialog.getSaveFileName(self, "Save Parallel Projections",  os.path.join(self.lastSaveDirName, filename), "Parallel projections (*.papr)"))
612            if name == "": return
613        else:
614            name = filename
615
616        # take care of extension
617        if os.path.splitext(name)[1] != ".papr": name += ".papr"
618
619        dirName, shortFileName = os.path.split(name)
620        self.lastSaveDirName = dirName
621
622        # open, write and save file
623        file = open(name, "wt")
624        for val in self.allResults:
625            file.write(str(val) + "\n")
626        file.close()
627
628    def loadResults(self):
629        self.clearResults()
630
631        name = str(QFileDialog.getOpenFileName(self, "Open Parallel Projections",  self.lastSaveDirName, "Parallel projections (*.papr)"))
632        if name == "": return
633
634        dirName, shortFileName = os.path.split(name)
635        self.lastSaveDirName = dirName
636
637        file = open(name, "rt")
638        line = file.readline()[:-1]; ind = 0
639        while (line != ""):
640            (val, attrList) = eval(line)
641            self.allResults.insert(ind, (val, attrList))
642            self.resultList.addItem("%.2f - %s" % (val, str(attrList)), ind)
643            line = file.readline()[:-1]
644            ind+=1
645        file.close()
646
647
648#test widget appearance
649if __name__=="__main__":
650    a=QApplication(sys.argv)
651    ow=OWParallelCoordinates()
652    ow.show()
653    ow.graph.discPalette = ColorPaletteGenerator(rgbColors = [(127, 201, 127), (190, 174, 212), (253, 192, 134)])
654    data = orange.ExampleTable("../../doc/datasets/iris.tab")
655#    data = orange.ExampleTable(r"e:\Development\Orange Datasets\UCI\wine.tab")
656    #data = orange.ExampleTable(r"e:\Development\Orange Datasets\UCI\zoo.tab")
657    ow.setData(data)
658    ow.handleNewSignals()
659   
660    a.exec_()
Note: See TracBrowser for help on using the repository browser.