Work in progress
svn: r11661
This commit is contained in:
parent
3feb8c375c
commit
cd713adf74
@ -59,6 +59,11 @@ class FanChartWidget(gtk.Widget):
|
||||
'size-allocate': 'override',
|
||||
'size-request': 'override',
|
||||
}
|
||||
GENCOLOR = ((229,191,252),
|
||||
(191,191,252),
|
||||
(191,222,252),
|
||||
(183,219,197),
|
||||
(206,246,209))
|
||||
|
||||
def __init__(self, generations):
|
||||
"""
|
||||
@ -70,19 +75,30 @@ class FanChartWidget(gtk.Widget):
|
||||
## gotten from experiments with "sans serif 8":
|
||||
self.degrees_per_radius = .80
|
||||
self.generations = generations
|
||||
self.rotate_value = 0
|
||||
self.data = {}
|
||||
for i in range(self.generations):
|
||||
self.data[i] = [None for j in range(2 ** i)]
|
||||
self.rotate_value = 90
|
||||
self.set_generations(self.generations)
|
||||
self.center = 50
|
||||
self.layout = self.create_pango_layout('cairo')
|
||||
self.layout.set_font_description(pango.FontDescription("sans serif 8"))
|
||||
|
||||
def reset_generations(self):
|
||||
self.set_generations(self.generations)
|
||||
|
||||
def set_generations(self, generations):
|
||||
self.generations = generations
|
||||
self.angle = {}
|
||||
self.data = {}
|
||||
for i in range(self.generations):
|
||||
self.data[i] = [None for j in range(2 ** i)]
|
||||
self.angle[i] = []
|
||||
angle = 0
|
||||
slice = 360.0 / (2 ** i)
|
||||
gender = True
|
||||
for a in range(len(self.data[i])):
|
||||
# start, stop, male, state
|
||||
self.angle[i].append([angle, angle + slice, gender, 1])
|
||||
angle += slice
|
||||
gender = not gender
|
||||
|
||||
def do_realize(self):
|
||||
self.set_flags(self.flags() | gtk.REALIZED)
|
||||
@ -132,16 +148,17 @@ class FanChartWidget(gtk.Widget):
|
||||
def _expose_cairo(self, event, cr):
|
||||
x, y, w, h = self.allocation
|
||||
cr.translate(w/2., h/2.)
|
||||
cr.save()
|
||||
cr.rotate(self.rotate_value * math.pi/180)
|
||||
for generation in range(self.generations - 1, 0, -1):
|
||||
slice = 360.0 / len(self.data[generation])
|
||||
current = 0
|
||||
for person in self.data[generation]:
|
||||
for p in range(len(self.data[generation])):
|
||||
person = self.data[generation][p]
|
||||
if person:
|
||||
start, stop, male, state = self.angle[generation][p]
|
||||
name = name_displayer.display(person)
|
||||
gender = person.get_gender()
|
||||
self.draw_person(cr, gender, name, current, generation)
|
||||
current += slice
|
||||
if state > 0:
|
||||
self.draw_person(cr, gender, name, start, stop, generation, state)
|
||||
cr.set_source_rgb(1, 1, 1) # white
|
||||
cr.move_to(0,0)
|
||||
cr.arc(0, 0, self.center, 0, 2 * math.pi)
|
||||
@ -151,48 +168,72 @@ class FanChartWidget(gtk.Widget):
|
||||
cr.arc(0, 0, self.center, 0, 2 * math.pi)
|
||||
cr.stroke()
|
||||
person = self.data[0][0]
|
||||
# Draw center person:
|
||||
cr.restore()
|
||||
if person:
|
||||
cr.save()
|
||||
name = name_displayer.display(person)
|
||||
layout = self.create_pango_layout(name)
|
||||
layout.set_font_description(pango.FontDescription("sans serif 8"))
|
||||
cr.move_to(-self.center + 10, -4)
|
||||
cr.show_layout(layout)
|
||||
self.draw_text(cr, name, self.center - 10, 95, 455)
|
||||
#layout = self.create_pango_layout(name)
|
||||
#layout.set_font_description(pango.FontDescription("sans serif 8"))
|
||||
#cr.move_to(-self.center + 10, -4) # start place for center text
|
||||
#cr.show_layout(layout)
|
||||
cr.restore()
|
||||
fontw, fonth = self.layout.get_pixel_size()
|
||||
cr.move_to((w - fontw - 4), (h - fonth ))
|
||||
cr.update_layout(self.layout)
|
||||
cr.show_layout(self.layout)
|
||||
|
||||
def draw_person(self, cr, gender, name, start, generation):
|
||||
def draw_person(self, cr, gender, name, start, stop, generation, state):
|
||||
"""
|
||||
Display the piece of pie for a given person. start and stop
|
||||
are in degrees.
|
||||
"""
|
||||
x, y, w, h = self.allocation
|
||||
start_rad = start * math.pi/180
|
||||
if gender == 0:
|
||||
cr.set_source_rgb(1, 0.69, 0.69) # pink
|
||||
elif gender == 1:
|
||||
cr.set_source_rgb(0, 1, 1) # cyan
|
||||
stop_rad = stop * math.pi/180
|
||||
r,g,b = self.GENCOLOR[generation % len(self.GENCOLOR)]
|
||||
if gender == 1:
|
||||
r -= r * .10
|
||||
g -= g * .10
|
||||
b -= b * .10
|
||||
cr.set_source_rgb(r/255., g/255., b/255.)
|
||||
radius = generation * self.pixels_per_generation + self.center
|
||||
cr.move_to(0, 0)
|
||||
cr.arc(0, 0, radius, start_rad, start_rad + (2 * math.pi) / 2 ** generation)
|
||||
cr.arc(0, 0, radius, start_rad, stop_rad)
|
||||
cr.move_to(0, 0)
|
||||
cr.fill()
|
||||
cr.set_source_rgb(0, 0, 0) # black
|
||||
cr.arc(0, 0, radius, start_rad, start_rad)
|
||||
cr.arc(0, 0, radius, start_rad, stop_rad)
|
||||
cr.line_to(0, 0)
|
||||
cr.arc(0, 0, radius, start_rad, start_rad + (2 * math.pi) / 2 ** generation)
|
||||
cr.arc(0, 0, radius, start_rad, stop_rad)
|
||||
cr.line_to(0, 0)
|
||||
cr.set_line_width(1)
|
||||
cr.set_line_width(state)
|
||||
cr.stroke()
|
||||
degrees = 6 # self.text_degrees(name, radius)/2
|
||||
#FIXME: if degrees bigger than arc available, trim or wrap?
|
||||
self.draw_text(cr, name,
|
||||
radius - self.pixels_per_generation/2,
|
||||
start + 90 + degrees)
|
||||
self.draw_text(cr, name, radius - self.pixels_per_generation/2,
|
||||
start, stop)
|
||||
|
||||
def text_degrees(self, text, radius):
|
||||
"""
|
||||
Returns the number of degrees of text at a given radius.
|
||||
"""
|
||||
return 360.0 * len(text)/(radius * self.degrees_per_radius)
|
||||
|
||||
def draw_text(self, cr, text, radius, pos):
|
||||
def text_limit(self, text, degrees, radius):
|
||||
while self.text_degrees(text, radius) > degrees:
|
||||
text = text[:-1]
|
||||
return text
|
||||
|
||||
def draw_text(self, cr, text, radius, start, stop):
|
||||
"""
|
||||
Display text at a particular radius, between start and stop
|
||||
degrees.
|
||||
"""
|
||||
# trim to fit:
|
||||
text = self.text_limit(text, stop - start, radius - 15)
|
||||
# center text:
|
||||
pos = start + ((stop - start) - self.text_degrees(text, radius))/2.0 + 90
|
||||
# offset for cairo-font system is 90
|
||||
x, y, w, h = self.allocation
|
||||
cr.save()
|
||||
# Create a PangoLayout, set the font and text
|
||||
@ -202,7 +243,7 @@ class FanChartWidget(gtk.Widget):
|
||||
layout = self.create_pango_layout(text[i])
|
||||
layout.set_font_description(pango.FontDescription("sans serif 8"))
|
||||
angle = 360.0 * i / (radius * self.degrees_per_radius) + pos
|
||||
cr.set_source_rgb(0, 0, 1) # blue clickable
|
||||
cr.set_source_rgb(0, 0, 0) # black
|
||||
cr.rotate(angle * (math.pi / 180));
|
||||
# Inform Pango to re-layout the text with the new transformation
|
||||
cr.update_layout(layout)
|
||||
@ -217,58 +258,124 @@ class FanChartGramplet(Gramplet):
|
||||
self.generations = 6
|
||||
self.gui.fan = FanChartWidget(self.generations)
|
||||
self.gui.get_container_widget().remove(self.gui.textview)
|
||||
vbox = gtk.VBox()
|
||||
self.scale = gtk.HScale()
|
||||
self.scale.set_draw_value(0)
|
||||
self.scale.set_value_pos(gtk.POS_LEFT)
|
||||
self.scale.set_range(0, 360)
|
||||
self.scale.connect("value-changed", self.scale_changed)
|
||||
vbox.pack_start(self.scale, False, False)
|
||||
vbox.pack_start(self.gui.fan)
|
||||
self.gui.get_container_widget().add_with_viewport(vbox)
|
||||
self.gui.get_container_widget().add_with_viewport(self.gui.fan)
|
||||
#container is a gtk.ScrolledWindow
|
||||
container = self.gui.get_container_widget()
|
||||
container.connect("button-press-event", self.on_mouse_down)
|
||||
vbox.show()
|
||||
self.gui.fan.show()
|
||||
self.scale.show()
|
||||
|
||||
def scale_changed(self, widget):
|
||||
self.gui.fan.rotate_value = widget.get_value()
|
||||
self.gui.fan.queue_draw()
|
||||
def expand_parents(self, generation, selected, to_right):
|
||||
print "expanding", generation, selected
|
||||
if generation >= self.generations: return
|
||||
selected = 2 * selected
|
||||
start,stop,male,state = self.gui.fan.angle[generation][selected]
|
||||
slice = (stop - start) * 2.0
|
||||
current = start
|
||||
if state > 0:
|
||||
if to_right:
|
||||
current = start+slice
|
||||
self.gui.fan.angle[generation][selected] = start,current,male,state
|
||||
else:
|
||||
current = stop - slice
|
||||
self.gui.fan.angle[generation][selected] = current,stop,male,state
|
||||
self.expand_parents(generation + 1, selected, to_right)
|
||||
start,stop,male,state = self.gui.fan.angle[generation][selected+1]
|
||||
if state > 0:
|
||||
self.gui.fan.angle[generation][selected+1] = current,current+slice,male,state
|
||||
self.expand_parents(generation + 1, selected+1, to_right)
|
||||
|
||||
def shrink_parents(self, generation, selected, from_right):
|
||||
pass
|
||||
|
||||
def change_slice(self, generation, selected):
|
||||
gstart, gstop, gmale, gstate = self.gui.fan.angle[generation][selected]
|
||||
if gstate == 1: # normal, let's expand
|
||||
if gmale:
|
||||
# go to right
|
||||
stop = gstop + (gstop - gstart)
|
||||
self.gui.fan.angle[generation][selected] = [gstart,stop,gmale,2]
|
||||
start,stop,male,state = self.gui.fan.angle[generation][selected+1]
|
||||
self.gui.fan.angle[generation][selected+1] = [stop,stop,male,0]
|
||||
else:
|
||||
# go to left
|
||||
start = gstart - (gstop - gstart)
|
||||
self.gui.fan.angle[generation][selected] = [start,gstop,gmale,2]
|
||||
start,stop,male,state = self.gui.fan.angle[generation][selected-1]
|
||||
self.gui.fan.angle[generation][selected-1] = [start,start,male,0]
|
||||
# now we need to expand each of the slices outward:
|
||||
#self.expand_parents(generation + 1, selected, gmale)
|
||||
elif gstate == 2: # expanded, let's shrink
|
||||
if gmale:
|
||||
# shrink from right
|
||||
slice = (gstop - gstart)/2.0
|
||||
stop = gstop - slice
|
||||
self.gui.fan.angle[generation][selected] = [gstart,stop,gmale,1]
|
||||
start,stop,male,state = self.gui.fan.angle[generation][selected+1]
|
||||
self.gui.fan.angle[generation][selected+1] = [stop-slice,stop,male,1]
|
||||
else:
|
||||
# shrink from left
|
||||
slice = (gstop - gstart)/2.0
|
||||
start = gstop - slice
|
||||
self.gui.fan.angle[generation][selected] = [start,gstop,gmale,1]
|
||||
start,stop,male,state = self.gui.fan.angle[generation][selected-1]
|
||||
self.gui.fan.angle[generation][selected-1] = [start,start+slice,male,1]
|
||||
# now we need to shrink each of the slices outward:
|
||||
#self.shrink_parents(self, generation + 1, selected, gmale)
|
||||
|
||||
def on_mouse_down(self, widget, e):
|
||||
# compute angle, radius, find out who would be there (rotated)
|
||||
x, y, w, h = self.gui.fan.allocation
|
||||
cx = w/2
|
||||
cy = h/2
|
||||
radius = math.sqrt((e.x - cx) ** 2 + (e.y - cy - 20) ** 2)
|
||||
radius = math.sqrt((e.x - cx) ** 2 + (e.y - cy) ** 2)
|
||||
if radius < self.gui.fan.center:
|
||||
generation = 0
|
||||
else:
|
||||
generation = int((radius - self.gui.fan.center) /
|
||||
self.gui.fan.pixels_per_generation) + 1
|
||||
rads = math.atan2( (e.y - cy), (e.x - cx - 20) )
|
||||
rads = math.atan2( (e.y - cy), (e.x - cx) )
|
||||
if rads < 0:
|
||||
rads = math.pi + (math.pi + rads)
|
||||
pos = (rads/(math.pi * 2) - self.gui.fan.rotate_value/360.)
|
||||
pos = int(math.floor(pos * 2 ** generation))
|
||||
pos = pos % (2 ** generation)
|
||||
if (0 < generation < self.generations and
|
||||
0 <= pos < len(self.gui.fan.data[generation])):
|
||||
person = self.gui.fan.data[generation][pos]
|
||||
if person:
|
||||
#name = name_displayer.display(person)
|
||||
self.dbstate.change_active_person(person)
|
||||
pos = ((rads/(math.pi * 2) - self.gui.fan.rotate_value/360.) * 360.0) % 360
|
||||
# find what person is in this position:
|
||||
selected = None
|
||||
if (0 < generation < self.generations):
|
||||
for p in range(len(self.gui.fan.angle[generation])):
|
||||
start, stop, male, state = self.gui.fan.angle[generation][p]
|
||||
if state == 0: continue
|
||||
if start <= pos <= stop:
|
||||
selected = p
|
||||
break
|
||||
print "---------------"
|
||||
print " angle:", pos
|
||||
print " generation:", generation
|
||||
print " selected:", selected
|
||||
if selected == None:
|
||||
if radius < self.gui.fan.center:
|
||||
pass # TODO: select children
|
||||
elif e.x > cx: # on right, rotate clockwise
|
||||
self.gui.fan.rotate_value += 90.0
|
||||
else: # on left, rotate counterclockwise
|
||||
self.gui.fan.rotate_value -= 90.0
|
||||
self.gui.fan.rotate_value %= 360
|
||||
self.gui.fan.queue_draw()
|
||||
return True
|
||||
self.change_slice(generation, selected)
|
||||
person = self.gui.fan.data[generation][selected]
|
||||
if person:
|
||||
name = name_displayer.display(person)
|
||||
print name, start, stop
|
||||
#self.dbstate.change_active_person(person)
|
||||
self.gui.fan.queue_draw()
|
||||
return True
|
||||
|
||||
def db_changed(self):
|
||||
self.sa = SimpleAccess(self.dbstate.db)
|
||||
|
||||
def active_changed(self, handle):
|
||||
self.gui.fan.rotate_value = 0
|
||||
self.scale.set_value(0)
|
||||
self.scale.set_value_pos(gtk.POS_LEFT)
|
||||
# Reset everything
|
||||
self.gui.fan.rotate_value = 90
|
||||
self.gui.fan.reset_generations()
|
||||
self.update()
|
||||
|
||||
def main(self):
|
||||
@ -277,8 +384,12 @@ class FanChartGramplet(Gramplet):
|
||||
parent = 0
|
||||
for p in self.gui.fan.data[current - 1]:
|
||||
self.gui.fan.data[current][parent] = self.sa.father(p)
|
||||
if self.gui.fan.data[current][parent] is None:
|
||||
self.gui.fan.angle[current][parent][3] = 0 # start,stop,g,state
|
||||
parent += 1
|
||||
self.gui.fan.data[current][parent] = self.sa.mother(p)
|
||||
if self.gui.fan.data[current][parent] is None:
|
||||
self.gui.fan.angle[current][parent][3] = 0 # start,stop,g,state
|
||||
parent += 1
|
||||
self.gui.fan.queue_draw()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user