{"spec_id":"quiver-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nquiver-basic: Basic Quiver Plot\nLibrary: plotnine 0.15.7 | Python 3.13.14\nQuality: 84/100 | Updated: 2026-07-24\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    annotate,\n    arrow,\n    coord_equal,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_segment,\n    ggplot,\n    labs,\n    scale_color_gradient,\n    theme,\n    theme_minimal,\n)\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# Data: 13x13 grid rotation flow field (u = -y, v = x) — a slightly coarser\n# grid than 15x15 gives corner arrowheads more breathing room.\ngrid_size = 13\nx_vals = np.linspace(-3, 3, grid_size)\ny_vals = np.linspace(-3, 3, grid_size)\nX, Y = np.meshgrid(x_vals, y_vals)\nx = X.flatten()\ny = Y.flatten()\n\n# Rotation field: u = -y, v = x (counter-clockwise rotation)\nu = -y\nv = x\n\n# Magnitude for color encoding\nmagnitude = np.sqrt(u**2 + v**2)\n\n# Scale arrows for visibility without overlap. A fixed minimum length keeps\n# near-origin arrows (magnitude ~ 0) directionally readable instead of\n# collapsing to invisible stubs, while length still grows with magnitude.\narrow_scale = 0.14\nmin_len = 0.08\ndirection_norm = magnitude + 1e-6\ndisplay_len = min_len + arrow_scale * magnitude\nu_scaled = u / direction_norm * display_len\nv_scaled = v / direction_norm * display_len\n\n# DataFrame with segment start/end and magnitude\ndf = pd.DataFrame({\"x\": x, \"y\": y, \"xend\": x + u_scaled, \"yend\": y + v_scaled, \"magnitude\": magnitude})\n\n# Plot\nanyplot_theme = theme(\n    figure_size=(6, 6),\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG),\n    panel_grid_major=element_line(color=INK, size=0.3, alpha=0.15),\n    panel_grid_minor=element_line(color=INK, size=0.2, alpha=0.05),\n    panel_border=element_blank(),\n    axis_title=element_text(color=INK, size=10),\n    axis_text=element_text(color=INK_SOFT, size=8),\n    axis_line=element_line(color=INK_SOFT),\n    plot_title=element_text(color=INK, size=12, weight=\"bold\"),\n    legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n    legend_text=element_text(color=INK_SOFT, size=8),\n    legend_title=element_text(color=INK, size=8),\n    # Dock the colorbar to the plot instead of floating in the right margin.\n    legend_box_margin=0,\n    legend_margin=2,\n    legend_box_spacing=0.05,\n    plot_margin=0.01,\n)\n\nplot = (\n    ggplot(df, aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\", color=\"magnitude\"))\n    + geom_segment(arrow=arrow(length=0.15, type=\"closed\"), size=1.0)\n    # Imprint sequential colormap (imprint_seq): brand green -> blue, for\n    # single-polarity magnitude data (no meaningful zero-crossing midpoint).\n    + scale_color_gradient(low=\"#009E73\", high=\"#4467A3\", name=\"Magnitude\")\n    + labs(x=\"East (km)\", y=\"North (km)\", title=\"quiver-basic · python · plotnine · anyplot.ai\")\n    + theme_minimal()\n    + anyplot_theme\n    + coord_equal()\n    # Mark the rotation axis at the origin to give the eye a deliberate anchor point.\n    + annotate(\"point\", x=0, y=0, color=INK_SOFT, size=3, shape=\"o\", fill=PAGE_BG)\n    + annotate(\"text\", x=0.35, y=-3.35, label=\"rotation axis\", color=INK_SOFT, size=6, ha=\"left\")\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=6, height=6, units=\"in\", verbose=False)\n"}