@@ -332,6 +332,8 @@ def plot_genes(
332332 x_range : Optional [gplt_params .x_range ] = None ,
333333 title : Optional [gplt_params .title ] = None ,
334334 output_backend : gplt_params .output_backend = gplt_params .output_backend_default ,
335+ gene_labels : Optional [gplt_params .gene_labels ] = None ,
336+ labels : Optional [gplt_params .labels ] = None ,
335337 ) -> gplt_params .figure :
336338 debug = self ._log .debug
337339
@@ -407,6 +409,101 @@ def plot_genes(
407409 line_width = 0 ,
408410 )
409411
412+ if gene_labels :
413+ debug ("determine new figure height and range to accommodate gene labels" )
414+
415+ # Increase the figure height by a certain factor, to accommodate labels.
416+ height_increase_factor = 1.2
417+ fig .height = int (fig .height * height_increase_factor )
418+
419+ # Get the original y_range.
420+ # Note: fig.y_range is not subscriptable.
421+ orig_y_range = fig .y_range .start , fig .y_range .end
422+
423+ # Determine the midpoint of the original range, to rescale outward from there.
424+ orig_mid_y_range = (orig_y_range [0 ] + orig_y_range [1 ]) / 2
425+ orig_y_range_extent = orig_y_range [1 ] - orig_y_range [0 ]
426+
427+ # Determine the new start and end points of the extended range.
428+ new_y_range_extent = orig_y_range_extent * height_increase_factor
429+ new_y_range_extent_half = new_y_range_extent / 2
430+ new_y_start = orig_mid_y_range - new_y_range_extent_half
431+ new_y_end = orig_mid_y_range + new_y_range_extent_half
432+
433+ # Set the new y_range.
434+ fig .y_range = bokeh .models .Range1d (new_y_start , new_y_end )
435+
436+ debug ("determine midpoint of each gene rectangle" )
437+ data ["mid_x" ] = (data ["start" ] + data ["end" ]) / 2
438+
439+ debug ("make gene labels and pointers" )
440+
441+ # Put gene_labels into a new column, where the gene_id matches.
442+ # Fill unmapped genes with empty strings, otherwise "NaN" would be displayed.
443+ data ["gene_label" ] = data ["ID" ].map (gene_labels ).fillna ("" )
444+
445+ # Put gene pointers (▲ or ▼) in a new column, depending on the strand.
446+ # Except if the gene_label is null or an empty string, which should not be shown.
447+ data ["gene_pointer" ] = data .apply (
448+ lambda row : ("▼" if row ["strand" ] == "+" else "▲" )
449+ if row ["gene_label" ]
450+ else "" ,
451+ axis = 1 ,
452+ )
453+
454+ # Put the pointer above or below the gene rectangle, depending on + or - strand.
455+ neg_strand_pointer_y = orig_mid_y_range - 1.2
456+ pos_strand_pointer_y = orig_mid_y_range + 1.2
457+ data ["pointer_y" ] = data ["strand" ].apply (
458+ lambda strand : pos_strand_pointer_y
459+ if strand == "+"
460+ else neg_strand_pointer_y
461+ )
462+
463+ # Put the label above or below the gene rectangle, depending on + or - strand.
464+ neg_strand_label_y = orig_mid_y_range - 1.15
465+ pos_strand_label_y = orig_mid_y_range + 1.25
466+ data ["label_y" ] = data ["strand" ].apply (
467+ lambda strand : pos_strand_label_y
468+ if strand == "+"
469+ else neg_strand_label_y
470+ )
471+
472+ # Get the data as a ColumnDataSource.
473+ data_as_cds = bokeh .models .ColumnDataSource (data )
474+
475+ # Create a LabelSet for the gene pointers.
476+ gene_pointers_ls = bokeh .models .LabelSet (
477+ source = data_as_cds ,
478+ x = "mid_x" ,
479+ y = "pointer_y" ,
480+ text = "gene_pointer" ,
481+ text_align = "center" ,
482+ text_baseline = "middle" ,
483+ text_font_size = "9pt" ,
484+ text_color = "#444444" ,
485+ )
486+
487+ # Create a LabelSet for the gene labels.
488+ gene_labels_ls = bokeh .models .LabelSet (
489+ source = data_as_cds ,
490+ x = "mid_x" ,
491+ y = "label_y" ,
492+ text = "gene_label" ,
493+ text_align = "left" ,
494+ text_baseline = "middle" ,
495+ text_font_size = "9pt" ,
496+ text_color = "#444444" ,
497+ x_offset = 8 ,
498+ )
499+
500+ # Add the markers and labels to the figure.
501+ fig .add_layout (gene_pointers_ls )
502+ fig .add_layout (gene_labels_ls )
503+
504+ if labels :
505+ fig .add_layout (labels )
506+
410507 debug ("tidy up the plot" )
411508 fig .ygrid .visible = False
412509 yticks = [0.4 , 1.4 ]
0 commit comments