{"spec_id":"radar-innovation-timeline","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nradar-innovation-timeline: Innovation Radar with Time-Horizon Rings\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 85/100 | Updated: 2026-05-29\n\"\"\"\n\nimport math\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    coord_fixed,\n    element_blank,\n    element_rect,\n    element_text,\n    geom_path,\n    geom_point,\n    geom_polygon,\n    geom_segment,\n    geom_text,\n    ggplot,\n    guide_legend,\n    guides,\n    labs,\n    scale_alpha_manual,\n    scale_color_manual,\n    scale_fill_identity,\n    scale_size_manual,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n)\n\n\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\n\n# Imprint theme-adaptive chrome\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 categorical palette — hybrid-v3 sort\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\nANYPLOT_AMBER = \"#DDCC77\"\n\nnp.random.seed(42)\n\n# Layout: 270° arc open at bottom\nRINGS = [\"Adopt\", \"Trial\", \"Assess\", \"Hold\"]\nR_IN = [0.75, 2.25, 3.75, 5.25]\nR_OUT = [2.25, 3.75, 5.25, 6.75]\nR_MID = [1.5, 3.0, 4.5, 6.0]\n\n# Semantic ring fills from Imprint palette — green=ready, amber=caution, ochre=assess, red=hold\nRING_FILL_COLOR = {\n    \"Adopt\": IMPRINT[0],  # green — ready for adoption\n    \"Trial\": ANYPLOT_AMBER,  # amber — trial/caution\n    \"Assess\": IMPRINT[3],  # ochre — assess carefully\n    \"Hold\": IMPRINT[4],  # matte red — hold/avoid\n}\nRING_FILL_ALPHA = 0.18 if THEME == \"light\" else 0.28\n\nRING_SIZE = {\"Adopt\": 7, \"Trial\": 5.5, \"Assess\": 4.5, \"Hold\": 3.5}\nRING_ALPHA_PT = {\"Adopt\": 1.0, \"Trial\": 0.85, \"Assess\": 0.7, \"Hold\": 0.6}\n\n# Sectors: distinct from AI/Cloud/Security/DataEng per cross-library divergence requirement\nSECTORS = [\"AI & ML\", \"Sustainability\", \"Biotech\", \"Infrastructure\"]\n# Semantic Imprint assignments: green=nature, blue=tech, rose=health, ochre=industrial\nSEC_COLOR = {\n    \"Sustainability\": IMPRINT[0],  # #009E73 green — nature/environment\n    \"AI & ML\": IMPRINT[2],  # #4467A3 blue — digital/tech\n    \"Biotech\": IMPRINT[6],  # #954477 rose — wellness/health/life sciences\n    \"Infrastructure\": IMPRINT[3],  # #BD8233 ochre — industrial/construction\n}\n\nARC_TOTAL = 1.5 * math.pi  # 270°\nARC_START = -math.pi / 4  # -45°\nSEC_SPAN = ARC_TOTAL / len(SECTORS)\nSEC_START = {s: ARC_START + i * SEC_SPAN for i, s in enumerate(SECTORS)}\n\n# Innovation data: (name, sector, ring, angular_fraction)\ninnovations = [\n    # AI & ML — digital intelligence technologies\n    (\"RAG Pipelines\", \"AI & ML\", \"Adopt\", 0.20),\n    (\"LLM Inference Opt.\", \"AI & ML\", \"Adopt\", 0.85),\n    (\"Multimodal LLMs\", \"AI & ML\", \"Trial\", 0.35),\n    (\"AI Agent Frameworks\", \"AI & ML\", \"Trial\", 0.80),\n    (\"Causal AI Systems\", \"AI & ML\", \"Assess\", 0.35),\n    (\"Neural Arch. Search\", \"AI & ML\", \"Assess\", 0.75),\n    (\"Quantum ML\", \"AI & ML\", \"Hold\", 0.50),\n    # Sustainability — environmental & climate technologies\n    (\"Carbon Accounting SW\", \"Sustainability\", \"Adopt\", 0.30),\n    (\"Energy Analytics\", \"Sustainability\", \"Adopt\", 0.80),\n    (\"ESG Data Platforms\", \"Sustainability\", \"Trial\", 0.25),\n    (\"Green Code Standards\", \"Sustainability\", \"Trial\", 0.75),\n    (\"Carbon-Aware Compute\", \"Sustainability\", \"Assess\", 0.50),\n    (\"Atmospheric CO2 Tech\", \"Sustainability\", \"Hold\", 0.50),\n    # Biotech — life sciences & biology\n    (\"mRNA Therapeutics\", \"Biotech\", \"Adopt\", 0.30),\n    (\"CRISPR Diagnostics\", \"Biotech\", \"Adopt\", 0.80),\n    (\"Synthetic Biology\", \"Biotech\", \"Trial\", 0.20),\n    (\"Lab-Grown Proteins\", \"Biotech\", \"Trial\", 0.75),\n    (\"Organoid Platforms\", \"Biotech\", \"Assess\", 0.30),\n    (\"Xenotransplantation\", \"Biotech\", \"Assess\", 0.80),\n    (\"Age-Reversal Therapy\", \"Biotech\", \"Hold\", 0.50),\n    # Infrastructure — foundational computing platforms\n    (\"Platform Engineering\", \"Infrastructure\", \"Adopt\", 0.50),\n    (\"eBPF Observability\", \"Infrastructure\", \"Trial\", 0.25),\n    (\"GitOps at Scale\", \"Infrastructure\", \"Trial\", 0.75),\n    (\"Confidential Comput.\", \"Infrastructure\", \"Assess\", 0.30),\n    (\"Edge AI Chips\", \"Infrastructure\", \"Assess\", 0.75),\n    (\"Photonic Computing\", \"Infrastructure\", \"Hold\", 0.50),\n]\n\n# Compute polar point positions\nri_map = {r: i for i, r in enumerate(RINGS)}\nrows = []\nfor name, sector, ring, frac in innovations:\n    ri = ri_map[ring]\n    pad = SEC_SPAN * 0.15\n    angle = SEC_START[sector] + pad + frac * (SEC_SPAN - 2 * pad)\n    r = R_MID[ri] + np.random.uniform(-0.18, 0.18)\n    rows.append(\n        {\n            \"name\": name,\n            \"sector\": sector,\n            \"ring\": ring,\n            \"angle\": angle,\n            \"radius\": r,\n            \"x\": r * math.cos(angle),\n            \"y\": r * math.sin(angle),\n        }\n    )\n\ndf = pd.DataFrame(rows)\ndf[\"ring\"] = pd.Categorical(df[\"ring\"], categories=RINGS, ordered=True)\n\n# Ring fill polygons (annular wedges — outer arc then reversed inner arc)\narc_angles = np.linspace(ARC_START, ARC_START + ARC_TOTAL, 150)\nfill_rows = []\nfor i, rname in enumerate(RINGS):\n    fc = RING_FILL_COLOR[rname]\n    outer = [{\"x\": R_OUT[i] * math.cos(a), \"y\": R_OUT[i] * math.sin(a), \"ring_g\": rname, \"fc\": fc} for a in arc_angles]\n    inner = [\n        {\"x\": R_IN[i] * math.cos(a), \"y\": R_IN[i] * math.sin(a), \"ring_g\": rname, \"fc\": fc}\n        for a in reversed(arc_angles)\n    ]\n    fill_rows.extend(outer + inner)\nfill_df = pd.DataFrame(fill_rows)\n\n# Ring boundary arcs\ncirc_rows = [\n    {\"x\": rb * math.cos(a), \"y\": rb * math.sin(a), \"r\": rb} for rb in [0.75, 2.25, 3.75, 5.25, 6.75] for a in arc_angles\n]\ncirc_df = pd.DataFrame(circ_rows)\n\n# Sector dividing spokes\nspoke_angles = [ARC_START + i * SEC_SPAN for i in range(len(SECTORS) + 1)]\nspoke_df = pd.DataFrame(\n    [\n        {\"x1\": 0.75 * math.cos(a), \"y1\": 0.75 * math.sin(a), \"x2\": 6.75 * math.cos(a), \"y2\": 6.75 * math.sin(a)}\n        for a in spoke_angles\n    ]\n)\n\n# Sector header labels along outer edge\nslbl_df = pd.DataFrame(\n    [\n        {\"label\": s, \"x\": 7.8 * math.cos(SEC_START[s] + SEC_SPAN / 2), \"y\": 7.8 * math.sin(SEC_START[s] + SEC_SPAN / 2)}\n        for s in SECTORS\n    ]\n)\n\n# Ring labels in the bottom gap (270° open arc → 3π/2 angle)\ngap_angle = 3 * math.pi / 2\nrlbl_df = pd.DataFrame(\n    [\n        {\"label\": r, \"x\": R_MID[i] * math.cos(gap_angle), \"y\": R_MID[i] * math.sin(gap_angle)}\n        for i, r in enumerate(RINGS)\n    ]\n)\n\n# Innovation labels with text-width-aware collision avoidance\nlbl_offset = 0.55\nchar_w = 0.32  # estimated data units per character at geom_text size=3.8mm\nlabels = []\nfor _, row in df.iterrows():\n    lx = (row[\"radius\"] + lbl_offset) * math.cos(row[\"angle\"])\n    ly = (row[\"radius\"] + lbl_offset) * math.sin(row[\"angle\"])\n    w = len(row[\"name\"]) * char_w\n    x_min = lx if lx >= 0 else lx - w\n    x_max = (lx + w) if lx >= 0 else lx\n    labels.append({\"name\": row[\"name\"], \"x\": lx, \"y\": ly, \"sector\": row[\"sector\"], \"x_min\": x_min, \"x_max\": x_max})\n\n# Iterative nudge: push overlapping label bounding boxes apart vertically\nmin_sep = 0.90\nfor _ in range(40):\n    moved = False\n    for i in range(len(labels)):\n        for j in range(i + 1, len(labels)):\n            x_overlap = labels[i][\"x_max\"] > labels[j][\"x_min\"] and labels[j][\"x_max\"] > labels[i][\"x_min\"]\n            if x_overlap:\n                dy = labels[j][\"y\"] - labels[i][\"y\"]\n                if abs(dy) < min_sep:\n                    shift = (min_sep - abs(dy)) / 2\n                    labels[i][\"y\"] -= shift\n                    labels[j][\"y\"] += shift\n                    moved = True\n    if not moved:\n        break\n\nlbl_l_df = pd.DataFrame([lb for lb in labels if lb[\"x\"] >= 0])\nlbl_r_df = pd.DataFrame([lb for lb in labels if lb[\"x\"] < 0])\n\n# Build plot\nplot = (\n    ggplot()\n    + geom_polygon(aes(x=\"x\", y=\"y\", group=\"ring_g\", fill=\"fc\"), data=fill_df, size=0, alpha=RING_FILL_ALPHA)\n    + scale_fill_identity()\n    + geom_path(aes(x=\"x\", y=\"y\", group=\"r\"), data=circ_df, color=INK_MUTED, size=0.3)\n    + geom_segment(aes(x=\"x1\", y=\"y1\", xend=\"x2\", yend=\"y2\"), data=spoke_df, color=INK_MUTED, size=0.3)\n    + geom_point(aes(x=\"x\", y=\"y\", color=\"sector\", size=\"ring\", alpha=\"ring\"), data=df)\n    + scale_size_manual(values=RING_SIZE)\n    + scale_alpha_manual(values=RING_ALPHA_PT)\n    + geom_text(\n        aes(x=\"x\", y=\"y\", label=\"name\", color=\"sector\"),\n        data=lbl_l_df,\n        size=3.8,\n        ha=\"left\",\n        va=\"center\",\n        show_legend=False,\n    )\n    + geom_text(\n        aes(x=\"x\", y=\"y\", label=\"name\", color=\"sector\"),\n        data=lbl_r_df,\n        size=3.8,\n        ha=\"right\",\n        va=\"center\",\n        show_legend=False,\n    )\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=slbl_df, size=4.0, fontweight=\"bold\", color=INK)\n    + geom_text(\n        aes(x=\"x\", y=\"y\", label=\"label\"), data=rlbl_df, size=3.5, fontweight=\"bold\", color=INK_SOFT, ha=\"center\"\n    )\n    + scale_color_manual(values=SEC_COLOR, name=\"Category\")\n    + guides(color=guide_legend(override_aes={\"size\": 5}), size=False, alpha=False)\n    + coord_fixed(ratio=1)\n    + scale_x_continuous(limits=(-9.5, 9.5))\n    + scale_y_continuous(limits=(-7.5, 8.5))\n    + labs(\n        title=\"radar-innovation-timeline · python · plotnine · anyplot.ai\",\n        subtitle=\"Inner rings → near-term adoption  ·  Outer rings → future exploration\",\n    )\n    + theme(\n        figure_size=(6, 6),\n        plot_title=element_text(size=12, ha=\"center\", weight=\"bold\", color=INK),\n        plot_subtitle=element_text(size=8, ha=\"center\", color=INK_SOFT, style=\"italic\"),\n        legend_title=element_text(size=9, color=INK),\n        legend_text=element_text(size=8, color=INK_SOFT),\n        legend_position=(0.13, 0.10),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_key=element_rect(fill=ELEVATED_BG),\n        axis_title=element_blank(),\n        axis_text=element_blank(),\n        axis_ticks=element_blank(),\n        axis_line=element_blank(),\n        panel_grid_major=element_blank(),\n        panel_grid_minor=element_blank(),\n        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        plot_margin=0.02,\n    )\n)\n\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=6, height=6, units=\"in\")\n"}