{"spec_id":"gantt-dependencies","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\ngantt-dependencies: Gantt Chart with Dependencies\nLibrary: matplotlib 3.10.9 | Python 3.13.13\nQuality: 83/100 | Updated: 2026-06-02\n\"\"\"\n\nimport os\n\nimport matplotlib.dates as mdates\nimport matplotlib.pyplot as plt\nimport pandas as pd\nfrom matplotlib.lines import Line2D\nfrom matplotlib.patches import FancyArrowPatch, Patch\n\n\n# Theme 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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint palette — positions 1-4 for phases; semantic red for critical path\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\nCRITICAL_COLOR = \"#AE3030\"  # semantic red for critical path and milestone\n\n# Data — software development project with 4 phases and inter-task dependencies\ntasks_data = {\n    \"task\": [\n        \"Requirements Phase\",\n        \"Gather Requirements\",\n        \"Document Specs\",\n        \"Review Requirements\",\n        \"Design Phase\",\n        \"System Architecture\",\n        \"Database Design\",\n        \"UI/UX Design\",\n        \"Design Review\",\n        \"Development Phase\",\n        \"Backend Development\",\n        \"Frontend Development\",\n        \"API Integration\",\n        \"Code Review\",\n        \"Testing Phase\",\n        \"Unit Testing\",\n        \"Integration Testing\",\n        \"User Acceptance Testing\",\n    ],\n    \"start\": [\n        pd.Timestamp(\"2024-01-01\"),\n        pd.Timestamp(\"2024-01-01\"),\n        pd.Timestamp(\"2024-01-08\"),\n        pd.Timestamp(\"2024-01-15\"),\n        pd.Timestamp(\"2024-01-22\"),\n        pd.Timestamp(\"2024-01-22\"),\n        pd.Timestamp(\"2024-02-05\"),\n        pd.Timestamp(\"2024-02-05\"),\n        pd.Timestamp(\"2024-02-19\"),\n        pd.Timestamp(\"2024-02-26\"),\n        pd.Timestamp(\"2024-02-26\"),\n        pd.Timestamp(\"2024-03-11\"),\n        pd.Timestamp(\"2024-03-25\"),\n        pd.Timestamp(\"2024-04-08\"),\n        pd.Timestamp(\"2024-04-15\"),\n        pd.Timestamp(\"2024-04-15\"),\n        pd.Timestamp(\"2024-04-22\"),\n        pd.Timestamp(\"2024-04-29\"),\n    ],\n    \"end\": [\n        pd.Timestamp(\"2024-01-19\"),\n        pd.Timestamp(\"2024-01-07\"),\n        pd.Timestamp(\"2024-01-14\"),\n        pd.Timestamp(\"2024-01-19\"),\n        pd.Timestamp(\"2024-02-23\"),\n        pd.Timestamp(\"2024-02-02\"),\n        pd.Timestamp(\"2024-02-16\"),\n        pd.Timestamp(\"2024-02-16\"),\n        pd.Timestamp(\"2024-02-23\"),\n        pd.Timestamp(\"2024-04-12\"),\n        pd.Timestamp(\"2024-03-08\"),\n        pd.Timestamp(\"2024-03-22\"),\n        pd.Timestamp(\"2024-04-05\"),\n        pd.Timestamp(\"2024-04-12\"),\n        pd.Timestamp(\"2024-05-10\"),\n        pd.Timestamp(\"2024-04-21\"),\n        pd.Timestamp(\"2024-04-28\"),\n        pd.Timestamp(\"2024-05-10\"),\n    ],\n    \"group\": [\n        None,\n        \"Requirements Phase\",\n        \"Requirements Phase\",\n        \"Requirements Phase\",\n        None,\n        \"Design Phase\",\n        \"Design Phase\",\n        \"Design Phase\",\n        \"Design Phase\",\n        None,\n        \"Development Phase\",\n        \"Development Phase\",\n        \"Development Phase\",\n        \"Development Phase\",\n        None,\n        \"Testing Phase\",\n        \"Testing Phase\",\n        \"Testing Phase\",\n    ],\n    \"depends_on\": [\n        [],\n        [],\n        [\"Gather Requirements\"],\n        [\"Document Specs\"],\n        [\"Requirements Phase\"],\n        [],\n        [\"System Architecture\"],\n        [\"System Architecture\"],\n        [\"Database Design\", \"UI/UX Design\"],\n        [\"Design Phase\"],\n        [],\n        [\"Backend Development\"],\n        [\"Backend Development\", \"Frontend Development\"],\n        [\"API Integration\"],\n        [\"Development Phase\"],\n        [],\n        [\"Unit Testing\"],\n        [\"Integration Testing\"],\n    ],\n}\n\ndf = pd.DataFrame(tasks_data)\ndf[\"duration\"] = (df[\"end\"] - df[\"start\"]).dt.days\ntask_to_idx = {task: i for i, task in enumerate(df[\"task\"])}\n\n# Phase colors — Imprint palette positions 1-4\nphase_colors = {\n    \"Requirements Phase\": IMPRINT_PALETTE[0],  # #009E73 brand green\n    \"Design Phase\": IMPRINT_PALETTE[1],  # #C475FD lavender\n    \"Development Phase\": IMPRINT_PALETTE[2],  # #4467A3 blue\n    \"Testing Phase\": IMPRINT_PALETTE[3],  # #BD8233 ochre\n}\n\n# Critical path — iterative DP on dependency DAG (no function definitions)\ntask_successors = {t: [] for t in df[\"task\"]}\nfor _, r in df.iterrows():\n    for dep in r[\"depends_on\"]:\n        if dep in task_successors:\n            task_successors[dep].append(r[\"task\"])\n\n# Implicit edges: phase header → first children; last child → dependent phase header\nfor phase_name in phase_colors:\n    children = df[df[\"group\"] == phase_name]\n    for _, child in children.iterrows():\n        if not child[\"depends_on\"]:\n            task_successors[phase_name].append(child[\"task\"])\n    if not children.empty:\n        last_child = children.loc[children[\"end\"].idxmax(), \"task\"]\n        for _, r in df.iterrows():\n            if phase_name in r[\"depends_on\"] and r[\"task\"] in phase_colors:\n                task_successors[last_child].append(r[\"task\"])\n\n# Kahn's topological sort\nin_degree = dict.fromkeys(df[\"task\"], 0)\nfor t in df[\"task\"]:\n    for s in task_successors[t]:\n        if s in in_degree:\n            in_degree[s] += 1\n\nqueue = [t for t in df[\"task\"] if in_degree[t] == 0]\ntopo_order = []\nwhile queue:\n    node = queue.pop(0)\n    topo_order.append(node)\n    for s in task_successors[node]:\n        if s in in_degree:\n            in_degree[s] -= 1\n            if in_degree[s] == 0:\n                queue.append(s)\n\n# Longest path via DP — dist[t] = length of longest path ending at t\ndist = dict.fromkeys(df[\"task\"], 1)\nprev_node = dict.fromkeys(df[\"task\"])\nfor t in topo_order:\n    for s in task_successors[t]:\n        if dist[t] + 1 > dist[s]:\n            dist[s] = dist[t] + 1\n            prev_node[s] = t\n\nend_task = max(dist, key=dist.get)\ncritical_path = []\nnode = end_task\nwhile node is not None:\n    critical_path.append(node)\n    node = prev_node[node]\ncritical_set = set(critical_path)\n\n# Plot\nn_tasks = len(df)\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\nax.set_axisbelow(True)\n\nbar_height = 0.62\ngroup_bar_height = 0.38\n\n# Alternating row bands improve scanability across the wide timeline\nfor i in range(n_tasks):\n    if i % 2 == 0:\n        ax.axhspan(i - 0.5, i + 0.5, alpha=0.04, color=INK, zorder=0)\n\nfor _, row in df.iterrows():\n    task = row[\"task\"]\n    start = row[\"start\"]\n    duration = row[\"duration\"]\n    group = row[\"group\"]\n    y_pos = n_tasks - 1 - task_to_idx[task]\n    is_group = group is None and task in phase_colors\n    on_critical = task in critical_set\n\n    if is_group:\n        edge_clr = CRITICAL_COLOR if on_critical else INK_SOFT\n        edge_w = 2.0 if on_critical else 1.2\n        ax.barh(\n            y_pos,\n            duration,\n            left=start,\n            height=group_bar_height,\n            color=phase_colors[task],\n            alpha=0.95,\n            edgecolor=edge_clr,\n            linewidth=edge_w,\n            zorder=3,\n        )\n    else:\n        color = phase_colors[group] if group and group in phase_colors else IMPRINT_PALETTE[0]\n        edge_clr = CRITICAL_COLOR if on_critical else INK_SOFT\n        edge_w = 1.8 if on_critical else 0.5\n        ax.barh(\n            y_pos,\n            duration,\n            left=start,\n            height=bar_height,\n            color=color,\n            alpha=0.85 if on_critical else 0.70,\n            edgecolor=edge_clr,\n            linewidth=edge_w,\n            zorder=3,\n        )\n\n# Dependency arrows — offset endpoints into inter-bar gaps; alternate curvature for multi-predecessor tasks\nfor _, row in df.iterrows():\n    task = row[\"task\"]\n    y_pos = n_tasks - 1 - task_to_idx[task]\n    for dep_i, dep in enumerate(row[\"depends_on\"]):\n        if dep not in task_to_idx:\n            continue\n        dep_y = n_tasks - 1 - task_to_idx[dep]\n        dep_end = df[df[\"task\"] == dep].iloc[0][\"end\"]\n        dy = abs(y_pos - dep_y)\n        rad = 0.15 if dy <= 2 else (0.10 if dy <= 5 else 0.07)\n        is_crit = dep in critical_set and task in critical_set\n\n        # Vertical offset routes arrows through the gap between bars rather than crossing them\n        y_dir = 1 if dep_y > y_pos else -1\n        v_off = 0.20\n        # Alternate curvature for multi-predecessor cases to spread converging arrows apart\n        rad_sign = 1 if dep_i % 2 == 0 else -1\n        arrow = FancyArrowPatch(\n            (dep_end, dep_y - y_dir * v_off),\n            (row[\"start\"], y_pos + y_dir * v_off),\n            arrowstyle=\"-|>\",\n            color=CRITICAL_COLOR if is_crit else INK_SOFT,\n            linewidth=1.8 if is_crit else 1.0,\n            connectionstyle=f\"arc3,rad={rad_sign * rad}\",\n            alpha=0.85 if is_crit else 0.45,\n            mutation_scale=7,\n            zorder=5 if is_crit else 4,\n        )\n        ax.add_patch(arrow)\n\n# Y-axis labels — phase headers bold in INK, child tasks indented with wider margin in INK_SOFT\ntask_labels = df[\"task\"].tolist()[::-1]\nstyled_labels = [f\"    {lbl}\" if df[df[\"task\"] == lbl].iloc[0][\"group\"] is not None else lbl for lbl in task_labels]\nax.set_yticks(range(len(task_labels)))\nax.set_yticklabels(styled_labels, fontsize=6.5, color=INK_SOFT)\nfor i, lbl in enumerate(task_labels):\n    row_data = df[df[\"task\"] == lbl].iloc[0]\n    if row_data[\"group\"] is None and lbl in phase_colors:\n        tick_lbl = ax.get_yticklabels()[i]\n        tick_lbl.set_fontweight(\"bold\")\n        tick_lbl.set_color(INK)\n\n# X-axis date formatting — axes methods only (no plt.xticks)\nax.xaxis.set_major_formatter(mdates.DateFormatter(\"%b %d\"))\nax.xaxis.set_major_locator(mdates.WeekdayLocator(interval=2))\nax.tick_params(axis=\"x\", labelsize=8, colors=INK_SOFT, labelrotation=45)\nax.tick_params(axis=\"y\", length=0)  # hide tick marks, keep labels — cleaner alongside no left spine\nplt.setp(ax.get_xticklabels(), ha=\"right\")\n\n# Labels and title\ntitle = \"gantt-dependencies · python · matplotlib · anyplot.ai\"\ntitle_n = len(title)\ntitle_fs = max(8, round(12 * 67 / title_n)) if title_n > 67 else 12\nax.set_title(title, fontsize=title_fs, fontweight=\"medium\", color=INK, pad=8)\nax.set_xlabel(\"Timeline (2024)\", fontsize=10, color=INK)\nax.set_ylabel(\"Project Tasks\", fontsize=10, color=INK)\n\n# Grid — x-axis only; alternating row bands handle horizontal scanability\nax.grid(True, axis=\"x\", alpha=0.15, linewidth=0.6, color=INK)\n\n# Spines — remove top, right, and left for a clean open composition\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nax.spines[\"left\"].set_visible(False)\nax.spines[\"bottom\"].set_color(INK_SOFT)\n\n# Milestone marker at project completion\nproject_end = df[\"end\"].max()\nlast_y = n_tasks - 1 - task_to_idx[\"User Acceptance Testing\"]\nax.plot(\n    project_end,\n    last_y,\n    marker=\"D\",\n    markersize=7,\n    color=CRITICAL_COLOR,\n    markeredgecolor=INK,\n    markeredgewidth=0.8,\n    zorder=6,\n)\n\n# Legend\nlegend_patches = [\n    Patch(facecolor=clr, alpha=0.85, edgecolor=INK_SOFT, linewidth=0.5, label=phase)\n    for phase, clr in phase_colors.items()\n]\nlegend_patches += [\n    Line2D(\n        [0],\n        [0],\n        color=CRITICAL_COLOR,\n        linewidth=1.8,\n        alpha=0.85,\n        marker=\">\",\n        markersize=5,\n        markeredgecolor=CRITICAL_COLOR,\n        label=\"Critical Path\",\n    ),\n    Line2D(\n        [0],\n        [0],\n        color=INK_SOFT,\n        linewidth=1.0,\n        alpha=0.45,\n        marker=\">\",\n        markersize=5,\n        markeredgecolor=INK_SOFT,\n        label=\"Dependency\",\n    ),\n    Line2D(\n        [0],\n        [0],\n        color=CRITICAL_COLOR,\n        marker=\"D\",\n        markersize=6,\n        markeredgecolor=INK,\n        linestyle=\"None\",\n        label=\"Milestone\",\n    ),\n]\nleg = ax.legend(\n    handles=legend_patches, loc=\"lower right\", fontsize=7, framealpha=0.95, edgecolor=INK_SOFT, fancybox=False\n)\nif leg:\n    leg.get_frame().set_facecolor(ELEVATED_BG)\n    plt.setp(leg.get_texts(), color=INK_SOFT)\n\nax.set_xlim(df[\"start\"].min() - pd.Timedelta(days=3), df[\"end\"].max() + pd.Timedelta(days=3))\nfig.subplots_adjust(left=0.25, right=0.98, top=0.93, bottom=0.15)\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}