From 7c3fdf5007452e66e4dd6fd2ac806fc09d1d14ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Sat, 14 Oct 2017 17:20:51 +0200 Subject: [PATCH] add punchcard graphic Graphing code adapted from https://stackoverflow.com/a/14850998 --- eodb/events/management/commands/punchcard.py | 92 ++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 eodb/events/management/commands/punchcard.py diff --git a/eodb/events/management/commands/punchcard.py b/eodb/events/management/commands/punchcard.py new file mode 100644 index 0000000..156ad13 --- /dev/null +++ b/eodb/events/management/commands/punchcard.py @@ -0,0 +1,92 @@ +import matplotlib.pyplot as plt +import numpy as np + +from django.core.management.base import BaseCommand +from django.utils.dateparse import parse_date + +from eodb.events.models import Commit + + +class Command(BaseCommand): + def add_arguments(self, parser): + parser.add_argument('--username', metavar='USERNAME') + parser.add_argument('--datemin', metavar='DATEMIN') + parser.add_argument('--datemax', metavar='DATEMAX') + parser.add_argument('--committime', action='store_true', + help='use commit datetime instead of author datetime') + + def handle(self, *args, **options): + infos = {} + for i in range(7): + for j in range(24): + infos[(i, j)] = 0 + + filters = {'author_email__endswith': '@entrouvert.com'} + + title = 'Git activity' + + if options.get('username'): + filters['author_email__startswith'] = options['username'] + '@' + title += ' for %s' % options['username'] + + datetime_var = 'author_datetime' + if options.get('committime'): + datetime_var = 'commit_datetime' + if options.get('datemin'): + filters[datetime_var + '__gte'] = parse_date(options['datemin']) + title += ' from %s' % options['datemin'] + if options.get('datemax'): + filters[datetime_var + '__lt'] = parse_date(options['datemax']) + title += ' until %s' % options['datemax'] + + for commit in Commit.objects.filter(**filters): + coords = (getattr(commit, datetime_var).weekday(), getattr(commit, datetime_var).hour) + infos[coords] = infos[coords] + 1 + + draw_punchcard(infos) + plt.title(title) + plt.show() + +# https://stackoverflow.com/a/14850998 +def draw_punchcard(infos, + ax1=range(7), + ax2=range(24), + ax1_ticks=['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'], + ax2_ticks=range(24), + ax1_label='Day', + ax2_label='Hour'): + # - infos: Dictionary of quantities to display. + # They are indexed by key of type (val1,val2) with + # val1 included in ax1 and val2 included in ax2. + + # build the array which contains the values + data = np.zeros((len(ax1),len(ax2))) + for key in infos: + data[key[0],key[1]] = infos[key] + data = data/float(np.max(data)) + + # shape ratio + r = float(data.shape[1])/data.shape[0] + + # Draw the punchcard (create one circle per element) + # Ugly normalisation allows to obtain perfect circles instead of ovals.... + for y in range(data.shape[0]): + for x in range(data.shape[1]): + circle = plt.Circle((x/float(data.shape[1]-1)*(data.shape[0]-1),y/r), + data[y][x]/float(data.shape[1])*data.shape[0]/2) + plt.gca().add_artist(circle) + + plt.ylim(0-0.5, data.shape[0]-0.5) + plt.xlim(0, data.shape[0]) + plt.yticks(np.arange(0,len(ax1)/r-.1,1/r), ax1_ticks) + xt = np.linspace(0, len(ax1)-1, len(ax2)) + plt.xticks(xt, ax2_ticks) + plt.xlabel(ax2_label) + plt.ylabel(ax1_label) + plt.gca().invert_yaxis() + + # make sure the axes are equal, and resize the canvas to fit the plot + plt.axis('equal') + plt.axis([-1, 7, 7/r, -1]) + scale = 1 #0.5 + plt.gcf().set_size_inches(data.shape[1]*scale,data.shape[0]*scale, forward=True)