{"spec_id":"hive-basic","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nhive-basic: Basic Hive Plot\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-05-07\n\"\"\"\n\nimport os\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, Label\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\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\"\n\n# Okabe-Ito palette - first series is always #009E73\nIMPRINT = [\n    \"#009E73\",  # bluish green (brand)\n    \"#C475FD\",  # vermillion\n    \"#4467A3\",  # blue\n]\n\n# Data: Software module dependency network\n# Nodes assigned to 3 axes by module type: Core, Utility, Interface\nnp.random.seed(42)\n\nnodes = {\n    # Core modules (axis 0)\n    \"core_main\": {\"axis\": 0, \"degree\": 8},\n    \"core_data\": {\"axis\": 0, \"degree\": 6},\n    \"core_config\": {\"axis\": 0, \"degree\": 5},\n    \"core_init\": {\"axis\": 0, \"degree\": 7},\n    \"core_base\": {\"axis\": 0, \"degree\": 4},\n    # Utility modules (axis 1)\n    \"util_io\": {\"axis\": 1, \"degree\": 5},\n    \"util_parse\": {\"axis\": 1, \"degree\": 4},\n    \"util_cache\": {\"axis\": 1, \"degree\": 3},\n    \"util_log\": {\"axis\": 1, \"degree\": 6},\n    \"util_helper\": {\"axis\": 1, \"degree\": 2},\n    \"util_validate\": {\"axis\": 1, \"degree\": 4},\n    # Interface modules (axis 2)\n    \"api_rest\": {\"axis\": 2, \"degree\": 7},\n    \"api_graphql\": {\"axis\": 2, \"degree\": 5},\n    \"api_cli\": {\"axis\": 2, \"degree\": 4},\n    \"api_web\": {\"axis\": 2, \"degree\": 6},\n}\n\nedges = [\n    # Core to Utility connections\n    (\"core_main\", \"util_io\"),\n    (\"core_main\", \"util_log\"),\n    (\"core_main\", \"util_cache\"),\n    (\"core_data\", \"util_parse\"),\n    (\"core_data\", \"util_io\"),\n    (\"core_data\", \"util_validate\"),\n    (\"core_config\", \"util_io\"),\n    (\"core_config\", \"util_parse\"),\n    (\"core_init\", \"util_log\"),\n    (\"core_init\", \"util_helper\"),\n    (\"core_base\", \"util_validate\"),\n    # Utility to Interface connections\n    (\"util_io\", \"api_rest\"),\n    (\"util_io\", \"api_web\"),\n    (\"util_parse\", \"api_graphql\"),\n    (\"util_parse\", \"api_cli\"),\n    (\"util_cache\", \"api_rest\"),\n    (\"util_cache\", \"api_graphql\"),\n    (\"util_log\", \"api_rest\"),\n    (\"util_log\", \"api_cli\"),\n    (\"util_log\", \"api_web\"),\n    (\"util_validate\", \"api_rest\"),\n    (\"util_validate\", \"api_graphql\"),\n    # Core to Interface connections (cross-axis)\n    (\"core_main\", \"api_rest\"),\n    (\"core_main\", \"api_web\"),\n    (\"core_data\", \"api_graphql\"),\n    (\"core_init\", \"api_cli\"),\n]\n\n# Hive plot parameters\nn_axes = 3\n# Rotate axes to point outward from center: up-right, up-left, down\naxis_angles = [np.pi / 6, 5 * np.pi / 6, 3 * np.pi / 2]\ninner_radius = 600\nouter_radius = 1900\naxis_labels = [\"Core\", \"Utility\", \"Interface\"]\n\n# Calculate node positions on axes\nnode_positions = {}\nfor axis_id in range(n_axes):\n    axis_nodes = [(name, data) for name, data in nodes.items() if data[\"axis\"] == axis_id]\n    axis_nodes.sort(key=lambda x: x[1][\"degree\"])\n    n_nodes = len(axis_nodes)\n    for i, (name, data) in enumerate(axis_nodes):\n        t = (i + 0.5) / n_nodes\n        radius = inner_radius + t * (outer_radius - inner_radius)\n        angle = axis_angles[axis_id]\n        x = radius * np.cos(angle)\n        y = radius * np.sin(angle)\n        node_positions[name] = {\"x\": x, \"y\": y, \"axis\": axis_id, \"degree\": data[\"degree\"]}\n\n# Create figure - landscape 16:9 for better canvas utilization\np = figure(\n    width=4800,\n    height=2700,\n    title=\"hive-basic · bokeh · anyplot.ai\",\n    x_range=(-2400, 2400),\n    y_range=(-1500, 1500),\n    tools=\"\",\n    toolbar_location=None,\n)\n\n# Theme-adaptive styling\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = None\n\np.title.text_font_size = \"28pt\"\np.title.text_color = INK\n\n# Remove axes and grid for circular plot\np.axis.visible = False\np.grid.visible = False\n\n# Draw radial axes\nfor i, angle in enumerate(axis_angles):\n    x_start = inner_radius * np.cos(angle)\n    y_start = inner_radius * np.sin(angle)\n    x_end = outer_radius * np.cos(angle)\n    y_end = outer_radius * np.sin(angle)\n    p.line([x_start, x_end], [y_start, y_end], line_width=4, line_color=IMPRINT[i], line_alpha=0.6)\n\n    # Axis labels\n    label_radius = outer_radius + 300\n    label_x = label_radius * np.cos(angle)\n    label_y = label_radius * np.sin(angle)\n    label = Label(\n        x=label_x,\n        y=label_y,\n        text=axis_labels[i],\n        text_font_size=\"22pt\",\n        text_align=\"center\",\n        text_baseline=\"middle\",\n        text_color=INK,\n        text_font_style=\"bold\",\n    )\n    p.add_layout(label)\n\n# Draw edges as bezier curves\nfor source, target in edges:\n    src_pos = node_positions[source]\n    tgt_pos = node_positions[target]\n\n    ctrl_x = (src_pos[\"x\"] + tgt_pos[\"x\"]) * 0.2\n    ctrl_y = (src_pos[\"y\"] + tgt_pos[\"y\"]) * 0.2\n\n    t_vals = np.linspace(0, 1, 30)\n    curve_x = (1 - t_vals) ** 2 * src_pos[\"x\"] + 2 * (1 - t_vals) * t_vals * ctrl_x + t_vals**2 * tgt_pos[\"x\"]\n    curve_y = (1 - t_vals) ** 2 * src_pos[\"y\"] + 2 * (1 - t_vals) * t_vals * ctrl_y + t_vals**2 * tgt_pos[\"y\"]\n\n    edge_color = IMPRINT[src_pos[\"axis\"]]\n    p.line(curve_x.tolist(), curve_y.tolist(), line_width=2, line_color=edge_color, line_alpha=0.4)\n\n# Draw nodes with larger sizes for visibility\nfor axis_id in range(n_axes):\n    axis_node_data = [(name, data) for name, data in node_positions.items() if data[\"axis\"] == axis_id]\n    x_coords = [d[\"x\"] for _, d in axis_node_data]\n    y_coords = [d[\"y\"] for _, d in axis_node_data]\n    names = [name for name, _ in axis_node_data]\n    sizes = [20 + d[\"degree\"] * 8 for _, d in axis_node_data]\n\n    source = ColumnDataSource(data={\"x\": x_coords, \"y\": y_coords, \"size\": sizes, \"name\": names})\n\n    p.scatter(\n        x=\"x\",\n        y=\"y\",\n        size=\"size\",\n        source=source,\n        fill_color=IMPRINT[axis_id],\n        line_color=PAGE_BG,\n        line_width=3,\n        alpha=0.85,\n    )\n\n# Add node labels - positioned visibly\nfor name, data in node_positions.items():\n    angle = axis_angles[data[\"axis\"]]\n    perp_angle = angle + np.pi / 2\n    label_offset = 140\n    label_x = data[\"x\"] + label_offset * np.cos(perp_angle)\n    label_y = data[\"y\"] + label_offset * np.sin(perp_angle)\n\n    short_name = name.split(\"_\")[1] if \"_\" in name else name\n\n    node_label = Label(\n        x=label_x,\n        y=label_y,\n        text=short_name,\n        text_font_size=\"18pt\",\n        text_align=\"center\",\n        text_baseline=\"middle\",\n        text_color=INK,\n    )\n    p.add_layout(node_label)\n\n# Add legend for node size\nlegend_x = -2000\nlegend_y = 800\nlegend_title = Label(\n    x=legend_x,\n    y=legend_y,\n    text=\"Node Size = Degree\",\n    text_font_size=\"20pt\",\n    text_align=\"left\",\n    text_baseline=\"middle\",\n    text_color=INK,\n    text_font_style=\"bold\",\n)\np.add_layout(legend_title)\n\nlegend_sizes = [2, 5, 8]\nlegend_labels = [\"Low (2)\", \"Medium (5)\", \"High (8)\"]\nfor i, (deg, label_text) in enumerate(zip(legend_sizes, legend_labels, strict=True)):\n    node_size = 20 + deg * 8\n    item_y = legend_y - 130 - i * 130\n    p.scatter(\n        [legend_x + 80], [item_y], size=node_size, fill_color=INK_SOFT, line_color=PAGE_BG, line_width=2, alpha=0.7\n    )\n    size_label = Label(\n        x=legend_x + 200,\n        y=item_y,\n        text=label_text,\n        text_font_size=\"18pt\",\n        text_align=\"left\",\n        text_baseline=\"middle\",\n        text_color=INK_SOFT,\n    )\n    p.add_layout(size_label)\n\n# Save HTML\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot with headless Chrome via Selenium\nW, H = 4800, 2700\nopts = Options()\nfor arg in (\n    \"--headless=new\",\n    \"--no-sandbox\",\n    \"--disable-dev-shm-usage\",\n    \"--disable-gpu\",\n    f\"--window-size={W},{H}\",\n    \"--hide-scrollbars\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(W, H)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ntime.sleep(3)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}