{"spec_id":"contour-3d","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\ncontour-3d: 3D Contour Plot\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 93/100 | Updated: 2026-05-16\n\"\"\"\n\nimport os\nimport sys\nimport time\nfrom pathlib import Path\n\n\nscript_dir = os.path.dirname(os.path.abspath(__file__))\nif sys.path and (sys.path[0] == \"\" or sys.path[0] == script_dir):\n    sys.path.pop(0)\n\nos.chdir(script_dir)\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColorBar, LinearColorMapper, Range1d\nfrom bokeh.palettes import Viridis256\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\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\nnp.random.seed(42)\n\nn_points = 40\nx = np.linspace(-3, 3, n_points)\ny = np.linspace(-3, 3, n_points)\nX, Y = np.meshgrid(x, y)\n\nZ = 1.0 * np.exp(-(X**2 + Y**2) / 1.5) + 0.6 * np.exp(-((X - 1.5) ** 2 + (Y + 1.2) ** 2) / 0.8)\n\nz_min, z_max = Z.min(), Z.max()\n\nelev_rad = np.radians(25)\nazim_rad = np.radians(45)\ncos_azim = np.cos(azim_rad)\nsin_azim = np.sin(azim_rad)\nsin_elev = np.sin(elev_rad)\ncos_elev = np.cos(elev_rad)\n\nZ_scaled = (Z - z_min) / (z_max - z_min) * 2\n\nX_proj = np.zeros_like(X)\nZ_proj = np.zeros_like(X)\nDepth = np.zeros_like(X)\n\nfor i in range(n_points):\n    for j in range(n_points):\n        x_3d, y_3d, z_3d = X[i, j], Y[i, j], Z_scaled[i, j]\n        x_rot = x_3d * cos_azim - y_3d * sin_azim\n        y_rot = x_3d * sin_azim + y_3d * cos_azim\n        X_proj[i, j] = x_rot\n        Z_proj[i, j] = y_rot * sin_elev + z_3d * cos_elev\n        Depth[i, j] = y_rot * cos_elev - z_3d * sin_elev\n\nn_levels = 10\nlevels = np.linspace(z_min, z_max, n_levels)\n\ncolor_mapper = LinearColorMapper(palette=Viridis256, low=z_min, high=z_max)\n\nsurface_quads = []\nfor i in range(n_points - 1):\n    for j in range(n_points - 1):\n        xs = [X_proj[i, j], X_proj[i + 1, j], X_proj[i + 1, j + 1], X_proj[i, j + 1]]\n        ys = [Z_proj[i, j], Z_proj[i + 1, j], Z_proj[i + 1, j + 1], Z_proj[i, j + 1]]\n\n        avg_depth = (Depth[i, j] + Depth[i + 1, j] + Depth[i + 1, j + 1] + Depth[i, j + 1]) / 4\n        avg_z = (Z[i, j] + Z[i + 1, j] + Z[i + 1, j + 1] + Z[i, j + 1]) / 4\n\n        idx = int((avg_z - z_min) / (z_max - z_min) * 255)\n        idx = max(0, min(255, idx))\n        color = Viridis256[idx]\n\n        surface_quads.append((avg_depth, xs, ys, color))\n\nsurface_quads.sort(key=lambda q: q[0], reverse=True)\n\nfig_temp, ax_temp = plt.subplots()\ncontour_set = ax_temp.contour(x, y, Z, levels=levels)\nplt.close(fig_temp)\n\ncontour_lines_3d = []\nbase_contours = []\n\nfor level_idx, level in enumerate(levels):\n    z_height = (level - z_min) / (z_max - z_min) * 2\n\n    paths = contour_set.get_paths()\n    for path in paths:\n        vertices = path.vertices\n        if len(vertices) > 1:\n            line_xs = []\n            line_ys = []\n            line_depths = []\n            for pt in vertices:\n                x_pt, y_pt = pt\n                x_rot = x_pt * cos_azim - y_pt * sin_azim\n                y_rot = x_pt * sin_azim + y_pt * cos_azim\n                line_xs.append(x_rot)\n                line_ys.append(y_rot * sin_elev + z_height * cos_elev)\n                line_depths.append(y_rot * cos_elev - z_height * sin_elev)\n\n            if len(line_xs) > 1:\n                avg_depth = np.mean(line_depths)\n                contour_lines_3d.append((avg_depth, line_xs, line_ys))\n\n            line_xs_base = []\n            line_ys_base = []\n            line_depths_base = []\n            for pt in vertices:\n                x_pt, y_pt = pt\n                x_rot = x_pt * cos_azim - y_pt * sin_azim\n                y_rot = x_pt * sin_azim + y_pt * cos_azim\n                line_xs_base.append(x_rot)\n                line_ys_base.append(y_rot * sin_elev)\n                line_depths_base.append(y_rot * cos_elev)\n\n            if len(line_xs_base) > 1:\n                avg_depth = np.mean(line_depths_base)\n                idx = int(level_idx * 255 / (n_levels - 1))\n                color = Viridis256[idx]\n                base_contours.append((avg_depth, line_xs_base, line_ys_base, color))\n\np = figure(\n    width=4800,\n    height=2700,\n    title=\"contour-3d · bokeh · anyplot.ai\",\n    toolbar_location=\"right\",\n    tools=\"pan,wheel_zoom,box_zoom,reset,save\",\n)\n\np.xaxis.visible = False\np.yaxis.visible = False\n\nfor _depth, xs, ys, color in sorted(base_contours, key=lambda c: c[0], reverse=True):\n    p.line(x=xs, y=ys, line_color=color, line_width=3.5, line_alpha=0.65, line_dash=\"dashed\")\n\nfor _depth, xs, ys, color in surface_quads:\n    p.patch(x=xs, y=ys, fill_color=color, line_color=INK_SOFT, line_width=0.5, line_alpha=0.3, alpha=0.9)\n\nfor _depth, xs, ys in sorted(contour_lines_3d, key=lambda c: c[0], reverse=False):\n    p.line(x=xs, y=ys, line_color=INK, line_width=2.5, line_alpha=0.8)\n\nall_x_coords = [x for quad in surface_quads for x in quad[1]]\nall_y_coords = [y for quad in surface_quads for y in quad[2]]\n\nx_min_plot, x_max_plot = min(all_x_coords), max(all_x_coords)\ny_min_plot, y_max_plot = min(all_y_coords), max(all_y_coords)\n\nx_pad = (x_max_plot - x_min_plot) * 0.15\ny_pad = (y_max_plot - y_min_plot) * 0.12\n\np.x_range = Range1d(x_min_plot - x_pad * 1.2, x_max_plot + x_pad * 2.0)\np.y_range = Range1d(y_min_plot - y_pad * 0.8, y_max_plot + y_pad * 1.4)\n\nox, oy, oz = -3.5, -3.5, 0\norigin_x = ox * cos_azim - oy * sin_azim\norigin_y = (ox * sin_azim + oy * cos_azim) * sin_elev + oz * cos_elev\n\naxis_color = INK_SOFT\naxis_width = 6\n\nax, ay, az = 3.5, -3.5, 0\nx_axis_end_x = ax * cos_azim - ay * sin_azim\nx_axis_end_y = (ax * sin_azim + ay * cos_azim) * sin_elev + az * cos_elev\np.line(x=[origin_x, x_axis_end_x], y=[origin_y, x_axis_end_y], line_color=axis_color, line_width=axis_width)\n\nbx, by, bz = -3.5, 3.5, 0\ny_axis_end_x = bx * cos_azim - by * sin_azim\ny_axis_end_y = (bx * sin_azim + by * cos_azim) * sin_elev + bz * cos_elev\np.line(x=[origin_x, y_axis_end_x], y=[origin_y, y_axis_end_y], line_color=axis_color, line_width=axis_width)\n\ncx, cy, cz = -3.5, -3.5, 2.5\nz_axis_end_x = cx * cos_azim - cy * sin_azim\nz_axis_end_y = (cx * sin_azim + cy * cos_azim) * sin_elev + cz * cos_elev\np.line(x=[origin_x, z_axis_end_x], y=[origin_y, z_axis_end_y], line_color=axis_color, line_width=axis_width)\n\narrow_size = 0.25\n\nx_dir = np.array([x_axis_end_x - origin_x, x_axis_end_y - origin_y])\nx_dir = x_dir / np.linalg.norm(x_dir)\nx_perp = np.array([-x_dir[1], x_dir[0]])\np.patch(\n    x=[\n        x_axis_end_x,\n        x_axis_end_x - arrow_size * x_dir[0] + arrow_size * 0.5 * x_perp[0],\n        x_axis_end_x - arrow_size * x_dir[0] - arrow_size * 0.5 * x_perp[0],\n    ],\n    y=[\n        x_axis_end_y,\n        x_axis_end_y - arrow_size * x_dir[1] + arrow_size * 0.5 * x_perp[1],\n        x_axis_end_y - arrow_size * x_dir[1] - arrow_size * 0.5 * x_perp[1],\n    ],\n    fill_color=axis_color,\n    line_color=axis_color,\n)\n\ny_dir = np.array([y_axis_end_x - origin_x, y_axis_end_y - origin_y])\ny_dir = y_dir / np.linalg.norm(y_dir)\ny_perp = np.array([-y_dir[1], y_dir[0]])\np.patch(\n    x=[\n        y_axis_end_x,\n        y_axis_end_x - arrow_size * y_dir[0] + arrow_size * 0.5 * y_perp[0],\n        y_axis_end_x - arrow_size * y_dir[0] - arrow_size * 0.5 * y_perp[0],\n    ],\n    y=[\n        y_axis_end_y,\n        y_axis_end_y - arrow_size * y_dir[1] + arrow_size * 0.5 * y_perp[1],\n        y_axis_end_y - arrow_size * y_dir[1] - arrow_size * 0.5 * y_perp[1],\n    ],\n    fill_color=axis_color,\n    line_color=axis_color,\n)\n\nz_dir = np.array([z_axis_end_x - origin_x, z_axis_end_y - origin_y])\nz_dir = z_dir / np.linalg.norm(z_dir)\nz_perp = np.array([-z_dir[1], z_dir[0]])\np.patch(\n    x=[\n        z_axis_end_x,\n        z_axis_end_x - arrow_size * z_dir[0] + arrow_size * 0.5 * z_perp[0],\n        z_axis_end_x - arrow_size * z_dir[0] - arrow_size * 0.5 * z_perp[0],\n    ],\n    y=[\n        z_axis_end_y,\n        z_axis_end_y - arrow_size * z_dir[1] + arrow_size * 0.5 * z_perp[1],\n        z_axis_end_y - arrow_size * z_dir[1] - arrow_size * 0.5 * z_perp[1],\n    ],\n    fill_color=axis_color,\n    line_color=axis_color,\n)\n\ncolor_bar = ColorBar(\n    color_mapper=color_mapper,\n    width=80,\n    location=(0, 0),\n    title=\"Amplitude (a.u.)\",\n    title_text_font_size=\"40pt\",\n    major_label_text_font_size=\"32pt\",\n    title_standoff=30,\n    margin=50,\n    padding=25,\n)\np.add_layout(color_bar, \"right\")\n\np.title.text_font_size = \"28pt\"\np.title.text_color = INK\n\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = INK_SOFT\n\np.xgrid.grid_line_color = INK\np.ygrid.grid_line_color = INK\np.xgrid.grid_line_alpha = 0.10\np.ygrid.grid_line_alpha = 0.10\n\nif p.legend:\n    p.legend.background_fill_color = ELEVATED_BG\n    p.legend.border_line_color = INK_SOFT\n    p.legend.label_text_color = INK_SOFT\n\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\nW, H = 4800, 2700\nopts = Options()\nfor arg in (\n    \"--headless=new\",\n    \"--no-sandbox\",\n    \"--disable-dev-shm-usage\",\n    \"--disable-gpu\",\n    f\"--window-size={W},{H}\",\n    \"--hide-scrollbars\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(W, H)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ntime.sleep(3)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}