{"spec_id":"scatter-basic","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nscatter-basic: Basic Scatter Plot\nLibrary: altair 6.2.2 | Python 3.13.14\nQuality: 91/100 | Updated: 2026-06-25\n\"\"\"\n\nimport os\nimport sys\n\n\n# Remove script directory from sys.path so the altair package resolves, not this file\nsys.path = [p for p in sys.path if os.path.abspath(p or \".\") != os.path.dirname(os.path.abspath(__file__))]\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\n\n\n# Theme tokens — Imprint palette\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nBRAND = \"#009E73\"  # Imprint palette position 1\n\n# Data — study hours vs. exam scores (r ~ 0.70, moderate positive correlation)\nnp.random.seed(42)\nn = 180\nstudy_hours = np.random.uniform(1, 12, n)\nexam_scores = np.clip(40 + study_hours * 4.2 + np.random.normal(0, 12.0, n), 30, 100)\ndf = pd.DataFrame({\"hours\": study_hours, \"score\": exam_scores})\n\npearson_r = float(np.corrcoef(df[\"hours\"], df[\"score\"])[0, 1])\n\n# Scatter layer\npoints = (\n    alt.Chart(df)\n    .mark_circle(size=140, opacity=0.7, color=BRAND, stroke=PAGE_BG, strokeWidth=0.8)\n    .encode(\n        x=alt.X(\n            \"hours:Q\",\n            title=\"Study Hours per Week\",\n            scale=alt.Scale(domain=[0, 13], nice=False),\n            axis=alt.Axis(tickCount=6, ticks=False, labelPadding=10, titlePadding=14, domain=False),\n        ),\n        y=alt.Y(\n            \"score:Q\",\n            title=\"Exam Score (%)\",\n            scale=alt.Scale(domain=[25, 105], nice=False),\n            axis=alt.Axis(tickCount=8, ticks=False, labelPadding=10, titlePadding=14, domain=False),\n        ),\n        tooltip=[\n            alt.Tooltip(\"hours:Q\", title=\"Study hrs / wk\", format=\".1f\"),\n            alt.Tooltip(\"score:Q\", title=\"Exam %\", format=\".1f\"),\n        ],\n    )\n)\n\n# Regression line — Altair's transform_regression showcases its declarative layering grammar\nregression = (\n    alt.Chart(df)\n    .mark_line(color=INK_SOFT, strokeWidth=2.0, strokeDash=[6, 4], opacity=0.75)\n    .transform_regression(\"hours\", \"score\")\n    .encode(x=alt.X(\"hours:Q\"), y=alt.Y(\"score:Q\"))\n)\n\nchart = (\n    alt.layer(points, regression)\n    .properties(\n        width=620,\n        height=320,\n        background=PAGE_BG,\n        title=alt.Title(\n            \"scatter-basic · python · altair · anyplot.ai\",\n            subtitle=f\"n = {n}  ·  Pearson r = {pearson_r:.2f}\",\n            fontSize=16,\n            fontWeight=\"normal\",\n            color=INK,\n            subtitleFontSize=10,\n            subtitleColor=INK_MUTED,\n            subtitlePadding=4,\n            anchor=\"start\",\n            offset=16,\n        ),\n    )\n    .configure_view(fill=PAGE_BG, stroke=None)\n    .configure_axis(\n        labelFontSize=10,\n        titleFontSize=12,\n        titleFontWeight=\"normal\",\n        gridColor=INK,\n        gridOpacity=0.10,\n        gridWidth=0.8,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n    )\n)\n\n# Save PNG\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\n\n# Pad to exact target 3200 × 1800 (vl-convert pads outside width/height)\nTW, TH = 3200, 1800\n_img = Image.open(f\"plot-{THEME}.png\").convert(\"RGB\")\n_w, _h = _img.size\nif _w > TW or _h > TH:\n    raise SystemExit(\n        f\"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. \"\n        f\"Shrink chart .properties(width=, height=) values and re-render.\"\n    )\nif _w < TW or _h < TH:\n    _canvas = Image.new(\"RGB\", (TW, TH), PAGE_BG)\n    _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2))\n    _canvas.save(f\"plot-{THEME}.png\")\n\nchart.save(f\"plot-{THEME}.html\")\n"}