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)

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)
[11407]190        OWGUI.rubber(graph)
[8042]191
[11379]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 = []
[8042]206
207    def cmatrix(self, matrix=None):
208        self.closeContext()
[11379]209        self.origMatrix = matrix
210        self.data = data = None
[8042]211        if matrix:
[11407]212            self.data = data = getattr(matrix, "items", range(matrix.dim))
[11379]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
[8042]219        self.graph.closestPairs = None
[11379]220
[8042]221        if isinstance(data, orange.ExampleTable):
222            self.setExampleTable(data)
223        elif isinstance(data, orange.VarList):
224            self.setVarList(data)
[11407]225        elif data is not None:
226            self.setList(data)
[11379]227
[8042]228        if matrix:
[11379]229            self.mds = orngMDS.MDS(matrix)
230            self.mds.points = numpy.random.random(
231                size=[self.mds.n, self.mds.dim]
232            )
233
[8042]234            self.mds.getStress()
[11379]235            self.stress = self.getAvgStress(self.stressFunc[self.StressFunc][1])
[8042]236            if data and type(data) == orange.ExampleTable:
[11379]237                self.openContext("", self.data)
238            self.graph.setData(self.mds, self.colors, self.sizes, self.shapes,
239                               self.names, self.selectedInput)
[8042]240        else:
241            self.graph.clear()
242
243    def cselected(self, selected=[]):
[11379]244        self.selectedInputExamples = selected and selected or[]
245        if self.data and type(self.data) == orange.ExampleTable:
[8042]246            self.setExampleTable(self.data)
[11379]247            self.graph.setData(self.mds, self.colors, self.sizes, self.shapes,
248                               self.names, self.selectedInput)
[8042]249
250    def setExampleTable(self, data):
251        self.colorCombo.clear()
252        self.sizeCombo.clear()
253        self.shapeCombo.clear()
254        self.nameCombo.clear()
[11379]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:
[8042]267            self.colorCombo.addItem(name)
[11379]268        for name in ["Same size"] + map(lambda a: a.name, contAttributes):
[8042]269            self.sizeCombo.addItem(name)
[11379]270        for name in ["Same shape"] + map(lambda a: a.name, discAttributes):
[8042]271            self.shapeCombo.addItem(name)
[11379]272        for name in ["No name"] + attrName:
[8042]273            self.nameCombo.addItem(name)
274
275        try:
[11379]276            self.graph.NameAttr = \
277                1 + [name.lower() for name in attrName].index("name")
[8042]278        except:
279            pass
280
[11379]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))]
[8042]293        try:
[11379]294            selectedInput = self.selectedInputExamples.select(data.domain)
[8042]295        except:
[11379]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
[8042]304        def check(ex, a):
305            try:
306                ex[a]
[11379]307            except Exception:
[8042]308                return False
309            return not ex[a].isSpecial()
[11379]310
[8042]311        for j, attr in enumerate(attributes):
[11379]312            if attr.varType == orange.VarTypes.Discrete:
313                c = OWColorPalette.ColorPaletteHSV(len(attr.values))
[8042]314                for i in range(len(data)):
[11379]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)
[8042]332                for i in range(len(data)):
[11379]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
[8042]343            else:
344                for i in range(len(data)):
[11379]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
[8042]353        if data and data.domain.classVar:
354            if data.domain.classVar.varType == orange.VarTypes.Discrete:
[11379]355                self.graph.ColorAttr = len(self.colors[0]) - 1  # index 0 is Same color!
[8042]356            elif data.domain.classVar.varType == orange.VarTypes.Continuous:
[11379]357                self.graph.SizeAttr = len(self.sizes[0]) - 1  # index 0 is Same color!
[8042]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)
[11379]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)
[8042]373        try:
[11379]374            c = OWColorPalette.ColorPaletteHSV(len(data))
[8042]375            for i, d in enumerate(data):
[11379]376                self.colors[i][1] = c[i]
377                self.names[i][1] = " " + str(d.name)
[8042]378        except Exception, val:
379            print val
380
[11407]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
[11379]397    def updateStressBySize(self, noRepaint=False):
[8042]398        self.sizeCombo.setDisabled(self.graph.stressBySize)
399        if not noRepaint:
[9066]400            self.graph.updateData()
[11379]401
[8042]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])
[11379]409            self.stress = self.getAvgStress(self.stressFunc[self.StressFunc][1])
410
[8042]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
[11379]419#  from the widget. If somebody has time to fix orngMDS first, he's welcome.
[8042]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])
[11379]426            self.stress = self.getAvgStress(self.stressFunc[self.StressFunc][1])
[8042]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])
[11379]436            self.stress = self.getAvgStress(self.stressFunc[self.StressFunc][1])
[8042]437        self.graph.updateData()
438
439    def randomize(self):
440        if not getattr(self, "mds", None):
441            return
[11379]442        self.mds.points = numpy.random.random(size=[self.mds.n, 2])
[8042]443        if self.computeStress:
444            self.mds.getStress(self.stressFunc[self.StressFunc][1])
[11379]445            self.stress = self.getAvgStress(self.stressFunc[self.StressFunc][1])
[8042]446        self.graph.updateData()
447
448    def jitter(self):
449        if not getattr(self, "mds", None):
450            return
[9066]451        mi = numpy.min(self.mds.points, axis=0)
452        ma = numpy.max(self.mds.points, axis=0)
453        st = 0.05 * (ma - mi)
[8042]454        for i in range(self.mds.n):
455            for j in range(2):
[11379]456                self.mds.points[i][j] += st[j] * (random() - 0.5)
[8042]457        if self.computeStress:
458            self.mds.getStress(self.stressFunc[self.StressFunc][1])
[11379]459            self.stress = self.getAvgStress(self.stressFunc[self.StressFunc][1])
[8042]460        self.graph.updateData()
461
462    def testStart(self):
463        if not getattr(self, "mds", None):
464            return
[11379]465        if self.done == False:
466            self.done = True
[8042]467            return
[11379]468        self.done = False
[8042]469        self.startButton.setText("Stop Optimization")
470        self.stopping.setDisabled(1)
471        self.progressBarInit()
[11379]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)):
[8042]482            self.graph.updateData()
483        self.startButton.setText("Optimize")
484        self.stopping.setDisabled(0)
485        self.progressBarFinished()
[11379]486        self.done = True
[8042]487
[11379]488    def callback(self, a, b=None):
489        if not self.iterNum % (math.pow(10, self.RefreshMode)):
[8042]490            self.graph.updateData()
[11379]491        self.iterNum += 1
[8042]492        self.infoB.setText("Num. steps: %i" % self.iterNum)
493        self.infoA.setText("Avg. Stress: %f" % self.mds.avgStress)
[11379]494        self.progressBarSet(int(a * 100))
[8042]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()
[11379]506
[8042]507    def sendSelections(self):
508        if not getattr(self, "mds", None):
509            return
[11379]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:
[8042]515            self.sendExampleTable(selectedInd)
516
517    def sendExampleTable(self, selectedInd):
[11379]518        if self.selectionOptions == 0:
[9546]519            self.send("Data", orange.ExampleTable(self.data.getitems(selectedInd)))
[8042]520        else:
[11379]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])
[8042]526                domain.addmetas(self.data.domain.getmetas())
527            else:
[11379]528                domain = orange.Domain(self.data.domain)
[8042]529                domain.addmeta(orange.newmetaid(), xAttr)
530                domain.addmeta(orange.newmetaid(), yAttr)
[11379]531            selection = orange.ExampleTable(domain)
[8042]532            selection.extend(self.data.getitems(selectedInd))
533            for i in range(len(selectedInd)):
[11379]534                selection[i][xAttr] = self.mds.points[selectedInd[i]][0]
535                selection[i][yAttr] = self.mds.points[selectedInd[i]][1]
[9546]536            self.send("Data", selection)
[8042]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):
[11379]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
[8042]565        self.reportSection("Chart")
566        self.reportImage(self.graph.saveToFileDirect)
[11379]567
[8042]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
[11379]586        # self.curveKeys=[]
[8042]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,
[11379]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
[9066]606        self.axis_margin = 10
[8042]607
608    def setData(self, mds, colors, sizes, shapes, names, showFilled):
609        self.mds = mds
[11379]610        # self.data=data
[8042]611        self.colors = colors
612        self.sizes = sizes
613        self.shapes = shapes
614        self.names = names
[11379]615        self.showFilled = showFilled  # map(lambda d: not d, showFilled)
[8042]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)
[11379]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))
[8042]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))))
[11379]639
[8042]640        for c in self.distanceLineKeys:
641            try:
642                c.detach()
[11379]643            except RuntimeError:
644                # underlying C/C++ object has been deleted
[8042]645                pass
646        self.distanceLineKeys = []
[11379]647
[8042]648        hdist = self.closestPairs[:np]
649        if not hdist:
650            return
[11379]651
652        black = QColor(192, 192, 192)
[8042]653        if self.differentWidths:
654            mindist = hdist[0][0]
655            maxdist = hdist[-1][0]
656        else:
657            mindist = maxdist = 0
658        if maxdist != mindist:
[11379]659            k = 3 / (maxdist - mindist) ** 2
[8042]660            for dist, i, j in hdist:
661                pti, ptj = self.mds.points[i], self.mds.points[j]
[11379]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))
[8042]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]
[11379]670                c = self.addCurve("A", black, black, 0, QwtPlotCurve.Lines,
671                                  xData=[pti[0], ptj[0]], yData=[pti[1], ptj[1]],
672                                  lineWidth=2)
[8042]673                c.setZ(10)
674                self.distanceLineKeys.append(c)
[11379]675
[8042]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()
[11379]684                    except RuntimeError:
685                        # underlying C/C++ object has been deleted
686                        pass
[8042]687                self.distanceLineKeys = []
688            self.replot()
689
690    def setPoints(self):
691        if not self.mds:
[11379]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]
[8042]697
[11379]698            set = []
[8042]699            for c in colors:
[11379]700                if c not in set:
[8042]701                    set.append(c)
702
[11379]703            dict = {}
[8042]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:
[11379]709                    dict[hsv] = [i]
710
[8042]711            for color in set:
[11379]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)
[8042]718                c.setZ(100)
[11379]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)
[8042]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)
[11379]732                ks = self.PointSize / max(1, maxs - mins)
733                cs = 1 / max(1., maxs - mins)
[8042]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],
[11379]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
[8042]747                c.setZ(100)
[11379]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)
[8042]753                    c.setZ(100)
754
[11379]755        if len(self.mds.points) > 0:
[8042]756            X = [point[0] for point in self.mds.points]
757            Y = [point[1] for point in self.mds.points]
[9051]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
[11379]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)
[8042]766
767    def sendData(self, *args):
768        pass
769
[11379]770
771if __name__ == "__main__":
772    app = QApplication(sys.argv)
773    w = OWMDS()
[8042]774    w.show()
[11379]775    data = orange.ExampleTable("iris")
776
[8042]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)):
[11379]782        for j in range(i + 1):
[8042]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.