source: orange/orange/OrangeWidgets/Data/OWDataTable.py @ 5956:8876698df79b

Revision 5956:8876698df79b, 15.4 KB checked in by gregor <gregor@…>, 4 years ago (diff)
  • sizes of the cells should be now appropriate also in case of float vals
Line 
1"""
2<name>Data Table</name>
3<description>Shows data in a spreadsheet.</description>
4<icon>icons/DataTable.png</icon>
5<priority>100</priority>
6<contact>Peter Juvan (peter.juvan@fri.uni-lj.si)</contact>
7"""
8
9# OWDataTable.py
10#
11# wishes:
12# ignore attributes, filter examples by attribute values, do
13# all sorts of preprocessing (including discretization) on the table,
14# output a new table and export it in variety of formats.
15
16from OWWidget import *
17import OWGUI
18import math
19from orngDataCaching import *
20
21##############################################################################
22
23OrangeValueRole = Qt.UserRole + 1
24
25class OWDataTable(OWWidget):
26    settingsList = ["showDistributions", "showMeta", "distColorRgb", "showAttributeLabels"]
27
28    def __init__(self, parent=None, signalManager = None):
29        OWWidget.__init__(self, parent, signalManager, "Data Table")
30
31        self.inputs = [("Examples", ExampleTable, self.dataset, Multiple + Default)]
32        self.outputs = []
33
34        self.data = {}          # key: id, value: ExampleTable
35        self.showMetas = {}     # key: id, value: (True/False, columnList)
36        self.showMeta = 1
37        self.showAttributeLabels = 1
38        self.showDistributions = 1
39        self.distColorRgb = (220,220,220, 255)
40        self.distColor = QColor(*self.distColorRgb)
41        self.locale = QLocale()
42
43        self.loadSettings()
44
45        # info box
46        infoBox = OWGUI.widgetBox(self.controlArea, "Info")
47        self.infoEx = OWGUI.widgetLabel(infoBox, 'No data on input.')
48        self.infoMiss = OWGUI.widgetLabel(infoBox, ' ')
49        OWGUI.widgetLabel(infoBox, ' ')
50        self.infoAttr = OWGUI.widgetLabel(infoBox, ' ')
51        self.infoMeta = OWGUI.widgetLabel(infoBox, ' ')
52        OWGUI.widgetLabel(infoBox, ' ')
53        self.infoClass = OWGUI.widgetLabel(infoBox, ' ')
54        infoBox.setMinimumWidth(200)
55        OWGUI.separator(self.controlArea)
56
57        # settings box
58        boxSettings = OWGUI.widgetBox(self.controlArea, "Settings")
59        self.cbShowMeta = OWGUI.checkBox(boxSettings, self, "showMeta", 'Show meta attributes', callback = self.cbShowMetaClicked)
60        self.cbShowMeta.setEnabled(False)
61        self.cbShowAttLbls = OWGUI.checkBox(boxSettings, self, "showAttributeLabels", 'Show attribute labels (if any)', callback = self.cbShowAttLabelsClicked)
62        self.cbShowAttLbls.setEnabled(True)
63        self.cbShowDistributions = OWGUI.checkBox(boxSettings, self, "showDistributions", 'Visualize continuous values', callback = self.cbShowDistributions)
64        colBox = OWGUI.indentedBox(boxSettings, orientation = "horizontal")
65        OWGUI.widgetLabel(colBox, "Color: ")
66        self.colButton = OWGUI.toolButton(colBox, self, self.changeColor, width=20, height=20, debuggingEnabled = 0)
67        OWGUI.rubber(colBox)
68
69        resizeColsBox = OWGUI.widgetBox(boxSettings, 0, "horizontal", 0)
70        OWGUI.label(resizeColsBox, self, "Resize columns: ")
71        OWGUI.button(resizeColsBox, self, "+", self.increaseColWidth, tooltip = "Increase the width of the columns", width=30)
72        OWGUI.button(resizeColsBox, self, "-", self.decreaseColWidth, tooltip = "Decrease the width of the columns", width=30)
73        OWGUI.rubber(resizeColsBox)
74
75        self.btnResetSort = OWGUI.button(boxSettings, self, "Restore Order of Examples", callback = self.btnResetSortClicked, tooltip = "Show examples in the same order as they appear in the file")
76
77        OWGUI.rubber(self.controlArea)
78
79        # GUI with tabs
80        self.tabs = OWGUI.tabWidget(self.mainArea)
81        self.id2table = {}  # key: widget id, value: table
82        self.table2id = {}  # key: table, value: widget id
83        self.connect(self.tabs,SIGNAL("currentChanged(QWidget*)"),self.tabClicked)
84
85        self.updateColor()
86
87    def changeColor(self):
88        color = QColorDialog.getColor(self.distColor, self)
89        if color.isValid():
90            self.distColorRgb = color.getRgb()
91            self.updateColor()
92
93    def updateColor(self):
94        self.distColor = QColor(*self.distColorRgb)
95        w = self.colButton.width()-8
96        h = self.colButton.height()-8
97        pixmap = QPixmap(w, h)
98        painter = QPainter()
99        painter.begin(pixmap)
100        painter.fillRect(0,0,w,h, QBrush(self.distColor))
101        painter.end()
102        self.colButton.setIcon(QIcon(pixmap))
103
104    def increaseColWidth(self):
105        table = self.tabs.currentWidget()
106        for col in range(table.columnCount()):
107            w = table.columnWidth(col)
108            table.setColumnWidth(col, w + 10)
109
110    def decreaseColWidth(self):
111        table = self.tabs.currentWidget()
112        for col in range(table.columnCount()):
113            w = table.columnWidth(col)
114            minW = table.sizeHintForColumn(col)
115            table.setColumnWidth(col, max(w - 10, minW))
116
117
118    def dataset(self, data, id=None):
119        """Generates a new table and adds it to a new tab when new data arrives;
120        or hides the table and removes a tab when data==None;
121        or replaces the table when new data arrives together with already existing id."""
122        if data != None:  # can be an empty table!
123            if self.data.has_key(id):
124                # remove existing table
125                self.data.pop(id)
126                self.showMetas.pop(id)
127                self.id2table[id].hide()
128                self.tabs.removeTab(self.tabs.indexOf(self.id2table[id]))
129                self.table2id.pop(self.id2table.pop(id))
130            self.data[id] = data
131            self.showMetas[id] = (True, [])
132
133            table = OWGUI.table(None, 0,0)
134            table.setSelectionBehavior(QAbstractItemView.SelectRows)
135
136            self.id2table[id] = table
137            self.table2id[table] = id
138            if data.name:
139                tabName = "%s " % data.name
140            else:
141                tabName = ""
142            tabName += "(" + str(id[1]) + ")"
143            if id[2] != None:
144                tabName += " [" + str(id[2]) + "]"
145            self.tabs.addTab(table, tabName)
146
147            self.progressBarInit()
148            self.setTable(table, data)
149            self.progressBarFinished()
150            self.tabs.setCurrentIndex(self.tabs.indexOf(table))
151            self.setInfo(data)
152            self.cbShowMeta.setEnabled(len(self.showMetas[id][1])>0)        # enable showMetas checkbox only if metas exist
153
154        elif self.data.has_key(id):
155            table = self.id2table[id]
156            self.data.pop(id)
157            self.showMetas.pop(id)
158            table.hide()
159            self.tabs.removeTab(self.tabs.indexOf(table))
160            self.table2id.pop(self.id2table.pop(id))
161            self.setInfo(self.data.get(self.table2id.get(self.tabs.currentWidget(),None),None))
162
163        # disable showMetas checkbox if there is no data on input
164        if len(self.data) == 0:
165            self.cbShowMeta.setEnabled(False)
166
167    # Writes data into table, adjusts the column width.
168    def setTable(self, table, data):
169        if data==None:
170            return
171        qApp.setOverrideCursor(Qt.WaitCursor)
172        vars = data.domain.variables
173        m = data.domain.getmetas(False)
174        ml = [(k, m[k]) for k in m]
175        ml.sort(lambda x,y: cmp(y[0], x[0]))
176        metas = [x[1] for x in ml]
177        metaKeys = [x[0] for x in ml]
178
179        mo = data.domain.getmetas(True).items()
180        if mo:
181            mo.sort(lambda x,y: cmp(x[1].name.lower(),y[1].name.lower()))
182#            metas.append(None)
183#            metaKeys.append(None)
184
185        varsMetas = vars + metas
186
187        numVars = len(data.domain.variables)
188        numMetas = len(metas)
189        numVarsMetas = numVars + numMetas
190        numEx = len(data)
191        numSpaces = int(math.log(max(numEx,1), 10))+1
192
193        table.clear()
194        table.oldSortingIndex = -1
195        table.oldSortingOrder = 1
196        table.setColumnCount(numVarsMetas)
197        table.setRowCount(numEx)
198
199        table.dist = getCached(data, orange.DomainBasicAttrStat, (data,))
200       
201        table.setItemDelegate(TableItemDelegate(self, table))
202        table.variableNames = [var.name for var in varsMetas]
203        table.data = data
204        id = self.table2id.get(table, None)
205
206        # set the header (attribute names)
207        table.setHorizontalHeaderLabels(table.variableNames)
208        if self.showAttributeLabels:
209            table.setHorizontalHeaderLabels([table.variableNames[i] + ("\n%s" % a.group if hasattr(a, "group") else "") for (i, a) in enumerate(table.data.domain.attributes)])
210
211        #table.hide()
212        clsColor = QColor(160,160,160)
213        metaColor = QColor(220,220,200)
214        white = QColor(Qt.white)
215        for j,(key,attr) in enumerate(zip(range(numVars) + metaKeys, varsMetas)):
216            self.progressBarSet(j*100.0/numVarsMetas)
217            if attr == data.domain.classVar:
218                bgColor = clsColor
219            elif attr in metas or attr is None:
220                bgColor = metaColor
221                self.showMetas[id][1].append(j) # store indices of meta attributes
222            else:
223                bgColor = white
224
225            for i in range(numEx):
226##                table.setItem(i, j, TableWidgetItem(data[i][key]
227##                OWGUI.tableItem(table, i,j, str(data[i][key]), backColor = bgColor)
228                if data.domain[key].varType == orange.VarTypes.Continuous:
229                    item = OWGUI.tableItem(table, i,j, float(str(data[i][key])), backColor = bgColor)
230                else:
231                    item = OWGUI.tableItem(table, i,j, str(data[i][key]), backColor = bgColor)
232##                item.setData(OrangeValueRole, QVariant(str(data[i][key])))
233
234        table.resizeRowsToContents()
235        table.resizeColumnsToContents()
236
237        self.connect(table.horizontalHeader(), SIGNAL("sectionClicked(int)"), self.sortByColumn)
238        #table.verticalHeader().setMovable(False)
239
240        qApp.restoreOverrideCursor()
241        #table.setCurrentCell(-1,-1)
242        #table.show()
243 
244
245    def sortByColumn(self, index):
246        table = self.tabs.currentWidget()
247        table.horizontalHeader().setSortIndicatorShown(1)
248        header = table.horizontalHeader()
249        if index == table.oldSortingIndex:
250            order = table.oldSortingOrder == Qt.AscendingOrder and Qt.DescendingOrder or Qt.AscendingOrder
251        else:
252            order = Qt.AscendingOrder
253        table.sortByColumn(index, order)
254        table.oldSortingIndex = index
255        table.oldSortingOrder = order
256        #header.setSortIndicator(index, order)
257
258    def tabClicked(self, qTableInstance):
259        """Updates the info box and showMetas checkbox when a tab is clicked.
260        """
261        id = self.table2id.get(qTableInstance,None)
262        self.setInfo(self.data.get(id,None))
263        show_col = self.showMetas.get(id,None)
264        if show_col:
265            self.cbShowMeta.setChecked(show_col[0])
266            self.cbShowMeta.setEnabled(len(show_col[1])>0)
267
268    def cbShowMetaClicked(self):
269        table = self.tabs.currentWidget()
270        id = self.table2id.get(table, None)
271        if self.showMetas.has_key(id):
272            show,col = self.showMetas[id]
273            self.showMetas[id] = (not show,col)
274        if show:
275            for c in col:
276                table.hideColumn(c)
277        else:
278            for c in col:
279                table.showColumn(c)
280                table.resizeColumnToContents(c)
281
282    def cbShowAttLabelsClicked(self):
283        for table in self.table2id.keys():
284            if self.showAttributeLabels:
285                table.setHorizontalHeaderLabels([table.variableNames[i] + ("\n%s" % a.group if hasattr(a, "group") else "") for (i, a) in enumerate(table.data.domain.attributes)])
286            else:
287                table.setHorizontalHeaderLabels(table.variableNames)
288            # h = table.horizontalHeader().adjustSize()
289
290    def cbShowDistributions(self):
291        table = self.tabs.currentWidget()
292        table.reset()
293
294    # show data in the default order
295    def btnResetSortClicked(self):
296        table = self.tabs.currentWidget()
297        id = self.table2id[table]
298        data = self.data[id]
299        self.progressBarInit()
300        self.setTable(table, data)
301        self.progressBarFinished()
302
303    def setInfo(self, data):
304        """Updates data info.
305        """
306        def sp(l, capitalize=False):
307            n = len(l)
308            if n == 0:
309                if capitalize:
310                    return "No", "s"
311                else:
312                    return "no", "s"
313            elif n == 1:
314                return str(n), ''
315            else:
316                return str(n), 's'
317
318        if data == None:
319            self.infoEx.setText('No data on input.')
320            self.infoMiss.setText('')
321            self.infoAttr.setText('')
322            self.infoMeta.setText('')
323            self.infoClass.setText('')
324        else:
325            self.infoEx.setText("%s example%s," % sp(data))
326            missData = orange.Preprocessor_takeMissing(data)
327            self.infoMiss.setText('%s (%.1f%s) with missing values.' % (len(missData), len(data) and 100.*len(missData)/len(data), "%"))
328            self.infoAttr.setText("%s attribute%s," % sp(data.domain.attributes,True))
329            self.infoMeta.setText("%s meta attribute%s." % sp(data.domain.getmetas()))
330            if data.domain.classVar:
331                if data.domain.classVar.varType == orange.VarTypes.Discrete:
332                    self.infoClass.setText('Discrete class with %s value%s.' % sp(data.domain.classVar.values))
333                elif data.domain.classVar.varType == orange.VarTypes.Continuous:
334                    self.infoClass.setText('Continuous class.')
335                else:
336                    self.infoClass.setText("Class is neither discrete nor continuous.")
337            else:
338                self.infoClass.setText('Classless domain.')
339               
340
341class TableItemDelegate(QItemDelegate):
342    def __init__(self, widget = None, table = None):
343        QItemDelegate.__init__(self, widget)
344        self.table = table
345        self.widget = widget
346
347    def paint(self, painter, option, index):
348        painter.save()
349        self.drawBackground(painter, option, index)
350        value, ok = index.data(Qt.DisplayRole).toDouble()
351
352        if ok:        # in case we get "?" it is not ok
353            if self.widget.showDistributions:
354                col = index.column()
355                if col < len(self.table.dist) and self.table.dist[col]:        # meta attributes and discrete attributes don't have a key
356                    dist = self.table.dist[col]
357                    smallerWidth = option.rect.width() * (dist.max - value) / (dist.max-dist.min or 1)
358                    painter.fillRect(option.rect.adjusted(0,0,-smallerWidth,0), self.widget.distColor)
359##            text = self.widget.locale.toString(value)    # we need this to convert doubles like 1.39999999909909 into 1.4
360##        else:
361        text = index.data(Qt.DisplayRole).toString()
362        ##text = index.data(OrangeValueRole).toString()
363
364        self.drawDisplay(painter, option, option.rect, text)
365        painter.restore()
366
367
368
369
370if __name__=="__main__":
371    a = QApplication(sys.argv)
372    ow = OWDataTable()
373
374    #d1 = orange.ExampleTable(r'..\..\doc\datasets\auto-mpg')
375    #d2 = orange.ExampleTable('test-labels')
376    #d3 = orange.ExampleTable(r'..\..\doc\datasets\sponge.tab')
377    #d4 = orange.ExampleTable(r'..\..\doc\datasets\wpbc.csv')
378    #d5 = orange.ExampleTable(r'..\..\doc\datasets\adult_sample.tab')
379    d5 = orange.ExampleTable(r"E:\Development\Orange Datasets\UCI\wine.tab")
380    #d5 = orange.ExampleTable(r"e:\Development\Orange Datasets\Cancer\SRBCT.tab")
381    ow.show()
382    #ow.dataset(d1,"auto-mpg")
383    #ow.dataset(d2,"voting")
384    #ow.dataset(d4,"wpbc")
385    ow.dataset(d5,"adult_sample")
386    a.exec_()
387    ow.saveSettings()
Note: See TracBrowser for help on using the repository browser.