source: orange/Orange/OrangeWidgets/Unsupervised/OWMDS.py @ 11407:c521b70bdbaa

Revision 11407:c521b70bdbaa, 30.4 KB checked in by Ales Erjavec <ales.erjavec@…>, 13 months ago (diff)

Handle input matrices without (or with nonstandard) 'items' attribute.

(references #1264)

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