Fanchart: code reorganization to allow reuse by a descendant fanchart

svn: r20375
This commit is contained in:
Benny Malengier 2012-09-13 11:58:28 +00:00
parent 6708e3b6b5
commit 516916cb5c
3 changed files with 347 additions and 299 deletions

View File

@ -133,20 +133,14 @@ EXPANDED = 2
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# #
# FanChartWidget # FanChartBaseWidget
# #
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
class FanChartWidget(Gtk.DrawingArea): class FanChartBaseWidget(Gtk.DrawingArea):
""" """ a base widget for fancharts"""
Interactive Fan Chart Widget.
"""
def __init__(self, dbstate, callback_popup=None): def __init__(self, dbstate, callback_popup=None):
"""
Fan Chart Widget. Handles visualization of data in self.data.
See main() of FanChartGramplet for example of model format.
"""
GObject.GObject.__init__(self) GObject.GObject.__init__(self)
self.dbstate = dbstate self.dbstate = dbstate
self.translating = False self.translating = False
@ -192,16 +186,265 @@ class FanChartWidget(Gtk.DrawingArea):
self._mouse_click = False self._mouse_click = False
self.rotate_value = 90 # degrees, initially, 1st gen male on right half self.rotate_value = 90 # degrees, initially, 1st gen male on right half
self.center_xy = [0, 0] # distance from center (x, y) self.center_xy = [0, 0] # distance from center (x, y)
#default values #(re)compute everything
self.reset(None, 9, BACKGROUND_GRAD_GEN, True, True, 'Sans', '#0000FF', self.reset()
'#FF0000', None, 0.5, FORM_CIRCLE)
self.set_size_request(120, 120) self.set_size_request(120, 120)
def reset(self, root_person_handle, maxgen, background, childring, def reset(self):
"""
Reset the fan chart. This should trigger computation of all data
structures needed
"""
raise NotImplementedError
def do_size_request(self, requisition):
"""
Overridden method to handle size request events.
"""
if self.form == FORM_CIRCLE:
requisition.width = 2 * self.halfdist()
requisition.height = requisition.width
elif self.form == FORM_HALFCIRCLE:
requisition.width = 2 * self.halfdist()
requisition.height = requisition.width / 2 + CENTER + PAD_PX
elif self.form == FORM_QUADRANT:
requisition.width = self.halfdist() + CENTER + PAD_PX
requisition.height = requisition.width
def do_get_preferred_width(self):
""" GTK3 uses width for height sizing model. This method will
override the virtual method
"""
req = Gtk.Requisition()
self.do_size_request(req)
return req.width, req.width
def do_get_preferred_height(self):
""" GTK3 uses width for height sizing model. This method will
override the virtual method
"""
req = Gtk.Requisition()
self.do_size_request(req)
return req.height, req.height
def halfdist(self):
"""
Compute the half radius of the circle
"""
raise NotImplementedError
def on_draw(self, widget, cr, scale=1.):
"""
callback to draw the fanchart
"""
raise NotImplementedError
def people_generator(self):
"""
a generator over all people outside of the core person
"""
raise NotImplementedError
def innerpeople_generator(self):
"""
a generator over all people inside of the core person
"""
raise NotImplementedError
def set_userdata_timeperiod(self, person, userdata):
"""
set the userdata as used by timeperiod
"""
period = None
if person:
period = get_timeperiod(self.dbstate.db, person)
if period is not None:
if period > self.maxperiod:
self.maxperiod = period
if period < self.minperiod:
self.minperiod = period
userdata.append(period)
def set_userdata_age(self, person, userdata):
agecol = (255, 255, 255) # white
if person:
age = get_age(self.dbstate.db, person)
if age is not None:
age = age[0]
if age < 0:
age = 0
elif age > MAX_AGE:
age = MAX_AGE
#now determine fraction for gradient
agefrac = age / MAX_AGE
agecol = colorsys.hsv_to_rgb(
(1-agefrac) * self.cstart_hsv[0] + agefrac * self.cend_hsv[0],
(1-agefrac) * self.cstart_hsv[1] + agefrac * self.cend_hsv[1],
(1-agefrac) * self.cstart_hsv[2] + agefrac * self.cend_hsv[2],
)
userdata.append((agecol[0]*255, agecol[1]*255, agecol[2]*255))
def prepare_background_box(self):
"""
Method that is called every reset of the chart, to precomputed values
needed for the background of the boxes
"""
maxgen = self.generations
cstart = gui.utils.hex_to_rgb(self.grad_start)
cend = gui.utils.hex_to_rgb(self.grad_end)
self.cstart_hsv = colorsys.rgb_to_hsv(cstart[0]/255, cstart[1]/255,
cstart[2]/255)
self.cend_hsv = colorsys.rgb_to_hsv(cend[0]/255, cend[1]/255,
cend[2]/255)
if self.background in [BACKGROUND_GENDER, BACKGROUND_SINGLE_COLOR]:
# nothing to precompute
self.colors = None
self.maincolor = cstart
elif self.background == BACKGROUND_GRAD_GEN:
#compute the colors, -1, 0, ..., maxgen
divs = [x/(maxgen-1) for x in range(maxgen)]
rgb_colors = [colorsys.hsv_to_rgb(
(1-x) * self.cstart_hsv[0] + x * self.cend_hsv[0],
(1-x) * self.cstart_hsv[1] + x * self.cend_hsv[1],
(1-x) * self.cstart_hsv[2] + x * self.cend_hsv[2],
) for x in divs]
self.colors = [(255*r, 255*g, 255*b) for r, g, b in rgb_colors]
elif self.background == BACKGROUND_GRAD_PERIOD:
# we fill in in the data structure what the period is, None if not found
self.colors = None
self.minperiod = 1e10
self.maxperiod = -1e10
gen_people = self.people_generator()
for person, userdata in gen_people:
self.set_userdata_timeperiod(person, userdata)
# same for child
gen_inner = self.innerpeople_generator()
for child, userdata in gen_inner:
self.set_userdata_timeperiod(child, userdata)
#now create gradient data, 5 values from min to max rounded to nearest 50
if self.maxperiod < self.minperiod:
self.maxperiod = self.minperiod = gen.lib.date.Today().get_year()
rper = self.maxperiod // 50
if rper * 50 != self.maxperiod:
self.maxperiod = rper * 50 + 50
self.minperiod = 50 * (self.minperiod // 50)
periodrange = self.maxperiod - self.minperiod
steps = 2 * GRADIENTSCALE - 1
divs = [x/(steps-1) for x in range(steps)]
self.gradval = ['%d' % int(self.minperiod + x * periodrange) for x in divs]
for i in range(len(self.gradval)):
if i % 2 == 1:
self.gradval[i] = ''
self.gradcol = [colorsys.hsv_to_rgb(
(1-div) * self.cstart_hsv[0] + div * self.cend_hsv[0],
(1-div) * self.cstart_hsv[1] + div * self.cend_hsv[1],
(1-div) * self.cstart_hsv[2] + div * self.cend_hsv[2],
) for div in divs]
elif self.background == BACKGROUND_GRAD_AGE:
# we fill in in the data structure what the color age is, white if no age
self.colors = None
gen_people = self.people_generator()
for person, userdata in gen_people:
self.set_userdata_age(person, userdata)
# same for child
gen_inner = self.innerpeople_generator()
for child, userdata in gen_inner:
self.set_userdata_age(child, userdata)
#now create gradient data, 5 values from 0 to max
steps = 2 * GRADIENTSCALE - 1
divs = [x/(steps-1) for x in range(steps)]
self.gradval = ['%d' % int(x * MAX_AGE) for x in divs]
self.gradval[-1] = '%d+' % MAX_AGE
for i in range(len(self.gradval)):
if i % 2 == 1:
self.gradval[i] = ''
self.gradcol = [colorsys.hsv_to_rgb(
(1-div) * self.cstart_hsv[0] + div * self.cend_hsv[0],
(1-div) * self.cstart_hsv[1] + div * self.cend_hsv[1],
(1-div) * self.cstart_hsv[2] + div * self.cend_hsv[2],
) for div in divs]
else:
# known colors per generation, set or compute them
self.colors = GENCOLOR[self.background]
def background_box(self, person, generation, userdata):
"""
determine red, green, blue value of background of the box of person,
which has gender gender, and is in ring generation
"""
if generation == 0 and self.background in [BACKGROUND_GENDER,
BACKGROUND_GRAD_GEN, BACKGROUND_SCHEME1,
BACKGROUND_SCHEME2]:
# white for center person:
color = (255, 255, 255)
elif self.background == BACKGROUND_GENDER:
try:
alive = probably_alive(person, self.dbstate.db)
except RuntimeError:
alive = False
backgr, border = gui.utils.color_graph_box(alive, person.gender)
color = gui.utils.hex_to_rgb(backgr)
elif self.background == BACKGROUND_SINGLE_COLOR:
color = self.maincolor
elif self.background == BACKGROUND_GRAD_AGE:
color = userdata[0]
elif self.background == BACKGROUND_GRAD_PERIOD:
period = userdata[0]
if period is None:
color = (255, 255, 255) # white
else:
periodfrac = ((period - self.minperiod)
/ (self.maxperiod - self.minperiod))
periodcol = colorsys.hsv_to_rgb(
(1-periodfrac) * self.cstart_hsv[0] + periodfrac * self.cend_hsv[0],
(1-periodfrac) * self.cstart_hsv[1] + periodfrac * self.cend_hsv[1],
(1-periodfrac) * self.cstart_hsv[2] + periodfrac * self.cend_hsv[2],
)
color = (periodcol[0]*255, periodcol[1]*255, periodcol[2]*255)
else:
if self.background == BACKGROUND_GRAD_GEN and generation < 0:
generation = 0
color = self.colors[generation % len(self.colors)]
if person.gender == gen.lib.Person.MALE:
color = [x*.9 for x in color]
# now we set transparency data
if self.filter and not self.filter.match(person.handle, self.dbstate.db):
if self.background == BACKGROUND_SINGLE_COLOR:
alpha = 0. # no color shown
else:
alpha = self.alpha_filter
else:
alpha = 1.
return color[0], color[1], color[2], alpha
#-------------------------------------------------------------------------
#
# FanChartWidget
#
#-------------------------------------------------------------------------
class FanChartWidget(FanChartBaseWidget):
"""
Interactive Fan Chart Widget.
"""
def __init__(self, dbstate, 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, BACKGROUND_GRAD_GEN, True, True, 'Sans', '#0000FF',
'#FF0000', None, 0.5, FORM_CIRCLE)
FanChartBaseWidget.__init__(self, dbstate, callback_popup=None)
def set_values(self, root_person_handle, maxgen, background, childring,
radialtext, fontdescr, grad_start, grad_end, radialtext, fontdescr, grad_start, grad_end,
filter, alpha_filter, form): filter, alpha_filter, form):
""" """
Reset all of the data: Reset the values to be used:
root_person_handle = person to show root_person_handle = person to show
maxgen = maximum generations to show maxgen = maximum generations to show
background = config setting of which background procedure to use (int) background = config setting of which background procedure to use (int)
@ -213,8 +456,8 @@ class FanChartWidget(Gtk.DrawingArea):
alpha = the alpha transparency value (0-1) to apply to filtered out data alpha = the alpha transparency value (0-1) to apply to filtered out data
form = the FORM_ constant for the fanchart form = the FORM_ constant for the fanchart
""" """
self.cache_fontcolor = {} self.rootpersonh = root_person_handle
self.generations = maxgen
self.radialtext = radialtext self.radialtext = radialtext
self.childring = childring self.childring = childring
self.background = background self.background = background
@ -225,16 +468,49 @@ class FanChartWidget(Gtk.DrawingArea):
self.alpha_filter = alpha_filter self.alpha_filter = alpha_filter
self.form = form self.form = form
self.set_generations(maxgen) def reset(self):
"""
Reset the fan chart. This triggers computation of all data
structures needed
"""
self.cache_fontcolor = {}
self.set_generations()
# fill the data structure: self.data, self.childrenroot, self.angle # fill the data structure: self.data, self.childrenroot, self.angle
self._fill_data_structures(root_person_handle) self._fill_data_structures()
# prepare the colors for the boxes # prepare the colors for the boxes
self.prepare_background_box() self.prepare_background_box()
def _fill_data_structures(self, root_person_handle): def set_generations(self):
person = self.dbstate.db.get_person_from_handle(root_person_handle) """
Set the generations to max, and fill data structures with initial data.
"""
self.angle = {}
self.data = {}
self.childrenroot = []
for i in range(self.generations):
# name, person, parents?, children?
self.data[i] = [(None,) * 5] * 2 ** i
self.angle[i] = []
factor = 1
angle = 0
if self.form == FORM_HALFCIRCLE:
factor = 1/2
angle = 90
elif self.form == FORM_QUADRANT:
angle = 180
factor = 1/4
slice = 360.0 / (2 ** i) * factor
gender = True
for count in range(len(self.data[i])):
# start, stop, male, state
self.angle[i].append([angle, angle + slice, gender, NORMAL])
angle += slice
gender = not gender
def _fill_data_structures(self):
person = self.dbstate.db.get_person_from_handle(self.rootpersonh)
if not person: if not person:
name = None name = None
else: else:
@ -329,64 +605,6 @@ class FanChartWidget(Gtk.DrawingArea):
if person_handle: if person_handle:
return self.dbstate.db.get_person_from_handle(person_handle) return self.dbstate.db.get_person_from_handle(person_handle)
return None return None
def set_generations(self, generations):
"""
Set the generations to max, and fill data structures with initial data.
"""
self.generations = generations
self.angle = {}
self.data = {}
self.childrenroot = []
for i in range(self.generations):
# name, person, parents?, children?
self.data[i] = [(None,) * 5] * 2 ** i
self.angle[i] = []
factor = 1
angle = 0
if self.form == FORM_HALFCIRCLE:
factor = 1/2
angle = 90
elif self.form == FORM_QUADRANT:
angle = 180
factor = 1/4
slice = 360.0 / (2 ** i) * factor
gender = True
for count in range(len(self.data[i])):
# start, stop, male, state
self.angle[i].append([angle, angle + slice, gender, NORMAL])
angle += slice
gender = not gender
def do_size_request(self, requisition):
"""
Overridden method to handle size request events.
"""
if self.form == FORM_CIRCLE:
requisition.width = 2 * self.halfdist()
requisition.height = requisition.width
elif self.form == FORM_HALFCIRCLE:
requisition.width = 2 * self.halfdist()
requisition.height = requisition.width / 2 + CENTER + PAD_PX
elif self.form == FORM_QUADRANT:
requisition.width = self.halfdist() + CENTER + PAD_PX
requisition.height = requisition.width
def do_get_preferred_width(self):
""" GTK3 uses width for height sizing model. This method will
override the virtual method
"""
req = Gtk.Requisition()
self.do_size_request(req)
return req.width, req.width
def do_get_preferred_height(self):
""" GTK3 uses width for height sizing model. This method will
override the virtual method
"""
req = Gtk.Requisition()
self.do_size_request(req)
return req.height, req.height
def nrgen(self): def nrgen(self):
#compute the number of generations present #compute the number of generations present
@ -410,6 +628,24 @@ class FanChartWidget(Gtk.DrawingArea):
nrgen = self.nrgen() nrgen = self.nrgen()
return PIXELS_PER_GENERATION * nrgen + CENTER + BORDER_EDGE_WIDTH return PIXELS_PER_GENERATION * nrgen + CENTER + BORDER_EDGE_WIDTH
def people_generator(self):
"""
a generator over all people outside of the core person
"""
for generation in range(self.generations):
for p in range(len(self.data[generation])):
(text, person, parents, child, userdata) = self.data[generation][p]
yield (person, userdata)
def innerpeople_generator(self):
"""
a generator over all people inside of the core person
"""
for childdata in self.childrenroot:
child_handle, child_gender, has_child, userdata = childdata
child = self.dbstate.db.get_person_from_handle(child_handle)
yield (child, userdata)
def on_draw(self, widget, cr, scale=1.): def on_draw(self, widget, cr, scale=1.):
""" """
The main method to do the drawing. The main method to do the drawing.
@ -417,8 +653,7 @@ class FanChartWidget(Gtk.DrawingArea):
To draw raw on the cairo context cr, set widget=None. To draw raw on the cairo context cr, set widget=None.
""" """
# first do size request of what we will need # first do size request of what we will need
nrgen = self.nrgen() halfdist = self.halfdist()
halfdist = PIXELS_PER_GENERATION * nrgen + CENTER
if widget: if widget:
if self.form == FORM_CIRCLE: if self.form == FORM_CIRCLE:
self.set_size_request(2 * halfdist, 2 * halfdist) self.set_size_request(2 * halfdist, 2 * halfdist)
@ -460,7 +695,6 @@ class FanChartWidget(Gtk.DrawingArea):
cr.set_source_rgb(1, 1, 1) # white cr.set_source_rgb(1, 1, 1) # white
cr.move_to(0,0) cr.move_to(0,0)
cr.arc(0, 0, CENTER, 0, 2 * math.pi) cr.arc(0, 0, CENTER, 0, 2 * math.pi)
cr.move_to(0,0)
cr.fill() cr.fill()
cr.set_source_rgb(0, 0, 0) # black cr.set_source_rgb(0, 0, 0) # black
cr.arc(0, 0, CENTER, 0, 2 * math.pi) cr.arc(0, 0, CENTER, 0, 2 * math.pi)
@ -469,7 +703,7 @@ class FanChartWidget(Gtk.DrawingArea):
# Draw center person: # Draw center person:
(text, person, parents, child, userdata) = self.data[0][0] (text, person, parents, child, userdata) = self.data[0][0]
if person: if person:
r, g, b, a = self.background_box(person, person.gender, 0, userdata) r, g, b, a = self.background_box(person, 0, userdata)
cr.arc(0, 0, CENTER, 0, 2 * math.pi) cr.arc(0, 0, CENTER, 0, 2 * math.pi)
if self.childring and child: if self.childring and child:
cr.arc_negative(0, 0, TRANSLATE_PX + CHILDRING_WIDTH, 2 * math.pi, 0) cr.arc_negative(0, 0, TRANSLATE_PX + CHILDRING_WIDTH, 2 * math.pi, 0)
@ -504,7 +738,7 @@ class FanChartWidget(Gtk.DrawingArea):
cr.save() cr.save()
start_rad = start * math.pi/180 start_rad = start * math.pi/180
stop_rad = stop * math.pi/180 stop_rad = stop * math.pi/180
r, g, b, a = self.background_box(person, gender, generation, userdata) r, g, b, a = self.background_box(person, generation, userdata)
radius = generation * PIXELS_PER_GENERATION + CENTER radius = generation * PIXELS_PER_GENERATION + CENTER
# If max generation, and they have parents: # If max generation, and they have parents:
if generation == self.generations - 1 and parents: if generation == self.generations - 1 and parents:
@ -606,7 +840,7 @@ class FanChartWidget(Gtk.DrawingArea):
#now again to fill #now again to fill
person = self.dbstate.db.get_person_from_handle(child_handle) person = self.dbstate.db.get_person_from_handle(child_handle)
if person: if person:
r, g, b, a = self.background_box(person, person.gender, -1, userdata) r, g, b, a = self.background_box(person, -1, userdata)
else: else:
r=255; g=255; b=255; a=1 r=255; g=255; b=255; a=1
cr.move_to(rmin*math.cos(thetamin), rmin*math.sin(thetamin)) cr.move_to(rmin*math.cos(thetamin), rmin*math.sin(thetamin))
@ -771,191 +1005,6 @@ class FanChartWidget(Gtk.DrawingArea):
starth = starth+gradheight starth = starth+gradheight
cr.restore() cr.restore()
def prepare_background_box(self):
"""
Method that is called every reset of the chart, to precomputed values
needed for the background of the boxes
"""
maxgen = self.generations
cstart = gui.utils.hex_to_rgb(self.grad_start)
cend = gui.utils.hex_to_rgb(self.grad_end)
self.cstart_hsv = colorsys.rgb_to_hsv(cstart[0]/255, cstart[1]/255,
cstart[2]/255)
self.cend_hsv = colorsys.rgb_to_hsv(cend[0]/255, cend[1]/255,
cend[2]/255)
if self.background in [BACKGROUND_GENDER, BACKGROUND_SINGLE_COLOR]:
# nothing to precompute
self.colors = None
self.maincolor = cstart
elif self.background == BACKGROUND_GRAD_GEN:
#compute the colors, -1, 0, ..., maxgen
divs = [x/(maxgen-1) for x in range(maxgen)]
rgb_colors = [colorsys.hsv_to_rgb(
(1-x) * self.cstart_hsv[0] + x * self.cend_hsv[0],
(1-x) * self.cstart_hsv[1] + x * self.cend_hsv[1],
(1-x) * self.cstart_hsv[2] + x * self.cend_hsv[2],
) for x in divs]
self.colors = [(255*r, 255*g, 255*b) for r, g, b in rgb_colors]
elif self.background == BACKGROUND_GRAD_PERIOD:
# we fill in in the data structure what the period is, None if not found
self.colors = None
self.minperiod = 1e10
self.maxperiod = -1e10
for generation in range(self.generations):
for p in range(len(self.data[generation])):
period = None
(text, person, parents, child, userdata) = self.data[generation][p]
if person:
period = get_timeperiod(self.dbstate.db, person)
if period is not None:
if period > self.maxperiod:
self.maxperiod = period
if period < self.minperiod:
self.minperiod = period
userdata.append(period)
# same for child
for childdata in self.childrenroot:
period = None
child_handle, child_gender, has_child, userdata = childdata
child = self.dbstate.db.get_person_from_handle(child_handle)
period = get_timeperiod(self.dbstate.db, child)
if period is not None:
if period > self.maxperiod:
self.maxperiod = period
if period < self.minperiod:
self.minperiod = period
userdata.append(period)
#now create gradient data, 5 values from min to max rounded to nearest 50
if self.maxperiod < self.minperiod:
self.maxperiod = self.minperiod = gen.lib.date.Today().get_year()
rper = self.maxperiod // 50
if rper * 50 != self.maxperiod:
self.maxperiod = rper * 50 + 50
self.minperiod = 50 * (self.minperiod // 50)
periodrange = self.maxperiod - self.minperiod
steps = 2 * GRADIENTSCALE - 1
divs = [x/(steps-1) for x in range(steps)]
self.gradval = ['%d' % int(self.minperiod + x * periodrange) for x in divs]
for i in range(len(self.gradval)):
if i % 2 == 1:
self.gradval[i] = ''
self.gradcol = [colorsys.hsv_to_rgb(
(1-div) * self.cstart_hsv[0] + div * self.cend_hsv[0],
(1-div) * self.cstart_hsv[1] + div * self.cend_hsv[1],
(1-div) * self.cstart_hsv[2] + div * self.cend_hsv[2],
) for div in divs]
elif self.background == BACKGROUND_GRAD_AGE:
# we fill in in the data structure what the color age is, white if no age
self.colors = None
for generation in range(self.generations):
for p in range(len(self.data[generation])):
agecol = (255, 255, 255) # white
(text, person, parents, child, userdata) = self.data[generation][p]
if person:
age = get_age(self.dbstate.db, person)
if age is not None:
age = age[0]
if age < 0:
age = 0
elif age > MAX_AGE:
age = MAX_AGE
#now determine fraction for gradient
agefrac = age / MAX_AGE
agecol = colorsys.hsv_to_rgb(
(1-agefrac) * self.cstart_hsv[0] + agefrac * self.cend_hsv[0],
(1-agefrac) * self.cstart_hsv[1] + agefrac * self.cend_hsv[1],
(1-agefrac) * self.cstart_hsv[2] + agefrac * self.cend_hsv[2],
)
userdata.append((agecol[0]*255, agecol[1]*255, agecol[2]*255))
# same for child
for childdata in self.childrenroot:
agecol = (255, 255, 255) # white
child_handle, child_gender, has_child, userdata = childdata
child = self.dbstate.db.get_person_from_handle(child_handle)
age = get_age(self.dbstate.db, child)
if age is not None:
age = age[0]
if age < 0:
age = 0
elif age > MAX_AGE:
age = MAX_AGE
#now determine fraction for gradient
agefrac = age / MAX_AGE
agecol = colorsys.hsv_to_rgb(
(1-agefrac) * self.cstart_hsv[0] + agefrac * self.cend_hsv[0],
(1-agefrac) * self.cstart_hsv[1] + agefrac * self.cend_hsv[1],
(1-agefrac) * self.cstart_hsv[2] + agefrac * self.cend_hsv[2],
)
userdata.append((agecol[0]*255, agecol[1]*255, agecol[2]*255))
#now create gradient data, 5 values from 0 to max
steps = 2 * GRADIENTSCALE - 1
divs = [x/(steps-1) for x in range(steps)]
self.gradval = ['%d' % int(x * MAX_AGE) for x in divs]
self.gradval[-1] = '%d+' % MAX_AGE
for i in range(len(self.gradval)):
if i % 2 == 1:
self.gradval[i] = ''
self.gradcol = [colorsys.hsv_to_rgb(
(1-div) * self.cstart_hsv[0] + div * self.cend_hsv[0],
(1-div) * self.cstart_hsv[1] + div * self.cend_hsv[1],
(1-div) * self.cstart_hsv[2] + div * self.cend_hsv[2],
) for div in divs]
else:
# known colors per generation, set or compute them
self.colors = GENCOLOR[self.background]
def background_box(self, person, gender, generation, userdata):
"""
determine red, green, blue value of background of the box of person,
which has gender gender, and is in ring generation
"""
if generation == 0 and self.background in [BACKGROUND_GENDER,
BACKGROUND_GRAD_GEN, BACKGROUND_SCHEME1,
BACKGROUND_SCHEME2]:
# white for center person:
color = (255, 255, 255)
elif self.background == BACKGROUND_GENDER:
try:
alive = probably_alive(person, self.dbstate.db)
except RuntimeError:
alive = False
backgr, border = gui.utils.color_graph_box(alive, person.gender)
color = gui.utils.hex_to_rgb(backgr)
elif self.background == BACKGROUND_SINGLE_COLOR:
color = self.maincolor
elif self.background == BACKGROUND_GRAD_AGE:
color = userdata[0]
elif self.background == BACKGROUND_GRAD_PERIOD:
period = userdata[0]
if period is None:
color = (255, 255, 255) # white
else:
periodfrac = ((period - self.minperiod)
/ (self.maxperiod - self.minperiod))
periodcol = colorsys.hsv_to_rgb(
(1-periodfrac) * self.cstart_hsv[0] + periodfrac * self.cend_hsv[0],
(1-periodfrac) * self.cstart_hsv[1] + periodfrac * self.cend_hsv[1],
(1-periodfrac) * self.cstart_hsv[2] + periodfrac * self.cend_hsv[2],
)
color = (periodcol[0]*255, periodcol[1]*255, periodcol[2]*255)
else:
if self.background == BACKGROUND_GRAD_GEN and generation < 0:
generation = 0
color = self.colors[generation % len(self.colors)]
if gender == gen.lib.Person.MALE:
color = [x*.9 for x in color]
# now we set transparency data
if self.filter and not self.filter.match(person.handle, self.dbstate.db):
if self.background == BACKGROUND_SINGLE_COLOR:
alpha = 0. # no color shown
else:
alpha = self.alpha_filter
else:
alpha = 1.
return color[0], color[1], color[2], alpha
def fontcolor(self, r, g, b): def fontcolor(self, r, g, b):
""" """
return the font color based on the r, g, b of the background return the font color based on the r, g, b of the background
@ -1294,13 +1343,11 @@ class FanChartWidget(Gtk.DrawingArea):
if sel_data and sel_data.get_data(): if sel_data and sel_data.get_data():
(drag_type, idval, handle, val) = pickle.loads(sel_data.get_data()) (drag_type, idval, handle, val) = pickle.loads(sel_data.get_data())
self.goto(self, handle) self.goto(self, handle)
class FanChartGrampsGUI(object): class FanChartGrampsGUI(object):
""" class for functions fanchart GUI elements will need in Gramps """ class for functions fanchart GUI elements will need in Gramps
""" """
def __init__(self, maxgen, background, childring, radialtext, font, def __init__(self, on_childmenu_changed):
on_childmenu_changed):
""" """
Common part of GUI that shows Fan Chart, needs to know what to do if Common part of GUI that shows Fan Chart, needs to know what to do if
one moves via Fan Ch def set_fan(self, fan):art to a new person one moves via Fan Ch def set_fan(self, fan):art to a new person
@ -1309,17 +1356,6 @@ class FanChartGrampsGUI(object):
self.fan = None self.fan = None
self.on_childmenu_changed = on_childmenu_changed self.on_childmenu_changed = on_childmenu_changed
self.format_helper = FormattingHelper(self.dbstate) self.format_helper = FormattingHelper(self.dbstate)
self.maxgen = maxgen
self.background = background
self.childring = childring
self.radialtext = radialtext
self.fonttype = font
self.grad_start = '#0000FF'
self.grad_end = '#FF0000'
self.generic_filter = None # the filter to use. Named as in PageView
self.alpha_filter = 0.2 # transparency of filtered out values
self.form = FORM_HALFCIRCLE
def set_fan(self, fan): def set_fan(self, fan):
""" """
@ -1335,10 +1371,11 @@ class FanChartGrampsGUI(object):
data. data.
""" """
root_person_handle = self.get_active('Person') root_person_handle = self.get_active('Person')
self.fan.reset(root_person_handle, self.maxgen, self.background, self.childring, self.fan.set_values(root_person_handle, self.maxgen, self.background,
self.radialtext, self.fonttype, self.childring, self.radialtext, self.fonttype,
self.grad_start, self.grad_end, self.grad_start, self.grad_end,
self.generic_filter, self.alpha_filter, self.form) self.generic_filter, self.alpha_filter, self.form)
self.fan.reset()
self.fan.queue_draw() self.fan.queue_draw()
def on_popup(self, obj, event, person_handle): def on_popup(self, obj, event, person_handle):

View File

@ -54,7 +54,8 @@ import gen.lib
from gen.errors import WindowActiveError from gen.errors import WindowActiveError
from gui.editors import EditPerson from gui.editors import EditPerson
import gui.utils import gui.utils
from gui.widgets.fanchart import FanChartWidget, FanChartGrampsGUI from gui.widgets.fanchart import (FanChartWidget, FanChartGrampsGUI,
FORM_HALFCIRCLE, BACKGROUND_SCHEME1)
class FanChartGramplet(FanChartGrampsGUI, Gramplet): class FanChartGramplet(FanChartGrampsGUI, Gramplet):
""" """
@ -63,8 +64,17 @@ class FanChartGramplet(FanChartGrampsGUI, Gramplet):
def __init__(self, gui, nav_group=0): def __init__(self, gui, nav_group=0):
Gramplet.__init__(self, gui, nav_group) Gramplet.__init__(self, gui, nav_group)
FanChartGrampsGUI.__init__(self, 6, 0, True, True, 'Sans', FanChartGrampsGUI.__init__(self, self.on_childmenu_changed)
self.on_childmenu_changed) self.maxgen = 6
self.background = BACKGROUND_SCHEME1
self.childring = True
self.radialtext = True
self.fonttype = 'Sans'
self.grad_start = '#0000FF'
self.grad_end = '#FF0000'
self.generic_filter = None
self.alpha_filter = 0.2
self.form = FORM_HALFCIRCLE
self.set_fan(FanChartWidget(self.dbstate, self.on_popup)) self.set_fan(FanChartWidget(self.dbstate, self.on_popup))
# Replace the standard textview with the fan chart widget: # Replace the standard textview with the fan chart widget:
self.gui.get_container_widget().remove(self.gui.textview) self.gui.get_container_widget().remove(self.gui.textview)

View File

@ -75,17 +75,19 @@ class FanChartView(fanchart.FanChartGrampsGUI, NavigationView):
dbstate.db.get_bookmarks(), dbstate.db.get_bookmarks(),
PersonBookmarks, PersonBookmarks,
nav_group) nav_group)
fanchart.FanChartGrampsGUI.__init__(self, fanchart.FanChartGrampsGUI.__init__(self, self.on_childmenu_changed)
self._config.get('interface.fanview-maxgen'), #set needed values
self._config.get('interface.fanview-background'), self.maxgen = self._config.get('interface.fanview-maxgen')
self._config.get('interface.fanview-childrenring'), self.background = self._config.get('interface.fanview-background')
self._config.get('interface.fanview-radialtext'), self.childring = self._config.get('interface.fanview-childrenring')
self._config.get('interface.fanview-font'), self.radialtext = self._config.get('interface.fanview-radialtext')
self.on_childmenu_changed) self.fonttype = self._config.get('interface.fanview-font')
self.grad_start = self._config.get('interface.color-start-grad') self.grad_start = self._config.get('interface.color-start-grad')
self.grad_end = self._config.get('interface.color-end-grad') self.grad_end = self._config.get('interface.color-end-grad')
self.form = self._config.get('interface.fanview-form') self.form = self._config.get('interface.fanview-form')
self.generic_filter = None
self.alpha_filter = 0.2
dbstate.connect('active-changed', self.active_changed) dbstate.connect('active-changed', self.active_changed)
dbstate.connect('database-changed', self.change_db) dbstate.connect('database-changed', self.change_db)
@ -227,8 +229,7 @@ class FanChartView(fanchart.FanChartGrampsGUI, NavigationView):
""" """
Print or save the view that is currently shown Print or save the view that is currently shown
""" """
widthpx = 2*(fanchart.PIXELS_PER_GENERATION * self.fan.nrgen() widthpx = 2 * self.fan.halfdist()
+ fanchart.CENTER)
heightpx = widthpx heightpx = widthpx
if self.form == fanchart.FORM_HALFCIRCLE: if self.form == fanchart.FORM_HALFCIRCLE:
heightpx = heightpx / 2 + fanchart.CENTER + fanchart.PAD_PX heightpx = heightpx / 2 + fanchart.CENTER + fanchart.PAD_PX