class at_graph(Frame): def __init__(self, parent): Frame.__init__(self, parent) self.parent = parent self.u = utils('atoutput.pkl') self.km = dict() self.price = dict() self.km[0] = (min(self.u.all_km), max(self.u.all_km)) self.price[0] = (min(self.u.all_price), max(self.u.all_price)) self.zoom_level = 0 try: self.parent.title("Auto trader results") self.is_standalone = True except: self.is_standalone = False self.style = Style() self.style.theme_use("classic") # Assume the parent is the root widget; make the frame take up the # entire widget size. print self.is_standalone if self.is_standalone: self.w, self.h = map(int, self.parent.geometry().split('+')[0].split('x')) self.w, self.h = 800, 800 else: self.w, self.h = 600, 600 self.c = None # Are they hovering over a data point? self.is_hovering = False # Filter the description strings: lower and whiten any non-matching # data point. self.filter = '' self.re = list() self.replot() def replot(self, zlfrac=None): """Replot the graph. If zlfrac is not None, then it should be a fractional value between 0 and 1; this is used to do smooth zooming, which doesn't plot the axes (it only redraws the car points).""" if self.c is not None: self.c.destroy() self.c = Canvas(self, height=self.h, width=self.w, bd=1, bg='#f3f5f9') self.c.grid(sticky=S, pady=1, padx=1) zl = self.zoom_level if zlfrac is not None: z1l, z1h = self.zoom_price_start z2l, z2h = self.zoom_price_end price_low = z1l + (z2l - z1l) * zlfrac price_high = z1h + (z2h - z1h) * zlfrac z1l, z1h = self.zoom_km_start z2l, z2h = self.zoom_km_end km_low = z1l + (z2l - z1l) * zlfrac km_high = z1h + (z2h - z1h) * zlfrac self.axis((price_low, price_high), 'y', draw=False) self.axis((km_low, km_high), 'x', draw=False) self.car_points(draw=False) else: self.axis(self.price[zl], 'y') self.axis(self.km[zl], 'x') self.car_points() self.pack(fill=BOTH, expand=1) def xyp(self, x, y): "Given x in km and y in $, return canvas position (xp, yp)." xp = int(math.floor((1.0 * x - self.x1) / (self.x2 - self.x1) \ * (self.xp2 - self.xp1) + self.xp1 + 0.5)) yp = int(math.floor((1.0 * y - self.y1) / (self.y2 - self.y1) \ * (self.yp2 - self.yp1) + self.yp1 + 0.5)) return (xp, yp) def axis(self, arange, ax, draw=True): "Add an axis ax='x' or 'y', with arange=(min, max) values." if draw: a1, a2, ast = self.u.axis(*arange) else: a1, a2 = arange ast = (a2 - a1) * 0.2 nt = int(math.floor((a2 - a1) / ast + 0.5)) + 1 st_offset = 50 # Remember the min and max axis values, along with the canvas points # that correspond to each location (xp1 and xp2). This allows us to # calculate where on the canvas a particular (x, y) value falls. if ax == 'x': self.x1, self.x2 = a1, a2 self.xp1, self.xp2 = st_offset, self.w - st_offset self.xtick = [a1 + i * ast for i in range(nt)] # Remember where the midpoint of the axis is, relative to canvas. self.xmid = (self.xp1 + self.xp2) / 2 else: self.y1, self.y2 = a1, a2 self.yp1, self.yp2 = self.h - st_offset, st_offset self.ytick = [a1 + i * ast for i in range(nt)] # Remember where the midpoint of the axis is, relative to canvas. self.ymid = (self.yp1 + self.yp2) / 2 # Figure out tick labels. atick = ['%g' % ((a1 + i * ast) / 1000) for i in range(nt)] # Figure out maximum decimal places on all tick labels, and ensure # they all have that many decimal places. max_dec = max(map(lambda x: 0 if '.' not in x else len(x.split('.')[1]), atick)) if max_dec > 0: atick = map(lambda x: x + '.' + '0'*max_dec if '.' not in x else x + '0'*(max_dec - len(x.split('.')[1])), atick) yst, xst = self.h - st_offset, st_offset # Draw axis line proper, and axis label. if draw: if ax == 'x': self.c.create_line(xst, yst, self.w - st_offset, yst) xp = (xst + self.w - st_offset) / 2 self.c.create_text(xp, yst + 30, text='Mileage (km x 1000)') else: self.c.create_line(xst, yst, xst, st_offset) self.c.create_text(xst, st_offset - 30, text='Price') self.c.create_text(xst, st_offset - 15, text='($000)') tick_anchor = [N, E][ax == 'y'] tick_x, tick_y = xst, yst tick_step = ([self.w, self.h][ax == 'y'] - st_offset * 2 * 1.0) / \ (nt - 1) label_offset = 3 for i1, tick in enumerate(atick): x_of, y_of = -label_offset, label_offset if ax == 'y': y_of = int(-i1 * tick_step) else: x_of = int(i1 * tick_step) if draw: self.c.create_text(tick_x + x_of, tick_y + y_of, text=tick, anchor=tick_anchor) x_mini, y_mini = 0, 0 x_maxi, y_maxi = 0, 0 if ax == 'y': x_of += label_offset x_mini, x_maxi = 8, self.w - st_offset * 2 # Remember what y coord this grid line is at. if i1 == 0: self.y_grid = [] self.y_grid.append(tick_y + y_of) else: y_of -= label_offset y_mini, y_maxi = -8, st_offset * 2 - self.h # Remember what x coord this grid line is at. if i1 == 0: self.x_grid = [] self.x_grid.append(tick_x + x_of) if draw: # Draw the little solid tick, next to the axis. self.c.create_line(tick_x + x_of, tick_y + y_of, tick_x + x_of + x_mini, tick_y + y_of + y_mini) # Draw a dashed grid line, across the entire graph. self.c.create_line(tick_x + x_of, tick_y + y_of, tick_x + x_of + x_maxi, tick_y + y_of + y_maxi, dash=(1, 3)) def car_points(self, draw=True): "Plot the cars themselves." # 199 215 151 151 199 224 230 162 157 250 224 167 178 165 192 249 200 216 204 204 204 191 173 158 color_order = ['#c7d797', '#97c7e0', '#e6a29d', '#fae0a7', '#b2a5c0', '#f9c8d8', '#bfad9e', '#cccccc'] #color_order = ['#98df8a', '#dbdb8d', '#aec7e8', '#c9acd4', '#f7b6d2', # '#ffbb80', '#dc9b8d', '#e9ab17', '#dddddd'] # Those colors above aren't saturated enough. Saturate them more. color_order = map(lambda x: resaturate(x, -80), color_order) # Change color depending on year. cy = dict() for i1, year in enumerate(reversed(sorted(set(self.u.all_year)))): cy[year] = color_order[-1] if i1 < len(color_order): cy[year] = color_order[i1] i1 = -1 # Tuples of (index into self.u.all_* arrays, x position, y position). self.ov_dict = dict() if draw: self.c.focus_set() self.c.bind('<Button-1>', func=self.zoom) self.c.bind('<Button-2>', func=self.unzoom) self.c.bind('<Left>', func=self.left_key) self.c.bind('<Right>', func=self.right_key) self.c.bind('<Up>', func=self.up_key) self.c.bind('<Down>', func=self.down_key) legend = set() osz = 3 + self.zoom_level * 1 # Total vehicle count, and vehicles which pass the filter count. self.vcount = self.fcount = 0 for year, km, price in zip(self.u.all_year, self.u.all_km, self.u.all_price): x, y = self.xyp(km, price) i1 += 1 if x < self.x_grid[0] or x > self.x_grid[-1] or \ y > self.y_grid[0] or y < self.y_grid[-1]: continue self.vcount += 1 legend.add((year, cy[year])) filtered = False if not re.search(self.filter, self.u.all_descr[i1], re.I): filtered = True # If a data point is filtered out, make its outline reflect its # model year, and fill it with white. # # Else, make its outline and fill color reflect the model year, and # upon mouseover, make it entirely red. ov = self.c.create_oval(x-osz, y-osz, x+osz, y+osz, outline=cy[year], activeoutline=['red', cy[year]][filtered], fill=[cy[year], 'white'][filtered], activefill=['red', 'white'][filtered], ) self.ov_dict[ov] = (i1, x, y, cy[year], filtered) # If a data point is filtered out, mousing over it does nothing, # and also, lower it behind everything else. if filtered: self.c.lower(ov) else: self.fcount += 1 if draw: use_tag = 'Tag %d' % i1 self.c.addtag_withtag(use_tag, ov) self.c.tag_bind(use_tag, sequence='<Enter>', func=self.mouseover) self.c.tag_bind(use_tag, sequence='<Leave>', func=self.mouseoff) self.c.tag_bind(use_tag, sequence='<Button-1>', func=self.select) if draw: # OK, add a legend for every year that's displayed. i1 = 0 for yr, color in reversed(sorted(legend)): xp, yp = self.x_grid[-1] + 10, self.y_grid[-1] + 15 * i1 self.c.create_oval(xp-osz, yp-osz, xp+osz, yp+osz, outline=color, fill=color) self.c.create_text(xp + 8, yp, text=str(yr), anchor=W) i1 += 1 # And, add a title. tistr = 'Vehicle count: %d' % self.vcount if self.fcount != self.vcount: tistr = 'Filtered vehicle count: %d' % self.fcount xp = (self.x_grid[0] + self.x_grid[-1]) / 2 yp = self.y_grid[-1] - 30 self.c.create_text(xp, yp, text=tistr, font=('Helvetica', '16')) zstr1 = 'Click on a blank graph location to zoom in' zstr2 = 'Right click to zoom out' if self.zoom_level == 0: zstr = zstr1 elif self.zoom_level == 2: zstr = zstr2 else: zstr = zstr1 + ', or r' + zstr2[1:] self.c.create_text(xp, yp + 16, text=zstr, font=('Helvetica', '14')) def mouseover(self, event): oval = event.widget.find_closest(event.x, event.y)[0] # XXX Sometimes, the closest widget is an axis grid line, not an oval. # Need to handle this correctly eventually. if oval not in self.ov_dict: return self.is_hovering = True ind, x, y, color, filtered = self.ov_dict[oval] # Figure out how high the box needs to be by creating the text off- # graph, then getting its bbox and deleting it. w = 200 de_text = self.u.all_descr[ind] deobj = self.c.create_text(self.w + 3, self.h + 3, text=de_text, anchor=N+W, width=w-6, font=('Helvetica', '14')) bbox = self.c.bbox(deobj) self.c.delete(deobj) h = 18 + bbox[3] - bbox[1] border = 5 if x > self.xmid: x -= (w + border) else: x += border if y > self.ymid: y -= (h + border) else: y += border self.re = list() self.re.append(self.c.create_rectangle(x, y, x + w, y + h, fill=resaturate(color, 50))) pr_text = '$%s' % self.u.commafy(self.u.all_price[ind]) self.re.append(self.c.create_text(x + 3, y + 3, text=pr_text, anchor=N+W, font=('Helvetica', '10'))) km_text = '%skm' % self.u.commafy(self.u.all_km[ind]) self.re.append(self.c.create_text(x + w - 3, y + 3, text=km_text, anchor=N+E, font=('Helvetica', '10'))) wh_text = self.u.all_wherestr[ind] if wh_text[0].isdigit(): wh_text += ' away' self.re.append(self.c.create_text(x + w/2, y + 3, text=wh_text, anchor=N, font=('Helvetica', '10'))) self.re.append(self.c.create_text(x + 3, y + 16, text=de_text, anchor=N+W, width=w-6, font=('Helvetica', '14'))) def set_filter(self, st): "Given a string 'st', filter ovals whose description doesn't match." self.filter = st self.replot() def mouseoff(self, event): "Code for mousing off a data point." # The tooptip rectangle and all its sub-objects need to be destroyed. map(self.c.delete, self.re) # Also, need to note that we're no longer over an oval -- that way, # Button-1 events will cause a zoom, rather than launching a web page. self.is_hovering = False def _zoom_animation(self): import time from math import tanh scale = 5 for i1 in range(-scale, scale+1): self.replot(zlfrac=0.5 + 0.5*tanh(i1*2.0/scale)/tanh(2.0)) self.c.update() def zoom(self, event): # Only zoom in if we're actually within the graph boundaries. if event.x <= self.x_grid[0] or event.x > self.x_grid[-1]: return if event.y >= self.y_grid[0] or event.y < self.y_grid[-1]: return # Don't zoom if we're hovering over a data point: let the web browser # event handler operate. if self.is_hovering: return # Don't zoom in more than twice. if self.zoom_level >= 2: return # Find the grid square which we're inside. for i1 in range(len(self.x_grid) - 1): if event.x <= self.x_grid[i1 + 1]: xgrid = i1 + 1 break for i1 in range(len(self.y_grid) - 1): if event.y >= self.y_grid[i1 + 1]: ygrid = i1 + 1 break self.zoom_level += 1 zl = self.zoom_level # Make the limits of the new graph be those of the grid square which # was clicked inside. self.km[zl] = (self.xtick[xgrid-1], self.xtick[xgrid]) self.price[zl] = (self.ytick[ygrid-1], self.ytick[ygrid]) if zl == 1: self.zoom_price_start = self.u.axis(*self.price[0])[:2] self.zoom_km_start = self.u.axis(*self.km[0])[:2] else: self.zoom_price_start = self.price[zl - 1] self.zoom_km_start = self.km[zl - 1] self.zoom_price_end = self.price[zl] self.zoom_km_end = self.km[zl] self._zoom_animation() self.replot() def unzoom(self, event): # If already at maximum zoom, nothing to be done. if self.zoom_level == 0: return # If not clicking inside graph boundaries, don't unzoom. if event.x <= self.x_grid[0] or event.x > self.x_grid[-1]: return if event.y >= self.y_grid[0] or event.y < self.y_grid[-1]: return self.zoom_level -= 1 zl = self.zoom_level self.zoom_price_start = self.price[zl + 1] self.zoom_km_start = self.km[zl + 1] if zl == 0: self.zoom_price_end = self.u.axis(*self.price[0])[:2] self.zoom_km_end = self.u.axis(*self.km[0])[:2] else: self.zoom_price_end = self.price[zl] self.zoom_km_end = self.km[zl] self._zoom_animation() self.replot() def left_key(self, event): zl = self.zoom_level if zl == 0: return # If at left edge already, don't scroll. kz = self.km[zl] if self.km[0][0] > kz[0]: return self.zoom_price_start = self.zoom_price_end = self.price[zl] self.zoom_km_start = kz self.km[zl] = (kz[0] - (kz[1] - kz[0]), kz[0]) self.zoom_km_end = self.km[zl] self._zoom_animation() self.replot() def right_key(self, event): zl = self.zoom_level if zl == 0: return # If at right edge already, don't scroll. kz = self.km[zl] if self.km[0][1] < kz[1]: return self.zoom_price_start = self.zoom_price_end = self.price[zl] self.zoom_km_start = kz self.km[zl] = (kz[1], kz[1] + (kz[1] - kz[0])) self.zoom_km_end = self.km[zl] self._zoom_animation() self.replot() def down_key(self, event): zl = self.zoom_level if zl == 0: return # If at bottom edge already, don't scroll. pz = self.price[zl] if self.price[0][0] > pz[0]: return self.zoom_km_start = self.zoom_km_end = self.km[zl] self.zoom_price_start = pz self.price[zl] = (pz[0] - (pz[1] - pz[0]), pz[0]) self.zoom_price_end = self.price[zl] self._zoom_animation() self.replot() def up_key(self, event): zl = self.zoom_level if zl == 0: return # If at top edge already, don't scroll. pz = self.price[zl] if self.price[0][1] < pz[1]: return self.zoom_km_start = self.zoom_km_end = self.km[zl] self.zoom_price_start = pz self.price[zl] = (pz[1], pz[1] + (pz[1] - pz[0])) self.zoom_price_end = self.price[zl] self._zoom_animation() self.replot() def select(self, event): "Open a web page, when a data point has been clicked on." oval = event.widget.find_closest(event.x, event.y)[0] # XXX As above, sometimes the closest widget is a grid line, not an # oval. Need to handle this correctly, eventually. if oval not in self.ov_dict: return ind, xp, yp, color, filtered = self.ov_dict[oval] webbrowser.open(self.u.all_alink[ind])
class AppAnalysis: def __init__(self, root): self.canvas = Canvas(root, width = 400, height = 350) self.canvas.configure(cursor="crosshair") self.canvas.pack(expand=YES, fill=BOTH, side='right') self.canvas.bind("<Key>", self.handle_key) self.canvas.bind("<Double-Button-1>", self.set_focus) self.canvas.bind("<Button-1>", self.set_cursor) self.canvas.bind("<Return>", self.remove_highlight) self.image, self.ponto1, self.ponto2 = (None, None, None) self.menubar = Menu(root) filemenu = Menu(self.menubar, tearoff=0) filemenu.add_command(label="Open Image", command=self.openImage) filemenu.add_command(label="Save", command=self.hello) filemenu.add_separator() filemenu.add_command(label="Exit", command=root.quit) self.menubar.add_cascade(label="File", menu=filemenu) editmenu = Menu(self.menubar, tearoff=0) for e in ("Cut","Copy","Paste"): editmenu.add_command(label=e, command=self.hello) self.menubar.add_cascade(label="Edit", menu=editmenu) filtermenu = Menu(self.menubar, tearoff=0) filtermenu.add_command(label="Threshold", command=self.thresholdFilter) self.menubar.add_cascade(label="Filter", menu=filtermenu) reportmenu = Menu(self.menubar, tearoff=0) reportmenu.add_command(label="Relatorio.txt", command=self.generateReport) reportmenu.add_command(label="Relatorio.pdf") reportmenu.add_command(label="Email") self.menubar.add_cascade(label="Report", menu=reportmenu) helpmenu = Menu(self.menubar, tearoff=0) helpmenu.add_command(label="About", command=self.hello) self.menubar.add_cascade(label="Help", menu=helpmenu) root.config(menu=self.menubar) self.toolbar = Frame(root) self.toolbar.pack(side='left', fill='both') clean = Label(self.toolbar, text='Clean') clean.bind("<Button-1>", self.clean) b = Label(self.toolbar, text='B') c = Label(self.toolbar, text='C') d = Label(self.toolbar, text='D') for w in (clean,b,c,d): w.configure(relief="groove", font="Times 12 bold") w.pack(fill='both') def openImage(self): arquivo = tkFileDialog.askopenfile(parent=self.canvas,mode='rb', title='Imagem') e = ['GIF','JPEG','JPG','BMP','PNG','TIF'] if(e.__contains__(arquivo.name.split(".")[-1].upper())): self.ponto1, self.ponto2 = (None,None) img_tmp = Image.open(arquivo) #self.img_name = path.dirname(path.abspath(arquivo.name)) self.img_name = arquivo.name print self.img_name self.new_img_name = arquivo.name.split('/')[-1] + "_tmp.gif" pathtemp = mydir +"/temp/"+ self.new_img_name img_tmp.save(pathtemp) self.image = PhotoImage(file=pathtemp) self.setImage() self.canvas.bind("<Button-1>", self.click) self.proporcao = "" def clean(self, event): self.ponto1, self.ponto2 = (None,None) self.setImage() self.proporcao = "" def setImage(self): self.canvas.delete(ALL) if self.image.width() > 200 and self.image.height > 200: self.canvas.config(width = self.image.width()) self.canvas.config(height = self.image.height()) self.canvas.create_image(0, 0, image=self.image, anchor=NW) def generateReport(self): report = GeradorRelatorio(self.img_name) report.start() def hello(self): print "hello!" def thresholdFilter(self): img = Image.open(self.img_name) new_img = img.filter(ImageFilter.BLUR) aux = mydir +"/temp/"+ self.new_img_name new_img.save(aux) self.image = PhotoImage(file=aux) self.setImage() def click(self, event): if not self.ponto1: self.canvas.create_oval(event.x, event.y, event.x+5, event.y+5, fill="red") self.ponto1 = (event.x,event.y) else: if not self.ponto2: self.canvas.create_oval(event.x, self.ponto1[1], event.x+5, self.ponto1[1]+5, fill="red") self.ponto2 = (event.x,self.ponto1[1]) pontos = [self.ponto1[0]+1,self.ponto1[1]+2, self.ponto2[0]+1,self.ponto2[1]+2] self.canvas.create_line(pontos, tags="theline", fill='red') x = (self.ponto2[0] + self.ponto1[0]) / 2 self.canvas.create_text(x, self.ponto1[1]+8, text="1 umm") def remove_highlight(self,event): self.canvas.delete("highlight") def highlight(self, item): bbox = self.canvas.bbox(item) self.canvas.delete("highlight") if bbox: i = self.canvas.create_rectangle( bbox, fill="white", tag="highlight" ) self.canvas.lower(i, item) def has_focus(self): return self.canvas.focus() def has_selection(self): return self.canvas.tk.call(self.canvas._w, 'select', 'item') def set_focus(self, event): if self.canvas.type(CURRENT) != "text": return self.highlight(CURRENT) self.canvas.focus_set() self.canvas.focus(CURRENT) self.canvas.select_from(CURRENT, 0) self.canvas.select_to(CURRENT, END) def set_cursor(self, event): item = self.has_focus() if not item: return x = self.canvas.canvasx(event.x) y = self.canvas.canvasy(event.y) self.canvas.icursor(item, "@%d,%d" % (x, y)) self.canvas.select_clear() def handle_key(self, event): item = self.has_focus() if not item: return insert = self.canvas.index(item, INSERT) if event.char >= " ": if self.has_selection(): self.canvas.dchars(item, SEL_FIRST, SEL_LAST) self.canvas.select_clear() self.canvas.insert(item, "insert", event.char) self.highlight(item) elif event.keysym == "BackSpace": if self.has_selection(): self.canvas.dchars(item, SEL_FIRST, SEL_LAST) self.canvas.select_clear() else: if insert > 0: self.canvas.dchars(item, insert-1, insert) self.highlight(item) elif event.keysym == "Home": self.canvas.icursor(item, 0) self.canvas.select_clear() elif event.keysym == "End": self.canvas.icursor(item, END) self.canvas.select_clear() elif event.keysym == "Right": self.canvas.icursor(item, insert+1) self.canvas.select_clear() elif event.keysym == "Left": self.canvas.icursor(item, insert-1) self.canvas.select_clear() else: pass