|
| 1 | +""" pyplots.ai |
| 2 | +scatter-categorical: Categorical Scatter Plot |
| 3 | +Library: bokeh 3.8.1 | Python 3.13.11 |
| 4 | +Quality: 92/100 | Created: 2025-12-30 |
| 5 | +""" |
| 6 | + |
| 7 | +import numpy as np |
| 8 | +from bokeh.io import export_png, output_file, save |
| 9 | +from bokeh.models import ColumnDataSource, Legend |
| 10 | +from bokeh.plotting import figure |
| 11 | + |
| 12 | + |
| 13 | +# Data |
| 14 | +np.random.seed(42) |
| 15 | + |
| 16 | +# Generate three distinct groups with different patterns |
| 17 | +n_per_group = 50 |
| 18 | + |
| 19 | +# Group A: Lower left cluster |
| 20 | +x_a = np.random.normal(25, 5, n_per_group) |
| 21 | +y_a = np.random.normal(30, 6, n_per_group) |
| 22 | +cat_a = ["Product A"] * n_per_group |
| 23 | + |
| 24 | +# Group B: Upper middle cluster |
| 25 | +x_b = np.random.normal(50, 7, n_per_group) |
| 26 | +y_b = np.random.normal(70, 8, n_per_group) |
| 27 | +cat_b = ["Product B"] * n_per_group |
| 28 | + |
| 29 | +# Group C: Right side, moderate y |
| 30 | +x_c = np.random.normal(75, 6, n_per_group) |
| 31 | +y_c = np.random.normal(50, 7, n_per_group) |
| 32 | +cat_c = ["Product C"] * n_per_group |
| 33 | + |
| 34 | +# Combine data |
| 35 | +x = np.concatenate([x_a, x_b, x_c]) |
| 36 | +y = np.concatenate([y_a, y_b, y_c]) |
| 37 | +categories = cat_a + cat_b + cat_c |
| 38 | + |
| 39 | +# Define colors for each category (Python Blue, Python Yellow, colorblind-safe red) |
| 40 | +color_map = {"Product A": "#306998", "Product B": "#FFD43B", "Product C": "#E74C3C"} |
| 41 | + |
| 42 | +# Create figure with interactive tools |
| 43 | +p = figure( |
| 44 | + width=4800, |
| 45 | + height=2700, |
| 46 | + title="scatter-categorical · bokeh · pyplots.ai", |
| 47 | + x_axis_label="Marketing Spend ($K)", |
| 48 | + y_axis_label="Customer Engagement Score", |
| 49 | + tools="pan,wheel_zoom,box_zoom,reset,hover,save", |
| 50 | + tooltips=[("Category", "@cat"), ("X", "@x{0.1}"), ("Y", "@y{0.1}")], |
| 51 | +) |
| 52 | + |
| 53 | +# Plot each category separately for legend |
| 54 | +legend_items = [] |
| 55 | + |
| 56 | +for cat, color in color_map.items(): |
| 57 | + mask = [categories[i] == cat for i in range(len(categories))] |
| 58 | + cat_x = [x[i] for i in range(len(x)) if mask[i]] |
| 59 | + cat_y = [y[i] for i in range(len(y)) if mask[i]] |
| 60 | + cat_source = ColumnDataSource(data={"x": cat_x, "y": cat_y, "cat": [cat] * len(cat_x)}) |
| 61 | + r = p.scatter(x="x", y="y", source=cat_source, size=25, color=color, alpha=0.7, line_color="white", line_width=1) |
| 62 | + legend_items.append((cat, [r])) |
| 63 | + |
| 64 | +# Add legend |
| 65 | +legend = Legend(items=legend_items, location="top_right") |
| 66 | +legend.label_text_font_size = "28pt" |
| 67 | +legend.glyph_height = 40 |
| 68 | +legend.glyph_width = 40 |
| 69 | +legend.spacing = 12 |
| 70 | +legend.padding = 15 |
| 71 | +legend.background_fill_alpha = 0.8 |
| 72 | +p.add_layout(legend) |
| 73 | + |
| 74 | +# Title styling |
| 75 | +p.title.text_font_size = "48pt" |
| 76 | +p.title.text_font_style = "bold" |
| 77 | + |
| 78 | +# Axis label styling |
| 79 | +p.xaxis.axis_label_text_font_size = "36pt" |
| 80 | +p.yaxis.axis_label_text_font_size = "36pt" |
| 81 | + |
| 82 | +# Tick label styling |
| 83 | +p.xaxis.major_label_text_font_size = "28pt" |
| 84 | +p.yaxis.major_label_text_font_size = "28pt" |
| 85 | + |
| 86 | +# Grid styling |
| 87 | +p.grid.grid_line_alpha = 0.3 |
| 88 | +p.grid.grid_line_dash = "dashed" |
| 89 | + |
| 90 | +# Background |
| 91 | +p.background_fill_color = "#fafafa" |
| 92 | + |
| 93 | +# Axis styling |
| 94 | +p.axis.axis_line_width = 2 |
| 95 | +p.axis.major_tick_line_width = 2 |
| 96 | + |
| 97 | +# Save PNG |
| 98 | +export_png(p, filename="plot.png") |
| 99 | + |
| 100 | +# Save interactive HTML |
| 101 | +output_file("plot.html", title="scatter-categorical · bokeh · pyplots.ai") |
| 102 | +save(p) |
0 commit comments