Bladeren bron

Graphing with matplotlib.

tags/v0.1^2
Ben Kurtovic 13 jaren geleden
bovenliggende
commit
1701016a7d
2 gewijzigde bestanden met toevoegingen van 49 en 9 verwijderingen
  1. +5
    -2
      README.md
  2. +44
    -7
      bot/tasks/afc_history.py

+ 5
- 2
README.md Bestand weergeven

@@ -26,5 +26,8 @@ integration between bot parts, and easier maintenance.


EarwigBot uses the MySQL library EarwigBot uses the MySQL library
[oursql](http://packages.python.org/oursql/) (>= 0.9.2) for communicating with [oursql](http://packages.python.org/oursql/) (>= 0.9.2) for communicating with
MediaWiki databases, and some tasks use their own tables for storage. It is not
required.
MediaWiki databases, and some tasks use their own tables for storage.
Additionally, the afc_history task uses
[matplotlib](http://matplotlib.sourceforge.net/) and
[numpy](http://numpy.scipy.org/) for graphing AfC statistics. Neither of these
modules are required for the main bot itself.

+ 44
- 7
bot/tasks/afc_history.py Bestand weergeven

@@ -1,10 +1,14 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-


from collections import OrderedDict
from datetime import datetime, timedelta from datetime import datetime, timedelta
from itertools import count
from os.path import expanduser from os.path import expanduser
from threading import Lock from threading import Lock
from time import sleep from time import sleep


from matplotlib import pyplot as plt
from numpy import arange
import oursql import oursql


from classes import BaseTask from classes import BaseTask
@@ -26,16 +30,22 @@ class Task(BaseTask):
submissions every day. submissions every day.


This information is saved to a MySQL database ("u_earwig_afc_history") and This information is saved to a MySQL database ("u_earwig_afc_history") and
used to generate attractive graphs showing the number of AfC submissions
over time.
used to generate a graph showing the number of AfC submissions by date
with matplotlib and numpy. The chart is saved as a PNG to
config.tasks["afc_history"]["graph"]["dest"], which defaults to
"afc_history.png".
""" """
name = "afc_history" name = "afc_history"


def __init__(self): def __init__(self):
cfg = config.tasks.get(self.name, {}) cfg = config.tasks.get(self.name, {})
self.destination = cfg.get("destination", "afc_history.png")
self.num_days = cfg.get("days", 90)
self.categories = cfg.get("categories", {}) self.categories = cfg.get("categories", {})


# Graph stuff:
self.graph = cfg.get("graph", {})
self.dest = self.graph.get("dest", "afc_history.png")

# Connection data for our SQL database: # Connection data for our SQL database:
kwargs = cfg.get("sql", {}) kwargs = cfg.get("sql", {})
kwargs["read_default_file"] = expanduser("~/.my.cnf") kwargs["read_default_file"] = expanduser("~/.my.cnf")
@@ -49,7 +59,7 @@ class Task(BaseTask):
action = kwargs.get("action") action = kwargs.get("action")
try: try:
num_days = int(kwargs.get("days", 90))
num_days = int(kwargs.get("days", self.num_days))
if action == "update": if action == "update":
self.update(num_days) self.update(num_days)
elif action == "generate": elif action == "generate":
@@ -69,7 +79,7 @@ class Task(BaseTask):


def generate(self, num_days): def generate(self, num_days):
self.logger.info("Generating chart for past {0} days".format(num_days)) self.logger.info("Generating chart for past {0} days".format(num_days))
data = {}
data = OrderedDict()
generator = self.backwards_cat_iterator() generator = self.backwards_cat_iterator()
for d in xrange(num_days): for d in xrange(num_days):
category = generator.next() category = generator.next()
@@ -77,8 +87,8 @@ class Task(BaseTask):
data[date] = self.get_date_counts(date) data[date] = self.get_date_counts(date)


dest = expanduser(self.destination) dest = expanduser(self.destination)
with open(dest, "wb") as fp:
fp.write(str(data))
self.generate_chart(reversed(data))
plt.savefig(dest)
self.logger.info("Chart saved to {0}".format(dest)) self.logger.info("Chart saved to {0}".format(dest))


def backwards_cat_iterator(self): def backwards_cat_iterator(self):
@@ -155,3 +165,30 @@ class Task(BaseTask):
count = cursor.fetchall()[0][0] count = cursor.fetchall()[0][0]
counts[status] = count counts[status] = count
return counts return counts

def generate_chart(self, data):
pends = [d[STATUS_PEND] for d in data.itervalues()]
declines = [d[STATUS_DECLINE] for d in data.itervalues()]
accepts = [d[STATUS_ACCEPT] for d in data.itervalues()]
ind = arange(len(data))
width = self.graph.get("width", 0.75)
xstep = self.graph.get("xAxisStep", 6)
xticks = arange(xstep-1, ind.size+xstep-1, xstep) + width/2.0
xlabels = [d for c, d in zip(count(1), data.keys()) if not c % xstep]
pcolor = self.graph.get("pendingColor", "y")
dcolor = self.graph.get("declinedColor", "r")
acolor = self.graph.get("acceptedColor", "g")

p1 = plt.bar(ind, pends, width, color=pcolor)
p2 = plt.bar(ind, declines, width, color=dcolor, bottom=pends)
p3 = plt.bar(ind, accepts, width, color=acolor, bottom=declines)

plt.title("AfC submissions per date")
plt.ylabel("Submissions")
plt.xlabel("Date")
plt.xticks(xticks, xlabels)
plt.legend((p1[0], p2[0], p3[0]), ("Pending", "Declined", "Accepted"))

fig = plt.gcf()
fig.set_size_inches(12, 9) # 1200, 900
fig.autofmt_xdate()

Laden…
Annuleren
Opslaan