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=None):
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 if display_info is None: 145 display_info = [] 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 bar_chart = VerticalBarChart() 158 if self.chart_title: 159 self._draw_title(cur_drawing, self.chart_title, 160 start_x, start_y, end_x, end_y) 161 # set the position of the bar chart 162 x_start, x_end, y_start, y_end = \ 163 self._determine_position(start_x, start_y, end_x, end_y) 164 165 bar_chart.x = x_start 166 bar_chart.y = y_start 167 bar_chart.width = abs(x_start - x_end) 168 bar_chart.height = abs(y_start - y_end) 169 170 # set the information in the bar chart 171 bar_chart.data = self.display_info 172 bar_chart.valueAxis.valueMin = min(self.display_info[0]) 173 bar_chart.valueAxis.valueMax = max(self.display_info[0]) 174 for data_set in self.display_info[1:]: 175 if min(data_set) < bar_chart.valueAxis.valueMin: 176 bar_chart.valueAxis.valueMin = min(data_set) 177 if max(data_set) > bar_chart.valueAxis.valueMax: 178 bar_chart.valueAxis.valueMax = max(data_set) 179 180 # set other formatting options 181 if len(self.display_info) == 1: 182 bar_chart.groupSpacing = 0 183 style = TypedPropertyCollection(BarChartProperties) 184 style.strokeWidth = 0 185 style.strokeColor = colors.green 186 style[0].fillColor = colors.green 187 188 bar_chart.bars = style 189 190 # set the labels 191 # XXX labels don't work yet 192 # bar_chart.valueAxis.title = self.x_axis_title 193 # bar_chart.categoryAxis.title = self.y_axis_title 194 195 cur_drawing.add(bar_chart)
196
197 - def _draw_title(self, cur_drawing, title, start_x, start_y, end_x, end_y):
198 """Add the title of the figure to the drawing (PRIVATE).""" 199 x_center = start_x + (end_x - start_x) / 2 200 y_pos = end_y + (self.padding_percent * (start_y - end_y)) / 2 201 title_string = String(x_center, y_pos, title) 202 title_string.fontName = 'Helvetica-Bold' 203 title_string.fontSize = self.chart_title_size 204 title_string.textAnchor = "middle" 205 206 cur_drawing.add(title_string)
207
208 - def _determine_position(self, start_x, start_y, end_x, end_y):
209 """Calculate the position of the chart with blank space (PRIVATE). 210 211 This uses some padding around the chart, and takes into account 212 whether the chart has a title. It returns 4 values, which are, 213 in order, the x_start, x_end, y_start and y_end of the chart 214 itself. 215 """ 216 x_padding = self.padding_percent * (end_x - start_x) 217 y_padding = self.padding_percent * (start_y - end_y) 218 219 new_x_start = start_x + x_padding 220 new_x_end = end_x - x_padding 221 222 if self.chart_title: 223 new_y_start = start_y - y_padding - self.chart_title_size 224 else: 225 new_y_start = start_y - y_padding 226 227 new_y_end = end_y + y_padding 228 229 return new_x_start, new_x_end, new_y_start, new_y_end
230 231
232 -class LineDistribution(object):
233 """Display the distribution of values as connected lines. 234 235 This distribution displays the change in values across the object as 236 lines. This also allows multiple distributions to be displayed on a 237 single graph. 238 """
239 - def __init__(self):
240 pass
241
242 - def draw(self, cur_drawing, start_x, start_y, end_x, end_y):
243 pass
244