diff --git a/src/calibre/trac/donations/server.py b/src/calibre/trac/donations/server.py index 5da2efa058..fc0f9eeded 100644 --- a/src/calibre/trac/donations/server.py +++ b/src/calibre/trac/donations/server.py @@ -6,9 +6,14 @@ __docformat__ = 'restructuredtext en' ''' Keep track of donations to calibre. ''' -import sys, cStringIO, textwrap, traceback, re +import sys, cStringIO, textwrap, traceback, re, os, time from datetime import date, timedelta from math import sqrt +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt +import matplotlib.dates as mdates + import cherrypy from lxml import etree @@ -150,7 +155,7 @@ class Stats: ctable = '

Top %d countries

'%num_of_countries + ctable return textwrap.dedent('''
-

Donations in %(period)d days [%(min)s - %(max)s]:

+

Donations in %(period)d days [%(min)s — %(max)s]:

@@ -177,14 +182,48 @@ def expose(func): class Server(object): + TRENDS = '/tmp/donations_trend.png' + def __init__(self, apache=False, root='/', data_file='/tmp/donations.xml'): self.apache = apache self.document_root = root - self.tree = etree.parse(data_file) - self.root = self.tree.getroot() + self.data_file = data_file self.read_records() + def calculate_trend(self): + def months(start, end): + pos = range_for_month(start.year, start.month)[0] + while pos <= end: + yield (pos.year, pos.month) + if pos.month == 12: + pos = pos.replace(year = pos.year+1) + pos = pos.replace(month = 1) + else: + pos = pos.replace(month = pos.month + 1) + + _months = list(months(self.earliest, self.latest))[:-1][:12] + _months = [range_for_month(*m) for m in _months] + _months = [self.get_slice(*m) for m in _months] + x = [m.min for m in _months] + y = [m.total for m in _months] + ml = mdates.MonthLocator() # every month + fig = plt.figure(None, (8, 3), 96)#, facecolor, edgecolor, frameon, FigureClass) + ax = fig.add_subplot(111) + ax.bar(x, y, align='center', width=20, color='g') + ax.xaxis.set_major_locator(ml) + ax.xaxis.set_major_formatter(mdates.DateFormatter('%b %y')) + ax.set_xlim(_months[0].min-timedelta(days=15), _months[-1].min+timedelta(days=15)) + ax.set_xlabel('Month') + ax.set_ylabel('Income ($)') + fig.autofmt_xdate() + fig.savefig(self.TRENDS) + #plt.show() + + def read_records(self): + self.tree = etree.parse(self.data_file) + self.last_read_time = time.time() + self.root = self.tree.getroot() self.records = [] min_date, max_date = date.today(), date.fromordinal(1) for x in self.root.xpath('//donation'): @@ -194,6 +233,7 @@ class Server(object): min_date = min(min_date, d) max_date = max(max_date, d) self.earliest, self.latest = min_date, max_date + self.calculate_trend() def get_slice(self, start_date, end_date): stats = Stats([r for r in self.records if r.date >= start_date and r.date <= end_date]) @@ -214,6 +254,8 @@ class Server(object): return date(*map(int, raw.split('-'))) def build_page(self, period_type, data): + if os.stat(self.data_file).st_mtime >= self.last_read_time: + self.read_records() month = date.today().month year = date.today().year mm = data[1] if period_type == 'month' else month @@ -335,9 +377,21 @@ class Server(object): } return true; } + + function rationalize_periods() { + var form = document.forms[0]; + var disabled = !form.period_type[0].checked; + form.month_month.disabled = disabled; + form.month_year.disabled = disabled; + disabled = !form.period_type[1].checked; + form.year_year.disabled = disabled; + disabled = !form.period_type[2].checked; + form.range_left.disabled = disabled; + form.range_right.disabled = disabled; + } - +
Total$%(total).2f (%(num)d)
Daily average$%(da).2f ± %(dd).2f
@@ -357,13 +411,13 @@ class Server(object):
Choose a period
- + Month: %(month_month)s %(month_year)s

- + Year: %(year_year)s

- + Range (YYYY-MM-DD):  to 

@@ -374,6 +428,10 @@ class Server(object):

+
+

Income trends for the last year

+ Income trends +
''')%dict( @@ -392,6 +450,11 @@ class Server(object): cherrypy.response.headers['Content-Type'] = 'application/xhtml+xml' return self.build_page('month', (year, month)) + @expose + def trend_png(self): + cherrypy.response.headers['Content-Type'] = 'image/png' + return open(self.TRENDS, 'rb').read() + @expose def show(self, period_type='month', month_month='', month_year='', year_year='', range_left='', range_right=''):