#!/usr/bin/python2 # -*- coding: utf-8 -*- #Copyright 2008-2010 Steffen Schaumburg #This program is free software: you can redistribute it and/or modify #it under the terms of the GNU Affero General Public License as published by #the Free Software Foundation, version 3 of the License. # #This program is distributed in the hope that it will be useful, #but WITHOUT ANY WARRANTY; without even the implied warranty of #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #GNU General Public License for more details. # #You should have received a copy of the GNU Affero General Public License #along with this program. If not, see . #In the "official" distribution you can find the license in agpl-3.0.txt. import threading import pygtk pygtk.require('2.0') import gtk import os import sys import traceback from time import * from datetime import datetime #import pokereval try: import matplotlib matplotlib.use('GTKCairo') from matplotlib.figure import Figure from matplotlib.backends.backend_gtk import FigureCanvasGTK as FigureCanvas from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar from matplotlib.font_manager import FontProperties from numpy import arange, cumsum from pylab import * except ImportError, inst: print """Failed to load libs for graphing, graphing will not function. Please in stall numpy and matplotlib if you want to use graphs.""" print """This is of no consequence for other parts of the program, e.g. import and HUD are NOT affected by this problem.""" print "ImportError: %s" % inst.args import fpdb_import import Database import Filters import Charset class GuiGraphViewer (threading.Thread): def __init__(self, querylist, config, parent, debug=True): """Constructor for GraphViewer""" self.sql = querylist self.conf = config self.debug = debug self.parent = parent #print "start of GraphViewer constructor" self.db = Database.Database(self.conf, sql=self.sql) filters_display = { "Heroes" : True, "Sites" : True, "Games" : True, "Limits" : True, "LimitSep" : True, "LimitType" : True, "Type" : False, "UseType" : 'ring', "Seats" : False, "SeatSep" : False, "Dates" : True, "Groups" : False, "Button1" : True, "Button2" : True } self.filters = Filters.Filters(self.db, self.conf, self.sql, display = filters_display) self.filters.registerButton1Name("Refresh _Graph") self.filters.registerButton1Callback(self.generateGraph) self.filters.registerButton2Name("_Export to File") self.filters.registerButton2Callback(self.exportGraph) self.mainHBox = gtk.HBox(False, 0) self.mainHBox.show() self.leftPanelBox = self.filters.get_vbox() self.hpane = gtk.HPaned() self.hpane.pack1(self.leftPanelBox) self.mainHBox.add(self.hpane) # hierarchy: self.mainHBox / self.hpane / self.graphBox / self.canvas / self.fig / self.ax self.graphBox = gtk.VBox(False, 0) self.graphBox.show() self.hpane.pack2(self.graphBox) self.hpane.show() self.fig = None #self.exportButton.set_sensitive(False) self.canvas = None self.db.rollback() def get_vbox(self): """returns the vbox of this thread""" return self.mainHBox #end def get_vbox def clearGraphData(self): try: try: if self.canvas: self.graphBox.remove(self.canvas) except: pass if self.fig != None: self.fig.clear() self.fig = Figure(figsize=(5,4), dpi=100) if self.canvas is not None: self.canvas.destroy() self.canvas = FigureCanvas(self.fig) # a gtk.DrawingArea except: err = traceback.extract_tb(sys.exc_info()[2])[-1] print "***Error: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1]) raise def generateGraph(self, widget, data): try: self.clearGraphData() sitenos = [] playerids = [] sites = self.filters.getSites() heroes = self.filters.getHeroes() siteids = self.filters.getSiteIds() limits = self.filters.getLimits() games = self.filters.getGames() for i in ('show', 'none'): if i in limits: limits.remove(i) # Which sites are selected? for site in sites: if sites[site] == True: sitenos.append(siteids[site]) _hname = Charset.to_utf8(heroes[site]) result = self.db.get_player_id(self.conf, site, _hname) if result is not None: playerids.append(int(result)) if not sitenos: #Should probably pop up here. print "No sites selected - defaulting to PokerStars" self.db.rollback() return if not playerids: print "No player ids found" self.db.rollback() return if not limits: print "No limits found" self.db.rollback() return #Set graph properties self.ax = self.fig.add_subplot(111) #Get graph data from DB starttime = time() (green, blue, red) = self.getRingProfitGraph(playerids, sitenos, limits, games) print "Graph generated in: %s" %(time() - starttime) #Set axis labels and grid overlay properites self.ax.set_xlabel("Hands", fontsize = 12) self.ax.set_ylabel("$", fontsize = 12) self.ax.grid(color='g', linestyle=':', linewidth=0.2) if green == None or green == []: self.ax.set_title("No Data for Player(s) Found") green = ([ 0., 0., 0., 0., 500., 1000., 900., 800., 700., 600., 500., 400., 300., 200., 100., 0., 500., 1000., 1000., 1000., 1000., 1000., 1000., 1000., 1000., 1000., 1000., 1000., 1000., 1000., 875., 750., 625., 500., 375., 250., 125., 0., 0., 0., 0., 500., 1000., 900., 800., 700., 600., 500., 400., 300., 200., 100., 0., 500., 1000., 1000.]) red = ([ 0., 0., 0., 0., 500., 1000., 900., 800., 700., 600., 500., 400., 300., 200., 100., 0., 0., 0., 0., 0., 0., 0., 125., 250., 375., 500., 500., 500., 500., 500., 500., 500., 500., 500., 375., 250., 125., 0., 0., 0., 0., 500., 1000., 900., 800., 700., 600., 500., 400., 300., 200., 100., 0., 500., 1000., 1000.]) blue = ([ 0., 0., 0., 0., 500., 1000., 900., 800., 700., 600., 500., 400., 300., 200., 100., 0., 0., 0., 0., 0., 0., 0., 125., 250., 375., 500., 625., 750., 875., 1000., 875., 750., 625., 500., 375., 250., 125., 0., 0., 0., 0., 500., 1000., 900., 800., 700., 600., 500., 400., 300., 200., 100., 0., 500., 1000., 1000.]) self.ax.plot(green, color='green', label='Hands: %d\nProfit: $%.2f' %(len(green), green[-1])) self.ax.plot(blue, color='blue', label='Showdown: $%.2f' %(blue[-1])) self.ax.plot(red, color='red', label='Non-showdown: $%.2f' %(red[-1])) self.graphBox.add(self.canvas) self.canvas.show() self.canvas.draw() #TODO: Do something useful like alert user #print "No hands returned by graph query" else: self.ax.set_title("Profit graph for ring games") #text = "Profit: $%.2f\nTotal Hands: %d" %(green[-1], len(green)) #self.ax.annotate(text, # xy=(10, -10), # xycoords='axes points', # horizontalalignment='left', verticalalignment='top', # fontsize=10) #Draw plot self.ax.plot(green, color='green', label='Hands: %d\nProfit: $%.2f' %(len(green), green[-1])) self.ax.plot(blue, color='blue', label='Showdown: $%.2f' %(blue[-1])) self.ax.plot(red, color='red', label='Non-showdown: $%.2f' %(red[-1])) if sys.version[0:3] == '2.5': self.ax.legend(loc='upper left', shadow=True, prop=FontProperties(size='smaller')) else: self.ax.legend(loc='upper left', fancybox=True, shadow=True, prop=FontProperties(size='smaller')) self.graphBox.add(self.canvas) self.canvas.show() self.canvas.draw() #self.exportButton.set_sensitive(True) except: err = traceback.extract_tb(sys.exc_info()[2])[-1] print "***Error: "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1]) #end of def showClicked def getRingProfitGraph(self, names, sites, limits, games): tmp = self.sql.query['getRingProfitAllHandsPlayerIdSite'] # print "DEBUG: getRingProfitGraph" start_date, end_date = self.filters.getDates() #Buggered if I can find a way to do this 'nicely' take a list of integers and longs # and turn it into a tuple readale by sql. # [5L] into (5) not (5,) and [5L, 2829L] into (5, 2829) nametest = str(tuple(names)) sitetest = str(tuple(sites)) #nametest = nametest.replace("L", "") q = [] for m in self.filters.display.items(): if m[0] == 'Games' and m[1]: for n in games: if games[n]: q.append(n) if len(q) > 0: gametest = str(tuple(q)) gametest = gametest.replace("L", "") gametest = gametest.replace(",)",")") gametest = gametest.replace("u'","'") gametest = "and gt.category in %s" % gametest else: gametest = "and gt.category IS NULL" tmp = tmp.replace("", gametest) lims = [int(x) for x in limits if x.isdigit()] potlims = [int(x[0:-2]) for x in limits if len(x) > 2 and x[-2:] == 'pl'] nolims = [int(x[0:-2]) for x in limits if len(x) > 2 and x[-2:] == 'nl'] limittest = "and ( (gt.limitType = 'fl' and gt.bigBlind in " # and ( (limit and bb in()) or (nolimit and bb in ()) ) if lims: blindtest = str(tuple(lims)) blindtest = blindtest.replace("L", "") blindtest = blindtest.replace(",)",")") limittest = limittest + blindtest + ' ) ' else: limittest = limittest + '(-1) ) ' limittest = limittest + " or (gt.limitType = 'pl' and gt.bigBlind in " if potlims: blindtest = str(tuple(potlims)) blindtest = blindtest.replace("L", "") blindtest = blindtest.replace(",)",")") limittest = limittest + blindtest + ' ) ' else: limittest = limittest + '(-1) ) ' limittest = limittest + " or (gt.limitType = 'nl' and gt.bigBlind in " if nolims: blindtest = str(tuple(nolims)) blindtest = blindtest.replace("L", "") blindtest = blindtest.replace(",)",")") limittest = limittest + blindtest + ' ) )' else: limittest = limittest + '(-1) ) )' if type == 'ring': limittest = limittest + " and gt.type = 'ring' " elif type == 'tour': limittest = limittest + " and gt.type = 'tour' " #Must be a nicer way to deal with tuples of size 1 ie. (2,) - which makes sql barf tmp = tmp.replace("", nametest) tmp = tmp.replace("", sitetest) tmp = tmp.replace("", start_date) tmp = tmp.replace("", end_date) tmp = tmp.replace("", limittest) tmp = tmp.replace(",)", ")") #print "DEBUG: sql query:" #print tmp self.db.cursor.execute(tmp) #returns (HandId,Winnings,Costs,Profit) winnings = self.db.cursor.fetchall() self.db.rollback() if len(winnings) == 0: return (None, None, None) green = map(lambda x:float(x[1]), winnings) blue = map(lambda x: float(x[1]) if x[2] == True else 0.0, winnings) red = map(lambda x: float(x[1]) if x[2] == False else 0.0, winnings) greenline = cumsum(green) blueline = cumsum(blue) redline = cumsum(red) return (greenline/100, blueline/100, redline/100) #end of def getRingProfitGraph def exportGraph (self, widget, data): if self.fig is None: return # Might want to disable export button until something has been generated. dia_chooser = gtk.FileChooserDialog(title="Please choose the directory you wish to export to:", action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OK,gtk.RESPONSE_OK)) dia_chooser.set_destroy_with_parent(True) dia_chooser.set_transient_for(self.parent) try: dia_chooser.set_filename(self.exportFile) # use previously chosen export path as default except: pass response = dia_chooser.run() if response <> gtk.RESPONSE_OK: print 'Closed, no graph exported' dia_chooser.destroy() return # generate a unique filename for export now = datetime.now() now_formatted = now.strftime("%Y%m%d%H%M%S") self.exportFile = dia_chooser.get_filename() + "/fpdb" + now_formatted + ".png" dia_chooser.destroy() #print "DEBUG: self.exportFile = %s" %(self.exportFile) self.fig.savefig(self.exportFile, format="png") #display info box to confirm graph created diainfo = gtk.MessageDialog(parent=self.parent, flags=gtk.DIALOG_DESTROY_WITH_PARENT, type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_OK, message_format="Graph created") diainfo.format_secondary_text(self.exportFile) diainfo.run() diainfo.destroy() #end of def exportGraph