source: orange/Orange/OrangeWidgets/Unsupervised/OWMDS.py @ 11378:202647aa198a

Revision 11378:202647aa198a, 31.2 KB checked in by Ales Erjavec <ales.erjavec@…>, 14 months ago (diff)

Removed obsolete 'Structured Data Files' output channel.

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