@@ -290,7 +290,7 @@ class Position(enum.IntFlag):
290290
291291 # Start row/column where the heatmap items are inserted
292292 # (after the titles/legends/dendrograms)
293- Row0 = 4
293+ Row0 = 5
294294 Col0 = 3
295295 # The (color) legend row and column
296296 LegendRow , LegendCol = 0 , 4
@@ -302,6 +302,8 @@ class Position(enum.IntFlag):
302302 DendrogramRow = 2
303303 # The row for top column annotation labels
304304 TopLabelsRow = 3
305+ # Top color annotation row
306+ TopAnnotationRow = 4
305307 # Vertical split title column
306308 GroupTitleColumn = 0
307309
@@ -324,6 +326,7 @@ def __init__(self, parent=None, **kwargs):
324326 self .col_dendrograms = [] # type: List[Optional[DendrogramWidget]]
325327 self .row_dendrograms = [] # type: List[Optional[DendrogramWidget]]
326328 self .right_side_colors = [] # type: List[Optional[GraphicsPixmapWidget]]
329+ self .top_side_colors = [] # type: List[Optional[GraphicsPixmapWidget]]
327330 self .heatmap_colormap_legend = None
328331 self .bottom_legend_container = None
329332 self .__layout = GridLayout ()
@@ -355,6 +358,7 @@ def clear(self):
355358 self .col_dendrograms = []
356359 self .row_dendrograms = []
357360 self .right_side_colors = []
361+ self .top_side_colors = []
358362 self .heatmap_colormap_legend = None
359363 self .bottom_legend_container = None
360364 self .parts = None
@@ -375,12 +379,14 @@ def setHeatmaps(self, parts: 'Parts') -> None:
375379 # The row for the horizontal dendrograms
376380 DendrogramRow = self .DendrogramRow
377381 RightLabelColumn = Col0 + 2 * M + 1
382+ TopAnnotationRow = self .TopAnnotationRow
378383 TopLabelsRow = self .TopLabelsRow
379384 BottomLabelsRow = Row0 + N
380385 colormap = self .__colormap
381386 column_dendrograms : List [Optional [DendrogramWidget ]] = [None ] * M
382387 row_dendrograms : List [Optional [DendrogramWidget ]] = [None ] * N
383388 right_side_colors : List [Optional [GraphicsPixmapWidget ]] = [None ] * N
389+ top_side_colors : List [Optional [GraphicsPixmapWidget ]] = [None ] * M
384390
385391 data = parts .data
386392 if parts .col_names is None :
@@ -504,8 +510,6 @@ def setHeatmaps(self, parts: 'Parts') -> None:
504510 objectName = "row-labels-right"
505511 )
506512 labelslist .setMaximumWidth (300 )
507- pm = QPixmap (1 , rowitem .size )
508- pm .fill (Qt .transparent )
509513 rowauxsidecolor = GraphicsPixmapWidget (
510514 parent = self , visible = False ,
511515 scaleContents = True , aspectMode = Qt .IgnoreAspectRatio ,
@@ -532,10 +536,20 @@ def setHeatmaps(self, parts: 'Parts') -> None:
532536 visible = self .__columnLabelPosition & Position .Top ,
533537 objectName = "column-labels-top" ,
534538 )
539+ colauxsidecolor = GraphicsPixmapWidget (
540+ parent = self , visible = False ,
541+ scaleContents = True , aspectMode = Qt .IgnoreAspectRatio ,
542+ sizePolicy = QSizePolicy (QSizePolicy .Ignored ,
543+ QSizePolicy .Maximum ),
544+ minimumSize = QSizeF (- 1 , 10 )
545+ )
546+
535547 grid .addItem (labelslist , TopLabelsRow , Col0 + 2 * j + 1 ,
536548 Qt .AlignBottom | Qt .AlignLeft )
549+ grid .addItem (colauxsidecolor , TopAnnotationRow , Col0 + 2 * j + 1 )
537550 col_annotation_widgets .append (labelslist )
538551 col_annotation_widgets_top .append (labelslist )
552+ top_side_colors [j ] = colauxsidecolor
539553
540554 # Bottom attr annotations
541555 labelslist = TextListWidget (
@@ -562,6 +576,16 @@ def setHeatmaps(self, parts: 'Parts') -> None:
562576 self .TopLabelsRow , RightLabelColumn - 1 ,
563577 )
564578
579+ col_color_annotation_header = QGraphicsSimpleTextItem ("" , self )
580+ grid .addItem (SimpleLayoutItem (
581+ col_color_annotation_header , anchor = (1 , 1 ), anchorItem = (1 , 1 ),
582+ resizeContents = True ,
583+ aspectMode = Qt .KeepAspectRatio ,
584+ sizePolicy = QSizePolicy (QSizePolicy .Maximum , QSizePolicy .Preferred ),
585+ ),
586+ TopAnnotationRow , 0 , 1 , Col0 , alignment = Qt .AlignRight
587+ )
588+
565589 legend = GradientLegendWidget (
566590 parts .span [0 ], parts .span [1 ],
567591 colormap ,
@@ -572,12 +596,38 @@ def setHeatmaps(self, parts: 'Parts') -> None:
572596 )
573597 legend .setMaximumWidth (300 )
574598 grid .addItem (legend , self .LegendRow , self .LegendCol , 1 , M * 2 )
575- legend_container = QGraphicsWidget (
576- visible = False ,
577- sizePolicy = QSizePolicy (QSizePolicy .Maximum , QSizePolicy .Fixed )
599+
600+ def container (parent = None , orientation = Qt .Horizontal , margin = 0 , spacing = 0 , ** kwargs ):
601+ widget = QGraphicsWidget (** kwargs )
602+ layout = QGraphicsLinearLayout (orientation )
603+ layout .setContentsMargins (margin , margin , margin , margin )
604+ layout .setSpacing (spacing )
605+ widget .setLayout (layout )
606+ if parent is not None :
607+ widget .setParentItem (parent )
608+
609+ return widget
610+ # Container for color annotation legends
611+ legend_container = container (
612+ spacing = 3 ,
613+ sizePolicy = QSizePolicy (QSizePolicy .Maximum , QSizePolicy .Fixed ),
614+ visible = False , objectName = "annotation-legend-container"
615+ )
616+ legend_container_rows = container (
617+ parent = legend_container ,
618+ sizePolicy = QSizePolicy (QSizePolicy .Maximum , QSizePolicy .Fixed ),
619+ visible = False , objectName = "row-annotation-legend-container"
578620 )
579- legend_container .setLayout (QGraphicsLinearLayout ())
580- legend_container .layout ().setContentsMargins (0 , 0 , 0 , 0 )
621+ legend_container_cols = container (
622+ parent = legend_container ,
623+ sizePolicy = QSizePolicy (QSizePolicy .Maximum , QSizePolicy .Fixed ),
624+ visible = False , objectName = "col-annotation-legend-container"
625+ )
626+ # ? keep refs to child containers; segfault in scene.clear() ?
627+ legend_container ._refs = (legend_container_rows , legend_container_cols )
628+ legend_container .layout ().addItem (legend_container_rows )
629+ legend_container .layout ().addItem (legend_container_cols )
630+
581631 grid .addItem (legend_container , BottomLabelsRow + 1 , Col0 + 1 , 1 , M * 2 - 1 ,
582632 alignment = Qt .AlignRight )
583633
@@ -589,6 +639,7 @@ def setHeatmaps(self, parts: 'Parts') -> None:
589639 self .col_dendrograms = column_dendrograms
590640 self .row_dendrograms = row_dendrograms
591641 self .right_side_colors = right_side_colors
642+ self .top_side_colors = top_side_colors
592643 self .heatmap_colormap_legend = legend
593644 self .bottom_legend_container = legend_container
594645 self .parts = parts
@@ -691,50 +742,113 @@ def setRowSideColorAnnotations(
691742 name: str
692743 Name/title for the annotation column.
693744 """
694- items = self .right_side_colors
695745 col = self .Col0 + 2 * len (self .parts .columns )
746+ legend_layout = self .bottom_legend_container .layout ()
747+ legend_container = legend_layout .itemAt (1 )
748+ self .__setColorAnnotationsHelper (
749+ data , colormap , name , self .right_side_colors , col , Qt .Vertical ,
750+ legend_container
751+ )
752+ legend_container .setVisible (True )
753+
754+ def setColumnSideColorAnnotations (
755+ self , data : np .ndarray , colormap : ColorMap = None , name = ""
756+ ):
757+ """
758+ Set an optional column color annotations.
759+
760+ Parameters
761+ ----------
762+ data: Optional[np.ndarray]
763+ A sequence such that it is accepted by `colormap.apply`. If None
764+ then the color annotations are cleared.
765+ colormap: ColorMap
766+ name: str
767+ Name/title for the annotation column.
768+ """
769+ row = self .TopAnnotationRow
770+ legend_layout = self .bottom_legend_container .layout ()
771+ legend_container = legend_layout .itemAt (0 )
772+ self .__setColorAnnotationsHelper (
773+ data , colormap , name , self .top_side_colors , row , Qt .Horizontal ,
774+ legend_container )
775+ legend_container .setVisible (True )
776+
777+ def __setColorAnnotationsHelper (
778+ self , data : np .ndarray , colormap : ColorMap , name : str ,
779+ items : List [GraphicsPixmapWidget ], position : int ,
780+ orientation : Qt .Orientation , legend_container : QGraphicsWidget ):
696781 layout = self .__layout
697- nameitem = layout .itemAt (self .TopLabelsRow , col )
698- width = QFontMetrics (self .font ()).lineSpacing ()
699- legend_container = self .bottom_legend_container
782+ if orientation == Qt .Horizontal :
783+ nameitem = layout .itemAt (position , 0 )
784+ else :
785+ nameitem = layout .itemAt (self .TopLabelsRow , position )
786+ size = QFontMetrics (self .font ()).lineSpacing ()
700787 layout_clear (legend_container .layout ())
701788
789+ def grid_set_maximum_size (position : int , size : float ):
790+ if orientation == Qt .Horizontal :
791+ layout .setRowMaximumHeight (position , size )
792+ else :
793+ layout .setColumnMaximumWidth (position , size )
794+
795+ def set_minimum_size (item : QGraphicsLayoutItem , size : float ):
796+ if orientation == Qt .Horizontal :
797+ item .setMinimumHeight (size )
798+ else :
799+ item .setMinimumWidth (size )
800+ item .updateGeometry ()
801+
802+ def reset_minimum_size (item : QGraphicsLayoutItem ):
803+ set_minimum_size (item , - 1 )
804+
702805 def set_hidden (item : GraphicsPixmapWidget ):
703806 item .setVisible (False )
704- item .setMinimumWidth (- 1 )
705- item .updateGeometry ()
807+ reset_minimum_size (item ,)
706808
707809 def set_visible (item : GraphicsPixmapWidget ):
708810 item .setVisible (True )
709- item .setMinimumWidth (10 )
811+ set_minimum_size (item , 10 )
812+
813+ def set_preferred_size (item , size ):
814+ if orientation == Qt .Horizontal :
815+ item .setPreferredHeight (size )
816+ else :
817+ item .setPreferredWidth (size )
710818 item .updateGeometry ()
711819
712820 if data is None :
713821 apply_all (filter (None , items ), set_hidden )
714- layout .setColumnMaximumWidth (col , 0 )
822+ grid_set_maximum_size (position , 0 )
823+
715824 nameitem .item .setVisible (False )
716825 nameitem .updateGeometry ()
717826 legend_container .setVisible (False )
718827 return
719828 else :
720829 apply_all (filter (None , items ), set_visible )
721- layout . setColumnMaximumWidth ( col , FLT_MAX )
830+ grid_set_maximum_size ( position , FLT_MAX )
722831 legend_container .setVisible (True )
723832
724- parts = self .parts .rows
833+ if orientation == Qt .Horizontal :
834+ parts = self .parts .columns
835+ else :
836+ parts = self .parts .rows
725837 for p , item in zip (parts , items ):
726838 if item is not None :
727839 subset = data [p .normalized_indices ]
728840 subset = colormap .apply (subset )
729- img = qimage_from_array (subset .reshape ((- 1 , 1 , subset .shape [- 1 ])))
841+ rgbdata = subset .reshape ((- 1 , 1 , subset .shape [- 1 ]))
842+ if orientation == Qt .Horizontal :
843+ rgbdata = rgbdata .reshape ((1 , - 1 , rgbdata .shape [- 1 ]))
844+ img = qimage_from_array (rgbdata )
730845 item .setPixmap (img )
731846 item .setVisible (True )
732- item . setPreferredWidth ( width )
847+ set_preferred_size ( item , size )
733848
734849 nameitem .item .setText (name )
735850 nameitem .item .setVisible (True )
736- nameitem .setPreferredWidth (width )
737- nameitem .updateGeometry ()
851+ set_preferred_size (nameitem , size )
738852
739853 container = legend_container .layout ()
740854 if isinstance (colormap , CategoricalColorMap ):
0 commit comments