{"spec_id":"quiver-basic","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nquiver-basic: Basic Quiver Plot\nLibrary: seaborn 0.13.2 | Python 3.13.14\nQuality: 86/100 | Updated: 2026-07-24\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nfrom matplotlib.colors import LinearSegmentedColormap\nfrom matplotlib.patches import Polygon\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\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.10,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Imprint sequential colormap — magnitude is single-polarity (always >= 0)\nimprint_seq = LinearSegmentedColormap.from_list(\"imprint_seq\", [\"#009E73\", \"#4467A3\"])\n\n# Data - idealized ocean eddy current field (u = -0.05y, v = 0.05x), 20x20 grid\ngrid_size = 20\neast_km = np.linspace(-30, 30, grid_size)\nnorth_km = np.linspace(-30, 30, grid_size)\nEast, North = np.meshgrid(east_km, north_km)\nx = East.flatten()\ny = North.flatten()\n\nu = -0.05 * y\nv = 0.05 * x\nmagnitude = np.sqrt(u**2 + v**2)\n\n# Scale displacement by a constant factor so arrow length stays proportional\n# to magnitude (this is what makes it a true quiver plot, not a normalized one)\nspacing = east_km[1] - east_km[0]\narrow_scale = (0.85 * spacing) / magnitude.max()\nx_end = x + arrow_scale * u\ny_end = y + arrow_scale * v\n\nnorm = plt.Normalize(0, magnitude.max())\n\n# Build shaft segments for a seaborn continuous-hue lineplot, and filled\n# triangular arrowheads (matplotlib patches) colored to match each shaft.\n# head_length is clamped to a minimum absolute size (independent of seg_length)\n# so low-magnitude arrows near the vortex center still read as directional\n# arrows instead of collapsing to arrowhead-less stubs/dots.\nhead_ratio = 0.32\nhead_half_angle = 0.45\nmax_seg_length = 0.85 * spacing\nmin_head_length = 0.35 * head_ratio * max_seg_length\n\nline_data = []\nhead_patches = []\nfor i in range(len(x)):\n    mag = magnitude[i]\n    if mag < 0.05:\n        continue\n\n    angle = np.arctan2(y_end[i] - y[i], x_end[i] - x[i])\n    seg_length = mag * arrow_scale\n    head_length = max(head_ratio * seg_length, min_head_length)\n\n    line_data.append({\"x\": x[i], \"y\": y[i], \"segment\": i, \"order\": 0, \"magnitude\": mag})\n    line_data.append({\"x\": x_end[i], \"y\": y_end[i], \"segment\": i, \"order\": 1, \"magnitude\": mag})\n\n    base_x = x_end[i] - head_length * np.cos(angle)\n    base_y = y_end[i] - head_length * np.sin(angle)\n    half_width = head_length * np.tan(head_half_angle)\n    left = (base_x - half_width * np.sin(angle), base_y + half_width * np.cos(angle))\n    right = (base_x + half_width * np.sin(angle), base_y - half_width * np.cos(angle))\n    head_patches.append(([(x_end[i], y_end[i]), left, right], imprint_seq(norm(mag))))\n\ndf = pd.DataFrame(line_data)\n\n# Plot — square canvas: the grid-based vector field has no preferred horizontal\n# axis, and aspect='equal' would otherwise waste horizontal space on a 16:9 canvas\nfig, ax = plt.subplots(figsize=(6, 6), dpi=400, facecolor=PAGE_BG, constrained_layout=True)\n\nsns.lineplot(\n    data=df,\n    x=\"x\",\n    y=\"y\",\n    hue=\"magnitude\",\n    hue_norm=(0, magnitude.max()),\n    units=\"segment\",\n    estimator=None,\n    sort=False,\n    palette=imprint_seq,\n    linewidth=1.6,\n    legend=False,\n    ax=ax,\n)\n\nfor vertices, color in head_patches:\n    ax.add_patch(Polygon(vertices, closed=True, facecolor=color, edgecolor=\"none\"))\n\n# Colorbar for magnitude\nsm = plt.cm.ScalarMappable(cmap=imprint_seq, norm=norm)\nsm.set_array([])\ncbar = plt.colorbar(sm, ax=ax, shrink=0.8, pad=0.02)\ncbar.set_label(\"Current Speed (m/s)\", fontsize=10, color=INK)\ncbar.ax.tick_params(labelsize=8, colors=INK_SOFT)\ncbar.outline.set_edgecolor(INK_SOFT)\n\n# Style\nax.set_xlabel(\"Distance East (km)\", fontsize=10, color=INK)\nax.set_ylabel(\"Distance North (km)\", fontsize=10, color=INK)\nax.set_title(\"quiver-basic · python · seaborn · anyplot.ai\", fontsize=12, fontweight=\"medium\", color=INK)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\nax.set_aspect(\"equal\")\nax.yaxis.grid(True, alpha=0.10, linewidth=0.8)\nax.xaxis.grid(True, alpha=0.10, linewidth=0.8)\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nax.spines[\"left\"].set_color(INK_SOFT)\nax.spines[\"bottom\"].set_color(INK_SOFT)\nax.set_xlim(-34, 34)\nax.set_ylim(-34, 34)\n\n# Save\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}