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