{"spec_id":"radar-innovation-timeline","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nradar-innovation-timeline: Innovation Radar with Time-Horizon Rings\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 73/100 | Updated: 2026-05-29\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent this file (pygal.py) from shadowing the pygal package on sys.path\n_this_dir = os.path.abspath(os.path.dirname(__file__))\nsys.path[:] = [p for p in sys.path if os.path.realpath(p or \".\") != _this_dir]\n\nimport pygal\nfrom pygal.style import Style\n\n\n# Theme tokens\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\n# Subtle ring zone fill colors — drawn first (positions 0–3 in color_sequence),\n# then Imprint sector colors at positions 4–7.\n# Light theme: warm tints graduating from pale green (Adopt) to off-white (Hold)\n# Dark theme: dark tints graduating from dark green to near-background\nif THEME == \"light\":\n    zone_colors = (\"#C8EBE0\", \"#E2D8F5\", \"#D4DCEA\", \"#EDEAE2\")  # Adopt/Trial/Assess/Hold\nelse:\n    zone_colors = (\"#1A3028\", \"#26203A\", \"#1E2430\", \"#252420\")  # Adopt/Trial/Assess/Hold\n\n# Imprint palette — first 4 positions mapped to sectors (positions 4–7 in full palette)\nsectors = [\"Data Engineering\", \"Cloud Platforms\", \"Security\", \"DevOps\"]\nsector_colors_tuple = (\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\")\nsector_colors = dict(zip(sectors, sector_colors_tuple, strict=False))\nring_values = {\"Adopt\": 1, \"Trial\": 2, \"Assess\": 3, \"Hold\": 4}\nrings_ordered = [\"Adopt\", \"Trial\", \"Assess\", \"Hold\"]\n\n# Data — 24 innovations across 4 sectors and 4 time-horizon rings\ninnovations = [\n    (\"dbt Core\", \"Data Engineering\", \"Adopt\"),\n    (\"Apache Iceberg\", \"Data Engineering\", \"Adopt\"),\n    (\"Kafka Streams\", \"Data Engineering\", \"Trial\"),\n    (\"Delta Lake\", \"Data Engineering\", \"Trial\"),\n    (\"Vector Databases\", \"Data Engineering\", \"Assess\"),\n    (\"Arrow Flight RPC\", \"Data Engineering\", \"Hold\"),\n    (\"Service Mesh\", \"Cloud Platforms\", \"Adopt\"),\n    (\"eBPF Monitoring\", \"Cloud Platforms\", \"Trial\"),\n    (\"Green Cloud APIs\", \"Cloud Platforms\", \"Trial\"),\n    (\"Multi-Cloud Orch.\", \"Cloud Platforms\", \"Assess\"),\n    (\"Serverless Edge\", \"Cloud Platforms\", \"Assess\"),\n    (\"Quantum Cloud\", \"Cloud Platforms\", \"Hold\"),\n    (\"Zero Trust Network\", \"Security\", \"Adopt\"),\n    (\"AI-Native SOC\", \"Security\", \"Trial\"),\n    (\"Supply Chain Sec.\", \"Security\", \"Trial\"),\n    (\"Post-Quantum Crypto\", \"Security\", \"Assess\"),\n    (\"Confidential Compute\", \"Security\", \"Assess\"),\n    (\"Homomorphic Encrypt.\", \"Security\", \"Hold\"),\n    (\"Platform Engineering\", \"DevOps\", \"Adopt\"),\n    (\"AI Code Assistants\", \"DevOps\", \"Adopt\"),\n    (\"FinOps Automation\", \"DevOps\", \"Trial\"),\n    (\"Chaos Engineering\", \"DevOps\", \"Trial\"),\n    (\"Internal Dev Portal\", \"DevOps\", \"Assess\"),\n    (\"GitOps at Scale\", \"DevOps\", \"Hold\"),\n]\n\n# Sort items within each sector by ring (near-term first for visual clarity)\nsector_items = {s: [] for s in sectors}\nfor name, sector, ring in innovations:\n    sector_items[sector].append((name, ring))\nfor s in sectors:\n    sector_items[s].sort(key=lambda x: ring_values[x[1]])\n\n# Angular layout: [header_slot, item_slots x6, gap_slot] per sector = 8 slots × 4 = 32\nslots_per_sector = 8\ntotal_slots = len(sectors) * slots_per_sector\n\nx_labels = []\nitem_placements = []\nfor sector_idx, sector in enumerate(sectors):\n    start_slot = sector_idx * slots_per_sector\n    x_labels.append(sector)  # Sector header shown as major label\n    for i, (name, ring) in enumerate(sector_items[sector]):\n        x_labels.append(name)\n        item_placements.append((name, sector, ring, start_slot + 1 + i))\n    x_labels.append(\"\")  # Gap slot between sectors\n\n# Build series: one per sector, None for positions belonging to other sectors\nseries_data = {s: [None] * total_slots for s in sectors}\nfor name, sector, ring, slot in item_placements:\n    series_data[sector][slot] = {\"value\": ring_values[ring], \"label\": name}\n\n# Ring zone background fill series — all spokes set to ring boundary value.\n# Drawn outermost-first (Hold=4) so each inner fill layers on top, creating\n# distinct concentric bands: Hold → Assess → Trial → Adopt from outer to inner.\nring_zone_data = {ring: [ring_values[ring]] * total_slots for ring in rings_ordered}\n\n# Full color sequence: zone fills first (4), then sector data colors (4)\ncolor_sequence = zone_colors + sector_colors_tuple\n\ntitle_str = \"radar-innovation-timeline · python · pygal · anyplot.ai\"\nn_chars = len(title_str)\ntitle_font_size = round(66 * 67 / n_chars) if n_chars > 67 else 66\ntitle_font_size = max(title_font_size, 44)\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=color_sequence,\n    title_font_size=title_font_size,\n    label_font_size=56,\n    major_label_font_size=56,\n    legend_font_size=44,\n    value_font_size=36,\n    opacity=0.35,  # Low opacity so zone fills are subtle background tints\n    opacity_hover=0.7,\n    stroke_width=2.5,\n)\n\nchart = pygal.Radar(\n    width=2400,\n    height=2400,\n    style=custom_style,\n    title=title_str,\n    show_legend=True,\n    legend_at_bottom=True,\n    legend_at_bottom_columns=4,\n    legend_box_size=30,\n    fill=True,  # fill=True enables zone fills; sector series dots still show on top\n    stroke=False,\n    show_dots=True,\n    dots_size=22,\n    show_y_guides=True,\n    show_x_guides=False,\n    range=(0, 5),\n    inner_radius=0.12,\n    y_labels=[\n        {\"value\": 1, \"label\": \"Adopt\"},\n        {\"value\": 2, \"label\": \"Trial\"},\n        {\"value\": 3, \"label\": \"Assess\"},\n        {\"value\": 4, \"label\": \"Hold\"},\n    ],\n    show_minor_x_labels=True,\n    x_labels_major=sectors,\n    x_label_rotation=0,\n    margin_bottom=80,\n    margin_left=80,\n    margin_right=80,\n    margin_top=60,\n    truncate_label=12,\n)\n\nchart.x_labels = x_labels\n\n# Add ring zone fills first (drawn behind sector data) — outermost (Hold) to innermost (Adopt)\nfor ring in reversed(rings_ordered):\n    chart.add(ring, ring_zone_data[ring])\n\n# Add sector data series on top of zone fills\nfor sector in sectors:\n    chart.add(sector, series_data[sector])\n\nchart.render_to_png(f\"plot-{THEME}.png\")\nchart.render_to_file(f\"plot-{THEME}.html\")\n"}