Spaces:
No application file
No application file
DrVai-Rag-Testing
/
myenv
/lib
/python3.10
/site-packages
/Bio
/Graphics
/GenomeDiagram
/_AbstractDrawer.py
| # Copyright 2003-2008 by Leighton Pritchard. All rights reserved. | |
| # Revisions copyright 2008-2017 by Peter Cock. | |
| # | |
| # This file is part of the Biopython distribution and governed by your | |
| # choice of the "Biopython License Agreement" or the "BSD 3-Clause License". | |
| # Please see the LICENSE file that should have been included as part of this | |
| # package. | |
| # | |
| # Contact: Leighton Pritchard, The James Hutton Institute, | |
| # Invergowrie, Dundee, Scotland, DD2 5DA, UK | |
| # Leighton.Pritchard@hutton.ac.uk | |
| ################################################################################ | |
| """AbstractDrawer module (considered to be a private module, the API may change!). | |
| Provides: | |
| - AbstractDrawer - Superclass for methods common to the Drawer objects | |
| - page_sizes - Method that returns a ReportLab pagesize when passed | |
| a valid ISO size | |
| - draw_box - Method that returns a closed path object when passed | |
| the proper coordinates. For HORIZONTAL boxes only. | |
| - angle2trig - Method that returns a tuple of values that are the | |
| vector for rotating a point through a passed angle, | |
| about an origin | |
| - intermediate_points - Method that returns a list of values intermediate | |
| between the points in a passed dataset | |
| For drawing capabilities, this module uses reportlab to draw and write | |
| the diagram: http://www.reportlab.com | |
| For dealing with biological information, the package expects Biopython objects | |
| like SeqFeatures. | |
| """ | |
| # ReportLab imports | |
| from reportlab.lib import pagesizes | |
| from reportlab.lib import colors | |
| from reportlab.graphics.shapes import Polygon | |
| from math import pi, sin, cos | |
| from itertools import islice | |
| ################################################################################ | |
| # METHODS | |
| ################################################################################ | |
| # Utility method to translate strings to ISO page sizes | |
| def page_sizes(size): | |
| """Convert size string into a Reportlab pagesize. | |
| Arguments: | |
| - size - A string representing a standard page size, eg 'A4' or 'LETTER' | |
| """ | |
| sizes = { # ReportLab pagesizes, keyed by ISO string | |
| "A0": pagesizes.A0, | |
| "A1": pagesizes.A1, | |
| "A2": pagesizes.A2, | |
| "A3": pagesizes.A3, | |
| "A4": pagesizes.A4, | |
| "A5": pagesizes.A5, | |
| "A6": pagesizes.A6, | |
| "B0": pagesizes.B0, | |
| "B1": pagesizes.B1, | |
| "B2": pagesizes.B2, | |
| "B3": pagesizes.B3, | |
| "B4": pagesizes.B4, | |
| "B5": pagesizes.B5, | |
| "B6": pagesizes.B6, | |
| "ELEVENSEVENTEEN": pagesizes.ELEVENSEVENTEEN, | |
| "LEGAL": pagesizes.LEGAL, | |
| "LETTER": pagesizes.LETTER, | |
| } | |
| try: | |
| return sizes[size] | |
| except KeyError: | |
| raise ValueError(f"{size} not in list of page sizes") from None | |
| def _stroke_and_fill_colors(color, border): | |
| """Deal with border and fill colors (PRIVATE).""" | |
| if not isinstance(color, colors.Color): | |
| raise ValueError(f"Invalid color {color!r}") | |
| if color == colors.white and border is None: | |
| # Force black border on white boxes with undefined border | |
| strokecolor = colors.black | |
| elif border is None: | |
| strokecolor = color # use fill color | |
| elif border: | |
| if not isinstance(border, colors.Color): | |
| raise ValueError(f"Invalid border color {border!r}") | |
| strokecolor = border | |
| else: | |
| # e.g. False | |
| strokecolor = None | |
| return strokecolor, color | |
| def draw_box( | |
| point1, point2, color=colors.lightgreen, border=None, colour=None, **kwargs | |
| ): | |
| """Draw a box. | |
| Arguments: | |
| - point1, point2 - coordinates for opposite corners of the box | |
| (x,y tuples) | |
| - color /colour - The color for the box (colour takes priority | |
| over color) | |
| - border - Border color for the box | |
| Returns a closed path object, beginning at (x1,y1) going round | |
| the four points in order, and filling with the passed color. | |
| """ | |
| x1, y1 = point1 | |
| x2, y2 = point2 | |
| # Let the UK spelling (colour) override the USA spelling (color) | |
| if colour is not None: | |
| color = colour | |
| del colour | |
| strokecolor, color = _stroke_and_fill_colors(color, border) | |
| x1, y1, x2, y2 = min(x1, x2), min(y1, y2), max(x1, x2), max(y1, y2) | |
| return Polygon( | |
| [x1, y1, x2, y1, x2, y2, x1, y2], | |
| strokeColor=strokecolor, | |
| fillColor=color, | |
| strokewidth=0, | |
| **kwargs, | |
| ) | |
| def draw_cut_corner_box( | |
| point1, point2, corner=0.5, color=colors.lightgreen, border=None, **kwargs | |
| ): | |
| """Draw a box with the corners cut off.""" | |
| x1, y1 = point1 | |
| x2, y2 = point2 | |
| if not corner: | |
| return draw_box(point1, point2, color, border) | |
| elif corner < 0: | |
| raise ValueError("Arrow head length ratio should be positive") | |
| strokecolor, color = _stroke_and_fill_colors(color, border) | |
| boxheight = y2 - y1 | |
| boxwidth = x2 - x1 | |
| x_corner = min(boxheight * 0.5 * corner, boxwidth * 0.5) | |
| y_corner = min(boxheight * 0.5 * corner, boxheight * 0.5) | |
| points = [ | |
| x1, | |
| y1 + y_corner, | |
| x1, | |
| y2 - y_corner, | |
| x1 + x_corner, | |
| y2, | |
| x2 - x_corner, | |
| y2, | |
| x2, | |
| y2 - y_corner, | |
| x2, | |
| y1 + y_corner, | |
| x2 - x_corner, | |
| y1, | |
| x1 + x_corner, | |
| y1, | |
| ] | |
| return Polygon( | |
| deduplicate(points), | |
| strokeColor=strokecolor, | |
| strokeWidth=1, | |
| strokeLineJoin=1, # 1=round | |
| fillColor=color, | |
| **kwargs, | |
| ) | |
| def draw_polygon( | |
| list_of_points, color=colors.lightgreen, border=None, colour=None, **kwargs | |
| ): | |
| """Draw polygon. | |
| Arguments: | |
| - list_of_point - list of (x,y) tuples for the corner coordinates | |
| - color / colour - The color for the box | |
| Returns a closed path object, beginning at (x1,y1) going round | |
| the four points in order, and filling with the passed colour. | |
| """ | |
| # Let the UK spelling (colour) override the USA spelling (color) | |
| if colour is not None: | |
| color = colour | |
| del colour | |
| strokecolor, color = _stroke_and_fill_colors(color, border) | |
| xy_list = [] | |
| for (x, y) in list_of_points: | |
| xy_list.append(x) | |
| xy_list.append(y) | |
| return Polygon( | |
| deduplicate(xy_list), | |
| strokeColor=strokecolor, | |
| fillColor=color, | |
| strokewidth=0, | |
| **kwargs, | |
| ) | |
| def draw_arrow( | |
| point1, | |
| point2, | |
| color=colors.lightgreen, | |
| border=None, | |
| shaft_height_ratio=0.4, | |
| head_length_ratio=0.5, | |
| orientation="right", | |
| colour=None, | |
| **kwargs, | |
| ): | |
| """Draw an arrow. | |
| Returns a closed path object representing an arrow enclosed by the | |
| box with corners at {point1=(x1,y1), point2=(x2,y2)}, a shaft height | |
| given by shaft_height_ratio (relative to box height), a head length | |
| given by head_length_ratio (also relative to box height), and | |
| an orientation that may be 'left' or 'right'. | |
| """ | |
| x1, y1 = point1 | |
| x2, y2 = point2 | |
| if shaft_height_ratio < 0 or 1 < shaft_height_ratio: | |
| raise ValueError("Arrow shaft height ratio should be in range 0 to 1") | |
| if head_length_ratio < 0: | |
| raise ValueError("Arrow head length ratio should be positive") | |
| # Let the UK spelling (colour) override the USA spelling (color) | |
| if colour is not None: | |
| color = colour | |
| del colour | |
| strokecolor, color = _stroke_and_fill_colors(color, border) | |
| # Depending on the orientation, we define the bottom left (x1, y1) and | |
| # top right (x2, y2) coordinates differently, but still draw the box | |
| # using the same relative coordinates: | |
| xmin, ymin = min(x1, x2), min(y1, y2) | |
| xmax, ymax = max(x1, x2), max(y1, y2) | |
| if orientation == "right": | |
| x1, x2, y1, y2 = xmin, xmax, ymin, ymax | |
| elif orientation == "left": | |
| x1, x2, y1, y2 = xmax, xmin, ymin, ymax | |
| else: | |
| raise ValueError( | |
| f"Invalid orientation {orientation!r}, should be 'left' or 'right'" | |
| ) | |
| # We define boxheight and boxwidth accordingly, and calculate the shaft | |
| # height from these. We also ensure that the maximum head length is | |
| # the width of the box enclosure | |
| boxheight = y2 - y1 | |
| boxwidth = x2 - x1 | |
| shaftheight = boxheight * shaft_height_ratio | |
| headlength = min(abs(boxheight) * head_length_ratio, abs(boxwidth)) | |
| if boxwidth < 0: | |
| headlength *= -1 # reverse it | |
| shafttop = 0.5 * (boxheight + shaftheight) | |
| shaftbase = boxheight - shafttop | |
| headbase = boxwidth - headlength | |
| midheight = 0.5 * boxheight | |
| points = [ | |
| x1, | |
| y1 + shafttop, | |
| x1 + headbase, | |
| y1 + shafttop, | |
| x1 + headbase, | |
| y2, | |
| x2, | |
| y1 + midheight, | |
| x1 + headbase, | |
| y1, | |
| x1 + headbase, | |
| y1 + shaftbase, | |
| x1, | |
| y1 + shaftbase, | |
| ] | |
| return Polygon( | |
| deduplicate(points), | |
| strokeColor=strokecolor, | |
| # strokeWidth=max(1, int(boxheight/40.)), | |
| strokeWidth=1, | |
| # default is mitre/miter which can stick out too much: | |
| strokeLineJoin=1, # 1=round | |
| fillColor=color, | |
| **kwargs, | |
| ) | |
| def deduplicate(points): | |
| """Remove adjacent duplicate points. | |
| This is important for use with the Polygon class since reportlab has a | |
| bug with duplicate points. | |
| Arguments: | |
| - points - list of points [x1, y1, x2, y2,...] | |
| Returns a list in the same format with consecutive duplicates removed | |
| """ | |
| assert len(points) % 2 == 0 | |
| if len(points) < 2: | |
| return points | |
| newpoints = points[0:2] | |
| for x, y in zip(islice(points, 2, None, 2), islice(points, 3, None, 2)): | |
| if x != newpoints[-2] or y != newpoints[-1]: | |
| newpoints.append(x) | |
| newpoints.append(y) | |
| return newpoints | |
| def angle2trig(theta): | |
| """Convert angle to a reportlab ready tuple. | |
| Arguments: | |
| - theta - Angle in degrees, counter clockwise from horizontal | |
| Returns a representation of the passed angle in a format suitable | |
| for ReportLab rotations (i.e. cos(theta), sin(theta), -sin(theta), | |
| cos(theta) tuple) | |
| """ | |
| c = cos(theta * pi / 180) | |
| s = sin(theta * pi / 180) | |
| return (c, s, -s, c) # Vector for rotating point around an origin | |
| def intermediate_points(start, end, graph_data): | |
| """Generate intermediate points describing provided graph data.. | |
| Returns a list of (start, end, value) tuples describing the passed | |
| graph data as 'bins' between position midpoints. | |
| """ | |
| newdata = [] # data in form (X0, X1, val) | |
| # add first block | |
| newdata.append( | |
| ( | |
| start, | |
| graph_data[0][0] + (graph_data[1][0] - graph_data[0][0]) / 2.0, | |
| graph_data[0][1], | |
| ) | |
| ) | |
| # add middle set | |
| for index in range(1, len(graph_data) - 1): | |
| lastxval, lastyval = graph_data[index - 1] | |
| xval, yval = graph_data[index] | |
| nextxval, nextyval = graph_data[index + 1] | |
| newdata.append( | |
| (lastxval + (xval - lastxval) / 2.0, xval + (nextxval - xval) / 2.0, yval) | |
| ) | |
| # add last block | |
| newdata.append((xval + (nextxval - xval) / 2.0, end, graph_data[-1][1])) | |
| return newdata | |
| ################################################################################ | |
| # CLASSES | |
| ################################################################################ | |
| class AbstractDrawer: | |
| """Abstract Drawer. | |
| Attributes: | |
| - tracklines Boolean for whether to draw lines delineating tracks | |
| - pagesize Tuple describing the size of the page in pixels | |
| - x0 Float X co-ord for leftmost point of drawable area | |
| - xlim Float X co-ord for rightmost point of drawable area | |
| - y0 Float Y co-ord for lowest point of drawable area | |
| - ylim Float Y co-ord for topmost point of drawable area | |
| - pagewidth Float pixel width of drawable area | |
| - pageheight Float pixel height of drawable area | |
| - xcenter Float X co-ord of center of drawable area | |
| - ycenter Float Y co-ord of center of drawable area | |
| - start Int, base to start drawing from | |
| - end Int, base to stop drawing at | |
| - length Size of sequence to be drawn | |
| - cross_track_links List of tuples each with four entries (track A, | |
| feature A, track B, feature B) to be linked. | |
| """ | |
| def __init__( | |
| self, | |
| parent, | |
| pagesize="A3", | |
| orientation="landscape", | |
| x=0.05, | |
| y=0.05, | |
| xl=None, | |
| xr=None, | |
| yt=None, | |
| yb=None, | |
| start=None, | |
| end=None, | |
| tracklines=0, | |
| cross_track_links=None, | |
| ): | |
| """Create the object. | |
| Arguments: | |
| - parent Diagram object containing the data that the drawer draws | |
| - pagesize String describing the ISO size of the image, or a tuple | |
| of pixels | |
| - orientation String describing the required orientation of the | |
| final drawing ('landscape' or 'portrait') | |
| - x Float (0->1) describing the relative size of the X | |
| margins to the page | |
| - y Float (0->1) describing the relative size of the Y | |
| margins to the page | |
| - xl Float (0->1) describing the relative size of the left X | |
| margin to the page (overrides x) | |
| - xr Float (0->1) describing the relative size of the right X | |
| margin to the page (overrides x) | |
| - yt Float (0->1) describing the relative size of the top Y | |
| margin to the page (overrides y) | |
| - yb Float (0->1) describing the relative size of the lower Y | |
| margin to the page (overrides y) | |
| - start Int, the position to begin drawing the diagram at | |
| - end Int, the position to stop drawing the diagram at | |
| - tracklines Boolean flag to show (or not) lines delineating tracks | |
| on the diagram | |
| - cross_track_links List of tuples each with four entries (track A, | |
| feature A, track B, feature B) to be linked. | |
| """ | |
| self._parent = parent # The calling Diagram object | |
| # Perform 'administrative' tasks of setting up the page | |
| self.set_page_size(pagesize, orientation) # Set drawing size | |
| self.set_margins(x, y, xl, xr, yt, yb) # Set page margins | |
| self.set_bounds(start, end) # Set limits on what will be drawn | |
| self.tracklines = tracklines # Set flags | |
| if cross_track_links is None: | |
| cross_track_links = [] | |
| else: | |
| self.cross_track_links = cross_track_links | |
| def set_page_size(self, pagesize, orientation): | |
| """Set page size of the drawing.. | |
| Arguments: | |
| - pagesize Size of the output image, a tuple of pixels (width, | |
| height, or a string in the reportlab.lib.pagesizes | |
| set of ISO sizes. | |
| - orientation String: 'landscape' or 'portrait' | |
| """ | |
| if isinstance(pagesize, str): # A string, so translate | |
| pagesize = page_sizes(pagesize) | |
| elif isinstance(pagesize, tuple): # A tuple, so don't translate | |
| pass | |
| else: | |
| raise ValueError(f"Page size {pagesize} not recognised") | |
| shortside, longside = min(pagesize), max(pagesize) | |
| orientation = orientation.lower() | |
| if orientation not in ("landscape", "portrait"): | |
| raise ValueError(f"Orientation {orientation} not recognised") | |
| if orientation == "landscape": | |
| self.pagesize = (longside, shortside) | |
| else: | |
| self.pagesize = (shortside, longside) | |
| def set_margins(self, x, y, xl, xr, yt, yb): | |
| """Set page margins. | |
| Arguments: | |
| - x Float(0->1), Absolute X margin as % of page | |
| - y Float(0->1), Absolute Y margin as % of page | |
| - xl Float(0->1), Left X margin as % of page | |
| - xr Float(0->1), Right X margin as % of page | |
| - yt Float(0->1), Top Y margin as % of page | |
| - yb Float(0->1), Bottom Y margin as % of page | |
| Set the page margins as proportions of the page 0->1, and also | |
| set the page limits x0, y0 and xlim, ylim, and page center | |
| xorigin, yorigin, as well as overall page width and height | |
| """ | |
| # Set left, right, top and bottom margins | |
| xmargin_l = xl or x | |
| xmargin_r = xr or x | |
| ymargin_top = yt or y | |
| ymargin_btm = yb or y | |
| # Set page limits, center and height/width | |
| self.x0, self.y0 = self.pagesize[0] * xmargin_l, self.pagesize[1] * ymargin_btm | |
| self.xlim, self.ylim = ( | |
| self.pagesize[0] * (1 - xmargin_r), | |
| self.pagesize[1] * (1 - ymargin_top), | |
| ) | |
| self.pagewidth = self.xlim - self.x0 | |
| self.pageheight = self.ylim - self.y0 | |
| self.xcenter, self.ycenter = ( | |
| self.x0 + self.pagewidth / 2.0, | |
| self.y0 + self.pageheight / 2.0, | |
| ) | |
| def set_bounds(self, start, end): | |
| """Set start and end points for the drawing as a whole. | |
| Arguments: | |
| - start - The first base (or feature mark) to draw from | |
| - end - The last base (or feature mark) to draw to | |
| """ | |
| low, high = self._parent.range() # Extent of tracks | |
| if start is not None and end is not None and start > end: | |
| start, end = end, start | |
| if start is None or start < 0: # Check validity of passed args and | |
| start = 0 # default to 0 | |
| if end is None or end < 0: | |
| end = high + 1 # default to track range top limit | |
| self.start, self.end = int(start), int(end) | |
| self.length = self.end - self.start + 1 | |
| def is_in_bounds(self, value): | |
| """Check if given value is within the region selected for drawing. | |
| Arguments: | |
| - value - A base position | |
| """ | |
| if value >= self.start and value <= self.end: | |
| return 1 | |
| return 0 | |
| def __len__(self): | |
| """Return the length of the region to be drawn.""" | |
| return self.length | |
| def _current_track_start_end(self): | |
| track = self._parent[self.current_track_level] | |
| if track.start is None: | |
| start = self.start | |
| else: | |
| start = max(self.start, track.start) | |
| if track.end is None: | |
| end = self.end | |
| else: | |
| end = min(self.end, track.end) | |
| return start, end | |