|
1 | 1 | """ pyplots.ai |
2 | 2 | heatmap-basic: Basic Heatmap |
3 | | -Library: letsplot 4.8.1 | Python 3.13.11 |
4 | | -Quality: 91/100 | Created: 2025-12-23 |
| 3 | +Library: letsplot 4.8.2 | Python 3.14.3 |
| 4 | +Quality: 91/100 | Updated: 2026-02-15 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import numpy as np |
8 | | -import pandas as pd |
9 | 8 | from lets_plot import ( |
10 | 9 | LetsPlot, |
11 | 10 | aes, |
| 11 | + element_blank, |
| 12 | + element_rect, |
12 | 13 | element_text, |
13 | 14 | geom_text, |
14 | 15 | geom_tile, |
15 | 16 | ggplot, |
16 | 17 | ggsize, |
| 18 | + guide_colorbar, |
17 | 19 | labs, |
18 | | - scale_fill_gradient2, |
| 20 | + layer_tooltips, |
| 21 | + scale_color_identity, |
| 22 | + scale_fill_viridis, |
| 23 | + scale_x_discrete, |
| 24 | + scale_y_discrete, |
19 | 25 | theme, |
20 | 26 | theme_minimal, |
21 | 27 | ) |
|
24 | 30 |
|
25 | 31 | LetsPlot.setup_html() |
26 | 32 |
|
27 | | -# Data - Department performance scores by quarter |
| 33 | +# Data - Monthly energy consumption (kWh) by building zone |
28 | 34 | np.random.seed(42) |
29 | | -departments = ["Sales", "Marketing", "R&D", "Operations", "Finance", "HR"] |
30 | | -quarters = ["Q1", "Q2", "Q3", "Q4", "Q5", "Q6", "Q7", "Q8"] |
| 35 | +months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] |
| 36 | +zones = ["Lobby", "Offices", "Lab", "Server Room", "Cafeteria", "Warehouse"] |
31 | 37 |
|
32 | | -# Generate performance scores with some patterns |
33 | | -n_rows = len(quarters) |
34 | | -n_cols = len(departments) |
35 | | -base_scores = np.random.rand(n_rows, n_cols) * 60 + 20 # Range 20-80 |
| 38 | +# Realistic patterns: seasonal variation + zone-specific baselines |
| 39 | +baselines = np.array([120, 280, 350, 520, 180, 90]) |
| 40 | +seasonal = np.array([1.3, 1.2, 1.0, 0.8, 0.7, 0.75, 0.85, 0.9, 0.8, 0.9, 1.1, 1.25]) |
36 | 41 |
|
37 | | -# Add some trend patterns to make data more interesting |
38 | | -trend = np.linspace(0, 15, n_rows).reshape(-1, 1) # Slight upward trend |
39 | | -values = base_scores + trend |
40 | | -values = np.clip(values, 15, 95) # Keep in realistic range |
41 | | -values = np.round(values, 1) |
| 42 | +values = np.outer(seasonal, baselines) |
| 43 | +noise = np.random.normal(0, 15, (len(months), len(zones))) |
| 44 | +values = np.round(values + noise, 0).astype(int) |
42 | 45 |
|
43 | | -# Create long-form DataFrame for lets-plot |
44 | | -rows = [] |
45 | | -for i, quarter in enumerate(quarters): |
46 | | - for j, dept in enumerate(departments): |
47 | | - rows.append({"Department": dept, "Quarter": quarter, "Score": values[i, j]}) |
| 46 | +# Build long-form data using vectorized operations |
| 47 | +n_months, n_zones = len(months), len(zones) |
| 48 | +zone_col = np.tile(zones, n_months).tolist() |
| 49 | +month_col = np.repeat(months, n_zones).tolist() |
| 50 | +kwh_col = values.flatten().tolist() |
48 | 51 |
|
49 | | -df = pd.DataFrame(rows) |
| 52 | +# Adaptive text color: white on dark cells, dark on light cells |
| 53 | +median_val = int(np.median(kwh_col)) |
| 54 | +text_color = ["#ffffff" if v > median_val else "#1a1a2e" for v in kwh_col] |
50 | 55 |
|
51 | | -# Preserve category order (reverse quarters for top-to-bottom display) |
52 | | -df["Department"] = pd.Categorical(df["Department"], categories=departments, ordered=True) |
53 | | -df["Quarter"] = pd.Categorical(df["Quarter"], categories=quarters[::-1], ordered=True) |
| 56 | +data = {"Zone": zone_col, "Month": month_col, "kWh": kwh_col, "label_color": text_color} |
54 | 57 |
|
55 | | -# Create heatmap using geom_tile with value annotations |
| 58 | +# Heatmap with perceptually-uniform viridis colormap and interactive tooltips |
56 | 59 | plot = ( |
57 | | - ggplot(df, aes(x="Department", y="Quarter", fill="Score")) |
58 | | - + geom_tile(width=0.95, height=0.95) |
59 | | - + geom_text(aes(label="Score"), size=14, color="white", fontface="bold") |
60 | | - + scale_fill_gradient2(low="#306998", mid="#FFD43B", high="#DC2626", midpoint=55, name="Performance\nScore") |
61 | | - + labs(x="Department", y="Quarter", title="heatmap-basic · letsplot · pyplots.ai") |
| 60 | + ggplot(data, aes(x="Zone", y="Month", fill="kWh")) |
| 61 | + + geom_tile( |
| 62 | + width=0.92, |
| 63 | + height=0.92, |
| 64 | + tooltips=layer_tooltips() |
| 65 | + .line("@Zone | @Month") |
| 66 | + .line("Energy: @kWh kWh") |
| 67 | + .line("Median: " + str(median_val) + " kWh"), |
| 68 | + ) |
| 69 | + + geom_text(aes(label="kWh", color="label_color"), size=14, fontface="bold") |
| 70 | + + scale_color_identity() |
| 71 | + + scale_fill_viridis( |
| 72 | + option="viridis", direction=-1, name="Energy (kWh)", guide=guide_colorbar(barwidth=18, barheight=300, nbin=256) |
| 73 | + ) |
| 74 | + + scale_x_discrete(limits=zones) |
| 75 | + + scale_y_discrete(limits=months[::-1]) |
| 76 | + + labs(x="Building Zone", y="Month", title="heatmap-basic · letsplot · pyplots.ai") |
62 | 77 | + theme_minimal() |
63 | 78 | + theme( |
64 | | - axis_title=element_text(size=20), |
65 | | - axis_text=element_text(size=16), |
66 | | - axis_text_x=element_text(angle=45), |
67 | | - plot_title=element_text(size=24), |
| 79 | + plot_title=element_text(size=26, face="bold", color="#1a1a2e"), |
| 80 | + axis_title=element_text(size=20, color="#2d2d44"), |
| 81 | + axis_text_x=element_text(size=16, face="bold", color="#2d2d44"), |
| 82 | + axis_text_y=element_text(size=16, color="#2d2d44"), |
68 | 83 | legend_text=element_text(size=14), |
69 | | - legend_title=element_text(size=16), |
| 84 | + legend_title=element_text(size=16, face="bold"), |
| 85 | + panel_grid=element_blank(), |
| 86 | + plot_background=element_rect(fill="#fafafa", color="#fafafa"), |
| 87 | + plot_margin=[40, 20, 20, 20], |
70 | 88 | ) |
71 | 89 | + ggsize(1600, 900) |
72 | 90 | ) |
73 | 91 |
|
74 | 92 | # Save PNG (scale=3 gives 4800x2700) |
75 | 93 | ggsave(plot, "plot.png", path=".", scale=3) |
76 | 94 |
|
77 | | -# Save HTML for interactivity |
| 95 | +# Save HTML for interactive tooltips (lets-plot distinctive feature) |
78 | 96 | ggsave(plot, "plot.html", path=".") |
0 commit comments