11import concurrent .futures
22from dataclasses import dataclass
3- from typing import Optional , Union , Sequence , List , TypedDict , Tuple , Dict
3+ from typing import (
4+ Optional , Union , Sequence , List , TypedDict , Tuple , Dict , Any , Container
5+ )
46
57from scipy .sparse import issparse
68
7- from AnyQt .QtWidgets import QTableView , QHeaderView , QApplication , QStyle
8- from AnyQt .QtGui import QColor , QClipboard
9- from AnyQt .QtCore import Qt , QSize , QMetaObject , QItemSelectionModel
9+ from AnyQt .QtWidgets import (
10+ QTableView , QHeaderView , QApplication , QStyle , QStyleOptionHeader ,
11+ QStyleOptionViewItem
12+ )
13+ from AnyQt .QtGui import QColor , QClipboard , QPainter
14+ from AnyQt .QtCore import (
15+ Qt , QSize , QMetaObject , QItemSelectionModel , QModelIndex , QRect
16+ )
1017from AnyQt .QtCore import Slot
1118
19+ from orangewidget .gui import OrangeUserRole
20+
1221import Orange .data
1322from Orange .data import Variable
1423from Orange .data .table import Table
2635from Orange .widgets .utils .itemmodels import TableModel
2736from Orange .widgets .utils .state_summary import format_summary_details
2837from Orange .widgets .utils import disconnected
38+ from Orange .widgets .utils .headerview import HeaderView
2939from Orange .widgets .data .utils .tableview import RichTableView
3040from Orange .widgets .data .utils import tablesummary as tsummary
3141
3242
43+ SubsetRole = next (OrangeUserRole )
44+
45+
46+ class HeaderViewWithSubsetIndicator (HeaderView ):
47+ _IndicatorChar = "\N{BULLET} "
48+
49+ def paintSection (
50+ self , painter : QPainter , rect : QRect , logicalIndex : int
51+ ) -> None :
52+ opt = QStyleOptionHeader ()
53+ self .initStyleOption (opt )
54+ self .initStyleOptionForIndex (opt , logicalIndex )
55+ model = self .model ()
56+ if model is None :
57+ return # pragma: no cover
58+ opt .rect = rect
59+ issubset = model .headerData (logicalIndex , Qt .Vertical , SubsetRole )
60+ style = self .style ()
61+ # draw background
62+ style .drawControl (QStyle .CE_HeaderSection , opt , painter , self )
63+ indicator_rect = QRect (rect )
64+ text_rect = QRect (rect )
65+ indicator_width = opt .fontMetrics .horizontalAdvance (
66+ self ._IndicatorChar + " "
67+ )
68+ indicator_rect .setWidth (indicator_width )
69+ text_rect .setLeft (indicator_width )
70+ if issubset :
71+ optindicator = QStyleOptionHeader (opt )
72+ optindicator .rect = indicator_rect
73+ optindicator .textAlignment = Qt .AlignCenter
74+ optindicator .text = self ._IndicatorChar
75+ # draw subset indicator
76+ style .drawControl (QStyle .CE_HeaderLabel , optindicator , painter , self )
77+ opt .rect = text_rect
78+ # draw section label
79+ style .drawControl (QStyle .CE_HeaderLabel , opt , painter , self )
80+
81+ def sectionSizeFromContents (self , logicalIndex : int ) -> QSize :
82+ opt = QStyleOptionHeader ()
83+ self .initStyleOption (opt )
84+ super ().initStyleOptionForIndex (opt , logicalIndex )
85+ opt .text = self ._IndicatorChar + " " + opt .text
86+ return self .style ().sizeFromContents (QStyle .CT_HeaderSection , opt , QSize (), self )
87+
88+
3389class DataTableView (gui .HScrollStepMixin , RichTableView ):
34- pass
90+ def __init__ (self , * args , ** kwargs ):
91+ super ().__init__ (* args , ** kwargs )
92+ vheader = HeaderViewWithSubsetIndicator (
93+ Qt .Vertical , self , highlightSections = True
94+ )
95+ vheader .setSectionsClickable (True )
96+ self .setVerticalHeader (vheader )
97+
98+
99+ class _TableDataDelegate (TableDataDelegate ):
100+ DefaultRoles = TableDataDelegate .DefaultRoles + (SubsetRole ,)
101+
102+
103+ class SubsetTableDataDelegate (_TableDataDelegate ):
104+ def __init__ (self , * args , ** kwargs ):
105+ super ().__init__ (* args , ** kwargs )
106+ self .subset_opacity = 0.5
35107
108+ def paint (
109+ self , painter : QPainter , option : QStyleOptionViewItem ,
110+ index : QModelIndex
111+ ) -> None :
112+ issubset = self .cachedData (index , SubsetRole )
113+ opacity = painter .opacity ()
114+ if not issubset :
115+ painter .setOpacity (self .subset_opacity )
116+ super ().paint (painter , option , index )
117+ if not issubset :
118+ painter .setOpacity (opacity )
36119
37- class TableBarItemDelegate (gui .TableBarItem , TableDataDelegate ):
120+
121+ class TableBarItemDelegate (SubsetTableDataDelegate , gui .TableBarItem ,
122+ _TableDataDelegate ):
38123 pass
39124
40125
126+ class _TableModel (RichTableModel ):
127+ SubsetRole = SubsetRole
128+
129+ def __init__ (self , * args , subsets = None , ** kwargs ):
130+ super ().__init__ (* args , ** kwargs )
131+ self ._subset = subsets or set ()
132+
133+ def setSubsetRowIds (self , subsetids : Container [int ]):
134+ self ._subset = subsetids
135+ if self .rowCount ():
136+ self .headerDataChanged .emit (Qt .Vertical , 0 , self .rowCount () - 1 )
137+ self .dataChanged .emit (
138+ self .index (0 , 0 ),
139+ self .index (self .rowCount () - 1 , self .columnCount () - 1 ),
140+ [SubsetRole ],
141+ )
142+
143+ def _is_subset (self , row ):
144+ row = self .mapToSourceRows (row )
145+ try :
146+ id_ = self .source .ids [row ]
147+ except (IndexError , AttributeError ): # pragma: no cover
148+ return False
149+ return int (id_ ) in self ._subset
150+
151+ def data (self , index : QModelIndex , role = Qt .DisplayRole ) -> Any :
152+ if role == _TableModel .SubsetRole :
153+ return self ._is_subset (index .row ())
154+ return super ().data (index , role )
155+
156+ def headerData (self , section , orientation , role ):
157+ if orientation == Qt .Vertical and role == _TableModel .SubsetRole :
158+ return self ._is_subset (section )
159+ return super ().headerData (section , orientation , role )
160+
161+
41162@dataclass
42163class InputData :
43164 table : Table
@@ -61,7 +182,8 @@ class OWTable(OWWidget):
61182 keywords = "data table, view"
62183
63184 class Inputs :
64- data = Input ("Data" , Table )
185+ data = Input ("Data" , Table , default = True )
186+ data_subset = Input ("Data Subset" , Table )
65187
66188 class Outputs :
67189 selected_data = Output ("Selected Data" , Table , default = True )
@@ -95,6 +217,7 @@ class Warning(OWWidget.Warning):
95217 def __init__ (self ):
96218 super ().__init__ ()
97219 self .input : Optional [InputData ] = None
220+ self ._subset_ids : Optional [set ] = None
98221 self .__pending_selection : Optional [_Selection ] = self .stored_selection
99222 self .__pending_sort : Optional [_Sorting ] = self .stored_sort
100223 self .dist_color = QColor (220 , 220 , 220 , 255 )
@@ -128,11 +251,8 @@ def __init__(self):
128251 attribute = Qt .WA_LayoutUsesWidgetRect )
129252 gui .auto_send (self .buttonsArea , self , "auto_commit" )
130253
131- view = DataTableView (
132- sortingEnabled = True
133- )
134- view .setSortingEnabled (True )
135- view .setItemDelegate (TableDataDelegate (view ))
254+ view = DataTableView (sortingEnabled = True )
255+ view .setItemDelegate (SubsetTableDataDelegate (view ))
136256 view .selectionFinished .connect (self .update_selection )
137257
138258 if self .select_rows :
@@ -164,27 +284,35 @@ def set_dataset(self, data: Optional[Table]):
164284 self .view .setModel (None )
165285 self .view .horizontalHeader ().setSortIndicator (- 1 , Qt .AscendingOrder )
166286 if data is not None :
287+ summary = tsummary .table_summary (data )
167288 self .input = InputData (
168289 table = data ,
169- summary = tsummary . table_summary ( data ) ,
170- model = RichTableModel (data )
290+ summary = summary ,
291+ model = _TableModel (data )
171292 )
172- self ._setup_table_view ()
293+ if isinstance (summary .len , concurrent .futures .Future ):
294+ def update (_ ):
295+ QMetaObject .invokeMethod (
296+ self , "_update_info" , Qt .QueuedConnection )
297+ summary .len .add_done_callback (update )
173298 else :
174299 self .input = None
175300
301+ @Inputs .data_subset
302+ def set_subset_dataset (self , subset : Optional [Table ]):
303+ """Set the data subset"""
304+ if subset is not None and not isinstance (subset , SqlTable ):
305+ ids = set (subset .ids )
306+ else :
307+ ids = None
308+ self ._subset_ids = ids
309+
176310 def handleNewSignals (self ):
177311 super ().handleNewSignals ()
178312 self .Warning .non_sortable_input .clear ()
179313 self .Warning .missing_sort_columns .clear ()
180314 data : Optional [Table ] = self .input .table if self .input else None
181- slot = self .input
182- if slot is not None and isinstance (slot .summary .len , concurrent .futures .Future ):
183- def update (_ ):
184- QMetaObject .invokeMethod (
185- self , "_update_info" , Qt .QueuedConnection )
186- slot .summary .len .add_done_callback (update )
187-
315+ self ._setup_table_view ()
188316 self ._update_input_summary ()
189317
190318 if data is not None and self .__pending_sort is not None :
@@ -205,23 +333,12 @@ def _setup_table_view(self):
205333 return
206334
207335 datamodel = self .input .model
336+ datamodel .setSubsetRowIds (self ._subset_ids or set ())
337+
208338 view = self .view
209339 data = self .input .table
210340 rowcount = data .approx_len ()
211341
212- if self .color_by_class and data .domain .has_discrete_class :
213- color_schema = [
214- QColor (* c ) for c in data .domain .class_var .colors ]
215- else :
216- color_schema = None
217- if self .show_distributions :
218- view .setItemDelegate (
219- TableBarItemDelegate (
220- view , color = self .dist_color , color_schema = color_schema )
221- )
222- else :
223- view .setItemDelegate (TableDataDelegate (view ))
224-
225342 view .setModel (datamodel )
226343
227344 vheader = view .verticalHeader ()
@@ -249,6 +366,7 @@ def _setup_table_view(self):
249366 assert view .model ().rowCount () <= maxrows
250367 assert vheader .sectionSize (0 ) > 1 or datamodel .rowCount () == 0
251368
369+ self ._setup_view_delegate ()
252370 # update the header (attribute names)
253371 self ._update_variable_labels ()
254372
@@ -287,8 +405,11 @@ def _update_variable_labels(self):
287405
288406 def _on_distribution_color_changed (self ):
289407 if self .input is None :
290- return
291- widget = self .view
408+ return # pragma: no cover
409+ self ._setup_view_delegate ()
410+
411+ def _setup_view_delegate (self ):
412+ assert self .input is not None
292413 model = self .input .model
293414 data = model .source
294415 class_var = data .domain .class_var
@@ -297,11 +418,13 @@ def _on_distribution_color_changed(self):
297418 else :
298419 color_schema = None
299420 if self .show_distributions :
300- delegate = TableBarItemDelegate (widget , color = self .dist_color ,
301- color_schema = color_schema )
421+ delegate = TableBarItemDelegate (
422+ self .view , color = self .dist_color , color_schema = color_schema
423+ )
302424 else :
303- delegate = TableDataDelegate (widget )
304- widget .setItemDelegate (delegate )
425+ delegate = SubsetTableDataDelegate (self .view )
426+ delegate .subset_opacity = 0.5 if self ._subset_ids is not None else 1.0
427+ self .view .setItemDelegate (delegate )
305428
306429 def _on_select_rows_changed (self ):
307430 if self .input is None :
0 commit comments