Exemple #1
0
class TimeLine(Report):
    """ TimeLine Report """
    def __init__(self, database, options, user):
        """
        Create the Timeline object that produces the report.

        The arguments are:

        database        - the GRAMPS database instance
        options         - instance of the Options class for this report
        user            - instance of gen.user.User()

        This report needs the following parameters (class variables)
        that come in the options class.

        filter    - Filter to be applied to the people of the database.
                    The option class carries its number, and the function
                    returning the list of filters.
        sortby        - Sorting method to be used.
        name_format   - Preferred format to display names
        incl_private  - Whether to include private data
        living_people - How to handle living people
        years_past_death - Consider as living this many years after death
        """
        Report.__init__(self, database, options, user)
        self._user = user
        menu = options.menu

        lang = options.menu.get_option_by_name('trans').get_value()
        rlocale = self.set_locale(lang)

        stdoptions.run_private_data_option(self, menu)
        living_opt = stdoptions.run_living_people_option(self, menu, rlocale)
        self.database = CacheProxyDb(self.database)

        self.filter = menu.get_option_by_name('filter').get_filter()
        self.fil_name = "(%s)" % self.filter.get_name(rlocale)

        living_value = menu.get_option_by_name('living_people').get_value()
        for (value, description) in living_opt.get_items(xml_items=True):
            if value == living_value:
                living_desc = self._(description)
                break
        self.living_desc = self._("(Living people: %(option_name)s)") % {
            'option_name': living_desc
        }

        stdoptions.run_name_format_option(self, menu)

        sort_func_num = menu.get_option_by_name('sortby').get_value()
        sort_functions = _get_sort_functions(Sort(self.database))
        self.sort_name = self._(sort_functions[sort_func_num][0])
        self.sort_func = sort_functions[sort_func_num][1]
        self.calendar = config.get('preferences.calendar-format-report')
        self.plist = []
        self.header = 2.6

    def write_report(self):
        # Apply the filter
        with self._user.progress(_('Timeline'), _('Applying filter...'),
                                 self.database.get_number_of_people()) as step:
            self.plist = self.filter.apply(self.database,
                                           self.database.iter_person_handles(),
                                           step)

        # Find the range of dates to include
        (low, high) = self.find_year_range()

        # Generate the actual timeline
        self.generate_timeline(low, high)

    def generate_timeline(self, low, high):
        """ generate the timeline """
        st_size = self.name_size()
        style_sheet = self.doc.get_style_sheet()
        font = style_sheet.get_paragraph_style('TLG-Name').get_font()
        incr = utils.pt2cm(font.get_size())
        pad = incr * 0.75
        _x1, _x2, _y1, _y2 = (0, 0, 0, 0)
        start = st_size + 0.5
        stop = self.doc.get_usable_width() - 0.5
        size = stop - start
        self.header = 2.6

        # Sort the people as requested
        with self._user.progress(_('Timeline'), _('Sorting dates...'),
                                 0) as step:
            self.plist.sort(key=self.sort_func)

        self.doc.start_page()
        self.build_grid(low, high, start, stop, True)

        index = 1
        current = 1

        length = len(self.plist)

        with self._user.progress(_('Timeline'), _('Calculating timeline...'),
                                 length) as step:

            for p_id in self.plist:
                person = self.database.get_person_from_handle(p_id)
                birth = get_birth_or_fallback(self.database, person)
                if birth:
                    bth = birth.get_date_object()
                    bth = bth.to_calendar(self.calendar).get_year()
                else:
                    bth = None

                death = get_death_or_fallback(self.database, person)
                if death:
                    dth = death.get_date_object()
                    dth = dth.to_calendar(self.calendar).get_year()
                else:
                    dth = None

                dname = self._name_display.display(person)
                mark = utils.get_person_mark(self.database, person)
                self.doc.draw_text('TLG-text', dname, incr + pad,
                                   self.header + (incr + pad) * index, mark)

                _y1 = self.header + (pad + incr) * index
                _y2 = self.header + ((pad + incr) * index) + incr
                _y3 = (_y1 + _y2) / 2.0
                w05 = 0.05

                if bth:
                    start_offset = ((float(bth - low) / float(high - low)) *
                                    size)
                    _x1 = start + start_offset
                    path = [(_x1, _y1), (_x1 + w05, _y3), (_x1, _y2),
                            (_x1 - w05, _y3)]
                    self.doc.draw_path('TLG-line', path)

                if dth:
                    start_offset = ((float(dth - low) / float(high - low)) *
                                    size)
                    _x1 = start + start_offset
                    path = [(_x1, _y1), (_x1 + w05, _y3), (_x1, _y2),
                            (_x1 - w05, _y3)]
                    self.doc.draw_path('TLG-solid', path)

                if bth and dth:
                    start_offset = (
                        (float(bth - low) / float(high - low)) * size) + w05
                    stop_offset = (
                        (float(dth - low) / float(high - low)) * size) - w05

                    _x1 = start + start_offset
                    _x2 = start + stop_offset
                    self.doc.draw_line('open', _x1, _y3, _x2, _y3)

                if (_y2 + incr) >= self.doc.get_usable_height():
                    if current != length:
                        self.doc.end_page()
                        self.doc.start_page()
                        self.build_grid(low, high, start, stop)
                    index = 1
                    _x1, _x2, _y1, _y2 = (0, 0, 0, 0)
                else:
                    index += 1
                current += 1
                step()
            self.doc.end_page()

    def build_grid(self, year_low, year_high, start_pos, stop_pos, toc=False):
        """
        Draws the grid outline for the chart. Sets the document label,
        draws the vertical lines, and adds the year labels. Arguments
        are:

        year_low  - lowest year on the chart
        year_high - highest year on the chart
        start_pos - x position of the lowest leftmost grid line
        stop_pos  - x position of the rightmost grid line
        """
        self.draw_title(toc)
        self.draw_columns(start_pos, stop_pos)
        if year_high is not None and year_low is not None:
            self.draw_year_headings(year_low, year_high, start_pos, stop_pos)
        else:
            self.draw_no_date_heading()

    def draw_columns(self, start_pos, stop_pos):
        """
        Draws the columns out of vertical lines.

        start_pos - x position of the lowest leftmost grid line
        stop_pos  - x position of the rightmost grid line
        """
        top_y = self.header
        bottom_y = self.doc.get_usable_height()
        delta = (stop_pos - start_pos) / 5
        for val in range(0, 6):
            xpos = start_pos + (val * delta)
            self.doc.draw_line('TLG-grid', xpos, top_y, xpos, bottom_y)

    def draw_title(self, toc):
        """
        Draws the title for the page.
        """
        width = self.doc.get_usable_width()
        title = "%(str1)s -- %(str2)s" % {
            'str1': self._("Timeline Chart"),
            # feature request 2356: avoid genitive form
            'str2': self._("Sorted by %s") % self.sort_name
        }
        title3 = self.living_desc
        mark = None
        if toc:
            mark = IndexMark(title, INDEX_TYPE_TOC, 1)
        self.doc.center_text('TLG-title', title, width / 2.0, 0, mark)
        style_sheet = self.doc.get_style_sheet()
        title_font = style_sheet.get_paragraph_style('TLG-Title').get_font()
        title_y = 1.2 - (utils.pt2cm(title_font.get_size()) * 1.2)
        self.doc.center_text('TLG-title', self.fil_name, width / 2.0, title_y)
        title_y = 1.8 - (utils.pt2cm(title_font.get_size()) * 1.2)
        self.doc.center_text('TLG-title', title3, width / 2.0, title_y)

    def draw_year_headings(self, year_low, year_high, start_pos, stop_pos):
        """
        Draws the column headings (years) for the page.
        """
        style_sheet = self.doc.get_style_sheet()
        label_font = style_sheet.get_paragraph_style('TLG-Label').get_font()
        label_y = self.header - (utils.pt2cm(label_font.get_size()) * 1.2)
        incr = (year_high - year_low) / 5
        delta = (stop_pos - start_pos) / 5
        for val in range(0, 6):
            xpos = start_pos + (val * delta)
            year_str = str(int(year_low + (incr * val)))
            self.doc.center_text('TLG-label', year_str, xpos, label_y)

    def draw_no_date_heading(self):
        """
        Draws a single heading that says "No Date Information"
        """
        width = self.doc.get_usable_width()
        style_sheet = self.doc.get_style_sheet()
        label_font = style_sheet.get_paragraph_style('TLG-Label').get_font()
        label_y = self.header - (utils.pt2cm(label_font.get_size()) * 1.2)
        self.doc.center_text('TLG-label', self._("No Date Information"),
                             width / 2.0, label_y)

    def find_year_range(self):
        """
        Finds the range of years that will be displayed on the chart.

        Returns a tuple of low and high years. If no dates are found, the
        function returns (None, None).
        """
        low = None
        high = None

        def min_max_year(low, high, year):
            """ convenience function """
            if year is not None and year != 0:
                if low is not None:
                    low = min(low, year)
                else:
                    low = year
                if high is not None:
                    high = max(high, year)
                else:
                    high = year
            return (low, high)

        with self._user.progress(_('Timeline'), _('Finding date range...'),
                                 len(self.plist)) as step:

            for p_id in self.plist:
                person = self.database.get_person_from_handle(p_id)
                birth = get_birth_or_fallback(self.database, person)
                if birth:
                    bth = birth.get_date_object()
                    bth = bth.to_calendar(self.calendar).get_year()
                    (low, high) = min_max_year(low, high, bth)

                death = get_death_or_fallback(self.database, person)
                if death:
                    dth = death.get_date_object()
                    dth = dth.to_calendar(self.calendar).get_year()
                    (low, high) = min_max_year(low, high, dth)
                step()

            # round the dates to the nearest decade
            if low is not None:
                low = int((low / 10)) * 10
            else:
                low = high

            if high is not None:
                high = int(((high + 9) / 10)) * 10
            else:
                high = low

            # Make sure the difference is a multiple of 50 so
            # all year ranges land on a decade.
            if low is not None and high is not None:
                low -= 50 - ((high - low) % 50)

        return (low, high)

    def name_size(self):
        """ get the length of the name """
        self.plist = self.filter.apply(self.database,
                                       self.database.iter_person_handles())

        style_sheet = self.doc.get_style_sheet()
        gstyle = style_sheet.get_draw_style('TLG-text')
        pname = gstyle.get_paragraph_style()
        pstyle = style_sheet.get_paragraph_style(pname)
        font = pstyle.get_font()

        size = 0
        for p_id in self.plist:
            person = self.database.get_person_from_handle(p_id)
            dname = self._name_display.display(person)
            size = max(self.doc.string_width(font, dname), size)
        return utils.pt2cm(size)
Exemple #2
0
class StatisticsChart(Report):
    """ StatisticsChart report """
    def __init__(self, database, options, user):
        """
        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
        options         - instance of the Options class for this report
        user            - a gen.user.User() instance
        incl_private  - Whether to include private data
        living_people - How to handle living people
        years_past_death - Consider as living this many years after death
        """
        Report.__init__(self, database, options, user)
        menu = options.menu
        self._user = user

        lang = menu.get_option_by_name('trans').get_value()
        rlocale = self.set_locale(lang)
        # override default gettext, or English output will have "person|Title"
        self._ = rlocale.translation.sgettext

        stdoptions.run_private_data_option(self, menu)
        living_opt = stdoptions.run_living_people_option(self, menu, rlocale)
        self.database = CacheProxyDb(self.database)

        get_option_by_name = menu.get_option_by_name
        get_value = lambda name: get_option_by_name(name).get_value()

        filter_opt = get_option_by_name('filter')
        self.filter = filter_opt.get_filter()
        self.fil_name = "(%s)" % self.filter.get_name(rlocale)

        self.bar_items = get_value('bar_items')
        year_from = get_value('year_from')
        year_to = get_value('year_to')
        gender = get_value('gender')

        living_value = get_value('living_people')
        for (value, description) in living_opt.get_items(xml_items=True):
            if value == living_value:
                living_desc = self._(description)
                break
        self.living_desc = self._("(Living people: %(option_name)s)") % {
            'option_name': living_desc
        }

        # title needs both data extraction method name + gender name
        if gender == Person.MALE:
            genders = self._("Men")
        elif gender == Person.FEMALE:
            genders = self._("Women")
        else:
            genders = None

        # needed for keyword based localization
        mapping = {
            'genders': genders,
            'year_from': year_from,
            'year_to': year_to
        }

        if genders:
            span_string = self._("%(genders)s born "
                                 "%(year_from)04d-%(year_to)04d") % mapping
        else:
            span_string = self._("Persons born "
                                 "%(year_from)04d-%(year_to)04d") % mapping

        # extract requested items from the database and count them
        self._user.begin_progress(_('Statistics Charts'),
                                  _('Collecting data...'),
                                  self.database.get_number_of_people())
        tables = _Extract.collect_data(self.database, self.filter, menu,
                                       gender, year_from, year_to,
                                       get_value('no_years'),
                                       self._user.step_progress, rlocale)
        self._user.end_progress()

        self._user.begin_progress(_('Statistics Charts'), _('Sorting data...'),
                                  len(tables))
        self.data = []
        sortby = get_value('sortby')
        reverse = get_value('reverse')
        for table in tables:
            # generate sorted item lookup index index
            lookup = self.index_items(table[1], sortby, reverse)
            # document heading
            heading = "%(str1)s -- %(str2)s" % {
                'str1': self._(table[0]),
                'str2': span_string
            }
            self.data.append((heading, table[0], table[1], lookup))
            self._user.step_progress()
        self._user.end_progress()

    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(key=lambda x: self.lookup_items[x],
                       reverse=True if reverse else False)

        return index

    def write_report(self):
        "output the selected statistics..."

        mark = IndexMark(self._('Statistics Charts'), INDEX_TYPE_TOC, 1)
        self._user.begin_progress(_('Statistics Charts'),
                                  _('Saving charts...'), len(self.data))
        for data in sorted(self.data):
            self.doc.start_page()
            if mark:
                self.doc.draw_text('SC-title', '', 0, 0, mark)  # put it in TOC
                mark = None  # crock, but we only want one of them
            if len(data[3]) < self.bar_items:
                self.output_piechart(*data[:4])
            else:
                self.output_barchart(*data[:4])
            self.doc.end_page()
            self._user.step_progress()
        self._user.end_progress()

    def output_piechart(self, title1, 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
        style_sheet = self.doc.get_style_sheet()
        pstyle = style_sheet.get_paragraph_style('SC-Title')
        mark = IndexMark(title1, INDEX_TYPE_TOC, 2)
        self.doc.center_text('SC-title', title1, middle_w, 0, mark)
        yoffset = utils.pt2cm(pstyle.get_font().get_size())
        self.doc.center_text('SC-title', self.fil_name, middle_w, yoffset)
        yoffset = 2 * utils.pt2cm(pstyle.get_font().get_size())
        self.doc.center_text('SC-title', self.living_desc, middle_w, yoffset)

        # collect data for output
        color = 0
        chart_data = []
        for key in lookup:
            style = "SC-color-%d" % color
            text = "%s (%d)" % (self._(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 = self._("%s (persons):") % self._(typename)
        draw_legend(self.doc, legendx, yoffset, chart_data, text, 'SC-legend')

    def output_barchart(self, title1, typename, data, lookup):

        pt2cm = utils.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
        pstyle = style_sheet.get_paragraph_style('SC-Title')
        mark = IndexMark(title1, INDEX_TYPE_TOC, 2)
        self.doc.center_text('SC-title', title1, middle, 0, mark)
        yoffset = pt2cm(pstyle.get_font().get_size())
        self.doc.center_text('SC-title', self.fil_name, middle, yoffset)
        yoffset = 2 * pt2cm(pstyle.get_font().get_size())
        self.doc.center_text('SC-title', self.living_desc, middle, yoffset)
        yoffset = 3 * pt2cm(pstyle.get_font().get_size())

        # header
        yoffset += (row_h + pad)
        text = self._("%s (persons):") % self._(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)" % (self._(key), data[key])
            self.doc.draw_text('SC-text', text, textx, yoffset)

        return