Programming Language Tree Graphic and Code

Imperative Tree

Declarative Tree

The Code

# Import pandas for reading and working with CSV data
import pandas as pd

# Import Digraph from graphviz to create visual graphs
from graphviz import Digraph

# Path to the input CSV file containing the data
CSV_FILE = "data.csv"

# Directory where the generated SVG files will be saved
OUTPUT_DIR = "_static"

# Direction of the graph layout: TB = Top-to-Bottom
GRAPH_DIRECTION = "TB"  # Top-to-Bottom


def make_tooltip(row):
    """
    Builds a multi-line tooltip string for a language node
    using information from a single row of the DataFrame.
    """
    return (
        f"Genealogy: {row['Genealogy']}\n"
        f"Date Started: {row['DateStarted']}\n"
        f"Role: {row['Role']}\n"
        f"Environment: {row['Environment']}\n"
        f"Activity Status: {row['ActivityStatus']}\n"
        f"Career Note: {row['CareerNote']}"
    )


def generate_tree(df, trunk_name):
    """
    Generates a Graphviz tree for a given trunk (e.g., Imperative or Declarative)
    using a filtered DataFrame.
    """

    # Create a new directed graph that will be rendered as an SVG
    dot = Digraph(format="svg")

    # Set global graph attributes such as direction and font styling
    dot.attr(rankdir=GRAPH_DIRECTION, fontname="Helvetica", fontsize="10")

    # Track which nodes have already been added to avoid duplicates
    seen = set()

    # Loop through each row in the DataFrame
    for _, row in df.iterrows():

        # Extract and clean paradigm and language values
        paradigm = row["Paradigm"].strip()
        language = row["Language"].strip()

        # Add the trunk node once (root of the tree)
        if trunk_name not in seen:
            dot.node(
                trunk_name,
                trunk_name,
                shape="box",
                style="filled",
                fillcolor="#E8E8E8"
            )
            seen.add(trunk_name)

        # Add each paradigm node once
        if paradigm not in seen:
            dot.node(
                paradigm,
                paradigm,
                shape="ellipse",
                style="filled",
                fillcolor="#BFDFFF"
            )
            seen.add(paradigm)

        # Add each language node once, including a tooltip with extra info
        if language not in seen:
            dot.node(
                language,
                language,
                shape="oval",
                style="filled",
                fillcolor="#FFF2B2",
                tooltip=make_tooltip(row)
            )
            seen.add(language)

        # Create an edge from the trunk to the paradigm
        dot.edge(trunk_name, paradigm)

        # Create an edge from the paradigm to the language
        dot.edge(paradigm, language)

    # Build the output file path using the trunk name
    output_path = f"{OUTPUT_DIR}/paradigm_tree_{trunk_name.lower()}"

    # Render the graph to an SVG file and remove intermediate files
    dot.render(output_path, cleanup=True)

    # Print confirmation that the file was generated
    print(f"Generated: {output_path}.svg")


def main():
    """
    Main entry point of the script.
    Reads the CSV and generates trees for each trunk type.
    """

    # Read the CSV file into a pandas DataFrame
    df = pd.read_csv(CSV_FILE)

    # Filter rows belonging to the Imperative trunk
    df_imperative = df[df["Trunk"].str.lower() == "imperative"]

    # Generate the Imperative tree
    generate_tree(df_imperative, "Imperative")

    # Filter rows belonging to the Declarative trunk
    df_declarative = df[df["Trunk"].str.lower() == "declarative"]

    # Generate the Declarative tree
    generate_tree(df_declarative, "Declarative")


# Ensures main() only runs when this file is executed directly
if __name__ == "__main__":
    main()