]> de.git.xonotic.org Git - xonotic/xonstat.git/commitdiff
clean dangling commit on actually merged branch
authornyov <nyov@nexnode.net>
Sun, 6 May 2012 02:17:24 +0000 (04:17 +0200)
committernyov <nyov@nexnode.net>
Sun, 6 May 2012 02:17:24 +0000 (04:17 +0200)
Merge remote-tracking branch 'dmazary/patch-3'

16 files changed:
README.md [new file with mode: 0644]
README.txt [deleted file]
TODO [new file with mode: 0644]
XonStat.egg-info/requires.txt
xonstat/__init__.py
xonstat/static/css/style.css
xonstat/static/js/jquery.flot.min.js [new file with mode: 0644]
xonstat/templates/base.mako
xonstat/templates/game_info.mako
xonstat/templates/nav.mako
xonstat/templates/player_info.mako
xonstat/util.py
xonstat/views/__init__.py
xonstat/views/main.py
xonstat/views/player.py
xonstat/views/submission.py

diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..07eceb0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,36 @@
+This is **XonStat**, the application in front of [xonstatdb][xonstatdb].  
+[XonStat][xonstat] handles the submission of statistical information from the open source first person shooter [Xonotic][xonotic].
+
+----
+
+To start, first run the following from the root directory to set up dependencies:
+
+    python setup.py develop
+
+Next you'll want to set up [xonstatdb][xonstatdb]. This is maintained as a separate project here:
+
+    https://github.com/antzucaro/xonstatdb
+
+Next you'll want to open up development.ini and change a few things for added security. Chief among these is the "sqlalchemy.url" setting, which contains your username and password for the database. Change that match the new password you gave xonstat during the installation of xonstatdb. The other setting to change is "session.secret," which is used to keep your web session (cookies and such) secure.
+
+To start the server run the following from the root directory. I recommend running this within a GNU screen session:
+
+    paster serve development.ini #(or production.ini if you've configured that settings file instead)
+
+To get a Xonotic server configured to use this server, change the CVAR `g_playerstats_uri` to point to the correct host, port, and URL path. By default this is:
+
+    http://localhost:6543/stats/submit
+
+...so in the server console (or in your config) you can put:
+
+    set g_playerstats_uri http://localhost:6543/stats/submit
+
+If you have any questions or issues please open up a bug report here, or - better yet ! - fork it and send me a pull request.
+
+[xonstatdb]: https://github.com/antzucaro/xonstatdb
+[xonstat]: http://stats.xonotic.org/
+[xonotic]: http://www.xonotic.org/
+
+----
+
+Project is licensed GPLv2+.
diff --git a/README.txt b/README.txt
deleted file mode 100644 (file)
index 5fce5eb..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-This is XonStat, the application in front of xonstatdb. XonStat handles the submission of statistical information from the open source first person shooter Xonotic. 
-
-To start, first run the following from the root directory to set up dependencies:
-
-    python setup.py develop
-
-Next you'll want to set up xonstatdb. This is maintained as a separate project here:
-
-    https://github.com/antzucaro/xonstatdb
-
-Next you'll want to open up development.ini and change a few things for added security. Chief among these is the "sqlalchemy.url" setting, which contains your username and password for the database. Change that match the new password you gave xonstat during the installation of xonstatdb. The other setting to change is "security.secret," which is used to keep your web session (cookies and such) secure. 
-
-To start the server run the following from the root directory. I recommend running this within a GNU screen session:
-
-    paster serve development.ini #(or production.ini if you've configured that settings file instead)
-
-To get a Xonotic server configured to use this server, change the CVAR "g_playerstats_uri" to point to the correct host, port, and URL path. By default this is:
-
-    http://localhost:6543/stats/submit
-
-...so in the command line of the server (or in your config) you can put:
-
-    set g_playerstats_uri http://localhost:6543/stats/submit
-
-If you have any questions or issues please open up a bug report here, or - better yet ! - fork it and send me a pull request. 
-
-TODO:
-
-- "e matches" and "e joins" seem to be mutually exclusive. Add a check for either (instead of just joins" before adding a player_game_stats record).
-
-- map names are being recorded multiple times in the maps table. They should be found when being played subsequent times. 
diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..a279b07
--- /dev/null
+++ b/TODO
@@ -0,0 +1,6 @@
+TODO:
+
+- "e matches" and "e joins" seem to be mutually exclusive. Add a check for either (instead of just joins" before adding a player_game_stats record).
+
+- map names are being recorded multiple times in the maps table. They should be found when being played subsequent times. 
+
index 01d49e4e156886d7ce0d2acdfa7d8e1d7f3bca08..2950c076dd8a670e8470f2433144ab9f37ce02b4 100644 (file)
@@ -3,4 +3,8 @@ SQLAlchemy
 transaction
 repoze.tm2>=1.0b1
 zope.sqlalchemy
-WebError
\ No newline at end of file
+WebError
+sqlahelper
+webhelpers
+psycopg2
+PasteScript
index d47c365679b6765329754d39acf83baee21c5512..b1f08d2c5ab93f395167037c548dcda6b7f1c4b7 100755 (executable)
@@ -1,4 +1,3 @@
-import pyramid_jinja2
 import sqlahelper
 from pyramid.config import Configurator
 from sqlalchemy import engine_from_config
@@ -17,8 +16,6 @@ def main(global_config, **settings):
 
     config = Configurator(settings=settings)
 
-    config.add_renderer('.jinja2', pyramid_jinja2.renderer_factory)
-
     config.add_static_view('static', 'xonstat:static')
 
     # ROOT ROUTE
@@ -26,6 +23,10 @@ def main(global_config, **settings):
     config.add_view(main_index, route_name="main_index",
         renderer="main_index.mako")
 
+    # MAIN SUBMISSION ROUTE
+    config.add_route("stats_submit", "stats/submit")
+    config.add_view(stats_submit, route_name="stats_submit")
+
     # PLAYER ROUTES
     config.add_route("player_game_index_paged",
             "/player/{player_id:\d+}/games/page/{page:\d+}")
@@ -49,6 +50,10 @@ def main(global_config, **settings):
     config.add_view(player_info, route_name="player_info",
         renderer="player_info.mako")
 
+    config.add_route("player_accuracy", "/player/{id:\d+}/accuracy")
+    config.add_view(player_accuracy, route_name="player_accuracy",
+        renderer="json")
+
     # GAME ROUTES
     config.add_route("game_index", "/games")
     config.add_view(game_index, route_name="game_index",
@@ -101,10 +106,6 @@ def main(global_config, **settings):
     config.add_view(map_info, route_name="map_info",
         renderer="map_info.mako")
 
-    config.add_route("stats_submit", "stats/submit")
-    config.add_view(stats_submit, route_name="stats_submit",
-        renderer="index.jinja2")
-
     # SEARCH ROUTES
     config.add_route("search", "search")
     config.add_view(search, route_name="search",
index f43a3fd1c2fc15e6a25c74dbc13f36544b40a2c4..0d8bcb3767091747d23f81a7159108bbeb382733 100755 (executable)
@@ -3399,6 +3399,7 @@ a.thumbnail:hover {
   -moz-border-image: url(img/web_border.png) 85 85 85 stretch;
   -webkit-border-image: url(img/web_border.png) 72 85 85 stretch;
   -o-border-image: url(img/web_border.png) 96 96 96 stretch;
+  background-color: #000000;
   border-image: url(img/web_border.png) 96 96 96 stretch;
   border-width: 40px;
   left: -40px;
@@ -3424,6 +3425,17 @@ a.thumbnail:hover {
 #navsearch select {
   width: 80px;
 }
+
+header {
+  height:0;
+}
+header a {
+  display:block;
+  overflow:hidden;
+}
+header h1 { display:none; }
+header h2 { display:none; }
+
 /* Game scoreboard */
 .game {
   background-image: -moz-linear-gradient(center bottom , #062C52, #041B33);
@@ -3454,3 +3466,32 @@ a.thumbnail:hover {
 .game tr:hover {
     background-color: #222;
 }
+
+/* accuracy and weapon graphs */
+.weapon-nav {
+  height: 70px;
+  margin-bottom: 20px;
+}
+.weapon-nav ul {
+  display: block;
+  list-style: none outside none;
+}
+.weapon-nav li {
+  cursor: pointer;
+  float: left;
+  margin-right: 10px;
+}
+.weapon-nav li:hover {
+  border-bottom: 2px solid #001021;
+}
+.weapon-nav .weapon-active {
+  border-bottom: 2px solid #436688;
+}
+.weapon-nav p {
+  text-align: center;
+}
+
+.flot table, .flot td {
+       border: 0;
+}
+
diff --git a/xonstat/static/js/jquery.flot.min.js b/xonstat/static/js/jquery.flot.min.js
new file mode 100644 (file)
index 0000000..4467fc5
--- /dev/null
@@ -0,0 +1,6 @@
+/* Javascript plotting library for jQuery, v. 0.7.
+ *
+ * Released under the MIT license by IOLA, December 2007.
+ *
+ */
+(function(b){b.color={};b.color.make=function(d,e,g,f){var c={};c.r=d||0;c.g=e||0;c.b=g||0;c.a=f!=null?f:1;c.add=function(h,j){for(var k=0;k<h.length;++k){c[h.charAt(k)]+=j}return c.normalize()};c.scale=function(h,j){for(var k=0;k<h.length;++k){c[h.charAt(k)]*=j}return c.normalize()};c.toString=function(){if(c.a>=1){return"rgb("+[c.r,c.g,c.b].join(",")+")"}else{return"rgba("+[c.r,c.g,c.b,c.a].join(",")+")"}};c.normalize=function(){function h(k,j,l){return j<k?k:(j>l?l:j)}c.r=h(0,parseInt(c.r),255);c.g=h(0,parseInt(c.g),255);c.b=h(0,parseInt(c.b),255);c.a=h(0,c.a,1);return c};c.clone=function(){return b.color.make(c.r,c.b,c.g,c.a)};return c.normalize()};b.color.extract=function(d,e){var c;do{c=d.css(e).toLowerCase();if(c!=""&&c!="transparent"){break}d=d.parent()}while(!b.nodeName(d.get(0),"body"));if(c=="rgba(0, 0, 0, 0)"){c="transparent"}return b.color.parse(c)};b.color.parse=function(c){var d,f=b.color.make;if(d=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(c)){return f(parseInt(d[1],10),parseInt(d[2],10),parseInt(d[3],10))}if(d=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(c)){return f(parseInt(d[1],10),parseInt(d[2],10),parseInt(d[3],10),parseFloat(d[4]))}if(d=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(c)){return f(parseFloat(d[1])*2.55,parseFloat(d[2])*2.55,parseFloat(d[3])*2.55)}if(d=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(c)){return f(parseFloat(d[1])*2.55,parseFloat(d[2])*2.55,parseFloat(d[3])*2.55,parseFloat(d[4]))}if(d=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(c)){return f(parseInt(d[1],16),parseInt(d[2],16),parseInt(d[3],16))}if(d=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(c)){return f(parseInt(d[1]+d[1],16),parseInt(d[2]+d[2],16),parseInt(d[3]+d[3],16))}var e=b.trim(c).toLowerCase();if(e=="transparent"){return f(255,255,255,0)}else{d=a[e]||[0,0,0];return f(d[0],d[1],d[2])}};var a={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);(function(c){function b(av,ai,J,af){var Q=[],O={colors:["#edc240","#afd8f8","#cb4b4b","#4da74d","#9440ed"],legend:{show:true,noColumns:1,labelFormatter:null,labelBoxBorderColor:"#ccc",container:null,position:"ne",margin:5,backgroundColor:null,backgroundOpacity:0.85},xaxis:{show:null,position:"bottom",mode:null,color:null,tickColor:null,transform:null,inverseTransform:null,min:null,max:null,autoscaleMargin:null,ticks:null,tickFormatter:null,labelWidth:null,labelHeight:null,reserveSpace:null,tickLength:null,alignTicksWithAxis:null,tickDecimals:null,tickSize:null,minTickSize:null,monthNames:null,timeformat:null,twelveHourClock:false},yaxis:{autoscaleMargin:0.02,position:"left"},xaxes:[],yaxes:[],series:{points:{show:false,radius:3,lineWidth:2,fill:true,fillColor:"#ffffff",symbol:"circle"},lines:{lineWidth:2,fill:false,fillColor:null,steps:false},bars:{show:false,lineWidth:2,barWidth:1,fill:true,fillColor:null,align:"left",horizontal:false},shadowSize:3},grid:{show:true,aboveData:false,color:"#545454",backgroundColor:null,borderColor:null,tickColor:null,labelMargin:5,axisMargin:8,borderWidth:2,minBorderMargin:null,markings:null,markingsColor:"#f4f4f4",markingsLineWidth:2,clickable:false,hoverable:false,autoHighlight:true,mouseActiveRadius:10},hooks:{}},az=null,ad=null,y=null,H=null,A=null,p=[],aw=[],q={left:0,right:0,top:0,bottom:0},G=0,I=0,h=0,w=0,ak={processOptions:[],processRawData:[],processDatapoints:[],drawSeries:[],draw:[],bindEvents:[],drawOverlay:[],shutdown:[]},aq=this;aq.setData=aj;aq.setupGrid=t;aq.draw=W;aq.getPlaceholder=function(){return av};aq.getCanvas=function(){return az};aq.getPlotOffset=function(){return q};aq.width=function(){return h};aq.height=function(){return w};aq.offset=function(){var aB=y.offset();aB.left+=q.left;aB.top+=q.top;return aB};aq.getData=function(){return Q};aq.getAxes=function(){var aC={},aB;c.each(p.concat(aw),function(aD,aE){if(aE){aC[aE.direction+(aE.n!=1?aE.n:"")+"axis"]=aE}});return aC};aq.getXAxes=function(){return p};aq.getYAxes=function(){return aw};aq.c2p=C;aq.p2c=ar;aq.getOptions=function(){return O};aq.highlight=x;aq.unhighlight=T;aq.triggerRedrawOverlay=f;aq.pointOffset=function(aB){return{left:parseInt(p[aA(aB,"x")-1].p2c(+aB.x)+q.left),top:parseInt(aw[aA(aB,"y")-1].p2c(+aB.y)+q.top)}};aq.shutdown=ag;aq.resize=function(){B();g(az);g(ad)};aq.hooks=ak;F(aq);Z(J);X();aj(ai);t();W();ah();function an(aD,aB){aB=[aq].concat(aB);for(var aC=0;aC<aD.length;++aC){aD[aC].apply(this,aB)}}function F(){for(var aB=0;aB<af.length;++aB){var aC=af[aB];aC.init(aq);if(aC.options){c.extend(true,O,aC.options)}}}function Z(aC){var aB;c.extend(true,O,aC);if(O.xaxis.color==null){O.xaxis.color=O.grid.color}if(O.yaxis.color==null){O.yaxis.color=O.grid.color}if(O.xaxis.tickColor==null){O.xaxis.tickColor=O.grid.tickColor}if(O.yaxis.tickColor==null){O.yaxis.tickColor=O.grid.tickColor}if(O.grid.borderColor==null){O.grid.borderColor=O.grid.color}if(O.grid.tickColor==null){O.grid.tickColor=c.color.parse(O.grid.color).scale("a",0.22).toString()}for(aB=0;aB<Math.max(1,O.xaxes.length);++aB){O.xaxes[aB]=c.extend(true,{},O.xaxis,O.xaxes[aB])}for(aB=0;aB<Math.max(1,O.yaxes.length);++aB){O.yaxes[aB]=c.extend(true,{},O.yaxis,O.yaxes[aB])}if(O.xaxis.noTicks&&O.xaxis.ticks==null){O.xaxis.ticks=O.xaxis.noTicks}if(O.yaxis.noTicks&&O.yaxis.ticks==null){O.yaxis.ticks=O.yaxis.noTicks}if(O.x2axis){O.xaxes[1]=c.extend(true,{},O.xaxis,O.x2axis);O.xaxes[1].position="top"}if(O.y2axis){O.yaxes[1]=c.extend(true,{},O.yaxis,O.y2axis);O.yaxes[1].position="right"}if(O.grid.coloredAreas){O.grid.markings=O.grid.coloredAreas}if(O.grid.coloredAreasColor){O.grid.markingsColor=O.grid.coloredAreasColor}if(O.lines){c.extend(true,O.series.lines,O.lines)}if(O.points){c.extend(true,O.series.points,O.points)}if(O.bars){c.extend(true,O.series.bars,O.bars)}if(O.shadowSize!=null){O.series.shadowSize=O.shadowSize}for(aB=0;aB<O.xaxes.length;++aB){V(p,aB+1).options=O.xaxes[aB]}for(aB=0;aB<O.yaxes.length;++aB){V(aw,aB+1).options=O.yaxes[aB]}for(var aD in ak){if(O.hooks[aD]&&O.hooks[aD].length){ak[aD]=ak[aD].concat(O.hooks[aD])}}an(ak.processOptions,[O])}function aj(aB){Q=Y(aB);ax();z()}function Y(aE){var aC=[];for(var aB=0;aB<aE.length;++aB){var aD=c.extend(true,{},O.series);if(aE[aB].data!=null){aD.data=aE[aB].data;delete aE[aB].data;c.extend(true,aD,aE[aB]);aE[aB].data=aD.data}else{aD.data=aE[aB]}aC.push(aD)}return aC}function aA(aC,aD){var aB=aC[aD+"axis"];if(typeof aB=="object"){aB=aB.n}if(typeof aB!="number"){aB=1}return aB}function m(){return c.grep(p.concat(aw),function(aB){return aB})}function C(aE){var aC={},aB,aD;for(aB=0;aB<p.length;++aB){aD=p[aB];if(aD&&aD.used){aC["x"+aD.n]=aD.c2p(aE.left)}}for(aB=0;aB<aw.length;++aB){aD=aw[aB];if(aD&&aD.used){aC["y"+aD.n]=aD.c2p(aE.top)}}if(aC.x1!==undefined){aC.x=aC.x1}if(aC.y1!==undefined){aC.y=aC.y1}return aC}function ar(aF){var aD={},aC,aE,aB;for(aC=0;aC<p.length;++aC){aE=p[aC];if(aE&&aE.used){aB="x"+aE.n;if(aF[aB]==null&&aE.n==1){aB="x"}if(aF[aB]!=null){aD.left=aE.p2c(aF[aB]);break}}}for(aC=0;aC<aw.length;++aC){aE=aw[aC];if(aE&&aE.used){aB="y"+aE.n;if(aF[aB]==null&&aE.n==1){aB="y"}if(aF[aB]!=null){aD.top=aE.p2c(aF[aB]);break}}}return aD}function V(aC,aB){if(!aC[aB-1]){aC[aB-1]={n:aB,direction:aC==p?"x":"y",options:c.extend(true,{},aC==p?O.xaxis:O.yaxis)}}return aC[aB-1]}function ax(){var aG;var aM=Q.length,aB=[],aE=[];for(aG=0;aG<Q.length;++aG){var aJ=Q[aG].color;if(aJ!=null){--aM;if(typeof aJ=="number"){aE.push(aJ)}else{aB.push(c.color.parse(Q[aG].color))}}}for(aG=0;aG<aE.length;++aG){aM=Math.max(aM,aE[aG]+1)}var aC=[],aF=0;aG=0;while(aC.length<aM){var aI;if(O.colors.length==aG){aI=c.color.make(100,100,100)}else{aI=c.color.parse(O.colors[aG])}var aD=aF%2==1?-1:1;aI.scale("rgb",1+aD*Math.ceil(aF/2)*0.2);aC.push(aI);++aG;if(aG>=O.colors.length){aG=0;++aF}}var aH=0,aN;for(aG=0;aG<Q.length;++aG){aN=Q[aG];if(aN.color==null){aN.color=aC[aH].toString();++aH}else{if(typeof aN.color=="number"){aN.color=aC[aN.color].toString()}}if(aN.lines.show==null){var aL,aK=true;for(aL in aN){if(aN[aL]&&aN[aL].show){aK=false;break}}if(aK){aN.lines.show=true}}aN.xaxis=V(p,aA(aN,"x"));aN.yaxis=V(aw,aA(aN,"y"))}}function z(){var aO=Number.POSITIVE_INFINITY,aI=Number.NEGATIVE_INFINITY,aB=Number.MAX_VALUE,aU,aS,aR,aN,aD,aJ,aT,aP,aH,aG,aC,a0,aX,aL;function aF(a3,a2,a1){if(a2<a3.datamin&&a2!=-aB){a3.datamin=a2}if(a1>a3.datamax&&a1!=aB){a3.datamax=a1}}c.each(m(),function(a1,a2){a2.datamin=aO;a2.datamax=aI;a2.used=false});for(aU=0;aU<Q.length;++aU){aJ=Q[aU];aJ.datapoints={points:[]};an(ak.processRawData,[aJ,aJ.data,aJ.datapoints])}for(aU=0;aU<Q.length;++aU){aJ=Q[aU];var aZ=aJ.data,aW=aJ.datapoints.format;if(!aW){aW=[];aW.push({x:true,number:true,required:true});aW.push({y:true,number:true,required:true});if(aJ.bars.show||(aJ.lines.show&&aJ.lines.fill)){aW.push({y:true,number:true,required:false,defaultValue:0});if(aJ.bars.horizontal){delete aW[aW.length-1].y;aW[aW.length-1].x=true}}aJ.datapoints.format=aW}if(aJ.datapoints.pointsize!=null){continue}aJ.datapoints.pointsize=aW.length;aP=aJ.datapoints.pointsize;aT=aJ.datapoints.points;insertSteps=aJ.lines.show&&aJ.lines.steps;aJ.xaxis.used=aJ.yaxis.used=true;for(aS=aR=0;aS<aZ.length;++aS,aR+=aP){aL=aZ[aS];var aE=aL==null;if(!aE){for(aN=0;aN<aP;++aN){a0=aL[aN];aX=aW[aN];if(aX){if(aX.number&&a0!=null){a0=+a0;if(isNaN(a0)){a0=null}else{if(a0==Infinity){a0=aB}else{if(a0==-Infinity){a0=-aB}}}}if(a0==null){if(aX.required){aE=true}if(aX.defaultValue!=null){a0=aX.defaultValue}}}aT[aR+aN]=a0}}if(aE){for(aN=0;aN<aP;++aN){a0=aT[aR+aN];if(a0!=null){aX=aW[aN];if(aX.x){aF(aJ.xaxis,a0,a0)}if(aX.y){aF(aJ.yaxis,a0,a0)}}aT[aR+aN]=null}}else{if(insertSteps&&aR>0&&aT[aR-aP]!=null&&aT[aR-aP]!=aT[aR]&&aT[aR-aP+1]!=aT[aR+1]){for(aN=0;aN<aP;++aN){aT[aR+aP+aN]=aT[aR+aN]}aT[aR+1]=aT[aR-aP+1];aR+=aP}}}}for(aU=0;aU<Q.length;++aU){aJ=Q[aU];an(ak.processDatapoints,[aJ,aJ.datapoints])}for(aU=0;aU<Q.length;++aU){aJ=Q[aU];aT=aJ.datapoints.points,aP=aJ.datapoints.pointsize;var aK=aO,aQ=aO,aM=aI,aV=aI;for(aS=0;aS<aT.length;aS+=aP){if(aT[aS]==null){continue}for(aN=0;aN<aP;++aN){a0=aT[aS+aN];aX=aW[aN];if(!aX||a0==aB||a0==-aB){continue}if(aX.x){if(a0<aK){aK=a0}if(a0>aM){aM=a0}}if(aX.y){if(a0<aQ){aQ=a0}if(a0>aV){aV=a0}}}}if(aJ.bars.show){var aY=aJ.bars.align=="left"?0:-aJ.bars.barWidth/2;if(aJ.bars.horizontal){aQ+=aY;aV+=aY+aJ.bars.barWidth}else{aK+=aY;aM+=aY+aJ.bars.barWidth}}aF(aJ.xaxis,aK,aM);aF(aJ.yaxis,aQ,aV)}c.each(m(),function(a1,a2){if(a2.datamin==aO){a2.datamin=null}if(a2.datamax==aI){a2.datamax=null}})}function j(aB,aC){var aD=document.createElement("canvas");aD.className=aC;aD.width=G;aD.height=I;if(!aB){c(aD).css({position:"absolute",left:0,top:0})}c(aD).appendTo(av);if(!aD.getContext){aD=window.G_vmlCanvasManager.initElement(aD)}aD.getContext("2d").save();return aD}function B(){G=av.width();I=av.height();if(G<=0||I<=0){throw"Invalid dimensions for plot, width = "+G+", height = "+I}}function g(aC){if(aC.width!=G){aC.width=G}if(aC.height!=I){aC.height=I}var aB=aC.getContext("2d");aB.restore();aB.save()}function X(){var aC,aB=av.children("canvas.base"),aD=av.children("canvas.overlay");if(aB.length==0||aD==0){av.html("");av.css({padding:0});if(av.css("position")=="static"){av.css("position","relative")}B();az=j(true,"base");ad=j(false,"overlay");aC=false}else{az=aB.get(0);ad=aD.get(0);aC=true}H=az.getContext("2d");A=ad.getContext("2d");y=c([ad,az]);if(aC){av.data("plot").shutdown();aq.resize();A.clearRect(0,0,G,I);y.unbind();av.children().not([az,ad]).remove()}av.data("plot",aq)}function ah(){if(O.grid.hoverable){y.mousemove(aa);y.mouseleave(l)}if(O.grid.clickable){y.click(R)}an(ak.bindEvents,[y])}function ag(){if(M){clearTimeout(M)}y.unbind("mousemove",aa);y.unbind("mouseleave",l);y.unbind("click",R);an(ak.shutdown,[y])}function r(aG){function aC(aH){return aH}var aF,aB,aD=aG.options.transform||aC,aE=aG.options.inverseTransform;if(aG.direction=="x"){aF=aG.scale=h/Math.abs(aD(aG.max)-aD(aG.min));aB=Math.min(aD(aG.max),aD(aG.min))}else{aF=aG.scale=w/Math.abs(aD(aG.max)-aD(aG.min));aF=-aF;aB=Math.max(aD(aG.max),aD(aG.min))}if(aD==aC){aG.p2c=function(aH){return(aH-aB)*aF}}else{aG.p2c=function(aH){return(aD(aH)-aB)*aF}}if(!aE){aG.c2p=function(aH){return aB+aH/aF}}else{aG.c2p=function(aH){return aE(aB+aH/aF)}}}function L(aD){var aB=aD.options,aF,aJ=aD.ticks||[],aI=[],aE,aK=aB.labelWidth,aG=aB.labelHeight,aC;function aH(aM,aL){return c('<div style="position:absolute;top:-10000px;'+aL+'font-size:smaller"><div class="'+aD.direction+"Axis "+aD.direction+aD.n+'Axis">'+aM.join("")+"</div></div>").appendTo(av)}if(aD.direction=="x"){if(aK==null){aK=Math.floor(G/(aJ.length>0?aJ.length:1))}if(aG==null){aI=[];for(aF=0;aF<aJ.length;++aF){aE=aJ[aF].label;if(aE){aI.push('<div class="tickLabel" style="float:left;width:'+aK+'px">'+aE+"</div>")}}if(aI.length>0){aI.push('<div style="clear:left"></div>');aC=aH(aI,"width:10000px;");aG=aC.height();aC.remove()}}}else{if(aK==null||aG==null){for(aF=0;aF<aJ.length;++aF){aE=aJ[aF].label;if(aE){aI.push('<div class="tickLabel">'+aE+"</div>")}}if(aI.length>0){aC=aH(aI,"");if(aK==null){aK=aC.children().width()}if(aG==null){aG=aC.find("div.tickLabel").height()}aC.remove()}}}if(aK==null){aK=0}if(aG==null){aG=0}aD.labelWidth=aK;aD.labelHeight=aG}function au(aD){var aC=aD.labelWidth,aL=aD.labelHeight,aH=aD.options.position,aF=aD.options.tickLength,aG=O.grid.axisMargin,aJ=O.grid.labelMargin,aK=aD.direction=="x"?p:aw,aE;var aB=c.grep(aK,function(aN){return aN&&aN.options.position==aH&&aN.reserveSpace});if(c.inArray(aD,aB)==aB.length-1){aG=0}if(aF==null){aF="full"}var aI=c.grep(aK,function(aN){return aN&&aN.reserveSpace});var aM=c.inArray(aD,aI)==0;if(!aM&&aF=="full"){aF=5}if(!isNaN(+aF)){aJ+=+aF}if(aD.direction=="x"){aL+=aJ;if(aH=="bottom"){q.bottom+=aL+aG;aD.box={top:I-q.bottom,height:aL}}else{aD.box={top:q.top+aG,height:aL};q.top+=aL+aG}}else{aC+=aJ;if(aH=="left"){aD.box={left:q.left+aG,width:aC};q.left+=aC+aG}else{q.right+=aC+aG;aD.box={left:G-q.right,width:aC}}}aD.position=aH;aD.tickLength=aF;aD.box.padding=aJ;aD.innermost=aM}function U(aB){if(aB.direction=="x"){aB.box.left=q.left;aB.box.width=h}else{aB.box.top=q.top;aB.box.height=w}}function t(){var aC,aE=m();c.each(aE,function(aF,aG){aG.show=aG.options.show;if(aG.show==null){aG.show=aG.used}aG.reserveSpace=aG.show||aG.options.reserveSpace;n(aG)});allocatedAxes=c.grep(aE,function(aF){return aF.reserveSpace});q.left=q.right=q.top=q.bottom=0;if(O.grid.show){c.each(allocatedAxes,function(aF,aG){S(aG);P(aG);ap(aG,aG.ticks);L(aG)});for(aC=allocatedAxes.length-1;aC>=0;--aC){au(allocatedAxes[aC])}var aD=O.grid.minBorderMargin;if(aD==null){aD=0;for(aC=0;aC<Q.length;++aC){aD=Math.max(aD,Q[aC].points.radius+Q[aC].points.lineWidth/2)}}for(var aB in q){q[aB]+=O.grid.borderWidth;q[aB]=Math.max(aD,q[aB])}}h=G-q.left-q.right;w=I-q.bottom-q.top;c.each(aE,function(aF,aG){r(aG)});if(O.grid.show){c.each(allocatedAxes,function(aF,aG){U(aG)});k()}o()}function n(aE){var aF=aE.options,aD=+(aF.min!=null?aF.min:aE.datamin),aB=+(aF.max!=null?aF.max:aE.datamax),aH=aB-aD;if(aH==0){var aC=aB==0?1:0.01;if(aF.min==null){aD-=aC}if(aF.max==null||aF.min!=null){aB+=aC}}else{var aG=aF.autoscaleMargin;if(aG!=null){if(aF.min==null){aD-=aH*aG;if(aD<0&&aE.datamin!=null&&aE.datamin>=0){aD=0}}if(aF.max==null){aB+=aH*aG;if(aB>0&&aE.datamax!=null&&aE.datamax<=0){aB=0}}}}aE.min=aD;aE.max=aB}function S(aG){var aM=aG.options;var aH;if(typeof aM.ticks=="number"&&aM.ticks>0){aH=aM.ticks}else{aH=0.3*Math.sqrt(aG.direction=="x"?G:I)}var aT=(aG.max-aG.min)/aH,aO,aB,aN,aR,aS,aQ,aI;if(aM.mode=="time"){var aJ={second:1000,minute:60*1000,hour:60*60*1000,day:24*60*60*1000,month:30*24*60*60*1000,year:365.2425*24*60*60*1000};var aK=[[1,"second"],[2,"second"],[5,"second"],[10,"second"],[30,"second"],[1,"minute"],[2,"minute"],[5,"minute"],[10,"minute"],[30,"minute"],[1,"hour"],[2,"hour"],[4,"hour"],[8,"hour"],[12,"hour"],[1,"day"],[2,"day"],[3,"day"],[0.25,"month"],[0.5,"month"],[1,"month"],[2,"month"],[3,"month"],[6,"month"],[1,"year"]];var aC=0;if(aM.minTickSize!=null){if(typeof aM.tickSize=="number"){aC=aM.tickSize}else{aC=aM.minTickSize[0]*aJ[aM.minTickSize[1]]}}for(var aS=0;aS<aK.length-1;++aS){if(aT<(aK[aS][0]*aJ[aK[aS][1]]+aK[aS+1][0]*aJ[aK[aS+1][1]])/2&&aK[aS][0]*aJ[aK[aS][1]]>=aC){break}}aO=aK[aS][0];aN=aK[aS][1];if(aN=="year"){aQ=Math.pow(10,Math.floor(Math.log(aT/aJ.year)/Math.LN10));aI=(aT/aJ.year)/aQ;if(aI<1.5){aO=1}else{if(aI<3){aO=2}else{if(aI<7.5){aO=5}else{aO=10}}}aO*=aQ}aG.tickSize=aM.tickSize||[aO,aN];aB=function(aX){var a2=[],a0=aX.tickSize[0],a3=aX.tickSize[1],a1=new Date(aX.min);var aW=a0*aJ[a3];if(a3=="second"){a1.setUTCSeconds(a(a1.getUTCSeconds(),a0))}if(a3=="minute"){a1.setUTCMinutes(a(a1.getUTCMinutes(),a0))}if(a3=="hour"){a1.setUTCHours(a(a1.getUTCHours(),a0))}if(a3=="month"){a1.setUTCMonth(a(a1.getUTCMonth(),a0))}if(a3=="year"){a1.setUTCFullYear(a(a1.getUTCFullYear(),a0))}a1.setUTCMilliseconds(0);if(aW>=aJ.minute){a1.setUTCSeconds(0)}if(aW>=aJ.hour){a1.setUTCMinutes(0)}if(aW>=aJ.day){a1.setUTCHours(0)}if(aW>=aJ.day*4){a1.setUTCDate(1)}if(aW>=aJ.year){a1.setUTCMonth(0)}var a5=0,a4=Number.NaN,aY;do{aY=a4;a4=a1.getTime();a2.push(a4);if(a3=="month"){if(a0<1){a1.setUTCDate(1);var aV=a1.getTime();a1.setUTCMonth(a1.getUTCMonth()+1);var aZ=a1.getTime();a1.setTime(a4+a5*aJ.hour+(aZ-aV)*a0);a5=a1.getUTCHours();a1.setUTCHours(0)}else{a1.setUTCMonth(a1.getUTCMonth()+a0)}}else{if(a3=="year"){a1.setUTCFullYear(a1.getUTCFullYear()+a0)}else{a1.setTime(a4+aW)}}}while(a4<aX.max&&a4!=aY);return a2};aR=function(aV,aY){var a0=new Date(aV);if(aM.timeformat!=null){return c.plot.formatDate(a0,aM.timeformat,aM.monthNames)}var aW=aY.tickSize[0]*aJ[aY.tickSize[1]];var aX=aY.max-aY.min;var aZ=(aM.twelveHourClock)?" %p":"";if(aW<aJ.minute){fmt="%h:%M:%S"+aZ}else{if(aW<aJ.day){if(aX<2*aJ.day){fmt="%h:%M"+aZ}else{fmt="%b %d %h:%M"+aZ}}else{if(aW<aJ.month){fmt="%b %d"}else{if(aW<aJ.year){if(aX<aJ.year){fmt="%b"}else{fmt="%b %y"}}else{fmt="%y"}}}}return c.plot.formatDate(a0,fmt,aM.monthNames)}}else{var aU=aM.tickDecimals;var aP=-Math.floor(Math.log(aT)/Math.LN10);if(aU!=null&&aP>aU){aP=aU}aQ=Math.pow(10,-aP);aI=aT/aQ;if(aI<1.5){aO=1}else{if(aI<3){aO=2;if(aI>2.25&&(aU==null||aP+1<=aU)){aO=2.5;++aP}}else{if(aI<7.5){aO=5}else{aO=10}}}aO*=aQ;if(aM.minTickSize!=null&&aO<aM.minTickSize){aO=aM.minTickSize}aG.tickDecimals=Math.max(0,aU!=null?aU:aP);aG.tickSize=aM.tickSize||aO;aB=function(aX){var aZ=[];var a0=a(aX.min,aX.tickSize),aW=0,aV=Number.NaN,aY;do{aY=aV;aV=a0+aW*aX.tickSize;aZ.push(aV);++aW}while(aV<aX.max&&aV!=aY);return aZ};aR=function(aV,aW){return aV.toFixed(aW.tickDecimals)}}if(aM.alignTicksWithAxis!=null){var aF=(aG.direction=="x"?p:aw)[aM.alignTicksWithAxis-1];if(aF&&aF.used&&aF!=aG){var aL=aB(aG);if(aL.length>0){if(aM.min==null){aG.min=Math.min(aG.min,aL[0])}if(aM.max==null&&aL.length>1){aG.max=Math.max(aG.max,aL[aL.length-1])}}aB=function(aX){var aY=[],aV,aW;for(aW=0;aW<aF.ticks.length;++aW){aV=(aF.ticks[aW].v-aF.min)/(aF.max-aF.min);aV=aX.min+aV*(aX.max-aX.min);aY.push(aV)}return aY};if(aG.mode!="time"&&aM.tickDecimals==null){var aE=Math.max(0,-Math.floor(Math.log(aT)/Math.LN10)+1),aD=aB(aG);if(!(aD.length>1&&/\..*0$/.test((aD[1]-aD[0]).toFixed(aE)))){aG.tickDecimals=aE}}}}aG.tickGenerator=aB;if(c.isFunction(aM.tickFormatter)){aG.tickFormatter=function(aV,aW){return""+aM.tickFormatter(aV,aW)}}else{aG.tickFormatter=aR}}function P(aF){var aH=aF.options.ticks,aG=[];if(aH==null||(typeof aH=="number"&&aH>0)){aG=aF.tickGenerator(aF)}else{if(aH){if(c.isFunction(aH)){aG=aH({min:aF.min,max:aF.max})}else{aG=aH}}}var aE,aB;aF.ticks=[];for(aE=0;aE<aG.length;++aE){var aC=null;var aD=aG[aE];if(typeof aD=="object"){aB=+aD[0];if(aD.length>1){aC=aD[1]}}else{aB=+aD}if(aC==null){aC=aF.tickFormatter(aB,aF)}if(!isNaN(aB)){aF.ticks.push({v:aB,label:aC})}}}function ap(aB,aC){if(aB.options.autoscaleMargin&&aC.length>0){if(aB.options.min==null){aB.min=Math.min(aB.min,aC[0].v)}if(aB.options.max==null&&aC.length>1){aB.max=Math.max(aB.max,aC[aC.length-1].v)}}}function W(){H.clearRect(0,0,G,I);var aC=O.grid;if(aC.show&&aC.backgroundColor){N()}if(aC.show&&!aC.aboveData){ac()}for(var aB=0;aB<Q.length;++aB){an(ak.drawSeries,[H,Q[aB]]);d(Q[aB])}an(ak.draw,[H]);if(aC.show&&aC.aboveData){ac()}}function D(aB,aI){var aE,aH,aG,aD,aF=m();for(i=0;i<aF.length;++i){aE=aF[i];if(aE.direction==aI){aD=aI+aE.n+"axis";if(!aB[aD]&&aE.n==1){aD=aI+"axis"}if(aB[aD]){aH=aB[aD].from;aG=aB[aD].to;break}}}if(!aB[aD]){aE=aI=="x"?p[0]:aw[0];aH=aB[aI+"1"];aG=aB[aI+"2"]}if(aH!=null&&aG!=null&&aH>aG){var aC=aH;aH=aG;aG=aC}return{from:aH,to:aG,axis:aE}}function N(){H.save();H.translate(q.left,q.top);H.fillStyle=am(O.grid.backgroundColor,w,0,"rgba(255, 255, 255, 0)");H.fillRect(0,0,h,w);H.restore()}function ac(){var aF;H.save();H.translate(q.left,q.top);var aH=O.grid.markings;if(aH){if(c.isFunction(aH)){var aK=aq.getAxes();aK.xmin=aK.xaxis.min;aK.xmax=aK.xaxis.max;aK.ymin=aK.yaxis.min;aK.ymax=aK.yaxis.max;aH=aH(aK)}for(aF=0;aF<aH.length;++aF){var aD=aH[aF],aC=D(aD,"x"),aI=D(aD,"y");if(aC.from==null){aC.from=aC.axis.min}if(aC.to==null){aC.to=aC.axis.max}if(aI.from==null){aI.from=aI.axis.min}if(aI.to==null){aI.to=aI.axis.max}if(aC.to<aC.axis.min||aC.from>aC.axis.max||aI.to<aI.axis.min||aI.from>aI.axis.max){continue}aC.from=Math.max(aC.from,aC.axis.min);aC.to=Math.min(aC.to,aC.axis.max);aI.from=Math.max(aI.from,aI.axis.min);aI.to=Math.min(aI.to,aI.axis.max);if(aC.from==aC.to&&aI.from==aI.to){continue}aC.from=aC.axis.p2c(aC.from);aC.to=aC.axis.p2c(aC.to);aI.from=aI.axis.p2c(aI.from);aI.to=aI.axis.p2c(aI.to);if(aC.from==aC.to||aI.from==aI.to){H.beginPath();H.strokeStyle=aD.color||O.grid.markingsColor;H.lineWidth=aD.lineWidth||O.grid.markingsLineWidth;H.moveTo(aC.from,aI.from);H.lineTo(aC.to,aI.to);H.stroke()}else{H.fillStyle=aD.color||O.grid.markingsColor;H.fillRect(aC.from,aI.to,aC.to-aC.from,aI.from-aI.to)}}}var aK=m(),aM=O.grid.borderWidth;for(var aE=0;aE<aK.length;++aE){var aB=aK[aE],aG=aB.box,aQ=aB.tickLength,aN,aL,aP,aJ;if(!aB.show||aB.ticks.length==0){continue}H.strokeStyle=aB.options.tickColor||c.color.parse(aB.options.color).scale("a",0.22).toString();H.lineWidth=1;if(aB.direction=="x"){aN=0;if(aQ=="full"){aL=(aB.position=="top"?0:w)}else{aL=aG.top-q.top+(aB.position=="top"?aG.height:0)}}else{aL=0;if(aQ=="full"){aN=(aB.position=="left"?0:h)}else{aN=aG.left-q.left+(aB.position=="left"?aG.width:0)}}if(!aB.innermost){H.beginPath();aP=aJ=0;if(aB.direction=="x"){aP=h}else{aJ=w}if(H.lineWidth==1){aN=Math.floor(aN)+0.5;aL=Math.floor(aL)+0.5}H.moveTo(aN,aL);H.lineTo(aN+aP,aL+aJ);H.stroke()}H.beginPath();for(aF=0;aF<aB.ticks.length;++aF){var aO=aB.ticks[aF].v;aP=aJ=0;if(aO<aB.min||aO>aB.max||(aQ=="full"&&aM>0&&(aO==aB.min||aO==aB.max))){continue}if(aB.direction=="x"){aN=aB.p2c(aO);aJ=aQ=="full"?-w:aQ;if(aB.position=="top"){aJ=-aJ}}else{aL=aB.p2c(aO);aP=aQ=="full"?-h:aQ;if(aB.position=="left"){aP=-aP}}if(H.lineWidth==1){if(aB.direction=="x"){aN=Math.floor(aN)+0.5}else{aL=Math.floor(aL)+0.5}}H.moveTo(aN,aL);H.lineTo(aN+aP,aL+aJ)}H.stroke()}if(aM){H.lineWidth=aM;H.strokeStyle=O.grid.borderColor;H.strokeRect(-aM/2,-aM/2,h+aM,w+aM)}H.restore()}function k(){av.find(".tickLabels").remove();var aG=['<div class="tickLabels" style="font-size:smaller">'];var aJ=m();for(var aD=0;aD<aJ.length;++aD){var aC=aJ[aD],aF=aC.box;if(!aC.show){continue}aG.push('<div class="'+aC.direction+"Axis "+aC.direction+aC.n+'Axis" style="color:'+aC.options.color+'">');for(var aE=0;aE<aC.ticks.length;++aE){var aH=aC.ticks[aE];if(!aH.label||aH.v<aC.min||aH.v>aC.max){continue}var aK={},aI;if(aC.direction=="x"){aI="center";aK.left=Math.round(q.left+aC.p2c(aH.v)-aC.labelWidth/2);if(aC.position=="bottom"){aK.top=aF.top+aF.padding}else{aK.bottom=I-(aF.top+aF.height-aF.padding)}}else{aK.top=Math.round(q.top+aC.p2c(aH.v)-aC.labelHeight/2);if(aC.position=="left"){aK.right=G-(aF.left+aF.width-aF.padding);aI="right"}else{aK.left=aF.left+aF.padding;aI="left"}}aK.width=aC.labelWidth;var aB=["position:absolute","text-align:"+aI];for(var aL in aK){aB.push(aL+":"+aK[aL]+"px")}aG.push('<div class="tickLabel" style="'+aB.join(";")+'">'+aH.label+"</div>")}aG.push("</div>")}aG.push("</div>");av.append(aG.join(""))}function d(aB){if(aB.lines.show){at(aB)}if(aB.bars.show){e(aB)}if(aB.points.show){ao(aB)}}function at(aE){function aD(aP,aQ,aI,aU,aT){var aV=aP.points,aJ=aP.pointsize,aN=null,aM=null;H.beginPath();for(var aO=aJ;aO<aV.length;aO+=aJ){var aL=aV[aO-aJ],aS=aV[aO-aJ+1],aK=aV[aO],aR=aV[aO+1];if(aL==null||aK==null){continue}if(aS<=aR&&aS<aT.min){if(aR<aT.min){continue}aL=(aT.min-aS)/(aR-aS)*(aK-aL)+aL;aS=aT.min}else{if(aR<=aS&&aR<aT.min){if(aS<aT.min){continue}aK=(aT.min-aS)/(aR-aS)*(aK-aL)+aL;aR=aT.min}}if(aS>=aR&&aS>aT.max){if(aR>aT.max){continue}aL=(aT.max-aS)/(aR-aS)*(aK-aL)+aL;aS=aT.max}else{if(aR>=aS&&aR>aT.max){if(aS>aT.max){continue}aK=(aT.max-aS)/(aR-aS)*(aK-aL)+aL;aR=aT.max}}if(aL<=aK&&aL<aU.min){if(aK<aU.min){continue}aS=(aU.min-aL)/(aK-aL)*(aR-aS)+aS;aL=aU.min}else{if(aK<=aL&&aK<aU.min){if(aL<aU.min){continue}aR=(aU.min-aL)/(aK-aL)*(aR-aS)+aS;aK=aU.min}}if(aL>=aK&&aL>aU.max){if(aK>aU.max){continue}aS=(aU.max-aL)/(aK-aL)*(aR-aS)+aS;aL=aU.max}else{if(aK>=aL&&aK>aU.max){if(aL>aU.max){continue}aR=(aU.max-aL)/(aK-aL)*(aR-aS)+aS;aK=aU.max}}if(aL!=aN||aS!=aM){H.moveTo(aU.p2c(aL)+aQ,aT.p2c(aS)+aI)}aN=aK;aM=aR;H.lineTo(aU.p2c(aK)+aQ,aT.p2c(aR)+aI)}H.stroke()}function aF(aI,aQ,aP){var aW=aI.points,aV=aI.pointsize,aN=Math.min(Math.max(0,aP.min),aP.max),aX=0,aU,aT=false,aM=1,aL=0,aR=0;while(true){if(aV>0&&aX>aW.length+aV){break}aX+=aV;var aZ=aW[aX-aV],aK=aW[aX-aV+aM],aY=aW[aX],aJ=aW[aX+aM];if(aT){if(aV>0&&aZ!=null&&aY==null){aR=aX;aV=-aV;aM=2;continue}if(aV<0&&aX==aL+aV){H.fill();aT=false;aV=-aV;aM=1;aX=aL=aR+aV;continue}}if(aZ==null||aY==null){continue}if(aZ<=aY&&aZ<aQ.min){if(aY<aQ.min){continue}aK=(aQ.min-aZ)/(aY-aZ)*(aJ-aK)+aK;aZ=aQ.min}else{if(aY<=aZ&&aY<aQ.min){if(aZ<aQ.min){continue}aJ=(aQ.min-aZ)/(aY-aZ)*(aJ-aK)+aK;aY=aQ.min}}if(aZ>=aY&&aZ>aQ.max){if(aY>aQ.max){continue}aK=(aQ.max-aZ)/(aY-aZ)*(aJ-aK)+aK;aZ=aQ.max}else{if(aY>=aZ&&aY>aQ.max){if(aZ>aQ.max){continue}aJ=(aQ.max-aZ)/(aY-aZ)*(aJ-aK)+aK;aY=aQ.max}}if(!aT){H.beginPath();H.moveTo(aQ.p2c(aZ),aP.p2c(aN));aT=true}if(aK>=aP.max&&aJ>=aP.max){H.lineTo(aQ.p2c(aZ),aP.p2c(aP.max));H.lineTo(aQ.p2c(aY),aP.p2c(aP.max));continue}else{if(aK<=aP.min&&aJ<=aP.min){H.lineTo(aQ.p2c(aZ),aP.p2c(aP.min));H.lineTo(aQ.p2c(aY),aP.p2c(aP.min));continue}}var aO=aZ,aS=aY;if(aK<=aJ&&aK<aP.min&&aJ>=aP.min){aZ=(aP.min-aK)/(aJ-aK)*(aY-aZ)+aZ;aK=aP.min}else{if(aJ<=aK&&aJ<aP.min&&aK>=aP.min){aY=(aP.min-aK)/(aJ-aK)*(aY-aZ)+aZ;aJ=aP.min}}if(aK>=aJ&&aK>aP.max&&aJ<=aP.max){aZ=(aP.max-aK)/(aJ-aK)*(aY-aZ)+aZ;aK=aP.max}else{if(aJ>=aK&&aJ>aP.max&&aK<=aP.max){aY=(aP.max-aK)/(aJ-aK)*(aY-aZ)+aZ;aJ=aP.max}}if(aZ!=aO){H.lineTo(aQ.p2c(aO),aP.p2c(aK))}H.lineTo(aQ.p2c(aZ),aP.p2c(aK));H.lineTo(aQ.p2c(aY),aP.p2c(aJ));if(aY!=aS){H.lineTo(aQ.p2c(aY),aP.p2c(aJ));H.lineTo(aQ.p2c(aS),aP.p2c(aJ))}}}H.save();H.translate(q.left,q.top);H.lineJoin="round";var aG=aE.lines.lineWidth,aB=aE.shadowSize;if(aG>0&&aB>0){H.lineWidth=aB;H.strokeStyle="rgba(0,0,0,0.1)";var aH=Math.PI/18;aD(aE.datapoints,Math.sin(aH)*(aG/2+aB/2),Math.cos(aH)*(aG/2+aB/2),aE.xaxis,aE.yaxis);H.lineWidth=aB/2;aD(aE.datapoints,Math.sin(aH)*(aG/2+aB/4),Math.cos(aH)*(aG/2+aB/4),aE.xaxis,aE.yaxis)}H.lineWidth=aG;H.strokeStyle=aE.color;var aC=ae(aE.lines,aE.color,0,w);if(aC){H.fillStyle=aC;aF(aE.datapoints,aE.xaxis,aE.yaxis)}if(aG>0){aD(aE.datapoints,0,0,aE.xaxis,aE.yaxis)}H.restore()}function ao(aE){function aH(aN,aM,aU,aK,aS,aT,aQ,aJ){var aR=aN.points,aI=aN.pointsize;for(var aL=0;aL<aR.length;aL+=aI){var aP=aR[aL],aO=aR[aL+1];if(aP==null||aP<aT.min||aP>aT.max||aO<aQ.min||aO>aQ.max){continue}H.beginPath();aP=aT.p2c(aP);aO=aQ.p2c(aO)+aK;if(aJ=="circle"){H.arc(aP,aO,aM,0,aS?Math.PI:Math.PI*2,false)}else{aJ(H,aP,aO,aM,aS)}H.closePath();if(aU){H.fillStyle=aU;H.fill()}H.stroke()}}H.save();H.translate(q.left,q.top);var aG=aE.points.lineWidth,aC=aE.shadowSize,aB=aE.points.radius,aF=aE.points.symbol;if(aG>0&&aC>0){var aD=aC/2;H.lineWidth=aD;H.strokeStyle="rgba(0,0,0,0.1)";aH(aE.datapoints,aB,null,aD+aD/2,true,aE.xaxis,aE.yaxis,aF);H.strokeStyle="rgba(0,0,0,0.2)";aH(aE.datapoints,aB,null,aD/2,true,aE.xaxis,aE.yaxis,aF)}H.lineWidth=aG;H.strokeStyle=aE.color;aH(aE.datapoints,aB,ae(aE.points,aE.color),0,false,aE.xaxis,aE.yaxis,aF);H.restore()}function E(aN,aM,aV,aI,aQ,aF,aD,aL,aK,aU,aR,aC){var aE,aT,aJ,aP,aG,aB,aO,aH,aS;if(aR){aH=aB=aO=true;aG=false;aE=aV;aT=aN;aP=aM+aI;aJ=aM+aQ;if(aT<aE){aS=aT;aT=aE;aE=aS;aG=true;aB=false}}else{aG=aB=aO=true;aH=false;aE=aN+aI;aT=aN+aQ;aJ=aV;aP=aM;if(aP<aJ){aS=aP;aP=aJ;aJ=aS;aH=true;aO=false}}if(aT<aL.min||aE>aL.max||aP<aK.min||aJ>aK.max){return}if(aE<aL.min){aE=aL.min;aG=false}if(aT>aL.max){aT=aL.max;aB=false}if(aJ<aK.min){aJ=aK.min;aH=false}if(aP>aK.max){aP=aK.max;aO=false}aE=aL.p2c(aE);aJ=aK.p2c(aJ);aT=aL.p2c(aT);aP=aK.p2c(aP);if(aD){aU.beginPath();aU.moveTo(aE,aJ);aU.lineTo(aE,aP);aU.lineTo(aT,aP);aU.lineTo(aT,aJ);aU.fillStyle=aD(aJ,aP);aU.fill()}if(aC>0&&(aG||aB||aO||aH)){aU.beginPath();aU.moveTo(aE,aJ+aF);if(aG){aU.lineTo(aE,aP+aF)}else{aU.moveTo(aE,aP+aF)}if(aO){aU.lineTo(aT,aP+aF)}else{aU.moveTo(aT,aP+aF)}if(aB){aU.lineTo(aT,aJ+aF)}else{aU.moveTo(aT,aJ+aF)}if(aH){aU.lineTo(aE,aJ+aF)}else{aU.moveTo(aE,aJ+aF)}aU.stroke()}}function e(aD){function aC(aJ,aI,aL,aG,aK,aN,aM){var aO=aJ.points,aF=aJ.pointsize;for(var aH=0;aH<aO.length;aH+=aF){if(aO[aH]==null){continue}E(aO[aH],aO[aH+1],aO[aH+2],aI,aL,aG,aK,aN,aM,H,aD.bars.horizontal,aD.bars.lineWidth)}}H.save();H.translate(q.left,q.top);H.lineWidth=aD.bars.lineWidth;H.strokeStyle=aD.color;var aB=aD.bars.align=="left"?0:-aD.bars.barWidth/2;var aE=aD.bars.fill?function(aF,aG){return ae(aD.bars,aD.color,aF,aG)}:null;aC(aD.datapoints,aB,aB+aD.bars.barWidth,0,aE,aD.xaxis,aD.yaxis);H.restore()}function ae(aD,aB,aC,aF){var aE=aD.fill;if(!aE){return null}if(aD.fillColor){return am(aD.fillColor,aC,aF,aB)}var aG=c.color.parse(aB);aG.a=typeof aE=="number"?aE:0.4;aG.normalize();return aG.toString()}function o(){av.find(".legend").remove();if(!O.legend.show){return}var aH=[],aF=false,aN=O.legend.labelFormatter,aM,aJ;for(var aE=0;aE<Q.length;++aE){aM=Q[aE];aJ=aM.label;if(!aJ){continue}if(aE%O.legend.noColumns==0){if(aF){aH.push("</tr>")}aH.push("<tr>");aF=true}if(aN){aJ=aN(aJ,aM)}aH.push('<td class="legendColorBox"><div style="border:1px solid '+O.legend.labelBoxBorderColor+';padding:1px"><div style="width:4px;height:0;border:5px solid '+aM.color+';overflow:hidden"></div></div></td><td class="legendLabel">'+aJ+"</td>")}if(aF){aH.push("</tr>")}if(aH.length==0){return}var aL='<table style="font-size:smaller;color:'+O.grid.color+'">'+aH.join("")+"</table>";if(O.legend.container!=null){c(O.legend.container).html(aL)}else{var aI="",aC=O.legend.position,aD=O.legend.margin;if(aD[0]==null){aD=[aD,aD]}if(aC.charAt(0)=="n"){aI+="top:"+(aD[1]+q.top)+"px;"}else{if(aC.charAt(0)=="s"){aI+="bottom:"+(aD[1]+q.bottom)+"px;"}}if(aC.charAt(1)=="e"){aI+="right:"+(aD[0]+q.right)+"px;"}else{if(aC.charAt(1)=="w"){aI+="left:"+(aD[0]+q.left)+"px;"}}var aK=c('<div class="legend">'+aL.replace('style="','style="position:absolute;'+aI+";")+"</div>").appendTo(av);if(O.legend.backgroundOpacity!=0){var aG=O.legend.backgroundColor;if(aG==null){aG=O.grid.backgroundColor;if(aG&&typeof aG=="string"){aG=c.color.parse(aG)}else{aG=c.color.extract(aK,"background-color")}aG.a=1;aG=aG.toString()}var aB=aK.children();c('<div style="position:absolute;width:'+aB.width()+"px;height:"+aB.height()+"px;"+aI+"background-color:"+aG+';"> </div>').prependTo(aK).css("opacity",O.legend.backgroundOpacity)}}}var ab=[],M=null;function K(aI,aG,aD){var aO=O.grid.mouseActiveRadius,a0=aO*aO+1,aY=null,aR=false,aW,aU;for(aW=Q.length-1;aW>=0;--aW){if(!aD(Q[aW])){continue}var aP=Q[aW],aH=aP.xaxis,aF=aP.yaxis,aV=aP.datapoints.points,aT=aP.datapoints.pointsize,aQ=aH.c2p(aI),aN=aF.c2p(aG),aC=aO/aH.scale,aB=aO/aF.scale;if(aH.options.inverseTransform){aC=Number.MAX_VALUE}if(aF.options.inverseTransform){aB=Number.MAX_VALUE}if(aP.lines.show||aP.points.show){for(aU=0;aU<aV.length;aU+=aT){var aK=aV[aU],aJ=aV[aU+1];if(aK==null){continue}if(aK-aQ>aC||aK-aQ<-aC||aJ-aN>aB||aJ-aN<-aB){continue}var aM=Math.abs(aH.p2c(aK)-aI),aL=Math.abs(aF.p2c(aJ)-aG),aS=aM*aM+aL*aL;if(aS<a0){a0=aS;aY=[aW,aU/aT]}}}if(aP.bars.show&&!aY){var aE=aP.bars.align=="left"?0:-aP.bars.barWidth/2,aX=aE+aP.bars.barWidth;for(aU=0;aU<aV.length;aU+=aT){var aK=aV[aU],aJ=aV[aU+1],aZ=aV[aU+2];if(aK==null){continue}if(Q[aW].bars.horizontal?(aQ<=Math.max(aZ,aK)&&aQ>=Math.min(aZ,aK)&&aN>=aJ+aE&&aN<=aJ+aX):(aQ>=aK+aE&&aQ<=aK+aX&&aN>=Math.min(aZ,aJ)&&aN<=Math.max(aZ,aJ))){aY=[aW,aU/aT]}}}}if(aY){aW=aY[0];aU=aY[1];aT=Q[aW].datapoints.pointsize;return{datapoint:Q[aW].datapoints.points.slice(aU*aT,(aU+1)*aT),dataIndex:aU,series:Q[aW],seriesIndex:aW}}return null}function aa(aB){if(O.grid.hoverable){u("plothover",aB,function(aC){return aC.hoverable!=false})}}function l(aB){if(O.grid.hoverable){u("plothover",aB,function(aC){return false})}}function R(aB){u("plotclick",aB,function(aC){return aC.clickable!=false})}function u(aC,aB,aD){var aE=y.offset(),aH=aB.pageX-aE.left-q.left,aF=aB.pageY-aE.top-q.top,aJ=C({left:aH,top:aF});aJ.pageX=aB.pageX;aJ.pageY=aB.pageY;var aK=K(aH,aF,aD);if(aK){aK.pageX=parseInt(aK.series.xaxis.p2c(aK.datapoint[0])+aE.left+q.left);aK.pageY=parseInt(aK.series.yaxis.p2c(aK.datapoint[1])+aE.top+q.top)}if(O.grid.autoHighlight){for(var aG=0;aG<ab.length;++aG){var aI=ab[aG];if(aI.auto==aC&&!(aK&&aI.series==aK.series&&aI.point[0]==aK.datapoint[0]&&aI.point[1]==aK.datapoint[1])){T(aI.series,aI.point)}}if(aK){x(aK.series,aK.datapoint,aC)}}av.trigger(aC,[aJ,aK])}function f(){if(!M){M=setTimeout(s,30)}}function s(){M=null;A.save();A.clearRect(0,0,G,I);A.translate(q.left,q.top);var aC,aB;for(aC=0;aC<ab.length;++aC){aB=ab[aC];if(aB.series.bars.show){v(aB.series,aB.point)}else{ay(aB.series,aB.point)}}A.restore();an(ak.drawOverlay,[A])}function x(aD,aB,aF){if(typeof aD=="number"){aD=Q[aD]}if(typeof aB=="number"){var aE=aD.datapoints.pointsize;aB=aD.datapoints.points.slice(aE*aB,aE*(aB+1))}var aC=al(aD,aB);if(aC==-1){ab.push({series:aD,point:aB,auto:aF});f()}else{if(!aF){ab[aC].auto=false}}}function T(aD,aB){if(aD==null&&aB==null){ab=[];f()}if(typeof aD=="number"){aD=Q[aD]}if(typeof aB=="number"){aB=aD.data[aB]}var aC=al(aD,aB);if(aC!=-1){ab.splice(aC,1);f()}}function al(aD,aE){for(var aB=0;aB<ab.length;++aB){var aC=ab[aB];if(aC.series==aD&&aC.point[0]==aE[0]&&aC.point[1]==aE[1]){return aB}}return -1}function ay(aE,aD){var aC=aD[0],aI=aD[1],aH=aE.xaxis,aG=aE.yaxis;if(aC<aH.min||aC>aH.max||aI<aG.min||aI>aG.max){return}var aF=aE.points.radius+aE.points.lineWidth/2;A.lineWidth=aF;A.strokeStyle=c.color.parse(aE.color).scale("a",0.5).toString();var aB=1.5*aF,aC=aH.p2c(aC),aI=aG.p2c(aI);A.beginPath();if(aE.points.symbol=="circle"){A.arc(aC,aI,aB,0,2*Math.PI,false)}else{aE.points.symbol(A,aC,aI,aB,false)}A.closePath();A.stroke()}function v(aE,aB){A.lineWidth=aE.bars.lineWidth;A.strokeStyle=c.color.parse(aE.color).scale("a",0.5).toString();var aD=c.color.parse(aE.color).scale("a",0.5).toString();var aC=aE.bars.align=="left"?0:-aE.bars.barWidth/2;E(aB[0],aB[1],aB[2]||0,aC,aC+aE.bars.barWidth,0,function(){return aD},aE.xaxis,aE.yaxis,A,aE.bars.horizontal,aE.bars.lineWidth)}function am(aJ,aB,aH,aC){if(typeof aJ=="string"){return aJ}else{var aI=H.createLinearGradient(0,aH,0,aB);for(var aE=0,aD=aJ.colors.length;aE<aD;++aE){var aF=aJ.colors[aE];if(typeof aF!="string"){var aG=c.color.parse(aC);if(aF.brightness!=null){aG=aG.scale("rgb",aF.brightness)}if(aF.opacity!=null){aG.a*=aF.opacity}aF=aG.toString()}aI.addColorStop(aE/(aD-1),aF)}return aI}}}c.plot=function(g,e,d){var f=new b(c(g),e,d,c.plot.plugins);return f};c.plot.version="0.7";c.plot.plugins=[];c.plot.formatDate=function(l,f,h){var o=function(d){d=""+d;return d.length==1?"0"+d:d};var e=[];var p=false,j=false;var n=l.getUTCHours();var k=n<12;if(h==null){h=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]}if(f.search(/%p|%P/)!=-1){if(n>12){n=n-12}else{if(n==0){n=12}}}for(var g=0;g<f.length;++g){var m=f.charAt(g);if(p){switch(m){case"h":m=""+n;break;case"H":m=o(n);break;case"M":m=o(l.getUTCMinutes());break;case"S":m=o(l.getUTCSeconds());break;case"d":m=""+l.getUTCDate();break;case"m":m=""+(l.getUTCMonth()+1);break;case"y":m=""+l.getUTCFullYear();break;case"b":m=""+h[l.getUTCMonth()];break;case"p":m=(k)?("am"):("pm");break;case"P":m=(k)?("AM"):("PM");break;case"0":m="";j=true;break}if(m&&j){m=o(m);j=false}e.push(m);if(!j){p=false}}else{if(m=="%"){p=true}else{e.push(m)}}}return e.join("")};function a(e,d){return d*Math.floor(e/d)}})(jQuery);
\ No newline at end of file
index c421da7cf74f791e51adf53f1fcc61975827e3eb..fa33ee4c2cd2fdbc3e47202e08ddc00b70987898 100755 (executable)
@@ -20,7 +20,8 @@
 
     <%block name="css">
     <link href="../assets/css/bootstrap-responsive.css" rel="stylesheet">
-    <link href="/static/css/style.min.css" rel="stylesheet">
+    <!-- <link href="/static/css/style.min.css" rel="stylesheet"> -->
+    <link href="/static/css/style.css" rel="stylesheet">
     </%block>
   </head>
 
       </div> <!-- /main row -->
 
       <%block name="footer">
-      <p class="pagination-centered">XonStat is an open source (GPLv2) project created by Antibody. Fork it <a href="https://github.com/antzucaro/XonStat" title="Go to the project page">on Github!</a></p>
+      <p class="pagination-centered">XonStat is an open source (GPLv2) project created by Antibody. Fork it <a href="https://github.com/antzucaro/XonStat" title="Go to the project page">on Github!</a> <br />Questions? Check the <a href="https://github.com/antzucaro/XonStat/wiki/FAQ" title="FAQ">FAQ</a> first. <br />Issues? Log them either <a href="http://dev.xonotic.org/projects/xonstat" title="Xonotic Redmin Issue Tracker">here</a> or <a href="https://github.com/antzucaro/XonStat/issues" title="GitHub issue tracker">here</a> - I check both!</p>
       </%block>
 
       <%block name="js">
       </%block>
+
+      <script type="text/javascript">
+      var _gaq = _gaq || [];
+      _gaq.push(['_setAccount', 'UA-30391685-1']);
+      _gaq.push(['_trackPageview']);
+
+      (function() {
+        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+      })();
+    </script>
+
     </body>
 </html>
index 4d16a920cf75fba6223e70004c5db0903a5aaba5..67446bc079a9705d6179aa92bc754d0b7e6e905c 100755 (executable)
@@ -11,7 +11,10 @@ ${nav.nav('games')}
       <script src="/static/js/jquery-1.7.1.min.js"></script>
       <script src="/static/js/bootstrap-collapse.min.js"></script>
       <script>
-        $(".collapse").collapse()
+        $(".collapse").collapse();
+
+        // show accordion only when loaded to prevent rollup from being seen
+        $("#acc-accordion").css('display', '');
       </script>
 </%block>
 
@@ -47,7 +50,7 @@ Game Information
 <div class="row">
   <div class="span12">
     <h3>Accuracy Information</h3>
-    <div class="accordion" id="acc-accordion">
+    <div class="accordion" id="acc-accordion" style="display:none;">
     % for pgstat in pgstats:
     % if pgstat.player_game_stat_id in pwstats:
       <div class="accordion-group">
index 288fd2e23434d1e62eac364f888edabef584325a..f4db7d0a71f2dd4c9f6d8493d03d1d6fbb4e0c5f 100755 (executable)
@@ -7,7 +7,12 @@
             <span class="i-bar"></span>
             <span class="i-bar"></span>
           </a>
-          <a class="brand" href="${request.route_url('main_index')}"><img src="/static/css/img/Xonotic_icon.png" /></a>
+          <header>
+            <a class="brand" href="${request.route_url('main_index')}">
+             <img src="/static/css/img/Xonotic_icon.png" /><h1>Xonotic Game Statistics</h1>
+            </a>
+            <h2>Xonotic is a fast-paced open-source GPL first person shooter</h2>
+          </header>
           <div class="nav-collapse">
             <ul class="nav">
               <li 
index 1482ae96e8b314b0847146d75be34ea104bc2697..1e0188698683da98d93447d3c02b5f3203e375a4 100755 (executable)
@@ -6,6 +6,94 @@
 ${nav.nav('players')}
 </%block>
 
+<%block name="js">
+    % if player is not None:
+      <script src="/static/js/jquery-1.7.1.min.js"></script>
+      <script src="/static/js/jquery.flot.min.js"></script>
+      <script type="text/javascript">
+      $(function () {
+
+          function plot_acc_graph(data) {
+              var games = new Array();
+              var avgs = new Array();
+              var accs = new Array();
+
+              var i=0;
+              for(i=0; i < data.games; i++) {
+                  avgs[i] = [i, data.avg];
+                  accs[i] = [i, data.accs[i][1]];
+                  game_link = '/game/' + data.accs[i][0];
+                  j = 20 - i;
+                  games[i] = [i, '<a href="' + game_link + '">' + j + '</a>'];
+              }
+
+              $.plot(
+                  $("#acc-graph"), 
+                  [ { label: 'average', data: avgs, hoverable: false, clickable: false }, 
+                    { label: 'accuracy', data: accs, lines: {show:true}, points: {show:true}, hoverable: true, clickable: true }, ],
+                  { yaxis: {ticks: 10, min: 0, max: 100 },
+                    xaxis: {ticks: games},
+                    grid: { hoverable: true, clickable: true },
+              });
+          }
+
+          function showTooltip(x, y, contents) {
+            $('<div id="tooltip">' + contents + '</div>').css( {
+                position: 'absolute',
+                display: 'none',
+                top: y - 35,
+                left: x + 10,
+                border: '1px solid #fdd',
+                padding: '2px',
+                'background-color': '#333333',
+                opacity: 0.80
+            }).appendTo("body").fadeIn(200);
+          }
+
+          var previousPoint = null;
+          $('#acc-graph').bind("plothover", function (event, pos, item) {
+              if (item) {
+                  if (previousPoint != item.dataIndex) {
+                    previousPoint = item.dataIndex;
+                    
+                    $("#tooltip").remove();
+                    var x = item.datapoint[0].toFixed(2),
+                        y = item.datapoint[1].toFixed(2);
+                    
+                    showTooltip(item.pageX, item.pageY, y + "%");
+                  }
+              }
+              else {
+                  $("#tooltip").remove();
+                  previousPoint = null;
+              }
+          });
+
+          $.ajax({
+              url: '${request.route_url("player_accuracy", id=player.player_id)}',
+              method: 'GET',
+              dataType: 'json',
+              success: plot_acc_graph
+          });
+
+          $(".acc-weap").click(function () {
+              var dataurl = $(this).find('a').attr('href');
+
+              $('.weapon-active').removeClass('weapon-active');
+              $(this).addClass('weapon-active');
+
+              $.ajax({
+                  url: dataurl,
+                  method: 'GET',
+                  dataType: 'json',
+                  success: plot_acc_graph
+              });
+          });
+      })
+      </script>
+    % endif
+</%block>
+
 <%block name="title">
 Player Information
 </%block>
@@ -22,10 +110,15 @@ Player Information
     <p>
        Member Since: <small>${player.create_dt.strftime('%m/%d/%Y at %I:%M %p')} </small><br />
        Last Seen: <small>${recent_games[0][1].fuzzy_date()} </small><br />
-       Playing Time: <small>${game_stats['total_alivetime']} </small><br />
+       Playing Time: <small>${total_stats['alivetime']} </small><br />
+       % if total_games > 0 and total_stats['wins'] is not None:
+       Win Percentage: <small>${round(float(total_stats['wins'])/total_games * 100, 2)}% (${total_stats['wins']} wins, ${total_games - total_stats['wins']} losses) </small><br />
+       % endif
+       % if total_stats['kills'] > 0 and total_stats['deaths'] > 0:
+       Kill Ratio: <small>${round(float(total_stats['kills'])/total_stats['deaths'], 3)} (${total_stats['kills']} kills, ${total_stats['deaths']} deaths) </small><br />
+       % endif
        <% games_breakdown_str = ', '.join(["{0} {1}".format(ng, gt) for (gt, ng) in games_breakdown]) %>
        Games Played: <small>${total_games} (${games_breakdown_str})</small><br />
-       Average Rank: <small>${game_stats['avg_rank']} </small><br />
        % if elos_display is not None and len(elos_display) > 0:
        Elo:
           <small>${', '.join(elos_display)} </small>
@@ -37,61 +130,69 @@ Player Information
     </p>
   </div>
 </div>
-% endif
 
 
-% if game_stats:
+% if accs is not None:
 <div class="row">
-  <div class="span12">
-    <h3>Overall Game Stats</h2>
-    <table class="table table-bordered table-condensed">
-      <thead>
-        <tr>
-          <th>Score</th>
-          <th>Carrier Kills</th>
-          <th>Kills</th>
-          <th>Collects</th>
-          <th>Deaths</th>
-          <th>Destroys</th>
-          <th>Suicides</th>
-          <th>Destroys (with key)</th>
-          <th>Captures</th>
-          <th>Pushes</th>
-          <th>Pickups</th>
-          <th>Pushed</th>
-          <th>Drops</th>
-          <th>Returns</th>
-        </tr>
-      </thead>
-      <tbody>
-        <tr>
-          <td>${game_stats['total_score']}</td>
-          <td>${game_stats['total_carrier_frags']}</td>
-          <td>${game_stats['total_kills']}</td>
-          <td>${game_stats['total_collects']}</td>
-          <td>${game_stats['total_deaths']}</td>
-          <td>${game_stats['total_destroys']}</td>
-          <td>${game_stats['total_suicides']}</td>
-          <td>${game_stats['total_destroys']}</td>
-          <td>${game_stats['total_captures']}</td>
-          <td>${game_stats['total_pushes']}</td>
-          <td>${game_stats['total_pickups']}</td>
-          <td>${game_stats['total_pushed']}</td>
-          <td>${game_stats['total_drops']}</td>
-          <td>${game_stats['total_returns']}</td>
-        </tr>
-      </tbody>
-    </table>
-    % endif
-  </div>
-</div>
+  <div class="span10">
+    <h3>Accuracy</h3>
+    <div id="acc-graph" class="flot" style="width:800px; height:200px;">
+    </div>
 
+    <div class="weapon-nav">
+      <ul>
+        % if 'nex' in recent_weapons:
+        <li>
+          <div class="acc-weap weapon-active">
+            <img src="${request.static_url("xonstat:static/images/nex.png")}" />
+            <p><small>Nex</small></p>
+            <a href="${request.route_url('player_accuracy', id=player.player_id, _query={'weapon':'nex'})}" title="Show nex accuracy"></a>
+          </div>
+        </li>
+        % endif
+
+        % if 'rifle' in recent_weapons:
+        <li>
+          <div class="acc-weap">
+            <img src="${request.static_url("xonstat:static/images/rifle.png")}" />
+            <p><small>Rifle</small></p>
+            <a href="${request.route_url('player_accuracy', id=player.player_id, _query={'weapon':'rifle'})}" title="Show rifle accuracy"></a>
+          </div>
+        </li>
+        % endif
+
+        % if 'minstanex' in recent_weapons:
+        <li>
+          <div class="acc-weap">
+            <img src="${request.static_url("xonstat:static/images/minstanex.png")}" />
+            <p><small>Minstanex</small></p>
+            <a href="${request.route_url('player_accuracy', id=player.player_id, _query={'weapon':'minstanex'})}" title="Show minstanex accuracy"></a>
+          </div>
+        </li>
+        % endif
+
+        % if 'uzi' in recent_weapons:
+        <li>
+          <div class="acc-weap">
+            <img src="${request.static_url("xonstat:static/images/uzi.png")}" />
+            <p><small>Uzi</small></p>
+            <a href="${request.route_url('player_accuracy', id=player.player_id, _query={'weapon':'uzi'})}" title="Show uzi accuracy"></a>
+          </div>
+        </li>
+        % endif
+
+        % if 'shotgun' in recent_weapons:
+        <li>
+          <div class="acc-weap">
+            <img src="${request.static_url("xonstat:static/images/shotgun.png")}" />
+            <p><small>Shotgun</small></p>
+            <a href="${request.route_url('player_accuracy', id=player.player_id, _query={'weapon':'shotgun'})}" title="Show shotgun accuracy"></a>
+          </div>
+        </li>
+        % endif
+      </ul>
+    </div>
 
-% if weapon_stats:
-<div class="row">
-  <div class="span12">
-    <h3>Overall Accuracy</h3>
-    ${accuracy(weapon_stats)}
   </div>
 </div>
 % endif
@@ -100,41 +201,50 @@ Player Information
 ##### RECENT GAMES (v2) ####
 % if recent_games:
 <div class="row">
-  <div class="span6">
+  <div class="span12">
     <h3>Recent Games</h3>
     <table class="table table-bordered table-condensed">
       <thead>
         <tr>
-           <th>Game Type</th>
+           <th></th>
+           <th>Type</th>
+           <th>Server</th>
            <th>Map</th>
            <th>Result</th>
            <th>Played</th>
-           <th>Permalink</th>
         </tr>
       </thead>
       <tbody>
       % for (gamestat, game, server, map) in recent_games:
         <tr>
-           <td><img title="${game.game_type_cd}" src="/static/images/icons/24x24/${game.game_type_cd}.png" alt="${game.game_type_cd}" /></td>
+           <td><a class="btn btn-primary btn-small" href="${request.route_url('game_info', id=game.game_id)}" title="View detailed information about this game">view</a></td>
+           <td style="width:20px;"><img title="${game.game_type_cd}" src="/static/images/icons/24x24/${game.game_type_cd}.png" alt="${game.game_type_cd}" /></td>
+           <td>${server.name}</td>
            <td>${map.name}</td>
            <td>
-           % if gamestat.team != None and gamestat.team == game.winner:
-           Won (#${gamestat.rank})
-           % elif gamestat.team != None and gamestat.team != game.winner:
-           Lost (#${gamestat.rank})
-               % else:
-               #${gamestat.rank}
-           % endif
+           % if gamestat.team != None:
+             % if gamestat.team == game.winner:
+             Win
+             % else:
+             Loss
+             % endif
+          % else:
+            % if gamestat.rank == 1:
+            Win
+            % else:
+            Loss (#${gamestat.rank})
+            % endif
+          % endif
            </td>
            <td>${game.fuzzy_date()}</td>
-           <td><a class="recent_game_box" href="${request.route_url("game_info", id=game.game_id)}" name="Game info page for game #${game.game_id}">View</a></td>
         </tr>
       % endfor
       </tbody>
     </table>
-    % if game_stats['total_games_played'] > 10:
+    % if total_games > 10:
     <a href="${request.route_url("player_game_index", player_id=player.player_id, page=1)}" title="Game index for ${player.nick}">More games played by ${player.nick_html_colors()|n}...</a>
     % endif
   </div>
 </div>
 % endif
+% endif
index aaa52d5962cf0d04893b6f01c86706cd30bc1416..1bea47c94d8100e3047464dc6601657d010d9de3 100755 (executable)
@@ -42,16 +42,16 @@ _qfont_table = [
 
 # Hex-colored spans for decimal color codes ^0 - ^9
 _dec_spans = [
- "<span style='color:#333333'>",
- "<span style='color:#FF0000'>",
- "<span style='color:#33FF00'>",
- "<span style='color:#FFFF00'>",
- "<span style='color:#3366FF'>",
- "<span style='color:#33FFFF'>",
- "<span style='color:#FF3366'>",
- "<span style='color:#FFFFFF'>",
- "<span style='color:#999999'>",
- "<span style='color:#666666'>"
+ "<span style='color:rgb(128,128,128)'>",
+ "<span style='color:rgb(255,0,0)'>",
+ "<span style='color:rgb(51,255,0)'>",
+ "<span style='color:rgb(255,255,0)'>",
+ "<span style='color:rgb(51,102,255)'>",
+ "<span style='color:rgb(51,255,255)'>",
+ "<span style='color:rgb(255,51,102)'>",
+ "<span style='color:rgb(255,255,255)'>",
+ "<span style='color:rgb(153,153,153)'>",
+ "<span style='color:rgb(128,128,128)'>"
 ]
 
 # Color code patterns
@@ -80,16 +80,16 @@ def strip_colors(qstr=''):
     if qstr == None:
         qstr = ''
     return _all_colors.sub('', qstr)
-    
-    
+
+
 def hex_repl(match):
     """Convert Darkplaces hex color codes to CSS rgb.
     Brighten colors with HSL light value less than 50%"""
 
     # Extend hex char to 8 bits and to 0.0-1.0 scale
-    r = int(match.group(0) * 2, 16) / 255.0
-    g = int(match.group(1) * 2, 16) / 255.0
-    b = int(match.group(2) * 2, 16) / 255.0
+    r = int(match.group(1) * 2, 16) / 255.0
+    g = int(match.group(2) * 2, 16) / 255.0
+    b = int(match.group(3) * 2, 16) / 255.0
 
     # Check if color is too dark
     hue, light, satur = rgb_to_hls(r, g, b)
@@ -101,7 +101,6 @@ def hex_repl(match):
     return '<span style="color:rgb(%d,%d,%d)">' % (255 * r, 255 * g, 255 * b)
 
 
-
 def html_colors(qstr=''):
     qstr = html_escape(qfont_decode(qstr).replace('^^', '^'))
     html = _dec_colors.sub(lambda match: _dec_spans[int(match.group(1))], qstr)
@@ -119,7 +118,7 @@ def pretty_date(time=False):
     pretty string like 'an hour ago', 'Yesterday', '3 months ago',
     'just now', etc
     """
-    now = datetime.now()
+    now = datetime.utcnow()
     if type(time) is int:
         diff = now - datetime.fromtimestamp(time)
     elif isinstance(time,datetime):
index 87c5e76ed398caa068060a257abf5436de0beda9..06ab55a5fc304e15b46e0425462ac39b9187f5e7 100755 (executable)
@@ -1,5 +1,6 @@
 from xonstat.views.submission import stats_submit\r
 from xonstat.views.player import player_index, player_info, player_game_index\r
+from xonstat.views.player import player_accuracy\r
 from xonstat.views.game import game_index, game_info, rank_index\r
 from xonstat.views.map import map_info, map_index\r
 from xonstat.views.server import server_info, server_game_index, server_index\r
index 0d90776b4cf1d2f7c0261415e599c8861e606165..51624d503d07d6c8c87df1b3b22b23e632fdc61c 100755 (executable)
@@ -22,19 +22,21 @@ def main_index(request):
     duel_ranks = DBSession.query(PlayerRank.player_id, PlayerRank.nick, 
             PlayerRank.elo).\
             filter(PlayerRank.game_type_cd=='duel').\
-            order_by(PlayerRank.rank).all()[0:10]
+            order_by(PlayerRank.rank).\
+            limit(10).all()
 
     duel_ranks = [(player_id, html_colors(nick), elo) \
             for (player_id, nick, elo) in duel_ranks]
 
     for i in range(leaderboard_count-len(duel_ranks)):
-        duel_ranks.append(('-', '-', '-', '-'))
+        duel_ranks.append(('-', '-', '-'))
 
     # top ranked CTF-ers
     ctf_ranks = DBSession.query(PlayerRank.player_id, PlayerRank.nick, 
             PlayerRank.elo).\
             filter(PlayerRank.game_type_cd=='ctf').\
-            order_by(PlayerRank.rank).all()[0:10]
+            order_by(PlayerRank.rank).\
+            limit(10).all()
 
     ctf_ranks = [(player_id, html_colors(nick), elo) \
             for (player_id, nick, elo) in ctf_ranks]
@@ -46,7 +48,8 @@ def main_index(request):
     dm_ranks = DBSession.query(PlayerRank.player_id, PlayerRank.nick, 
             PlayerRank.elo).\
             filter(PlayerRank.game_type_cd=='dm').\
-            order_by(PlayerRank.rank).all()[0:10]
+            order_by(PlayerRank.rank).\
+            limit(10).all()
 
     dm_ranks = [(player_id, html_colors(nick), elo) \
             for (player_id, nick, elo) in dm_ranks]
@@ -54,16 +57,18 @@ def main_index(request):
     for i in range(leaderboard_count-len(dm_ranks)):
         dm_ranks.append(('-', '-', '-'))
 
+    right_now = datetime.utcnow()
+    back_then = datetime.utcnow() - timedelta(days=leaderboard_lifetime)
+
     # top players by playing time
     top_players = DBSession.query(Player.player_id, Player.nick, 
             func.sum(PlayerGameStat.alivetime)).\
             filter(Player.player_id == PlayerGameStat.player_id).\
             filter(Player.player_id > 2).\
-            filter(PlayerGameStat.create_dt > 
-                    (datetime.utcnow() - timedelta(days=leaderboard_lifetime))).\
+            filter(expr.between(PlayerGameStat.create_dt, back_then, right_now)).\
             order_by(expr.desc(func.sum(PlayerGameStat.alivetime))).\
             group_by(Player.nick).\
-            group_by(Player.player_id).all()[0:10]
+            group_by(Player.player_id).limit(10).all()
 
     top_players = [(player_id, html_colors(nick), score) \
             for (player_id, nick, score) in top_players]
@@ -75,11 +80,10 @@ def main_index(request):
     top_servers = DBSession.query(Server.server_id, Server.name, 
             func.count()).\
             filter(Game.server_id==Server.server_id).\
-            filter(Game.create_dt > 
-                (datetime.utcnow() - timedelta(days=leaderboard_lifetime))).\
+            filter(expr.between(Game.create_dt, back_then, right_now)).\
             order_by(expr.desc(func.count(Game.game_id))).\
             group_by(Server.server_id).\
-            group_by(Server.name).all()[0:10]
+            group_by(Server.name).limit(10).all()
 
     for i in range(leaderboard_count-len(top_servers)):
         top_servers.append(('-', '-', '-'))
@@ -88,11 +92,10 @@ def main_index(request):
     top_maps = DBSession.query(Game.map_id, Map.name, 
             func.count()).\
             filter(Map.map_id==Game.map_id).\
-            filter(Game.create_dt > 
-                (datetime.utcnow() - timedelta(days=leaderboard_lifetime))).\
+            filter(expr.between(Game.create_dt, back_then, right_now)).\
             order_by(expr.desc(func.count())).\
             group_by(Game.map_id).\
-            group_by(Map.name).all()[0:10]
+            group_by(Map.name).limit(10).all()
 
     for i in range(leaderboard_count-len(top_maps)):
         top_maps.append(('-', '-', '-'))
@@ -103,7 +106,8 @@ def main_index(request):
             filter(Game.map_id==Map.map_id).\
             filter(PlayerGameStat.game_id==Game.game_id).\
             filter(PlayerGameStat.rank==1).\
-            order_by(expr.desc(Game.start_dt)).all()[0:recent_games_count]
+            filter(expr.between(Game.create_dt, back_then, right_now)).\
+            order_by(expr.desc(Game.start_dt)).limit(recent_games_count).all()
 
     for i in range(recent_games_count-len(recent_games)):
         recent_games.append(('-', '-', '-', '-'))
index 9bdb6ed756f56b2fe77ba42ba734fa6c31dcae94..c20a61894982b7c790ece948815f5e681acc04ac 100755 (executable)
@@ -1,4 +1,5 @@
 import datetime\r
+import json\r
 import logging\r
 import re\r
 import sqlalchemy as sa\r
@@ -6,7 +7,7 @@ import sqlalchemy.sql.functions as func
 import time\r
 from pyramid.response import Response\r
 from pyramid.url import current_route_url\r
-from sqlalchemy import desc\r
+from sqlalchemy import desc, distinct\r
 from webhelpers.paginate import Page, PageURL\r
 from xonstat.models import *\r
 from xonstat.util import page_url\r
@@ -47,7 +48,7 @@ def player_index(request):
             }\r
 \r
 \r
-def games_played(player_id):\r
+def get_games_played(player_id):\r
     """\r
     Provides a breakdown by gametype of the games played by player_id.\r
 \r
@@ -68,6 +69,87 @@ def games_played(player_id):
     return (total, games_played)\r
 \r
 \r
+# TODO: should probably factor the above function into this one such that\r
+# total_stats['ctf_games'] is the count of CTF games and so on...\r
+def get_total_stats(player_id):\r
+    """\r
+    Provides aggregated stats by player_id.\r
+\r
+    Returns a dict with the keys 'kills', 'deaths', 'alivetime'.\r
+\r
+    kills = how many kills a player has over all games\r
+    deaths = how many deaths a player has over all games\r
+    alivetime = how long a player has played over all games\r
+\r
+    If any of the above are None, they are set to 0.\r
+    """\r
+    total_stats = {}\r
+    (total_stats['kills'], total_stats['deaths'], total_stats['alivetime']) = DBSession.\\r
+            query("total_kills", "total_deaths", "total_alivetime").\\r
+            from_statement(\r
+                "select sum(kills) total_kills, "\r
+                "sum(deaths) total_deaths, "\r
+                "sum(alivetime) total_alivetime "\r
+                "from player_game_stats "\r
+                "where player_id=:player_id"\r
+            ).params(player_id=player_id).one()\r
+\r
+    (total_stats['wins'],) = DBSession.\\r
+            query("total_wins").\\r
+            from_statement(\r
+                "select count(*) total_wins "\r
+                "from games g, player_game_stats pgs "\r
+                "where g.game_id = pgs.game_id "\r
+                "and player_id=:player_id "\r
+                "and (g.winner = pgs.team or pgs.rank = 1)"\r
+            ).params(player_id=player_id).one()\r
+\r
+    for (key,value) in total_stats.items():\r
+        if value == None:\r
+            total_stats[key] = 0\r
+\r
+    return total_stats\r
+\r
+\r
+def get_accuracy_stats(player_id, weapon_cd, games):\r
+    """\r
+    Provides accuracy for weapon_cd by player_id for the past N games.\r
+    """\r
+    # Reaching back 90 days should give us an accurate enough average\r
+    # We then multiply this out for the number of data points (games) to\r
+    # create parameters for a flot graph\r
+    try:\r
+        raw_avg = DBSession.query(func.sum(PlayerWeaponStat.hit),\r
+                func.sum(PlayerWeaponStat.fired)).\\r
+                filter(PlayerWeaponStat.player_id == player_id).\\r
+                filter(PlayerWeaponStat.weapon_cd == weapon_cd).\\r
+                one()\r
+\r
+        avg = round(float(raw_avg[0])/raw_avg[1]*100, 2)\r
+\r
+        # Determine the raw accuracy (hit, fired) numbers for $games games\r
+        # This is then enumerated to create parameters for a flot graph\r
+        raw_accs = DBSession.query(PlayerWeaponStat.game_id, \r
+            PlayerWeaponStat.hit, PlayerWeaponStat.fired).\\r
+                filter(PlayerWeaponStat.player_id == player_id).\\r
+                filter(PlayerWeaponStat.weapon_cd == weapon_cd).\\r
+                order_by(PlayerWeaponStat.game_id.desc()).\\r
+                limit(games).\\r
+                all()\r
+\r
+        # they come out in opposite order, so flip them in the right direction\r
+        raw_accs.reverse()\r
+\r
+        accs = []\r
+        for i in range(len(raw_accs)):\r
+            accs.append((raw_accs[i][0], round(float(raw_accs[i][1])/raw_accs[i][2]*100, 2)))\r
+    except:\r
+        accs = []\r
+        avg = 0.0\r
+\r
+    return (avg, accs)\r
+\r
+\r
 def player_info(request):\r
     """\r
     Provides detailed information on a specific player\r
@@ -80,8 +162,14 @@ def player_info(request):
         player = DBSession.query(Player).filter_by(player_id=player_id).\\r
                 filter(Player.active_ind == True).one()\r
 \r
-        (total_games, games_breakdown) = games_played(player.player_id)\r
+        # games played, alivetime, wins, kills, deaths\r
+        total_stats = get_total_stats(player.player_id)\r
+\r
+        # games breakdown - N games played (X ctf, Y dm) etc\r
+        (total_games, games_breakdown) = get_games_played(player.player_id)\r
+\r
 \r
+        # friendly display of elo information and preliminary status\r
         elos = DBSession.query(PlayerElo).filter_by(player_id=player_id).\\r
                 filter(PlayerElo.game_type_cd.in_(['ctf','duel','dm'])).\\r
                 order_by(PlayerElo.elo.desc()).all()\r
@@ -96,19 +184,19 @@ def player_info(request):
             elos_display.append(str.format(round(elo.elo, 3),\r
                 elo.game_type_cd))\r
 \r
-        weapon_stats = DBSession.query("descr", "weapon_cd", "actual_total", \r
-                "max_total", "hit_total", "fired_total", "frags_total").\\r
-                from_statement(\r
-                    "select cw.descr, cw.weapon_cd, sum(actual) actual_total, "\r
-                    "sum(max) max_total, sum(hit) hit_total, "\r
-                    "sum(fired) fired_total, sum(frags) frags_total "\r
-                    "from player_weapon_stats ws, cd_weapon cw "\r
-                    "where ws.weapon_cd = cw.weapon_cd "\r
-                    "and player_id = :player_id "\r
-                    "group by descr, cw.weapon_cd "\r
-                    "order by descr"\r
-                ).params(player_id=player_id).all()\r
-\r
+        # which weapons have been used in the past 90 days\r
+        # and also, used in 5 games or more?\r
+        back_then = datetime.datetime.utcnow() - datetime.timedelta(days=90)\r
+        recent_weapons = []\r
+        for weapon in DBSession.query(PlayerWeaponStat.weapon_cd, func.count()).\\r
+                filter(PlayerWeaponStat.player_id == player_id).\\r
+                filter(PlayerWeaponStat.create_dt > back_then).\\r
+                group_by(PlayerWeaponStat.weapon_cd).\\r
+                having(func.count() > 4).\\r
+                all():\r
+                    recent_weapons.append(weapon[0])\r
+\r
+        # recent games table, all data\r
         recent_games = DBSession.query(PlayerGameStat, Game, Server, Map).\\r
                 filter(PlayerGameStat.player_id == player_id).\\r
                 filter(PlayerGameStat.game_id == Game.game_id).\\r
@@ -116,60 +204,23 @@ def player_info(request):
                 filter(Game.map_id == Map.map_id).\\r
                 order_by(Game.game_id.desc())[0:10]\r
 \r
-        game_stats = {}\r
-        (game_stats['avg_rank'], game_stats['total_kills'], \r
-                game_stats['total_deaths'], game_stats['total_suicides'], \r
-                game_stats['total_score'], game_stats['total_time'], \r
-                game_stats['total_held'], game_stats['total_captures'], \r
-                game_stats['total_pickups'],game_stats['total_drops'], \r
-                game_stats['total_returns'], game_stats['total_collects'], \r
-                game_stats['total_destroys'], game_stats['total_dhk'], \r
-                game_stats['total_pushes'], game_stats['total_pushed'], \r
-                game_stats['total_carrier_frags'], \r
-                game_stats['total_alivetime'],\r
-                game_stats['total_games_played']) = DBSession.\\r
-                        query("avg_rank", "total_kills", "total_deaths", \r
-                "total_suicides", "total_score", "total_time", "total_held",\r
-                "total_captures", "total_pickups", "total_drops", \r
-                "total_returns", "total_collects", "total_destroys", \r
-                "total_dhk", "total_pushes", "total_pushed", \r
-                "total_carrier_frags", "total_alivetime", \r
-                "total_games_played").\\r
-                from_statement(\r
-                    "select round(avg(rank)) avg_rank, sum(kills) total_kills, "\r
-                    "sum(deaths) total_deaths, sum(suicides) total_suicides, "\r
-                    "sum(score) total_score, sum(time) total_time, "\r
-                    "sum(held) total_held, sum(captures) total_captures, "\r
-                    "sum(pickups) total_pickups, sum(drops) total_drops, "\r
-                    "sum(returns) total_returns, sum(collects) total_collects, "\r
-                    "sum(destroys) total_destroys, sum(destroys_holding_key) total_dhk, "\r
-                    "sum(pushes) total_pushes, sum(pushed) total_pushed, "\r
-                    "sum(carrier_frags) total_carrier_frags, "\r
-                    "sum(alivetime) total_alivetime, count(*) total_games_played "\r
-                    "from player_game_stats "\r
-                    "where player_id=:player_id"\r
-                ).params(player_id=player_id).one()\r
-\r
-        for (key,value) in game_stats.items():\r
-            if value == None:\r
-                game_stats[key] = '-'\r
-\r
     except Exception as e:\r
         player = None\r
         elos_display = None\r
-        weapon_stats = None\r
-        game_stats = None\r
+        total_stats = None\r
         recent_games = None\r
         total_games = None\r
         games_breakdown = None\r
+        recent_weapons = []\r
 \r
-    return {'player':player, \r
+    return {'player':player,\r
             'elos_display':elos_display,\r
             'recent_games':recent_games,\r
-            'weapon_stats':weapon_stats,\r
-            'game_stats':game_stats, \r
+            'total_stats':total_stats,\r
             'total_games':total_games,\r
-            'games_breakdown':games_breakdown}\r
+            'games_breakdown':games_breakdown,\r
+            'recent_weapons':recent_weapons,\r
+            }\r
 \r
 \r
 def player_game_index(request):\r
@@ -209,3 +260,48 @@ def player_game_index(request):
     return {'player_id':player_id,\r
             'games':games,\r
             'pgstats':pgstats}\r
+\r
+def player_accuracy(request):\r
+    """\r
+    Provides a JSON response representing the accuracy for the given weapon.\r
+\r
+    Parameters:\r
+       weapon = which weapon to display accuracy for. Valid values are 'nex',\r
+                'shotgun', 'uzi', and 'minstanex'.\r
+       games = over how many games to display accuracy. Can be up to 50.\r
+    """\r
+    player_id = request.matchdict['id']\r
+    allowed_weapons = ['nex', 'rifle', 'shotgun', 'uzi', 'minstanex']\r
+    weapon_cd = 'nex'\r
+    games = 20\r
+\r
+    if request.params.has_key('weapon'):\r
+        if request.params['weapon'] in allowed_weapons:\r
+            weapon_cd = request.params['weapon']\r
+\r
+    if request.params.has_key('games'):\r
+        try:\r
+            games = request.params['games']\r
+\r
+            if games < 0:\r
+                games = 20\r
+            if games > 50:\r
+                games = 50\r
+        except:\r
+            games = 20\r
+\r
+    (avg, accs) = get_accuracy_stats(player_id, weapon_cd, games)\r
+\r
+    # if we don't have enough data for the given weapon\r
+    if len(accs) < games:\r
+        games = len(accs)\r
+\r
+    return {\r
+            'player_id':player_id, \r
+            'player_url':request.route_url('player_info', id=player_id), \r
+            'weapon':weapon_cd, \r
+            'games':games, \r
+            'avg':avg, \r
+            'accs':accs\r
+            }\r
+\r
index 6d5bd5a02fa371f5106b9aa014bd944b29cf4519..631d93b0f3bb3207c46599ea56af761ff5e86348 100755 (executable)
@@ -549,7 +549,7 @@ def stats_submit(request):
 \r
         if not has_required_metadata(game_meta):\r
             log.debug("ERROR: Required game meta missing")\r
-            raise pyramid.exceptions.HTTPUnprocessableEntity("Missing game meta")\r
+            raise pyramid.httpexceptions.HTTPUnprocessableEntity("Missing game meta")\r
 \r
         if not is_supported_gametype(game_meta['G']):\r
             log.debug("ERROR: Unsupported gametype")\r