Files
gramps/gramps/gui/widgets/fanchartdesc.py
Bastien Jacquet fec5d532d1 Feature: Gep-030 FanChart2Way
...New FanChart consisting of both ascendants and descendants.

It can be checked-out there :
https://sourceforge.net/u/bubblegum00/gramps/ci/geps/gep-030-FanChart2Way/~/tree/

<snip>

I find it quite handy, so please let me know if you have any comments or suggestions.

NB: I heavily cleaned up the FanChart code on my way.

Regards,

Bastien Jacquet

https://sourceforge.net/p/gramps/mailman/message/32908110/
........................................................
Refactor fanchart for further modification

Fix radial text pos_start radian alignment

Simplify father's and mother's details getter

Rename draw_gradient to draw_gradient_legend

refactor prepare_background_box

Add option not to flip names

Add a display_format(self, person, num) function as proposed by Benny Malengier on 2012-12-13
It just returns name_display.name_formats[num][_F_FN](person.get_primary_name())

Add option to show the name on two lines

Allow for variable radius depending on generation

Switch to WORD_CHAR wrapping of name (ie word, and char if 0-length word)

Move rescaling tentative inside wrap_truncate_layout

Fix person_under_cursor bugs

Refactor root angle computation

Refactor code positionning the fan

Refactor personpos_at_angle

move implementation of person_under_cursor outside of FanChartBaseWidget class

Change draw_person to take angles in radians

Use same structure for innerring as for outerring

Uses cursor_to_polar and cursor_on_tranlation_dot

Slightly change person_under_cursor logic to return an "address" in the fan

Uses radian_in_bounds to compare angles modulo 2 PI

Fixup test on cursor over inner ring

Fix Center size for FanchartDesc

Fixup fanchart check up to last generation

Give same signature to draw_person

Refactor the common code of self.draw_person in a single function in Base class

Fix center box comment

Refactoring inside celladdress

Remove manual central box drawing since done with draw_person

Fixup draw_person color for duplicates

Use draw_person for central person too

Make __compute_angle and __rec_fill_data public for use in FanChart2Way

Add 2Way View

Rewrite create_map_rect_to_sector to allow bottom-outside-oriented text-arc

Allow to automatically right upside-up bottom arc-text

Correct icons for Fanchart2Way

Small code refactoring

Refactor code of fanchartdesc to use self.rootangle_rad

Rename change_slice to toggle_cell_state

Fanchart2Way code formating and changes

Small refactoring of fanchartdesc innerring fill data

Remove the name from the local temporary data structure

Remove the name from the local temporary data structure (in Fanchart2Way)

Change background gradient to follow the user-selected gradient colors

rename parentsroot to innerring

Some renaming for clearer code

Show last generation of partners in descendant fanchart

Show last partner in Fanchart2Way

Fanchart2Way : Add option to disable gradient on the background

Fixup flipupsidedownname parameter for gramplet usage of fancharts

Fixup twolinename parameter for gramplet usage of fancharts

Add FanChart2Way in available gramplets

Tentative fix for last view on Fanchart2Way

Show step-sibling in Fancharts context-menu

Fix overestimation of descendant halfdist

(SM) Trailing White spaces removed

(SM) Fix config box Table Grid

(SM) Move Icons gramps-fanchart2way to new location

(SM) Add Copyright for Bastien Jacquet

(SM) Fix BSDDB AttributeError NoneType object has no attr

(SM) Update patch to account for bug 9771; fix missing right-click menu items

(Nick Hall/eno93) Fix set_text method takes the length of the utf-8, not the length of the unicode as the second parameter ((Gramps.py:3697): Pango-WARNING **: Invalid UTF-8 string passed to pango_layout_set_text())
2016-11-26 18:00:08 +00:00

650 lines
27 KiB
Python

#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2001-2007 Donald N. Allingham, Martin Hawlisch
# Copyright (C) 2009 Douglas S. Blank
# Copyright (C) 2012 Benny Malengier
#
# 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.
#
## Based on the paper:
## http://www.cs.utah.edu/~draperg/research/fanchart/draperg_FHT08.pdf
## and the applet:
## http://www.cs.utah.edu/~draperg/research/fanchart/demo/
## Found by redwood:
## http://www.gramps-project.org/bugs/view.php?id=2611
#-------------------------------------------------------------------------
#
# Python modules
#
#-------------------------------------------------------------------------
from gi.repository import Pango
from gi.repository import GObject
from gi.repository import Gdk
from gi.repository import Gtk
from gi.repository import PangoCairo
import cairo
import math
import colorsys
import pickle
from html import escape
#-------------------------------------------------------------------------
#
# Gramps modules
#
#-------------------------------------------------------------------------
from gramps.gen.display.name import displayer as name_displayer
from gramps.gen.errors import WindowActiveError
from ..editors import EditPerson, EditFamily
from ..utils import hex_to_rgb
from ..ddtargets import DdTargets
from gramps.gen.utils.alive import probably_alive
from gramps.gen.utils.libformatting import FormattingHelper
from gramps.gen.utils.db import (find_children, find_parents, find_witnessed_people,
get_age, get_timeperiod)
from gramps.gen.plug.report.utils import find_spouse
from .fanchart import *
#-------------------------------------------------------------------------
#
# Constants
#
#-------------------------------------------------------------------------
pi = math.pi
PIXELS_PER_GENPERSON_RATIO = 0.55 # ratio of generation radius for person (rest for partner)
PIXELS_PER_GEN_SMALL = 80
PIXELS_PER_GEN_LARGE = 160
N_GEN_SMALL = 4
PIXELS_PER_GENFAMILY = 25 # size of radius for family
PIXELS_PER_RECLAIM = 4 # size of the radius of pixels taken from family to reclaim space
PIXELS_PARTNER_GAP = 0 # Padding between someone and his partner
PIXELS_CHILDREN_GAP = 5 # Padding between generations
PARENTRING_WIDTH = 12 # width of the parent ring inside the person
ANGLE_CHEQUI = 0 #Algorithm with homogeneous children distribution
ANGLE_WEIGHT = 1 #Algorithm for angle computation based on nr of descendants
TYPE_BOX_NORMAL = 0
TYPE_BOX_FAMILY = 1
#-------------------------------------------------------------------------
#
# FanChartDescWidget
#
#-------------------------------------------------------------------------
class FanChartDescWidget(FanChartBaseWidget):
"""
Interactive Fan Chart Widget.
"""
CENTER = 50 # we require a larger center as CENTER includes the 1st partner
def __init__(self, dbstate, uistate, callback_popup=None):
"""
Fan Chart Widget. Handles visualization of data in self.data.
See main() of FanChartGramplet for example of model format.
"""
self.set_values(None, 9, True, True, BACKGROUND_GRAD_GEN, 'Sans', '#0000FF',
'#FF0000', None, 0.5, FORM_CIRCLE, ANGLE_WEIGHT, '#888a85')
FanChartBaseWidget.__init__(self, dbstate, uistate, callback_popup)
def set_values(self, root_person_handle, maxgen, flipupsidedownname, twolinename, background,
fontdescr, grad_start, grad_end,
filter, alpha_filter, form, angle_algo, dupcolor):
"""
Reset the values to be used:
:param root_person_handle: person to show
:param maxgen: maximum generations to show
:param flipupsidedownname: flip name on the left of the fanchart for the display of person's name
:param background: config setting of which background procedure to use
:type background: int
:param fontdescr: string describing the font to use
:param grad_start: colors to use for background procedure
:param grad_end: colors to use for background procedure
:param filter: the person filter to apply to the people in the chart
:param alpha_filter: the alpha transparency value (0-1) to apply to
filtered out data
:param form: the ``FORM_`` constant for the fanchart
:param angle_algo: alorithm to use to calculate the sizes of the boxes
:param dupcolor: color to use for people or families that occur a second
or more time
"""
self.rootpersonh = root_person_handle
self.generations = maxgen
self.background = background
self.fontdescr = fontdescr
self.grad_start = grad_start
self.grad_end = grad_end
self.filter = filter
self.alpha_filter = alpha_filter
self.form = form
self.anglealgo = angle_algo
self.dupcolor = hex_to_rgb(dupcolor)
self.childring = False
self.flipupsidedownname = flipupsidedownname
self.twolinename = twolinename
def set_generations(self):
"""
Set the generations to max, and fill data structures with initial data.
"""
if self.form == FORM_CIRCLE:
self.rootangle_rad = [math.radians(0), math.radians(360)]
elif self.form == FORM_HALFCIRCLE:
self.rootangle_rad = [math.radians(90), math.radians(90 + 180)]
elif self.form == FORM_QUADRANT:
self.rootangle_rad = [math.radians(90), math.radians(90 + 90)]
self.handle2desc = {}
self.famhandle2desc = {}
self.handle2fam = {}
self.gen2people = {}
self.gen2fam = {}
self.innerring = []
self.gen2people[0] = [(None, False, 0, 2*pi, 0, 0, [], NORMAL)] #no center person
self.gen2fam[0] = [] #no families
self.angle = {}
self.angle[-2] = []
for i in range(1, self.generations+1):
self.gen2fam[i] = []
self.gen2people[i] = []
self.gen2people[self.generations] = [] #indication of more children
def _fill_data_structures(self):
self.set_generations()
if not self.rootpersonh:
return
person = self.dbstate.db.get_person_from_handle(self.rootpersonh)
if not person:
#nothing to do, just return
return
# person, duplicate or not, start angle, slice size,
# text, parent pos in fam, nrfam, userdata, status
self.gen2people[0] = [[person, False, 0, 2*pi, 0, 0, [], NORMAL]]
self.handle2desc[self.rootpersonh] = 0
# fill in data for the parents
self.innerring = []
handleparents = []
family_handle_list = person.get_parent_family_handle_list()
if family_handle_list:
for family_handle in family_handle_list:
family = self.dbstate.db.get_family_from_handle(family_handle)
if not family:
continue
for hparent in [family.get_father_handle(), family.get_mother_handle()]:
if hparent and hparent not in handleparents:
parent = self.dbstate.db.get_person_from_handle(hparent)
if parent:
self.innerring.append((parent, []))
handleparents.append(hparent)
#recursively fill in the datastructures:
nrdesc = self._rec_fill_data(0, person, 0, self.generations)
self.handle2desc[person.handle] += nrdesc
self._compute_angles(*self.rootangle_rad)
def _rec_fill_data(self, gen, person, pos, maxgen):
"""
Recursively fill in the data
"""
totdesc = 0
marriage_handle_list = person.get_family_handle_list()
self.gen2people[gen][pos][5] = len(marriage_handle_list)
for family_handle in marriage_handle_list:
totdescfam = 0
family = self.dbstate.db.get_family_from_handle(family_handle)
spouse_handle = find_spouse(person, family)
if spouse_handle:
spouse = self.dbstate.db.get_person_from_handle(spouse_handle)
else:
spouse = None
# family may occur via father and via mother in the chart, only
# first to show and count.
fam_duplicate = family_handle in self.famhandle2desc
# family, duplicate or not, start angle, slice size,
# spouse pos in gen, nrchildren, userdata, parnter, status
self.gen2fam[gen].append([family, fam_duplicate, 0, 0, pos, 0, [], spouse, NORMAL])
posfam = len(self.gen2fam[gen]) - 1
if not fam_duplicate and gen < maxgen-1:
nrchild = len(family.get_child_ref_list())
self.gen2fam[gen][posfam][5] = nrchild
for child_ref in family.get_child_ref_list():
child = self.dbstate.db.get_person_from_handle(child_ref.ref)
child_dup = child_ref.ref in self.handle2desc
if not child_dup:
self.handle2desc[child_ref.ref] = 0 # mark this child as processed
# person, duplicate or not, start angle, slice size,
# parent pos in fam, nrfam, userdata, status
self.gen2people[gen+1].append([child, child_dup, 0, 0, posfam, 0, [], NORMAL])
totdescfam += 1 #add this person as descendant
pospers = len(self.gen2people[gen+1]) - 1
if not child_dup:
nrdesc = self._rec_fill_data(gen+1, child, pospers, maxgen)
self.handle2desc[child_ref.ref] += nrdesc
totdescfam += nrdesc # add children of him as descendants
if not fam_duplicate:
self.famhandle2desc[family_handle] = totdescfam
totdesc += totdescfam
return totdesc
def _compute_angles(self, start_rad, stop_rad):
"""
Compute the angles of the boxes
"""
#first we compute the size of the slice.
#set angles root person
start, slice = start_rad, stop_rad - start_rad
nr_gen = len(self.gen2people)-1
# Fill in central person angles
gen = 0
data = self.gen2people[gen][0]
data[2] = start
data[3] = slice
for gen in range(0, nr_gen):
prevpartnerdatahandle = None
offset = 0
for data_fam in self.gen2fam[gen]: # for each partner/fam of gen-1
#obtain start and stop from the people of this partner
persondata = self.gen2people[gen][data_fam[4]]
dupfam = data_fam[1]
if dupfam:
# we don't show again the descendants here
nrdescfam = 0
else:
nrdescfam = self.famhandle2desc[data_fam[0].handle]
nrdescperson = self.handle2desc[persondata[0].handle]
nrfam = persondata[5]
personstart, personslice = persondata[2:4]
if prevpartnerdatahandle != persondata[0].handle:
#partner of a new person: reset the offset
offset = 0
prevpartnerdatahandle = persondata[0].handle
slice = personslice/(nrdescperson+nrfam)*(nrdescfam+1)
if data_fam[8] == COLLAPSED:
slice = 0
elif data_fam[8] == EXPANDED:
slice = personslice
data_fam[2] = personstart + offset
data_fam[3] = slice
offset += slice
## if nrdescperson == 0:
## #no offspring, draw as large as fraction of
## #nr families
## nrfam = persondata[6]
## slice = personslice/nrfam
## data_fam[2] = personstart + offset
## data_fam[3] = slice
## offset += slice
## elif nrdescfam == 0:
## #no offspring this family, but there is another
## #family. We draw this as a weight of 1
## nrfam = persondata[6]
## slice = personslice/(nrdescperson + nrfam - 1)*(nrdescfam+1)
## data_fam[2] = personstart + offset
## data_fam[3] = slice
## offset += slice
## else:
## #this family has offspring. We give it space for it's
## #weight in offspring
## nrfam = persondata[6]
## slice = personslice/(nrdescperson + nrfam - 1)*(nrdescfam+1)
## data_fam[2] = personstart + offset
## data_fam[3] = slice
## offset += slice
prevfamdatahandle = None
offset = 0
for persondata in self.gen2people[gen+1] if gen < nr_gen else []:
#obtain start and stop of family this is child of
parentfamdata = self.gen2fam[gen][persondata[4]]
nrdescfam = 0
if not parentfamdata[1]:
nrdescfam = self.famhandle2desc[parentfamdata[0].handle]
nrdesc = 0
if not persondata[1]:
nrdesc = self.handle2desc[persondata[0].handle]
famstart = parentfamdata[2]
famslice = parentfamdata[3]
nrchild = parentfamdata[5]
#now we divide this slice to the weight of children,
#adding one for every child
if self.anglealgo == ANGLE_CHEQUI:
slice = famslice / nrchild
elif self.anglealgo == ANGLE_WEIGHT:
slice = famslice/(nrdescfam) * (nrdesc + 1)
else:
raise NotImplementedError('Unknown angle algorithm %d' % self.anglealgo)
if prevfamdatahandle != parentfamdata[0].handle:
#reset the offset
offset = 0
prevfamdatahandle = parentfamdata[0].handle
if persondata[7] == COLLAPSED:
slice = 0
elif persondata[7] == EXPANDED:
slice = famslice
persondata[2] = famstart + offset
persondata[3] = slice
offset += slice
def nrgen(self):
#compute the number of generations present
for gen in range(self.generations - 1, 0, -1):
if len(self.gen2people[gen]) > 0:
return gen + 1
return 1
def halfdist(self):
"""
Compute the half radius of the circle
"""
radius = PIXELS_PER_GEN_SMALL * N_GEN_SMALL + PIXELS_PER_GEN_LARGE \
* ( self.nrgen() - N_GEN_SMALL ) + self.CENTER
return radius
def get_radiusinout_for_generation(self,generation):
radius_first_gen = self.CENTER - (1-PIXELS_PER_GENPERSON_RATIO) * PIXELS_PER_GEN_SMALL
if generation < N_GEN_SMALL:
radius_start = PIXELS_PER_GEN_SMALL * generation + radius_first_gen
return (radius_start,radius_start + PIXELS_PER_GEN_SMALL)
else:
radius_start = PIXELS_PER_GEN_SMALL * N_GEN_SMALL + PIXELS_PER_GEN_LARGE \
* ( generation - N_GEN_SMALL ) + radius_first_gen
return (radius_start,radius_start + PIXELS_PER_GEN_LARGE)
def get_radiusinout_for_generation_pair(self,generation):
radiusin, radiusout = self.get_radiusinout_for_generation(generation)
radius_spread = radiusout - radiusin - PIXELS_CHILDREN_GAP - PIXELS_PARTNER_GAP
radiusin_pers = radiusin + PIXELS_CHILDREN_GAP
radiusout_pers = radiusin_pers + PIXELS_PER_GENPERSON_RATIO * radius_spread
radiusin_partner = radiusout_pers + PIXELS_PARTNER_GAP
radiusout_partner = radiusout
return (radiusin_pers,radiusout_pers,radiusin_partner,radiusout_partner)
def people_generator(self):
"""
a generator over all people outside of the core person
"""
for generation in range(self.generations):
for data in self.gen2people[generation]:
yield (data[0], data[6])
for generation in range(self.generations):
for data in self.gen2fam[generation]:
yield (data[7], data[6])
def innerpeople_generator(self):
"""
a generator over all people inside of the core person
"""
for parentdata in self.innerring:
parent, userdata = parentdata
yield (parent, userdata)
def on_draw(self, widget, cr, scale=1.):
"""
The main method to do the drawing.
If widget is given, we assume we draw in GTK3 and use the allocation.
To draw raw on the cairo context cr, set widget=None.
"""
# first do size request of what we will need
halfdist = self.halfdist()
if widget:
if self.form == FORM_CIRCLE:
self.set_size_request(2 * halfdist, 2 * halfdist)
elif self.form == FORM_HALFCIRCLE:
self.set_size_request(2 * halfdist, halfdist + self.CENTER
+ PAD_PX)
elif self.form == FORM_QUADRANT:
self.set_size_request(halfdist + self.CENTER + PAD_PX,
halfdist + self.CENTER + PAD_PX)
cr.scale(scale, scale)
# when printing, we need not recalculate
if widget:
self.center_xy = self.center_xy_from_delta()
cr.translate(*self.center_xy)
cr.save()
# Draw center person:
(person, dup, start, slice, parentfampos, nrfam, userdata, status) \
= self.gen2people[0][0]
if person:
r, g, b, a = self.background_box(person, 0, userdata)
radiusin_pers,radiusout_pers,radiusin_partner,radiusout_partner = \
self.get_radiusinout_for_generation_pair(0)
if not self.innerring: radiusin_pers = TRANSLATE_PX
self.draw_person(cr, person, radiusin_pers, radiusout_pers, math.pi/2, math.pi/2 + 2*math.pi,
0, False, userdata, is_central_person =True)
#draw center to move chart
cr.set_source_rgb(0, 0, 0) # black
cr.move_to(TRANSLATE_PX, 0)
cr.arc(0, 0, TRANSLATE_PX, 0, 2 * math.pi)
if self.innerring: # has at least one parent
cr.fill()
self.draw_innerring_people(cr)
else:
cr.stroke()
#now write all the families and children
cr.rotate(self.rotate_value * math.pi/180)
for gen in range(self.generations):
radiusin_pers,radiusout_pers,radiusin_partner,radiusout_partner = \
self.get_radiusinout_for_generation_pair(gen)
if gen > 0:
for pdata in self.gen2people[gen]:
# person, duplicate or not, start angle, slice size,
# parent pos in fam, nrfam, userdata, status
pers, dup, start, slice, pospar, nrfam, userdata, status = \
pdata
if status != COLLAPSED:
self.draw_person(cr, pers, radiusin_pers, radiusout_pers,
start, start + slice, gen, dup, userdata,
thick=status != NORMAL)
#if gen < self.generations-1:
for famdata in self.gen2fam[gen]:
# family, duplicate or not, start angle, slice size,
# spouse pos in gen, nrchildren, userdata, status
fam, dup, start, slice, posfam, nrchild, userdata,\
partner, status = famdata
if status != COLLAPSED:
more_pers_flag = (gen == self.generations - 1
and len(fam.get_child_ref_list()) > 0)
self.draw_person(cr, partner, radiusin_partner, radiusout_partner, start, start + slice,
gen, dup, userdata, thick = (status != NORMAL), has_moregen_indicator = more_pers_flag )
cr.restore()
if self.background in [BACKGROUND_GRAD_AGE, BACKGROUND_GRAD_PERIOD]:
self.draw_gradient_legend(cr, widget, halfdist)
def cell_address_under_cursor(self, curx, cury):
"""
Determine the cell address in the fan under the cursor
position x and y.
None if outside of diagram
"""
radius, rads, raw_rads = self.cursor_to_polar(curx, cury, get_raw_rads=True)
btype = TYPE_BOX_NORMAL
if radius < TRANSLATE_PX:
return None
elif (self.innerring and self.angle[-2] and
radius < CHILDRING_WIDTH + TRANSLATE_PX):
generation = -2 # indication of one of the children
elif radius < self.CENTER:
generation = 0
else:
generation = None
for gen in range(self.generations):
radiusin_pers,radiusout_pers,radiusin_partner,radiusout_partner \
= self.get_radiusinout_for_generation_pair(gen)
if radiusin_pers <= radius <= radiusout_pers:
generation, btype = gen, TYPE_BOX_NORMAL
break
if radiusin_partner <= radius <= radiusout_partner:
generation, btype = gen, TYPE_BOX_FAMILY
break
# find what person is in this position:
selected = None
if not (generation is None) and 0 <= generation:
selected = self.personpos_at_angle(generation, rads, btype)
elif generation == -2:
for p in range(len(self.angle[generation])):
start, stop, state = self.angle[generation][p]
if self.radian_in_bounds(start, raw_rads, stop):
selected = p
break
if (generation is None or selected is None):
return None
return generation, selected, btype
def draw_innerring_people(self, cr):
cr.move_to(TRANSLATE_PX + CHILDRING_WIDTH, 0)
cr.set_source_rgb(0, 0, 0) # black
cr.set_line_width(1)
cr.arc(0, 0, TRANSLATE_PX + CHILDRING_WIDTH, 0, 2 * math.pi)
cr.stroke()
nrparent = len(self.innerring)
#Y axis is downward. positve angles are hence clockwise
startangle = math.pi
if nrparent <= 2:
angleinc = math.pi
elif nrparent <= 4:
angleinc = math.pi/2
else:
angleinc = 2 * math.pi / nrchild
for data in self.innerring:
self.draw_innerring(cr, data[0], data[1], startangle, angleinc)
startangle += angleinc
def personpos_at_angle(self, generation, rads, btype):
"""
returns the person in generation generation at angle.
"""
selected = None
datas = None
if btype == TYPE_BOX_NORMAL:
if generation==0:
return 0 # central person is always ok !
datas = self.gen2people[generation]
elif btype == TYPE_BOX_FAMILY:
datas = self.gen2fam[generation]
else:
return None
for p, pdata in enumerate(datas):
# person, duplicate or not, start angle, slice size,
# parent pos in fam, nrfam, userdata, status
start, stop = pdata[2], pdata[2] + pdata[3]
if self.radian_in_bounds(start, rads, stop):
selected = p
break
return selected
def person_at(self, cell_address):
"""
returns the person at generation, pos, btype
"""
generation, pos, btype = cell_address
if generation == -2:
person, userdata = self.innerring[pos]
elif btype == TYPE_BOX_NORMAL:
# person, duplicate or not, start angle, slice size,
# parent pos in fam, nrfam, userdata, status
person = self.gen2people[generation][pos][0]
elif btype == TYPE_BOX_FAMILY:
# family, duplicate or not, start angle, slice size,
# spouse pos in gen, nrchildren, userdata, person, status
person = self.gen2fam[generation][pos][7]
return person
def family_at(self, cell_address):
"""
returns the family at generation, pos, btype
"""
generation, pos, btype = cell_address
if pos is None or btype == TYPE_BOX_NORMAL or generation < 0:
return None
return self.gen2fam[generation][pos][0]
def do_mouse_click(self):
# no drag occured, expand or collapse the section
self.toggle_cell_state(self._mouse_click_cell_address)
self._compute_angles(*self.rootangle_rad)
self._mouse_click = False
self.queue_draw()
def toggle_cell_state(self, cell_address):
generation, selected, btype = cell_address
if generation < 1:
return
if btype == TYPE_BOX_NORMAL:
data = self.gen2people[generation][selected]
parpos = data[4]
status = data[7]
if status == NORMAL:
#should be expanded, rest collapsed
for entry in self.gen2people[generation]:
if entry[4] == parpos:
entry[7] = COLLAPSED
data[7] = EXPANDED
else:
#is expanded, set back to normal
for entry in self.gen2people[generation]:
if entry[4] == parpos:
entry[7] = NORMAL
if btype == TYPE_BOX_FAMILY:
data = self.gen2fam[generation][selected]
parpos = data[4]
status = data[8]
if status == NORMAL:
#should be expanded, rest collapsed
for entry in self.gen2fam[generation]:
if entry[4] == parpos:
entry[8] = COLLAPSED
data[8] = EXPANDED
else:
#is expanded, set back to normal
for entry in self.gen2fam[generation]:
if entry[4] == parpos:
entry[8] = NORMAL
class FanChartDescGrampsGUI(FanChartGrampsGUI):
""" class for functions fanchart GUI elements will need in Gramps
"""
def main(self):
"""
Fill the data structures with the active data. This initializes all
data.
"""
root_person_handle = self.get_active('Person')
self.fan.set_values(root_person_handle, self.maxgen, self.flipupsidedownname, self.twolinename, self.background,
self.fonttype, self.grad_start, self.grad_end,
self.generic_filter, self.alpha_filter, self.form,
self.angle_algo, self.dupcolor)
self.fan.reset()
self.fan.queue_draw()