diff --git a/gramps/gen/plug/docgen/graphdoc.py b/gramps/gen/plug/docgen/graphdoc.py index 61f712aee..ac7168741 100644 --- a/gramps/gen/plug/docgen/graphdoc.py +++ b/gramps/gen/plug/docgen/graphdoc.py @@ -11,6 +11,7 @@ # Copyright (C) 2017 Jonathan Biegert # Copyright (C) 2017 Mindaugas Baranauskas # Copyright (C) 2017 Paul Culley +# Copyright (C) 2018 Christophe aka khrys63 # # 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 @@ -364,6 +365,34 @@ class GVDoc(metaclass=ABCMeta): :return: nothing """ + @abstractmethod + def add_samerank(self, id1, id2): + """ + Force the same line for two linked nodes. + + :param id1: The unique identifier of the starting node. + Example: "p55" + :type id1: string + :param id2: The unique identifier of the ending node. + Example: "p55" + :type id2: string + :return: nothing + """ + + @abstractmethod + def rewrite_label(self, id, label): + """ + Rewrite the node label. + + :param id: The unique identifier of the node. + Example: "p55" + :type id: string + :param label: The text to be displayed in the node. + Example: "John Smith" + :type label: string + :return: nothing + """ + @abstractmethod def start_subgraph(self, graph_id): """ @@ -600,6 +629,22 @@ class GVDocBase(BaseDoc, GVDoc): else: self.write('# %s\n' % text) + def add_samerank(self, id1, id2): + """ + Force the same line for two linked nodes. + + Implements GVDocBase.add_samerank(). + """ + self.write(' {rank=same "%s" "%s"}\n' % (id1, id2)) + + def rewrite_label(self, id, label): + """ + Rewrite the node label. + + Implements GVDocBase.rewrite_label(). + """ + self.write(' "%s" [label = "%s"]\n' % (id, label)) + def start_subgraph(self, graph_id): """ Implement GVDocBase.start_subgraph() """ graph_id = graph_id.replace(' ', '_') # for user-defined ID with space diff --git a/gramps/plugins/graph/gvhourglass.py b/gramps/plugins/graph/gvhourglass.py index c15990043..a2d9f4174 100644 --- a/gramps/plugins/graph/gvhourglass.py +++ b/gramps/plugins/graph/gvhourglass.py @@ -7,6 +7,7 @@ # Copyright (C) 2010 Jakim Friant # Copyright (C) 2013-2014 Paul Franklin # Copyright (C) 2015 Detlef Wolz +# Copyright (C) 2018 Christophe aka khrys63 # # 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 @@ -98,6 +99,8 @@ class HourGlassReport(Report): self.__family_father = [] # links allocated from family to father self.__family_mother = [] # links allocated from family to mother + self.__node_label = {} # labels of node for merge sosa number + self.max_descend = menu.get_option_by_name('maxdescend').get_value() self.max_ascend = menu.get_option_by_name('maxascend').get_value() pid = menu.get_option_by_name('pid').get_value() @@ -113,6 +116,10 @@ class HourGlassReport(Report): } self.roundcorners = menu.get_option_by_name('roundcorners').get_value() + self.ahnentafel = menu.get_option_by_name('ahnentafel').get_value() + + self.ahnentafelnum = menu.get_option_by_name('ahnentafelnum').get_value() + self.includeid = menu.get_option_by_name('inc_id').get_value() arrow_str = menu.get_option_by_name('arrow').get_value() @@ -131,8 +138,8 @@ class HourGlassReport(Report): """ Generate the report. """ - self.add_person(self.center_person) - self.traverse_up(self.center_person, 1) + self.add_person(self.center_person, 1) + self.traverse_up(self.center_person, 1, 1) self.traverse_down(self.center_person, 1) def traverse_down(self, person, gen): @@ -153,27 +160,32 @@ class HourGlassReport(Report): # Avoid going down paths twice when descendant cousins marry self.__used_people.append(child_handle) child = self.__db.get_person_from_handle(child_handle) - self.add_person(child) + self.add_person(child, 0) self.doc.add_link(family.get_gramps_id(), child.get_gramps_id(), head=self.arrowheadstyle, tail=self.arrowtailstyle) self.traverse_down(child, gen+1) - def traverse_up(self, person, gen): + def traverse_up(self, person, gen, sosanumber): """ Recursively find the ancestors of the given person. """ + fathersosanumber = sosanumber * 2 + mothersosanumber = fathersosanumber + 1 if gen > self.max_ascend: return family_handle = person.get_main_parents_family_handle() + person_id=person.get_gramps_id() if family_handle: family = self.__db.get_family_from_handle(family_handle) family_id = family.get_gramps_id() self.add_family(family) - self.doc.add_link(family_id, person.get_gramps_id(), + self.doc.add_link(family_id, person_id, head=self.arrowtailstyle, tail=self.arrowheadstyle ) + father_id = '' + mother_id = '' # create link from family to father father_handle = family.get_father_handle() @@ -181,14 +193,19 @@ class HourGlassReport(Report): # allocate only one father per family self.__family_father.append(family_handle) father = self.__db.get_person_from_handle(father_handle) - self.add_person(father) + self.add_person(father, fathersosanumber) + father_id = father.get_gramps_id() self.doc.add_link(father.get_gramps_id(), family_id, head=self.arrowtailstyle, tail=self.arrowheadstyle ) + # update node with its father node id + self.__node_label[person_id] = [self.__node_label[person_id][0], father_id, self.__node_label[person_id][2]] # no need to go up if he is a father in another family if father_handle not in self.__used_people: self.__used_people.append(father_handle) - self.traverse_up(father, gen+1) + self.traverse_up(father, gen+1, fathersosanumber) + elif family_handle in self.__family_father and self.ahnentafelnum: + self.rewrite_sosa_number(self.__db.get_person_from_handle(father_handle).get_gramps_id(), fathersosanumber) # create link from family to mother mother_handle = family.get_mother_handle() @@ -196,16 +213,39 @@ class HourGlassReport(Report): # allocate only one mother per family self.__family_mother.append(family_handle) mother = self.__db.get_person_from_handle(mother_handle) - self.add_person(mother) + self.add_person(mother, mothersosanumber) + mother_id = mother.get_gramps_id() self.doc.add_link(mother.get_gramps_id(), family_id, head=self.arrowtailstyle, tail=self.arrowheadstyle) + # update node with its mother node id + self.__node_label[person_id] = [self.__node_label[person_id][0], self.__node_label[person_id][1], mother_id] # no need to go up if she is a mother in another family if mother_handle not in self.__used_people: self.__used_people.append(mother_handle) - self.traverse_up(mother, gen+1) + self.traverse_up(mother, gen+1, mothersosanumber) + elif family_handle in self.__family_mother and self.ahnentafelnum: + self.rewrite_sosa_number(self.__db.get_person_from_handle(mother_handle).get_gramps_id(), mothersosanumber) - def add_person(self, person): + if self.ahnentafel and mother_handle and father_handle and father_id != '' and mother_id != '': + self.doc.add_link(father_id, mother_id, + style='invis') + self.doc.add_samerank(father_id, mother_id) + + def rewrite_sosa_number(self, pid, sosanumber): + """ + Rewrite the Sosa number of a node for multiple sosa member in the tree. + """ + self.__node_label[pid][0]+=" - #%s" % (sosanumber) + self.doc.rewrite_label(pid,self.__node_label[pid][0]) + + # Recursively rewrite for all ancestors + if self.__node_label[pid][1] != '': + self.rewrite_sosa_number(self.__node_label[pid][1], sosanumber*2) + if self.__node_label[pid][2] != '': + self.rewrite_sosa_number(self.__node_label[pid][2], sosanumber*2+1) + + def add_person(self, person, sosanumber): """ Add a person to the Graph. The node id will be the person's gramps id. """ @@ -236,11 +276,17 @@ class HourGlassReport(Report): elif self.includeid == 2: # own line label = "%s \\n(%s%s)\\n(%s)" % (name, birth, death, p_id) + if self.ahnentafelnum and sosanumber != 0: + label +="\\n #%s" % (sosanumber) + label = label.replace('"', '\\\"') (shape, style, color, fill) = self.get_gender_style(person) self.doc.add_node(p_id, label, shape, color, style, fill) + # save node with them label, father node id, mother node id and sosanumber + self.__node_label[p_id] = [label, '', ''] + def add_family(self, family): """ Add a family to the Graph. The node id will be the family's gramps id. @@ -352,7 +398,9 @@ class HourGlassOptions(MenuReportOptions): stdoptions.add_gramps_id_option(menu, category_name, ownline=True) + ################################ category_name = _("Report Options (2)") + ################################ stdoptions.add_name_format_option(menu, category_name) @@ -384,3 +432,13 @@ class HourGlassOptions(MenuReportOptions): color_family = ColorOption(_('Families'), '#ffffe0') color_family.set_help(_('The color to use to display families.')) menu.add_option(category_name, 'colorfamilies', color_family) + + ahnentafelorder = BooleanOption(_("Force Ahnentafel order"), False) # 10826 + ahnentafelorder.set_help( + _("Force Sosa / Sosa-Stradonitz / Ahnentafel layout order for all ancestors, so that fathers are always on the left branch and mothers are on the right branch.")) + menu.add_option(category_name, "ahnentafel", ahnentafelorder) + + ahnentafelnumvisible = BooleanOption(_("Ahnentafel number visible"), False) # 10826 + ahnentafelnumvisible.set_help( + _("Show Sosa / Sosa-Stradonitz / Ahnentafel number under all others informations.")) + menu.add_option(category_name, "ahnentafelnum", ahnentafelnumvisible)