source: orange/Orange/OrangeWidgets/Unsupervised/OWMDS.py @ 11379:6bf8920120ac

Revision 11379:6bf8920120ac, 29.8 KB checked in by Ales Erjavec <ales.erjavec@…>, 14 months ago (diff)

Code style fixes.

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