#----------------------------------------------- #aplotter.py - ascii art function plotter #Copyright (c) 2006, Imri Goldberg #All rights reserved. # #Redistribution and use in source and binary forms, #with or without modification, are permitted provided #that the following conditions are met: # # * Redistributions of source code must retain the # above copyright notice, this list of conditions # and the following disclaimer. # * Redistributions in binary form must reproduce the # above copyright notice, this list of conditions # and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of the nor the names of # its contributors may be used to endorse or promote products # derived from this software without specific prior written permission. # #THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" #AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE #IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE #ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE #LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL #DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR #SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER #CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, #OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #----------------------------------------------- import math EPSILON = 0.000001 def transposed(mat): result = [] for i in xrange(len(mat[0])): result.append([x[i] for x in mat]) return result def y_reversed(mat): result = [] for i in range(len(mat)): result.append(list(reversed(mat[i]))) return result def sign(x): if 0 math.tan(3*math.pi/8): draw_symbol = "|" elif slope > math.tan(math.pi/8) and slope < math.tan(3*math.pi/8): draw_symbol = "/" elif abs(slope) < math.tan(math.pi/8): draw_symbol = "-" elif slope < math.tan(-math.pi/8) and slope > math.tan(-3*math.pi/8): draw_symbol = "\\" elif slope < math.tan(-3*math.pi/8): draw_symbol = "|" return draw_symbol def plot_labels(self, output_buffer, plot_data): if plot_data.y_size < 2: return margin_factor = 1 do_plot_x_label = True do_plot_y_label = True x_str = "%+g" if plot_data.x_size < 16: do_plot_x_label = False elif plot_data.x_size < 23: x_str = "%+.2g" y_str = "%+g" if plot_data.x_size < 8: do_plot_y_label = False elif plot_data.x_size < 11: y_str = "%+.2g" act_min_x = (plot_data.min_x + plot_data.x_mod*margin_factor) act_max_x = (plot_data.max_x - plot_data.x_mod*margin_factor) act_min_y = (plot_data.min_y + plot_data.y_mod*margin_factor) act_max_y = (plot_data.max_y - plot_data.y_mod*margin_factor) if abs(act_min_x) < 1: min_x_str = "%+.2g" % act_min_x else: min_x_str = x_str % act_min_x if abs(act_max_x) < 1: max_x_str = "%+.2g" % act_max_x else: max_x_str = x_str % act_max_x if abs(act_min_y) < 1: min_y_str = "%+.2g" % act_min_y else: min_y_str = y_str % act_min_y if abs(act_max_y) < 1: max_y_str = "%+.2g" % act_max_y else: max_y_str = y_str % act_max_y min_x_coord = self.get_coord(act_min_x,plot_data.min_x,plot_data.x_step) max_x_coord = self.get_coord(act_max_x,plot_data.min_x,plot_data.x_step) min_y_coord = self.get_coord(act_min_y,plot_data.min_y,plot_data.y_step) max_y_coord = self.get_coord(act_max_y,plot_data.min_y,plot_data.y_step) #print plot_data y_zero_coord = self.get_coord(0, plot_data.min_y, plot_data.y_step) #if plot_data.min_x < 0 and plot_data.max_x > 0: x_zero_coord = self.get_coord(0, plot_data.min_x, plot_data.x_step) #else: #pass output_buffer[x_zero_coord][min_y_coord] = "+" output_buffer[x_zero_coord][max_y_coord] = "+" output_buffer[min_x_coord][y_zero_coord] = "+" output_buffer[max_x_coord][y_zero_coord] = "+" if do_plot_x_label: for i,c in enumerate(min_x_str): output_buffer[min_x_coord+i][y_zero_coord-1] = c for i,c in enumerate(max_x_str): output_buffer[max_x_coord+i-len(max_x_str)][y_zero_coord-1] = c if do_plot_y_label: for i,c in enumerate(max_y_str): output_buffer[x_zero_coord+i][max_y_coord] = c for i,c in enumerate(min_y_str): output_buffer[x_zero_coord+i][min_y_coord] = c def plot_data(self, xy_seq, output_buffer, plot_data): if self.plot_slope: xy_seq = list(xy_seq) #sort according to the x coord xy_seq.sort(key = lambda c: c[0]) prev_p = xy_seq[0] e_xy_seq = enumerate(xy_seq) e_xy_seq.next() for i,(x,y) in e_xy_seq: draw_symbol = self.dot line_drawn = self.plot_line(prev_p, (x,y), output_buffer, plot_data) prev_p = (x,y) if not line_drawn: if i > 0 and i < len(xy_seq)-1: px,py = xy_seq[i-1] nx,ny = xy_seq[i+1] if abs(nx-px) > EPSILON: slope = (1.0/plot_data.ratio)*(ny-py)/(nx-px) draw_symbol = self.get_symbol_by_slope(slope, draw_symbol) if x < plot_data.min_x or x >= plot_data.max_x or y < plot_data.min_y or y >= plot_data.max_y: continue x_coord = self.get_coord(x, plot_data.min_x, plot_data.x_step) y_coord = self.get_coord(y, plot_data.min_y, plot_data.y_step) if x_coord >= 0 and x_coord < len(output_buffer) and y_coord >= 0 and y_coord < len(output_buffer[0]): if self.draw_axes: if y_coord == self.get_coord(0, plot_data.min_y, plot_data.y_step) and draw_symbol == "-": draw_symbol = "=" output_buffer[x_coord][y_coord] = draw_symbol else: for x,y in xy_seq: if x < plot_data.min_x or x >= plot_data.max_x or y < plot_data.min_y or y >= plot_data.max_y: continue x_coord = self.get_coord(x, plot_data.min_x, plot_data.x_step) y_coord = self.get_coord(y, plot_data.min_y, plot_data.y_step) if x_coord >= 0 and x_coord < len(output_buffer) and y_coord > 0 and y_coord < len(output_buffer[0]): output_buffer[x_coord][y_coord] = self.dot def plot_line(self, start, end, output_buffer, plot_data): start_coord = self.get_coord(start[0], plot_data.min_x, plot_data.x_step), self.get_coord(start[1], plot_data.min_y, plot_data.y_step) end_coord = self.get_coord(end[0], plot_data.min_x, plot_data.x_step), self.get_coord(end[1], plot_data.min_y, plot_data.y_step) x0,y0 = start_coord x1,y1 = end_coord if (x0,y0) == (x1,y1): return True clipped_line = clip_line(start, end, (plot_data.min_x, plot_data.min_y), (plot_data.max_x, plot_data.max_y)) if clipped_line != None: start,end = clipped_line else: return False start_coord = self.get_coord(start[0], plot_data.min_x, plot_data.x_step), self.get_coord(start[1], plot_data.min_y, plot_data.y_step) end_coord = self.get_coord(end[0], plot_data.min_x, plot_data.x_step), self.get_coord(end[1], plot_data.min_y, plot_data.y_step) x0,y0 = start_coord x1,y1 = end_coord if (x0,y0) == (x1,y1): return True x_zero_coord = self.get_coord(0, plot_data.min_x, plot_data.x_step) y_zero_coord = self.get_coord(0, plot_data.min_y, plot_data.y_step) if start[0]-end[0] == 0: draw_symbol = "|" else: slope = (1.0/plot_data.ratio)*(end[1]-start[1])/(end[0]-start[0]) draw_symbol = self.get_symbol_by_slope(slope, self.dot) try: delta = x1-x0, y1-y0 if abs(delta[0])>abs(delta[1]): s = sign(delta[0]) slope = float(delta[1])/delta[0] for i in range(0,abs(int(delta[0]))): cur_draw_symbol = draw_symbol x = i*s cur_y = int(y0+slope*x) if self.draw_axes and cur_y == y_zero_coord and draw_symbol == "-": cur_draw_symbol = "=" output_buffer[x0+x][cur_y] = cur_draw_symbol else: s = sign(delta[1]) slope = float(delta[0])/delta[1] for i in range(0,abs(int(delta[1]))): y = i*s cur_draw_symbol = draw_symbol cur_y = y0+y if self.draw_axes and cur_y == y_zero_coord and draw_symbol == "-": cur_draw_symbol = "=" output_buffer[int(x0+slope*y)][cur_y] = cur_draw_symbol except: print start, end print start_coord, end_coord print plot_data raise return False def plot_single(self, seq, min_x = None, max_x = None, min_y = None, max_y = None): return self.plot_double(range(len(seq)),seq, min_x, max_x, min_y, max_y) def plot_double(self, x_seq, y_seq, min_x = None, max_x = None, min_y = None, max_y = None): if min_x == None: min_x = min(x_seq) if max_x == None: max_x = max(x_seq) if min_y == None: min_y = min(y_seq) if max_y == None: max_y = max(y_seq) if max_y == min_y: max_y += 1 x_mod = (max_x-min_x)*self.x_margin y_mod = (max_y-min_y)*self.y_margin min_x-=x_mod max_x+=x_mod min_y-=y_mod max_y+=y_mod plot_data = self.PlotData(self.x_size, self.y_size, min_x, max_x, min_y, max_y, x_mod, y_mod) output_buffer = [[" "]*self.y_size for i in range(self.x_size)] if self.will_draw_axes: self.draw_axes(output_buffer, plot_data) self.plot_data(zip(x_seq, y_seq), output_buffer, plot_data) if self.will_plot_labels: self.plot_labels(output_buffer, plot_data) trans_result = transposed(y_reversed(output_buffer)) result = self.new_line.join(["".join(row) for row in trans_result]) return result def draw_axes(self, output_buffer, plot_data): draw_x = False draw_y = False if plot_data.min_x <= 0 and plot_data.max_x > 0: draw_y = True zero_x = self.get_coord(0, plot_data.min_x, plot_data.x_step) for y in xrange(plot_data.y_size): output_buffer[zero_x][y] = "|" if plot_data.min_y <= 0 and plot_data.max_y > 0: draw_x = True zero_y = self.get_coord(0, plot_data.min_y, plot_data.y_step) for x in xrange(plot_data.x_size): output_buffer[x][zero_y] = "-" if draw_x and draw_y: output_buffer[zero_x][zero_y] = "+" @staticmethod def get_coord(val, min, step): result = int((val - min)/step) return result def clip_line(line_pt_1, line_pt_2, rect_bottom_left, rect_top_right): ts = [0.0,1.0] if line_pt_1[0] == line_pt_2[0]: return ((line_pt_1[0], max(min(line_pt_1[1], line_pt_2[1]), rect_bottom_left[1])), (line_pt_1[0], min(max(line_pt_1[1], line_pt_2[1]), rect_top_right[1]))) if line_pt_1[1] == line_pt_2[1]: return ((max(min(line_pt_1[0], line_pt_2[0]), rect_bottom_left[0]), line_pt_1[1]), (min(max(line_pt_1[0], line_pt_2[0]), rect_top_right[0]), line_pt_1[1])) if ((rect_bottom_left[0] <= line_pt_1[0] and line_pt_1[0] < rect_top_right[0]) and (rect_bottom_left[1] <= line_pt_1[1] and line_pt_1[1] < rect_top_right[1]) and (rect_bottom_left[0] <= line_pt_2[0] and line_pt_2[0] < rect_top_right[0]) and (rect_bottom_left[1] <= line_pt_2[1] and line_pt_2[1] < rect_top_right[1])): return line_pt_1, line_pt_2 ts.append( float(rect_bottom_left[0]-line_pt_1[0])/(line_pt_2[0]-line_pt_1[0]) ) ts.append( float(rect_top_right[0]-line_pt_1[0])/(line_pt_2[0]-line_pt_1[0]) ) ts.append( float(rect_bottom_left[1]-line_pt_1[1])/(line_pt_2[1]-line_pt_1[1]) ) ts.append( float(rect_top_right[1]-line_pt_1[1])/(line_pt_2[1]-line_pt_1[1]) ) ts.sort() if ts[2] < 0 or ts[2] >= 1 or ts[3] < 0 or ts[2]>= 1: return None result = [(pt_1 + t*(pt_2-pt_1)) for t in (ts[2],ts[3]) for (pt_1, pt_2) in zip(line_pt_1, line_pt_2)] return (result[0],result[1]), (result[2], result[3]) def plot(*args,**flags): limit_flags_names = set(["min_x","min_y","max_x","max_y"]) limit_flags = dict([(n,flags[n]) for n in limit_flags_names & set(flags)]) settting_flags = dict([(n,flags[n]) for n in set(flags) - limit_flags_names]) if len(args) == 1: p = Plotter(**settting_flags) print p.plot_single(args[0],**limit_flags) elif len(args) == 2: p = Plotter(**settting_flags) print p.plot_double(args[0],args[1],**limit_flags) else: raise NotImplementedError("can't draw multiple graphs yet") __all__ = ["Plotter","plot"]