How to make visual research of a person/people with with their associations?

The technical bits of this topic are waaaaay beyond me but this diagram looks just like a Concept Map. I used these back in the day / before I retired. The software I used to create these maps is here:
https://cmap.ihmc.us/
I have no idea if this software allows the creation of such maps to be automated or not but it was certainly very easy to use manually.

I hope this helps.

1 Like

In Gephi, you need to add a layout algoritm and run it, you do that (seen to the left).
Import your project, add a layout and run it, in addition you can change the style etc. in the top left “menu”.


Two different “layouts” just as an example.
I did not add any styles, just to keep it as simple as possible.

Same goes for most network graph software, but if you import a gedcom to yEd, it will automatically run the “genealogy graph” or “org. chart”.
And I think it also use a standard one for when you import a network graph file, I don’t remember, but think it is a forced directed algoritm, they use one of yWorks own network algoritms.

1 Like

Yes, I tried change different algorithms. But looks like your graph has not so many nodes and edges. It really glitches for me. And also your labels are turned off. Turn on labels and there will be something unreadable. Such graph is needed to make deep analyse of relations. Much more better looks yED graph. It looks very good. I see unrelated groops there, they are separated. But anyway I need use filters because of glitches. And this is still blocking me.

I already can export filtered json, but extractor.py converts not empty json to empty .gv file :upside_down_face:.

This was just an example; I have networks with over a million nodes and 2.4 million or so edges.

But a graph that big needs to be zoomed to a specific area of interest to actually make any sense in addition to add some styles to differentiate the nodes and edges.

But, yes, yEd have a nice easy sight for your eyes in the default graph, they have some really great algoritms, I use a few of them in Cytoscape, but since they are free there, it seems that they have slowed them down in that software.
Problem yEd is that it has a tendency to crash if you try to import network graph files from other software.

1 Like

Yes, my solution was a bit hacky so I am not entirely surprised it doesn’t work 100%. I think I will need to develop a more robust addon for Gramps itself which will hopefully not have the same issues.

Yeah I have noticed this as well on occasion.

2 Likes

@RenTheRoot here is also my version of the script. It takes into account such cases as:

  • parents and children nodes and edges
  • associated people nodes and edges
  • marriages events nodes and edges with people
  • shared events nodes and edges with people
    But it still has issues. Currently I receive such graph on a small test DB:

Maybe you will use also some my ideas for your future export addon. What does not work here:

  • nodes for events and people looks identically, but should have different colors
  • at least shared events should be turned on/off via setting because some users could have events with a lot of people, like “Census”.
  • spouses arenot aligned horizontally. And I think Spouces with their Marriage event should be a grouped.
# Import required libraries
import json
import graphviz

# Define constants for line colors and styles
SPOUSE_EDGE_COLOR = '#005000'  # Black 
SPOUSE_EDGE_STYLE = 'bold'

CHILD_EDGE_COLOR = '#00FF00'  #  Green
CHILD_EDGE_STYLE = 'bold'

ASSOCIATION_EDGE_COLOR = '#0000FF' # Blue
ASSOCIATION_EDGE_STYLE = 'dotted' 

SHARED_EVENT_EDGE_STYLE = '#FF0000' # Red
SHARED_EVENT_EDGE_COLOR = 'dashed'

# Define functions to process components from JSON data

# Function to parse JSON lines into components
def getAllComponents(lines):
    components = []
    for line in lines:
        components.append(json.loads(line))
    print("components count: ", len(components))
    return components

# Function to extract people from components
def getAllPeople(components):
    people = {i["handle"]: i for i in components if i["_class"] == "Person"}
    print("People count: ", len(people))
    return people

# Function to extract events from components
def getAllEvents(components):
    events = {i["handle"]: i for i in components if i["_class"]=="Event"}
    print("Events count: ", len(events))
    return events

# Function to extract families from components
def getAllFamilies(components):
    families = {i["handle"]: i for i in components if i["_class"]=="Family"}
    print("Families count: ", len(families))
    return families

# Function to get the full name of a person
def getFullname(person):
    primaryName = person.get("primary_name")
    firstName = primaryName.get("first_name")
    surnameList = primaryName.get("surname_list")
    surnamesStr = " ".join([i.get("surname") for i in surnameList])
    fullnameStr = f"{firstName} {surnamesStr}"
    return fullnameStr

# Function to get the name of an event
def getEventName(eventHandle):
    eventName = "Event"
    event = events[eventHandle]
    eventType = event.get("type")
    if eventType and eventType.get("string"):
        eventName = eventType.get("string")
    return eventName

# Function to get the Gramps ID of an event
def getEventGrampsId(eventHandle):
    event = events[eventHandle]
    return event.get("gramps_id")

# Function to extract shared events from people's event references
def getSharedEvents():
    sharedEvents = {}
    for personHandle, person in people.items():
        eventRefList = person.get("event_ref_list", [])
        for eventRef in eventRefList:
            eventHandle = eventRef.get("ref")
            if eventHandle not in events:
                continue
            eventName = getEventName(eventHandle)
            eventGrampsId = getEventGrampsId(eventHandle)
            if eventHandle not in sharedEvents:
                sharedEvents[eventHandle] = {"gramps_id": eventGrampsId, "event_name": eventName, "people_handles": [personHandle]}
            else:
                sharedEvents[eventHandle]["people_handles"].append(personHandle)
    print("Shared Events count: ", len(sharedEvents))            
    return sharedEvents

# Function to extract marriage events from all events
def getMarriageEvents():
    marriageEvents = {}
    for handle, event in events.items():
        if getEventName(handle) != 'Marriage':
            continue
        marriageEvents[handle] = event
    return marriageEvents

# Read JSON file and parse its contents into components
with open('Untitled_2.json', 'r',encoding='utf-8') as f:
    lines = f.readlines()
components = getAllComponents(lines)

# Extract people, events, families, marriage events, and shared events from components
people = getAllPeople(components)
events = getAllEvents(components)
families = getAllFamilies(components)
marriageEvents = getMarriageEvents()
sharedEvents = getSharedEvents()

# Create a Graphviz Digraph object
dot = graphviz.Digraph()

# Add nodes for each person, displaying their full name and Gramps ID
for handle, person in people.items():
    fullnameStr = getFullname(person)
    grampsId = person.get("gramps_id")
    dot.node(handle, f"{grampsId}: {fullnameStr}")

# Add nodes for marriage events
for handle, family in families.items():
    eventRefList = family.get("event_ref_list", []);
    for eventRef in eventRefList:
        eventHandle = eventRef.get("ref")
        if not eventHandle or eventHandle not in marriageEvents:
            continue
        event = marriageEvents.get(eventHandle)
        grampsId = event.get("gramps_id")
        dot.node(eventHandle, f"{grampsId}: Marriage") 

# Add nodes for shared events with at least two people
for eventHandle, sharedEvent in sharedEvents.items():
    peopleHandles = sharedEvent.get("people_handles");   
    if len(peopleHandles) < 2:
        continue
    eventName = sharedEvent.get("event_name");
    if (eventName == "Marriage"):
        continue
    eventGrampsId = sharedEvent.get("gramps_id")
    dot.node(eventHandle, f"{eventGrampsId}: {eventName}")   
    
# Add spouse and their children edges
for handle, family in families.items():
    fatherHandle = family.get("father_handle")
    motherHandle = family.get("mother_handle")
    eventRefList = family.get("event_ref_list")
    if not fatherHandle or not motherHandle or fatherHandle not in people or motherHandle not in people:
        continue
    with dot.subgraph() as s:
        s.attr(rank='same')
        s.node(fatherHandle)
        s.node(motherHandle)
    dot.edge(fatherHandle, motherHandle, label="Spouse", dir="both", style=SPOUSE_EDGE_STYLE, color=SPOUSE_EDGE_COLOR)
    for eventRef in eventRefList:
        eventHandle = eventRef.get("ref")
        if not eventHandle or eventHandle not in marriageEvents:
            continue
        dot.edge(fatherHandle, eventHandle, label="", dir="none", style=SPOUSE_EDGE_STYLE, color=SPOUSE_EDGE_COLOR)
        dot.edge(motherHandle, eventHandle, label="", dir="none", style=SPOUSE_EDGE_STYLE, color=SPOUSE_EDGE_COLOR)
        childRefList = family.get("child_ref_list", [])
        for childRef in childRefList:
            childHandle = childRef.get("ref")
            if (childHandle and childHandle in people):
                dot.edge(eventHandle, childHandle, label="Child", dir="forward", style=CHILD_EDGE_STYLE, color=CHILD_EDGE_COLOR)  

# Add association edges between people
for handle, person in people.items():
    personRefList = person.get("person_ref_list", []);
    for personRef in personRefList:
        personRefHandle = personRef.get("ref")
        personRefRel = personRef.get("rel")
        if (personRefHandle and personRefHandle in people):
            dot.edge(handle, personRefHandle, label=personRefRel, dir="forward", style=ASSOCIATION_EDGE_STYLE, color=ASSOCIATION_EDGE_COLOR)

# Add edges from shared events to associated people, labeling the relationship role
for eventHandle, sharedEvent in sharedEvents.items():
    peopleHandles = sharedEvent.get("people_handles")
    for personHandle in peopleHandles:
        if (personHandle not in people):
            continue
        person = people.get(personHandle)
        eventRefList = person.get("event_ref_list")
        for eventRef in eventRefList:
            ref = eventRef.get("ref")
            role = eventRef.get("role")
            roleName = role.get("string")
            if eventHandle != ref:
                continue
            dot.edge(eventHandle, personHandle, label=roleName, dir="back", style=SHARED_EVENT_EDGE_STYLE, color=SHARED_EVENT_EDGE_COLOR)    

with open("out.gv", 'w') as f:            
    f.write(dot.source)
1 Like