{"spec_id":"gantt-basic","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\ngantt-basic: Basic Gantt Chart\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-10\n\"\"\"\n\nimport os\nimport sys\nfrom datetime import date\n\n\n# Ensure venv site-packages is at the front of sys.path to avoid shadowing\nvenv_site = \"/home/runner/work/anyplot/anyplot/.venv/lib/python3.13/site-packages\"\nsys.path.insert(0, venv_site)\n\nimport cairosvg\nimport pygal\nfrom pygal.style import Style\n\n\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\nIMPRINT = (\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\")\n\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=IMPRINT,\n    title_font_size=28,\n    label_font_size=18,\n    major_label_font_size=16,\n    legend_font_size=16,\n    value_font_size=14,\n    tooltip_font_size=14,\n)\n\ntasks = [\n    (\"Requirements Analysis\", \"Planning\", date(2025, 1, 6), date(2025, 1, 17), True),\n    (\"System Design\", \"Planning\", date(2025, 1, 13), date(2025, 1, 31), True),\n    (\"Database Design\", \"Design\", date(2025, 1, 27), date(2025, 2, 7), False),\n    (\"UI/UX Design\", \"Design\", date(2025, 1, 20), date(2025, 2, 14), False),\n    (\"Backend Development\", \"Development\", date(2025, 2, 3), date(2025, 3, 14), True),\n    (\"Frontend Development\", \"Development\", date(2025, 2, 10), date(2025, 3, 21), True),\n    (\"API Integration\", \"Development\", date(2025, 3, 3), date(2025, 3, 21), False),\n    (\"Unit Testing\", \"Testing\", date(2025, 3, 10), date(2025, 3, 28), False),\n    (\"Integration Testing\", \"Testing\", date(2025, 3, 24), date(2025, 4, 11), True),\n    (\"User Acceptance Testing\", \"Testing\", date(2025, 4, 7), date(2025, 4, 18), True),\n    (\"Documentation\", \"Deployment\", date(2025, 4, 7), date(2025, 4, 18), False),\n    (\"Deployment\", \"Deployment\", date(2025, 4, 14), date(2025, 4, 25), True),\n]\n\nreference_date = date(2025, 1, 1)\n\ncategory_colors = {\n    \"Planning\": IMPRINT[0],\n    \"Design\": IMPRINT[1],\n    \"Development\": IMPRINT[2],\n    \"Testing\": IMPRINT[3],\n    \"Deployment\": IMPRINT[4],\n}\n\nall_dates = []\nfor _, _, start, end, _ in tasks:\n    all_dates.extend([start, end])\nmin_date = min(all_dates)\nmax_date = max(all_dates)\n\nstart_day = (min_date - reference_date).days - 3\nend_day = (max_date - reference_date).days + 3\nday_range = end_day - start_day\n\ntask_labels = [t[0] for t in tasks]\nnum_tasks = len(tasks)\n\nchart = pygal.HorizontalBar(\n    width=4800,\n    height=2700,\n    style=custom_style,\n    title=\"Software Development Timeline · gantt-basic · pygal · anyplot.ai\",\n    show_legend=True,\n    legend_at_bottom=True,\n    legend_box_size=24,\n    print_values=False,\n    show_y_guides=False,\n    show_x_guides=False,\n    show_x_labels=False,\n    margin=60,\n    spacing=20,\n    range=(0, 100),\n)\n\nchart.x_labels = task_labels\n\ncategories_in_order = [\"Planning\", \"Design\", \"Development\", \"Testing\", \"Deployment\"]\nfor cat in categories_in_order:\n    chart.add(cat, [None] * num_tasks)\n\nsvg_string = chart.render().decode(\"utf-8\")\n\nPLOT_ORIGIN_X = 466\nPLOT_ORIGIN_Y = 140\nPLOT_WIDTH = 4273.6\nPLOT_HEIGHT = 2368.0\n\nPLOT_LEFT = PLOT_ORIGIN_X\nPLOT_TOP = PLOT_ORIGIN_Y\n\nrow_height = PLOT_HEIGHT / num_tasks\nbar_height = row_height * 0.55\n\nbar_elements = []\n\nfor i, (task_name, category, start, end, on_critical_path) in enumerate(tasks):\n    start_day_val = (start - reference_date).days\n    end_day_val = (end - reference_date).days\n\n    x_start = PLOT_LEFT + ((start_day_val - start_day) / day_range) * PLOT_WIDTH\n    x_end = PLOT_LEFT + ((end_day_val - start_day) / day_range) * PLOT_WIDTH\n    width = x_end - x_start\n\n    reversed_i = num_tasks - 1 - i\n    y_center = PLOT_TOP + (reversed_i + 0.5) * row_height\n    y_top = y_center - bar_height / 2\n\n    color = category_colors[category]\n    duration = (end - start).days\n\n    stroke_style = \"\"\n    if on_critical_path:\n        stroke_style = f' stroke=\"{INK}\" stroke-width=\"2.5\"'\n\n    bar_elements.append(\n        f'<rect x=\"{x_start:.1f}\" y=\"{y_top:.1f}\" width=\"{width:.1f}\" '\n        f'height=\"{bar_height:.1f}\" fill=\"{color}\" rx=\"6\" ry=\"6\" opacity=\"0.9\"{stroke_style}>'\n        f\"<title>{task_name}&#10;{start.strftime('%b %d')} - {end.strftime('%b %d')} \"\n        f\"({duration} days){' • CRITICAL PATH' if on_critical_path else ''}</title></rect>\"\n    )\n\nmilestone_dates = [\n    (date(2025, 1, 31), \"Planning Complete\"),\n    (date(2025, 2, 14), \"Design Complete\"),\n    (date(2025, 3, 21), \"Development Complete\"),\n    (date(2025, 4, 11), \"Testing Complete\"),\n]\n\nmilestone_markers = []\nfor milestone_date, milestone_label in milestone_dates:\n    day_val = (milestone_date - reference_date).days\n    if start_day <= day_val <= end_day:\n        x_pos = PLOT_LEFT + ((day_val - start_day) / day_range) * PLOT_WIDTH\n        milestone_markers.append(\n            f'<line x1=\"{x_pos:.1f}\" y1=\"{PLOT_TOP - 15}\" x2=\"{x_pos:.1f}\" '\n            f'y2=\"{PLOT_TOP}\" stroke=\"{INK}\" stroke-width=\"2.5\" opacity=\"0.6\"/>'\n        )\n        milestone_markers.append(f'<circle cx=\"{x_pos:.1f}\" cy=\"{PLOT_TOP - 25}\" r=\"4\" fill=\"{INK}\" opacity=\"0.6\"/>')\n        milestone_markers.append(\n            f'<text x=\"{x_pos:.1f}\" y=\"{PLOT_TOP - 35}\" '\n            f'font-family=\"Consolas, monospace\" font-size=\"12\" fill=\"{INK}\" opacity=\"0.6\" '\n            f'text-anchor=\"middle\">{milestone_label}</text>'\n        )\n\nmonth_markers = []\nfor month in range(1, 5):\n    month_date = date(2025, month, 1)\n    day_val = (month_date - reference_date).days\n    if start_day <= day_val <= end_day:\n        x_pos = PLOT_LEFT + ((day_val - start_day) / day_range) * PLOT_WIDTH\n        month_name = month_date.strftime(\"%b\")\n        month_markers.append(\n            f'<line x1=\"{x_pos:.1f}\" y1=\"{PLOT_TOP}\" x2=\"{x_pos:.1f}\" '\n            f'y2=\"{PLOT_TOP + PLOT_HEIGHT}\" stroke=\"{INK_MUTED}\" stroke-width=\"2\" '\n            f'stroke-dasharray=\"8,4\"/>'\n        )\n        month_markers.append(\n            f'<text x=\"{x_pos:.1f}\" y=\"{PLOT_TOP + PLOT_HEIGHT + 40}\" '\n            f'font-family=\"Consolas, monospace\" font-size=\"18\" fill=\"{INK_MUTED}\" '\n            f'text-anchor=\"middle\">{month_name} 1</text>'\n        )\n\nmonth_markers.append(\n    f'<text x=\"{PLOT_LEFT + PLOT_WIDTH / 2}\" y=\"{PLOT_TOP + PLOT_HEIGHT + 90}\" '\n    f'font-family=\"Consolas, monospace\" font-size=\"22\" fill=\"{INK_MUTED}\" '\n    f'text-anchor=\"middle\">Timeline (2025)</text>'\n)\n\nall_elements = \"\\n\".join(bar_elements + milestone_markers + month_markers)\nsvg_output = svg_string.replace(\"</svg>\", f\"{all_elements}\\n</svg>\")\n\nsvg_output = svg_output.replace(\">No data<\", \"><\")\n\nwith open(f\"plot-{THEME}.html\", \"w\") as f:\n    f.write(svg_output)\n\ncairosvg.svg2png(bytestring=svg_output.encode(), write_to=f\"plot-{THEME}.png\")\n"}