source: orange/Orange/OrangeWidgets/Data/OWSelectData.py @ 11764:30121d80d7ee

Revision 11764:30121d80d7ee, 38.6 KB checked in by Ales Erjavec <ales.erjavec@…>, 5 months ago (diff)

Fixed "Select Data" widget layout.

Line 
1import orange
2from OWWidget import *
3import OWGUI
4from orngDataCaching import *
5
6NAME = "Select Data"
7DESCRIPTION = "Selects data instances based on conditions over data features."
8LONG_DESCRIPTION = ""
9ICON = "icons/SelectData.svg"
10PRIORITY = 1150
11AUTHOR = "Peter Juvan"
12AUTHOR_EMAIL = "peter.juvan(@at@)fri.uni-lj.si"
13INPUTS = [("Data", Orange.data.Table, "setData")]
14OUTPUTS = [("Matching Data", Orange.data.Table, Default),
15           ("Unmatched Data", Orange.data.Table, )]
16
17
18class OWSelectData(OWWidget):
19    settingsList = ["updateOnChange", "purgeAttributes", "purgeClasses"]
20    contextHandlers = {"": PerfectDomainContextHandler(fields=["Conditions"],
21                                                       matchValues=2)}
22
23    def __init__(self, parent=None, signalManager=None, name="Select data"):
24        OWWidget.__init__(self, parent, signalManager, name, wantMainArea=0)
25
26        self.inputs = [("Data", ExampleTable, self.setData)]
27        self.outputs = [("Matching Data", ExampleTable, Default),
28                        ("Unmatched Data", ExampleTable)]
29
30        self.name2var = {}   # key: variable name, item: orange.Variable
31        self.Conditions = []
32
33        self.currentVar = None
34        self.NegateCondition = False
35        self.currentOperatorDict = {orange.VarTypes.Continuous: Operator(Operator.operatorsC[0], orange.VarTypes.Continuous),
36                                    orange.VarTypes.Discrete: Operator(Operator.operatorsD[0],orange.VarTypes.Discrete),
37                                    orange.VarTypes.String: Operator(Operator.operatorsS[0], orange.VarTypes.String)}
38        self.Num1 = 0.0
39        self.Num2 = 0.0
40        self.Str1 = ""
41        self.Str2 = ""
42        self.attrSearchText = ""
43        self.currentVals = []
44        self.CaseSensitive = False
45        self.updateOnChange = True
46        self.purgeAttributes = True
47        self.purgeClasses = True
48        self.oldPurgeClasses = True
49
50        self.loadedVarNames = []
51        self.loadedConditions = []
52        self.loadSettings()
53
54        w = QWidget(self)
55        self.controlArea.layout().addWidget(w)
56        grid = QGridLayout()
57        grid.setMargin(0)
58        w.setLayout(grid)
59
60        boxAttrCond = OWGUI.widgetBox(self, '', orientation = QGridLayout(), addToLayout = 0)
61        grid.addWidget(boxAttrCond, 0,0,1,3)
62        glac = boxAttrCond.layout()
63        glac.setColumnStretch(0,2)
64        glac.setColumnStretch(1,1)
65        glac.setColumnStretch(2,2)
66
67        boxAttr = OWGUI.widgetBox(self, 'Attribute', addToLayout = 0)
68        glac.addWidget(boxAttr,0,0)
69        self.lbAttr = OWGUI.listBox(boxAttr, self, callback = self.lbAttrChange)
70
71        self.leSelect = OWGUI.lineEdit(boxAttr, self, "attrSearchText", label = "Search: ", orientation = "horizontal", callback = self.setLbAttr, callbackOnType = 1)
72
73        boxOper = OWGUI.widgetBox(self, 'Operator')
74        # operators 0: empty
75        self.lbOperatosNone = OWGUI.listBox(boxOper, self)
76        # operators 1: discrete
77        self.lbOperatorsD = OWGUI.listBox(boxOper, self, callback = self.lbOperatorsChange)
78        self.lbOperatorsD.hide()
79        self.lbOperatorsD.addItems(Operator.operatorsD + [Operator.operatorDef])
80        # operators 2: continuous
81        self.lbOperatorsC = OWGUI.listBox(boxOper, self, callback = self.lbOperatorsChange)
82        self.lbOperatorsC.hide()
83        self.lbOperatorsC.addItems(Operator.operatorsC + [Operator.operatorDef])
84        # operators 6: string
85        self.lbOperatorsS = OWGUI.listBox(boxOper, self, callback = self.lbOperatorsChange)
86        self.lbOperatorsS.hide()
87        self.lbOperatorsS.addItems(Operator.operatorsS + [Operator.operatorDef])
88        self.lbOperatorsDict = {0: self.lbOperatosNone,
89                                orange.VarTypes.Continuous: self.lbOperatorsC,
90                                orange.VarTypes.Discrete: self.lbOperatorsD,
91                                orange.VarTypes.String: self.lbOperatorsS}
92
93        glac.addWidget(boxOper,0,1)
94        self.cbNot = OWGUI.checkBox(boxOper, self, "NegateCondition", "Negate")
95
96        self.boxIndices = {}
97        boxValues = OWGUI.widgetBox(self, "Values", addToLayout=0)
98        self.valuesStack = QStackedWidget()
99        boxValues.layout().addWidget(self.valuesStack)
100        glac.addWidget(boxValues, 0, 2)
101
102        # values 0: empty
103        boxVal = OWGUI.widgetBox(self.valuesStack, addToLayout = 0)
104        self.boxIndices[0] = boxVal
105        self.valuesStack.addWidget(boxVal)
106
107        # values 1: discrete
108        boxVal = OWGUI.widgetBox(self.valuesStack, addToLayout = 0)
109        self.boxIndices[orange.VarTypes.Discrete] = boxVal
110        self.valuesStack.addWidget(boxVal)
111        self.lbVals = OWGUI.listBox(boxVal, self, callback = self.lbValsChange)
112
113        # values 2: continuous between num and num
114        boxVal = OWGUI.widgetBox(self.valuesStack, addToLayout = 0)
115        self.boxIndices[orange.VarTypes.Continuous] = boxVal
116        self.valuesStack.addWidget(boxVal)
117        self.leNum1 = OWGUI.lineEdit(boxVal, self, "Num1", validator=QDoubleValidator(self))
118        self.lblAndCon = OWGUI.widgetLabel(boxVal, "and")
119        self.leNum2 = OWGUI.lineEdit(boxVal, self, "Num2", validator=QDoubleValidator(self))
120        boxAttrStat = OWGUI.widgetBox(boxVal, "Statistics")
121        self.lblMin = OWGUI.widgetLabel(boxAttrStat, "Min: ")
122        self.lblAvg = OWGUI.widgetLabel(boxAttrStat, "Avg: ")
123        self.lblMax = OWGUI.widgetLabel(boxAttrStat, "Max: ")
124        self.lblDefined = OWGUI.widgetLabel(boxAttrStat, "Defined for ---- examples")
125        OWGUI.rubber(boxAttrStat)
126
127        # values 6: string between str and str
128        boxVal = OWGUI.widgetBox(self.valuesStack, addToLayout=0)
129        self.boxIndices[orange.VarTypes.String] = boxVal
130        self.valuesStack.addWidget(boxVal)
131        self.leStr1 = OWGUI.lineEdit(boxVal, self, "Str1")
132        self.lblAndStr = OWGUI.widgetLabel(boxVal, "and")
133        self.leStr2 = OWGUI.lineEdit(boxVal, self, "Str2")
134        self.cbCaseSensitive = OWGUI.checkBox(boxVal, self, "CaseSensitive", "Case sensitive")
135
136        self.boxButtons = OWGUI.widgetBox(self, orientation = "horizontal")
137        grid.addWidget(self.boxButtons, 1,0,1,3)
138        self.btnNew = OWGUI.button(self.boxButtons, self, "Add", self.OnNewCondition)
139        self.btnUpdate = OWGUI.button(self.boxButtons, self, "Modify", self.OnUpdateCondition)
140        self.btnRemove = OWGUI.button(self.boxButtons, self, "Remove", self.OnRemoveCondition)
141        self.btnOR = OWGUI.button(self.boxButtons, self, "OR", self.OnDisjunction)
142        self.btnMoveUp = OWGUI.button(self.boxButtons, self, "Move Up", self.btnMoveUpClicked)
143        self.btnMoveDown = OWGUI.button(self.boxButtons, self, "Move Down", self.btnMoveDownClicked)
144        self.btnRemove.setEnabled(False)
145        self.btnUpdate.setEnabled(False)
146        self.btnMoveUp.setEnabled(False)
147        self.btnMoveDown.setEnabled(False)
148
149
150        boxCriteria = OWGUI.widgetBox(self, 'Data Selection Criteria', addToLayout = 0)
151        grid.addWidget(boxCriteria, 2,0,1,3)
152        self.criteriaTable = QTableWidget(boxCriteria)
153        boxCriteria.layout().addWidget(self.criteriaTable)
154        self.criteriaTable.setShowGrid(False)
155        self.criteriaTable.setSelectionMode(QTableWidget.SingleSelection)
156        self.criteriaTable.setColumnCount(2)
157        self.criteriaTable.verticalHeader().setClickable(False)
158        #self.criteriaTable.verticalHeader().setResizeEnabled(False,-1)
159        self.criteriaTable.horizontalHeader().setClickable(False)
160        self.criteriaTable.setHorizontalHeaderLabels(["Active", "Condition"])
161        self.criteriaTable.resizeColumnToContents(0)
162        self.criteriaTable.setSelectionBehavior(QAbstractItemView.SelectRows)
163        self.criteriaTable.horizontalHeader().setResizeMode(1, QHeaderView.Stretch)
164        self.connect(self.criteriaTable, SIGNAL('cellClicked(int, int)'), self.currentCriteriaChange)
165
166        boxDataIn = OWGUI.widgetBox(self, 'Data In', addToLayout = 0)
167        grid.addWidget(boxDataIn, 3,0)
168        self.dataInExamplesLabel = OWGUI.widgetLabel(boxDataIn, "num examples")
169        self.dataInAttributesLabel = OWGUI.widgetLabel(boxDataIn, "num attributes")
170        OWGUI.rubber(boxDataIn)
171
172        boxDataOut = OWGUI.widgetBox(self, 'Data Out', addToLayout = 0)
173        grid.addWidget(boxDataOut, 3,1)
174        self.dataOutExamplesLabel = OWGUI.widgetLabel(boxDataOut, "num examples")
175        self.dataOutAttributesLabel = OWGUI.widgetLabel(boxDataOut, "num attributes")
176        OWGUI.rubber(boxDataOut)
177
178        boxSettings = OWGUI.widgetBox(self, 'Commit', addToLayout = 0)
179        grid.addWidget(boxSettings, 3,2)
180        cb = OWGUI.checkBox(boxSettings, self, "purgeAttributes", "Remove unused values/attributes", box=None, callback=self.OnPurgeChange)
181        self.purgeClassesCB = OWGUI.checkBox(OWGUI.indentedBox(boxSettings, sep=OWGUI.checkButtonOffsetHint(cb)), self, "purgeClasses", "Remove unused classes", callback=self.OnPurgeChange)
182        OWGUI.checkBox(boxSettings, self, "updateOnChange", "Commit on change", box=None)
183        btnUpdate = OWGUI.button(boxSettings, self, "Commit", self.setOutput, default=True)
184
185        self.icons = self.createAttributeIconDict()
186        self.setData(None)
187        self.lbOperatorsD.setCurrentRow(0)
188        self.lbOperatorsC.setCurrentRow(0)
189        self.lbOperatorsS.setCurrentRow(0)
190        self.resize(500,661)
191        grid.setRowStretch(0, 10)
192        grid.setRowStretch(2, 10)
193
194
195    def setData(self, data):
196        self.closeContext("")
197        self.data = data
198        self.bas = getCached(data, orange.DomainBasicAttrStat, (data,))
199               
200        self.name2var = {}
201        self.Conditions = []
202
203        if self.data:
204            optmetas = self.data.domain.getmetas(True).values()
205            optmetas.sort(lambda x,y: cmp(x.name, y.name))
206            self.varList = self.data.domain.variables.native() + self.data.domain.getmetas(False).values() + optmetas
207            for v in self.varList:
208                self.name2var[v.name] = v
209            self.setLbAttr()
210            self.boxButtons.setEnabled(True)
211        else:
212            self.varList = []
213            self.currentVar = None
214
215            self.lbAttr.clear()
216            self.leSelect.clear()
217            self.boxButtons.setEnabled(False)
218
219        self.openContext("", data)
220        self.synchronizeTable()
221        self.criteriaTable.setCurrentCell(-1,1)
222
223        self.updateOperatorStack()
224        self.updateValuesStack()
225        self.updateInfoIn(self.data)
226        self.setOutput()
227
228
229    def setLbAttr(self):
230        self.lbAttr.clear()
231        if not self.attrSearchText:
232            for v in self.varList:
233                self.lbAttr.addItem(QListWidgetItem(self.icons[v.varType], v.name))
234        else:
235            flen = len(self.attrSearchText)
236            for v in self.varList:
237                if v.name[:flen].lower() == self.attrSearchText.lower():
238                    self.lbAttr.addItem(QListWidgetItem(self.icons[v.varType], v.name))
239
240        if self.lbAttr.count():
241            self.lbAttr.item(0).setSelected(True)
242        else:
243            self.lbAttrChange()
244
245
246    def setOutputIf(self):
247        if self.updateOnChange:
248            self.setOutput()
249
250    def setOutput(self):
251        matchingOutput = self.data
252        nonMatchingOutput = None
253        hasClass = False
254        if self.data:
255            hasClass = bool(self.data.domain.classVar)
256            filterList = self.getFilterList(self.data.domain, self.Conditions, enabledOnly=True)
257            if len(filterList)>0:
258                filter = orange.Filter_disjunction([orange.Filter_conjunction(l) for l in filterList])
259            else:
260                filter = orange.Filter_conjunction([]) # a filter that does nothing
261            matchingOutput = filter(self.data, 1)
262            matchingOutput.name = self.data.name
263            nonMatchingOutput = filter(self.data, 1, negate=1)
264            nonMatchingOutput.name = self.data.name
265
266            if self.purgeAttributes or self.purgeClasses:
267                remover = orange.RemoveUnusedValues(removeOneValued=True)
268
269                newDomain = remover(matchingOutput, 0, True, self.purgeClasses)
270                if newDomain != matchingOutput.domain:
271                    matchingOutput = orange.ExampleTable(newDomain, matchingOutput)
272
273                newDomain = remover(nonMatchingOutput, 0, True, self.purgeClasses)
274                if newDomain != nonMatchingOutput.domain:
275                    nonmatchingOutput = orange.ExampleTable(newDomain, nonMatchingOutput)
276
277        self.send("Matching Data", matchingOutput)
278        self.send("Unmatched Data", nonMatchingOutput)
279
280        self.updateInfoOut(matchingOutput)
281
282
283    def getFilterList(self, domain, conditions, enabledOnly):
284        """Returns list of lists of orange filters, e.g. [[f1,f2],[f3]].
285        OR is always enabled (with no respect to cond.enabled)
286        """
287        fdList = [[]]
288        for cond in conditions:
289            if cond.type == "OR":
290                fdList.append([])
291            elif cond.enabled or not enabledOnly:
292                fdList[-1].append(cond.operator.getFilter(domain, cond.varName, cond.val1, cond.val2, cond.negated, cond.caseSensitive))
293        return fdList
294
295
296    def lbAttrChange(self):
297        if self.lbAttr.selectedItems() == []: return
298        text = str(self.lbAttr.selectedItems()[0].text())
299        prevVar = self.currentVar
300        if prevVar:
301            prevVarType = prevVar.varType
302            prevVarName = prevVar.name
303        else:
304            prevVarType = None
305            prevVarName = None
306        try:
307            self.currentVar = self.data.domain[text]
308        except:
309            self.currentVar = None
310        if self.currentVar:
311            currVarType = self.currentVar.varType
312            currVarName = self.currentVar.name
313        else:
314            currVarType = None
315            currVarName = None
316        if currVarType != prevVarType:
317            self.updateOperatorStack()
318        if currVarName != prevVarName:
319            self.updateValuesStack()
320
321
322    def lbOperatorsChange(self):
323        """Updates value stack, only if necessary.
324        """
325        if self.currentVar:
326            varType = self.currentVar.varType
327            selItems = self.lbOperatorsDict[varType].selectedItems()
328            if not selItems: return
329            self.currentOperatorDict[varType] = Operator(str(selItems[0].text()), varType)
330            self.updateValuesStack()
331
332
333    def lbValsChange(self):
334        """Updates list of selected discrete values (self.currentVals).
335        """
336        self.currentVals = []
337        for i in range(0, self.lbVals.count()):
338            if self.lbVals.item(i).isSelected():
339                self.currentVals.append(str(self.lbVals.item(i).text()))
340
341
342    def OnPurgeChange(self):
343        if self.purgeAttributes:
344            if not self.purgeClassesCB.isEnabled():
345                self.purgeClassesCB.setEnabled(True)
346                self.purgeClasses = self.oldPurgeClasses
347        else:
348            if self.purgeClassesCB.isEnabled():
349                self.purgeClassesCB.setEnabled(False)
350                self.oldPurgeClasses = self.purgeClasses
351                self.purgeClasses = False
352
353        self.setOutputIf()
354
355
356    def OnNewCondition(self):
357        cond = self.getConditionFromSelection()
358        if not cond:
359            return
360
361        where = min(self.criteriaTable.currentRow() + 1, self.criteriaTable.rowCount())
362        self.Conditions.insert(where, cond)
363        self.synchronizeTable()
364        self.criteriaTable.setCurrentCell(where, 1)
365        self.setOutputIf()
366        self.leSelect.clear()
367
368
369    def OnUpdateCondition(self):
370        row = self.criteriaTable.currentRow()
371        if row < 0:
372            return
373        cond = self.getConditionFromSelection()
374        if not cond:
375            return
376        self.Conditions[row] = cond
377        self.synchronizeTable()
378        self.setOutputIf()
379        self.leSelect.clear()
380
381
382    def OnRemoveCondition(self):
383        """Removes current condition table row, shifts rows up, updates conditions and sends out new data.
384        """
385        # update self.Conditions
386        currRow = self.criteriaTable.currentRow()
387        if currRow < 0:
388            return
389        self.Conditions.pop(currRow)
390        self.synchronizeTable()
391        self.criteriaTable.setCurrentCell(min(currRow, self.criteriaTable.rowCount()-1), 1)
392        self.setOutputIf()
393
394
395    def OnDisjunction(self):
396        """Updates conditions and condition table, sends out new data.
397        """
398        # update self.Conditions
399        where = min(self.criteriaTable.currentRow() + 1, self.criteriaTable.rowCount())
400        self.Conditions.insert(where, Condition(True, "OR"))
401        self.synchronizeTable()
402        self.criteriaTable.setCurrentCell(where, 1)
403        self.setOutputIf()
404
405
406    def btnMoveUpClicked(self):
407        """Moves the selected condition one row up.
408        """
409        currRow = self.criteriaTable.currentRow()
410        numRows = self.criteriaTable.rowCount()
411        if currRow < 1 or currRow >= numRows:
412            return
413        self.Conditions = self.Conditions[:currRow-1] + [self.Conditions[currRow], self.Conditions[currRow-1]] + self.Conditions[currRow+1:]
414        self.synchronizeTable()
415        self.criteriaTable.setCurrentCell(max(0, currRow-1), 1)
416        self.updateMoveButtons()
417        self.setOutputIf()
418
419
420    def btnMoveDownClicked(self):
421        """Moves the selected condition one row down.
422        """
423        currRow = self.criteriaTable.currentRow()
424        numRows = self.criteriaTable.rowCount()
425        if currRow < 0 or currRow >= numRows-1:
426            return
427        self.Conditions = self.Conditions[:currRow] + [self.Conditions[currRow+1], self.Conditions[currRow]] + self.Conditions[currRow+2:]
428        self.synchronizeTable()
429        self.criteriaTable.setCurrentCell(min(currRow+1, self.criteriaTable.rowCount()-1), 1)
430        self.updateMoveButtons()
431        self.setOutputIf()
432
433
434    def currentCriteriaChange(self, row, col):
435        """Handles current row change in criteria table;
436        select attribute and operator, and set values according to the selected condition.
437        """
438        if row < 0:
439            return
440        cond = self.Conditions[row]
441        if cond.type != "OR":
442            # attribute
443            lbItems = self.lbAttr.findItems(cond.varName, Qt.MatchExactly)
444            if lbItems != []:
445                self.lbAttr.setCurrentItem(lbItems[0])
446            # not
447            self.cbNot.setChecked(cond.negated)
448            # operator
449            for vt,lb in self.lbOperatorsDict.items():
450                if vt == self.name2var[cond.varName].varType:
451                    lb.show()
452                else:
453                    lb.hide()
454            lbItems = self.lbOperatorsDict[self.name2var[cond.varName].varType].findItems(str(cond.operator), Qt.MatchExactly)
455            if lbItems != []:
456                self.lbOperatorsDict[self.name2var[cond.varName].varType].setCurrentItem(lbItems[0])
457            # values
458            self.valuesStack.setCurrentWidget(self.boxIndices[self.name2var[cond.varName].varType])
459            if self.name2var[cond.varName].varType == orange.VarTypes.Continuous:
460                self.leNum1.setText(str(cond.val1))
461                if cond.operator.isInterval:
462                    self.leNum2.setText(str(cond.val2))
463            elif self.name2var[cond.varName].varType == orange.VarTypes.String:
464                self.leStr1.setText(str(cond.val1))
465                if cond.operator.isInterval:
466                    self.leStr2.setText(str(cond.val2))
467                self.cbCaseSensitive.setChecked(cond.caseSensitive)
468            elif self.name2var[cond.varName].varType == orange.VarTypes.Discrete:
469                self.lbVals.clearSelection()
470                for val in cond.val1:
471                    lbItems = self.lbVals.findItems(val, Qt.MatchExactly)
472                    for item in lbItems:
473                        item.setSelected(1)
474        self.updateMoveButtons()
475
476
477    def criteriaActiveChange(self, condition, active):
478        """Handles clicks on criteria table checkboxes, send out new data.
479        """
480        condition.enabled = active
481        # update the numbers of examples that matches "OR" filter
482        self.updateFilteredDataLens(condition)
483        # send out new data
484        if self.updateOnChange:
485            self.setOutput()
486
487
488    ############################################################################################################################################################
489    ## Interface state management - updates interface elements based on selection in list boxes ################################################################
490    ############################################################################################################################################################
491
492    def updateMoveButtons(self):
493        """enable/disable Move Up/Down buttons
494        """
495        row = self.criteriaTable.currentRow()
496        numRows = self.criteriaTable.rowCount()
497        if row > 0:
498            self.btnMoveUp.setEnabled(True)
499        else:
500            self.btnMoveUp.setEnabled(False)
501        if row < numRows-1:
502            self.btnMoveDown.setEnabled(True)
503        else:
504            self.btnMoveDown.setEnabled(False)
505
506
507    def updateOperatorStack(self):
508        """Raises listbox with appropriate operators.
509        """
510        if self.currentVar:
511            varType = self.currentVar.varType
512            self.btnNew.setEnabled(True)
513        else:
514            varType = 0
515            self.btnNew.setEnabled(False)
516        for vt,lb in self.lbOperatorsDict.items():
517            if vt == varType:
518                lb.show()
519                try:
520                    lb.setCurrentRow(self.data.domain.isOptionalMeta(self.currentVar) and lb.count() - 1)
521                except:
522                    lb.setCurrentRow(0)
523            else:
524                lb.hide()
525
526
527    def updateValuesStack(self):
528        """Raises appropriate widget for values from stack,
529        fills listBox for discrete attributes,
530        shows statistics for continuous attributes.
531        """
532        if self.currentVar:
533            varType = self.currentVar.varType
534        else:
535            varType = 0
536        currentOper = self.currentOperatorDict.get(varType,None)
537        if currentOper:
538            # raise widget
539            self.valuesStack.setCurrentWidget(self.boxIndices[currentOper.varType])
540            if currentOper.varType==orange.VarTypes.Discrete:
541                # store selected discrete values, refill values list box, set single/multi selection mode, restore selected item(s)
542                selectedItemNames = []
543                for i in range(self.lbVals.count()):
544                    if self.lbVals.item(i).isSelected():
545                        selectedItemNames.append(str(self.lbVals.item(i).text()))
546                self.lbVals.clear()
547                curVarValues = []
548                for value in self.currentVar:
549                    curVarValues.append(str(value))
550                curVarValues.sort()
551                for value in curVarValues:
552                    self.lbVals.addItem(str(value))
553                if currentOper.isInterval:
554                    self.lbVals.setSelectionMode(QListWidget.MultiSelection)
555                else:
556                    self.lbVals.setSelectionMode(QListWidget.SingleSelection)
557                isSelected = False
558                for name in selectedItemNames:
559                    items = self.lbVals.findItems(name, Qt.MatchExactly)
560                    for item in items:
561                        item.setSelected(1)
562                        isSelected = True
563                        if not currentOper.isInterval:
564                            break
565                if not isSelected:
566                    if self.lbVals.count() > 0:
567                        self.lbVals.item(0).setSelected(True)
568                    else:
569                        self.currentVals = []
570            elif currentOper.varType==orange.VarTypes.Continuous:
571                # show / hide "and" label and 2nd line edit box
572                if currentOper.isInterval:
573                    self.lblAndCon.show()
574                    self.leNum2.show()
575                else:
576                    self.lblAndCon.hide()
577                    self.leNum2.hide()
578                # display attribute statistics
579                if self.currentVar in self.data.domain.variables:
580                    basstat = self.bas[self.currentVar]
581                else:
582                    basstat = orange.BasicAttrStat(self.currentVar, self.data)
583
584                if basstat.n:
585                    min, avg, max = ["%.3f" % x for x in (basstat.min, basstat.avg, basstat.max)]
586                    self.Num1, self.Num2 = basstat.min, basstat.max
587                else:
588                    min = avg = max = "-"
589                    self.Num1 = self.Num2 = 0
590
591                self.lblMin.setText("Min: %s" % min)
592                self.lblAvg.setText("Avg: %s" % avg)
593                self.lblMax.setText("Max: %s" % max)
594                self.lblDefined.setText("Defined for %i example(s)" % basstat.n)
595
596            elif currentOper.varType==orange.VarTypes.String:
597                # show / hide "and" label and 2nd line edit box
598                if currentOper.isInterval:
599                    self.lblAndStr.show()
600                    self.leStr2.show()
601                else:
602                    self.lblAndStr.hide()
603                    self.leStr2.hide()
604        else:
605            self.valuesStack.setCurrentWidget(self.boxIndices[0])
606
607
608    def getConditionFromSelection(self):
609        """Returns a condition according to the currently selected attribute / operator / values.
610        """
611        if self.currentVar:
612            if self.currentVar.varType == orange.VarTypes.Continuous:
613                try:
614                    val1 = float(self.Num1)
615                    val2 = float(self.Num2)
616                except ValueError:
617                    return
618            elif self.currentVar.varType == orange.VarTypes.String:
619                val1 = self.Str1
620                val2 = self.Str2
621            elif self.currentVar.varType == orange.VarTypes.Discrete:
622                val1 = self.currentVals
623                if not val1:
624                    return
625                val2 = None
626            if not self.currentOperatorDict[self.currentVar.varType].isInterval:
627                val2 = None
628            return Condition(True, "AND", self.currentVar.name, self.currentOperatorDict[self.currentVar.varType], self.NegateCondition, val1, val2, self.CaseSensitive)
629
630
631    def synchronizeTable(self):
632#        for row in range(len(self.Conditions), self.criteriaTable.rowCount()):
633#            self.criteriaTable.clearCellWidget(row,0)
634#            self.criteriaTable.clearCell(row,1)
635
636        currentRow = self.criteriaTable.currentRow()
637        self.criteriaTable.clearContents()
638        self.criteriaTable.setRowCount(len(self.Conditions))
639
640        for row, cond in enumerate(self.Conditions):
641            if cond.type == "OR":
642                cw = QLabel("", self)
643            else:
644                cw = QCheckBox(str(len(cond.operator.getFilter(self.data.domain, cond.varName, cond.val1, cond.val2, cond.negated, cond.caseSensitive)(self.data))), self)
645#                cw.setChecked(cond.enabled)
646                self.connect(cw, SIGNAL("toggled(bool)"), lambda val, cond=cond: self.criteriaActiveChange(cond, val))
647
648            self.criteriaTable.setCellWidget(row, 0, cw)
649# This is a fix for Qt bug (4.3). When Qt is fixed, the setChecked above should suffice
650# but now it unchecks the checkbox as it is inserted
651            if cond.type != "OR":
652                cw.setChecked(cond.enabled)
653
654            # column 1
655            if cond.type == "OR":
656                txt = "OR"
657            else:
658                soper = str(cond.operator)
659                if cond.negated and soper in Operator.negations:
660                    txt = "'%s' %s " % (cond.varName, Operator.negations[soper])
661                else:
662                    txt = (cond.negated and "NOT " or "") + "'%s' %s " % (cond.varName, soper)
663                if cond.operator != Operator.operatorDef:
664                    if cond.operator.varType == orange.VarTypes.Discrete:
665                        if cond.operator.isInterval:
666                            if len(cond.val1) > 0:
667                                txt += "["
668                                for name in cond.val1:
669                                    txt += "%s, " % name
670                                txt = txt[0:-2] + "]"
671                            else:
672                                txt += "[]"
673                        else:
674                            txt += cond.val1[0]
675                    elif cond.operator.varType == orange.VarTypes.String:
676                        if cond.caseSensitive:
677                            cs = " (C)"
678                        else:
679                            cs = ""
680                        if cond.operator.isInterval:
681                            txt += "'%s'%s and '%s'%s" % (cond.val1, cs, cond.val2, cs)
682                        else:
683                            txt += "'%s'%s" % (cond.val1, cs)
684                    elif cond.operator.varType == orange.VarTypes.Continuous:
685                        if cond.operator.isInterval:
686                            txt += str(cond.val1) + " and " + str(cond.val2)
687                        else:
688                            txt += str(cond.val1)
689
690            OWGUI.tableItem(self.criteriaTable, row, 1, txt)
691
692        self.criteriaTable.setCurrentCell(max(currentRow, len(self.Conditions) - 1), 0)
693        self.criteriaTable.resizeRowsToContents()
694        self.updateFilteredDataLens()
695
696        en = len(self.Conditions)
697        self.btnUpdate.setEnabled(en)
698        self.btnRemove.setEnabled(en)
699        self.updateMoveButtons()
700
701
702    def updateFilteredDataLens(self, cond=None):
703        """Updates the number of examples that match individual conditions in criteria table.
704        If cond is given, updates the given row and the corresponding OR row;
705        if cond==None, updates the number of examples in OR rows.
706        """
707        if cond:
708            condIdx = self.Conditions.index(cond)
709            # idx1: the first non-OR condition above the clicked condition
710            # idx2: the first OR condition below the clicked condition
711            idx1 = 0
712            idx2 = len(self.Conditions)
713            for i in range(condIdx,idx1-1,-1):
714                if self.Conditions[i].type == "OR":
715                    idx1 = i+1
716                    break
717            for i in range(condIdx+1,idx2):
718                if self.Conditions[i].type == "OR":
719                    idx2 = i
720                    break
721            fdListAll = self.getFilterList(self.data.domain, self.Conditions[idx1:idx2], enabledOnly=False)
722            fdListEnabled = self.getFilterList(self.data.domain, self.Conditions[idx1:idx2], enabledOnly=True)
723            # if we click on the row which has a preceeding OR: update OR at index idx1-1
724            if idx1 > 0:
725                self.criteriaTable.cellWidget(idx1-1,0).setText(str(len(orange.Filter_conjunction(fdListEnabled[0])(self.data))))
726            # update the clicked row
727            self.criteriaTable.cellWidget(condIdx,0).setText(str(len(fdListAll[0][condIdx-idx1](self.data))))
728
729        elif len(self.Conditions) > 0:
730            # update all "OR" rows
731            fdList = self.getFilterList(self.data.domain, self.Conditions, enabledOnly=True)
732            idx = 1
733            for row,cond in enumerate(self.Conditions):
734                if cond.type == "OR":
735                    self.criteriaTable.cellWidget(row,0).setText(str(len(orange.Filter_conjunction(fdList[idx])(self.data))))
736                    idx += 1
737
738
739    def updateInfoIn(self, data):
740        """Updates data in info box.
741        """
742        if data:
743            varList = data.domain.variables.native() + data.domain.getmetas().values()
744            self.dataInAttributesLabel.setText("%s attribute%s" % self.sp(varList))
745            self.dataInExamplesLabel.setText("%s example%s" % self.sp(data))
746        else:
747            self.dataInExamplesLabel.setText("No examples.")
748            self.dataInAttributesLabel.setText("No attributes.")
749
750
751    def updateInfoOut(self, data):
752        """Updates data out info box.
753        """
754        if data:
755            varList = data.domain.variables.native() + data.domain.getmetas().values()
756            self.dataOutAttributesLabel.setText("%s attribute%s" % self.sp(varList))
757            self.dataOutExamplesLabel.setText("%s example%s" % self.sp(data))
758        else:
759            self.dataOutExamplesLabel.setText("No examples.")
760            self.dataOutAttributesLabel.setText("No attributes.")
761
762
763    ############################################################################################################################################################
764    ## Utility functions #######################################################################################################################################
765    ############################################################################################################################################################
766
767    def sp(self, l, capitalize=True):
768        """Input: list; returns tupple (str(len(l)), "s"/"")
769        """
770        n = len(l)
771        if n == 0:
772            if capitalize:
773                return "No", "s"
774            else:
775                return "no", "s"
776        elif n == 1:
777            return str(n), ''
778        else:
779            return str(n), 's'
780
781
782    def prinConditions(self):
783        """For debugging only.
784        """
785        print "idx\tE\ttype\tattr\toper\tneg\tval1\tval2\tcs"
786        for i,cond in enumerate(self.Conditions):
787            if cond.type == "OR":
788                print "%i\t%i\t%s" % (i+1, int(cond.enabled),cond.type)
789            else:
790                print "%i\t%i\t%s\t%s\t%s\t%i\t%s\t%s\t%i" % (i+1,
791                int(cond.enabled),cond.type,cond.varName,str(cond.operator),
792                int(cond.negated),str(cond.val1),str(cond.val2),int(cond.caseSensitive))
793               
794    def sendReport(self):
795        self.reportSettings("Output", [("Remove unused values/attributes", self.purgeAttributes),
796                                       ("Remove unused classes", self.purgeClasses)])
797        text = "<table>\n<th>Attribute</th><th>Condition</th><th>Value</th>/n"
798        for i, cond in enumerate(self.Conditions):
799            if cond.type == "OR":
800                text += "<tr><td span=3>\"OR\"</td></tr>\n"
801            else:
802                text += "<tr><td>%s</td><td>%s</td><td>%s</td></tr>\n" % (cond.varName, repr(cond.operator), cond.val1)
803           
804        text += "</table>" 
805        import OWReport
806        self.reportSection("Conditions")
807        self.reportRaw(OWReport.reportTable(self.criteriaTable))
808#        self.reportTable("Conditions", self.criteriaTable)
809
810
811class Condition:
812    def __init__(self, enabled, type, attribute = None, operator = None, negate = False, value1 = None, value2 = None, caseSensitive = False):
813        self.enabled = enabled                  # True/False
814        self.type = type                        # "AND"/"OR"
815        self.varName = attribute                # orange.Variable
816        self.operator = operator                # Operator
817        self.negated = negate                   # True/False
818        self.val1 = value1                      # string/float
819        self.val2 = value2                      # string/float
820        self.caseSensitive = caseSensitive      # True/False
821
822
823class Operator:
824    operatorsD = staticmethod(["equals","in"])
825    operatorsC = staticmethod(["=","<","<=",">",">=","between","outside"])
826    operatorsS = staticmethod(["=","<","<=",">",">=","contains","begins with","ends with","between","outside"])
827    operatorDef = staticmethod("is defined")
828    getOperators = staticmethod(lambda: Operator.operatorsD + Operator.operatorsS + [Operator.operatorDef])
829   
830    negations = {"equals": "does not equal", "in": "is not in",
831                 "between": "not between", "outside": "not outside",
832                 "contains": "does not contain", "begins with": "does not begin with", "ends with": "does not end with",
833                 "is defined": "is undefined"}
834
835    _operFilter = {"=":orange.Filter_values.Equal,
836                   "<":orange.Filter_values.Less,
837                   "<=":orange.Filter_values.LessEqual,
838                   ">":orange.Filter_values.Greater,
839                   ">=":orange.Filter_values.GreaterEqual,
840                   "between":orange.Filter_values.Between,
841                   "outside":orange.Filter_values.Outside,
842                   "contains":orange.Filter_values.Contains,
843                   "begins with":orange.Filter_values.BeginsWith,
844                   "ends with":orange.Filter_values.EndsWith}
845
846    def __init__(self, operator, varType):
847        """Members: operator, varType, isInterval.
848        """
849        assert operator in Operator.getOperators(), "Unknown operator: %s" % str(operator)
850        self.operator = operator
851        self.varType = varType
852        self.isInterval = False
853        if operator in Operator.operatorsC and Operator.operatorsC.index(operator) > 4 \
854           or operator in Operator.operatorsD and Operator.operatorsD.index(operator) > 0 \
855           or operator in Operator.operatorsS and Operator.operatorsS.index(operator) > 7:
856            self.isInterval = True
857
858    def __eq__(self, other):
859        assert other in Operator.getOperators()
860        return  self.operator == other
861
862    def __ne__(self, other):
863        assert other in Operator.getOperators()
864        return self.operator != other
865
866    def __repr__(self):
867        return str(self.operator)
868
869    def __strr__(self):
870        return str(self.operator)
871
872    def getFilter(self, domain, variable, value1, value2, negate, caseSensitive):
873        """Returns orange filter.
874        """
875        if self.operator == Operator.operatorDef:
876            try:
877                id = domain.index(variable)
878            except:
879                error("Error: unknown attribute (%s)." % variable)
880
881            if id >= 0:
882                f = orange.Filter_isDefined(domain=domain)
883                for v in domain.variables:
884                    f.check[v] = 0
885                f.check[variable] = 1
886            else: # variable is a meta
887                    f = orange.Filter_hasMeta(id = domain.index(variable))
888        elif self.operator in Operator.operatorsD:
889            f = orange.Filter_values(domain=domain)
890            f[variable] = value1
891        else:
892            f = orange.Filter_values(domain=domain)
893            if value2:
894                f[variable] = (Operator._operFilter[str(self.operator)], value1, value2)
895            else:
896                f[variable] = (Operator._operFilter[str(self.operator)], value1)
897            if self.varType == orange.VarTypes.String:
898                f[variable].caseSensitive = caseSensitive
899        f.negate = negate
900        return f
901
902
903
904if __name__=="__main__":
905    import sys
906
907    data = orange.ExampleTable('brown-selected.tab')
908    # add meta attribute
909    #data.domain.addmeta(orange.newmetaid(), orange.StringVariable("workclass_name"))
910
911    a=QApplication(sys.argv)
912    ow=OWSelectData()
913    ow.show()
914    ow.setData(data)
915    a.exec_()
916
Note: See TracBrowser for help on using the repository browser.