{"spec_id":"arc-basic","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\narc-basic: Basic Arc Diagram\nLibrary: letsplot 4.10.1 | Python 3.13.13\nQuality: 87/100 | Updated: 2026-05-30\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    element_blank,\n    element_rect,\n    element_text,\n    geom_path,\n    geom_point,\n    geom_segment,\n    geom_text,\n    ggplot,\n    ggsize,\n    labs,\n    layer_tooltips,\n    scale_alpha_identity,\n    scale_color_identity,\n    scale_size_identity,\n    theme,\n    xlim,\n    ylim,\n)\nfrom lets_plot.export import ggsave\n\n\nLetsPlot.setup_html()\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\"\n\n# Imprint palette — 3 weight levels in canonical order, first = brand green\nARC_COLORS = {\n    1: \"#009E73\",  # Weak — Imprint position 1 (brand green)\n    2: \"#C475FD\",  # Moderate — Imprint position 2 (lavender)\n    3: \"#4467A3\",  # Strong — Imprint position 3 (blue)\n}\nARC_ALPHAS = {1: 0.70, 2: 0.82, 3: 0.95}\nNODE_FILL = \"#BD8233\"  # Imprint position 4 (ochre) — warm entity anchor\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.3, n_nodes)\ny_baseline = 0.06\n\nconnections = [0] * n_nodes\nfor s, t, w in edges:\n    connections[s] += w\n    connections[t] += w\n\nweight_labels = {1: \"Weak\", 2: \"Moderate\", 3: \"Strong\"}\n\n# Arc path data — increased floor size for weak arc visibility\narc_data = []\nfor edge_id, (start, end, weight) in enumerate(edges):\n    x_start = x_positions[start]\n    x_end = x_positions[end]\n    distance = abs(end - start)\n    height = 0.08 * distance\n    n_points = 50\n    t_vals = np.linspace(0, np.pi, n_points)\n    arc_x = x_start + (x_end - x_start) * (1 - np.cos(t_vals)) / 2\n    arc_y = y_baseline + height * np.sin(t_vals)\n    line_size = 1.8 + weight * 1.3  # raised floor: weak=3.1, moderate=4.4, strong=5.7\n    for i in range(n_points):\n        arc_data.append(\n            {\n                \"x\": arc_x[i],\n                \"y\": arc_y[i],\n                \"edge_id\": edge_id,\n                \"size\": line_size,\n                \"color\": ARC_COLORS[weight],\n                \"alpha\": ARC_ALPHAS[weight],\n                \"connection\": f\"{nodes[start]} ↔ {nodes[end]}\",\n                \"strength\": weight_labels[weight],\n            }\n        )\n\narc_df = pd.DataFrame(arc_data)\n\nmax_conn = max(connections)\nnode_sizes = [10 + 8 * (c / max_conn) for c in connections]\nnode_df = pd.DataFrame(\n    {\"x\": x_positions, \"y\": [y_baseline] * n_nodes, \"name\": nodes, \"node_size\": node_sizes, \"connections\": connections}\n)\n\nbaseline_df = pd.DataFrame({\"x\": [x_positions[0]], \"xend\": [x_positions[-1]], \"y\": [y_baseline], \"yend\": [y_baseline]})\n\nlabel_df = pd.DataFrame({\"x\": x_positions, \"y\": [y_baseline - 0.038] * n_nodes, \"name\": nodes})\n\n# Legend — upper-left to balance canvas composition and utilize empty space\nlegend_x = 0.0\nlegend_y_start = 0.79\nlegend_spacing = 0.068\nlegend_line_len = 0.085\nlegend_lines = pd.DataFrame(\n    {\n        \"x\": [legend_x] * 3,\n        \"xend\": [legend_x + legend_line_len] * 3,\n        \"y\": [legend_y_start - i * legend_spacing for i in range(3)],\n        \"yend\": [legend_y_start - i * legend_spacing for i in range(3)],\n        \"color\": [ARC_COLORS[3], ARC_COLORS[2], ARC_COLORS[1]],\n        \"size\": [1.8 + 3 * 1.3, 1.8 + 2 * 1.3, 1.8 + 1 * 1.3],\n        \"alpha\": [ARC_ALPHAS[3], ARC_ALPHAS[2], ARC_ALPHAS[1]],\n    }\n)\nlegend_text_df = pd.DataFrame(\n    {\n        \"x\": [legend_x + legend_line_len + 0.013] * 3,\n        \"y\": [legend_y_start - i * legend_spacing for i in range(3)],\n        \"label\": [\"Strong (3)\", \"Moderate (2)\", \"Weak (1)\"],\n    }\n)\nlegend_title_df = pd.DataFrame({\"x\": [legend_x], \"y\": [legend_y_start + 0.075], \"label\": [\"Connection Strength\"]})\n\nalice_df = node_df[node_df[\"name\"] == \"Alice\"].copy()\narc_peak_x = float((x_positions[0] + x_positions[9]) / 2)\narc_peak_y = float(y_baseline + 0.08 * 9 + 0.04)\narc_label_df = pd.DataFrame({\"x\": [arc_peak_x], \"y\": [arc_peak_y], \"label\": [\"longest range\"]})\n\n# Plot\nplot = (\n    ggplot()\n    + geom_segment(\n        data=baseline_df, mapping=aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\"), color=INK_SOFT, size=0.6, alpha=0.4\n    )\n    + geom_path(\n        data=arc_df,\n        mapping=aes(x=\"x\", y=\"y\", group=\"edge_id\", size=\"size\", color=\"color\", alpha=\"alpha\"),\n        tooltips=layer_tooltips().title(\"@connection\").line(\"Strength|@strength\"),\n    )\n    + scale_size_identity()\n    + scale_color_identity()\n    + scale_alpha_identity()\n    + geom_point(data=alice_df, mapping=aes(x=\"x\", y=\"y\"), size=22, color=\"#009E73\", fill=PAGE_BG, stroke=2.5, shape=21)\n    + geom_point(\n        data=node_df,\n        mapping=aes(x=\"x\", y=\"y\", size=\"node_size\"),\n        color=INK,\n        fill=NODE_FILL,\n        stroke=1.5,\n        shape=21,\n        tooltips=layer_tooltips().title(\"@name\").line(\"Total weight|@connections\"),\n    )\n    + geom_text(data=label_df, mapping=aes(x=\"x\", y=\"y\", label=\"name\"), size=9, color=INK, fontface=\"bold\", vjust=1)\n    + geom_segment(\n        data=legend_lines,\n        mapping=aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\", color=\"color\", size=\"size\", alpha=\"alpha\"),\n        tooltips=\"none\",\n    )\n    + geom_text(data=legend_text_df, mapping=aes(x=\"x\", y=\"y\", label=\"label\"), size=7, color=INK_SOFT, hjust=0)\n    + geom_text(\n        data=legend_title_df, mapping=aes(x=\"x\", y=\"y\", label=\"label\"), size=8, color=INK, fontface=\"bold\", hjust=0\n    )\n    + geom_text(data=arc_label_df, mapping=aes(x=\"x\", y=\"y\", label=\"label\"), size=7, color=INK_SOFT, fontface=\"italic\")\n    + xlim(-0.05, 1.48)\n    + ylim(-0.12, 0.92)\n    + labs(\n        title=\"arc-basic · python · letsplot · anyplot.ai\",\n        subtitle=\"Character interactions in a story chapter — node size reflects connection strength\",\n    )\n    + theme(\n        axis_title=element_blank(),\n        axis_text=element_blank(),\n        axis_ticks=element_blank(),\n        axis_line=element_blank(),\n        panel_grid=element_blank(),\n        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        plot_title=element_text(size=16, face=\"bold\", color=INK),\n        plot_subtitle=element_text(size=13, color=INK_SOFT),\n        legend_position=\"none\",\n    )\n    + ggsize(800, 450)\n)\n\n# Save — theme-suffixed filenames, scale=4 → 3200×1800 px\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}