view py/stats2.py @ 56:5cf88bd4e556

stats2.py: add some 3D perspective to the graphs.
author John "Elwin" Edwards
date Mon, 26 May 2014 19:50:02 -0700
parents 0f4163dbbafc
children 876786c55450
line wrap: on
line source

#!/usr/bin/python3

import psycopg2
import rlgall
from datetime import datetime

sitename = "rlgallery.org"
svgpath = rlgall.webdir
timestr = datetime.utcnow().strftime("%Y-%m-%d %H:%M")

dochead = """<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="900" height="600">
"""
stylesheet = """<style type="text/css">
rect.frame {{
 fill:#ffffff;
 fill-opacity:1;
 stroke:#000000;
 stroke-width:2;
 stroke-opacity:1;
}}
rect.bar {{
 fill:#{0};
 fill-opacity:1;
 stroke:#000000;
 stroke-width:2;
 stroke-opacity:1;
}}
g.bar3d polygon {{
 fill:#{0};
 fill-opacity:1;
 stroke:#000000;
 stroke-width:1;
 stroke-opacity:1;
}}
</style>
"""
framerect = '<rect width="750" height="500" x="100" y="50" class="frame"/>\n'
barstr = '<rect width="{0}" height="{1}" x="{2}" y="{3}" class="bar"/>\n'
xllabel = '<text x="{0}" y="570" font-size="15" text-anchor="middle">{1}</text>\n'
ylabel = '<text x="90" y="{0:.2f}" font-size="16" text-anchor="end">{1}</text>\n'
xlabelf = '<text x="475" y="590" font-size="15" text-anchor="middle">{0}</text>\n'
ltitle = '<text x="100" y="35" font-size="16" text-anchor="start">{0}</text>\n'
ctitle = '<text x="475" y="34" font-size="18" text-anchor="middle">{0}</text>\n'
rtitle = '<text x="850" y="35" font-size="16" text-anchor="end">{0}</text>\n'
ylabelf = """<g transform="translate(100, 300)">
 <g transform="rotate(-90)">
 <text x="0" y="-60" font-size="16" text-anchor="middle">{0}</text>
 </g>
</g>
"""

def makepolygon(coords):
  points = [ "{0:.2f},{1:.2f}".format(pt[0], pt[1]) for pt in coords ]
  pointstr = " ".join(points)
  return '<polygon points="{}"/>\n'.format(pointstr)

def bar3d(x, w, h):
  ydelta = w / 2
  xdelta = ydelta / 3
  flowerleft = (x, 550)
  flowerright = (x + w, 550)
  fupperleft = (x, 550 - h)
  fupperright = (x + w, 550 - h)
  blowerright = (x + w + xdelta, 550 - ydelta)
  bupperleft = (x + xdelta, 550 - h - ydelta)
  bupperright = (x + w + xdelta, 550 - h - ydelta)
  frontface = makepolygon([flowerleft, flowerright, fupperright, fupperleft])
  rightface = makepolygon([blowerright, flowerright, fupperright, bupperright])
  topface = makepolygon([bupperleft, bupperright, fupperright, fupperleft])
  gopen = '<g class="bar3d" clip-path="url(#framer)">\n'
  gclose = '</g>\n'
  return gopen + "".join([frontface, rightface, topface]) + gclose

class SVGChart():
  def __init__(self, filename):
    self.of = open(filename, "w", encoding="utf-8")
    self.of.write(dochead)
  def style(self, barcolor):
    self.of.write(stylesheet.format(barcolor))
  def write(self, obj):
    self.of.write(obj)
  def close(self):
    self.of.write('</svg>\n')
    self.of.close()

def ylimits(x):
  ll = [2, 3, 4, 5, 6, 8, 10, 15]
  m = 1
  size = 0
  while True:
    for i in ll:
      if i * m > x * 1.1:
        size = i
        lim = i * m
        break
    if size:
      break
    else:
      m *= 10
  if size in [3, 6]:
    if lim == 3:
      divs = 3
    else:
      divs = 6
  elif size in [4, 8]:
    divs = 4
  else:
    if lim == 2:
      divs = 2
    else:
      divs = 5
  return divs, lim

def titles(l, c, r):
  return ltitle.format(l) + ctitle.format(c) + rtitle.format(r) 

def mkxlgraph(game, xldata):
  xlgraph = SVGChart("{0}/xl-{1}.svg".format(svgpath, game.uname))
  xlgraph.style("0000ff")
  xlgraph.write(framerect)
  xlgraph.write('<clipPath id="framer">\n')
  xlgraph.write(framerect)
  xlgraph.write('</clipPath>\n')
  xldivs, xlmax = ylimits(max([ pt[1] for pt in xldata ]))
  scale = 500 / xlmax
  for xl, count in xldata:
    barx = xl * 50 + 60
    barh = scale * count
    bary = 550 - barh
    if count > 0:
      xlgraph.write(bar3d(barx, 30, barh))
    xlgraph.write(xllabel.format(barx + 15, xl))
  for yl in range(xldivs + 1):
    labeln = int(xlmax * yl / xldivs)
    labelh = 550 + 8 - 500 * yl / xldivs
    xlgraph.write(ylabel.format(labelh, labeln))
  xlgraph.write(xlabelf.format("Experience level"))
  xlgraph.write(ylabelf.format("# of games"))
  xlgraph.write(titles(sitename, game.name, timestr))
  xlgraph.close()
  return

def mkscoregraph(game, scoredata):
  if isinstance(game, rlgall.ARogueGame):
    scorewidth = 1500
  else:
    scorewidth = 1000
  scoregraph = SVGChart("{0}/score-{1}.svg".format(svgpath, game.uname))
  scoregraph.style("ffff00")
  scoregraph.write(framerect)
  scoregraph.write('<clipPath id="framer">\n')
  scoregraph.write(framerect)
  scoregraph.write('</clipPath>\n')
  scoredivs, scoremax = ylimits(max([ pt[1] for pt in scoredata ]))
  scale = 500 / scoremax
  for block, count in scoredata:
    n = block // scorewidth
    barx = n * 75 + 100
    barh = scale * count
    bary = 550 - barh
    if count > 0:
      scoregraph.write(bar3d(barx, 75, barh))
    scoregraph.write(xllabel.format(barx, block))
  for yl in range(scoredivs + 1):
    labeln = int(scoremax * yl / scoredivs)
    labelh = 550 + 8 - 500 * yl / scoredivs
    scoregraph.write(ylabel.format(labelh, labeln))
  scoregraph.write(xlabelf.format("Score"))
  scoregraph.write(ylabelf.format("# of games"))
  scoregraph.write(titles(sitename, game.name, timestr))
  scoregraph.close()
  return

def mkdeepgraph(game, deepdata):
  deepgraph = SVGChart("{0}/deep-{1}.svg".format(svgpath, game.uname))
  deepgraph.style("808000")
  deepgraph.write(framerect)
  deepgraph.write('<clipPath id="framer">\n')
  deepgraph.write(framerect)
  deepgraph.write('</clipPath>\n')
  deepdivs, deepmax = ylimits(max([ pt[1] for pt in deepdata ]))
  scale = 500 / deepmax
  for lev, count in deepdata:
    barx = lev * 25 + 75
    barh = scale * count
    bary = 550 - barh
    if count > 0:
      deepgraph.write(bar3d(barx, 25, barh))
    if lev % 3 == 0:
      deepgraph.write(xllabel.format(barx + 12.5, lev))
  for yl in range(deepdivs + 1):
    labeln = int(deepmax * yl / deepdivs)
    labelh = 550 + 8 - 500 * yl / deepdivs
    deepgraph.write(ylabel.format(labelh, labeln))
  deepgraph.write(xlabelf.format("Deepest dungeon level"))
  deepgraph.write(ylabelf.format("# of games"))
  deepgraph.write(titles(sitename, game.name, timestr))
  deepgraph.close()
  return

for game in rlgall.gamelist:
  xldata = game.getXLCounts(15)
  deepdata = game.getDepthCounts(30)
  if isinstance(game, rlgall.ARogueGame):
    scorewidth = 1500
  else:
    scorewidth = 1000
  scoredata = game.getScoreCounts(10, scorewidth)
  mkxlgraph(game, xldata)
  mkscoregraph(game, scoredata)
  mkdeepgraph(game, deepdata)

exit()