~rosco2/ubuntu/wily/gramps/bug-1492304

« back to all changes in this revision

Viewing changes to src/plugins/GraphViz.py

  • Committer: Bazaar Package Importer
  • Author(s): James A. Treacy
  • Date: 2004-06-16 16:53:36 UTC
  • Revision ID: james.westby@ubuntu.com-20040616165336-kjzczqef4gnxrn2b
Tags: upstream-1.0.4
ImportĀ upstreamĀ versionĀ 1.0.4

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# Gramps - a GTK+/GNOME based genealogy program
 
3
#
 
4
# Copyright (C) 2000-2004  Donald N. Allingham
 
5
#
 
6
# This program is free software; you can redistribute it and/or modify
 
7
# it under the terms of the GNU General Public License as published by
 
8
# the Free Software Foundation; either version 2 of the License, or
 
9
# (at your option) any later version.
 
10
#
 
11
# This program is distributed in the hope that it will be useful,
 
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
14
# GNU General Public License for more details.
 
15
#
 
16
# You should have received a copy of the GNU General Public License
 
17
# along with this program; if not, write to the Free Software
 
18
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
19
#
 
20
 
 
21
# $Id: GraphViz.py,v 1.23.2.3 2004/03/19 00:29:04 rshura Exp $
 
22
 
 
23
"Generate files/Relationship graph"
 
24
 
 
25
#------------------------------------------------------------------------
 
26
#
 
27
# python modules
 
28
#
 
29
#------------------------------------------------------------------------
 
30
import os
 
31
import string
 
32
 
 
33
#------------------------------------------------------------------------
 
34
#
 
35
# GNOME/gtk
 
36
#
 
37
#------------------------------------------------------------------------
 
38
import gtk
 
39
 
 
40
#------------------------------------------------------------------------
 
41
#
 
42
# GRAMPS modules
 
43
#
 
44
#------------------------------------------------------------------------
 
45
import Utils
 
46
import Report
 
47
import BaseDoc
 
48
import GenericFilter
 
49
import Errors
 
50
 
 
51
from gettext import gettext as _
 
52
from latin_utf8 import utf8_to_latin
 
53
 
 
54
#------------------------------------------------------------------------
 
55
#
 
56
# constants
 
57
#
 
58
#------------------------------------------------------------------------
 
59
 
 
60
_PS_FONT = 'Helvetica'
 
61
_TT_FONT = 'FreeSans'
 
62
 
 
63
#------------------------------------------------------------------------
 
64
#
 
65
# GraphVizDialog
 
66
#
 
67
#------------------------------------------------------------------------
 
68
class GraphVizDialog(Report.ReportDialog):
 
69
 
 
70
    report_options = {}
 
71
 
 
72
    def __init__(self,database,person):
 
73
 
 
74
        Report.ReportDialog.__init__(self,database,person,self.report_options)
 
75
 
 
76
    def get_title(self):
 
77
        """The window title for this dialog"""
 
78
        return "%s - %s - GRAMPS" % (_("Relationship Graph"),
 
79
                                     _("Graphical Reports"))
 
80
 
 
81
    def get_target_browser_title(self):
 
82
        """The title of the window created when the 'browse' button is
 
83
        clicked in the 'Save As' frame."""
 
84
        return _("Graphviz File")
 
85
 
 
86
    def get_report_generations(self):
 
87
        """Default to 10 generations, no page breaks."""
 
88
        return (10, 0)
 
89
 
 
90
    def get_report_filters(self):
 
91
        """Set up the list of possible content filters."""
 
92
 
 
93
        name = self.person.getPrimaryName().getName()
 
94
        
 
95
        all = GenericFilter.GenericFilter()
 
96
        all.set_name(_("Entire Database"))
 
97
        all.add_rule(GenericFilter.Everyone([]))
 
98
 
 
99
        des = GenericFilter.GenericFilter()
 
100
        des.set_name(_("Descendants of %s") % name)
 
101
        des.add_rule(GenericFilter.IsDescendantOf([self.person.getId(),1]))
 
102
 
 
103
        ans = GenericFilter.GenericFilter()
 
104
        ans.set_name(_("Ancestors of %s") % name)
 
105
        ans.add_rule(GenericFilter.IsAncestorOf([self.person.getId(),1]))
 
106
 
 
107
        com = GenericFilter.GenericFilter()
 
108
        com.set_name(_("People with common ancestor with %s") % name)
 
109
        com.add_rule(GenericFilter.HasCommonAncestorWith([self.person.getId()]))
 
110
 
 
111
        return [all,des,ans,com]
 
112
 
 
113
    def add_user_options(self):
 
114
        self.arrowstyle_optionmenu = gtk.OptionMenu()
 
115
        menu = gtk.Menu()
 
116
 
 
117
        menuitem = gtk.MenuItem(_("Descendants <- Ancestors"))
 
118
        menuitem.set_data('t', ('none', 'normal'))
 
119
        menuitem.show()
 
120
        menu.append(menuitem)
 
121
 
 
122
        menuitem = gtk.MenuItem(_("Descendants -> Ancestors"))
 
123
        menuitem.set_data('t', ('normal', 'none'))
 
124
        menuitem.show()
 
125
        menu.append(menuitem)
 
126
 
 
127
        menuitem = gtk.MenuItem(_("Descendants <-> Ancestors"))
 
128
        menuitem.set_data('t', ('normal', 'normal'))
 
129
        menuitem.show()
 
130
        menu.append(menuitem)
 
131
 
 
132
        menuitem = gtk.MenuItem(_("Descendants - Ancestors"))
 
133
        menuitem.set_data('t', ('none', 'none'))
 
134
        menuitem.show()
 
135
        menu.append(menuitem)
 
136
 
 
137
        menu.set_active(0)
 
138
 
 
139
        self.arrowstyle_optionmenu.set_menu(menu)
 
140
 
 
141
        self.font_optionmenu = gtk.OptionMenu()
 
142
        menu = gtk.Menu()
 
143
 
 
144
        menuitem = gtk.MenuItem(_("TrueType"))
 
145
        menuitem.set_data('t', _TT_FONT)
 
146
        menuitem.show()
 
147
        menu.append(menuitem)
 
148
 
 
149
        menuitem = gtk.MenuItem(_("PostScript"))
 
150
        menuitem.set_data('t', _PS_FONT)
 
151
        menuitem.show()
 
152
        menu.append(menuitem)
 
153
 
 
154
        self.font_optionmenu.set_menu(menu)
 
155
 
 
156
        self.add_frame_option(_("GraphViz Options"),
 
157
                              _("Font Options"),
 
158
                              self.font_optionmenu,
 
159
                              _("Choose the font family."))
 
160
 
 
161
        self.add_frame_option(_("GraphViz Options"),
 
162
                              _("Arrowhead Options"),
 
163
                              self.arrowstyle_optionmenu,
 
164
                              _("Choose the direction that the arrows point."))
 
165
        
 
166
        msg = _("Include Birth, Marriage and Death Dates")
 
167
        self.includedates_cb = gtk.CheckButton(msg)
 
168
        self.includedates_cb.set_active(1)
 
169
        self.add_frame_option(_("GraphViz Options"), '',
 
170
                              self.includedates_cb,
 
171
                              _("Include the dates that the individual "
 
172
                                "was born, got married and/or died "
 
173
                                "in the graph labels."))
 
174
 
 
175
        self.just_year_cb = gtk.CheckButton(_("Limit dates to years only"))
 
176
        self.just_year_cb.set_active(0)
 
177
        self.add_frame_option(_("GraphViz Options"), '',
 
178
                              self.just_year_cb,
 
179
                              _("Prints just dates' year, neither "
 
180
                                "month or day nor date approximation "
 
181
                                "or interval are shown."))
 
182
 
 
183
        self.includedates_cb.connect('toggled',self.toggle_date)
 
184
 
 
185
        self.includeurl_cb = gtk.CheckButton(_("Include URLs"))
 
186
        self.includeurl_cb.set_active(1)
 
187
        self.add_frame_option(_("GraphViz Options"), '',
 
188
                              self.includeurl_cb,
 
189
                              _("Include a URL in each graph node so "
 
190
                                "that PDF and imagemap files can be "
 
191
                                "generated that contain active links "
 
192
                                "to the files generated by the 'Generate "
 
193
                                "Web Site' report."))
 
194
 
 
195
        self.colorize_cb = gtk.CheckButton(_("Colorize Graph"))
 
196
        self.colorize_cb.set_active(1)
 
197
        self.add_frame_option(_("GraphViz Options"),
 
198
                              '',
 
199
                              self.colorize_cb,
 
200
                              _("Males will be outlined in blue, females "
 
201
                                "will be outlined in pink.  If the sex of "
 
202
                                "an individual is unknown it will be "
 
203
                                "outlined in black."))
 
204
 
 
205
        self.adoptionsdashed_cb = gtk.CheckButton(_("Indicate non-birth relationships with dashed lines"))
 
206
        self.adoptionsdashed_cb.set_active(1)
 
207
        self.add_frame_option(_("GraphViz Options"),
 
208
                              '',
 
209
                              self.adoptionsdashed_cb,
 
210
                              _("Non-birth relationships will show up "
 
211
                                "as dashed lines in the graph."))
 
212
 
 
213
        self.show_families_cb = gtk.CheckButton(_("Show family nodes"))
 
214
        self.show_families_cb.set_active(0)
 
215
        self.add_frame_option(_("GraphViz Options"),
 
216
                              '',
 
217
                              self.show_families_cb,
 
218
                              _("Families will show up as ellipses, linked "
 
219
                                "to parents and children."))
 
220
 
 
221
        tb_margin_adj = gtk.Adjustment(value=0.5, lower=0.25,
 
222
                                      upper=100.0, step_incr=0.25)
 
223
        lr_margin_adj = gtk.Adjustment(value=0.5, lower=0.25,
 
224
                                      upper=100.0, step_incr=0.25)
 
225
 
 
226
        self.tb_margin_sb = gtk.SpinButton(adjustment=tb_margin_adj, digits=2)
 
227
        self.lr_margin_sb = gtk.SpinButton(adjustment=lr_margin_adj, digits=2)
 
228
 
 
229
        self.add_frame_option(_("Page Options"),
 
230
                              _("Top & Bottom Margins"),
 
231
                              self.tb_margin_sb)
 
232
        self.add_frame_option(_("Page Options"),
 
233
                              _("Left & Right Margins"),
 
234
                              self.lr_margin_sb)
 
235
 
 
236
        hpages_adj = gtk.Adjustment(value=1, lower=1, upper=25, step_incr=1)
 
237
        vpages_adj = gtk.Adjustment(value=1, lower=1, upper=25, step_incr=1)
 
238
 
 
239
        self.hpages_sb = gtk.SpinButton(adjustment=hpages_adj, digits=0)
 
240
        self.vpages_sb = gtk.SpinButton(adjustment=vpages_adj, digits=0)
 
241
 
 
242
        self.add_frame_option(_("Page Options"),
 
243
                              _("Number of Horizontal Pages"),
 
244
                              self.hpages_sb,
 
245
                              _("GraphViz can create very large graphs by "
 
246
                                "spreading the graph across a rectangular "
 
247
                                "array of pages. This controls the number "
 
248
                                "pages in the array horizontally."))
 
249
        self.add_frame_option(_("Page Options"),
 
250
                              _("Number of Vertical Pages"),
 
251
                              self.vpages_sb,
 
252
                              _("GraphViz can create very large graphs "
 
253
                                "by spreading the graph across a "
 
254
                                "rectangular array of pages. This "
 
255
                                "controls the number pages in the array "
 
256
                                "vertically."))
 
257
 
 
258
    def toggle_date(self,obj):
 
259
        if self.includedates_cb.get_active():
 
260
            self.just_year_cb.set_sensitive(1)
 
261
        else:
 
262
            self.just_year_cb.set_sensitive(0)
 
263
 
 
264
    def make_doc_menu(self):
 
265
        """Build a one item menu of document types that are
 
266
        appropriate for this report."""
 
267
        name = "Graphviz (dot)"
 
268
        menuitem = gtk.MenuItem (name)
 
269
        menuitem.set_data ("d", name)
 
270
        menuitem.set_data("paper",1)
 
271
        if os.system ("dot </dev/null 2>/dev/null") == 0:
 
272
            menuitem.set_data ("printable", _("Generate print output"))
 
273
        menuitem.show ()
 
274
        myMenu = gtk.Menu ()
 
275
        myMenu.append (menuitem)
 
276
        self.format_menu.set_menu(myMenu)
 
277
 
 
278
    def make_document(self):
 
279
        """Do Nothing.  This document will be created in the
 
280
        make_report routine."""
 
281
        pass
 
282
    
 
283
    def setup_style_frame(self):
 
284
        """The style frame is not used in this dialog."""
 
285
        pass
 
286
 
 
287
    def parse_style_frame(self):
 
288
        """The style frame is not used in this dialog."""
 
289
        pass
 
290
 
 
291
    def parse_other_frames(self):
 
292
        menu = self.arrowstyle_optionmenu.get_menu()
 
293
        self.arrowheadstyle, self.arrowtailstyle = menu.get_active().get_data('t')
 
294
        self.includedates = self.includedates_cb.get_active()
 
295
        self.includeurl = self.includeurl_cb.get_active()
 
296
        self.tb_margin = self.tb_margin_sb.get_value()
 
297
        self.lr_margin = self.lr_margin_sb.get_value()
 
298
        self.colorize = self.colorize_cb.get_active()
 
299
        self.adoptionsdashed = self.adoptionsdashed_cb.get_active()
 
300
        self.hpages = self.hpages_sb.get_value_as_int()
 
301
        self.vpages = self.vpages_sb.get_value_as_int()
 
302
        self.show_families = self.show_families_cb.get_active()
 
303
        self.just_year = self.just_year_cb.get_active()
 
304
 
 
305
        menu = self.font_optionmenu.get_menu()
 
306
        self.fontstyle = menu.get_active().get_data('t')
 
307
 
 
308
    def make_report(self):
 
309
        """Create the object that will produce the GraphViz file."""
 
310
        width = self.paper.get_width_inches()
 
311
        height = self.paper.get_height_inches()
 
312
 
 
313
        file = open(self.target_path,"w")
 
314
 
 
315
        try:
 
316
            ind_list = self.filter.apply(self.db, self.db.getPersonMap().values())
 
317
        except Errors.FilterError, msg:
 
318
            from QuestionDialog import ErrorDialog
 
319
            (m1,m2) = msg.messages()
 
320
            ErrorDialog(m1,m2)
 
321
 
 
322
        write_dot(file, ind_list, self.orien, width, height,
 
323
                  self.tb_margin, self.lr_margin, self.hpages,
 
324
                  self.vpages, self.includedates, self.includeurl,
 
325
                  self.colorize, self.adoptionsdashed, self.arrowheadstyle,
 
326
                  self.arrowtailstyle, self.show_families, self.just_year,
 
327
                  self.fontstyle)
 
328
 
 
329
        if self.print_report.get_active ():
 
330
            os.environ["DOT"] = self.target_path
 
331
            os.system ('dot -Tps "$DOT" | %s &' %
 
332
                       Report.get_print_dialog_app ())
 
333
 
 
334
#------------------------------------------------------------------------
 
335
#
 
336
#
 
337
#
 
338
#------------------------------------------------------------------------
 
339
def report(database,person):
 
340
    GraphVizDialog(database,person)
 
341
 
 
342
#------------------------------------------------------------------------
 
343
#
 
344
#
 
345
#
 
346
#------------------------------------------------------------------------
 
347
def write_dot(file, ind_list, orien, width, height, tb_margin,
 
348
              lr_margin, hpages, vpages, includedates, includeurl,
 
349
              colorize, adoptionsdashed, arrowheadstyle, arrowtailstyle,
 
350
              show_families, just_year, fontstyle):
 
351
    file.write("digraph g {\n")
 
352
    file.write("bgcolor=white;\n")
 
353
    file.write("rankdir=LR;\n")
 
354
    file.write("center=1;\n")
 
355
    file.write("margin=0.5;\n")
 
356
    file.write("ratio=fill;\n")
 
357
    file.write("size=\"%3.1f,%3.1f\";\n" % ((width*hpages)-(lr_margin*2)-((hpages-1)*1.0),
 
358
                                            (height*vpages)-(tb_margin*2)-((vpages-1)*1.0)))
 
359
    file.write("page=\"%3.1f,%3.1f\";\n" % (width,height))
 
360
 
 
361
    if orien == BaseDoc.PAPER_LANDSCAPE:
 
362
        file.write("rotate=90;\n")
 
363
 
 
364
    if len(ind_list) > 1:
 
365
        dump_index(ind_list,file,includedates,includeurl,colorize,
 
366
                   arrowheadstyle,arrowtailstyle,show_families,just_year,fontstyle)
 
367
        dump_person(ind_list,file,adoptionsdashed,arrowheadstyle,
 
368
                    arrowtailstyle,show_families)
 
369
 
 
370
    file.write("}\n")
 
371
    file.close()
 
372
 
 
373
#------------------------------------------------------------------------
 
374
#
 
375
#
 
376
#
 
377
#------------------------------------------------------------------------
 
378
def dump_person(person_list,file,adoptionsdashed,arrowheadstyle,
 
379
                arrowtailstyle,show_families):
 
380
    # Hash people in a dictionary for faster inclusion checking.
 
381
    person_dict = {}
 
382
    for p in person_list:
 
383
        person_dict[p.getId()] = 1
 
384
 
 
385
    for person in person_list:
 
386
        pid = string.replace(person.getId(),'-','_')
 
387
        for family, mrel, frel in person.getParentList():
 
388
            father   = family.getFather()
 
389
            mother   = family.getMother()
 
390
            fadopted = frel != _("Birth")
 
391
            madopted = mrel != _("Birth")
 
392
            if (show_families and
 
393
                (father and person_dict.has_key(father.getId()) or
 
394
                 mother and person_dict.has_key(mother.getId()))):
 
395
                # Link to the family node.
 
396
                famid = string.replace(family.getId(),'-','_')
 
397
                file.write('p%s -> f%s ['  % (pid, famid))
 
398
                file.write('arrowhead=%s, arrowtail=%s, ' %
 
399
                           (arrowheadstyle, arrowtailstyle))
 
400
                if adoptionsdashed and (fadopted or madopted):
 
401
                    file.write('style=dashed')
 
402
                else:
 
403
                    file.write('style=solid')
 
404
                file.write('];\n')
 
405
            else:
 
406
                # Link to the parents' nodes directly.
 
407
                if father and person_dict.has_key(father.getId()):
 
408
                    fid = string.replace(father.getId(),'-','_')
 
409
                    file.write('p%s -> p%s ['  % (pid, fid))
 
410
                    file.write('arrowhead=%s, arrowtail=%s, ' %
 
411
                               (arrowheadstyle, arrowtailstyle))
 
412
                    if adoptionsdashed and fadopted:
 
413
                        file.write('style=dashed')
 
414
                    else:
 
415
                        file.write('style=solid')
 
416
                    file.write('];\n')
 
417
                if mother and person_dict.has_key(mother.getId()):
 
418
                    mid = string.replace(mother.getId(),'-','_')
 
419
                    file.write('p%s -> p%s ['  % (pid, mid))
 
420
                    file.write('arrowhead=%s, arrowtail=%s, ' %
 
421
                               (arrowheadstyle, arrowtailstyle))
 
422
                    if adoptionsdashed and madopted:
 
423
                        file.write('style=dashed')
 
424
                    else:
 
425
                        file.write('style=solid')
 
426
                    file.write('];\n')
 
427
 
 
428
#------------------------------------------------------------------------
 
429
#
 
430
#
 
431
#
 
432
#------------------------------------------------------------------------
 
433
def dump_index(person_list,file,includedates,includeurl,colorize,
 
434
               arrowheadstyle,arrowtailstyle,show_families,just_year,font):
 
435
    # The list of families for which we have output the node, so we
 
436
    # don't do it twice.
 
437
    families_done = []
 
438
    for person in person_list:
 
439
        # Output the person's node.
 
440
        label = person.getPrimaryName().getName()
 
441
        id = string.replace(person.getId(),'-','_')
 
442
        if includedates:
 
443
            if person.getBirth().getDateObj().getYearValid():
 
444
                if just_year:
 
445
                    birth = '%i' % person.getBirth().getDateObj().getYear()
 
446
                else:
 
447
                    birth = person.getBirth().getDate()
 
448
            else:
 
449
                birth = ''
 
450
            if person.getDeath().getDateObj().getYearValid():
 
451
                if just_year:
 
452
                    death = '%i' % person.getDeath().getDateObj().getYear()
 
453
                else:
 
454
                    death = person.getDeath().getDate()
 
455
            else:
 
456
                death = ''
 
457
            label = label + '\\n(%s - %s)' % (birth, death)
 
458
        file.write('p%s [shape=box, ' % id)
 
459
        if includeurl:
 
460
            file.write('URL="%s.html", ' % id)
 
461
        if colorize:
 
462
            gender = person.getGender()
 
463
            if gender == person.male:
 
464
                file.write('color=dodgerblue4, ')
 
465
            elif gender == person.female:
 
466
                file.write('color=deeppink, ')
 
467
            else:
 
468
                file.write('color=black, ')
 
469
        if font == _TT_FONT:
 
470
            file.write('fontname="%s", label="%s"];\n' % (font,label))
 
471
        else:
 
472
            file.write('fontname="%s", label="%s"];\n' % (font,utf8_to_latin(label)))
 
473
        # Output families's nodes.
 
474
        if show_families:
 
475
            family_list = person.getFamilyList()
 
476
            for fam in family_list:
 
477
                fid = string.replace(fam.getId(),'-','_')
 
478
                if fam not in families_done:
 
479
                    families_done.append(fam)
 
480
                    file.write('f%s [shape=ellipse, ' % fid)
 
481
                    marriage = ""
 
482
                    m = fam.getMarriage()
 
483
                    if m != None:
 
484
                        do = m.getDateObj()
 
485
                        if do != None:
 
486
                            if do.getYearValid():
 
487
                                if just_year:
 
488
                                    marriage = '%i' % do.getYear()
 
489
                                else:
 
490
                                    marriage = m.getDate()
 
491
                    file.write('fontname="%s", label="%s"];\n' % (font,marriage))
 
492
                # Link this person to all his/her families.
 
493
                file.write('f%s -> p%s [' % (fid, id))
 
494
                file.write('arrowhead=%s, arrowtail=%s, ' %
 
495
                           (arrowheadstyle, arrowtailstyle))
 
496
                file.write('style=solid];\n')
 
497
 
 
498
 
 
499
 
 
500
#------------------------------------------------------------------------
 
501
#
 
502
#
 
503
#
 
504
#------------------------------------------------------------------------
 
505
def get_description():
 
506
    return _("Generates relationship graphs, currently only in GraphViz "
 
507
             "format. GraphViz (dot) can transform the graph into "
 
508
             "postscript, jpeg, png, vrml, svg, and many other formats. "
 
509
             "For more information or to get a copy of GraphViz, "
 
510
             "goto http://www.graphviz.org")
 
511
 
 
512
#------------------------------------------------------------------------
 
513
#
 
514
 
515
#
 
516
#------------------------------------------------------------------------
 
517
from Plugins import register_report
 
518
import sys
 
519
 
 
520
ver = sys.version_info
 
521
if ver[0] == 2 and ver[1] == 2:
 
522
    register_report(
 
523
        report,
 
524
        _("Relationship Graph"),
 
525
        status=(_("Beta")),
 
526
        category=_("Graphical Reports"),
 
527
        description=get_description(),
 
528
        author_name="Donald N. Allingham",
 
529
        author_email="dallingham@users.sourceforge.net"
 
530
        )
 
531