{"spec_id":"surface-basic","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nsurface-basic: Basic 3D Surface Plot\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-05\n\"\"\"\n\nimport os\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\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 - create a smooth surface z = sin(x) * cos(y)\nnp.random.seed(42)\n\n# Grid setup - 50x50 for smoother surface\nn_points = 50\nx = np.linspace(-3, 3, n_points)\ny = np.linspace(-3, 3, n_points)\nX, Y = np.meshgrid(x, y)\n\n# Surface function\nZ = np.sin(X) * np.cos(Y)\n\n# 3D to 2D projection (elevation=25, azimuth=45)\nelev_rad = np.radians(25)\nazim_rad = np.radians(45)\n\n# Rotation around z-axis (azimuth)\nX_rot = X * np.cos(azim_rad) - Y * np.sin(azim_rad)\nY_rot = X * np.sin(azim_rad) + Y * np.cos(azim_rad)\n\n# Rotation around x-axis (elevation) and project\nX_proj = X_rot\nZ_proj = Y_rot * np.sin(elev_rad) + Z * np.cos(elev_rad)\n\n# Build facets with overlapping boundaries to reduce edge artifacts\nrect_data = []\noverlap = 0.02\nfor i in range(n_points - 1):\n    for j in range(n_points - 1):\n        # Get projected quad corners\n        corners_x = [X_proj[i, j], X_proj[i, j + 1], X_proj[i + 1, j], X_proj[i + 1, j + 1]]\n        corners_z = [Z_proj[i, j], Z_proj[i, j + 1], Z_proj[i + 1, j], Z_proj[i + 1, j + 1]]\n\n        # Add slight overlap to reduce visual gaps\n        x1 = min(corners_x) - overlap\n        x2 = max(corners_x) + overlap\n        y1 = min(corners_z) - overlap\n        y2 = max(corners_z) + overlap\n\n        # Average z for color (original Z, not projected)\n        avg_z = (Z[i, j] + Z[i, j + 1] + Z[i + 1, j + 1] + Z[i + 1, j]) / 4\n\n        # Depth for sorting (painter's algorithm - back to front)\n        depth = (Y_rot[i, j] + Y_rot[i, j + 1] + Y_rot[i + 1, j + 1] + Y_rot[i + 1, j]) / 4\n\n        rect_data.append({\"x1\": x1, \"x2\": x2, \"y1\": y1, \"y2\": y2, \"z\": avg_z, \"depth\": depth})\n\n# Sort by depth (back to front)\nrect_data.sort(key=lambda r: r[\"depth\"], reverse=True)\n\n# Assign order for rendering\nfor idx, r in enumerate(rect_data):\n    r[\"order\"] = idx\n\ndf_rects = pd.DataFrame(rect_data)\n\n# Create surface chart with filled rectangles\nsurface = (\n    alt.Chart(df_rects)\n    .mark_rect(strokeWidth=0.3, stroke=INK_SOFT, strokeOpacity=0.15)\n    .encode(\n        x=alt.X(\n            \"x1:Q\",\n            axis=alt.Axis(\n                title=\"X axis (projected)\",\n                labelFontSize=18,\n                titleFontSize=22,\n                labelColor=INK_SOFT,\n                titleColor=INK,\n                domainColor=INK_SOFT,\n                tickColor=INK_SOFT,\n                gridOpacity=0.10,\n                gridColor=INK,\n            ),\n        ),\n        y=alt.Y(\n            \"y1:Q\",\n            axis=alt.Axis(\n                title=\"Z axis (height)\",\n                labelFontSize=18,\n                titleFontSize=22,\n                labelColor=INK_SOFT,\n                titleColor=INK,\n                domainColor=INK_SOFT,\n                tickColor=INK_SOFT,\n                gridOpacity=0.10,\n                gridColor=INK,\n            ),\n        ),\n        x2=alt.X2(\"x2:Q\"),\n        y2=alt.Y2(\"y2:Q\"),\n        color=alt.Color(\n            \"z:Q\",\n            scale=alt.Scale(scheme=\"viridis\"),\n            legend=alt.Legend(\n                title=\"Surface Value\",\n                titleFontSize=20,\n                labelFontSize=16,\n                fillColor=ELEVATED_BG,\n                strokeColor=INK_SOFT,\n                labelColor=INK_SOFT,\n                titleColor=INK,\n            ),\n        ),\n        order=alt.Order(\"order:Q\"),\n        tooltip=[\n            alt.Tooltip(\"z:Q\", title=\"Surface value\", format=\".3f\"),\n            alt.Tooltip(\"x1:Q\", title=\"X coordinate\", format=\".2f\"),\n            alt.Tooltip(\"y1:Q\", title=\"Z coordinate\", format=\".2f\"),\n        ],\n    )\n)\n\n# Add pan and zoom interactivity\npan_zoom = alt.selection_interval(bind=\"scales\", encodings=[\"x\", \"y\"])\n\n# Combine into final chart with interactivity\nchart = (\n    surface.add_params(pan_zoom)\n    .properties(\n        width=1600,\n        height=900,\n        title=alt.Title(\"surface-basic · altair · anyplot.ai\", fontSize=28, color=INK),\n        background=PAGE_BG,\n    )\n    .configure_view(strokeWidth=0, fill=PAGE_BG)\n    .configure_axis(grid=True, gridOpacity=0.10)\n    .configure_title(color=INK)\n)\n\n# Save outputs\nchart.save(f\"plot-{THEME}.png\", scale_factor=3.0)\nchart.save(f\"plot-{THEME}.html\")\n"}