{"spec_id":"circos-basic","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\ncircos-basic: Circos Plot\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-05-15\n\"\"\"\n\nimport os\n\nimport matplotlib.patches as mpatches\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport seaborn as sns\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\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\"]\n\n# Set seaborn theme for consistent styling\nsns.set_theme(\n    style=\"white\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.10,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Data: Software module dependencies (10 modules with inter-module calls)\nnp.random.seed(42)\n\n# Define segments (modules) with their sizes (lines of code)\nsegments = [\"Core\", \"Auth\", \"API\", \"Database\", \"Cache\", \"Queue\", \"Logging\", \"Utils\", \"Metrics\", \"Config\"]\nn_segments = len(segments)\n\n# Segment sizes represent lines of code\nsegment_sizes = np.array([2500, 1800, 2200, 2800, 1200, 1400, 900, 800, 1100, 600])\n\n# Create dependency connections (source module, target module, call count)\nconnections = [\n    (0, 1, 45),  # Core -> Auth\n    (0, 2, 65),  # Core -> API\n    (0, 7, 38),  # Core -> Utils\n    (1, 3, 55),  # Auth -> Database\n    (1, 7, 28),  # Auth -> Utils\n    (2, 1, 42),  # API -> Auth\n    (2, 3, 72),  # API -> Database\n    (2, 4, 35),  # API -> Cache\n    (3, 4, 48),  # Database -> Cache\n    (3, 6, 25),  # Database -> Logging\n    (4, 5, 32),  # Cache -> Queue\n    (5, 6, 20),  # Queue -> Logging\n    (6, 7, 15),  # Logging -> Utils\n    (9, 0, 18),  # Config -> Core\n    (9, 1, 12),  # Config -> Auth\n]\n\n# Use Okabe-Ito palette, cycling through colors for segments\ncolors = [IMPRINT[i % len(IMPRINT)] for i in range(n_segments)]\n\n# Create square figure for circular symmetry\nfig, ax = plt.subplots(figsize=(12, 12), facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\nax.set_aspect(\"equal\")\n\n# Calculate segment positions (angles)\ntotal_size = segment_sizes.sum()\ngap_fraction = 0.02\ntotal_gap = gap_fraction * n_segments\navailable_angle = 2 * np.pi * (1 - total_gap / (2 * np.pi))\n\nangles = []\ncurrent_angle = np.pi / 2\n\nfor size in segment_sizes:\n    segment_angle = (size / total_size) * available_angle\n    start_angle = current_angle\n    end_angle = current_angle - segment_angle\n    angles.append((start_angle, end_angle))\n    current_angle = end_angle - gap_fraction\n\n# Draw outer ring segments\nouter_radius = 1.0\nring_width = 0.12\n\nfor i, (start, end) in enumerate(angles):\n    theta = np.linspace(end, start, 50)\n    inner = outer_radius - ring_width\n    x_outer = outer_radius * np.cos(theta)\n    y_outer = outer_radius * np.sin(theta)\n    x_inner = inner * np.cos(theta[::-1])\n    y_inner = inner * np.sin(theta[::-1])\n    x = np.concatenate([x_outer, x_inner])\n    y = np.concatenate([y_outer, y_inner])\n    ax.fill(x, y, color=colors[i], alpha=0.85, edgecolor=PAGE_BG, linewidth=1.5)\n\n    # Add segment label\n    mid_angle = (start + end) / 2\n    label_radius = outer_radius + 0.14\n    label_x = label_radius * np.cos(mid_angle)\n    label_y = label_radius * np.sin(mid_angle)\n    rotation_deg = np.degrees(mid_angle)\n    norm_angle = rotation_deg % 360\n    if 90 < norm_angle < 270:\n        rotation = rotation_deg + 180\n        ha = \"right\"\n    else:\n        rotation = rotation_deg\n        ha = \"left\"\n    ax.text(\n        label_x,\n        label_y,\n        segments[i],\n        ha=ha,\n        va=\"center\",\n        fontsize=16,\n        fontweight=\"bold\",\n        rotation=rotation,\n        rotation_mode=\"anchor\",\n        color=INK,\n    )\n\n# Draw inner data track (code volume as bar heights)\ninner_track_outer = outer_radius - ring_width - 0.03\ninner_track_inner = inner_track_outer - 0.15\n\nfor i, (start, end) in enumerate(angles):\n    height_fraction = segment_sizes[i] / segment_sizes.max()\n    track_height = (inner_track_outer - inner_track_inner) * height_fraction\n    theta = np.linspace(end, start, 30)\n    inner = inner_track_outer - track_height\n    x_outer = inner_track_outer * np.cos(theta)\n    y_outer = inner_track_outer * np.sin(theta)\n    x_inner = inner * np.cos(theta[::-1])\n    y_inner = inner * np.sin(theta[::-1])\n    x = np.concatenate([x_outer, x_inner])\n    y = np.concatenate([y_outer, y_inner])\n    ax.fill(x, y, color=colors[i], alpha=0.5, edgecolor=\"none\")\n\n# Draw ribbons (connections between modules)\nribbon_radius = inner_track_inner - 0.05\nmax_value = max(c[2] for c in connections)\nmin_value = min(c[2] for c in connections)\nctrl_radius = ribbon_radius * 0.1\nn_points = 50\nt = np.linspace(0, 1, n_points)\n\nfor source, target, value in connections:\n    # Width calculation: map values to 0.25-0.7 range for better distinction\n    normalized_value = (value - min_value) / (max_value - min_value)\n    width_fraction = 0.25 + normalized_value * 0.45\n\n    start1, end1 = angles[source]\n    start2, end2 = angles[target]\n    seg1_span = (start1 - end1) * width_fraction * 0.4\n    seg2_span = (start2 - end2) * width_fraction * 0.4\n    mid1 = (start1 + end1) / 2\n    mid2 = (start2 + end2) / 2\n    ribbon_start1 = mid1 + seg1_span / 2\n    ribbon_end1 = mid1 - seg1_span / 2\n    ribbon_start2 = mid2 + seg2_span / 2\n    ribbon_end2 = mid2 - seg2_span / 2\n\n    # First bezier curve\n    p0 = np.array([ribbon_radius * np.cos(ribbon_start1), ribbon_radius * np.sin(ribbon_start1)])\n    p3 = np.array([ribbon_radius * np.cos(ribbon_start2), ribbon_radius * np.sin(ribbon_start2)])\n    p1 = ctrl_radius * np.array([np.cos(ribbon_start1), np.sin(ribbon_start1)])\n    p2 = ctrl_radius * np.array([np.cos(ribbon_start2), np.sin(ribbon_start2)])\n    curve1 = (\n        (1 - t)[:, None] ** 3 * p0\n        + 3 * (1 - t)[:, None] ** 2 * t[:, None] * p1\n        + 3 * (1 - t)[:, None] * t[:, None] ** 2 * p2\n        + t[:, None] ** 3 * p3\n    )\n\n    # Second bezier curve\n    p0 = np.array([ribbon_radius * np.cos(ribbon_end1), ribbon_radius * np.sin(ribbon_end1)])\n    p3 = np.array([ribbon_radius * np.cos(ribbon_end2), ribbon_radius * np.sin(ribbon_end2)])\n    p1 = ctrl_radius * np.array([np.cos(ribbon_end1), np.sin(ribbon_end1)])\n    p2 = ctrl_radius * np.array([np.cos(ribbon_end2), np.sin(ribbon_end2)])\n    curve2 = (\n        (1 - t)[:, None] ** 3 * p0\n        + 3 * (1 - t)[:, None] ** 2 * t[:, None] * p1\n        + 3 * (1 - t)[:, None] * t[:, None] ** 2 * p2\n        + t[:, None] ** 3 * p3\n    )\n\n    # Arcs at source and target segments\n    arc1_angles = np.linspace(ribbon_start1, ribbon_end1, 10)\n    arc1 = ribbon_radius * np.column_stack([np.cos(arc1_angles), np.sin(arc1_angles)])\n    arc2_angles = np.linspace(ribbon_end2, ribbon_start2, 10)\n    arc2 = ribbon_radius * np.column_stack([np.cos(arc2_angles), np.sin(arc2_angles)])\n\n    # Combine vertices and draw polygon with improved transparency\n    vertices = np.vstack([arc1, curve1, arc2, curve2[::-1]])\n    polygon = plt.Polygon(vertices, facecolor=colors[source], edgecolor=\"none\", alpha=0.35, zorder=1)\n    ax.add_patch(polygon)\n\n# Configure axes\nax.set_xlim(-1.7, 1.7)\nax.set_ylim(-1.7, 1.7)\nax.axis(\"off\")\n\n# Title with correct format\nax.set_title(\"circos-basic · seaborn · anyplot.ai\", fontsize=26, fontweight=\"bold\", pad=20, color=INK)\n\n# Add legend explaining the visualization\nlegend_elements = [\n    mpatches.Patch(facecolor=IMPRINT[0], alpha=0.85, label=\"Outer ring: Module (arc size ∝ code volume)\"),\n    mpatches.Patch(facecolor=IMPRINT[0], alpha=0.5, label=\"Inner track: Module size (bar height)\"),\n    mpatches.Patch(facecolor=IMPRINT[0], alpha=0.35, label=\"Ribbons: Dependencies (width ∝ call count)\"),\n]\nax.legend(\n    handles=legend_elements,\n    loc=\"lower center\",\n    bbox_to_anchor=(0.5, -0.08),\n    ncol=1,\n    fontsize=14,\n    frameon=True,\n    facecolor=ELEVATED_BG,\n    edgecolor=INK_SOFT,\n)\n\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=300, bbox_inches=\"tight\", facecolor=PAGE_BG)\n"}