# # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2003-2006 Donald N. Allingham # Copyright (C) 2004-2005 Eero Tamminen # Copyright (C) 2007-2008 Brian G. Matherly # Copyright (C) 2008 Peter Landgren # Copyright (C) 2010 Jakim Friant # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # # $Id$ """Reports/Graphical Reports/Statistics Report""" #------------------------------------------------------------------------ # # python modules # #------------------------------------------------------------------------ import time from gen.ggettext import sgettext as _ #------------------------------------------------------------------------ # # GRAMPS modules # #------------------------------------------------------------------------ # Person and relation types from gen.lib import Person, FamilyRelType, EventType, EventRoleType from gen.lib.date import Date # gender and report type names from gen.plug.docgen import (FontStyle, ParagraphStyle, GraphicsStyle, FONT_SANS_SERIF, FONT_SERIF, PARA_ALIGN_CENTER, PARA_ALIGN_LEFT) from gen.plug.menu import BooleanOption, NumberOption, EnumeratedListOption, \ FilterOption, PersonOption from gen.plug.report import Report from gen.plug.report import utils as ReportUtils from gui.plug.report import MenuReportOptions import DateHandler from gui.utils import ProgressMeter #------------------------------------------------------------------------ # # Private Functions # #------------------------------------------------------------------------ def draw_wedge(doc, style, centerx, centery, radius, start_angle, end_angle, short_radius=0): from math import pi, cos, sin while end_angle < start_angle: end_angle += 360 p = [] degreestoradians = pi / 180.0 radiansdelta = degreestoradians / 2 sangle = start_angle * degreestoradians eangle = end_angle * degreestoradians while eangle < sangle: eangle = eangle + 2 * pi angle = sangle if short_radius == 0: if (end_angle - start_angle) != 360: p.append((centerx, centery)) else: origx = (centerx + cos(angle) * short_radius) origy = (centery + sin(angle) * short_radius) p.append((origx, origy)) while angle < eangle: x = centerx + cos(angle) * radius y = centery + sin(angle) * radius p.append((x, y)) angle = angle + radiansdelta x = centerx + cos(eangle) * radius y = centery + sin(eangle) * radius p.append((x, y)) if short_radius: x = centerx + cos(eangle) * short_radius y = centery + sin(eangle) * short_radius p.append((x, y)) angle = eangle while angle >= sangle: x = centerx + cos(angle) * short_radius y = centery + sin(angle) * short_radius p.append((x, y)) angle -= radiansdelta doc.draw_path(style, p) delta = (eangle - sangle) / 2.0 rad = short_radius + (radius - short_radius) / 2.0 return ( (centerx + cos(sangle + delta) * rad), (centery + sin(sangle + delta) * rad)) def draw_pie_chart(doc, center_x, center_y, radius, data, start=0): """ Draws a pie chart in the specified document. The data passed is plotted as a pie chart. The data should consist of the actual data. Percentages of each slice are determined by the routine. @param doc: Document to which the pie chart should be added @type doc: BaseDoc derived class @param center_x: x coordinate in centimeters where the center of the pie chart should be. 0 is the left hand edge of the document. @type center_x: float @param center_y: y coordinate in centimeters where the center of the pie chart should be. 0 is the top edge of the document @type center_y: float @param radius: radius of the pie chart. The pie charts width and height will be twice this value. @type radius: float @param data: List of tuples containing the data to be plotted. The values are (graphics_format, value), where graphics_format is a BaseDoc GraphicsStyle, and value is a floating point number. Any other items in the tuple are ignored. This allows you to share the same data list with the L{draw_legend} function. @type data: list @param start: starting point in degrees, where the default of 0 indicates a start point extending from the center to right in a horizontal line. @type start: float """ total = 0.0 for item in data: total += item[1] for item in data: incr = 360.0*(item[1]/total) draw_wedge(doc, item[0], center_x, center_y, radius, start, start + incr) start += incr def draw_legend(doc, start_x, start_y, data, title, label_style): """ Draws a legend for a graph in the specified document. The data passed is used to define the legend. First item style is used for the optional Legend title. @param doc: Document to which the legend chart should be added @type doc: BaseDoc derived class @param start_x: x coordinate in centimeters where the left hand corner of the legend is placed. 0 is the left hand edge of the document. @type start_x: float @param start_y: y coordinate in centimeters where the top of the legend should be. 0 is the top edge of the document @type start_y: float @param data: List of tuples containing the data to be used to create the legend. In order to be compatible with the graph plots, the first and third values of the tuple used. The format is (graphics_format, value, legend_description). @type data: list """ style_sheet = doc.get_style_sheet() if title: gstyle = style_sheet.get_draw_style(label_style) pstyle_name = gstyle.get_paragraph_style() pstyle = style_sheet.get_paragraph_style(pstyle_name) size = ReportUtils.pt2cm(pstyle.get_font().get_size()) doc.draw_text(label_style, title, start_x + (3*size), start_y - (size*0.25)) start_y += size * 1.3 for (format, size, legend) in data: gstyle = style_sheet.get_draw_style(format) pstyle_name = gstyle.get_paragraph_style() pstyle = style_sheet.get_paragraph_style(pstyle_name) size = ReportUtils.pt2cm(pstyle.get_font().get_size()) doc.draw_box(format, "", start_x, start_y, (2*size), size) doc.draw_text(label_style, legend, start_x + (3*size), start_y - (size*0.25)) start_y += size * 1.3 _t = time.localtime(time.time()) _TODAY = DateHandler.parser.parse("%04d-%02d-%02d" % _t[:3]) def estimate_age(db, person, end_handle=None, start_handle=None, today=_TODAY): """ Estimates the age of a person based off the birth and death dates of the person. A tuple containing the estimated upper and lower bounds of the person's age is returned. If either the birth or death date is missing, a (-1, -1) is returned. @param db: GRAMPS database to which the Person object belongs @type db: DbBase @param person: Person object to calculate the age of @type person: Person @param end_handle: Determines the event handle that determines the upper limit of the age. If None, the death event is used @type end_handle: str @param start_handle: Determines the event handle that determines the lower limit of the event. If None, the birth event is used @type start_handle: str @returns: tuple containing the lower and upper bounds of the person's age, or (-1, -1) if it could not be determined. @rtype: tuple """ bhandle = None if start_handle: bhandle = start_handle else: bref = person.get_birth_ref() if bref: bhandle = bref.get_reference_handle() dhandle = None if end_handle: dhandle = end_handle else: dref = person.get_death_ref() if dref: dhandle = dref.get_reference_handle() # if either of the events is not defined, return an error message if not bhandle: return (-1, -1) bdata = db.get_event_from_handle(bhandle).get_date_object() if dhandle: ddata = db.get_event_from_handle(dhandle).get_date_object() else: if today is not None: ddata = today else: return (-1, -1) # if the date is not valid, return an error message if not bdata.get_valid() or not ddata.get_valid(): return (-1, -1) # if a year is not valid, return an error message if not bdata.get_year_valid() or not ddata.get_year_valid(): return (-1, -1) bstart = bdata.get_start_date() bstop = bdata.get_stop_date() dstart = ddata.get_start_date() dstop = ddata.get_stop_date() def _calc_diff(low, high): if (low[1], low[0]) > (high[1], high[0]): return high[2] - low[2] - 1 else: return high[2] - low[2] if bstop == dstop == Date.EMPTY: lower = _calc_diff(bstart, dstart) age = (lower, lower) elif bstop == Date.EMPTY: lower = _calc_diff(bstart, dstart) upper = _calc_diff(bstart, dstop) age = (lower, upper) elif dstop == Date.EMPTY: lower = _calc_diff(bstop, dstart) upper = _calc_diff(bstart, dstart) age = (lower, upper) else: lower = _calc_diff(bstop, dstart) upper = _calc_diff(bstart, dstop) age = (lower, upper) return age #------------------------------------------------------------------------ # # Global options and their names # #------------------------------------------------------------------------ class _options: # sort type identifiers SORT_VALUE = 0 SORT_KEY = 1 sorts = [ (SORT_VALUE, "Item count", _("Item count")), (SORT_KEY, "Item name", _("Item name")) ] genders = [ (Person.UNKNOWN, "Both", _("Both")), (Person.MALE, "Men", _("Men")), (Person.FEMALE, "Women", _("Women")) ] #------------------------------------------------------------------------ # # Data extraction methods from the database # #------------------------------------------------------------------------ class Extract(object): def __init__(self): """Methods for extracting statistical data from the database""" # key, non-localized name, localized name, type method, data method self.extractors = { 'data_title': ("Title", _("person|Title"), self.get_person, self.get_title), 'data_sname': ("Surname", _("Surname"), self.get_person, self.get_surname), 'data_fname': ("Forename", _("Forename"), self.get_person, self.get_forename), 'data_gender': ("Gender", _("Gender"), self.get_person, self.get_gender), 'data_byear': ("Birth year", _("Birth year"), self.get_birth, self.get_year), 'data_dyear': ("Death year", _("Death year"), self.get_death, self.get_year), 'data_bmonth': ("Birth month", _("Birth month"), self.get_birth, self.get_month), 'data_dmonth': ("Death month", _("Death month"), self.get_death, self.get_month), 'data_bplace': ("Birth place", _("Birth place"), self.get_birth, self.get_place), 'data_dplace': ("Death place", _("Death place"), self.get_death, self.get_place), 'data_mplace': ("Marriage place", _("Marriage place"), self.get_marriage_handles, self.get_places), 'data_mcount': ("Number of relationships", _("Number of relationships"), self.get_family_handles, self.get_handle_count), 'data_fchild': ("Age when first child born", _("Age when first child born"), self.get_child_handles, self.get_first_child_age), 'data_lchild': ("Age when last child born", _("Age when last child born"), self.get_child_handles, self.get_last_child_age), 'data_ccount': ("Number of children", _("Number of children"), self.get_child_handles, self.get_handle_count), 'data_mage': ("Age at marriage", _("Age at marriage"), self.get_marriage_handles, self.get_event_ages), 'data_dage': ("Age at death", _("Age at death"), self.get_person, self.get_death_age), 'data_age': ("Age", _("Age"), self.get_person, self.get_person_age), 'data_etypes': ("Event type", _("Event type"), self.get_event_handles, self.get_event_type) } # ----------------- data extraction methods -------------------- # take an object and return a list of strings def get_title(self, person): "return title for given person" # TODO: return all titles, not just primary ones... title = person.get_primary_name().get_title() if title: return [title] else: return [_("(Preferred) title missing")] def get_forename(self, person): "return forenames for given person" # TODO: return all forenames, not just primary ones... firstnames = person.get_primary_name().get_first_name().strip() if firstnames: return firstnames.split() else: return [_("(Preferred) forename missing")] def get_surname(self, person): "return surnames for given person" # TODO: return all surnames, not just primary ones... surnames = person.get_primary_name().get_surname().strip() if surnames: return surnames.split() else: return [_("(Preferred) surname missing")] def get_gender(self, person): "return gender for given person" # TODO: why there's no Person.getGenderName? # It could be used by getDisplayInfo & this... if person.gender == Person.MALE: return [_("Men")] if person.gender == Person.FEMALE: return [_("Women")] return [_("Gender unknown")] def get_year(self, event): "return year for given event" date = event.get_date_object() if date: year = date.get_year() if year: return [str(year)] return [_("Date(s) missing")] def get_month(self, event): "return month for given event" date = event.get_date_object() if date: month = date.get_month() if month: return [DateHandler.displayer.long_months[month]] return [_("Date(s) missing")] def get_place(self, event): "return place for given event" place_handle = event.get_place_handle() if place_handle: place = self.db.get_place_from_handle(place_handle).get_title() if place: return [place] return [_("Place missing")] def get_places(self, data): "return places for given (person,event_handles)" places = [] person, event_handles = data for event_handle in event_handles: event = self.db.get_event_from_handle(event_handle) place_handle = event.get_place_handle() if place_handle: place = self.db.get_place_from_handle(place_handle).get_title() if place: places.append(place) else: places.append(_("Place missing")) return places def get_person_age(self, person): "return age for given person, if alive" death_ref = person.get_death_ref() if not death_ref: return [self.estimate_age(person)] return [_("Already dead")] def get_death_age(self, person): "return age at death for given person, if dead" death_ref = person.get_death_ref() if death_ref: return [self.estimate_age(person, death_ref.ref)] return [_("Still alive")] def get_event_ages(self, data): "return ages at given (person,event_handles)" person, event_handles = data ages = [self.estimate_age(person, h) for h in event_handles] if ages: return ages return [_("Events missing")] def get_event_type(self, data): "return event types at given (person,event_handles)" types = [] person, event_handles = data for event_handle in event_handles: event = self.db.get_event_from_handle(event_handle) evtType = str(event.get_type()) types.append(evtType) if types: return types return [_("Events missing")] def get_first_child_age(self, data): "return age when first child in given (person,child_handles) was born" ages, errors = self.get_sorted_child_ages(data) if ages: errors.append(ages[0]) return errors return [_("Children missing")] def get_last_child_age(self, data): "return age when last child in given (person,child_handles) was born" ages, errors = self.get_sorted_child_ages(data) if ages: errors.append(ages[-1]) return errors return [_("Children missing")] def get_handle_count(self, data): "return number of handles in given (person, handle_list) used for child count, family count" return ["%3d" % len(data[1])] # ------------------- utility methods ------------------------- def get_sorted_child_ages(self, data): "return (sorted_ages,errors) for given (person,child_handles)" ages = [] errors = [] person, child_handles = data for child_handle in child_handles: child = self.db.get_person_from_handle(child_handle) birth_ref = child.get_birth_ref() if birth_ref: ages.append(self.estimate_age(person, birth_ref.ref)) else: errors.append(_("Birth missing")) continue ages.sort() return (ages, errors) def estimate_age(self, person, end=None, begin=None): """return estimated age (range) for given person or error message. age string is padded with spaces so that it can be sorted""" age = estimate_age(self.db, person, end, begin) if age[0] < 0 or age[1] < 0: # inadequate information return _("Date(s) missing") if age[0] == age[1]: # exact year return "%3d" % age[0] else: # minimum and maximum return "%3d-%d" % (age[0], age[1]) # ------------------- type methods ------------------------- # take db and person and return suitable gramps object(s) def get_person(self, person): "return person" return person def get_birth(self, person): "return birth event for given person or None" birth_ref = person.get_birth_ref() if birth_ref: return self.db.get_event_from_handle(birth_ref.ref) return None def get_death(self, person): "return death event for given person or None" death_ref = person.get_death_ref() if death_ref: return self.db.get_event_from_handle(death_ref.ref) return None def get_child_handles(self, person): "return list of child handles for given person or None" children = [] for fam_handle in person.get_family_handle_list(): fam = self.db.get_family_from_handle(fam_handle) for child_ref in fam.get_child_ref_list(): children.append(child_ref.ref) # TODO: it would be good to return only biological children, # but GRAMPS doesn't offer any efficient way to check that # (I don't want to check each children's parent family mother # and father relations as that would make this *much* slower) if children: return (person, children) return None def get_marriage_handles(self, person): "return list of marriage event handles for given person or None" marriages = [] for family_handle in person.get_family_handle_list(): family = self.db.get_family_from_handle(family_handle) if int(family.get_relationship()) == FamilyRelType.MARRIED: for event_ref in family.get_event_ref_list(): event = self.db.get_event_from_handle(event_ref.ref) if event.get_type() == EventType.MARRIAGE and \ (event_ref.get_role() == EventRoleType.FAMILY or event_ref.get_role() == EventRoleType.PRIMARY ): marriages.append(event_ref.ref) if marriages: return (person, marriages) return None def get_family_handles(self, person): "return list of family handles for given person or None" families = person.get_family_handle_list() if families: return (person, families) return None def get_event_handles(self, person): "return list of event handles for given person or None" events = [ref.ref for ref in person.get_event_ref_list()] if events: return (person, events) return None # ----------------- data collection methods -------------------- def get_person_data(self, person, collect): """Add data from the database to 'collect' for the given person, using methods rom the 'collect' data dict tuple """ for chart in collect: # get the information type_func = chart[2] data_func = chart[3] obj = type_func(person) # e.g. get_date() if obj: value = data_func(obj) # e.g. get_year() else: value = [_("Personal information missing")] # list of information found for key in value: if key in chart[1]: chart[1][key] += 1 else: chart[1][key] = 1 def collect_data(self, db, filter_func, menu, genders, year_from, year_to, no_years): """goes through the database and collects the selected personal data persons fitting the filter and birth year criteria. The arguments are: db - the GRAMPS database filter_func - filtering function selected by the StatisticsDialog options - report options_dict which sets which methods are used genders - which gender(s) to include into statistics year_from - use only persons who've born this year of after year_to - use only persons who've born this year or before no_years - use also people without known birth year Returns an array of tuple of: - Extraction method title - Dict of values with their counts (- Method) """ self.db = db # store for use by methods data = [] ext = self.extractors # which methods to use for name in self.extractors: option = menu.get_option_by_name(name) if option.get_value() == True: # localized data title, value dict, type and data method data.append((ext[name][1], {}, ext[name][2], ext[name][3])) # go through the people and collect data for person_handle in filter_func.apply(db, db.iter_person_handles()): person = db.get_person_from_handle(person_handle) # check whether person has suitable gender if person.gender != genders and genders != Person.UNKNOWN: continue # check whether birth year is within required range birth = self.get_birth(person) if birth: birthdate = birth.get_date_object() if birthdate.get_year_valid(): year = birthdate.get_year() if not (year >= year_from and year <= year_to): continue else: # if death before range, person's out of range too... death = self.get_death(person) if death: deathdate = death.get_date_object() if deathdate.get_year_valid() and deathdate.get_year() < year_from: continue if not no_years: # do not accept people who are not known to be in range continue else: continue else: continue self.get_person_data(person, data) return data # GLOBAL: required so that we get access to _Extract.extractors[] # Unfortunately class variables cannot reference instance methods :-/ _Extract = Extract() #------------------------------------------------------------------------ # # Statistics report # #------------------------------------------------------------------------ class StatisticsChart(Report): def __init__(self, database, options_class): """ Create the Statistics object that produces the report. Uses the Extractor class to extract the data from the database. The arguments are: database - the GRAMPS database instance person - currently selected person options_class - instance of the Options class for this report To see what the options are, check the options help in the options class. """ Report.__init__(self, database, options_class) menu = options_class.menu self.filter_option = menu.get_option_by_name('filter') self.filter = self.filter_option.get_filter() self.bar_items = menu.get_option_by_name('bar_items').get_value() year_from = menu.get_option_by_name('year_from').get_value() year_to = menu.get_option_by_name('year_to').get_value() gender = menu.get_option_by_name('gender').get_value() # title needs both data extraction method name + gender name if gender == Person.MALE: genders = _("Men") elif gender == Person.FEMALE: genders = _("Women") else: genders = None # needed for keyword based localization mapping = { 'genders': genders, 'year_from': year_from, 'year_to': year_to } self.progress = ProgressMeter(_('Statistics Charts')) # extract requested items from the database and count them self.progress.set_pass(_('Collecting data...'), 1) tables = _Extract.collect_data(database, self.filter, menu, gender, year_from, year_to, menu.get_option_by_name('no_years').get_value()) self.progress.step() self.progress.set_pass(_('Sorting data...'), len(tables)) self.data = [] sortby = menu.get_option_by_name('sortby').get_value() reverse = menu.get_option_by_name('reverse').get_value() for table in tables: # generate sorted item lookup index index lookup = self.index_items(table[1], sortby, reverse) # document heading mapping['chart_title'] = table[0] if genders: heading = _("%(genders)s born %(year_from)04d-%(year_to)04d: %(chart_title)s") % mapping else: heading = _("Persons born %(year_from)04d-%(year_to)04d: %(chart_title)s") % mapping self.data.append((heading, table[0], table[1], lookup)) self.progress.step() #DEBUG #print heading #print table[1] def lookup_compare(self, a, b): "compare given keys according to corresponding lookup values" return cmp(self.lookup_items[a], self.lookup_items[b]) def index_items(self, data, sort, reverse): """creates & stores a sorted index for the items""" # sort by item keys index = sorted(data, reverse=True if reverse else False) if sort == _options.SORT_VALUE: # set for the sorting function self.lookup_items = data # then sort by value index.sort(self.lookup_compare, reverse=True if reverse else False) return index def write_report(self): "output the selected statistics..." self.progress.set_pass(_('Saving charts...'), len(self.data)) for data in self.data: self.doc.start_page() if len(data[2]) < self.bar_items: self.output_piechart(*data[:4]) else: self.output_barchart(*data[:4]) self.doc.end_page() self.progress.step() self.progress.close() def output_piechart(self, title, typename, data, lookup): # set layout variables middle_w = self.doc.get_usable_width() / 2 middle_h = self.doc.get_usable_height() / 2 middle = min(middle_w,middle_h) # start output self.doc.center_text('SC-title', title, middle_w, 0) style_sheet = self.doc.get_style_sheet() pstyle = style_sheet.get_paragraph_style('SC-Title') yoffset = ReportUtils.pt2cm(pstyle.get_font().get_size()) # collect data for output color = 0 chart_data = [] for key in lookup: style = "SC-color-%d" % color text = "%s (%d)" % (key, data[key]) # graphics style, value, and it's label chart_data.append((style, data[key], text)) color = (color+1) % 7 # There are only 7 color styles defined margin = 1.0 legendx = 2.0 # output data... radius = middle - 2*margin yoffset += margin + radius draw_pie_chart(self.doc, middle_w, yoffset, radius, chart_data, -90) yoffset += radius + 2*margin if middle == middle_h: # Landscape legendx = 1.0 yoffset = margin text = _("%s (persons):") % typename draw_legend(self.doc, legendx, yoffset, chart_data, text,'SC-legend') def output_barchart(self, title, typename, data, lookup): pt2cm = ReportUtils.pt2cm style_sheet = self.doc.get_style_sheet() pstyle = style_sheet.get_paragraph_style('SC-Text') font = pstyle.get_font() # set layout variables width = self.doc.get_usable_width() row_h = pt2cm(font.get_size()) max_y = self.doc.get_usable_height() - row_h pad = row_h * 0.5 # check maximum value max_value = max(data[k] for k in lookup) if lookup else 0 # horizontal area for the gfx bars margin = 1.0 middle = width/2.0 textx = middle + margin/2.0 stopx = middle - margin/2.0 maxsize = stopx - margin # start output self.doc.center_text('SC-title', title, middle, 0) pstyle = style_sheet.get_paragraph_style('SC-Title') yoffset = pt2cm(pstyle.get_font().get_size()) #print title # header yoffset += (row_h + pad) text = _("%s (persons):") % typename self.doc.draw_text('SC-text', text, textx, yoffset) for key in lookup: yoffset += (row_h + pad) if yoffset > max_y: # for graphical report, page_break() doesn't seem to work self.doc.end_page() self.doc.start_page() yoffset = 0 # right align bar to the text value = data[key] startx = stopx - (maxsize * value / max_value) self.doc.draw_box('SC-bar',"",startx,yoffset,stopx-startx,row_h) # text after bar text = "%s (%d)" % (key, data[key]) self.doc.draw_text('SC-text', text, textx, yoffset) #print key + ":", return #------------------------------------------------------------------------ # # StatisticsChartOptions # #------------------------------------------------------------------------ class StatisticsChartOptions(MenuReportOptions): def __init__(self, name, dbase): self.__pid = None self.__filter = None self.__db = dbase MenuReportOptions.__init__(self, name, dbase) def add_menu_options(self, menu): """ Add options to the menu for the statistics report. """ category_name = _("Report Options") self.__filter = FilterOption(_("Filter"), 0) self.__filter.set_help( _("Determines what people are included in the report.")) menu.add_option(category_name, "filter", self.__filter) self.__filter.connect('value-changed', self.__filter_changed) self.__pid = PersonOption(_("Filter Person")) self.__pid.set_help(_("The center person for the filter.")) menu.add_option(category_name, "pid", self.__pid) self.__pid.connect('value-changed', self.__update_filters) self.__update_filters() sortby = EnumeratedListOption(_('Sort chart items by'), _options.SORT_VALUE ) for item_idx in range(len(_options.sorts)): item = _options.sorts[item_idx] sortby.add_item(item_idx,item[2]) sortby.set_help( _("Select how the statistical data is sorted.")) menu.add_option(category_name,"sortby",sortby) reverse = BooleanOption(_("Sort in reverse order"), False) reverse.set_help(_("Check to reverse the sorting order.")) menu.add_option(category_name,"reverse", reverse) this_year = time.localtime()[0] year_from = NumberOption(_("People Born After"), 1700, 1, this_year) year_from.set_help(_("Birth year from which to include people.")) menu.add_option(category_name,"year_from", year_from) year_to = NumberOption(_("People Born Before"), this_year, 1, this_year) year_to.set_help(_("Birth year until which to include people")) menu.add_option(category_name,"year_to", year_to) no_years = BooleanOption(_("Include people without known birth years"), False) no_years.set_help(_("Whether to include people without " "known birth years.")) menu.add_option(category_name,"no_years", no_years) gender = EnumeratedListOption(_('Genders included'), Person.UNKNOWN ) for item_idx in range(len(_options.genders)): item = _options.genders[item_idx] gender.add_item(item[0],item[2]) gender.set_help( _("Select which genders are included into " "statistics.")) menu.add_option(category_name,"gender",gender) bar_items = NumberOption(_("Max. items for a pie"), 8, 0, 20) bar_items.set_help(_("With fewer items pie chart and legend will be " "used instead of a bar chart.")) menu.add_option(category_name,"bar_items", bar_items) # ------------------------------------------------- # List of available charts on separate option tabs idx = 0 half = (len(_Extract.extractors))/2 self.charts = {} for key in _Extract.extractors: if idx < half: category_name = _("Charts 1") else: category_name = _("Charts 2") opt = BooleanOption(_Extract.extractors[key][1], False) opt.set_help(_("Include charts with indicated data.")) menu.add_option(category_name,key, opt) idx += 1 # Enable a couple of charts by default menu.get_option_by_name("data_gender").set_value(True) menu.get_option_by_name("data_ccount").set_value(True) menu.get_option_by_name("data_bmonth").set_value(True) def __update_filters(self): """ Update the filter list based on the selected person """ gid = self.__pid.get_value() person = self.__db.get_person_from_gramps_id(gid) filter_list = ReportUtils.get_person_filters(person, False) self.__filter.set_filters(filter_list) def __filter_changed(self): """ Handle filter change. If the filter is not specific to a person, disable the person option """ filter_value = self.__filter.get_value() if filter_value in [1, 2, 3, 4]: # Filters 1, 2, 3 and 4 rely on the center person self.__pid.set_available(True) else: # The rest don't self.__pid.set_available(False) def make_default_style(self, default_style): """Make the default output style for the Statistics report.""" # Paragraph Styles f = FontStyle() f.set_size(10) f.set_type_face(FONT_SERIF) p = ParagraphStyle() p.set_font(f) p.set_alignment(PARA_ALIGN_LEFT) p.set_description(_("The style used for the items and values.")) default_style.add_paragraph_style("SC-Text",p) f = FontStyle() f.set_size(14) f.set_type_face(FONT_SANS_SERIF) p = ParagraphStyle() p.set_font(f) p.set_alignment(PARA_ALIGN_CENTER) p.set_description(_("The style used for the title of the page.")) default_style.add_paragraph_style("SC-Title",p) """ Graphic Styles: SC-title - Contains the SC-Title paragraph style used for the title of the document SC-text - Contains the SC-Name paragraph style used for the individual's name SC-color-N - The colors for drawing pies. SC-bar - A red bar with 0.5pt black line. """ g = GraphicsStyle() g.set_paragraph_style("SC-Title") g.set_color((0,0,0)) g.set_fill_color((255,255,255)) g.set_line_width(0) default_style.add_draw_style("SC-title",g) g = GraphicsStyle() g.set_paragraph_style("SC-Text") g.set_color((0,0,0)) g.set_fill_color((255,255,255)) g.set_line_width(0) default_style.add_draw_style("SC-text",g) width = 0.8 # red g = GraphicsStyle() g.set_paragraph_style('SC-Text') g.set_color((0,0,0)) g.set_fill_color((255,0,0)) g.set_line_width(width) default_style.add_draw_style("SC-color-0",g) # orange g = GraphicsStyle() g.set_paragraph_style('SC-Text') g.set_color((0,0,0)) g.set_fill_color((255,158,33)) g.set_line_width(width) default_style.add_draw_style("SC-color-1",g) # green g = GraphicsStyle() g.set_paragraph_style('SC-Text') g.set_color((0,0,0)) g.set_fill_color((0,178,0)) g.set_line_width(width) default_style.add_draw_style("SC-color-2",g) # violet g = GraphicsStyle() g.set_paragraph_style('SC-Text') g.set_color((0,0,0)) g.set_fill_color((123,0,123)) g.set_line_width(width) default_style.add_draw_style("SC-color-3",g) # yellow g = GraphicsStyle() g.set_paragraph_style('SC-Text') g.set_color((0,0,0)) g.set_fill_color((255,255,0)) g.set_line_width(width) default_style.add_draw_style("SC-color-4",g) # blue g = GraphicsStyle() g.set_paragraph_style('SC-Text') g.set_color((0,0,0)) g.set_fill_color((0,105,214)) g.set_line_width(width) default_style.add_draw_style("SC-color-5",g) # gray g = GraphicsStyle() g.set_paragraph_style('SC-Text') g.set_color((0,0,0)) g.set_fill_color((210,204,210)) g.set_line_width(width) default_style.add_draw_style("SC-color-6",g) g = GraphicsStyle() g.set_color((0,0,0)) g.set_fill_color((255,0,0)) g.set_line_width(width) default_style.add_draw_style("SC-bar",g) # legend g = GraphicsStyle() g.set_paragraph_style('SC-Text') g.set_color((0,0,0)) g.set_fill_color((255,255,255)) g.set_line_width(0) default_style.add_draw_style("SC-legend",g)