{"spec_id":"radar-multi","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nradar-multi: Multi-Series Radar Chart\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 85/100 | Updated: 2026-05-07\n\"\"\"\n\nimport math\nimport os\nimport sys\n\n\n# Ensure we import plotnine module, not this file\nsys.path = [p for p in sys.path if p != os.path.dirname(__file__)]\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    element_blank,\n    element_rect,\n    element_text,\n    geom_line,\n    geom_point,\n    geom_polygon,\n    geom_text,\n    ggplot,\n    labs,\n    scale_color_manual,\n    scale_fill_manual,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n)\n\n\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\"\n\n# Data - Product comparison across key attributes\ncategories = [\"Price\", \"Quality\", \"Durability\", \"Support\", \"Features\", \"Design\"]\nn = len(categories)\n\n# Four products for comparison (scale 0-100)\nproducts = {\n    \"Product A\": [85, 90, 75, 80, 70, 85],\n    \"Product B\": [70, 75, 90, 65, 85, 70],\n    \"Product C\": [95, 60, 70, 90, 75, 60],\n    \"Product D\": [60, 80, 85, 75, 90, 80],\n}\n\n# Okabe-Ito palette - first series is always #009E73\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n\n# Create angles for each category (evenly spaced around circle)\nangles = [i * 2 * math.pi / n for i in range(n)]\n\n# Build dataframe with x,y coordinates for polar plotting\n# For radar chart, we need to close each polygon by repeating first point\ndata_rows = []\nfor series_name, values in products.items():\n    for i, (cat, val, angle) in enumerate(zip(categories, values, angles, strict=True)):\n        data_rows.append({\"category\": cat, \"value\": val, \"angle\": angle, \"series\": series_name, \"order\": i})\n    # Close the polygon\n    data_rows.append(\n        {\"category\": categories[0], \"value\": values[0], \"angle\": angles[0], \"series\": series_name, \"order\": n}\n    )\n\ndf = pd.DataFrame(data_rows)\n\n# Convert to cartesian coordinates for plotting\ndf[\"x\"] = df[\"value\"] * np.cos(df[\"angle\"] - math.pi / 2)\ndf[\"y\"] = df[\"value\"] * np.sin(df[\"angle\"] - math.pi / 2)\n\n# Create gridlines data (circles at 20, 40, 60, 80, 100)\ngrid_rows = []\ngrid_angles = np.linspace(0, 2 * math.pi, 101)\nfor radius in [20, 40, 60, 80, 100]:\n    for angle in grid_angles:\n        grid_rows.append(\n            {\"x\": radius * math.cos(angle - math.pi / 2), \"y\": radius * math.sin(angle - math.pi / 2), \"radius\": radius}\n        )\n\ngrid_df = pd.DataFrame(grid_rows)\n\n# Create axis lines (spokes)\nspoke_rows = []\nfor angle in angles:\n    spoke_rows.append({\"x\": 0, \"y\": 0, \"angle_group\": angle})\n    spoke_rows.append(\n        {\"x\": 105 * math.cos(angle - math.pi / 2), \"y\": 105 * math.sin(angle - math.pi / 2), \"angle_group\": angle}\n    )\n\nspoke_df = pd.DataFrame(spoke_rows)\n\n# Create axis labels data (positioned just outside the chart)\nlabel_rows = []\nfor cat, angle in zip(categories, angles, strict=True):\n    label_rows.append(\n        {\"label\": cat, \"x\": 120 * math.cos(angle - math.pi / 2), \"y\": 120 * math.sin(angle - math.pi / 2)}\n    )\n\nlabel_df = pd.DataFrame(label_rows)\n\n# Plot\nplot = (\n    ggplot()\n    # Gridlines (circles)\n    + geom_line(\n        aes(x=\"x\", y=\"y\", group=\"radius\"), data=grid_df, color=INK_SOFT, size=0.5, alpha=0.15, linetype=\"dashed\"\n    )\n    # Spokes\n    + geom_line(aes(x=\"x\", y=\"y\", group=\"angle_group\"), data=spoke_df, color=INK_SOFT, size=0.5, alpha=0.15)\n    # Filled polygons for each series (with transparency for overlap visibility)\n    + geom_polygon(aes(x=\"x\", y=\"y\", fill=\"series\", group=\"series\"), data=df, alpha=0.2)\n    # Lines connecting points\n    + geom_line(aes(x=\"x\", y=\"y\", color=\"series\", group=\"series\"), data=df, size=1.5)\n    # Points at each vertex (exclude closing points)\n    + geom_point(aes(x=\"x\", y=\"y\", color=\"series\"), data=df[df[\"order\"] < n], size=5)\n    # Category labels\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=label_df, size=5, color=INK)\n    # Apply Okabe-Ito colors\n    + scale_fill_manual(values=IMPRINT)\n    + scale_color_manual(values=IMPRINT)\n    # Axis scaling\n    + scale_x_continuous(limits=(-150, 150))\n    + scale_y_continuous(limits=(-150, 150))\n    # Labels and title\n    + labs(title=\"radar-multi · plotnine · anyplot.ai\", fill=\"Product\", color=\"Product\")\n    # Theme for clean radar appearance with theme-adaptive colors\n    + theme(\n        figure_size=(12, 12),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        plot_title=element_text(size=24, color=INK),\n        legend_background=element_rect(fill=PAGE_BG, color=INK_SOFT),\n        legend_title=element_text(size=18, color=INK),\n        legend_text=element_text(size=16, color=INK_SOFT),\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    )\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=300)\n"}