2 # -*- coding: utf-8 -*-
4 #Copyright 2008-2010 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
50 install numpy and matplotlib if you want to use graphs.""")
51 print _("""This is of no consequence for other parts of the program, e.g. import
52 and HUD are NOT affected by this problem.""")
53 print "ImportError: %s" % inst
.args
55 class GuiGraphViewer (threading
.Thread
):
57 def __init__(self
, querylist
, config
, parent
, debug
=True):
58 """Constructor for GraphViewer"""
63 #print "start of GraphViewer constructor"
64 self
.db
= Database
.Database(self
.conf
, sql
=self
.sql
)
67 filters_display
= { "Heroes" : True,
84 self
.filters
= Filters
.Filters(self
.db
, self
.conf
, self
.sql
, display
= filters_display
)
85 self
.filters
.registerButton1Name(_("Refresh _Graph"))
86 self
.filters
.registerButton1Callback(self
.generateGraph
)
87 self
.filters
.registerButton2Name(_("_Export to File"))
88 self
.filters
.registerButton2Callback(self
.exportGraph
)
90 self
.mainHBox
= gtk
.HBox(False, 0)
93 self
.leftPanelBox
= self
.filters
.get_vbox()
95 self
.hpane
= gtk
.HPaned()
96 self
.hpane
.pack1(self
.leftPanelBox
)
97 self
.mainHBox
.add(self
.hpane
)
98 # hierarchy: self.mainHBox / self.hpane / self.graphBox / self.canvas / self.fig / self.ax
100 self
.graphBox
= gtk
.VBox(False, 0)
102 self
.hpane
.pack2(self
.graphBox
)
106 #self.exportButton.set_sensitive(False)
113 """returns the vbox of this thread"""
117 def clearGraphData(self
):
122 self
.graphBox
.remove(self
.canvas
)
128 self
.fig
= Figure(figsize
=(5,4), dpi
=100)
129 if self
.canvas
is not None:
130 self
.canvas
.destroy()
132 self
.canvas
= FigureCanvas(self
.fig
) # a gtk.DrawingArea
134 err
= traceback
.extract_tb(sys
.exc_info()[2])[-1]
135 print _("***Error: ")+err
[2]+"("+str(err
[1])+"): "+str(sys
.exc_info()[1])
138 def generateGraph(self
, widget
, data
):
140 self
.clearGraphData()
145 sites
= self
.filters
.getSites()
146 heroes
= self
.filters
.getHeroes()
147 siteids
= self
.filters
.getSiteIds()
148 limits
= self
.filters
.getLimits()
149 games
= self
.filters
.getGames()
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
, 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: $%.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
, 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
)
291 lims
= [int(x
) for x
in limits
if x
.isdigit()]
292 potlims
= [int(x
[0:-2]) for x
in limits
if len(x
) > 2 and x
[-2:] == 'pl']
293 nolims
= [int(x
[0:-2]) for x
in limits
if len(x
) > 2 and x
[-2:] == 'nl']
294 limittest
= "and ( (gt.limitType = 'fl' and gt.bigBlind in "
295 # and ( (limit and bb in()) or (nolimit and bb in ()) )
297 blindtest
= str(tuple(lims
))
298 blindtest
= blindtest
.replace("L", "")
299 blindtest
= blindtest
.replace(",)",")")
300 limittest
= limittest
+ blindtest
+ ' ) '
302 limittest
= limittest
+ '(-1) ) '
303 limittest
= limittest
+ " or (gt.limitType = 'pl' and gt.bigBlind in "
305 blindtest
= str(tuple(potlims
))
306 blindtest
= blindtest
.replace("L", "")
307 blindtest
= blindtest
.replace(",)",")")
308 limittest
= limittest
+ blindtest
+ ' ) '
310 limittest
= limittest
+ '(-1) ) '
311 limittest
= limittest
+ " or (gt.limitType = 'nl' and gt.bigBlind in "
313 blindtest
= str(tuple(nolims
))
314 blindtest
= blindtest
.replace("L", "")
315 blindtest
= blindtest
.replace(",)",")")
316 limittest
= limittest
+ blindtest
+ ' ) )'
318 limittest
= limittest
+ '(-1) ) )'
321 limittest
= limittest
+ " and gt.type = 'ring' "
323 limittest
= limittest
+ " and gt.type = 'tour' "
325 #Must be a nicer way to deal with tuples of size 1 ie. (2,) - which makes sql barf
326 tmp
= tmp
.replace("<player_test>", nametest
)
327 tmp
= tmp
.replace("<site_test>", sitetest
)
328 tmp
= tmp
.replace("<startdate_test>", start_date
)
329 tmp
= tmp
.replace("<enddate_test>", end_date
)
330 tmp
= tmp
.replace("<limit_test>", limittest
)
331 tmp
= tmp
.replace(",)", ")")
333 #print "DEBUG: sql query:"
335 self
.db
.cursor
.execute(tmp
)
336 #returns (HandId,Winnings,Costs,Profit)
337 winnings
= self
.db
.cursor
.fetchall()
340 if len(winnings
) == 0:
341 return (None, None, None)
343 green
= map(lambda x
:float(x
[1]), winnings
)
344 blue
= map(lambda x
: float(x
[1]) if x
[2] == True else 0.0, winnings
)
345 red
= map(lambda x
: float(x
[1]) if x
[2] == False else 0.0, winnings
)
346 greenline
= cumsum(green
)
347 blueline
= cumsum(blue
)
348 redline
= cumsum(red
)
349 return (greenline
/100, blueline
/100, redline
/100)
350 #end of def getRingProfitGraph
352 def exportGraph (self
, widget
, data
):
354 return # Might want to disable export button until something has been generated.
356 dia_chooser
= gtk
.FileChooserDialog(title
=_("Please choose the directory you wish to export to:"),
357 action
=gtk
.FILE_CHOOSER_ACTION_SELECT_FOLDER
,
358 buttons
=(gtk
.STOCK_CANCEL
,gtk
.RESPONSE_CANCEL
,gtk
.STOCK_OK
,gtk
.RESPONSE_OK
))
359 dia_chooser
.set_destroy_with_parent(True)
360 dia_chooser
.set_transient_for(self
.parent
)
362 dia_chooser
.set_filename(self
.exportFile
) # use previously chosen export path as default
366 response
= dia_chooser
.run()
368 if response
<> gtk
.RESPONSE_OK
:
369 print _('Closed, no graph exported')
370 dia_chooser
.destroy()
373 # generate a unique filename for export
375 now_formatted
= now
.strftime("%Y%m%d%H%M%S")
376 self
.exportFile
= dia_chooser
.get_filename() + "/fpdb" + now_formatted
+ ".png"
377 dia_chooser
.destroy()
379 #print "DEBUG: self.exportFile = %s" %(self.exportFile)
380 self
.fig
.savefig(self
.exportFile
, format
="png")
382 #display info box to confirm graph created
383 diainfo
= gtk
.MessageDialog(parent
=self
.parent
,
384 flags
=gtk
.DIALOG_DESTROY_WITH_PARENT
,
385 type=gtk
.MESSAGE_INFO
,
386 buttons
=gtk
.BUTTONS_OK
,
387 message_format
=_("Graph created"))
388 diainfo
.format_secondary_text(self
.exportFile
)
392 #end of def exportGraph