ctdo-trac/TracRendezVous/tracrendezvous/rendezvous/utils.py

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