Repository 'cpt_linear_genome_plot'
hg clone https://toolshed.g2.bx.psu.edu/repos/cpt/cpt_linear_genome_plot

Changeset 0:621754dd31f8 (2022-06-17)
Next changeset 1:e923c686ead9 (2023-06-05)
Commit message:
Uploaded
added:
cpt_linear_genome_plot/dna_features_viewer/BiopythonTranslator/BiopythonTranslator.py
cpt_linear_genome_plot/dna_features_viewer/BiopythonTranslator/BiopythonTranslatorBase.py
cpt_linear_genome_plot/dna_features_viewer/BiopythonTranslator/BlackBoxlessLabelTranslator.py
cpt_linear_genome_plot/dna_features_viewer/BiopythonTranslator/__init__.py
cpt_linear_genome_plot/dna_features_viewer/CircularGraphicRecord/ArrowWedge.py
cpt_linear_genome_plot/dna_features_viewer/CircularGraphicRecord/CircularGraphicRecord.py
cpt_linear_genome_plot/dna_features_viewer/CircularGraphicRecord/__init__.py
cpt_linear_genome_plot/dna_features_viewer/GraphicFeature.py
cpt_linear_genome_plot/dna_features_viewer/GraphicRecord/BokehPlottableMixin.py
cpt_linear_genome_plot/dna_features_viewer/GraphicRecord/GraphicRecord.py
cpt_linear_genome_plot/dna_features_viewer/GraphicRecord/MatplotlibPlottableMixin.py
cpt_linear_genome_plot/dna_features_viewer/GraphicRecord/MultilinePlottableMixin.py
cpt_linear_genome_plot/dna_features_viewer/GraphicRecord/SequenceAndTranslationMixin.py
cpt_linear_genome_plot/dna_features_viewer/GraphicRecord/__init__.py
cpt_linear_genome_plot/dna_features_viewer/README.md
cpt_linear_genome_plot/dna_features_viewer/__init__.py
cpt_linear_genome_plot/dna_features_viewer/biotools.py
cpt_linear_genome_plot/dna_features_viewer/compute_features_levels.py
cpt_linear_genome_plot/dna_features_viewer/version.py
cpt_linear_genome_plot/linear_genome_plot.py
cpt_linear_genome_plot/linear_genome_plot.xml
cpt_linear_genome_plot/macros.xml
cpt_linear_genome_plot/test-data/Mu50-profile.xml.xml
cpt_linear_genome_plot/test-data/Mu50.sam
cpt_linear_genome_plot/test-data/mga.fa
cpt_linear_genome_plot/test-data/mu_reanno.gb
cpt_linear_genome_plot/test-data/out_img.svg
cpt_linear_genome_plot/test-data/out_img_multi.svg
cpt_linear_genome_plot/test-data/out_img_zoom.svg
cpt_linear_genome_plot/test-data/out_stats.txt
cpt_linear_genome_plot/test-data/out_stats_multi.txt
cpt_linear_genome_plot/test-data/out_stats_zoom.txt
cpt_linear_genome_plot/test-data/tmp.svg
cpt_linear_genome_plot/test-data/tmp_multi.svg
cpt_linear_genome_plot/test-data/tmp_zoom.svg
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/dna_features_viewer/BiopythonTranslator/BiopythonTranslator.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/dna_features_viewer/BiopythonTranslator/BiopythonTranslator.py Fri Jun 17 12:59:24 2022 +0000
[
@@ -0,0 +1,130 @@
+from .BiopythonTranslatorBase import BiopythonTranslatorBase
+
+
+class BiopythonTranslator(BiopythonTranslatorBase):
+    """A translator from SeqRecords to dna_features_viewer GraphicRecord.
+
+    This can be subclassed to create custom "themes" (see the example
+    ``custom_biopython_translator.py`` in the docs).
+
+    This class is meant to be customized by subclassing and changing the
+    methods (``compute_feature_label``, etc.) and/or the attributes
+    (``default_feature_color`` etc).
+
+    Attributes
+    ----------
+
+    default_feature_color = "#7245dc"
+    graphic_record_parameters
+      Dictionnary containing keyword arguments that will be passed to the
+      (Circular)GraphicRecord constructor
+
+    ignored_features_types
+      A list or tuple of strings indicating all the feature types that should
+      always be ignored (i.e. not included in the graphic record) by the
+      translator
+
+    label_fields
+      This list of strings provides the order in which the different
+      attributes of a Genbank feature will be considered, when automatically
+      determining the feature label. For instance if the list is
+      ["label", "source", "locus_tag"] and a feature has no label but has a
+      "source", the "source" will be displayed in the plots.
+
+    Parameters
+    ----------
+
+    features_filters
+      List of filters (some_biopython_feature) => True/False.
+      Only features passing all the filters are kept.
+      This only works if you haven't redefined ``compute_filtered_features``
+
+    features_properties
+      A function (feature)=> properties_dict
+
+    """
+
+    default_feature_color = "#7245dc"
+    ignored_features_types = ()
+    label_fields = [
+        "label",
+        "name",
+        "gene",
+        "product",
+        "source",
+        "locus_tag",
+        "note",
+    ]
+
+    def __init__(self, features_filters=(), features_properties=None):
+        self.features_filters = features_filters
+        self.features_properties = features_properties
+
+    def compute_feature_color(self, feature):
+        """Compute a color for this feature.
+
+        If the feature has a ``color`` qualifier it will be used. Otherwise,
+        the classe's ``default_feature_color`` is used.
+
+        To change the behaviour, create a subclass of ``BiopythonTranslator``
+        and overwrite this method.
+        """
+        if "color" in feature.qualifiers:
+            color = feature.qualifiers["color"]
+            if isinstance(color[0], str):
+                return "".join(feature.qualifiers["color"])
+            else:
+                return color
+        else:
+            return self.default_feature_color
+
+    def compute_feature_fontdict(self, feature):
+        """Compute a font dict for this feature."""
+        return None
+
+    def compute_feature_box_linewidth(self, feature):
+        """Compute a box_linewidth for this feature."""
+        return 0.3
+
+    def compute_feature_box_color(self, feature):
+        """Compute a box_color for this feature."""
+        return "auto"
+
+    def compute_feature_label_link_color(self, feature):
+        """Compute the color of the line linking the label to its feature."""
+        return "black"
+
+    def compute_filtered_features(self, features):
+        """Return the list of features minus the ignored ones.
+        
+        By the method keeps any feature whose type is not in
+        ignored_features_types and for which all filter(f) pass
+        """
+        return [
+            f
+            for f in features
+            if all([fl(f) for fl in self.features_filters])
+            and f.type not in self.ignored_features_types
+        ]
+
+    def compute_feature_label(self, feature):
+        """Compute the label of the feature."""
+        label = feature.type
+        for key in self.label_fields:
+            if key in feature.qualifiers and len(feature.qualifiers[key]):
+                label = feature.qualifiers[key]
+                break
+        if isinstance(label, list):
+            label = "|".join(label)
+        return label
+
+    def compute_feature_linewidth(self, feature):
+        """Compute the edge width of the feature's arrow/rectangle."""
+        return 1.0
+
+    def compute_feature_legend_text(self, feature):
+        return None
+
+    def compute_feature_html(self, feature):
+        """Gets the 'label' of the feature."""
+        return self.compute_feature_label(feature)
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/dna_features_viewer/BiopythonTranslator/BiopythonTranslatorBase.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/dna_features_viewer/BiopythonTranslator/BiopythonTranslatorBase.py Fri Jun 17 12:59:24 2022 +0000
[
@@ -0,0 +1,119 @@
+from ..biotools import load_record
+from ..GraphicRecord import GraphicRecord
+from ..CircularGraphicRecord import CircularGraphicRecord
+from ..GraphicFeature import GraphicFeature
+
+
+class BiopythonTranslatorBase:
+    """Base class for all BiopythonTranslators.
+
+    This class needs to be complemented with methods compute_feature_label,
+    compute_features_color, etc. to be usable. See BiopythonTranslator for
+    an example of minimal working subclass.
+
+    Parameters
+    ----------
+
+    features_filters
+      List of filters (some_biopython_feature) => True/False.
+      Only features passing all the filters are kept.
+      This only works if you haven't redefined ``compute_filtered_features``
+
+    features_properties
+      A function (feature)=> properties_dict
+
+    """
+    graphic_record_parameters = {}
+
+    def __init__(self, features_filters=(), features_properties=None):
+        self.features_filters = features_filters
+        self.features_properties = features_properties
+
+    def translate_feature(self, feature):
+        """Translate a Biopython feature into a Dna Features Viewer feature."""
+        properties = dict(
+            label=self.compute_feature_label(feature),
+            color=self.compute_feature_color(feature),
+            html=self.compute_feature_html(feature),
+            fontdict=self.compute_feature_fontdict(feature),
+            box_linewidth=self.compute_feature_box_linewidth(feature),
+            box_color=self.compute_feature_box_color(feature),
+            linewidth=self.compute_feature_linewidth(feature),
+            label_link_color=self.compute_feature_label_link_color(feature),
+            legend_text=self.compute_feature_legend_text(feature)
+        )
+        if self.features_properties is not None:
+            other_properties = self.features_properties
+            if hasattr(other_properties, '__call__'):
+                other_properties = other_properties(feature)
+            properties.update(other_properties)
+
+        return GraphicFeature(
+            start=feature.location.start,
+            end=feature.location.end,
+            strand=feature.location.strand,
+            **properties
+        )
+
+    def translate_record(self, record, record_class=None):
+        """Create a new GraphicRecord from a BioPython Record object.
+
+        Parameters
+        ----------
+
+        record
+          A BioPython Record object or the path to a Genbank or a GFF file.
+
+        record_class
+          The graphic record class to use, e.g. GraphicRecord (default) or
+          CircularGraphicRecord. Strings 'circular' and 'linear' can also be
+          provided.
+        """
+        classes = {
+            "linear": GraphicRecord,
+            "circular": CircularGraphicRecord,
+            None: GraphicRecord,
+        }
+        if record_class in classes:
+            record_class = classes[record_class]
+
+        if isinstance(record, str) or hasattr(record, 'read'):
+            record = load_record(record)
+        filtered_features = self.compute_filtered_features(record.features)
+        return record_class(
+            sequence_length=len(record),
+            sequence=str(record.seq),
+            features=[
+                self.translate_feature(feature)
+                for feature in filtered_features
+                if feature.location is not None
+            ],
+            **self.graphic_record_parameters
+        )
+    
+    @classmethod
+    def quick_class_plot(cls, record, figure_width=12, **kwargs):
+        """Allows super quick and dirty plotting of Biopython records.
+
+        This is really meant for use in a Jupyter/Ipython notebook with
+        the "%matplotlib inline" setting.
+
+        >>> from dna_features_viewer import BiopythonTranslator
+        >>> BiopythonTranslator.quick_plot(my_record)
+        """
+        graphic_record = cls().translate_record(record)
+        ax, _ = graphic_record.plot(figure_width=figure_width, **kwargs)
+        return ax
+    
+    def quick_plot(self, record, figure_width=12, **kwargs):
+        """Allows super quick and dirty plotting of Biopython records.
+
+        This is really meant for use in a Jupyter/Ipython notebook with
+        the "%matplotlib inline" setting.
+
+        >>> from dna_features_viewer import BiopythonTranslator
+        >>> BiopythonTranslator.quick_plot(my_record)
+        """
+        graphic_record = self.translate_record(record)
+        ax, _ = graphic_record.plot(figure_width=figure_width, **kwargs)
+        return ax
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/dna_features_viewer/BiopythonTranslator/BlackBoxlessLabelTranslator.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/dna_features_viewer/BiopythonTranslator/BlackBoxlessLabelTranslator.py Fri Jun 17 12:59:24 2022 +0000
b
@@ -0,0 +1,13 @@
+from .BiopythonTranslator import BiopythonTranslator
+
+class BlackBoxlessLabelTranslator(BiopythonTranslator):
+    """Translates Biopython records into GraphicRecords where annotations
+    appear black on a white background with no box. Which can be cleaner.""" 
+
+    def compute_feature_box_linewidth(self, feature):
+        """Return 0 as this translator doesn't show a box."""
+        return 0
+
+    def compute_feature_box_color(self, feature):
+        """Return white."""
+        return "white"
\ No newline at end of file
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/dna_features_viewer/BiopythonTranslator/__init__.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/dna_features_viewer/BiopythonTranslator/__init__.py Fri Jun 17 12:59:24 2022 +0000
[
@@ -0,0 +1,4 @@
+from .BiopythonTranslator import BiopythonTranslator
+from .BlackBoxlessLabelTranslator import BlackBoxlessLabelTranslator
+
+__all__ = ["BiopythonTranslator", "BlackBoxlessLabelTranslator"]
\ No newline at end of file
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/dna_features_viewer/CircularGraphicRecord/ArrowWedge.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/dna_features_viewer/CircularGraphicRecord/ArrowWedge.py Fri Jun 17 12:59:24 2022 +0000
[
@@ -0,0 +1,110 @@
+"""Implements the missing Matplotlib ArrowWedge patch class.
+
+This is a plain arrow curved alongside a protion of circle, like you would
+expect a circular genetic feature to look.
+"""
+import numpy as np
+import matplotlib.patches as mpatches
+
+
+class ArrowWedge(mpatches.Wedge):
+    """Matplotlib patch shaped as a tick fraction of circle with a pointy end.
+
+    This is the patch used by CircularGraphicRecord to draw features.
+
+    Parameters
+    ----------
+
+    center
+      Center of the circle around which the arrow-wedge is drawn.
+
+    radius
+      Radius of the circle around which the arrow-wedge is drawn.
+
+    theta1
+      Start angle of the wedge
+
+    theta2
+      End angle of the wedge
+
+    width
+      Width or thickness of the arrow-wedge.
+
+    direction
+      Determines whether the pointy end points in direct sense (+1) or
+      indirect sense (-1) or no sense at all (0)
+    """
+
+    def __init__(
+        self, center, radius, theta1, theta2, width, direction=+1, **kwargs
+    ):
+
+        self.direction = direction
+        self.radius = radius
+        mpatches.Wedge.__init__(
+            self, center, radius, theta1, theta2, width, **kwargs
+        )
+        self._recompute_path()
+
+    def _recompute_path(self):
+        """Recompute the full path forming the "tick" arrowed wedge
+        
+        This method overwrites "mpatches.Wedge._recompute_path" in the
+        super-class.
+        """
+
+        if self.direction not in [-1, +1]:
+            return mpatches.Wedge._recompute_path(self)
+
+        theta1, theta2 = self.theta1, self.theta2
+        arrow_angle = min(5, abs(theta2 - theta1) / 2)
+        normalized_arrow_width = self.width / 2.0 / self.radius
+        if self.direction == +1:
+            angle_start_arrow = theta1 + arrow_angle
+            arc = mpatches.Path.arc(angle_start_arrow, theta2)
+            outer_arc = arc.vertices[::-1] * (1 + normalized_arrow_width)
+            inner_arc = arc.vertices * (1 - normalized_arrow_width)
+            arrow_vertices = [
+                outer_arc[-1],
+                np.array(
+                    [np.cos(np.deg2rad(theta1)), np.sin(np.deg2rad(theta1))]
+                ),
+                inner_arc[0],
+            ]
+        else:
+            angle_start_arrow = theta2 - arrow_angle
+            arc = mpatches.Path.arc(theta1, angle_start_arrow)
+            outer_arc = (
+                arc.vertices * (self.radius + self.width / 2.0) / self.radius
+            )
+            inner_arc = (
+                arc.vertices[::-1]
+                * (self.radius - self.width / 2.0)
+                / self.radius
+            )
+            arrow_vertices = [
+                outer_arc[-1],
+                np.array(
+                    [np.cos(np.deg2rad(theta2)), np.sin(np.deg2rad(theta2))]
+                ),
+                inner_arc[0],
+            ]
+        p = np.vstack([outer_arc, arrow_vertices, inner_arc])
+
+        path_vertices = np.vstack([p, inner_arc[-1, :], (0, 0)])
+
+        path_codes = np.hstack(
+            [
+                arc.codes,
+                4 * [mpatches.Path.LINETO],
+                arc.codes[1:],
+                mpatches.Path.LINETO,
+                mpatches.Path.CLOSEPOLY,
+            ]
+        )
+        path_codes[len(arc.codes)] = mpatches.Path.LINETO
+
+        # Shift and scale the wedge to the final location.
+        path_vertices *= self.r
+        path_vertices += np.asarray(self.center)
+        self._path = mpatches.Path(path_vertices, path_codes)
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/dna_features_viewer/CircularGraphicRecord/CircularGraphicRecord.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/dna_features_viewer/CircularGraphicRecord/CircularGraphicRecord.py Fri Jun 17 12:59:24 2022 +0000
[
@@ -0,0 +1,171 @@
+"""Implements the CircularGraphicRecord class.
+"""
+
+import matplotlib.patches as mpatches
+import numpy as np
+
+from ..GraphicRecord import GraphicRecord
+from .ArrowWedge import ArrowWedge
+
+
+class CircularGraphicRecord(GraphicRecord):
+    """Set of Genetic Features of a same DNA sequence, to be plotted together.
+
+    Parameters
+    ----------
+
+    sequence_length
+      Length of the DNA sequence, in number of nucleotides
+
+    features
+      list of GraphicalFeature objects.
+
+    top_position
+      The index in the sequence that will end up at the top of the circle
+
+    feature_level_height
+      Width in inches of one "level" for feature arrows.
+
+    annotation_height
+      Width in inches of one "level" for feature annotations.
+
+    labels_spacing
+      Distance in basepairs to keep between labels to avoid "quasi-collisions"
+
+    **kw
+      Other keyword arguments - do not use, these parameters are allowed for
+      making it easier to use GraphicRecord and CircularGraphicRecord
+      interchangeably.
+
+    """
+
+    default_elevate_outline_annotations = True
+    min_y_height_of_text_line = 0.1
+
+    def __init__(
+        self,
+        sequence_length,
+        features,
+        top_position=0,
+        feature_level_height=0.2,
+        annotation_height="auto",
+        labels_spacing=12,
+        **kw
+    ):
+
+        self.radius = 1.0
+        self.sequence_length = sequence_length
+        self.features = features
+        self.top_position = top_position
+        self.feature_level_height = feature_level_height
+        self.annotation_height = annotation_height
+        self.labels_spacing = labels_spacing
+
+    def initialize_ax(self, ax, draw_line, with_ruler):
+        """Initialize the ax with a circular line, sets limits, aspect etc.
+        """
+
+        if draw_line:
+            circle = mpatches.Circle(
+                (0, -self.radius), self.radius, facecolor="none", edgecolor="k"
+            )
+            ax.add_patch(circle)
+        ax.axis("off")
+        if with_ruler:
+            # only display the xaxis ticks
+            ax.set_frame_on(False)
+            ax.yaxis.set_visible(False)
+            ax.xaxis.tick_bottom()
+        else:
+            # don't display anything
+            ax.axis("off")
+
+        ax.set_xlim(-1.1 * self.radius, 1.1 * self.radius)
+        ax.set_ylim(-self.radius, 3 * self.radius)
+        ax.set_aspect("equal")
+
+    def finalize_ax(
+        self,
+        ax,
+        features_levels,
+        annotations_max_level,
+        auto_figure_height=False,
+        ideal_yspan=None,
+        annotations_are_elevated=True
+    ):
+        """Final display range and figure dimension tweakings."""
+        annotation_height = self.determine_annotation_height(
+            annotations_max_level
+        )
+        ymin = -2 * self.radius - self.feature_level_height * (
+            features_levels + 1
+        )
+        ymax = (
+            self.feature_level_height * (features_levels + 1)
+            + annotation_height * (annotations_max_level + 1)
+        )
+        if ideal_yspan is not None:
+            ymax = max(annotation_height * ideal_yspan + ymin, ymax)
+        xmin = -self.radius - self.feature_level_height * (features_levels + 1)
+        xmax = -xmin
+        ax.set_xlim(xmin, xmax)
+        ax.set_ylim(ymin, ymax)
+        ratio = 1.0 * (ymax - ymin) / (xmax - xmin)
+
+        if auto_figure_height:
+            figure_width = ax.figure.get_size_inches()[0]
+            ax.figure.set_size_inches(figure_width, figure_width * ratio)
+
+    def plot_feature(self, ax, feature, level):
+        """Plot an ArrowWedge representing the feature at the giben height
+        level.
+
+
+        """
+        a_start = self.position_to_angle(feature.start)
+        a_end = self.position_to_angle(feature.end)
+        a_start, a_end = sorted([a_start, a_end])
+        r = self.radius + level * self.feature_level_height
+        patch = ArrowWedge(
+            center=(0, -self.radius),
+            radius=r,
+            theta1=a_start,
+            theta2=a_end,
+            width=0.7 * self.feature_level_height,
+            direction=feature.strand,
+            edgecolor=feature.linecolor,
+            linewidth=feature.linewidth,
+            facecolor=feature.color,
+            zorder=1,
+        )
+        ax.add_patch(patch)
+
+    def position_to_angle(self, position):
+        """Convert a sequence position into an angle in the figure."""
+        a = 360.0 * (position - self.top_position) / self.sequence_length
+        return 90 - a
+
+    def coordinates_in_plot(self, position, level):
+        """Convert a sequence position and height level to (x, y) coordinates.
+        """
+        r = self.radius + level * self.feature_level_height
+        angle = self.position_to_angle(position)
+        rad_angle = np.deg2rad(angle)
+        return np.array(
+            [r * np.cos(rad_angle), r * np.sin(rad_angle) - self.radius]
+        )
+
+    def determine_annotation_height(self, max_annotations_level):
+        """Auto-select the annotations height.
+
+        Annotation height is 0.2 at most, or else whatever will make
+        the figure a 5*radius tall rectangle where the circular plasmid
+        occupies the bottom-2 5th and the annotations occupy the top-3 5th.
+        """
+        return min(0.25, 3.0 * self.radius / (1.0 + max_annotations_level))
+
+    def compute_padding(self, ax):
+        ""
+        ax_width = ax.get_window_extent(ax.figure.canvas.get_renderer()).width
+        xmin, xmax = ax.get_xlim()
+        return 3 * self.labels_spacing * (xmax - xmin) / (1.0 * ax_width)
\ No newline at end of file
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/dna_features_viewer/CircularGraphicRecord/__init__.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/dna_features_viewer/CircularGraphicRecord/__init__.py Fri Jun 17 12:59:24 2022 +0000
[
@@ -0,0 +1,3 @@
+from .CircularGraphicRecord import CircularGraphicRecord
+
+__all__ = ["CircularGraphicRecord"]
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/dna_features_viewer/GraphicFeature.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/dna_features_viewer/GraphicFeature.py Fri Jun 17 12:59:24 2022 +0000
[
@@ -0,0 +1,162 @@
+from copy import deepcopy
+
+class GraphicFeature:
+    """Genetic Feature to be plotted.
+
+    Parameters
+    ----------
+
+    start, end
+      Coordinates of the feature in the final sequence.
+
+    strand
+      Directionality of the feature. can be +1/-1/0 for direct sense,
+      anti-sense, or no directionality.
+
+    label
+      Short descriptive text associated and plotted with the feature
+
+    color
+      Color of the feature, any Matplotlib-compatible format is accepted,
+      such as "white", "w", "#ffffff", (1,1,1), etc.
+
+    linecolor
+      Color of the feature's border, any Matplotlib-compatible format is
+      accepted, such as "white", "w", "#ffffff", (1,1,1), etc.
+
+    linewidth
+      Width of the line (=edge) surrounding the graphic feature, in pixels.
+
+    thickness
+      Vertical span of the feature
+
+    box_color
+      Color of the label box. Set to None for no box around the label.
+      Leave to "auto" for a box color that is a lightened version of the
+      feature's color.
+
+    data
+      Any other keyword is kept into the feature.data[] dictionary.
+
+    fontdict
+      A Matplotlib fontdict for the font to be used in the label, e.g.
+      ``size=11``, ``weight='bold'``, ``family='Helvetica'``, etc.
+
+    open_left, open_right
+      Set to True if this feature does not end on the right or left because it
+      is a cropped version of a bigger feature.
+
+    box_linewidth
+      Width of the line delimiting the text box when the annotation is outside
+      the graphic feature. Set to 0 for no box borders
+
+    box_color
+      Background color of the annotation's text box. If left to "auto" the
+      color will be a lighter version of the feature's color.
+
+    label_link_color
+      Color of the line linking the text annotation to its respective graphic
+      feature. Set to auto for the line to automatically be a darker version
+      of the feature's color.
+    """
+
+    feature_type = "feature"
+
+    def __init__(
+        self,
+        start=None,
+        end=None,
+        strand=None,
+        label=None,
+        color="#000080",
+        thickness=14,
+        linewidth=1.0,
+        linecolor="#000000",
+        fontdict=None,
+        html=None,
+        open_left=False,
+        open_right=False,
+        box_linewidth=1,
+        box_color="auto",
+        legend_text=None,
+        label_link_color="black",
+        **data
+    ):
+        self.start = start
+        self.end = end
+        self.strand = strand
+        self.label = label
+        self.color = color
+        self.linecolor = linecolor
+        self.data = data
+        self.thickness = thickness
+        self.linewidth = linewidth
+        self.box_linewidth = box_linewidth
+        self.box_color = box_color
+        self.label_link_color = label_link_color
+        self.fontdict = dict(
+            [("fontsize", 11)] + list((fontdict or {}).items())
+        )
+        self.html = html
+        self.open_left = open_left
+        self.open_right = open_right
+        self.legend_text = legend_text
+
+    def split_in_two(self, x_coord=0):
+        """Return two features by cutting this feature at x_coord."""
+        copy1 = deepcopy(self)
+        copy2 = deepcopy(self)
+        copy1.end = x_coord
+        copy2.start = x_coord + 1
+        return copy1, copy2
+
+    def crop(self, window):
+        """Return a the fragment of the feature that is in the window.
+
+        If there is no overlap between the feature location and the window,
+        None is returned.
+        """
+        s, e = window
+        if (s > self.end) or (e < self.start):
+            return None
+        copy = deepcopy(self)
+        if s > self.start:
+            copy.start = s
+            copy.open_left = True
+        if e < self.end:
+            copy.end = e
+            copy.open_right = True
+        return copy
+
+    def overlaps_with(self, other):
+        """Return whether the feature's location overlaps with feature `other`
+        """
+        loc1, loc2 = (self.start, self.end), (other.start, other.end)
+        loc1, loc2 = sorted(loc1), sorted(loc2)
+        loc1, loc2 = sorted([loc1, loc2], key=lambda loc: loc[0])
+        return loc1[1] > loc2[0]
+
+    @property
+    def length(self):
+        """Return the length of the feature (end-start)"""
+        return abs(self.end - self.start)
+
+    @property
+    def x_center(self):
+        """Return the x-center of the feature, (start+end)/2"""
+        return 0.5 * (self.start + self.end - 1)
+
+    @staticmethod
+    def from_biopython_feature(feature, **props):
+        """Create a GraphicalFeature from a Biopython.Feature object."""
+        return GraphicFeature(
+            start=feature.location.start,
+            end=feature.location.end,
+            strand=feature.location.strand,
+            **props
+        )
+
+    def __repr__(self):
+        return ("GF(%(label)s, %(start)d-%(end)d " % self.__dict__) + (
+            ")" if self.strand is None else "(%d))" % self.strand
+        )
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/dna_features_viewer/GraphicRecord/BokehPlottableMixin.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/dna_features_viewer/GraphicRecord/BokehPlottableMixin.py Fri Jun 17 12:59:24 2022 +0000
[
@@ -0,0 +1,174 @@
+try:
+    from bokeh.plotting import figure, ColumnDataSource
+    from bokeh.models import Range1d, HoverTool
+
+    BOKEH_AVAILABLE = True
+except ImportError:
+    BOKEH_AVAILABLE = False
+
+try:
+    import pandas as pd
+
+    PANDAS_AVAILABLE = True
+except ImportError:
+    PANDAS_AVAILABLE = False
+
+import matplotlib.pyplot as plt
+
+
+class BokehPlottableMixin:
+    def bokeh_feature_patch(
+        self,
+        start,
+        end,
+        strand,
+        figure_width=5,
+        width=0.4,
+        level=0,
+        arrow_width_inches=0.05,
+        **kwargs
+    ):
+        """Return a dict with points coordinates of a Bokeh Feature arrow.
+
+        Parameters
+        ----------
+
+        start, end, strand
+
+        """
+        hw = width / 2.0
+        x1, x2 = (start, end) if (strand >= 0) else (end, start)
+        bp_per_width = figure_width / self.sequence_length
+        delta = arrow_width_inches / bp_per_width
+        if strand >= 0:
+            head_base = max(x1, x2 - delta)
+        else:
+            head_base = min(x1, x2 + delta)
+        result = dict(
+            xs=[x1, x1, head_base, x2, head_base, x1],
+            ys=[e + level for e in [-hw, hw, hw, 0, -hw, -hw]],
+        )
+        result.update(kwargs)
+        return result
+
+    def plot_with_bokeh(self, figure_width=5, figure_height="auto", tools="auto"):
+        """Plot the graphic record using Bokeh.
+
+        Examples
+        --------
+
+        >>>
+
+
+        """
+        if not BOKEH_AVAILABLE:
+            raise ImportError("``plot_with_bokeh`` requires Bokeh installed.")
+        if not PANDAS_AVAILABLE:
+            raise ImportError("``plot_with_bokeh`` requires Pandas installed.")
+
+        # Set up default tools
+        if tools == "auto":
+            tools = [HoverTool(tooltips="@hover_html"), "xpan,xwheel_zoom,reset,tap"]
+
+        # FIRST PLOT WITH MATPLOTLIB AND GATHER INFOS ON THE PLOT
+        ax, (features_levels, plot_data) = self.plot(figure_width=figure_width)
+        width, height = [int(100 * e) for e in ax.figure.get_size_inches()]
+        plt.close(ax.figure)
+        if figure_height == "auto":
+            height = int(0.5 * height)
+        else:
+            height = 100 * figure_height
+        height = max(height, 185) # Minimal height to see all icons
+
+        max_y = max(
+            [data["annotation_y"] for f, data in plot_data.items()]
+            + list(features_levels.values())
+        )
+
+        # BUILD THE PLOT ()
+        plot = figure(
+            plot_width=width,
+            plot_height=height,
+            tools=tools,
+            x_range=Range1d(0, self.sequence_length),
+            y_range=Range1d(-1, max_y + 1),
+        )
+        plot.patches(
+            xs="xs",
+            ys="ys",
+            color="color",
+            line_color="#000000",
+            source=ColumnDataSource(
+                pd.DataFrame.from_records(
+                    [
+                        self.bokeh_feature_patch(
+                            feature.start,
+                            feature.end,
+                            feature.strand,
+                            figure_width=figure_width,
+                            level=level,
+                            color=feature.color,
+                            label=feature.label,
+                            hover_html=(
+                                feature.html
+                                if feature.html is not None
+                                else feature.label
+                            ),
+                        )
+                        for feature, level in features_levels.items()
+                    ]
+                )
+            ),
+        )
+
+        if plot_data != {}:
+            plot.text(
+                x="x",
+                y="y",
+                text="text",
+                text_align="center",
+                text_font_size="12px",
+                text_font="arial",
+                text_font_style="normal",
+                source=ColumnDataSource(
+                    pd.DataFrame.from_records(
+                        [
+                            dict(
+                                x=feature.x_center,
+                                y=pdata["annotation_y"],
+                                text=feature.label,
+                                color=feature.color,
+                            )
+                            for feature, pdata in plot_data.items()
+                        ]
+                    )
+                ),
+            )
+            plot.segment(
+                x0="x0",
+                x1="x1",
+                y0="y0",
+                y1="y1",
+                line_width=0.5,
+                color="#000000",
+                source=ColumnDataSource(
+                    pd.DataFrame.from_records(
+                        [
+                            dict(
+                                x0=feature.x_center,
+                                x1=feature.x_center,
+                                y0=pdata["annotation_y"],
+                                y1=pdata["feature_y"],
+                            )
+                            for feature, pdata in plot_data.items()
+                        ]
+                    )
+                ),
+            )
+
+        plot.yaxis.visible = False
+        plot.outline_line_color = None
+        plot.grid.grid_line_color = None
+        plot.toolbar.logo = None
+
+        return plot
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/dna_features_viewer/GraphicRecord/GraphicRecord.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/dna_features_viewer/GraphicRecord/GraphicRecord.py Fri Jun 17 12:59:24 2022 +0000
[
@@ -0,0 +1,199 @@
+from ..biotools import find_narrowest_text_wrap
+
+from Bio.Seq import Seq
+from Bio.SeqRecord import SeqRecord
+from Bio.SeqFeature import FeatureLocation, SeqFeature
+from Bio.Alphabet import DNAAlphabet
+
+from .MatplotlibPlottableMixin import MatplotlibPlottableMixin
+from .BokehPlottableMixin import BokehPlottableMixin
+
+
+class GraphicRecord(MatplotlibPlottableMixin, BokehPlottableMixin):
+    """Set of Genetic Features of a same DNA sequence, to be plotted together.
+
+    Parameters
+    ----------
+
+    sequence_length
+      Length of the DNA sequence, in number of nucleotides
+
+    features
+      list of GraphicalFeature objects.
+
+    feature_level_height
+      Width in inches of one "level" for feature arrows.
+
+    annotation_height
+      Width in inches of one "level" for feature annotations.
+
+    first_index
+      Indicates the first index to plot in case the sequence is actually a
+      subsequence of a larger one. For instance, if the Graphic record
+      represents the segment (400, 420) of a sequence, we will have
+      ``first_index=400`` and ``sequence_length=20``.
+
+    plots_indexing
+      Indicates which standard to use to show nucleotide indices in the plots.
+      If 'biopython', the standard python indexing is used (starting at 0).
+      If 'genbank', the indexing follows the Genbank standard (starting at 1).
+    
+    labels_spacing
+      Number of pixels that will "pad" every labels to force some horizontal
+      space between two labels or between a label and the borders of a feature.
+    
+    ticks_resolution
+      Leave to "auto" for an auto-selected number of ticks on the ruler, or set
+      to e.g. 50 for a tick every 50 nucleotide. 
+
+    Attributes
+    ----------
+
+      default_font_family
+        Default font to use for a feature that doesn't declare a font.
+      
+      default_ruler_color
+        Default ruler color to use when no color is given at plot() time.
+    
+      default_box_color
+        Default box color for non-inline annotations. If set to None, no
+        boxes will be drawn unless the features declare a box_color.
+        If "auto", a color (clearer version of the feature's color) will be
+        computed, for all features also declaring their box_color as "auto".
+
+      default_elevate_outline_annotations
+        Value to use for elevate_outline_annotations when no specific value is
+        given at ``graphic_record.plot(...)`` time. Set to true to have all
+        text annotations appears above all features, or false else.
+    """
+
+    default_font_family = None
+    default_ruler_color = "grey"
+    default_box_color = "auto"
+    min_y_height_of_text_line = 0.5
+
+    def __init__(
+        self,
+        sequence_length=None,
+        sequence=None,
+        features=(),
+        feature_level_height=1,
+        first_index=0,
+        plots_indexing="biopython",
+        labels_spacing=8,
+        ticks_resolution='auto'
+    ):
+        if sequence_length is None:
+            sequence_length = len(sequence)
+        self.features = features
+        self.sequence_length = sequence_length
+        self.feature_level_height = feature_level_height
+        self.sequence = sequence
+        self.first_index = first_index
+        self.plots_indexing = plots_indexing
+        self.labels_spacing = labels_spacing
+        self.ticks_resolution = ticks_resolution
+
+    @property
+    def last_index(self):
+        return self.first_index + self.sequence_length
+
+    @property
+    def span(self):
+        """Return the display span (start, end) accounting for first_index."""
+        return self.first_index, self.last_index
+
+    def to_biopython_record(self, sequence):
+        """
+        Example
+        -------
+        from Bio import SeqIO
+        gr_record = GraphicRecord(features=features, sequence_length=len(seq),
+                                  sequence=seq)
+        bio_record = gr_record.to_biopython_record()
+        with open("example.gb", "w+") as f:
+            SeqIO.write(record, f, "genbank")
+        """
+        features = [
+            SeqFeature(
+                FeatureLocation(f.start, f.end, f.strand),
+                type=f.feature_type,
+                qualifiers={"label": f.label},
+            )
+            for f in self.features
+        ]
+        if not isinstance(sequence, Seq):
+            sequence = Seq(sequence, alphabet=DNAAlphabet())
+        return SeqRecord(seq=sequence, features=features)
+
+    def crop(self, window):
+        start, end = window
+        first_index = self.first_index
+        if (start < first_index) or (end > self.last_index):
+            raise ValueError("out-of-bound cropping")
+        new_features = []
+        for f in self.features:
+            cropped_feature = f.crop(window)
+            if cropped_feature is not None:  # = has ovelap with the window
+                new_features.append(cropped_feature)
+
+        return GraphicRecord(
+            sequence=self.sequence[start - first_index : end - first_index]
+            if self.sequence is not None
+            else None,
+            sequence_length=end - start,
+            features=new_features,
+            feature_level_height=self.feature_level_height,
+            first_index=start,
+            plots_indexing=self.plots_indexing,
+            labels_spacing=self.labels_spacing,
+            ticks_resolution=self.ticks_resolution
+        )
+
+    def determine_annotation_height(self, levels):
+        """By default the ideal annotation level height is the same as the
+        feature_level_height."""
+        # TODO: Improve me! ideally, annotation width would be linked to the
+        # height of one line of text, so dependent on font size and ax
+        # height/span.
+        return self.feature_level_height
+
+    def coordinates_in_plot(self, x, level):
+        """Convert a sequence position and height level into a (x, y) position.
+        """
+        return (x, level * self.feature_level_height)
+
+    def split_overflowing_features_circularly(self):
+        """Split the features that overflow over the edge for circular
+        constructs (inplace)."""
+        new_features = []
+        for f in self.features:
+            if f.start < 0 < f.end:
+                f1, f2 = f.split_in_two(-1)
+                f1.start, f1.end = (
+                    f1.start + self.sequence_length,
+                    f1.end + self.sequence_length,
+                )
+                new_features += [f1, f2]
+            elif f.start < self.sequence_length < f.end:
+                f1, f2 = f.split_in_two(self.sequence_length - 1)
+                f2.start, f2.end = (
+                    f2.start - self.sequence_length,
+                    f2.end - self.sequence_length,
+                )
+                new_features += [f1, f2]
+            else:
+                new_features.append(f)
+        self.features = new_features
+
+    def _format_label(self, label, max_label_length=50, max_line_length=40):
+        if len(label) > max_label_length:
+            label = label[: max_label_length - 1] + "…"
+        if len(label) > max_line_length:
+            label = find_narrowest_text_wrap(label, max_line_length)
+        return label
+
+    def compute_padding(self, ax):
+        ax_width = ax.get_window_extent(ax.figure.canvas.get_renderer()).width
+        xmin, xmax = ax.get_xlim()
+        return self.labels_spacing * (xmax - xmin) / (1.0 * ax_width)
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/dna_features_viewer/GraphicRecord/MatplotlibPlottableMixin.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/dna_features_viewer/GraphicRecord/MatplotlibPlottableMixin.py Fri Jun 17 12:59:24 2022 +0000
[
b'@@ -0,0 +1,651 @@\n+"""Useful functions for the library"""\n+\n+import colorsys\n+\n+import matplotlib.pyplot as plt\n+import matplotlib.patches as mpatches\n+from matplotlib.patches import Patch\n+import matplotlib.ticker as ticker\n+\n+from ..compute_features_levels import compute_features_levels\n+from ..GraphicFeature import GraphicFeature\n+from matplotlib.colors import colorConverter\n+from .MultilinePlottableMixin import MultilinePlottableMixin\n+from .SequenceAndTranslationMixin import SequenceAndTranslationMixin\n+\n+\n+class MatplotlibPlottableMixin(\n+    MultilinePlottableMixin, SequenceAndTranslationMixin\n+):\n+    """Class mixin for matplotlib-related methods."""\n+\n+    default_elevate_outline_annotations = False\n+    default_strand_in_label_threshold = None\n+\n+    def initialize_ax(self, ax, draw_line, with_ruler, ruler_color=None):\n+        """Initialize the ax: remove axis, draw a horizontal line, etc.\n+\n+        Parameters\n+        ----------\n+\n+        draw_line\n+          True/False to draw the horizontal line or not.\n+\n+        with_ruler\n+          True/False to draw the indices indicators along the line.\n+\n+        """\n+        ruler_color = ruler_color or self.default_ruler_color\n+        start, end = self.span\n+        plot_start, plot_end = start - 0.8, end - 0.2\n+        if draw_line:\n+            ax.plot([plot_start, plot_end], [0, 0], zorder=-1000, c="k")\n+\n+        if with_ruler:  # only display the xaxis ticks\n+            ax.set_frame_on(False)\n+            ax.yaxis.set_visible(False)\n+            ax.xaxis.tick_bottom()\n+            if ruler_color is not None:\n+                ax.tick_params(axis="x", colors=ruler_color)\n+        else:  # don\'t display anything\n+            ax.axis("off")\n+\n+        ax.set_xlim(plot_start, plot_end)\n+        if self.first_index != 0:\n+            ax.ticklabel_format(useOffset=False, style="plain")\n+        fmt = lambda x, p: "{:,}".format(int(x))\n+        ax.xaxis.set_major_formatter(ticker.FuncFormatter(fmt))\n+        if self.ticks_resolution == "auto":\n+            ax.xaxis.set_major_locator(ticker.MaxNLocator(integer=True))\n+        else:\n+            locator = ticker.MultipleLocator(self.ticks_resolution)\n+            ax.xaxis.set_major_locator(locator)\n+\n+    def finalize_ax(\n+        self,\n+        ax,\n+        features_levels,\n+        annotations_max_level,\n+        auto_figure_height=False,\n+        ideal_yspan=None,\n+        annotations_are_elevated=True,\n+    ):\n+        """Prettify the figure with some last changes.\n+        \n+        Changes include redefining y-bounds and figure height.\n+\n+        Parameters\n+        ==========\n+        ax\n+          ax on which the record was plotted\n+        \n+        features_levels\n+          \n+        annotations_max_level\n+          Number indicating to the method the maximum height for an\n+          annotation, so the method can set ymax accordingly\n+\n+        auto_figure_height\n+          If true, the figure\'height will be automatically re-set to a nice\n+          value (counting ~0.4inch per level in the figure).\n+\n+        ideal_yspan\n+          if provided, can help the method select a better ymax to make sure\n+          all constraints fit.\n+\n+        """\n+\n+        # Compute the "natural" ymax\n+        annotation_height = self.determine_annotation_height(None)\n+        features_ymax = self.feature_level_height * (features_levels + 1)\n+        annotations_ymax = annotation_height * annotations_max_level\n+        if annotations_are_elevated:\n+            ymax = features_ymax + annotations_ymax\n+        else:\n+            ymax = max(features_ymax, annotations_ymax) + 1\n+        ymin = min(ax.get_ylim()[0], -0.5)\n+\n+        # ymax could be even bigger if a "ideal_yspan" has been set.\n+        if (ideal_yspan is not None) and not (auto_figure_height):\n+            ymax = max(ideal_yspan + ymin, ymax)\n+        ax.set_ylim(ymin, ymax)\n+        if auto_figure_height:\n+            figure_width = ax.figure.get_size_inches()[0]\n+ '..b'ature_level"]\n+            )\n+\n+            # PLOT THE LABEL-TO-FEATURE LINK\n+            link_color = feature.label_link_color\n+            if link_color == "auto":\n+                link_color = change_luminosity(feature.color, luminosity=0.2)\n+            ax.plot([x, fx], [new_y, fy], c=link_color, lw=0.5, zorder=-10)\n+            labels_data[feature.data["feature"]] = dict(\n+                feature_y=fy, annotation_y=new_y\n+            )\n+\n+        if plot_sequence:\n+            self.plot_sequence(ax, **(sequence_params or {}))\n+\n+        self.finalize_ax(\n+            ax=ax,\n+            features_levels=max([1] + list(features_levels.values())),\n+            annotations_max_level=max_annotations_level,\n+            auto_figure_height=auto_figure_height,\n+            ideal_yspan=ideal_yspan,\n+            annotations_are_elevated=elevate_outline_annotations,\n+        )\n+        return ax, (features_levels, labels_data)\n+\n+    def plot_legend(\n+        self, ax, allow_ambiguity=False, include_edge=True, **legend_kwargs\n+    ):\n+        handles = []\n+        features_parameters = {}\n+        for feature in self.features:\n+            text = feature.legend_text\n+            if text is None:\n+                continue\n+            parameters = dict(\n+                label=text, facecolor=feature.color, edgecolor="black",\n+            )\n+            if include_edge:\n+                parameters.update(\n+                    dict(\n+                        linewidth=feature.linewidth,\n+                        edgecolor=feature.linecolor,\n+                    )\n+                )\n+            if text in features_parameters:\n+                previous_parameters = features_parameters[text]\n+                if (not allow_ambiguity) and any(\n+                    [\n+                        parameters[k] != previous_parameters[k]\n+                        for k in parameters\n+                    ]\n+                ):\n+                    raise ValueError(\n+                        "Cannot generate an unambiguous legend as two"\n+                    )\n+                continue\n+            features_parameters[text] = parameters\n+            handles.append(Patch(**parameters))\n+        ax.legend(handles=handles, **legend_kwargs)\n+\n+\n+def change_luminosity(\n+    color, luminosity=None, min_luminosity=None, factor=None\n+):\n+    """Return a version of the color with different luminosity.\n+\n+    Parameters\n+    ----------\n+    color\n+      A color in any Matplotlib-compatible format such as "white", "w",\n+      (1,1,1), "#ffffff", etc.\n+    luminosity\n+      A float in 0-1. If provided, the returned color has this level of\n+      luminosity.\n+    factor\n+      Only used if `luminosity` is not set. Positive factors increase\n+      luminosity and negative factors decrease it. More precisely, the\n+      luminosity of the new color is L^(-factor), where L is the current\n+      luminosity, between 0 and 1. \n+    """\n+    r, g, b = colorConverter.to_rgb(color)\n+    h, l, s = colorsys.rgb_to_hls(r, g, b)\n+    new_l = l\n+    if luminosity is not None:\n+        new_l = luminosity\n+    if factor is not None:\n+        new_l = l ** (-factor)\n+    if min_luminosity is not None:\n+        new_l = max(new_l, min_luminosity)\n+\n+    return colorsys.hls_to_rgb(h, new_l, s)\n+\n+\n+def get_text_box(text, margin=0):\n+    """Return the coordinates of a Matplotlib Text.\n+\n+    `text` is a Matplotlib text obtained with ax.text().\n+    This returns `(x1,y1, x2, y2)` where (x1,y1) is the lower left corner\n+    and (x2, y2) is the upper right corner of the text, in data coordinates.\n+    If a margin m is supplied, the returned result is (x1-m, y1-m, x2+m, y2+m)\n+    """\n+    renderer = text.axes.figure.canvas.get_renderer()\n+    bbox = text.get_window_extent(renderer)  # bounding box\n+    __x1, y1, __x2, y2 = bbox.get_points().flatten()\n+    bbox = bbox.transformed(text.axes.transData.inverted())\n+    x1, __y1, x2, __y2 = bbox.get_points().flatten()\n+    return [x1, y1, x2, y2]\n'
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/dna_features_viewer/GraphicRecord/MultilinePlottableMixin.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/dna_features_viewer/GraphicRecord/MultilinePlottableMixin.py Fri Jun 17 12:59:24 2022 +0000
[
@@ -0,0 +1,153 @@
+import matplotlib.pyplot as plt
+from matplotlib.backends.backend_pdf import PdfPages
+import numpy
+
+
+class MultilinePlottableMixin:
+    def plot_on_multiple_lines(
+        self,
+        n_lines=None,
+        nucl_per_line=None,
+        plot_sequence=False,
+        figure_width="auto",
+        **plot_params
+    ):
+        """Plot the features on different lines (one Matplotlib ax per line)
+
+        Parameters
+        ----------
+
+        n_lines
+          Number of lines on which the record will be plotted. A number of
+          nucleotides per line can be provided instead (see below).
+        
+        nucl_per_line
+          Number of nucleotides to be represented on every line (determines
+          the number of lines ``n_lines``).
+        
+        plot_sequence
+          Whether to plot the nucleotide sequence on each line
+
+        figure_width
+          Width of the figure in inches. Leave to auto for a width of either 10
+          (if not sequence is plotted) or 0.15*nucl_per_line inches
+          (if a sequence is plotted).
+
+        **plot_params
+          Parameters from ``graphic_record.plot()`` to be used in the plotting
+          of the individual lines. This includes ``draw_line``, ``with_ruler``,
+          ``annotate_inline``, ``plot_sequence``,
+          ``evelate_outline_annotations``, ``strand_in_label_pixel_threshold``
+        
+        Returns
+        -------
+
+        figure, axes
+          The matplotlib figure and axes generated.
+        """
+
+        if n_lines is None:
+            n_lines = int(numpy.ceil(self.sequence_length / nucl_per_line))
+        else:
+            nucl_per_line = self.sequence_length // n_lines + 1
+
+        if figure_width == "auto":
+            if plot_sequence:
+                figure_width = 0.15 * nucl_per_line
+            else:
+                figure_width = 10
+
+        figures_heights = []
+
+        def plot_line(line_index, ax=None):
+            first, last = self.first_index, self.last_index
+            line_start = first + line_index * nucl_per_line
+            line_virtual_end = first + (line_index + 1) * nucl_per_line
+            line_end = min(last, line_virtual_end)
+            line_record = self.crop((line_start, line_end))
+            line_ax, _ = line_record.plot(
+                figure_width=figure_width,
+                x_lim=(line_start, line_virtual_end),
+                ax=ax,
+                plot_sequence=plot_sequence,
+                **plot_params
+            )
+            return line_ax
+
+        for line_index in range(n_lines):
+            line_ax = plot_line(line_index)
+            figures_heights.append(line_ax.figure.get_figheight())
+            plt.close(line_ax.figure)
+        fig, axes = plt.subplots(
+            n_lines,
+            1,
+            gridspec_kw={"height_ratios": figures_heights},
+            figsize=(figure_width, 0.9 * sum(figures_heights)),
+        )
+        if n_lines == 1:
+            axes = [axes]
+        for line_index, ax in enumerate(axes):
+            plot_line(line_index, ax=ax)
+        fig.tight_layout()
+        return fig, axes
+
+    def plot_on_multiple_pages(
+        self,
+        pdf_target,
+        n_lines=None,
+        nucl_per_line=None,
+        lines_per_page=5,
+        figure_width="auto",
+        **plot_params
+    ):
+        """Plot the features on different lines on different pages of a PDF.
+
+        This function returns None
+        
+        Parameters
+        ----------
+
+        pdf_target
+          Either a path to a PDF, or a file(-like) handle.
+
+        n_lines
+          Number of lines on which the record will be plotted. A number of
+          nucleotides per line can be provided instead (see below).
+        
+        nucl_per_line
+          Number of nucleotides to be represented on every line (determines
+          the number of lines ``n_lines``).
+        
+        lines_per_page
+          Number of lines on each page
+        
+        plot_sequence
+          Whether to plot the nucleotide sequence on each line
+
+        figure_width
+          Width of the figure in inches. Leave to auto for a width of either 10
+          (if not sequence is plotted) or 0.15*nucl_per_line inches
+          (if a sequence is plotted).
+
+        **plot_params
+          Parameters from ``graphic_record.plot()`` to be used in the plotting
+          of the individual lines. This includes ``draw_line``, ``with_ruler``,
+          ``annotate_inline``, ``plot_sequence``,
+          ``evelate_outline_annotations``, ``strand_in_label_pixel_threshold``
+        """
+        nucl_per_page = nucl_per_line * lines_per_page
+        number_of_pages = int(numpy.ceil(self.sequence_length / nucl_per_page))
+        with PdfPages(pdf_target) as pdf:
+            for page_index in range(number_of_pages):
+                first, last = self.first_index, self.last_index
+                page_start = first + page_index * nucl_per_page
+                page_end = first + (page_index + 1) * nucl_per_page
+                page_end = min(last, page_end)
+                page_record = self.crop((page_start, page_end))
+                fig, axes = page_record.plot_on_multiple_lines(
+                    nucl_per_line=nucl_per_line,
+                    figure_width=figure_width,
+                    **plot_params
+                )
+                pdf.savefig(fig)
+                plt.close(fig)
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/dna_features_viewer/GraphicRecord/SequenceAndTranslationMixin.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/dna_features_viewer/GraphicRecord/SequenceAndTranslationMixin.py Fri Jun 17 12:59:24 2022 +0000
[
@@ -0,0 +1,145 @@
+from ..biotools import extract_graphical_translation
+
+
+class SequenceAndTranslationMixin:
+    def plot_sequence(
+        self, ax, location=None, y_offset=1, fontdict=None, guides_intensity=0
+    ):
+        """Plot a sequence of nucleotides at the bottom of the plot.
+
+        Parameters
+        ----------
+
+        ax
+          Which axes the translation should be plotted to
+
+        location
+          location of the segment to translate, either (start, end) or
+          (start, end, strand)
+
+        y_offset
+          Number of text levels under the plot's base line where to draw the
+          nucleotides. Should be 1 if the nucleotide sequence is to be plotted
+          directly under the main line.
+
+        fontdict
+          Matplotlib fontdict for the text, e.g.
+          ``{'size': 11, 'weight':'bold'}``
+
+        background
+          tuple (color1, color2) of alternate colors to plot behind each
+          nucleotide position to guide vision. Leave to None for no background.
+
+        guides_intensity
+          Intensity of the vertical guides marking the different nucleotides
+          (0 = no guides)
+        """
+        if self.sequence is None:
+            raise ValueError("No sequence in the graphic record")
+        if location is None:
+            location = self.span
+        location_start, location_end = location
+        fontdict = dict(size=11)
+        fontdict.update(fontdict or {})
+        for i, nucleotide in enumerate(self.sequence):
+            index = i + location_start
+            if location_start <= index <= location_end:
+                ax.text(
+                    index,
+                    -0.7 * self.feature_level_height * y_offset,
+                    nucleotide,
+                    ha="center",
+                    va="center",
+                    fontdict=fontdict,
+                )
+        if guides_intensity:
+            color = (0, 0, 0, guides_intensity)
+            for i in range(location_start, location_end + 1):
+                ax.axvline(i - 0.5, linewidth=0.1, color=color, zorder=-10000)
+        ymin = ax.get_ylim()[0]
+        if ymin < -500:
+            ymin = 0
+        ax.set_ylim(bottom=min(ymin, -y_offset * self.feature_level_height))
+
+    def plot_translation(
+        self,
+        ax,
+        location=None,
+        y_offset=2,
+        fontdict=None,
+        guides_intensity=0.5,
+        translation=None,
+        long_form_translation=True,
+    ):
+        """Plot a sequence of amino-acids at the bottom of the plot.
+
+        Parameters
+        ----------
+
+        ax
+          Which axes the translation should be plotted to
+
+        location
+          location of the segment to translate (start, end)
+
+        y_offset
+          Number of text levels under the plot's base line where to draw the
+          amino acid names. Should be 2 if the nucleotide sequence is also
+          plotted at level 1.
+
+        fontdict
+          Matplotlib fontdict for the text, e.g.
+          ``{'size': 11, 'weight':'bold'}``
+
+        background
+          tuple (color1, color2) of alternate colors to plot behind each
+          amino acid position to guide vision. Leave to None for no background.
+
+        translation
+          Sequence of amino acids either as a string ``'MAKG...'`` or as a list
+          ``['Met', 'Ala', ...]``
+        
+
+        """
+        start, end = location[0], location[1]
+        strand = location[2] if (len(location) == 3) else 1
+        s, e = self.span
+        start = max(start, s + ((start - s) % 3))
+        end = min(end, e - ((end - e) % 3))
+        if translation is None:
+            new_loc = start - self.first_index, end - self.first_index, strand
+            translation = extract_graphical_translation(
+                self.sequence,
+                location=new_loc,
+                long_form=long_form_translation,
+            )
+        texts = [
+            ((start + 3 * i, start + 3 * (i + 1)), aa)
+            for i, aa in enumerate(translation)
+        ]
+
+        y = -0.7 * y_offset * self.feature_level_height
+        ymin = ax.get_ylim()[0]
+        ax.set_ylim(bottom=min(ymin, -y_offset * self.feature_level_height))
+        fontdict = fontdict or {}
+        guides_color = (0, 0, 0, guides_intensity)
+        for i, ((start, end), text) in enumerate(texts):
+            ax.text(
+                0.5 * (start + end - 1),
+                y,
+                text,
+                ha="center",
+                va="center",
+                fontdict=fontdict,
+            )
+            if guides_intensity:
+                ax.axvline(
+                    start - 0.5,
+                    linewidth=0.1,
+                    color=guides_color,
+                    zorder=-10000,
+                )
+        if guides_intensity:
+            ax.axvline(
+                end - 0.5, linewidth=0.1, color=guides_color, zorder=-10000
+            )
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/dna_features_viewer/GraphicRecord/__init__.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/dna_features_viewer/GraphicRecord/__init__.py Fri Jun 17 12:59:24 2022 +0000
[
@@ -0,0 +1,3 @@
+from .GraphicRecord import GraphicRecord
+
+__all__ = ['GraphicRecord']
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/dna_features_viewer/README.md
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/dna_features_viewer/README.md Fri Jun 17 12:59:24 2022 +0000
b
@@ -0,0 +1,13 @@
+# Code organization
+
+This document walks you trough the Geneblocks code. Please request changes if anything is unclear.
+
+- **GraphicFeature.py** implements a class for defining a *GraphicFeature*, which is an annotation (start, end, strand, label) with graphical properties (color, line width, font family...)
+
+- **GraphicRecord/** implements the *GraphicRecord* class, which can plot a set of *GraphicFeatures* using Matplotlib or Bokeh. To keep file sizes acceptable, many methods are implemented in separate files (*bokeh_plots.py*, *matplotlib_plots.py*) and added to *GraphicRecord* via class mixins.
+
+- **CircularGraphicRecord/** implements the *GraphicRecord* class, which inherits from *GraphicRecord* but draws features circularly using custom Matplotlib patches called "arrow-wedge" (defined in file *ArrowWedge.py*).
+
+- **compute_features_levels.py** implements the algorithm for deciding the levels on which the different features (and annotations) are drawn
+
+- **biotools.py** implements generic biology-related methods (reverse_complement, annotation of Biopython records, etc.)
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/dna_features_viewer/__init__.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/dna_features_viewer/__init__.py Fri Jun 17 12:59:24 2022 +0000
[
@@ -0,0 +1,22 @@
+""" dna_features_viewer/__init__.py """
+
+from .GraphicRecord import GraphicRecord
+from .CircularGraphicRecord import CircularGraphicRecord
+from .GraphicFeature import GraphicFeature
+from .BiopythonTranslator import (
+    BiopythonTranslator,
+    BlackBoxlessLabelTranslator,
+)
+from .biotools import load_record, annotate_biopython_record
+
+from .version import __version__
+
+__all__ = [
+    "GraphicRecord",
+    "CircularGraphicRecord",
+    "GraphicFeature",
+    "BiopythonTranslator",
+    "BlackBoxlessLabelTranslator",
+    "annotate_biopython_record",
+    "__version__",
+]
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/dna_features_viewer/biotools.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/dna_features_viewer/biotools.py Fri Jun 17 12:59:24 2022 +0000
[
@@ -0,0 +1,170 @@
+import textwrap
+from Bio.Seq import Seq
+from Bio.SeqFeature import SeqFeature, FeatureLocation
+from Bio.PDB.Polypeptide import aa1, aa3
+from Bio import SeqIO
+
+try:
+    from BCBio import GFF
+except ImportError:
+
+    class GFF:
+        def parse(*a):
+            """Not available. Please install bcbio-gff."""
+            raise ImportError(
+                "Please install the bcbio-gff library to parse GFF data"
+            )
+
+
+def complement(dna_sequence):
+    """Return the complement of the DNA sequence.
+
+    For instance ``complement("ATGCCG")`` returns ``"TACGGC"``.
+
+    Uses BioPython for speed.
+    """
+    return str(Seq(dna_sequence).complement())
+
+
+def reverse_complement(sequence):
+    """Return the reverse-complement of the DNA sequence.
+
+    For instance ``complement("ATGCCG")`` returns ``"GCCGTA"``.
+
+    Uses BioPython for speed.
+    """
+    return complement(sequence)[::-1]
+
+
+aa_short_to_long_form_dict = {
+    _aa1: _aa3[0] + _aa3[1:].lower()
+    for (_aa1, _aa3) in zip(aa1 + "*", aa3 + ["*"])
+}
+
+
+def translate(dna_sequence, long_form=False):
+    """Translate the DNA sequence into an amino-acids sequence MLKYQT...
+
+    If long_form is true, a list of 3-letter amino acid representations
+    is returned instead (['Ala', 'Ser', ...]).
+    """
+    result = str(Seq(dna_sequence).translate())
+    if long_form:
+        result = [aa_short_to_long_form_dict[aa] for aa in result]
+    return result
+
+
+def extract_graphical_translation(sequence, location, long_form=False):
+    """Return a string of the "graphical" translation of a sequence's subsegment.
+
+    Here "graphical" means that the amino acid sequence is always given
+    left-to-right, as it will appear under the sequence in the plot. This matters
+    when the location is on the -1 strand. In this case, the amino-acids are
+    determined by (part of) the reverse-complement of the sequence, however
+    the sequence returned will be the mirror of the translated sequence, as
+    this is the left-to-right order in which the codons corresponding to the
+    amino-acids appear in the sequence.
+
+    Parameters
+    ----------
+    sequence
+      An "ATGC" string.
+
+    location
+      Either (start, end) or (start, end, strand), with strand in (0, 1, -1).
+
+    long_form
+      if True, a list of 3-letter amino acid representations is returned instead
+      (['Ala', 'Ser', ...]).
+
+    """
+    if len(location) == 3:
+        start, end, strand = location
+    else:
+        start, end = location
+        strand = 1
+    subsequence = sequence[start:end]
+    if strand == -1:
+        subsequence = reverse_complement(subsequence)
+    translation = translate(subsequence, long_form=long_form)
+    if strand == -1:
+        translation = translation[::-1]
+    return translation
+
+
+def load_record(path):
+    """Load a Genbank file """
+    if isinstance(path, str):
+        # Input is a file path
+        if path.lower().endswith('.gff'):
+            return list(GFF.parse(path))[0]
+        else:
+            return SeqIO.read(path, "genbank")
+    else:
+        # Input is a file-like object
+        try:
+            return SeqIO.read(path, "genbank")
+        except:
+            path.seek(0)
+            return list(GFF.parse(path))[0]
+
+
+def annotate_biopython_record(
+    seqrecord,
+    location="full",
+    feature_type="misc_feature",
+    margin=0,
+    **qualifiers
+):
+    """Add a feature to a Biopython SeqRecord.
+
+    Parameters
+    ----------
+
+    seqrecord
+      The biopython seqrecord to be annotated.
+
+    location
+      Either (start, end) or (start, end, strand). (strand defaults to +1)
+
+    feature_type
+      The type associated with the feature
+
+    margin
+      Number of extra bases added on each side of the given location.
+
+    qualifiers
+      Dictionnary that will be the Biopython feature's `qualifiers` attribute.
+    """
+    if location == "full":
+        location = (margin, len(seqrecord) - margin)
+
+    strand = location[2] if len(location) == 3 else 1
+    seqrecord.features.append(
+        SeqFeature(
+            FeatureLocation(location[0], location[1], strand),
+            qualifiers=qualifiers,
+            type=feature_type,
+        )
+    )
+
+
+def find_narrowest_text_wrap(text, max_line_length):
+    """Wrap the text into a multi-line text minimizing the longest line length.
+
+    This is done by first wrapping the text using max_line_length, then
+    attempt new wraps by iteratively decreasing the line_length, as long as the
+    number of lines stays the same as with max_line_length.
+    """
+    narrowest_wrap = textwrap.wrap(text, max_line_length)
+    narrowest_width = max([len(l) for l in narrowest_wrap])
+    for line_length in range(max_line_length - 1, 0, -1):
+        wrap = textwrap.wrap(text, line_length)
+        if len(wrap) <= len(narrowest_wrap):
+            width = max([len(l) for l in wrap])
+            if width < narrowest_width:
+                narrowest_wrap = wrap
+                narrowest_width = width
+        else:
+            break
+    return "\n".join(narrowest_wrap)
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/dna_features_viewer/compute_features_levels.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/dna_features_viewer/compute_features_levels.py Fri Jun 17 12:59:24 2022 +0000
[
@@ -0,0 +1,75 @@
+"""Implements the method used for deciding which feature goes to which level
+when plotting."""
+
+import itertools
+import math
+
+
+class Graph:
+    """Minimal implementation of non-directional graphs.
+
+    Parameters
+    ----------
+
+    nodes
+      A list of objects. They must be hashable
+    edges
+      A list of the form [(n1,n2), (n3,n4)...] where (n1, n2) represents
+      an edge between nodes n1 and n2
+    """
+
+    def __init__(self, nodes, edges):
+        self.nodes = nodes
+        self.neighbors = {n: [] for n in nodes}
+        for n1, n2 in edges:
+            self.neighbors[n1].append(n2)
+            self.neighbors[n2].append(n1)
+
+
+def compute_features_levels(features):
+    """Compute the vertical levels on which the features should be displayed
+    in order to avoid collisions.
+
+    `features` must be a list of `dna_features_viewer.GraphicFeature`.
+
+    The method used is basically a graph coloring:
+    - The nodes of the graph are features and they will be colored with a level
+    - Two nodes are neighbors if and only if their features's locations overlap
+    - Levels are attributed to nodes iteratively starting with the nodes
+      corresponding to the largest features.
+    - A node receives the lowest level (starting at 0) that is not already
+      the level of one of its neighbors.
+    """
+    edges = [
+        (f1, f2)
+        for f1, f2 in itertools.combinations(features, 2)
+        if f1.overlaps_with(f2)
+    ]
+    graph = Graph(features, edges)
+    levels = {
+        n: n.data.get("fixed_level", None)
+        for n in graph.nodes
+    }
+
+    def collision(node, level):
+        """Return whether the node placed at base_level collides with its
+        neighbors in the graph."""
+        line_factor = 0.5
+        nlines = node.data.get("nlines", 1) 
+        for neighbor in graph.neighbors[node]:
+            neighbor_level = levels[neighbor]
+            if neighbor_level is None:
+                continue
+            neighbor_lines = neighbor.data.get("nlines", 1)
+            min_distance = line_factor * (nlines + neighbor_lines)
+            if abs(level - neighbor_level) < min_distance:
+                return True
+        return False
+
+    for node in sorted(graph.nodes, key=lambda f: -f.length):
+        if levels[node] is None:
+            level = 0
+            while collision(node, level):
+                level += 0.5
+            levels[node] = level
+    return levels
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/dna_features_viewer/version.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/dna_features_viewer/version.py Fri Jun 17 12:59:24 2022 +0000
b
@@ -0,0 +1,1 @@
+__version__ = "3.0.1"
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/linear_genome_plot.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/linear_genome_plot.py Fri Jun 17 12:59:24 2022 +0000
[
b'@@ -0,0 +1,281 @@\n+#!/usr/bin/env python\n+from Bio import SeqIO\n+from dna_features_viewer import BiopythonTranslator, GraphicRecord\n+from matplotlib import rc_context\n+import matplotlib\n+import matplotlib.pyplot as plt\n+from itertools import cycle\n+import re\n+import sys\n+import argparse\n+\n+class CPTTranslator(BiopythonTranslator):\n+    """\n+    This is a customized translator from the dna_features_viewer module to fit Galaxy\n+    """\n+\n+    global custom_feature_colors\n+    global box_status\n+    global label_fields\n+    global custom_name_colors\n+    global ignored_features_types\n+    global ignored_gene_labels\n+    global ignored_feature_labels\n+\n+    def compute_feature_color(self, feature):\n+        if feature.type == "CDS":\n+            if "product" in feature.qualifiers:\n+                color_specific = any(re.search(("(\\\\b"+str(item)+"\\\\b)"),feature.qualifiers["product"][0]) for item in custom_name_colors.keys()) or any(re.search((item),feature.qualifiers["product"][0]) for item in custom_name_colors.keys())\n+                if color_specific:\n+                    try:\n+                        return custom_name_colors[feature.qualifiers["product"][0]]\n+                    except KeyError:\n+                        for item in custom_name_colors.keys():\n+                            if item in feature.qualifiers["product"][0]:\n+                                custom_name_colors[feature.qualifiers["product"][0]] = custom_name_colors[item]\n+                                return custom_name_colors[feature.qualifiers["product"][0]]\n+                                #print(feature.qualifiers["product"][0])\n+                else:\n+                    try:\n+                        return custom_feature_colors[feature.type]\n+                    except KeyError:\n+                        return BiopythonTranslator.compute_feature_color(self, feature)\n+        else:\n+            if feature.type not in ignored_features_types:\n+                try:\n+                    return custom_feature_colors[feature.type]\n+                except KeyError:\n+                    return BiopythonTranslator.compute_feature_color(self, feature)\n+\n+    def compute_feature_label(self, feature): # remove the chop_blocks\n+        self.label_fields = label_fields\n+        if feature.type == "CDS":\n+            if "product" in feature.qualifiers:\n+                if ignored_gene_labels: #  product name drop\n+                    verify_chops = any(re.search(("(\\\\b"+str(item)+"\\\\b)"),feature.qualifiers["product"][0]) for item in ignored_gene_labels) or any(re.search((item), feature.qualifiers["product"][0]) for item in ignored_gene_labels)\n+                    if verify_chops:\n+                        return None\n+                    else:\n+                        return BiopythonTranslator.compute_feature_label(self, feature)\n+                else:\n+                    return BiopythonTranslator.compute_feature_label(self, feature)\n+        elif feature.type in ignored_feature_labels:\n+            return None\n+        else:\n+            return BiopythonTranslator.compute_feature_label(self, feature)\n+\n+    def compute_filtered_features(self, features):\n+        return [\n+            feature for feature in features if feature.type not in ignored_features_types\n+        ]\n+\n+    def compute_feature_legend_text(self, feature):\n+        return feature.type\n+\n+    def compute_feature_box_color(self, feature):\n+        if feature.type == "CDS":\n+            return "white"\n+\n+    def compute_feature_label_link_color(self, feature):\n+        return "black"\n+\n+    def compute_feature_box_linewidth(self, feature):\n+        if box_status:\n+            return 0.5\n+        else:\n+            return 0\n+\n+def parse_gbk(file):\n+    """ simple function to parse out the feature information AND products """\n+\n+    record = SeqIO.read(file,"genbank")\n+    count = 0\n+    feature_types = {}\n+    product_names = []\n+    for feat in record.features:\n+        if feat.type not in f'..b'gene_ids,gene_ids_colors))\n+    else:\n+        custom_name_colors = {}\n+\n+    ##  Ignored Features\n+    #ignored_features_types = str.split(args.features_excluded,",")\n+    if args.common_features_excluded:\n+        ignored_features_types = str.split(args.common_features_excluded, ",")\n+        if args.features_excluded:\n+            ignored_features_types += str.split(args.features_excluded,",")\n+    elif args.features_excluded:\n+        ignored_features_types = str.split(args.features_excluded,",")\n+    else:\n+        ignored_features_types = False\n+\n+    print(ignored_features_types)\n+    \n+    ## product labels\n+    if args.common_ignore_product_labels:\n+        ignored_gene_labels = str.split(args.common_ignore_product_labels,",")\n+        if args.ignore_labeling:\n+            ignored_gene_labels += str.split(args.ignore_labeling,",")\n+    elif args.ignore_labeling:\n+        ignored_gene_labels = str.split(args.ignore_labeling,",")\n+    else:\n+        ignored_gene_labels = False\n+    \n+    print(ignored_gene_labels)\n+\n+    if args.feature_label_order != [\'\']:\n+        label_fields = str.split(args.feature_label_order,",")\n+\n+    #if ignored_gene_labels == [\'\']:\n+    #    ignored_gene_labels = False\n+\n+    ##  Ignored Labeling\n+    if args.common_ignore_feature_labels:\n+        ignored_feature_labels = str.split(args.common_ignore_feature_labels,",")\n+        if args.ignored_feature_labels:\n+            ignored_feature_labels += str.split(args.ignored_feature_labels,",")\n+    elif args.ignored_feature_labels:\n+        ignored_feature_labels = str.split(args.ignored_feature_labels,",")\n+    else:\n+        ignored_feature_labels = False\n+    \n+    print(ignored_feature_labels)\n+    ##  Print Statements for Debugging\n+    #print(custom_feature_colors)\n+    #print(custom_name_colors)\n+    #print(ignored_features_types)\n+    #print(ignored_gene_labels)\n+    #print(label_fields)\n+\n+    ## Part III ; PLOT\n+    # Housekeeping\n+    rc_context({"font.family": ["monospace"],}) # courier-like\n+    matplotlib.use(\'Agg\') # I think this has to be used...\n+\n+    if args.label_algo:\n+        lab_algo = True\n+    else:\n+        lab_algo = False\n+\n+    translator = CPTTranslator()\n+    graphic_record = translator.translate_record(genome)\n+\n+    with open("tmp.svg", "wb") as img:\n+        img.truncate(0)\n+        img.close()\n+\n+    if args.sz and not args.multiline: #  if user is wanting to look at a subset region of the genome\n+        zoom_start, zoom_end = args.sz, args.ez\n+        cropped = graphic_record.crop((zoom_start,zoom_end))\n+        ax, _ = cropped.plot(figure_width=args.plot_width, annotate_inline=lab_algo,figure_height=None)\n+        if args.translation_on:\n+            crop_seq = (args.st - 1, args.et)\n+            cropped.plot_translation(ax, location=crop_seq, fontdict={\'size\':8, \'weight\':\'bold\'},y_offset=1)\n+        ax.set_title(args.title)\n+        # Galaxy specific shenanigans\n+        tmp_fig = "./tmp.svg"\n+        plt.savefig(tmp_fig)\n+        plt.close()\n+    elif args.multiline:\n+        if args.sz:\n+            zoom_start, zoom_end = args.sz, args.ez\n+        else:\n+            zoom_start, zoom_end = 1, graphic_record.sequence_length\n+        cropped = graphic_record.crop((zoom_start,zoom_end))\n+        ax, _ = cropped.plot_on_multiple_lines(\n+            figure_width=args.plot_width,\n+            annotate_inline=lab_algo,\n+            figure_height=None,\n+            nucl_per_line=args.nucl_per_line,\n+            plot_sequence=False\n+        )\n+        #ax.set_title(args.title)\n+        tmp_fig = "./tmp.svg"\n+        plt.savefig(tmp_fig)\n+        plt.close()\n+    else:\n+        ax, _ = graphic_record.plot(figure_width=args.plot_width, annotate_inline=lab_algo)\n+        ax.set_title(args.title)\n+        tmp_fig = "./tmp.svg"\n+        # Galaxy specific shenanigans\n+        plt.savefig(tmp_fig)\n+        plt.close()\n+    with open("tmp.svg", "rb") as img:\n+        for line in img:\n+            args.out_img.write(line)\n'
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/linear_genome_plot.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/linear_genome_plot.xml Fri Jun 17 12:59:24 2022 +0000
[
b'@@ -0,0 +1,289 @@\n+<tool id="edu.tamu.cpt.genome_viz.linear_genome_plot" name="Linear Genome Plot" version="1.0">\n+    <description>Linear Genome Plot</description>\n+    <macros>\n+        <import>macros.xml</import>\n+    </macros>\n+    <expand macro="requirements">\n+    </expand>\n+    <command detect_errors="aggressive"><![CDATA[\n+python $__tool_directory__/linear_genome_plot.py\n+$input_file\n+--plot_width $plot_width\n+--common_features_excluded  "$common_features_excluded"\n+--features_excluded "$features_excluded"\n+--common_ignore_feature_labels "$common_ignore_feature_labels"\n+--ignored_feature_labels "$ignored_feature_labels"\n+--common_ignore_product_labels "$common_ignore_product_labels"\n+--ignore_labeling "$ignore_labeling"\n+--feature_label_order "$feature_label_order"\n+--title "$title"\n+$label_algo\n+$label_box\n+#if $selectregion.custom_region:\n+    --sz $selectregion.start_zoom\n+    --ez $selectregion.end_zoom\n+#end if\n+#if $multiline.multi_line:\n+    --multiline\n+    --nucl_per_line $multiline.nucl_per_line\n+#end if\n+#for $feature_color in $feature_colors:\n+    #if $feature_color.feature_color_selector.feature_color_options == "add_colors":\n+        --feature_id #for $feat_id in $feature_color.feature_color_selector.feature_id:\n+            "${feat_id}" #end for\n+        --feature_id_color #for $feat_col in $feature_color.feature_color_selector.feature_id_color:\n+            "${feat_col}" #end for\n+    #end if\n+#end for\n+#for $gene_color in $gene_colors:\n+    #if $gene_color.gene_color_selector.gene_color_options == "add_colors":\n+        --gene_id #for $gene_id in $gene_color.gene_color_selector.gene_id:\n+            "${gene_id}" #end for\n+        --gene_id_color #for $gene_col in $gene_color.gene_color_selector.gene_id_color:\n+            "${gene_col}" #end for\n+    #end if\n+#end for\n+--file_stats $file_stats\n+--out_img $out_img\n+    ]]></command>\n+    <inputs>\n+        <param label="Annotated Genome File (Gbk)" name="input_file" type="data" format="genbank" />\n+        <param label="Plot Width" name="plot_width" type="integer" value="10" help="Width of the plot. Increase for larger genomes." />\n+        <param label="Box Label" name="label_box" type="boolean" checked="true" help="Select \'no\' to have no label box around feature labels" truevalue="--label_box" falsevalue="" />\n+        <conditional name="selectregion">\n+            <param label="Select Custom Region" name="custom_region" type="boolean" checked="false" help="Plot a specific region of the genome" />\n+            <when value="true">\n+                <param name="start_zoom" label="Start Zoom" type="integer" help="start zoom" optional="true"/>\n+                <param name="end_zoom" label="End Zoom" type="integer" help="end zoom" optional="true"/>\n+            </when>\n+            <when value="false">\n+            </when>\n+        </conditional>\n+        <conditional name="multiline">\n+            <param label="Multi Line Plot" name="multi_line" type="boolean" checked="false" help="Breaks up the plot into multiple lines"/>\n+            <when value="true">\n+                <param name="nucl_per_line" label="Nucleotides Per Line" type="integer" optional="true" />\n+            </when>\n+            <when value="false">\n+            </when>\n+        </conditional>\n+        <param argument="common_features_excluded" label="Common Feature(s) to EXCLUDE" type="select" multiple="true" help="Common Features to be excluded from the plot">\n+            <option value="source" selected="true">source</option>\n+            <option value="gene" selected="true">gene</option>\n+            <option value="CDS">CDS</option>\n+            <option value="RBS">RBS</option>\n+            <option value="misc_feature">misc_feature</option>\n+            <option value="misc_difference">misc_difference</option>\n+        </param>\n+        <param label="Extra Feature(s) to EXCLUDE" name="features_excluded" type="text" optional="true" help="Feature(s) to exclude from plot (example: variation'..b' features that are excluded. **"Extra Feature(s) to EXCLUDE"** is where you can pass one, or multiple, feature(s) to be excluded from the plot. Separate by commas with NO spaces.\n+\n+* Feature(s) label(s) to EXCLUDE :: The **"Common Feature(s) label(s) to EXCLUDE from labeling"** select menu has frequent labels from features that are excluded. Input specific features that you do not want to include in the **"Extra Feature(s) label(s) to EXCLUDE"**. Use this when you still want to plot the feature but not label it. A good sample case is if you are zoomed into a specific region and looking at overlapping genes but do not want to label all of the RBS sites. \n+\n+* Products(s) names label(s) to EXCLUDE :: The **"Common Product names to EXCLUDE from labeling"** select menu has frequent product names that are excluded. Input specific names that you do not want to include in the **"Extra Product(s) name label(s) to EXCLUDE"**. Any product with this name will NOT be labeled. It must be spelt, spaced, and capitalized exactly as it is within the file to be caught. However, if you wish to exclude labels with common patterns, say JX0101.orf01, you could pass "JX" to the input box and it will skip all names containing JX. Realize that passing something like protein will also eliminate labels such as DNA binding protein. Of note, if you choose to label by locus_tags, and want to skip over each, for example, hypothetical protein; the script will still pass their label and not label them.\n+\n+* Name ordering :: In case you want to customize the selection method of labeling genes by a specific feature, use this argument. "product" will use the product names from within the file. If you want to use product, and if there is no name, use locus_tag as the next name, pass the following: product,locus_tag.\n+\n+* Label algorithm dictates label placement :: DNA-features (the orginal python package) has a very nice spacing algorithm for deciding how to space and where to put the label. Selecting no will force ALL features to placed outside of their gene box. If more tuning is desired, a custom pixel control can be implemented as an argument (not currently implemented) to allow more user control for label placement within gene boxes.\n+\n+* Select Custom Region :: If you would like to zoom in and look at a specific region of select a start and end site to zoom in on. Values are based on Nucleotides.\n+\n+* Multiline :: Permits plotting of the genome across multiple lines. Select the amount of nucleotides per line you would like in the **"Nucleotides Per Line"** field. Smaller widths (which are recommended for multiline plots) and larger nucleotides per line will have faster compute times. This can also be combined with the custom region parameters.\n+\n+* Feature Colors :: Customizing Feature colors based on their exact name as listed in the file (check the output stats file for spelling).\n+\n+* Product Name Colors :: Customizing Product colors based on their exact name as listed in the file (check the output stats file for spelling). **NOW**, in addition to the exact name, general words can be used to grab similiarly named features. For example, if the genome has a two component spanin system (i-spanin + o-spanin), using "spanin" would change the color for both the i-spanin and o-spanin.\n+\n+**Output**\n+\n+* file_stats will output the different product names as well as the count of each respective feature\n+* svg output\n+\n+**Output Example**\n+\n+Using the zoom function and custom colors for various product names\n+\n+.. image:: $PATH_TO_IMAGES/sample.png\n+\n+    ]]></help>\n+    <citations>\n+        <citation type="doi">https://doi.org/10.1101/2020.01.09.900589</citation>\n+        <citation type="bibtex">\n+            @unpublished{galaxyTools,\n+            author = {C. Ross},\n+            title = {CPT Galaxy Tools},\n+            year = {2020-},\n+            note = {https://github.com/tamu-cpt/galaxy-tools/}\n+            }\n+        </citation>\n+    </citations>\n+</tool>\n'
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/macros.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/macros.xml Fri Jun 17 12:59:24 2022 +0000
[
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<macros>
+ <xml name="requirements">
+ <requirements>
+ <requirement type="package" version="3.8.13">python</requirement>
+ <requirement type="package" version="1.77">biopython</requirement>
+ <requirement type="package" version="1.2.2">cpt_gffparser</requirement>  
+            <requirement type="package" version="1.0.5">pandas</requirement>
+            <requirement type="package" version="3.3.2">matplotlib</requirement>
+            <!-- <requirement type="package" version="3.0.1">dna-features-viewer</requirement> -->
+ <yield/>
+ </requirements>
+        <version_command>
+ <![CDATA[
+ cd $__tool_directory__ && git rev-parse HEAD
+ ]]>
+ </version_command>
+ </xml>
+</macros>
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/test-data/Mu50-profile.xml.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/test-data/Mu50-profile.xml.xml Fri Jun 17 12:59:24 2022 +0000
b
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<BRIG blastOptions="" legendPosition="upper-right">
+  <cgview_settings arrowheadLength="medium" backboneColor="black" backboneRadius="600" backboneThickness="medium" backgroundColor="white" borderColor="black" featureSlotSpacing="medium" featureThickness="30" giveFeaturePositions="false" globalLabel="true" height="2500" isLinear="false" labelFont="SansSerif,plain,25" labelLineLength="medium" labelLineThickness="medium" labelPlacementQuality="best" labelsToKeep="1000" longTickColor="black" minimumFeatureLength="medium" moveInnerLabelsToOuter="true" origin="12" rulerFont="SansSerif,plain,35" rulerFontColor="black" rulerPadding="40" rulerUnits="bases" shortTickColor="black" shortTickThickness="medium" showBorder="true" showShading="true" showWarning="false" tickDensity="0.2333" tickThickness="medium" titleFont="SansSerif,plain,45" titleFontColor="black" useColoredLabelBackgrounds="false" useInnerLabels="true" warningFont="Default,plain,35" warningFontColor="black" width="2500" zeroTickColor="black" tickLength="medium" />
+  <brig_settings Ring1="102,0,102" Ring2="0,102,102" Ring3="0,102,0" Ring4="0,0,153" Ring5="102,102,0" Ring6="0,153,0" Ring7="204,51,0" Ring8="0,102,102" Ring9="0,153,102" Ring10="204,0,51" defaultUpper="70" defaultLower="50" defaultMinimum="50" genbankFiles="gbk" fastaFiles="fna,faa,fas,fasta,fa" emblFiles="embl" blastLocation="" divider="3" multiplier="3" memory="1500" defaultSpacer="0" />
+  <special value="GC Content" />
+  <special value="GC Skew" />
+</BRIG>
+
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/test-data/Mu50.sam
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/test-data/Mu50.sam Fri Jun 17 12:59:24 2022 +0000
b
b'@@ -0,0 +1,50001 @@\n+@SQ\tSN:S.aureusMu50-plasmid-AP003367.gbk\tLN:25107\n+r1.1\t4\t*\t0\t0\t*\t*\t0\t0\tGTTACCTCTTGCTCTAGTCTGATTCTAAACCTTTTTTTAGTTCTTTCGCTTAATAAGTTACCTCTAATTCAGCAAAACGCTACTCGATCATTGTAAAAAACATCTTTCCCATTGGGTCTTTTGTATTGATATTCATATCAATAATTTGTAATTCAATACTTATTATTTTCTAACCATTGGGCTAAATCTATCAGTTGCTTAGTAGTCCTGCCTAATCTATCTAGCTTGAAATAACTAAAGTGTCGCCTTCACGTAAATAATCTAAACTATTTATCTAGTTC\t*\n+r2.1\t4\t*\t0\t0\t*\t*\t0\t0\tTAAAAAAGAAAGTCTTTCCGTTTTTCTTAATAGTTATTAGGTTATTTCCGTATTGATCTGTTATAGAGCCATACACGGTATTATTGTCTTTATTTTTTATTCAATATTACTTTTAATTTTTTGTATTTGCTCATTGTATTGTTCTTGATTTTCTTTACTTTTAACTAAATAACACAAAGTTTTTAGCATCATTTTCAGATAAAGTTTCTTCTTTATTAGTATAACTTTTTAGCAATACTATTGTAGTCACTTATGTCTGTACTAATTTTTCAAATTCTTTAACGTTGTCCTTTAGCATTATTCAAACTGATTAAATAG\t*\n+r3.1\t4\t*\t0\t0\t*\t*\t0\t0\tGGTACGAAGATATATTAAATTGATTTTTGAATTTTTGAAAAAAGAAAGAAATAGCTAATGCAGTTACTTTTAGACCCTCATAAAAATAATCCAAGAGACAATAAGGGCATACCAAAAAATCTGGGTTTTAGAATTATTGAAGTATTGCCAGAACATGAATTACACGAGGGCAAAAAAGAAGATTGTTATTTAATGGAATATAGATATGATGATAATGCCACAAATGTTAAGGCAATGAAAATATTTAACTTGAGCATTACTTTGATAATTTCAAAGTAGATAG\t*\n+r4.1\t4\t*\t0\t0\t*\t*\t0\t0\tTAATCATCTAAATTAGTTAAGTTATAATCAAATTCAGAATCATACTAGTACTTATAATATGTGGTGTTTGATATTTTTTCGTTAGAATCGTCTAAATCATAAACTGGTTGAGTATACACTTCGTTATAGAAATTATTTTCTACTAGACGTAAACTTACCTACTATCGCTTTTTATTAGTACATATCTCTTTGATCATTCATTTGTTTATCACTTGCTGGCACAAAATAAAATTCAGAATTTAGTTCATAATCGGTGTTGTTCAAAAATTTCCTCGGGGTGTAA\t*\n+r5.1\t4\t*\t0\t0\t*\t*\t0\t0\tTATAAATGTTTTCTTTAAACCAAAGCTAATTTACCACATTGGGTTAAGTTTCTTATTATTAAAGAAGAACTTTGTATGATTTCAACTGCTTACTATCTTCTCATTTGGTTATTTCTCTCTTGTTTCTTCTTCTTTTTCTAGATGATTAATATTGTTTTGCTTTTCAGTTTCGTTCGCATAGTACATAAGAAAGTCACTAGCATTTATCGTTGGTAAATTAATGTGATTAGTTTGTTCATTTTCATGTTCAATACGATTGTCATGTATCATTTCTATCTACG\t*\n+r6.1\t4\t*\t0\t0\t*\t*\t0\t0\tAGAATAATTGTATAACACAATACAGCCATTTAAATTTCGCAAGATTTTTTGTTGTAATATGTAAAAAAAATAGATTATAATCCTTATAGACCGATCGCACGGTCTATAAGGATTGGAGGGGAACTTAAATGATTTCATTTTTTACAAAAACTACTGATATGATGACATCAAAAAAAAGATGGACTGCACTAGTAGTATTAGCTGTTAGTTTGTTTGTTGTTACAATAGGATATGACAATATTAATTATGGCTTTACCGGAATTAGTAAGAGAGTTAGAGCCTTC\t*\n+r7.1\t4\t*\t0\t0\t*\t*\t0\t0\tAAATGAGAGAATAATTTTCTAAATTCATTACGCTTTTGGGTATCGAAAAATCACTAAGATGTATATCGAGTAATTTTTTCAAAAAAAACATCAATTTTGCTCTTGTGCTCTTTGAGCCAAACGTCGCAAACTTTTCCAACTTCTCTATTAGATATTACTTTAATAAGGTTTGGGTCTATATATTGAAGAGTGTCTTCTTCAATAGAAATATTTAAATAATCAGTTGATTAGCATCATGATTGAATTTGGTACATACTAGTGTCACTCATA\t*\n+r8.1\t4\t*\t0\t0\t*\t*\t0\t0\tTCATGTAACTTAAAAAATAGATGAAAGTTGCTACTTAGTGCTCCTAAAAATATAGTTATAGTTAAGTTCTACATCAAATATTTTAAAAATATCTGCTCTATTCATCAGTTAATCACTCCTTTCAAGGTTTATTAATACTAATAAATTATTAGATATAGGTATATCATATTATTAATTTAAGAAAATTGTCTTTATAATTTTACTTAATAATAAAAAAGTAGAACCATTTAAATTAATGGTTCTACTTTTTTAACTAGTTACTAATTTTAAAAATAAACGTAATCTACAATATCTAAAAATATATGTTTAGTAC\t*\n+r9.1\t4\t*\t0\t0\t*\t*\t0\t0\tTATTGGTTATGCTCGTGTATCTACCAGAGATCAAAGTTTAGATTTACAAATAGACGCTTTTGAGTAATTTTCGGTTGTGAGAAAATATTTAGCGAAAAACGTTAGTGGGCGTAAAGTAAATAGAACTGAACTAGATAAATGTTTAGATTATTTACTGAAGGCGACACTTTAGTTATTTTACAAGCGTAGATAGATTAGGCAGGACTACTAAGCAACTGATAGATTTAGCCCAATGGTTAGAAATAATAATTATTGAATTACAAATTATTGATATGAATATCAA\t*\n+r10.1\t4\t*\t0\t0\t*\t*\t0\t0\tAAACTCGATTGATTCATGATTATATCGATCAACCAAAATATTCAAAAGCTTGCGCATCATTGGATGATGGATTCGAAGCGCCTTTCAATATACCGTACAAGGAAATTCCCACAATCGACTAAAGAGTACCAATACTAATTGAACGACTGAATCAAGAAGTACGCAGAAGAGAAAAGATTATTCGCATCTTCCCCAATCAAACATAGCCAAGTCGCTTAATTGGTAGCCGTTCTTATGGACCTACATGATAGAA\t*\n+r11.1\t4\t*\t0\t0\t*\t*\t0\t0\tTACTTTGAAATTATCTAAAGTAATGCTCAATTAAATATTTTCATTGCCTTAACGATTTGTGGTGATTATCATCATATCTATATTCCTACGTTAAATAACAATCTTCTTTTTTGCCCTCGTGTAATTCATGTTCTGGCAAATACTTCAATTAATTCTAAAACCAGATTTTTGGTATGCCTTATTGCTCTTGGATTATTTTTATGAGGGTCTAAAATAACTGCATTAGCATTTCTTTTCTTTTTTCAAAAATTCAAAAATCAATTTAATATATCTTGTACCAATTCCTTTAC\t*\n+r12.1\t4\t*\t0\t0\t*\t*\t0\t0\tCAGATATTATTCCATGGGTTGTAATAGTATTAGCTAATTTACCATATAGTGATATTTGTTAAACGTAAGTTTATCGAAGTTCTGATCCAATGTTAGACGTAAGACTTTTTAAAAAGAGATCATTTTCAGCTGGTACAATTGCTGCATTTAATGACAATGTTTGCAATGGCATCTGTTTTGTTATTAGCTTCACAATCGGTTACAGGTTGTGGAAGAACTTTCTCCTTTTAAAGCTGGCTTATACCTATTACCTATGGCAATAGGAGA\t*\n+r13.1\t4\t*\t0\t0\t*\t*\t0\t0\tAGCTTCTAATTCACAACTTTTTCTTTATAAATTGCACTATTTTTTGGCTTGTGGATTTACTTTTGAGCCTTTTGGTATTTCTGAACATAAACATTTTTAATACCTTTTAAATCATTTCTTGTAGATATTAACTGATACCAAACTCGTGCATATTCAATTTCTTTCGAGTTTAAAAATTATTTAAGTAACTTTTATT'..b'GAATTTTGAACAACACCGATTATGAACTAAATTCTGAATTTTATTATGTGCCAGCAAGTGATAAACTAAATGAATGATCAAAGAGATATGACTAATAAAAGACATAGTAGGTAATTTAGTCTATAGAAATAATTTCTATAACGAAGTGTATACTCGAACCAGTTTAGTGATTTAGACGATTCTAACG\t*\n+r49989.1\t4\t*\t0\t0\t*\t*\t0\t0\tAATGAATGTAAGGCCTCAACTTCTATTAACTACGCCATATCTCTGATAAATGTTTTCGTAAATACTTATTTCTGATCGCCCAACTAACCTAAACTGAATAAATGCTGTAATATCAGTGTTGTATACCACTATAAGAAGGGCTATCATTCTCTGGAAATTGTTGTATATGAATATAAAATTCATTTTTAGGGGAATATGTTTATCATTTTATTAGAGAAGTTACGACTAAACACATCTGTTTTATTAGTTAAAAGCCATACCAATAAAATGATTTCTAG\t*\n+r49990.1\t4\t*\t0\t0\t*\t*\t0\t0\tATGATGAGTGCATTCGTGAACTTGAAGCTAATTTATTAAGTGAACGAACTAAAAAAGGTTTAGGAAAGCTGCAAGAGCAAGAGGGAGAAAAGGTGGAAGACCTTCACTACCAGATCATAAGAAAAGAGAGATCAAATTCGTTATATGATGAACAAAAGCTGTCTGGTGAAGAAATTGCTGAACAAACAGGAGTGAGTCGTTACTACTGTATATTAGGATTATTAAAGAGTCTAAGAAAAATATAAAGTACTAAATTAAAGTTTTAATATACCCTTTAATTG\t*\n+r49991.1\t4\t*\t0\t0\t*\t*\t0\t0\tAAGAGGCGTAATATACGCCTCCTTAAAACAATATAATGTGTTTTGTATCTCTATAGTTGTTTCTTTCCGGCAGTATTAATTTTTATATCTGCGCCGACAACGCCGATTCTAGTTCGTGCAGTATTAATTTTTATATCTGCGCCACAACGCCGATTCTAGTTCGTGTAGTATTAATTTTTATATCGTGACGCCACAACGCCGATTCTTTTCTTCTTATATTATATCAATACCTGTCATGTTATGCAATGTTTAGGATTACTTTTTAAACTCTCGGAAAA\t*\n+r49992.1\t4\t*\t0\t0\t*\t*\t0\t0\tCGATAATTGGTCCAAAAACAGCACCTATCATGAAGCGATTGACATACTAGCGTAATGCAGTGGCCCTTTTTTAGGTTTTCAAAAATTACTCTTATCATTGAAAAGAGTAGTTGGCGATTATTAAAGCACCTGAATACCTAAGTAAAAATCGAATAGCTATTACGAACTCGTGCACTTTCTGCGAAAAAATATAGCTAATGAAACGAGGCCAAATAAAGCAAATCCAGTTAAAAATGCTTTTTTTCTTCCCCATTTTATCAGCAAAGGCACTCAA\t*\n+r49993.1\t4\t*\t0\t0\t*\t*\t0\t0\tATGTCCAATTTTTGTTTCCAAATTATTTTTCTCCTTAACTTTAAGACTATATTTAAACTCAATTTTTTCTATCGATATTTATGAACATTAGTATTTTTTTATGCGAAATACCTGTGAACAATCGCATAATTCAAAGGTTTTTCTATAATTATACACGTTTTAAAATACATTGTGATAATACATAGAATGTACCTTATCTAAGTATATAAATGTTTTCTTTAAACCAAAGCTAATTACCACATTGGGTTAAGTTTCTTATTAATTAAAGAATGAACTTTGTATGATTCAACTGCGTTACTA\t*\n+r49994.1\t4\t*\t0\t0\t*\t*\t0\t0\tTTTCACTTTTTTTGAATTAATCGTACGCTTTAAAACGCTTAAGAACATTAACAATTTCAAATTCCATATCTTCTAATAAATAATATGCATCAATAGAATTGTTGAAGGATGTTTTGGCTTTTAATAACACAGATTAATAAGTTAATTTCAGAGTACAGAAAAGTTACTTAAATAACTTGAATACTTTGTGGTAGTTCTTCAAGTAATTGAAATTTTAAAGCTTCATCATTGAATTTGAATAGTAGTGTATTTGAATGATTTGTATGATTACTAGAATATGTTAATTTCTA\t*\n+r49995.1\t4\t*\t0\t0\t*\t*\t0\t0\tCATTCATTTCTCTTTTGAATATTATTTCTATCTATAATCTAACTCGTCTTCGGTATAAAAACATTGTAACTTGGCATAACATTGTTTAACTGGTTCTGTTGCAAAGTTGAATTTATAGTATAATTTTAACAAAAAGGAGTCTTCTGTATGAACTATTTCAGATAATAAACAATTTAACAAGGATGTTATCACTGTAGCGGTGGCTACTATCTAAGATATGCATTGAGTTATCGTGATATATCTGAAATATTAAGGGAACGTGGTGTCAACG\t*\n+r49996.1\t4\t*\t0\t0\t*\t*\t0\t0\tTTATATCTTCTAATTTGTTCATTTTTCTAGTCCGTAATAAGTAACGGGTTGGTACTCGACTACCCTCTTTTTCACAGCGTTTTCTTTCATTTTAGTTTCTAATCTATATTCTTCTCTTGTAAGTGGAATTTCGGCAATTTTTTCTAAACCTTCGCGCTTCAAAAGAACGACTATCATATCGTCTTAAACTTTGTTCGACGTTCTGAGTATTGATTTAATTTTAATTCCCAATGCTTACGAACTTACTTTGAAATCAAAGTCGGCAAATCGTTCTTGTTTTCTAACTTTATTAACCATTTTTATTTAAAATCGCATTGCC\t*\n+r49997.1\t4\t*\t0\t0\t*\t*\t0\t0\tAAATTAAGCATCATGCTAGCAGTTTAAGCGAACACTGACATGATAAATTAGTGGGTTGCTATACTTTTTTACTTTGCAACAGAACCAAACTAAAAGATAAAATAACATCTTGGCTTGATAAAGATAACAACTAAAAACGAATAATATAGGAGGGGTTTTTTGGGGAACTTTAGATTTTGATCACGAAGGATACAGAAAACTATTACCATTAAAAGATTTCAAACATTTTACTAAAACATAGCGACAGCAAGACTAGAAATCATTTTATTGGTAGTGGCTTTTAATAATAAAACAGA\t*\n+r49998.1\t4\t*\t0\t0\t*\t*\t0\t0\tCATTTATTCAGTTTAGGTTAGTTGGGCGTCAGAAAATAAGTATTTAGAAAACATTATCAGAGATTATGGCGTATTAATAGAAGTTGAGGACCTTAACATTCATTTAAAAAAGGTATGGGGGCAATGCTTGTAAGTCAATTGGAAAAGTTATCAGATAAACTGTTTATACCTATATATCTTTATGATACTAATTTAAAAGATGAATTATATTATCAAGACTTAGGATTCTTTGATACTACTAAAAAAGGGAATCATGGAGAACCACTTTTAGTATATAAACCTAAAAATCTAG\t*\n+r49999.1\t4\t*\t0\t0\t*\t*\t0\t0\tCATATTACGAACAAAAAAATCTTCGCGAAATTTAAATGGCTGTATTGTGTTATACAATTATTCTTTTGAATTTTTTGTGCTATCATTGATAGTACTTAATACTCATTAAAGGCGTGATGAACTTGAAAGATAAAAATACGTAGGTGTCGCAAAGGGAATTATTTATAAAAAATGGATATAATGCCACTACTACTGGAGAAATTGTTAAATTATCAGAAAGTAGTAAAGGGAATCTTTATTATCACTTTAAAACAAAAGAAAACTATTTTTTAGAAATTTTAAATATAGAAGAATCTAAATGG\t*\n+r50000.1\t4\t*\t0\t0\t*\t*\t0\t0\tTGCAAGGCTGAACTAACAATGGTGGCATTAGATAGTGATGAACTTGATTCATTACTTTATATGCTAGTTACATGGAACGTTATGCTATCTTTTGAACTAATGATAAAAAAAGACGCCTAATTTTAGGCGCCTTTTTAACTAATCTACTAACTAACTTATTATATCTAATTTTACTTGCTAACTTAGCTACTGTTTTATTGTCGTAGGGTTTAATATAGACTCGTTTCATATTCTCCTCTATTGCTTTTTTTGTTATCTTATTCATTTGACTATAATCGAC\t*\n'
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/test-data/mga.fa
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/test-data/mga.fa Fri Jun 17 12:59:24 2022 +0000
b
b'@@ -0,0 +1,2300 @@\n+>01\n+TACTACTATTACTACTACTAAGTACCTTTGTTATGTACTACTATTACTACTACTATTACT\n+ACTACTATTACTATTACTACTACTATTACTACTACTATTACTACTACTATTACTACTACT\n+ATTACTACTACTACTAAGTACCTGGGAATTCTTTTACCTCTCTCACTCAGCCTATTACTT\n+ATTACCGACTTCCCTAACTACTTATTCTATAGTTATAATATTCATTTATTATACAATACT\n+TAAACTATAGTATTCTACTGTTAATCTATGCTGAAGCGGTCTTAATCTATGGTTATTATA\n+TAATAATCTTATATAATGGTACATTAATCTAGTATATTACATTAGAATCATTCTAATCTA\n+GGATTTTAATCTTTAGACCCTAGGAAAAGTGGTACTAAAATATAAAACCCTATAGGTATG\n+GGATTCTTATTTTTAAAATTACTAAAAAGTATTAGGTTTTCCCTAGGGCAAAGTTTTAAT\n+GTACTTAAAATAGTAAGTAGCTACTTATCATTTAGGGTTCTATAATTGAGAATATTGAGA\n+GATAATCCGCTTCAATTGTAATTAATTGTTGACAACTATGAAGCGGGTATGCTATAATTA\n+GGTATAGTCAAATTTAGGAGATGAAATAGATGATTGATATATACTTAGGAGAAGGTTATA\n+ATAAAGAATACTTGTCTAAAGCACTCAGATTAATCAATGACCATGCTCCTAGGGAGTTAA\n+GTTATGATTTTAATAATGTAGAAGCGGATGTTAATATTCACACAATGTTATATGTTAAAC\n+CTGAAGATAGATTTATATATAAGGATATATCCTATTACTTCCCGGGTGATTTAATTATTT\n+GTATAGTTGATGATGATGCTATTGTATACCACCAAGGTGAGCAGATTTCAGGTATTAGTA\n+TTTTAAGAATACTAGAAGAGATATTTTAAGGAGGATAAGTAATCATGATAGGAATAACAA\n+TATTAATTACGATAATGAGTATATCAACTATCTCTATGTATATTTATTTTTTAGTAGACT\n+TGATTCAGTCAATCAGATATAATAGTTTTGATAAGGTAATTAACGTCATAACATTTGTAC\n+TTATGACAGTTATAATAGCATCAGGTATTTTAGCTATACTTGGAATATAGAGCTCATTTA\n+AGAAGCGGTTAAGTAGTTAGAGGGGATTTGTCCTAAAATAGTATACCGCTTCTATATGGA\n+AGGCTGAGAGGTCTTAGAATTGAAAGGAGAGATATAATGATTCATATATTTGTAAAAGAG\n+GATTATAATAAAGAAACATTAAGGAGTTTACTTGAGTATATTAATGATACTGTAGGTAGG\n+GAATTAACTTATGGTATTAATACAGACTATGATAAGGATGTCGTGATTGAAACCGATGAC\n+CCTATAGATGAGGAGGATACAATTGAGTTATCAGGTACAAACATGTTCAAGGATGACTTA\n+TGTATTCTTATAGAAGAGCTATACTGTAAGGCATTTGTTAATGGTGAACCTGTTATTATA\n+CGTAAGTATGTAGAGGAGATGTTATAATGATTATAATATTTTTAACTGAAAAATATGATG\n+CCAAGGCTTTAAAGAAAGTATTAGAACATATTGATAATTGTAGTAGTAGAGGTCTTAGCT\n+ATTTAATGGGAAAAGGAGAAGCGGATGTATGTATAGAGAAAAATGTATTTAGAGAAAGAG\n+ATGATGTAAGGATTAACTCAAACATTATTGATGAAGGTAAACTTTGTATACTAATAAATA\n+GACATGGTTTAGAATGTAGCTACTATAGAGGTATATCATGTAATATTGGTTCCTTCGTAA\n+AGGAGAGATTATAATGATAGAGATATACCTTAGTGAAAATTATGATAAGAATTTACTAAA\n+AGCAGAATTAAAATGGATTAAAGAGACCGCTTCAAGAGAACTAACTTATGATGTTAATAG\n+AAGTCCAGGATTGGATGTTTATGTTAATCCCTATAGGTGTACTAAAGACGAAGTTGAAGA\n+ATGGAGTACACTTCCTCCATTTGAAGATGATATACTTGTATTTATAGCGGAGACGTGGAT\n+ACATGAATATCTTAAGGGTGAATCAATAGGTGTAGATAGTATGGAAGAGTATGTAAAGGA\n+GATGTAACTAATGTTTAAGGTATATTATACAGTCTACCATAGAGGTAGTATGAAAACTAT\n+TAAGGATAAGCTAGATAGAAGTAGTTTAATATACTTCTTGTATGATACTTGGTATAAAGA\n+TATTAGTAACGTATTCCCTAATCACTATAATAAAGAGTTTGGGAGTAAGAGTGATGATAT\n+AGATATAGATAAACTTATTGAAGCGGTTAATGAGGAAGGTATATTACTTATCAATAGAGG\n+TAATTATGTTACAATAAGAGAATGGTAGGATAGGATAAACTTAGGATAGAAAATAATTTA\n+GGATGAGTTACAATAGGATAGGATAGGATAGGGGGTTAAGTTAGGATGGATACTTTAACA\n+TACACTATTATTCATAAAGAATCTGATAGGGTAATAGCTAGCGGTTTAAATGAGACAGAA\n+ACTATGAACTTAGTTCAAAGGATGATAAATACTAATCTAGTTACTGATATATCATTAGAT\n+GATTATAAACGCAGACCACATGGAAAGATAGATGTAGTCAATTTACTAGTAGATATTAGA\n+AGACAAGGCGTATTTGATTTCAATCACATTTGGCACGTAGGATAGGAGGGATAGGATGAT\n+AGTTATATATACAGATGTTTCTAAGGATTATTTAAAAGACGAGTTCTTACCTTGGCTTAA\n+TGAAAGGGATAGATACTTAGAATACTATAAAGATGAATTACCTGAGGATATAGATTCCTC\n+TTATATTGTATCAGTTGTATACTGTAAGGATATGGAAGGTCTATTAGAAAGAAAAGACAT\n+TGTTCTTGATAATAGTTATAATGAACCTGTAGCTTTATTAGGTGTTCCTGAGTTTTTTGG\n+TAATTATAGTAATTATTTCTATTATAGAGGAGAAAGTATTAGTAAACATGACCTAGGAGA\n+AATTGTTAGGTTAAAAGCTTGGCAACGTATGGGTGGGGATTGACTAAGTAGCTCTCCCTA\n+ATTTCACTAAGTAGCTCCCTAGGAATTGCCTAAGTAGCTCGGTATGATTTTACCCTAAGT\n+AGCTCCCTCTGTTTTCTACTAGTTTATTTTAACCGCTTCAGGTGTCTATATATATATAGA\n+CGGTTGGAATAATATCAGACCGCAAAAATAAATACACTAGGATATTATTCCTAGTGTATT\n+ATATAATTTTTTTATAGAATATTTATAACATTGTATTCAAATTCATTTACTTCATGTTGT\n+GATTTAATTAAATTTTTAATTAATCCGTTTTGTGTTTTATACTCTTTTATTAGTTTTTCA\n+TTTTCTATAATTAAATTATTAAATTCTTCTTTTGTTGTTTCCTCATCTACATAAAATTTA\n+CTTTCATATATTTCATAATATTTTTTATCTGTTCCGCCATCTAAATCATCTGATATTTGA\n+TAATTTTTGAATATAATTTCTTTTGTTTCTAATTCATTTACTAATAATTGTGATTTTGCA\n+TATTGTAATACATCTTCATTGTCCCACATTGGAATATAGTTTATTTTCATTTAAATCAAA\n+TCCTTTTCTTATAATTTTTTTATATAATATTTGTAGAAGCGGTTGGGGTTTGTCCCTTGC\n+CTTACTACACTTTATATATTACAGTATAGTTATTCAGAAGTCAATACTTTTGAGTAACTT\n+TTTTTAAATTCTTTTTTCTTCTATATAATAGTAGTTTTTAGCCCTAAAAATGTTTTTAAA\n+AGAATTTGCATTTTCTTATTGACTTTATTATCATATGGTAGTAATATAAAGGTACAGCAA\n+GGGAAC'..b'ACTTTTGGGGT\n+AATATGACTAAAACTTTACCTAGATTAAAGGATATTATTATGGAACGTAATGGTAAAGTA\n+GTAATCAGACCTGATAGTGGAGACCCTGTTAAAATTATTTGCGGAGACCCTGATGCAGAC\n+ACTGAATATGAACGTAAAGGTGCAGTAGAAGTGCTTTGGGATACATTTGGAGGTACTGAA\n+ACTGAAAAAGGGTACAAAGTATTAGATGAACATGTAGGATTAATTTATGGAGACTCTATT\n+AACTATGAACGTGCTCAACAAATTTGTGAAGGATTAAAAGAAAAAGGTTTTGCAAGTATT\n+AATGTTGTATTAGGTGTAGGTAGTTTCTCTTACCAATTTAATACTCGTGATACCCACGGG\n+TTTGCAATCAAAGCAACGTATGCTAAGATTAAAAATGAAGAAAAACTTATCTATAAAAAT\n+CCTAAAACAGATAGTGGTAAACGTTCACATAAAGGTCGAGTAGCTGTATATAAAGACGGT\n+TCATGGGAAGATAACTTAACCTTACATCAATGGCTAAACAAACAAAATGTTAATCAATTA\n+GAAAGAGTATTTGAAGATGGTAAACTTTATAGAGACCAGTCGTTAAGTGAAATTAGAGAA\n+ATAATTAAAAATAATTAATAAATATTTAAACTCCCTATTGACAAAGGGAGTTTTTTATTA\n+TATAGTAGGGCTATAGTAAATAAAGGAGTGAAAGAAATGATTTATAAAATATCAAAACAT\n+AATTACTATAGTAGATTTGAGCATTCCACTTATCCTCCTGATGAGGGGTTTGCGTATGTA\n+GATTATGTAGATGTGATTCTTATTGGTGTAGATAATCCTAGGAAAAGAAAGATTATTACC\n+TTAAAAGTAAATGAGTTCAACCCGGATGACTACAGAGTAGGTCATAAGTACAATATTATA\n+AAAATACTATGGTTTGAAAAATGGGAATGGTTAAAGCCATAAGTAAAAGGAGAGAAATAA\n+AATGATTATAGATAAATTAAATGGAGTTAAATTAGAAATAGGTGGGCATGTCGTATCATT\n+TAGTGTAAGAAAGTTTAATACAATTAATGGTGAGAGACAATTAATAGACTACCATCATAT\n+TAAAAGAAATAGACAACAGTACTTTAGAACTACTGAAGAATTTTATAATGAATATAAAGA\n+AATTAAGCCTGACAAAAATGAAATAGATGAAATGTTTGAATCTCTAGGTTATGTAGATAC\n+TGAGTTAGATGATGTAGTAAGAAACCAGGAAAAGGTTACTGAAATATTAGGAGTTAGTGA\n+ACAATATTTAAATCAGTTATCTTATAAAGCTATAGAGGAGTATGTAGATAAAGTAGTTAC\n+ACTTGAAATTAAAGAGTTGAAAGGAGAGAAATAGCATGAATAATAACTGGGAAAAAGAAG\n+GAGTTAACTATTGGGAAAACGAAGACTGTCCTAGGGAATACTTAGAGAAAGCATTCATTG\n+ACCTGGTAGAATATGTTGAAGGAGTTACAGTACCACCTAAAGATGTTAAGCAGTTAAGAG\n+AAGATAAACTTAGAGAAGATATTGGGTTTTATGAGTACGTAGCTGATAAATAAATTAGTA\n+TCTACCTATTGACTTAGGTAGGTATCTATTATATAATAGTATACAAGGAGATGAAAATAT\n+GAAAAAGTTAATAGTATTACTTACAATTACTATTTCTCTATTACTAGGGGGTTGCTCTCC\n+TGATAACCATGAAGGTAAAGTAGTAGGAGTAGGTGAATACAGAGAACCAACTACTTATAT\n+AAAATCAGGTAGCGTTACTGTACCAGTCATTGGTGAAATGAAATACTATGTAGATTTAGA\n+GACAGATAAAGGAGAAGACCGTGTATATCTTAATAAAGAGGTCTATCATAAGTTTGATAA\n+AGGTGATGATTTCTCTAATGTAGGTGAGAAAGTGTATAAGAATGATGAATTAATATATAA\n+AGGAGACTAACTATGTATTTAAATGATTATGTAGGTAAATTTATAAAGGAAGATAACTAT\n+TATGGATATCAATCTACAGACTTAGTATCTAATTATGTTCAACGATTAACTCTAGGTAGG\n+TACAAAACTAAGTTAAATGCTAATAAAATGAAATACGAAAGATTACCTAGTTCTTGGAAA\n+ATAATTAAAGCCAAAGATTTGTTAAGAACAGATGATTATAGAGAAGGAGATATATTTGTA\n+TCAGAAAGAATCTCCGTATTCGGTTTTAATGGTATTATTGTATATAACCATGATTTTAAC\n+AATGTAACTGTTATTACTCAAAATAGAGATGGTAAAGCTACTAATCCTGTAGAGGAGCAT\n+TTATATCCAAAGAAAGATATTGATTATATTATTAGACCTATCGAGAGGGACTACAGGGAA\n+TACTTTAAAAAATCAGATTCAAAAGAAAAAGTTACTCTTTCGAAGCAAGAATATAAAAAA\n+TTATTAGAGGCTTATAATAAAATGAAGGAAGTGTTTAAGTAATATGAATAGTACAAAATT\n+AGTAGAGTACTTTACAAATAAACAAGGTAAATCTCTAATATTACCTGATGAAAATAAAGT\n+TGAGTTATATAGAGTTGATGTAACACCTTATACTATGAGACTTAATTTCACTTACAATAC\n+AGAAGTTGTAGCTATAGATATTGATAAGTTACACTCAGATTCTATAGAAATGCATATACC\n+ACAAGGTCTTTATATAACAACTGTTGTTAAAATTACTAGTACGCAGAGTATTAGTTCAGT\n+TCTTCATAAGGTATTAGAGGAATGGGTAAGACAAGTACAAAATGATGGTATATTCGGATT\n+CGTATGGGAGTAATTATAATGATAAGTATAGAACATGATTATACAATAAGAACTGTAGAT\n+AATAGAAAATATACTTATTATAGTAAATACGAATCACTAGTTACTTTGTATGAAAATATT\n+ATGAGTAAAGATTGTATTGAAGTAACTAAATATGGGAAAGATAAAAAAGTTATTATTGAT\n+ACTAGACATATTGTATCTATTGAACGATGGTAAATAATAAGGAGGAGTAAACTATTATGA\n+TAAATGCAGGGCATGCTAAGTACCTATCAGAAATTTATGAAGATGATGTACATTATGAAA\n+CTATAGATAGTATTGTAGAAGATATACTAGATAATATTAATGATGGTATTATTGAAGAAG\n+CTATGAAAGGTAATACAAGTTATCAATATGTTCTTAGAGACTTAAGAGTAGATAATGAAG\n+TAGAATATAGAGTTATAGAAGAACTTACTAACCAAGGATATAGTGTAAACCACATTAGTA\n+ATGATATAGAGTACCCTTCTATATCTACAAATAATTTAGCAGGGTTAGATTACTTAAATA\n+TTAAATGGTAAGGGAGGGAATTAATATGATAAATAAATATAAAAAGTTATGGGATGAAAT\n+AACTCAACAAATTGTTAATGTAGAAATTATTAACTTTAAAAATGAAACAGTAACAATAGA\n+ATCTACAGATGATTCAGGATTATCAGAGATAAGAGGTTTTGAAGAAGTAGAGTTTATAGA\n+TTACTATGGATAAGATGTTTAAAGTATATAATTTATAAGGAGGAAACATATGGACTTGTT\n+TGCAAAAATAATTATTATGTCTATAGGAGTTGTTCCCTTGTTAACTATTATTGTTGCACA\n+GCTAATTACAGATTACCATGATAATCATTAAGTATTATAGTAATAGGAAGGACAATATTT\n+AGAGTGAGAGTATGTTGACTAATGAGGAAGATATAGAATGAGAACCTAACCAAGTAAAAC\n+TAAGTACCTTTGTTATGTACTACTATTACTACTACTACTATTACTACTACTATTACTACT\n+ACTACTACTACTACTATTACTATTACTACTACTATTACTACTACTAAGTACCTTTGTTAT\n+GTACTACTATTACTA\n+\n'
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/test-data/mu_reanno.gb
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/test-data/mu_reanno.gb Fri Jun 17 12:59:24 2022 +0000
b
b'@@ -0,0 +1,1877 @@\n+LOCUS       Exported               36717 bp ds-DNA     linear   PHG 03-JUN-2020\n+DEFINITION  Enterobacteria phage Mu, complete genome.\n+ACCESSION   NC_000929\n+VERSION     .\n+KEYWORDS    .\n+SOURCE      Escherichia phage Mu\n+  ORGANISM  Escherichia phage Mu\n+REFERENCE   1  (bases 1 to 36717)\n+  AUTHORS   Morgan GJ, Hatfull GF, Casjens S, Hendrix RW.\n+  TITLE     Bacteriophage Mu genome sequence: analysis and comparison with \n+            Mu-like prophages in Haemophilus, Neisseria and Deinococcus\n+  JOURNAL   J. Mol. Biol. 317 (3), 337-359 (2002)\n+  PUBMED    11922669\n+REFERENCE   2  (bases 1 to 36717)\n+  TITLE     Direct Submission\n+  JOURNAL   Submitted (05-MAY-2009) National Center for Biotechnology \n+            Information, NIH, Bethesda, MD 20894, USA\n+REFERENCE   3  (bases 1 to 36717)\n+  AUTHORS   Morgan G, Hatfull G, Hendrix R.\n+  TITLE     Direct Submission\n+  JOURNAL   Submitted (13-AUG-1998) Pittsburgh Bacteriophage Institute and \n+            Department of Biological Sciences, University of Pittsburgh, \n+            Pittsburgh, PA 15260, USA\n+REFERENCE   4  (bases 1 to 36717)\n+  AUTHORS   .\n+  TITLE     Direct Submission\n+  JOURNAL   Exported Wednesday, Jun 3, 2020 from SnapGene 5.1.3\n+            https://www.snapgene.com\n+COMMENT     SGRef: number: 1; type: "Journal Article"; journalName: "J. Mol.\n+            Biol."; date: "2002"; volume: "317"; issue: "3"; pages: "337-359"\n+COMMENT     SGRef: number: 2; type: "Journal Article"; journalName: "Submitted\n+            (05-MAY-2009) National Center for Biotechnology Information, NIH,\n+            Bethesda, MD 20894, USA"\n+COMMENT     SGRef: number: 3; type: "Journal Article"; journalName: "Submitted\n+            (13-AUG-1998) Pittsburgh Bacteriophage Institute and Department of\n+            Biological Sciences, University of Pittsburgh, Pittsburgh, PA 15260,\n+            USA"\n+COMMENT     PROVISIONAL REFSEQ: This record has not yet been subject to final \n+            NCBI review. The reference sequence was derived from AF083977. \n+            COMPLETENESS: full length.\n+FEATURES             Location/Qualifiers\n+     source          1..36717\n+                     /organism="Escherichia phage Mu"\n+                     /host="Escherichia coli"\n+                     /mol_type="genomic DNA"\n+                     /label=G(+) form\n+                     /note="G(+) form"\n+                     /db_xref="taxon:2681603"\n+     gene            complement(339..942)\n+                     /locus_tag="Mup01"\n+                     /label=Mup01\n+                     /db_xref="GeneID:2636266"\n+     CDS             complement(339..929)\n+                     /codon_start=1\n+                     /transl_table=11\n+                     /locus_tag="Mup01"\n+                     /product="repressor protein c"\n+                     /label=Mup01\n+                     /note="Mup01 or immunity repressor or MuR for repressor of\n+                     replication or Rep c; see PMIDs  16154589 and 9546656,\n+                     11135677 for structure; contains InterPro domain IPR003314\n+                     Mu-type HTH domain also found in Mu transposase (Mup03 or\n+                     A)"\n+                     /db_xref="GeneID:2636266"\n+                     /protein_id="NP_050605.1"\n+                     /translation="MAADGMPGSVAGVHYRANVQGWTKQKKEGVKGGKAVEYDVMSMPT\n+                     KEREQVIAHLGLSTPDTGAQANEKQDSSELINKLTTTLINMIEELEPDEARKALKLLSK\n+                     GGLLALMPLVFNEQKLYSFIGFSQQSIQTLMMLDALPEEKRKEILSKYGIHEQESVVVP\n+                     SQEPQEVKKAV"\n+     RBS             complement(938..942)\n+                     /locus_tag="Mup01"\n+     gene            1085..1326\n+                     /locus_tag="Mup02"\n+                     /label=Mup02\n+                     /db_xref="GeneID:2636289"\n+     RBS             1085..1090\n+                     /locus_tag="Mup02"\n+     CDS             1099..1326\n+                     /codon_start=1\n+                     /transl_table=11\n+       '..b'3601 agcatccact ctgttaattt ttgtttattc tcgtcggaaa tgatgcccag ccgtagctgt\n+    33661 gagtcccata gctgtgtttt atccctgacg agctgtagca ggctttgctt ttcattttcc\n+    33721 gcttgttgcc tctgctcttc ctcggtataa gttcgcttta tcactacacc atctttgaac\n+    33781 atccatttac ccgaaatatc agcccggcga tttgctgtaa tatcaggaac ctcaacgacg\n+    33841 tttgcgcctt ctggattaat tgctgaaaca tccttttcaa tacaaataat aacgccgttg\n+    33901 tggtcataga ccattttcaa cgtatcaggc tgaaagttct tttgttcctc ataccagttt\n+    33961 ttcccatcct ctgtataaag ccatttgatg ttaaattgtt tcgttagctg gtattgctct\n+    34021 tttgttttag ggttgccagc agtaatattt tttaagtgca tcataattaa atactccccg\n+    34081 cgttatacca cgttccatta atgcaatact gaattggcct tgcctgagtt gtatcaatta\n+    34141 attcatcacg gtttccgtta actgaacccg taacgacata acctgacctg tcagaccagc\n+    34201 cgggaccatt ccatgtctga acagatgaca gaccgccaag acgaatacct gtaataaacc\n+    34261 ttgagttaca ttctgcctgc gtatatgcac caacatctcc cgctgatggc ttacgtgttg\n+    34321 tggtgtaaat ttctgaccag tcagcttcga atccgtaacc atcacgcgct gaacgataaa\n+    34381 aaataccgcc gttcctgtaa ttcacacgga actgtacggc agggcaactc cccgtattca\n+    34441 tattgaagtg gaggattaat gtcgatgcgc caccaatatt tgcgttatag accccgctat\n+    34501 tccagttcca gccaacagct ttatcatttc cgacagtgct tcctgtttgt cctaaagcaa\n+    34561 atgcaggctg ctggtttttc gtgttgtagt ctcgtcgcca gccaggagca taagcatcac\n+    34621 catgattaat ataagtgaat tgagcgttag tgattccgcc gccgctggac gtactcggcg\n+    34681 tagtaacgcg tatggtcatt gcgccgcgag tgccaataac ttccaccaca gcacctgcaa\n+    34741 gacaaatatt tccgcaacct gtatctgtaa tgaccttatt gtttgcataa gcccatgagc\n+    34801 ctttgcacat ccagtaagga tggttaaatg ccccctgact ctccagccac gaaataaatt\n+    34861 gtgcggttgt ccagacctga ctatcgccac caatattcag ccatgcgcta tatgcgcgac\n+    34921 aggccccaat atttttggtg aaggtatctt ttcctggaat atctgcgccg ttctggtttt\n+    34981 gctgtaatgc gccagaagcc tgatttaccg tttcctgtaa accgaggttt tggataatgg\n+    35041 tcgattttgg cacgcatggc atgattggcg cttttaaaca ggagatccag agtgctgatt\n+    35101 ggctatgtaa gggtatcaac aaatgaccag aatacagacc tgcaacgaaa cgctcttgtt\n+    35161 tgtgcaggat gtgaacaaat atttgaagat aaattaagcg gaacaaggac agaccgaccg\n+    35221 ggattaaaac gcgctttaaa gcgccttcaa aaaggtgaca cactggttgt ctggaaactg\n+    35281 gatcgcctcg ggcgaagcat gaaacatttg atttctctcg taggggaatt acgagagcga\n+    35341 gggattaatt ttcgcagtct tactgacagt attgatacgt catctccaat ggggcgtttt\n+    35401 ttcttccacg ttatgggtgc cctggctgaa atggaacgag aactaattat cgagcgaacg\n+    35461 atggctggac ttgctgccgc cagaaataaa ggccgtattg gtgggcgacc acctaaacta\n+    35521 accaaagcgg aatgggagca ggccgggcgt ttattagcac aaggaatccc ccgcaagcag\n+    35581 gttgcattga tctacgatgt ggccctgtca actctgtata aaaaacaccc cgcgaaacga\n+    35641 gcgcatatag aaaacgacga tcgaatcaat taaatcgatc ggtaatacag atcgattatg\n+    35701 ccccaataac cacactcaac ccatgatgtt ttttaagata gtggcgaatt gatgcaaagg\n+    35761 aggtgagatg aaatcaattc gctgtaaaaa ctgcaacaaa ctgttattta aggcggattc\n+    35821 ctttgatcac attgaaatca ggtgtccgcg ttgcaaacgt cacatcataa tgctgaatgc\n+    35881 ctgcgagcat cccacggaga aacattgtgg gaaaagagaa aaaatcacgc attctgacga\n+    35941 aaccgtgcgt tattgagtat gaaggccaga ttgttggcta tggttcaaag gagctgcgcg\n+    36001 ttgaaaccat atcctgctgg ctggcccgca caattattca gacaaagcac tattcccgcc\n+    36061 gttttgtgaa taactcttac cttcacctgg gggtattcag cggacgcgat ctggttggcg\n+    36121 ttctccagtg gggatatgcc cttaacccca actcaggtcg tcgtgtcgtg cttgaaacgg\n+    36181 ataaccgggg ctatatggag ttgaaccgca tgtggctaca cgacgacatg ccccgcaact\n+    36241 ctgaatcacg ggccatcagc tacgcgctga aagttatcag attactgtat ccgtcagtgg\n+    36301 agtgggttca gtcctttgca gatgaacgct gcggacgcgc aggcgttgtg tatcaggcgt\n+    36361 cgaattttga ttttattggc agtcatgaaa gtacgttcta cgagctggat ggtgagtggt\n+    36421 atcacgagat aacgatgaac gcgattaagc gaggtggaca acgaggcgtg tatttacggg\n+    36481 ctaataaaga gcgtgccgtg gtacacaaat ttaatcagta tcgctacatc agattcctga\n+    36541 acaaacgagc aaggaagcgg ctaaatacca aactattcaa ggttcagcca taccctaagt\n+    36601 gatccccatg taatgaataa aaagcagtaa ttaatacatc tgtttcattt gaagcgcgaa\n+    36661 agctaaagtt ttcgcattta tcgtgaaacg ctttcgcgtt tttcgtgcgc cgcttca\n+//\n'
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/test-data/out_img.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/test-data/out_img.svg Fri Jun 17 12:59:24 2022 +0000
b
b'@@ -0,0 +1,4404 @@\n+<?xml version="1.0" encoding="utf-8" standalone="no"?>\n+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"\n+  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n+<!-- Created with matplotlib (https://matplotlib.org/) -->\n+<svg height="244.8pt" version="1.1" viewBox="0 0 7200 244.8" width="7200pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">\n+ <metadata>\n+  <rdf:RDF xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">\n+   <cc:Work>\n+    <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>\n+    <dc:date>2020-10-19T13:19:20.299132</dc:date>\n+    <dc:format>image/svg+xml</dc:format>\n+    <dc:creator>\n+     <cc:Agent>\n+      <dc:title>Matplotlib v3.3.2, https://matplotlib.org/</dc:title>\n+     </cc:Agent>\n+    </dc:creator>\n+   </cc:Work>\n+  </rdf:RDF>\n+ </metadata>\n+ <defs>\n+  <style type="text/css">*{stroke-linecap:butt;stroke-linejoin:round;}</style>\n+ </defs>\n+ <g id="figure_1">\n+  <g id="patch_1">\n+   <path d="M 0 244.8 \n+L 7200 244.8 \n+L 7200 0 \n+L 0 0 \n+z\n+" style="fill:#ffffff;"/>\n+  </g>\n+  <g id="axes_1">\n+   <g id="line2d_1">\n+    <path clip-path="url(#p27f68a7d99)" d="M 900 203.372308 \n+L 6480 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:1.5;"/>\n+   </g>\n+   <g id="line2d_2">\n+    <path clip-path="url(#p27f68a7d99)" d="M 1481.56154 159.873231 \n+L 1481.56154 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_3">\n+    <path clip-path="url(#p27f68a7d99)" d="M 6407.312406 72.875077 \n+L 6407.312406 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_4">\n+    <path clip-path="url(#p27f68a7d99)" d="M 5324.065135 116.374154 \n+L 5324.065135 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_5">\n+    <path clip-path="url(#p27f68a7d99)" d="M 996.31904 174.372923 \n+L 996.31904 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_6">\n+    <path clip-path="url(#p27f68a7d99)" d="M 6277.073502 145.373538 \n+L 6277.073502 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_7">\n+    <path clip-path="url(#p27f68a7d99)" d="M 2815.332538 145.373538 \n+L 2815.332538 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_8">\n+    <path clip-path="url(#p27f68a7d99)" d="M 4522.039676 130.873846 \n+L 4522.039676 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_9">\n+    <path clip-path="url(#p27f68a7d99)" d="M 2193.012452 116.374154 \n+L 2193.012452 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_10">\n+    <path clip-path="url(#p27f68a7d99)" d="M 3766.973168 116.374154 \n+L 3766.973168 145.373538 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_11">\n+    <path clip-path="url(#p27f68a7d99)" d="M 4126.003987 87.374769 \n+L 4126.003987 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_12">\n+    <path clip-path="url(#p27f68a7d99)" d="M 5639.480358 145.373538 \n+L 5639.480358 174.372923 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_13">\n+    <path clip-path="url(#p27f68a7d99)" d="M 6036.199915 116.374154 \n+L 6036.199915 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_14">\n+    <path clip-path="url(#p27f68a7d99)" d="M 5951.248284 145.373538 \n+L 5951.248284 203.372308 \n+" style="fill:none;stroke:#000000;stroke'..b' 6274.926898 107.751185 \n+Q 6271.626898 107.751185 6271.626898 111.051185 \n+L 6271.626898 121.697123 \n+Q 6271.626898 124.997123 6274.926898 124.997123 \n+z\n+" style="fill:#ffffff;stroke:#808080;stroke-linejoin:miter;stroke-width:0.5;"/>\n+    </g>\n+    <!-- translational activator Com -->\n+    <g transform="translate(6274.926898 119.409466)scale(0.11 -0.11)">\n+     <use xlink:href="#DejaVuSans-116"/>\n+     <use x="39.208984" xlink:href="#DejaVuSans-114"/>\n+     <use x="80.322266" xlink:href="#DejaVuSans-97"/>\n+     <use x="141.601562" xlink:href="#DejaVuSans-110"/>\n+     <use x="204.980469" xlink:href="#DejaVuSans-115"/>\n+     <use x="257.080078" xlink:href="#DejaVuSans-108"/>\n+     <use x="284.863281" xlink:href="#DejaVuSans-97"/>\n+     <use x="346.142578" xlink:href="#DejaVuSans-116"/>\n+     <use x="385.351562" xlink:href="#DejaVuSans-105"/>\n+     <use x="413.134766" xlink:href="#DejaVuSans-111"/>\n+     <use x="474.316406" xlink:href="#DejaVuSans-110"/>\n+     <use x="537.695312" xlink:href="#DejaVuSans-97"/>\n+     <use x="598.974609" xlink:href="#DejaVuSans-108"/>\n+     <use x="626.757812" xlink:href="#DejaVuSans-32"/>\n+     <use x="658.544922" xlink:href="#DejaVuSans-97"/>\n+     <use x="719.824219" xlink:href="#DejaVuSans-99"/>\n+     <use x="774.804688" xlink:href="#DejaVuSans-116"/>\n+     <use x="814.013672" xlink:href="#DejaVuSans-105"/>\n+     <use x="841.796875" xlink:href="#DejaVuSans-118"/>\n+     <use x="900.976562" xlink:href="#DejaVuSans-97"/>\n+     <use x="962.255859" xlink:href="#DejaVuSans-116"/>\n+     <use x="1001.464844" xlink:href="#DejaVuSans-111"/>\n+     <use x="1062.646484" xlink:href="#DejaVuSans-114"/>\n+     <use x="1103.759766" xlink:href="#DejaVuSans-32"/>\n+     <use x="1135.546875" xlink:href="#DejaVuSans-67"/>\n+     <use x="1205.371094" xlink:href="#DejaVuSans-111"/>\n+     <use x="1266.552734" xlink:href="#DejaVuSans-109"/>\n+    </g>\n+   </g>\n+   <g id="text_56">\n+    <g id="patch_148">\n+     <path d="M 2379.103248 124.997123 \n+L 2426.968717 124.997123 \n+Q 2430.268717 124.997123 2430.268717 121.697123 \n+L 2430.268717 111.051185 \n+Q 2430.268717 107.751185 2426.968717 107.751185 \n+L 2379.103248 107.751185 \n+Q 2375.803248 107.751185 2375.803248 111.051185 \n+L 2375.803248 121.697123 \n+Q 2375.803248 124.997123 2379.103248 124.997123 \n+z\n+" style="fill:#ffffff;stroke:#808080;stroke-linejoin:miter;stroke-width:0.5;"/>\n+    </g>\n+    <!-- antiholin -->\n+    <g transform="translate(2379.103248 119.409466)scale(0.11 -0.11)">\n+     <use xlink:href="#DejaVuSans-97"/>\n+     <use x="61.279297" xlink:href="#DejaVuSans-110"/>\n+     <use x="124.658203" xlink:href="#DejaVuSans-116"/>\n+     <use x="163.867188" xlink:href="#DejaVuSans-105"/>\n+     <use x="191.650391" xlink:href="#DejaVuSans-104"/>\n+     <use x="255.029297" xlink:href="#DejaVuSans-111"/>\n+     <use x="316.210938" xlink:href="#DejaVuSans-108"/>\n+     <use x="343.994141" xlink:href="#DejaVuSans-105"/>\n+     <use x="371.777344" xlink:href="#DejaVuSans-110"/>\n+    </g>\n+   </g>\n+   <g id="text_57">\n+    <!-- Mu Test label -->\n+    <g transform="translate(3650.880938 23.376)scale(0.12 -0.12)">\n+     <use xlink:href="#DejaVuSans-77"/>\n+     <use x="86.279297" xlink:href="#DejaVuSans-117"/>\n+     <use x="149.658203" xlink:href="#DejaVuSans-32"/>\n+     <use x="181.445312" xlink:href="#DejaVuSans-84"/>\n+     <use x="225.529297" xlink:href="#DejaVuSans-101"/>\n+     <use x="287.052734" xlink:href="#DejaVuSans-115"/>\n+     <use x="339.152344" xlink:href="#DejaVuSans-116"/>\n+     <use x="378.361328" xlink:href="#DejaVuSans-32"/>\n+     <use x="410.148438" xlink:href="#DejaVuSans-108"/>\n+     <use x="437.931641" xlink:href="#DejaVuSans-97"/>\n+     <use x="499.210938" xlink:href="#DejaVuSans-98"/>\n+     <use x="562.6875" xlink:href="#DejaVuSans-101"/>\n+     <use x="624.210938" xlink:href="#DejaVuSans-108"/>\n+    </g>\n+   </g>\n+  </g>\n+ </g>\n+ <defs>\n+  <clipPath id="p27f68a7d99">\n+   <rect height="188.496" width="5580" x="900" y="29.376"/>\n+  </clipPath>\n+ </defs>\n+</svg>\n'
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/test-data/out_img_multi.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/test-data/out_img_multi.svg Fri Jun 17 12:59:24 2022 +0000
b
b'@@ -0,0 +1,2135 @@\n+<?xml version="1.0" encoding="utf-8" standalone="no"?>\n+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"\n+  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n+<!-- Created with matplotlib (https://matplotlib.org/) -->\n+<svg height="738.72pt" version="1.1" viewBox="0 0 720 738.72" width="720pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">\n+ <metadata>\n+  <rdf:RDF xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">\n+   <cc:Work>\n+    <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>\n+    <dc:date>2020-10-20T14:36:18.903835</dc:date>\n+    <dc:format>image/svg+xml</dc:format>\n+    <dc:creator>\n+     <cc:Agent>\n+      <dc:title>Matplotlib v3.3.2, https://matplotlib.org/</dc:title>\n+     </cc:Agent>\n+    </dc:creator>\n+   </cc:Work>\n+  </rdf:RDF>\n+ </metadata>\n+ <defs>\n+  <style type="text/css">*{stroke-linecap:butt;stroke-linejoin:round;}</style>\n+ </defs>\n+ <g id="figure_1">\n+  <g id="patch_1">\n+   <path d="M 0 738.72 \n+L 720 738.72 \n+L 720 0 \n+L 0 0 \n+z\n+" style="fill:#ffffff;"/>\n+  </g>\n+  <g id="axes_1">\n+   <g id="line2d_1">\n+    <path clip-path="url(#p2fb0868ea9)" d="M 10.341245 130.110097 \n+L 698.817811 130.110097 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:1.5;"/>\n+   </g>\n+   <g id="patch_2">\n+    <path clip-path="url(#p2fb0868ea9)" d="M 9.939834 123.110097 \n+Q 144.800726 123.110097 279.661617 123.110097 \n+L 279.661617 123.110097 \n+Q 283.860835 126.610097 288.060053 130.110097 \n+Q 283.860835 133.610097 279.661617 137.110097 \n+L 279.661617 137.110097 \n+Q 144.800726 137.110097 9.939834 137.110097 \n+L 9.939834 123.110097 \n+z\n+" style="fill:#ffffff;stroke:#000000;stroke-linecap:round;"/>\n+   </g>\n+   <g id="patch_3">\n+    <path clip-path="url(#p2fb0868ea9)" d="M 385.545491 123.110097 \n+Q 468.221286 123.110097 550.897081 123.110097 \n+L 550.897081 123.110097 \n+Q 555.098014 126.610097 559.298947 130.110097 \n+Q 555.098014 133.610097 550.897081 137.110097 \n+L 550.897081 137.110097 \n+Q 468.221286 137.110097 385.545491 137.110097 \n+L 385.545491 123.110097 \n+z\n+" style="fill:#ffffff;stroke:#000000;stroke-linecap:round;"/>\n+   </g>\n+   <g id="patch_4">\n+    <path clip-path="url(#p2fb0868ea9)" d="M 676.854916 107.282573 \n+Q 604.498444 107.282573 532.141972 107.282573 \n+L 532.141972 107.282573 \n+Q 527.943703 103.782573 523.745434 100.282573 \n+Q 527.943703 96.782573 532.141972 93.282573 \n+L 532.141972 93.282573 \n+Q 604.498444 93.282573 676.854916 93.282573 \n+L 676.854916 107.282573 \n+z\n+" style="fill:#ffffff;stroke:#000000;stroke-linecap:round;"/>\n+   </g>\n+   <g id="patch_5">\n+    <path clip-path="url(#p2fb0868ea9)" d="M 280.031841 93.282573 \n+Q 328.301838 93.282573 376.571834 93.282573 \n+L 376.571834 93.282573 \n+Q 380.771941 96.782573 384.972047 100.282573 \n+Q 380.771941 103.782573 376.571834 107.282573 \n+L 376.571834 107.282573 \n+Q 328.301838 107.282573 280.031841 107.282573 \n+L 280.031841 93.282573 \n+z\n+" style="fill:#ffffff;stroke:#000000;stroke-linecap:round;"/>\n+   </g>\n+   <g id="patch_6">\n+    <path clip-path="url(#p2fb0868ea9)" d="M 560.445834 123.110097 \n+Q 604.415292 123.110097 648.384749 123.110097 \n+L 648.384749 123.110097 \n+Q 652.584567 126.610097 656.784384 130.110097 \n+Q 652.584567 133.610097 648.384749 137.110097 \n+L 648.384749 137.110097 \n+Q 604.415292 137.110097 560.445834 137.110097 \n+L 560.445834 123.110097 \n+z\n+" style="fill:#ffffff;stroke:#000000;stroke-linecap:round;"/>\n+   </g>\n+   <g id="patch_7">\n+    <path clip-path="url(#p2fb0868ea9)" d="M 273.723959 93.282573 \n+Q 274.474924 93.282573 275.225889 93.282573 \n+L 275.225889 93.282573 \n+Q 276.195256 96.782573 277.164622 100.282573 \n+Q 276.195256 103.782573 275.225889 107.282573 \n+L 275.225889 107.282573 \n+Q 274.474924 107.282573 273.723959 107.282573 \n+L 273.723959 93.282573 \n+z\n+" style="fill:#cd5c5c;stroke:#000000;stroke-linecap:round;"/>\n+   </g>\n+   <g id="patch_8">\n+    <pat'..b'1 649.864972 499.252941 \n+L 649.864972 509.898878 \n+Q 649.864972 513.198878 653.164972 513.198878 \n+z\n+" style="fill:#ffffff;"/>\n+    </g>\n+    <!-- releasin -->\n+    <g transform="translate(653.164972 507.611222)scale(0.11 -0.11)">\n+     <use xlink:href="#DejaVuSans-114"/>\n+     <use x="38.863281" xlink:href="#DejaVuSans-101"/>\n+     <use x="100.386719" xlink:href="#DejaVuSans-108"/>\n+     <use x="128.169922" xlink:href="#DejaVuSans-101"/>\n+     <use x="189.693359" xlink:href="#DejaVuSans-97"/>\n+     <use x="250.972656" xlink:href="#DejaVuSans-115"/>\n+     <use x="303.072266" xlink:href="#DejaVuSans-105"/>\n+     <use x="330.855469" xlink:href="#DejaVuSans-110"/>\n+    </g>\n+   </g>\n+  </g>\n+  <g id="axes_5">\n+   <g id="line2d_40">\n+    <path clip-path="url(#p9e40ee8b5d)" d="M 10.341245 697.877989 \n+L 125.374061 697.877989 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:1.5;"/>\n+   </g>\n+   <g id="patch_38">\n+    <path clip-path="url(#p9e40ee8b5d)" d="M 9.939834 690.877989 \n+Q 67.855886 690.877989 125.771937 690.877989 \n+L 125.771937 690.877989 \n+Q 125.773704 694.377989 125.775472 697.877989 \n+Q 125.773704 701.377989 125.771937 704.877989 \n+L 125.771937 704.877989 \n+Q 67.855886 704.877989 9.939834 704.877989 \n+L 9.939834 690.877989 \n+z\n+" style="fill:#ffffff;stroke:#000000;stroke-linecap:round;"/>\n+   </g>\n+   <g id="matplotlib.axis_5">\n+    <g id="xtick_33">\n+     <g id="line2d_41">\n+      <g>\n+       <use style="fill:#808080;stroke:#808080;stroke-width:0.8;" x="39.472188" xlink:href="#m865a180ad8" y="710.84"/>\n+      </g>\n+     </g>\n+     <g id="text_43">\n+      <!-- 11,850 -->\n+      <g style="fill:#808080;" transform="translate(21.976875 725.438438)scale(0.1 -0.1)">\n+       <use xlink:href="#DejaVuSans-49"/>\n+       <use x="63.623047" xlink:href="#DejaVuSans-49"/>\n+       <use x="127.246094" xlink:href="#DejaVuSans-44"/>\n+       <use x="159.033203" xlink:href="#DejaVuSans-56"/>\n+       <use x="222.65625" xlink:href="#DejaVuSans-53"/>\n+       <use x="286.279297" xlink:href="#DejaVuSans-48"/>\n+      </g>\n+     </g>\n+    </g>\n+    <g id="xtick_34">\n+     <g id="line2d_42">\n+      <g>\n+       <use style="fill:#808080;stroke:#808080;stroke-width:0.8;" x="125.48875" xlink:href="#m865a180ad8" y="710.84"/>\n+      </g>\n+     </g>\n+     <g id="text_44">\n+      <!-- 12,000 -->\n+      <g style="fill:#808080;" transform="translate(107.993438 725.438438)scale(0.1 -0.1)">\n+       <use xlink:href="#DejaVuSans-49"/>\n+       <use x="63.623047" xlink:href="#DejaVuSans-50"/>\n+       <use x="127.246094" xlink:href="#DejaVuSans-44"/>\n+       <use x="159.033203" xlink:href="#DejaVuSans-48"/>\n+       <use x="222.65625" xlink:href="#DejaVuSans-48"/>\n+       <use x="286.279297" xlink:href="#DejaVuSans-48"/>\n+      </g>\n+     </g>\n+    </g>\n+   </g>\n+   <g id="text_45">\n+    <!-- releasin -->\n+    <g transform="translate(46.174763 700.913301)scale(0.11 -0.11)">\n+     <use xlink:href="#DejaVuSans-114"/>\n+     <use x="38.863281" xlink:href="#DejaVuSans-101"/>\n+     <use x="100.386719" xlink:href="#DejaVuSans-108"/>\n+     <use x="128.169922" xlink:href="#DejaVuSans-101"/>\n+     <use x="189.693359" xlink:href="#DejaVuSans-97"/>\n+     <use x="250.972656" xlink:href="#DejaVuSans-115"/>\n+     <use x="303.072266" xlink:href="#DejaVuSans-105"/>\n+     <use x="330.855469" xlink:href="#DejaVuSans-110"/>\n+    </g>\n+   </g>\n+  </g>\n+ </g>\n+ <defs>\n+  <clipPath id="p2fb0868ea9">\n+   <rect height="134.22386" width="688.1325" x="10.8" y="10.8"/>\n+  </clipPath>\n+  <clipPath id="pe825af461e">\n+   <rect height="113.574035" width="688.1325" x="10.8" y="172.90386"/>\n+  </clipPath>\n+  <clipPath id="p4d11cbafed">\n+   <rect height="113.574035" width="688.1325" x="10.8" y="314.357895"/>\n+  </clipPath>\n+  <clipPath id="paf4c887adf">\n+   <rect height="113.574035" width="688.1325" x="10.8" y="455.81193"/>\n+  </clipPath>\n+  <clipPath id="p9e40ee8b5d">\n+   <rect height="113.574035" width="688.1325" x="10.8" y="597.265965"/>\n+  </clipPath>\n+ </defs>\n+</svg>\n'
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/test-data/out_img_zoom.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/test-data/out_img_zoom.svg Fri Jun 17 12:59:24 2022 +0000
b
b'@@ -0,0 +1,1688 @@\n+<?xml version="1.0" encoding="utf-8" standalone="no"?>\n+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"\n+  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n+<!-- Created with matplotlib (https://matplotlib.org/) -->\n+<svg height="244.8pt" version="1.1" viewBox="0 0 720 244.8" width="720pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">\n+ <metadata>\n+  <rdf:RDF xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">\n+   <cc:Work>\n+    <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>\n+    <dc:date>2020-10-19T13:19:51.116707</dc:date>\n+    <dc:format>image/svg+xml</dc:format>\n+    <dc:creator>\n+     <cc:Agent>\n+      <dc:title>Matplotlib v3.3.2, https://matplotlib.org/</dc:title>\n+     </cc:Agent>\n+    </dc:creator>\n+   </cc:Work>\n+  </rdf:RDF>\n+ </metadata>\n+ <defs>\n+  <style type="text/css">*{stroke-linecap:butt;stroke-linejoin:round;}</style>\n+ </defs>\n+ <g id="figure_1">\n+  <g id="patch_1">\n+   <path d="M 0 244.8 \n+L 720 244.8 \n+L 720 0 \n+L 0 0 \n+z\n+" style="fill:#ffffff;"/>\n+  </g>\n+  <g id="axes_1">\n+   <g id="line2d_1">\n+    <path clip-path="url(#pf91a91e396)" d="M 90 203.372308 \n+L 647.977684 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:1.5;"/>\n+   </g>\n+   <g id="line2d_2">\n+    <path clip-path="url(#pf91a91e396)" d="M 258.299352 116.374154 \n+L 258.299352 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_3">\n+    <path clip-path="url(#pf91a91e396)" d="M 506.848584 145.373538 \n+L 506.848584 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_4">\n+    <path clip-path="url(#pf91a91e396)" d="M 444.139418 116.374154 \n+L 444.139418 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_5">\n+    <path clip-path="url(#pf91a91e396)" d="M 310.408215 145.373538 \n+L 310.408215 174.372923 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_6">\n+    <path clip-path="url(#pf91a91e396)" d="M 555.4984 87.374769 \n+L 555.4984 174.372923 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_7">\n+    <path clip-path="url(#pf91a91e396)" d="M 384.554551 58.375385 \n+L 384.554551 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_8">\n+    <path clip-path="url(#pf91a91e396)" d="M 580.548592 116.374154 \n+L 580.548592 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_9">\n+    <path clip-path="url(#pf91a91e396)" d="M 632.155335 145.373538 \n+L 632.155335 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_10">\n+    <path clip-path="url(#pf91a91e396)" d="M 412.505879 87.374769 \n+L 412.505879 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="patch_2">\n+    <path clip-path="url(#pf91a91e396)" d="M 227.50268 196.372308 \n+Q 254.098268 196.372308 280.693856 196.372308 \n+L 280.693856 196.372308 \n+Q 284.89494 199.872308 289.096025 203.372308 \n+Q 284.89494 206.872308 280.693856 210.372308 \n+L 280.693856 210.372308 \n+Q 254.098268 210.372308 227.50268 210.372308 \n+L 227.50268 196.372308 \n+z\n+" style="fill:#ffffff;stroke:#000000;stroke-linecap:round;"/>\n+   </g>\n+   <g id="patch_3">\n+    <path clip-path="url(#pf91a91e396)" d="M 477.893017 196.372308 \n+Q 502.647694 196.372308 527.402371 196.372308 \n+L 527.402371 196.372308 \n+Q 531.603261 199.872308 535.804151 203.372308 \n+Q 531.603261 206.872308 527.402371 210.372308 \n+L 527.402371 210.372308 \n+Q 502.647694 210.372308 477.893017 210.372308 \n+L 477.893017 196.372308 \n+z\n+" st'..b'96507 \n+Q 657.138226 153.996507 657.138226 150.696507 \n+L 657.138226 140.05057 \n+Q 657.138226 136.75057 653.838226 136.75057 \n+L 610.472445 136.75057 \n+Q 607.172445 136.75057 607.172445 140.05057 \n+L 607.172445 150.696507 \n+Q 607.172445 153.996507 610.472445 153.996507 \n+z\n+" style="fill:#ffffff;stroke:#808080;stroke-linejoin:miter;stroke-width:0.5;"/>\n+    </g>\n+    <!-- releasin -->\n+    <g transform="translate(610.472445 148.408851)scale(0.11 -0.11)">\n+     <use xlink:href="#DejaVuSans-114"/>\n+     <use x="38.863281" xlink:href="#DejaVuSans-101"/>\n+     <use x="100.386719" xlink:href="#DejaVuSans-108"/>\n+     <use x="128.169922" xlink:href="#DejaVuSans-101"/>\n+     <use x="189.693359" xlink:href="#DejaVuSans-97"/>\n+     <use x="250.972656" xlink:href="#DejaVuSans-115"/>\n+     <use x="303.072266" xlink:href="#DejaVuSans-105"/>\n+     <use x="330.855469" xlink:href="#DejaVuSans-110"/>\n+    </g>\n+   </g>\n+   <g id="text_18">\n+    <g id="patch_41">\n+     <path d="M 388.573145 95.997738 \n+L 436.438613 95.997738 \n+Q 439.738613 95.997738 439.738613 92.697738 \n+L 439.738613 82.0518 \n+Q 439.738613 78.7518 436.438613 78.7518 \n+L 388.573145 78.7518 \n+Q 385.273145 78.7518 385.273145 82.0518 \n+L 385.273145 92.697738 \n+Q 385.273145 95.997738 388.573145 95.997738 \n+z\n+" style="fill:#ffffff;stroke:#808080;stroke-linejoin:miter;stroke-width:0.5;"/>\n+    </g>\n+    <!-- antiholin -->\n+    <g transform="translate(388.573145 90.410082)scale(0.11 -0.11)">\n+     <use xlink:href="#DejaVuSans-97"/>\n+     <use x="61.279297" xlink:href="#DejaVuSans-110"/>\n+     <use x="124.658203" xlink:href="#DejaVuSans-116"/>\n+     <use x="163.867188" xlink:href="#DejaVuSans-105"/>\n+     <use x="191.650391" xlink:href="#DejaVuSans-104"/>\n+     <use x="255.029297" xlink:href="#DejaVuSans-111"/>\n+     <use x="316.210938" xlink:href="#DejaVuSans-108"/>\n+     <use x="343.994141" xlink:href="#DejaVuSans-105"/>\n+     <use x="371.777344" xlink:href="#DejaVuSans-110"/>\n+    </g>\n+   </g>\n+   <g id="text_19">\n+    <!-- Mu Test label -->\n+    <g transform="translate(329.880938 23.376)scale(0.12 -0.12)">\n+     <defs>\n+      <path d="M -0.296875 72.90625 \n+L 61.375 72.90625 \n+L 61.375 64.59375 \n+L 35.5 64.59375 \n+L 35.5 0 \n+L 25.59375 0 \n+L 25.59375 64.59375 \n+L -0.296875 64.59375 \n+z\n+" id="DejaVuSans-84"/>\n+      <path d="M 48.6875 27.296875 \n+Q 48.6875 37.203125 44.609375 42.84375 \n+Q 40.53125 48.484375 33.40625 48.484375 \n+Q 26.265625 48.484375 22.1875 42.84375 \n+Q 18.109375 37.203125 18.109375 27.296875 \n+Q 18.109375 17.390625 22.1875 11.75 \n+Q 26.265625 6.109375 33.40625 6.109375 \n+Q 40.53125 6.109375 44.609375 11.75 \n+Q 48.6875 17.390625 48.6875 27.296875 \n+z\n+M 18.109375 46.390625 \n+Q 20.953125 51.265625 25.265625 53.625 \n+Q 29.59375 56 35.59375 56 \n+Q 45.5625 56 51.78125 48.09375 \n+Q 58.015625 40.1875 58.015625 27.296875 \n+Q 58.015625 14.40625 51.78125 6.484375 \n+Q 45.5625 -1.421875 35.59375 -1.421875 \n+Q 29.59375 -1.421875 25.265625 0.953125 \n+Q 20.953125 3.328125 18.109375 8.203125 \n+L 18.109375 0 \n+L 9.078125 0 \n+L 9.078125 75.984375 \n+L 18.109375 75.984375 \n+z\n+" id="DejaVuSans-98"/>\n+     </defs>\n+     <use xlink:href="#DejaVuSans-77"/>\n+     <use x="86.279297" xlink:href="#DejaVuSans-117"/>\n+     <use x="149.658203" xlink:href="#DejaVuSans-32"/>\n+     <use x="181.445312" xlink:href="#DejaVuSans-84"/>\n+     <use x="225.529297" xlink:href="#DejaVuSans-101"/>\n+     <use x="287.052734" xlink:href="#DejaVuSans-115"/>\n+     <use x="339.152344" xlink:href="#DejaVuSans-116"/>\n+     <use x="378.361328" xlink:href="#DejaVuSans-32"/>\n+     <use x="410.148438" xlink:href="#DejaVuSans-108"/>\n+     <use x="437.931641" xlink:href="#DejaVuSans-97"/>\n+     <use x="499.210938" xlink:href="#DejaVuSans-98"/>\n+     <use x="562.6875" xlink:href="#DejaVuSans-101"/>\n+     <use x="624.210938" xlink:href="#DejaVuSans-108"/>\n+    </g>\n+   </g>\n+  </g>\n+ </g>\n+ <defs>\n+  <clipPath id="pf91a91e396">\n+   <rect height="188.496" width="558" x="90" y="29.376"/>\n+  </clipPath>\n+ </defs>\n+</svg>\n'
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/test-data/out_stats.txt
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/test-data/out_stats.txt Fri Jun 17 12:59:24 2022 +0000
b
@@ -0,0 +1,68 @@
+---::: FILE BREAKDOWN :::---
+
+------::: Feature Count :::------
+Feature: source ::::: Count: 1
+Feature: gene ::::: Count: 58
+Feature: CDS ::::: Count: 58
+Feature: RBS ::::: Count: 57
+Feature: misc_feature ::::: Count: 7
+Feature: misc_difference ::::: Count: 1
+------::: Product Names :::------
+Product Name: repressor protein c
+Product Name: DNA binding protein ner
+Product Name: transposase
+Product Name: AAA-ATPase DNA transposition protein
+Product Name: protein kil
+Product Name: uncharacterized protein
+Product Name: uncharacterized protein
+Product Name: uncharacterized protein
+Product Name: uncharacterized protein
+Product Name: host nuclease inhibitor Gam
+Product Name: uncharacterized protein
+Product Name: uncharacterized protein
+Product Name: uncharacterized protein
+Product Name: uncharacterized protein
+Product Name: uncharacterized protein
+Product Name: hypothetical protein
+Product Name: protein GemA
+Product Name: middle operon regulator Mor
+Product Name: uncharacterized protein
+Product Name: pinholin
+Product Name: antiholin
+Product Name: transcription regulator C
+Product Name: SAR endolysin
+Product Name: i-spanin
+Product Name: o-spanin
+Product Name: uncharacterized protein
+Product Name: releasin
+Product Name: winged HTH domain-containing protein
+Product Name: terminase small subunit
+Product Name: terminase large subunit
+Product Name: portal protein
+Product Name: minor head protein
+Product Name: capsid morphogenesis protein
+Product Name: capsid maturation protease
+Product Name: scaffolding protein Z
+Product Name: major capsid protein
+Product Name: head-to-tail connector complex protein
+Product Name: head-to-tail connector complex protein
+Product Name: tail terminator protein
+Product Name: putative sheath terminator protein
+Product Name: tail sheath
+Product Name: tail tube protein
+Product Name: tail assembly chaperone
+Product Name: tail assembly chaperone frameshift product
+Product Name: tape measure protein
+Product Name: DNA circularization protein
+Product Name: baseplate hub protein
+Product Name: baseplate spike protein
+Product Name: baseplate wedge protein
+Product Name: baseplate wedge protein
+Product Name: baseplate wedge protein
+Product Name: tail fiber S
+Product Name: tail fiber assembly chaperone
+Product Name: tail fiber assembly protein
+Product Name: tail fiber fragment
+Product Name: serine recombinase Gin
+Product Name: translational activator Com
+Product Name: adenine modification enzyme Mom
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/test-data/out_stats_multi.txt
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/test-data/out_stats_multi.txt Fri Jun 17 12:59:24 2022 +0000
b
@@ -0,0 +1,68 @@
+---::: FILE BREAKDOWN :::---
+
+------::: Feature Count :::------
+Feature: source ::::: Count: 1
+Feature: gene ::::: Count: 58
+Feature: CDS ::::: Count: 58
+Feature: RBS ::::: Count: 57
+Feature: misc_feature ::::: Count: 7
+Feature: misc_difference ::::: Count: 1
+------::: Product Names :::------
+Product Name: repressor protein c
+Product Name: DNA binding protein ner
+Product Name: transposase
+Product Name: AAA-ATPase DNA transposition protein
+Product Name: protein kil
+Product Name: uncharacterized protein
+Product Name: uncharacterized protein
+Product Name: uncharacterized protein
+Product Name: uncharacterized protein
+Product Name: host nuclease inhibitor Gam
+Product Name: uncharacterized protein
+Product Name: uncharacterized protein
+Product Name: uncharacterized protein
+Product Name: uncharacterized protein
+Product Name: uncharacterized protein
+Product Name: hypothetical protein
+Product Name: protein GemA
+Product Name: middle operon regulator Mor
+Product Name: uncharacterized protein
+Product Name: pinholin
+Product Name: antiholin
+Product Name: transcription regulator C
+Product Name: SAR endolysin
+Product Name: i-spanin
+Product Name: o-spanin
+Product Name: uncharacterized protein
+Product Name: releasin
+Product Name: winged HTH domain-containing protein
+Product Name: terminase small subunit
+Product Name: terminase large subunit
+Product Name: portal protein
+Product Name: minor head protein
+Product Name: capsid morphogenesis protein
+Product Name: capsid maturation protease
+Product Name: scaffolding protein Z
+Product Name: major capsid protein
+Product Name: head-to-tail connector complex protein
+Product Name: head-to-tail connector complex protein
+Product Name: tail terminator protein
+Product Name: putative sheath terminator protein
+Product Name: tail sheath
+Product Name: tail tube protein
+Product Name: tail assembly chaperone
+Product Name: tail assembly chaperone frameshift product
+Product Name: tape measure protein
+Product Name: DNA circularization protein
+Product Name: baseplate hub protein
+Product Name: baseplate spike protein
+Product Name: baseplate wedge protein
+Product Name: baseplate wedge protein
+Product Name: baseplate wedge protein
+Product Name: tail fiber S
+Product Name: tail fiber assembly chaperone
+Product Name: tail fiber assembly protein
+Product Name: tail fiber fragment
+Product Name: serine recombinase Gin
+Product Name: translational activator Com
+Product Name: adenine modification enzyme Mom
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/test-data/out_stats_zoom.txt
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/test-data/out_stats_zoom.txt Fri Jun 17 12:59:24 2022 +0000
b
@@ -0,0 +1,68 @@
+---::: FILE BREAKDOWN :::---
+
+------::: Feature Count :::------
+Feature: source ::::: Count: 1
+Feature: gene ::::: Count: 58
+Feature: CDS ::::: Count: 58
+Feature: RBS ::::: Count: 57
+Feature: misc_feature ::::: Count: 7
+Feature: misc_difference ::::: Count: 1
+------::: Product Names :::------
+Product Name: repressor protein c
+Product Name: DNA binding protein ner
+Product Name: transposase
+Product Name: AAA-ATPase DNA transposition protein
+Product Name: protein kil
+Product Name: uncharacterized protein
+Product Name: uncharacterized protein
+Product Name: uncharacterized protein
+Product Name: uncharacterized protein
+Product Name: host nuclease inhibitor Gam
+Product Name: uncharacterized protein
+Product Name: uncharacterized protein
+Product Name: uncharacterized protein
+Product Name: uncharacterized protein
+Product Name: uncharacterized protein
+Product Name: hypothetical protein
+Product Name: protein GemA
+Product Name: middle operon regulator Mor
+Product Name: uncharacterized protein
+Product Name: pinholin
+Product Name: antiholin
+Product Name: transcription regulator C
+Product Name: SAR endolysin
+Product Name: i-spanin
+Product Name: o-spanin
+Product Name: uncharacterized protein
+Product Name: releasin
+Product Name: winged HTH domain-containing protein
+Product Name: terminase small subunit
+Product Name: terminase large subunit
+Product Name: portal protein
+Product Name: minor head protein
+Product Name: capsid morphogenesis protein
+Product Name: capsid maturation protease
+Product Name: scaffolding protein Z
+Product Name: major capsid protein
+Product Name: head-to-tail connector complex protein
+Product Name: head-to-tail connector complex protein
+Product Name: tail terminator protein
+Product Name: putative sheath terminator protein
+Product Name: tail sheath
+Product Name: tail tube protein
+Product Name: tail assembly chaperone
+Product Name: tail assembly chaperone frameshift product
+Product Name: tape measure protein
+Product Name: DNA circularization protein
+Product Name: baseplate hub protein
+Product Name: baseplate spike protein
+Product Name: baseplate wedge protein
+Product Name: baseplate wedge protein
+Product Name: baseplate wedge protein
+Product Name: tail fiber S
+Product Name: tail fiber assembly chaperone
+Product Name: tail fiber assembly protein
+Product Name: tail fiber fragment
+Product Name: serine recombinase Gin
+Product Name: translational activator Com
+Product Name: adenine modification enzyme Mom
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/test-data/tmp.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/test-data/tmp.svg Fri Jun 17 12:59:24 2022 +0000
b
b'@@ -0,0 +1,4404 @@\n+<?xml version="1.0" encoding="utf-8" standalone="no"?>\n+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"\n+  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n+<!-- Created with matplotlib (https://matplotlib.org/) -->\n+<svg height="244.8pt" version="1.1" viewBox="0 0 7200 244.8" width="7200pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">\n+ <metadata>\n+  <rdf:RDF xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">\n+   <cc:Work>\n+    <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>\n+    <dc:date>2020-10-19T13:19:20.299132</dc:date>\n+    <dc:format>image/svg+xml</dc:format>\n+    <dc:creator>\n+     <cc:Agent>\n+      <dc:title>Matplotlib v3.3.2, https://matplotlib.org/</dc:title>\n+     </cc:Agent>\n+    </dc:creator>\n+   </cc:Work>\n+  </rdf:RDF>\n+ </metadata>\n+ <defs>\n+  <style type="text/css">*{stroke-linecap:butt;stroke-linejoin:round;}</style>\n+ </defs>\n+ <g id="figure_1">\n+  <g id="patch_1">\n+   <path d="M 0 244.8 \n+L 7200 244.8 \n+L 7200 0 \n+L 0 0 \n+z\n+" style="fill:#ffffff;"/>\n+  </g>\n+  <g id="axes_1">\n+   <g id="line2d_1">\n+    <path clip-path="url(#p27f68a7d99)" d="M 900 203.372308 \n+L 6480 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:1.5;"/>\n+   </g>\n+   <g id="line2d_2">\n+    <path clip-path="url(#p27f68a7d99)" d="M 1481.56154 159.873231 \n+L 1481.56154 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_3">\n+    <path clip-path="url(#p27f68a7d99)" d="M 6407.312406 72.875077 \n+L 6407.312406 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_4">\n+    <path clip-path="url(#p27f68a7d99)" d="M 5324.065135 116.374154 \n+L 5324.065135 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_5">\n+    <path clip-path="url(#p27f68a7d99)" d="M 996.31904 174.372923 \n+L 996.31904 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_6">\n+    <path clip-path="url(#p27f68a7d99)" d="M 6277.073502 145.373538 \n+L 6277.073502 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_7">\n+    <path clip-path="url(#p27f68a7d99)" d="M 2815.332538 145.373538 \n+L 2815.332538 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_8">\n+    <path clip-path="url(#p27f68a7d99)" d="M 4522.039676 130.873846 \n+L 4522.039676 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_9">\n+    <path clip-path="url(#p27f68a7d99)" d="M 2193.012452 116.374154 \n+L 2193.012452 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_10">\n+    <path clip-path="url(#p27f68a7d99)" d="M 3766.973168 116.374154 \n+L 3766.973168 145.373538 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_11">\n+    <path clip-path="url(#p27f68a7d99)" d="M 4126.003987 87.374769 \n+L 4126.003987 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_12">\n+    <path clip-path="url(#p27f68a7d99)" d="M 5639.480358 145.373538 \n+L 5639.480358 174.372923 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_13">\n+    <path clip-path="url(#p27f68a7d99)" d="M 6036.199915 116.374154 \n+L 6036.199915 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_14">\n+    <path clip-path="url(#p27f68a7d99)" d="M 5951.248284 145.373538 \n+L 5951.248284 203.372308 \n+" style="fill:none;stroke:#000000;stroke'..b' 6274.926898 107.751185 \n+Q 6271.626898 107.751185 6271.626898 111.051185 \n+L 6271.626898 121.697123 \n+Q 6271.626898 124.997123 6274.926898 124.997123 \n+z\n+" style="fill:#ffffff;stroke:#808080;stroke-linejoin:miter;stroke-width:0.5;"/>\n+    </g>\n+    <!-- translational activator Com -->\n+    <g transform="translate(6274.926898 119.409466)scale(0.11 -0.11)">\n+     <use xlink:href="#DejaVuSans-116"/>\n+     <use x="39.208984" xlink:href="#DejaVuSans-114"/>\n+     <use x="80.322266" xlink:href="#DejaVuSans-97"/>\n+     <use x="141.601562" xlink:href="#DejaVuSans-110"/>\n+     <use x="204.980469" xlink:href="#DejaVuSans-115"/>\n+     <use x="257.080078" xlink:href="#DejaVuSans-108"/>\n+     <use x="284.863281" xlink:href="#DejaVuSans-97"/>\n+     <use x="346.142578" xlink:href="#DejaVuSans-116"/>\n+     <use x="385.351562" xlink:href="#DejaVuSans-105"/>\n+     <use x="413.134766" xlink:href="#DejaVuSans-111"/>\n+     <use x="474.316406" xlink:href="#DejaVuSans-110"/>\n+     <use x="537.695312" xlink:href="#DejaVuSans-97"/>\n+     <use x="598.974609" xlink:href="#DejaVuSans-108"/>\n+     <use x="626.757812" xlink:href="#DejaVuSans-32"/>\n+     <use x="658.544922" xlink:href="#DejaVuSans-97"/>\n+     <use x="719.824219" xlink:href="#DejaVuSans-99"/>\n+     <use x="774.804688" xlink:href="#DejaVuSans-116"/>\n+     <use x="814.013672" xlink:href="#DejaVuSans-105"/>\n+     <use x="841.796875" xlink:href="#DejaVuSans-118"/>\n+     <use x="900.976562" xlink:href="#DejaVuSans-97"/>\n+     <use x="962.255859" xlink:href="#DejaVuSans-116"/>\n+     <use x="1001.464844" xlink:href="#DejaVuSans-111"/>\n+     <use x="1062.646484" xlink:href="#DejaVuSans-114"/>\n+     <use x="1103.759766" xlink:href="#DejaVuSans-32"/>\n+     <use x="1135.546875" xlink:href="#DejaVuSans-67"/>\n+     <use x="1205.371094" xlink:href="#DejaVuSans-111"/>\n+     <use x="1266.552734" xlink:href="#DejaVuSans-109"/>\n+    </g>\n+   </g>\n+   <g id="text_56">\n+    <g id="patch_148">\n+     <path d="M 2379.103248 124.997123 \n+L 2426.968717 124.997123 \n+Q 2430.268717 124.997123 2430.268717 121.697123 \n+L 2430.268717 111.051185 \n+Q 2430.268717 107.751185 2426.968717 107.751185 \n+L 2379.103248 107.751185 \n+Q 2375.803248 107.751185 2375.803248 111.051185 \n+L 2375.803248 121.697123 \n+Q 2375.803248 124.997123 2379.103248 124.997123 \n+z\n+" style="fill:#ffffff;stroke:#808080;stroke-linejoin:miter;stroke-width:0.5;"/>\n+    </g>\n+    <!-- antiholin -->\n+    <g transform="translate(2379.103248 119.409466)scale(0.11 -0.11)">\n+     <use xlink:href="#DejaVuSans-97"/>\n+     <use x="61.279297" xlink:href="#DejaVuSans-110"/>\n+     <use x="124.658203" xlink:href="#DejaVuSans-116"/>\n+     <use x="163.867188" xlink:href="#DejaVuSans-105"/>\n+     <use x="191.650391" xlink:href="#DejaVuSans-104"/>\n+     <use x="255.029297" xlink:href="#DejaVuSans-111"/>\n+     <use x="316.210938" xlink:href="#DejaVuSans-108"/>\n+     <use x="343.994141" xlink:href="#DejaVuSans-105"/>\n+     <use x="371.777344" xlink:href="#DejaVuSans-110"/>\n+    </g>\n+   </g>\n+   <g id="text_57">\n+    <!-- Mu Test label -->\n+    <g transform="translate(3650.880938 23.376)scale(0.12 -0.12)">\n+     <use xlink:href="#DejaVuSans-77"/>\n+     <use x="86.279297" xlink:href="#DejaVuSans-117"/>\n+     <use x="149.658203" xlink:href="#DejaVuSans-32"/>\n+     <use x="181.445312" xlink:href="#DejaVuSans-84"/>\n+     <use x="225.529297" xlink:href="#DejaVuSans-101"/>\n+     <use x="287.052734" xlink:href="#DejaVuSans-115"/>\n+     <use x="339.152344" xlink:href="#DejaVuSans-116"/>\n+     <use x="378.361328" xlink:href="#DejaVuSans-32"/>\n+     <use x="410.148438" xlink:href="#DejaVuSans-108"/>\n+     <use x="437.931641" xlink:href="#DejaVuSans-97"/>\n+     <use x="499.210938" xlink:href="#DejaVuSans-98"/>\n+     <use x="562.6875" xlink:href="#DejaVuSans-101"/>\n+     <use x="624.210938" xlink:href="#DejaVuSans-108"/>\n+    </g>\n+   </g>\n+  </g>\n+ </g>\n+ <defs>\n+  <clipPath id="p27f68a7d99">\n+   <rect height="188.496" width="5580" x="900" y="29.376"/>\n+  </clipPath>\n+ </defs>\n+</svg>\n'
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/test-data/tmp_multi.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/test-data/tmp_multi.svg Fri Jun 17 12:59:24 2022 +0000
b
b'@@ -0,0 +1,2135 @@\n+<?xml version="1.0" encoding="utf-8" standalone="no"?>\n+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"\n+  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n+<!-- Created with matplotlib (https://matplotlib.org/) -->\n+<svg height="738.72pt" version="1.1" viewBox="0 0 720 738.72" width="720pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">\n+ <metadata>\n+  <rdf:RDF xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">\n+   <cc:Work>\n+    <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>\n+    <dc:date>2020-10-20T14:36:18.903835</dc:date>\n+    <dc:format>image/svg+xml</dc:format>\n+    <dc:creator>\n+     <cc:Agent>\n+      <dc:title>Matplotlib v3.3.2, https://matplotlib.org/</dc:title>\n+     </cc:Agent>\n+    </dc:creator>\n+   </cc:Work>\n+  </rdf:RDF>\n+ </metadata>\n+ <defs>\n+  <style type="text/css">*{stroke-linecap:butt;stroke-linejoin:round;}</style>\n+ </defs>\n+ <g id="figure_1">\n+  <g id="patch_1">\n+   <path d="M 0 738.72 \n+L 720 738.72 \n+L 720 0 \n+L 0 0 \n+z\n+" style="fill:#ffffff;"/>\n+  </g>\n+  <g id="axes_1">\n+   <g id="line2d_1">\n+    <path clip-path="url(#p2fb0868ea9)" d="M 10.341245 130.110097 \n+L 698.817811 130.110097 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:1.5;"/>\n+   </g>\n+   <g id="patch_2">\n+    <path clip-path="url(#p2fb0868ea9)" d="M 9.939834 123.110097 \n+Q 144.800726 123.110097 279.661617 123.110097 \n+L 279.661617 123.110097 \n+Q 283.860835 126.610097 288.060053 130.110097 \n+Q 283.860835 133.610097 279.661617 137.110097 \n+L 279.661617 137.110097 \n+Q 144.800726 137.110097 9.939834 137.110097 \n+L 9.939834 123.110097 \n+z\n+" style="fill:#ffffff;stroke:#000000;stroke-linecap:round;"/>\n+   </g>\n+   <g id="patch_3">\n+    <path clip-path="url(#p2fb0868ea9)" d="M 385.545491 123.110097 \n+Q 468.221286 123.110097 550.897081 123.110097 \n+L 550.897081 123.110097 \n+Q 555.098014 126.610097 559.298947 130.110097 \n+Q 555.098014 133.610097 550.897081 137.110097 \n+L 550.897081 137.110097 \n+Q 468.221286 137.110097 385.545491 137.110097 \n+L 385.545491 123.110097 \n+z\n+" style="fill:#ffffff;stroke:#000000;stroke-linecap:round;"/>\n+   </g>\n+   <g id="patch_4">\n+    <path clip-path="url(#p2fb0868ea9)" d="M 676.854916 107.282573 \n+Q 604.498444 107.282573 532.141972 107.282573 \n+L 532.141972 107.282573 \n+Q 527.943703 103.782573 523.745434 100.282573 \n+Q 527.943703 96.782573 532.141972 93.282573 \n+L 532.141972 93.282573 \n+Q 604.498444 93.282573 676.854916 93.282573 \n+L 676.854916 107.282573 \n+z\n+" style="fill:#ffffff;stroke:#000000;stroke-linecap:round;"/>\n+   </g>\n+   <g id="patch_5">\n+    <path clip-path="url(#p2fb0868ea9)" d="M 280.031841 93.282573 \n+Q 328.301838 93.282573 376.571834 93.282573 \n+L 376.571834 93.282573 \n+Q 380.771941 96.782573 384.972047 100.282573 \n+Q 380.771941 103.782573 376.571834 107.282573 \n+L 376.571834 107.282573 \n+Q 328.301838 107.282573 280.031841 107.282573 \n+L 280.031841 93.282573 \n+z\n+" style="fill:#ffffff;stroke:#000000;stroke-linecap:round;"/>\n+   </g>\n+   <g id="patch_6">\n+    <path clip-path="url(#p2fb0868ea9)" d="M 560.445834 123.110097 \n+Q 604.415292 123.110097 648.384749 123.110097 \n+L 648.384749 123.110097 \n+Q 652.584567 126.610097 656.784384 130.110097 \n+Q 652.584567 133.610097 648.384749 137.110097 \n+L 648.384749 137.110097 \n+Q 604.415292 137.110097 560.445834 137.110097 \n+L 560.445834 123.110097 \n+z\n+" style="fill:#ffffff;stroke:#000000;stroke-linecap:round;"/>\n+   </g>\n+   <g id="patch_7">\n+    <path clip-path="url(#p2fb0868ea9)" d="M 273.723959 93.282573 \n+Q 274.474924 93.282573 275.225889 93.282573 \n+L 275.225889 93.282573 \n+Q 276.195256 96.782573 277.164622 100.282573 \n+Q 276.195256 103.782573 275.225889 107.282573 \n+L 275.225889 107.282573 \n+Q 274.474924 107.282573 273.723959 107.282573 \n+L 273.723959 93.282573 \n+z\n+" style="fill:#cd5c5c;stroke:#000000;stroke-linecap:round;"/>\n+   </g>\n+   <g id="patch_8">\n+    <pat'..b'1 649.864972 499.252941 \n+L 649.864972 509.898878 \n+Q 649.864972 513.198878 653.164972 513.198878 \n+z\n+" style="fill:#ffffff;"/>\n+    </g>\n+    <!-- releasin -->\n+    <g transform="translate(653.164972 507.611222)scale(0.11 -0.11)">\n+     <use xlink:href="#DejaVuSans-114"/>\n+     <use x="38.863281" xlink:href="#DejaVuSans-101"/>\n+     <use x="100.386719" xlink:href="#DejaVuSans-108"/>\n+     <use x="128.169922" xlink:href="#DejaVuSans-101"/>\n+     <use x="189.693359" xlink:href="#DejaVuSans-97"/>\n+     <use x="250.972656" xlink:href="#DejaVuSans-115"/>\n+     <use x="303.072266" xlink:href="#DejaVuSans-105"/>\n+     <use x="330.855469" xlink:href="#DejaVuSans-110"/>\n+    </g>\n+   </g>\n+  </g>\n+  <g id="axes_5">\n+   <g id="line2d_40">\n+    <path clip-path="url(#p9e40ee8b5d)" d="M 10.341245 697.877989 \n+L 125.374061 697.877989 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:1.5;"/>\n+   </g>\n+   <g id="patch_38">\n+    <path clip-path="url(#p9e40ee8b5d)" d="M 9.939834 690.877989 \n+Q 67.855886 690.877989 125.771937 690.877989 \n+L 125.771937 690.877989 \n+Q 125.773704 694.377989 125.775472 697.877989 \n+Q 125.773704 701.377989 125.771937 704.877989 \n+L 125.771937 704.877989 \n+Q 67.855886 704.877989 9.939834 704.877989 \n+L 9.939834 690.877989 \n+z\n+" style="fill:#ffffff;stroke:#000000;stroke-linecap:round;"/>\n+   </g>\n+   <g id="matplotlib.axis_5">\n+    <g id="xtick_33">\n+     <g id="line2d_41">\n+      <g>\n+       <use style="fill:#808080;stroke:#808080;stroke-width:0.8;" x="39.472188" xlink:href="#m865a180ad8" y="710.84"/>\n+      </g>\n+     </g>\n+     <g id="text_43">\n+      <!-- 11,850 -->\n+      <g style="fill:#808080;" transform="translate(21.976875 725.438438)scale(0.1 -0.1)">\n+       <use xlink:href="#DejaVuSans-49"/>\n+       <use x="63.623047" xlink:href="#DejaVuSans-49"/>\n+       <use x="127.246094" xlink:href="#DejaVuSans-44"/>\n+       <use x="159.033203" xlink:href="#DejaVuSans-56"/>\n+       <use x="222.65625" xlink:href="#DejaVuSans-53"/>\n+       <use x="286.279297" xlink:href="#DejaVuSans-48"/>\n+      </g>\n+     </g>\n+    </g>\n+    <g id="xtick_34">\n+     <g id="line2d_42">\n+      <g>\n+       <use style="fill:#808080;stroke:#808080;stroke-width:0.8;" x="125.48875" xlink:href="#m865a180ad8" y="710.84"/>\n+      </g>\n+     </g>\n+     <g id="text_44">\n+      <!-- 12,000 -->\n+      <g style="fill:#808080;" transform="translate(107.993438 725.438438)scale(0.1 -0.1)">\n+       <use xlink:href="#DejaVuSans-49"/>\n+       <use x="63.623047" xlink:href="#DejaVuSans-50"/>\n+       <use x="127.246094" xlink:href="#DejaVuSans-44"/>\n+       <use x="159.033203" xlink:href="#DejaVuSans-48"/>\n+       <use x="222.65625" xlink:href="#DejaVuSans-48"/>\n+       <use x="286.279297" xlink:href="#DejaVuSans-48"/>\n+      </g>\n+     </g>\n+    </g>\n+   </g>\n+   <g id="text_45">\n+    <!-- releasin -->\n+    <g transform="translate(46.174763 700.913301)scale(0.11 -0.11)">\n+     <use xlink:href="#DejaVuSans-114"/>\n+     <use x="38.863281" xlink:href="#DejaVuSans-101"/>\n+     <use x="100.386719" xlink:href="#DejaVuSans-108"/>\n+     <use x="128.169922" xlink:href="#DejaVuSans-101"/>\n+     <use x="189.693359" xlink:href="#DejaVuSans-97"/>\n+     <use x="250.972656" xlink:href="#DejaVuSans-115"/>\n+     <use x="303.072266" xlink:href="#DejaVuSans-105"/>\n+     <use x="330.855469" xlink:href="#DejaVuSans-110"/>\n+    </g>\n+   </g>\n+  </g>\n+ </g>\n+ <defs>\n+  <clipPath id="p2fb0868ea9">\n+   <rect height="134.22386" width="688.1325" x="10.8" y="10.8"/>\n+  </clipPath>\n+  <clipPath id="pe825af461e">\n+   <rect height="113.574035" width="688.1325" x="10.8" y="172.90386"/>\n+  </clipPath>\n+  <clipPath id="p4d11cbafed">\n+   <rect height="113.574035" width="688.1325" x="10.8" y="314.357895"/>\n+  </clipPath>\n+  <clipPath id="paf4c887adf">\n+   <rect height="113.574035" width="688.1325" x="10.8" y="455.81193"/>\n+  </clipPath>\n+  <clipPath id="p9e40ee8b5d">\n+   <rect height="113.574035" width="688.1325" x="10.8" y="597.265965"/>\n+  </clipPath>\n+ </defs>\n+</svg>\n'
b
diff -r 000000000000 -r 621754dd31f8 cpt_linear_genome_plot/test-data/tmp_zoom.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cpt_linear_genome_plot/test-data/tmp_zoom.svg Fri Jun 17 12:59:24 2022 +0000
b
b'@@ -0,0 +1,1688 @@\n+<?xml version="1.0" encoding="utf-8" standalone="no"?>\n+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"\n+  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n+<!-- Created with matplotlib (https://matplotlib.org/) -->\n+<svg height="244.8pt" version="1.1" viewBox="0 0 720 244.8" width="720pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">\n+ <metadata>\n+  <rdf:RDF xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">\n+   <cc:Work>\n+    <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>\n+    <dc:date>2020-10-19T13:19:51.116707</dc:date>\n+    <dc:format>image/svg+xml</dc:format>\n+    <dc:creator>\n+     <cc:Agent>\n+      <dc:title>Matplotlib v3.3.2, https://matplotlib.org/</dc:title>\n+     </cc:Agent>\n+    </dc:creator>\n+   </cc:Work>\n+  </rdf:RDF>\n+ </metadata>\n+ <defs>\n+  <style type="text/css">*{stroke-linecap:butt;stroke-linejoin:round;}</style>\n+ </defs>\n+ <g id="figure_1">\n+  <g id="patch_1">\n+   <path d="M 0 244.8 \n+L 720 244.8 \n+L 720 0 \n+L 0 0 \n+z\n+" style="fill:#ffffff;"/>\n+  </g>\n+  <g id="axes_1">\n+   <g id="line2d_1">\n+    <path clip-path="url(#pf91a91e396)" d="M 90 203.372308 \n+L 647.977684 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:1.5;"/>\n+   </g>\n+   <g id="line2d_2">\n+    <path clip-path="url(#pf91a91e396)" d="M 258.299352 116.374154 \n+L 258.299352 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_3">\n+    <path clip-path="url(#pf91a91e396)" d="M 506.848584 145.373538 \n+L 506.848584 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_4">\n+    <path clip-path="url(#pf91a91e396)" d="M 444.139418 116.374154 \n+L 444.139418 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_5">\n+    <path clip-path="url(#pf91a91e396)" d="M 310.408215 145.373538 \n+L 310.408215 174.372923 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_6">\n+    <path clip-path="url(#pf91a91e396)" d="M 555.4984 87.374769 \n+L 555.4984 174.372923 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_7">\n+    <path clip-path="url(#pf91a91e396)" d="M 384.554551 58.375385 \n+L 384.554551 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_8">\n+    <path clip-path="url(#pf91a91e396)" d="M 580.548592 116.374154 \n+L 580.548592 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_9">\n+    <path clip-path="url(#pf91a91e396)" d="M 632.155335 145.373538 \n+L 632.155335 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="line2d_10">\n+    <path clip-path="url(#pf91a91e396)" d="M 412.505879 87.374769 \n+L 412.505879 203.372308 \n+" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.5;"/>\n+   </g>\n+   <g id="patch_2">\n+    <path clip-path="url(#pf91a91e396)" d="M 227.50268 196.372308 \n+Q 254.098268 196.372308 280.693856 196.372308 \n+L 280.693856 196.372308 \n+Q 284.89494 199.872308 289.096025 203.372308 \n+Q 284.89494 206.872308 280.693856 210.372308 \n+L 280.693856 210.372308 \n+Q 254.098268 210.372308 227.50268 210.372308 \n+L 227.50268 196.372308 \n+z\n+" style="fill:#ffffff;stroke:#000000;stroke-linecap:round;"/>\n+   </g>\n+   <g id="patch_3">\n+    <path clip-path="url(#pf91a91e396)" d="M 477.893017 196.372308 \n+Q 502.647694 196.372308 527.402371 196.372308 \n+L 527.402371 196.372308 \n+Q 531.603261 199.872308 535.804151 203.372308 \n+Q 531.603261 206.872308 527.402371 210.372308 \n+L 527.402371 210.372308 \n+Q 502.647694 210.372308 477.893017 210.372308 \n+L 477.893017 196.372308 \n+z\n+" st'..b'96507 \n+Q 657.138226 153.996507 657.138226 150.696507 \n+L 657.138226 140.05057 \n+Q 657.138226 136.75057 653.838226 136.75057 \n+L 610.472445 136.75057 \n+Q 607.172445 136.75057 607.172445 140.05057 \n+L 607.172445 150.696507 \n+Q 607.172445 153.996507 610.472445 153.996507 \n+z\n+" style="fill:#ffffff;stroke:#808080;stroke-linejoin:miter;stroke-width:0.5;"/>\n+    </g>\n+    <!-- releasin -->\n+    <g transform="translate(610.472445 148.408851)scale(0.11 -0.11)">\n+     <use xlink:href="#DejaVuSans-114"/>\n+     <use x="38.863281" xlink:href="#DejaVuSans-101"/>\n+     <use x="100.386719" xlink:href="#DejaVuSans-108"/>\n+     <use x="128.169922" xlink:href="#DejaVuSans-101"/>\n+     <use x="189.693359" xlink:href="#DejaVuSans-97"/>\n+     <use x="250.972656" xlink:href="#DejaVuSans-115"/>\n+     <use x="303.072266" xlink:href="#DejaVuSans-105"/>\n+     <use x="330.855469" xlink:href="#DejaVuSans-110"/>\n+    </g>\n+   </g>\n+   <g id="text_18">\n+    <g id="patch_41">\n+     <path d="M 388.573145 95.997738 \n+L 436.438613 95.997738 \n+Q 439.738613 95.997738 439.738613 92.697738 \n+L 439.738613 82.0518 \n+Q 439.738613 78.7518 436.438613 78.7518 \n+L 388.573145 78.7518 \n+Q 385.273145 78.7518 385.273145 82.0518 \n+L 385.273145 92.697738 \n+Q 385.273145 95.997738 388.573145 95.997738 \n+z\n+" style="fill:#ffffff;stroke:#808080;stroke-linejoin:miter;stroke-width:0.5;"/>\n+    </g>\n+    <!-- antiholin -->\n+    <g transform="translate(388.573145 90.410082)scale(0.11 -0.11)">\n+     <use xlink:href="#DejaVuSans-97"/>\n+     <use x="61.279297" xlink:href="#DejaVuSans-110"/>\n+     <use x="124.658203" xlink:href="#DejaVuSans-116"/>\n+     <use x="163.867188" xlink:href="#DejaVuSans-105"/>\n+     <use x="191.650391" xlink:href="#DejaVuSans-104"/>\n+     <use x="255.029297" xlink:href="#DejaVuSans-111"/>\n+     <use x="316.210938" xlink:href="#DejaVuSans-108"/>\n+     <use x="343.994141" xlink:href="#DejaVuSans-105"/>\n+     <use x="371.777344" xlink:href="#DejaVuSans-110"/>\n+    </g>\n+   </g>\n+   <g id="text_19">\n+    <!-- Mu Test label -->\n+    <g transform="translate(329.880938 23.376)scale(0.12 -0.12)">\n+     <defs>\n+      <path d="M -0.296875 72.90625 \n+L 61.375 72.90625 \n+L 61.375 64.59375 \n+L 35.5 64.59375 \n+L 35.5 0 \n+L 25.59375 0 \n+L 25.59375 64.59375 \n+L -0.296875 64.59375 \n+z\n+" id="DejaVuSans-84"/>\n+      <path d="M 48.6875 27.296875 \n+Q 48.6875 37.203125 44.609375 42.84375 \n+Q 40.53125 48.484375 33.40625 48.484375 \n+Q 26.265625 48.484375 22.1875 42.84375 \n+Q 18.109375 37.203125 18.109375 27.296875 \n+Q 18.109375 17.390625 22.1875 11.75 \n+Q 26.265625 6.109375 33.40625 6.109375 \n+Q 40.53125 6.109375 44.609375 11.75 \n+Q 48.6875 17.390625 48.6875 27.296875 \n+z\n+M 18.109375 46.390625 \n+Q 20.953125 51.265625 25.265625 53.625 \n+Q 29.59375 56 35.59375 56 \n+Q 45.5625 56 51.78125 48.09375 \n+Q 58.015625 40.1875 58.015625 27.296875 \n+Q 58.015625 14.40625 51.78125 6.484375 \n+Q 45.5625 -1.421875 35.59375 -1.421875 \n+Q 29.59375 -1.421875 25.265625 0.953125 \n+Q 20.953125 3.328125 18.109375 8.203125 \n+L 18.109375 0 \n+L 9.078125 0 \n+L 9.078125 75.984375 \n+L 18.109375 75.984375 \n+z\n+" id="DejaVuSans-98"/>\n+     </defs>\n+     <use xlink:href="#DejaVuSans-77"/>\n+     <use x="86.279297" xlink:href="#DejaVuSans-117"/>\n+     <use x="149.658203" xlink:href="#DejaVuSans-32"/>\n+     <use x="181.445312" xlink:href="#DejaVuSans-84"/>\n+     <use x="225.529297" xlink:href="#DejaVuSans-101"/>\n+     <use x="287.052734" xlink:href="#DejaVuSans-115"/>\n+     <use x="339.152344" xlink:href="#DejaVuSans-116"/>\n+     <use x="378.361328" xlink:href="#DejaVuSans-32"/>\n+     <use x="410.148438" xlink:href="#DejaVuSans-108"/>\n+     <use x="437.931641" xlink:href="#DejaVuSans-97"/>\n+     <use x="499.210938" xlink:href="#DejaVuSans-98"/>\n+     <use x="562.6875" xlink:href="#DejaVuSans-101"/>\n+     <use x="624.210938" xlink:href="#DejaVuSans-108"/>\n+    </g>\n+   </g>\n+  </g>\n+ </g>\n+ <defs>\n+  <clipPath id="pf91a91e396">\n+   <rect height="188.496" width="558" x="90" y="29.376"/>\n+  </clipPath>\n+ </defs>\n+</svg>\n'