Skip to content

Commit eab555b

Browse files
committed
heatmap: Add column color annotations
1 parent 11a2ef5 commit eab555b

File tree

1 file changed

+120
-21
lines changed

1 file changed

+120
-21
lines changed

Orange/widgets/visualize/utils/heatmap.py

Lines changed: 120 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ class Position(enum.IntFlag):
278278

279279
# Start row/column where the heatmap items are inserted
280280
# (after the titles/legends/dendrograms)
281-
Row0 = 4
281+
Row0 = 5
282282
Col0 = 3
283283
# The (color) legend row and column
284284
LegendRow, LegendCol = 0, 4
@@ -290,6 +290,8 @@ class Position(enum.IntFlag):
290290
DendrogramRow = 2
291291
# The row for top column annotation labels
292292
TopLabelsRow = 3
293+
# Top color annotation row
294+
TopAnnotationRow = 4
293295
# Vertical split title column
294296
GroupTitleColumn = 0
295297

@@ -312,6 +314,7 @@ def __init__(self, parent=None, **kwargs):
312314
self.col_dendrograms = [] # type: List[Optional[DendrogramWidget]]
313315
self.row_dendrograms = [] # type: List[Optional[DendrogramWidget]]
314316
self.right_side_colors = [] # type: List[Optional[GraphicsPixmapWidget]]
317+
self.top_side_colors = [] # type: List[Optional[GraphicsPixmapWidget]]
315318
self.heatmap_colormap_legend = None
316319
self.bottom_legend_container = None
317320
self.__layout = GridLayout()
@@ -343,6 +346,7 @@ def clear(self):
343346
self.col_dendrograms = []
344347
self.row_dendrograms = []
345348
self.right_side_colors = []
349+
self.top_side_colors = []
346350
self.heatmap_colormap_legend = None
347351
self.bottom_legend_container = None
348352
self.parts = None
@@ -363,12 +367,14 @@ def setHeatmaps(self, parts: 'Parts') -> None:
363367
# The row for the horizontal dendrograms
364368
DendrogramRow = self.DendrogramRow
365369
RightLabelColumn = Col0 + 2 * M + 1
370+
TopAnnotationRow = self.TopAnnotationRow
366371
TopLabelsRow = self.TopLabelsRow
367372
BottomLabelsRow = Row0 + N
368373
colormap = self.__colormap
369374
column_dendrograms: List[Optional[DendrogramWidget]] = [None] * M
370375
row_dendrograms: List[Optional[DendrogramWidget]] = [None] * N
371376
right_side_colors: List[Optional[GraphicsPixmapWidget]] = [None] * N
377+
top_side_colors: List[Optional[GraphicsPixmapWidget]] = [None] * M
372378

373379
data = parts.data
374380
if parts.col_names is None:
@@ -492,8 +498,6 @@ def setHeatmaps(self, parts: 'Parts') -> None:
492498
objectName="row-labels-right"
493499
)
494500
labelslist.setMaximumWidth(300)
495-
pm = QPixmap(1, rowitem.size)
496-
pm.fill(Qt.transparent)
497501
rowauxsidecolor = GraphicsPixmapWidget(
498502
parent=self, visible=False,
499503
scaleContents=True, aspectMode=Qt.IgnoreAspectRatio,
@@ -520,10 +524,20 @@ def setHeatmaps(self, parts: 'Parts') -> None:
520524
visible=self.__columnLabelPosition & Position.Top,
521525
objectName="column-labels-top",
522526
)
527+
colauxsidecolor = GraphicsPixmapWidget(
528+
parent=self, visible=False,
529+
scaleContents=True, aspectMode=Qt.IgnoreAspectRatio,
530+
sizePolicy=QSizePolicy(QSizePolicy.Ignored,
531+
QSizePolicy.Maximum),
532+
minimumSize=QSizeF(-1, 10)
533+
)
534+
523535
grid.addItem(labelslist, TopLabelsRow, Col0 + 2 * j + 1,
524536
Qt.AlignBottom | Qt.AlignLeft)
537+
grid.addItem(colauxsidecolor, TopAnnotationRow, Col0 + 2 * j + 1)
525538
col_annotation_widgets.append(labelslist)
526539
col_annotation_widgets_top.append(labelslist)
540+
top_side_colors[j] = colauxsidecolor
527541

528542
# Bottom attr annotations
529543
labelslist = TextListWidget(
@@ -560,12 +574,33 @@ def setHeatmaps(self, parts: 'Parts') -> None:
560574
)
561575
legend.setMaximumWidth(300)
562576
grid.addItem(legend, self.LegendRow, self.LegendCol, 1, M * 2)
563-
legend_container = QGraphicsWidget(
577+
578+
def container(orientation=Qt.Horizontal, **kwargs):
579+
container = QGraphicsWidget(**kwargs)
580+
layout = QGraphicsLinearLayout(orientation)
581+
layout.setContentsMargins(0, 0, 0, 0)
582+
container.setLayout(layout)
583+
return container
584+
# Container for color annotation legends
585+
legend_container = container(
564586
visible=False,
565-
sizePolicy=QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed)
587+
sizePolicy=QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed),
588+
objectName="annotation-legend-container"
566589
)
567-
legend_container.setLayout(QGraphicsLinearLayout())
568-
legend_container.layout().setContentsMargins(0, 0, 0, 0)
590+
legend_container.layout().setSpacing(3)
591+
legend_container_rows = container(
592+
sizePolicy=QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed),
593+
visible=False, objectName="row-annotation-legend-container"
594+
)
595+
legend_container_cols = container(
596+
sizePolicy=QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed),
597+
visible=False, objectName="row-annotation-legend-container"
598+
)
599+
# ? segfault on scene.clear() ?
600+
legend_container._keep_ref = (legend_container_cols, legend_container_rows)
601+
legend_container.layout().addItem(legend_container_rows)
602+
legend_container.layout().addItem(legend_container_cols)
603+
569604
grid.addItem(legend_container, BottomLabelsRow + 1, Col0 + 1, 1, M * 2 - 1,
570605
alignment=Qt.AlignRight)
571606

@@ -577,6 +612,7 @@ def setHeatmaps(self, parts: 'Parts') -> None:
577612
self.col_dendrograms = column_dendrograms
578613
self.row_dendrograms = row_dendrograms
579614
self.right_side_colors = right_side_colors
615+
self.top_side_colors = top_side_colors
580616
self.heatmap_colormap_legend = legend
581617
self.bottom_legend_container = legend_container
582618
self.parts = parts
@@ -679,50 +715,113 @@ def setRowSideColorAnnotations(
679715
name: str
680716
Name/title for the annotation column.
681717
"""
682-
items = self.right_side_colors
683718
col = self.Col0 + 2 * len(self.parts.columns)
719+
legend_layout = self.bottom_legend_container.layout()
720+
legend_container = legend_layout.itemAt(1)
721+
self.__setColorAnnotationsHelper(
722+
data, colormap, name, self.right_side_colors, col, Qt.Vertical,
723+
legend_container
724+
)
725+
legend_container.setVisible(True)
726+
727+
def setColumnSideColorAnnotations(
728+
self, data: np.ndarray, colormap: ColorMap=None, name=""
729+
):
730+
"""
731+
Set an optional column color annotations.
732+
733+
Parameters
734+
----------
735+
data: Optional[np.ndarray]
736+
A sequence such that it is accepted by `colormap.apply`. If None
737+
then the color annotations are cleared.
738+
colormap: ColorMap
739+
name: str
740+
Name/title for the annotation column.
741+
"""
742+
row = self.TopAnnotationRow
743+
legend_layout = self.bottom_legend_container.layout()
744+
legend_container = legend_layout.itemAt(0)
745+
self.__setColorAnnotationsHelper(
746+
data, colormap, name, self.top_side_colors, row, Qt.Horizontal,
747+
legend_container)
748+
legend_container.setVisible(True)
749+
750+
def __setColorAnnotationsHelper(
751+
self, data: np.ndarray, colormap: ColorMap, name: str,
752+
items: List[GraphicsPixmapWidget], position: int,
753+
orientation: Qt.Orientation, legend_container: QGraphicsWidget):
684754
layout = self.__layout
685-
nameitem = layout.itemAt(self.TopLabelsRow, col)
686-
width = QFontMetrics(self.font()).lineSpacing()
687-
legend_container = self.bottom_legend_container
755+
if orientation == Qt.Horizontal:
756+
nameitem = layout.itemAt(position, 0)
757+
else:
758+
nameitem = layout.itemAt(self.TopLabelsRow, position)
759+
size = QFontMetrics(self.font()).lineSpacing()
688760
layout_clear(legend_container.layout())
689761

762+
def grid_set_maximum_size(position: int, size: float):
763+
if orientation == Qt.Horizontal:
764+
layout.setRowMaximumHeight(position, size)
765+
else:
766+
layout.setColumnMaximumWidth(position, size)
767+
768+
def set_minimum_size(item: QGraphicsLayoutItem, size: float):
769+
if orientation == Qt.Horizontal:
770+
item.setMinimumHeight(size)
771+
else:
772+
item.setMinimumWidth(size)
773+
item.updateGeometry()
774+
775+
def reset_minimum_size(item: QGraphicsLayoutItem):
776+
set_minimum_size(item, -1)
777+
690778
def set_hidden(item: GraphicsPixmapWidget):
691779
item.setVisible(False)
692-
item.setMinimumWidth(-1)
693-
item.updateGeometry()
780+
reset_minimum_size(item,)
694781

695782
def set_visible(item: GraphicsPixmapWidget):
696783
item.setVisible(True)
697-
item.setMinimumWidth(10)
784+
set_minimum_size(item, 10)
785+
786+
def set_preferred_size(item, size):
787+
if orientation == Qt.Horizontal:
788+
item.setPreferredHeight(size)
789+
else:
790+
item.setPreferredWidth(size)
698791
item.updateGeometry()
699792

700793
if data is None:
701794
apply_all(filter(None, items), set_hidden)
702-
layout.setColumnMaximumWidth(col, 0)
795+
grid_set_maximum_size(position, 0)
796+
703797
nameitem.item.setVisible(False)
704798
nameitem.updateGeometry()
705799
legend_container.setVisible(False)
706800
return
707801
else:
708802
apply_all(filter(None, items), set_visible)
709-
layout.setColumnMaximumWidth(col, FLT_MAX)
803+
grid_set_maximum_size(position, FLT_MAX)
710804
legend_container.setVisible(True)
711805

712-
parts = self.parts.rows
806+
if orientation == Qt.Horizontal:
807+
parts = self.parts.columns
808+
else:
809+
parts = self.parts.rows
713810
for p, item in zip(parts, items):
714811
if item is not None:
715812
subset = data[p.normalized_indices]
716813
subset = colormap.apply(subset)
717-
img = qimage_from_array(subset.reshape((-1, 1, subset.shape[-1])))
814+
rgbdata = subset.reshape((-1, 1, subset.shape[-1]))
815+
if orientation == Qt.Horizontal:
816+
rgbdata = rgbdata.reshape((1, -1, rgbdata.shape[-1]))
817+
img = qimage_from_array(rgbdata)
718818
item.setPixmap(img)
719819
item.setVisible(True)
720-
item.setPreferredWidth(width)
820+
set_preferred_size(item, size)
721821

722822
nameitem.item.setText(name)
723823
nameitem.item.setVisible(True)
724-
nameitem.setPreferredWidth(width)
725-
nameitem.updateGeometry()
824+
set_preferred_size(nameitem, size)
726825

727826
container = legend_container.layout()
728827
if isinstance(colormap, CategoricalColorMap):

0 commit comments

Comments
 (0)