|
1 | 1 | """ pyplots.ai |
2 | 2 | density-basic: Basic Density Plot |
3 | | -Library: highcharts unknown | Python 3.13.11 |
4 | | -Quality: 91/100 | Created: 2025-12-23 |
| 3 | +Library: highcharts 1.10.3 | Python 3.14.3 |
| 4 | +Quality: 92/100 | Updated: 2026-02-23 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import tempfile |
|
18 | 18 | from selenium.webdriver.chrome.options import Options |
19 | 19 |
|
20 | 20 |
|
21 | | -# Data - simulating heights (cm) with realistic distribution |
| 21 | +# Data - simulating heights (cm) with bimodal distribution |
22 | 22 | np.random.seed(42) |
23 | | -# Mix of two normal distributions to show bimodal feature (male/female heights) |
24 | 23 | n_samples = 500 |
25 | | -values_a = np.random.normal(165, 7, n_samples // 2) # Female heights |
26 | | -values_b = np.random.normal(178, 8, n_samples // 2) # Male heights |
| 24 | +values_a = np.random.normal(162, 6, n_samples // 2) # Female heights |
| 25 | +values_b = np.random.normal(178, 6, n_samples // 2) # Male heights |
27 | 26 | values = np.concatenate([values_a, values_b]) |
28 | 27 |
|
29 | | -# Kernel Density Estimation (Gaussian kernel) - inline calculation |
30 | | -x_min, x_max = values.min() - 10, values.max() + 10 |
31 | | -x_grid = np.linspace(x_min, x_max, 200) |
| 28 | +# Kernel Density Estimation (Gaussian kernel) |
| 29 | +x_min, x_max = values.min() - 12, values.max() + 12 |
| 30 | +x_grid = np.linspace(x_min, x_max, 300) |
32 | 31 |
|
33 | 32 | # Silverman's rule of thumb for bandwidth |
34 | 33 | n = len(values) |
35 | | -bandwidth = 1.06 * np.std(values) * n ** (-1 / 5) |
| 34 | +bandwidth = 0.9 * min(np.std(values), np.subtract(*np.percentile(values, [75, 25])) / 1.34) * n ** (-1 / 5) |
36 | 35 |
|
37 | 36 | # Compute Gaussian KDE |
38 | 37 | density = np.zeros_like(x_grid) |
39 | 38 | for xi in values: |
40 | 39 | density += np.exp(-0.5 * ((x_grid - xi) / bandwidth) ** 2) |
41 | 40 | density /= n * bandwidth * np.sqrt(2 * np.pi) |
42 | 41 |
|
| 42 | +# Find the two peak positions for annotation |
| 43 | +midpoint = len(x_grid) // 2 |
| 44 | +peak1_idx = np.argmax(density[:midpoint]) |
| 45 | +peak2_idx = midpoint + np.argmax(density[midpoint:]) |
| 46 | +peak1_x = float(x_grid[peak1_idx]) |
| 47 | +peak2_x = float(x_grid[peak2_idx]) |
| 48 | + |
43 | 49 | # Create chart |
44 | 50 | chart = Chart(container="container") |
45 | 51 | chart.options = HighchartsOptions() |
|
50 | 56 | "width": 4800, |
51 | 57 | "height": 2700, |
52 | 58 | "backgroundColor": "#ffffff", |
53 | | - "marginBottom": 200, |
54 | | - "marginLeft": 200, |
| 59 | + "marginBottom": 180, |
| 60 | + "marginLeft": 180, |
| 61 | + "marginRight": 60, |
| 62 | + "marginTop": 140, |
| 63 | + "style": {"fontFamily": "Arial, Helvetica, sans-serif"}, |
55 | 64 | } |
56 | 65 |
|
57 | 66 | # Title |
58 | 67 | chart.options.title = { |
59 | | - "text": "density-basic · highcharts · pyplots.ai", |
60 | | - "style": {"fontSize": "72px", "fontWeight": "bold"}, |
| 68 | + "text": "density-basic \u00b7 highcharts \u00b7 pyplots.ai", |
| 69 | + "style": {"fontSize": "64px", "fontWeight": "600", "color": "#2c3e50"}, |
| 70 | + "margin": 40, |
61 | 71 | } |
62 | 72 |
|
63 | | -# X-axis |
| 73 | +# Disable credits |
| 74 | +chart.options.credits = {"enabled": False} |
| 75 | + |
| 76 | +# X-axis - clean L-shaped frame with plotBands/plotLines for peak emphasis |
64 | 77 | chart.options.x_axis = { |
65 | | - "title": {"text": "Height (cm)", "style": {"fontSize": "48px"}}, |
66 | | - "labels": {"style": {"fontSize": "36px"}}, |
67 | | - "gridLineWidth": 1, |
68 | | - "gridLineColor": "rgba(0, 0, 0, 0.25)", |
| 78 | + "title": {"text": "Height (cm)", "style": {"fontSize": "48px", "color": "#444444"}, "margin": 24}, |
| 79 | + "labels": {"style": {"fontSize": "36px", "color": "#666666"}}, |
| 80 | + "lineColor": "#cccccc", |
| 81 | + "lineWidth": 2, |
| 82 | + "tickWidth": 0, |
| 83 | + "tickInterval": 5, |
| 84 | + "gridLineWidth": 0, |
| 85 | + "plotBands": [ |
| 86 | + {"from": peak1_x - 8, "to": peak1_x + 8, "color": "rgba(48, 105, 152, 0.06)", "zIndex": 0}, |
| 87 | + {"from": peak2_x - 8, "to": peak2_x + 8, "color": "rgba(48, 105, 152, 0.06)", "zIndex": 0}, |
| 88 | + ], |
| 89 | + "plotLines": [ |
| 90 | + { |
| 91 | + "value": peak1_x, |
| 92 | + "color": "rgba(48, 105, 152, 0.45)", |
| 93 | + "width": 3, |
| 94 | + "dashStyle": "Dash", |
| 95 | + "zIndex": 4, |
| 96 | + "label": { |
| 97 | + "text": f"Peak: {peak1_x:.0f} cm", |
| 98 | + "style": {"fontSize": "32px", "color": "#306998", "fontWeight": "600"}, |
| 99 | + "rotation": 0, |
| 100 | + "y": 16, |
| 101 | + }, |
| 102 | + }, |
| 103 | + { |
| 104 | + "value": peak2_x, |
| 105 | + "color": "rgba(48, 105, 152, 0.45)", |
| 106 | + "width": 3, |
| 107 | + "dashStyle": "Dash", |
| 108 | + "zIndex": 4, |
| 109 | + "label": { |
| 110 | + "text": f"Peak: {peak2_x:.0f} cm", |
| 111 | + "style": {"fontSize": "32px", "color": "#306998", "fontWeight": "600"}, |
| 112 | + "rotation": 0, |
| 113 | + "y": 16, |
| 114 | + }, |
| 115 | + }, |
| 116 | + ], |
69 | 117 | } |
70 | 118 |
|
71 | | -# Y-axis - start at 0 for proper density representation |
| 119 | +# Y-axis - subtle horizontal grid only |
72 | 120 | chart.options.y_axis = { |
73 | | - "title": {"text": "Density", "style": {"fontSize": "48px"}}, |
74 | | - "labels": {"style": {"fontSize": "36px"}}, |
| 121 | + "title": {"text": "Density", "style": {"fontSize": "48px", "color": "#444444"}, "margin": 24}, |
| 122 | + "labels": {"style": {"fontSize": "36px", "color": "#666666"}}, |
75 | 123 | "gridLineWidth": 1, |
76 | | - "gridLineColor": "rgba(0, 0, 0, 0.25)", |
| 124 | + "gridLineColor": "rgba(0, 0, 0, 0.10)", |
| 125 | + "lineColor": "#cccccc", |
| 126 | + "lineWidth": 2, |
| 127 | + "tickAmount": 7, |
77 | 128 | "min": 0, |
78 | 129 | } |
79 | 130 |
|
80 | | -# Plot options with semi-transparent fill |
| 131 | +# Plot options |
81 | 132 | chart.options.plot_options = { |
82 | 133 | "area": { |
83 | 134 | "fillColor": { |
84 | 135 | "linearGradient": {"x1": 0, "y1": 0, "x2": 0, "y2": 1}, |
85 | | - "stops": [[0, "rgba(48, 105, 152, 0.6)"], [1, "rgba(48, 105, 152, 0.1)"]], |
| 136 | + "stops": [[0, "rgba(48, 105, 152, 0.45)"], [1, "rgba(48, 105, 152, 0.03)"]], |
86 | 137 | }, |
87 | 138 | "lineWidth": 5, |
88 | 139 | "marker": {"enabled": False}, |
89 | 140 | "color": "#306998", |
| 141 | + "states": {"hover": {"lineWidth": 5}}, |
90 | 142 | }, |
91 | 143 | "scatter": { |
92 | | - "marker": {"radius": 8, "fillColor": "#FFD43B", "symbol": "diamond", "lineWidth": 2, "lineColor": "#D4AA00"} |
| 144 | + "marker": {"radius": 15, "fillColor": "rgba(48, 105, 152, 0.55)", "symbol": "diamond", "lineWidth": 0}, |
| 145 | + "states": {"hover": {"enabled": False}}, |
93 | 146 | }, |
| 147 | + "series": {"animation": False}, |
94 | 148 | } |
95 | 149 |
|
96 | | -# Legend - enabled with clear styling |
| 150 | +# Disable tooltip for static export |
| 151 | +chart.options.tooltip = {"enabled": False} |
| 152 | + |
| 153 | +# Legend |
97 | 154 | chart.options.legend = { |
98 | 155 | "enabled": True, |
99 | 156 | "layout": "horizontal", |
100 | 157 | "align": "right", |
101 | 158 | "verticalAlign": "top", |
102 | 159 | "floating": True, |
103 | | - "x": -50, |
104 | | - "y": 80, |
105 | | - "itemStyle": {"fontSize": "36px"}, |
106 | | - "symbolHeight": 24, |
107 | | - "symbolWidth": 40, |
| 160 | + "x": -40, |
| 161 | + "y": 60, |
| 162 | + "itemStyle": {"fontSize": "34px", "fontWeight": "normal", "color": "#555555"}, |
| 163 | + "symbolHeight": 20, |
| 164 | + "symbolWidth": 32, |
| 165 | + "itemDistance": 40, |
| 166 | + "borderWidth": 0, |
108 | 167 | } |
109 | 168 |
|
110 | 169 | # Add density curve as area series |
111 | 170 | area_series = AreaSeries() |
112 | | -area_series.data = [[float(x), float(y)] for x, y in zip(x_grid, density, strict=True)] |
113 | | -area_series.name = "Density Curve" |
| 171 | +area_series.data = [[round(float(x), 2), round(float(y), 6)] for x, y in zip(x_grid, density, strict=True)] |
| 172 | +area_series.name = "Density" |
114 | 173 | chart.add_series(area_series) |
115 | 174 |
|
116 | | -# Add rug plot as small vertical tick marks at y=0 |
117 | | -# Sample every 5th point to show distribution without overcrowding |
118 | | -rug_sample = values[::5] |
119 | | -rug_y = 0.0005 # Small positive value just above x-axis |
120 | | -rug_data = [[float(v), rug_y] for v in sorted(rug_sample)] |
| 175 | +# Add rug plot - vertical tick marks along x-axis |
| 176 | +rug_sample = values[::3] # Show every 3rd observation for good coverage |
| 177 | +rug_y = max(density) * 0.008 # Small positive value near axis |
| 178 | +rug_data = [[round(float(v), 2), round(float(rug_y), 6)] for v in sorted(rug_sample)] |
121 | 179 |
|
122 | 180 | rug_series = ScatterSeries() |
123 | 181 | rug_series.data = rug_data |
124 | | -rug_series.name = "Observations (Rug)" |
125 | | -rug_series.marker = {"symbol": "diamond", "fillColor": "#FFD43B", "lineColor": "#D4AA00", "lineWidth": 2, "radius": 10} |
| 182 | +rug_series.name = "Observations" |
| 183 | +rug_series.marker = {"symbol": "diamond", "fillColor": "rgba(48, 105, 152, 0.55)", "lineWidth": 0, "radius": 15} |
126 | 184 | chart.add_series(rug_series) |
127 | 185 |
|
128 | 186 | # Download Highcharts JS for inline embedding |
|
149 | 207 | f.write(html_content) |
150 | 208 | temp_path = f.name |
151 | 209 |
|
152 | | -# Also save HTML for interactive version |
| 210 | +# Save standalone HTML for interactive version |
153 | 211 | with open("plot.html", "w", encoding="utf-8") as f: |
154 | | - # For standalone HTML, use CDN link |
155 | 212 | standalone_html = f"""<!DOCTYPE html> |
156 | 213 | <html> |
157 | 214 | <head> |
|
170 | 227 | chrome_options.add_argument("--no-sandbox") |
171 | 228 | chrome_options.add_argument("--disable-dev-shm-usage") |
172 | 229 | chrome_options.add_argument("--disable-gpu") |
173 | | -chrome_options.add_argument("--window-size=5000,3000") |
| 230 | +chrome_options.add_argument("--window-size=4900,2800") |
174 | 231 |
|
175 | 232 | driver = webdriver.Chrome(options=chrome_options) |
176 | 233 | driver.get(f"file://{temp_path}") |
177 | | -time.sleep(5) # Wait for chart to render |
| 234 | +time.sleep(5) |
178 | 235 |
|
179 | | -# Screenshot the chart element specifically for exact 4800x2700 |
180 | 236 | container = driver.find_element("id", "container") |
181 | 237 | container.screenshot("plot.png") |
182 | 238 | driver.quit() |
183 | 239 |
|
184 | | -Path(temp_path).unlink() # Clean up temp file |
| 240 | +Path(temp_path).unlink() |
0 commit comments