Source code for mavis.illustrate.elements

"""
This is the primary module responsible for generating svg visualizations

"""
import re

from .util import dynamic_label_color, generate_interval_mapping, LabelMapping, split_intervals_into_tracks, Tag
from ..annotate.variant import FusionTranscript
from ..constants import CODON_SIZE, GIEMSA_STAIN, ORIENT, STRAND
from ..error import DrawingFitError, NotSpecifiedError
from ..interval import Interval

# draw gene level view
# draw gene box
HEX_WHITE = '#FFFFFF'
HEX_BLACK = '#000000'


[docs]def draw_legend(config, canvas, swatches, border=True): """ generates an svg group object representing the legend """ main_group = canvas.g(class_='legend') y = config.padding if border else 0 x = config.padding if border else 0 for swatch, label in swatches: svg_group = canvas.g() svg_group.add(canvas.rect( (0, 0), (config.legend_swatch_size, config.legend_swatch_size), fill=swatch, stroke=config.legend_swatch_stroke )) svg_group.add(canvas.text( label, insert=(config.legend_swatch_size + config.padding, config.legend_swatch_size / 2), fill=config.legend_font_color, style=config.font_style.format(text_anchor='start', font_size=config.legend_font_size), class_='label' )) svg_group.translate(x, y) main_group.add(svg_group) y += config.legend_swatch_size + config.padding width = max([len(l) for c, l in swatches]) * config.legend_font_size * config.font_width_height_ratio + \ config.padding * (3 if border else 1) + config.legend_swatch_size if border: main_group.add(canvas.rect( (0, 0), (width, y), fill='none', stroke=config.legend_border_stroke, stroke_width=config.legend_border_stroke_width )) else: y -= config.padding setattr(main_group, 'height', y) setattr(main_group, 'width', width) setattr(main_group, 'labels', None) setattr(main_group, 'mapping', None) return main_group
[docs]def draw_exon_track(config, canvas, transcript, mapping, colors=None, x_start=None, x_end=None, translation=None): """ """ colors = {} if colors is None else colors main_group = canvas.g(class_='exon_track') y = config.track_height / 2 exons = sorted(transcript.exons, key=lambda x: x.start) start = Interval.convert_ratioed_pos(mapping, exons[0].start).start if x_start is None else x_start end = Interval.convert_ratioed_pos(mapping, exons[-1].end).end if x_end is None else x_end main_group.add( canvas.rect( (start, y - config.scaffold_height / 2), (end - start + 1, config.scaffold_height), fill=config.scaffold_color, class_='scaffold' )) # draw the exons for exon in exons: start = Interval.convert_ratioed_pos(mapping, exon.start).start end = Interval.convert_ratioed_pos(mapping, exon.end).end pxi = Interval(start, end) exon_number = 'n' try: exon_number = transcript.exon_number(exon) except KeyError: pass group = draw_exon( config, canvas, exon, pxi.length(), config.track_height, colors.get(exon, config.exon1_color), label=exon_number, translation=translation ) group.translate(pxi.start, y - config.track_height / 2) main_group.add(group) setattr(main_group, 'height', y + config.track_height / 2) setattr(main_group, 'width', end - start + 1) return main_group
[docs]def draw_transcript_with_translation( config, canvas, translation, labels, colors, mapping, reference_genome=None, x_start=None, x_end=None ): main_group = canvas.g() ust = translation.transcript.reference_object spl_tx = translation.transcript if x_start is None: x_start = Interval.convert_ratioed_pos(mapping, ust.start).start if x_end is None: x_end = Interval.convert_ratioed_pos(mapping, ust.end).end label_prefix = config.transcript_label_prefix if isinstance(ust, FusionTranscript): label_prefix = config.fusion_label_prefix # if the splicing takes up more room than the track we need to adjust for it y = config.splice_height exon_track_group = draw_exon_track(config, canvas, ust, mapping, colors, translation=translation) exon_track_group.translate(0, y) exon_track_group.add(canvas.text( labels.add(spl_tx, label_prefix), insert=( 0 - config.padding, config.track_height / 2 + config.font_central_shift_ratio * config.label_font_size ), fill=config.label_color, style=config.font_style.format(font_size=config.label_font_size, text_anchor='end'), class_='label' )) # draw the splicing pattern splice_group = canvas.g(class_='splicing') for p1, p2 in zip(spl_tx.splicing_pattern[::2], spl_tx.splicing_pattern[1::2]): a = Interval.convert_pos(mapping, p1) b = Interval.convert_pos(mapping, p2) polyline = [(a, y), (a + (b - a) / 2, y - config.splice_height), (b, y)] p = canvas.polyline(polyline, fill='none') p.dasharray(config.splice_stroke_dasharray) p.stroke(config.splice_color, width=config.splice_stroke_width) splice_group.add(p) y += config.track_height / 2 main_group.add(splice_group) main_group.add(exon_track_group) y += config.track_height / 2 protein_group = canvas.g(class_='protein') y += config.padding protein_group.translate(0, y) # translation track # convert the AA position to cdna position, then convert the cdna to genomic, etc # ==================== adding the translation track ============ translated_genomic_regions = [ spl_tx.convert_cdna_to_genomic(translation.start), spl_tx.convert_cdna_to_genomic(translation.end) ] translated_genomic_regions = [Interval(*sorted(translated_genomic_regions))] for p1, p2 in zip(spl_tx.splicing_pattern[::2], spl_tx.splicing_pattern[1::2]): try: spliced_out_interval = Interval(p1 + 1, p2 - 1) temp = [] for region in translated_genomic_regions: temp.extend(region - spliced_out_interval) translated_genomic_regions = temp except AttributeError: pass s = Interval.convert_pos(mapping, translated_genomic_regions[0].start) t = Interval.convert_pos(mapping, translated_genomic_regions[-1].end) gt = canvas.g(class_='translation') protein_group.add(gt) h = config.translation_track_height for sec in translated_genomic_regions: start = Interval.convert_pos(mapping, sec.start) end = Interval.convert_pos(mapping, sec.end) gt.add( canvas.rect( (start, h / 2 - config.translation_track_height / 2), (end - start + 1, config.translation_track_height), fill=config.translation_scaffold_color, class_='scaffold' )) gt.add(canvas.text( config.translation_end_marker if spl_tx.get_strand() == STRAND.NEG else config.translation_start_marker, insert=( s - config.translation_marker_padding, h / 2 + config.font_central_shift_ratio * config.translation_font_size ), fill=config.label_color, style=config.font_style.format(font_size=config.translation_font_size, text_anchor='end'), class_='label' )) gt.add(canvas.text( config.translation_start_marker if spl_tx.get_strand() == STRAND.NEG else config.translation_end_marker, insert=( t + config.translation_marker_padding, h / 2 + config.font_central_shift_ratio * config.translation_font_size ), fill=config.label_color, style=config.font_style.format(font_size=config.translation_font_size, text_anchor='start'), class_='label' )) gt.add(Tag('title', 'translation cdna({}_{}) c.{}_{} p.{}_{}'.format( translation.start, translation.end, 1, len(translation), 1, len(translation) // CODON_SIZE))) py = h # now draw the domain tracks # need to convert the domain AA positions to cds positions to genomic for i, d in enumerate(sorted(translation.domains, key=lambda x: x.name)): if not re.match(config.domain_name_regex_filter, str(d.name)): continue py += config.padding domain_group = canvas.g(class_='domain') domain_group.add(canvas.rect( (x_start, config.domain_track_height / 2), (x_end - x_start, config.domain_scaffold_height), fill=config.domain_scaffold_color, class_='scaffold' )) fill = config.domain_color percent_match = None try: match, total = d.score_region_mapping(reference_genome) percent_match = int(round(match * 100 / total, 0)) fill = config.domain_fill_gradient[percent_match % len(config.domain_fill_gradient) - 1] except (NotSpecifiedError, AttributeError): pass for region in d.regions: # convert the AA position to cdna position, then convert the cdna to genomic, etc s = translation.convert_aa_to_cdna(region.start) t = translation.convert_aa_to_cdna(region.end) s = Interval.convert_pos(mapping, spl_tx.convert_cdna_to_genomic(s.start)) t = Interval.convert_pos(mapping, spl_tx.convert_cdna_to_genomic(t.end)) if s > t: t, s = (s, t) domain_region_group = canvas.g(class_='domain_region') domain_region_group.add(canvas.rect((s, 0), (t - s + 1, config.domain_track_height), fill=fill, class_='region')) domain_region_group.add(Tag('title', 'domain {} region p.{}_{}{}'.format( d.name if d.name else '', region.start, region.end, ' matched({}%)'.format(percent_match) if percent_match is not None else ''))) domain_group.add(domain_region_group) domain_group.translate(0, py) f = config.label_color if not config.dynamic_labels else dynamic_label_color(config.domain_color) label_group = None for patt, link in config.domain_links.items(): if re.match(patt, d.name): label_group = canvas.a(link.format(d), target='_blank') break if label_group is None: label_group = canvas.g() domain_group.add(label_group) label_group.add(canvas.text( labels.add(d.name, config.domain_label_prefix), insert=( 0 - config.padding, config.domain_track_height / 2 + config.font_central_shift_ratio * config.domain_label_font_size), fill=f, class_='label', style=config.font_style.format(font_size=config.domain_label_font_size, text_anchor='end') )) protein_group.add(domain_group) py += config.domain_track_height y += py main_group.add(protein_group) setattr(main_group, 'height', y) return main_group
[docs]def draw_ustranscript( config, canvas, ust, target_width=None, breakpoints=[], labels=LabelMapping(), colors={}, mapping=None, reference_genome=None, masks=None ): """ builds an svg group representing the transcript. Exons are drawn in a track with the splicing information and domains are drawn in separate tracks below if there are multiple splicing variants then multiple exon tracks are drawn Args: canvas (svgwrite.drawing.Drawing): the main svgwrite object used to create new svg elements target_width (int): the target width of the diagram t (Transcript): the transcript being drawn exon_color (str): the color being used for the fill of the exons utr_color (str): the color for the fill of the UTR regions abrogated_splice_sites (:class:`list` of :class:`int`): list of positions to ignore as splice sites breakpoints (:class:`list` of :class:`Breakpoint`): the breakpoints to overlay Return: svgwrite.container.Group: the group element for the transcript diagram Has the added parameters of labels, height, and mapping """ if ust.get_strand() not in [STRAND.POS, STRAND.NEG]: raise NotSpecifiedError('strand must be positive or negative to draw the ust') if (mapping is None and target_width is None) or (mapping is not None and target_width is not None): raise AttributeError('mapping and target_width arguments are required and mutually exclusive') if mapping is None: mapping = generate_interval_mapping( ust.exons, target_width, config.exon_intron_ratio, config.exon_min_width, min_inter_width=config.min_width ) main_group = canvas.g(class_='ust') y = config.breakpoint_top_margin if len(breakpoints) > 0 else 0 x_start = Interval.convert_ratioed_pos(mapping, ust.start).start x_end = Interval.convert_ratioed_pos(mapping, ust.end).end if target_width: x_start = 0 x_end = target_width if masks is None: masks = [] try: if len(breakpoints) == 1: b = breakpoints[0] if b.orient == ORIENT.RIGHT: masks = [Interval(ust.start, b.start - 1)] elif b.orient == ORIENT.LEFT: masks = [Interval(b.end + 1, ust.end)] elif len(breakpoints) == 2: b1, b2 = sorted(breakpoints) if b1.orient == ORIENT.LEFT and b2.orient == ORIENT.RIGHT: masks = [Interval(b1.end + 1, b2.start - 1)] except AttributeError: pass label_prefix = config.transcript_label_prefix if isinstance(ust, FusionTranscript): label_prefix = config.fusion_label_prefix if len(ust.translations) == 0: y += config.splice_height exon_track_group = draw_exon_track(config, canvas, ust, mapping, colors) exon_track_group.translate(0, y) exon_track_group.add(canvas.text( labels.add(ust, label_prefix), insert=(0 - config.padding, config.track_height / 2 + config.font_central_shift_ratio * config.label_font_size), fill=config.label_color, style=config.font_style.format(font_size=config.label_font_size, text_anchor='end'), class_='label' )) main_group.add(exon_track_group) y += config.track_height else: # draw the protein features if there are any for i, tl in enumerate(ust.translations): gp = draw_transcript_with_translation( config, canvas, tl, labels, colors, mapping, x_start=x_start, x_end=x_end ) gp.translate(0, y) if i < len(ust.translations) - 1: y += config.inner_margin y += gp.height main_group.add(gp) y += config.breakpoint_bottom_margin if len(breakpoints) > 0 else 0 # add masks for mask in masks: pixel = Interval.convert_ratioed_pos(mapping, mask.start) | Interval.convert_ratioed_pos(mapping, mask.end) m = canvas.rect( (pixel.start, 0), (pixel.length(), y), class_='mask', fill=config.mask_fill, opacity=config.mask_opacity, pointer_events='none' ) main_group.add(m) # now overlay the breakpoints on top of everything for i, b in enumerate(breakpoints): pixel = Interval.convert_ratioed_pos(mapping, b.start) | Interval.convert_ratioed_pos(mapping, b.end) bg = draw_breakpoint(config, canvas, b, pixel.length(), y, label=labels.add(b, config.breakpoint_label_prefix)) bg.translate(pixel.start, 0) main_group.add(bg) setattr(main_group, 'height', y) setattr(main_group, 'width', x_end - x_start) setattr(main_group, 'mapping', mapping) setattr(main_group, 'labels', labels) return main_group
[docs]def draw_genes(config, canvas, genes, target_width, breakpoints=None, colors=None, labels=None, plots=None, masks=None): """ draws the genes given in order of their start position trying to minimize the number of tracks required to avoid overlap Args: canvas (svgwrite.drawing.Drawing): the main svgwrite object used to create new svg elements target_width (int): the target width of the diagram genes (:class:`list` of :class:`Gene`): the list of genes to draw breakpoints (:class:`list` of :class:`Breakpoint`): the breakpoints to overlay colors (:class:`dict` of :class:`Gene` and :class:`str`): dictionary of the colors assigned to each Gene as fill Return: svgwrite.container.Group: the group element for the diagram. Has the added parameters of labels, height, and mapping """ # mutable default argument parameters breakpoints = [] if breakpoints is None else breakpoints colors = {} if colors is None else colors labels = LabelMapping() if labels is None else labels plots = plots if plots else [] st = max(min([g.start for g in genes] + [b.start for b in breakpoints]) - config.gene_min_buffer, 1) end = max([g.end for g in genes] + [b.end for b in breakpoints]) + config.gene_min_buffer main_group = canvas.g(class_='genes') mapping = generate_interval_mapping( [g for g in genes], target_width, config.gene_intergenic_ratio, config.gene_min_width, start=st, end=end, min_inter_width=config.min_width ) if masks is None: masks = [] try: if len(breakpoints) == 1: b = breakpoints[0] if b.orient == ORIENT.RIGHT: masks = [Interval(st, b.start - 1)] elif b.orient == ORIENT.LEFT: masks = [Interval(b.end + 1, end)] elif len(breakpoints) == 2: b1, b2 = sorted(breakpoints) if b1.orient == ORIENT.LEFT and b2.orient == ORIENT.RIGHT: masks = [Interval(b1.end, b2.start)] except AttributeError: pass gene_px_intervals = {} for i, gene in enumerate(sorted(genes, key=lambda x: x.start)): s = Interval.convert_ratioed_pos(mapping, gene.start) t = Interval.convert_ratioed_pos(mapping, gene.end) gene_px_intervals[Interval(s.start, t.end)] = gene labels.add(gene, config.gene_label_prefix) tracks = split_intervals_into_tracks(gene_px_intervals) y = config.breakpoint_top_margin main_group.add( canvas.rect( (0, y + config.track_height / 2 - config.scaffold_height / 2 + (len(tracks) - 1) * (config.track_height + config.padding)), (target_width, config.scaffold_height), fill=config.scaffold_color, class_='scaffold' )) tracks.reverse() for track in tracks: # svg works from top down for genepx in track: # draw the gene gene = gene_px_intervals[genepx] group = draw_gene( config, canvas, gene, genepx.length(), config.track_height, colors.get(gene, config.gene1_color), labels.get_key(gene) ) group.translate(genepx.start, y) main_group.add(group) y += config.track_height + config.padding y += config.breakpoint_bottom_margin - config.padding # adding the masks is the final step for mask in masks: pixel = Interval.convert_ratioed_pos(mapping, mask.start) | Interval.convert_ratioed_pos(mapping, mask.end) m = canvas.rect( (pixel.start, 0), (pixel.length(), y), class_='mask', fill=config.mask_fill, pointer_events='none', opacity=config.mask_opacity ) main_group.add(m) # now overlay the breakpoints on top of everything for i, b in enumerate(sorted(breakpoints)): s = Interval.convert_ratioed_pos(mapping, b.start).start t = Interval.convert_ratioed_pos(mapping, b.end).end bg = draw_breakpoint(config, canvas, b, abs(t - s) + 1, y, label=labels.add(b, config.breakpoint_label_prefix)) bg.translate(s, 0) main_group.add(bg) setattr(main_group, 'height', y) setattr(main_group, 'width', target_width) setattr(main_group, 'mapping', mapping) setattr(main_group, 'labels', labels) return main_group
[docs]def draw_vmarker(config, canvas, marker, width, height, label='', color=None): """ Args: canvas (svgwrite.drawing.Drawing): the main svgwrite object used to create new svg elements breakpoint (Breakpoint): the breakpoint to draw width (int): the pixel width height (int): the pixel height Return: svgwrite.container.Group: the group element for the diagram """ color = config.marker_color if color is None else color width = max([config.abs_min_width, width]) g = canvas.g(class_='marker') y = config.padding + config.marker_label_font_size / 2 t = canvas.text( label, insert=(width / 2, y + config.font_central_shift_ratio * config.marker_label_font_size), fill=color, style=config.font_style.format(text_anchor='middle', font_size=config.marker_label_font_size), class_='label' ) y += config.marker_label_font_size / 2 + config.padding g.add(t) g.add(canvas.rect((0, y), (width, height - y), stroke=color, fill='none')) g.add(Tag('title', 'marker {}:{}-{} {}'.format( marker.reference_object, marker.start, marker.end, marker.name))) return g
[docs]def draw_breakpoint(config, canvas, breakpoint, width, height, label=''): """ Args: canvas (svgwrite.drawing.Drawing): the main svgwrite object used to create new svg elements breakpoint (Breakpoint): the breakpoint to draw width (int): the pixel width height (int): the pixel height Return: svgwrite.container.Group: the group element for the diagram """ g = canvas.g(class_='breakpoint') y = config.padding + config.breakpoint_label_font_size / 2 t = canvas.text( label, insert=(width / 2, y + config.font_central_shift_ratio * config.breakpoint_label_font_size), fill=HEX_BLACK, style=config.font_style.format(text_anchor='middle', font_size=config.breakpoint_label_font_size), class_='label' ) y += config.breakpoint_label_font_size / 2 + config.padding g.add(t) r = canvas.rect((0, y), (width, height - y), stroke=config.breakpoint_color, fill='none') r.dasharray(config.breakpoint_stroke_dasharray) g.add(r) if breakpoint.orient == ORIENT.LEFT: l = canvas.line((0, y), (0, height)) l.stroke(config.breakpoint_color, width=config.breakpoint_orient_stroke_width) g.add(l) elif breakpoint.orient == ORIENT.RIGHT: l = canvas.line((width, y), (width, height)) l.stroke(config.breakpoint_color, width=config.breakpoint_orient_stroke_width) g.add(l) g.add(Tag('title', 'Breakpoint {}:g.{}_{}{} {}'.format( breakpoint.chr, breakpoint.start, breakpoint.end, breakpoint.strand, breakpoint.orient))) return g
[docs]def draw_exon(config, canvas, exon, width, height, fill, label='', translation=None): """ generates the svg object representing an exon Args: canvas (svgwrite.drawing.Drawing): the main svgwrite object used to create new svg elements exon (Exon): the exon to draw width (int): the pixel width height (int): the pixel height fill (str): the fill color to use for the exon Return: svgwrite.container.Group: the group element for the diagram .. todo:: add markers for exons with abrogated splice sites """ g = canvas.g(class_='exon') label = str(label) g.add(canvas.rect((0, 0), (width, height), fill=fill)) t = canvas.text( label, insert=(width / 2, height / 2 + config.font_central_shift_ratio * config.exon_font_size), fill=config.label_color if not config.dynamic_labels else dynamic_label_color(fill), style=config.font_style.format(font_size=config.exon_font_size, text_anchor='middle'), class_='label' ) g.add(t) title = 'Exon {} g.{}_{}'.format( exon.name if exon.name else '', exon.start, exon.end) if translation: cds_start = translation.convert_genomic_to_cds_notation(exon.start) cds_end = translation.convert_genomic_to_cds_notation(exon.end) if exon.get_strand() == STRAND.NEG: cds_start, cds_end = cds_end, cds_start title += ' c.{}_{}'.format(cds_start, cds_end) try: cdna_start = translation.transcript.convert_genomic_to_cdna(exon.start) cdna_end = translation.transcript.convert_genomic_to_cdna(exon.end) if cdna_end < cdna_start: cdna_start, cdna_end = cdna_end, cdna_start title += ' cdna({}_{})'.format(cdna_start, cdna_end) except IndexError: title += ' cdna(N/A)' title += ' length({})'.format(len(exon)) g.add(Tag('title', title)) return g
[docs]def draw_template(config, canvas, template, target_width, labels=None, colors=None, breakpoints=None): """ Creates the template/chromosome illustration Return: svgwrite.container.Group: the group element for the diagram """ labels = LabelMapping() if labels is None else labels colors = {} if colors is None else colors breakpoints = [] if not breakpoints else breakpoints total_height = config.template_track_height + config.breakpoint_top_margin + config.breakpoint_bottom_margin group = canvas.g(class_='template') # 1 as input since we don't want to change the ratio here mapping = generate_interval_mapping( template.bands, target_width, 1, config.template_band_min_width, start=template.start, end=template.end ) scaffold = canvas.rect( (0, 0), (target_width, config.scaffold_height), fill=config.scaffold_color ) group.add(scaffold) scaffold.translate((0, config.breakpoint_top_margin + config.template_track_height / 2 - config.scaffold_height / 2)) label_group = canvas.g() label_group.add(canvas.text( labels.add(template, config.template_label_prefix), insert=( 0 - config.padding, config.breakpoint_top_margin + config.template_track_height / 2 + config.font_central_shift_ratio * config.label_font_size), fill=config.label_color, style=config.font_style.format(font_size=config.label_font_size, text_anchor='end'), class_='label' )) label_group.add(Tag('title', 'template {}'.format(template.name))) group.add(label_group) for band in template.bands: s = Interval.convert_pos(mapping, band[0]) t = Interval.convert_pos(mapping, band[1]) bgroup = canvas.g(class_='cytoband') f = config.template_band_fill.get(band.data.get('giemsa_stain', None), config.template_default_fill) r = None w = t - s + 1 if band.data.get('giemsa_stain', None) == GIEMSA_STAIN.ACEN: if band.name[0] == 'p': r = canvas.polyline( [(0, 0), (w, config.template_track_height / 2), (0, config.template_track_height)], fill=f, stroke=config.template_band_stroke, stroke_width=config.template_band_stroke_width ) else: r = canvas.polyline( [(w, 0), (0, config.template_track_height / 2), (w, config.template_track_height)], fill=f, stroke=config.template_band_stroke, stroke_width=config.template_band_stroke_width ) else: r = canvas.rect( (0, 0), (w, config.template_track_height), fill=f, stroke=config.template_band_stroke, stroke_width=config.template_band_stroke_width ) bgroup.add(r) bgroup.add( Tag('title', 'cytoband {0}:y.{1} {0}:g.{2}_{3}'.format( template.name, band.name, band.start, band.end))) bgroup.translate((s, config.breakpoint_top_margin)) group.add(bgroup) # now draw the breakpoints overtop for i, b in enumerate(sorted(breakpoints)): s = Interval.convert_pos(mapping, b.start) t = Interval.convert_pos(mapping, b.end) bg = draw_breakpoint( config, canvas, b, abs(t - s) + 1, total_height, label=labels.add(b, config.breakpoint_label_prefix)) bg.translate(s, 0) group.add(bg) setattr( group, 'height', total_height) return group
[docs]def draw_gene(config, canvas, gene, width, height, fill, label='', reference_genome=None): """ generates the svg object representing a gene Args: canvas (svgwrite.drawing.Drawing): the main svgwrite object used to create new svg elements gene (Gene): the gene to draw width (int): the pixel width height (int): the pixel height fill (str): the fill color to use for the gene Return: svgwrite.container.Group: the group element for the diagram """ group = canvas.g(class_='gene') if width < config.gene_min_width: raise DrawingFitError('width of {} is not sufficient to draw a gene of minimum width {}'.format( width, config.gene_min_width), gene) wrect = width - config.gene_arrow_width if wrect < 1: raise DrawingFitError('width is not sufficient to draw gene') label_color = config.label_color if not config.dynamic_labels else dynamic_label_color(fill) if gene.get_strand() == STRAND.POS: group.add( canvas.rect( (0, 0), (wrect, height), fill=fill )) group.add( canvas.polyline( [(wrect, 0), (wrect + config.gene_arrow_width, height / 2), (wrect, height)], fill=fill )) group.add( canvas.text( label, insert=(wrect / 2, height / 2 + config.font_central_shift_ratio * config.label_font_size), fill=label_color, style=config.font_style.format(font_size=config.label_font_size, text_anchor='middle'), class_='label' )) elif gene.get_strand() == STRAND.NEG: group.add( canvas.rect( (config.gene_arrow_width, 0), (wrect, height), fill=fill )) group.add( canvas.polyline( [(config.gene_arrow_width, 0), (0, height / 2), (config.gene_arrow_width, height)], fill=fill )) group.add( canvas.text( label, insert=( wrect / 2 + config.gene_arrow_width, height / 2 + config.font_central_shift_ratio * config.label_font_size ), fill=label_color, style=config.font_style.format(font_size=config.label_font_size, text_anchor='middle'), class_='label' )) else: group.add( canvas.rect( (0, 0), (width, height), fill=fill )) group.add( canvas.text( label, insert=(width / 2, height / 2 + config.font_central_shift_ratio * config.label_font_size), fill=label_color, style=config.font_style.format(font_size=config.label_font_size, text_anchor='middle'), class_='label' )) aliases = '' try: if gene.aliases: aliases = ' aka {}'.format(';'.join(sorted(gene.aliases))) except AttributeError: pass group.add( Tag('title', 'Gene {} {}:g.{}_{}{}{}'.format(gene.name if gene.name else '', gene.chr, gene.start, gene.end, gene.get_strand(), aliases))) return group