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

Source Code for Module Bio.Graphics.Distribution

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