{"spec_id":"circos-basic","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\ncircos-basic: Circos Plot\nLibrary: matplotlib 3.10.9 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-05-15\n\"\"\"\n\nimport os\nimport sys\n\n\n# Remove current directory from path to avoid name collision with this script\nsys.path = [p for p in sys.path if p != \"\"]\n\nimport matplotlib.patches as mpatches\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom matplotlib.path import Path\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 (positions 1-7, plus position 8 for neutral)\nIMPRINT = [\n    \"#009E73\",  # 1: bluish green (brand)\n    \"#C475FD\",  # 2: vermillion\n    \"#4467A3\",  # 3: blue\n    \"#BD8233\",  # 4: reddish purple\n    \"#AE3030\",  # 5: orange\n    \"#2ABCCD\",  # 6: sky blue\n    \"#954477\",  # 7: yellow\n]\n\n# Data: Genomic chromosome interactions\nnp.random.seed(42)\n\n# Define chromosomes\nchromosomes = [\"chr1\", \"chr2\", \"chr3\", \"chr4\", \"chr5\", \"chr6\", \"chr7\", \"chr8\"]\nn_chroms = len(chromosomes)\n\n# Chromosome sizes (relative size on outer ring)\nchrom_sizes = np.array([200, 180, 160, 140, 120, 100, 80, 60])\nchrom_sizes = chrom_sizes / chrom_sizes.sum() * 360  # Convert to degrees\n\n# Inter-chromosomal connections (synteny blocks)\nconnections = [\n    (\"chr1\", \"chr2\", 25),\n    (\"chr1\", \"chr3\", 18),\n    (\"chr1\", \"chr4\", 12),\n    (\"chr2\", \"chr3\", 22),\n    (\"chr2\", \"chr5\", 15),\n    (\"chr3\", \"chr4\", 20),\n    (\"chr3\", \"chr6\", 10),\n    (\"chr4\", \"chr5\", 16),\n    (\"chr4\", \"chr7\", 8),\n    (\"chr5\", \"chr6\", 14),\n    (\"chr6\", \"chr7\", 12),\n    (\"chr7\", \"chr8\", 9),\n    (\"chr1\", \"chr8\", 11),\n    (\"chr2\", \"chr8\", 7),\n]\n\n# Create figure (square for circular plot)\nfig, ax = plt.subplots(figsize=(12, 12), facecolor=PAGE_BG)\nax.set_aspect(\"equal\")\nax.axis(\"off\")\n\n# Calculate segment positions\ngap = 2  # Gap between segments in degrees\ntotal_gap = gap * n_chroms\navailable = 360 - total_gap\nsegment_angles = chrom_sizes / 360 * available\n\n# Calculate start and end angles for each segment\nstarts = []\nends = []\ncurrent = 90  # Start at top\n\nfor angle in segment_angles:\n    starts.append(current)\n    ends.append(current - angle)\n    current = current - angle - gap\n\nchrom_dict = {name: i for i, name in enumerate(chromosomes)}\n\n# Draw outer ring segments\nr_outer = 1.0\nr_inner = 0.85\nn_arc_points = 50\n\nfor i in range(n_chroms):\n    start, end = starts[i], ends[i]\n    theta1_rad = np.radians(end)\n    theta2_rad = np.radians(start)\n    theta = np.linspace(theta1_rad, theta2_rad, n_arc_points)\n\n    # Outer arc\n    x_outer = r_outer * np.cos(theta)\n    y_outer = r_outer * np.sin(theta)\n    # Inner arc (reversed)\n    x_inner = r_inner * np.cos(theta[::-1])\n    y_inner = r_inner * np.sin(theta[::-1])\n    # Combine into closed polygon\n    x = np.concatenate([x_outer, x_inner])\n    y = np.concatenate([y_outer, y_inner])\n\n    color_idx = i % len(IMPRINT)\n    ax.fill(x, y, color=IMPRINT[color_idx], alpha=0.85, edgecolor=PAGE_BG, linewidth=1.5)\n\n    # Add segment label\n    mid_angle = np.radians((start + end) / 2)\n    label_r = r_outer + 0.12\n    lx = label_r * np.cos(mid_angle)\n    ly = label_r * np.sin(mid_angle)\n    ax.text(lx, ly, chromosomes[i], fontsize=16, fontweight=\"bold\", ha=\"center\", va=\"center\", color=INK)\n\n# Draw inner data track (simulated expression values)\ntrack_data = np.random.uniform(0.4, 0.95, n_chroms)\nr_track_outer = 0.82\nr_track_inner = 0.70\n\nfor i in range(n_chroms):\n    start, end = starts[i], ends[i]\n    track_height = (r_track_outer - r_track_inner) * track_data[i]\n    theta1_rad = np.radians(end)\n    theta2_rad = np.radians(start)\n    theta = np.linspace(theta1_rad, theta2_rad, n_arc_points)\n\n    x_outer = (r_track_inner + track_height) * np.cos(theta)\n    y_outer = (r_track_inner + track_height) * np.sin(theta)\n    x_inner = r_track_inner * np.cos(theta[::-1])\n    y_inner = r_track_inner * np.sin(theta[::-1])\n    x = np.concatenate([x_outer, x_inner])\n    y = np.concatenate([y_outer, y_inner])\n\n    color_idx = i % len(IMPRINT)\n    ax.fill(x, y, color=IMPRINT[color_idx], alpha=0.5, edgecolor=\"none\")\n\n# Draw connections (ribbons for synteny blocks)\nmax_value = max(c[2] for c in connections)\nr_ribbon = r_inner - 0.02\n\nfor source, target, value in connections:\n    idx1 = chrom_dict[source]\n    idx2 = chrom_dict[target]\n\n    # Calculate positions within segments\n    mid1 = np.radians((starts[idx1] + ends[idx1]) / 2)\n    mid2 = np.radians((starts[idx2] + ends[idx2]) / 2)\n\n    # Ribbon width proportional to value\n    width_factor = value / max_value * 0.12\n\n    # Points for segment 1\n    angle1_start = mid1 - width_factor\n    angle1_end = mid1 + width_factor\n    x1_start = r_ribbon * np.cos(angle1_start)\n    y1_start = r_ribbon * np.sin(angle1_start)\n    x1_end = r_ribbon * np.cos(angle1_end)\n    y1_end = r_ribbon * np.sin(angle1_end)\n\n    # Points for segment 2\n    angle2_start = mid2 - width_factor\n    angle2_end = mid2 + width_factor\n    x2_start = r_ribbon * np.cos(angle2_start)\n    y2_start = r_ribbon * np.sin(angle2_start)\n    x2_end = r_ribbon * np.cos(angle2_end)\n    y2_end = r_ribbon * np.sin(angle2_end)\n\n    # Control points at center for bezier curves\n    ctrl_factor = 0.3\n    ctrl1_x = ctrl_factor * (x1_start + x2_end) / 2\n    ctrl1_y = ctrl_factor * (y1_start + y2_end) / 2\n    ctrl2_x = ctrl_factor * (x1_end + x2_start) / 2\n    ctrl2_y = ctrl_factor * (y1_end + y2_start) / 2\n\n    # Path vertices\n    verts = [\n        (x1_start, y1_start),\n        (ctrl1_x, ctrl1_y),\n        (x2_end, y2_end),\n        (x2_start, y2_start),\n        (ctrl2_x, ctrl2_y),\n        (x1_end, y1_end),\n        (x1_start, y1_start),\n    ]\n    codes = [Path.MOVETO, Path.CURVE3, Path.CURVE3, Path.LINETO, Path.CURVE3, Path.CURVE3, Path.CLOSEPOLY]\n\n    path = Path(verts, codes)\n    color_idx = idx1 % len(IMPRINT)\n    patch = mpatches.PathPatch(path, facecolor=IMPRINT[color_idx], alpha=0.4, edgecolor=\"none\")\n    ax.add_patch(patch)\n\n# Title\nax.set_title(\"circos-basic · matplotlib · anyplot.ai\", fontsize=24, fontweight=\"medium\", color=INK, pad=20)\n\n# Set limits with padding\nax.set_xlim(-1.4, 1.4)\nax.set_ylim(-1.4, 1.4)\n\n# Legend (outside the plot)\nlegend_elements = [\n    mpatches.Patch(facecolor=IMPRINT[i % len(IMPRINT)], label=chromosomes[i], alpha=0.85) for i in range(n_chroms)\n]\nleg = ax.legend(\n    handles=legend_elements,\n    loc=\"lower right\",\n    fontsize=14,\n    frameon=True,\n    fancybox=False,\n    framealpha=0.95,\n    ncol=1,\n    bbox_to_anchor=(1.32, 0.0),\n    title=\"Chromosomes\",\n    title_fontsize=15,\n)\n\n# Style legend\nif leg:\n    leg.get_frame().set_facecolor(ELEVATED_BG)\n    leg.get_frame().set_edgecolor(INK_SOFT)\n    leg.get_frame().set_linewidth(0.8)\n    plt.setp(leg.get_texts(), color=INK_SOFT)\n    plt.setp(leg.get_title(), color=INK)\n\nplt.tight_layout()\nplt.savefig(f\"plot-{THEME}.png\", dpi=300, bbox_inches=\"tight\", facecolor=PAGE_BG)\n"}