{"spec_id":"scatter-basic","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nscatter-basic: Basic Scatter Plot\nLibrary: bokeh 3.10.1 | Python 3.14.9\nQuality: 83/100 | Updated: 2026-10-31\n\"\"\"\n\nimport os\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nimport pandas as pd\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, HoverTool, Range1d\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\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nBRAND = \"#009E73\"  # Imprint palette position 1\n\nW, H = 3200, 1800\n\nX = \"Study Hours per Day\"  # role: x\nY = \"Exam Score (%)\"  # role: y\n\n# Data: replace this block with your own table, for example df = pd.read_csv(\"scores.csv\")\nrng = np.random.default_rng(42)\nhours = rng.uniform(0.8, 9.6, 180)\nscores = np.clip(hours * 7.2 + rng.normal(0, 6.5, 180) + 26, 18, 99)\ndf = pd.DataFrame({X: hours, Y: scores})\n\n# Plot\nx_pad = (df[X].max() - df[X].min()) * 0.06\ny_pad = (df[Y].max() - df[Y].min()) * 0.06\np = figure(\n    width=W,\n    height=H,\n    title=\"scatter-basic · bokeh · anyplot.ai\",\n    x_axis_label=X,\n    y_axis_label=Y,\n    toolbar_location=None,\n    x_range=Range1d(df[X].min() - x_pad, df[X].max() + x_pad),\n    y_range=Range1d(df[Y].min() - y_pad, df[Y].max() + y_pad),\n    min_border_bottom=160,\n    min_border_left=180,\n    min_border_top=110,\n    min_border_right=50,\n)\n\nsource = ColumnDataSource(df)\npoints = p.scatter(x=X, y=Y, source=source, size=24, color=BRAND, alpha=0.7, line_color=PAGE_BG, line_width=1)\n\n# HoverTool: Bokeh's distinctive interactive feature (HTML only; PNG stays clean)\np.add_tools(HoverTool(renderers=[points], tooltips=[(X, f\"@{{{X}}}{{0.1}}\"), (Y, f\"@{{{Y}}}{{0.0}}\")]))\n\n# Typography\np.title.text_font_size = \"50pt\"\np.title.text_font_style = \"bold\"\np.title.text_color = INK\np.title.align = \"center\"\np.xaxis.axis_label_text_font_size = \"42pt\"\np.yaxis.axis_label_text_font_size = \"42pt\"\np.xaxis.axis_label_text_font_style = \"normal\"\np.yaxis.axis_label_text_font_style = \"normal\"\np.xaxis.major_label_text_font_size = \"34pt\"\np.yaxis.major_label_text_font_size = \"34pt\"\n\n# Theme-adaptive chrome\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = None\np.xaxis.axis_label_text_color = INK\np.yaxis.axis_label_text_color = INK\np.xaxis.major_label_text_color = INK_SOFT\np.yaxis.major_label_text_color = INK_SOFT\np.xaxis.axis_line_color = INK_SOFT\np.yaxis.axis_line_color = INK_SOFT\np.xaxis.major_tick_line_color = INK_SOFT\np.yaxis.major_tick_line_color = INK_SOFT\np.xaxis.minor_tick_line_color = None\np.yaxis.minor_tick_line_color = None\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\np.xgrid.grid_line_width = 1\np.ygrid.grid_line_width = 1\n\n# Save\noutput_file(f\"plot-{THEME}.html\", title=\"scatter-basic · bokeh · anyplot.ai\")\nsave(p)\n\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()}\")\ndriver.execute_cdp_cmd(\n    \"Emulation.setDeviceMetricsOverride\", {\"width\": W, \"height\": H, \"deviceScaleFactor\": 1, \"mobile\": False}\n)\ntime.sleep(3)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}