{"spec_id":"hive-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nhive-basic: Basic Hive Plot\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 95/100 | Updated: 2026-05-07\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    coord_fixed,\n    element_rect,\n    element_text,\n    geom_point,\n    geom_segment,\n    geom_text,\n    ggplot,\n    labs,\n    scale_color_manual,\n    theme,\n    theme_void,\n    xlim,\n    ylim,\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\"\nBRAND = \"#009E73\"\nCOLOR_2 = \"#C475FD\"\nCOLOR_3 = \"#4467A3\"\n\nnp.random.seed(42)\n\nnodes = pd.DataFrame(\n    {\n        \"id\": [\n            \"auth\",\n            \"db\",\n            \"core\",\n            \"session\",\n            \"kernel\",\n            \"runtime\",\n            \"engine\",\n            \"cache\",\n            \"logger\",\n            \"config\",\n            \"validator\",\n            \"crypto\",\n            \"parser\",\n            \"queue\",\n            \"api\",\n            \"web\",\n            \"cli\",\n            \"router\",\n            \"http\",\n            \"grpc\",\n            \"websocket\",\n        ],\n        \"category\": [\n            \"core\",\n            \"core\",\n            \"core\",\n            \"core\",\n            \"core\",\n            \"core\",\n            \"core\",\n            \"utility\",\n            \"utility\",\n            \"utility\",\n            \"utility\",\n            \"utility\",\n            \"utility\",\n            \"utility\",\n            \"interface\",\n            \"interface\",\n            \"interface\",\n            \"interface\",\n            \"interface\",\n            \"interface\",\n            \"interface\",\n        ],\n        \"degree\": [8, 7, 9, 6, 5, 4, 6, 5, 6, 4, 5, 4, 3, 4, 6, 5, 4, 5, 5, 3, 4],\n    }\n)\n\nedges = pd.DataFrame(\n    {\n        \"source\": [\n            \"api\",\n            \"api\",\n            \"api\",\n            \"web\",\n            \"web\",\n            \"web\",\n            \"cli\",\n            \"cli\",\n            \"auth\",\n            \"auth\",\n            \"auth\",\n            \"db\",\n            \"db\",\n            \"cache\",\n            \"logger\",\n            \"config\",\n            \"validator\",\n            \"core\",\n            \"core\",\n            \"core\",\n            \"router\",\n            \"router\",\n            \"session\",\n            \"session\",\n            \"http\",\n            \"crypto\",\n            \"grpc\",\n            \"websocket\",\n            \"kernel\",\n            \"runtime\",\n        ],\n        \"target\": [\n            \"auth\",\n            \"db\",\n            \"logger\",\n            \"auth\",\n            \"session\",\n            \"router\",\n            \"config\",\n            \"logger\",\n            \"db\",\n            \"crypto\",\n            \"session\",\n            \"cache\",\n            \"logger\",\n            \"logger\",\n            \"config\",\n            \"validator\",\n            \"logger\",\n            \"db\",\n            \"cache\",\n            \"logger\",\n            \"http\",\n            \"auth\",\n            \"cache\",\n            \"crypto\",\n            \"parser\",\n            \"parser\",\n            \"auth\",\n            \"session\",\n            \"runtime\",\n            \"engine\",\n        ],\n    }\n)\n\naxis_angles = {\"core\": 90, \"utility\": 210, \"interface\": 330}\naxis_colors = {\"core\": BRAND, \"utility\": COLOR_2, \"interface\": COLOR_3}\n\nmax_degree = nodes[\"degree\"].max()\n\nnodes_by_category = {}\nfor cat in [\"core\", \"utility\", \"interface\"]:\n    cat_nodes = nodes[nodes[\"category\"] == cat].sort_values(\"degree\", ascending=False).reset_index(drop=True)\n    nodes_by_category[cat] = cat_nodes\n\npositions = []\nfor cat, cat_nodes in nodes_by_category.items():\n    angle_deg = axis_angles[cat]\n    angle_rad = np.radians(angle_deg)\n\n    for _idx, row in cat_nodes.iterrows():\n        base_radius = 0.25 + (row[\"degree\"] / max_degree) * 0.70\n\n        x = base_radius * np.cos(angle_rad)\n        y = base_radius * np.sin(angle_rad)\n\n        node_size = 6 + (row[\"degree\"] / max_degree) * 16\n\n        positions.append(\n            {\n                \"id\": row[\"id\"],\n                \"x\": x,\n                \"y\": y,\n                \"category\": row[\"category\"],\n                \"degree\": row[\"degree\"],\n                \"node_size\": node_size,\n            }\n        )\n\nnode_positions = pd.DataFrame(positions)\n\naxis_lines = []\nfor cat, angle in axis_angles.items():\n    angle_rad = np.radians(angle)\n    axis_lines.append(\n        {\"x\": 0, \"y\": 0, \"xend\": 1.0 * np.cos(angle_rad), \"yend\": 1.0 * np.sin(angle_rad), \"category\": cat}\n    )\naxis_df = pd.DataFrame(axis_lines)\n\nedge_data = []\nfor _, row in edges.iterrows():\n    src_match = node_positions[node_positions[\"id\"] == row[\"source\"]]\n    tgt_match = node_positions[node_positions[\"id\"] == row[\"target\"]]\n\n    if len(src_match) == 0 or len(tgt_match) == 0:\n        continue\n\n    src_pos = src_match.iloc[0]\n    tgt_pos = tgt_match.iloc[0]\n\n    src_cat = src_pos[\"category\"]\n    tgt_cat = tgt_pos[\"category\"]\n\n    if src_cat == tgt_cat:\n        mid_factor = 0.3\n    else:\n        mid_factor = 0.15\n\n    ctrl_x = mid_factor * (src_pos[\"x\"] + tgt_pos[\"x\"]) / 2\n    ctrl_y = mid_factor * (src_pos[\"y\"] + tgt_pos[\"y\"]) / 2\n\n    n_points = 20\n    for i in range(n_points):\n        t0 = i / n_points\n        t1 = (i + 1) / n_points\n\n        x0 = (1 - t0) ** 2 * src_pos[\"x\"] + 2 * (1 - t0) * t0 * ctrl_x + t0**2 * tgt_pos[\"x\"]\n        y0 = (1 - t0) ** 2 * src_pos[\"y\"] + 2 * (1 - t0) * t0 * ctrl_y + t0**2 * tgt_pos[\"y\"]\n        x1 = (1 - t1) ** 2 * src_pos[\"x\"] + 2 * (1 - t1) * t1 * ctrl_x + t1**2 * tgt_pos[\"x\"]\n        y1 = (1 - t1) ** 2 * src_pos[\"y\"] + 2 * (1 - t1) * t1 * ctrl_y + t1**2 * tgt_pos[\"y\"]\n\n        edge_data.append(\n            {\n                \"x\": x0,\n                \"y\": y0,\n                \"xend\": x1,\n                \"yend\": y1,\n                \"src_cat\": src_cat,\n                \"tgt_cat\": tgt_cat,\n                \"edge_color\": axis_colors[src_cat],\n            }\n        )\n\nedge_df = pd.DataFrame(edge_data)\n\naxis_labels = []\nfor cat, angle in axis_angles.items():\n    angle_rad = np.radians(angle)\n    axis_labels.append(\n        {\"x\": 1.18 * np.cos(angle_rad), \"y\": 1.18 * np.sin(angle_rad), \"label\": cat.upper(), \"category\": cat}\n    )\nlabel_df = pd.DataFrame(axis_labels)\n\nnode_labels = []\nfor _, row in node_positions.iterrows():\n    angle_deg = axis_angles[row[\"category\"]]\n    angle_rad = np.radians(angle_deg)\n\n    offset = 0.18\n    perp_angle = angle_rad + np.pi / 2\n    label_x = row[\"x\"] + offset * np.cos(perp_angle)\n    label_y = row[\"y\"] + offset * np.sin(perp_angle)\n\n    node_labels.append({\"x\": label_x, \"y\": label_y, \"label\": row[\"id\"], \"category\": row[\"category\"]})\nnode_labels_df = pd.DataFrame(node_labels)\n\nplot = (\n    ggplot()\n    + geom_segment(aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\", color=\"src_cat\"), data=edge_df, size=1.5, alpha=0.5)\n    + geom_segment(aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\", color=\"category\"), data=axis_df, size=3.5, alpha=0.8)\n    + geom_point(aes(x=\"x\", y=\"y\", color=\"category\", size=\"node_size\"), data=node_positions, alpha=0.95)\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=node_labels_df, size=10, color=INK)\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\", color=\"category\"), data=label_df, size=18)\n    + scale_color_manual(values=axis_colors)\n    + coord_fixed(ratio=1)\n    + xlim(-1.6, 1.6)\n    + ylim(-1.6, 1.6)\n    + labs(title=\"hive-basic · plotnine · anyplot.ai\")\n    + theme_void()\n    + theme(\n        figure_size=(16, 16),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        plot_title=element_text(size=28, color=INK, weight=\"bold\"),\n        legend_position=\"none\",\n        plot_margin=0.01,\n    )\n)\n\nplot.save(f\"plot-{THEME}.png\", dpi=300, verbose=False)\n"}