3
3
from typing import Union , Dict , List
4
4
5
5
import numpy as np
6
+ from sklearn .exceptions import UndefinedMetricWarning
6
7
7
8
from AnyQt .QtWidgets import QHeaderView , QStyledItemDelegate , QMenu , \
8
- QApplication
9
- from AnyQt .QtGui import QStandardItemModel , QStandardItem , QClipboard
9
+ QApplication , QToolButton
10
+ from AnyQt .QtGui import QStandardItemModel , QStandardItem , QClipboard , QColor
10
11
from AnyQt .QtCore import Qt , QSize , QObject , pyqtSignal as Signal , \
11
12
QSortFilterProxyModel
12
- from sklearn .exceptions import UndefinedMetricWarning
13
+
14
+ from orangewidget .gui import OrangeUserRole
13
15
14
16
from Orange .data import Domain , Variable
15
17
from Orange .evaluation import scoring
@@ -128,7 +130,84 @@ def is_bad(x):
128
130
return left < right
129
131
130
132
131
- DEFAULT_HINTS = {"Model_" : True , "Train" : False , "Test" : False }
133
+ DEFAULT_HINTS = {"Model_" : True , "Train_" : False , "Test_" : False }
134
+
135
+
136
+ class PersistentMenu (QMenu ):
137
+ def mouseReleaseEvent (self , e ):
138
+ action = self .activeAction ()
139
+ if action :
140
+ action .setEnabled (False )
141
+ super ().mouseReleaseEvent (e )
142
+ action .setEnabled (True )
143
+ action .trigger ()
144
+ else :
145
+ super ().mouseReleaseEvent (e )
146
+
147
+
148
+ class SelectableColumnsHeader (QHeaderView ):
149
+ SelectMenuRole = next (OrangeUserRole )
150
+ ShownHintRole = next (OrangeUserRole )
151
+ sectionVisibleChanged = Signal (int , bool )
152
+
153
+ def __init__ (self , shown_columns_hints , * args , ** kwargs ):
154
+ super ().__init__ (Qt .Horizontal , * args , ** kwargs )
155
+ self .show_column_hints = shown_columns_hints
156
+ self .button = QToolButton (self )
157
+ self .button .setArrowType (Qt .DownArrow )
158
+ self .button .setFixedSize (24 , 12 )
159
+ col = self .button .palette ().color (self .button .backgroundRole ())
160
+ self .button .setStyleSheet (
161
+ f"border: none; background-color: { col .name (QColor .NameFormat .HexRgb )} " )
162
+ self .setContextMenuPolicy (Qt .CustomContextMenu )
163
+ self .customContextMenuRequested .connect (self .show_column_chooser )
164
+ self .button .clicked .connect (self ._on_button_clicked )
165
+
166
+ def showEvent (self , e ):
167
+ self ._set_pos ()
168
+ self .button .show ()
169
+ super ().showEvent (e )
170
+
171
+ def resizeEvent (self , e ):
172
+ self ._set_pos ()
173
+ super ().resizeEvent (e )
174
+
175
+ def _set_pos (self ):
176
+ w , h = self .button .width (), self .button .height ()
177
+ vw , vh = self .viewport ().width (), self .viewport ().height ()
178
+ self .button .setGeometry (vw - w , (vh - h ) // 2 , w , h )
179
+
180
+ def __data (self , section , role ):
181
+ return self .model ().headerData (section , Qt .Horizontal , role )
182
+
183
+ def show_column_chooser (self , pos ):
184
+ # pylint: disable=unsubscriptable-object, unsupported-assignment-operation
185
+ menu = PersistentMenu ()
186
+ for section in range (self .count ()):
187
+ name , enabled = self .__data (section , self .SelectMenuRole )
188
+ hint_id = self .__data (section , self .ShownHintRole )
189
+ action = menu .addAction (name )
190
+ action .setDisabled (not enabled )
191
+ action .setCheckable (True )
192
+ action .setChecked (self .show_column_hints [hint_id ])
193
+
194
+ @action .triggered .connect # pylint: disable=cell-var-from-loop
195
+ def update (checked , q = hint_id , section = section ):
196
+ self .show_column_hints [q ] = checked
197
+ self .setSectionHidden (section , not checked )
198
+ self .sectionVisibleChanged .emit (section , checked )
199
+ self .resizeSections (self .ResizeToContents )
200
+
201
+ pos .setY (self .viewport ().height ())
202
+ menu .exec (self .mapToGlobal (pos ))
203
+
204
+ def _on_button_clicked (self ):
205
+ self .show_column_chooser (self .button .pos ())
206
+
207
+ def update_shown_columns (self ):
208
+ for section in range (self .count ()):
209
+ hint_id = self .__data (section , self .ShownHintRole )
210
+ self .setSectionHidden (section , not self .show_column_hints [hint_id ])
132
211
133
212
134
213
class ScoreTable (OWComponent , QObject ):
@@ -138,6 +217,7 @@ class ScoreTable(OWComponent, QObject):
138
217
# backwards compatibility
139
218
@property
140
219
def shown_scores (self ):
220
+ # pylint: disable=unsubscriptable-object
141
221
column_names = {
142
222
self .model .horizontalHeaderItem (col ).data (Qt .DisplayRole )
143
223
for col in range (1 , self .model .columnCount ())}
@@ -166,65 +246,45 @@ def __init__(self, master):
166
246
header .setSectionResizeMode (QHeaderView .ResizeToContents )
167
247
header .setDefaultAlignment (Qt .AlignCenter )
168
248
header .setStretchLastSection (False )
169
- header .setContextMenuPolicy (Qt .CustomContextMenu )
170
- header .customContextMenuRequested .connect (self .show_column_chooser )
171
249
172
250
for score in Score .registry .values ():
173
251
self .show_score_hints .setdefault (score .__name__ , score .default_visible )
174
252
175
253
self .model = QStandardItemModel (master )
176
- self .model .setHorizontalHeaderLabels (["Method" ])
254
+ header = SelectableColumnsHeader (self .show_score_hints )
255
+ header .setSectionsClickable (True )
256
+ self .view .setHorizontalHeader (header )
177
257
self .sorted_model = ScoreModel ()
178
258
self .sorted_model .setSourceModel (self .model )
179
259
self .view .setModel (self .sorted_model )
180
260
self .view .setItemDelegate (self .ItemDelegate ())
181
-
182
- def show_column_chooser (self , pos ):
183
- menu = QMenu ()
184
- header = self .view .horizontalHeader ()
185
- for col in range (1 , self .model .columnCount ()):
186
- item = self .model .horizontalHeaderItem (col )
187
- qualname = item .data (Qt .UserRole )
188
- if col < 3 :
189
- option = item .data (Qt .DisplayRole )
190
- else :
191
- score = Score .registry [qualname ]
192
- option = score .long_name
193
- if score .name != score .long_name :
194
- option += f" ({ score .name } )"
195
- action = menu .addAction (option )
196
- action .setCheckable (True )
197
- action .setChecked (self .show_score_hints [qualname ])
198
-
199
- @action .triggered .connect
200
- def update (checked , q = qualname ):
201
- self .show_score_hints [q ] = checked
202
- self ._update_shown_columns ()
203
-
204
- menu .exec (header .mapToGlobal (pos ))
205
-
206
- def _update_shown_columns (self ):
207
- self .view .resizeColumnsToContents ()
208
- header = self .view .horizontalHeader ()
209
- for section in range (1 , header .count ()):
210
- qualname = self .model .horizontalHeaderItem (section ).data (Qt .UserRole )
211
- header .setSectionHidden (section , not self .show_score_hints [qualname ])
212
- self .shownScoresChanged .emit ()
261
+ header .sectionVisibleChanged .connect (self .shownScoresChanged .emit )
262
+ self .sorted_model .dataChanged .connect (self .view .resizeColumnsToContents )
213
263
214
264
def update_header (self , scorers : List [Score ]):
215
265
self .model .setColumnCount (3 + len (scorers ))
216
- for i , name , id_ in ((0 , "Model" , "Model_" ),
217
- (1 , "Train time [s]" , "Train" ),
218
- (2 , "Test time [s]" , "Test" )):
266
+ SelectMenuRole = SelectableColumnsHeader .SelectMenuRole
267
+ ShownHintRole = SelectableColumnsHeader .ShownHintRole
268
+ for i , name , long_name , id_ , in ((0 , "Model" , "Model" , "Model_" ),
269
+ (1 , "Train" , "Train time [s]" , "Train_" ),
270
+ (2 , "Test" , "Test time [s]" , "Test_" )):
219
271
item = QStandardItem (name )
220
- item .setData (id_ , Qt .UserRole )
272
+ item .setData ((long_name , i != 0 ), SelectMenuRole )
273
+ item .setData (id_ , ShownHintRole )
274
+ item .setToolTip (long_name )
221
275
self .model .setHorizontalHeaderItem (i , item )
222
276
for col , score in enumerate (scorers , start = 3 ):
223
277
item = QStandardItem (score .name )
224
- item .setData (score .__name__ , Qt .UserRole )
278
+ name = score .long_name
279
+ if name != score .name :
280
+ name += f" ({ score .name } )"
281
+ item .setData ((name , True ), SelectMenuRole )
282
+ item .setData (score .__name__ , ShownHintRole )
225
283
item .setToolTip (score .long_name )
226
284
self .model .setHorizontalHeaderItem (col , item )
227
- self ._update_shown_columns ()
285
+
286
+ self .view .horizontalHeader ().update_shown_columns ()
287
+ self .view .resizeColumnsToContents ()
228
288
229
289
def copy_selection_to_clipboard (self ):
230
290
mime = table_selection_to_mime_data (self .view )
0 commit comments