diff --git a/pyfpdb/GuiTourneyGraphViewer.py b/pyfpdb/GuiTourneyGraphViewer.py
new file mode 100644
index 00000000..e213e1f4
--- /dev/null
+++ b/pyfpdb/GuiTourneyGraphViewer.py
@@ -0,0 +1,319 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+#Copyright 2008-2010 Carl Gherardi
+#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
+
+import locale
+lang=locale.getdefaultlocale()[0][0:2]
+if lang=="en":
+ def _(string): return string
+else:
+ import gettext
+ try:
+ trans = gettext.translation("fpdb", localedir="locale", languages=[lang])
+ trans.install()
+ except IOError:
+ def _(string): return string
+
+import fpdb_import
+import Database
+import Filters
+import Charset
+
+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
+ install 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
+
+class GuiTourneyGraphViewer (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" : False,
+ "Limits" : False,
+ "LimitSep" : False,
+ "LimitType" : False,
+ "Type" : False,
+ "UseType" : 'tour',
+ "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()
+
+ # 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
+
+ #Set graph properties
+ self.ax = self.fig.add_subplot(111)
+
+ #Get graph data from DB
+ starttime = time()
+ green = self.getData(playerids, sitenos)
+ print _("Graph generated in: %s") %(time() - starttime)
+
+
+ #Set axis labels and grid overlay properites
+ self.ax.set_xlabel(_("Tournaments"), 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=_('Tournaments: %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(_("Tournament Results"))
+
+ #Draw plot
+ self.ax.plot(green, color='green', label=_('Tournaments: %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 getData(self, names, sites):
+ tmp = self.sql.query['tourneyResults']
+ print "DEBUG: getData"
+ 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))
+
+ #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(",)", ")")
+
+ 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
+
+ green = map(lambda x:float(x[0]), 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)
+
+ 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
diff --git a/pyfpdb/SQL.py b/pyfpdb/SQL.py
index abc812a8..87857908 100644
--- a/pyfpdb/SQL.py
+++ b/pyfpdb/SQL.py
@@ -3027,6 +3027,9 @@ class Sql:
#elif db_server == 'sqlite':
# self.query['playerStatsByPosition'] = """ """
+ ####################################
+ # Cash Game Graph query
+ ####################################
self.query['getRingProfitAllHandsPlayerIdSite'] = """
SELECT hp.handId, hp.totalProfit, hp.sawShowdown
FROM HandsPlayers hp
@@ -3043,6 +3046,28 @@ class Sql:
GROUP BY h.startTime, hp.handId, hp.sawShowdown, hp.totalProfit
ORDER BY h.startTime"""
+ ####################################
+ # Tourney Results query
+ ####################################
+ self.query['tourneyResults'] = """
+ SELECT (tp.winnings - tt.buyIn - tt.fee) as profit, tp.koCount, tp.rebuyCount, tp.addOnCount, tt.buyIn, tt.fee
+ FROM TourneysPlayers tp
+ INNER JOIN Players pl ON (pl.id = tp.playerId)
+ INNER JOIN Tourneys t ON (t.id = tp.tourneyId)
+ INNER JOIN TourneyTypes tt ON (tt.id = t.tourneyTypeId)
+ WHERE pl.id in
+ AND pl.siteId in
+ AND t.startTime > ''
+ AND t.startTime < ''
+ GROUP BY t.startTime, tp.tourneyId, tp.winningsCurrency,
+ tp.winnings, tp.koCount,
+ tp.rebuyCount, tp.addOnCount,
+ tt.buyIn, tt.fee
+ ORDER BY t.startTime"""
+
+ #AND gt.type = 'ring'
+ #
+ #
####################################
# Session stats query
diff --git a/pyfpdb/fpdb.pyw b/pyfpdb/fpdb.pyw
index 385d07ef..61af3c25 100755
--- a/pyfpdb/fpdb.pyw
+++ b/pyfpdb/fpdb.pyw
@@ -122,6 +122,7 @@ import GuiTourneyViewer
import GuiPositionalStats
import GuiAutoImport
import GuiGraphViewer
+import GuiTourneyGraphViewer
import GuiSessionViewer
import SQL
import Database
@@ -778,6 +779,7 @@ class fpdb:
+
@@ -817,6 +819,7 @@ class fpdb:
('autoimp', None, _('_Auto Import and HUD'), _('A'), 'Auto Import and HUD', self.tab_auto_import),
('hudConfigurator', None, _('_HUD Configurator'), _('H'), 'HUD Configurator', self.diaHudConfigurator),
('graphs', None, _('_Graphs'), _('G'), 'Graphs', self.tabGraphViewer),
+ ('tourneygraphs', None, _('Tourney Graphs'), None, 'TourneyGraphs', self.tabTourneyGraphViewer),
('ringplayerstats', None, _('Ring _Player Stats (tabulated view, not on pgsql)'), _('P'), 'Ring Player Stats (tabulated view)', self.tab_ring_player_stats),
('tourneyplayerstats', None, _('_Tourney Player Stats (tabulated view, not on pgsql)'), _('T'), 'Tourney Player Stats (tabulated view, mysql only)', self.tab_tourney_player_stats),
('tourneyviewer', None, _('Tourney _Viewer'), None, 'Tourney Viewer)', self.tab_tourney_viewer_stats),
@@ -1066,6 +1069,13 @@ You can find the full license texts in agpl-3.0.txt, gpl-2.0.txt, gpl-3.0.txt an
gv_tab = new_gv_thread.get_vbox()
self.add_and_display_tab(gv_tab, _("Graphs"))
+ def tabTourneyGraphViewer(self, widget, data=None):
+ """opens a graph viewer tab"""
+ new_gv_thread = GuiTourneyGraphViewer.GuiTourneyGraphViewer(self.sql, self.config, self.window)
+ self.threads.append(new_gv_thread)
+ gv_tab = new_gv_thread.get_vbox()
+ self.add_and_display_tab(gv_tab, _("Tourney Graphs"))
+
def __init__(self):
# no more than 1 process can this lock at a time:
self.lock = interlocks.InterProcessLock(name="fpdb_global_lock")