Package csb :: Package bio :: Package io :: Module svg
[frames] | no frames]

Source Code for Module csb.bio.io.svg

  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   
11 -class SSCartoonBuilder(object):
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 # this is to compensate for antialiasing and rounded caps 55 self._height = float(height) 56 self._x = 0 57 self._y = 0 58 59 self._svg = ''
60
61 - def build(self):
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
106 - def _format(self, path):
107 108 formatted = [] 109 110 for i in path: 111 112 if i == -0: 113 i = 0 114 115 if isinstance(i, float): 116 i = round(i, ndigits=7) 117 if i == -0: 118 i = 0 119 formatted.append('{0:.7f}'.format(i)) 120 else: 121 formatted.append(str(i)) 122 123 return ' '.join(formatted)
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 # quadratic bezier control points: sine curve's min, max and inflection points (0, 1, 0, -1, 0, 1 ...) 138 # one arc is the curve from 0 to pi/2 139 if i < arcs: 140 # inner arc 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 # last arc; stretch it to make the helix pixel-precise, ending also at y=0 147 # also the number of arcs/controlpoints must be even, otherwise the path is broken 148 149 # remaining pixels on x 150 remainder = helix_end - self._x 151 152 if i % 2 == 0: 153 # even number of arcs, just extend the last arc with the remainder 154 self._x += remainder 155 self._y = 0 156 path.append(self._x) 157 path.append(self._y) 158 else: 159 # odd number of arcs 160 161 # 1) keep this arc at the expected y, but stretch it half of the x remainder 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 # 2) append a final arc, ending at [helix_end, 0] 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 # first control point 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 # second 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