Use debian 2.7 only
[fpbd-bostik.git] / pyfpdb / GuiTourneyGraphViewer.py
blobaf4b9162728b06701e3c33b16965b50e7906e253
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):
115 try:
116 if self.canvas:
117 self.graphBox.remove(self.canvas)
118 except:
119 pass
121 if self.fig != None:
122 self.fig.clear()
123 self.fig = Figure(figsize=(5,4), dpi=100)
124 if self.canvas is not None:
125 self.canvas.destroy()
127 self.canvas = FigureCanvas(self.fig) # a gtk.DrawingArea
129 def generateGraph(self, widget, data):
130 self.clearGraphData()
132 sitenos = []
133 playerids = []
135 sites = self.filters.getSites()
136 heroes = self.filters.getHeroes()
137 siteids = self.filters.getSiteIds()
139 # Which sites are selected?
140 for site in sites:
141 if sites[site] == True:
142 sitenos.append(siteids[site])
143 _hname = Charset.to_utf8(heroes[site])
144 result = self.db.get_player_id(self.conf, site, _hname)
145 if result is not None:
146 playerids.append(int(result))
148 if not sitenos:
149 #Should probably pop up here.
150 print _("No sites selected - defaulting to PokerStars")
151 self.db.rollback()
152 return
154 if not playerids:
155 print _("No player ids found")
156 self.db.rollback()
157 return
159 #Set graph properties
160 self.ax = self.fig.add_subplot(111)
162 #Get graph data from DB
163 starttime = time()
164 green = self.getData(playerids, sitenos)
165 print _("Graph generated in: %s") %(time() - starttime)
168 #Set axis labels and grid overlay properites
169 self.ax.set_xlabel(_("Tournaments"), fontsize = 12)
170 self.ax.set_ylabel("$", fontsize = 12)
171 self.ax.grid(color='g', linestyle=':', linewidth=0.2)
172 if green == None or green == []:
173 self.ax.set_title(_("No Data for Player(s) Found"))
174 green = ([ 0., 0., 0., 0., 500., 1000., 900., 800.,
175 700., 600., 500., 400., 300., 200., 100., 0.,
176 500., 1000., 1000., 1000., 1000., 1000., 1000., 1000.,
177 1000., 1000., 1000., 1000., 1000., 1000., 875., 750.,
178 625., 500., 375., 250., 125., 0., 0., 0.,
179 0., 500., 1000., 900., 800., 700., 600., 500.,
180 400., 300., 200., 100., 0., 500., 1000., 1000.])
181 red = ([ 0., 0., 0., 0., 500., 1000., 900., 800.,
182 700., 600., 500., 400., 300., 200., 100., 0.,
183 0., 0., 0., 0., 0., 0., 125., 250.,
184 375., 500., 500., 500., 500., 500., 500., 500.,
185 500., 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 blue = ([ 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., 625., 750., 875., 1000., 875., 750.,
192 625., 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.])
196 self.ax.plot(green, color='green', label=_('Tournaments') + ': %d\n' % len(green) + _('Profit') + ': $%.2f' % green[-1])
197 self.graphBox.add(self.canvas)
198 self.canvas.show()
199 self.canvas.draw()
201 #TODO: Do something useful like alert user
202 else:
203 self.ax.set_title(_("Tournament Results"))
205 #Draw plot
206 self.ax.plot(green, color='green', label=_('Tournaments') + ': %d\n' % len(green) + _('Profit') + ': $%.2f' % green[-1])
207 if sys.version[0:3] == '2.5':
208 self.ax.legend(loc='upper left', shadow=True, prop=FontProperties(size='smaller'))
209 else:
210 self.ax.legend(loc='upper left', fancybox=True, shadow=True, prop=FontProperties(size='smaller'))
212 self.graphBox.add(self.canvas)
213 self.canvas.show()
214 self.canvas.draw()
215 #self.exportButton.set_sensitive(True)
217 #end of def showClicked
219 def getData(self, names, sites):
220 tmp = self.sql.query['tourneyResults']
221 print "DEBUG: getData"
222 start_date, end_date = self.filters.getDates()
224 #Buggered if I can find a way to do this 'nicely' take a list of integers and longs
225 # and turn it into a tuple readale by sql.
226 # [5L] into (5) not (5,) and [5L, 2829L] into (5, 2829)
227 nametest = str(tuple(names))
228 sitetest = str(tuple(sites))
230 #Must be a nicer way to deal with tuples of size 1 ie. (2,) - which makes sql barf
231 tmp = tmp.replace("<player_test>", nametest)
232 tmp = tmp.replace("<site_test>", sitetest)
233 tmp = tmp.replace("<startdate_test>", start_date)
234 tmp = tmp.replace("<enddate_test>", end_date)
235 tmp = tmp.replace(",)", ")")
237 print "DEBUG: sql query:"
238 print tmp
239 self.db.cursor.execute(tmp)
240 #returns (HandId,Winnings,Costs,Profit)
241 winnings = self.db.cursor.fetchall()
242 self.db.rollback()
244 if len(winnings) == 0:
245 return None
247 green = map(lambda x:float(x[1]), winnings)
248 #blue = map(lambda x: float(x[1]) if x[2] == True else 0.0, winnings)
249 #red = map(lambda x: float(x[1]) if x[2] == False else 0.0, winnings)
250 greenline = cumsum(green)
251 #blueline = cumsum(blue)
252 #redline = cumsum(red)
253 return (greenline/100)
255 def exportGraph (self, widget, data):
256 if self.fig is None:
257 return # Might want to disable export button until something has been generated.
259 dia_chooser = gtk.FileChooserDialog(title=_("Please choose the directory you wish to export to:"),
260 action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
261 buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OK,gtk.RESPONSE_OK))
262 dia_chooser.set_destroy_with_parent(True)
263 dia_chooser.set_transient_for(self.parent)
264 try:
265 dia_chooser.set_filename(self.exportFile) # use previously chosen export path as default
266 except:
267 pass
269 response = dia_chooser.run()
271 if response <> gtk.RESPONSE_OK:
272 print _('Closed, no graph exported')
273 dia_chooser.destroy()
274 return
276 # generate a unique filename for export
277 now = datetime.now()
278 now_formatted = now.strftime("%Y%m%d%H%M%S")
279 self.exportFile = dia_chooser.get_filename() + "/fpdb" + now_formatted + ".png"
280 dia_chooser.destroy()
282 #print "DEBUG: self.exportFile = %s" %(self.exportFile)
283 self.fig.savefig(self.exportFile, format="png")
285 #display info box to confirm graph created
286 diainfo = gtk.MessageDialog(parent=self.parent,
287 flags=gtk.DIALOG_DESTROY_WITH_PARENT,
288 type=gtk.MESSAGE_INFO,
289 buttons=gtk.BUTTONS_OK,
290 message_format=_("Graph created"))
291 diainfo.format_secondary_text(self.exportFile)
292 diainfo.run()
293 diainfo.destroy()
295 #end of def exportGraph