The same plot in 14 other libraries — Python: Altair, lets-plot, Matplotlib, Plotly, plotnine, Pygal, Seaborn; R: ggplot2; Julia: Makie.jl; JavaScript: Chart.js, D3.js, Apache ECharts, Highcharts, MUI X Charts. Compare all 15 side by side: Basic Scatter Plot in Python, R, Julia and JavaScript.
A fundamental 2D scatter plot that displays the relationship between two numeric variables by plotting points on a Cartesian coordinate system. This visualization is essential for exploring correlations, identifying patterns, detecting outliers, and understanding the distribution of paired data points.

""" anyplot.ai
scatter-basic: Basic Scatter Plot
Library: bokeh 3.10.1 | Python 3.14.9
Quality: 83/100 | Updated: 2026-10-31
"""
import os
import time
from pathlib import Path
import numpy as np
import pandas as pd
from bokeh.io import output_file, save
from bokeh.models import ColumnDataSource, HoverTool, Range1d
from bokeh.plotting import figure
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
BRAND = "#009E73" # Imprint palette position 1
W, H = 3200, 1800
X = "Study Hours per Day" # role: x
Y = "Exam Score (%)" # role: y
# Data: replace this block with your own table, for example df = pd.read_csv("scores.csv")
rng = np.random.default_rng(42)
hours = rng.uniform(0.8, 9.6, 180)
scores = np.clip(hours * 7.2 + rng.normal(0, 6.5, 180) + 26, 18, 99)
df = pd.DataFrame({X: hours, Y: scores})
# Plot
x_pad = (df[X].max() - df[X].min()) * 0.06
y_pad = (df[Y].max() - df[Y].min()) * 0.06
p = figure(
width=W,
height=H,
title="scatter-basic · bokeh · anyplot.ai",
x_axis_label=X,
y_axis_label=Y,
toolbar_location=None,
x_range=Range1d(df[X].min() - x_pad, df[X].max() + x_pad),
y_range=Range1d(df[Y].min() - y_pad, df[Y].max() + y_pad),
min_border_bottom=160,
min_border_left=180,
min_border_top=110,
min_border_right=50,
)
source = ColumnDataSource(df)
points = p.scatter(x=X, y=Y, source=source, size=24, color=BRAND, alpha=0.7, line_color=PAGE_BG, line_width=1)
# HoverTool: Bokeh's distinctive interactive feature (HTML only; PNG stays clean)
p.add_tools(HoverTool(renderers=[points], tooltips=[(X, f"@{{{X}}}{{0.1}}"), (Y, f"@{{{Y}}}{{0.0}}")]))
# Typography
p.title.text_font_size = "50pt"
p.title.text_font_style = "bold"
p.title.text_color = INK
p.title.align = "center"
p.xaxis.axis_label_text_font_size = "42pt"
p.yaxis.axis_label_text_font_size = "42pt"
p.xaxis.axis_label_text_font_style = "normal"
p.yaxis.axis_label_text_font_style = "normal"
p.xaxis.major_label_text_font_size = "34pt"
p.yaxis.major_label_text_font_size = "34pt"
# Theme-adaptive chrome
p.background_fill_color = PAGE_BG
p.border_fill_color = PAGE_BG
p.outline_line_color = None
p.xaxis.axis_label_text_color = INK
p.yaxis.axis_label_text_color = INK
p.xaxis.major_label_text_color = INK_SOFT
p.yaxis.major_label_text_color = INK_SOFT
p.xaxis.axis_line_color = INK_SOFT
p.yaxis.axis_line_color = INK_SOFT
p.xaxis.major_tick_line_color = INK_SOFT
p.yaxis.major_tick_line_color = INK_SOFT
p.xaxis.minor_tick_line_color = None
p.yaxis.minor_tick_line_color = None
p.xgrid.grid_line_color = INK
p.ygrid.grid_line_color = INK
p.xgrid.grid_line_alpha = 0.10
p.ygrid.grid_line_alpha = 0.10
p.xgrid.grid_line_width = 1
p.ygrid.grid_line_width = 1
# Save
output_file(f"plot-{THEME}.html", title="scatter-basic · bokeh · anyplot.ai")
save(p)
opts = Options()
for arg in (
"--headless=new",
"--no-sandbox",
"--disable-dev-shm-usage",
"--disable-gpu",
f"--window-size={W},{H}",
"--hide-scrollbars",
):
opts.add_argument(arg)
driver = webdriver.Chrome(options=opts)
driver.set_window_size(W, H)
driver.get(f"file://{Path(f'plot-{THEME}.html').resolve()}")
driver.execute_cdp_cmd(
"Emulation.setDeviceMetricsOverride", {"width": W, "height": H, "deviceScaleFactor": 1, "mobile": False}
)
time.sleep(3)
driver.save_screenshot(f"plot-{THEME}.png")
driver.quit()
Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/scatter-basic/bokeh/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.
{
"spec_id": "scatter-basic",
"language": "python",
"library": "bokeh",
"page": "https://anyplot.ai/scatter-basic/python/bokeh",
"hub": "https://anyplot.ai/scatter-basic",
"code_json": "https://api.anyplot.ai/specs/scatter-basic/bokeh/code",
"spec_json": "https://api.anyplot.ai/specs/scatter-basic",
"render_light_png": "https://storage.googleapis.com/anyplot-images/plots/scatter-basic/python/bokeh/plot-light.png",
"render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/scatter-basic/python/bokeh/plot-dark.png",
"interactive_light_html": "https://storage.googleapis.com/anyplot-images/plots/scatter-basic/python/bokeh/plot-light.html",
"interactive_dark_html": "https://storage.googleapis.com/anyplot-images/plots/scatter-basic/python/bokeh/plot-dark.html",
"quality_score": 83.0,
"license": "MIT",
"guide": "https://anyplot.ai/llms.txt"
}Part of Basic Scatter Plot on anyplot.ai.