From 1ce04c48743badce734ef3a9cdea6f76b9b7aa37 Mon Sep 17 00:00:00 2001 From: Benny Malengier Date: Sat, 8 Sep 2012 12:17:20 +0000 Subject: [PATCH] Fanchart, last feature I wanted: time period coloring of the boxes svn: r20353 --- src/gen/utils/db.py | 31 +++++++ src/gui/widgets/fanchart.py | 150 ++++++++++++++++++++++--------- src/plugins/view/fanchartview.py | 26 ++++-- 3 files changed, 154 insertions(+), 53 deletions(-) diff --git a/src/gen/utils/db.py b/src/gen/utils/db.py index 6c760c99f..b0adc5b9a 100644 --- a/src/gen/utils/db.py +++ b/src/gen/utils/db.py @@ -128,6 +128,37 @@ def get_age(db, person, fallback=True, calendar="gregorian"): age = age.tuple() return age +def get_timeperiod(db, person): + """ + Compute the timeperiod a person lived in + person : person handle or person object + Return: the year, None otherwise + """ + if isinstance(person, str): + # a handle is passed + person = db.get_person_from_handle(person) + # the period is the year of birth + birth = get_birth_or_fallback(db, person) + if birth is not None: + birth_date = birth.get_date_object().to_calendar("gregorian") + if (birth_date and birth_date.get_valid()): + return birth_date.get_year() + death = get_death_or_fallback(db, person) + # no birth, period is death - 20 + if death is not None: + death_date = death.get_date_object().to_calendar("gregorian") + if (death_date and death_date.get_valid()): + return death_date.get_year() - 20 + # no birth and death, look for another event date we can use + for event_ref in person.get_primary_event_ref_list(): + if event_ref: + event = db.get_event_from_handle(event_ref.ref) + if event: + event_date = event.get_date_object().to_calendar("gregorian") + if (event_date and event_date.get_valid()): + return event_date.get_year() + return None + def get_event_ref(db, family, event_type): """ Return a reference to a primary family event of the given event type. diff --git a/src/gui/widgets/fanchart.py b/src/gui/widgets/fanchart.py index 6dc291264..b52330ae9 100644 --- a/src/gui/widgets/fanchart.py +++ b/src/gui/widgets/fanchart.py @@ -62,7 +62,7 @@ from gui.ddtargets import DdTargets from gen.utils.alive import probably_alive from gen.utils.libformatting import FormattingHelper from gen.utils.db import (find_children, find_parents, find_witnessed_people, - get_age) + get_age, get_timeperiod) #------------------------------------------------------------------------- # @@ -98,6 +98,7 @@ BACKGROUND_WHITE = 3 BACKGROUND_GRAD_GEN = 4 BACKGROUND_GRAD_AGE = 5 BACKGROUND_SINGLE_COLOR = 6 +BACKGROUND_GRAD_PERIOD = 7 GENCOLOR = { BACKGROUND_SCHEME1: ((255, 63, 0), (255,175, 15), @@ -418,31 +419,30 @@ class FanChartWidget(Gtk.DrawingArea): # first do size request of what we will need nrgen = self.nrgen() halfdist = PIXELS_PER_GENERATION * nrgen + CENTER - 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 + CENTER + PAD_PX) - elif self.form == FORM_QUADRANT: - self.set_size_request(halfdist + CENTER + PAD_PX, halfdist + CENTER + PAD_PX) - - #obtain the allocation - alloc = self.get_allocation() - x, y, w, h = alloc.x, alloc.y, alloc.width, alloc.height - if widget is None: # printing, use the size we need - w = 2 * halfdist - h = 2 * 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 + CENTER + PAD_PX) + elif self.form == FORM_QUADRANT: + self.set_size_request(halfdist + CENTER + PAD_PX, halfdist + CENTER + PAD_PX) + + #obtain the allocation + alloc = self.get_allocation() + x, y, w, h = alloc.x, alloc.y, alloc.width, alloc.height cr.scale(scale, scale) - - if self.form == FORM_CIRCLE: - self.center_x = w/2 - self.center_xy[0] - self.center_y = h/2 - self.center_xy[1] - elif self.form == FORM_HALFCIRCLE: - self.center_x = w/2. - self.center_xy[0] - self.center_y = h - CENTER - PAD_PX- self.center_xy[1] - elif self.form == FORM_QUADRANT: - self.center_x = CENTER + PAD_PX - self.center_xy[0] - self.center_y = h - CENTER - PAD_PX - self.center_xy[1] + # when printing, we need not recalculate + if widget: + if self.form == FORM_CIRCLE: + self.center_x = w/2 - self.center_xy[0] + self.center_y = h/2 - self.center_xy[1] + elif self.form == FORM_HALFCIRCLE: + self.center_x = w/2. - self.center_xy[0] + self.center_y = h - CENTER - PAD_PX- self.center_xy[1] + elif self.form == FORM_QUADRANT: + self.center_x = CENTER + PAD_PX - self.center_xy[0] + self.center_y = h - CENTER - PAD_PX - self.center_xy[1] cr.translate(self.center_x, self.center_y) cr.save() @@ -488,7 +488,7 @@ class FanChartWidget(Gtk.DrawingArea): cr.stroke() if child and self.childring: self.drawchildring(cr) - if self.background in [BACKGROUND_GRAD_AGE]: + if self.background in [BACKGROUND_GRAD_AGE, BACKGROUND_GRAD_PERIOD]: self.draw_gradient(cr, widget, halfdist) def draw_person(self, cr, gender, name, start, stop, generation, @@ -746,10 +746,9 @@ class FanChartWidget(Gtk.DrawingArea): alloc = self.get_allocation() x, y, w, h = alloc.x, alloc.y, alloc.width, alloc.height cr.save() - if widget: - cr.translate(-w/2. + self.center_xy[0], -h/2. + self.center_xy[1]) - else: - cr.translate(-halfdist + self.center_xy[0], -halfdist + self.center_xy[1]) + + cr.translate(-self.center_x, -self.center_y) + font = Pango.FontDescription(self.fontdescr) fontsize = self.fontsize font.set_size(fontsize * Pango.SCALE) @@ -774,9 +773,9 @@ class FanChartWidget(Gtk.DrawingArea): maxgen = self.generations cstart = gui.utils.hex_to_rgb(self.grad_start) cend = gui.utils.hex_to_rgb(self.grad_end) - cstart_hsv = colorsys.rgb_to_hsv(cstart[0]/255, cstart[1]/255, + self.cstart_hsv = colorsys.rgb_to_hsv(cstart[0]/255, cstart[1]/255, cstart[2]/255) - cend_hsv = colorsys.rgb_to_hsv(cend[0]/255, cend[1]/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 @@ -786,13 +785,63 @@ class FanChartWidget(Gtk.DrawingArea): #compute the colors, -1, 0, ..., maxgen divs = [x/(maxgen-1) for x in range(maxgen)] rgb_colors = [colorsys.hsv_to_rgb( - (1-x) * cstart_hsv[0] + x * cend_hsv[0], - (1-x) * cstart_hsv[1] + x * cend_hsv[1], - (1-x) * cstart_hsv[2] + x * cend_hsv[2], + (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 age is, None if no 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 @@ -808,9 +857,9 @@ class FanChartWidget(Gtk.DrawingArea): #now determine fraction for gradient agefrac = age / MAX_AGE agecol = colorsys.hsv_to_rgb( - (1-agefrac) * cstart_hsv[0] + agefrac * cend_hsv[0], - (1-agefrac) * cstart_hsv[1] + agefrac * cend_hsv[1], - (1-agefrac) * cstart_hsv[2] + agefrac * cend_hsv[2], + (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 @@ -828,9 +877,9 @@ class FanChartWidget(Gtk.DrawingArea): #now determine fraction for gradient agefrac = age / MAX_AGE agecol = colorsys.hsv_to_rgb( - (1-agefrac) * cstart_hsv[0] + agefrac * cend_hsv[0], - (1-agefrac) * cstart_hsv[1] + agefrac * cend_hsv[1], - (1-agefrac) * cstart_hsv[2] + agefrac * cend_hsv[2], + (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 @@ -842,9 +891,9 @@ class FanChartWidget(Gtk.DrawingArea): if i % 2 == 1: self.gradval[i] = '' self.gradcol = [colorsys.hsv_to_rgb( - (1-div) * cstart_hsv[0] + div * cend_hsv[0], - (1-div) * cstart_hsv[1] + div * cend_hsv[1], - (1-div) * cstart_hsv[2] + div * cend_hsv[2], + (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 @@ -871,6 +920,19 @@ class FanChartWidget(Gtk.DrawingArea): 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 diff --git a/src/plugins/view/fanchartview.py b/src/plugins/view/fanchartview.py index 91eb616d6..e50b4d78f 100644 --- a/src/plugins/view/fanchartview.py +++ b/src/plugins/view/fanchartview.py @@ -180,7 +180,6 @@ class FanChartView(fanchart.FanChartGrampsGUI, NavigationView): Method called when active person changes. """ # Reset everything but rotation angle (leave it as is) - print 'active changed' self.update() def _connect_db_signals(self): @@ -229,8 +228,15 @@ class FanChartView(fanchart.FanChartGrampsGUI, NavigationView): Print or save the view that is currently shown """ widthpx = 2*(fanchart.PIXELS_PER_GENERATION * self.fan.nrgen() - + self.fan.center) - prt = CairoPrintSave(widthpx, self.fan.on_draw, self.uistate.window) + + fanchart.CENTER) + heightpx = widthpx + if self.form == fanchart.FORM_HALFCIRCLE: + heightpx = heightpx / 2 + fanchart.CENTER + fanchart.PAD_PX + elif self.form == fanchart.FORM_QUADRANT: + heightpx = heightpx / 2 + fanchart.CENTER + fanchart.PAD_PX + widthpx = heightpx + + prt = CairoPrintSave(widthpx, heightpx, self.fan.on_draw, self.uistate.window) prt.run() def on_childmenu_changed(self, obj, person_handle): @@ -278,6 +284,7 @@ class FanChartView(fanchart.FanChartGrampsGUI, NavigationView): (fanchart.BACKGROUND_GRAD_AGE, _('Age (0-100) based gradient')), (fanchart.BACKGROUND_SINGLE_COLOR, _('Single main (filter) color')), + (fanchart.BACKGROUND_GRAD_PERIOD, _('Time period based gradient')), (fanchart.BACKGROUND_WHITE, _('White')), (fanchart.BACKGROUND_SCHEME1, _('Color scheme classic report')), (fanchart.BACKGROUND_SCHEME2, _('Color scheme classic view')), @@ -288,7 +295,6 @@ class FanChartView(fanchart.FanChartGrampsGUI, NavigationView): if curval == nr: break nrval += 1 - print nrval configdialog.add_combo(table, _('Background'), 2, 'interface.fanview-background', @@ -407,12 +413,13 @@ class CairoPrintSave(): """ - def __init__(self, widthpx, drawfunc, parent): + def __init__(self, widthpx, heightpx, drawfunc, parent): """ This class provides the things needed so as to dump a cairo drawing on a context to output """ self.widthpx = widthpx + self.heightpx = heightpx self.drawfunc = drawfunc self.parent = parent @@ -434,7 +441,7 @@ class CairoPrintSave(): paper_size = Gtk.PaperSize.new_custom("custom", "Custom Size", round(self.widthpx * 0.2646), - round(self.widthpx * 0.2646), + round(self.heightpx * 0.2646), Gtk.Unit.MM) page_setup = Gtk.PageSetup() page_setup.set_paper_size(paper_size) @@ -471,9 +478,10 @@ class CairoPrintSave(): cr = context.get_cairo_context() pxwidth = round(context.get_width()) pxheight = round(context.get_height()) - dpi_x = context.get_dpi_x() - dpi_y = context.get_dpi_y() - self.drawfunc(None, cr, scale=pxwidth/self.widthpx) + scale = min(pxwidth/self.widthpx, pxheight/self.heightpx) + if scale > 1: + scale = 1 + self.drawfunc(None, cr, scale=scale) def on_paginate(self, operation, context): """Paginate the whole document in chunks.