{"spec_id":"arc-basic","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\narc-basic: Basic Arc Diagram\nLibrary: matplotlib 3.10.9 | Python 3.13.13\nQuality: 93/100 | Updated: 2026-05-30\n\"\"\"\n\nimport os\n\nimport matplotlib.colors as mcolors\nimport matplotlib.patches as mpatches\nimport matplotlib.path as mpath\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n\n# Theme tokens (Imprint palette — theme-adaptive chrome)\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nBRAND = \"#009E73\"  # Imprint palette position 1 — ALWAYS first series\n\n# Imprint sequential cmap for continuous arc weights (single-polarity)\nimprint_seq = mcolors.LinearSegmentedColormap.from_list(\"imprint_seq\", [\"#009E73\", \"#4467A3\"])\n\n# Data: Character interactions in a story chapter\nnodes = [\"Alice\", \"Bob\", \"Carol\", \"David\", \"Eve\", \"Frank\", \"Grace\", \"Henry\", \"Iris\", \"Jack\"]\nn_nodes = len(nodes)\n\n# Edges: (source_index, target_index, weight)\nedges = [\n    (0, 1, 3),  # Alice-Bob (strong)\n    (0, 3, 2),  # Alice-David\n    (1, 2, 2),  # Bob-Carol\n    (2, 4, 1),  # Carol-Eve\n    (3, 5, 2),  # David-Frank\n    (4, 6, 1),  # Eve-Grace\n    (0, 7, 1),  # Alice-Henry (long-range)\n    (1, 5, 2),  # Bob-Frank\n    (2, 3, 3),  # Carol-David (strong)\n    (5, 8, 1),  # Frank-Iris\n    (6, 9, 2),  # Grace-Jack\n    (0, 9, 1),  # Alice-Jack (longest range)\n    (3, 7, 2),  # David-Henry\n    (7, 8, 1),  # Henry-Iris\n    (8, 9, 2),  # Iris-Jack\n]\n\nweights = [w for _, _, w in edges]\nweight_min, weight_max = min(weights), max(weights)\n\n# Weighted node degrees for size variation (hub characters are larger)\nnode_degrees = [0] * n_nodes\nfor s, e, w in edges:\n    node_degrees[s] += w\n    node_degrees[e] += w\n\nnorm = mcolors.Normalize(vmin=weight_min, vmax=weight_max)\n\n# Plot\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\nx_positions = np.linspace(0.06, 0.90, n_nodes)\ny_baseline = 0.08\n\n# Arcs via PathPatch with cubic Bézier curves (distinctive matplotlib feature)\n# Sort by weight so heavier arcs render on top\nfor start, end, weight in sorted(edges, key=lambda e: e[2]):\n    x_start = x_positions[start]\n    x_end = x_positions[end]\n    distance = abs(end - start)\n    peak = 0.065 * distance\n\n    path = mpath.Path(\n        [\n            (x_start, y_baseline),\n            (x_start, y_baseline + peak * 1.35),\n            (x_end, y_baseline + peak * 1.35),\n            (x_end, y_baseline),\n        ],\n        [mpath.Path.MOVETO, mpath.Path.CURVE4, mpath.Path.CURVE4, mpath.Path.CURVE4],\n    )\n\n    patch = mpatches.PathPatch(\n        path,\n        facecolor=\"none\",\n        edgecolor=imprint_seq(norm(weight)),\n        linewidth=0.8 + weight * 1.0,\n        alpha=0.8,\n        capstyle=\"round\",\n    )\n    ax.add_patch(patch)\n\n# Node sizes proportional to weighted degree (reveals hub characters)\nmax_degree = max(node_degrees)\nnode_sizes = [120 + 200 * (d / max_degree) for d in node_degrees]\n\n# Protagonist Alice in brand green; other characters in muted tone\nnode_colors = [BRAND if i == 0 else INK_MUTED for i in range(n_nodes)]\nnode_edge_colors = [INK if i == 0 else INK_SOFT for i in range(n_nodes)]\n\nax.scatter(\n    x_positions,\n    [y_baseline] * n_nodes,\n    s=node_sizes,\n    c=node_colors,\n    edgecolors=node_edge_colors,\n    linewidths=1.5,\n    zorder=5,\n)\n\n# Node labels with typographic hierarchy\nfor i, (x, name) in enumerate(zip(x_positions, nodes, strict=True)):\n    ax.text(\n        x,\n        y_baseline - 0.04,\n        name,\n        ha=\"center\",\n        va=\"top\",\n        fontsize=8,\n        fontweight=\"heavy\" if i == 0 else \"bold\",\n        color=INK if i == 0 else INK_SOFT,\n    )\n\n# Colorbar for connection strength (Imprint sequential cmap)\nsm = plt.cm.ScalarMappable(cmap=imprint_seq, norm=norm)\nsm.set_array([])\ncbar = fig.colorbar(sm, ax=ax, shrink=0.4, aspect=15, pad=0.02)\ncbar.set_label(\"Connection Strength\", fontsize=8, color=INK)\ncbar.set_ticks([1, 2, 3])\ncbar.ax.tick_params(labelsize=8, colors=INK_SOFT)\ncbar.outline.set_edgecolor(INK_SOFT)\nplt.setp(cbar.ax.yaxis.get_ticklabels(), color=INK_SOFT)\n\n# Subtle baseline spanning the node range\nax.plot(\n    [x_positions[0] - 0.02, x_positions[-1] + 0.02],\n    [y_baseline, y_baseline],\n    color=INK,\n    linewidth=0.8,\n    alpha=0.15,\n    zorder=1,\n)\n\nax.set_xlim(-0.02, 0.98)\nax.set_ylim(-0.06, 0.68)\nax.axis(\"off\")\n\n# Title — 44 chars, below 67-char baseline so no scaling needed\ntitle = \"arc-basic · python · matplotlib · anyplot.ai\"\nax.set_title(title, fontsize=12, fontweight=\"medium\", color=INK, pad=20)\n\nax.text(\n    0.5,\n    1.01,\n    \"Node size reflects connection activity · Alice (green) is the central character\",\n    ha=\"center\",\n    va=\"bottom\",\n    fontsize=8,\n    color=INK_MUTED,\n    fontstyle=\"italic\",\n    transform=ax.transAxes,\n)\n\nplt.tight_layout(pad=1.0)\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}