Package Bio :: Package Graphics :: Module Distribution
[hide private]
[frames] | no frames]

Source Code for Module Bio.Graphics.Distribution

  1  """Display information distributed across a Chromosome-like object. 
  2   
  3  These classes are meant to show the distribution of some kind of information 
  4  as it changes across any kind of segment. It was designed with chromosome 
  5  distributions in mind, but could also work for chromosome regions, BAC clones 
  6  or anything similar. 
  7   
  8  Reportlab is used for producing the graphical output. 
  9  """ 
 10  # standard library 
 11  import math 
 12   
 13  # reportlab 
 14  from reportlab.pdfgen import canvas 
 15  from reportlab.lib.pagesizes import letter 
 16  from reportlab.lib.units import inch 
 17  from reportlab.lib import colors 
 18   
 19  from reportlab.graphics.shapes import Drawing, String 
 20  from reportlab.graphics.charts.barcharts import VerticalBarChart 
 21  from reportlab.graphics.charts.barcharts import BarChartProperties 
 22  from reportlab.graphics.widgetbase import TypedPropertyCollection 
 23  from reportlab.graphics import renderPDF, renderPS 
 24   
 25  from Bio.Graphics import _write 
 26   
27 -class DistributionPage:
28 """Display a grouping of distributions on a page. 29 30 This organizes Distributions, and will display them nicely 31 on a single page. 32 """
33 - def __init__(self, output_format = 'pdf'):
34 self.distributions = [] 35 36 # customizable attributes 37 self.number_of_columns = 1 38 self.page_size = letter 39 self.title_size = 20 40 41 self.output_format = output_format
42
43 - def draw(self, output_file, title):
44 """Draw out the distribution information. 45 46 Arguments: 47 48 o output_file - The name of the file to output the information to, 49 or a handle to write to. 50 51 o title - A title to display on the graphic. 52 """ 53 width, height = self.page_size 54 cur_drawing = Drawing(width, height) 55 56 self._draw_title(cur_drawing, title, width, height) 57 58 # calculate the x and y position changes for each distribution 59 cur_x_pos = inch * .5 60 end_x_pos = width - inch * .5 61 cur_y_pos = height - 1.5 * inch 62 end_y_pos = .5 * inch 63 x_pos_change = ((end_x_pos - cur_x_pos) / 64 float(self.number_of_columns)) 65 num_y_rows = math.ceil(float(len(self.distributions)) 66 / float(self.number_of_columns)) 67 y_pos_change = (cur_y_pos - end_y_pos) / num_y_rows 68 69 self._draw_distributions(cur_drawing, cur_x_pos, x_pos_change, 70 cur_y_pos, y_pos_change, num_y_rows) 71 self._draw_legend(cur_drawing, 2.5 * inch, width) 72 73 return _write(cur_drawing, output_file, self.output_format)
74
75 - def _draw_title(self, cur_drawing, title, width, height):
76 """Add the title of the figure to the drawing. 77 """ 78 title_string = String(width / 2, height - inch, title) 79 title_string.fontName = 'Helvetica-Bold' 80 title_string.fontSize = self.title_size 81 title_string.textAnchor = "middle" 82 83 cur_drawing.add(title_string)
84
85 - def _draw_distributions(self, cur_drawing, start_x_pos, x_pos_change, 86 start_y_pos, y_pos_change, num_y_drawings):
87 """Draw all of the distributions on the page. 88 89 Arguments: 90 91 o cur_drawing - The drawing we are working with. 92 93 o start_x_pos - The x position on the page to start drawing at. 94 95 o x_pos_change - The change in x position between each figure. 96 97 o start_y_pos - The y position on the page to start drawing at. 98 99 o y_pos_change - The change in y position between each figure. 100 101 o num_y_drawings - The number of drawings we'll have in the y 102 (up/down) direction. 103 """ 104 for y_drawing in range(int(num_y_drawings)): 105 # if we are on the last y position, we may not be able 106 # to fill all of the x columns 107 if ((y_drawing + 1) * self.number_of_columns > 108 len(self.distributions)): 109 num_x_drawings = len(self.distributions) - \ 110 y_drawing * self.number_of_columns 111 else: 112 num_x_drawings = self.number_of_columns 113 for x_drawing in range(num_x_drawings): 114 dist_num = y_drawing * self.number_of_columns + x_drawing 115 cur_distribution = self.distributions[dist_num] 116 117 # find the x and y boundaries of the distribution 118 x_pos = start_x_pos + x_drawing * x_pos_change 119 end_x_pos = x_pos + x_pos_change 120 end_y_pos = start_y_pos - y_drawing * y_pos_change 121 y_pos = end_y_pos - y_pos_change 122 123 # draw the distribution 124 cur_distribution.draw(cur_drawing, x_pos, y_pos, end_x_pos, 125 end_y_pos)
126
127 - def _draw_legend(self, cur_drawing, start_y, width):
128 """Add a legend to the figure. 129 130 Subclasses can implement to provide a specialized legend. 131 """ 132 pass
133
134 -class BarChartDistribution:
135 """Display the distribution of values as a bunch of bars. 136 """
137 - def __init__(self, display_info = []):
138 """Initialize a Bar Chart display of distribution info. 139 140 Class attributes: 141 142 o display_info - the information to be displayed in the distribution. 143 This should be ordered as a list of lists, where each internal list 144 is a data set to display in the bar chart. 145 """ 146 self.display_info = display_info 147 148 self.x_axis_title = "" 149 self.y_axis_title = "" 150 self.chart_title = "" 151 self.chart_title_size = 10 152 153 self.padding_percent = 0.15
154
155 - def draw(self, cur_drawing, start_x, start_y, end_x, end_y):
156 """Draw a bar chart with the info in the specified range. 157 """ 158 bar_chart = VerticalBarChart() 159 if self.chart_title: 160 self._draw_title(cur_drawing, self.chart_title, 161 start_x, start_y, end_x, end_y) 162 # set the position of the bar chart 163 x_start, x_end, y_start, y_end = \ 164 self._determine_position(start_x, start_y, end_x, end_y) 165 166 bar_chart.x = x_start 167 bar_chart.y = y_start 168 bar_chart.width = abs(x_start - x_end) 169 bar_chart.height = abs(y_start - y_end) 170 171 # set the information in the bar chart 172 bar_chart.data = self.display_info 173 bar_chart.valueAxis.valueMin = min(self.display_info[0]) 174 bar_chart.valueAxis.valueMax = max(self.display_info[0]) 175 for data_set in self.display_info[1:]: 176 if min(data_set) < bar_chart.valueAxis.valueMin: 177 bar_chart.valueAxis.valueMin = min(data_set) 178 if max(data_set) > bar_chart.valueAxis.valueMax: 179 bar_chart.valueAxis.valueMax = max(data_set) 180 181 # set other formatting options 182 if len(self.display_info) == 1: 183 bar_chart.groupSpacing = 0 184 style = TypedPropertyCollection(BarChartProperties) 185 style.strokeWidth = 0 186 style.strokeColor = colors.green 187 style[0].fillColor = colors.green 188 189 bar_chart.bars = style 190 191 192 # set the labels 193 # XXX labels don't work yet 194 # bar_chart.valueAxis.title = self.x_axis_title 195 # bar_chart.categoryAxis.title = self.y_axis_title 196 197 cur_drawing.add(bar_chart)
198
199 - def _draw_title(self, cur_drawing, title, start_x, start_y, end_x, end_y):
200 """Add the title of the figure to the drawing. 201 """ 202 x_center = start_x + (end_x - start_x) / 2 203 y_pos = end_y + (self.padding_percent * (start_y - end_y)) / 2 204 title_string = String(x_center, y_pos, title) 205 title_string.fontName = 'Helvetica-Bold' 206 title_string.fontSize = self.chart_title_size 207 title_string.textAnchor = "middle" 208 209 cur_drawing.add(title_string)
210
211 - def _determine_position(self, start_x, start_y, end_x, end_y):
212 """Calculate the position of the chart with blank space. 213 214 This uses some padding around the chart, and takes into account 215 whether the chart has a title. It returns 4 values, which are, 216 in order, the x_start, x_end, y_start and y_end of the chart 217 itself. 218 """ 219 x_padding = self.padding_percent * (end_x - start_x) 220 y_padding = self.padding_percent * (start_y - end_y) 221 222 new_x_start = start_x + x_padding 223 new_x_end = end_x - x_padding 224 225 if self.chart_title: 226 new_y_start = start_y - y_padding - self.chart_title_size 227 else: 228 new_y_start = start_y - y_padding 229 230 new_y_end = end_y + y_padding 231 232 return new_x_start, new_x_end, new_y_start, new_y_end
233
234 -class LineDistribution:
235 """Display the distribution of values as connected lines. 236 237 This distribution displays the change in values across the object as 238 lines. This also allows multiple distributions to be displayed on a 239 single graph. 240 """
241 - def __init__(self):
242 pass
243
244 - def draw(self, cur_drawing, start_x, start_y, end_x, end_y):
245 pass
246