|
1 | 1 | """ anyplot.ai |
2 | 2 | scatter-basic: Basic Scatter Plot |
3 | 3 | Library: plotly 6.7.0 | Python 3.14.4 |
4 | | -Quality: 86/100 | Created: 2026-04-23 |
| 4 | +Quality: 89/100 | Updated: 2026-04-23 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import os |
8 | 8 |
|
9 | 9 | import numpy as np |
10 | 10 | import plotly.graph_objects as go |
| 11 | +from scipy.stats import gaussian_kde |
11 | 12 |
|
12 | 13 |
|
13 | | -# Theme tokens (see prompts/default-style-guide.md "Theme-adaptive Chrome") |
| 14 | +# Theme tokens |
14 | 15 | THEME = os.getenv("ANYPLOT_THEME", "light") |
15 | 16 | PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
16 | 17 | ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" |
|
21 | 22 |
|
22 | 23 | # Data: study hours vs exam scores, moderate positive correlation |
23 | 24 | np.random.seed(42) |
24 | | -n_students = 160 |
| 25 | +n_students = 180 |
25 | 26 | study_hours = np.random.uniform(1, 10, n_students) |
26 | 27 | exam_scores = 45 + study_hours * 5 + np.random.randn(n_students) * 8 |
27 | 28 | exam_scores = np.clip(exam_scores, 0, 100) |
28 | 29 |
|
| 30 | +# Per-point local density → subtle alpha variation so sparse outliers gain |
| 31 | +# presence while dense clusters reveal overlap through transparency. |
| 32 | +density = gaussian_kde(np.vstack([study_hours, exam_scores]))(np.vstack([study_hours, exam_scores])) |
| 33 | +density_rank = (density - density.min()) / (density.max() - density.min()) |
| 34 | +point_alpha = 0.90 - 0.35 * density_rank # sparse: 0.90, dense: 0.55 |
| 35 | + |
| 36 | +# Percentile rank for richer hover context |
| 37 | +score_percentile = np.argsort(np.argsort(exam_scores)) / (n_students - 1) * 100 |
| 38 | + |
29 | 39 | # Plot |
30 | 40 | fig = go.Figure( |
31 | 41 | go.Scatter( |
32 | 42 | x=study_hours, |
33 | 43 | y=exam_scores, |
34 | 44 | mode="markers", |
35 | | - marker={"size": 13, "color": BRAND, "opacity": 0.7, "line": {"width": 1.2, "color": PAGE_BG}}, |
36 | | - hovertemplate="Study: %{x:.1f} h<br>Score: %{y:.1f}%<extra></extra>", |
| 45 | + marker={"size": 14, "color": BRAND, "opacity": point_alpha, "line": {"width": 1.2, "color": PAGE_BG}}, |
| 46 | + customdata=np.stack([score_percentile], axis=-1), |
| 47 | + hovertemplate=( |
| 48 | + "<b>Study Hours</b>: %{x:.1f} h/day<br>" |
| 49 | + "<b>Exam Score</b>: %{y:.1f}%<br>" |
| 50 | + "<b>Percentile</b>: %{customdata[0]:.0f}<extra></extra>" |
| 51 | + ), |
37 | 52 | showlegend=False, |
38 | 53 | ) |
39 | 54 | ) |
|
58 | 73 | "mirror": False, |
59 | 74 | "range": [0, 11], |
60 | 75 | "dtick": 2, |
| 76 | + "ticksuffix": " h", |
61 | 77 | }, |
62 | 78 | yaxis={ |
63 | 79 | "title": {"text": "Exam Score (%)", "font": {"size": 22, "color": INK}, "standoff": 12}, |
|
70 | 86 | "mirror": False, |
71 | 87 | "range": [35, 105], |
72 | 88 | "dtick": 10, |
| 89 | + "ticksuffix": "%", |
73 | 90 | }, |
74 | 91 | paper_bgcolor=PAGE_BG, |
75 | 92 | plot_bgcolor=PAGE_BG, |
76 | | - font={"color": INK}, |
77 | | - margin={"l": 90, "r": 60, "t": 100, "b": 80}, |
78 | | - hoverlabel={"bgcolor": ELEVATED_BG, "bordercolor": INK_SOFT, "font": {"color": INK}}, |
| 93 | + font={"color": INK, "family": "Inter, Helvetica Neue, Arial, sans-serif"}, |
| 94 | + margin={"l": 100, "r": 70, "t": 110, "b": 90}, |
| 95 | + hovermode="closest", |
| 96 | + hoverlabel={"bgcolor": ELEVATED_BG, "bordercolor": INK_SOFT, "font": {"color": INK, "size": 15}, "align": "left"}, |
79 | 97 | ) |
80 | 98 |
|
81 | 99 | # Save |
82 | 100 | fig.write_image(f"plot-{THEME}.png", width=1600, height=900, scale=3) |
83 | | -fig.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn") |
| 101 | +fig.write_html( |
| 102 | + f"plot-{THEME}.html", |
| 103 | + include_plotlyjs="cdn", |
| 104 | + config={ |
| 105 | + "displaylogo": False, |
| 106 | + "modeBarButtonsToRemove": ["lasso2d", "select2d", "autoScale2d"], |
| 107 | + "toImageButtonOptions": {"format": "png", "filename": "scatter-basic-plotly"}, |
| 108 | + }, |
| 109 | +) |
0 commit comments