@@ -281,7 +281,7 @@ class Position(enum.IntFlag):
281281
282282 # Start row/column where the heatmap items are inserted
283283 # (after the titles/legends/dendrograms)
284- Row0 = 4
284+ Row0 = 5
285285 Col0 = 3
286286 # The (color) legend row and column
287287 LegendRow , LegendCol = 0 , 4
@@ -293,6 +293,8 @@ class Position(enum.IntFlag):
293293 DendrogramRow = 2
294294 # The row for top column annotation labels
295295 TopLabelsRow = 3
296+ # Top color annotation row
297+ TopAnnotationRow = 4
296298 # Vertical split title column
297299 GroupTitleColumn = 0
298300
@@ -315,6 +317,7 @@ def __init__(self, parent=None, **kwargs):
315317 self .col_dendrograms = [] # type: List[Optional[DendrogramWidget]]
316318 self .row_dendrograms = [] # type: List[Optional[DendrogramWidget]]
317319 self .right_side_colors = [] # type: List[Optional[GraphicsPixmapWidget]]
320+ self .top_side_colors = [] # type: List[Optional[GraphicsPixmapWidget]]
318321 self .heatmap_colormap_legend = None
319322 self .bottom_legend_container = None
320323 self .__layout = GridLayout ()
@@ -346,6 +349,7 @@ def clear(self):
346349 self .col_dendrograms = []
347350 self .row_dendrograms = []
348351 self .right_side_colors = []
352+ self .top_side_colors = []
349353 self .heatmap_colormap_legend = None
350354 self .bottom_legend_container = None
351355 self .parts = None
@@ -366,12 +370,14 @@ def setHeatmaps(self, parts: 'Parts') -> None:
366370 # The row for the horizontal dendrograms
367371 DendrogramRow = self .DendrogramRow
368372 RightLabelColumn = Col0 + 2 * M + 1
373+ TopAnnotationRow = self .TopAnnotationRow
369374 TopLabelsRow = self .TopLabelsRow
370375 BottomLabelsRow = Row0 + N
371376 colormap = self .__colormap
372377 column_dendrograms : List [Optional [DendrogramWidget ]] = [None ] * M
373378 row_dendrograms : List [Optional [DendrogramWidget ]] = [None ] * N
374379 right_side_colors : List [Optional [GraphicsPixmapWidget ]] = [None ] * N
380+ top_side_colors : List [Optional [GraphicsPixmapWidget ]] = [None ] * M
375381
376382 data = parts .data
377383 if parts .col_names is None :
@@ -495,8 +501,6 @@ def setHeatmaps(self, parts: 'Parts') -> None:
495501 objectName = "row-labels-right"
496502 )
497503 labelslist .setMaximumWidth (300 )
498- pm = QPixmap (1 , rowitem .size )
499- pm .fill (Qt .transparent )
500504 rowauxsidecolor = GraphicsPixmapWidget (
501505 parent = self , visible = False ,
502506 scaleContents = True , aspectMode = Qt .IgnoreAspectRatio ,
@@ -523,10 +527,20 @@ def setHeatmaps(self, parts: 'Parts') -> None:
523527 visible = self .__columnLabelPosition & Position .Top ,
524528 objectName = "column-labels-top" ,
525529 )
530+ colauxsidecolor = GraphicsPixmapWidget (
531+ parent = self , visible = False ,
532+ scaleContents = True , aspectMode = Qt .IgnoreAspectRatio ,
533+ sizePolicy = QSizePolicy (QSizePolicy .Ignored ,
534+ QSizePolicy .Maximum ),
535+ minimumSize = QSizeF (- 1 , 10 )
536+ )
537+
526538 grid .addItem (labelslist , TopLabelsRow , Col0 + 2 * j + 1 ,
527539 Qt .AlignBottom | Qt .AlignLeft )
540+ grid .addItem (colauxsidecolor , TopAnnotationRow , Col0 + 2 * j + 1 )
528541 col_annotation_widgets .append (labelslist )
529542 col_annotation_widgets_top .append (labelslist )
543+ top_side_colors [j ] = colauxsidecolor
530544
531545 # Bottom attr annotations
532546 labelslist = TextListWidget (
@@ -553,6 +567,16 @@ def setHeatmaps(self, parts: 'Parts') -> None:
553567 self .TopLabelsRow , RightLabelColumn - 1 ,
554568 )
555569
570+ col_color_annotation_header = QGraphicsSimpleTextItem ("" , self )
571+ grid .addItem (SimpleLayoutItem (
572+ col_color_annotation_header , anchor = (1 , 1 ), anchorItem = (1 , 1 ),
573+ resizeContents = True ,
574+ aspectMode = Qt .KeepAspectRatio ,
575+ sizePolicy = QSizePolicy (QSizePolicy .Maximum , QSizePolicy .Preferred ),
576+ ),
577+ TopAnnotationRow , 0 , 1 , Col0 , alignment = Qt .AlignRight
578+ )
579+
556580 legend = GradientLegendWidget (
557581 parts .span [0 ], parts .span [1 ],
558582 colormap ,
@@ -563,12 +587,38 @@ def setHeatmaps(self, parts: 'Parts') -> None:
563587 )
564588 legend .setMaximumWidth (300 )
565589 grid .addItem (legend , self .LegendRow , self .LegendCol , 1 , M * 2 )
566- legend_container = QGraphicsWidget (
567- visible = False ,
568- sizePolicy = QSizePolicy (QSizePolicy .Maximum , QSizePolicy .Fixed )
590+
591+ def container (parent = None , orientation = Qt .Horizontal , margin = 0 , spacing = 0 , ** kwargs ):
592+ widget = QGraphicsWidget (** kwargs )
593+ layout = QGraphicsLinearLayout (orientation )
594+ layout .setContentsMargins (margin , margin , margin , margin )
595+ layout .setSpacing (spacing )
596+ widget .setLayout (layout )
597+ if parent is not None :
598+ widget .setParentItem (parent )
599+
600+ return widget
601+ # Container for color annotation legends
602+ legend_container = container (
603+ spacing = 3 ,
604+ sizePolicy = QSizePolicy (QSizePolicy .Maximum , QSizePolicy .Fixed ),
605+ visible = False , objectName = "annotation-legend-container"
606+ )
607+ legend_container_rows = container (
608+ parent = legend_container ,
609+ sizePolicy = QSizePolicy (QSizePolicy .Maximum , QSizePolicy .Fixed ),
610+ visible = False , objectName = "row-annotation-legend-container"
569611 )
570- legend_container .setLayout (QGraphicsLinearLayout ())
571- legend_container .layout ().setContentsMargins (0 , 0 , 0 , 0 )
612+ legend_container_cols = container (
613+ parent = legend_container ,
614+ sizePolicy = QSizePolicy (QSizePolicy .Maximum , QSizePolicy .Fixed ),
615+ visible = False , objectName = "col-annotation-legend-container"
616+ )
617+ # ? keep refs to child containers; segfault in scene.clear() ?
618+ legend_container ._refs = (legend_container_rows , legend_container_cols )
619+ legend_container .layout ().addItem (legend_container_rows )
620+ legend_container .layout ().addItem (legend_container_cols )
621+
572622 grid .addItem (legend_container , BottomLabelsRow + 1 , Col0 + 1 , 1 , M * 2 - 1 ,
573623 alignment = Qt .AlignRight )
574624
@@ -580,6 +630,7 @@ def setHeatmaps(self, parts: 'Parts') -> None:
580630 self .col_dendrograms = column_dendrograms
581631 self .row_dendrograms = row_dendrograms
582632 self .right_side_colors = right_side_colors
633+ self .top_side_colors = top_side_colors
583634 self .heatmap_colormap_legend = legend
584635 self .bottom_legend_container = legend_container
585636 self .parts = parts
@@ -682,50 +733,113 @@ def setRowSideColorAnnotations(
682733 name: str
683734 Name/title for the annotation column.
684735 """
685- items = self .right_side_colors
686736 col = self .Col0 + 2 * len (self .parts .columns )
737+ legend_layout = self .bottom_legend_container .layout ()
738+ legend_container = legend_layout .itemAt (1 )
739+ self .__setColorAnnotationsHelper (
740+ data , colormap , name , self .right_side_colors , col , Qt .Vertical ,
741+ legend_container
742+ )
743+ legend_container .setVisible (True )
744+
745+ def setColumnSideColorAnnotations (
746+ self , data : np .ndarray , colormap : ColorMap = None , name = ""
747+ ):
748+ """
749+ Set an optional column color annotations.
750+
751+ Parameters
752+ ----------
753+ data: Optional[np.ndarray]
754+ A sequence such that it is accepted by `colormap.apply`. If None
755+ then the color annotations are cleared.
756+ colormap: ColorMap
757+ name: str
758+ Name/title for the annotation column.
759+ """
760+ row = self .TopAnnotationRow
761+ legend_layout = self .bottom_legend_container .layout ()
762+ legend_container = legend_layout .itemAt (0 )
763+ self .__setColorAnnotationsHelper (
764+ data , colormap , name , self .top_side_colors , row , Qt .Horizontal ,
765+ legend_container )
766+ legend_container .setVisible (True )
767+
768+ def __setColorAnnotationsHelper (
769+ self , data : np .ndarray , colormap : ColorMap , name : str ,
770+ items : List [GraphicsPixmapWidget ], position : int ,
771+ orientation : Qt .Orientation , legend_container : QGraphicsWidget ):
687772 layout = self .__layout
688- nameitem = layout .itemAt (self .TopLabelsRow , col )
689- width = QFontMetrics (self .font ()).lineSpacing ()
690- legend_container = self .bottom_legend_container
773+ if orientation == Qt .Horizontal :
774+ nameitem = layout .itemAt (position , 0 )
775+ else :
776+ nameitem = layout .itemAt (self .TopLabelsRow , position )
777+ size = QFontMetrics (self .font ()).lineSpacing ()
691778 layout_clear (legend_container .layout ())
692779
780+ def grid_set_maximum_size (position : int , size : float ):
781+ if orientation == Qt .Horizontal :
782+ layout .setRowMaximumHeight (position , size )
783+ else :
784+ layout .setColumnMaximumWidth (position , size )
785+
786+ def set_minimum_size (item : QGraphicsLayoutItem , size : float ):
787+ if orientation == Qt .Horizontal :
788+ item .setMinimumHeight (size )
789+ else :
790+ item .setMinimumWidth (size )
791+ item .updateGeometry ()
792+
793+ def reset_minimum_size (item : QGraphicsLayoutItem ):
794+ set_minimum_size (item , - 1 )
795+
693796 def set_hidden (item : GraphicsPixmapWidget ):
694797 item .setVisible (False )
695- item .setMinimumWidth (- 1 )
696- item .updateGeometry ()
798+ reset_minimum_size (item ,)
697799
698800 def set_visible (item : GraphicsPixmapWidget ):
699801 item .setVisible (True )
700- item .setMinimumWidth (10 )
802+ set_minimum_size (item , 10 )
803+
804+ def set_preferred_size (item , size ):
805+ if orientation == Qt .Horizontal :
806+ item .setPreferredHeight (size )
807+ else :
808+ item .setPreferredWidth (size )
701809 item .updateGeometry ()
702810
703811 if data is None :
704812 apply_all (filter (None , items ), set_hidden )
705- layout .setColumnMaximumWidth (col , 0 )
813+ grid_set_maximum_size (position , 0 )
814+
706815 nameitem .item .setVisible (False )
707816 nameitem .updateGeometry ()
708817 legend_container .setVisible (False )
709818 return
710819 else :
711820 apply_all (filter (None , items ), set_visible )
712- layout . setColumnMaximumWidth ( col , FLT_MAX )
821+ grid_set_maximum_size ( position , FLT_MAX )
713822 legend_container .setVisible (True )
714823
715- parts = self .parts .rows
824+ if orientation == Qt .Horizontal :
825+ parts = self .parts .columns
826+ else :
827+ parts = self .parts .rows
716828 for p , item in zip (parts , items ):
717829 if item is not None :
718830 subset = data [p .normalized_indices ]
719831 subset = colormap .apply (subset )
720- img = qimage_from_array (subset .reshape ((- 1 , 1 , subset .shape [- 1 ])))
832+ rgbdata = subset .reshape ((- 1 , 1 , subset .shape [- 1 ]))
833+ if orientation == Qt .Horizontal :
834+ rgbdata = rgbdata .reshape ((1 , - 1 , rgbdata .shape [- 1 ]))
835+ img = qimage_from_array (rgbdata )
721836 item .setPixmap (img )
722837 item .setVisible (True )
723- item . setPreferredWidth ( width )
838+ set_preferred_size ( item , size )
724839
725840 nameitem .item .setText (name )
726841 nameitem .item .setVisible (True )
727- nameitem .setPreferredWidth (width )
728- nameitem .updateGeometry ()
842+ set_preferred_size (nameitem , size )
729843
730844 container = legend_container .layout ()
731845 if isinstance (colormap , CategoricalColorMap ):
0 commit comments