{"spec_id":"gantt-dependencies","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\ngantt-dependencies: Gantt Chart with Dependencies\nLibrary: letsplot 4.10.1 | Python 3.13.13\nQuality: 85/100 | Updated: 2026-06-02\n\"\"\"\n\nimport os\n\nimport pandas as pd\nfrom lets_plot import *\nfrom lets_plot.export import ggsave\n\n\nLetsPlot.setup_html()\n\n# Theme-adaptive chrome — Imprint palette tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Imprint categorical palette\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\n\n# Group colors: Imprint palette positions 1–4\ngroup_order = [\"Requirements\", \"Design\", \"Development\", \"Testing\"]\ngroup_colors = {\n    \"Requirements\": IMPRINT_PALETTE[0],  # brand green\n    \"Design\": IMPRINT_PALETTE[1],  # lavender\n    \"Development\": IMPRINT_PALETTE[2],  # blue\n    \"Testing\": IMPRINT_PALETTE[3],  # ochre\n}\n\n# Dependency arrow colors: Imprint palette positions 5–7\ndep_colors = {\n    \"finish-to-start\": IMPRINT_PALETTE[4],  # matte red\n    \"start-to-start\": IMPRINT_PALETTE[5],  # cyan\n    \"finish-to-finish\": IMPRINT_PALETTE[6],  # rose\n}\n\n# Data — software development project with phases and dependencies\ntasks_data = [\n    # Requirements phase\n    {\n        \"task\": \"Gather Requirements\",\n        \"start\": \"2024-01-01\",\n        \"end\": \"2024-01-08\",\n        \"group\": \"Requirements\",\n        \"depends_on\": [],\n    },\n    {\n        \"task\": \"Stakeholder Interviews\",\n        \"start\": \"2024-01-03\",\n        \"end\": \"2024-01-10\",\n        \"group\": \"Requirements\",\n        \"depends_on\": [(\"Gather Requirements\", \"start-to-start\")],\n    },\n    {\n        \"task\": \"Document Specs\",\n        \"start\": \"2024-01-10\",\n        \"end\": \"2024-01-17\",\n        \"group\": \"Requirements\",\n        \"depends_on\": [(\"Stakeholder Interviews\", \"finish-to-start\")],\n    },\n    # Design phase\n    {\n        \"task\": \"Architecture Design\",\n        \"start\": \"2024-01-17\",\n        \"end\": \"2024-01-27\",\n        \"group\": \"Design\",\n        \"depends_on\": [(\"Document Specs\", \"finish-to-start\")],\n    },\n    {\n        \"task\": \"UI/UX Mockups\",\n        \"start\": \"2024-01-20\",\n        \"end\": \"2024-01-30\",\n        \"group\": \"Design\",\n        \"depends_on\": [(\"Document Specs\", \"finish-to-start\")],\n    },\n    {\n        \"task\": \"Database Schema\",\n        \"start\": \"2024-01-22\",\n        \"end\": \"2024-01-31\",\n        \"group\": \"Design\",\n        \"depends_on\": [(\"Architecture Design\", \"start-to-start\")],\n    },\n    # Development phase\n    {\n        \"task\": \"Backend API\",\n        \"start\": \"2024-01-31\",\n        \"end\": \"2024-02-21\",\n        \"group\": \"Development\",\n        \"depends_on\": [(\"Database Schema\", \"finish-to-start\"), (\"Architecture Design\", \"finish-to-start\")],\n    },\n    {\n        \"task\": \"Frontend Components\",\n        \"start\": \"2024-01-30\",\n        \"end\": \"2024-02-18\",\n        \"group\": \"Development\",\n        \"depends_on\": [(\"UI/UX Mockups\", \"finish-to-start\")],\n    },\n    {\n        \"task\": \"Integration\",\n        \"start\": \"2024-02-18\",\n        \"end\": \"2024-02-28\",\n        \"group\": \"Development\",\n        \"depends_on\": [(\"Backend API\", \"finish-to-finish\"), (\"Frontend Components\", \"finish-to-start\")],\n    },\n    # Testing phase\n    {\n        \"task\": \"Unit Testing\",\n        \"start\": \"2024-02-10\",\n        \"end\": \"2024-02-24\",\n        \"group\": \"Testing\",\n        \"depends_on\": [(\"Backend API\", \"start-to-start\")],\n    },\n    {\n        \"task\": \"Integration Testing\",\n        \"start\": \"2024-02-28\",\n        \"end\": \"2024-03-08\",\n        \"group\": \"Testing\",\n        \"depends_on\": [(\"Integration\", \"finish-to-start\")],\n    },\n    {\n        \"task\": \"User Acceptance\",\n        \"start\": \"2024-03-08\",\n        \"end\": \"2024-03-15\",\n        \"group\": \"Testing\",\n        \"depends_on\": [(\"Integration Testing\", \"finish-to-start\")],\n    },\n]\n\ndf = pd.DataFrame(tasks_data)\ndf[\"start\"] = pd.to_datetime(df[\"start\"])\ndf[\"end\"] = pd.to_datetime(df[\"end\"])\n\n# Build y positions — reading order top-to-bottom, then flipped for chart\ny_positions = {}\ntask_info = {}\nreading_order = []\n\nfor group in group_order:\n    group_tasks = df[df[\"group\"] == group]\n    reading_order.append(group)\n    task_info[group] = {\n        \"start\": group_tasks[\"start\"].min(),\n        \"end\": group_tasks[\"end\"].max(),\n        \"is_group\": True,\n        \"group\": group,\n    }\n    for _, row in group_tasks.sort_values(\"start\").iterrows():\n        reading_order.append(row[\"task\"])\n        task_info[row[\"task\"]] = {\n            \"start\": row[\"start\"],\n            \"end\": row[\"end\"],\n            \"is_group\": False,\n            \"group\": row[\"group\"],\n            \"depends_on\": row[\"depends_on\"],\n        }\n\nn = len(reading_order)\nfor i, name in enumerate(reading_order):\n    y_positions[name] = n - 1 - i\n\n# Prepare plot DataFrames\nplot_data = []\nfor name, y in y_positions.items():\n    info = task_info[name]\n    plot_data.append(\n        {\n            \"task\": name,\n            \"y\": y,\n            \"start\": info[\"start\"],\n            \"end\": info[\"end\"],\n            \"is_group\": info[\"is_group\"],\n            \"group\": info[\"group\"],\n            \"duration\": (info[\"end\"] - info[\"start\"]).days,\n        }\n    )\n\nplot_df = pd.DataFrame(plot_data)\ngroups_df = plot_df[plot_df[\"is_group\"]]\ntasks_df = plot_df[~plot_df[\"is_group\"]]\n\n# Build dependency arrows\narrows_data = []\nfor task_name, info in task_info.items():\n    if info[\"is_group\"] or not info.get(\"depends_on\"):\n        continue\n    for dep_name, dep_type in info[\"depends_on\"]:\n        if dep_name not in task_info:\n            continue\n        dep_info = task_info[dep_name]\n        if dep_type == \"start-to-start\":\n            x_from, x_to = dep_info[\"start\"], info[\"start\"]\n        elif dep_type == \"finish-to-finish\":\n            x_from, x_to = dep_info[\"end\"], info[\"end\"]\n        else:\n            x_from, x_to = dep_info[\"end\"], info[\"start\"]\n        arrows_data.append(\n            {\n                \"x\": x_from,\n                \"y\": y_positions[dep_name],\n                \"xend\": x_to,\n                \"yend\": y_positions[task_name],\n                \"dep_type\": dep_type,\n                \"from_task\": dep_name,\n                \"to_task\": task_name,\n            }\n        )\n\narrows_df = pd.DataFrame(arrows_data) if arrows_data else None\n\n# Build plot\nx_min = plot_df[\"start\"].min()\nx_max = plot_df[\"end\"].max()\n\nplot = ggplot()\n\n# Alternating background bands (theme-adaptive warm tones)\nband_even = \"#EDECEA\" if THEME == \"light\" else ELEVATED_BG\nband_odd = PAGE_BG\n\nfor i, group in enumerate(group_order):\n    group_task_ys = [y_positions[t] for t, info in task_info.items() if info[\"group\"] == group]\n    y_lo = min(group_task_ys) - 0.45\n    y_hi = max(group_task_ys) + 0.45\n    band_color = band_even if i % 2 == 0 else band_odd\n    band_df = pd.DataFrame(\n        [{\"xmin\": x_min - pd.Timedelta(days=22), \"xmax\": x_max + pd.Timedelta(days=5), \"ymin\": y_lo, \"ymax\": y_hi}]\n    )\n    plot += geom_rect(aes(xmin=\"xmin\", xmax=\"xmax\", ymin=\"ymin\", ymax=\"ymax\"), data=band_df, fill=band_color, alpha=0.8)\n\n# Group header bars (full phase span, ink color)\nplot += geom_segment(\n    aes(x=\"start\", xend=\"end\", y=\"y\", yend=\"y\"),\n    data=groups_df,\n    size=11,\n    color=INK,\n    alpha=0.92,\n    tooltips=layer_tooltips().title(\"@task\").line(\"@start — @end\").line(\"Duration: @duration days\"),\n)\n\n# Task bars — color mapped to group via Imprint palette\ntask_tooltips = (\n    layer_tooltips().title(\"@task\").line(\"@start — @end\").line(\"Group: @group\").line(\"Duration: @duration days\")\n)\nplot += geom_segment(\n    aes(x=\"start\", xend=\"end\", y=\"y\", yend=\"y\", color=\"group\"),\n    data=tasks_df,\n    size=7,\n    alpha=0.85,\n    tooltips=task_tooltips,\n    show_legend=False,\n)\nplot += scale_color_manual(values=[group_colors[g] for g in group_order], breaks=group_order)\n\n# Dependency arrows — three types, each in a distinct Imprint hue\nif arrows_df is not None and not arrows_df.empty:\n    for dep_type, color in dep_colors.items():\n        type_df = arrows_df[arrows_df[\"dep_type\"] == dep_type]\n        if not type_df.empty:\n            plot += geom_segment(\n                aes(x=\"x\", xend=\"xend\", y=\"y\", yend=\"yend\"),\n                data=type_df,\n                size=1.5,\n                color=color,\n                alpha=0.88,\n                arrow=arrow(angle=25, length=6, type=\"closed\"),\n                tooltips=layer_tooltips().line(\"@from_task → @to_task\").line(\"Type: @dep_type\"),\n            )\n\n# Labels: group names right of bars (bold), task names left of bars\nlabel_offset = pd.Timedelta(days=1)\ngroup_labels = groups_df.assign(label_x=groups_df[\"end\"] + label_offset)\ntask_labels_df = tasks_df.assign(label_x=tasks_df[\"start\"] - label_offset)\n\nplot += geom_text(aes(x=\"label_x\", y=\"y\", label=\"task\"), data=group_labels, hjust=0, size=5, fontface=\"bold\", color=INK)\nplot += geom_text(aes(x=\"label_x\", y=\"y\", label=\"task\"), data=task_labels_df, hjust=1, size=4, color=INK_SOFT)\n\n# Dependency legend (consolidated DataFrames)\nlegend_x = x_max - pd.Timedelta(days=12)\nlegend_xend = legend_x + pd.Timedelta(days=5)\nlegend_text_x = legend_xend + pd.Timedelta(days=1)\nlegend_entries = [\n    (\"Finish-to-Start\", \"finish-to-start\", -1.5),\n    (\"Start-to-Start\", \"start-to-start\", -2.2),\n    (\"Finish-to-Finish\", \"finish-to-finish\", -2.9),\n]\n\nplot += geom_text(\n    aes(x=\"x\", y=\"y\", label=\"label\"),\n    data=pd.DataFrame([{\"x\": legend_x, \"y\": -0.7, \"label\": \"Dependencies:\"}]),\n    hjust=0,\n    size=4,\n    fontface=\"bold\",\n    color=INK,\n)\n\nlegend_seg_df = pd.DataFrame(\n    [{\"x\": legend_x, \"xend\": legend_xend, \"y\": y, \"dep_type\": dt} for _, dt, y in legend_entries]\n)\nfor dep_type, color in dep_colors.items():\n    seg = legend_seg_df[legend_seg_df[\"dep_type\"] == dep_type]\n    if not seg.empty:\n        plot += geom_segment(\n            aes(x=\"x\", xend=\"xend\", y=\"y\", yend=\"y\"),\n            data=seg,\n            size=1.5,\n            color=color,\n            arrow=arrow(angle=25, length=6, type=\"closed\"),\n        )\n\nplot += geom_text(\n    aes(x=\"x\", y=\"y\", label=\"label\"),\n    data=pd.DataFrame([{\"x\": legend_text_x, \"y\": y, \"label\": label} for label, _, y in legend_entries]),\n    hjust=0,\n    size=4,\n    color=INK_SOFT,\n)\n\n# Scales\nx_breaks = pd.date_range(start=x_min, end=x_max + pd.Timedelta(days=10), freq=\"W-MON\").tolist()\nplot += scale_x_datetime(\n    format=\"%b %d\", limits=[x_min - pd.Timedelta(days=22), x_max + pd.Timedelta(days=12)], breaks=x_breaks\n)\nplot += scale_y_continuous(breaks=[], labels=[], limits=[-3.8, n + 0.5])\nplot += labs(\n    x=\"Project Timeline (2024)\",\n    y=\"\",\n    title=\"gantt-dependencies · python · letsplot · anyplot.ai\",\n    subtitle=\"Software development lifecycle — task dependencies and critical path across phases\",\n)\n\n# Theme — canvas 800×450, saved at scale=4 → 3200×1800 px\nplot += theme_minimal()\nplot += theme(\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG),\n    axis_title_x=element_text(size=12, color=INK_SOFT),\n    axis_title_y=element_blank(),\n    axis_text_x=element_text(size=10, angle=45, color=INK_SOFT),\n    axis_text_y=element_blank(),\n    plot_title=element_text(size=16, face=\"bold\", color=INK),\n    plot_subtitle=element_text(size=13, color=INK_SOFT),\n    panel_grid_major_y=element_blank(),\n    panel_grid_minor=element_blank(),\n    panel_grid_major_x=element_line(color=INK_SOFT, size=0.3),\n    panel_border=element_blank(),\n    plot_margin=[20, 15, 15, 80],\n)\nplot += ggsize(800, 450)\n\n# Save — PNG at scale=4 (3200×1800) + interactive HTML\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}