Completed
Push — master ( 93c3d2...b7ad63 )
by Andrei
04:01
created

cluster_visualizer   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 270
Duplicated Lines 3.33 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 9
loc 270
rs 8.3999
c 1
b 0
f 0
wmc 46

7 Methods

Rating   Name   Duplication   Size   Complexity  
B __init__() 0 50 4
F append_cluster() 0 53 15
A append_clusters() 0 14 2
A set_canvas_title() 0 13 2
B append_cluster_attribute() 0 26 3
D __draw_canvas_cluster() 9 25 8
F show() 0 61 12

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like cluster_visualizer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""!
2
3
@brief pyclustering module for cluster analysis.
4
5
@authors Andrei Novikov ([email protected])
6
@date 2014-2017
7
@copyright GNU Public License
8
9
@cond GNU_PUBLIC_LICENSE
10
    PyClustering is free software: you can redistribute it and/or modify
11
    it under the terms of the GNU General Public License as published by
12
    the Free Software Foundation, either version 3 of the License, or
13
    (at your option) any later version.
14
    
15
    PyClustering is distributed in the hope that it will be useful,
16
    but WITHOUT ANY WARRANTY; without even the implied warranty of
17
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
    GNU General Public License for more details.
19
    
20
    You should have received a copy of the GNU General Public License
21
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
@endcond
23
24
"""
25
26
import matplotlib.pyplot as plt;
27
import matplotlib.gridspec as gridspec;
28
29
import math;
30
31
from pyclustering.utils.color import color as color_list;
32
33
34
class canvas_cluster_descr:
35
    """!
36
    @brief Description of cluster for representation on canvas.
37
    
38
    """
39
40
    def __init__(self, cluster, data, marker, markersize, color):
41
        """!
42
        @brief Constructor of cluster representation on the canvas.
43
        
44
        @param[in] cluster (list): Single cluster that consists of objects or indexes from data.
45
        @param[in] data (list): Objects that should be displayed, can be None if clusters consist of objects instead of indexes.
46
        @param[in] marker (string): Type of marker that is used for drawing objects.
47
        @param[in] markersize (uint): Size of marker that is used for drawing objects.
48
        @param[in] color (string): Color of the marker that is used for drawing objects.
49
        
50
        """
51
        ## Cluster that may consist of objects or indexes of objects from data.
52
        self.cluster = cluster;
53
        
54
        ## Data where objects are stored. It can be None if clusters consist of objects instead of indexes.
55
        self.data = data;
56
        
57
        ## Marker that is used for drawing objects.
58
        self.marker = marker;
59
        
60
        ## Size of marker that is used for drawing objects.
61
        self.markersize = markersize;
62
        
63
        ## Color that is used for coloring marker.
64
        self.color = color;
65
        
66
        ## Attribures of the clusters - additional collections of data points that are regarded to the cluster.
67
        self.attributes = [];
68
    
69
70
class cluster_visualizer:
71
    """!
72
    @brief Common visualizer of clusters on 2D or 3D surface.
73
    
74
    """
75
76
    def __init__(self, number_canvases = 1, size_row = 1):
77
        """!
78
        @brief Constructor of cluster visualizer.
79
        
80
        @param[in] number_canvases (uint): Number of canvases that is used for visualization.
81
        @param[in] size_row (uint): Amount of canvases that can be placed in one row.
82
        
83
        Example:
84
        @code
85
            # load 2D data sample
86
            sample_2d = read_sample(SIMPLE_SAMPLES.SAMPLE_SIMPLE1);
87
            
88
            # load 3D data sample
89
            sample_3d = read_sample(FCPS_SAMPLES.SAMPLE_HEPTA);
90
            
91
            # extract clusters from the first sample using DBSCAN algorithm
92
            dbscan_instance = dbscan(sample_2d, 0.4, 2, False);
93
            dbscan_instance.process();
94
            clusters_sample_2d = dbscan_instance.get_clusters();
95
        
96
            # extract clusters from the second sample using DBSCAN algorithm
97
            dbscan_instance = dbscan(sample_3d, 1, 3, True);
98
            dbscan_instance.process();
99
            clusters_sample_3d = dbscan_instance.get_clusters();
100
            
101
            # create plot with two canvases where each row contains 2 canvases.
102
            size = 2;
103
            row_size = 2;
104
            visualizer = cluster_visualizer(size, row_size);
105
            
106
            # place clustering result of sample_2d to the first canvas
107
            visualizer.append_clusters(clusters_sample_2d, sample_2d, 0, markersize = 5);
108
            
109
            # place clustering result of sample_3d to the second canvas
110
            visualizer.append_clusters(clusters_sample_3d, sample_3d, 1, markersize = 30);
111
            
112
            # show plot
113
            visualizer.show();
114
        @endcode
115
        
116
        """
117
        
118
        self.__number_canvases = number_canvases;
119
        self.__size_row = size_row;
120
        self.__canvas_clusters = [ [] for _ in range(number_canvases) ];
121
        self.__canvas_dimensions = [ None for _ in range(number_canvases) ];
122
        self.__canvas_titles = [ None for _ in range(number_canvases) ];
123
        
124
        self.__default_2d_marker_size = 5;
125
        self.__default_3d_marker_size = 30;
126
    
127
    
128
    def append_cluster(self, cluster, data = None, canvas = 0, marker = '.', markersize = None, color = None):
129
        """!
130
        @brief Appends cluster to canvas for drawing.
131
        
132
        @param[in] cluster (list): cluster that may consist of indexes of objects from the data or object itself.
133
        @param[in] data (list): If defines that each element of cluster is considered as a index of object from the data.
134
        @param[in] canvas (uint): Number of canvas that should be used for displaying cluster.
135
        @param[in] marker (string): Marker that is used for displaying objects from cluster on the canvas.
136
        @param[in] markersize (uint): Size of marker.
137
        @param[in] color (string): Color of marker.
138
        
139
        @return Returns index of cluster descriptor on the canvas.
140
        
141
        """
142
        
143
        if (len(cluster) == 0):
144
            return;
145
        
146
        if (canvas > self.__number_canvases):
147
            raise NameError('Canvas does ' + canvas + ' not exists.');
148
        
149
        if (color is None):
150
            index_color = len(self.__canvas_clusters[canvas]) % len(color_list.TITLES);
151
            color = color_list.TITLES[index_color];
152
        
153
        added_canvas_descriptor = canvas_cluster_descr(cluster, data, marker, markersize, color);
154
        self.__canvas_clusters[canvas].append( added_canvas_descriptor );
155
        
156
        dimension = 0;
157
        if (data is None):
158
            dimension = len(cluster[0]);
159
            if (self.__canvas_dimensions[canvas] is None):
160
                self.__canvas_dimensions[canvas] = dimension;
161
            elif (self.__canvas_dimensions[canvas] != dimension):
162
                raise NameError('Only clusters with the same dimension of objects can be displayed on canvas.');
163
                
164
        else:
165
            dimension = len(data[0]);
166
            if (self.__canvas_dimensions[canvas] is None):
167
                self.__canvas_dimensions[canvas] = dimension;
168
            elif (self.__canvas_dimensions[canvas] != dimension):
169
                raise NameError('Only clusters with the same dimension of objects can be displayed on canvas.');
170
171
        if ( (dimension < 1) and (dimension > 3) ):
172
            raise NameError('Only objects with size dimension 1 (1D plot), 2 (2D plot) or 3 (3D plot) can be displayed.');
173
        
174
        if (markersize is None):
175
            if ( (dimension == 1) or (dimension == 2) ):
176
                added_canvas_descriptor.markersize = self.__default_2d_marker_size;
177
            elif (dimension == 3):
178
                added_canvas_descriptor.markersize = self.__default_3d_marker_size;
179
        
180
        return len(self.__canvas_clusters[canvas]) - 1;
181
    
182
    
183
    def append_cluster_attribute(self, index_canvas, index_cluster, data, marker = None, markersize = None):
184
        """!
185
        @brief Append cluster attribure for cluster on specific canvas.
186
        @details Attribute it is data that is visualized for specific cluster using its color, marker and markersize if last two is not specified.
187
        
188
        @param[in] index_canvas (uint): Index canvas where cluster is located.
189
        @param[in] index_cluster (uint): Index cluster whose attribute should be added.
190
        @param[in] data (list): List of points (data) that represents attribute.
191
        @param[in] marker (string): Marker that is used for displaying objects from cluster on the canvas.
192
        @param[in] markersize (uint): Size of marker.
193
        
194
        """
195
        
196
        cluster_descr = self.__canvas_clusters[index_canvas][index_cluster];
197
        attribute_marker = marker;
198
        if (attribute_marker is None):
199
            attribute_marker = cluster_descr.marker;
200
        
201
        attribure_markersize = markersize;
202
        if (attribure_markersize is None):
203
            attribure_markersize = cluster_descr.markersize;
204
        
205
        attribute_color = cluster_descr.color;
206
        
207
        added_attribute_cluster_descriptor = canvas_cluster_descr(data, None, attribute_marker, attribure_markersize, attribute_color);
208
        self.__canvas_clusters[index_canvas][index_cluster].attributes.append(added_attribute_cluster_descriptor);
209
    
210
    
211
    def append_clusters(self, clusters, data = None, canvas = 0, marker = '.', markersize = None):
212
        """!
213
        @brief Appends list of cluster to canvas for drawing.
214
        
215
        @param[in] clusters (list): List of clusters where each cluster may consist of indexes of objects from the data or object itself.
216
        @param[in] data (list): If defines that each element of cluster is considered as a index of object from the data.
217
        @param[in] canvas (uint): Number of canvas that should be used for displaying clusters.
218
        @param[in] marker (string): Marker that is used for displaying objects from clusters on the canvas.
219
        @param[in] markersize (uint): Size of marker.
220
        
221
        """
222
        
223
        for cluster in clusters:
224
            self.append_cluster(cluster, data, canvas, marker, markersize);
225
    
226
    
227
    def set_canvas_title(self, text, canvas = 0):
228
        """!
229
        @brief Set title for specified canvas.
230
        
231
        @param[in] text (string): Title for canvas.
232
        @param[in] canvas (uint): Index of canvas where title should be displayed.
233
        
234
        """
235
        
236
        if (canvas > self.__number_canvases):
237
            raise NameError('Canvas does ' + canvas + ' not exists.');
238
        
239
        self.__canvas_titles[canvas] = text;
240
    
241
    
242
    def show(self, figure = None, visible_axis = True, visible_grid = True, display = True):
243
        """!
244
        @brief Shows clusters (visualize).
245
        
246
        @param[in] figure (fig): Defines requirement to use specified figure, if None - new figure is created for drawing clusters.
247
        @param[in] visible_axis (bool): Defines visibility of axes on each canvas, if True - axes are invisible.
248
        @param[in] visible_grid (bool): Defines visibility of axes on each canvas, if True - grid is displayed.
249
        @param[in] display (bool): Defines requirement to display clusters on a stage, if True - clusters are displayed, if False - plt.show() should be called by user."
250
        
251
        @return (fig) Figure where clusters are shown.
252
        
253
        """
254
        
255
        canvas_shift = 0;
256
        cluster_figure = None;
257
        if (figure is not None):
258
            canvas_shift = len(figure.get_axes());
259
            cluster_figure = figure;
260
        else:
261
            cluster_figure = plt.figure();
262
        
263
        maximum_cols = self.__size_row;
264
        maximum_rows = math.ceil( (self.__number_canvases + canvas_shift) / maximum_cols);
265
        
266
        grid_spec = gridspec.GridSpec(maximum_rows, maximum_cols);
267
        
268
        for index_canvas in range(len(self.__canvas_clusters)):
269
            canvas_data = self.__canvas_clusters[index_canvas];
270
            dimension = self.__canvas_dimensions[index_canvas];
271
            
272
            #ax = axes[real_index];
273
            if ( (dimension == 1) or (dimension == 2) ):
274
                ax = cluster_figure.add_subplot(grid_spec[index_canvas + canvas_shift]);
275
            else:
276
                ax = cluster_figure.add_subplot(grid_spec[index_canvas + canvas_shift], projection='3d');
277
            
278
            if (len(canvas_data) == 0):
279
                plt.setp(ax, visible = False);
280
            
281
            for cluster_descr in canvas_data:
282
                self.__draw_canvas_cluster(ax, dimension, cluster_descr);
283
                
284
                for attribute_descr in cluster_descr.attributes:
285
                    self.__draw_canvas_cluster(ax, dimension, attribute_descr);
286
            
287
            if (visible_axis is True):
288
                ax.xaxis.set_ticklabels([]);
289
                ax.yaxis.set_ticklabels([]);
290
                
291
                if (dimension == 3):
292
                    ax.zaxis.set_ticklabels([]);
293
            
294
            if (self.__canvas_titles[index_canvas] is not None):
295
                ax.set_title(self.__canvas_titles[index_canvas]);
296
            
297
            ax.grid(visible_grid);
298
        
299
        if (display is True):
300
            plt.show();
301
        
302
        return cluster_figure;
303
    
304
    
305
    """!
306
    @brief Draw canvas cluster descriptor.
307
    
308
    @param[in] ax (Axis): Axis of the canvas where canvas cluster descriptor should be displayed.
309
    @param[in] dimension (uint): Canvas dimension.
310
    @param[in] cluster_descr (canvas_cluster_descr): Canvas cluster descriptor that should be displayed.
311
312
    @return (fig) Figure where clusters are shown.
313
    
314
    """
315
    def __draw_canvas_cluster(self, ax, dimension, cluster_descr):
316
        cluster = cluster_descr.cluster;
317
        data = cluster_descr.data;
318
        marker = cluster_descr.marker;
319
        markersize = cluster_descr.markersize;
320
        color = cluster_descr.color;
321
        
322
        for item in cluster:
323 View Code Duplication
            if (dimension == 1):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
324
                if (data is None):
325
                    ax.plot(item[0], 0.0, color = color, marker = marker, markersize = markersize);
326
                else:
327
                    ax.plot(data[item][0], 0.0, color = color, marker = marker, markersize = markersize);
328
329
            elif (dimension == 2):
330 View Code Duplication
                if (data is None):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
331
                    ax.plot(item[0], item[1], color = color, marker = marker, markersize = markersize);
332
                else:
333
                    ax.plot(data[item][0], data[item][1], color = color, marker = marker, markersize = markersize);
334
        
335
            elif (dimension == 3):
336
                if (data is None):
337
                    ax.scatter(item[0], item[1], item[2], c = color, marker = marker, s = markersize);
338
                else:
339
                    ax.scatter(data[item][0], data[item][1], data[item][2], c = color, marker = marker, s = markersize);