{"spec_id":"wireframe-3d-basic","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nwireframe-3d-basic: Basic 3D Wireframe Plot\nLibrary: pygal 3.1.3 | Python 3.13.14\nQuality: 81/100 | Updated: 2026-08-04\n\"\"\"\n\nimport os\nimport sys\n\n\n# Remove current directory from path to avoid importing local pygal.py\nsys.path = [p for p in sys.path if not (p == \"\" or p == \".\" or p.endswith(\"implementations/python\"))]\n\nimport numpy as np\nimport pygal\nfrom pygal.style import Style\n\n\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\nBRAND = \"#009E73\"  # Imprint palette position 1 — wireframe surface color\nBRAND_FADE = \"rgba(0, 158, 115, 0.55)\"  # translucent wireframe strokes fake depth ordering\n\nISO_ANGLE = np.pi / 6\nCOS_A, SIN_A = np.cos(ISO_ANGLE), np.sin(ISO_ANGLE)\n\n\ndef isometric_project(x, y, z):\n    \"\"\"Project 3D coordinates onto a 2D isometric view.\"\"\"\n    x2d = (x - y) * COS_A\n    y2d = z + (x + y) * SIN_A * 0.5\n    return x2d, y2d\n\n\n# Data — kept at the lower end of the spec's 20x20-50x50 range so the\n# isometric projection (no hidden-line removal) doesn't overplot into an\n# unreadable crosshatch.\ngrid_size = 20\nx = np.linspace(-5, 5, grid_size)\ny = np.linspace(-5, 5, grid_size)\nX, Y = np.meshgrid(x, y)\nZ = np.sin(np.sqrt(X**2 + Y**2))\n\n# One continuous polyline per row/column instead of a segment-per-edge —\n# keeps the same mesh but at 2*grid_size series instead of ~grid_size^2*2.\nrow_lines = [[isometric_project(X[i, j], Y[i, j], Z[i, j]) for j in range(grid_size)] for i in range(grid_size)]\ncol_lines = [[isometric_project(X[i, j], Y[i, j], Z[i, j]) for i in range(grid_size)] for j in range(grid_size)]\n\n# Small axis gizmo anchored at the world origin, projected the same way as\n# the surface, so it stays true to the isometric view instead of a separate\n# 2D inset. Its 3 legend entries are the only explicit \"X/Y/Z axis\" labels\n# pygal.XY's two native axis titles can't provide on their own.\naxis_extent = 5.8\nz_extent = 1.6\nx_axis = [isometric_project(-axis_extent, 0, 0), isometric_project(axis_extent, 0, 0)]\ny_axis = [isometric_project(0, -axis_extent, 0), isometric_project(0, axis_extent, 0)]\nz_axis = [isometric_project(0, 0, -z_extent), isometric_project(0, 0, z_extent)]\n\n# Y axis has no native pygal ticks (unlike X/Z, which get real numeric ticks\n# from the chart's own axes), so hand-draw perpendicular tick marks along its\n# gizmo line at real world-Y positions.\ny_tangent = np.array([-COS_A, SIN_A * 0.5])\ny_tangent /= np.linalg.norm(y_tangent)\ny_perp = np.array([-y_tangent[1], y_tangent[0]])\ntick_half_len = 0.22\ny_tick_values = np.linspace(-5, 5, 5)\ny_ticks = []\nfor v in y_tick_values:\n    cx, cy = isometric_project(0, v, 0)\n    p0 = (cx - y_perp[0] * tick_half_len, cy - y_perp[1] * tick_half_len)\n    p1 = (cx + y_perp[0] * tick_half_len, cy + y_perp[1] * tick_half_len)\n    y_ticks.append([p0, p1])\n\n# Title fontsize scales with title length (default-style-guide.md \"Title fontsize\")\ntitle = \"wireframe-3d-basic · python · pygal · anyplot.ai\"\ntitle_font_size = round(66 * min(1.0, 67 / len(title)))\n\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=(BRAND_FADE,) * (2 * grid_size) + (INK, INK, INK) + (INK,) * len(y_ticks),\n    title_font_size=title_font_size,\n    label_font_size=56,\n    major_label_font_size=44,\n    legend_font_size=44,\n    value_font_size=36,\n)\n\n# Plot\nchart = pygal.XY(\n    style=custom_style,\n    width=3200,\n    height=1800,\n    title=title,\n    x_title=\"X Axis\",\n    y_title=\"Z Axis (projected)\",\n    show_legend=True,\n)\n\nfor points in row_lines:\n    chart.add(None, points, show_dots=False, stroke_style={\"width\": 1.6})\nfor points in col_lines:\n    chart.add(None, points, show_dots=False, stroke_style={\"width\": 1.6})\n\nchart.add(\"X axis\", x_axis, show_dots=True, dots_size=6, stroke_style={\"width\": 4})\nchart.add(\"Y axis\", y_axis, show_dots=True, dots_size=6, stroke_style={\"width\": 4})\nchart.add(\"Z axis\", z_axis, show_dots=True, dots_size=6, stroke_style={\"width\": 4})\n\nfor points in y_ticks:\n    chart.add(None, points, show_dots=False, stroke_style={\"width\": 3})\n\n# Save\nchart.render_to_png(f\"plot-{THEME}.png\")\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(chart.render())\n"}