Major updates to GraphViz plugin.

Re-factoring:
- split large functions to smaller ones to make code more readable
- renamed few methods and variables to what they actually do
- collect the report text to a buffer first so that it can be
  converted to latin-1 at one go
- Move common style stuff to GraphViz header and remove from .dot
  comments stuff that's obvious from first .dot file settings
Fixes:
- latin-1 conversion option is explicit, not depending on font
- set the GraphViz charset attribute according to latin1
- quote node and edge identifiers
- rankdir options correspond now to TB and LR (earlier LR and RL)
New features:
- Use christening/burial dates if birth/death dates are missing
- User can add a (e.g. copyright) note to the graph and set its
  location (newlines and double quotes are automatically escaped)
- User can give the paging direction
- User can set the GraphViz aspect ratio setting
- The weight option (either for children or parents)
- Women have rounded boxes, except in filled mode
  (current GraphViz doesn't support boxes that are both rounded and filled)
New default settings:
- set the ratio to "compress" instead of "fill"
- "mclimit=2.0", prevents crossed lines
- minimum node separation is 0.25


svn: r6010
This commit is contained in:
Eero Tamminen 2006-02-27 22:02:27 +00:00
parent 9209f7eca9
commit 2bf08ece8f
2 changed files with 436 additions and 237 deletions

View File

@ -1,3 +1,32 @@
2006-02-28 Eero Tamminen <eerot@sf>
* src/plugins/GraphViz.py: major changes:
Re-factoring:
- split large functions to smaller ones to make code more readable
- renamed few methods and variables to what they actually do
- collect the report text to a buffer first so that it can be
converted to latin-1 at one go
- Move common style stuff to GraphViz header and remove from .dot
comments stuff that's obvious from first .dot file settings
Fixes:
- latin-1 conversion option is explicit, not depending on font
- set the GraphViz charset attribute according to latin1
- quote node and edge identifiers
- rankdir options correspond now to TB and LR (earlier LR and RL)
New features:
- Use christening/burial dates if birth/death dates are missing
- User can add a (e.g. copyright) note to the graph and set its
location (newlines and double quotes are automatically escaped)
- User can give the paging direction
- User can set the GraphViz aspect ratio setting
- The weight option (either for children or parents)
- Women have rounded boxes, except in filled mode
(current GraphViz doesn't support boxes that are
both rounded and filled)
New default settings:
- set the ratio to "compress" instead of "fill"
- "mclimit=2.0", prevents crossed lines
- minimum node separation is 0.25
2006-02-27 Alex Roitman <shura@gramps-project.org> 2006-02-27 Alex Roitman <shura@gramps-project.org>
* configure.in: Bump up release number. * configure.in: Bump up release number.
* Release: Version 2.0.10 "Holy Hand Grenade of Antioch" released. * Release: Version 2.0.10 "Holy Hand Grenade of Antioch" released.

View File

@ -18,7 +18,6 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# #
# $Id$ # $Id$
"Generate files/Relationship graph" "Generate files/Relationship graph"
@ -68,19 +67,42 @@ class _options:
("gif", "GIF image", _("GIF image")), ("gif", "GIF image", _("GIF image")),
) )
fonts = ( fonts = (
# Last items tells whether strings need to be converted to Latin1 ("", "Default", _("Default")),
("", "Default", _("Default"), 1), ("Helvetica", "Postscript / Helvetica", _("Postscript / Helvetica")),
("Helvetica", "Postscript / Helvetica", _("Postscript / Helvetica"), 1), ("FreeSans", "Truetype / FreeSans", _("Truetype / FreeSans")),
("FreeSans", "Truetype / FreeSans", _("Truetype / FreeSans"), 0),
) )
colors = ( colors = (
("outline", "B&W Outline", _("B&W outline")), ("outline", "B&W Outline", _("B&W outline")),
("colored", "Colored outline", _("Colored outline")), ("colored", "Colored outline", _("Colored outline")),
("filled", "Color fill", _("Color fill")), ("filled", "Color fill", _("Color fill")),
) )
ratio = (
("compress", "Minimal size", _("Minimal size")),
("fill", "Fill the given area", _("Fill the given area")),
("expand", "Automatically use optimal number of pages", _("Automatically use optimal number of pages"))
)
attract = (
("parents", "Parents", _("Parents")),
("children", "Children", _("Children")),
("none", "Neither", _("Neither"))
)
rankdir = ( rankdir = (
("TB", "Vertical", _("Vertical")),
("LR", "Horizontal", _("Horizontal")), ("LR", "Horizontal", _("Horizontal")),
("RL", "Vertical", _("Vertical")), )
pagedir = (
("BL", "Bottom, left", _("Bottom, left")),
("BR", "Bottom, right", _("Bottom, right")),
("TL", "Top, left", _("Top, left")),
("TR", "Top, right", _("Top, Right")),
("RB", "Right, bottom", _("Right, bottom")),
("RT", "Right, top", _("Right, top")),
("LB", "Left, bottom", _("Left, bottom")),
("LT", "Left, top", _("Left, top")),
)
noteloc = (
("t", "Top", _("Top")),
("b", "Bottom", _("Bottom")),
) )
arrowstyles = ( arrowstyles = (
('d', "Descendants <- Ancestors", _("Descendants <- Ancestors")), ('d', "Descendants <- Ancestors", _("Descendants <- Ancestors")),
@ -115,7 +137,7 @@ class GraphViz:
The option class carries its number, and the function The option class carries its number, and the function
returning the list of filters. returning the list of filters.
font - Font to use. font - Font to use.
latin - Set if font supports only Latin1 latin - Set if text needs to be converted to latin-1
arrow - Arrow styles for heads and tails. arrow - Arrow styles for heads and tails.
showfamily - Whether to show family nodes. showfamily - Whether to show family nodes.
incid - Whether to include IDs. incid - Whether to include IDs.
@ -123,12 +145,16 @@ class GraphViz:
justyears - Use years only. justyears - Use years only.
placecause - Whether to replace missing dates with place or cause placecause - Whether to replace missing dates with place or cause
url - Whether to include URLs. url - Whether to include URLs.
rankdir - Graph direction rankdir - Graph direction, LR or RL
ratio - Output aspect ration, fill/compress/auto
color - Whether to use outline, colored outline or filled color in graph color - Whether to use outline, colored outline or filled color in graph
dashedl - Whether to use dashed lines for non-birth relationships. dashedl - Whether to use dashed lines for non-birth relationships.
margin - Margins, in cm. margin - Margins, in cm.
pagesh - Number of pages in horizontal direction. pagesh - Number of pages in horizontal direction.
pagesv - Number of pages in vertical direction. pagesv - Number of pages in vertical direction.
pagedir - Paging direction
note - Note to add to the graph
noteloc - Note location t/b
""" """
colored = { colored = {
'male': 'dodgerblue4', 'male': 'dodgerblue4',
@ -151,11 +177,14 @@ class GraphViz:
self.height = self.paper.get_height_inches() self.height = self.paper.get_height_inches()
options = options_class.handler.options_dict options = options_class.handler.options_dict
self.pagedir = options['pagedir']
self.hpages = options['pagesh'] self.hpages = options['pagesh']
self.vpages = options['pagesv'] self.vpages = options['pagesv']
margin_cm = options['margin'] margin_cm = options['margin']
self.margin = round(margin_cm/2.54,2) self.margin = round(margin_cm/2.54,2)
if margin_cm > 0.1: if margin_cm > 0.1:
# GraphViz has rounding errors so have to make the real
# margins slightly smaller than (page - content size)
self.margin_small = round((margin_cm-0.1)/2.54,2) self.margin_small = round((margin_cm-0.1)/2.54,2)
else: else:
self.margin_small = 0 self.margin_small = 0
@ -167,8 +196,9 @@ class GraphViz:
self.just_years = options['justyears'] self.just_years = options['justyears']
self.placecause = options['placecause'] self.placecause = options['placecause']
self.rankdir = options['rankdir'] self.rankdir = options['rankdir']
self.attract = options['attract']
self.ratio = options['ratio']
self.fontname = options['font'] self.fontname = options['font']
self.latin = options['latin']
self.colorize = options['color'] self.colorize = options['color']
if self.colorize == 'colored': if self.colorize == 'colored':
self.colors = colored self.colors = colored
@ -184,185 +214,300 @@ class GraphViz:
else: else:
self.arrowtailstyle = 'none' self.arrowtailstyle = 'none'
self.latin = options['latin']
self.noteloc = options['noteloc']
self.note = options['note']
filter_num = options_class.get_filter_number() filter_num = options_class.get_filter_number()
filters = options_class.get_report_filters(person) filters = options_class.get_report_filters(person)
filters.extend(GenericFilter.CustomFilters.get_filters()) filters.extend(GenericFilter.CustomFilters.get_filters())
self.filter = filters[filter_num] self.filter = filters[filter_num]
buffer = self.get_report()
self.f = open(options_class.get_output(),'w') self.f = open(options_class.get_output(),'w')
self.write_report() if self.latin:
self.f.write(buffer.encode('iso-8859-1'))
else:
self.f.write(buffer)
self.f.close() self.f.close()
def write_report(self):
self.ind_list = self.filter.apply(self.database, def get_report(self):
"return string of the .dot file contents"
self.person_handles = self.filter.apply(self.database,
self.database.get_person_handles(sort_handles=False)) self.database.get_person_handles(sort_handles=False))
self.write_header() # graph size
self.f.write("digraph GRAMPS_relationship_graph {\n")
self.f.write("bgcolor=white;\n")
self.f.write("rankdir=%s;\n" % self.rankdir)
self.f.write("center=1;\n")
self.f.write("margin=%3.2f;\n" % self.margin_small)
self.f.write("ratio=fill;\n")
if self.orient == PAPER_LANDSCAPE: if self.orient == PAPER_LANDSCAPE:
self.f.write("size=\"%3.2f,%3.2f\";\n" % ( rotate = 90
(self.height-self.margin*2)*self.hpages, sizew = (self.height - self.margin*2) * self.hpages
(self.width-self.margin*2)*self.vpages sizeh = (self.width - self.margin*2) * self.vpages
))
else: else:
self.f.write("size=\"%3.2f,%3.2f\";\n" % ( rotate = 0
(self.width-self.margin*2)*self.hpages, sizew = (self.width - self.margin*2) * self.hpages
(self.height-self.margin*2)*self.vpages sizeh = (self.height - self.margin*2) * self.vpages
))
self.f.write("page=\"%3.2f,%3.2f\";\n" % (self.width,self.height))
if self.orient == PAPER_LANDSCAPE:
self.f.write("rotate=90;\n")
if len(self.ind_list) > 1: buffer = self.get_comment_header()
self.dump_index() buffer += """
self.dump_person() digraph GRAMPS_relationship_graph {
/* whole graph attributes */
bgcolor=white;
center=1;
ratio=%s;
rankdir=%s;
mclimit=2.0;
margin="%3.2f";
pagedir="%s";
page="%3.2f,%3.2f";
size="%3.2f,%3.2f";
rotate=%d;
/* default node and edge attributes */
nodesep=0.25;
edge [syle=solid, arrowhead=%s arrowtail=%s];
""" % (
self.ratio,
self.rankdir,
self.margin_small,
self.pagedir,
self.width, self.height,
sizew, sizeh,
rotate,
self.arrowheadstyle,
self.arrowtailstyle
)
self.f.write("}\n") if self.fontname:
font = 'fontname="%s"' % self.fontname
else:
font = ''
if self.colorize == 'filled':
buffer += 'node [style=filled %s];\n' % font
else:
buffer += 'node [%s];\n' % font
if self.latin:
# GraphViz default is UTF-8
buffer += 'charset="iso-8859-1";\n'
def dump_person(self): if len(self.person_handles) > 1:
# Hash people in a dictionary for faster inclusion checking. buffer += "/* persons and their families */\n"
buffer += self.get_persons_and_families()
buffer += "/* link children to families */\n"
buffer += self.get_child_links_to_families()
if self.note:
buffer += 'labelloc="%s";\n' % self.noteloc
buffer += 'label="%s";\n' % self.note.replace('\n', '\\n').replace('"', '\\\"')
buffer += 'fontsize="20";\n' # in points
return buffer + "}\n"
def get_comment_header(self):
"return comment of Gramps options which are not Graphviz options"
return """/*
GRAMPS - Relationship graph
Generated on %s.
Report content options:
include URLs : %s
IDs : %s
dates : %s
just year : %s
place or cause : %s
colorize : %s
dotted adoptions : %s
show family nodes : %s
pages horizontal : %s
vertical : %s
For other options, see graph settings below.
If you need to switch between iso-8859-1 and utf-8 text encodings,
e.g. because you're using different font or -T output format,
just use iconv:
iconv -f iso-8859-1 -t utf-8 old.dot > new.dot
iconv -t utf-8 -f iso-8859-1 old.dot > new.dot
*/
""" % (
asctime(),
bool(self.includeurl),
bool(self.includeid),
bool(self.includedates),
bool(self.just_years),
bool(self.placecause),
bool(self.colorize),
bool(self.adoptionsdashed),
bool(self.show_families),
self.hpages, self.vpages
)
def get_child_links_to_families(self):
"returns string of GraphViz edges linking parents to families or children"
person_dict = {} person_dict = {}
for p_id in self.ind_list: # Hash people in a dictionary for faster inclusion checking
person_dict[p_id] = 1 for person_handle in self.person_handles:
person_dict[person_handle] = 1
for person_handle in self.ind_list: buffer = ""
for person_handle in self.person_handles:
person = self.database.get_person_from_handle(person_handle) person = self.database.get_person_from_handle(person_handle)
pid = person.get_gramps_id().replace('-','_') p_id = person.get_gramps_id()
for family_handle, mrel, frel in person.get_parent_family_handle_list(): for fam_handle,mrel,frel in person.get_parent_family_handle_list():
family = self.database.get_family_from_handle(family_handle) family = self.database.get_family_from_handle(fam_handle)
father_handle = family.get_father_handle() father_handle = family.get_father_handle()
mother_handle = family.get_mother_handle() mother_handle = family.get_mother_handle()
fadopted = frel != RelLib.Person.CHILD_REL_BIRTH
madopted = mrel != RelLib.Person.CHILD_REL_BIRTH
famid = family.get_gramps_id().replace('-','_')
if (self.show_families and if (self.show_families and
(father_handle and person_dict.has_key(father_handle) or ((father_handle and father_handle in person_dict) or
mother_handle and person_dict.has_key(mother_handle))): (mother_handle and mother_handle in person_dict))):
# Link to the family node. # Link to the family node if either parent is in graph
self.f.write('p%s -> f%s [' % (pid, famid)) buffer += self.get_family_link(p_id, family, frel, mrel)
self.f.write('arrowhead=%s, arrowtail=%s, ' %
(self.arrowheadstyle, self.arrowtailstyle))
if self.adoptionsdashed and (fadopted or madopted):
self.f.write('style=dotted')
else:
self.f.write('style=solid')
self.f.write('];\n')
else: else:
# Link to the parents' nodes directly. # Link to the parents' nodes directly, if they are in graph
if father_handle and person_dict.has_key(father_handle): if father_handle and father_handle in person_dict:
father = self.database.get_person_from_handle(father_handle) buffer += self.get_parent_link(p_id, father_handle, frel)
fid = father.get_gramps_id().replace('-','_') if mother_handle and mother_handle in person_dict:
self.f.write('p%s -> p%s [' % (pid, fid)) buffer += self.get_parent_link(p_id, mother_handle, mrel)
self.f.write('arrowhead=%s, arrowtail=%s, ' % return buffer
(self.arrowheadstyle, self.arrowtailstyle))
if self.adoptionsdashed and fadopted:
self.f.write('style=dotted')
else:
self.f.write('style=solid')
self.f.write('];\n')
if mother_handle and person_dict.has_key(mother_handle):
mother = self.database.get_person_from_handle(mother_handle)
mid = mother.get_gramps_id().replace('-','_')
self.f.write('p%s -> p%s [' % (pid, mid))
self.f.write('arrowhead=%s, arrowtail=%s, ' %
(self.arrowheadstyle, self.arrowtailstyle))
if self.adoptionsdashed and madopted:
self.f.write('style=dotted')
else:
self.f.write('style=solid')
self.f.write('];\n')
def dump_index(self):
# The list of families for which we have output the node, so we def get_family_link(self, p_id, family, frel, mrel):
# don't do it twice. "returns string of GraphViz edge linking child to family"
families_done = [] weight = style = ''
for person_handle in self.ind_list: adopted = ((frel != RelLib.Person.CHILD_REL_BIRTH) or
(mrel != RelLib.Person.CHILD_REL_BIRTH))
if adopted and self.adoptionsdashed:
style = 'style=dotted'
if self.attract == "children":
weight = 'weight="5"'
return '"p%s" -> "f%s" [%s %s];\n' % (p_id,
family.get_gramps_id(), style, weight)
def get_parent_link(self, p_id, parent_handle, rel):
"returns string of GraphViz edge linking child to parent"
style = ''
if (rel != RelLib.Person.CHILD_REL_BIRTH) and self.adoptionsdashed:
style = 'style=dotted'
parent = self.database.get_person_from_handle(parent_handle)
return '"p%s" -> "p%s" [%s];\n' % (p_id, parent.get_gramps_id(), style)
def get_persons_and_families(self):
"returns string of GraphViz nodes for persons and their families"
# The list of families for which we have output the node,
# so we don't do it twice
buffer = ""
families_done = {}
for person_handle in self.person_handles:
person = self.database.get_person_from_handle(person_handle) person = self.database.get_person_from_handle(person_handle)
# Output the person's node. p_id = person.get_gramps_id()
label = person.get_primary_name().get_name() # Output the person's node
the_id = person.get_gramps_id().replace('-','_') label = self.get_person_label(person)
if self.includeid: style = self.get_gender_style(person)
label = label + " (%s)" % the_id url = ""
if self.includedates:
birth_handle = person.get_birth_handle()
if birth_handle:
birth_event = self.database.get_event_from_handle(birth_handle)
birth = self.dump_event(birth_event)
else:
birth = ''
death_handle = person.get_death_handle()
if death_handle:
death_event = self.database.get_event_from_handle(death_handle)
death = self.dump_event(death_event)
else:
death = ''
label = label + '\\n(%s - %s)' % (birth, death)
self.f.write('p%s [shape=box, ' % the_id)
if self.includeurl: if self.includeurl:
h = person.get_handle() h = person_handle
self.f.write('URL="ppl/%s/%s/%s.html", ' % (h[0],h[1],h)) url = ', URL="ppl/%s/%s/%s.html", ' % (h[0],h[1],h)
if self.colorize != 'outline': buffer += '"p%s" [label="%s", %s%s];\n' % (p_id, label, style, url)
if self.colorize == 'filled':
style = 'style=filled, fillcolor'
else:
style = 'color'
gender = person.get_gender()
if gender == person.MALE:
self.f.write('%s=%s, ' % (style, self.colors['male']))
elif gender == person.FEMALE:
self.f.write('%s=%s, ' % (style, self.colors['female']))
else:
self.f.write('%s=%s, ' % (style, self.colors['unknown']))
if self.latin:
label = label.encode('iso-8859-1')
self.f.write('fontname="%s", label="%s"];\n' % (self.fontname,label))
# Output families's nodes. # Output families where person is a parent
if self.show_families: if self.show_families:
family_list = person.get_family_handle_list() family_list = person.get_family_handle_list()
for fam_handle in family_list: for fam_handle in family_list:
fam = self.database.get_family_from_handle(fam_handle) fam = self.database.get_family_from_handle(fam_handle)
fid = fam.get_gramps_id().replace('-','_') fam_id = fam.get_gramps_id()
if fam_handle not in families_done: if fam_handle not in families_done:
families_done.append(fam_handle) families_done[fam_handle] = 1
self.f.write('f%s [shape=ellipse, ' % fid) label = ""
if self.colorize == 'colored':
self.f.write('color=%s, ' % self.colors['family'])
elif self.colorize == 'filled':
self.f.write('style=filled fillcolor=%s, ' % self.colors['family'])
marriage = ""
for event_handle in fam.get_event_list(): for event_handle in fam.get_event_list():
if event_handle: if event_handle:
event = self.database.get_event_from_handle(event_handle) event = self.database.get_event_from_handle(event_handle)
if event.get_name() == "Marriage": if event.get_name() == "Marriage":
m = event label = self.get_event_string(event)
break break
else:
m = None
if m:
marriage = self.dump_event(m)
if self.includeid: if self.includeid:
marriage = marriage + " (%s)" % fid label = "%s (%s)" % (label, fam_id)
self.f.write('fontname="%s", label="%s"];\n' color = ""
% (self.fontname,marriage)) if self.colorize == 'colored':
color = ', color="%s"' % self.colors['family']
elif self.colorize == 'filled':
color = ', fillcolor="%s"' % self.colors['family']
buffer += '"f%s" [shape=ellipse, label="%s"%s];\n' % (fam_id, label, color)
# Link this person to all his/her families. # Link this person to all his/her families.
self.f.write('f%s -> p%s [' % (fid, the_id)) if self.attract == "parents":
self.f.write('arrowhead=%s, arrowtail=%s, ' % weight = 'weight="5"'
(self.arrowheadstyle, self.arrowtailstyle)) else:
self.f.write('style=solid];\n') weight = ''
buffer += '"f%s" -> "p%s" [%s];\n' % (fam_id, p_id, weight)
return buffer
def dump_event(self,event):
def get_gender_style(self, person):
"return gender specific person style"
gender = person.get_gender()
if gender == person.MALE:
shape = 'shape="box"'
elif gender == person.FEMALE:
shape = 'shape="box", style="rounded"'
else:
shape = 'shape="hexagon"'
if self.colorize == 'outline':
return shape
else:
if gender == person.MALE:
color = self.colors['male']
elif gender == person.FEMALE:
color = self.colors['female']
else:
color = self.colors['unknown']
if self.colorize == 'filled':
# In current GraphViz boxes cannot be both rounded and filled
return 'shape="box", fillcolor="%s"' % color
else:
return '%s, color="%s"' % (shape, color)
def get_person_label(self, person):
"return person label string"
label = person.get_primary_name().get_name()
p_id = person.get_gramps_id()
if self.includeid:
label = label + " (%s)" % p_id
if self.includedates:
birth, death = self.get_date_strings(person)
label = label + '\\n(%s - %s)' % (birth, death)
return label.replace('"', '\\\"')
def get_date_strings(self, person):
"returns tuple of birth/christening and death/burying date strings"
birth_handle = person.get_birth_handle()
if birth_handle:
birth_event = self.database.get_event_from_handle(birth_handle)
birth = self.get_event_string(birth_event)
else:
birth = ''
death_handle = person.get_death_handle()
if death_handle:
death_event = self.database.get_event_from_handle(death_handle)
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_handle in person.get_event_list():
event = self.database.get_event_from_handle(event_handle)
if event.get_name() == "Christening":
if not birth:
birth = self.get_event_string(event)
elif event.get_name() == "Burial":
if not death:
death = self.get_event_string(event)
return (birth, death)
def get_event_string(self, event):
""" """
Compile an event label. return string for for an event label.
Based on the data availability and preferences, we select one Based on the data availability and preferences, we select one
of the following for a given event: of the following for a given event:
@ -386,33 +531,6 @@ class GraphViz:
return event.get_cause() return event.get_cause()
return '' return ''
def write_header(self):
"""
Write header listing the options used.
"""
self.f.write("/* GRAMPS - Relationship graph\n")
self.f.write(" *\n")
self.f.write(" * Report options:\n")
self.f.write(" * font style : %s\n" % self.fontname)
self.f.write(" * style arrow head : %s\n" % self.arrowheadstyle)
self.f.write(" * tail : %s\n" % self.arrowtailstyle)
self.f.write(" * graph direction : %s\n" % self.rankdir)
self.f.write(" * include URLs : %s\n" % bool(self.includeurl))
self.f.write(" * IDs : %s\n" % bool(self.includeid))
self.f.write(" * dates : %s\n" % bool(self.includedates))
self.f.write(" * just year : %s\n" % bool(self.just_years))
self.f.write(" * place or cause : %s\n" % bool(self.placecause))
self.f.write(" * colorize : %s\n" % bool(self.colorize))
self.f.write(" * dotted adoptions : %s\n" % bool(self.adoptionsdashed))
self.f.write(" * show family nodes : %s\n" % bool(self.show_families))
self.f.write(" * margin : %3.2fin\n" % self.margin_small)
self.f.write(" * pages horizontal : %s\n" % self.hpages)
self.f.write(" * vertical : %s\n" % self.vpages)
self.f.write(" * page width : %3.2fin\n" % self.width)
self.f.write(" * height : %3.2fin\n" % self.height)
self.f.write(" *\n")
self.f.write(" * Generated on %s by GRAMPS\n" % asctime())
self.f.write(" */\n\n")
#------------------------------------------------------------------------ #------------------------------------------------------------------------
# #
@ -440,12 +558,17 @@ class GraphVizOptions(ReportOptions.ReportOptions):
'justyears' : 0, 'justyears' : 0,
'placecause' : 1, 'placecause' : 1,
'url' : 1, 'url' : 1,
'ratio' : "compress",
'attract' : "none",
'rankdir' : "LR", 'rankdir' : "LR",
'color' : "filled", 'color' : "filled",
'dashedl' : 1, 'dashedl' : 1,
'margin' : 1.0, 'margin' : 1.0,
'pagedir' : 'BL',
'pagesh' : 1, 'pagesh' : 1,
'pagesv' : 1, 'pagesv' : 1,
'note' : '',
'noteloc' : 'b',
'gvof' : 'ps', 'gvof' : 'ps',
} }
@ -477,7 +600,13 @@ class GraphVizOptions(ReportOptions.ReportOptions):
'url' : ("=0/1","Whether to include URLs.", 'url' : ("=0/1","Whether to include URLs.",
["Do not include URLs","Include URLs"], ["Do not include URLs","Include URLs"],
True), True),
'rankdir' : ("=str","Graph direction.", 'ratio' : ("=str","Graph aspect ratio.",
[ "%s\t%s" % (item[0],item[1]) for item in _options.ratio ],
False),
'attract' : ("=str","What to group closer.",
[ "%s\t%s" % (item[0],item[1]) for item in _options.attract ],
False),
'rankdir' : ("=str","Graph direction.",
[ "%s\t%s" % (item[0],item[1]) for item in _options.rankdir ], [ "%s\t%s" % (item[0],item[1]) for item in _options.rankdir ],
False), False),
'color' : ("=str","Whether and how to colorize graph.", 'color' : ("=str","Whether and how to colorize graph.",
@ -488,10 +617,18 @@ class GraphVizOptions(ReportOptions.ReportOptions):
True), True),
'margin' : ("=num","Margin size.", 'margin' : ("=num","Margin size.",
"Floating point value, in cm"), "Floating point value, in cm"),
'pagedir' : ("=str","Paging direction.",
[ "%s\t%s" % (item[0],item[1]) for item in _options.pagedir ],
False),
'pagesh' : ("=num","Number of pages in horizontal direction.", 'pagesh' : ("=num","Number of pages in horizontal direction.",
"Integer values"), "Integer values"),
'pagesv' : ("=num","Number of pages in vertical direction.", 'pagesv' : ("=num","Number of pages in vertical direction.",
"Integer values"), "Integer values"),
'note' : ("=str","Note to add to the graph.",
"Text"),
'noteloc' : ("=str","Note location.",
[ "%s\t%s" % (item[0],item[1]) for item in _options.noteloc ],
False),
'gvof' : ("=str","Output format to convert dot file into.", 'gvof' : ("=str","Output format to convert dot file into.",
[ "%s\t%s" % (item[0],item[1]) for item in _options.formats ], [ "%s\t%s" % (item[0],item[1]) for item in _options.formats ],
False), False),
@ -533,6 +670,22 @@ class GraphVizOptions(ReportOptions.ReportOptions):
def make_doc_menu(self,dialog,active=None): def make_doc_menu(self,dialog,active=None):
pass pass
def add_list(self, options, default):
"returns compobox of given options and default value"
box = gtk.ComboBox()
store = gtk.ListStore(str)
box.set_model(store)
cell = gtk.CellRendererText()
box.pack_start(cell,True)
box.add_attribute(cell,'text',0)
index = 0
for item in options:
store.append(row=[item[2]])
if item[0] == default:
box.set_active(index)
index = index + 1
return box
def add_user_options(self,dialog): def add_user_options(self,dialog):
if self.handler.module_name == "rel_graph2": if self.handler.module_name == "rel_graph2":
dialog.make_doc_menu = self.make_doc_menu dialog.make_doc_menu = self.make_doc_menu
@ -586,36 +739,16 @@ class GraphVizOptions(ReportOptions.ReportOptions):
_("Include individual and family IDs.")) _("Include individual and family IDs."))
# GraphViz output options tab # GraphViz output options tab
self.rank_box = gtk.ComboBox() self.rank_box = self.add_list(_options.rankdir,
store = gtk.ListStore(str) self.options_dict['rankdir'])
self.rank_box.set_model(store)
cell = gtk.CellRendererText()
self.rank_box.pack_start(cell,True)
self.rank_box.add_attribute(cell,'text',0)
index = 0
for item in _options.rankdir:
store.append(row=[item[2]])
if item[0] == self.options_dict['rankdir']:
self.rank_box.set_active(index)
index = index + 1
dialog.add_frame_option(_("GraphViz Options"), dialog.add_frame_option(_("GraphViz Options"),
_("Graph direction"), _("Graph direction"),
self.rank_box, self.rank_box,
_("Whether generations go from top to bottom " _("Whether generations go from top to bottom "
"or left to right.")) "or left to right."))
self.color_box = gtk.ComboBox() self.color_box = self.add_list(_options.colors,
store = gtk.ListStore(str) self.options_dict['color'])
self.color_box.set_model(store)
cell = gtk.CellRendererText()
self.color_box.pack_start(cell,True)
self.color_box.add_attribute(cell,'text',0)
index = 0
for item in _options.colors:
store.append(row=[item[2]])
if item[0] == self.options_dict['color']:
self.color_box.set_active(index)
index = index + 1
dialog.add_frame_option(_("GraphViz Options"), dialog.add_frame_option(_("GraphViz Options"),
_("Graph coloring"), _("Graph coloring"),
self.color_box, self.color_box,
@ -623,39 +756,15 @@ class GraphVizOptions(ReportOptions.ReportOptions):
"with red. If the sex of an individual " "with red. If the sex of an individual "
"is unknown it will be shown with gray.")) "is unknown it will be shown with gray."))
self.arrowstyle_box = gtk.ComboBox() self.arrowstyle_box = self.add_list(_options.arrowstyles,
store = gtk.ListStore(str) self.options_dict['arrow'])
self.arrowstyle_box.set_model(store)
cell = gtk.CellRendererText()
self.arrowstyle_box.pack_start(cell,True)
self.arrowstyle_box.add_attribute(cell,'text',0)
index = 0
for item in _options.arrowstyles:
store.append(row=[item[2]])
if item[0] == self.options_dict['arrow']:
self.arrowstyle_box.set_active(index)
index = index + 1
dialog.add_frame_option(_("GraphViz Options"), dialog.add_frame_option(_("GraphViz Options"),
_("Arrowhead direction"), _("Arrowhead direction"),
self.arrowstyle_box, self.arrowstyle_box,
_("Choose the direction that the arrows point.")) _("Choose the direction that the arrows point."))
self.font_box = gtk.ComboBox() self.font_box = self.add_list(_options.fonts,
store = gtk.ListStore(str) self.options_dict['font'])
self.font_box.set_model(store)
cell = gtk.CellRendererText()
self.font_box.pack_start(cell,True)
self.font_box.add_attribute(cell,'text',0)
index = 0
for item in _options.fonts:
if item[3]:
name = "%s (iso-latin1 font)" % item[2]
else:
name = item[2]
store.append(row=[name])
if item[0] == self.options_dict['font']:
self.font_box.set_active(index)
index = index + 1
dialog.add_frame_option(_("GraphViz Options"), dialog.add_frame_option(_("GraphViz Options"),
_("Font family"), _("Font family"),
self.font_box, self.font_box,
@ -664,6 +773,13 @@ class GraphVizOptions(ReportOptions.ReportOptions):
"FreeSans is available from: " "FreeSans is available from: "
"http://www.nongnu.org/freefont/")) "http://www.nongnu.org/freefont/"))
self.latin_cb = gtk.CheckButton(_("Output format/font requires text as latin-1"))
self.latin_cb.set_active(self.options_dict['latin'])
dialog.add_frame_option(_("GraphViz Options"), '',
self.latin_cb,
_("If text doesn't show correctly in report, use this. "
"Required e.g. for default font with PS output."))
self.adoptionsdashed_cb = gtk.CheckButton(_("Indicate non-birth relationships with dotted lines")) self.adoptionsdashed_cb = gtk.CheckButton(_("Indicate non-birth relationships with dotted lines"))
self.adoptionsdashed_cb.set_active(self.options_dict['dashedl']) self.adoptionsdashed_cb.set_active(self.options_dict['dashedl'])
dialog.add_frame_option(_("GraphViz Options"), '', dialog.add_frame_option(_("GraphViz Options"), '',
@ -678,13 +794,31 @@ class GraphVizOptions(ReportOptions.ReportOptions):
_("Families will show up as ellipses, linked " _("Families will show up as ellipses, linked "
"to parents and children.")) "to parents and children."))
# Page options tab # Page/layout options tab
self.attract_box = self.add_list(_options.attract,
self.options_dict['attract'])
dialog.add_frame_option(_("Layout Options"),
_("What to group closer together"),
self.attract_box,
_("Whether to group parents or children "
"together or whether both have same "
"weights."))
self.ratio_box = self.add_list(_options.ratio,
self.options_dict['ratio'])
dialog.add_frame_option(_("Layout Options"),
_("Aspect ratio"),
self.ratio_box,
_("Affects greatly how the graph is layed out "
"on the page. Multiple pages overrides the "
"pages settings below."))
margin_adj = gtk.Adjustment(value=self.options_dict['margin'], margin_adj = gtk.Adjustment(value=self.options_dict['margin'],
lower=0.0, upper=10.0, step_incr=1.0) lower=0.0, upper=10.0, step_incr=1.0)
self.margin_sb = gtk.SpinButton(adjustment=margin_adj, digits=1) self.margin_sb = gtk.SpinButton(adjustment=margin_adj, digits=1)
dialog.add_frame_option(_("Page Options"), dialog.add_frame_option(_("Layout Options"),
_("Margin size"), _("Margin size"),
self.margin_sb) self.margin_sb)
@ -696,14 +830,14 @@ class GraphVizOptions(ReportOptions.ReportOptions):
self.hpages_sb = gtk.SpinButton(adjustment=hpages_adj, digits=0) self.hpages_sb = gtk.SpinButton(adjustment=hpages_adj, digits=0)
self.vpages_sb = gtk.SpinButton(adjustment=vpages_adj, digits=0) self.vpages_sb = gtk.SpinButton(adjustment=vpages_adj, digits=0)
dialog.add_frame_option(_("Page Options"), dialog.add_frame_option(_("Layout Options"),
_("Number of Horizontal Pages"), _("Number of Horizontal Pages"),
self.hpages_sb, self.hpages_sb,
_("GraphViz can create very large graphs by " _("GraphViz can create very large graphs by "
"spreading the graph across a rectangular " "spreading the graph across a rectangular "
"array of pages. This controls the number " "array of pages. This controls the number "
"pages in the array horizontally.")) "pages in the array horizontally."))
dialog.add_frame_option(_("Page Options"), dialog.add_frame_option(_("Layout Options"),
_("Number of Vertical Pages"), _("Number of Vertical Pages"),
self.vpages_sb, self.vpages_sb,
_("GraphViz can create very large graphs " _("GraphViz can create very large graphs "
@ -711,6 +845,32 @@ class GraphVizOptions(ReportOptions.ReportOptions):
"rectangular array of pages. This " "rectangular array of pages. This "
"controls the number pages in the array " "controls the number pages in the array "
"vertically.")) "vertically."))
self.pagedir_box = self.add_list(_options.pagedir,
self.options_dict['pagedir'])
dialog.add_frame_option(_("Layout Options"),
_("Paging direction"),
self.pagedir_box,
_("The order in which the graph pages are output."))
# Notes tab
self.textbox = gtk.TextView()
self.textbox.get_buffer().set_text(self.options_dict['note'])
self.textbox.set_editable(1)
swin = gtk.ScrolledWindow()
swin.set_shadow_type(gtk.SHADOW_IN)
swin.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)
swin.add(self.textbox)
dialog.add_frame_option(_("Notes"),
_("Note to add to the graph"),
swin,
_("This text will be added to the graph."))
self.noteloc_box = self.add_list(_options.noteloc,
self.options_dict['noteloc'])
dialog.add_frame_option(_("Notes"),
_("Note location"),
self.noteloc_box,
_("Whether note will on top or bottom of the page."))
def toggle_date(self, obj): def toggle_date(self, obj):
self.just_years_cb.set_sensitive(self.includedates_cb.get_active()) self.just_years_cb.set_sensitive(self.includedates_cb.get_active())
@ -727,6 +887,10 @@ class GraphVizOptions(ReportOptions.ReportOptions):
self.options_dict['incid'] = int(self.includeid_cb.get_active()) self.options_dict['incid'] = int(self.includeid_cb.get_active())
self.options_dict['justyears'] = int(self.just_years_cb.get_active()) self.options_dict['justyears'] = int(self.just_years_cb.get_active())
self.options_dict['placecause'] = int(self.place_cause_cb.get_active()) self.options_dict['placecause'] = int(self.place_cause_cb.get_active())
self.options_dict['attract'] = \
_options.attract[self.attract_box.get_active()][0]
self.options_dict['ratio'] = \
_options.ratio[self.ratio_box.get_active()][0]
self.options_dict['rankdir'] = \ self.options_dict['rankdir'] = \
_options.rankdir[self.rank_box.get_active()][0] _options.rankdir[self.rank_box.get_active()][0]
self.options_dict['color'] = \ self.options_dict['color'] = \
@ -735,8 +899,14 @@ class GraphVizOptions(ReportOptions.ReportOptions):
_options.arrowstyles[self.arrowstyle_box.get_active()][0] _options.arrowstyles[self.arrowstyle_box.get_active()][0]
self.options_dict['font'] = \ self.options_dict['font'] = \
_options.fonts[self.font_box.get_active()][0] _options.fonts[self.font_box.get_active()][0]
self.options_dict['latin'] = \ self.options_dict['pagedir'] = \
_options.fonts[self.font_box.get_active()][3] _options.pagedir[self.pagedir_box.get_active()][0]
self.options_dict['noteloc'] = \
_options.noteloc[self.noteloc_box.get_active()][0]
b = self.textbox.get_buffer()
self.options_dict['note'] = \
b.get_text(b.get_start_iter(), b.get_end_iter(), False)
if self.handler.module_name == "rel_graph2": if self.handler.module_name == "rel_graph2":
self.options_dict['gvof'] = dialog.format_menu.get_format_str() self.options_dict['gvof'] = dialog.format_menu.get_format_str()