From 6af58e348b7a578483d325a0fb9b830215611290 Mon Sep 17 00:00:00 2001 From: Brian Matherly Date: Thu, 20 Dec 2007 06:26:38 +0000 Subject: [PATCH] Various enhancements to the Graphviz report system. Added GVRelGraph.py - adapted GraphViz.py to use Graphviz report system. svn: r9541 --- ChangeLog | 9 + src/BaseDoc.py | 2 +- src/ReportBase/_GraphvizReportDialog.py | 96 ++++- src/plugins/GVHourGlass.py | 4 +- src/plugins/GVRelGraph.py | 509 ++++++++++++++++++++++++ src/plugins/GraphViz.py | 10 +- 6 files changed, 611 insertions(+), 19 deletions(-) create mode 100644 src/plugins/GVRelGraph.py diff --git a/ChangeLog b/ChangeLog index 4d0f8d576..55f304217 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2007-12-20 Brian Matherly + * src/ReportBase/_GraphvizReportDialog.py: + * src/plugins/GraphViz.py: + * src/plugins/GVHourGlass.py: + * src/BaseDoc.py: + Various enhancements to the Graphviz report system. + * src/plugins/GVRelGraph.py: Added - adapted GraphViz.py to use Graphviz + report system. + 2007-12-20 Douglas S. Blank * src/plugins/ExportVCard.py: fixed date.get_text() as signal for text * src/plugins/ExportVCalendar.py: fixed date.get_text() ditto diff --git a/src/BaseDoc.py b/src/BaseDoc.py index 432571106..a8a111a0e 100644 --- a/src/BaseDoc.py +++ b/src/BaseDoc.py @@ -1585,7 +1585,7 @@ class GVDoc: """ raise NotImplementedError - def add_link(self, id1, id2): + def add_link(self, id1, id2, style="", head="", tail=""): """ Add a link between two nodes. diff --git a/src/ReportBase/_GraphvizReportDialog.py b/src/ReportBase/_GraphvizReportDialog.py index 7a983300c..8b7797e54 100644 --- a/src/ReportBase/_GraphvizReportDialog.py +++ b/src/ReportBase/_GraphvizReportDialog.py @@ -48,7 +48,7 @@ import Config from _Constants import CATEGORY_GRAPHVIZ from _ReportDialog import ReportDialog from _PaperMenu import PaperFrame -from PluginUtils import NumberOption, EnumeratedListOption +from PluginUtils import NumberOption, EnumeratedListOption, TextOption #------------------------------------------------------------------------------- # @@ -56,8 +56,8 @@ from PluginUtils import NumberOption, EnumeratedListOption # #------------------------------------------------------------------------------- _FONTS = [ { 'name' : _("Default"), 'value' : "" }, - { 'name' : _("Postscript / Helvetica"), 'value' : "Helvetica" }, - { 'name' : _("Truetype / FreeSans"), 'value' : "FreeSans" } ] + { 'name' : _("Postscript / Helvetica"), 'value' : "Helvetica" }, + { 'name' : _("Truetype / FreeSans"), 'value' : "FreeSans" } ] _RANKDIR = [ { 'name' : _("Vertical"), 'value' : "TB" }, { 'name' : _("Horizontal"), 'value' : "LR" } ] @@ -75,6 +75,10 @@ _RATIO = [ { 'name' : _("Minimal size"), 'value' : "compress" }, { 'name' : _("Fill the given area"), 'value': "fill" }, { 'name' : _("Use optimal number of pages"), 'value': "expand" } ] + +_NOTELOC = [ { 'name' : _("Top"), 'value' : "t" }, + { 'name' : _("Bottom"), 'value' : "b" }] + _dot_found = 0 _gs_cmd = "" @@ -116,6 +120,9 @@ class GVDocBase(BaseDoc.BaseDoc,BaseDoc.GVDoc): self.hpages = self.options['h_pages'] self.vpages = self.options['v_pages'] self.ratio = _RATIO[ self.options['ratio'] ]['value'] + self.noteloc = self.options['noteloc'] + self.notesize = self.options['notesize'] + self.note = self.options['note'] paper_size = paper_style.get_size() pheight = paper_size.get_height_inches() @@ -163,39 +170,72 @@ class GVDocBase(BaseDoc.BaseDoc,BaseDoc.GVDoc): This isn't useful by itself. Other classes need to override this and actually generate a file. """ + if self.note: + self.dot.write( 'labelloc="%s";\n' % self.noteloc ) + self.dot.write( 'label="' ) + for line in self.note: + self.dot.write( '%s\\n' % line.replace('"', '\\\"') ) + self.dot.write( '";\n') + self.dot.write( 'fontsize="%d";\n' % self.notesize ) + self.dot.write( '}' ) - def add_node(self, id, label, shape="box", fillcolor="white", url=""): + def add_node(self, id, label, shape="", color = "", + style="", fillcolor="", url="" ): """ Add a node to this graph. Nodes can be different shapes like boxes and circles. Implementes BaseDoc.GVDoc.add_node(). """ - line = ' "%s" [style=filled, ' % id + line = ' "%s" [' % id line += 'label="%s", ' % label - line += 'shape="%s", ' % shape - line += 'fillcolor="%s", ' % fillcolor + + if shape: + line += 'shape="%s", ' % shape + + if color: + line += 'color="%s", ' % color + + if fillcolor: + line += 'fillcolor="%s", ' % fillcolor if self.fontfamily: line += 'fontname="%s", ' % self.fontfamily line += 'fontsize="%0.1f", ' % self.fontsize + if style: + line += 'style="%s", ' % style + if url: - line += 'URL="%s"' % url - + line += 'URL="%s", ' % url + line += '];\n' self.dot.write(line) - def add_link(self, id1, id2): + def add_link(self, id1, id2, style="", head="", tail=""): """ Add a link between two nodes. Implementes BaseDoc.GVDoc.add_link(). """ - self.dot.write(' "%s" -> "%s";\n' % (id1, id2)) + self.dot.write(' "%s" -> "%s" ' % (id1, id2)) + + if style or head or tail: + self.dot.write('[') + + if style: + self.dot.write('style=%s, ' % style) + if head: + self.dot.write('arrowhead=%s, ' % head) + if tail: + self.dot.write('arrowtail=%s, ' % tail) + + self.dot.write(']') + + self.dot.write(';\n') #------------------------------------------------------------------------------- # @@ -569,8 +609,10 @@ class GraphvizReportDialog(ReportDialog): self.options = option_class(self.raw_name) elif type(option_class) == InstanceType: self.options = option_class - + + ################################ category = _("GraphViz Options") + ################################ font_family = EnumeratedListOption(_("Font family"), 0) index = 0 @@ -629,6 +671,26 @@ class GraphvizReportDialog(ReportDialog): "array of pages. This controls the number " "pages in the array vertically.")) self.options.add_menu_option(category,"v_pages",v_pages) + + ################################ + category = _("Notes") + ################################ + + note = TextOption(_("Note to add to the graph"), + [""] ) + note.set_help(_("This text will be added to the graph.")) + self.options.add_menu_option(category,"note",note) + + noteloc = EnumeratedListOption(_("Note location"), 't') + for i in range( 0, len(_NOTELOC) ): + noteloc.add_item(_NOTELOC[i]["value"], _NOTELOC[i]["name"]) + noteloc.set_help(_("Whether note will appear on top " + "or bottom of the page.")) + self.options.add_menu_option(category,"noteloc",noteloc) + + notesize = NumberOption(_("Note size"),32,8,128) + notesize.set_help(_("The size of note text, in points.")) + self.options.add_menu_option(category,"notesize",notesize) self.options.load_previous_values() @@ -693,6 +755,16 @@ class GraphvizReportDialog(ReportDialog): self.print_report.set_label (_("Open with application")) self.print_report.set_sensitive (False) + fname = self.target_fileentry.get_full_path(0) + (spath,ext) = os.path.splitext(fname) + + ext_val = obj.get_ext() + if ext_val: + fname = spath + ext_val + else: + fname = spath + self.target_fileentry.set_filename(fname) + def make_document(self): """Create a document of the type requested by the user. """ diff --git a/src/plugins/GVHourGlass.py b/src/plugins/GVHourGlass.py index c39218341..7d6b12c6e 100644 --- a/src/plugins/GVHourGlass.py +++ b/src/plugins/GVHourGlass.py @@ -127,7 +127,7 @@ class HourGlassReport(Report): else: color = 'lightgray' - self.doc.add_node(p_id,label,"box",color) + self.doc.add_node(p_id,label,"box","","filled",color) def add_family(self,family): """ @@ -138,7 +138,7 @@ class HourGlassReport(Report): marriage = ReportUtils.find_marriage(self.db,family) if marriage: label = DateHandler.get_date(marriage) - self.doc.add_node(family_id,label,"ellipse","lightyellow") + self.doc.add_node(family_id,label,"ellipse","","filled","lightyellow") #------------------------------------------------------------------------ # diff --git a/src/plugins/GVRelGraph.py b/src/plugins/GVRelGraph.py new file mode 100644 index 000000000..6a89cf70a --- /dev/null +++ b/src/plugins/GVRelGraph.py @@ -0,0 +1,509 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2007 Brian G. Matherly +# +# Adapted from GraphViz.py (now deprecated) +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2007 Johan Gonqvist +# Contributions by Lorenzo Cappelletti +# +# 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: $ + +""" +Create a relationship graph using Graphviz +""" + +#------------------------------------------------------------------------ +# +# GRAMPS modules +# +#------------------------------------------------------------------------ +from PluginUtils import register_report, FilterListOption, \ + EnumeratedListOption, BooleanOption +from ReportBase import Report, MenuReportOptions, \ + MODE_GUI, MODE_CLI, CATEGORY_GRAPHVIZ +from BasicUtils import name_displayer +import DateHandler +import gen.lib +import Utils +import ThumbNails + +#------------------------------------------------------------------------ +# +# Constant options items +# +#------------------------------------------------------------------------ +_COLORS = [ { 'name' : _("B&W outline"), 'value' : "outline" }, + { 'name' : _("Colored outline"), 'value' : "colored" }, + { 'name' : _("Color fill"), 'value' : "filled" }] + +_ARROWS = [ { 'name' : _("Descendants <- Ancestors"), 'value' : 'd' }, + { 'name' : _("Descendants -> Ancestors"), 'value' : 'a' }, + { 'name' : _("Descendants <-> Ancestors"), 'value' : 'da' }, + { 'name' : _("Descendants - Ancestors"), 'value' : '' }] + +#------------------------------------------------------------------------ +# +# Report class +# +#------------------------------------------------------------------------ +class RelGraphReport(Report): + + def __init__(self,database,person,options_class): + """ + Creates ComprehensiveAncestorsReport object that produces the report. + + The arguments are: + + database - the GRAMPS database instance + person - currently selected person + options_class - instance of the Options class for this report + + 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. + arrow - Arrow styles for heads and tails. + showfamily - Whether to show family nodes. + incid - Whether to include IDs. + incdate - Whether to include dates. + justyears - Use years only. + placecause - Whether to replace missing dates with place or cause + url - Whether to include URLs. + inclimg - Include images or not + imgpos - Image position, above/beside name + color - Whether to use outline, colored outline or filled color in graph + dashed - Whether to use dashed lines for non-birth relationships. + """ + Report.__init__(self,database,person,options_class) + + colored = { + 'male': 'dodgerblue4', + 'female': 'deeppink', + 'unknown': 'black', + 'family': 'darkgreen' + } + filled = { + 'male': 'lightblue', + 'female': 'lightpink', + 'unknown': 'lightgray', + 'family': 'lightyellow' + } + self.database = database + + options = options_class.handler.options_dict + self.includeid = options['incid'] + self.includedates = options['incdate'] + self.includeurl = options['url'] + self.includeimg = options['includeImages'] + self.imgpos = options['imageOnTheSide'] + self.adoptionsdashed = options['dashed'] + self.show_families = options['showfamily'] + self.just_years = options['justyears'] + self.placecause = options['placecause'] + self.colorize = options['color'] + if self.colorize == 'colored': + self.colors = colored + elif self.colorize == 'filled': + self.colors = filled + arrow_str = options['arrow'] + if arrow_str.find('a') + 1: + self.arrowheadstyle = 'normal' + else: + self.arrowheadstyle = 'none' + if arrow_str.find('d') + 1: + self.arrowtailstyle = 'normal' + else: + self.arrowtailstyle = 'none' + + filter_option = options_class.menu.get_option_by_name('filter') + filter_index = int(filter_option.get_value()) + filters = filter_option.get_filters() + self.filter = filters[filter_index] + + def write_report(self): + self.person_handles = self.filter.apply(self.database, + self.database.get_person_handles(sort_handles=False)) + + if len(self.person_handles) > 1: + self.add_persons_and_families() + self.add_child_links_to_families() + + def add_child_links_to_families(self): + "returns string of GraphViz edges linking parents to families or children" + person_dict = {} + # Hash people in a dictionary for faster inclusion checking + for person_handle in self.person_handles: + person_dict[person_handle] = 1 + for person_handle in self.person_handles: + person = self.database.get_person_from_handle(person_handle) + p_id = person.get_gramps_id() + for fam_handle in person.get_parent_family_handle_list(): + family = self.database.get_family_from_handle(fam_handle) + father_handle = family.get_father_handle() + mother_handle = family.get_mother_handle() + for child_ref in family.get_child_ref_list(): + if child_ref.ref == person_handle: + frel = child_ref.frel + mrel = child_ref.mrel + break + if (self.show_families and + ((father_handle and father_handle in person_dict) or + (mother_handle and mother_handle in person_dict))): + # Link to the family node if either parent is in graph + self.add_family_link(p_id,family,frel,mrel) + else: + # Link to the parents' nodes directly, if they are in graph + if father_handle and father_handle in person_dict: + self.add_parent_link(p_id,father_handle,frel) + if mother_handle and mother_handle in person_dict: + self.add_parent_link(p_id,mother_handle,mrel) + + def add_family_link(self, p_id, family, frel, mrel): + "Links the child to a family" + style = 'solid' + adopted = ((int(frel) != gen.lib.ChildRefType.BIRTH) or + (int(mrel) != gen.lib.ChildRefType.BIRTH)) + if adopted and self.adoptionsdashed: + style = 'dotted' + self.doc.add_link( p_id, family.get_gramps_id(), style, + self.arrowheadstyle, self.arrowtailstyle ) + + def add_parent_link(self, p_id, parent_handle, rel): + "Links the child to a parent" + style = 'solid' + if (int(rel) != gen.lib.ChildRefType.BIRTH) and self.adoptionsdashed: + style = 'dotted' + parent = self.database.get_person_from_handle(parent_handle) + self.doc.add_link( p_id, parent.get_gramps_id(), style, + self.arrowheadstyle, self.arrowtailstyle ) + + def add_persons_and_families(self): + "adds nodes for persons and their families" + # variable to communicate with get_person_label + self.bUseHtmlOutput = False + + # The list of families for which we have output the node, + # so we don't do it twice + families_done = {} + for person_handle in self.person_handles: + # determine per person if we use HTML style label + if self.includeimg: + self.bUseHtmlOutput = True + person = self.database.get_person_from_handle(person_handle) + p_id = person.get_gramps_id() + # Output the person's node + label = self.get_person_label(person) + (shape,style,color,fill) = self.get_gender_style(person) + url = "" + if self.includeurl: + h = person_handle + dirpath = "ppl/%s/%s" % (h[0], h[1]) + dirpath = dirpath.lower() + url = "%s/%s.html" % (dirpath,h) + + if self.bUseHtmlOutput: + label = '<%s>' % label + + self.doc.add_node(p_id,label,shape,color,style,fill) + + # Output families where person is a parent + if self.show_families: + family_list = person.get_family_handle_list() + for fam_handle in family_list: + fam = self.database.get_family_from_handle(fam_handle) + fam_id = fam.get_gramps_id() + if fam_handle not in families_done: + families_done[fam_handle] = 1 + label = "" + for event_ref in fam.get_event_ref_list(): + event = self.database.get_event_from_handle( + event_ref.ref) + if int(event.get_type()) == gen.lib.EventType.MARRIAGE: + label = self.get_event_string(event) + break + if self.includeid: + label = "%s (%s)" % (label, fam_id) + color = "" + fill = "" + style = "" + if self.colorize == 'colored': + color = self.colors['family'] + elif self.colorize == 'filled': + fill = self.colors['family'] + style = "filled" + self.doc.add_node(fam_id,label,"ellipse",color,style,fill) + # Link this person to all his/her families. + self.doc.add_link( fam_id, p_id, "", + self.arrowheadstyle, self.arrowtailstyle ) + + def get_gender_style(self, person): + "return gender specific person style" + gender = person.get_gender() + shape = "box" + style = "" + color = "" + fill = "" + if gender == person.MALE: + shape="box" + elif gender == person.FEMALE: + shape="box" + style="rounded" + else: + shape="hexagon" + if self.colorize == 'colored': + if gender == person.MALE: + color = self.colors['male'] + elif gender == person.FEMALE: + color = self.colors['female'] + else: + color = self.colors['unknown'] + elif self.colorize == 'filled': + if style != "": + style += ",filled" + else: + style = "filled" + if gender == person.MALE: + fill = self.colors['male'] + elif gender == person.FEMALE: + fill = self.colors['female'] + else: + fill = self.colors['unknown'] + return(shape,style,color,fill) + + def get_person_label(self, person): + "return person label string" + # see if we have an image to use for this person + imagePath = None + if self.bUseHtmlOutput: + mediaList = person.get_media_list() + if len(mediaList) > 0: + mediaHandle = mediaList[0].get_reference_handle() + media = self.database.get_object_from_handle(mediaHandle) + mediaMimeType = media.get_mime_type() + if mediaMimeType[0:5] == "image": + imagePath = ThumbNails.get_thumbnail_path(media.get_path()) + #test if thumbnail actually exists in thumbs (import of data means media files might not be present + imagePath = Utils.find_file(imagePath) + + label = u"" + lineDelimiter = '\\n' + + # if we have an image, then start an HTML table; remember to close the table afterwards! + if self.bUseHtmlOutput and imagePath: + lineDelimiter = '
' + label += '' % imagePath + if self.imgpos == 0: + #trick it into not stretching the image + label += '
' + else : + label += '' + else : + #no need for html label with this person + self.bUseHtmlOutput = False + + # at the very least, the label must have the person's name + nm = name_displayer.display_name(person.get_primary_name()) + if self.bUseHtmlOutput : + # avoid < and > in the name, as this is html text + label += nm.replace('<','<').replace('>','>') + else : + label += nm + p_id = person.get_gramps_id() + if self.includeid: + label += " (%s)" % p_id + if self.includedates: + birth, death = self.get_date_strings(person) + label = label + '%s(%s - %s)' % (lineDelimiter,birth, death) + + # see if we have a table that needs to be terminated + if self.bUseHtmlOutput: + label += '
' + return label + else : + # non html label is enclosed by "" so excape other " + return label.replace('"', '\\\"') + + def get_date_strings(self, person): + "returns tuple of birth/christening and death/burying date strings" + birth_ref = person.get_birth_ref() + if birth_ref: + birth_event = self.database.get_event_from_handle(birth_ref.ref) + birth = self.get_event_string(birth_event) + else: + birth = '' + death_ref = person.get_death_ref() + if death_ref: + death_event = self.database.get_event_from_handle(death_ref.ref) + death = self.get_event_string(death_event) + else: + death = '' + if birth and death: + return (birth, death) + # missing info, use (first) christening/burial instead + for event_ref in person.get_primary_event_ref_list(): + event = self.database.get_event_from_handle(event_ref.ref) + if int(event.get_type()) == gen.lib.EventType.CHRISTEN: + if not birth: + birth = self.get_event_string(event) + elif int(event.get_type()) == gen.lib.EventType.BURIAL: + if not death: + death = self.get_event_string(event) + return (birth, death) + + def get_event_string(self, event): + """ + return string for for an event label. + + Based on the data availability and preferences, we select one + of the following for a given event: + year only + complete date + place name + cause + empty string + """ + if event: + if event.get_date_object().get_year_valid(): + if self.just_years: + return '%i' % event.get_date_object().get_year() + else: + return DateHandler.get_date(event) + elif self.placecause: + place_handle = event.get_place_handle() + place = self.database.get_place_from_handle(place_handle) + if place and place.get_title(): + return place.get_title() + else: + return '' #event.get_cause() + return '' + + +#------------------------------------------------------------------------ +# +# Options class +# +#------------------------------------------------------------------------ +class RelGraphOptions(MenuReportOptions): + """ + Defines options and provides handling interface. + """ + def __init__(self,name,person_id=None): + MenuReportOptions.__init__(self,name,person_id) + + def add_menu_options(self,menu): + ################################ + category_name = _("Report Options") + ################################ + + filter = FilterListOption(_("Filter")) + filter.add_item("person") + filter.set_help(_("Select the filter to be applied to the report")) + menu.add_option(category_name,"filter", filter) + + incdate = BooleanOption( + _("Include Birth, Marriage and Death dates"), True) + incdate.set_help(_("Include the dates that the individual was born, " + "got married and/or died in the graph labels.")) + menu.add_option(category_name,"incdate", incdate) + + justyears = BooleanOption(_("Limit dates to years only"), False) + justyears.set_help(_("Prints just dates' year, neither " + "month or day nor date approximation " + "or interval are shown.")) + menu.add_option(category_name,"justyears", justyears) + + placecause = BooleanOption(_("Place/cause when no date"), True) + placecause.set_help(_("When no birth, marriage, or death date is " + "available, the correspondent place field (or " + "cause field when blank place) will be used.")) + menu.add_option(category_name,"placecause", placecause) + + url = BooleanOption(_("Include URLs"), False) + url.set_help(_("Include a URL in each graph node so " + "that PDF and imagemap files can be " + "generated that contain active links " + "to the files generated by the 'Narrated " + "Web Site' report.")) + menu.add_option(category_name,"url", url) + + incid = BooleanOption(_("Include IDs"), False) + incid.set_help(_("Include individual and family IDs.")) + menu.add_option(category_name,"incid", incid) + + includeImages = BooleanOption( + _('Include thumbnail images of people'), False) + includeImages.set_help(_("Whether to include thumbnails of people.")) + menu.add_option(category_name,"includeImages", includeImages) + + imageOnTheSide = EnumeratedListOption(_("Thumbnail Location"), 0) + imageOnTheSide.add_item(0, _('Above the name')) + imageOnTheSide.add_item(1, _('Beside the name')) + imageOnTheSide.set_help(_("Where the thumbnail image should appear " + "relative to the name")) + menu.add_option(category_name,"imageOnTheSide",imageOnTheSide) + + ################################ + category_name = _("Graph Style") + ################################ + + color = EnumeratedListOption(_("Graph coloring"), "filled") + for i in range( 0, len(_COLORS) ): + color.add_item(_COLORS[i]["value"], _COLORS[i]["name"]) + color.set_help(_("Males will be shown with blue, females " + "with red. If the sex of an individual " + "is unknown it will be shown with gray.")) + menu.add_option(category_name,"color",color) + + arrow = EnumeratedListOption(_("Arrowhead direction"), 'd') + for i in range( 0, len(_ARROWS) ): + arrow.add_item(_ARROWS[i]["value"], _ARROWS[i]["name"]) + arrow.set_help(_("Choose the direction that the arrows point.")) + menu.add_option(category_name,"arrow",arrow) + + dashed = BooleanOption( + _("Indicate non-birth relationships with dotted lines"), True) + dashed.set_help(_("Non-birth relationships will show up " + "as dotted lines in the graph.")) + menu.add_option(category_name,"dashed", dashed) + + showfamily = BooleanOption(_("Show family nodes"), True) + showfamily.set_help(_("Families will show up as ellipses, linked " + "to parents and children.")) + menu.add_option(category_name,"showfamily", showfamily) + +#------------------------------------------------------------------------ +# +# +# +#------------------------------------------------------------------------ +register_report( + name = 'rel_graph', + category = CATEGORY_GRAPHVIZ, + report_class = RelGraphReport, + options_class = RelGraphOptions, + modes = MODE_GUI | MODE_CLI, + translated_name = _("Relationship Graph"), + status = _("Stable"), + description = _("Generates a relationship graphs using Graphviz."), + author_name ="Brian G. Matherly", + author_email ="brian@gramps-project.org" + ) \ No newline at end of file diff --git a/src/plugins/GraphViz.py b/src/plugins/GraphViz.py index a774f4b5b..448fc5567 100644 --- a/src/plugins/GraphViz.py +++ b/src/plugins/GraphViz.py @@ -1318,16 +1318,17 @@ def get_description_graphics(): # #------------------------------------------------------------------------ register_report( - name = 'rel_graph', + name = 'rel_graph1', category = CATEGORY_CODE, report_class = GraphVizDialog, options_class = cl_report, modes = MODE_GUI | MODE_CLI, - translated_name = _("Relationship Graph"), + translated_name = _("Relationship Graph (code)"), status = _("Stable"), description= get_description(), author_name="Donald N. Allingham", - author_email="don@gramps-project.org" + author_email="don@gramps-project.org", + unsupported=True ) if _dot_found: @@ -1341,5 +1342,6 @@ if _dot_found: status = _("Stable"), description= get_description_graphics(), author_name="Donald N. Allingham", - author_email="don@gramps-project.org" + author_email="don@gramps-project.org", + unsupported=True )