Back to index

python-biopython  1.60
Distribution.py
Go to the documentation of this file.
00001 """Display information distributed across a Chromosome-like object.
00002 
00003 These classes are meant to show the distribution of some kind of information
00004 as it changes across any kind of segment. It was designed with chromosome
00005 distributions in mind, but could also work for chromosome regions, BAC clones
00006 or anything similar.
00007 
00008 Reportlab is used for producing the graphical output.
00009 """
00010 # standard library
00011 import math
00012 
00013 # reportlab
00014 from reportlab.pdfgen import canvas
00015 from reportlab.lib.pagesizes import letter
00016 from reportlab.lib.units import inch
00017 from reportlab.lib import colors
00018 
00019 from reportlab.graphics.shapes import Drawing, String
00020 from reportlab.graphics.charts.barcharts import VerticalBarChart
00021 from reportlab.graphics.charts.barcharts import BarChartProperties
00022 from reportlab.graphics.widgetbase import TypedPropertyCollection
00023 from reportlab.graphics import renderPDF, renderPS
00024 
00025 from Bio.Graphics import _write
00026 
00027 class DistributionPage(object):
00028     """Display a grouping of distributions on a page.
00029 
00030     This organizes Distributions, and will display them nicely
00031     on a single page.
00032     """
00033     def __init__(self, output_format = 'pdf'):
00034         self.distributions = []
00035 
00036         # customizable attributes
00037         self.number_of_columns = 1
00038         self.page_size = letter
00039         self.title_size = 20
00040 
00041         self.output_format = output_format
00042 
00043     def draw(self, output_file, title):
00044         """Draw out the distribution information.
00045 
00046         Arguments:
00047 
00048         o output_file - The name of the file to output the information to,
00049                         or a handle to write to.
00050 
00051         o title - A title to display on the graphic.
00052         """
00053         width, height = self.page_size
00054         cur_drawing = Drawing(width, height)
00055 
00056         self._draw_title(cur_drawing, title, width, height)
00057 
00058         # calculate the x and y position changes for each distribution
00059         cur_x_pos = inch * .5
00060         end_x_pos = width - inch * .5
00061         cur_y_pos = height - 1.5 * inch
00062         end_y_pos = .5 * inch
00063         x_pos_change = ((end_x_pos - cur_x_pos) /
00064                         float(self.number_of_columns))
00065         num_y_rows = math.ceil(float(len(self.distributions))
00066                                / float(self.number_of_columns))
00067         y_pos_change = (cur_y_pos - end_y_pos) / num_y_rows
00068         
00069         self._draw_distributions(cur_drawing, cur_x_pos, x_pos_change,
00070                                  cur_y_pos, y_pos_change, num_y_rows)
00071         self._draw_legend(cur_drawing, 2.5 * inch, width)
00072 
00073         return _write(cur_drawing, output_file, self.output_format)
00074 
00075     def _draw_title(self, cur_drawing, title, width, height):
00076         """Add the title of the figure to the drawing.
00077         """
00078         title_string = String(width / 2, height - inch, title)
00079         title_string.fontName = 'Helvetica-Bold'
00080         title_string.fontSize = self.title_size
00081         title_string.textAnchor = "middle"
00082 
00083         cur_drawing.add(title_string)
00084 
00085     def _draw_distributions(self, cur_drawing, start_x_pos, x_pos_change,
00086                             start_y_pos, y_pos_change, num_y_drawings):
00087         """Draw all of the distributions on the page.
00088 
00089         Arguments:
00090 
00091         o cur_drawing - The drawing we are working with.
00092 
00093         o start_x_pos - The x position on the page to start drawing at.
00094 
00095         o x_pos_change - The change in x position between each figure.
00096 
00097         o start_y_pos - The y position on the page to start drawing at.
00098 
00099         o y_pos_change - The change in y position between each figure.
00100 
00101         o num_y_drawings - The number of drawings we'll have in the y
00102         (up/down) direction.
00103         """
00104         for y_drawing in range(int(num_y_drawings)):
00105             # if we are on the last y position, we may not be able
00106             # to fill all of the x columns
00107             if ((y_drawing + 1) * self.number_of_columns >
00108                 len(self.distributions)):
00109                 num_x_drawings = len(self.distributions) - \
00110                                  y_drawing * self.number_of_columns
00111             else:
00112                 num_x_drawings = self.number_of_columns
00113             for x_drawing in range(num_x_drawings):
00114                 dist_num = y_drawing * self.number_of_columns + x_drawing
00115                 cur_distribution = self.distributions[dist_num]
00116 
00117                 # find the x and y boundaries of the distribution
00118                 x_pos = start_x_pos + x_drawing * x_pos_change
00119                 end_x_pos = x_pos + x_pos_change
00120                 end_y_pos = start_y_pos - y_drawing * y_pos_change
00121                 y_pos = end_y_pos - y_pos_change
00122 
00123                 # draw the distribution
00124                 cur_distribution.draw(cur_drawing, x_pos, y_pos, end_x_pos,
00125                                       end_y_pos)
00126 
00127     def _draw_legend(self, cur_drawing, start_y, width):
00128         """Add a legend to the figure.
00129 
00130         Subclasses can implement to provide a specialized legend.
00131         """
00132         pass
00133 
00134 class BarChartDistribution(object):
00135     """Display the distribution of values as a bunch of bars.
00136     """
00137     def __init__(self, display_info = []):
00138         """Initialize a Bar Chart display of distribution info.
00139 
00140         Class attributes:
00141 
00142         o display_info - the information to be displayed in the distribution.
00143         This should be ordered as a list of lists, where each internal list
00144         is a data set to display in the bar chart.
00145         """
00146         self.display_info = display_info
00147 
00148         self.x_axis_title = ""
00149         self.y_axis_title = ""
00150         self.chart_title = ""
00151         self.chart_title_size = 10
00152 
00153         self.padding_percent = 0.15
00154 
00155     def draw(self, cur_drawing, start_x, start_y, end_x, end_y):
00156         """Draw a bar chart with the info in the specified range.
00157         """
00158         bar_chart = VerticalBarChart()
00159         if self.chart_title:
00160             self._draw_title(cur_drawing, self.chart_title,
00161                              start_x, start_y, end_x, end_y)
00162         # set the position of the bar chart
00163         x_start, x_end, y_start, y_end = \
00164            self._determine_position(start_x, start_y, end_x, end_y)
00165                                           
00166         bar_chart.x = x_start
00167         bar_chart.y = y_start
00168         bar_chart.width = abs(x_start - x_end)
00169         bar_chart.height = abs(y_start - y_end)
00170 
00171         # set the information in the bar chart
00172         bar_chart.data = self.display_info
00173         bar_chart.valueAxis.valueMin = min(self.display_info[0])
00174         bar_chart.valueAxis.valueMax = max(self.display_info[0])
00175         for data_set in self.display_info[1:]:
00176             if min(data_set) < bar_chart.valueAxis.valueMin:
00177                 bar_chart.valueAxis.valueMin = min(data_set)
00178             if max(data_set) > bar_chart.valueAxis.valueMax:
00179                 bar_chart.valueAxis.valueMax = max(data_set)
00180 
00181         # set other formatting options
00182         if len(self.display_info) == 1:
00183             bar_chart.groupSpacing = 0
00184             style = TypedPropertyCollection(BarChartProperties)
00185             style.strokeWidth = 0
00186             style.strokeColor = colors.green
00187             style[0].fillColor = colors.green
00188 
00189             bar_chart.bars = style
00190         
00191 
00192         # set the labels
00193         # XXX labels don't work yet
00194         # bar_chart.valueAxis.title = self.x_axis_title
00195         # bar_chart.categoryAxis.title = self.y_axis_title
00196 
00197         cur_drawing.add(bar_chart)
00198 
00199     def _draw_title(self, cur_drawing, title, start_x, start_y, end_x, end_y):
00200         """Add the title of the figure to the drawing.
00201         """
00202         x_center = start_x + (end_x - start_x) / 2
00203         y_pos = end_y + (self.padding_percent * (start_y - end_y)) / 2
00204         title_string = String(x_center, y_pos, title)
00205         title_string.fontName = 'Helvetica-Bold'
00206         title_string.fontSize = self.chart_title_size
00207         title_string.textAnchor = "middle"
00208 
00209         cur_drawing.add(title_string)
00210 
00211     def _determine_position(self, start_x, start_y, end_x, end_y):
00212         """Calculate the position of the chart with blank space.
00213 
00214         This uses some padding around the chart, and takes into account
00215         whether the chart has a title. It returns 4 values, which are,
00216         in order, the x_start, x_end, y_start and y_end of the chart
00217         itself.
00218         """
00219         x_padding = self.padding_percent * (end_x - start_x)
00220         y_padding = self.padding_percent * (start_y - end_y)
00221 
00222         new_x_start = start_x + x_padding
00223         new_x_end = end_x - x_padding
00224 
00225         if self.chart_title:
00226             new_y_start = start_y - y_padding - self.chart_title_size
00227         else:
00228             new_y_start = start_y - y_padding
00229 
00230         new_y_end = end_y + y_padding
00231         
00232         return new_x_start, new_x_end, new_y_start, new_y_end
00233 
00234 class LineDistribution(object):
00235     """Display the distribution of values as connected lines.
00236 
00237     This distribution displays the change in values across the object as
00238     lines. This also allows multiple distributions to be displayed on a
00239     single graph.
00240     """
00241     def __init__(self):
00242         pass
00243 
00244     def draw(self, cur_drawing, start_x, start_y, end_x, end_y):
00245         pass