{"spec_id":"scatter-basic","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nscatter-basic: Basic Scatter Plot\nLibrary: bokeh 3.9.0 | Python 3.14.4\nQuality: 90/100 | Updated: 2026-04-23\n\"\"\"\n\nimport os\n\nimport numpy as np\nfrom bokeh.io import export_png, output_file, save\nfrom bokeh.models import ColumnDataSource, HoverTool\nfrom bokeh.plotting import figure\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\"\n\n# Data — study hours vs exam scores, moderate positive correlation\nnp.random.seed(42)\nn_points = 180\nstudy_hours = np.random.uniform(0.8, 9.6, n_points)\nexam_scores = study_hours * 7.2 + np.random.normal(0, 6.5, n_points) + 26\nexam_scores = np.clip(exam_scores, 18, 99)\n\nsource = ColumnDataSource(data={\"study_hours\": study_hours, \"exam_scores\": exam_scores})\n\n# Plot\np = figure(\n    width=4800,\n    height=2700,\n    title=\"scatter-basic · bokeh · anyplot.ai\",\n    x_axis_label=\"Study Hours per Day\",\n    y_axis_label=\"Exam Score (%)\",\n    toolbar_location=None,\n    x_range=(0, 10.5),\n    y_range=(10, 104),\n)\n\nscatter_renderer = p.scatter(\n    x=\"study_hours\", y=\"exam_scores\", source=source, size=34, color=BRAND, alpha=0.7, line_color=PAGE_BG, line_width=1.2\n)\n\n# HoverTool — Bokeh's distinctive interactive feature (HTML only; PNG stays clean)\nhover = HoverTool(\n    renderers=[scatter_renderer],\n    tooltips=[(\"Study Hours\", \"@study_hours{0.1} hrs\"), (\"Exam Score\", \"@exam_scores{0.0}%\")],\n)\np.add_tools(hover)\n\n# Typography — sized for 4800×2700 canvas\np.title.text_font_size = \"42pt\"\np.title.text_font_style = \"bold\"\np.title.text_color = INK\np.title.align = \"center\"\n\np.xaxis.axis_label_text_font_size = \"32pt\"\np.yaxis.axis_label_text_font_size = \"32pt\"\np.xaxis.axis_label_text_font_style = \"normal\"\np.yaxis.axis_label_text_font_style = \"normal\"\np.xaxis.major_label_text_font_size = \"24pt\"\np.yaxis.major_label_text_font_size = \"24pt\"\np.xaxis.axis_label_standoff = 28\np.yaxis.axis_label_standoff = 28\n\n# Theme-adaptive chrome\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = None\n\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\n\n# Clean L-frame: keep left+bottom axis lines only (handled above via axis_line_color)\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 = 2\np.ygrid.grid_line_width = 2\n\np.xaxis.ticker.desired_num_ticks = 10\np.yaxis.ticker.desired_num_ticks = 8\n\n# Save\nexport_png(p, filename=f\"plot-{THEME}.png\")\noutput_file(f\"plot-{THEME}.html\", title=\"scatter-basic · bokeh · anyplot.ai\")\nsave(p)\n"}