|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | lollipop-basic: Basic Lollipop Chart |
3 | | -Library: bokeh 3.8.1 | Python 3.13.11 |
4 | | -Quality: 91/100 | Created: 2025-12-23 |
| 3 | +Library: bokeh 3.9.0 | Python 3.14.4 |
| 4 | +Quality: 85/100 | Updated: 2026-04-26 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import os |
| 8 | + |
7 | 9 | from bokeh.io import export_png, output_file, save |
8 | 10 | from bokeh.models import ColumnDataSource, NumeralTickFormatter |
9 | 11 | from bokeh.plotting import figure |
10 | 12 |
|
11 | 13 |
|
12 | | -# Data - Product sales by category, sorted by value |
| 14 | +# Theme tokens |
| 15 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 16 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 17 | +ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" |
| 18 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 19 | +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
| 20 | +BRAND = "#009E73" |
| 21 | + |
| 22 | +# Data — product sales by category (pre-sorted descending) |
13 | 23 | categories = [ |
14 | 24 | "Electronics", |
15 | 25 | "Clothing", |
|
22 | 32 | "Automotive", |
23 | 33 | "Office", |
24 | 34 | ] |
25 | | -values = [85000, 72000, 58000, 45000, 42000, 38000, 35000, 28000, 22000, 15000] |
26 | | - |
27 | | -# Sort by value (descending) for better readability |
28 | | -sorted_pairs = sorted(zip(categories, values, strict=True), key=lambda x: x[1], reverse=True) |
29 | | -categories = [p[0] for p in sorted_pairs] |
30 | | -values = [p[1] for p in sorted_pairs] |
| 35 | +values = [85000, 72000, 61000, 53000, 47000, 39000, 33000, 28000, 22000, 15000] |
31 | 36 |
|
32 | | -# Create source |
33 | 37 | source = ColumnDataSource(data={"categories": categories, "values": values, "zeros": [0] * len(values)}) |
34 | 38 |
|
35 | | -# Create figure with categorical x-axis |
| 39 | +# Plot |
36 | 40 | p = figure( |
37 | 41 | width=4800, |
38 | 42 | height=2700, |
39 | 43 | x_range=categories, |
40 | | - title="lollipop-basic · bokeh · pyplots.ai", |
| 44 | + title="lollipop-basic · bokeh · anyplot.ai", |
41 | 45 | x_axis_label="Product Category", |
42 | | - y_axis_label="Sales ($)", |
| 46 | + y_axis_label="Sales (USD)", |
| 47 | + toolbar_location=None, |
43 | 48 | ) |
44 | 49 |
|
45 | | -# Draw stems (thin lines from baseline to value) |
46 | | -p.segment(x0="categories", y0="zeros", x1="categories", y1="values", source=source, line_width=4, color="#306998") |
| 50 | +p.segment(x0="categories", y0="zeros", x1="categories", y1="values", source=source, line_width=4, color=BRAND) |
47 | 51 |
|
48 | | -# Draw markers (circles at data values) |
49 | | -p.scatter(x="categories", y="values", source=source, size=25, color="#FFD43B", line_color="#306998", line_width=3) |
| 52 | +p.scatter(x="categories", y="values", source=source, size=42, color=BRAND, line_color=PAGE_BG, line_width=3) |
50 | 53 |
|
51 | | -# Styling for 4800x2700 canvas |
52 | | -p.title.text_font_size = "32pt" |
53 | | -p.xaxis.axis_label_text_font_size = "24pt" |
54 | | -p.yaxis.axis_label_text_font_size = "24pt" |
| 54 | +# Style |
| 55 | +p.title.text_font_size = "28pt" |
| 56 | +p.title.text_color = INK |
| 57 | +p.title.text_font_style = "normal" |
| 58 | + |
| 59 | +p.xaxis.axis_label_text_font_size = "22pt" |
| 60 | +p.yaxis.axis_label_text_font_size = "22pt" |
55 | 61 | p.xaxis.major_label_text_font_size = "18pt" |
56 | 62 | p.yaxis.major_label_text_font_size = "18pt" |
57 | 63 |
|
58 | | -# Rotate x-axis labels for readability |
59 | | -p.xaxis.major_label_orientation = 0.7 |
| 64 | +p.xaxis.axis_label_text_color = INK |
| 65 | +p.yaxis.axis_label_text_color = INK |
| 66 | +p.xaxis.major_label_text_color = INK_SOFT |
| 67 | +p.yaxis.major_label_text_color = INK_SOFT |
| 68 | + |
| 69 | +p.xaxis.axis_line_color = INK_SOFT |
| 70 | +p.yaxis.axis_line_color = INK_SOFT |
| 71 | +p.xaxis.major_tick_line_color = INK_SOFT |
| 72 | +p.yaxis.major_tick_line_color = INK_SOFT |
| 73 | +p.xaxis.minor_tick_line_color = None |
| 74 | +p.yaxis.minor_tick_line_color = None |
| 75 | + |
| 76 | +p.xaxis.major_label_orientation = 0.6 |
60 | 77 |
|
61 | | -# Grid styling - subtle |
62 | 78 | p.xgrid.grid_line_color = None |
63 | | -p.ygrid.grid_line_alpha = 0.3 |
64 | | -p.ygrid.grid_line_dash = "dashed" |
| 79 | +p.ygrid.grid_line_color = INK |
| 80 | +p.ygrid.grid_line_alpha = 0.10 |
65 | 81 |
|
66 | | -# Background |
67 | | -p.background_fill_color = "#fafafa" |
68 | | -p.border_fill_color = "#ffffff" |
| 82 | +p.background_fill_color = PAGE_BG |
| 83 | +p.border_fill_color = PAGE_BG |
| 84 | +p.outline_line_color = None |
69 | 85 |
|
70 | | -# Format y-axis with thousands separator |
71 | 86 | p.yaxis.formatter = NumeralTickFormatter(format="$0,0") |
72 | 87 |
|
73 | | -# Save as PNG and HTML |
74 | | -export_png(p, filename="plot.png") |
75 | | -output_file("plot.html") |
| 88 | +# Save |
| 89 | +export_png(p, filename=f"plot-{THEME}.png") |
| 90 | +output_file(f"plot-{THEME}.html") |
76 | 91 | save(p) |
0 commit comments