Skip to content

Commit

Permalink
Update DNA Segment Map for bug 0012564 and 0012712
Browse files Browse the repository at this point in the history
Addresses issues :
Add start / stop cM to tooltip
Add relationship to tooltip
Add Common Ancestors to tooltip
Add right click on segment to bring up Note Editor
Fix maternal/paternal/unknown side calculation
Grey paternal side of X chromosome for males
Add support for M/P/U flag (per-line) on input file
  • Loading branch information
GaryGriffin committed Oct 19, 2022
1 parent 64e98ea commit 2043213
Showing 1 changed file with 120 additions and 45 deletions.
165 changes: 120 additions & 45 deletions DNA/dnasegmentmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from gi.repository import GObject
from gi.repository import PangoCairo
from gramps.gui.editors import EditPerson
from gramps.gui.editors import EditNote

#------------------------------------------------------------------------
#
Expand Down Expand Up @@ -62,6 +63,9 @@ def db_changed(self):
self.connect(self.dbstate.db, 'family-update', self.update)
self.connect(self.dbstate.db, 'family-add', self.update)
self.connect(self.dbstate.db, 'family-delete', self.update)
self.connect(self.dbstate.db, 'note-add', self.update)
self.connect(self.dbstate.db, 'note-delete', self.update)
self.connect(self.dbstate.db, 'note-update', self.update)
self.connect_signal('Person',self.update)

def build_gui(self):
Expand All @@ -73,10 +77,10 @@ def build_gui(self):
self.vbox.set_spacing(12)
return self.vbox


def main(self):
for widget in self.vbox.get_children():
self.vbox.remove(widget)

active_handle = self.get_active('Person')
if active_handle:
active = self.dbstate.db.get_person_from_handle(active_handle)
Expand All @@ -88,37 +92,46 @@ def main(self):
segmap.dbstate = self.dbstate
segmap.uistate = self.uistate
segmap.segments = []
segmap.backref = self
segmap.gender = active.gender
segmap.active = active
segmap.relationship = self.relationship

for assoc in active.get_person_ref_list():
if assoc.get_relation() == 'DNA':
rgb_color = [random.random(),random.random(),random.random()]
associate = self.dbstate.db.get_person_from_handle(assoc.ref)
data, msg = self.relationship.get_relationship_distance_new(self.dbstate.db,active,associate)
id_str = _(_nd.display(associate) + ' [' + associate.get_gramps_id() + ']')
if data[0] == -1:
data, msg = self.relationship.get_relationship_distance_new(self.dbstate.db,active,associate,False, True, True)
if data[0][0] == -1: # Unrelated
side = 'U'
elif data[0][0] == 1: #parent / child
if self.dbstate.db.get_person_from_handle(data[0][1]).gender == 0:
side = 'M'
else:
side = 'P'
elif (len(data) > 1 and data[0][0] == data[1][0] and data[0][2][0] != data[1][2][0]): #shares both parents
side = 'U'
else:
side = data[2][0].upper()
side = data[0][2][0].upper()
# Get Notes attached to Association
for handle in assoc.get_note_list():
note = self.dbstate.db.get_note_from_handle(handle)
for line in note.get().split('\n'):
assoc_handle = assoc.ref
write_chromo(line, side, rgb_color, assoc, segmap)
write_chromo(line, side, rgb_color, assoc, note, segmap)
# Get Notes attached to Citation which is attached to the Association
for citation_handle in assoc.get_citation_list():
citation = self.dbstate.db.get_citation_from_handle(citation_handle)
for handle in citation.get_note_list():
note = self.dbstate.db.get_note_from_handle(handle)
for line in note.get().split('\n'):
assoc_handle = assoc.ref
write_chromo(line, side, rgb_color, assoc, segmap)
write_chromo(line, side, rgb_color, assoc, note, segmap)
if len(segmap.segments) > 0:
segmap.show()
self.vbox.pack_start(segmap, True, True, 0)

def write_chromo(line, side, rgb_color, assoc, segmap):
# GEDmatch HTML scrape need commas removed and tab changed to comma
def write_chromo(line, side, rgb_color, assoc, note, segmap):

if re.search('\t',line) != None:
line2 = re.sub(',','',line)
line = re.sub('\t',',',line2)
Expand All @@ -137,10 +150,17 @@ def write_chromo(line, side, rgb_color, assoc, segmap):
snp = int(field[4])
except:
snp = 0
seg_comment = ''
updated_side = side
if len(field) > 5:
if field[5] in { "M", "P", "U"}:
updated_side = field[5].strip()
else:
seg_comment = field[5].strip()
handle = assoc.ref
associate = segmap.dbstate.db.get_person_from_handle(assoc.ref)
id_str = _(_nd.display(associate) + ' [' + associate.get_gramps_id() + ']')
segmap.segments.append([chromo, start, stop, side, cms, snp, id_str, rgb_color, associate, handle])
segmap.segments.append([chromo, start, stop, updated_side, cms, snp, id_str, rgb_color, associate, handle, note])

def get_base(num):
try:
Expand Down Expand Up @@ -173,12 +193,13 @@ def __init__(self):
Gdk.EventMask.BUTTON_RELEASE_MASK)
self.connect('motion-notify-event', self.on_pointer_motion)
self.connect('button-press-event', self.on_button_press)

self.connect('clicked',self.on_legend_hover, 0)
self.title = ''
self.axis = ''
self.grid_lines = True
self.__rects = None
self.__active = -1
self.highlight = None

self.chromosomes = (
('1', 248956422),
Expand Down Expand Up @@ -207,6 +228,9 @@ def __init__(self):

self.labels = [chromo[0] for chromo in self.chromosomes]

def on_legend_hover(self, _dummy, value, handle_data):
# print(value)
return
def set_title(self, title):
"""
Set the main chart title.
Expand Down Expand Up @@ -249,7 +273,6 @@ def do_draw(self, cr):
context = self.get_style_context()
fg_color = context.get_color(context.get_state())
cr.set_source_rgba(*fg_color)

# Title
layout = self.create_pango_layout(self.title)
width, height = layout.get_pixel_size()
Expand Down Expand Up @@ -316,10 +339,6 @@ def do_draw(self, cr):
cr.line_to(tick_pos, (2 * spacing) + offset)
cr.stroke()
cr.set_dash([])
#layout = self.create_pango_layout('%d' % count)
#width, height = layout.get_pixel_size()
#cr.move_to(tick_pos - (width / 2), bottom + 5)
#PangoCairo.show_layout(cr, layout)
count += tick_step

offset += spacing
Expand All @@ -346,13 +365,22 @@ def do_draw(self, cr):
cr.fill_preserve()
cr.set_source_rgba(*fg_color)
cr.stroke()
# Grey out paternal X background for males
if self.gender == 1:
cr.rectangle(label_width, 22 * 2 * (chr_height + spacing) + offset, chart_width * chromo[1] / maximum, chr_height)
cr.set_source_rgba(0.8, 0.8, 0.8, 1)
cr.fill_preserve()
cr.set_source_rgba(*fg_color)
cr.stroke()

# Segments
cr.set_line_width(1)
self.__rects = []
self.__legendrects = []
self.__associates = []
self.__notes = []
self.__assoc_handle = []
self.__legend_str = []

legend_offset_y = 10 * (chr_height + spacing) + offset
legend_offset_x = allocation.width * 0.75
Expand All @@ -363,7 +391,7 @@ def do_draw(self, cr):
PangoCairo.show_layout(cr, layout)
legend_offset_y += chr_height + 2 * spacing

for chromo, start, stop, side, cms, snp, assoc_name, rgb_color, associate, handle in self.segments:
for chromo, start, stop, side, cms, snp, assoc_name, rgb_color, associate, handle, note in self.segments:

try:
i = self.labels.index(chromo)
Expand All @@ -375,20 +403,22 @@ def do_draw(self, cr):
chr_offset += chr_height
if side == 'U':
chr_mult = 2
cr.rectangle(label_width + chart_width * start / maximum,
chr_offset,
chart_width * (stop-start) / maximum,
chr_mult * chr_height)
self.__rects.append((label_width + chart_width * start / maximum,
chr_offset,
chart_width * (stop-start) / maximum,
chr_mult * chr_height))

cr.set_source_rgba(rgb_color[0], rgb_color[1], rgb_color[2], 1/chr_mult)
cr.fill_preserve()
cr.set_source_rgba(*fg_color)
cr.stroke()

alpha_color = 1 / chr_mult
if self.highlight == None or self.highlight == assoc_name:
cr.rectangle(label_width + chart_width * start / maximum,
chr_offset,
chart_width * (stop-start) / maximum,
chr_mult * chr_height)
self.__rects.append((label_width + chart_width * start / maximum,
chr_offset,
chart_width * (stop-start) / maximum,
chr_mult * chr_height))

cr.set_source_rgba(rgb_color[0], rgb_color[1], rgb_color[2], alpha_color)
cr.fill_preserve()
cr.set_source_rgba(*fg_color)
cr.stroke()
self.__notes.append(note)
# Legend entry
if last_name != assoc_name:
last_name = assoc_name
Expand All @@ -406,6 +436,7 @@ def do_draw(self, cr):
self.__legendrects.append((legend_offset_x, legend_offset_y,len(assoc_name) * 6, chr_height))
self.__associates.append(associate)
self.__assoc_handle.append(handle)
self.__legend_str.append(last_name)
cr.set_source_rgba(0,0,0,1)
legend_offset_y += chr_height + 2 * spacing
PangoCairo.show_layout(cr, layout)
Expand Down Expand Up @@ -436,6 +467,29 @@ def on_pointer_motion(self, _dummy, event):
tooltip_text = _("{0}\n{1} cMs".format(self.segments[active][6], self.segments[active][4]))
if self.segments[active][5] > 0:
tooltip_text += _(", {0} SNPs".format(self.segments[active][5]))
# Add relationship and common ancestor to tooltip
rel_strings , common_an = self.relationship.get_all_relationships(self.dbstate.db,self.active,self.segments[active][8])
if len(rel_strings) > 0 :
tooltip_text += _("\nRelationship: {0}".format(rel_strings[0]))
if len(common_an) > 0:
common = common_an[0]
length = len(common)
if length == 1:
p1 = self.dbstate.db.get_person_from_handle(common[0])
if common[0] in [self.segments[active][8].handle, self.active.handle]:
commontext = ''
else :
name = _nd.display(p1)
commontext = " " + _("%s") % name
elif length >= 2:
p1str = _nd.display(self.dbstate.db.get_person_from_handle(common[0]))
p2str = _nd.display(self.dbstate.db.get_person_from_handle(common[1]))
commontext = " " + _("%(ancestor1)s and %(ancestor2)s") % {
'ancestor1': p1str,
'ancestor2': p2str
}
tooltip_text += _("\nCommon Ancestors: {0}".format(commontext))
#
self.set_tooltip_text(tooltip_text)
# Tooltip for Legend
if active == -1:
Expand All @@ -445,9 +499,15 @@ def on_pointer_motion(self, _dummy, event):
event.y > rect[1] and event.y < rect[1] + rect[3]):
activeLegend = i
if activeLegend == -1:
self.set_tooltip_text('')
if self.highlight != None:
self.set_tooltip_text('')
self.highlight = None
self.emit('clicked',0)
else:
self.set_tooltip_text(_('Click to make this person active\nRight-click to edit this person'))
if self.highlight == None:
self.set_tooltip_text(_('Click to make this person active\nRight-click to edit this person'))
self.highlight = self.__legend_str[activeLegend]
self.emit('clicked',activeLegend)
return False

def on_button_press(self, _dummy, event):
Expand All @@ -459,20 +519,35 @@ def on_button_press(self, _dummy, event):
@type event: Gdk.Event
"""
active = -1
#
# Traverse legend for pointer
#
for i, rect in enumerate(self.__legendrects):
if (event.x > rect[0] and event.x < rect[0] + rect[2] and
event.y > rect[1] and event.y < rect[1] + rect[3]):
active = i
if active != -1:
# Primary Button Press
if (event.button == 1 and
event.type == Gdk.EventType.BUTTON_PRESS and
active != -1):
self.uistate.set_active(self.__assoc_handle[active], 'Person')
if (event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS):
self.uistate.set_active(self.__assoc_handle[active], 'Person')
#Secondary Button Press
if (event.button == 3 and
event.type == Gdk.EventType.BUTTON_PRESS and
active != -1):
try:
EditPerson(self.dbstate, self.uistate, [], self.__associates[active])
except:
return False
if (event.button == 3 and event.type == Gdk.EventType.BUTTON_PRESS):
try:
EditPerson(self.dbstate, self.uistate, [], self.__associates[active])
except:
return False
return
#
# Traverse painted chromosomes for pointer
#
for i, rect in enumerate(self.__rects):
if (event.x > rect[0] and event.x < rect[0] + rect[2] and
event.y > rect[1] and event.y < rect[1] + rect[3]):
active = i
if active != -1:
# Secondary Button Press
if (event.button == 3 and event.type == Gdk.EventType.BUTTON_PRESS):
try:
EditNote(self.dbstate, self.uistate, [], self.__notes[active])
except:
return False

0 comments on commit 2043213

Please sign in to comment.