2 # -*- coding: utf-8 -*-
4 #Copyright 2008-2011 Steffen Schaumburg
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.
19 _
= L10n
.get_translation()
29 from datetime
import datetime
38 calluse
= not 'matplotlib' in sys
.modules
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
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 GuiGraphViewer (threading
.Thread
):
55 def __init__(self
, querylist
, config
, parent
, debug
=True):
56 """Constructor for GraphViewer"""
61 #print "start of GraphViewer constructor"
62 self
.db
= Database
.Database(self
.conf
, sql
=self
.sql
)
65 filters_display
= { "Heroes" : True,
83 self
.filters
= Filters
.Filters(self
.db
, self
.conf
, self
.sql
, display
= filters_display
)
84 self
.filters
.registerButton1Name(_("Refresh _Graph"))
85 self
.filters
.registerButton1Callback(self
.generateGraph
)
86 self
.filters
.registerButton2Name(_("_Export to File"))
87 self
.filters
.registerButton2Callback(self
.exportGraph
)
89 self
.mainHBox
= gtk
.HBox(False, 0)
92 self
.leftPanelBox
= self
.filters
.get_vbox()
94 self
.hpane
= gtk
.HPaned()
95 self
.hpane
.pack1(self
.leftPanelBox
)
96 self
.mainHBox
.add(self
.hpane
)
97 # hierarchy: self.mainHBox / self.hpane / self.graphBox / self.canvas / self.fig / self.ax
99 self
.graphBox
= gtk
.VBox(False, 0)
101 self
.hpane
.pack2(self
.graphBox
)
105 #self.exportButton.set_sensitive(False)
112 """returns the vbox of this thread"""
116 def clearGraphData(self
):
121 self
.graphBox
.remove(self
.canvas
)
127 self
.fig
= Figure(figsize
=(5,4), dpi
=100)
128 if self
.canvas
is not None:
129 self
.canvas
.destroy()
131 self
.canvas
= FigureCanvas(self
.fig
) # a gtk.DrawingArea
133 err
= traceback
.extract_tb(sys
.exc_info()[2])[-1]
134 print _("Error:")+" "+err
[2]+"("+str(err
[1])+"): "+str(sys
.exc_info()[1])
137 def generateGraph(self
, widget
, data
):
139 self
.clearGraphData()
144 sites
= self
.filters
.getSites()
145 heroes
= self
.filters
.getHeroes()
146 siteids
= self
.filters
.getSiteIds()
147 limits
= self
.filters
.getLimits()
148 games
= self
.filters
.getGames()
149 currencies
= self
.filters
.getCurrencies()
150 graphops
= self
.filters
.getGraphOps()
153 for i
in ('show', 'none'):
156 # Which sites are selected?
158 if sites
[site
] == True:
159 sitenos
.append(siteids
[site
])
160 _hname
= Charset
.to_utf8(heroes
[site
])
161 result
= self
.db
.get_player_id(self
.conf
, site
, _hname
)
162 if result
is not None:
163 playerids
.append(int(result
))
164 names
= names
+ "\n"+_hname
+ " on "+site
167 #Should probably pop up here.
168 print _("No sites selected - defaulting to PokerStars")
173 print _("No player ids found")
178 print _("No limits found")
182 #Set graph properties
183 self
.ax
= self
.fig
.add_subplot(111)
185 #Get graph data from DB
187 (green
, blue
, red
) = self
.getRingProfitGraph(playerids
, sitenos
, limits
, games
, currencies
, graphops
['dspin'])
188 print _("Graph generated in: %s") %(time() - starttime
)
192 #Set axis labels and grid overlay properites
193 self
.ax
.set_xlabel(_("Hands"), fontsize
= 12)
194 # SET LABEL FOR X AXIS
195 self
.ax
.set_ylabel(graphops
['dspin'], fontsize
= 12)
196 self
.ax
.grid(color
='g', linestyle
=':', linewidth
=0.2)
197 if green
== None or green
== []:
198 self
.ax
.set_title(_("No Data for Player(s) Found"))
199 green
= ([ 0., 0., 0., 0., 500., 1000., 900., 800.,
200 700., 600., 500., 400., 300., 200., 100., 0.,
201 500., 1000., 1000., 1000., 1000., 1000., 1000., 1000.,
202 1000., 1000., 1000., 1000., 1000., 1000., 875., 750.,
203 625., 500., 375., 250., 125., 0., 0., 0.,
204 0., 500., 1000., 900., 800., 700., 600., 500.,
205 400., 300., 200., 100., 0., 500., 1000., 1000.])
206 red
= ([ 0., 0., 0., 0., 500., 1000., 900., 800.,
207 700., 600., 500., 400., 300., 200., 100., 0.,
208 0., 0., 0., 0., 0., 0., 125., 250.,
209 375., 500., 500., 500., 500., 500., 500., 500.,
210 500., 500., 375., 250., 125., 0., 0., 0.,
211 0., 500., 1000., 900., 800., 700., 600., 500.,
212 400., 300., 200., 100., 0., 500., 1000., 1000.])
213 blue
= ([ 0., 0., 0., 0., 500., 1000., 900., 800.,
214 700., 600., 500., 400., 300., 200., 100., 0.,
215 0., 0., 0., 0., 0., 0., 125., 250.,
216 375., 500., 625., 750., 875., 1000., 875., 750.,
217 625., 500., 375., 250., 125., 0., 0., 0.,
218 0., 500., 1000., 900., 800., 700., 600., 500.,
219 400., 300., 200., 100., 0., 500., 1000., 1000.])
221 self
.ax
.plot(green
, color
='green', label
=_('Hands: %d\nProfit: (%s): %.2f') %(len(green
), green
[-1]))
222 self
.ax
.plot(blue
, color
='blue', label
=_('Showdown') + ': $%.2f' %(blue
[-1]))
223 self
.ax
.plot(red
, color
='red', label
=_('Non-showdown') + ': $%.2f' %(red
[-1]))
224 self
.graphBox
.add(self
.canvas
)
228 #TODO: Do something useful like alert user
229 #print "No hands returned by graph query"
231 self
.ax
.set_title((_("Profit graph for ring games")+names
),fontsize
=12)
234 self
.ax
.plot(green
, color
='green', label
=_('Hands: %d\nProfit: (%s): %.2f') %(len(green
),graphops
['dspin'], green
[-1]))
235 if graphops
['showdown'] == 'ON':
236 self
.ax
.plot(blue
, color
='blue', label
=_('Showdown') + ' (%s): %.2f' %(graphops
['dspin'], blue
[-1]))
237 if graphops
['nonshowdown'] == 'ON':
238 self
.ax
.plot(red
, color
='red', label
=_('Non-showdown') + ' (%s): %.2f' %(graphops
['dspin'], red
[-1]))
240 if sys
.version
[0:3] == '2.5':
241 self
.ax
.legend(loc
='upper left', shadow
=True, prop
=FontProperties(size
='smaller'))
243 self
.ax
.legend(loc
='upper left', fancybox
=True, shadow
=True, prop
=FontProperties(size
='smaller'))
245 self
.graphBox
.add(self
.canvas
)
248 #self.exportButton.set_sensitive(True)
250 err
= traceback
.extract_tb(sys
.exc_info()[2])[-1]
251 print _("Error:")+" "+err
[2]+"("+str(err
[1])+"): "+str(sys
.exc_info()[1])
253 #end of def showClicked
256 def getRingProfitGraph(self
, names
, sites
, limits
, games
, currencies
, units
):
257 # tmp = self.sql.query['getRingProfitAllHandsPlayerIdSite']
258 # print "DEBUG: getRingProfitGraph"
261 tmp
= self
.sql
.query
['getRingProfitAllHandsPlayerIdSiteInDollars']
263 tmp
= self
.sql
.query
['getRingProfitAllHandsPlayerIdSiteInBB']
266 start_date
, end_date
= self
.filters
.getDates()
268 #Buggered if I can find a way to do this 'nicely' take a list of integers and longs
269 # and turn it into a tuple readale by sql.
270 # [5L] into (5) not (5,) and [5L, 2829L] into (5, 2829)
271 nametest
= str(tuple(names
))
272 sitetest
= str(tuple(sites
))
273 #nametest = nametest.replace("L", "")
276 for m
in self
.filters
.display
.items():
277 if m
[0] == 'Games' and m
[1]:
282 gametest
= str(tuple(q
))
283 gametest
= gametest
.replace("L", "")
284 gametest
= gametest
.replace(",)",")")
285 gametest
= gametest
.replace("u'","'")
286 gametest
= "and gt.category in %s" % gametest
288 gametest
= "and gt.category IS NULL"
289 tmp
= tmp
.replace("<game_test>", gametest
)
295 currencytest
= str(tuple(q
))
296 currencytest
= currencytest
.replace(",)",")")
297 currencytest
= currencytest
.replace("u'","'")
298 currencytest
= "AND gt.currency in %s" % currencytest
299 tmp
= tmp
.replace("<currency_test>", currencytest
)
301 lims
= [int(x
[0:-2]) for x
in limits
if len(x
) > 2 and x
[-2:] == 'fl']
302 potlims
= [int(x
[0:-2]) for x
in limits
if len(x
) > 2 and x
[-2:] == 'pl']
303 nolims
= [int(x
[0:-2]) for x
in limits
if len(x
) > 2 and x
[-2:] == 'nl']
304 capnolims
= [int(x
[0:-2]) for x
in limits
if len(x
) > 2 and x
[-2:] == 'cn']
305 limittest
= "and ( (gt.limitType = 'fl' and gt.bigBlind in "
306 # and ( (limit and bb in()) or (nolimit and bb in ()) )
308 blindtest
= str(tuple(lims
))
309 blindtest
= blindtest
.replace("L", "")
310 blindtest
= blindtest
.replace(",)",")")
311 limittest
= limittest
+ blindtest
+ ' ) '
313 limittest
= limittest
+ '(-1) ) '
314 limittest
= limittest
+ " or (gt.limitType = 'pl' and gt.bigBlind in "
316 blindtest
= str(tuple(potlims
))
317 blindtest
= blindtest
.replace("L", "")
318 blindtest
= blindtest
.replace(",)",")")
319 limittest
= limittest
+ blindtest
+ ' ) '
321 limittest
= limittest
+ '(-1) ) '
322 limittest
= limittest
+ " or (gt.limitType = 'nl' and gt.bigBlind in "
324 blindtest
= str(tuple(nolims
))
325 blindtest
= blindtest
.replace("L", "")
326 blindtest
= blindtest
.replace(",)",")")
327 limittest
= limittest
+ blindtest
+ ' ) '
329 limittest
= limittest
+ '(-1) ) '
330 limittest
= limittest
+ " or (gt.limitType = 'cn' and gt.bigBlind in "
332 blindtest
= str(tuple(capnolims
))
333 blindtest
= blindtest
.replace("L", "")
334 blindtest
= blindtest
.replace(",)",")")
335 limittest
= limittest
+ blindtest
+ ' ) )'
337 limittest
= limittest
+ '(-1) ) )'
340 limittest
= limittest
+ " and gt.type = 'ring' "
342 limittest
= limittest
+ " and gt.type = 'tour' "
344 #Must be a nicer way to deal with tuples of size 1 ie. (2,) - which makes sql barf
345 tmp
= tmp
.replace("<player_test>", nametest
)
346 tmp
= tmp
.replace("<site_test>", sitetest
)
347 tmp
= tmp
.replace("<startdate_test>", start_date
)
348 tmp
= tmp
.replace("<enddate_test>", end_date
)
349 tmp
= tmp
.replace("<limit_test>", limittest
)
350 tmp
= tmp
.replace(",)", ")")
352 #print "DEBUG: sql query:"
354 self
.db
.cursor
.execute(tmp
)
355 #returns (HandId,Winnings,Costs,Profit)
356 winnings
= self
.db
.cursor
.fetchall()
359 if len(winnings
) == 0:
360 return (None, None, None)
362 green
= map(lambda x
:float(x
[1]), winnings
)
363 blue
= map(lambda x
: float(x
[1]) if x
[2] == True else 0.0, winnings
)
364 red
= map(lambda x
: float(x
[1]) if x
[2] == False else 0.0, winnings
)
365 greenline
= cumsum(green
)
366 blueline
= cumsum(blue
)
367 redline
= cumsum(red
)
368 return (greenline
/100, blueline
/100, redline
/100)
369 #end of def getRingProfitGraph
371 def exportGraph (self
, widget
, data
):
373 return # Might want to disable export button until something has been generated.
375 dia_chooser
= gtk
.FileChooserDialog(title
=_("Please choose the directory you wish to export to:"),
376 action
=gtk
.FILE_CHOOSER_ACTION_SELECT_FOLDER
,
377 buttons
=(gtk
.STOCK_CANCEL
,gtk
.RESPONSE_CANCEL
,gtk
.STOCK_OK
,gtk
.RESPONSE_OK
))
378 dia_chooser
.set_destroy_with_parent(True)
379 dia_chooser
.set_transient_for(self
.parent
)
381 dia_chooser
.set_filename(self
.exportFile
) # use previously chosen export path as default
385 response
= dia_chooser
.run()
387 if response
<> gtk
.RESPONSE_OK
:
388 print _('Closed, no graph exported')
389 dia_chooser
.destroy()
392 # generate a unique filename for export
394 now_formatted
= now
.strftime("%Y%m%d%H%M%S")
395 self
.exportFile
= dia_chooser
.get_filename() + "/fpdb" + now_formatted
+ ".png"
396 dia_chooser
.destroy()
398 #print "DEBUG: self.exportFile = %s" %(self.exportFile)
399 self
.fig
.savefig(self
.exportFile
, format
="png")
401 #display info box to confirm graph created
402 diainfo
= gtk
.MessageDialog(parent
=self
.parent
,
403 flags
=gtk
.DIALOG_DESTROY_WITH_PARENT
,
404 type=gtk
.MESSAGE_INFO
,
405 buttons
=gtk
.BUTTONS_OK
,
406 message_format
=_("Graph created"))
407 diainfo
.format_secondary_text(self
.exportFile
)
411 #end of def exportGraph