# -*- 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