[hamster-applet] an attempt to clean up and refactor the overview page. no changes in UI.
- From: Toms Baugis <tbaugis src gnome org>
- To: svn-commits-list gnome org
- Subject: [hamster-applet] an attempt to clean up and refactor the overview page. no changes in UI.
- Date: Thu, 18 Jun 2009 17:10:54 -0400 (EDT)
commit 0f5fb72a3d3b3b9301c6d3ed2539c911cb76eaf7
Author: Toms Bauģis <toms baugis gmail com>
Date: Thu Jun 18 22:08:54 2009 +0100
an attempt to clean up and refactor the overview page. no changes in UI.
hamster/db.py | 15 --
hamster/stats.py | 388 +++++++++++++++++++---------------------------------
hamster/storage.py | 3 -
3 files changed, 141 insertions(+), 265 deletions(-)
---
diff --git a/hamster/db.py b/hamster/db.py
index a41768b..95fbddd 100644
--- a/hamster/db.py
+++ b/hamster/db.py
@@ -520,21 +520,6 @@ class Storage(storage.Storage):
"""
return self.fetchall(query, (_("Unsorted"), ))
- def __get_interval_activity_ids(self, date, end_date = None):
- """returns activities used in the specified interval"""
- query = """
- SELECT a.name, coalesce(b.name, ?) as category_name
- FROM activities a
- LEFT JOIN categories b on b.id = a.category_id
- WHERE a.id in (SELECT activity_id from facts
- WHERE (date(start_time) >= ? and date(start_time) <= ?)
- OR (date(end_time) >= ? and date(end_time) <= ?))
- """
- end_date = end_date or date
-
- return self.fetchall(query, (_("Unsorted"), date, end_date, date, end_date))
-
-
def __remove_fact(self, fact_id):
query = """
DELETE FROM facts
diff --git a/hamster/stats.py b/hamster/stats.py
index 849abd9..b14a965 100644
--- a/hamster/stats.py
+++ b/hamster/stats.py
@@ -314,55 +314,55 @@ class StatsViewer(object):
self.fact_store = gtk.TreeStore(int, str, str, str, str, str, gobject.TYPE_PYOBJECT)
self.setup_tree()
+
+ #graphs
self.background = (0.975,0.975,0.975)
self.get_widget("graph_frame").modify_bg(gtk.STATE_NORMAL,
gtk.gdk.Color(*[int(b*65536.0) for b in self.background]))
+ self.get_widget("explore_frame").modify_bg(gtk.STATE_NORMAL,
+ gtk.gdk.Color(*[int(b*65536.0) for b in self.background]))
+
-
x_offset = 90 # let's nicely align all graphs
self.category_chart = charting.BarChart(background = self.background,
bar_base_color = (238,221,221),
- bars_beveled = False,
legend_width = x_offset,
max_bar_width = 35,
show_stack_labels = True
)
- category_box = self.get_widget("totals_by_category")
- category_box.add(self.category_chart)
- category_box.set_size_request(130, -1)
+ self.get_widget("totals_by_category").add(self.category_chart)
self.day_chart = charting.BarChart(background = self.background,
- bar_base_color = (220, 220, 220),
- bars_beveled = False,
- show_scale = True,
- max_bar_width = 35,
- grid_stride = 4,
- legend_width = 20)
+ bar_base_color = (220, 220, 220),
+ show_scale = True,
+ max_bar_width = 35,
+ grid_stride = 4,
+ legend_width = 20)
self.get_widget("totals_by_day").add(self.day_chart)
self.activity_chart = charting.HorizontalBarChart(orient = "horizontal",
- max_bar_width = 25,
- values_on_bars = True,
- stretch_grid = True,
- legend_width = x_offset,
- value_format = "%.1f",
- background = self.background,
- bars_beveled = False,
- animate = False)
+ max_bar_width = 25,
+ values_on_bars = True,
+ stretch_grid = True,
+ legend_width = x_offset,
+ value_format = "%.1f",
+ background = self.background,
+ bars_beveled = False,
+ animate = False)
self.get_widget("totals_by_activity").add(self.activity_chart);
self.view_date = dt.date.today()
- #set to monday
+ #set to monday
self.start_date = self.view_date - \
dt.timedelta(self.view_date.weekday() + 1)
# look if we need to start on sunday or monday
self.start_date = self.start_date + \
- dt.timedelta(self.locale_first_weekday())
+ dt.timedelta(stuff.locale_first_weekday())
self.end_date = self.start_date + dt.timedelta(6)
@@ -386,25 +386,23 @@ class StatsViewer(object):
self._gui.connect_signals(self)
self.fact_tree.grab_focus()
+ self.timeline = TimeLine()
+ self.get_widget("explore_everything").add(self.timeline)
+ self.get_widget("explore_everything").show_all()
+
+
self.report_chooser = None
self.do_graph()
self.init_stats()
def init_stats(self):
- self.get_widget("explore_frame").modify_bg(gtk.STATE_NORMAL,
- gtk.gdk.Color(*[int(b*65536.0) for b in self.background]))
-
- if not self.stat_facts:
- self.stat_facts = runtime.storage.get_facts(dt.date(1970, 1, 1), dt.date.today())
+ self.stat_facts = runtime.storage.get_facts(dt.date(1970, 1, 1), dt.date.today())
- by_year = self._totals(self.stat_facts,
+ by_year = stuff.totals(self.stat_facts,
lambda fact: fact["start_time"].year,
lambda fact: 1)
year_box = self.get_widget("year_box")
- for child in year_box.get_children():
- year_box.remove(child)
-
class YearButton(gtk.ToggleButton):
def __init__(self, label, year, on_clicked):
gtk.ToggleButton.__init__(self, label)
@@ -423,10 +421,6 @@ class StatsViewer(object):
year_box.show_all()
- self.timeline = TimeLine()
- self.get_widget("explore_everything").add(self.timeline)
- self.get_widget("explore_everything").show_all()
-
self.chart_category_totals = charting.HorizontalBarChart(value_format = "%.1f",
bars_beveled = False,
background = self.background,
@@ -486,32 +480,10 @@ class StatsViewer(object):
self.context.show_layout(self.layout)
-
self.explore_summary = CairoText(self.background)
self.get_widget("explore_summary").add(self.explore_summary)
self.get_widget("explore_summary").show_all()
- def on_pages_switch_page(self, notebook, page, pagenum):
- if pagenum == 1:
- year = None
- for child in self.get_widget("year_box").get_children():
- if child.get_active():
- year = child.year
-
- self.stats(year)
-
-
- def on_year_changed(self, button):
- if self.bubbling: return
-
- for child in button.parent.get_children():
- if child != button and child.get_active():
- self.bubbling = True
- child.set_active(False)
- self.bubbling = False
-
- self.stats(button.year)
-
def stats(self, year = None):
facts = self.stat_facts
if year:
@@ -541,24 +513,22 @@ A week of usage would be nice!"""))
# Totals by category
- categories = self._totals(facts,
+ categories = stuff.totals(facts,
lambda fact: fact["category"],
lambda fact: fact['delta'].seconds / 60 / 60.0)
category_keys = sorted(categories.keys())
- categories = [categories[key][0] for key in category_keys]
+ categories = [categories[key] for key in category_keys]
self.chart_category_totals.plot(category_keys, categories)
-
# Totals by weekday
- weekdays = self._totals(facts,
- lambda fact: (fact["start_time"].weekday(),
- fact["start_time"].strftime("%a")),
- lambda fact: fact['delta'].seconds / 60 / 60.0)
-
+ 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][0] for key in weekday_keys] #get values in the order
+ 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)
@@ -721,35 +691,6 @@ than 15 minutes you seem to be a busy bee." % ("<b>%d</b>" % short_percent))
self.explore_summary.set_text(summary)
-
-
- def _totals(self, iter, keyfunc, sumfunc):
- """groups items by field described in keyfunc and counts totals using value
- from sumfunc
- """
- data = sorted(iter, key=keyfunc)
-
- totals = {}
- max_total = -10000
- for k, group in groupby(data, keyfunc):
- totals[k] = sum([sumfunc(entry) for entry in group])
- max_total = max(max_total, totals[k])
-
- for total in totals: #add normalized version too
- totals[total] = (totals[total], totals[total] / float(max_total))
-
- return totals
-
- def more_on_left(self):
- z = min(round((self.end_date - self.start_date).days / 21.0)+1, 5)
- self.start_date = self.start_date - dt.timedelta(days = z)
- self.do_graph()
-
- def less_on_left(self):
- z = min(round((self.end_date - self.start_date).days / 21.0)+1, 5)
- self.start_date = self.start_date + dt.timedelta(days=z)
- self.do_graph()
-
def setup_tree(self):
def parent_painter(column, cell, model, iter):
cell_text = model.get_value(iter, 1)
@@ -809,159 +750,116 @@ than 15 minutes you seem to be a busy bee." % ("<b>%d</b>" % short_percent))
self.fact_tree.append_column(timeColumn)
self.fact_tree.set_model(self.fact_store)
+
+ 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"])
- def locale_first_weekday(self):
- """figure if week starts on monday or sunday"""
- import os
- first_weekday = 6 #by default settle on monday
-
- try:
- process = os.popen("locale first_weekday week-1stday")
- week_offset, week_start = process.read().split('\n')[:2]
- process.close()
- week_start = dt.date(*time.strptime(week_start, "%Y%m%d")[:3])
- week_offset = dt.timedelta(int(week_offset) - 1)
- beginning = week_start + week_offset
- first_weekday = int(beginning.strftime("%w"))
- except:
- print "WARNING - Failed to get first weekday from locale"
- pass
-
- return first_weekday
- def get_facts(self, facts):
- self.fact_store.clear()
- totals = {}
-
- by_activity = {}
- by_category = {}
- by_day = {}
-
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])
- by_day[self.start_date + dt.timedelta(i)] = {"duration": 0, "row_pointer": day_row}
-
-
-
-
- for fact in facts:
- start_date = fact["date"]
-
- duration = None
- if fact["delta"]:
- duration = 24 * fact["delta"].days + fact["delta"].seconds / 60
-
- self.fact_store.append(by_day[start_date]["row_pointer"],
- [fact["id"],
- fact["start_time"].strftime('%H:%M') + " " +
- fact["name"],
- stuff.format_duration(duration),
- fact["start_time"].strftime('%Y-%m-%d'),
- fact["description"],
- fact["category"],
- fact
- ])
-
- if duration:
- by_day[start_date]["duration"] += duration
-
-
- for day in by_day:
- self.fact_store.set_value(by_day[day]["row_pointer"], 2,
- stuff.format_duration(by_day[day]["duration"]))
+ 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()
-
- self.get_widget("report_button").set_sensitive(len(facts) > 0)
-
- def get_totals(self, facts, all_days):
- # get list of used activities in interval
- activities = [act[0] for act in
- runtime.storage.get_interval_activity_ids(self.start_date, self.end_date)]
+ 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)]
- # fill in the activity totals blanks
- # don't want to add ability to be able to specify color per bar
- # so we will be disguising our bar chart as multibar chart
- activity_totals = {}
- for act in activities:
- activity_totals[act] = {}
- for cat in self.popular_categories:
- activity_totals[act][cat] = 0
+ 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]
- # fill in the category totals blanks
- day_category_totals = {}
- for day in all_days:
- day_category_totals[day] = {}
- for cat in self.popular_categories:
- day_category_totals[day][cat] = 0
-
- #now we do the counting
- for fact in facts:
- duration = None
- start_date = fact['date']
-
- duration = fact["delta"].days * 24 * 60 + fact["delta"].seconds / 60
+ #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]
- activity_totals[fact['name']][fact['category']] += duration
- day_category_totals[start_date][fact['category']] += duration
+ self.day_chart.plot(day_keys, res, stack_keys = all_categories)
- # convert dictionaries into lists so we don't have to care about keys anymore
- res_categories = []
- for day in all_days:
- res_categories.append([day_category_totals[day][cat] / 60.0
- for cat in self.popular_categories])
-
- #sort activities by duration, longest first
- activity_totals = activity_totals.items()
- activity_totals = sorted(activity_totals,
- key = lambda(k,v): (max(v.values()), k),
- reverse = True)
-
- activities = [] #we have changed the order
- res_activities = []
- for act in activity_totals:
- activities.append(act[0])
- res_activities.append([act[1][cat] / 60.0
- for cat in self.popular_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]
- return {'keys': activities, 'values': res_activities}, \
- {'keys': self.popular_categories, 'values': res_categories}
+ 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 do_graph(self):
+ 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
+ # 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
+ # 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
@@ -971,60 +869,56 @@ than 15 minutes you seem to be a busy bee." % ("<b>%d</b>" % short_percent))
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))
- label = self.get_widget("overview_label")
- label.set_markup("<b>%s</b>" % overview_label)
- label2 = self.get_widget("dayview_caption")
- label2.set_markup("%s" % (dayview_caption))
+ def do_graph(self):
+ self.set_title()
- fact_list = runtime.storage.get_facts(self.start_date, self.end_date)
+ facts = runtime.storage.get_facts(self.start_date, self.end_date)
- self.get_facts(fact_list)
+ self.get_widget("report_button").set_sensitive(len(facts) > 0)
+ self.fact_store.clear()
- if not fact_list:
+ if not facts:
self.get_widget("graphs").hide()
self.get_widget("no_data_label").show()
return
- else:
- self.get_widget("graphs").show()
- self.get_widget("no_data_label").hide()
-
- all_days = [self.start_date + dt.timedelta(i)
- for i in range((self.end_date - self.start_date).days + 1)]
+ self.fill_tree(facts)
- activity_totals, day_category_totals = self.get_totals(fact_list, all_days)
+ self.get_widget("no_data_label").hide()
+ self.get_widget("graphs").show()
+ self.do_charts(facts)
+
-
- self.activity_chart.plot(activity_totals['keys'],
- activity_totals['values'],
- stack_keys = self.popular_categories)
+ def get_widget(self, name):
+ """ skip one variable (huh) """
+ return self._gui.get_object(name)
- #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]
+ def on_pages_switch_page(self, notebook, page, pagenum):
+ if pagenum == 1:
+ year = None
+ for child in self.get_widget("year_box").get_children():
+ if child.get_active():
+ year = child.year
+
+ self.stats(year)
else:
- day_keys = [_("%(m_b)s %(m_d)s") % stuff.dateDict(day, "m_") for day in all_days]
-
- self.day_chart.plot(day_keys, day_category_totals['values'],
- stack_keys = day_category_totals['keys'])
-
- category_totals = [[sum(value) for value in zip(*day_category_totals['values'])]]
+ self.do_graph()
- category_keys = []
- for i in range(len(day_category_totals['keys'])):
- category_keys.append("%s %.1f" % (day_category_totals['keys'][i], category_totals[0][i]))
+ def on_year_changed(self, button):
+ if self.bubbling: return
-
- self.category_chart.plot([_("Total")], category_totals,
- stack_keys = category_keys)
-
-
- def get_widget(self, name):
- """ skip one variable (huh) """
- return self._gui.get_object(name)
+ for child in button.parent.get_children():
+ if child != button and child.get_active():
+ self.bubbling = True
+ child.set_active(False)
+ self.bubbling = False
+
+ self.stats(button.year)
def on_prev_clicked(self, button):
if self.week_view.get_active():
@@ -1056,7 +950,7 @@ than 15 minutes you seem to be a busy bee." % ("<b>%d</b>" % short_percent))
self.view_date = dt.date.today()
if self.week_view.get_active():
self.start_date = self.view_date - dt.timedelta(self.view_date.weekday() + 1)
- self.start_date = self.start_date + dt.timedelta(self.locale_first_weekday())
+ self.start_date = self.start_date + dt.timedelta(stuff.locale_first_weekday())
self.end_date = self.start_date + dt.timedelta(6)
elif self.month_view.get_active():
@@ -1073,7 +967,7 @@ than 15 minutes you seem to be a busy bee." % ("<b>%d</b>" % short_percent))
def on_week_toggled(self, button):
self.start_date = self.view_date - dt.timedelta(self.view_date.weekday() + 1)
- self.start_date = self.start_date + dt.timedelta(self.locale_first_weekday())
+ self.start_date = self.start_date + dt.timedelta(stuff.locale_first_weekday())
self.end_date = self.start_date + dt.timedelta(6)
self.do_graph()
diff --git a/hamster/storage.py b/hamster/storage.py
index 1b3dfa0..edb0297 100644
--- a/hamster/storage.py
+++ b/hamster/storage.py
@@ -52,9 +52,6 @@ class Storage(object):
def get_popular_categories(self):
return self.__get_popular_categories()
- def get_interval_activity_ids(self, date, end_date = None):
- return self.__get_interval_activity_ids(date, end_date)
-
def remove_fact(self, fact_id):
fact = self.get_fact(fact_id)
if fact:
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]