def set_title(self): dates_dict = stuff.dateDict(self.start_date, "start_") dates_dict.update(stuff.dateDict(self.end_date, "end_")) if self.start_date.year != self.end_date.year: # overview label if start and end years don't match # letter after prefixes (start_, end_) is the one of # standard python date formatting ones- you can use all of them overview_label = _(u"Overview for %(start_B)s %(start_d)s, %(start_Y)s – %(end_B)s %(end_d)s, %(end_Y)s") % dates_dict elif self.start_date.month != self.end_date.month: # overview label if start and end month do not match # letter after prefixes (start_, end_) is the one of # standard python date formatting ones- you can use all of them overview_label = _(u"Overview for %(start_B)s %(start_d)s – %(end_B)s %(end_d)s, %(end_Y)s") % dates_dict else: # overview label for interval in same month # letter after prefixes (start_, end_) is the one of # standard python date formatting ones- you can use all of them overview_label = _(u"Overview for %(start_B)s %(start_d)s – %(end_d)s, %(end_Y)s") % dates_dict if self.week_view.get_active(): dayview_caption = _("Week") else: dayview_caption = _("Month") self.get_widget("overview_label").set_markup("<b>%s</b>" % overview_label) self.get_widget("dayview_caption").set_markup("%s" % (dayview_caption))
def do_charts(self, facts): all_categories = self.popular_categories #the single "totals" (by category) bar category_sums = stuff.totals(facts, lambda fact: fact["category"], lambda fact: stuff.duration_minutes(fact["delta"]) / 60.0) category_totals = [category_sums.get(cat, 0) for cat in all_categories] category_keys = ["%s %.1f" % (cat, category_sums.get(cat, 0.0)) for cat in all_categories] self.category_chart.plot([_("Total")], [category_totals], stack_keys = category_keys) # day / category chart all_days = [self.start_date + dt.timedelta(i) for i in range((self.end_date - self.start_date).days + 1)] by_date_cat = stuff.totals(facts, lambda fact: (fact["date"], fact["category"]), lambda fact: stuff.duration_minutes(fact["delta"]) / 60.0) res = [[by_date_cat.get((day, cat), 0) for cat in all_categories] for day in all_days] #show days or dates depending on scale if (self.end_date - self.start_date).days < 20: day_keys = [day.strftime("%a") for day in all_days] else: day_keys = [_("%(m_b)s %(m_d)s") % stuff.dateDict(day, "m_") for day in all_days] self.day_chart.plot(day_keys, res, stack_keys = all_categories) #totals by activity, disguised under a stacked bar chart to get category colors activity_sums = stuff.totals(facts, lambda fact: (fact["name"], fact["category"]), lambda fact: stuff.duration_minutes(fact["delta"])) by_duration = sorted(activity_sums.items(), key = lambda x: x[1], reverse = True) by_duration_keys = [entry[0][0] for entry in by_duration] category_sums = [[entry[1] / 60.0 * (entry[0][1] == cat) for cat in all_categories] for entry in by_duration] self.activity_chart.plot(by_duration_keys, category_sums, stack_keys = all_categories)
def _write_fact(self, report, fact): end_time = fact["end_time"] # ongoing task in current day end_time_str = "" if end_time: end_time_str = end_time.strftime('%H:%M') category = "" if fact["category"] != _("Unsorted"): #do not print "unsorted" in list category = fact["category"] description = fact["description"] or "" # fact date column in HTML report report.write("""<tr class="row%d"> <td class="smallCell">%s</td> <td class="largeCell">%s</td> <td class="smallCell">%s</td> <td class="smallCell">%s</td> <td class="smallCell">%s</td> <td class="smallCell">%s</td> <td class="largeCell">%s</td> </tr> """ % (int(self.even_row), _("%(report_b)s %(report_d)s, %(report_Y)s") % stuff.dateDict(fact["start_time"], "report_"), fact["name"], category, fact["start_time"].strftime('%H:%M'), end_time_str, stuff.format_duration(fact["delta"]) or "", description)) self.even_row = not self.even_row # save data for summary table if fact["delta"]: id_string = "<td class=\"smallCell\">%s</td><td class=\"largeCell\">%s</td>" % (fact["category"], fact["name"]) self.sum_time[id_string] = self.sum_time.get(id_string, 0) + fact["delta"]
def fill_tree(self, facts): day_dict = {} for day, facts in groupby(facts, lambda fact: fact["date"]): day_dict[day] = sorted(list(facts), key=lambda fact:fact["start_time"]) for i in range((self.end_date - self.start_date).days + 1): current_date = self.start_date + dt.timedelta(i) # date format in overview window fact listing # prefix is "o_",letter after prefix is regular python format. you can use all of them fact_date = _("%(o_A)s, %(o_b)s %(o_d)s") % stuff.dateDict(current_date, "o_") day_total = dt.timedelta() for fact in day_dict.get(current_date, []): day_total += fact["delta"] day_row = self.fact_store.append(None, [-1, fact_date, stuff.format_duration(day_total), current_date.strftime('%Y-%m-%d'), "", "", None]) for fact in day_dict.get(current_date, []): self.fact_store.append(day_row, [fact["id"], fact["start_time"].strftime('%H:%M') + " " + fact["name"], stuff.format_duration(fact["delta"]), fact["start_time"].strftime('%Y-%m-%d'), fact["description"], fact["category"], fact ]) self.fact_tree.expand_all()
def stats(self, year = None): facts = self.stat_facts if year: facts = filter(lambda fact: fact["start_time"].year == year, facts) if not facts or (facts[-1]["start_time"] - facts[0]["start_time"]) < dt.timedelta(days=6): self.get_widget("statistics_box").hide() self.get_widget("explore_controls").hide() label = self.get_widget("not_enough_records_label") if not facts: label.set_text(_("""There is no data to generate statistics yet. A week of usage would be nice!""")) else: label.set_text(_("Still collecting data - check back after a week has passed!")) label.show() return else: self.get_widget("statistics_box").show() self.get_widget("explore_controls").show() self.get_widget("not_enough_records_label").hide() # All dates in the scope self.timeline.draw(facts) # Totals by category categories = stuff.totals(facts, lambda fact: fact["category"], lambda fact: fact['delta'].seconds / 60 / 60.0) category_keys = sorted(categories.keys()) categories = [categories[key] for key in category_keys] self.chart_category_totals.plot(category_keys, categories) # Totals by weekday weekdays = stuff.totals(facts, lambda fact: (fact["start_time"].weekday(), fact["start_time"].strftime("%a")), lambda fact: fact['delta'].seconds / 60 / 60.0) weekday_keys = sorted(weekdays.keys(), key = lambda x: x[0]) #sort weekdays = [weekdays[key] for key in weekday_keys] #get values in the order weekday_keys = [key[1] for key in weekday_keys] #now remove the weekday and keep just the abbreviated one self.chart_weekday_totals.plot(weekday_keys, weekdays) split_minutes = 5 * 60 + 30 #the mystical hamster midnight # starts and ends by weekday by_weekday = {} for date, date_facts in groupby(facts, lambda fact: fact["start_time"].date()): date_facts = list(date_facts) weekday = (date_facts[0]["start_time"].weekday(), date_facts[0]["start_time"].strftime("%a")) by_weekday.setdefault(weekday, []) start_times, end_times = [], [] for fact in date_facts: start_time = fact["start_time"].time() start_time = start_time.hour * 60 + start_time.minute if fact["end_time"]: end_time = fact["end_time"].time() end_time = end_time.hour * 60 + end_time.minute if start_time < split_minutes: start_time += 24 * 60 if end_time < start_time: end_time += 24 * 60 start_times.append(start_time) end_times.append(end_time) if start_times and end_times: by_weekday[weekday].append((min(start_times), max(end_times))) for day in by_weekday: by_weekday[day] = (sum([fact[0] for fact in by_weekday[day]]) / len(by_weekday[day]), sum([fact[1] for fact in by_weekday[day]]) / len(by_weekday[day])) min_weekday = min([by_weekday[day][0] for day in by_weekday]) max_weekday = max([by_weekday[day][1] for day in by_weekday]) weekday_keys = sorted(by_weekday.keys(), key = lambda x: x[0]) weekdays = [by_weekday[key] for key in weekday_keys] weekday_keys = [key[1] for key in weekday_keys] # get rid of the weekday number as int # starts and ends by category by_category = {} for date, date_facts in groupby(facts, lambda fact: fact["start_time"].date()): date_facts = sorted(list(date_facts), key = lambda x: x["category"]) for category, category_facts in groupby(date_facts, lambda x: x["category"]): category_facts = list(category_facts) by_category.setdefault(category, []) start_times, end_times = [], [] for fact in category_facts: start_time = fact["start_time"] start_time = start_time.hour * 60 + start_time.minute if fact["end_time"]: end_time = fact["end_time"].time() end_time = end_time.hour * 60 + end_time.minute if start_time < split_minutes: start_time += 24 * 60 if end_time < start_time: end_time += 24 * 60 start_times.append(start_time) end_times.append(end_time) if start_times and end_times: by_category[category].append((min(start_times), max(end_times))) for cat in by_category: by_category[cat] = (sum([fact[0] for fact in by_category[cat]]) / len(by_category[cat]), sum([fact[1] for fact in by_category[cat]]) / len(by_category[cat])) min_category = min([by_category[day][0] for day in by_category]) max_category = max([by_category[day][1] for day in by_category]) category_keys = sorted(by_category.keys(), key = lambda x: x[0]) categories = [by_category[key] for key in category_keys] #get starting and ending hours for graph and turn them into exact hours that divide by 3 min_hour = min([min_weekday, min_category]) / 60 * 60 max_hour = max([max_weekday, max_category]) / 60 * 60 self.chart_weekday_starts_ends.plot_day(weekday_keys, weekdays, min_hour, max_hour) self.chart_category_starts_ends.plot_day(category_keys, categories, min_hour, max_hour) #now the factoids! summary = "" # first record if not year: #date format for case when year has not been selected first_date = _("%(first_b)s %(first_d)s, %(first_Y)s") % \ stuff.dateDict(facts[0]["start_time"], "first_") else: #date format when year has been selected first_date = _("%(first_b)s %(first_d)s") % \ stuff.dateDict(facts[0]["start_time"], "first_") summary += _("First activity was recorded on %s.") % \ ("<b>%s</b>" % first_date) # total time tracked total_delta = dt.timedelta(days=0) for fact in facts: total_delta += fact["delta"] if total_delta.days > 1: summary += " " + _("""Time tracked so far is %(human_days)s human days \ (%(human_years)s years) or %(working_days)s working days \ (%(working_years)s years).""") % \ ({"human_days": ("<b>%d</b>" % total_delta.days), "human_years": ("<b>%.2f</b>" % (total_delta.days / 365.0)), "working_days": ("<b>%d</b>" % (total_delta.days * 3)), # 8 should be pretty much an average working day "working_years": ("<b>%.2f</b>" % (total_delta.days * 3 / 365.0))}) # longest fact max_fact = None for fact in facts: if not max_fact or fact["delta"] > max_fact["delta"]: max_fact = fact datedict = stuff.dateDict(max_fact["start_time"], "max_") datedict["hours"] = "<b>%.1f</b>" % (max_fact["delta"].seconds / 60 / 60.0 + max_fact["delta"].days * 24) summary += "\n" + _("Longest continuous work happened on \ %(max_b)s %(max_d)s, %(max_Y)s and was %(hours)s hours.") % datedict # total records (in selected scope) summary += " " + _("There are %s records.") % ("<b>%d</b>" % len(facts)) early_start, early_end = dt.time(5,0), dt.time(9,0) late_start, late_end = dt.time(20,0), dt.time(5,0) fact_count = len(facts) def percent(condition): matches = [fact for fact in facts if condition(fact)] return round(len(matches) / float(fact_count) * 100) early_percent = percent(lambda fact: early_start < fact["start_time"].time() < early_end) late_percent = percent(lambda fact: fact["start_time"].time() > late_start or fact["start_time"].time() < late_end) short_percent = percent(lambda fact: fact["delta"] <= dt.timedelta(seconds = 60 * 15)) if fact_count < 100: summary += "\n\n" + _("Hamster would like to observe you some more!") elif early_percent >= 20: summary += "\n\n" + _("With %s percent of all facts starting before \ 9am you seem to be an early bird." % ("<b>%d</b>" % early_percent)) elif late_percent >= 20: summary += "\n\n" + _("With %s percent of all facts starting after \ 11pm you seem to be a night owl." % ("<b>%d</b>" % late_percent)) elif short_percent >= 20: summary += "\n\n" + _("With %s percent of all tasks being shorter \ than 15 minutes you seem to be a busy bee." % ("<b>%d</b>" % short_percent)) self.explore_summary.set_text(summary)
def __init__(self, path, start_date, end_date): ReportWriter.__init__(self, path, datetime_format = None) dates_dict = stuff.dateDict(start_date, "start_") dates_dict.update(stuff.dateDict(end_date, "end_")) if start_date.year != end_date.year: self.title = _(u"Overview for %(start_B)s %(start_d)s, %(start_Y)s – %(end_B)s %(end_d)s, %(end_Y)s") % dates_dict elif start_date.month != end_date.month: self.title = _(u"Overview for %(start_B)s %(start_d)s – %(end_B)s %(end_d)s, %(end_Y)s") % dates_dict else: self.title = _(u"Overview for %(start_B)s %(start_d)s – %(end_d)s, %(end_Y)s") % dates_dict if start_date == end_date: self.title = _(u"Overview for %(start_B)s %(start_d)s, %(start_Y)s") % dates_dict self.sum_time = {} self.even_row = True """TODO bring template to external file or write to PDF""" self.file.write("""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <meta name="author" content="hamster-applet" /> <title>%(title)s</title> <style type="text/css"> body { font-family: "Sans"; font-size: 12px; padding: 12px; color: #303030; } h1 { border-bottom: 2px solid #303030; padding-bottom: 4px; } h2 { margin-top: 2em; border-bottom: 2px solid #303030; } table { width:800px; } th { font-size: 14px; text-align: center; padding-bottom: 6px; } .smallCell { text-align: center; width: 100px; padding: 3px; } .largeCell { text-align: left; padding: 3px 3px 3px 5px; } .row0 { background-color: #EAE8E3; } .row1 { background-color: #ffffff; } </style> </head> <body> <h1>%(title)s</h1>""" % {"title": self.title} + """ <table> <tr> <th class="smallCell">""" + _("Date") + """</th> <th class="largeCell">""" + _("Activity") + """</th> <th class="smallCell">""" + _("Category") + """</th> <th class="smallCell">""" + _("Start") + """</th> <th class="smallCell">""" + _("End") + """</th> <th class="smallCell">""" + _("Duration") + """</th> <th class="largeCell">""" + _("Description") + """</th> </tr>""")