{"spec_id":"scatter-basic","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nscatter-basic: Basic Scatter Plot\nLibrary: plotly 6.8.0 | Python 3.13.14\nQuality: 90/100 | Updated: 2026-06-25\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\nfrom scipy.stats import gaussian_kde\nfrom scipy.stats import t as t_dist\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\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\nBRAND = \"#009E73\"  # Imprint palette position 1 — always first series\n\n# Data: study hours vs exam scores, moderate positive correlation\nnp.random.seed(42)\nn_students = 180\nstudy_hours = np.random.uniform(1, 10, n_students)\nexam_scores = 45 + study_hours * 5 + np.random.randn(n_students) * 8\nexam_scores = np.clip(exam_scores, 0, 100)\n\n# Per-point local density → subtle alpha variation so sparse outliers gain\n# presence while dense clusters reveal overlap through transparency.\ndensity = gaussian_kde(np.vstack([study_hours, exam_scores]))(np.vstack([study_hours, exam_scores]))\ndensity_rank = (density - density.min()) / (density.max() - density.min())\npoint_alpha = 0.90 - 0.35 * density_rank  # sparse: 0.90, dense: 0.55\n\n# Percentile rank for richer hover context\nscore_percentile = np.argsort(np.argsort(exam_scores)) / (n_students - 1) * 100\n\n# Linear regression trendline + 95% CI band\nslope, intercept = np.polyfit(study_hours, exam_scores, 1)\nx_line = np.linspace(1.0, 10.0, 100)\ny_line = slope * x_line + intercept\nxbar = np.mean(study_hours)\nSxx = np.sum((study_hours - xbar) ** 2)\ny_hat = slope * study_hours + intercept\ns_res = np.sqrt(np.sum((exam_scores - y_hat) ** 2) / (n_students - 2))\nt_crit = t_dist.ppf(0.975, df=n_students - 2)\nse_ci = s_res * np.sqrt(1 / n_students + (x_line - xbar) ** 2 / Sxx)\nci_half = t_crit * se_ci\n\n# Plot\nfig = go.Figure()\n\n# 95% CI band (drawn first, behind trendline and points)\nfig.add_trace(\n    go.Scatter(\n        x=np.concatenate([x_line, x_line[::-1]]),\n        y=np.concatenate([y_line + ci_half, (y_line - ci_half)[::-1]]),\n        fill=\"toself\",\n        fillcolor=\"rgba(0,158,115,0.10)\",\n        line={\"color\": \"rgba(0,0,0,0)\"},\n        hoverinfo=\"skip\",\n        showlegend=False,\n    )\n)\n\n# Regression trendline\nfig.add_trace(\n    go.Scatter(\n        x=x_line,\n        y=y_line,\n        mode=\"lines\",\n        line={\"color\": INK_SOFT, \"width\": 2, \"dash\": \"dot\"},\n        hoverinfo=\"skip\",\n        showlegend=False,\n    )\n)\n\n# Scatter points\nfig.add_trace(\n    go.Scatter(\n        x=study_hours,\n        y=exam_scores,\n        mode=\"markers\",\n        marker={\"size\": 10, \"color\": BRAND, \"opacity\": point_alpha, \"line\": {\"width\": 1.2, \"color\": ELEVATED_BG}},\n        customdata=np.stack([score_percentile], axis=-1),\n        hovertemplate=(\n            \"<b>Study Hours</b>: %{x:.1f} h/day<br>\"\n            \"<b>Exam Score</b>: %{y:.1f}%<br>\"\n            \"<b>Percentile</b>: %{customdata[0]:.0f}<extra></extra>\"\n        ),\n        showlegend=False,\n    )\n)\n\n# Layout\nfig.update_layout(\n    autosize=False,\n    title={\n        \"text\": \"scatter-basic · python · plotly · anyplot.ai\",\n        \"font\": {\"size\": 16, \"color\": INK},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n        \"y\": 0.95,\n    },\n    xaxis={\n        \"title\": {\"text\": \"Study Hours per Day\", \"font\": {\"size\": 12, \"color\": INK}, \"standoff\": 12},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"gridcolor\": GRID,\n        \"gridwidth\": 1,\n        \"zeroline\": False,\n        \"showline\": False,\n        \"range\": [0, 11],\n        \"dtick\": 2,\n        \"ticksuffix\": \" h\",\n    },\n    yaxis={\n        \"title\": {\"text\": \"Exam Score (%)\", \"font\": {\"size\": 12, \"color\": INK}, \"standoff\": 12},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"gridcolor\": GRID,\n        \"gridwidth\": 1,\n        \"zeroline\": False,\n        \"showline\": False,\n        \"range\": [35, 105],\n        \"dtick\": 10,\n        \"ticksuffix\": \"%\",\n    },\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK, \"family\": \"Inter, Helvetica Neue, Arial, sans-serif\"},\n    margin={\"l\": 80, \"r\": 40, \"t\": 80, \"b\": 60},\n    hovermode=\"closest\",\n    hoverlabel={\"bgcolor\": ELEVATED_BG, \"bordercolor\": INK_SOFT, \"font\": {\"color\": INK, \"size\": 13}, \"align\": \"left\"},\n)\n\n# Save — landscape 3200×1800 (width=800, height=450, scale=4)\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\nfig.write_html(\n    f\"plot-{THEME}.html\",\n    include_plotlyjs=\"cdn\",\n    config={\n        \"displaylogo\": False,\n        \"modeBarButtonsToRemove\": [\"lasso2d\", \"select2d\", \"autoScale2d\"],\n        \"toImageButtonOptions\": {\"format\": \"png\", \"filename\": \"scatter-basic-plotly\"},\n    },\n)\n"}