source: orange/Orange/OrangeWidgets/Visualize Qt/OWMDSQt.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>MDS Qt</name>
3<description>Multi dimensional scaling (Qt)</description>
4<icon>icons/MDS.png</icon>
5<contact>Ales Erjavec (ales.erjavec(@at@)fri.uni-lj.si)</contact>
6<priority>2500</priority>
7"""
8
9from OWWidget import *
10import Orange
11import orange
12import orngMDS
13import OWGUI
14import numpy, sys, math, time, os
15import OWColorPalette
16import OWToolbars
17#from OWGraph import *
18#from PyQt4.Qwt5 import *
19
20from OrangeWidgets.plot.owplot import OWPlot, OWCurve
21from OrangeWidgets.plot.owpoint import OWPoint
22
23from random import random
24
25
26class OWMDSQt(OWWidget):
27    settingsList=["graph.PointSize", "graph.proportionGraphed", "graph.ColorAttr", "graph.SizeAttr",
28                  "graph.ShapeAttr", "graph.NameAttr", "graph.ShowStress", "graph.NumStressLines",
29                  "graph.ShowName", "graph.differentWidths", "graph.stressByTransparency", "graph.useAntialiasing"
30                  "StressFunc", "applyLSMT", "toolbarSelection", "autoSendSelection", "selectionOptions", "computeStress",
31                  "RefreshMode"]
32    contextHandlers={"":DomainContextHandler("", [ContextField("graph.ColorAttr", DomainContextHandler.Optional),
33                                                  ContextField("graph.SizeAttr", DomainContextHandler.Optional),
34                                                  ContextField("graph.ShapeAttr", DomainContextHandler.Optional),
35                                                  ContextField("graph.NameAttr", DomainContextHandler.Optional),
36                                                  ContextField("graph.ShowName", DomainContextHandler.Optional)])}
37   
38    def __init__(self, parent=None, signalManager=None, name="Multi Dimensional Scaling"):
39        OWWidget.__init__(self, parent, signalManager, name, wantGraph=True)
40        self.inputs=[("Distances", orange.SymMatrix, self.cmatrix), ("Data Subset", ExampleTable, self.cselected)]
41        self.outputs=[("Data", ExampleTable)]
42
43        self.StressFunc=3
44        self.minStressDelta=5e-5
45        self.maxIterations=5000
46        self.maxImprovment=10
47        self.autoSendSelection=0
48        self.toolbarSelection=0
49        self.selectionOptions=0
50        self.computeStress=1
51        self.ReDraw=1
52        self.NumIter=1
53        self.RefreshMode=0
54        self.applyLSMT = 0
55
56        self.stressFunc=[("Kruskal stress", orngMDS.KruskalStress),
57                              ("Sammon stress", orngMDS.SammonStress),
58                              ("Signed Sammon stress", orngMDS.SgnSammonStress),
59                              ("Signed relative stress", orngMDS.SgnRelStress)]
60       
61        self.graph=MDSPlot(self.mainArea)
62        self.mainArea.layout().addWidget(self.graph)
63       
64        self.loadSettings()
65
66        tabs=OWGUI.tabWidget(self.controlArea)
67       
68        mds=OWGUI.createTabPage(tabs, "MDS")
69        graph=OWGUI.createTabPage(tabs, "Graph")
70
71        ##MDS Tab       
72        init=OWGUI.widgetBox(mds, "Initialization")
73        OWGUI.button(init, self, "Randomize", self.randomize)
74        OWGUI.button(init, self, "Jitter", self.jitter)
75        OWGUI.button(init, self, "Torgerson", self.torgerson)
76        opt=OWGUI.widgetBox(mds, "Optimization")
77
78        self.startButton=OWGUI.button(opt, self, "Optimize", self.testStart)
79        OWGUI.button(opt, self, "Single Step", self.smacofStep)
80        box = OWGUI.widgetBox(opt, "Stress Function")
81        OWGUI.comboBox(box, self, "StressFunc", items=[a[0] for a in self.stressFunc], callback=self.updateStress)       
82        OWGUI.radioButtonsInBox(opt, self, "RefreshMode", ["Every step", "Every 10 steps", "Every 100 steps"], "Refresh During Optimization", callback=lambda :1)
83       
84        self.stopping=OWGUI.widgetBox(opt, "Stopping Conditions")
85        OWGUI.hSlider(OWGUI.widgetBox(self.stopping, "Min. stress change", flat=True),
86                      self, "minStressDelta", minValue=5e-5, maxValue=1e-2, step=5e-5, 
87                      labelFormat="%.5f", intOnly=0)
88        OWGUI.hSlider(OWGUI.widgetBox(self.stopping, "Max. number of steps", flat=True), 
89                      self, "maxIterations", minValue=10, maxValue=5000, step=10, 
90                      labelFormat="%i")
91
92        ##Graph Tab       
93        OWGUI.hSlider(graph, self, "graph.PointSize", box="Point Size", minValue=1, maxValue=20, callback=self.graph.updateData)
94        self.colorCombo=OWGUI.comboBox(graph, self, "graph.ColorAttr", box="Color", callback=self.graph.updateData)
95        self.sizeCombo=OWGUI.comboBox(graph, self, "graph.SizeAttr", box="Size", callback=self.graph.updateData)
96        self.shapeCombo=OWGUI.comboBox(graph, self, "graph.ShapeAttr", box="Shape", callback=self.graph.updateData)
97        self.nameCombo=OWGUI.comboBox(graph, self, "graph.NameAttr", box="Label", callback=self.graph.updateData)
98       
99        box = OWGUI.widgetBox(graph, "Distances & Stress")
100        OWGUI.checkBox(box, self, "graph.ShowStress", "Show similar pairs", callback = self.graph.updateLinesRepaint)
101        b2 = OWGUI.widgetBox(box)
102        OWGUI.widgetLabel(b2, "Proportion of connected pairs")
103        OWGUI.separator(b2, height=3)
104        sl = OWGUI.hSlider(b2, self, "graph.proportionGraphed", minValue=0, maxValue=20, callback=self.graph.updateLinesRepaint, tooltip="Proportion of connected pairs (Maximum of 1000 lines will be drawn")
105        OWGUI.checkBox(box, self, "graph.differentWidths", "Show distance by line width", callback = self.graph.updateLinesRepaint)
106        OWGUI.checkBox(box, self, "graph.stressByTransparency", "Show stress by transparency", callback = self.graph.updateData)
107        OWGUI.checkBox(box, self, "graph.stressBySize", "Show stress by symbol size", callback = self.updateStressBySize)
108        self.updateStressBySize(True)
109       
110        OWGUI.checkBox(graph, self, "graph.antialias_points", label="Use antialiasing", box="Antialiasing", tooltip="Use antialiasing for beter quality graphics", callback=self.graph.updateData)
111
112        self.zoomToolbar=OWToolbars.ZoomSelectToolbar(self, graph, self.graph, self.autoSendSelection)
113        self.connect(self.zoomToolbar.buttonSendSelections, SIGNAL("clicked()"), self.sendSelections)
114        self.graph.autoSendSelectionCallback = lambda :self.autoSendSelection and self.sendSelections()
115
116        OWGUI.checkBox(graph, self, "autoSendSelection", "Auto send selected")
117        OWGUI.radioButtonsInBox(graph, self, "selectionOptions", ["Don't append", "Append coordinates", "Append coordinates as meta"], box="Append coordinates", callback=self.sendIf)
118
119        mds.setSizePolicy(QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum))
120        graph.setSizePolicy(QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum))
121        self.controlArea.setMinimumWidth(250)
122       
123        OWGUI.rubber(mds)
124        OWGUI.rubber(self.controlArea)
125       
126        infoBox=OWGUI.widgetBox(mds, "Info")
127        self.infoA=OWGUI.widgetLabel(infoBox, "Avg. stress:")
128        self.infoB=OWGUI.widgetLabel(infoBox, "Num. steps")
129#        OWGUI.button(self.controlArea, self, "Save", self.graph.saveToFile, debuggingEnabled = 0)
130        self.connect(self.graphButton, SIGNAL("clicked()"), self.graph.saveToFile)
131        self.resize(900,630)
132
133        self.done=True
134        self.data=None
135        self.selectedInputExamples=[]
136        self.selectedInput=[]
137
138    def cmatrix(self, matrix=None):
139        self.closeContext()
140        self.origMatrix=matrix
141        self.data=data=None
142        if matrix:
143            self.data=data=getattr(matrix, "items")
144            matrix.matrixType = orange.SymMatrix.Symmetric
145           
146        self.graph.ColorAttr=0
147        self.graph.SizeAttr=0
148        self.graph.ShapeAttr=0
149        self.graph.NameAttr=0
150        self.graph.closestPairs = None
151        if isinstance(data, orange.ExampleTable):
152            self.setExampleTable(data)
153        elif isinstance(data, list):
154            self.setList(data)
155        elif isinstance(data, orange.VarList):
156            self.setVarList(data)
157        if matrix:
158            self.mds=orngMDS.MDS(matrix)
159            self.mds.points=numpy.random.random(size=[self.mds.n, self.mds.dim])
160            self.mds.getStress()
161            self.stress=self.getAvgStress(self.stressFunc[self.StressFunc][1])
162            if data and type(data) == orange.ExampleTable:
163                self.openContext("",self.data) 
164            self.graph.setData(self.mds, self.colors, self.sizes, self.shapes, self.names, self.selectedInput)
165        else:
166            self.graph.clear()
167
168    def cselected(self, selected=[]):
169        self.selectedInputExamples=selected or []
170        if self.data and type(self.data)==orange.ExampleTable:
171            self.setExampleTable(self.data)
172            self.graph.setData(self.mds, self.colors, self.sizes, self.shapes, self.names, self.selectedInput)
173
174    def setExampleTable(self, data):
175        self.colorCombo.clear()
176        self.sizeCombo.clear()
177        self.shapeCombo.clear()
178        self.nameCombo.clear()
179        attributes=[attr for attr in data.domain.variables+data.domain.getmetas().values() or [] ]
180        discAttributes=filter(lambda a: a.varType==orange.VarTypes.Discrete, attributes)
181        contAttributes=filter(lambda a: a.varType==orange.VarTypes.Continuous, attributes)
182        attrName=[attr.name for attr in attributes]
183        for name in ["Same color"]+attrName:
184            self.colorCombo.addItem(name)
185        for name in ["Same size"]+map(lambda a:a.name, contAttributes):
186            self.sizeCombo.addItem(name)
187        for name in ["Same shape"]+map(lambda a: a.name, discAttributes):
188            self.shapeCombo.addItem(name)
189        for name in ["No name"]+attrName:
190            self.nameCombo.addItem(name)
191
192#        if data.domain.classVar:
193#            if data.domain.classVar.varType == orange.VarTypes.Discrete:
194#                self.graph.ColorAttr = len(data.domain.variables) # index 0 is Same color!
195#            elif data.domain.classVar.varType == orange.VarTypes.Continuous:
196#                self.graph.SizeAttr = len(data.domain.variables) # index 0 is Same color!
197        try:
198            self.graph.NameAttr = 1 + [name.lower() for name in attrName].index("name")
199        except:
200            pass
201       
202        self.attributes=attributes
203        self.discAttributes=discAttributes
204        self.contAttributes=contAttributes
205
206        self.colors=[[Qt.black]*(len(attributes)+1) for i in range(len(data))]
207        self.shapes=[[OWPoint.Ellipse]*(len(discAttributes)+1) for i in range(len(data))]
208        self.sizes=[[1.0]*(len(contAttributes)+1) for i in range(len(data))]
209        self.names=[[""]*(len(attributes)+1) for i in range(len(data))]
210        try:
211            selectedInput=self.selectedInputExamples.select(data.domain)
212        except Exception:
213            selectedInput=[]
214        self.selectedInput=map(lambda d: selectedInput and (d in selectedInput), data)
215        contI=discI=attrI=1
216        def check(ex, a):
217            try:
218                ex[a]
219            except:
220                return False
221            return not ex[a].isSpecial()
222       
223        for j, attr in enumerate(attributes):
224            if attr.varType==orange.VarTypes.Discrete:
225                c=OWColorPalette.ColorPaletteHSV(len(attr.values))
226                for i in range(len(data)):
227                    self.colors[i][attrI]= check(data[i],attr)  and c[int(data[i][attr])] or Qt.black
228##                    self.shapes[i][discI]= data[i][attr].isSpecial() and self.graph.shapeList[0] or self.graph.shapeList[int(data[i][attr])%len(self.graph.shapeList)]
229                    self.shapes[i][discI]= check(data[i],attr) and self.graph.shapeList[int(data[i][attr])%len(self.graph.shapeList)] or self.graph.shapeList[0]
230                    self.names[i][attrI]= check(data[i],attr) and " "+str(data[i][attr]) or ""
231                    #self.sizes[i][contI]=5
232                attrI+=1
233                discI+=1
234            elif attr.varType==orange.VarTypes.Continuous:
235                c=OWColorPalette.ColorPaletteBW(-1)
236                #val=[e[attr] for e in data if not e[attr].isSpecial()]
237                val=[e[attr] for e in data if check(e, attr)]
238                minVal=min(val or [0])
239                maxVal=max(val or [1])
240                span = max(maxVal - minVal, 1e-6)
241                for i in range(len(data)):
242                    self.colors[i][attrI]=check(data[i],attr) and c.getColor((data[i][attr] - minVal)/span) or Qt.black
243                    #self.shapes[i][discI]=self.graph.shapeList[0]
244                    self.names[i][attrI]=check(data[i],attr) and " "+str(data[i][attr]) or ""
245                    self.sizes[i][contI]=check(data[i],attr) and (float(self.data[i][attr]) - minVal) / span or 1.0
246                contI+=1
247                attrI+=1
248            else:
249                for i in range(len(data)):
250                    self.colors[i][attrI]=Qt.black
251                    #self.shapes[i][j+1]=self.graph.shapeList[0]
252                    self.names[i][attrI]= check(data[i],attr) and " "+str(data[i][attr]) or ""
253                    #self.sizes[i][j+1]=5
254                attrI+=1
255        if data and data.domain.classVar:
256            if data.domain.classVar.varType == orange.VarTypes.Discrete:
257                self.graph.ColorAttr = len(self.colors[0]) - 1 # index 0 is Same color!
258            elif data.domain.classVar.varType == orange.VarTypes.Continuous:
259                self.graph.SizeAttr = len(self.sizes[0]) - 1 # index 0 is Same color!
260
261    def setList(self, data):
262        self.colorCombo.clear()
263        self.sizeCombo.clear()
264        self.shapeCombo.clear()
265        self.nameCombo.clear()
266        for name in ["Same color", "strain"]:
267            self.colorCombo.addItem(name)
268        for name in ["No name", "name", "strain"]:
269            self.nameCombo.addItem(name)
270
271        self.colors=[[Qt.black]*3 for i in range(len(data))]
272        self.shapes=[[OWPoint.Ellipse] for i in range(len(data))]
273        self.sizes=[[1.0] for i in range(len(data))]
274        self.selectedInput=[False]*len(data)
275
276        if type(data[0]) in [str, unicode]:
277            self.names = [("", di, "", "") for di in data]
278        else:
279            self.names=[[""]*4 for i in range(len(data))]
280            try:
281                strains=list(set([d.strain for d in data]))
282                c=OWColorPalette.ColorPaletteHSV(len(strains))
283                for i, d in enumerate(data):
284                    self.colors[i][1]=c[strains.index(d.strain)]
285                    self.names[i][1]=" "+d.name
286                    self.names[i][2]=" "+d.strain
287            except Exception, val:
288                print val
289
290    def setVarList(self, data):
291        self.colorCombo.clear()
292        self.sizeCombo.clear()
293        self.shapeCombo.clear()
294        self.nameCombo.clear()
295        for name in ["Same color", "Variable"]:
296            self.colorCombo.addItem(name)
297        for name in ["No name", "Var name"]:
298            self.nameCombo.addItem(name)
299        self.colors=[[Qt.black]*3 for i in range(len(data))]
300        self.shapes=[[OWPoint.Ellipse] for i in range(len(data))]
301        self.sizes=[[1.0] for i in range(len(data))]
302        self.names=[[""]*4 for i in range(len(data))]
303        self.selectedInput=[False]*len(data)
304        try:
305            c=OWColorPalette.ColorPaletteHSV(len(data))
306            for i, d in enumerate(data):
307                self.colors[i][1]=c[i]
308                self.names[i][1]=" " +str(d.name)
309        except Exception, val:
310            print val
311
312    def updateStressBySize(self, noRepaint = False):
313        self.sizeCombo.setDisabled(self.graph.stressBySize)
314        if not noRepaint:
315            self.graph.updateData()
316       
317    def smacofStep(self):
318        if not getattr(self, "mds", None):
319            return
320        for i in range(self.NumIter):
321            self.mds.SMACOFstep()
322        if self.computeStress:
323            self.mds.getStress(self.stressFunc[self.StressFunc][1])
324            self.stress=self.getAvgStress(self.stressFunc[self.StressFunc][1])
325        #st=time.clock()
326        if self.ReDraw:
327            self.graph.updateData()
328        #print "Update:", time.clock()-st
329
330## I (Janez) disabled LSMT because it is implemented as it never should be:
331#  orngMDS.LSMT transforms the distance matrix itself (indeed there is
332#  the original stored, too), and from that point on there is no way the
333#  user can "untransform" it, except for resending the signal
334#  Since the basic problem is in bad design of orngMDS, I removed the option
335#  from the widget. If somebody has time to fix orngMDS first, he's welcome.
336    def LSMT(self):
337        if not getattr(self, "mds", None):
338            return
339        self.mds.LSMT()
340        if self.computeStress:
341            self.mds.getStress(self.stressFunc[self.StressFunc][1])
342            self.stress=self.getAvgStress(self.stressFunc[self.StressFunc][1])
343        if self.ReDraw:
344            self.graph.updateData()
345
346    def torgerson(self):
347        if not getattr(self, "mds", None):
348            return
349        self.mds.Torgerson()
350        if self.computeStress:
351            self.mds.getStress(self.stressFunc[self.StressFunc][1])
352            self.stress=self.getAvgStress(self.stressFunc[self.StressFunc][1])
353        self.graph.updateData()
354
355    def randomize(self):
356        if not getattr(self, "mds", None):
357            return
358        self.mds.points = numpy.random.random(size=[self.mds.n,2])
359        if self.computeStress:
360            self.mds.getStress(self.stressFunc[self.StressFunc][1])
361            self.stress=self.getAvgStress(self.stressFunc[self.StressFunc][1])
362        self.graph.updateData()
363
364    def jitter(self):
365        if not getattr(self, "mds", None):
366            return
367        mi = numpy.min(self.mds.points, axis=0)
368        ma = numpy.max(self.mds.points, axis=0)
369        st = 0.05 * (ma - mi)
370        for i in range(self.mds.n):
371            for j in range(2):
372                self.mds.points[i][j] += st[j]*(random() - 0.5)
373        if self.computeStress:
374            self.mds.getStress(self.stressFunc[self.StressFunc][1])
375            self.stress=self.getAvgStress(self.stressFunc[self.StressFunc][1])
376        self.graph.updateData()
377
378    def start(self):
379        if not getattr(self, "mds", None):
380            return
381        if self.done==False:
382            self.done=True
383            return
384        self.done=False
385        self.startButton.setText("Stop")
386        numIter=0
387        self.progressBarInit()
388        pcur=0
389        startStress=oldStress=stress=self.getAvgStress(self.stressFunc[self.StressFunc][1])
390        startTime=time.clock()
391        hist=[stress]*3
392        while not self.done and numIter<self.maxIterations:
393            for i in range(self.NumIter):
394                self.mds.SMACOFstep()
395                qApp.processEvents()
396            if self.computeStress:
397                self.mds.getStress(self.stressFunc[self.StressFunc][1])
398                self.stress=stress=self.getAvgStress(self.stressFunc[self.StressFunc][1])
399                hist.pop(0)
400                hist.append(abs(oldStress-stress))
401            numIter+=1
402            self.infoB.setText("Num. steps: %i" % numIter)
403            qApp.processEvents()
404            if self.ReDraw:
405                self.graph.updateData()
406            qApp.processEvents()
407            if self.computeStress and abs(sum(hist)/3)<abs(self.minStressDelta*oldStress):
408                break
409            ## Update progress bar
410            p1=abs(self.minStressDelta*oldStress)/max(sum(hist)/3, 1e-6)*100
411            if p1>100: p1=0
412            pcur=min(max([p1, float(numIter)/self.maxIterations*100, pcur]),99)
413            self.progressBarSet(int(pcur))
414
415            oldStress=stress
416        self.startButton.setText("Optimize")
417        self.progressBarFinished()
418        #if not self.ReDraw:
419        self.graph.updateData()
420        self.done=True
421        #print "time %i " % (time.clock()-startTime)
422
423    def testStart(self):
424        if not getattr(self, "mds", None):
425            return
426        if self.done==False:
427            self.done=True
428            return
429        self.done=False
430        self.startButton.setText("Stop Optimization")
431        self.stopping.setDisabled(1)
432        self.progressBarInit()
433        self.iterNum=0
434        self.mds.progress_callback=self.callback
435        self.mds.mds.optimize(self.maxIterations, self.stressFunc[self.StressFunc][1], self.minStressDelta)
436        if self.iterNum%(math.pow(10,self.RefreshMode)):
437            self.graph.updateData()
438        self.startButton.setText("Optimize")
439        self.stopping.setDisabled(0)
440        self.progressBarFinished()
441        self.done=True
442
443    def callback(self, a,b=None):
444        if not self.iterNum%(math.pow(10,self.RefreshMode)):
445            self.graph.updateData()
446        self.iterNum+=1
447        self.infoB.setText("Num. steps: %i" % self.iterNum)
448        self.infoA.setText("Avg. Stress: %f" % self.mds.avgStress)
449        self.progressBarSet(int(a*100))
450        qApp.processEvents()
451        if self.done:
452            return 0
453        else:
454            return 1
455
456
457    def getAvgStress(self, stressf=orngMDS.SgnRelStress):
458        return self.mds.avgStress
459        """
460        self.mds.getDistance()
461        total=0.0
462        total=sum([abs(a[0]) for a in self.mds.arr])
463        self.infoA.setText("Avg. stress: %.7f" % (total/(self.mds.n*self.mds.n)))
464        return total/(self.mds.n*self.mds.n)
465        """
466
467    def sendIf(self, i=-1):
468        if self.autoSendSelection:
469            self.sendSelections()
470       
471    def sendSelections(self):
472        if not getattr(self, "mds", None):
473            return
474        points = self.graph.main_curve.points()
475        selectedInd = [i for i, p in enumerate(points) if p.is_selected()]
476        if type(self.data)==orange.ExampleTable:
477            self.sendExampleTable(selectedInd)
478        elif type(self.data)==list:
479            self.sendList(selectedInd)
480
481    def sendExampleTable(self, selectedInd):
482        if self.selectionOptions==0:
483            self.send("Data", orange.ExampleTable(self.data.getitems(selectedInd)))
484        else:
485            xAttr=orange.FloatVariable("X")
486            yAttr=orange.FloatVariable("Y")
487            if self.selectionOptions==1:
488                domain=orange.Domain([xAttr, yAttr]+[v for v in self.data.domain.variables])
489                domain.addmetas(self.data.domain.getmetas())
490            else:
491                domain=orange.Domain(self.data.domain)
492                domain.addmeta(orange.newmetaid(), xAttr)
493                domain.addmeta(orange.newmetaid(), yAttr)
494            selection=orange.ExampleTable(domain)
495            selection.extend(self.data.getitems(selectedInd))
496            for i in range(len(selectedInd)):
497                selection[i][xAttr]=self.mds.points[selectedInd[i]][0]
498                selection[i][yAttr]=self.mds.points[selectedInd[i]][1]
499            self.send("Data", selection)
500
501    def sendList(self, selectedInd):
502        if self.data and type(self.data[0]) == str:
503            xAttr=orange.FloatVariable("X")
504            yAttr=orange.FloatVariable("Y")
505            nameAttr=  orange.StringVariable("name")
506            if self.selectionOptions == 1:
507                domain = orange.Domain([xAttr, yAttr, nameAttr])
508                selection = orange.ExampleTable(domain)
509                for i in range(len(selectedInd)):
510                    selection.append(list(self.mds.points[selectedInd[i]]) + [self.data[i]])
511            else:
512                domain = orange.Domain([nameAttr])
513                if self.selectionOptions:
514                    domain.addmeta(orange.newmetaid(), xAttr)
515                    domain.addmeta(orange.newmetaid(), yAttr)
516                selection = orange.ExampleTable(domain)
517                for i in range(len(selectedInd)):
518                    selection.append([self.data[i]])
519                    if self.selectionOptions:
520                        selection[i][xAttr]=self.mds.points[selectedInd[i]][0]
521                        selection[i][yAttr]=self.mds.points[selectedInd[i]][1]
522            self.send("Data", selection)
523            return
524               
525        if not selectedInd:
526            self.send("Structured Data Files", None)
527        else:
528            datasets=[self.data[i] for i in selectedInd]
529            names=list(set([d.dirname for d in datasets]))
530            data=[(name, [d for d in filter(lambda a:a.strain==name, datasets)]) for name in names]
531            self.send("Structured Data Files",data)
532
533    def updateStress(self):
534        if not getattr(self, "mds", None):
535            return
536        self.mds.getStress(self.stressFunc[self.StressFunc][1])
537        self.graph.replot()
538
539    def sendReport(self):
540        self.reportSettings("Optimization",
541                            [("Stress function", self.stressFunc[self.StressFunc][0]),
542                             ("Minimal stress change", self.minStressDelta),
543                             ("Maximal number of steps", self.maxIterations)])
544        if self.graph.ColorAttr or self.graph.stressBySize or self.graph.SizeAttr or self.graph.ShapeAttr or self.graph.NameAttr or self.graph.ShowStress:
545            self.reportSettings("Visual settings",
546                                [self.graph.ColorAttr and ("Point color", self.colorCombo.currentText()),
547                                 self.graph.stressBySize  and ("Point size", "&lt;stress&gt;")
548                                    or self.graph.SizeAttr and ("Point size", self.sizeCombo.currentText()),
549                                 self.graph.ShapeAttr and ("Point shape", self.shapeCombo.currentText()),
550                                 self.graph.NameAttr and ("Labels", self.nameCombo.currentText()),
551                                 self.graph.ShowStress and ("Proportion of connected pairs", self.graph.proportionGraphed)])
552        self.reportSection("Chart")
553        self.reportImage(self.graph.saveToFileDirect)
554                             
555
556class MDSPlot(OWPlot):
557    def __init__(self, parent=None, name=None):
558        OWPlot.__init__(self, parent, name)
559        self.use_animations = False
560        self.animate_points = False
561        self.antialias_points = True
562        self.data = None
563        self.mds = None
564        self.PointSize = 5
565        self.ColorAttr = 0
566        self.SizeAttr = 0
567        self.ShapeAttr = 0
568        self.NameAttr = 0
569        self.ShowStress = False
570        self.differentWidths = True
571        self.stressByTransparency = True
572        self.stressBySize = False
573        self.NumStressLines = 10
574        self.proportionGraphed = 20
575        self.ShowName = True
576        #self.curveKeys=[]
577        self.pointKeys = []
578        self.points = []
579        self.lines = []
580        self.lineKeys = []
581        self.distanceLineCurves = []
582        self.colors = []
583        self.sizes = []
584        self.closestPairs = None
585        self.shapeList = [OWPoint.Ellipse,
586                          OWPoint.Rect,
587                          OWPoint.Diamond,
588                          OWPoint.Triangle,
589                          OWPoint.DTriangle ,
590                          OWPoint.UTriangle,
591                          OWPoint.LTriangle,
592                          OWPoint.RTriangle,
593                          OWPoint.Cross,
594                          OWPoint.XCross ]
595
596    def setData(self, mds, colors, sizes, shapes, names, showFilled):
597        self.mds = mds
598        self.colors = colors
599        self.sizes = sizes
600        self.shapes = shapes
601        self.names = names
602        self.showFilled = showFilled
603        self.updateData()
604
605    def updateData(self):
606        self.clear()
607        self.distanceLineCurves = []
608        if self.ShowStress:
609            self.updateDistanceLines()
610        self.setPoints()
611        self.replot()
612
613    def updateDistanceLines(self):
614        if not self.mds:
615            return
616        N = len(self.mds.points)
617        np = min(int(N*(N-1)/2. * self.proportionGraphed/100.), 1000) # draw maximum of 1000 closest pairs
618        needlines = int(math.ceil((1 + math.sqrt(1+8*np)) / 2)) 
619
620        if self.closestPairs is None or len(self.closestPairs) < np:
621            import heapq
622            m = self.mds.originalDistances
623            self.closestPairs = sorted(heapq.nsmallest(np, ((m[i, j], i, j) for i in range(m.dim) for j in range(i))))
624               
625        for c in self.distanceLineCurves:
626            try:
627                c.detach()
628            except RuntimeError, ex: #underlying C/C++ object has been deleted
629                pass
630        self.distanceLineCurves = []
631               
632        hdist = self.closestPairs[:np]
633        if not hdist:
634            return
635   
636        black = QColor(192,192,192)
637        if self.differentWidths:
638            mindist = hdist[0][0]
639            maxdist = hdist[-1][0]
640        else:
641            mindist = maxdist = 0
642        if maxdist != mindist:
643            k = 3 / (maxdist - mindist)**2
644            for dist, i, j in hdist:
645                pti, ptj = self.mds.points[i], self.mds.points[j]
646                c = self.add_curve("n_lines", black, black, 2, style=OWCurve.Lines, xData=[pti[0],ptj[0]], yData=[pti[1],ptj[1]], lineWidth = max(1, (maxdist - dist)**2 * k))
647                c.set_in_background(True)
648                self.distanceLineCurves.append(c)
649        else:
650            for dist, i, j in hdist:
651                pti, ptj = self.mds.points[i], self.mds.points[j]
652                c = self.add_curve("n_lines", black, black, 2, OWCurve.Lines, xData=[pti[0],ptj[0]], yData=[pti[1],ptj[1]], lineWidth = 2)
653                c.set_in_background(True)
654                self.distanceLineCurves.append(c)
655       
656                   
657    def updateLinesRepaint(self):
658        if self.mds:
659            if self.ShowStress:
660                self.updateDistanceLines()
661            else:
662                for c in self.distanceLineCurves:
663                    try:
664                        c.detach()
665                    except RuntimeError, ex: #underlying C/C++ object has been deleted
666                        pass 
667                self.distanceLineCurves = []
668            self.replot()
669
670    def setPoints(self):
671        if not self.mds:
672            return 
673        x_data = [p[0] for p in self.mds.points]
674        y_data = [p[1] for p in self.mds.points]
675       
676        if self.stressBySize or self.stressByTransparency:
677            stresses = map(sum, self.mds.stress)
678           
679            mins, maxs = min(stresses), max(stresses)
680            stress_scale = 1. / max(1e-7, maxs - mins)
681#            stress_scale = 1. / max(1e-7, maxs - mins)
682           
683        if self.ColorAttr != 0:
684            colors = [c[self.ColorAttr] for c in self.colors]
685        else:
686            colors = [QColor(Qt.black) for _ in self.colors] #QColor(Qt.black)
687           
688        if self.stressByTransparency:
689            for c, s in zip(colors, stresses):
690                c.setAlpha(math.floor((1.0 - ((s - mins) * stress_scale)) * 255))
691           
692        if self.stressBySize:
693            sizes = [(s - mins) * stress_scale * self.PointSize for s in stresses]
694        elif self.SizeAttr != 0:
695            sizes = [s[self.SizeAttr] * self.PointSize for s in self.sizes]
696        else:
697            sizes = [self.PointSize]
698           
699        if self.ShapeAttr != 0:
700            shapes = [s[self.ShapeAttr] for s in self.shapes]
701        else:
702            shapes = [self.shapeList[0]]
703       
704        if self.NameAttr != 0:
705            labels = [n[self.NameAttr] for n in self.names]
706        else:
707            labels = []
708           
709        self.set_main_curve_data(x_data, y_data, colors, labels, sizes, shapes, self.showFilled)
710       
711    def sendData(self, *args):
712        pass
713   
714
715if __name__=="__main__":
716    app=QApplication(sys.argv)
717    w=OWMDSQt()
718    w.show()
719    data=orange.ExampleTable("../../doc/datasets/iris.tab")
720##    data = orange.ExampleTable(r"E:\Development\Orange Datasets\UCI\iris.tab")
721##    data=orange.ExampleTable("/home/ales/src/MDSjakulin/eu_nations.txt")
722    matrix = orange.SymMatrix(len(data))
723    dist = orange.ExamplesDistanceConstructor_Euclidean(data)
724    matrix = orange.SymMatrix(len(data))
725    matrix.setattr('items', data)
726    for i in range(len(data)):
727        for j in range(i+1):
728            matrix[i, j] = dist(data[i], data[j])
729
730    w.cmatrix(matrix)
731    w.cselected(orange.ExampleTable(data[:50]))
732    app.exec_()
733    w.saveSettings()
734
Note: See TracBrowser for help on using the repository browser.