1212from AnyQt .QtCore import (
1313 Qt , QObject , QTimer , pyqtSignal as Signal , pyqtSlot as Slot
1414)
15- from AnyQt .QtWidgets import QSlider , QCheckBox , QWidget
15+ from AnyQt .QtWidgets import QSlider , QCheckBox , QWidget , QLabel
1616
1717from Orange .clustering .louvain import table_to_knn_graph , Louvain
1818from Orange .data import Table , DiscreteVariable
4040_DEFAULT_K_NEIGHBORS = 30
4141
4242
43- METRICS = [(' Euclidean' , 'l2' ), (' Manhattan' , 'l1' )]
43+ METRICS = [(" Euclidean" , "l2" ), (" Manhattan" , "l1" )]
4444
4545
4646class OWLouvainClustering (widget .OWWidget ):
47- name = ' Louvain Clustering'
48- description = ' Detects communities in a network of nearest neighbors.'
49- icon = ' icons/LouvainClustering.svg'
47+ name = " Louvain Clustering"
48+ description = " Detects communities in a network of nearest neighbors."
49+ icon = " icons/LouvainClustering.svg"
5050 priority = 2110
5151
5252 want_main_area = False
5353
5454 settingsHandler = DomainContextHandler ()
5555
5656 class Inputs :
57- data = Input (' Data' , Table , default = True )
57+ data = Input (" Data" , Table , default = True )
5858
5959 if Graph is not None :
6060 class Outputs :
6161 annotated_data = Output (ANNOTATED_DATA_SIGNAL_NAME , Table , default = True )
62- graph = Output (' Network' , Graph )
62+ graph = Output (" Network" , Graph )
6363 else :
6464 class Outputs :
6565 annotated_data = Output (ANNOTATED_DATA_SIGNAL_NAME , Table , default = True )
@@ -75,8 +75,7 @@ class Information(widget.OWWidget.Information):
7575 modified = Msg ("Press commit to recompute clusters and send new data" )
7676
7777 class Error (widget .OWWidget .Error ):
78- empty_dataset = Msg ('No features in data' )
79- general_error = Msg ('Error occured during clustering\n {}' )
78+ empty_dataset = Msg ("No features in data" )
8079
8180 def __init__ (self ):
8281 super ().__init__ ()
@@ -98,40 +97,44 @@ def __init__(self):
9897 self .__commit_timer = QTimer (self , singleShot = True )
9998 self .__commit_timer .timeout .connect (self .commit )
10099
101- pca_box = gui .vBox (self .controlArea , 'PCA Preprocessing' )
100+ # Set up UI
101+ info_box = gui .vBox (self .controlArea , "Info" )
102+ self .info_label = gui .widgetLabel (info_box , "No data on input." ) # type: QLabel
103+
104+ pca_box = gui .vBox (self .controlArea , "PCA Preprocessing" )
102105 self .apply_pca_cbx = gui .checkBox (
103- pca_box , self , ' apply_pca' , label = ' Apply PCA preprocessing' ,
106+ pca_box , self , " apply_pca" , label = " Apply PCA preprocessing" ,
104107 callback = self ._invalidate_graph ,
105108 ) # type: QCheckBox
106109 self .pca_components_slider = gui .hSlider (
107- pca_box , self , ' pca_components' , label = ' Components: ' , minValue = 2 ,
110+ pca_box , self , " pca_components" , label = " Components: " , minValue = 2 ,
108111 maxValue = _MAX_PCA_COMPONENTS ,
109112 callback = self ._invalidate_pca_projection , tracking = False
110113 ) # type: QSlider
111114
112- graph_box = gui .vBox (self .controlArea , ' Graph parameters' )
115+ graph_box = gui .vBox (self .controlArea , " Graph parameters" )
113116 self .metric_combo = gui .comboBox (
114- graph_box , self , ' metric_idx' , label = ' Distance metric' ,
117+ graph_box , self , " metric_idx" , label = " Distance metric" ,
115118 items = [m [0 ] for m in METRICS ], callback = self ._invalidate_graph ,
116119 orientation = Qt .Horizontal ,
117120 ) # type: gui.OrangeComboBox
118121 self .k_neighbors_spin = gui .spin (
119- graph_box , self , ' k_neighbors' , minv = 1 , maxv = _MAX_K_NEIGBOURS ,
120- label = ' k neighbors' , controlWidth = 80 , alignment = Qt .AlignRight ,
122+ graph_box , self , " k_neighbors" , minv = 1 , maxv = _MAX_K_NEIGBOURS ,
123+ label = " k neighbors" , controlWidth = 80 , alignment = Qt .AlignRight ,
121124 callback = self ._invalidate_graph ,
122125 ) # type: gui.SpinBoxWFocusOut
123126 self .resolution_spin = gui .hSlider (
124- graph_box , self , ' resolution' , minValue = 0 , maxValue = 5. , step = 1e-1 ,
125- label = ' Resolution' , intOnly = False , labelFormat = ' %.1f' ,
127+ graph_box , self , " resolution" , minValue = 0 , maxValue = 5. , step = 1e-1 ,
128+ label = " Resolution" , intOnly = False , labelFormat = " %.1f" ,
126129 callback = self ._invalidate_partition , tracking = False ,
127130 ) # type: QSlider
128131 self .resolution_spin .parent ().setToolTip (
129- ' The resolution parameter affects the number of clusters to find. '
130- ' Smaller values tend to produce more clusters and larger values '
131- ' retrieve less clusters.'
132+ " The resolution parameter affects the number of clusters to find. "
133+ " Smaller values tend to produce more clusters and larger values "
134+ " retrieve less clusters."
132135 )
133136 self .apply_button = gui .auto_commit (
134- self .controlArea , self , ' auto_commit' , ' Apply' , box = None ,
137+ self .controlArea , self , " auto_commit" , " Apply" , box = None ,
135138 commit = lambda : self .commit (),
136139 callback = lambda : self ._on_auto_commit_changed (),
137140 ) # type: QWidget
@@ -248,6 +251,7 @@ def commit(self):
248251 run_on_graph , graph , resolution = self .resolution , state = state
249252 )
250253
254+ self .info_label .setText ("Running..." )
251255 self .__set_state_busy ()
252256 self .__start_task (task , state )
253257
@@ -269,7 +273,7 @@ def __set_partial_results(self, result):
269273
270274 @Slot (object )
271275 def __on_done (self , future ):
272- # type: (Future[' Results' ]) -> None
276+ # type: (Future[" Results" ]) -> None
273277 assert future .done ()
274278 assert self .__task is not None
275279 assert self .__task .future is future
@@ -278,12 +282,9 @@ def __on_done(self, future):
278282 task .deleteLater ()
279283
280284 self .__set_state_ready ()
281- try :
282- result = future .result ()
283- except Exception as err : # pylint: disable=broad-except
284- self .Error .general_error (str (err ), exc_info = True )
285- else :
286- self .__set_results (result )
285+
286+ result = future .result ()
287+ self .__set_results (result )
287288
288289 @Slot (str )
289290 def setStatusMessage (self , text ):
@@ -330,7 +331,7 @@ def __cancel_task(self, wait=True):
330331 w .done .connect (state .deleteLater )
331332
332333 def __set_results (self , results ):
333- # type: (' Results' ) -> None
334+ # type: (" Results" ) -> None
334335 # NOTE: All of these have already been set by __set_partial_results,
335336 # we double check that they are aliases
336337 if results .pca_projection is not None :
@@ -346,6 +347,11 @@ def __set_results(self, results):
346347 assert results .resolution == self .resolution
347348 assert self .partition is results .partition
348349 self .partition = results .partition
350+
351+ # Display the number of found clusters in the UI
352+ num_clusters = len (np .unique (self .partition ))
353+ self .info_label .setText ("%d clusters found." % num_clusters )
354+
349355 self ._send_data ()
350356
351357 def _send_data (self ):
@@ -359,8 +365,8 @@ def _send_data(self):
359365 new_partition = list (map (index_map .get , self .partition ))
360366
361367 cluster_var = DiscreteVariable (
362- get_unique_names (domain , ' Cluster' ),
363- values = [' C%d' % (i + 1 ) for i , _ in enumerate (np .unique (new_partition ))]
368+ get_unique_names (domain , " Cluster" ),
369+ values = [" C%d" % (i + 1 ) for i , _ in enumerate (np .unique (new_partition ))]
364370 )
365371
366372 new_domain = add_columns (domain , metas = [cluster_var ])
@@ -406,6 +412,8 @@ def set_data(self, data):
406412 self .k_neighbors_spin .setMaximum (min (_MAX_K_NEIGBOURS , len (data ) - 1 ))
407413 self .k_neighbors_spin .setValue (min (_DEFAULT_K_NEIGHBORS , len (data ) - 1 ))
408414
415+ self .info_label .setText ("Clustering not yet run." )
416+
409417 self .commit ()
410418
411419 def clear (self ):
@@ -416,6 +424,7 @@ def clear(self):
416424 self .partition = None
417425 self .Error .clear ()
418426 self .Information .modified .clear ()
427+ self .info_label .setText ("No data on input." )
419428
420429 def onDeleteWidget (self ):
421430 self .__cancel_task (wait = True )
@@ -427,13 +436,13 @@ def onDeleteWidget(self):
427436 def send_report (self ):
428437 pca = report .bool_str (self .apply_pca )
429438 if self .apply_pca :
430- pca += report .plural (' , {number} component{s}' , self .pca_components )
439+ pca += report .plural (" , {number} component{s}" , self .pca_components )
431440
432441 self .report_items ((
433- (' PCA preprocessing' , pca ),
434- (' Metric' , METRICS [self .metric_idx ][0 ]),
435- (' k neighbors' , self .k_neighbors ),
436- (' Resolution' , self .resolution ),
442+ (" PCA preprocessing" , pca ),
443+ (" Metric" , METRICS [self .metric_idx ][0 ]),
444+ (" k neighbors" , self .k_neighbors ),
445+ (" Resolution" , self .resolution ),
437446 ))
438447
439448
@@ -614,5 +623,5 @@ def run_on_graph(graph, resolution, state):
614623 return res
615624
616625
617- if __name__ == ' __main__' : # pragma: no cover
626+ if __name__ == " __main__" : # pragma: no cover
618627 WidgetPreview (OWLouvainClustering ).run (Table ("iris" ))
0 commit comments