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