source: orange/orange/OrangeWidgets/Unsupervised/OWMDS.py @ 9546:2b6cc6f397fe

Revision 9546:2b6cc6f397fe, 34.1 KB checked in by ales_erjavec <ales.erjavec@…>, 2 years ago (diff)

Renamed widget channel names in line with the new naming rules/convention.
Added backwards compatibility in orngDoc loadDocument to enable loading of schemas saved before the change.

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