""" A temporary report to compute lifespan and the relative ages of parents and children for later determining a new "probably alive". """ import os import gtk, gtk.glade from PluginUtils import register_report from ReportBase import CATEGORY_VIEW, MODE_GUI import ManagedWindow __AUTHOR__ = "Doug Blank " __VERSION__ = "$Revision: 3 $" xmlgui = """ True GTK_WINDOW_TOPLEVEL GTK_WIN_POS_NONE False 700 700 True False True False False GDK_WINDOW_TYPE_HINT_DIALOG GDK_GRAVITY_NORTH_WEST True False False True False 0 True GTK_BUTTONBOX_END True True True gtk-close True GTK_RELIEF_NORMAL True 0 0 False True GTK_PACK_END 6 True False 6 True False False GTK_JUSTIFY_CENTER False False 0.5 0.5 0 0 PANGO_ELLIPSIZE_NONE -1 False 0 8 False False True True GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_OUT GTK_CORNER_TOP_LEFT True True True False True GTK_JUSTIFY_LEFT GTK_WRAP_NONE True 0 0 0 0 0 0 0 True True 0 True True """ def ticks(width, start = 0, stop = 100, fill = " "): """ Returns the tickmark numbers for a graph axis """ count = int(width / 10.0) retval = "%-3d" % start space = int((width - count * 3) / float(count - 1)) incr = (stop - start) / float(count - 1) lastincr = 0 for i in range(count - 2): retval += " " * space newincr = int(start + (i + 1) * incr) if newincr != lastincr: retval += "%3d" % newincr else: retval += " | " lastincr = newincr rest = width - len(retval) - 3 + 1 retval += " " * rest retval += "%3d" % int(stop) return retval def format(text, width, align = "left", borders = "||", fill = " "): """ Returns a formatted string for nice, fixed-font display """ if align == "center": text = text.center(width, fill) elif align == "left": text = (text + (fill * width))[:width] elif align == "right": text = ((fill * width) + text)[-width:] if borders[0] != None: text = borders[0] + text if borders[1] != None: text = text + borders[1] return text def compute_stats(hash): """ Returns the statistics of a dictionary of data """ hashkeys = hash.keys() hashkeys.sort() count = sum(hash.values()) sumval = sum([k * hash[k] for k in hash]) minval = min(hashkeys) maxval = max(hashkeys) median = 0 average = 0 if count > 0: current = 0 for k in hashkeys: if current + hash[k] > count/2: median = k break current += hash[k] average = sumval/float(count) retval = "Statistics:\n" retval += " Total : %d\n" % count retval += " Minimum: %d\n" % minval retval += " Average: %.2f\n" % average retval += " Median : %d\n" % median retval += " Maximum: %d\n" % maxval return retval def create_bargraph(hash, title, column, graph_width, bin_size, max_val): """ Create a bargraph based on the data in hash. hash is a dict, like: hash = {12: 4, 20: 6, 35: 13, 50: 5} where the key is the age, and the value stored is the count. """ # first, binify: bin = [0] * (max_val/bin_size) for value in hash.keys(): bin[value/bin_size] += hash[value] text = "" max_bin = float(max(bin)) if max_bin != 0: i = 0 text += "--------" + format("", graph_width, fill = "-", borders="++") + "\n" text += column.center(8) + format(title, graph_width, align="center") + "\n" text += "--------" + format("", graph_width, fill = "-", borders="++") + "\n" for bin in bin: text += (" %3d-%3d" % (i * 5, (i+1)* 5,)) + format("X" * int(bin/max_bin * graph_width), graph_width) + "\n" i += 1 text += "--------" + format("", graph_width, fill = "-", borders="++") + "\n" text += " Counts " + ticks(graph_width, start = 0, stop = int(max_bin)) + "\n\n" text += compute_stats(hash) text += "\n" return text class AgeStats(ManagedWindow.ManagedWindow): def __init__(self, dbstate, uistate, person): self.title = "Probably Alive Statistics" ManagedWindow.ManagedWindow.__init__(self, uistate, [], self.__class__) self.database = dbstate.db topDialog = gtk.glade.xml_new_from_buffer(xmlgui, len(xmlgui), "summary", "gramps") #glade_file = "%s/summary.glade" % os.path.dirname(__file__) #topDialog = gtk.glade.XML(glade_file, "summary", "gramps") topDialog.signal_autoconnect({"destroy_passed_object" : self.close}) window = topDialog.get_widget("summary") title = topDialog.get_widget("title") self.set_window(window, title, self.title) handles = self.database.get_person_handles(sort_handles=False) age_dict = {} mother_dict = {} father_dict = {} text = "" for h in handles: p = self.database.get_person_from_handle(h) # if birth_date and death_date, compute age birth_ref = p.get_birth_ref() birth_date = None if birth_ref: birth_event = self.database.get_event_from_handle(birth_ref.ref) birth_date = birth_event.get_date_object() death_ref = p.get_death_ref() death_date = None if death_ref: death_event = self.database.get_event_from_handle(death_ref.ref) death_date = death_event.get_date_object() if death_date and birth_date and birth_date.get_year() != 0: age = death_date.get_year() - birth_date.get_year() if age >= 0 and age < 120: age_dict[age] = age_dict.get(age, 0) + 1 else: print "Age out of range: %d for %s" % (age, p.get_primary_name().get_first_name() + " " + p.get_primary_name().get_surname()) # for each parent m/f: family_list = p.get_parent_family_handle_list() if len(family_list) > 0: family = self.database.get_family_from_handle(family_list[0]) # first is primary, I think if family: f_handle = family.get_father_handle() m_handle = family.get_mother_handle() # if they have a birth_date, compute difference each m/f if f_handle: f = self.database.get_person_from_handle(f_handle) bref = f.get_birth_ref() if bref: bevent = self.database.get_event_from_handle(bref.ref) bdate = bevent.get_date_object() if bdate and birth_date and birth_date.get_year() != 0: diff = birth_date.get_year() - bdate.get_year() if diff >= 0 and diff < 60: father_dict[diff] = father_dict.get(diff, 0) + 1 else: print "Father diff out of range: %d for %s" % (diff, p.get_primary_name().get_first_name() + " " + p.get_primary_name().get_surname()) if m_handle: m = self.database.get_person_from_handle(m_handle) bref = m.get_birth_ref() if bref: bevent = self.database.get_event_from_handle(bref.ref) bdate = bevent.get_date_object() if bdate and birth_date and birth_date.get_year() != 0: diff = birth_date.get_year() - bdate.get_year() if diff >= 0 and diff < 60: mother_dict[diff] = mother_dict.get(diff, 0) + 1 else: print "Mother diff out of range: %d for %s" % (diff, p.get_primary_name().get_first_name() + " " + p.get_primary_name().get_surname()) width = 60 graph_width = width - 8 text += create_bargraph(age_dict, "Lifespan Age Distribution", "Age", graph_width, 5, 120) text += create_bargraph(father_dict, "Father - Child Age Diff Distribution", "Diff", graph_width, 5, 60) text += create_bargraph(mother_dict, "Mother - Child Age Diff Distribution", "Diff", graph_width, 5, 60) textwindow = topDialog.get_widget("textwindow") # Load a fixed font buffer = textwindow.get_buffer() tag = buffer.create_tag("title") tag.set_property("font", "Courier 12") buffer.set_text(text) start, end = buffer.get_bounds() buffer.apply_tag_by_name("title", start, end) self.show() def build_menu_names(self, obj): return (self.title, None) register_report( name = 'age_stats', category = CATEGORY_VIEW, report_class = AgeStats, options_class = None, modes = MODE_GUI, translated_name = "Lifespan Analysis", status = "Stable", description= "Computes the relative ages of parents and children" )