{"spec_id":"hive-basic","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nhive-basic: Basic Hive Plot\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 83/100 | Updated: 2026-05-07\n\"\"\"\n\nimport math\nimport os\nimport re\nimport sys\n\nimport cairosvg\nimport numpy as np\n\n\n# Avoid import shadowing: temporarily remove current directory from path\n_cwd = os.getcwd()\nsys.path = [p for p in sys.path if os.path.abspath(p) != _cwd]\n\nimport pygal\nfrom pygal.style import Style\n\n\n# Restore path for any later imports\nsys.path.insert(0, _cwd)\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\"]\n\nnp.random.seed(42)\n\n# Data: Software module dependency network\n# Nodes assigned to 3 axes by module type: Core (0), Utility (1), Interface (2)\nnodes = [\n    # Core modules (axis 0)\n    {\"id\": 0, \"label\": \"kernel\", \"axis\": 0, \"degree\": 8},\n    {\"id\": 1, \"label\": \"config\", \"axis\": 0, \"degree\": 6},\n    {\"id\": 2, \"label\": \"database\", \"axis\": 0, \"degree\": 7},\n    {\"id\": 3, \"label\": \"auth\", \"axis\": 0, \"degree\": 5},\n    {\"id\": 4, \"label\": \"cache\", \"axis\": 0, \"degree\": 4},\n    {\"id\": 5, \"label\": \"logger\", \"axis\": 0, \"degree\": 6},\n    {\"id\": 6, \"label\": \"events\", \"axis\": 0, \"degree\": 5},\n    # Utility modules (axis 1)\n    {\"id\": 7, \"label\": \"helpers\", \"axis\": 1, \"degree\": 5},\n    {\"id\": 8, \"label\": \"validators\", \"axis\": 1, \"degree\": 4},\n    {\"id\": 9, \"label\": \"formatters\", \"axis\": 1, \"degree\": 3},\n    {\"id\": 10, \"label\": \"parsers\", \"axis\": 1, \"degree\": 4},\n    {\"id\": 11, \"label\": \"converters\", \"axis\": 1, \"degree\": 3},\n    {\"id\": 12, \"label\": \"crypto\", \"axis\": 1, \"degree\": 4},\n    {\"id\": 13, \"label\": \"compress\", \"axis\": 1, \"degree\": 2},\n    # Interface modules (axis 2)\n    {\"id\": 14, \"label\": \"rest_api\", \"axis\": 2, \"degree\": 6},\n    {\"id\": 15, \"label\": \"graphql\", \"axis\": 2, \"degree\": 5},\n    {\"id\": 16, \"label\": \"websocket\", \"axis\": 2, \"degree\": 4},\n    {\"id\": 17, \"label\": \"cli\", \"axis\": 2, \"degree\": 3},\n    {\"id\": 18, \"label\": \"admin_ui\", \"axis\": 2, \"degree\": 4},\n    {\"id\": 19, \"label\": \"public_ui\", \"axis\": 2, \"degree\": 5},\n]\n\n# Edges: Dependencies between modules\nedges = [\n    # Core → Core\n    (0, 1),\n    (0, 2),\n    (0, 3),\n    (1, 4),\n    (1, 5),\n    (2, 4),\n    (2, 5),\n    (3, 6),\n    # Core → Utility\n    (0, 7),\n    (1, 8),\n    (2, 10),\n    (3, 12),\n    (4, 9),\n    (5, 9),\n    (6, 7),\n    # Core → Interface\n    (0, 14),\n    (0, 15),\n    (2, 14),\n    (2, 15),\n    (3, 14),\n    (3, 16),\n    (5, 17),\n    # Utility → Utility\n    (7, 8),\n    (8, 10),\n    (9, 11),\n    (10, 11),\n    (12, 13),\n    # Utility → Interface\n    (7, 14),\n    (7, 19),\n    (8, 14),\n    (8, 15),\n    (9, 17),\n    (10, 15),\n    (12, 16),\n    # Interface → Interface\n    (14, 18),\n    (14, 19),\n    (15, 19),\n    (16, 18),\n]\n\n# Axis configuration (3 radial axes at 120 degrees apart)\nn_axes = 3\naxis_angles = [2 * math.pi * i / n_axes - math.pi / 2 for i in range(n_axes)]\naxis_names = [\"Core\", \"Utility\", \"Interface\"]\n\n# Calculate node positions along each axis\ncenter_x, center_y = 5.0, 5.0\ninner_radius = 1.2\nouter_radius = 4.0\n\n# Sort nodes by axis and degree for positioning\naxis_nodes = {i: [] for i in range(n_axes)}\nfor node in nodes:\n    axis_nodes[node[\"axis\"]].append(node)\n\n# Sort each axis by degree (descending) for inner-to-outer positioning\nfor axis_id in axis_nodes:\n    axis_nodes[axis_id].sort(key=lambda n: -n[\"degree\"])\n\n# Calculate positions\nnode_positions = {}\nfor axis_id in range(n_axes):\n    angle = axis_angles[axis_id]\n    nodes_on_axis = axis_nodes[axis_id]\n    n_nodes = len(nodes_on_axis)\n\n    for i, node in enumerate(nodes_on_axis):\n        if n_nodes > 1:\n            t = i / (n_nodes - 1)\n        else:\n            t = 0.5\n        radius = inner_radius + t * (outer_radius - inner_radius)\n        x = center_x + radius * math.cos(angle)\n        y = center_y + radius * math.sin(angle)\n        node_positions[node[\"id\"]] = (x, y, axis_id)\n\n# Create theme-adaptive style\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=(\n        INK_SOFT,\n        INK_SOFT,\n        INK_SOFT,\n        IMPRINT[0] + \"44\",\n        IMPRINT[1] + \"44\",\n        IMPRINT[2] + \"44\",\n        IMPRINT[0],\n        IMPRINT[1],\n        IMPRINT[2],\n    ),\n    title_font_size=28,\n    label_font_size=18,\n    major_label_font_size=16,\n    legend_font_size=16,\n    value_font_size=14,\n    stroke_width=2,\n)\n\n# Create XY chart\nchart = pygal.XY(\n    width=4800,\n    height=2700,\n    style=custom_style,\n    title=\"hive-basic · pygal · anyplot.ai\",\n    show_legend=True,\n    x_title=\"\",\n    y_title=\"\",\n    show_x_guides=False,\n    show_y_guides=False,\n    show_x_labels=False,\n    show_y_labels=False,\n    stroke=True,\n    dots_size=20,\n    legend_at_bottom=True,\n    range=(0, 10),\n    xrange=(0, 10),\n    print_labels=True,\n    print_values=False,\n)\n\n# Draw axis lines\nfor axis_id in range(n_axes):\n    angle = axis_angles[axis_id]\n    x1 = center_x + (inner_radius - 0.3) * math.cos(angle)\n    y1 = center_y + (inner_radius - 0.3) * math.sin(angle)\n    x2 = center_x + (outer_radius + 0.3) * math.cos(angle)\n    y2 = center_y + (outer_radius + 0.3) * math.sin(angle)\n    chart.add(\n        None,\n        [(x1, y1), (x2, y2)],\n        stroke=True,\n        show_dots=False,\n        fill=False,\n        stroke_style={\"width\": 4, \"linecap\": \"round\"},\n    )\n\n# Calculate axis label positions\nlabel_radius = outer_radius + 0.7\naxis_label_positions = []\nfor axis_id in range(n_axes):\n    angle = axis_angles[axis_id]\n    label_x = center_x + label_radius * math.cos(angle)\n    label_y = center_y + label_radius * math.sin(angle)\n    axis_label_positions.append((label_x, label_y, axis_names[axis_id], IMPRINT[axis_id]))\n\n# Draw edges as curved paths with bundling to reduce visual clutter\nedge_points_by_axis = {0: [], 1: [], 2: []}\n\nfor src, tgt in edges:\n    src_x, src_y, src_axis = node_positions[src]\n    tgt_x, tgt_y, tgt_axis = node_positions[tgt]\n\n    # Edge bundling: bend toward center more for inter-axis edges\n    if src_axis == tgt_axis:\n        pull = 0.75\n    else:\n        pull = 0.35\n\n    ctrl_x = center_x + pull * ((src_x + tgt_x) / 2 - center_x)\n    ctrl_y = center_y + pull * ((src_y + tgt_y) / 2 - center_y)\n\n    # Generate Bezier curve points\n    n_points = 25\n    for t_idx in range(n_points + 1):\n        t = t_idx / n_points\n        bx = (1 - t) ** 2 * src_x + 2 * (1 - t) * t * ctrl_x + t**2 * tgt_x\n        by = (1 - t) ** 2 * src_y + 2 * (1 - t) * t * ctrl_y + t**2 * tgt_y\n        edge_points_by_axis[src_axis].append((bx, by))\n    edge_points_by_axis[src_axis].append(None)\n\n# Add edges grouped by source axis\nfor axis_id in range(n_axes):\n    chart.add(\n        None,\n        edge_points_by_axis[axis_id],\n        stroke=True,\n        show_dots=False,\n        fill=False,\n        stroke_style={\"width\": 1, \"linecap\": \"round\"},\n    )\n\n# Add nodes grouped by axis with abbreviated labels to reduce overlap\nfor axis_id in range(n_axes):\n    nodes_on_axis = axis_nodes[axis_id]\n    node_points = []\n    for node in nodes_on_axis:\n        x, y, _ = node_positions[node[\"id\"]]\n        node_points.append({\"value\": (x, y), \"label\": node[\"label\"][:4].upper()})\n    chart.add(axis_names[axis_id], node_points, stroke=False)\n\n# Render SVG\nsvg_content = chart.render().decode(\"utf-8\")\n\n# Extract viewBox dimensions\nviewbox_match = re.search(r'viewBox=\"([^\"]+)\"', svg_content)\nif viewbox_match:\n    vb_parts = viewbox_match.group(1).split()\n    vb_width = float(vb_parts[2])\n    vb_height = float(vb_parts[3])\nelse:\n    vb_width = 4800\n    vb_height = 2700\n\n# Calculate plot area coordinates\nplot_left = vb_width * 0.08\nplot_right = vb_width * 0.95\nplot_top = vb_height * 0.12\nplot_bottom = vb_height * 0.88\n\nplot_width = plot_right - plot_left\nplot_height = plot_bottom - plot_top\ndata_range = 10.0\n\n\ndef data_to_svg(dx, dy):\n    \"\"\"Convert data coordinates to SVG coordinates.\"\"\"\n    sx = plot_left + (dx / data_range) * plot_width\n    sy = plot_bottom - (dy / data_range) * plot_height\n    return sx, sy\n\n\n# Create axis label text elements\naxis_label_svg = \"\"\nfor dx, dy, label, color in axis_label_positions:\n    sx, sy = data_to_svg(dx, dy)\n    if dy > center_y:\n        text_anchor = \"middle\"\n        dy_offset = -30\n        dx_offset = 0\n    elif dx < center_x:\n        text_anchor = \"end\"\n        dy_offset = 8\n        dx_offset = -22\n    else:\n        text_anchor = \"start\"\n        dy_offset = 8\n        dx_offset = 22\n\n    axis_label_svg += f'''\n    <text x=\"{sx + dx_offset}\" y=\"{sy + dy_offset}\" fill=\"{color}\"\n          font-size=\"24\" font-weight=\"bold\" font-family=\"sans-serif\"\n          text-anchor=\"{text_anchor}\">{label}</text>'''\n\n# Insert axis labels before closing tag\nsvg_content = svg_content.replace(\"</svg>\", f\"{axis_label_svg}\\n</svg>\")\n\n# Save SVG and convert to PNG\nwith open(f\"plot-{THEME}.svg\", \"w\") as f:\n    f.write(svg_content)\n\ncairosvg.svg2png(bytestring=svg_content.encode(\"utf-8\"), write_to=f\"plot-{THEME}.png\")\n\n# Save interactive HTML\nwith open(f\"plot-{THEME}.html\", \"w\") as f:\n    f.write(\n        f\"\"\"<!DOCTYPE html>\n<html>\n<head>\n    <title>hive-basic · pygal · anyplot.ai</title>\n    <style>\n        body {{ margin: 0; padding: 20px; background: {PAGE_BG}; }}\n        .container {{ max-width: 100%; margin: 0 auto; }}\n        object {{ width: 100%; height: auto; }}\n    </style>\n</head>\n<body>\n    <div class=\"container\">\n        <object type=\"image/svg+xml\" data=\"plot-{THEME}.svg\">\n            Hive plot not supported\n        </object>\n    </div>\n</body>\n</html>\"\"\"\n    )\n"}