1 """
2 Build SVG diagrams from various csb objects.
3 """
4
5 import math
6 import csb.core
7
8 from csb.bio.structure import SecondaryStructure, SecStructures
9
10
12 """
13 Creates 2D vector diagrams from L{SecondaryStructure} objects.
14
15 @param ss: source secondary structure (either a SS string or a SS object)
16 @type ss: str or L{SecondaryStructure}
17 @param width: output width of the diagram in pixels
18 @type width: int
19 @param height: output height of the diagram in pixels
20 @type height: int
21
22 @param thickness: stroke-width (2px by default)
23 @param helix: SVG color for helicies (red by default)
24 @param strand: SVG color for strands (blue by default)
25 @param coil: SVG color for coils (orange by default)
26 @param gap: SVG color for gaps (grey by default)
27 @param cap: stroke-linecap (round by default)
28 """
29
30 - def __init__(self, ss, width, height, thickness='2px',
31 helix='#C24641', strand='#6698FF', coil='#FF8C00', gap='#E0E0E0',
32 cap='round'):
33
34 if ss:
35 if isinstance(ss, csb.core.string):
36 self._ss = SecondaryStructure(ss)
37 else:
38 self._ss = ss.clone()
39 self._ss.to_three_state()
40 self._residues = sum(e.length for e in self._ss)
41 if self._residues == 0:
42 raise ValueError('Zero-length secondary structure')
43 else:
44 raise ValueError('Invalid secondary structure')
45
46 self.thickness = thickness
47 self.helixcolor = helix
48 self.strandcolor = strand
49 self.coilcolor = coil
50 self.gapcolor = gap
51 self.cap = cap
52
53 self._realwidth = float(width)
54 self._width = self._realwidth - 2
55 self._height = float(height)
56 self._x = 0
57 self._y = 0
58
59 self._svg = ''
60
62 """
63 Build a SVG image using the current size and color settings.
64
65 @return: SVG diagram
66 @rtype: str (SVG document)
67 """
68
69 self._x = 0
70 self._y = 0
71 self._svg = [r'''<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
72 width="{0._realwidth}" height="{0._height}">
73
74 <g transform="translate(0, {1})">'''.format(self, self._height / 2.0)]
75
76 for e in self._ss:
77
78 if e.type == SecStructures.Helix:
79 cartoon = self._helix(e.length)
80 color = self.helixcolor
81
82 elif e.type == SecStructures.Strand:
83 cartoon = self._strand(e.length)
84 color = self.strandcolor
85
86 elif e.type == SecStructures.Coil:
87 cartoon = self._coil(e.length)
88 color = self.coilcolor
89
90 elif e.type == SecStructures.Gap:
91 cartoon = self._gap(e.length)
92 color = self.gapcolor
93
94 else:
95 assert False, "Unhandled SS Type: {0!r}".format(e.type)
96
97 path = r''' <path fill="none" stroke="{0}" stroke-width="{1.thickness}" stroke-linecap="{1.cap}"
98 d="{2}" />'''.format(color, self, cartoon)
99
100 self._svg.append(path)
101
102 self._svg.append(' </g>')
103 self._svg.append('</svg>')
104 return '\n'.join(self._svg)
105
124
125 - def _helix(self, length, arc_width=3.0):
126
127 if length < 1:
128 return ''
129
130 helix_width = float(length) * self._width / self._residues
131 helix_end = self._x + helix_width
132 path = ['M', self._x, self._y, 'Q']
133
134 arcs = int(helix_width / arc_width)
135 for i in range(1, arcs + 1):
136
137
138
139 if i < arcs:
140
141 self._x += arc_width
142 self._y = math.sin(math.pi * i / 2) * (self._height / 2.0)
143 path.append(self._x)
144 path.append(self._y)
145 else:
146
147
148
149
150 remainder = helix_end - self._x
151
152 if i % 2 == 0:
153
154 self._x += remainder
155 self._y = 0
156 path.append(self._x)
157 path.append(self._y)
158 else:
159
160
161
162 self._x += remainder / 2.0
163 self._y = math.sin(math.pi * i / 2) * (self._height / 2.0)
164 path.append(self._x)
165 path.append(self._y)
166
167
168 self._x += remainder / 2.0
169 self._y = 0
170 path.append(self._x)
171 path.append(self._y)
172
173 return self._format(path)
174
175 - def _strand(self, length, arrow_width=3.0):
176
177 offset = 1.0
178 strand_width = float(length) * self._width / self._residues
179 path = ['M', self._x, self._y, 'H']
180
181 self._x += strand_width
182 path.append(self._x)
183
184 if offset < arrow_width < strand_width:
185 arrow_start = self._x - offset - arrow_width
186 path.extend(['M', self._x - offset, self._y])
187 path.extend(['L', arrow_start, self._y + self._height / 9])
188 path.extend(['L', arrow_start, self._y - self._height / 9])
189 path.extend(['L', self._x - offset, self._y])
190
191 return self._format(path)
192
193 - def _coil(self, length):
194
195 coil_width = float(length) * self._width / self._residues
196 path = ['M', self._x, self._y, 'Q']
197
198
199 self._x += coil_width / 2.0
200 self._y = self._height / -2.0
201 path.append(self._x)
202 path.append(self._y)
203
204
205 self._x += coil_width / 2.0
206 self._y = 0
207 path.append(self._x)
208 path.append(self._y)
209
210 return self._format(path)
211
212 - def _gap(self, length):
213
214 return self._strand(length, arrow_width=0)
215