205 lines
7.0 KiB
Python
205 lines
7.0 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
from re import compile as re_compile
|
|
from re import match
|
|
from decimal import Decimal, Context, getcontext
|
|
from datetime import date, time, datetime, timedelta
|
|
from os.path import join, dirname
|
|
from os.path import exists as path_exists
|
|
from os import mkdir
|
|
from sys import maxint
|
|
|
|
from trac.util import Ranges
|
|
from trac.wiki import WikiPage, WikiSystem
|
|
from trac.util.datefmt import utc, to_timestamp, localtz, format_time, get_timezone, timezone
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
|
|
__all__ = ["check_date_collision", "check_vote_collision","update_votes_graph"]
|
|
|
|
class ValidationError(ValueError):
|
|
def __str__(self):
|
|
return "ValidationError: value out of bounds!"
|
|
|
|
|
|
class VoteCollisionError(ValueError):
|
|
def __str__(self):
|
|
return "VoteCollisionError: that vote collides with another one from you!"
|
|
|
|
|
|
def check_vote_collision(newvote, votes):
|
|
for vote in votes:
|
|
if newvote.vote_id == vote.vote_id:
|
|
continue
|
|
if vote.time_begin < newvote.time_begin < vote.time_end:
|
|
raise VoteCollisionError
|
|
if vote.time_begin < newvote.time_end < vote.time_end:
|
|
raise VoteCollisionError
|
|
if newvote.time_begin < vote.time_begin < newvote.time_end:
|
|
raise VoteCollisionError
|
|
if newvote.time_begin < vote.time_end < newvote.time_end:
|
|
raise VoteCollisionError
|
|
|
|
def check_date_collision(newdate, dates):
|
|
for date in dates:
|
|
if newdate.date_id == date.date_id:
|
|
continue
|
|
if date.time_begin < newdate.time_begin < date.time_end:
|
|
raise VoteCollisionError
|
|
if date.time_begin < newdate.time_end < date.time_end:
|
|
raise VoteCollisionError
|
|
if newdate.time_begin < date.time_begin < newdate.time_end:
|
|
raise VoteCollisionError
|
|
if newdate.time_begin < date.time_end < newdate.time_end:
|
|
raise VoteCollisionError
|
|
|
|
def colorGen(steps):
|
|
r = 255
|
|
g = 0
|
|
b = 0
|
|
rsteps=512/steps
|
|
for ry in xrange(255):
|
|
g+=rsteps
|
|
g = max(255,g)
|
|
yield r,g,b
|
|
for yg in xrange(255, 0, -1):
|
|
r-=rsteps
|
|
r = min(0,r)
|
|
yield r,g,b
|
|
|
|
|
|
def create_graph(gname, votes, path, real_mindate, maxdate, size, selected_tz):
|
|
""" I wanna be a better gantt diagram - oh yeah ...
|
|
Render an diagram image of all votes users made. The
|
|
x-axis is the timeline beginning with the hour of the first vote as origin and
|
|
the hour after the last vote.
|
|
"""
|
|
|
|
import math
|
|
def trange(start, end, vhours, deltaT, width):
|
|
pixels = 0
|
|
units = pixelPerHour
|
|
scaledDeltaTime = timedelta(0,3600,0)
|
|
scaleFactor = 1
|
|
if vhours > 10:
|
|
scaleFactor = math.ceil(vhours / 10.0)
|
|
scaledDeltaTime *= int(scaleFactor)
|
|
units *= scaleFactor
|
|
e = end.replace(minute=0,second=0, microsecond=0)+timedelta(0,3600)
|
|
while start <= e:
|
|
yield pixels, (format_time(start, '%d', tzinfo=selected_tz), format_time(start, '%H:%M', tzinfo=selected_tz))
|
|
start += scaledDeltaTime
|
|
pixels += units
|
|
|
|
deltaT = maxdate - real_mindate
|
|
mindate = real_mindate.replace(minute=0, second=0, microsecond=0)
|
|
steps = len(votes)
|
|
if steps <= 0:
|
|
return
|
|
hours = int(deltaT.days * 24.0 + deltaT.seconds / 3600.0)
|
|
vhours = hours + 2
|
|
font = None
|
|
fontpath = join(dirname(__file__), "luxisr.ttf")
|
|
xBase = 0
|
|
pixelPerHour = (size[0]-xBase) / vhours
|
|
hOffset = mindate.hour
|
|
halfHour = pixelPerHour / 2
|
|
user2Row = {}
|
|
rowCount=0
|
|
for d in xrange(steps):
|
|
user=votes[d].user
|
|
if not user2Row.has_key(user):
|
|
user2Row[user] = rowCount
|
|
rowCount+=1
|
|
fontSize = 22
|
|
deltaHeightPerVote = fontSize*2
|
|
size[1] = (rowCount + 2) * deltaHeightPerVote
|
|
chart = Image.new("RGBA", size, (230, 230, 230, 255))
|
|
chartDraw = ImageDraw.Draw(chart)
|
|
yBase = size[1]-fontSize*2
|
|
font = ImageFont.truetype(fontpath, fontSize)
|
|
timefont = ImageFont.truetype(fontpath, 13)
|
|
del rowCount
|
|
for d in xrange(steps):
|
|
user=votes[d].user
|
|
n = user2Row[user]
|
|
i = votes[d].time_begin
|
|
j = votes[d].time_end
|
|
tmp=i-mindate
|
|
tmp=tmp.days * 24.0 + tmp.seconds / 3600.0
|
|
x1 = tmp * pixelPerHour + xBase
|
|
tmp=j - mindate
|
|
tmp=tmp.days * 24 + tmp.seconds / 3600.0
|
|
x2 = tmp * pixelPerHour + xBase
|
|
y1 = yBase - (n + 1) * deltaHeightPerVote
|
|
y2 = y1 + deltaHeightPerVote
|
|
chartDraw.rectangle((x1, y1, x2, y2), fill=(161, 235, 255))
|
|
chartDraw.line((x1, y1, x1, yBase), fill=(150, 150, 150))
|
|
chartDraw.line((x2, y1, x2, yBase), fill=(150, 150, 150))
|
|
chartDraw.line((xBase, y1, size[0], y1), fill=(150, 150, 150))
|
|
chartDraw.line((xBase, y2, size[0], y2), fill=(150, 150, 150))
|
|
chartDraw.text((xBase, y1 + deltaHeightPerVote/4), votes[d].user, font=font, fill=(0,0,0))
|
|
chartDraw.line((0, yBase, size[0], yBase), fill = (0, 0, 0, 255), width=2)
|
|
fontBaseLine = size[1] - 30
|
|
#chartDraw.text((xBase, fontSize/2), gname, font=font, fill=(0, 0, 250))
|
|
for xMajor, ts in trange(mindate, maxdate, vhours, deltaT, size[0] - xBase):
|
|
chartDraw.text((xMajor + xBase, fontBaseLine), ts[0], font=timefont, fill=(0, 0, 0))
|
|
chartDraw.text((xMajor + xBase, fontBaseLine + 15), ts[1], font=timefont, fill=(0, 0, 0))
|
|
chartDraw.line((xMajor + xBase, size[1] - fontSize, xMajor + xBase, yBase), fill=(0, 0, 0, 255), width=2)
|
|
chart.save(join(path, "date%d.png" % votes[0].date_id), "PNG")
|
|
|
|
|
|
|
|
def update_votes_graph(gname, dvotes, path, size, selected_tz):
|
|
if dvotes:
|
|
if not path_exists(path):
|
|
mkdir(path, 0755)
|
|
votes = sorted(dvotes, date_cmp)
|
|
matchCount, mindate, maxdate = date_stats(votes)
|
|
create_graph(gname, votes, path, mindate, maxdate, size, selected_tz)
|
|
|
|
|
|
def date_cmp(a,b):
|
|
if a.time_begin < b.time_begin:
|
|
return -1
|
|
elif a.time_begin > b.time_begin:
|
|
return 1
|
|
else:
|
|
return 0
|
|
|
|
def date_stats(votes):
|
|
""" I'm expecting a list of votes sorted by time_begin.
|
|
I'm returning a tuple of 3 values
|
|
* a list with match counts
|
|
* the minimum date in the votes
|
|
* the maximum date in the votes
|
|
"""
|
|
if len(votes) == 0:
|
|
return None, None, None
|
|
maxdate = datetime(1, 1, 1, tzinfo=utc)
|
|
mindate = votes[0].time_begin
|
|
ic=0
|
|
matchCount = [0 for i in xrange(len(votes))]
|
|
for vote in votes:
|
|
if vote.time_end > maxdate:
|
|
maxdate = vote.time_end
|
|
jc=0
|
|
for voteB in votes:
|
|
if voteB.time_begin < vote.time_end:
|
|
matchCount[ic] += 1
|
|
matchCount[jc] += 1
|
|
jc+=1
|
|
ic+=1
|
|
return matchCount, mindate, maxdate
|
|
|
|
|
|
def sortVotesPerUser(votes, users):
|
|
myvotes = [False for i in users]
|
|
for i in votes:
|
|
myvotes[users[i.user]] = i
|
|
|
|
|
|
def sortedUsers(users):
|
|
myusers = ["" for i in users]
|
|
for i in users:
|
|
myusers[users[i]] = i
|