Expanded my entry in contributors.txt.
[fpdb-dooglus.git] / pyfpdb / GuiTourneyGraphViewer.py
blob14e60c15810295ed59cd1643677303c86017eae7
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
4 #Copyright 2008-2011 Carl Gherardi
5 #This program is free software: you can redistribute it and/or modify
6 #it under the terms of the GNU Affero General Public License as published by
7 #the Free Software Foundation, version 3 of the License.
9 #This program is distributed in the hope that it will be useful,
10 #but WITHOUT ANY WARRANTY; without even the implied warranty of
11 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 #GNU General Public License for more details.
14 #You should have received a copy of the GNU Affero General Public License
15 #along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #In the "official" distribution you can find the license in agpl-3.0.txt.
18 import L10n
19 _ = L10n.get_translation()
21 import threading
22 import pygtk
23 pygtk.require('2.0')
24 import gtk
25 import os
26 import sys
27 import traceback
28 from time import *
29 from datetime import datetime
30 #import pokereval
32 import fpdb_import
33 import Database
34 import Filters
35 import Charset
37 try:
38 calluse = not 'matplotlib' in sys.modules
39 import matplotlib
40 if calluse:
41 matplotlib.use('GTKCairo')
42 from matplotlib.figure import Figure
43 from matplotlib.backends.backend_gtk import FigureCanvasGTK as FigureCanvas
44 from matplotlib.backends.backend_gtkagg import NavigationToolbar2GTKAgg as NavigationToolbar
45 from matplotlib.font_manager import FontProperties
46 from numpy import arange, cumsum
47 from pylab import *
48 except ImportError, inst:
49 print _("""Failed to load libs for graphing, graphing will not function. Please install numpy and matplotlib if you want to use graphs.""")
50 print _("""This is of no consequence for other parts of the program, e.g. import and HUD are NOT affected by this problem.""")
51 print "ImportError: %s" % inst.args
53 class GuiTourneyGraphViewer (threading.Thread):
55 def __init__(self, querylist, config, parent, debug=True):
56 """Constructor for GraphViewer"""
57 self.sql = querylist
58 self.conf = config
59 self.debug = debug
60 self.parent = parent
61 #print "start of GraphViewer constructor"
62 self.db = Database.Database(self.conf, sql=self.sql)
65 filters_display = { "Heroes" : True,
66 "Sites" : True,
67 "Games" : False,
68 "Limits" : False,
69 "LimitSep" : False,
70 "LimitType" : False,
71 "Type" : False,
72 "UseType" : 'tour',
73 "Seats" : False,
74 "SeatSep" : False,
75 "Dates" : True,
76 "Groups" : False,
77 "Button1" : True,
78 "Button2" : True
81 self.filters = Filters.Filters(self.db, self.conf, self.sql, display = filters_display)
82 self.filters.registerButton1Name(_("Refresh _Graph"))
83 self.filters.registerButton1Callback(self.generateGraph)
84 self.filters.registerButton2Name(_("_Export to File"))
85 self.filters.registerButton2Callback(self.exportGraph)
87 self.mainHBox = gtk.HBox(False, 0)
88 self.mainHBox.show()
90 self.leftPanelBox = self.filters.get_vbox()
92 self.hpane = gtk.HPaned()
93 self.hpane.pack1(self.leftPanelBox)
94 self.mainHBox.add(self.hpane)
95 # hierarchy: self.mainHBox / self.hpane / self.graphBox / self.canvas / self.fig / self.ax
97 self.graphBox = gtk.VBox(False, 0)
98 self.graphBox.show()
99 self.hpane.pack2(self.graphBox)
100 self.hpane.show()
102 self.fig = None
103 #self.exportButton.set_sensitive(False)
104 self.canvas = None
107 self.db.rollback()
109 def get_vbox(self):
110 """returns the vbox of this thread"""
111 return self.mainHBox
112 #end def get_vbox
114 def clearGraphData(self):
116 try:
117 try:
118 if self.canvas:
119 self.graphBox.remove(self.canvas)
120 except:
121 pass
123 if self.fig != None:
124 self.fig.clear()
125 self.fig = Figure(figsize=(5,4), dpi=100)
126 if self.canvas is not None:
127 self.canvas.destroy()
129 self.canvas = FigureCanvas(self.fig) # a gtk.DrawingArea
130 except:
131 err = traceback.extract_tb(sys.exc_info()[2])[-1]
132 print _("Error:")+" "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1])
133 raise
135 def generateGraph(self, widget, data):
136 try:
137 self.clearGraphData()
139 sitenos = []
140 playerids = []
142 sites = self.filters.getSites()
143 heroes = self.filters.getHeroes()
144 siteids = self.filters.getSiteIds()
146 # Which sites are selected?
147 for site in sites:
148 if sites[site] == True:
149 sitenos.append(siteids[site])
150 _hname = Charset.to_utf8(heroes[site])
151 result = self.db.get_player_id(self.conf, site, _hname)
152 if result is not None:
153 playerids.append(int(result))
155 if not sitenos:
156 #Should probably pop up here.
157 print _("No sites selected - defaulting to PokerStars")
158 self.db.rollback()
159 return
161 if not playerids:
162 print _("No player ids found")
163 self.db.rollback()
164 return
166 #Set graph properties
167 self.ax = self.fig.add_subplot(111)
169 #Get graph data from DB
170 starttime = time()
171 green = self.getData(playerids, sitenos)
172 print _("Graph generated in: %s") %(time() - starttime)
175 #Set axis labels and grid overlay properites
176 self.ax.set_xlabel(_("Tournaments"), fontsize = 12)
177 self.ax.set_ylabel("$", fontsize = 12)
178 self.ax.grid(color='g', linestyle=':', linewidth=0.2)
179 if green == None or green == []:
180 self.ax.set_title(_("No Data for Player(s) Found"))
181 green = ([ 0., 0., 0., 0., 500., 1000., 900., 800.,
182 700., 600., 500., 400., 300., 200., 100., 0.,
183 500., 1000., 1000., 1000., 1000., 1000., 1000., 1000.,
184 1000., 1000., 1000., 1000., 1000., 1000., 875., 750.,
185 625., 500., 375., 250., 125., 0., 0., 0.,
186 0., 500., 1000., 900., 800., 700., 600., 500.,
187 400., 300., 200., 100., 0., 500., 1000., 1000.])
188 red = ([ 0., 0., 0., 0., 500., 1000., 900., 800.,
189 700., 600., 500., 400., 300., 200., 100., 0.,
190 0., 0., 0., 0., 0., 0., 125., 250.,
191 375., 500., 500., 500., 500., 500., 500., 500.,
192 500., 500., 375., 250., 125., 0., 0., 0.,
193 0., 500., 1000., 900., 800., 700., 600., 500.,
194 400., 300., 200., 100., 0., 500., 1000., 1000.])
195 blue = ([ 0., 0., 0., 0., 500., 1000., 900., 800.,
196 700., 600., 500., 400., 300., 200., 100., 0.,
197 0., 0., 0., 0., 0., 0., 125., 250.,
198 375., 500., 625., 750., 875., 1000., 875., 750.,
199 625., 500., 375., 250., 125., 0., 0., 0.,
200 0., 500., 1000., 900., 800., 700., 600., 500.,
201 400., 300., 200., 100., 0., 500., 1000., 1000.])
203 self.ax.plot(green, color='green', label=_('Tournaments: %d\nProfit: $%.2f') %(len(green), green[-1]))
204 #self.ax.plot(blue, color='blue', label=_('Showdown: $%.2f') %(blue[-1]))
205 #self.ax.plot(red, color='red', label=_('Non-showdown: $%.2f') %(red[-1]))
206 self.graphBox.add(self.canvas)
207 self.canvas.show()
208 self.canvas.draw()
210 #TODO: Do something useful like alert user
211 #print "No hands returned by graph query"
212 else:
213 self.ax.set_title(_("Tournament Results"))
215 #Draw plot
216 self.ax.plot(green, color='green', label=_('Tournaments: %d\nProfit: $%.2f') %(len(green), green[-1]))
217 #self.ax.plot(blue, color='blue', label=_('Showdown: $%.2f') %(blue[-1]))
218 #self.ax.plot(red, color='red', label=_('Non-showdown: $%.2f') %(red[-1]))
219 if sys.version[0:3] == '2.5':
220 self.ax.legend(loc='upper left', shadow=True, prop=FontProperties(size='smaller'))
221 else:
222 self.ax.legend(loc='upper left', fancybox=True, shadow=True, prop=FontProperties(size='smaller'))
224 self.graphBox.add(self.canvas)
225 self.canvas.show()
226 self.canvas.draw()
227 #self.exportButton.set_sensitive(True)
228 except:
229 err = traceback.extract_tb(sys.exc_info()[2])[-1]
230 print _("Error:")+" "+err[2]+"("+str(err[1])+"): "+str(sys.exc_info()[1])
232 #end of def showClicked
234 def getData(self, names, sites):
235 tmp = self.sql.query['tourneyResults']
236 print "DEBUG: getData"
237 start_date, end_date = self.filters.getDates()
239 #Buggered if I can find a way to do this 'nicely' take a list of integers and longs
240 # and turn it into a tuple readale by sql.
241 # [5L] into (5) not (5,) and [5L, 2829L] into (5, 2829)
242 nametest = str(tuple(names))
243 sitetest = str(tuple(sites))
245 #Must be a nicer way to deal with tuples of size 1 ie. (2,) - which makes sql barf
246 tmp = tmp.replace("<player_test>", nametest)
247 tmp = tmp.replace("<site_test>", sitetest)
248 tmp = tmp.replace("<startdate_test>", start_date)
249 tmp = tmp.replace("<enddate_test>", end_date)
250 tmp = tmp.replace(",)", ")")
252 print "DEBUG: sql query:"
253 print tmp
254 self.db.cursor.execute(tmp)
255 #returns (HandId,Winnings,Costs,Profit)
256 winnings = self.db.cursor.fetchall()
257 self.db.rollback()
259 if len(winnings) == 0:
260 return None
262 green = map(lambda x:float(x[1]), winnings)
263 #blue = map(lambda x: float(x[1]) if x[2] == True else 0.0, winnings)
264 #red = map(lambda x: float(x[1]) if x[2] == False else 0.0, winnings)
265 greenline = cumsum(green)
266 #blueline = cumsum(blue)
267 #redline = cumsum(red)
268 return (greenline/100)
270 def exportGraph (self, widget, data):
271 if self.fig is None:
272 return # Might want to disable export button until something has been generated.
274 dia_chooser = gtk.FileChooserDialog(title=_("Please choose the directory you wish to export to:"),
275 action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
276 buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OK,gtk.RESPONSE_OK))
277 dia_chooser.set_destroy_with_parent(True)
278 dia_chooser.set_transient_for(self.parent)
279 try:
280 dia_chooser.set_filename(self.exportFile) # use previously chosen export path as default
281 except:
282 pass
284 response = dia_chooser.run()
286 if response <> gtk.RESPONSE_OK:
287 print _('Closed, no graph exported')
288 dia_chooser.destroy()
289 return
291 # generate a unique filename for export
292 now = datetime.now()
293 now_formatted = now.strftime("%Y%m%d%H%M%S")
294 self.exportFile = dia_chooser.get_filename() + "/fpdb" + now_formatted + ".png"
295 dia_chooser.destroy()
297 #print "DEBUG: self.exportFile = %s" %(self.exportFile)
298 self.fig.savefig(self.exportFile, format="png")
300 #display info box to confirm graph created
301 diainfo = gtk.MessageDialog(parent=self.parent,
302 flags=gtk.DIALOG_DESTROY_WITH_PARENT,
303 type=gtk.MESSAGE_INFO,
304 buttons=gtk.BUTTONS_OK,
305 message_format=_("Graph created"))
306 diainfo.format_secondary_text(self.exportFile)
307 diainfo.run()
308 diainfo.destroy()
310 #end of def exportGraph