Add compact Ancestry trees using Buchheim/Walker algorithm
This enhancement adds a new 'compact' field to the Narrated Web Report. A compact tree is one that is not a simple binary layout but uses the algorithm of Buchheim/Walker to create a layout that is sensible but also compact. Creating a compact layout is slower than a simple binary tree but the results are significantly improved and do not leave large areas of whitespace where there are no nodes to be shown.
This commit is contained in:
parent
acfbb0a763
commit
03a89c73e3
298
gramps/plugins/webreport/buchheim.py
Normal file
298
gramps/plugins/webreport/buchheim.py
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Gramps - a GTK+/GNOME based genealogy program
|
||||||
|
#
|
||||||
|
# Copyright (C) 2000-2007 Donald N. Allingham
|
||||||
|
# Copyright (C) 2007 Johan Gonqvist <johan.gronqvist@gmail.com>
|
||||||
|
# Copyright (C) 2007-2009 Gary Burton <gary.burton@zen.co.uk>
|
||||||
|
# Copyright (C) 2007-2009 Stephane Charette <stephanecharette@gmail.com>
|
||||||
|
# Copyright (C) 2008-2009 Brian G. Matherly
|
||||||
|
# Copyright (C) 2008 Jason M. Simanek <jason@bohemianalps.com>
|
||||||
|
# Copyright (C) 2008-2011 Rob G. Healey <robhealey1@gmail.com>
|
||||||
|
# Copyright (C) 2010 Doug Blank <doug.blank@gmail.com>
|
||||||
|
# Copyright (C) 2010 Jakim Friant
|
||||||
|
# Copyright (C) 2010,2015 Serge Noiraud
|
||||||
|
# Copyright (C) 2011 Tim G L Lyons
|
||||||
|
# Copyright (C) 2013 Benny Malengier
|
||||||
|
# Copyright (C) 2018 Paul D.Smith
|
||||||
|
#
|
||||||
|
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
#
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from gramps.gen.const import GRAMPS_LOCALE as glocale
|
||||||
|
|
||||||
|
LOG = logging.getLogger(".NarrativeWeb.BuchheimTree")
|
||||||
|
|
||||||
|
_ = glocale.translation.sgettext
|
||||||
|
|
||||||
|
#------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# DrawTree - a Buchheim draw tree which implements the
|
||||||
|
# tree drawing algorithm of:
|
||||||
|
#
|
||||||
|
# Improving Walker's algorithm to Run in Linear Time
|
||||||
|
# Christoph Buchheim, Michael Juenger, and Sebastian Leipert
|
||||||
|
#
|
||||||
|
# Also see:
|
||||||
|
#
|
||||||
|
# Positioning Nodes for General Trees
|
||||||
|
# John Q. Walker II
|
||||||
|
#
|
||||||
|
# The following modifications are noted:
|
||||||
|
#
|
||||||
|
# - The root node is 'west' according to the later nomenclature
|
||||||
|
# employed by Walker with the nodes stretching 'east'
|
||||||
|
# - This reverses the X & Y co-originates of the Buchheim paper
|
||||||
|
# - The algorithm has been tweaked to track the maximum X and Y
|
||||||
|
# as 'width' and 'height' to aid later layout
|
||||||
|
# - The Buchheim examples track a string identifying the actual
|
||||||
|
# node but this implementation tracks the handle of the
|
||||||
|
# DB node identifying the person in the Gramps DB. This is done
|
||||||
|
# to minimize occupancy at any one time.
|
||||||
|
#------------------------------------------------------------
|
||||||
|
class DrawTree(object):
|
||||||
|
def __init__(self, tree, parent=None, depth=0, number=1):
|
||||||
|
self.coord_x = -1.
|
||||||
|
self.coord_y = depth
|
||||||
|
self.width = self.coord_x
|
||||||
|
self.height = self.coord_y
|
||||||
|
self.tree = tree
|
||||||
|
self.children = [DrawTree(c, self, depth+1, i+1)
|
||||||
|
for i, c
|
||||||
|
in enumerate(tree.children)]
|
||||||
|
self.parent = parent
|
||||||
|
self.thread = None
|
||||||
|
self.mod = 0
|
||||||
|
self.ancestor = self
|
||||||
|
self.change = self.shift = 0
|
||||||
|
self._lmost_sibling = None
|
||||||
|
#this is the number of the node in its group of siblings 1..n
|
||||||
|
self.number = number
|
||||||
|
|
||||||
|
def left(self):
|
||||||
|
"""
|
||||||
|
Return the left most child if it exists.
|
||||||
|
"""
|
||||||
|
return self.thread or len(self.children) and self.children[0]
|
||||||
|
|
||||||
|
def right(self):
|
||||||
|
"""
|
||||||
|
Return the rightmost child if it exists.
|
||||||
|
"""
|
||||||
|
return self.thread or len(self.children) and self.children[-1]
|
||||||
|
|
||||||
|
def lbrother(self):
|
||||||
|
"""
|
||||||
|
Return the sibling to the left of this one.
|
||||||
|
"""
|
||||||
|
brother = None
|
||||||
|
if self.parent:
|
||||||
|
for node in self.parent.children:
|
||||||
|
if node == self:
|
||||||
|
return brother
|
||||||
|
else:
|
||||||
|
brother = node
|
||||||
|
return brother
|
||||||
|
|
||||||
|
def get_lmost_sibling(self):
|
||||||
|
"""
|
||||||
|
Return the leftmost sibling.
|
||||||
|
"""
|
||||||
|
if not self._lmost_sibling and self.parent and self != \
|
||||||
|
self.parent.children[0]:
|
||||||
|
self._lmost_sibling = self.parent.children[0]
|
||||||
|
return self._lmost_sibling
|
||||||
|
lmost_sibling = property(get_lmost_sibling)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%s: x=%s mod=%s" % (self.tree, self.coord_x, self.mod)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.__str__()
|
||||||
|
|
||||||
|
def handle(self):
|
||||||
|
"""
|
||||||
|
Return the handle of the tree, which is whatever we stored as
|
||||||
|
in the tree to reference out data.
|
||||||
|
"""
|
||||||
|
return self.tree.handle
|
||||||
|
|
||||||
|
|
||||||
|
def buchheim(tree, node_width, h_separation, node_height, v_separation):
|
||||||
|
"""
|
||||||
|
Calculate the position of elements of the graph given a minimum
|
||||||
|
generation width separation and minimum generation height separation.
|
||||||
|
"""
|
||||||
|
draw_tree = firstwalk(DrawTree(tree), node_height, v_separation)
|
||||||
|
min_x = second_walk(draw_tree, 0, node_width+h_separation, 0)
|
||||||
|
if min_x < 0:
|
||||||
|
third_walk(draw_tree, 0 - min_x)
|
||||||
|
|
||||||
|
return draw_tree
|
||||||
|
|
||||||
|
|
||||||
|
def third_walk(tree, adjust):
|
||||||
|
"""
|
||||||
|
The tree has have wandered into 'negative' co-ordinates so bring it back
|
||||||
|
into the piositive domain.
|
||||||
|
"""
|
||||||
|
tree.coord_x += adjust
|
||||||
|
tree.width = max(tree.width, tree.coord_x)
|
||||||
|
for child in tree.children:
|
||||||
|
third_walk(child, adjust)
|
||||||
|
|
||||||
|
|
||||||
|
def firstwalk(tree, node_height, v_separation):
|
||||||
|
"""
|
||||||
|
Determine horizontal positions.
|
||||||
|
"""
|
||||||
|
if not tree.children:
|
||||||
|
if tree.lmost_sibling:
|
||||||
|
tree.coord_y = tree.lbrother().coord_y + node_height + v_separation
|
||||||
|
else:
|
||||||
|
tree.coord_y = 0.
|
||||||
|
else:
|
||||||
|
default_ancestor = tree.children[0]
|
||||||
|
for child in tree.children:
|
||||||
|
firstwalk(child, node_height, v_separation)
|
||||||
|
default_ancestor = apportion(
|
||||||
|
child, default_ancestor, node_height + v_separation)
|
||||||
|
tree.height = max(tree.height, child.height)
|
||||||
|
assert tree.width >= child.width
|
||||||
|
execute_shifts(tree)
|
||||||
|
|
||||||
|
midpoint = (tree.children[0].coord_y + tree.children[-1].coord_y) / 2
|
||||||
|
|
||||||
|
brother = tree.lbrother()
|
||||||
|
if brother:
|
||||||
|
tree.coord_y = brother.coord_y + node_height + v_separation
|
||||||
|
tree.mod = tree.coord_y - midpoint
|
||||||
|
else:
|
||||||
|
tree.coord_y = midpoint
|
||||||
|
|
||||||
|
assert tree.width >= tree.coord_x
|
||||||
|
tree.height = max(tree.height, tree.coord_y)
|
||||||
|
return tree
|
||||||
|
|
||||||
|
|
||||||
|
def apportion(tree, default_ancestor, v_separation):
|
||||||
|
"""
|
||||||
|
Figure out relative positions of node in a tree.
|
||||||
|
"""
|
||||||
|
brother = tree.lbrother()
|
||||||
|
if brother is not None:
|
||||||
|
#in buchheim notation:
|
||||||
|
#i == inner; o == outer; r == right; l == left; r = +; l = -
|
||||||
|
vir = vor = tree
|
||||||
|
vil = brother
|
||||||
|
vol = tree.lmost_sibling
|
||||||
|
sir = sor = tree.mod
|
||||||
|
sil = vil.mod
|
||||||
|
sol = vol.mod
|
||||||
|
while vil.right() and vir.left():
|
||||||
|
vil = vil.right()
|
||||||
|
vir = vir.left()
|
||||||
|
vol = vol.left()
|
||||||
|
vor = vor.right()
|
||||||
|
vor.ancestor = tree
|
||||||
|
shift = (vil.coord_y + sil) - (vir.coord_y + sir) + v_separation
|
||||||
|
if shift > 0:
|
||||||
|
move_subtree(ancestor(
|
||||||
|
vil, tree, default_ancestor), tree, shift)
|
||||||
|
sir = sir + shift
|
||||||
|
sor = sor + shift
|
||||||
|
sil += vil.mod
|
||||||
|
sir += vir.mod
|
||||||
|
sol += vol.mod
|
||||||
|
sor += vor.mod
|
||||||
|
if vil.right() and not vor.right():
|
||||||
|
vor.thread = vil.right()
|
||||||
|
vor.mod += sil - sor
|
||||||
|
else:
|
||||||
|
if vir.left() and not vol.left():
|
||||||
|
vol.thread = vir.left()
|
||||||
|
vol.mod += sir - sol
|
||||||
|
default_ancestor = tree
|
||||||
|
return default_ancestor
|
||||||
|
|
||||||
|
|
||||||
|
def move_subtree(walk_l, walk_r, shift):
|
||||||
|
"""
|
||||||
|
Determine possible shifts required to accomodate new node, but don't
|
||||||
|
perform the shifts yet.
|
||||||
|
"""
|
||||||
|
subtrees = walk_r.number - walk_l.number
|
||||||
|
# print wl.tree, "is conflicted with", wr.tree, 'moving',
|
||||||
|
# subtrees, 'shift', shift
|
||||||
|
# print wl, wr, wr.number, wl.number, shift, subtrees, shift/subtrees
|
||||||
|
walk_r.change -= shift / subtrees
|
||||||
|
walk_r.shift += shift
|
||||||
|
walk_l.change += shift / subtrees
|
||||||
|
walk_r.coord_y += shift
|
||||||
|
walk_r.mod += shift
|
||||||
|
walk_r.height = max(walk_r.height, walk_r.coord_y)
|
||||||
|
|
||||||
|
|
||||||
|
def execute_shifts(tree):
|
||||||
|
"""
|
||||||
|
Shift a tree, and it's subtrees, to allow for the placement of a
|
||||||
|
new tree.
|
||||||
|
"""
|
||||||
|
shift = change = 0
|
||||||
|
for child in tree.children[::-1]:
|
||||||
|
# print "shift:", child, shift, child.change
|
||||||
|
child.coord_y += shift
|
||||||
|
child.mod += shift
|
||||||
|
change += child.change
|
||||||
|
shift += child.shift + change
|
||||||
|
child.height = max(child.height, child.coord_y)
|
||||||
|
tree.height = max(tree.height, child.height)
|
||||||
|
|
||||||
|
|
||||||
|
def ancestor(vil, tree, default_ancestor):
|
||||||
|
"""
|
||||||
|
The relevant text is at the bottom of page 7 of
|
||||||
|
Improving Walker's Algorithm to Run in Linear Time" by Buchheim et al
|
||||||
|
"""
|
||||||
|
if vil.ancestor in tree.parent.children:
|
||||||
|
return vil.ancestor
|
||||||
|
|
||||||
|
return default_ancestor
|
||||||
|
|
||||||
|
|
||||||
|
def second_walk(tree, modifier=0, h_separation=0, width=0, min_x=None):
|
||||||
|
"""
|
||||||
|
Note that some of this code is modified to orientate the root node 'west'
|
||||||
|
instead of 'north' in the Bushheim algorithms.
|
||||||
|
"""
|
||||||
|
tree.coord_y += modifier
|
||||||
|
tree.coord_x += width
|
||||||
|
|
||||||
|
if min_x is None or tree.coord_x < min_x:
|
||||||
|
min_x = tree.coord_x
|
||||||
|
|
||||||
|
for child in tree.children:
|
||||||
|
min_x = second_walk(
|
||||||
|
child, modifier + tree.mod, h_separation,
|
||||||
|
width + h_separation, min_x)
|
||||||
|
tree.width = max(tree.width, child.width)
|
||||||
|
tree.height = max(tree.height, child.height)
|
||||||
|
|
||||||
|
tree.width = max(tree.width, tree.coord_x)
|
||||||
|
tree.height = max(tree.height, tree.coord_y)
|
||||||
|
return min_x
|
68
gramps/plugins/webreport/layout.py
Normal file
68
gramps/plugins/webreport/layout.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Gramps - a GTK+/GNOME based genealogy program
|
||||||
|
#
|
||||||
|
# Copyright (C) 2000-2007 Donald N. Allingham
|
||||||
|
# Copyright (C) 2007 Johan Gonqvist <johan.gronqvist@gmail.com>
|
||||||
|
# Copyright (C) 2007-2009 Gary Burton <gary.burton@zen.co.uk>
|
||||||
|
# Copyright (C) 2007-2009 Stephane Charette <stephanecharette@gmail.com>
|
||||||
|
# Copyright (C) 2008-2009 Brian G. Matherly
|
||||||
|
# Copyright (C) 2008 Jason M. Simanek <jason@bohemianalps.com>
|
||||||
|
# Copyright (C) 2008-2011 Rob G. Healey <robhealey1@gmail.com>
|
||||||
|
# Copyright (C) 2010 Doug Blank <doug.blank@gmail.com>
|
||||||
|
# Copyright (C) 2010 Jakim Friant
|
||||||
|
# Copyright (C) 2010-2017 Serge Noiraud
|
||||||
|
# Copyright (C) 2011 Tim G L Lyons
|
||||||
|
# Copyright (C) 2013 Benny Malengier
|
||||||
|
# Copyright (C) 2016 Allen Crider
|
||||||
|
# Copyright (C) 2018 Paul D.Smith
|
||||||
|
#
|
||||||
|
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
class LayoutTree:
|
||||||
|
"""
|
||||||
|
Narrative Web Page generator.
|
||||||
|
|
||||||
|
Classe:
|
||||||
|
BuchheimTree - A tree suitable for passing to the Bushheim layout
|
||||||
|
algorithm
|
||||||
|
"""
|
||||||
|
def __init__(self, handle, *children):
|
||||||
|
self.handle = handle
|
||||||
|
self.children = []
|
||||||
|
if isinstance(children, list):
|
||||||
|
children = list(children)
|
||||||
|
for parent in children:
|
||||||
|
if parent is not None:
|
||||||
|
self.children.append(parent)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if isinstance(key, (int, slice)):
|
||||||
|
return self.children[key]
|
||||||
|
if isinstance(key, str):
|
||||||
|
for child in self.children:
|
||||||
|
if child.node == key:
|
||||||
|
return child
|
||||||
|
assert "Key not found"
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self.children.__iter__()
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.children)
|
@ -166,15 +166,36 @@ class MediaPages(BasePage):
|
|||||||
gc.collect() # Reduce memory usage when many images.
|
gc.collect() # Reduce memory usage when many images.
|
||||||
if index == media_count:
|
if index == media_count:
|
||||||
next_ = None
|
next_ = None
|
||||||
|
elif index < total:
|
||||||
|
next_ = sorted_media_handles[index]
|
||||||
|
elif len(self.unused_media_handles) > 0:
|
||||||
|
next_ = self.unused_media_handles[0]
|
||||||
else:
|
else:
|
||||||
next_ = self.unused_media_handles[idx]
|
next_ = None
|
||||||
self.mediapage(self.report, title,
|
self.mediapage(self.report, title,
|
||||||
media_handle,
|
handle, (prev, next_, index, media_count))
|
||||||
(prev, next_, index, media_count))
|
prev = handle
|
||||||
prev = media_handle
|
|
||||||
step()
|
step()
|
||||||
index += 1
|
index += 1
|
||||||
idx += 1
|
|
||||||
|
total = len(self.unused_media_handles)
|
||||||
|
idx = 1
|
||||||
|
prev = sorted_media_handles[len(sorted_media_handles)-1]
|
||||||
|
if total > 0:
|
||||||
|
for media_handle in self.unused_media_handles:
|
||||||
|
media = self.r_db.get_media_from_handle(media_handle)
|
||||||
|
gc.collect() # Reduce memory usage when many images.
|
||||||
|
if index == media_count:
|
||||||
|
next_ = None
|
||||||
|
else:
|
||||||
|
next_ = self.unused_media_handles[idx]
|
||||||
|
self.mediapage(self.report, title,
|
||||||
|
media_handle,
|
||||||
|
(prev, next_, index, media_count))
|
||||||
|
prev = media_handle
|
||||||
|
step()
|
||||||
|
index += 1
|
||||||
|
idx += 1
|
||||||
|
|
||||||
self.medialistpage(self.report, title, sorted_media_handles)
|
self.medialistpage(self.report, title, sorted_media_handles)
|
||||||
|
|
||||||
|
@ -289,10 +289,11 @@ class NavWebReport(Report):
|
|||||||
os.mkdir(dir_name)
|
os.mkdir(dir_name)
|
||||||
except IOError as value:
|
except IOError as value:
|
||||||
msg = _("Could not create the directory: %s"
|
msg = _("Could not create the directory: %s"
|
||||||
) % dir_name + "\n" + value[1]
|
) % dir_name + "\n" + value.strerror
|
||||||
self.user.notify_error(msg)
|
self.user.notify_error(msg)
|
||||||
return
|
return
|
||||||
except:
|
except Exception as exception:
|
||||||
|
LOG.exception(exception)
|
||||||
msg = _("Could not create the directory: %s") % dir_name
|
msg = _("Could not create the directory: %s") % dir_name
|
||||||
self.user.notify_error(msg)
|
self.user.notify_error(msg)
|
||||||
return
|
return
|
||||||
@ -307,12 +308,13 @@ class NavWebReport(Report):
|
|||||||
os.mkdir(image_dir_name)
|
os.mkdir(image_dir_name)
|
||||||
except IOError as value:
|
except IOError as value:
|
||||||
msg = _("Could not create the directory: %s"
|
msg = _("Could not create the directory: %s"
|
||||||
) % image_dir_name + "\n" + value[1]
|
) % image_dir_name + "\n" + value.strerror
|
||||||
self.user.notify_error(msg)
|
self.user.notify_error(msg)
|
||||||
return
|
return
|
||||||
except:
|
except Exception as exception:
|
||||||
|
LOG.exception(exception)
|
||||||
msg = _("Could not create the directory: %s"
|
msg = _("Could not create the directory: %s"
|
||||||
) % image_dir_name + "\n" + value[1]
|
) % image_dir_name + "\n" + str(exception)
|
||||||
self.user.notify_error(msg)
|
self.user.notify_error(msg)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
@ -453,7 +455,7 @@ class NavWebReport(Report):
|
|||||||
if self.archive:
|
if self.archive:
|
||||||
self.archive.close()
|
self.archive.close()
|
||||||
|
|
||||||
if len(_WRONGMEDIAPATH) > 0:
|
if _WRONGMEDIAPATH:
|
||||||
error = '\n'.join([
|
error = '\n'.join([
|
||||||
_('ID=%(grampsid)s, path=%(dir)s') % {
|
_('ID=%(grampsid)s, path=%(dir)s') % {
|
||||||
'grampsid' : x[0],
|
'grampsid' : x[0],
|
||||||
@ -856,7 +858,7 @@ class NavWebReport(Report):
|
|||||||
@param: bkref_class -- The class associated to this handle (source)
|
@param: bkref_class -- The class associated to this handle (source)
|
||||||
@param: bkref_handle -- The handle associated to this source
|
@param: bkref_handle -- The handle associated to this source
|
||||||
"""
|
"""
|
||||||
if len(self.obj_dict[Source][source_handle]) > 0:
|
if self.obj_dict[Source][source_handle]:
|
||||||
for bkref in self.bkref_dict[Source][source_handle]:
|
for bkref in self.bkref_dict[Source][source_handle]:
|
||||||
if bkref_handle == bkref[1]:
|
if bkref_handle == bkref[1]:
|
||||||
return
|
return
|
||||||
@ -893,7 +895,7 @@ class NavWebReport(Report):
|
|||||||
@param: bkref_class -- The class associated to this handle
|
@param: bkref_class -- The class associated to this handle
|
||||||
@param: bkref_handle -- The handle associated to this citation
|
@param: bkref_handle -- The handle associated to this citation
|
||||||
"""
|
"""
|
||||||
if len(self.obj_dict[Citation][citation_handle]) > 0:
|
if self.obj_dict[Citation][citation_handle]:
|
||||||
for bkref in self.bkref_dict[Citation][citation_handle]:
|
for bkref in self.bkref_dict[Citation][citation_handle]:
|
||||||
if bkref_handle == bkref[1]:
|
if bkref_handle == bkref[1]:
|
||||||
return
|
return
|
||||||
@ -926,7 +928,7 @@ class NavWebReport(Report):
|
|||||||
@param: bkref_class -- The class associated to this handle (media)
|
@param: bkref_class -- The class associated to this handle (media)
|
||||||
@param: bkref_handle -- The handle associated to this media
|
@param: bkref_handle -- The handle associated to this media
|
||||||
"""
|
"""
|
||||||
if len(self.obj_dict[Media][media_handle]) > 0:
|
if self.obj_dict[Media][media_handle]:
|
||||||
for bkref in self.bkref_dict[Media][media_handle]:
|
for bkref in self.bkref_dict[Media][media_handle]:
|
||||||
if bkref_handle == bkref[1]:
|
if bkref_handle == bkref[1]:
|
||||||
return
|
return
|
||||||
@ -967,7 +969,7 @@ class NavWebReport(Report):
|
|||||||
@param: bkref_class -- The class associated to this handle (source)
|
@param: bkref_class -- The class associated to this handle (source)
|
||||||
@param: bkref_handle -- The handle associated to this source
|
@param: bkref_handle -- The handle associated to this source
|
||||||
"""
|
"""
|
||||||
if len(self.obj_dict[Repository][repos_handle]) > 0:
|
if self.obj_dict[Repository][repos_handle]:
|
||||||
for bkref in self.bkref_dict[Repository][repos_handle]:
|
for bkref in self.bkref_dict[Repository][repos_handle]:
|
||||||
if bkref_handle == bkref[1]:
|
if bkref_handle == bkref[1]:
|
||||||
return
|
return
|
||||||
@ -1049,7 +1051,7 @@ class NavWebReport(Report):
|
|||||||
|
|
||||||
# copy all to images subdir:
|
# copy all to images subdir:
|
||||||
for from_path in imgs:
|
for from_path in imgs:
|
||||||
fdir, fname = os.path.split(from_path)
|
dummy_fdir, fname = os.path.split(from_path)
|
||||||
self.copy_file(from_path, fname, "images")
|
self.copy_file(from_path, fname, "images")
|
||||||
|
|
||||||
# copy Gramps marker icon for openstreetmap
|
# copy Gramps marker icon for openstreetmap
|
||||||
@ -1126,12 +1128,10 @@ class NavWebReport(Report):
|
|||||||
len(local_list)) as step:
|
len(local_list)) as step:
|
||||||
|
|
||||||
SurnameListPage(self, self.title, ind_list,
|
SurnameListPage(self, self.title, ind_list,
|
||||||
SurnameListPage.ORDER_BY_NAME,
|
SurnameListPage.ORDER_BY_NAME, self.surname_fname)
|
||||||
self.surname_fname)
|
|
||||||
|
|
||||||
SurnameListPage(self, self.title, ind_list,
|
SurnameListPage(self, self.title, ind_list,
|
||||||
SurnameListPage.ORDER_BY_COUNT,
|
SurnameListPage.ORDER_BY_COUNT, "surnames_count")
|
||||||
"surnames_count")
|
|
||||||
|
|
||||||
index = 1
|
index = 1
|
||||||
for (surname, handle_list) in local_list:
|
for (surname, handle_list) in local_list:
|
||||||
@ -1493,7 +1493,8 @@ class NavWebReport(Report):
|
|||||||
try:
|
try:
|
||||||
shutil.copyfile(from_fname, dest)
|
shutil.copyfile(from_fname, dest)
|
||||||
os.utime(dest, (mtime, mtime))
|
os.utime(dest, (mtime, mtime))
|
||||||
except:
|
except Exception as exception:
|
||||||
|
LOG.exception(exception)
|
||||||
print("Copying error: %s" % sys.exc_info()[1])
|
print("Copying error: %s" % sys.exc_info()[1])
|
||||||
print("Continuing...")
|
print("Continuing...")
|
||||||
elif self.warn_dir:
|
elif self.warn_dir:
|
||||||
@ -1666,9 +1667,10 @@ class NavWebOptions(MenuReportOptions):
|
|||||||
cright.set_help(_("The copyright to be used for the web files"))
|
cright.set_help(_("The copyright to be used for the web files"))
|
||||||
addopt("cright", cright)
|
addopt("cright", cright)
|
||||||
|
|
||||||
self.__css = EnumeratedListOption(_('StyleSheet'), CSS["default"]["id"])
|
self.__css = EnumeratedListOption(('StyleSheet'), CSS["default"]["id"])
|
||||||
for (fname, gid) in sorted([(CSS[key]["translation"], CSS[key]["id"])
|
for (dummy_fname, gid) in sorted(
|
||||||
for key in list(CSS.keys())]):
|
[(CSS[key]["translation"], CSS[key]["id"])
|
||||||
|
for key in list(CSS.keys())]):
|
||||||
if CSS[gid]["user"]:
|
if CSS[gid]["user"]:
|
||||||
self.__css.add_item(CSS[gid]["id"], CSS[gid]["translation"])
|
self.__css.add_item(CSS[gid]["id"], CSS[gid]["translation"])
|
||||||
self.__css.set_help(_('The stylesheet to be used for the web pages'))
|
self.__css.set_help(_('The stylesheet to be used for the web pages'))
|
||||||
@ -1710,10 +1712,11 @@ class NavWebOptions(MenuReportOptions):
|
|||||||
addopt("ancestortree", self.__ancestortree)
|
addopt("ancestortree", self.__ancestortree)
|
||||||
self.__ancestortree.connect('value-changed', self.__graph_changed)
|
self.__ancestortree.connect('value-changed', self.__graph_changed)
|
||||||
|
|
||||||
self.__graphgens = NumberOption(_("Graph generations"), 4, 2, 5)
|
self.__graphgens = NumberOption(_("Graph generations"), 4, 2, 10)
|
||||||
self.__graphgens.set_help(_("The number of generations to include in "
|
self.__graphgens.set_help(_("The number of generations to include in "
|
||||||
"the ancestor graph"))
|
"the ancestor graph"))
|
||||||
addopt("graphgens", self.__graphgens)
|
addopt("graphgens", self.__graphgens)
|
||||||
|
|
||||||
self.__graph_changed()
|
self.__graph_changed()
|
||||||
|
|
||||||
self.__securesite = BooleanOption(_("This is a secure site (https)"),
|
self.__securesite = BooleanOption(_("This is a secure site (https)"),
|
||||||
@ -1726,7 +1729,7 @@ class NavWebOptions(MenuReportOptions):
|
|||||||
Add more extra pages to the report
|
Add more extra pages to the report
|
||||||
"""
|
"""
|
||||||
category_name = _("Extra pages")
|
category_name = _("Extra pages")
|
||||||
addopt = partial( menu.add_option, category_name )
|
addopt = partial(menu.add_option, category_name)
|
||||||
default_path_name = config.get('paths.website-extra-page-name')
|
default_path_name = config.get('paths.website-extra-page-name')
|
||||||
self.__extra_page_name = StringOption(_("Extra page name"),
|
self.__extra_page_name = StringOption(_("Extra page name"),
|
||||||
default_path_name)
|
default_path_name)
|
||||||
|
@ -76,6 +76,8 @@ from gramps.plugins.webreport.common import (get_first_letters, _KEYPERSON,
|
|||||||
MARKER_PATH, OSM_MARKERS,
|
MARKER_PATH, OSM_MARKERS,
|
||||||
GOOGLE_MAPS, MARKERS, html_escape,
|
GOOGLE_MAPS, MARKERS, html_escape,
|
||||||
DROPMASTERS, FAMILYLINKS)
|
DROPMASTERS, FAMILYLINKS)
|
||||||
|
from gramps.plugins.webreport.layout import LayoutTree
|
||||||
|
from gramps.plugins.webreport.buchheim import buchheim
|
||||||
|
|
||||||
_ = glocale.translation.sgettext
|
_ = glocale.translation.sgettext
|
||||||
LOG = logging.getLogger(".NarrativeWeb")
|
LOG = logging.getLogger(".NarrativeWeb")
|
||||||
@ -87,6 +89,8 @@ _VGAP = 10
|
|||||||
_HGAP = 30
|
_HGAP = 30
|
||||||
_SHADOW = 5
|
_SHADOW = 5
|
||||||
_XOFFSET = 5
|
_XOFFSET = 5
|
||||||
|
_YOFFSET = 5
|
||||||
|
_LOFFSET = 20
|
||||||
|
|
||||||
#################################################
|
#################################################
|
||||||
#
|
#
|
||||||
@ -171,7 +175,7 @@ class PersonPages(BasePage):
|
|||||||
showparents = report.options['showparents']
|
showparents = report.options['showparents']
|
||||||
|
|
||||||
output_file, sio = self.report.create_file("individuals")
|
output_file, sio = self.report.create_file("individuals")
|
||||||
indlistpage, head, body = self.write_header(self._("Individuals"))
|
indlistpage, dummy_head, body = self.write_header(self._("Individuals"))
|
||||||
date = 0
|
date = 0
|
||||||
|
|
||||||
# begin Individuals division
|
# begin Individuals division
|
||||||
@ -330,7 +334,7 @@ class PersonPages(BasePage):
|
|||||||
family_list = person.get_family_handle_list()
|
family_list = person.get_family_handle_list()
|
||||||
first_family = True
|
first_family = True
|
||||||
#partner_name = None
|
#partner_name = None
|
||||||
tcell = () # pylint: disable=R0204
|
tcell = ()
|
||||||
if family_list:
|
if family_list:
|
||||||
for family_handle in family_list:
|
for family_handle in family_list:
|
||||||
family = self.r_db.get_family_from_handle(
|
family = self.r_db.get_family_from_handle(
|
||||||
@ -568,7 +572,7 @@ class PersonPages(BasePage):
|
|||||||
individualdetail += self.display_ind_associations(assocs)
|
individualdetail += self.display_ind_associations(assocs)
|
||||||
|
|
||||||
# for use in family map pages...
|
# for use in family map pages...
|
||||||
if len(place_lat_long) > 0:
|
if place_lat_long:
|
||||||
if self.report.options["familymappages"]:
|
if self.report.options["familymappages"]:
|
||||||
# save output_file, string_io and cur_fname
|
# save output_file, string_io and cur_fname
|
||||||
# before creating a new page
|
# before creating a new page
|
||||||
@ -625,7 +629,7 @@ class PersonPages(BasePage):
|
|||||||
minx, maxx = Decimal("0.00000001"), Decimal("0.00000001")
|
minx, maxx = Decimal("0.00000001"), Decimal("0.00000001")
|
||||||
miny, maxy = Decimal("0.00000001"), Decimal("0.00000001")
|
miny, maxy = Decimal("0.00000001"), Decimal("0.00000001")
|
||||||
xwidth, yheight = [], []
|
xwidth, yheight = [], []
|
||||||
midx_, midy_, spanx, spany = [None]*4
|
midx_, midy_, dummy_spanx, spany = [None]*4
|
||||||
|
|
||||||
number_markers = len(place_lat_long)
|
number_markers = len(place_lat_long)
|
||||||
if number_markers > 1:
|
if number_markers > 1:
|
||||||
@ -649,7 +653,7 @@ class PersonPages(BasePage):
|
|||||||
midx_, midy_ = conv_lat_lon(midx_, midy_, "D.D8")
|
midx_, midy_ = conv_lat_lon(midx_, midy_, "D.D8")
|
||||||
|
|
||||||
# get the integer span of latitude and longitude
|
# get the integer span of latitude and longitude
|
||||||
spanx = int(maxx - minx)
|
dummy_spanx = int(maxx - minx)
|
||||||
spany = int(maxy - miny)
|
spany = int(maxy - miny)
|
||||||
|
|
||||||
# set zoom level based on span of Longitude?
|
# set zoom level based on span of Longitude?
|
||||||
@ -940,17 +944,17 @@ class PersonPages(BasePage):
|
|||||||
# return family map link to its caller
|
# return family map link to its caller
|
||||||
return familymap
|
return familymap
|
||||||
|
|
||||||
def draw_box(self, center, col, person):
|
def draw_box(self, node, col, person):
|
||||||
"""
|
"""
|
||||||
Draw the box around the AncestorTree Individual name box...
|
draw the box around the AncestorTree Individual name box...
|
||||||
|
@param: node -- The node defining the box location
|
||||||
@param: center -- The center of the box
|
|
||||||
@param: col -- The generation number
|
@param: col -- The generation number
|
||||||
@param: person -- The person to set in the box
|
@param: person -- The person to set in the box
|
||||||
"""
|
"""
|
||||||
top = center - _HEIGHT/2
|
xoff = _XOFFSET + node.coord_x
|
||||||
xoff = _XOFFSET+col*(_WIDTH+_HGAP)
|
top = _YOFFSET + node.coord_y
|
||||||
sex = person.gender
|
|
||||||
|
sex = person.get_gender()
|
||||||
if sex == Person.MALE:
|
if sex == Person.MALE:
|
||||||
divclass = "male"
|
divclass = "male"
|
||||||
elif sex == Person.FEMALE:
|
elif sex == Person.FEMALE:
|
||||||
@ -991,9 +995,8 @@ class PersonPages(BasePage):
|
|||||||
newpath = newpath.replace('\\', "/")
|
newpath = newpath.replace('\\', "/")
|
||||||
thumbnail_url = newpath
|
thumbnail_url = newpath
|
||||||
else:
|
else:
|
||||||
(photo_url,
|
(dummy_photo_url, thumbnail_url) = \
|
||||||
thumbnail_url) = self.report.prepare_copy_media(
|
self.report.prepare_copy_media(photo)
|
||||||
photo)
|
|
||||||
thumbnail_url = "/".join(['..']*3 + [thumbnail_url])
|
thumbnail_url = "/".join(['..']*3 + [thumbnail_url])
|
||||||
if win():
|
if win():
|
||||||
thumbnail_url = thumbnail_url.replace('\\', "/")
|
thumbnail_url = thumbnail_url.replace('\\', "/")
|
||||||
@ -1020,140 +1023,194 @@ class PersonPages(BasePage):
|
|||||||
|
|
||||||
return [boxbg, shadow]
|
return [boxbg, shadow]
|
||||||
|
|
||||||
def extend_line(self, coord_y0, coord_x0):
|
def extend_line(self, c_node, p_node):
|
||||||
"""
|
"""
|
||||||
Draw and extended line
|
Draw a line 'half the distance out to the parents. connect_line()
|
||||||
|
will then draw the horizontal to the parent and the vertical connector
|
||||||
|
to this line.
|
||||||
|
|
||||||
@param: coord_y0 -- The starting point
|
@param c_node -- Child node to draw from
|
||||||
@param: coord_x0 -- The end of the line
|
@param p_node -- Parent node to draw towards
|
||||||
"""
|
"""
|
||||||
|
width = (p_node.coord_x - c_node.coord_x - _WIDTH + 1)/2
|
||||||
|
assert width > 0
|
||||||
|
coord_x0 = _XOFFSET + c_node.coord_x + _WIDTH
|
||||||
|
coord_y0 = c_node.coord_y + _LOFFSET + _VGAP/2
|
||||||
|
|
||||||
style = "top: %dpx; left: %dpx; width: %dpx"
|
style = "top: %dpx; left: %dpx; width: %dpx"
|
||||||
ext_bv = Html("div", class_="bvline", inline=True,
|
bvline = Html("div", class_="bvline", inline=True,
|
||||||
style=style % (coord_y0, coord_x0, _HGAP/2)
|
style=style % (coord_y0, coord_x0, width))
|
||||||
)
|
gvline = Html("div", class_="gvline", inline=True,
|
||||||
ext_gv = Html("div", class_="gvline", inline=True,
|
style=style % (
|
||||||
style=style % (coord_y0+_SHADOW,
|
coord_y0+_SHADOW, coord_x0, width+_SHADOW))
|
||||||
coord_x0, _HGAP/2+_SHADOW)
|
return [bvline, gvline]
|
||||||
)
|
|
||||||
return [ext_bv, ext_gv]
|
|
||||||
|
|
||||||
def connect_line(self, coord_y0, coord_y1, col):
|
def connect_line(self, coord_xc, coord_yc, coord_xp, coord_yp):
|
||||||
"""
|
"""
|
||||||
We need to draw a line between to points
|
Draw the line horizontally back from the parent towards the child and
|
||||||
|
then the vertical connecting this line to the line drawn towards us
|
||||||
|
from the child.
|
||||||
|
|
||||||
@param: coord_y0 -- The starting point
|
@param: coord_cx -- X coordinate for the child
|
||||||
@param: coord_y1 -- The end of the line
|
@param: coord_yp -- Y coordinate for the child
|
||||||
@param: col -- The generation number
|
@param: coord_xp -- X coordinate for the parent
|
||||||
|
@param: coord_yp -- Y coordinate for the parent
|
||||||
"""
|
"""
|
||||||
coord_y = min(coord_y0, coord_y1)
|
coord_y = min(coord_yc, coord_yp)
|
||||||
|
|
||||||
|
# xh is the X co-ordinate half way between the two nodes.
|
||||||
|
# dx is the X gap between the two nodes, remembering that the
|
||||||
|
# the coordinates are for the LEFT of both nodes.
|
||||||
|
coord_xh = (coord_xp + _WIDTH + coord_xc)/2
|
||||||
|
width_x = (coord_xp - _WIDTH - coord_xc)/2
|
||||||
|
assert width_x >= 0
|
||||||
stylew = "top: %dpx; left: %dpx; width: %dpx;"
|
stylew = "top: %dpx; left: %dpx; width: %dpx;"
|
||||||
styleh = "top: %dpx; left: %dpx; height: %dpx;"
|
styleh = "top: %dpx; left: %dpx; height: %dpx;"
|
||||||
coord_x0 = _XOFFSET + col * _WIDTH + (col-1)*_HGAP + _HGAP/2
|
|
||||||
cnct_bv = Html("div", class_="bvline", inline=True,
|
cnct_bv = Html("div", class_="bvline", inline=True,
|
||||||
style=stylew % (coord_y1, coord_x0, _HGAP/2))
|
style=stylew % (coord_yp, coord_xh, width_x))
|
||||||
cnct_gv = Html("div", class_="gvline", inline=True,
|
cnct_gv = Html("div", class_="gvline", inline=True,
|
||||||
style=stylew % (coord_y1+_SHADOW,
|
style=stylew % (coord_yp+_SHADOW,
|
||||||
coord_x0+_SHADOW,
|
coord_xh+_SHADOW,
|
||||||
_HGAP/2+_SHADOW))
|
width_x))
|
||||||
|
# Experience says that line heights need to be 1 longer than we
|
||||||
|
# expect. I suspect this is because HTML treats the lines as
|
||||||
|
# 'number of pixels starting at...' so to create a line between
|
||||||
|
# pixels 2 and 5 we need to light pixels 2, 3, 4, 5 - FOUR - and
|
||||||
|
# not 5 - 2 = 3.
|
||||||
cnct_bh = Html("div", class_="bhline", inline=True,
|
cnct_bh = Html("div", class_="bhline", inline=True,
|
||||||
style=styleh % (coord_y, coord_x0,
|
style=styleh % (coord_y, coord_xh,
|
||||||
abs(coord_y0-coord_y1)))
|
abs(coord_yp-coord_yc)+1))
|
||||||
cnct_gh = Html("div", class_="gvline", inline=True,
|
cnct_gh = Html("div", class_="gvline", inline=True,
|
||||||
style=styleh % (coord_y+_SHADOW,
|
style=styleh % (coord_y+_SHADOW,
|
||||||
coord_x0+_SHADOW,
|
coord_xh+_SHADOW,
|
||||||
abs(coord_y0-coord_y1)))
|
abs(coord_yp-coord_yc)+1))
|
||||||
|
cnct_gv = ''
|
||||||
|
cnct_gh = ''
|
||||||
return [cnct_bv, cnct_gv, cnct_bh, cnct_gh]
|
return [cnct_bv, cnct_gv, cnct_bh, cnct_gh]
|
||||||
|
|
||||||
def draw_connected_box(self, center1, center2, col, handle):
|
def draw_connected_box(self, p_node, c_node, gen, person):
|
||||||
"""
|
"""
|
||||||
Draws the connected box for Ancestor Tree on the Individual Page
|
@param: p_node -- Parent node to draw and connect from
|
||||||
|
@param: c_node -- Child node to connect towards
|
||||||
@param: center1 -- The first box to connect
|
@param: gen -- Generation providing an HTML style hint
|
||||||
@param: center2 -- The destination box to draw
|
@param: handle -- Parent node handle
|
||||||
@param: col -- The generation number
|
|
||||||
@param: handle -- The handle of the person to set in the new box
|
|
||||||
"""
|
"""
|
||||||
|
coord_cx = _XOFFSET + c_node.coord_x
|
||||||
|
coord_cy = _YOFFSET + c_node.coord_y
|
||||||
|
coord_px = _XOFFSET+p_node.coord_x
|
||||||
|
coord_py = _YOFFSET+p_node.coord_y
|
||||||
box = []
|
box = []
|
||||||
if not handle:
|
if person is None:
|
||||||
return box
|
return box
|
||||||
person = self.r_db.get_person_from_handle(handle)
|
box = self.draw_box(p_node, gen, person)
|
||||||
box = self.draw_box(center2, col, person)
|
box += self.connect_line(
|
||||||
box += self.connect_line(center1, center2, col)
|
coord_cx, coord_cy+_LOFFSET, coord_px, coord_py+_LOFFSET)
|
||||||
return box
|
return box
|
||||||
|
|
||||||
|
def create_layout_tree(self, p_handle, generations):
|
||||||
|
"""
|
||||||
|
Create a family subtree in a format that is suitable to pass to
|
||||||
|
the Buchheim algorithm.
|
||||||
|
|
||||||
|
@param: p_handle -- Handle for person at root of this subtree
|
||||||
|
@param: generation -- Generations left to add to tree.
|
||||||
|
"""
|
||||||
|
family_tree = None
|
||||||
|
if generations:
|
||||||
|
if p_handle:
|
||||||
|
person = self.r_db.get_person_from_handle(p_handle)
|
||||||
|
if person is None:
|
||||||
|
return None
|
||||||
|
family_handle = person.get_main_parents_family_handle()
|
||||||
|
f_layout_tree = None
|
||||||
|
m_layout_tree = None
|
||||||
|
if family_handle:
|
||||||
|
family = self.r_db.get_family_from_handle(family_handle)
|
||||||
|
if family is not None:
|
||||||
|
f_handle = family.get_father_handle()
|
||||||
|
m_handle = family.get_mother_handle()
|
||||||
|
f_layout_tree = self.create_layout_tree(
|
||||||
|
f_handle, generations-1)
|
||||||
|
m_layout_tree = self.create_layout_tree(
|
||||||
|
m_handle, generations-1)
|
||||||
|
|
||||||
|
family_tree = LayoutTree(
|
||||||
|
p_handle, f_layout_tree, m_layout_tree)
|
||||||
|
return family_tree
|
||||||
|
|
||||||
def display_tree(self):
|
def display_tree(self):
|
||||||
"""
|
"""
|
||||||
Display the Ancestor Tree
|
Display the Ancestor tree using a Buchheim tree.
|
||||||
|
|
||||||
|
Reference: Improving Walker's Algorithm to Run in Linear time
|
||||||
|
Christoph Buccheim, Michael Junger, Sebastian Leipert
|
||||||
|
|
||||||
|
This is more complex than a simple binary tree but it results in a much
|
||||||
|
more compact, but still sensible, layout which is especially good where
|
||||||
|
the tree has gaps that would otherwise result in large blank areas.
|
||||||
"""
|
"""
|
||||||
tree = []
|
family_handle = self.person.get_main_parents_family_handle()
|
||||||
if not self.person.get_main_parents_family_handle():
|
if not family_handle:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
generations = self.report.options['graphgens']
|
generations = self.report.options['graphgens']
|
||||||
max_in_col = 1 << (generations-1)
|
|
||||||
max_size = _HEIGHT*max_in_col + _VGAP*(max_in_col+1)
|
|
||||||
center = int(max_size/2)
|
|
||||||
|
|
||||||
|
# Begin by building a representation of the Ancestry tree that can be
|
||||||
|
# fed to the Buchheim algorithm. Note that the algorithm doesn't care
|
||||||
|
# who is the father and who is the mother.
|
||||||
|
#
|
||||||
|
# This routine is also about to go recursive!
|
||||||
|
layout_tree = self.create_layout_tree(
|
||||||
|
self.person.get_handle(), generations)
|
||||||
|
|
||||||
|
# We now apply the Buchheim algorith to this tree, and it assigns X
|
||||||
|
# and Y positions to all elements in the tree.
|
||||||
|
l_tree = buchheim(layout_tree, _WIDTH, _HGAP, _HEIGHT, _VGAP)
|
||||||
|
|
||||||
|
# We know the height in 'pixels' where every Ancestor will sit
|
||||||
|
# precisely on an integer unit boundary.
|
||||||
with Html("div", id="tree", class_="subsection") as tree:
|
with Html("div", id="tree", class_="subsection") as tree:
|
||||||
tree += Html("h4", self._('Ancestors'), inline=True)
|
tree += Html("h4", _('Ancestors'), inline=True)
|
||||||
with Html("div", id="treeContainer",
|
with Html("div", id="treeContainer",
|
||||||
style="width:%dpx; height:%dpx;" % (
|
style="width:%dpx; height:%dpx;" % (
|
||||||
_XOFFSET+(generations)*_WIDTH+(generations-1)*_HGAP,
|
l_tree.width + _XOFFSET + _WIDTH,
|
||||||
max_size)
|
l_tree.height + _HEIGHT + _VGAP)
|
||||||
) as container:
|
) as container:
|
||||||
tree += container
|
tree += container
|
||||||
container += self.draw_tree(1, generations, max_size,
|
container += self.draw_tree(l_tree, 1, None)
|
||||||
0, center, self.person.handle)
|
|
||||||
return tree
|
return tree
|
||||||
|
|
||||||
def draw_tree(self, gen_nr, maxgen, max_size, old_center,
|
def draw_tree(self, l_node, gen_nr, c_node):
|
||||||
new_center, person_handle):
|
|
||||||
"""
|
"""
|
||||||
Draws the Ancestor Tree
|
Draws the Ancestor Tree
|
||||||
|
|
||||||
|
@param: l_node -- The tree node to draw
|
||||||
@param: gen_nr -- The generation number to draw
|
@param: gen_nr -- The generation number to draw
|
||||||
@param: maxgen -- The maximum number of generations to draw
|
@param: c_node -- Child node of this parent
|
||||||
@param: max_size -- The maximum size of the drawing area
|
|
||||||
@param: old_center -- The position of the old box
|
|
||||||
@param: new_center -- The position of the new box
|
|
||||||
@param: person_handle -- The handle of the person to draw
|
|
||||||
"""
|
"""
|
||||||
tree = []
|
tree = []
|
||||||
if gen_nr > maxgen:
|
person = self.r_db.get_person_from_handle(l_node.handle())
|
||||||
return tree
|
if person is None:
|
||||||
gen_offset = int(max_size / pow(2, gen_nr+1))
|
return None
|
||||||
if person_handle:
|
|
||||||
person = self.r_db.get_person_from_handle(person_handle)
|
|
||||||
else:
|
|
||||||
person = None
|
|
||||||
if not person:
|
|
||||||
return tree
|
|
||||||
|
|
||||||
if gen_nr == 1:
|
if gen_nr == 1:
|
||||||
tree = self.draw_box(new_center, 0, person)
|
tree = self.draw_box(l_node, 0, person)
|
||||||
else:
|
else:
|
||||||
tree = self.draw_connected_box(old_center, new_center,
|
tree = self.draw_connected_box(
|
||||||
gen_nr-1, person_handle)
|
l_node, c_node, gen_nr-1, person)
|
||||||
|
|
||||||
if gen_nr == maxgen:
|
# If there are any parents, we need to draw the extend line. We only
|
||||||
return tree
|
# use the parent to define the end of the line so either will do and
|
||||||
|
# we know we have at least one of this test passes.
|
||||||
|
if l_node.children:
|
||||||
|
tree += self.extend_line(l_node, l_node.children[0])
|
||||||
|
|
||||||
family_handle = person.get_main_parents_family_handle()
|
# The parents are equivalent and the drawing routine figures out
|
||||||
if family_handle:
|
# whether they are male or female.
|
||||||
line_offset = _XOFFSET + gen_nr*_WIDTH + (gen_nr-1)*_HGAP
|
for p_node in l_node.children:
|
||||||
tree += self.extend_line(new_center, line_offset)
|
tree += self.draw_tree(p_node, gen_nr+1, l_node)
|
||||||
|
|
||||||
family = self.r_db.get_family_from_handle(family_handle)
|
|
||||||
|
|
||||||
f_center = new_center-gen_offset
|
|
||||||
f_handle = family.get_father_handle()
|
|
||||||
tree += self.draw_tree(gen_nr+1, maxgen, max_size,
|
|
||||||
new_center, f_center, f_handle)
|
|
||||||
|
|
||||||
m_center = new_center+gen_offset
|
|
||||||
m_handle = family.get_mother_handle()
|
|
||||||
tree += self.draw_tree(gen_nr+1, maxgen, max_size,
|
|
||||||
new_center, m_center, m_handle)
|
|
||||||
return tree
|
return tree
|
||||||
|
|
||||||
def display_ind_associations(self, assoclist):
|
def display_ind_associations(self, assoclist):
|
||||||
@ -1237,7 +1294,8 @@ class PersonPages(BasePage):
|
|||||||
if birthorder:
|
if birthorder:
|
||||||
children = sorted(children)
|
children = sorted(children)
|
||||||
|
|
||||||
for birthdate, birth, death, handle in children:
|
for dummy_birthdate, dummy_birth, \
|
||||||
|
dummy_death, handle in children:
|
||||||
if handle == self.person.get_handle():
|
if handle == self.person.get_handle():
|
||||||
child_ped(ol_html)
|
child_ped(ol_html)
|
||||||
elif handle:
|
elif handle:
|
||||||
@ -1357,7 +1415,7 @@ class PersonPages(BasePage):
|
|||||||
tcell = Html("td", pname, class_="ColumnValue")
|
tcell = Html("td", pname, class_="ColumnValue")
|
||||||
# display any notes associated with this name
|
# display any notes associated with this name
|
||||||
notelist = name.get_note_list()
|
notelist = name.get_note_list()
|
||||||
if len(notelist):
|
if notelist:
|
||||||
unordered = Html("ul")
|
unordered = Html("ul")
|
||||||
|
|
||||||
for notehandle in notelist:
|
for notehandle in notelist:
|
||||||
@ -1543,9 +1601,8 @@ class PersonPages(BasePage):
|
|||||||
child_ref.get_mother_relation())
|
child_ref.get_mother_relation())
|
||||||
return (None, None)
|
return (None, None)
|
||||||
|
|
||||||
def display_ind_parent_family(self, birthmother, birthfather, family,
|
def display_ind_parent_family(
|
||||||
table,
|
self, birthmother, birthfather, family, table, first=False):
|
||||||
first=False):
|
|
||||||
"""
|
"""
|
||||||
Display the individual parent family
|
Display the individual parent family
|
||||||
|
|
||||||
@ -1610,7 +1667,7 @@ class PersonPages(BasePage):
|
|||||||
# language but in the default language.
|
# language but in the default language.
|
||||||
# Does get_sibling_relationship_string work ?
|
# Does get_sibling_relationship_string work ?
|
||||||
reln = reln[0].upper() + reln[1:]
|
reln = reln[0].upper() + reln[1:]
|
||||||
except:
|
except Exception:
|
||||||
reln = self._("Not siblings")
|
reln = self._("Not siblings")
|
||||||
|
|
||||||
val1 = " "
|
val1 = " "
|
||||||
@ -1709,7 +1766,6 @@ class PersonPages(BasePage):
|
|||||||
Display step families
|
Display step families
|
||||||
|
|
||||||
@param: parent_handle -- The family parent handle to display
|
@param: parent_handle -- The family parent handle to display
|
||||||
@param: family -- The family
|
|
||||||
@param: all_family_handles -- All known family handles
|
@param: all_family_handles -- All known family handles
|
||||||
@param: birthmother -- The birth mother
|
@param: birthmother -- The birth mother
|
||||||
@param: birthfather -- The birth father
|
@param: birthfather -- The birth father
|
||||||
@ -1724,6 +1780,7 @@ class PersonPages(BasePage):
|
|||||||
self.display_ind_parent_family(birthmother, birthfather,
|
self.display_ind_parent_family(birthmother, birthfather,
|
||||||
parent_family, table)
|
parent_family, table)
|
||||||
all_family_handles.append(parent_family_handle)
|
all_family_handles.append(parent_family_handle)
|
||||||
|
return
|
||||||
|
|
||||||
def display_ind_center_person(self):
|
def display_ind_center_person(self):
|
||||||
"""
|
"""
|
||||||
@ -1741,7 +1798,7 @@ class PersonPages(BasePage):
|
|||||||
center_person,
|
center_person,
|
||||||
self.person)
|
self.person)
|
||||||
if relationship == "": # No relation to display
|
if relationship == "": # No relation to display
|
||||||
return
|
return None
|
||||||
|
|
||||||
# begin center_person division
|
# begin center_person division
|
||||||
section = ""
|
section = ""
|
||||||
|
@ -777,12 +777,14 @@ gramps/plugins/view/view.gpr.py
|
|||||||
gramps/plugins/webreport/addressbook.py
|
gramps/plugins/webreport/addressbook.py
|
||||||
gramps/plugins/webreport/addressbooklist.py
|
gramps/plugins/webreport/addressbooklist.py
|
||||||
gramps/plugins/webreport/basepage.py
|
gramps/plugins/webreport/basepage.py
|
||||||
|
gramps/plugins/webreport/buchheim.py
|
||||||
gramps/plugins/webreport/contact.py
|
gramps/plugins/webreport/contact.py
|
||||||
gramps/plugins/webreport/download.py
|
gramps/plugins/webreport/download.py
|
||||||
gramps/plugins/webreport/event.py
|
gramps/plugins/webreport/event.py
|
||||||
gramps/plugins/webreport/family.py
|
gramps/plugins/webreport/family.py
|
||||||
gramps/plugins/webreport/home.py
|
gramps/plugins/webreport/home.py
|
||||||
gramps/plugins/webreport/introduction.py
|
gramps/plugins/webreport/introduction.py
|
||||||
|
gramps/plugins/webreport/layout.py
|
||||||
gramps/plugins/webreport/media.py
|
gramps/plugins/webreport/media.py
|
||||||
gramps/plugins/webreport/narrativeweb.py
|
gramps/plugins/webreport/narrativeweb.py
|
||||||
gramps/plugins/webreport/person.py
|
gramps/plugins/webreport/person.py
|
||||||
|
BIN
test/AncestorTree/AncestorTree.gramps
Normal file
BIN
test/AncestorTree/AncestorTree.gramps
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user