# 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()