{"spec_id":"icicle-basic","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nicicle-basic: Basic Icicle Chart\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-13\n\"\"\"\n\nimport os\nimport sys\nimport xml.etree.ElementTree as ET\n\n\nsys.path.pop(0)\n\nimport cairosvg\nfrom pygal.style import Style\n\n\n# Theme tokens (see prompts/default-style-guide.md)\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\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# Okabe-Ito palette\nIMPRINT = (\n    \"#009E73\",  # bluish green (brand)\n    \"#C475FD\",  # vermillion\n    \"#4467A3\",  # blue\n    \"#BD8233\",  # reddish purple\n    \"#AE3030\",  # orange\n    \"#2ABCCD\",  # sky blue\n    \"#954477\",  # yellow\n)\n\n# Data: Organizational hierarchy (Company -> Divisions -> Departments -> Teams)\nhierarchy_data = [\n    (\"TechCorp\", None, 0),\n    (\"Engineering\", \"TechCorp\", 0),\n    (\"Operations\", \"TechCorp\", 0),\n    (\"Sales\", \"TechCorp\", 0),\n    (\"Backend\", \"Engineering\", 0),\n    (\"Frontend\", \"Engineering\", 0),\n    (\"DevOps\", \"Engineering\", 0),\n    (\"HR\", \"Operations\", 0),\n    (\"Finance\", \"Operations\", 0),\n    (\"APAC\", \"Sales\", 0),\n    (\"EMEA\", \"Sales\", 0),\n    (\"APIs\", \"Backend\", 45),\n    (\"Databases\", \"Backend\", 50),\n    (\"Services\", \"Backend\", 40),\n    (\"Web\", \"Frontend\", 35),\n    (\"Mobile\", \"Frontend\", 30),\n    (\"Infrastructure\", \"DevOps\", 55),\n    (\"Recruiting\", \"HR\", 25),\n    (\"Payroll\", \"HR\", 15),\n    (\"Accounting\", \"Finance\", 35),\n    (\"Planning\", \"Finance\", 25),\n    (\"Enterprise\", \"APAC\", 60),\n    (\"SMB\", \"APAC\", 40),\n    (\"EMEA_Enterprise\", \"EMEA\", 70),\n    (\"EMEA_SMB\", \"EMEA\", 50),\n]\n\n# Build tree structure\nnodes = {}\nchildren = {}\n\nfor name, parent, value in hierarchy_data:\n    nodes[name] = {\"name\": name, \"parent\": parent, \"value\": value}\n    if parent is not None:\n        if parent not in children:\n            children[parent] = []\n        children[parent].append(name)\n\n# Calculate total values bottom-up\nnode_depths = {\"TechCorp\": 0}\nqueue = [\"TechCorp\"]\ndepth_order = []\nwhile queue:\n    current = queue.pop(0)\n    depth_order.append(current)\n    if current in children:\n        for child in children[current]:\n            node_depths[child] = node_depths[current] + 1\n            queue.append(child)\n\nnode_values = {}\nfor node_name in reversed(depth_order):\n    if node_name not in children:\n        node_values[node_name] = nodes[node_name][\"value\"]\n    else:\n        node_values[node_name] = sum(node_values[child] for child in children[node_name])\n\n# Calculate positions (horizontal icicle layout)\npositions = {}\npositions[\"TechCorp\"] = {\"x_start\": 0, \"x_end\": 1, \"depth\": 0, \"value\": node_values[\"TechCorp\"]}\n\nfor node_name in depth_order:\n    if node_name in children:\n        pos = positions[node_name]\n        current_x = pos[\"x_start\"]\n        total_value = node_values[node_name]\n        for child in children[node_name]:\n            child_value = node_values[child]\n            child_width = (child_value / total_value) * (pos[\"x_end\"] - pos[\"x_start\"])\n            positions[child] = {\n                \"x_start\": current_x,\n                \"x_end\": current_x + child_width,\n                \"depth\": pos[\"depth\"] + 1,\n                \"value\": child_value,\n            }\n            current_x += child_width\n\nmax_depth = max(pos[\"depth\"] for pos in positions.values())\n\n# Chart dimensions\nWIDTH = 4800\nHEIGHT = 2700\nMARGIN_TOP = 140\nMARGIN_BOTTOM = 120\nMARGIN_LEFT = 60\nMARGIN_RIGHT = 200\nPLOT_WIDTH = WIDTH - MARGIN_LEFT - MARGIN_RIGHT\nPLOT_HEIGHT = HEIGHT - MARGIN_TOP - MARGIN_BOTTOM\n\n# Color by depth using Okabe-Ito subset with brand as primary\nDEPTH_COLORS = [IMPRINT[i % len(IMPRINT)] for i in range(max_depth + 1)]\n\n# Create pygal style for metadata extraction\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=22,\n    major_label_font_size=18,\n    legend_font_size=16,\n)\n\n# Build SVG manually (icicle requires custom rendering)\nsvg_ns = \"http://www.w3.org/2000/svg\"\nET.register_namespace(\"\", svg_ns)\n\nsvg_root = ET.Element(\"svg\", xmlns=svg_ns, width=str(WIDTH), height=str(HEIGHT), viewBox=f\"0 0 {WIDTH} {HEIGHT}\")\nsvg_root.set(\"style\", f\"background-color: {PAGE_BG};\")\n\n# Add title\ntitle_elem = ET.SubElement(svg_root, \"text\")\ntitle_elem.set(\"x\", str(WIDTH / 2))\ntitle_elem.set(\"y\", \"90\")\ntitle_elem.set(\"text-anchor\", \"middle\")\ntitle_elem.set(\"fill\", INK)\ntitle_elem.set(\"font-size\", \"28\")\ntitle_elem.set(\"font-family\", \"sans-serif\")\ntitle_elem.set(\"font-weight\", \"500\")\ntitle_elem.text = \"icicle-basic · pygal · anyplot.ai\"\n\n# Create main group for rectangles\ng = ET.SubElement(svg_root, \"g\")\ng.set(\"class\", \"icicle-chart\")\n\n# Draw rectangles\nrow_height = PLOT_HEIGHT / (max_depth + 1)\ngap = 2\n\nfor node_name, pos in positions.items():\n    depth = pos[\"depth\"]\n    x_start = pos[\"x_start\"]\n    x_end = pos[\"x_end\"]\n    width = x_end - x_start\n\n    # Calculate pixel positions\n    px_x = MARGIN_LEFT + x_start * PLOT_WIDTH\n    px_width = width * PLOT_WIDTH - gap\n    px_y = MARGIN_TOP + depth * row_height\n    px_height = row_height - gap\n\n    # Color by depth from Okabe-Ito\n    color = DEPTH_COLORS[depth % len(DEPTH_COLORS)]\n\n    # Create rectangle\n    rect = ET.SubElement(g, \"rect\")\n    rect.set(\"x\", f\"{px_x:.1f}\")\n    rect.set(\"y\", f\"{px_y:.1f}\")\n    rect.set(\"width\", f\"{max(0, px_width):.1f}\")\n    rect.set(\"height\", f\"{px_height:.1f}\")\n    rect.set(\"fill\", color)\n    rect.set(\"stroke\", PAGE_BG)\n    rect.set(\"stroke-width\", \"2\")\n\n    # Add tooltip\n    title = ET.SubElement(rect, \"title\")\n    title.text = f\"{node_name}: {pos['value']}\"\n\n    # Add label if rectangle is wide enough\n    if px_width > 70:\n        label = node_name.replace(\"_\", \" \")\n        max_chars = max(3, int(px_width / 24))\n        if len(label) > max_chars:\n            label = label[: max_chars - 2] + \"..\"\n\n        fontsize = min(22, max(14, int(px_width / 7)))\n\n        text = ET.SubElement(g, \"text\")\n        text.set(\"x\", f\"{px_x + px_width / 2:.1f}\")\n        text.set(\"y\", f\"{px_y + px_height / 2 + fontsize / 3:.1f}\")\n        text.set(\"text-anchor\", \"middle\")\n        text.set(\"dominant-baseline\", \"central\")\n        text.set(\"fill\", INK)\n        text.set(\"font-size\", str(fontsize))\n        text.set(\"font-family\", \"sans-serif\")\n        text.set(\"font-weight\", \"500\")\n        text.text = label\n\n# Add depth level legend on right\nlevel_labels = [\"Company\", \"Division\", \"Department\", \"Team\", \"Function\"]\nlabels_g = ET.SubElement(svg_root, \"g\")\nlabels_g.set(\"class\", \"level-labels\")\n\nfor depth in range(max_depth + 1):\n    y_pos = MARGIN_TOP + depth * row_height + row_height / 2\n    level_label = level_labels[depth] if depth < len(level_labels) else f\"Level {depth}\"\n\n    text = ET.SubElement(labels_g, \"text\")\n    text.set(\"x\", str(MARGIN_LEFT + PLOT_WIDTH + 30))\n    text.set(\"y\", f\"{y_pos + 6:.1f}\")\n    text.set(\"fill\", INK_SOFT)\n    text.set(\"font-size\", \"16\")\n    text.set(\"font-family\", \"sans-serif\")\n    text.text = level_label\n\n# Write SVG to file and render PNG\nsvg_output = ET.tostring(svg_root, encoding=\"unicode\")\n\nwith open(f\"plot-{THEME}.html\", \"w\") as f:\n    f.write(svg_output)\n\ncairosvg.svg2png(bytestring=svg_output.encode(\"utf-8\"), write_to=f\"plot-{THEME}.png\")\n"}