Exemple #1
0
    def init_stats(self):
        self.stat_facts = runtime.storage.get_facts(dt.date(1970, 1, 2), dt.date.today())

        if not self.stat_facts or self.stat_facts[-1].start_time.year == self.stat_facts[0].start_time.year:
            self.get_widget("explore_controls").hide()
        else:
            by_year = stuff.totals(self.stat_facts,
                                   lambda fact: fact.start_time.year,
                                   lambda fact: 1)

            year_box = self.get_widget("year_box")
            if len(year_box.get_children()) == 0:
                class YearButton(gtk.ToggleButton):
                    def __init__(self, label, year, on_clicked):
                        gtk.ToggleButton.__init__(self, label)
                        self.year = year
                        self.connect("clicked", on_clicked)

                all_button = YearButton(C_("years", "All").encode("utf-8"),
                                        None,
                                        self.on_year_changed)
                year_box.pack_start(all_button)
                self.bubbling = True # TODO figure out how to properly work with togglebuttons as radiobuttons
                all_button.set_active(True)
                self.bubbling = False # TODO figure out how to properly work with togglebuttons as radiobuttons

                years = sorted(by_year.keys())
                for year in years:
                    year_box.pack_start(YearButton(str(year), year, self.on_year_changed))

                year_box.show_all()
Exemple #2
0
    def init_stats(self):
        self.stat_facts = runtime.storage.get_facts(dt.date(1970, 1, 2),
                                                    dt.date.today())

        if not self.stat_facts or self.stat_facts[
                -1].start_time.year == self.stat_facts[0].start_time.year:
            self.get_widget("explore_controls").hide()
        else:
            by_year = stuff.totals(self.stat_facts,
                                   lambda fact: fact.start_time.year,
                                   lambda fact: 1)

            year_box = self.get_widget("year_box")
            if len(year_box.get_children()) == 0:

                class YearButton(gtk.ToggleButton):
                    def __init__(self, label, year, on_clicked):
                        gtk.ToggleButton.__init__(self, label)
                        self.year = year
                        self.connect("clicked", on_clicked)

                all_button = YearButton(
                    C_("years", "All").encode("utf-8"), None,
                    self.on_year_changed)
                year_box.pack_start(all_button)
                self.bubbling = True  # TODO figure out how to properly work with togglebuttons as radiobuttons
                all_button.set_active(True)
                self.bubbling = False  # TODO figure out how to properly work with togglebuttons as radiobuttons

                years = sorted(by_year.keys())
                for year in years:
                    year_box.pack_start(
                        YearButton(str(year), year, self.on_year_changed))

                year_box.show_all()
Exemple #3
0
    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(_(u"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
        durations = [(fact.start_time, fact.delta) for fact in facts]
        self.timechart.draw(durations, facts[0].date, facts[-1].date)


        # 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:
            n = len(by_weekday[day])
            # calculate mean and variance for starts and ends
            means = (sum([fact[0] for fact in by_weekday[day]]) / n,
                     sum([fact[1] for fact in by_weekday[day]]) / n)
            variances = (sum([(fact[0] - means[0]) ** 2 for fact in by_weekday[day]]) / n,
                         sum([(fact[1] - means[1]) ** 2 for fact in by_weekday[day]]) / n)

            # In the normal distribution, the range from
            # (mean - standard deviation) to infinit, or from
            # -infinit to (mean + standard deviation),  has an accumulated
            # probability of 84.1%. Meaning we are using the place where if we
            # picked a random start(or end), 84.1% of the times it will be
            # inside the range.
            by_weekday[day] = (int(means[0] - math.sqrt(variances[0])),
                               int(means[1] + math.sqrt(variances[1])))

        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:
            # For explanation see the comments in the starts and ends by day
            n = len(by_category[cat])
            means = (sum([fact[0] for fact in by_category[cat]]) / n,
                     sum([fact[1] for fact in by_category[cat]]) / n)
            variances = (sum([(fact[0] - means[0]) ** 2 for fact in by_category[cat]]) / n,
                         sum([(fact[1] - means[1]) ** 2 for fact in by_category[cat]]) / n)

            by_category[cat] = (int(means[0] - math.sqrt(variances[0])),
                                int(means[1] + math.sqrt(variances[1])))

        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 the first record if the year has not been selected
            # Using python datetime formatting syntax. See:
            # http://docs.python.org/library/time.html#time.strftime
            first_date = facts[0].start_time.strftime(C_("first record", "%b %d, %Y"))
        else:
            # date of first record when year has been selected
            # Using python datetime formatting syntax. See:
            # http://docs.python.org/library/time.html#time.strftime
            first_date = facts[0].start_time.strftime(C_("first record", "%b %d"))

        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:
            human_years_str = ngettext("%(num)s year",
                                       "%(num)s years",
                                       total_delta.days / 365) % {
                              'num': "<b>%s</b>" % locale.format("%.2f", (total_delta.days / 365.0))}
            working_years_str = ngettext("%(num)s year",
                                         "%(num)s years",
                                         total_delta.days * 3 / 365) % {
                         'num': "<b>%s</b>" % locale.format("%.2f",  (total_delta.days * 3 / 365.0)) }
            #FIXME: difficult string to properly pluralize
            summary += " " + _("""Time tracked so far is %(human_days)s human days \
(%(human_years)s) or %(working_days)s working days (%(working_years)s).""") % {
              "human_days": ("<b>%d</b>" % total_delta.days),
              "human_years": human_years_str,
              "working_days": ("<b>%d</b>" % (total_delta.days * 3)), # 8 should be pretty much an average working day
              "working_years": working_years_str }


        # longest fact
        max_fact = None
        for fact in facts:
            if not max_fact or fact.delta > max_fact.delta:
                max_fact = fact

        longest_date = max_fact.start_time.strftime(
            # How the date of the longest activity should be displayed in statistics
            # Using python datetime formatting syntax. See:
            # http://docs.python.org/library/time.html#time.strftime
            C_("date of the longest activity", "%b %d, %Y"))

        num_hours = max_fact.delta.seconds / 60 / 60.0 + max_fact.delta.days * 24
        hours = "<b>%s</b>" % locale.format("%.1f", num_hours)

        summary += "\n" + ngettext("Longest continuous work happened on \
%(date)s and was %(hours)s hour.",
                                  "Longest continuous work happened on \
%(date)s and was %(hours)s hours.",
                                  int(num_hours)) % {"date": longest_date,
                                                     "hours": hours}

        # total records (in selected scope)
        summary += " " + ngettext("There is %s record.",
                                  "There are %s records.",
                                  len(facts)) % ("<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 activities 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 activities 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 activities being shorter \
than 15 minutes, you seem to be a busy bee.") % ("<b>%d</b>" % short_percent)

        self.explore_summary.set_text(summary)
Exemple #4
0
    def init_stats(self):
        self.stat_facts = runtime.storage.get_facts(dt.date(1970, 1, 2), dt.date.today())

        if not self.stat_facts or self.stat_facts[-1].start_time.year == self.stat_facts[0].start_time.year:
            self.get_widget("explore_controls").hide()
        else:
            by_year = stuff.totals(self.stat_facts,
                                   lambda fact: fact.start_time.year,
                                   lambda fact: 1)

            year_box = self.get_widget("year_box")
            class YearButton(gtk.ToggleButton):
                def __init__(self, label, year, on_clicked):
                    gtk.ToggleButton.__init__(self, label)
                    self.year = year
                    self.connect("clicked", on_clicked)

            all_button = YearButton(C_("years", "All").encode("utf-8"),
                                    None,
                                    self.on_year_changed)
            year_box.pack_start(all_button)
            self.bubbling = True # TODO figure out how to properly work with togglebuttons as radiobuttons
            all_button.set_active(True)
            self.bubbling = False # TODO figure out how to properly work with togglebuttons as radiobuttons

            years = sorted(by_year.keys())
            for year in years:
                year_box.pack_start(YearButton(str(year), year, self.on_year_changed))

            year_box.show_all()

        self.chart_category_totals = charting.Chart(value_format = "%.1f",
                                                       max_bar_width = 20,
                                                       legend_width = 70,
                                                       interactive = False)
        self.get_widget("explore_category_totals").add(self.chart_category_totals)


        self.chart_weekday_totals = charting.Chart(value_format = "%.1f",
                                                      max_bar_width = 20,
                                                      legend_width = 70,
                                                      interactive = False)
        self.get_widget("explore_weekday_totals").add(self.chart_weekday_totals)

        self.chart_weekday_starts_ends = charting.HorizontalDayChart(max_bar_width = 20,
                                                                     legend_width = 70)
        self.get_widget("explore_weekday_starts_ends").add(self.chart_weekday_starts_ends)

        self.chart_category_starts_ends = charting.HorizontalDayChart(max_bar_width = 20,
                                                                      legend_width = 70)
        self.get_widget("explore_category_starts_ends").add(self.chart_category_starts_ends)




        #ah, just want summary look just like all the other text on the page
        class CairoText(graphics.Scene):
            def __init__(self):
                graphics.Scene.__init__(self)
                self.text = ""
                self.label = graphics.Label(self.text, 10)
                self.label.wrap = pango.WRAP_WORD
                self.add_child(self.label)
                self.connect("on-enter-frame", self.on_enter_frame)

            def set_text(self, text):
                self.label.text = text
                self.redraw()

            def on_enter_frame(self, scene, context):
                # now for the text - we want reduced contrast for relaxed visuals
                fg_color = self.get_style().fg[gtk.STATE_NORMAL].to_string()
                self.label.color = self.colors.contrast(fg_color,  80)

                self.label.width = self.width


        self.explore_summary = CairoText()
        self.get_widget("explore_summary").add(self.explore_summary)
        self.get_widget("explore_summary").show_all()
Exemple #5
0
    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(
                    _(u"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
        durations = [(fact.start_time, fact.delta) for fact in facts]
        self.timechart.draw(durations, facts[0].date, facts[-1].date)

        # 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:
            n = len(by_weekday[day])
            # calculate mean and variance for starts and ends
            means = (sum([fact[0] for fact in by_weekday[day]]) / n,
                     sum([fact[1] for fact in by_weekday[day]]) / n)
            variances = (sum([(fact[0] - means[0])**2
                              for fact in by_weekday[day]]) / n,
                         sum([(fact[1] - means[1])**2
                              for fact in by_weekday[day]]) / n)

            # In the normal distribution, the range from
            # (mean - standard deviation) to infinit, or from
            # -infinit to (mean + standard deviation),  has an accumulated
            # probability of 84.1%. Meaning we are using the place where if we
            # picked a random start(or end), 84.1% of the times it will be
            # inside the range.
            by_weekday[day] = (int(means[0] - math.sqrt(variances[0])),
                               int(means[1] + math.sqrt(variances[1])))

        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:
            # For explanation see the comments in the starts and ends by day
            n = len(by_category[cat])
            means = (sum([fact[0] for fact in by_category[cat]]) / n,
                     sum([fact[1] for fact in by_category[cat]]) / n)
            variances = (sum([(fact[0] - means[0])**2
                              for fact in by_category[cat]]) / n,
                         sum([(fact[1] - means[1])**2
                              for fact in by_category[cat]]) / n)

            by_category[cat] = (int(means[0] - math.sqrt(variances[0])),
                                int(means[1] + math.sqrt(variances[1])))

        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 the first record if the year has not been selected
            # Using python datetime formatting syntax. See:
            # http://docs.python.org/library/time.html#time.strftime
            first_date = facts[0].start_time.strftime(
                C_("first record", "%b %d, %Y"))
        else:
            # date of first record when year has been selected
            # Using python datetime formatting syntax. See:
            # http://docs.python.org/library/time.html#time.strftime
            first_date = facts[0].start_time.strftime(
                C_("first record", "%b %d"))

        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:
            human_years_str = ngettext(
                "%(num)s year", "%(num)s years", total_delta.days / 365) % {
                    'num':
                    "<b>%s</b>" % locale.format("%.2f",
                                                (total_delta.days / 365.0))
                }
            working_years_str = ngettext(
                "%(num)s year", "%(num)s years",
                total_delta.days * 3 / 365) % {
                    'num':
                    "<b>%s</b>" % locale.format("%.2f",
                                                (total_delta.days * 3 / 365.0))
                }
            #FIXME: difficult string to properly pluralize
            summary += " " + _(
                """Time tracked so far is %(human_days)s human days \
(%(human_years)s) or %(working_days)s working days (%(working_years)s).""") % {
                    "human_days": ("<b>%d</b>" % total_delta.days),
                    "human_years": human_years_str,
                    "working_days":
                    ("<b>%d</b>" % (total_delta.days * 3)
                     ),  # 8 should be pretty much an average working day
                    "working_years": working_years_str
                }

        # longest fact
        max_fact = None
        for fact in facts:
            if not max_fact or fact.delta > max_fact.delta:
                max_fact = fact

        longest_date = max_fact.start_time.strftime(
            # How the date of the longest activity should be displayed in statistics
            # Using python datetime formatting syntax. See:
            # http://docs.python.org/library/time.html#time.strftime
            C_("date of the longest activity", "%b %d, %Y"))

        num_hours = max_fact.delta.seconds / 60 / 60.0 + max_fact.delta.days * 24
        hours = "<b>%s</b>" % locale.format("%.1f", num_hours)

        summary += "\n" + ngettext(
            "Longest continuous work happened on \
%(date)s and was %(hours)s hour.", "Longest continuous work happened on \
%(date)s and was %(hours)s hours.", int(num_hours)) % {
                "date": longest_date,
                "hours": hours
            }

        # total records (in selected scope)
        summary += " " + ngettext("There is %s record.",
                                  "There are %s records.",
                                  len(facts)) % ("<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 activities 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 activities 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 activities being shorter \
than 15 minutes, you seem to be a busy bee.") % ("<b>%d</b>" % short_percent)

        self.explore_summary.set_text(summary)