# pylint: disable=E0401,C0103,R0912,R0914,R0915,R1702
"""
Synbiopython (c) Global BioFoundry Alliance 2020
Synbiopython is licensed under the MIT License.
This module is to implement the simple plotting of the gene circuit using the modified code
from quickplot.py in DNAplotlib library
# added quickplot style for writing Regulations.
# added new Regulation type: Derepression
Reference:
https://github.com/VoigtLab/dnaplotlib
Install:
pip install dnaplotlib
"""
import re
import matplotlib
import matplotlib.pyplot as plt
import dnaplotlib as dpl
[docs]class SimpleDNAplot:
"""Class to generate SBOL visual compliant gene circuit diagram.
Regulation type: Connection, Activation, Repression, Derepression
Each of the part will be numbered sequentially based on the part type/module
from left to right starting from index 0.
Example: p0-r0-c0-t0-p1-r1-c1-t1 for two modules with promoter, RBS, coding
region, and terminator.
Input = "p.pTet r.rbs34 c.orange.LacI t p.pLac r.rbs32 c.green.TetR t"
Regulations = "c0->p1.Repression c1->p0.Repression"
# The default color is black if color is not specified
"""
[docs] def set_circuit_design(self, Input, Regulation=None):
"""Generate the dictionary list containing circuit design information.
:param Input: a string containing the individual type of part, followed by color
and name separated by a space.
:type Input: str
:param Regulation: a string containing the from_part to the to_part connected
by an arrow. Type of interaction is specified after the topart followed
by the color. Default color of black is used is not specified.
:type Regulation: str, optional
:return: The part information and Regulations stored in the form of list of dictionaries.
:rtype: list of dict
"""
# Types mapping
types = {}
types["p"] = "Promoter"
types["i"] = "Ribozyme"
types["r"] = "RBS"
types["c"] = "CDS"
types["t"] = "Terminator"
types["s"] = "Spacer"
types["="] = "Scar"
types["o"] = "Origin"
types["es"] = "EmptySpace"
# Colours mapping
colors = {}
colors["black"] = (0.00, 0.00, 0.00)
colors["gray"] = (0.60, 0.60, 0.60)
colors["red"] = (0.89, 0.10, 0.11)
colors["orange"] = (1.00, 0.50, 0.00)
colors["yellow"] = (1.00, 1.00, 0.00)
colors["white"] = (1.00, 1.00, 1.00)
colors["green"] = (0.20, 0.63, 0.17)
colors["blue"] = (0.12, 0.47, 0.71)
colors["purple"] = (0.42, 0.24, 0.60)
colors["lightred"] = (0.98, 0.60, 0.60)
colors["lightorange"] = (0.99, 0.75, 0.44)
colors["lightyellow"] = (1.00, 1.00, 0.60)
colors["lightgreen"] = (0.70, 0.87, 0.54)
colors["lightblue"] = (0.65, 0.81, 0.89)
colors["lightpurple"] = (0.79, 0.70, 0.84)
# Generate the parts list from the arguments
part_list = []
part_length = []
part_idx = 1
for el in Input.split(" "):
if el != "":
part_parts = el.split(".")
part_label = ""
part_rgb = (0, 0, 0)
if len(part_parts) >= 1:
part_short_type = part_parts[0]
part_fwd = True
if part_short_type[0] == "-":
part_fwd = False
part_short_type = el[1]
if part_short_type in types.keys():
part_type = types[part_short_type]
else:
print("Error! Please specify the correct part type!")
part_label_yoffset = -5
part_label_color = "black"
part_label_size = 9
part_label_style = "normal"
if part_parts[0][0] == "-":
part_label_yoffset = 5
if len(part_parts) >= 2:
part_color = part_parts[1]
if part_color in colors.keys():
part_rgb = colors[part_color]
else:
part_label = part_parts[1]
if len(part_parts) >= 3:
part_label = part_parts[2]
elif part_label == "":
part_label = ""
if part_short_type == "c":
part_label_yoffset = 0
part_label_color = "white"
part_label_size = 10
part_label_style = "italic"
elif part_short_type == "o":
part_label_yoffset = -10
part_label_color = "black"
part_label_size = 8
part_label_style = "normal"
elif part_short_type in ("p", "r"):
part_label_yoffset = -5
part_label_color = "black"
part_label_size = 8
part_label_style = "normal"
else:
part_label_yoffset = -5
part_label_color = "black"
part_label_size = 8
part_label_style = "normal"
if part_parts[0][0] == "-":
part_label_yoffset = 5
part_list.append(
{
"name": str(part_idx),
"type": part_type,
"fwd": part_fwd,
"opts": {
"color": part_rgb,
"label": part_label,
"label_y_offset": part_label_yoffset,
"label_color": part_label_color,
"label_size": part_label_size,
"label_style": part_label_style,
},
}
)
part_length.append(part_short_type)
part_idx = part_idx + 1
# update the part_length to include the numbering to be used for regulations
for i in types:
n = 0
for a, _ in enumerate(part_length):
if part_length[a] is i:
part_length[a] = i + str(n)
n += 1
print("part_length", part_length)
# Update the Regulations from the arguments
Regulations = []
if Regulation is not None:
Reg_list = Regulation.split(" ")
for r, _ in enumerate(Reg_list):
first_yoffset = 0
second_yoffset = 0
arc_height_const = 17
arc_height_spacing = 4
arc_height_start = 13
if Reg_list[r] != "":
reg_parts = Reg_list[r].split(".")
if "Derepression" in reg_parts[1]:
first_yoffset = -5
second_yoffset = 4.5
reg_type = "Repression"
elif "Activation2" in reg_parts[1]:
first_yoffset = -5
second_yoffset = 4.5
reg_type = "Activation"
else:
second_yoffset = 0
reg_type = reg_parts[1]
tofr_part = reg_parts[0].split("->")
fr_part = tofr_part[0]
to_part = tofr_part[1]
fr_part_len = self.compute_dnalength(fr_part, part_length)
to_part_len = self.compute_dnalength(to_part, part_length)
fwd = (fr_part_len <= to_part_len) or (
"Derepression" in reg_parts[1]
)
if len(reg_parts) > 2:
reg_color = reg_parts[2]
if reg_color in colors.keys():
reg_rgb = colors[reg_color]
else:
reg_rgb = colors["black"]
arc_height_spacing = arc_height_spacing + first_yoffset
arc_height_const = arc_height_const + second_yoffset
arc_height_start = arc_height_start + second_yoffset
arc_height_end = arc_height_start * 1
arc_height = arc_height_const + arc_height_spacing
Regulations.append(
{
"type": reg_type,
"from_part": {"start": fr_part_len, "end": fr_part_len},
"to_part": {
"start": to_part_len,
"end": to_part_len,
"fwd": fwd,
},
"opts": {
"color": reg_rgb,
"linewidth": 1.5,
"arc_height_const": arc_height_const,
"arc_height_spacing": arc_height_spacing,
"arc_height_start": arc_height_start,
"arc_height_end": arc_height_end,
"arc_height": arc_height,
"arrowhead_length": 2,
},
}
)
# Modify the plot of the last Repression before Derepression
indices = [i for i, s in enumerate(Reg_list) if "Derepression" in s]
indices2 = [i for i, s in enumerate(Reg_list) if "Activation2" in s]
Rep = []
for i, _ in enumerate(indices):
ind = []
for j in range(indices[i]):
if "Repression" in Reg_list[j]:
ind.append(j)
Rep.append({str(indices[i]): ind})
Act = []
for i, _ in enumerate(indices2):
ind = []
for j in range(indices2[i]):
if "Activation" in Reg_list[j]:
ind.append(j)
Act.append({str(indices2[i]): ind})
arc_height_const = 17
arc_height_spacing = 4
first_yoffset = -5
if len(Rep) != 0:
for i in Rep:
arc_height_spacing = arc_height_spacing + first_yoffset
arc_height = arc_height_const + arc_height_spacing
Regulations[list(i.values())[0][-1]]["opts"][
"arc_height_spacing"
] = arc_height_spacing
Regulations[list(i.values())[0][-1]]["opts"][
"arc_height"
] = arc_height
arc_height_const = 17
arc_height_spacing = 4
first_yoffset = -5
if len(Act) != 0:
for i in Act:
arc_height_spacing = arc_height_spacing + first_yoffset
arc_height = arc_height_const + arc_height_spacing
Regulations[list(i.values())[0][-1]]["opts"][
"arc_height_spacing"
] = arc_height_spacing
Regulations[list(i.values())[0][-1]]["opts"][
"arc_height"
] = arc_height
else:
Regulations = None
return part_list, Regulations
[docs] @staticmethod
def compute_dnalength(part, part_length):
"""Calculate the position for the to_part or from_part for plotting arrows
automatically.
:param part: the to_part or from_part
:type part: str
:param part_length: all the parts with sequential numbering starting from 0
:type part_length: list of str
:return: dna length
:rtype: float
"""
# dna length
dnalen = {}
dnalen["p"] = 14.0
dnalen["i"] = 9.0
dnalen["r"] = 14.0
dnalen["c"] = 32.0
dnalen["t"] = 12.0
dnalen["s"] = 10.0
dnalen["="] = 7.0
dnalen["o"] = 17.0
dnalen["es"] = 20.0
dnalength = 0
ind = part_length.index(part)
for i in range(ind + 1):
p = re.sub(r"\d", "", part_length[i])
if p in dnalen.keys():
if i is ind:
dnalength += 0.5 * dnalen[p]
else:
dnalength += dnalen[p]
return dnalength
[docs] def plot_circuit(self, Input, Regulation=None, savefig=None):
"""Plot the SBOL-compliant gene circuit figure.
:param Input: Input design from users
:type Input: str
:param Regulation: Regulation strings from users
:type Regulation: str
:param savefig: path to store the output figure
:type savefig: str, optional
:return: max dna design length and export the gene circuit figure
:rtype: float
"""
# matplotlib.use("Qt5Agg")
matplotlib.use("Agg")
dr = dpl.DNARenderer(linewidth=1.5)
# Process the arguments
design, Regulations = self.set_circuit_design(Input, Regulation)
reg_renderers = dr.std_reg_renderers()
part_renderers = dr.SBOL_part_renderers()
# Generate the figure
fig = plt.figure(figsize=(1.0, 1.0), dpi=100)
ax = fig.add_subplot(1, 1, 1)
# Plot the design
dna_start, dna_end = dr.renderDNA(
ax, design, part_renderers, Regulations, reg_renderers
)
max_dna_len = dna_end - dna_start
# print("Max Dna length: ", max_dna_len)
# Format the axis
ax.set_xticks([])
ax.set_yticks([])
# Set bounds
ax.set_xlim([(-0.0 * max_dna_len), max_dna_len + (0.0 * max_dna_len)])
ax.set_ylim([-25, 25])
ax.set_aspect("equal")
ax.set_axis_off()
# Update the size of the figure to fit the constructs drawn
fig_x_dim = max_dna_len / 30
# print("x_dim: ", fig_x_dim)
if fig_x_dim < 1.0:
fig_x_dim = 1.0
fig_y_dim = 1.8
plt.gcf().set_size_inches(fig_x_dim, fig_y_dim, forward=True)
# Save the figure
plt.tight_layout()
if savefig is not None:
fig.savefig(savefig, transparent=True, dpi=300)
# plt.show()
return max_dna_len, fig