{"spec_id":"surface-basic","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nsurface-basic: Basic 3D Surface Plot\nLibrary: letsplot 4.10.1 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-06-17\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_polygon,\n    geom_text,\n    ggplot,\n    ggsize,\n    labs,\n    layer_tooltips,\n    scale_fill_gradient2,\n    theme,\n    theme_minimal,\n)\nfrom lets_plot.export import ggsave\n\n\nLetsPlot.setup_html()\n\n# Theme-adaptive chrome 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# Imprint diverging midpoint is theme-adaptive\nDIV_MID = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\n\n# Data: z = sin(x)*cos(y) — shows peaks, valleys, and saddle points on a 40×40 grid\nnp.random.seed(42)\nn_points = 40\nx = np.linspace(-3, 3, n_points)\ny = np.linspace(-3, 3, n_points)\nX, Y = np.meshgrid(x, y)\nZ = np.sin(X) * np.cos(Y)\n\n# 3D → 2D projection (elevation=25°, azimuth=45°)\nelev_rad = np.radians(25)\nazim_rad = np.radians(45)\nX_rot = X * np.cos(azim_rad) - Y * np.sin(azim_rad)\nY_rot = X * np.sin(azim_rad) + Y * np.cos(azim_rad)\nX_proj = X_rot\nZ_proj = Y_rot * np.sin(elev_rad) + Z * np.cos(elev_rad)\n\n# Build quads with painter's algorithm (back-to-front depth sort)\nquads = []\nfor i in range(n_points - 1):\n    for j in range(n_points - 1):\n        corners_x = [X_proj[i, j], X_proj[i, j + 1], X_proj[i + 1, j + 1], X_proj[i + 1, j]]\n        corners_y = [Z_proj[i, j], Z_proj[i, j + 1], Z_proj[i + 1, j + 1], Z_proj[i + 1, j]]\n        avg_z = (Z[i, j] + Z[i, j + 1] + Z[i + 1, j + 1] + Z[i + 1, j]) / 4\n        depth = (Y_rot[i, j] + Y_rot[i, j + 1] + Y_rot[i + 1, j + 1] + Y_rot[i + 1, j]) / 4\n        quads.append((depth, corners_x, corners_y, avg_z))\n\nquads.sort(key=lambda q: q[0], reverse=True)\n\npoly_data = []\nfor group_id, (_, corners_x, corners_y, avg_z) in enumerate(quads):\n    for cx, cy in zip(corners_x, corners_y, strict=True):\n        poly_data.append({\"x\": cx, \"y\": cy, \"z\": avg_z, \"group\": group_id})\n\ndf = pd.DataFrame(poly_data)\n\n# Annotate peak (x≈π/2, y≈0, z≈+1) and valley (x≈−π/2, y≈0, z≈−1)\nj_peak = np.argmin(np.abs(x - np.pi / 2))\ni_peak = np.argmin(np.abs(y - 0.0))\nj_valley = np.argmin(np.abs(x + np.pi / 2))\ni_valley = i_peak\n\nann_df = pd.DataFrame(\n    {\n        \"x\": [X_proj[i_peak, j_peak], X_proj[i_valley, j_valley]],\n        \"y\": [Z_proj[i_peak, j_peak] + 0.14, Z_proj[i_valley, j_valley] - 0.14],\n        \"z\": [1.0, -1.0],\n        \"group\": [-1, -2],\n        \"label\": [\"peak  z ≈ +1\", \"valley  z ≈ −1\"],\n    }\n)\n\n# Imprint diverging colormap: matte-red (negative) → neutral midpoint → blue (positive)\nplot = (\n    ggplot(df, aes(x=\"x\", y=\"y\", group=\"group\", fill=\"z\"))\n    + geom_polygon(color=INK_SOFT, size=0.15, alpha=1.0, tooltips=layer_tooltips().line(\"Z value: @z{.3f}\"))\n    + geom_text(aes(label=\"label\"), data=ann_df, color=INK, size=3.5, fontface=\"bold\")\n    + scale_fill_gradient2(low=\"#AE3030\", mid=DIV_MID, high=\"#4467A3\", midpoint=0, name=\"Z Value\")\n    + labs(x=\"X (projected)\", y=\"Z (height)\", title=\"surface-basic · python · letsplot · anyplot.ai\")\n    + theme_minimal()\n    + theme(\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        axis_title=element_text(size=12, color=INK),\n        axis_text=element_text(size=10, color=INK_SOFT),\n        plot_title=element_text(size=16, color=INK),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_text=element_text(size=10, color=INK_SOFT),\n        legend_title=element_text(size=12, color=INK),\n        panel_grid=element_blank(),\n        axis_line=element_blank(),\n    )\n    + ggsize(800, 450)\n)\n\n# scale=4 → 3200×1800 px (landscape, per canvas contract)\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}