{"spec_id":"quiver-basic","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nquiver-basic: Basic Quiver Plot\nLibrary: letsplot 4.11.0 | Python 3.13.14\nQuality: 88/100 | Updated: 2026-07-24\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    arrow,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_segment,\n    ggplot,\n    ggsize,\n    labs,\n    layer_tooltips,\n    scale_color_gradient,\n    theme,\n    theme_minimal,\n)\nfrom lets_plot.export import ggsave\n\n\nLetsPlot.setup_html()\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 - 2D rotation vector field: u = -y, v = x (circular flow pattern)\ngrid_size = 15\nx_range = np.linspace(-3, 3, grid_size)\ny_range = np.linspace(-3, 3, grid_size)\nX, Y = np.meshgrid(x_range, y_range)\nX = X.flatten()\nY = Y.flatten()\n\n# Vector components (rotation field: u = -y, v = x)\nU = -Y\nV = X\n\nmagnitude = np.sqrt(U**2 + V**2)\n\n# Grid spacing is 6/(grid_size-1) ~= 0.43; scale_factor keeps the longest\n# scaled arrow (at the outer ring, magnitude ~4.24) shorter than that\n# spacing so shafts/heads stay visually separated.\nscale_factor = 0.08\nU_scaled = U * scale_factor\nV_scaled = V * scale_factor\n\ndf = pd.DataFrame({\"x\": X, \"y\": Y, \"xend\": X + U_scaled, \"yend\": Y + V_scaled, \"magnitude\": magnitude, \"u\": U, \"v\": V})\n\n# Theme-adaptive chrome\nanyplot_theme = theme(\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_SOFT, size=0.15),\n    panel_grid_minor=element_blank(),\n    axis_title=element_text(color=INK, size=12),\n    axis_text=element_text(color=INK_SOFT, size=10),\n    axis_line=element_line(color=INK_SOFT),\n    plot_title=element_text(color=INK, size=16),\n    legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n    legend_text=element_text(color=INK_SOFT, size=10),\n    legend_title=element_text(color=INK, size=12),\n)\n\n# Plot — magnitude is single-polarity (>= 0), so the Imprint sequential gradient applies\nsegment_tooltips = layer_tooltips().line(\"Position|(@x, @y)\").line(\"u, v|(@u, @v)\").line(\"Magnitude|@magnitude\")\n\nplot = (\n    ggplot(df, aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\", color=\"magnitude\"))\n    + geom_segment(arrow=arrow(angle=20, length=8, type=\"closed\"), size=1.2, tooltips=segment_tooltips)\n    + scale_color_gradient(low=\"#009E73\", high=\"#4467A3\", name=\"Magnitude\")\n    + labs(x=\"X Position (m)\", y=\"Y Position (m)\", title=\"quiver-basic · python · letsplot · anyplot.ai\")\n    + ggsize(800, 450)\n    + theme_minimal()\n    + anyplot_theme\n)\n\n# Save PNG (scale 4x to get 3200 x 1800 px) and HTML\nggsave(plot, filename=f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, filename=f\"plot-{THEME}.html\", path=\".\")\n"}