4.2. Interactive Linked Open Data (LOD) Graph#

This notebook demonstrates how to construct and explore an interactive network graph that visualises Linked Open Data (LOD) relationships.
The chart represents entities (nodes) and their relationships (edges), allowing complex datasets to be explored intuitively.


4.2.1. How the Graph is Built#

4.2.1.1. Input Data#

  • The graph is generated from a Python list called lod_relations.

  • Each element in this list is a tuple of the form:
    (source_node, target_node, properties)

  • The properties dictionary describes the relationship between the two nodes.

    • Example: {'relation': 'born_in', 'date': '1564'}

This dataset includes people, places, works, and cultural objects, as well as the connections between them.


4.2.1.2. Node Types#

Nodes are classified into four categories, each with its own colour and shape for clarity:

  • 👤 Persons → Blue circles

  • 🏛 Places → Green squares

  • 📖 Works (books, plays, scientific texts) → Orange diamonds

  • 🗿 Objects (artefacts, collections) → Red stars

This visual encoding makes it easier to distinguish different kinds of entities at a glance.


4.2.1.3. Edge Types#

Edges represent relationships between nodes, such as:

  • born_in → Birthplace of a person

  • located_in → Geographical containment

  • wrote / authored → Creative works

  • is_housed_at → Museum or collection housing an object

Each relation type is colour-coded, and a legend is provided for reference.
For example:

  • born_in → Purple

  • located_in → Grey

  • wrote / authored → Orange

  • is_housed_at → Crimson


4.2.2. Interactive Features#

The graph is rendered using Plotly, an open-source interactive visualisation library. Unlike static diagrams, this graph supports full interactivity:

  • Zoom and Pan:
    Scroll with your mouse wheel to zoom, and click-drag to pan across the network.

  • Hover for Details:
    Hover over a node to view its label (e.g. William Shakespeare), or over an edge to see its relationship type.

  • Legend Filtering:
    Use the legend to highlight or hide categories of nodes or edges, focusing on specific types of data.

  • Edge Labels:
    Relationship types are displayed directly on the graph, positioned at the midpoint of edges for easy reading.


4.2.3. Why Use This Graph?#

This approach offers a clear and interactive way to:

  • Explore biographical and cultural connections (e.g. authors, works, and institutions).

  • Understand geographical context (e.g. where people lived, where artefacts originated).

  • Investigate networks of influence and collaboration across time and place.

By combining colour-coding, shapes, and interactive features, the graph enables both overview and detailed inspection of Linked Open Data relationships.

#@title Load sample dataset for LOD visualization

# Each tuple is (source_node, target_node, dictionary_of_properties)

# ===============================================================
# Sample dataset for LOD visualisation
# ===============================================================

lod_relations = [
    ('William Shakespeare', 'England', {'relation': 'born_in', 'date': '1564'}),
    ('William Shakespeare', 'Globe Theatre', {'relation': 'worked_at', 'note': 'Founding member'}),
    ('William Shakespeare', 'Hamlet', {'relation': 'wrote', 'date': '1600'}),
    ('Isaac Newton', 'Trinity College, Cambridge', {'relation': 'studied_at', 'date': '1661'}),
    ('Isaac Newton', 'United Kingdom', {'relation': 'born_in'}),
    ('Isaac Newton', 'Principia Mathematica', {'relation': 'authored', 'date': '1687'}),
    ('Charles Dickens', 'London', {'relation': 'lived_in'}),
    ('Charles Dickens', 'The Old Curiosity Shop', {'relation': 'wrote_about', 'date': '1841'}),
    ('Charles Dickens', 'Oliver Twist', {'relation': 'wrote', 'date': '1837'}),
    ('Ada Lovelace', 'United Kingdom', {'relation': 'born_in', 'date': '1815'}),
    ('Ada Lovelace', 'Charles Babbage', {'relation': 'collaborated_with'}),
    ('Charles Babbage', 'London', {'relation': 'lived_in'}),
    ('Charles Babbage', 'Analytical Engine', {'relation': 'designed'}),
    ('Globe Theatre', 'London', {'relation': 'located_in'}),
    ('Trinity College, Cambridge', 'Cambridge', {'relation': 'located_in'}),
    ('The Old Curiosity Shop', 'London', {'relation': 'located_in'}),
    ('Cambridge', 'United Kingdom', {'relation': 'located_in'}),
    ('London', 'United Kingdom', {'relation': 'capital_of'}),
    ('Tower of London', 'London', {'relation': 'located_in'}),
    ('Buckingham Palace', 'London', {'relation': 'located_in'}),
    ('Big Ben', 'London', {'relation': 'located_in'}),
    ('National Gallery', 'London', {'relation': 'located_in'}),
    ('British Museum', 'London', {'relation': 'located_in'}),
    ('British Museum', 'British Government', {'relation': 'funded_by'}),
    ('British Government', 'United Kingdom', {'relation': 'governs'}),
    ('Rosetta Stone', 'British Museum', {'relation': 'is_housed_at', 'date': '1802'}),
    ('Crown Jewels', 'Tower of London', {'relation': 'is_housed_at'}),
    ('British Museum', 'Elgin Marbles', {'relation': 'holds_collection_of'}),
    ('Elgin Marbles', 'Greece', {'relation': 'originated_in'}),
    ('Rosetta Stone', 'Egypt', {'relation': 'originated_in'}),
    ('Queen Elizabeth II', 'Buckingham Palace', {'relation': 'lived_at'}),
    ('Queen Elizabeth II', 'United Kingdom', {'relation': 'ruler_of'}),
    ('Lord Nelson', 'National Gallery', {'relation': 'is_represented_in'}),
    ('Lord Nelson', 'United Kingdom', {'relation': 'born_in'}),
    ('Trafalgar Square', 'Lord Nelson', {'relation': 'features_monument_to'}),
    ('Trafalgar Square', 'London', {'relation': 'located_in'}),
    ('Hamlet', 'Globe Theatre', {'relation': 'performed_at'}),
    ('Principia Mathematica', 'Royal Society', {'relation': 'presented_to', 'date': '1687'}),
    ('Royal Society', 'London', {'relation': 'located_in'}),
    ('Oliver Twist', 'London', {'relation': 'set_in'}),
    ('Analytical Engine', 'Modern Computing', {'relation': 'inspired'}),
]
#@title Build Network Diagram

import networkx as nx
import plotly.graph_objects as go

# ===============================================================
# Build graph
# ===============================================================
G = nx.DiGraph()
for source, target, props in lod_relations:
    G.add_node(source)
    G.add_node(target)
    G.add_edge(source, target, **props)

# Compute layout
pos = nx.spring_layout(G, k=0.55, iterations=100)

# ===============================================================
# Node classification: people, places, works, objects
# ===============================================================
def classify_node(name: str) -> str:
    """Rough classification by name membership."""
    people = ['William Shakespeare', 'Isaac Newton', 'Charles Dickens',
              'Ada Lovelace', 'Charles Babbage', 'Queen Elizabeth II',
              'Lord Nelson']
    works = ['Hamlet', 'Principia Mathematica', 'Oliver Twist',
             'Analytical Engine']
    objects = ['Rosetta Stone', 'Crown Jewels', 'Elgin Marbles']
    if name in people:
        return "Person"
    elif name in works:
        return "Work"
    elif name in objects:
        return "Object"
    else:
        return "Place"

# ===============================================================
# Edge categories and colours
# ===============================================================
edge_categories = {
    "born_in": "purple",
    "lived_in": "brown",
    "worked_at": "blue",
    "studied_at": "teal",
    "authored": "darkorange",
    "wrote": "darkorange",
    "wrote_about": "darkorange",
    "collaborated_with": "magenta",
    "designed": "darkgreen",
    "located_in": "grey",
    "capital_of": "black",
    "funded_by": "goldenrod",
    "governs": "red",
    "is_housed_at": "crimson",
    "holds_collection_of": "salmon",
    "originated_in": "green",
    "ruler_of": "darkred",
    "is_represented_in": "navy",
    "features_monument_to": "darkcyan",
    "performed_at": "orange",
    "presented_to": "olive",
    "set_in": "peru",
    "inspired": "darkblue"
}

# ===============================================================
# Prepare traces
# ===============================================================

# --- Edges (split by relation type) ---
edge_traces = []
for rel_type, color in edge_categories.items():
    edge_x, edge_y = [], []
    for u, v, d in G.edges(data=True):
        if d.get("relation") == rel_type:
            x0, y0 = pos[u]
            x1, y1 = pos[v]
            edge_x += [x0, x1, None]
            edge_y += [y0, y1, None]
    if edge_x:  # only add if edges exist for this relation
        edge_traces.append(go.Scatter(
            x=edge_x, y=edge_y,
            line=dict(width=1.5, color=color),
            hoverinfo='none',
            mode='lines',
            name=rel_type
        ))

# --- Nodes (separate by category) ---
traces = []
categories = {
    "Person": dict(color="royalblue", symbol="circle"),
    "Place": dict(color="forestgreen", symbol="square"),
    "Work": dict(color="darkorange", symbol="diamond"),
    "Object": dict(color="crimson", symbol="star")
}

for category, style in categories.items():
    node_x, node_y, node_text = [], [], []
    for node in G.nodes():
        if classify_node(node) == category:
            x, y = pos[node]
            node_x.append(x)
            node_y.append(y)
            node_text.append(node)
    traces.append(go.Scatter(
        x=node_x, y=node_y,
        mode='markers+text',
        hoverinfo='text',
        text=node_text,
        textposition="bottom center",
        marker=dict(
            size=16,
            color=style["color"],
            symbol=style["symbol"],
            line=dict(width=1, color='black')
        ),
        name=category,
        textfont=dict(color='black')
    ))

# --- Edge labels ---
edge_label_x, edge_label_y, edge_labels = [], [], []
for u, v, d in G.edges(data=True):
    x0, y0 = pos[u]
    x1, y1 = pos[v]
    edge_label_x.append((x0 + x1) / 2)
    edge_label_y.append((y0 + y1) / 2)
    edge_labels.append(d.get('relation', ''))

edge_label_trace = go.Scatter(
    x=edge_label_x, y=edge_label_y,
    mode='text',
    text=edge_labels,
    textposition="top center",
    hoverinfo='none',
    textfont=dict(color='black', size=9),
    showlegend=False
)

# ===============================================================
# Build figure
# ===============================================================
fig = go.Figure(
    data=edge_traces + traces + [edge_label_trace],
    layout=go.Layout(
        title='LOD Graph: People, Places, Works, and Objects',
        titlefont_size=18,
        showlegend=True,
        hovermode='closest',
        margin=dict(b=40, l=40, r=40, t=60),
        xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
        yaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
        dragmode='pan'
    )
)

fig.show()