source: orange/orange/OrangeWidgets/Prototypes/OWCompare.py @ 9546:2b6cc6f397fe

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

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

Line 
1"""<name>Compare Examples</name>
2<description>Compares examples, considering the attributes as distributions</description>
3<icon>icons/CompareExamples.png</icon>
4<priority>30</priority>
5<contact>Janez Demsar (janez.demsar@fri.uni-lj.si)</contact>"""
6
7from OWWidget import *
8from OWGUI import *
9from OWDlgs import OWChooseImageSizeDlg
10from PyQt4.QtGui import QGraphicsEllipseItem
11import OWQCanvasFuncts, OWColorPalette, math
12#import MapLayer
13
14class OWCompare(OWWidget):
15    # We cannot put attribute selection to context settings; see comment at func settingsFromWidget
16    contextHandlers = {"": PerfectDomainContextHandler("", [
17                                                       ("attrLabel", DomainContextHandler.Optional + DomainContextHandler.IncludeMetaAttributes),
18                                                       ("sortingOrder", DomainContextHandler.Optional + DomainContextHandler.IncludeMetaAttributes)])}
19    settingsList = ["normalize", "colorSettings", "selectedSchemaIndex"]
20
21    def __init__(self,parent=None, signalManager = None):
22        OWWidget.__init__(self, parent, signalManager, "Compare Examples")
23        self.inputs = [("Data", ExampleTable, self.setData, Default)]
24        self.outputs = [("Bars", list, Default)]
25        self.icons = self.createAttributeIconDict()
26
27        self.examples = None
28        self.attributes = []
29        self.selectedAttributes = []
30        self.normalize = 0
31        self.colorSettings = None
32        self.selectedSchemaIndex = 0
33        self.attrLabel = "(No labels)"
34        self.sortingOrder = "(Original order)"
35        self.barHeight, self.barWidth = 300, 42
36        self.pieHeight, self.pieWidth = 150, 150
37        self.resize(900, 550)
38        self.loadSettings()
39
40        dlg = self.createColorDialog()
41        self.discPalette = dlg.getDiscretePalette("discPalette")
42
43        OWGUI.listBox(self.controlArea, self, "selectedAttributes", labels="attributes", box="Attributes", selectionMode=QListWidget.ExtendedSelection, callback=self.updateDisplay)
44        self.attrLabelCombo = OWGUI.comboBox(self.controlArea, self, "attrLabel", box="Label", callback=self.updateDisplay, sendSelectedValue=1, valueType=str, emptyString="(No labels)")
45        self.sortingCombo = OWGUI.comboBox(self.controlArea, self, "sortingOrder", box="Sorting", callback=self.updateDisplay, sendSelectedValue=1, valueType=str, emptyString="(Original order)")
46        OWGUI.checkBox(self.controlArea, self, "normalize", "Normalize to equal height", box="Settings", callback=self.updateDisplay)
47        b1 = OWGUI.widgetBox(self.controlArea, "Colors", orientation = "horizontal")
48        OWGUI.button(b1, self, "Set Colors", self.setColors, debuggingEnabled = 0)
49       
50#        self.btnSendbars = OWGUI.button(self.controlArea, self, "Send Bars", self.sendBars)
51#        self.btnSendpie = OWGUI.button(self.controlArea, self, "Send Pie Charts", self.sendPies)
52       
53        self.canvas = QGraphicsScene(self)
54        self.canvasview = QGraphicsView(self.canvas, self.mainArea)
55        self.canvasview.setAlignment(Qt.AlignLeft | Qt.AlignTop)
56        self.mainArea.layout().addWidget( self.canvasview )
57
58    def createColorDialog(self):
59        dlg = OWColorPalette.ColorPaletteDlg(self, "Color Palette")
60        dlg.createDiscretePalette("discPalette", "Discrete Palette")
61        dlg.setColorSchemas(self.colorSettings, self.selectedSchemaIndex)
62        return dlg
63   
64    def setColors(self):
65        dlg = self.createColorDialog()
66        if dlg.exec_():
67            self.colorSettings = dlg.getColorSchemas()
68            self.selectedSchemaIndex = dlg.selectedSchemaIndex
69            self.discPalette = dlg.getDiscretePalette("discPalette")
70            self.updateDisplay()
71
72    def constructBar(self, distrib, numbs):
73        bar = QGraphicsItemGroup()
74        totHeight = 0
75        for j, num, dist in reversed(zip(range(len(numbs)), numbs, distrib)):
76            rect = QGraphicsRectItem(-self.barWidth/2, totHeight-dist, self.barWidth, dist)
77            rect.setBrush(QBrush(self.discPalette[j]))
78            rect.setPen(QPen(QColor(255, 255, 255), 1, Qt.SolidLine))
79            rect.setToolTip("%s: %.3f" % (self.attributes[self.selectedAttributes[j]][0], num) + (" (%2.1f%%)" % (dist/self.barHeight*100) if self.normalize else ""))
80            bar.addToGroup(rect)
81            totHeight -= dist
82        return bar
83
84    def constructPieChart(self, distrib, numbs):
85        pie = QGraphicsItemGroup()
86        totArc = 0
87        for j, num, dist in reversed(zip(range(len(numbs)), numbs, distrib)):
88            arc = QGraphicsEllipseItem(-self.pieWidth/2, -self.pieWidth/2, self.pieWidth, self.pieHeight)
89            arc.setPen(QPen(QColor(0, 0, 0), 1, Qt.SolidLine))
90            arc.setBrush(QBrush(self.discPalette[j]))
91            arc.setToolTip("%s: %.3f" % (self.attributes[self.selectedAttributes[j]][0], num) + (" (%2.1f%%)" % (dist/self.barHeight*100) if self.normalize else ""))
92            arc.setStartAngle(totArc)
93            arc.setSpanAngle(dist * 2880 / self.barHeight)
94            pie.addToGroup(arc)
95            totArc += dist * 2880 / self.barHeight
96        return pie
97
98    def getDistsNums(self, forceNormal=False):
99            nums = [[ex[i] for i in self.selectedAttributes] for ex in self.examples]
100            sums = [sum(n) for n in nums]
101            norms = [i or 1 for i in sums] if forceNormal or self.normalize else [max(sums) or 1]*len(sums) 
102            dists = [[self.barHeight*x/norm for x in num] for num, norm in zip(nums, norms)]
103            return dists, nums, sums
104       
105    def updateDisplay(self):
106        self.canvas.clear()
107        if self.examples and len(self.selectedAttributes):
108            width, height = self.barWidth, self.barHeight
109            dists, nums, sums = self.getDistsNums() 
110
111            order = range(len(self.examples))
112            sortingOrder = self.sortingOrder
113            if sortingOrder and sortingOrder != "(Original order)":
114                if sortingOrder=="Total":
115                    order.sort(lambda i, j: -cmp(sums[i], sums[j]))
116                else:
117                    order.sort(lambda i, j: -cmp(self.origExamples[i][sortingOrder], self.origExamples[j][sortingOrder]))
118           
119            for xpos, col in enumerate(order):
120                bar = self.constructBar(dists[col], nums[col])
121                self.canvas.addItem(bar)
122                bar.setPos(self.barWidth*(xpos*2+1.5), 50)
123                if self.attrLabel != "(No labels)":
124                    OWQCanvasFuncts.OWCanvasText(self.canvas, str(self.origExamples[col][self.attrLabel]).decode("utf-8"), width*(xpos*2+1.5), 60, alignment=Qt.AlignTop|Qt.AlignHCenter)
125            l = OWQCanvasFuncts.OWCanvasLine(self.canvas, width*.8, 51, width*(len(nums)*2+0.2), 50)
126            l.setZValue(1)
127            if self.normalize:
128                l = OWQCanvasFuncts.OWCanvasLine(self.canvas, width*.8, 49 - height, width*(len(nums)*2+0.2), 49 - height)
129                l.setZValue(1)
130   
131            for i, attr in enumerate(self.selectedAttributes):
132                OWQCanvasFuncts.OWCanvasRectangle(self.canvas, width*(xpos*2+3.5), 50+i*20 - height, 10, 10, pen=QPen(QColor(255, 255, 255), 1, Qt.SolidLine), brushColor=self.discPalette[i])
133                OWQCanvasFuncts.OWCanvasText(self.canvas, self.attributes[attr][0].decode("utf-8"), width*(xpos*2+3.5)+16, 46+i*20 - height)
134
135#    def sendCharts(self, constructor, forceNormal=False):
136#        l = []
137#        if self.examples and len(self.selectedAttributes):
138#            dists, nums, sums = self.getDistsNums(forceNormal)
139#            addLabel = self.attrLabel != _("(No labels)")
140#            l = [(constructor(dist, num),
141#                  str(ex[self.attrLabel]).decode("utf-8") if addLabel else "",
142#                  float(ex["latitude"]), float(ex["longitude"])
143#                 ) for dist, num, ex in zip(dists, nums, self.origExamples)
144#                   if not (ex["latitude"].isSpecial() or ex["longitude"].isSpecial())]
145#           
146#            legend = []
147#            for j in range(len(self.selectedAttributes)):
148#                legend.append((self.discPalette[j], self.attributes[self.selectedAttributes[j]][0]))
149#            self.send("Bars", MapLayer.MapLayer(l, legend, sums))
150#
151#    def sendBars(self):
152#        self.sendCharts(self.constructBar)
153#   
154#    def sendPies(self):
155#        self.sendCharts(self.constructPieChart, True)
156#   
157    # We cannot rely on domain-based context settings since not all attributes from the domain are available
158    # (the widget omits those with unknown values)
159    def settingsFromWidgetCallback(self, handler, context):
160        context.selectedAttributes = [self.attributes[i] for i in self.selectedAttributes]
161
162    def settingsToWidgetCallback(self, handler, context):
163        if hasattr(context, "selectedAttributes"):
164            self.selectedAttributes = [i for i in range(len(self.attributes)) if self.attributes[i] in context.selectedAttributes]
165
166    def findAttributeSubset(self):
167        ex = self.examples[0]
168        for i in range(len(ex)):
169            s = 0
170            for j, e in enumerate(ex[i:]):
171                s += e
172                if s > 1.01:
173                    break
174                if s >= 0.99:
175                    for ex2 in self.examples:
176                        if not 0.99 < sum(ex2[i:j+i+1]) < 1.01:
177                            break
178                    else:
179                        self.selectedAttributes = range(i, j+i+1)
180                        return
181       
182    def setData(self, data):
183        self.closeContext()
184        self.attrLabelCombo.clear()
185        self.sortingCombo.clear()
186        if data==None:
187            self.attributes = []
188            self.examples = None
189            self.origExamples = None
190#            self.btnSendbars.setDisabled(True)
191#            self.btnSendpie.setDisabled(True)
192        else:
193            contIndices = [i for i, attr in enumerate(data.domain.attributes) if attr.varType == orange.Variable.Continuous]
194            usedIndices = [i for i in contIndices if not any(d[i].isSpecial() for d in data)]
195           
196            self.origExamples = data
197            self.examples = [[ex[i] for i in usedIndices] for ex in self.origExamples]
198            self.attributes = [(data.domain.attributes[i].name, orange.Variable.Continuous) for i in usedIndices]
199           
200            self.attrLabelCombo.addItem("(No labels)")
201            self.sortingCombo.addItem("(Original order)")
202            self.sortingCombo.addItem("Total")
203            hasName = None
204            for metavar in [data.domain.getmeta(mykey) for mykey in data.domain.getmetas().keys()]:
205                self.attrLabelCombo.addItem(self.icons[metavar.varType], metavar.name.decode("utf-8"))
206                self.sortingCombo.addItem(self.icons[metavar.varType], metavar.name.decode("utf-8"))
207                if metavar.name.lower() == "name":
208                    hasName = metavar.name
209            for attr in data.domain:
210                self.attrLabelCombo.addItem(self.icons[attr.varType], attr.name.decode("utf-8"))
211                self.sortingCombo.addItem(self.icons[attr.varType], attr.name.decode("utf-8"))
212                if attr.name.lower() == "name":
213                    hasName = attr.name
214            if hasName:
215                self.attrLabel = hasName
216
217#            try:
218#                data[0]["latitude"], data[0]["longitude"]
219#                self.btnSendbars.setDisabled(False)
220#                self.btnSendpie.setDisabled(False)
221#            except:
222#                self.btnSendbars.setDisabled(True)
223#                self.btnSendpie.setDisabled(True)
224                 
225        self.openContext("", data)
226        if self.attributes and len(self.examples) and not self.selectedAttributes:
227            self.findAttributeSubset()
228           
229        self.updateDisplay()
230
231    def sendReport(self):
232        self.startReport("%s" % (self.windowTitle()))
233        self.reportImage(lambda *x: OWChooseImageSizeDlg(self.canvas).saveImage(*x))
Note: See TracBrowser for help on using the repository browser.