Skip to content

Instantly share code, notes, and snippets.

@filipefigcorreia
Last active May 28, 2020 17:26
Show Gist options
  • Save filipefigcorreia/8512158 to your computer and use it in GitHub Desktop.
Save filipefigcorreia/8512158 to your computer and use it in GitHub Desktop.
Copy of the original GrampsCrossing.py by Peter Hewett (see https://www.gramps-project.org/bugs/view.php?id=6184) that finds layouts with less edge crossings, for relationship graphs generated by GRAMPS. Modified to handle everything in memory (i.e., without actually outputting DOT and PDF files) until the best result is reached. Now also suppor…
#!/usr/bin/python
#
# Peter Hewett (Modified by Filipe Correia)
# Copyright GPL 2012
#
# reorder Gramps dot file to minimise crossings in rel chart
# usage:
# run rel_graph in Gramps to produce .gv file
# copy .gv file and this .py file to the same directory
# in that directory, run
# $./GrampsCrossing.py yourfile.gv
# output is: span, iteration, crossings1, crossings2
# it takes several minutes, depending on file size
# it doesn't alter your .gv file
# it leaves optimised .dot file and corresponding pdf in directory
#
import math
import subprocess
import sys
import signal
dot_markup = ""
def signal_handler(thesignal, frame):
print 'Ctrl+C pressed! Writing files and aborting...'
write_files(dot_markup)
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
def parse():
with open(sys.argv[1], 'r') as f:
src=f.readlines()
f.close()
# find end of header section
for i,l in enumerate(src):
i=i+1
if 'node ' in l:
break
# parse input file
dot_header=src[0:i+1]
dot_body=src[i+1:]
dot_links=[]
dot_people=[]
dot_spouses=[]
dot_families=[]
j = 0
while j < len(dot_body):
l = dot_body[j]
if ' -> ' in dot_body[j]:
dot_links.append(l)
elif l.startswith(' I'):
dot_people.append(l)
elif l.startswith(' subgraph'):
for y,k in enumerate(dot_body[j:]):
if k.startswith(' }'):
break
dot_spouses.append(''.join(dot_body[j:j+y+1]))
j += y
else:
dot_families.append(l)
j += 1
return dot_header, dot_people, dot_families, dot_spouses, dot_links
# function to return number of crossings for given dot file
def crossings(df, output_files=False):
if output_files:
cmd = 'dot -v -Tpdf > gcf1.pdf'
f=open('gcf1.dot', 'w')
f.write(''.join(df))
f.close()
else:
cmd = 'dot -v -Tpdf > /dev/null' # silent
result = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
try:
result.stdin.write(''.join(df))
result.stdin.close()
except IOError, e:
print e
print ''.join(list(result.stderr))
for l in result.stderr:
if 'crossings' in l:
break
p1 = l.find(':')
return int(l[p1+1:p1+4])
def write_files(dot_markup):
crossings(dot_markup, output_files=True)
if __name__=="__main__":
header, people, families, spouses, links = parse()
nsize=len(people)
span0 = int(math.log(nsize-1)/math.log(2))
print nsize, span0
span=2**span0
new_people=[]
nr_cross_best = crossings(header + people + links + spouses + families)
nr_cross_new = nr_cross_best
while span >= 1:
for i in range(nsize-span):
new_people = people[:]
tmp = new_people[i]
new_people[i] = new_people[i+span]
new_people[i+span] = tmp
dot_markup = header + new_people + links + spouses + families
nr_cross_new = crossings(dot_markup)
print span, i, nr_cross_best, nr_cross_new
if nr_cross_new < nr_cross_best:
people = new_people[:]
nr_cross_best=nr_cross_new
if nr_cross_best == 0:
break
span = span/2
write_files(dot_markup)
@filipefigcorreia
Copy link
Author

filipefigcorreia commented May 28, 2020

A version with improvements and compatible with Python 3 is now available here: https://github.com/hewettp/GrampsCrossing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment