Source code for homologyviz.arrow

"""Class for plotting arrows to represent genes.

Notes
-----
- This file is part of HomologyViz
- BSD 3-Clause License
- Copyright (c) 2024, Iván Muñoz Gutiérrez
"""

import matplotlib.pyplot as plt
import numpy as np
from numpy import ndarray


[docs] class Arrow: """ Generate coordinates for plotting horizontal arrow shapes. .. image:: _static/arrow_description.png :width: 60% :alt: Arrow attribute diagram :align: center This class computes the (x, y) coordinates needed to plot an arrow pointing left or right, representing genes or features along DNA sequences. The arrow consists of a rectangular tail and a triangular head. If the arrow is shorter than the specified head height, only the triangular head is drawn. The head and tail dimensions are customizable. Attributes ---------- x1 : int or float Start position along the x-axis. x2 : int or float End position along the x-axis. y : int or float Vertical position along the y-axis. ratio_tail_head_width : float Ratio of tail width to head width. Default is 0.5. head_width : int or float Width of the arrowhead in the y-axis. head_height : int or float Length of the arrowhead in the x-axis. tail_width : float Computed width of the tail based on `head_width` and `ratio_tail_head_width`. head_shoulder : float Distance from the top/bottom of the tail to the top/bottom of the head. """ def __init__( self, x1: float, x2: float, y: float, ratio_tail_head_width: float = 0.5, head_width: float = 2, head_height: float = 200, ): self.x1 = x1 self.x2 = x2 self.y = y self.ratio_tail_head_width = ratio_tail_head_width self.tail_width = head_width * ratio_tail_head_width self.head_width = head_width self.head_height = head_height self.head_shoulder = (head_width - self.tail_width) / 2
[docs] def coordinates_arrow_forward(self) -> tuple[ndarray, ndarray]: """ Compute coordinates for a right-pointing arrow. Returns ------- tuple of np.ndarray Arrays representing x and y coordinates of the arrow polygon. """ # TODO: Add __repr__ method for better debugging and logging. height = self.x2 - self.x1 # If total height is smaller or equal to the arrow's head hight, then plot # only head if height <= self.head_height: x_1 = self.x1 x_2 = self.x1 x_3 = self.x2 x_4 = self.x1 x_5 = self.x1 y_1 = self.y y_2 = self.y + (self.head_width * 0.5) y_3 = self.y y_4 = self.y - (self.head_width * 0.5) y_5 = self.y x_values = np.array([x_1, x_2, x_3, x_4, x_5]) y_values = np.array([y_1, y_2, y_3, y_4, y_5]) return (x_values, y_values) # Tail x-values x_1 = self.x1 x_2 = self.x1 # Head x-values x_3 = self.x2 - self.head_height x_4 = self.x2 - self.head_height x_5 = self.x2 x_6 = self.x2 - self.head_height x_7 = self.x2 - self.head_height # Tail x-values x_8 = self.x1 x_9 = self.x1 # Tail y-values y_1 = self.y y_2 = self.y + (self.tail_width * 0.5) # Head y-values y_3 = self.y + (self.tail_width * 0.5) y_4 = self.y + (self.tail_width * 0.5) + self.head_shoulder y_5 = self.y y_6 = self.y - (self.tail_width * 0.5) - self.head_shoulder y_7 = self.y - (self.tail_width * 0.5) # Tail y-values y_8 = self.y - (self.tail_width * 0.5) y_9 = self.y # make datapoints for plotting x_values = np.array([x_1, x_2, x_3, x_4, x_5, x_6, x_7, x_8, x_9]) y_values = np.array([y_1, y_2, y_3, y_4, y_5, y_6, y_7, y_8, y_9]) return (x_values, y_values)
[docs] def coordinates_arrow_reverse(self) -> tuple[ndarray, ndarray]: """ Compute coordinates for a left-pointing arrow. Returns ------- tuple of np.ndarray Arrays representing x and y coordinates of the arrow polygon. """ height = self.x1 - self.x2 # If total height is smaller or equal to the arrow's head hight plot # only head if height <= self.head_height: x_1 = self.x1 x_2 = self.x1 x_3 = self.x2 x_4 = self.x1 x_5 = self.x1 y_1 = self.y y_2 = self.y - (self.head_width * 0.5) y_3 = self.y y_4 = self.y + (self.head_width * 0.5) y_5 = self.y x_values = np.array([x_1, x_2, x_3, x_4, x_5]) y_values = np.array([y_1, y_2, y_3, y_4, y_5]) return (x_values, y_values) # Tail x-values x_1 = self.x1 x_2 = self.x1 # Head x-values x_3 = self.x2 + self.head_height x_4 = self.x2 + self.head_height x_5 = self.x2 x_6 = self.x2 + self.head_height x_7 = self.x2 + self.head_height # Tail x-values x_8 = self.x1 x_9 = self.x1 # Tail y-values y_1 = self.y y_2 = self.y - (self.tail_width * 0.5) # Head y-values y_3 = self.y - (self.tail_width * 0.5) y_4 = self.y - (self.tail_width * 0.5) - self.head_shoulder y_5 = self.y y_6 = self.y + (self.tail_width * 0.5) + self.head_shoulder y_7 = self.y + (self.tail_width * 0.5) # Tail y-values y_8 = self.y + (self.tail_width * 0.5) y_9 = self.y # make datapoints for plotting x_values = np.array([x_1, x_2, x_3, x_4, x_5, x_6, x_7, x_8, x_9]) y_values = np.array([y_1, y_2, y_3, y_4, y_5, y_6, y_7, y_8, y_9]) return (x_values, y_values)
[docs] def get_coordinates(self) -> tuple[ndarray, ndarray]: """ Get coordinates for either a forward or reverse arrow depending on direction. Returns ------- tuple of np.ndarray Arrays representing x and y coordinates of the arrow polygon. """ if self.x1 < self.x2: return self.coordinates_arrow_forward() else: return self.coordinates_arrow_reverse()
if __name__ == "__main__": #################### # Example of usage # #################### # Make arrow arrow1 = Arrow(x1=750, x2=2000, y=10) x_values, y_values = arrow1.get_coordinates() print(x_values) print(y_values) plt.fill(x_values, y_values, "b") arrow2 = Arrow(x1=1250, x2=250, y=20) x_values, y_values = arrow2.get_coordinates() plt.fill(x_values, y_values, "r") arrow3 = Arrow(x1=550, x2=500, y=30) x_values, y_values = arrow3.get_coordinates() plt.fill(x_values, y_values, "r") plt.show()