{"spec_id":"line-parametric","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nline-parametric: Parametric Curve Plot\nLibrary: bokeh 3.9.1 | Python 3.13.14\nQuality: 91/100 | Updated: 2026-06-20\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent bokeh.py from shadowing the bokeh package (script dir added to sys.path[0])\n_here = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p) != _here]\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.layouts import gridplot\nfrom bokeh.models import BasicTicker, ColorBar, ColumnDataSource, LinearColorMapper\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\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\n# Imprint sequential colormap — green (#009E73) → blue (#4467A3)\ndef _lerp_hex(c0, c1, t):\n    r0, g0, b0 = (int(c0[i : i + 2], 16) for i in (1, 3, 5))\n    r1, g1, b1 = (int(c1[i : i + 2], 16) for i in (1, 3, 5))\n    return \"#{:02X}{:02X}{:02X}\".format(\n        int(round(r0 + (r1 - r0) * t)), int(round(g0 + (g1 - g0) * t)), int(round(b0 + (b1 - b0) * t))\n    )\n\n\nIMPRINT_SEQ256 = [_lerp_hex(\"#009E73\", \"#4467A3\", i / 255.0) for i in range(256)]\n\n# Data — 1000 points for smooth curves\nn_points = 1000\nn_seg = n_points - 1\n\n# Lissajous figure: x = sin(3t), y = sin(2t), t ∈ [0, 2π]\nt_liss = np.linspace(0, 2 * np.pi, n_points)\nx_liss = np.sin(3 * t_liss)\ny_liss = np.sin(2 * t_liss)\n\n# Archimedean spiral: x = t·cos(t), y = t·sin(t), t ∈ [0, 4π]\nt_spiral = np.linspace(0, 4 * np.pi, n_points)\nx_spiral = t_spiral * np.cos(t_spiral)\ny_spiral = t_spiral * np.sin(t_spiral)\n\n\ndef make_segment_source(x, y):\n    n = len(x) - 1\n    return ColumnDataSource(\n        data={\n            \"xs\": [[x[i], x[i + 1]] for i in range(n)],\n            \"ys\": [[y[i], y[i + 1]] for i in range(n)],\n            \"color\": [IMPRINT_SEQ256[int(i / (n - 1) * 255)] for i in range(n)],\n        }\n    )\n\n\nseg_liss = make_segment_source(x_liss, y_liss)\nseg_spiral = make_segment_source(x_spiral, y_spiral)\n\n# Two panels side-by-side → 1600 + 1600 = 3200 px wide × 1800 px tall\nPANEL_W = 1600\nPANEL_H = 1800\n\np1 = figure(\n    width=PANEL_W,\n    height=PANEL_H,\n    title=\"line-parametric · python · bokeh · anyplot.ai\",\n    x_axis_label=\"x(t) = sin(3t)\",\n    y_axis_label=\"y(t) = sin(2t)\",\n    toolbar_location=None,\n    match_aspect=True,\n    min_border_bottom=160,\n    min_border_left=180,\n    min_border_top=110,\n    min_border_right=220,\n)\n\np1.multi_line(xs=\"xs\", ys=\"ys\", source=seg_liss, line_color=\"color\", line_width=9)\np1.scatter(\n    x=[x_liss[0]],\n    y=[y_liss[0]],\n    size=30,\n    fill_color=\"#009E73\",\n    line_color=PAGE_BG,\n    line_width=4,\n    legend_label=\"Start  t = 0\",\n)\np1.scatter(\n    x=[x_liss[-1]],\n    y=[y_liss[-1]],\n    size=30,\n    marker=\"square\",\n    fill_color=\"#4467A3\",\n    line_color=PAGE_BG,\n    line_width=4,\n    legend_label=\"End  t = 2π\",\n)\n\nliss_cbar = ColorBar(\n    color_mapper=LinearColorMapper(palette=IMPRINT_SEQ256, low=0, high=round(2 * np.pi, 2)),\n    ticker=BasicTicker(desired_num_ticks=5),\n    title=\"t (rad)\",\n    title_text_font_size=\"28pt\",\n    title_text_color=INK,\n    major_label_text_font_size=\"24pt\",\n    major_label_text_color=INK_SOFT,\n    label_standoff=14,\n    width=44,\n    padding=24,\n    background_fill_color=PAGE_BG,\n    border_line_color=INK_SOFT,\n)\np1.add_layout(liss_cbar, \"right\")\n\np2 = figure(\n    width=PANEL_W,\n    height=PANEL_H,\n    title=\"Archimedean Spiral · line-parametric · python · bokeh · anyplot.ai\",\n    x_axis_label=\"x(t) = t·cos(t)\",\n    y_axis_label=\"y(t) = t·sin(t)\",\n    toolbar_location=None,\n    match_aspect=True,\n    min_border_bottom=160,\n    min_border_left=180,\n    min_border_top=110,\n    min_border_right=220,\n)\n\np2.multi_line(xs=\"xs\", ys=\"ys\", source=seg_spiral, line_color=\"color\", line_width=9)\np2.scatter(\n    x=[x_spiral[0]],\n    y=[y_spiral[0]],\n    size=30,\n    fill_color=\"#009E73\",\n    line_color=PAGE_BG,\n    line_width=4,\n    legend_label=\"Start  t = 0\",\n)\np2.scatter(\n    x=[x_spiral[-1]],\n    y=[y_spiral[-1]],\n    size=30,\n    marker=\"square\",\n    fill_color=\"#4467A3\",\n    line_color=PAGE_BG,\n    line_width=4,\n    legend_label=\"End  t = 4π\",\n)\n\nspiral_cbar = ColorBar(\n    color_mapper=LinearColorMapper(palette=IMPRINT_SEQ256, low=0, high=round(4 * np.pi, 2)),\n    ticker=BasicTicker(desired_num_ticks=5),\n    title=\"t (rad)\",\n    title_text_font_size=\"28pt\",\n    title_text_color=INK,\n    major_label_text_font_size=\"24pt\",\n    major_label_text_color=INK_SOFT,\n    label_standoff=14,\n    width=44,\n    padding=24,\n    background_fill_color=PAGE_BG,\n    border_line_color=INK_SOFT,\n)\np2.add_layout(spiral_cbar, \"right\")\n\n# Theme-adaptive chrome for both panels\nfor p in [p1, p2]:\n    p.background_fill_color = PAGE_BG\n    p.border_fill_color = PAGE_BG\n    p.outline_line_color = None  # L-shaped frame: axes provide the bottom+left boundary\n\n    p.title.text_color = INK\n    p.title.text_font_size = \"36pt\"\n    p.title.text_font_style = \"normal\"\n\n    p.xaxis.axis_label_text_color = INK\n    p.yaxis.axis_label_text_color = INK\n    p.xaxis.axis_label_text_font_size = \"32pt\"\n    p.yaxis.axis_label_text_font_size = \"32pt\"\n    p.xaxis.axis_label_text_font_style = \"italic\"\n    p.yaxis.axis_label_text_font_style = \"italic\"\n    p.xaxis.major_label_text_color = INK_SOFT\n    p.yaxis.major_label_text_color = INK_SOFT\n    p.xaxis.major_label_text_font_size = \"26pt\"\n    p.yaxis.major_label_text_font_size = \"26pt\"\n    p.xaxis.axis_line_color = INK_SOFT\n    p.yaxis.axis_line_color = INK_SOFT\n    p.xaxis.major_tick_line_color = INK_SOFT\n    p.yaxis.major_tick_line_color = INK_SOFT\n    p.axis.minor_tick_line_color = None\n\n    p.xgrid.grid_line_color = INK\n    p.ygrid.grid_line_color = INK\n    p.xgrid.grid_line_alpha = 0.12\n    p.ygrid.grid_line_alpha = 0.12\n\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    p.legend.label_text_font_size = \"26pt\"\n    p.legend.glyph_width = 36\n    p.legend.glyph_height = 36\n    p.legend.location = \"top_right\"\n    p.legend.padding = 14\n    p.legend.spacing = 8\n\n# Layout\ngrid = gridplot([[p1, p2]], merge_tools=False, toolbar_location=None)\n\n# Save interactive HTML\nhtml_path = Path(f\"plot-{THEME}.html\")\noutput_file(str(html_path))\nsave(grid)\n\n# Strip default body margin so the plot fills the Selenium viewport exactly\nhtml = html_path.read_text()\nhtml = html.replace(\"<body>\", f'<body style=\"margin:0;padding:0;overflow:hidden;background:{PAGE_BG};\">', 1)\nhtml_path.write_text(html)\n\n# Screenshot with headless Chrome (Selenium) at canvas dimensions\nW, H = 3200, 1800\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)\n# Compensate for any browser chrome that reduces the actual viewport\nactual_h = driver.execute_script(\"return window.innerHeight\")\nif actual_h < H:\n    driver.set_window_size(W, H + (H - actual_h))\ndriver.get(f\"file://{html_path.resolve()}\")\ntime.sleep(3)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}