Total Complexity | 173 |
Total Lines | 939 |
Duplicated Lines | 0 % |
Complex classes like Orange.widgets.visualize.OWHeatMap 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 | import sys |
||
167 | class OWHeatMap(widget.OWWidget): |
||
168 | name = "Heat Map" |
||
169 | description = "Heatmap visualization." |
||
170 | icon = "icons/Heatmap.svg" |
||
171 | priority = 1040 |
||
172 | |||
173 | inputs = [("Data", Orange.data.Table, "set_dataset")] |
||
174 | outputs = [("Selected Data", Orange.data.Table, widget.Default)] |
||
175 | |||
176 | settingsHandler = settings.DomainContextHandler() |
||
177 | |||
178 | NoSorting, Clustering, OrderedClustering = 0, 1, 2 |
||
179 | NoPosition, PositionTop, PositionBottom = 0, 1, 2 |
||
180 | |||
181 | gamma = settings.Setting(0) |
||
182 | threshold_low = settings.Setting(0.0) |
||
183 | threshold_high = settings.Setting(1.0) |
||
184 | # Type of sorting to apply on rows |
||
185 | sort_rows = settings.Setting(NoSorting) |
||
186 | # Type of sorting to apply on columns |
||
187 | sort_columns = settings.Setting(NoSorting) |
||
188 | # Display stripe with averages |
||
189 | averages = settings.Setting(True) |
||
190 | # Display legend |
||
191 | legend = settings.Setting(True) |
||
192 | # Annotations |
||
193 | annotation_index = settings.ContextSetting(0) |
||
194 | # Stored color palette settings |
||
195 | color_settings = settings.Setting(None) |
||
196 | user_palettes = settings.Setting([]) |
||
197 | palette_index = settings.Setting(0) |
||
198 | column_label_pos = settings.Setting(PositionTop) |
||
199 | |||
200 | auto_commit = settings.Setting(True) |
||
201 | |||
202 | want_graph = True |
||
203 | |||
204 | def __init__(self, parent=None): |
||
205 | super().__init__(self, parent) |
||
206 | |||
207 | # set default settings |
||
208 | self.SpaceX = 10 |
||
209 | self.ShowAnnotation = 0 |
||
210 | |||
211 | self.colorSettings = None |
||
212 | self.selectedSchemaIndex = 0 |
||
213 | |||
214 | self.palette = None |
||
215 | self.keep_aspect = False |
||
216 | #: The data striped of discrete features |
||
217 | self.data = None |
||
218 | #: The original data with all features (retained to |
||
219 | #: preserve the domain on the output) |
||
220 | self.input_data = None |
||
221 | |||
222 | self.annotation_vars = ['(None)'] |
||
223 | self.__rows_cache = {} |
||
224 | self.__columns_cache = {} |
||
225 | |||
226 | # GUI definition |
||
227 | colorbox = gui.widgetBox(self.controlArea, "Color") |
||
228 | self.color_cb = gui.comboBox(colorbox, self, "palette_index") |
||
229 | self.color_cb.setIconSize(QSize(64, 16)) |
||
230 | palettes = sorted(colorbrewer.colorSchemes["sequential"].items()) |
||
231 | palettes += [("Green-Black-Red", |
||
232 | {3: [(0, 255, 0), (0, 0, 0), (255, 0, 0)]})] |
||
233 | palettes += self.user_palettes |
||
234 | model = color_palette_model(palettes, self.color_cb.iconSize()) |
||
235 | model.setParent(self) |
||
236 | self.color_cb.setModel(model) |
||
237 | self.color_cb.activated.connect(self.update_color_schema) |
||
238 | self.color_cb.setCurrentIndex(self.palette_index) |
||
239 | # TODO: Add 'Manage/Add/Remove' action. |
||
240 | |||
241 | form = QtGui.QFormLayout( |
||
242 | formAlignment=Qt.AlignLeft, |
||
243 | labelAlignment=Qt.AlignLeft, |
||
244 | fieldGrowthPolicy=QtGui.QFormLayout.AllNonFixedFieldsGrow |
||
245 | ) |
||
246 | |||
247 | lowslider = gui.hSlider( |
||
248 | colorbox, self, "threshold_low", minValue=0.0, maxValue=1.0, |
||
249 | step=0.05, ticks=True, intOnly=False, |
||
250 | createLabel=False, callback=self.update_color_schema) |
||
251 | highslider = gui.hSlider( |
||
252 | colorbox, self, "threshold_high", minValue=0.0, maxValue=1.0, |
||
253 | step=0.05, ticks=True, intOnly=False, |
||
254 | createLabel=False, callback=self.update_color_schema) |
||
255 | |||
256 | form.addRow("Low:", lowslider) |
||
257 | form.addRow("High:", highslider) |
||
258 | colorbox.layout().addLayout(form) |
||
259 | |||
260 | sortbox = gui.widgetBox(self.controlArea, "Sorting") |
||
261 | # For attributes |
||
262 | gui.comboBox(sortbox, self, "sort_columns", |
||
263 | items=["No sorting", |
||
264 | "Clustering", |
||
265 | "Clustering with leaf ordering"], |
||
266 | label='Columns', |
||
267 | callback=self.update_sorting_attributes) |
||
268 | |||
269 | # For examples |
||
270 | gui.comboBox(sortbox, self, "sort_rows", |
||
271 | items=["No sorting", |
||
272 | "Clustering", |
||
273 | "Clustering with leaf ordering"], |
||
274 | label='Rows', |
||
275 | callback=self.update_sorting_examples) |
||
276 | |||
277 | box = gui.widgetBox(self.controlArea, 'Annotation && Legends') |
||
278 | |||
279 | gui.checkBox(box, self, 'legend', 'Show legend', |
||
280 | callback=self.update_legend) |
||
281 | |||
282 | gui.checkBox(box, self, 'averages', 'Stripes with averages', |
||
283 | callback=self.update_averages_stripe) |
||
284 | |||
285 | annotbox = gui.widgetBox(box, "Row Annotations") |
||
286 | annotbox.setFlat(True) |
||
287 | self.annotations_cb = gui.comboBox(annotbox, self, "annotation_index", |
||
288 | items=self.annotation_vars, |
||
289 | callback=self.update_annotations) |
||
290 | |||
291 | posbox = gui.widgetBox(box, "Column Labels Position") |
||
292 | posbox.setFlat(True) |
||
293 | |||
294 | gui.comboBox( |
||
295 | posbox, self, "column_label_pos", |
||
296 | items=["None", "Top", "Bottom", "Top and Bottom"], |
||
297 | callback=self.update_column_annotations) |
||
298 | |||
299 | gui.checkBox(self.controlArea, self, "keep_aspect", |
||
300 | "Keep aspect ratio", box="Resize", |
||
301 | callback=self.__aspect_mode_changed) |
||
302 | |||
303 | splitbox = gui.widgetBox(self.controlArea, "Split By") |
||
304 | self.split_lb = QtGui.QListWidget() |
||
305 | self.split_lb.itemSelectionChanged.connect(self.update_heatmaps) |
||
306 | splitbox.layout().addWidget(self.split_lb) |
||
307 | |||
308 | gui.rubber(self.controlArea) |
||
309 | gui.auto_commit(self.controlArea, self, "auto_commit", "Commit") |
||
310 | |||
311 | # Scene with heatmap |
||
312 | self.heatmap_scene = self.scene = HeatmapScene(parent=self) |
||
313 | self.selection_manager = HeatmapSelectionManager(self) |
||
314 | self.selection_manager.selection_changed.connect( |
||
315 | self.__update_selection_geometry) |
||
316 | self.selection_manager.selection_finished.connect( |
||
317 | self.on_selection_finished) |
||
318 | self.heatmap_scene.set_selection_manager(self.selection_manager) |
||
319 | |||
320 | item = QtGui.QGraphicsRectItem(0, 0, 10, 10, None, self.heatmap_scene) |
||
321 | self.heatmap_scene.itemsBoundingRect() |
||
322 | self.heatmap_scene.removeItem(item) |
||
323 | |||
324 | policy = (Qt.ScrollBarAlwaysOn if self.keep_aspect |
||
325 | else Qt.ScrollBarAlwaysOff) |
||
326 | self.sceneView = QGraphicsView( |
||
327 | self.scene, |
||
328 | verticalScrollBarPolicy=policy, |
||
329 | horizontalScrollBarPolicy=policy) |
||
330 | |||
331 | self.sceneView.viewport().installEventFilter(self) |
||
332 | |||
333 | self.mainArea.layout().addWidget(self.sceneView) |
||
334 | self.heatmap_scene.widget = None |
||
335 | |||
336 | self.heatmap_widget_grid = [[]] |
||
337 | self.attr_annotation_widgets = [] |
||
338 | self.attr_dendrogram_widgets = [] |
||
339 | self.gene_annotation_widgets = [] |
||
340 | self.gene_dendrogram_widgets = [] |
||
341 | |||
342 | self.selection_rects = [] |
||
343 | self.selected_rows = [] |
||
344 | self.graphButton.clicked.connect(self.save_graph) |
||
345 | |||
346 | def sizeHint(self): |
||
347 | return QSize(800, 400) |
||
348 | |||
349 | def color_palette(self): |
||
350 | data = self.color_cb.itemData(self.palette_index, role=Qt.UserRole) |
||
351 | if data is None: |
||
352 | return [] |
||
353 | else: |
||
354 | _, colors = max(data.items()) |
||
355 | return color_palette_table( |
||
356 | colors, threshold_low=self.threshold_low, |
||
357 | threshold_high=self.threshold_high) |
||
358 | |||
359 | def selected_split_label(self): |
||
360 | """Return the current selected split label.""" |
||
361 | item = self.split_lb.currentItem() |
||
362 | return str(item.text()) if item else None |
||
363 | |||
364 | def clear(self): |
||
365 | self.data = None |
||
366 | self.input_data = None |
||
367 | self.annotations_cb.clear() |
||
368 | self.annotations_cb.addItem('(None)') |
||
369 | self.split_lb.clear() |
||
370 | self.annotation_vars = ['(None)'] |
||
371 | self.clear_scene() |
||
372 | self.selected_rows = [] |
||
373 | self.__columns_cache.clear() |
||
374 | self.__rows_cache.clear() |
||
375 | |||
376 | def clear_scene(self): |
||
377 | self.selection_manager.set_heatmap_widgets([[]]) |
||
378 | self.heatmap_scene.clear() |
||
379 | self.heatmap_scene.widget = None |
||
380 | self.heatmap_widget_grid = [[]] |
||
381 | self.col_annotation_widgets = [] |
||
382 | self.col_annotation_widgets_bottom = [] |
||
383 | self.col_annotation_widgets_top = [] |
||
384 | self.row_annotation_widgets = [] |
||
385 | self.col_dendrograms = [] |
||
386 | self.row_dendrograms = [] |
||
387 | self.selection_rects = [] |
||
388 | |||
389 | def set_dataset(self, data=None): |
||
390 | """Set the input dataset to display.""" |
||
391 | self.closeContext() |
||
392 | self.clear() |
||
393 | |||
394 | self.error(0) |
||
395 | self.warning(0) |
||
396 | input_data = data |
||
397 | if data is not None and \ |
||
398 | any(var.is_discrete for var in data.domain.attributes): |
||
399 | ndisc = sum(var.is_discrete for var in data.domain.attributes) |
||
400 | data = data.from_table( |
||
401 | Orange.data.Domain([var for var in data.domain.attributes |
||
402 | if var.is_continuous], |
||
403 | data.domain.class_vars, |
||
404 | data.domain.metas), |
||
405 | data) |
||
406 | if not data.domain.attributes: |
||
407 | self.error(0, "No continuous feature columns") |
||
408 | input_data = data = None |
||
409 | else: |
||
410 | self.warning(0, "{} discrete column{} removed" |
||
411 | .format(ndisc, "s" if ndisc > 1 else "")) |
||
412 | |||
413 | self.data = data |
||
414 | self.input_data = input_data |
||
415 | if data is not None: |
||
416 | variables = self.data.domain.class_vars + self.data.domain.metas |
||
417 | variables = [var for var in variables |
||
418 | if isinstance(var, (Orange.data.DiscreteVariable, |
||
419 | Orange.data.StringVariable))] |
||
420 | self.annotation_vars.extend(variables) |
||
421 | |||
422 | for var in variables: |
||
423 | self.annotations_cb.addItem(*gui.attributeItem(var)) |
||
424 | |||
425 | self.split_lb.addItems(candidate_split_labels(data)) |
||
426 | |||
427 | self.openContext(self.data) |
||
428 | if self.annotation_index >= len(self.annotation_vars): |
||
429 | self.annotation_index = 0 |
||
430 | |||
431 | self.update_heatmaps() |
||
432 | |||
433 | self.commit() |
||
434 | |||
435 | def update_heatmaps(self): |
||
436 | if self.data is not None: |
||
437 | self.clear_scene() |
||
438 | self.construct_heatmaps(self.data, self.selected_split_label()) |
||
439 | self.construct_heatmaps_scene(self.heatmapparts) |
||
440 | else: |
||
441 | self.clear() |
||
442 | |||
443 | def _make(self, data, group_var=None, group_key=None): |
||
444 | if group_var is not None: |
||
445 | assert group_var.is_discrete |
||
446 | _col_data, _ = data.get_column_view(group_var) |
||
447 | row_groups = [np.flatnonzero(_col_data == i) |
||
448 | for i in range(len(group_var.values))] |
||
449 | row_indices = [np.flatnonzero(_col_data == i) |
||
450 | for i in range(len(group_var.values))] |
||
451 | row_groups = [namespace(title=name, indices=ind, cluster=None, |
||
452 | cluster_ord=None) |
||
453 | for name, ind in zip(group_var.values, row_indices)] |
||
454 | else: |
||
455 | row_groups = [namespace(title=None, indices=slice(0, -1), |
||
456 | cluster=None, cluster_ord=None)] |
||
457 | |||
458 | if group_key is not None: |
||
459 | col_groups = split_domain(data.domain, group_key) |
||
460 | assert len(col_groups) > 0 |
||
461 | col_indices = [np.array([data.domain.index(var) for var in group]) |
||
462 | for _, group in col_groups] |
||
463 | col_groups = [namespace(title=name, domain=d, indices=ind, |
||
464 | cluster=None, cluster_ord=None) |
||
465 | for (name, d), ind in zip(col_groups, col_indices)] |
||
466 | else: |
||
467 | col_groups = [ |
||
468 | namespace( |
||
469 | title=None, indices=slice(0, len(data.domain.attributes)), |
||
470 | domain=data.domain, cluster=None, cluster_ord=None) |
||
471 | ] |
||
472 | |||
473 | minv, maxv = np.nanmin(data.X), np.nanmax(data.X) |
||
474 | |||
475 | parts = namespace( |
||
476 | rows=row_groups, columns=col_groups, |
||
477 | levels=(minv, maxv), |
||
478 | ) |
||
479 | return parts |
||
480 | |||
481 | def cluster_rows(self, data, parts, ordered=False): |
||
482 | row_groups = [] |
||
483 | for row in parts.rows: |
||
484 | if row.cluster is not None: |
||
485 | cluster = row.cluster |
||
486 | else: |
||
487 | cluster = None |
||
488 | if row.cluster_ord is not None: |
||
489 | cluster_ord = row.cluster_ord |
||
490 | else: |
||
491 | cluster_ord = None |
||
492 | |||
493 | need_dist = cluster is None or (ordered and cluster_ord is None) |
||
494 | if need_dist: |
||
495 | subset = data[row.indices] |
||
496 | subset = Orange.distance._preprocess(subset) |
||
497 | matrix = Orange.distance.Euclidean(subset) |
||
498 | |||
499 | if cluster is None: |
||
500 | cluster = hierarchical.dist_matrix_clustering(matrix) |
||
501 | |||
502 | if ordered and cluster_ord is None: |
||
503 | cluster_ord = hierarchical.optimal_leaf_ordering(cluster, matrix) |
||
504 | |||
505 | row_groups.append(namespace(title=row.title, indices=row.indices, |
||
506 | cluster=cluster, cluster_ord=cluster_ord)) |
||
507 | |||
508 | return namespace(columns=parts.columns, rows=row_groups, |
||
509 | levels=parts.levels) |
||
510 | |||
511 | def cluster_columns(self, data, parts, ordered=False): |
||
512 | if len(parts.columns) > 1: |
||
513 | data = vstack_by_subdomain(data, [col.domain for col in parts.columns]) |
||
514 | assert all(var.is_continuous for var in data.domain.attributes) |
||
515 | |||
516 | col0 = parts.columns[0] |
||
517 | if col0.cluster is not None: |
||
518 | cluster = col0.cluster |
||
519 | else: |
||
520 | cluster = None |
||
521 | if col0.cluster_ord is not None: |
||
522 | cluster_ord = col0.cluster_ord |
||
523 | else: |
||
524 | cluster_ord = None |
||
525 | need_dist = cluster is None or (ordered and cluster_ord is None) |
||
526 | |||
527 | if need_dist: |
||
528 | data = Orange.distance._preprocess(data) |
||
529 | matrix = Orange.distance.PearsonR(data, axis=0) |
||
530 | |||
531 | if cluster is None: |
||
532 | cluster = hierarchical.dist_matrix_clustering(matrix) |
||
533 | if ordered and cluster_ord is None: |
||
534 | cluster_ord = hierarchical.optimal_leaf_ordering(cluster, matrix) |
||
535 | |||
536 | col_groups = [namespace(title=col.title, indices=col.indices, |
||
537 | cluster=cluster, cluster_ord=cluster_ord, |
||
538 | domain=col.domain) |
||
539 | for col in parts.columns] |
||
540 | return namespace(columns=col_groups, rows=parts.rows, |
||
541 | levels=parts.levels) |
||
542 | |||
543 | def construct_heatmaps(self, data, split_label=None): |
||
544 | |||
545 | if split_label is not None: |
||
546 | groups = split_domain(data.domain, split_label) |
||
547 | assert len(groups) > 0 |
||
548 | else: |
||
549 | groups = [("", data.domain)] |
||
550 | |||
551 | if data.domain.has_discrete_class: |
||
552 | group_var = data.domain.class_var |
||
553 | else: |
||
554 | group_var = None |
||
555 | |||
556 | self.progressBarInit() |
||
557 | |||
558 | group_label = split_label |
||
559 | |||
560 | parts = self._make(data, group_var, group_label) |
||
561 | # Restore/update the row/columns items descriptions from cache if |
||
562 | # available |
||
563 | if group_var in self.__rows_cache: |
||
564 | parts.rows = self.__rows_cache[group_var].rows |
||
565 | if group_label in self.__columns_cache: |
||
566 | parts.columns = self.__columns_cache[group_label].columns |
||
567 | |||
568 | if self.sort_rows != OWHeatMap.NoSorting: |
||
569 | parts = self.cluster_rows( |
||
570 | self.data, parts, |
||
571 | ordered=self.sort_rows == OWHeatMap.OrderedClustering) |
||
572 | |||
573 | if self.sort_columns != OWHeatMap.NoSorting: |
||
574 | parts = self.cluster_columns( |
||
575 | self.data, parts, |
||
576 | ordered=self.sort_columns == OWHeatMap.OrderedClustering) |
||
577 | |||
578 | # Cache the updated parts |
||
579 | self.__rows_cache[group_var] = parts |
||
580 | self.__columns_cache[group_label] = parts |
||
581 | |||
582 | self.heatmapparts = parts |
||
583 | self.progressBarFinished() |
||
584 | |||
585 | def construct_heatmaps_scene(self, parts): |
||
586 | def select_row(item): |
||
587 | if self.sort_rows == OWHeatMap.NoSorting: |
||
588 | return namespace(title=item.title, indices=item.indices, |
||
589 | cluster=None) |
||
590 | elif self.sort_rows == OWHeatMap.Clustering: |
||
591 | return namespace(title=item.title, indices=item.indices, |
||
592 | cluster=item.cluster) |
||
593 | elif self.sort_rows == OWHeatMap.OrderedClustering: |
||
594 | return namespace(title=item.title, indices=item.indices, |
||
595 | cluster=item.cluster_ord) |
||
596 | |||
597 | def select_col(item): |
||
598 | if self.sort_columns == OWHeatMap.NoSorting: |
||
599 | return namespace(title=item.title, indices=item.indices, |
||
600 | cluster=None, domain=item.domain) |
||
601 | elif self.sort_columns == OWHeatMap.Clustering: |
||
602 | return namespace(title=item.title, indices=item.indices, |
||
603 | cluster=item.cluster, domain=item.domain) |
||
604 | elif self.sort_columns == OWHeatMap.OrderedClustering: |
||
605 | return namespace(title=item.title, indices=item.indices, |
||
606 | cluster=item.cluster_ord, domain=item.domain) |
||
607 | |||
608 | rows = [select_row(rowitem) for rowitem in parts.rows] |
||
609 | cols = [select_col(colitem) for colitem in parts.columns] |
||
610 | parts = namespace(columns=cols, rows=rows, levels=parts.levels) |
||
611 | |||
612 | self.setup_scene(parts) |
||
613 | |||
614 | def setup_scene(self, parts): |
||
615 | # parts = * a list of row descriptors (title, indices, cluster,) |
||
616 | # * a list of col descriptors (title, indices, cluster, domain) |
||
617 | |||
618 | self.heatmap_scene.clear() |
||
619 | # The top level container widget |
||
620 | widget = GraphicsWidget() |
||
621 | widget.layoutDidActivate.connect(self.__update_selection_geometry) |
||
622 | |||
623 | grid = QtGui.QGraphicsGridLayout() |
||
624 | grid.setSpacing(self.SpaceX) |
||
625 | self.heatmap_scene.addItem(widget) |
||
626 | |||
627 | N, M = len(parts.rows), len(parts.columns) |
||
628 | |||
629 | # Start row/column where the heatmap items are inserted |
||
630 | # (after the titles/legends/dendrograms) |
||
631 | Row0 = 3 |
||
632 | Col0 = 3 |
||
633 | LegendRow = 0 |
||
634 | # The column for the vertical dendrogram |
||
635 | DendrogramColumn = 0 |
||
636 | # The row for the horizontal dendrograms |
||
637 | DendrogramRow = 1 |
||
638 | RightLabelColumn = Col0 + M |
||
639 | TopLabelsRow = 2 |
||
640 | BottomLabelsRow = Row0 + 2 * N |
||
641 | |||
642 | widget.setLayout(grid) |
||
643 | |||
644 | palette = self.color_palette() |
||
645 | |||
646 | sort_i = [] |
||
647 | sort_j = [] |
||
648 | |||
649 | column_dendrograms = [None] * M |
||
650 | row_dendrograms = [None] * N |
||
651 | |||
652 | for i, rowitem in enumerate(parts.rows): |
||
653 | if rowitem.title: |
||
654 | title = QtGui.QGraphicsSimpleTextItem(rowitem.title, widget) |
||
655 | item = GraphicsSimpleTextLayoutItem(title, parent=grid) |
||
656 | grid.addItem(item, Row0 + i * 2, Col0) |
||
657 | |||
658 | if rowitem.cluster: |
||
659 | dendrogram = DendrogramWidget( |
||
660 | parent=widget, |
||
661 | selectionMode=DendrogramWidget.NoSelection, |
||
662 | hoverHighlightEnabled=True) |
||
663 | dendrogram.set_root(rowitem.cluster) |
||
664 | dendrogram.setMaximumWidth(100) |
||
665 | dendrogram.setMinimumWidth(100) |
||
666 | # Ignore dendrogram vertical size hint (heatmap's size |
||
667 | # should define the row's vertical size). |
||
668 | dendrogram.setSizePolicy( |
||
669 | QSizePolicy.Expanding, QSizePolicy.Ignored) |
||
670 | dendrogram.itemClicked.connect( |
||
671 | lambda item, partindex=i: |
||
672 | self.__select_by_cluster(item, partindex) |
||
673 | ) |
||
674 | |||
675 | grid.addItem(dendrogram, Row0 + i * 2 + 1, DendrogramColumn) |
||
676 | sort_i.append(np.array(leaf_indices(rowitem.cluster))) |
||
677 | row_dendrograms[i] = dendrogram |
||
678 | else: |
||
679 | sort_i.append(None) |
||
680 | |||
681 | for j, colitem in enumerate(parts.columns): |
||
682 | if colitem.title: |
||
683 | title = QtGui.QGraphicsSimpleTextItem(colitem.title, widget) |
||
684 | item = GraphicsSimpleTextLayoutItem(title, parent=grid) |
||
685 | grid.addItem(item, 1, Col0 + j) |
||
686 | |||
687 | if colitem.cluster: |
||
688 | dendrogram = DendrogramWidget( |
||
689 | parent=widget, |
||
690 | orientation=DendrogramWidget.Top, |
||
691 | selectionMode=DendrogramWidget.NoSelection, |
||
692 | hoverHighlightEnabled=False) |
||
693 | |||
694 | dendrogram.set_root(colitem.cluster) |
||
695 | dendrogram.setMaximumHeight(100) |
||
696 | dendrogram.setMinimumHeight(100) |
||
697 | # Ignore dendrogram horizontal size hint (heatmap's width |
||
698 | # should define the column width). |
||
699 | dendrogram.setSizePolicy( |
||
700 | QSizePolicy.Ignored, QSizePolicy.Expanding) |
||
701 | grid.addItem(dendrogram, DendrogramRow, Col0 + j) |
||
702 | sort_j.append(np.array(leaf_indices(colitem.cluster))) |
||
703 | column_dendrograms[j] = dendrogram |
||
704 | else: |
||
705 | sort_j.append(None) |
||
706 | |||
707 | heatmap_widgets = [] |
||
708 | for i in range(N): |
||
709 | heatmap_row = [] |
||
710 | for j in range(M): |
||
711 | row_ix = parts.rows[i].indices |
||
712 | col_ix = parts.columns[j].indices |
||
713 | hw = GraphicsHeatmapWidget(parent=widget) |
||
714 | data = self.data[row_ix, col_ix].X |
||
715 | |||
716 | if sort_i[i] is not None: |
||
717 | data = data[sort_i[i]] |
||
718 | if sort_j[j] is not None: |
||
719 | data = data[:, sort_j[j]] |
||
720 | |||
721 | hw.set_heatmap_data(data) |
||
722 | hw.set_levels(parts.levels) |
||
723 | hw.set_color_table(palette) |
||
724 | hw.set_show_averages(self.averages) |
||
725 | |||
726 | grid.addItem(hw, Row0 + i * 2 + 1, Col0 + j) |
||
727 | grid.setRowStretchFactor(Row0 + i * 2 + 1, data.shape[0] * 100) |
||
728 | heatmap_row.append(hw) |
||
729 | heatmap_widgets.append(heatmap_row) |
||
730 | |||
731 | row_annotation_widgets = [] |
||
732 | col_annotation_widgets = [] |
||
733 | col_annotation_widgets_top = [] |
||
734 | col_annotation_widgets_bottom = [] |
||
735 | |||
736 | for i, rowitem in enumerate(parts.rows): |
||
737 | if isinstance(rowitem.indices, slice): |
||
738 | indices = np.array( |
||
739 | range(*rowitem.indices.indices(self.data.X.shape[0]))) |
||
740 | else: |
||
741 | indices = rowitem.indices |
||
742 | if sort_i[i] is not None: |
||
743 | indices = indices[sort_i[i]] |
||
744 | |||
745 | labels = [str(i) for i in indices] |
||
746 | |||
747 | labelslist = GraphicsSimpleTextList( |
||
748 | labels, parent=widget, orientation=Qt.Vertical) |
||
749 | |||
750 | labelslist._indices = indices |
||
751 | labelslist.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) |
||
752 | labelslist.setContentsMargins(0.0, 0.0, 0.0, 0.0) |
||
753 | labelslist.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding) |
||
754 | |||
755 | grid.addItem(labelslist, Row0 + i * 2 + 1, RightLabelColumn) |
||
756 | grid.setAlignment(labelslist, Qt.AlignLeft) |
||
757 | row_annotation_widgets.append(labelslist) |
||
758 | |||
759 | for j, colitem in enumerate(parts.columns): |
||
760 | # Top attr annotations |
||
761 | if isinstance(colitem.indices, slice): |
||
762 | indices = np.array( |
||
763 | range(*colitem.indices.indices(self.data.X.shape[1]))) |
||
764 | else: |
||
765 | indices = colitem.indices |
||
766 | if sort_j[j] is not None: |
||
767 | indices = indices[sort_j[j]] |
||
768 | |||
769 | labels = [self.data.domain[i].name for i in indices] |
||
770 | |||
771 | labelslist = GraphicsSimpleTextList( |
||
772 | labels, parent=widget, orientation=Qt.Horizontal) |
||
773 | labelslist.setAlignment(Qt.AlignBottom | Qt.AlignLeft) |
||
774 | labelslist._indices = indices |
||
775 | |||
776 | labelslist.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) |
||
777 | |||
778 | grid.addItem(labelslist, TopLabelsRow, Col0 + j, |
||
779 | Qt.AlignBottom | Qt.AlignLeft) |
||
780 | col_annotation_widgets.append(labelslist) |
||
781 | col_annotation_widgets_top.append(labelslist) |
||
782 | |||
783 | # Bottom attr annotations |
||
784 | labelslist = GraphicsSimpleTextList( |
||
785 | labels, parent=widget, orientation=Qt.Horizontal) |
||
786 | labelslist.setAlignment(Qt.AlignTop | Qt.AlignHCenter) |
||
787 | labelslist.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) |
||
788 | |||
789 | grid.addItem(labelslist, BottomLabelsRow, Col0 + j) |
||
790 | col_annotation_widgets.append(labelslist) |
||
791 | col_annotation_widgets_bottom.append(labelslist) |
||
792 | |||
793 | legend = GradientLegendWidget( |
||
794 | parts.levels[0], parts.levels[1], |
||
795 | parent=widget) |
||
796 | |||
797 | legend.set_color_table(palette) |
||
798 | legend.setMinimumSize(QSizeF(100, 20)) |
||
799 | legend.setVisible(self.legend) |
||
800 | |||
801 | grid.addItem(legend, LegendRow, Col0) |
||
802 | |||
803 | self.heatmap_scene.widget = widget |
||
804 | self.heatmap_widget_grid = heatmap_widgets |
||
805 | self.selection_manager.set_heatmap_widgets(heatmap_widgets) |
||
806 | |||
807 | self.row_annotation_widgets = row_annotation_widgets |
||
808 | self.col_annotation_widgets = col_annotation_widgets |
||
809 | self.col_annotation_widgets_top = col_annotation_widgets_top |
||
810 | self.col_annotation_widgets_bottom = col_annotation_widgets_bottom |
||
811 | self.col_dendrograms = column_dendrograms |
||
812 | self.row_dendrograms = row_dendrograms |
||
813 | |||
814 | self.update_annotations() |
||
815 | self.update_column_annotations() |
||
816 | |||
817 | self.__update_size_constraints() |
||
818 | |||
819 | def __update_size_constraints(self): |
||
820 | if self.heatmap_scene.widget is not None: |
||
821 | mode = Qt.KeepAspectRatio if self.keep_aspect \ |
||
822 | else Qt.IgnoreAspectRatio |
||
823 | size = QSizeF(self.sceneView.viewport().size()) |
||
824 | widget = self.heatmap_scene.widget |
||
825 | layout = widget.layout() |
||
826 | if mode == Qt.IgnoreAspectRatio: |
||
827 | # Reset the row height constraints ... |
||
828 | for i, hm_row in enumerate(self.heatmap_widget_grid): |
||
829 | layout.setRowMaximumHeight(3 + i * 2 + 1, np.finfo(np.float32).max) |
||
830 | layout.setRowPreferredHeight(3 + i * 2 + 1, 0) |
||
831 | # ... and resize to match the viewport, taking the minimum size |
||
832 | # into account |
||
833 | minsize = widget.minimumSize() |
||
834 | size = size.expandedTo(minsize) |
||
835 | widget.resize(size) |
||
836 | else: |
||
837 | # First set/update the widget's width (the layout will |
||
838 | # distribute the available width to heatmap widgets in |
||
839 | # the grid) |
||
840 | minsize = widget.minimumSize() |
||
841 | widget.resize(size.expandedTo(minsize).width(), |
||
842 | widget.size().height()) |
||
843 | # calculate and set the heatmap row's heights based on |
||
844 | # the width |
||
845 | for i, hm_row in enumerate(self.heatmap_widget_grid): |
||
846 | heights = [] |
||
847 | for hm in hm_row: |
||
848 | hm_size = QSizeF(hm.heatmap_item.pixmap().size()) |
||
849 | hm_size = scaled( |
||
850 | hm_size, QSizeF(hm.size().width(), -1), |
||
851 | Qt.KeepAspectRatioByExpanding) |
||
852 | |||
853 | heights.append(hm_size.height()) |
||
854 | layout.setRowMaximumHeight(3 + i * 2 + 1, max(heights)) |
||
855 | layout.setRowPreferredHeight(3 + i * 2 + 1, max(heights)) |
||
856 | |||
857 | # set/update the widget's height |
||
858 | constraint = QSizeF(size.width(), -1) |
||
859 | sh = widget.effectiveSizeHint(Qt.PreferredSize, constraint) |
||
860 | minsize = widget.effectiveSizeHint(Qt.MinimumSize, constraint) |
||
861 | sh = sh.expandedTo(minsize).expandedTo(widget.minimumSize()) |
||
862 | |||
863 | # print("Resize 2", sh) |
||
864 | # print(" old:", widget.size().width(), widget.size().height()) |
||
865 | # print(" new:", widget.size().width(), sh.height()) |
||
866 | |||
867 | widget.resize(sh) |
||
868 | # print("Did resize") |
||
869 | self.__fixup_grid_layout() |
||
870 | |||
871 | def __fixup_grid_layout(self): |
||
872 | self.__update_margins() |
||
873 | rect = self.scene.widget.geometry() |
||
874 | self.heatmap_scene.setSceneRect(rect) |
||
875 | self.__update_selection_geometry() |
||
876 | |||
877 | def __aspect_mode_changed(self): |
||
878 | if self.keep_aspect: |
||
879 | policy = Qt.ScrollBarAlwaysOn |
||
880 | else: |
||
881 | policy = Qt.ScrollBarAlwaysOff |
||
882 | |||
883 | viewport = self.sceneView.viewport() |
||
884 | # Temp. remove the event filter so we won't process the resize twice |
||
885 | viewport.removeEventFilter(self) |
||
886 | self.sceneView.setVerticalScrollBarPolicy(policy) |
||
887 | self.sceneView.setHorizontalScrollBarPolicy(policy) |
||
888 | viewport.installEventFilter(self) |
||
889 | self.__update_size_constraints() |
||
890 | |||
891 | def eventFilter(self, reciever, event): |
||
892 | if reciever is self.sceneView.viewport() and \ |
||
893 | event.type() == QEvent.Resize: |
||
894 | self.__update_size_constraints() |
||
895 | |||
896 | return super().eventFilter(reciever, event) |
||
897 | |||
898 | def __update_margins(self): |
||
899 | """ |
||
900 | Update dendrogram and text list widgets margins to include the |
||
901 | space for average stripe. |
||
902 | """ |
||
903 | def offset(hm): |
||
904 | if hm.show_averages: |
||
905 | return hm.averages_item.size().width() |
||
906 | else: |
||
907 | return 0 |
||
908 | |||
909 | hm_row = self.heatmap_widget_grid[0] |
||
910 | hm_col = next(zip(*self.heatmap_widget_grid)) |
||
911 | dendrogram_col = self.col_dendrograms |
||
912 | dendrogram_row = self.row_dendrograms |
||
913 | |||
914 | col_annot = zip(self.col_annotation_widgets_top, |
||
915 | self.col_annotation_widgets_bottom) |
||
916 | row_annot = self.row_annotation_widgets |
||
917 | |||
918 | for hm, annot, dendrogram in zip(hm_row, col_annot, dendrogram_col): |
||
919 | width = hm.size().width() |
||
920 | left_offset = offset(hm) |
||
921 | col_count = hm.heatmap_data().shape[1] |
||
922 | half_col = (width - left_offset) / col_count / 2 |
||
923 | if dendrogram is not None: |
||
924 | _, top, _, bottom = dendrogram.getContentsMargins() |
||
925 | dendrogram.setContentsMargins( |
||
926 | left_offset + half_col, top, half_col, bottom) |
||
927 | |||
928 | _, top, right, bottom = annot[0].getContentsMargins() |
||
929 | annot[0].setContentsMargins(left_offset, top, right, bottom) |
||
930 | _, top, right, bottom = annot[1].getContentsMargins() |
||
931 | annot[1].setContentsMargins(left_offset, top, right, bottom) |
||
932 | |||
933 | for hm, annot, dendrogram in zip(hm_col, row_annot, dendrogram_row): |
||
934 | height = hm.size().height() |
||
935 | row_count = hm.heatmap_data().shape[0] |
||
936 | half_row = height / row_count / 2 |
||
937 | if dendrogram is not None: |
||
938 | left, _, right, _ = dendrogram.getContentsMargins() |
||
939 | dendrogram.setContentsMargins(left, half_row, right, half_row) |
||
940 | |||
941 | def heatmap_widgets(self): |
||
942 | """Iterate over heatmap widgets. |
||
943 | """ |
||
944 | for item in self.heatmap_scene.items(): |
||
945 | if isinstance(item, GraphicsHeatmapWidget): |
||
946 | yield item |
||
947 | |||
948 | def label_widgets(self): |
||
949 | """Iterate over GraphicsSimpleTextList widgets. |
||
950 | """ |
||
951 | for item in self.heatmap_scene.items(): |
||
952 | if isinstance(item, GraphicsSimpleTextList): |
||
953 | yield item |
||
954 | |||
955 | def dendrogram_widgets(self): |
||
956 | """Iterate over dendrogram widgets |
||
957 | """ |
||
958 | for item in self.heatmap_scene.items(): |
||
959 | if isinstance(item, DendrogramWidget): |
||
960 | yield item |
||
961 | |||
962 | def legend_widgets(self): |
||
963 | for item in self.heatmap_scene.items(): |
||
964 | if isinstance(item, GradientLegendWidget): |
||
965 | yield item |
||
966 | |||
967 | def update_averages_stripe(self): |
||
968 | """Update the visibility of the averages stripe. |
||
969 | """ |
||
970 | if self.data is not None: |
||
971 | for widget in self.heatmap_widgets(): |
||
972 | widget.set_show_averages(self.averages) |
||
973 | widget.layout().activate() |
||
974 | |||
975 | self.scene.widget.layout().activate() |
||
976 | self.__fixup_grid_layout() |
||
977 | |||
978 | def update_grid_spacing(self): |
||
979 | """Update layout spacing. |
||
980 | """ |
||
981 | if self.scene.widget: |
||
982 | layout = self.scene.widget.layout() |
||
983 | layout.setSpacing(self.SpaceX) |
||
984 | self.__fixup_grid_layout() |
||
985 | |||
986 | def update_color_schema(self): |
||
987 | palette = self.color_palette() |
||
988 | for heatmap in self.heatmap_widgets(): |
||
989 | heatmap.set_color_table(palette) |
||
990 | |||
991 | for legend in self.legend_widgets(): |
||
992 | legend.set_color_table(palette) |
||
993 | |||
994 | def update_sorting_examples(self): |
||
995 | if self.data: |
||
996 | self.update_heatmaps() |
||
997 | |||
998 | def update_sorting_attributes(self): |
||
999 | if self.data: |
||
1000 | self.update_heatmaps() |
||
1001 | |||
1002 | def update_legend(self): |
||
1003 | for item in self.heatmap_scene.items(): |
||
1004 | if isinstance(item, GradientLegendWidget): |
||
1005 | item.setVisible(self.legend) |
||
1006 | |||
1007 | def update_annotations(self): |
||
1008 | if self.data is not None: |
||
1009 | if self.annotation_vars: |
||
1010 | var = self.annotation_vars[self.annotation_index] |
||
1011 | if var == '(None)': |
||
1012 | var = None |
||
1013 | else: |
||
1014 | var = None |
||
1015 | |||
1016 | show = var is not None |
||
1017 | if show: |
||
1018 | annot_col, _ = self.data.get_column_view(var) |
||
1019 | else: |
||
1020 | annot_col = None |
||
1021 | |||
1022 | for labelslist in self.row_annotation_widgets: |
||
1023 | labelslist.setVisible(bool(show)) |
||
1024 | if show: |
||
1025 | indices = labelslist._indices |
||
1026 | data = annot_col[indices] |
||
1027 | labels = [var.str_val(val) for val in data] |
||
1028 | labelslist.set_labels(labels) |
||
1029 | |||
1030 | def update_column_annotations(self): |
||
1031 | if self.data is not None: |
||
1032 | show_top = self.column_label_pos & OWHeatMap.PositionTop |
||
1033 | show_bottom = self.column_label_pos & OWHeatMap.PositionBottom |
||
1034 | |||
1035 | for labelslist in self.col_annotation_widgets_top: |
||
1036 | labelslist.setVisible(show_top) |
||
1037 | |||
1038 | TopLabelsRow = 2 |
||
1039 | Row0 = 3 |
||
1040 | BottomLabelsRow = Row0 + 2 * len(self.heatmapparts.rows) |
||
1041 | |||
1042 | layout = self.heatmap_scene.widget.layout() |
||
1043 | layout.setRowMaximumHeight(TopLabelsRow, -1 if show_top else 0) |
||
1044 | layout.setRowSpacing(TopLabelsRow, -1 if show_top else 0) |
||
1045 | |||
1046 | for labelslist in self.col_annotation_widgets_bottom: |
||
1047 | labelslist.setVisible(show_bottom) |
||
1048 | |||
1049 | layout.setRowMaximumHeight(BottomLabelsRow, -1 if show_top else 0) |
||
1050 | |||
1051 | self.__fixup_grid_layout() |
||
1052 | |||
1053 | def __select_by_cluster(self, item, dendrogramindex): |
||
1054 | # User clicked on a dendrogram node. |
||
1055 | # Select all rows corresponding to the cluster item. |
||
1056 | node = item.node |
||
1057 | try: |
||
1058 | hm = self.heatmap_widget_grid[dendrogramindex][0] |
||
1059 | except IndexError: |
||
1060 | pass |
||
1061 | else: |
||
1062 | key = QtGui.QApplication.keyboardModifiers() |
||
1063 | clear = not (key & ((Qt.ControlModifier | Qt.ShiftModifier | |
||
1064 | Qt.AltModifier))) |
||
1065 | remove = (key & (Qt.ControlModifier | Qt.AltModifier)) |
||
1066 | append = (key & Qt.ControlModifier) |
||
1067 | self.selection_manager.selection_add( |
||
1068 | node.value.first, node.value.last - 1, hm, |
||
1069 | clear=clear, remove=remove, append=append) |
||
1070 | |||
1071 | def __update_selection_geometry(self): |
||
1072 | for item in self.selection_rects: |
||
1073 | item.setParentItem(None) |
||
1074 | self.heatmap_scene.removeItem(item) |
||
1075 | |||
1076 | self.selection_rects = [] |
||
1077 | self.selection_manager.update_selection_rects() |
||
1078 | rects = self.selection_manager.selection_rects |
||
1079 | for rect in rects: |
||
1080 | item = QtGui.QGraphicsRectItem(rect, None) |
||
1081 | pen = QPen(Qt.black, 2) |
||
1082 | pen.setCosmetic(True) |
||
1083 | item.setPen(pen) |
||
1084 | self.heatmap_scene.addItem(item) |
||
1085 | self.selection_rects.append(item) |
||
1086 | |||
1087 | def on_selection_finished(self): |
||
1088 | self.selected_rows = self.selection_manager.selections |
||
1089 | self.commit() |
||
1090 | |||
1091 | def commit(self): |
||
1092 | data = None |
||
1093 | if self.input_data is not None and self.selected_rows: |
||
1094 | sortind = np.hstack([labels._indices for labels in self.row_annotation_widgets]) |
||
1095 | indices = sortind[self.selected_rows] |
||
1096 | data = self.input_data[indices] |
||
1097 | |||
1098 | self.send("Selected Data", data) |
||
1099 | |||
1100 | def save_graph(self): |
||
1101 | from Orange.widgets.data.owsave import OWSave |
||
1102 | |||
1103 | save_img = OWSave(parent=self, data=self.scene, |
||
1104 | file_formats=FileFormats.img_writers) |
||
1105 | save_img.exec_() |
||
1106 | |||
1997 |
This can be caused by one of the following:
1. Missing Dependencies
This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.
2. Missing __init__.py files
This error could also result from missing
__init__.py
files in your module folders. Make sure that you place one file in each sub-folder.