{"spec_id":"arc-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\narc-basic: Basic Arc Diagram\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 86/100 | Updated: 2026-05-30\n\"\"\"\n\nimport os\nimport sys\n\n\nsys.path = [p for p in sys.path if p and not p.endswith(\"implementations\") and not p.endswith(\"/python\")]\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    coord_cartesian,\n    element_rect,\n    element_text,\n    geom_path,\n    geom_point,\n    geom_segment,\n    geom_text,\n    ggplot,\n    guide_colorbar,\n    labs,\n    scale_alpha_identity,\n    scale_color_gradient,\n    scale_size_identity,\n    theme,\n    theme_void,\n)\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\"\n\n# Imprint sequential gradient for arc weight encoding (position 1 → position 3)\nARC_LOW = \"#009E73\"  # weak connections — Imprint brand green\nARC_HIGH = \"#4467A3\"  # strong connections — Imprint blue\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\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\nx_positions = np.linspace(0, 1, n_nodes)\ny_baseline = 0.0\n\n# Node degree for proportional sizing (degree 2→6, 3→7.5, 4→9)\nnode_degree = [0] * n_nodes\nfor s, e, _ in edges:\n    node_degree[s] += 1\n    node_degree[e] += 1\nnode_sizes = [3.0 + d * 1.5 for d in node_degree]\n\nn_points = 60\ntheta = np.linspace(0, np.pi, n_points)\narc_rows = []\n\nfor arc_id, (start, end, weight) in enumerate(edges):\n    x_start, x_end = x_positions[start], x_positions[end]\n    x_center = (x_start + x_end) / 2\n    arc_radius = abs(x_end - x_start) / 2\n    height = 0.08 * abs(end - start)\n\n    x_arc = x_center - arc_radius * np.cos(theta)\n    y_arc = y_baseline + height * np.sin(theta)\n\n    arc_rows.append(\n        pd.DataFrame(\n            {\n                \"x\": x_arc,\n                \"y\": y_arc,\n                \"arc_id\": arc_id,\n                \"weight\": float(weight),\n                \"size\": 1.0 + weight * 0.45,  # weight=1: 1.45, weight=3: 2.35\n                \"alpha\": 0.62 + weight * 0.10,  # weight=1: 0.72, weight=3: 0.92\n            }\n        )\n    )\n\narc_df = pd.concat(arc_rows, ignore_index=True)\n\nbaseline_df = pd.DataFrame({\"x\": [x_positions[0]], \"xend\": [x_positions[-1]], \"y\": [y_baseline], \"yend\": [y_baseline]})\nnode_df = pd.DataFrame({\"x\": x_positions, \"y\": [y_baseline] * n_nodes, \"size\": node_sizes})\nlabel_df = pd.DataFrame({\"x\": x_positions, \"y\": [y_baseline - 0.035] * n_nodes, \"name\": nodes})\n\n# Callout annotation for Alice–Jack: the longest-range arc (nodes 0→9, height=0.72)\nalice_jack_apex_x = (x_positions[0] + x_positions[9]) / 2  # 0.5\nalice_jack_apex_y = 0.08 * abs(9 - 0)  # 0.72\ncallout_df = pd.DataFrame(\n    {\"x\": [alice_jack_apex_x], \"y\": [alice_jack_apex_y + 0.04], \"label\": [\"Alice–Jack: longest-range arc\"]}\n)\n\n# Title: \"Character Interactions · arc-basic · python · plotnine · anyplot.ai\" = 67 chars, no scaling\ntitle = \"Character Interactions · arc-basic · python · plotnine · anyplot.ai\"\n\nplot = (\n    ggplot()\n    + geom_segment(\n        baseline_df, aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\"), color=INK_MUTED, size=0.5, linetype=\"solid\"\n    )\n    + geom_path(arc_df, aes(x=\"x\", y=\"y\", group=\"arc_id\", color=\"weight\", size=\"size\", alpha=\"alpha\"))\n    + scale_color_gradient(\n        low=ARC_LOW,\n        high=ARC_HIGH,\n        name=\"Interaction\\nStrength\",\n        breaks=[1, 2, 3],\n        labels=[\"Weak\", \"Medium\", \"Strong\"],\n        guide=guide_colorbar(direction=\"vertical\"),\n    )\n    + scale_size_identity()\n    + scale_alpha_identity()\n    + geom_point(node_df, aes(x=\"x\", y=\"y\", size=\"size\"), color=INK, stroke=1.2, fill=PAGE_BG)\n    + geom_text(label_df, aes(x=\"x\", y=\"y\", label=\"name\"), size=5, color=INK, fontweight=\"bold\", va=\"top\")\n    + geom_text(callout_df, aes(x=\"x\", y=\"y\", label=\"label\"), size=3.5, color=INK_SOFT, ha=\"center\")\n    + coord_cartesian(xlim=(-0.06, 1.06), ylim=(-0.12, 0.82))\n    + labs(\n        title=title, subtitle=\"Narrative connections in Chapter 1 — arc thickness and color encode interaction strength\"\n    )\n    + theme_void()\n    + theme(\n        figure_size=(8, 4.5),\n        plot_title=element_text(size=12, ha=\"center\", weight=\"bold\", color=INK),\n        plot_subtitle=element_text(size=8, ha=\"center\", color=INK_SOFT),\n        plot_margin=0.02,\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        legend_position=\"right\",\n        legend_title=element_text(size=8, weight=\"bold\", color=INK),\n        legend_text=element_text(size=7, color=INK_SOFT),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_key_height=30,\n        legend_key_width=8,\n    )\n)\n\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=8, height=4.5, units=\"in\", verbose=False)\n"}