def __init__(self, project, name=None, parent=None, location=None): Item.__init__(self, project, name, parent, location) self.paint_xor_objects = False self.selected_datasets = [] self.mode = 'arrow' self.graph_objects = [] self.dragobj = None self.selected_object = None self.plot_height = 100 self.plot_width = 100 self.datasets = [] if location is not None: for i, l in enumerate(self.data.datasets): if not l.id.startswith('-'): self.datasets.append(Dataset(self, i)) self.datasets[-1].connect('modified', self.on_dataset_modified) for l in self.data.lines: if not l.id.startswith('-'): self.graph_objects.append(Line(self, l)) for l in self.data.text: if not l.id.startswith('-'): self.graph_objects.append(Text(self, l)) self.functions = [] # if location is not None: # for i in range(len(self.data.functions)): # if not self.data.functions[i].id.startswith('-'): # f = Function(self, i) # self.functions.append(f) # f.connect('modified', self.on_dataset_modified) # f.func.connect('modified', self.on_dataset_modified) self.ps = False self.axis_top = Axis('top', self) self.axis_bottom = Axis('bottom', self) self.axis_right = Axis('right', self) self.axis_left = Axis('left', self) self.axes = [self.axis_top, self.axis_right, self.axis_bottom, self.axis_left] self.grid_h = Grid('horizontal', self) self.grid_v = Grid('vertical', self) self.set_range(0.0, 100.5) if location is None: self.xmin, self.ymin = 0,0 self.ymax, self.xmax = 10, 10 self.newf() if self.xtype == '': self._xtype = 'linear' if self.ytype == '': self._ytype = 'linear' self.selected_function = None self.rubberband = Rubberband(self) self.cross = Cross(self) self.rangehandle = Rangehandle(self) self.objects = [self.rubberband, self.cross, self.rangehandle] self.textpainter = TextPainter(self) self.axis_title_font_size = 12. self.background_color = (1., 1., 1., 1.) self.pwidth = 120. self.pheight = 100. self.recalc = True
def __init__(self, project, name=None, parent=None, location=None): Item.__init__(self, project, name, parent, location) self.paint_xor_objects = False self.selected_datasets = [] self.mode = 'arrow' self.graph_objects = [] self.dragobj = None self.selected_object = None self.plot_height = 100 self.plot_width = 100 self.datasets = [] if location is not None: for i, l in enumerate(self.data.datasets): if not l.id.startswith('-'): self.datasets.append(Dataset(self, i)) self.datasets[-1].connect('modified', self.on_dataset_modified) for l in self.data.lines: if not l.id.startswith('-'): self.graph_objects.append(Line(self, l)) for l in self.data.text: if not l.id.startswith('-'): self.graph_objects.append(Text(self, l)) self.functions = [] # if location is not None: # for i in range(len(self.data.functions)): # if not self.data.functions[i].id.startswith('-'): # f = Function(self, i) # self.functions.append(f) # f.connect('modified', self.on_dataset_modified) # f.func.connect('modified', self.on_dataset_modified) self.ps = False self.axis_top = Axis('top', self) self.axis_bottom = Axis('bottom', self) self.axis_right = Axis('right', self) self.axis_left = Axis('left', self) self.axes = [ self.axis_top, self.axis_right, self.axis_bottom, self.axis_left ] self.grid_h = Grid('horizontal', self) self.grid_v = Grid('vertical', self) self.set_range(0.0, 100.5) if location is None: self.xmin, self.ymin = 0, 0 self.ymax, self.xmax = 10, 10 self.newf() if self.xtype == '': self._xtype = 'linear' if self.ytype == '': self._ytype = 'linear' self.selected_function = None self.rubberband = Rubberband(self) self.cross = Cross(self) self.rangehandle = Rangehandle(self) self.objects = [self.rubberband, self.cross, self.rangehandle] self.textpainter = TextPainter(self) self.axis_title_font_size = 12. self.background_color = (1., 1., 1., 1.) self.pwidth = 120. self.pheight = 100. self.recalc = True
class Graph(Item, HasSignals): def __init__(self, project, name=None, parent=None, location=None): Item.__init__(self, project, name, parent, location) self.paint_xor_objects = False self.selected_datasets = [] self.mode = 'arrow' self.graph_objects = [] self.dragobj = None self.selected_object = None self.plot_height = 100 self.plot_width = 100 self.datasets = [] if location is not None: for i, l in enumerate(self.data.datasets): if not l.id.startswith('-'): self.datasets.append(Dataset(self, i)) self.datasets[-1].connect('modified', self.on_dataset_modified) for l in self.data.lines: if not l.id.startswith('-'): self.graph_objects.append(Line(self, l)) for l in self.data.text: if not l.id.startswith('-'): self.graph_objects.append(Text(self, l)) self.functions = [] # if location is not None: # for i in range(len(self.data.functions)): # if not self.data.functions[i].id.startswith('-'): # f = Function(self, i) # self.functions.append(f) # f.connect('modified', self.on_dataset_modified) # f.func.connect('modified', self.on_dataset_modified) self.ps = False self.axis_top = Axis('top', self) self.axis_bottom = Axis('bottom', self) self.axis_right = Axis('right', self) self.axis_left = Axis('left', self) self.axes = [self.axis_top, self.axis_right, self.axis_bottom, self.axis_left] self.grid_h = Grid('horizontal', self) self.grid_v = Grid('vertical', self) self.set_range(0.0, 100.5) if location is None: self.xmin, self.ymin = 0,0 self.ymax, self.xmax = 10, 10 self.newf() if self.xtype == '': self._xtype = 'linear' if self.ytype == '': self._ytype = 'linear' self.selected_function = None self.rubberband = Rubberband(self) self.cross = Cross(self) self.rangehandle = Rangehandle(self) self.objects = [self.rubberband, self.cross, self.rangehandle] self.textpainter = TextPainter(self) self.axis_title_font_size = 12. self.background_color = (1., 1., 1., 1.) self.pwidth = 120. self.pheight = 100. self.recalc = True default_name_prefix = 'graph' def redraw(self, recalc=False): if recalc: self.recalc = True self.emit('redraw') def get_xmin(self): try: return float(self._zoom.split()[0]) except IndexError: return 0.0 def get_xmax(self): try: return float(self._zoom.split()[1]) except IndexError: return 1.0 def get_ymin(self): try: return float(self._zoom.split()[2]) except IndexError: return 0.0 def get_ymax(self): try: return float(self._zoom.split()[3]) except IndexError: return 1.0 def set_xmin(self, value): self._zoom = ' '.join([str(f) for f in [value, self.xmax, self.ymin, self.ymax]]) def set_xmax(self, value): self._zoom = ' '.join([str(f) for f in [self.xmin, value, self.ymin, self.ymax]]) def set_ymin(self, value): self._zoom = ' '.join([str(f) for f in [self.xmin, self.xmax, value, self.ymax]]) def set_ymax(self, value): self._zoom = ' '.join([str(f) for f in [self.xmin, self.xmax, self.ymin, value]]) xmin = property(get_xmin, set_xmin) xmax = property(get_xmax, set_xmax) ymin = property(get_ymin, set_ymin) ymax = property(get_ymax, set_ymax) # axis scales def set_xtype(self, _state, tp): if tp == 'log' and (self.xmin <= 0 or self.xmax <= 0): raise StopAction _state['old'] = self._xtype self._xtype = tp self.redraw(True) def undo_set_xtype(self, _state): self._xtype = _state['old'] self.redraw(True) set_xtype = action_from_methods2('graph-set-xaxis-scale', set_xtype, undo_set_xtype) def get_xtype(self): return self._xtype xtype = property(get_xtype, set_xtype) def set_ytype(self, _state, tp): if tp == 'log' and (self.xmin <= 0 or self.xmax <= 0): raise StopAction _state['old'] = self._ytype self._ytype = tp self.redraw(True) def undo_set_ytype(self, _state): self._ytype = _state['old'] self.redraw(True) set_ytype = action_from_methods2('graph-set-xaxis-scale', set_ytype, undo_set_ytype) def get_ytype(self): return self._ytype ytype = property(get_ytype, set_ytype) # titles def set_xtitle(self, state, title): state['old'], state['new'] = self._xtitle, title self._xtitle = title self.reshape() self.redraw() def undo_set_xtitle(self, state): self._xtitle = state['old'] self.reshape() self.redraw() def redo_set_xtitle(self, state): self._xtitle = state['new'] self.reshape() self.redraw() def get_xtitle(self): return self._xtitle set_xtitle = action_from_methods2('graph/set-xtitle', set_xtitle, undo_set_xtitle, redo=redo_set_xtitle) xtitle = property(get_xtitle, set_xtitle) def set_ytitle(self, state, title): state['old'], state['new'] = self._ytitle, title self._ytitle = title self.reshape() self.redraw() def undo_set_ytitle(self, state): self._ytitle = state['old'] self.reshape() self.redraw() def redo_set_ytitle(self, state): self._ytitle = state['new'] self.reshape() self.redraw() def get_ytitle(self): return self._ytitle set_ytitle = action_from_methods2('graph/set-ytitle', set_ytitle, undo_set_ytitle, redo=redo_set_ytitle) ytitle = property(get_ytitle, set_ytitle) def __repr__(self): return '<Graph %s%s>' % (self.name, '(deleted)'*self.id.startswith('-')) def newf(self): # ind = self.data.functions.append(id=create_id()) f = Function(self) f.connect('modified', self.on_dataset_modified) f.func.connect('modified', self.on_dataset_modified) f.func.connect('add-term', self.on_dataset_modified) f.func.connect('remove-term', self.on_dataset_modified) self.functions.append(f) self.emit('add-function', f) return f def create_legend(self): legend = self.new_object(Text) legend.text = '\n'.join('@%d@'%i + str(d) for i, d in enumerate(self.datasets)) # add and remove graph objects def new_object(self, state, typ): location = { Line: self.data.lines, Text: self.data.text }[typ] ind = location.append(id=create_id()) obj = typ(self, location[ind]) self.graph_objects.append(obj) state['obj'] = obj return obj def undo_new_object(self, state): obj = state['obj'] self.graph_objects.remove(obj) obj.id = '-'+obj.id self.redraw() def redo_new_object(self, state): obj = state['obj'] self.graph_objects.append(obj) location = { Line: self.data.lines, Text: self.data.text }[type(obj)] obj.id = obj.id[1:] self.redraw() new_object = action_from_methods2('graph/new-object', new_object, undo_new_object, redo=redo_new_object) def delete_object(self, state, obj): obj.id = '-'+obj.id self.graph_objects.remove(obj) state['obj'] = obj self.redraw() delete_object = action_from_methods2('graph/delete-object', delete_object, redo_new_object, redo=undo_new_object) # add and remove datasets def add(self, state, x, y): ind = self.data.datasets.append(worksheet=x.worksheet.id, id=create_id(), x=x.name.encode('utf-8'), y=y.name.encode('utf-8')) d = Dataset(self, ind) self.datasets.append(d) pos = len(self.datasets)-1 # print 'added dataset, index %d, position %d' % (ind, pos) d.connect('modified', self.on_dataset_modified) d.connect_signals() self.on_dataset_modified(d) self.emit('add-dataset', d) state['obj'] = d return pos def undo_add(self, state): d = state['obj'] # print 'undoing addition of dataset, index %d, position %d' % (d.ind, pos) self.datasets.remove(d) d.disconnect_signals() d.disconnect('modified', self.on_dataset_modified) self.emit('remove-dataset', d) self.redraw(True) d.id = '-'+d.id # self.data.datasets.delete(d.ind) def redo_add(self, state): d = state['obj'] d.id = d.id[1:] self.datasets.append(d) d.connect('modified', self.on_dataset_modified) d.connect_signals() self.emit('add-dataset', d) self.redraw(True) add = action_from_methods2('graph_add_dataset', add, undo_add, redo=redo_add) def remove(self, dataset): # we can do this even if `dataset` is a different object # than the one in self.datasets, if they have the same id # (see Dataset.__eq__) # TODO: why bother? just keep the object itself in the state ind = self.datasets.index(dataset) print 'removing dataset, index %d, position %d' % (dataset.ind, ind) dataset.id = '-'+dataset.id self.datasets.remove(dataset) try: dataset.disconnect('modified', self.on_dataset_modified) except NameError: pass self.emit('remove-dataset', dataset) self.redraw(True) return (dataset.ind, ind), None def undo_remove(self, data): ind, pos = data print 'undoing removal of dataset, index %d, position %d' % (ind, pos) dataset = Dataset(self, ind) dataset.id = dataset.id[1:] self.on_dataset_modified(dataset) self.datasets.insert(pos, dataset) dataset.connect('modified', self.on_dataset_modified) self.emit('add-dataset', dataset) self.redraw(True) remove = action_from_methods('graph_remove_dataset', remove, undo_remove) def on_dataset_modified(self, d=None): self.redraw(True) def paint_axes(self): for a in self.axes: a.paint_frame() self.grid_h.paint() self.grid_v.paint() def pos2y(self, pos): if pos.endswith('%'): return float(pos[:-1])*self.plot_height/100., '%' elif pos.endswith('y'): return self.data_to_phys(self.ymin, float(pos[:-1]))[1], 'y' elif pos.endswith('mm'): return float(pos[:-2]), 'mm' else: return float(pos), 'mm' def pos2x(self, pos): if pos.endswith('%'): return float(pos[:-1])*self.plot_width/100., '%' elif pos.endswith('x'): return self.data_to_phys(float(pos[:-1]), self.xmin)[0], 'x' elif pos.endswith('mm'): return float(pos[:-2]), 'mm' else: return float(pos), 'mm' def x2pos(self, x, typ): if typ=='%': return str(x*100./self.plot_width)+'%' elif typ=='x': return str(self.phys_to_data(x, 0)[0])+'x' elif typ=='mm': return str(x)+'mm' def y2pos(self, y, typ): if typ=='%': return str(y*100./self.plot_height)+'%' elif typ=='y': return str(self.phys_to_data(0, y)[1])+'y' elif typ=='mm': return str(y)+'mm' def data_to_phys(self, x, y): """ Takes a point x,y in data coordinates and transforms to physical coordinates """ x, xmin, xmax = map(self.axis_bottom.transform, (x, self.xmin, self.xmax)) y, ymin, ymax = map(self.axis_left.transform, (y, self.ymin, self.ymax)) px = self.plot_width * (x-xmin)/(xmax-xmin) py = self.plot_height * (y-ymin)/(ymax-ymin) # bt = self.axis_bottom.transform # lt = self.axis_left.transform # # px = self.plot_width * (bt(x) - bt(self.xmin)) / (bt(self.xmax) - bt(self.xmin)) # py = self.plot_height * (bt(y) - bt(self.ymin)) / (bt(self.ymax) - bt(self.ymin)) return px, py def phys_to_data(self, x, y): """ Takes a point x,y in physical coordinates and transforms to data coordinates """ xmin, xmax = map(self.axis_bottom.transform, (self.xmin, self.xmax)) ymin, ymax = map(self.axis_left.transform, (self.ymin, self.ymax)) px = x*(xmax-xmin)/self.plot_width + xmin py = y*(ymax-ymin)/self.plot_height + ymin return self.axis_bottom.invtransform(px), self.axis_left.invtransform(py) def mouse_to_phys(self, xm, ym): x = (xm / self.res) - self.marginl y = ((self.height_pixels-ym) / self.res) - self.marginb return x, y def mouse_to_data(self, xm, ym): x, y = self.mouse_to_phys(xm, ym) return self.phys_to_data(x, y) def autoscale(self): if len(self.datasets): xmin = min(d.xx.min() for d in self.datasets) xmax = max(d.xx.max() for d in self.datasets) ymin = min(d.yy.min() for d in self.datasets) ymax = max(d.yy.max() for d in self.datasets) self.zoom(xmin, xmax, ymin, ymax) def set_range(self, fr, to): self.fr, self.to = fr, to ##################### # zoom action # ##################### def zoom_do(self, state, xmin, xmax, ymin, ymax): eps = 1e-24 state['old'] = (self.xmin, self.xmax, self.ymin, self.ymax) if abs(xmin-xmax)<=eps or abs(ymin-ymax)<=eps: return self.xmin, self.xmax, self.ymin, self.ymax = xmin, xmax, ymin, ymax state['new'] = (xmin, xmax, ymin, ymax) def zoom_redo(self, state): self.xmin, self.xmax, self.ymin, self.ymax = state['new'] self.reshape() self.redraw(True) def zoom_undo(self, state): self.xmin, self.xmax, self.ymin, self.ymax = state['old'] self.reshape() self.redraw(True) def zoom_combine(self, state, other): return False zoom = action_from_methods2('graph-zoom', zoom_do, zoom_undo, redo=zoom_redo, combine=zoom_combine) def zoomout(self,x1, x2,x3, x4): if x3 == x4: return x1, x2 a = (x2-x1)/(x4-x3) c = x1 - a*x3 f1 = a*x1 + c f2 = a*x2 + c return min(f1, f2), max(f1, f2) def init(self): glClearColor(*self.background_color) glClear(GL_COLOR_BUFFER_BIT) # enable transparency glEnable (GL_BLEND) glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glDisable(GL_DEPTH_TEST) glShadeModel(GL_FLAT) # we need this to render pil fonts properly glPixelStorei(GL_UNPACK_ALIGNMENT, 1) glPixelStorei(GL_PACK_ALIGNMENT, 1) self.listno = glGenLists(1) def display(self, width=-1, height=-1): if not hasattr(self, 'listno'): return if width == -1 and height == -1: width, height = self.last_width, self.last_height else: self.last_width, self.last_height = width, height if not self.paint_xor_objects: if self.recalc: if not self.ps: for i, d in enumerate(self.datasets): glClearColor(*self.background_color) glClear(GL_COLOR_BUFFER_BIT) w, h, _, renderer=self.textpainter.render_text_chunk_symbol(str(i)) if renderer is not None: renderer((w/2)/self.res, (h/2)/self.res) i = wx.EmptyImage(w, h) data = glReadPixels(int(self.marginl*self.res), int(self.marginb*self.res), int(w), int(h), GL_RGB, GL_UNSIGNED_BYTE) i.SetData(data) d._legend_wxbitmap = i.ConvertToBitmap() self.emit('shape-changed') glDeleteLists(self.listno, 1) glNewList(self.listno, GL_COMPILE) glClearColor(*self.background_color) glClear(GL_COLOR_BUFFER_BIT) # set up clipping glClipPlane(GL_CLIP_PLANE0, [ 1, 0, 0, 0 ]) glClipPlane(GL_CLIP_PLANE1, [ -1, 0, 0, self.plot_width ]) glClipPlane(GL_CLIP_PLANE2, [ 0, 1, 0, 0 ]) glClipPlane(GL_CLIP_PLANE3, [ 0, -1, 0, self.plot_height ]) if len(self.datasets): for plane in [GL_CLIP_PLANE0, GL_CLIP_PLANE1, GL_CLIP_PLANE2, GL_CLIP_PLANE3]: glEnable(plane) for d in self.datasets: d.paint() for f in self.functions: f.paint() if len(self.datasets): for plane in [GL_CLIP_PLANE0, GL_CLIP_PLANE1, GL_CLIP_PLANE2, GL_CLIP_PLANE3]: glDisable(plane) self.paint_axes() glEndList() self.recalc = False glCallList(self.listno) for axis in self.axes: axis.paint_text() axis.paint_title() for o in self.graph_objects: o.draw() if self.mode == 'arrow' and self.selected_object == o: o.draw_handles() else: glLogicOp(GL_XOR) glEnable(GL_COLOR_LOGIC_OP) for o in self.objects: o.redraw() glDisable(GL_COLOR_LOGIC_OP) def reshape(self, width=-1, height=-1): if not hasattr(self, 'listno'): return t = time.time() if width == -1 and height == -1: width, height = self.last_width, self.last_height else: self.last_width, self.last_height = width, height # aspect ratio (width/height) self.aspect = self.pwidth/self.pheight # resolution (in pixels/mm) self.res = min(width/self.pwidth, height/self.pheight) displaydpi = 100. self.displayres = displaydpi / 25.4 # 25.4 = mm/inch self.magnification = self.res / self.displayres # set width and height self.width_pixels, self.height_pixels = width, height self.width_mm = width / self.res self.height_mm = height / self.res # measure titles facesize = self.axis_title_font_size*self.magnification if self.xtitle != '': _, tith = self.textpainter.render_text(self.xtitle, facesize, 0, 0, measure_only=True) else: tith=0 if self.ytitle != '': titw, _ = self.textpainter.render_text(self.ytitle, facesize, 0, 0, measure_only=True, orientation='v') else: titw=0 # measure tick labels try: self.ticw = max(self.textpainter.render_text(self.axis_left.totex(y), facesize, 0, 0, measure_only=True)[0] for y in self.axis_left.tics(self.ymin, self.ymax)[0]) # :-) except ValueError: self.ticw = 0 try: self.tich = max(self.textpainter.render_text(self.axis_bottom.totex(x), facesize, 0, 0, measure_only=True)[1] for x in self.axis_bottom.tics(self.xmin, self.xmax)[0]) except ValueError: self.tich = 0 # set margins (units are in mm) self.marginb = tith + self.tich + self.axis_title_font_size*self.magnification/2 + 2 self.margint = self.height_mm * 0.03 self.marginl = titw + self.ticw + self.axis_title_font_size*self.magnification/2 + 2 self.marginr = self.width_mm * 0.03 if self.width_mm/self.height_mm > self.aspect: self.marginr += (self.width_mm - self.height_mm*self.aspect)/2 self.marginl += (self.width_mm - self.height_mm*self.aspect)/2 else: self.margint += (self.height_mm - self.width_mm/self.aspect)/2 self.marginb += (self.height_mm - self.width_mm/self.aspect)/2 self.plot_width = self.width_mm - self.marginl - self.marginr self.plot_height = self.height_mm - self.margint - self.marginb # resize the viewport glViewport(0, 0, int(width), int(height)) self.viewport = glGetIntegerv(GL_VIEWPORT) # set opengl projection matrix with the origin # at the bottom left corner # of the graph # and scale in mm glMatrixMode (GL_PROJECTION) glLoadIdentity() glTranslated(-1.+2.*self.marginl/self.width_mm, -1.+2.*self.marginb/self.height_mm, 0) glScaled(2./self.width_mm, 2./self.height_mm, 1) # print >>sys.stderr, 'R: ', time.time()-t, "seconds" def export_ascii(self, outfile): # mathtext is not rendered directly self.pstext = [] save = self.width_pixels, self.height_pixels self.reshape(self.pwidth*self.displayres, self.pheight*self.displayres) d = tempfile.mkdtemp() filename = self.name + '.eps' f = open(os.path.join(d, filename), 'wb') gl2ps_BeginPage("Title", "Producer", self.viewport, f, filename) self.ps = True self.recalc = True self.display() self.ps = False gl2ps_EndPage() f.close() self.reshape(*save) f = open(d+'/'+filename, 'rb') for line in f: if line == '%%EndProlog\n': # insert encoded mathtext fonts # at the end of the prolog type42 = [] type42.append(FONTFILE) for fn in ['r', 'ex', 'mi', 'sy', 'tt']: type42.append(os.path.join(DATADIR, 'data', 'fonts', 'bakoma-cm', 'cm%s10.ttf'%fn)) for font in type42: print >>outfile, "%%BeginFont: "+FT2Font(str(font)).postscript_name print >>outfile, encodeTTFasPS(font) print >>outfile, "%%EndFont" outfile.write(line) elif line == 'showpage\n': # insert mathtext chunks # at the end of the file outfile.write(''.join(self.pstext)) outfile.write(line) else: # copy lines outfile.write(line) f.close() def show(self): for d in self.datasets: if not hasattr(d, 'xx'): d.recalculate() def button_press(self, x, y, button=None): if self.mode == 'zoom': if button in (1,3): self.paint_xor_objects = True self.pixx, self.pixy = x, y self.ix, self.iy = self.mouse_to_phys(x, y) self.rubberband.show(self.ix, self.iy, self.ix, self.iy) self.redraw() if button == 2: self.haha = True else: self.haha = False elif self.mode == 'hand': if self.selected_function is not None: self.selected_function.set_reg(False) self.selected_function.move(*self.mouse_to_data(x, y)) # self.emit('redraw') self._movefunc = DrawFunction(self, self.selected_function) self.objects.append(self._movefunc) self.paint_xor_objects = True self._movefunc.show(*self.mouse_to_data(x, y)) self.redraw() elif self.mode == 's-reader': self.paint_xor_objects = True self.cross.show(*self.mouse_to_phys(x, y)) self.redraw() self.emit('status-message', '%f, %f' % self.mouse_to_data(x, y)) elif self.mode == 'range': self.paint_xor_objects = True self.rangehandle.show(*self.mouse_to_phys(x, y)) self.redraw() elif self.mode == 'd-reader': qx, qy = self.mouse_to_data(x, y) distances = [] closest_ind = [] for d in self.datasets: dist = (d.xx-qx)*(d.xx-qx) + (d.yy-qy)*(d.yy-qy) arg = argmin(dist) closest_ind.append(arg) distances.append(dist[arg]) ind = argmin(distances) dataset = self.datasets[ind] x, y = dataset.xx[closest_ind[ind]], dataset.yy[closest_ind[ind]] self.paint_xor_objects = True self.cross.show(*self.data_to_phys(x, y)) self.redraw() self.emit('status-message', '%f, %f' % (x, y)) elif self.mode == 'arrow': if button == 1: x, y = self.mouse_to_phys(x, y) for o in self.graph_objects: if o.hittest(x, y): self.selected_object = o self.dragobj = o self.dragobj.rec = False self.dragobj_xor = Move(self.dragobj) self.objects.append(self.dragobj_xor) self.paint_xor_objects = True self.dragobj_xor.show(x, y) if o.hittest_handles(x, y): self.dragobj.dragstart = None break else: self.selected_object = None self.redraw() elif button == 3: self.emit('right-clicked', None) print >>sys.stderr, 'right-clicked', None elif self.mode in ('draw-line', 'draw-text'): xi, yi = self.mouse_to_phys(x, y) createobj = self.new_object({'draw-line': Line, 'draw-text': Text}[self.mode]) createobj.begin(xi, yi) self.dragobj = createobj self.dragobj_xor = Move(self.dragobj) self.objects.append(self.dragobj_xor) self.paint_xor_objects = True self.dragobj_xor.show(xi, yi) self.selected_object = createobj self.mode = 'arrow' self.redraw() self.emit('request-cursor', 'arrow') def button_doubleclick(self, x, y, button): if self.mode == 'arrow' and button == 1: x, y = self.mouse_to_phys(x, y) for o in self.graph_objects: if o.hittest_handles(x, y): self.emit('object-doubleclicked', o) o.emit('modified') break def button_release(self, x, y, button): if self.mode == 'zoom': if button == 2: self.autoscale() self.redraw(True) elif button == 1 or button == 3: self.rubberband.hide() self.redraw() self.paint_xor_objects = False zix, ziy = self.mouse_to_data(self.pixx, self.pixy) zfx, zfy = self.mouse_to_data(x, y) _xmin, _xmax = min(zix, zfx), max(zix, zfx) _ymin, _ymax = min(zfy, ziy), max(zfy, ziy) if button == 3: _xmin, _xmax = self.axis_bottom.transform(_xmin), self.axis_bottom.transform(_xmax) _ymin, _ymax = self.axis_left.transform(_ymin), self.axis_left.transform(_ymax) xmin, xmax = self.zoomout(self.axis_bottom.transform(self.xmin), self.axis_bottom.transform(self.xmax), _xmin, _xmax) ymin, ymax = self.zoomout(self.axis_left.transform(self.ymin), self.axis_left.transform(self.ymax), _ymin, _ymax) xmin, xmax = self.axis_bottom.invtransform(xmin), self.axis_bottom.invtransform(xmax) ymin, ymax = self.axis_left.invtransform(ymin), self.axis_left.invtransform(ymax) else: xmin, xmax, ymin, ymax = _xmin, _xmax, _ymin, _ymax self.zoom(xmin, xmax, ymin, ymax) self.reshape() self.redraw(True) elif self.mode == 'hand': if self.selected_function is not None: self.selected_function.set_reg(True) self.selected_function.move(*self.mouse_to_data(x, y)) del self.objects[-1] self.paint_xor_objects = False self.redraw(True) elif self.mode == 's-reader': self.cross.hide() self.redraw() self.paint_xor_objects = False elif self.mode == 'd-reader': self.cross.hide() self.redraw() self.paint_xor_objects = False elif self.mode == 'arrow': if button == 1: if self.dragobj is not None: self.dragobj.rec = True self.dragobj.record_position() self.dragobj = None self.dragobj_xor.hide() self.objects.remove(self.dragobj_xor) self.paint_xor_objects = False self.redraw() elif self.mode == 'range': if button is None: button = self.__button else: self.__button = button x, y = self.mouse_to_data(x, y) for d in self.selected_datasets: if button == 1: d.range = (x, d.range[1]) elif button == 3: d.range = (d.range[0], x) elif button == 2: d.range = (-inf, inf) self.rangehandle.hide() self.redraw() self.paint_xor_objects = False def button_motion(self, x, y, dragging): if self.mode == 'zoom' and dragging and hasattr(self, 'ix'): self.rubberband.move(self.ix, self.iy, *self.mouse_to_phys(x, y)) self.redraw() elif self.mode == 'range' and dragging: self.rangehandle.move(*self.mouse_to_phys(x, y)) self.redraw() # self.button_press(x, y) elif self.mode == 'hand' and dragging: if self.selected_function is not None: self.selected_function.move(*self.mouse_to_data(x, y)) self._movefunc.move(*self.mouse_to_data(x, y)) self.redraw() elif self.mode == 's-reader' and dragging: self.cross.move(*self.mouse_to_phys(x, y)) self.redraw() self.emit('status-message', '%f, %f' % self.mouse_to_data(x, y)) elif self.mode == 'd-reader' and dragging: qx, qy = self.mouse_to_data(x, y) distances = [] closest_ind = [] for d in self.datasets: dist = (d.xx-qx)*(d.xx-qx) + (d.yy-qy)*(d.yy-qy) arg = argmin(dist) closest_ind.append(arg) distances.append(dist[arg]) ind = argmin(distances) dataset = self.datasets[ind] x, y = dataset.xx[closest_ind[ind]], dataset.yy[closest_ind[ind]] self.cross.move(*self.data_to_phys(x, y)) self.redraw() self.emit('status-message', '%f, %f' % (x, y)) elif self.mode == 'arrow': if not hasattr(self, 'res'): # not initialized yet, do nothing return x, y = self.mouse_to_phys(x, y) if self.dragobj is not None: # drag a handle on an object self.dragobj_xor.move(x, y) self.redraw() self.emit('request-cursor', 'none') else: # look for handles for o in self.graph_objects: if o.hittest(x, y): self.emit('request-cursor', 'hand') break else: self.emit('request-cursor', 'arrow') def key_down(self, keycode): import wx if keycode == wx.WXK_DELETE and self.selected_object is not None: self.delete_object(self.selected_object) _xtype = wrap_attribute('xtype') _ytype = wrap_attribute('ytype') _xtitle = wrap_attribute('xtitle') _ytitle = wrap_attribute('ytitle') _zoom = wrap_attribute('zoom')
class Graph(Item, HasSignals): def __init__(self, project, name=None, parent=None, location=None): Item.__init__(self, project, name, parent, location) self.paint_xor_objects = False self.selected_datasets = [] self.mode = 'arrow' self.graph_objects = [] self.dragobj = None self.selected_object = None self.plot_height = 100 self.plot_width = 100 self.datasets = [] if location is not None: for i, l in enumerate(self.data.datasets): if not l.id.startswith('-'): self.datasets.append(Dataset(self, i)) self.datasets[-1].connect('modified', self.on_dataset_modified) for l in self.data.lines: if not l.id.startswith('-'): self.graph_objects.append(Line(self, l)) for l in self.data.text: if not l.id.startswith('-'): self.graph_objects.append(Text(self, l)) self.functions = [] # if location is not None: # for i in range(len(self.data.functions)): # if not self.data.functions[i].id.startswith('-'): # f = Function(self, i) # self.functions.append(f) # f.connect('modified', self.on_dataset_modified) # f.func.connect('modified', self.on_dataset_modified) self.ps = False self.axis_top = Axis('top', self) self.axis_bottom = Axis('bottom', self) self.axis_right = Axis('right', self) self.axis_left = Axis('left', self) self.axes = [ self.axis_top, self.axis_right, self.axis_bottom, self.axis_left ] self.grid_h = Grid('horizontal', self) self.grid_v = Grid('vertical', self) self.set_range(0.0, 100.5) if location is None: self.xmin, self.ymin = 0, 0 self.ymax, self.xmax = 10, 10 self.newf() if self.xtype == '': self._xtype = 'linear' if self.ytype == '': self._ytype = 'linear' self.selected_function = None self.rubberband = Rubberband(self) self.cross = Cross(self) self.rangehandle = Rangehandle(self) self.objects = [self.rubberband, self.cross, self.rangehandle] self.textpainter = TextPainter(self) self.axis_title_font_size = 12. self.background_color = (1., 1., 1., 1.) self.pwidth = 120. self.pheight = 100. self.recalc = True default_name_prefix = 'graph' def redraw(self, recalc=False): if recalc: self.recalc = True self.emit('redraw') def get_xmin(self): try: return float(self._zoom.split()[0]) except IndexError: return 0.0 def get_xmax(self): try: return float(self._zoom.split()[1]) except IndexError: return 1.0 def get_ymin(self): try: return float(self._zoom.split()[2]) except IndexError: return 0.0 def get_ymax(self): try: return float(self._zoom.split()[3]) except IndexError: return 1.0 def set_xmin(self, value): self._zoom = ' '.join( [str(f) for f in [value, self.xmax, self.ymin, self.ymax]]) def set_xmax(self, value): self._zoom = ' '.join( [str(f) for f in [self.xmin, value, self.ymin, self.ymax]]) def set_ymin(self, value): self._zoom = ' '.join( [str(f) for f in [self.xmin, self.xmax, value, self.ymax]]) def set_ymax(self, value): self._zoom = ' '.join( [str(f) for f in [self.xmin, self.xmax, self.ymin, value]]) xmin = property(get_xmin, set_xmin) xmax = property(get_xmax, set_xmax) ymin = property(get_ymin, set_ymin) ymax = property(get_ymax, set_ymax) # axis scales def set_xtype(self, _state, tp): if tp == 'log' and (self.xmin <= 0 or self.xmax <= 0): raise StopAction _state['old'] = self._xtype self._xtype = tp self.redraw(True) def undo_set_xtype(self, _state): self._xtype = _state['old'] self.redraw(True) set_xtype = action_from_methods2('graph-set-xaxis-scale', set_xtype, undo_set_xtype) def get_xtype(self): return self._xtype xtype = property(get_xtype, set_xtype) def set_ytype(self, _state, tp): if tp == 'log' and (self.xmin <= 0 or self.xmax <= 0): raise StopAction _state['old'] = self._ytype self._ytype = tp self.redraw(True) def undo_set_ytype(self, _state): self._ytype = _state['old'] self.redraw(True) set_ytype = action_from_methods2('graph-set-xaxis-scale', set_ytype, undo_set_ytype) def get_ytype(self): return self._ytype ytype = property(get_ytype, set_ytype) # titles def set_xtitle(self, state, title): state['old'], state['new'] = self._xtitle, title self._xtitle = title self.reshape() self.redraw() def undo_set_xtitle(self, state): self._xtitle = state['old'] self.reshape() self.redraw() def redo_set_xtitle(self, state): self._xtitle = state['new'] self.reshape() self.redraw() def get_xtitle(self): return self._xtitle set_xtitle = action_from_methods2('graph/set-xtitle', set_xtitle, undo_set_xtitle, redo=redo_set_xtitle) xtitle = property(get_xtitle, set_xtitle) def set_ytitle(self, state, title): state['old'], state['new'] = self._ytitle, title self._ytitle = title self.reshape() self.redraw() def undo_set_ytitle(self, state): self._ytitle = state['old'] self.reshape() self.redraw() def redo_set_ytitle(self, state): self._ytitle = state['new'] self.reshape() self.redraw() def get_ytitle(self): return self._ytitle set_ytitle = action_from_methods2('graph/set-ytitle', set_ytitle, undo_set_ytitle, redo=redo_set_ytitle) ytitle = property(get_ytitle, set_ytitle) def __repr__(self): return '<Graph %s%s>' % (self.name, '(deleted)' * self.id.startswith('-')) def newf(self): # ind = self.data.functions.append(id=create_id()) f = Function(self) f.connect('modified', self.on_dataset_modified) f.func.connect('modified', self.on_dataset_modified) f.func.connect('add-term', self.on_dataset_modified) f.func.connect('remove-term', self.on_dataset_modified) self.functions.append(f) self.emit('add-function', f) return f def create_legend(self): legend = self.new_object(Text) legend.text = '\n'.join('@%d@' % i + str(d) for i, d in enumerate(self.datasets)) # add and remove graph objects def new_object(self, state, typ): location = {Line: self.data.lines, Text: self.data.text}[typ] ind = location.append(id=create_id()) obj = typ(self, location[ind]) self.graph_objects.append(obj) state['obj'] = obj return obj def undo_new_object(self, state): obj = state['obj'] self.graph_objects.remove(obj) obj.id = '-' + obj.id self.redraw() def redo_new_object(self, state): obj = state['obj'] self.graph_objects.append(obj) location = {Line: self.data.lines, Text: self.data.text}[type(obj)] obj.id = obj.id[1:] self.redraw() new_object = action_from_methods2('graph/new-object', new_object, undo_new_object, redo=redo_new_object) def delete_object(self, state, obj): obj.id = '-' + obj.id self.graph_objects.remove(obj) state['obj'] = obj self.redraw() delete_object = action_from_methods2('graph/delete-object', delete_object, redo_new_object, redo=undo_new_object) # add and remove datasets def add(self, state, x, y): ind = self.data.datasets.append(worksheet=x.worksheet.id, id=create_id(), x=x.name.encode('utf-8'), y=y.name.encode('utf-8')) d = Dataset(self, ind) self.datasets.append(d) pos = len(self.datasets) - 1 # print 'added dataset, index %d, position %d' % (ind, pos) d.connect('modified', self.on_dataset_modified) d.connect_signals() self.on_dataset_modified(d) self.emit('add-dataset', d) state['obj'] = d return pos def undo_add(self, state): d = state['obj'] # print 'undoing addition of dataset, index %d, position %d' % (d.ind, pos) self.datasets.remove(d) d.disconnect_signals() d.disconnect('modified', self.on_dataset_modified) self.emit('remove-dataset', d) self.redraw(True) d.id = '-' + d.id # self.data.datasets.delete(d.ind) def redo_add(self, state): d = state['obj'] d.id = d.id[1:] self.datasets.append(d) d.connect('modified', self.on_dataset_modified) d.connect_signals() self.emit('add-dataset', d) self.redraw(True) add = action_from_methods2('graph_add_dataset', add, undo_add, redo=redo_add) def remove(self, dataset): # we can do this even if `dataset` is a different object # than the one in self.datasets, if they have the same id # (see Dataset.__eq__) # TODO: why bother? just keep the object itself in the state ind = self.datasets.index(dataset) print 'removing dataset, index %d, position %d' % (dataset.ind, ind) dataset.id = '-' + dataset.id self.datasets.remove(dataset) try: dataset.disconnect('modified', self.on_dataset_modified) except NameError: pass self.emit('remove-dataset', dataset) self.redraw(True) return (dataset.ind, ind), None def undo_remove(self, data): ind, pos = data print 'undoing removal of dataset, index %d, position %d' % (ind, pos) dataset = Dataset(self, ind) dataset.id = dataset.id[1:] self.on_dataset_modified(dataset) self.datasets.insert(pos, dataset) dataset.connect('modified', self.on_dataset_modified) self.emit('add-dataset', dataset) self.redraw(True) remove = action_from_methods('graph_remove_dataset', remove, undo_remove) def on_dataset_modified(self, d=None): self.redraw(True) def paint_axes(self): for a in self.axes: a.paint_frame() self.grid_h.paint() self.grid_v.paint() def pos2y(self, pos): if pos.endswith('%'): return float(pos[:-1]) * self.plot_height / 100., '%' elif pos.endswith('y'): return self.data_to_phys(self.ymin, float(pos[:-1]))[1], 'y' elif pos.endswith('mm'): return float(pos[:-2]), 'mm' else: return float(pos), 'mm' def pos2x(self, pos): if pos.endswith('%'): return float(pos[:-1]) * self.plot_width / 100., '%' elif pos.endswith('x'): return self.data_to_phys(float(pos[:-1]), self.xmin)[0], 'x' elif pos.endswith('mm'): return float(pos[:-2]), 'mm' else: return float(pos), 'mm' def x2pos(self, x, typ): if typ == '%': return str(x * 100. / self.plot_width) + '%' elif typ == 'x': return str(self.phys_to_data(x, 0)[0]) + 'x' elif typ == 'mm': return str(x) + 'mm' def y2pos(self, y, typ): if typ == '%': return str(y * 100. / self.plot_height) + '%' elif typ == 'y': return str(self.phys_to_data(0, y)[1]) + 'y' elif typ == 'mm': return str(y) + 'mm' def data_to_phys(self, x, y): """ Takes a point x,y in data coordinates and transforms to physical coordinates """ x, xmin, xmax = map(self.axis_bottom.transform, (x, self.xmin, self.xmax)) y, ymin, ymax = map(self.axis_left.transform, (y, self.ymin, self.ymax)) px = self.plot_width * (x - xmin) / (xmax - xmin) py = self.plot_height * (y - ymin) / (ymax - ymin) # bt = self.axis_bottom.transform # lt = self.axis_left.transform # # px = self.plot_width * (bt(x) - bt(self.xmin)) / (bt(self.xmax) - bt(self.xmin)) # py = self.plot_height * (bt(y) - bt(self.ymin)) / (bt(self.ymax) - bt(self.ymin)) return px, py def phys_to_data(self, x, y): """ Takes a point x,y in physical coordinates and transforms to data coordinates """ xmin, xmax = map(self.axis_bottom.transform, (self.xmin, self.xmax)) ymin, ymax = map(self.axis_left.transform, (self.ymin, self.ymax)) px = x * (xmax - xmin) / self.plot_width + xmin py = y * (ymax - ymin) / self.plot_height + ymin return self.axis_bottom.invtransform(px), self.axis_left.invtransform( py) def mouse_to_phys(self, xm, ym): x = (xm / self.res) - self.marginl y = ((self.height_pixels - ym) / self.res) - self.marginb return x, y def mouse_to_data(self, xm, ym): x, y = self.mouse_to_phys(xm, ym) return self.phys_to_data(x, y) def autoscale(self): if len(self.datasets): xmin = min(d.xx.min() for d in self.datasets) xmax = max(d.xx.max() for d in self.datasets) ymin = min(d.yy.min() for d in self.datasets) ymax = max(d.yy.max() for d in self.datasets) self.zoom(xmin, xmax, ymin, ymax) def set_range(self, fr, to): self.fr, self.to = fr, to ##################### # zoom action # ##################### def zoom_do(self, state, xmin, xmax, ymin, ymax): eps = 1e-24 state['old'] = (self.xmin, self.xmax, self.ymin, self.ymax) if abs(xmin - xmax) <= eps or abs(ymin - ymax) <= eps: return self.xmin, self.xmax, self.ymin, self.ymax = xmin, xmax, ymin, ymax state['new'] = (xmin, xmax, ymin, ymax) def zoom_redo(self, state): self.xmin, self.xmax, self.ymin, self.ymax = state['new'] self.reshape() self.redraw(True) def zoom_undo(self, state): self.xmin, self.xmax, self.ymin, self.ymax = state['old'] self.reshape() self.redraw(True) def zoom_combine(self, state, other): return False zoom = action_from_methods2('graph-zoom', zoom_do, zoom_undo, redo=zoom_redo, combine=zoom_combine) def zoomout(self, x1, x2, x3, x4): if x3 == x4: return x1, x2 a = (x2 - x1) / (x4 - x3) c = x1 - a * x3 f1 = a * x1 + c f2 = a * x2 + c return min(f1, f2), max(f1, f2) def init(self): glClearColor(*self.background_color) glClear(GL_COLOR_BUFFER_BIT) # enable transparency glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glDisable(GL_DEPTH_TEST) glShadeModel(GL_FLAT) # we need this to render pil fonts properly glPixelStorei(GL_UNPACK_ALIGNMENT, 1) glPixelStorei(GL_PACK_ALIGNMENT, 1) self.listno = glGenLists(1) def display(self, width=-1, height=-1): if not hasattr(self, 'listno'): return if width == -1 and height == -1: width, height = self.last_width, self.last_height else: self.last_width, self.last_height = width, height if not self.paint_xor_objects: if self.recalc: if not self.ps: for i, d in enumerate(self.datasets): glClearColor(*self.background_color) glClear(GL_COLOR_BUFFER_BIT) w, h, _, renderer = self.textpainter.render_text_chunk_symbol( str(i)) if renderer is not None: renderer((w / 2) / self.res, (h / 2) / self.res) i = wx.EmptyImage(w, h) data = glReadPixels(int(self.marginl * self.res), int(self.marginb * self.res), int(w), int(h), GL_RGB, GL_UNSIGNED_BYTE) i.SetData(data) d._legend_wxbitmap = i.ConvertToBitmap() self.emit('shape-changed') glDeleteLists(self.listno, 1) glNewList(self.listno, GL_COMPILE) glClearColor(*self.background_color) glClear(GL_COLOR_BUFFER_BIT) # set up clipping glClipPlane(GL_CLIP_PLANE0, [1, 0, 0, 0]) glClipPlane(GL_CLIP_PLANE1, [-1, 0, 0, self.plot_width]) glClipPlane(GL_CLIP_PLANE2, [0, 1, 0, 0]) glClipPlane(GL_CLIP_PLANE3, [0, -1, 0, self.plot_height]) if len(self.datasets): for plane in [ GL_CLIP_PLANE0, GL_CLIP_PLANE1, GL_CLIP_PLANE2, GL_CLIP_PLANE3 ]: glEnable(plane) for d in self.datasets: d.paint() for f in self.functions: f.paint() if len(self.datasets): for plane in [ GL_CLIP_PLANE0, GL_CLIP_PLANE1, GL_CLIP_PLANE2, GL_CLIP_PLANE3 ]: glDisable(plane) self.paint_axes() glEndList() self.recalc = False glCallList(self.listno) for axis in self.axes: axis.paint_text() axis.paint_title() for o in self.graph_objects: o.draw() if self.mode == 'arrow' and self.selected_object == o: o.draw_handles() else: glLogicOp(GL_XOR) glEnable(GL_COLOR_LOGIC_OP) for o in self.objects: o.redraw() glDisable(GL_COLOR_LOGIC_OP) def reshape(self, width=-1, height=-1): if not hasattr(self, 'listno'): return t = time.time() if width == -1 and height == -1: width, height = self.last_width, self.last_height else: self.last_width, self.last_height = width, height # aspect ratio (width/height) self.aspect = self.pwidth / self.pheight # resolution (in pixels/mm) self.res = min(width / self.pwidth, height / self.pheight) displaydpi = 100. self.displayres = displaydpi / 25.4 # 25.4 = mm/inch self.magnification = self.res / self.displayres # set width and height self.width_pixels, self.height_pixels = width, height self.width_mm = width / self.res self.height_mm = height / self.res # measure titles facesize = self.axis_title_font_size * self.magnification if self.xtitle != '': _, tith = self.textpainter.render_text(self.xtitle, facesize, 0, 0, measure_only=True) else: tith = 0 if self.ytitle != '': titw, _ = self.textpainter.render_text(self.ytitle, facesize, 0, 0, measure_only=True, orientation='v') else: titw = 0 # measure tick labels try: self.ticw = max( self.textpainter.render_text(self.axis_left.totex(y), facesize, 0, 0, measure_only=True)[0] for y in self.axis_left.tics(self.ymin, self.ymax)[0]) # :-) except ValueError: self.ticw = 0 try: self.tich = max( self.textpainter.render_text(self.axis_bottom.totex(x), facesize, 0, 0, measure_only=True)[1] for x in self.axis_bottom.tics(self.xmin, self.xmax)[0]) except ValueError: self.tich = 0 # set margins (units are in mm) self.marginb = tith + self.tich + self.axis_title_font_size * self.magnification / 2 + 2 self.margint = self.height_mm * 0.03 self.marginl = titw + self.ticw + self.axis_title_font_size * self.magnification / 2 + 2 self.marginr = self.width_mm * 0.03 if self.width_mm / self.height_mm > self.aspect: self.marginr += (self.width_mm - self.height_mm * self.aspect) / 2 self.marginl += (self.width_mm - self.height_mm * self.aspect) / 2 else: self.margint += (self.height_mm - self.width_mm / self.aspect) / 2 self.marginb += (self.height_mm - self.width_mm / self.aspect) / 2 self.plot_width = self.width_mm - self.marginl - self.marginr self.plot_height = self.height_mm - self.margint - self.marginb # resize the viewport glViewport(0, 0, int(width), int(height)) self.viewport = glGetIntegerv(GL_VIEWPORT) # set opengl projection matrix with the origin # at the bottom left corner # of the graph # and scale in mm glMatrixMode(GL_PROJECTION) glLoadIdentity() glTranslated(-1. + 2. * self.marginl / self.width_mm, -1. + 2. * self.marginb / self.height_mm, 0) glScaled(2. / self.width_mm, 2. / self.height_mm, 1) # print >>sys.stderr, 'R: ', time.time()-t, "seconds" def export_ascii(self, outfile): # mathtext is not rendered directly self.pstext = [] save = self.width_pixels, self.height_pixels self.reshape(self.pwidth * self.displayres, self.pheight * self.displayres) d = tempfile.mkdtemp() filename = self.name + '.eps' f = open(os.path.join(d, filename), 'wb') gl2ps_BeginPage("Title", "Producer", self.viewport, f, filename) self.ps = True self.recalc = True self.display() self.ps = False gl2ps_EndPage() f.close() self.reshape(*save) f = open(d + '/' + filename, 'rb') for line in f: if line == '%%EndProlog\n': # insert encoded mathtext fonts # at the end of the prolog type42 = [] type42.append(FONTFILE) for fn in ['r', 'ex', 'mi', 'sy', 'tt']: type42.append( os.path.join(DATADIR, 'data', 'fonts', 'bakoma-cm', 'cm%s10.ttf' % fn)) for font in type42: print >> outfile, "%%BeginFont: " + FT2Font( str(font)).postscript_name print >> outfile, encodeTTFasPS(font) print >> outfile, "%%EndFont" outfile.write(line) elif line == 'showpage\n': # insert mathtext chunks # at the end of the file outfile.write(''.join(self.pstext)) outfile.write(line) else: # copy lines outfile.write(line) f.close() def show(self): for d in self.datasets: if not hasattr(d, 'xx'): d.recalculate() def button_press(self, x, y, button=None): if self.mode == 'zoom': if button in (1, 3): self.paint_xor_objects = True self.pixx, self.pixy = x, y self.ix, self.iy = self.mouse_to_phys(x, y) self.rubberband.show(self.ix, self.iy, self.ix, self.iy) self.redraw() if button == 2: self.haha = True else: self.haha = False elif self.mode == 'hand': if self.selected_function is not None: self.selected_function.set_reg(False) self.selected_function.move(*self.mouse_to_data(x, y)) # self.emit('redraw') self._movefunc = DrawFunction(self, self.selected_function) self.objects.append(self._movefunc) self.paint_xor_objects = True self._movefunc.show(*self.mouse_to_data(x, y)) self.redraw() elif self.mode == 's-reader': self.paint_xor_objects = True self.cross.show(*self.mouse_to_phys(x, y)) self.redraw() self.emit('status-message', '%f, %f' % self.mouse_to_data(x, y)) elif self.mode == 'range': self.paint_xor_objects = True self.rangehandle.show(*self.mouse_to_phys(x, y)) self.redraw() elif self.mode == 'd-reader': qx, qy = self.mouse_to_data(x, y) distances = [] closest_ind = [] for d in self.datasets: dist = (d.xx - qx) * (d.xx - qx) + (d.yy - qy) * (d.yy - qy) arg = argmin(dist) closest_ind.append(arg) distances.append(dist[arg]) ind = argmin(distances) dataset = self.datasets[ind] x, y = dataset.xx[closest_ind[ind]], dataset.yy[closest_ind[ind]] self.paint_xor_objects = True self.cross.show(*self.data_to_phys(x, y)) self.redraw() self.emit('status-message', '%f, %f' % (x, y)) elif self.mode == 'arrow': if button == 1: x, y = self.mouse_to_phys(x, y) for o in self.graph_objects: if o.hittest(x, y): self.selected_object = o self.dragobj = o self.dragobj.rec = False self.dragobj_xor = Move(self.dragobj) self.objects.append(self.dragobj_xor) self.paint_xor_objects = True self.dragobj_xor.show(x, y) if o.hittest_handles(x, y): self.dragobj.dragstart = None break else: self.selected_object = None self.redraw() elif button == 3: self.emit('right-clicked', None) print >> sys.stderr, 'right-clicked', None elif self.mode in ('draw-line', 'draw-text'): xi, yi = self.mouse_to_phys(x, y) createobj = self.new_object({ 'draw-line': Line, 'draw-text': Text }[self.mode]) createobj.begin(xi, yi) self.dragobj = createobj self.dragobj_xor = Move(self.dragobj) self.objects.append(self.dragobj_xor) self.paint_xor_objects = True self.dragobj_xor.show(xi, yi) self.selected_object = createobj self.mode = 'arrow' self.redraw() self.emit('request-cursor', 'arrow') def button_doubleclick(self, x, y, button): if self.mode == 'arrow' and button == 1: x, y = self.mouse_to_phys(x, y) for o in self.graph_objects: if o.hittest_handles(x, y): self.emit('object-doubleclicked', o) o.emit('modified') break def button_release(self, x, y, button): if self.mode == 'zoom': if button == 2: self.autoscale() self.redraw(True) elif button == 1 or button == 3: self.rubberband.hide() self.redraw() self.paint_xor_objects = False zix, ziy = self.mouse_to_data(self.pixx, self.pixy) zfx, zfy = self.mouse_to_data(x, y) _xmin, _xmax = min(zix, zfx), max(zix, zfx) _ymin, _ymax = min(zfy, ziy), max(zfy, ziy) if button == 3: _xmin, _xmax = self.axis_bottom.transform( _xmin), self.axis_bottom.transform(_xmax) _ymin, _ymax = self.axis_left.transform( _ymin), self.axis_left.transform(_ymax) xmin, xmax = self.zoomout( self.axis_bottom.transform(self.xmin), self.axis_bottom.transform(self.xmax), _xmin, _xmax) ymin, ymax = self.zoomout( self.axis_left.transform(self.ymin), self.axis_left.transform(self.ymax), _ymin, _ymax) xmin, xmax = self.axis_bottom.invtransform( xmin), self.axis_bottom.invtransform(xmax) ymin, ymax = self.axis_left.invtransform( ymin), self.axis_left.invtransform(ymax) else: xmin, xmax, ymin, ymax = _xmin, _xmax, _ymin, _ymax self.zoom(xmin, xmax, ymin, ymax) self.reshape() self.redraw(True) elif self.mode == 'hand': if self.selected_function is not None: self.selected_function.set_reg(True) self.selected_function.move(*self.mouse_to_data(x, y)) del self.objects[-1] self.paint_xor_objects = False self.redraw(True) elif self.mode == 's-reader': self.cross.hide() self.redraw() self.paint_xor_objects = False elif self.mode == 'd-reader': self.cross.hide() self.redraw() self.paint_xor_objects = False elif self.mode == 'arrow': if button == 1: if self.dragobj is not None: self.dragobj.rec = True self.dragobj.record_position() self.dragobj = None self.dragobj_xor.hide() self.objects.remove(self.dragobj_xor) self.paint_xor_objects = False self.redraw() elif self.mode == 'range': if button is None: button = self.__button else: self.__button = button x, y = self.mouse_to_data(x, y) for d in self.selected_datasets: if button == 1: d.range = (x, d.range[1]) elif button == 3: d.range = (d.range[0], x) elif button == 2: d.range = (-inf, inf) self.rangehandle.hide() self.redraw() self.paint_xor_objects = False def button_motion(self, x, y, dragging): if self.mode == 'zoom' and dragging and hasattr(self, 'ix'): self.rubberband.move(self.ix, self.iy, *self.mouse_to_phys(x, y)) self.redraw() elif self.mode == 'range' and dragging: self.rangehandle.move(*self.mouse_to_phys(x, y)) self.redraw() # self.button_press(x, y) elif self.mode == 'hand' and dragging: if self.selected_function is not None: self.selected_function.move(*self.mouse_to_data(x, y)) self._movefunc.move(*self.mouse_to_data(x, y)) self.redraw() elif self.mode == 's-reader' and dragging: self.cross.move(*self.mouse_to_phys(x, y)) self.redraw() self.emit('status-message', '%f, %f' % self.mouse_to_data(x, y)) elif self.mode == 'd-reader' and dragging: qx, qy = self.mouse_to_data(x, y) distances = [] closest_ind = [] for d in self.datasets: dist = (d.xx - qx) * (d.xx - qx) + (d.yy - qy) * (d.yy - qy) arg = argmin(dist) closest_ind.append(arg) distances.append(dist[arg]) ind = argmin(distances) dataset = self.datasets[ind] x, y = dataset.xx[closest_ind[ind]], dataset.yy[closest_ind[ind]] self.cross.move(*self.data_to_phys(x, y)) self.redraw() self.emit('status-message', '%f, %f' % (x, y)) elif self.mode == 'arrow': if not hasattr(self, 'res'): # not initialized yet, do nothing return x, y = self.mouse_to_phys(x, y) if self.dragobj is not None: # drag a handle on an object self.dragobj_xor.move(x, y) self.redraw() self.emit('request-cursor', 'none') else: # look for handles for o in self.graph_objects: if o.hittest(x, y): self.emit('request-cursor', 'hand') break else: self.emit('request-cursor', 'arrow') def key_down(self, keycode): import wx if keycode == wx.WXK_DELETE and self.selected_object is not None: self.delete_object(self.selected_object) _xtype = wrap_attribute('xtype') _ytype = wrap_attribute('ytype') _xtitle = wrap_attribute('xtitle') _ytitle = wrap_attribute('ytitle') _zoom = wrap_attribute('zoom')