Skip to content

Commit 4329206

Browse files
feat(plotly): implement bar-stacked (#6134)
## Implementation: `bar-stacked` - python/plotly Implements the **python/plotly** version of `bar-stacked`. **File:** `plots/bar-stacked/implementations/python/plotly.py` **Parent Issue:** #1947 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/25594734391)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com>
1 parent 47356b5 commit 4329206

2 files changed

Lines changed: 260 additions & 187 deletions

File tree

Lines changed: 101 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,113 +1,138 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
bar-stacked: Stacked Bar Chart
3-
Library: plotly 6.5.0 | Python 3.13.11
4-
Quality: 97/100 | Created: 2025-12-26
3+
Library: plotly 6.7.0 | Python 3.13.13
4+
Quality: 93/100 | Updated: 2026-05-09
55
"""
66

7+
import os
8+
79
import plotly.graph_objects as go
810

911

12+
# Theme tokens
13+
THEME = os.getenv("ANYPLOT_THEME", "light")
14+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
15+
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
16+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
17+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
18+
GRID = "rgba(26,26,23,0.08)" if THEME == "light" else "rgba(240,239,232,0.08)"
19+
GRID_SUBTLE = "rgba(26,26,23,0.04)" if THEME == "light" else "rgba(240,239,232,0.04)"
20+
21+
# Okabe-Ito palette (first series always #009E73)
22+
COLORS = ["#009E73", "#D55E00", "#0072B2", "#CC79A7"]
23+
1024
# Data - Quarterly revenue by product category
1125
quarters = ["Q1 2024", "Q2 2024", "Q3 2024", "Q4 2024"]
26+
categories = ["Software", "Hardware", "Services", "Support"]
1227

1328
# Revenue in thousands USD for each product category
1429
software = [120, 145, 160, 180]
1530
hardware = [80, 75, 90, 95]
1631
services = [45, 55, 65, 75]
1732
support = [25, 30, 35, 40]
1833

19-
# Calculate totals for annotation
34+
all_data = [software, hardware, services, support]
35+
36+
# Calculate totals and per-component percentages for storytelling
2037
totals = [s + h + sv + sp for s, h, sv, sp in zip(software, hardware, services, support, strict=True)]
2138

22-
# Create figure
39+
# Create figure with enhanced styling
2340
fig = go.Figure()
2441

25-
# Add stacked bars (bottom to top)
26-
fig.add_trace(
27-
go.Bar(
28-
name="Software",
29-
x=quarters,
30-
y=software,
31-
marker_color="#306998",
32-
text=software,
33-
textposition="inside",
34-
textfont={"size": 18, "color": "white"},
35-
)
36-
)
42+
# Add stacked bars with custom hover templates for better storytelling
43+
for category, data, color in zip(categories, all_data, COLORS, strict=True):
44+
percentages = [f"{v / total * 100:.0f}%" for v, total in zip(data, totals, strict=True)]
3745

38-
fig.add_trace(
39-
go.Bar(
40-
name="Hardware",
41-
x=quarters,
42-
y=hardware,
43-
marker_color="#FFD43B",
44-
text=hardware,
45-
textposition="inside",
46-
textfont={"size": 18, "color": "#333"},
47-
)
48-
)
46+
custom_hover = [
47+
f"<b>{category}</b><br>" + f"Quarter: {q}<br>" + f"Revenue: ${v}K<br>" + f"% of Total: {pct}<extra></extra>"
48+
for q, v, pct in zip(quarters, data, percentages, strict=True)
49+
]
4950

50-
fig.add_trace(
51-
go.Bar(
52-
name="Services",
53-
x=quarters,
54-
y=services,
55-
marker_color="#4ECDC4",
56-
text=services,
57-
textposition="inside",
58-
textfont={"size": 18, "color": "white"},
51+
fig.add_trace(
52+
go.Bar(
53+
name=category,
54+
x=quarters,
55+
y=data,
56+
marker=dict(color=color, line=dict(color=ELEVATED_BG, width=0.5)),
57+
text=data,
58+
textposition="inside",
59+
textfont={"size": 18, "color": "white"},
60+
customdata=custom_hover,
61+
hovertemplate="%{customdata}",
62+
)
5963
)
60-
)
6164

62-
fig.add_trace(
63-
go.Bar(
64-
name="Support",
65-
x=quarters,
66-
y=support,
67-
marker_color="#FF6B6B",
68-
text=support,
69-
textposition="inside",
70-
textfont={"size": 18, "color": "white"},
71-
)
72-
)
65+
# Enhanced total annotations with visual emphasis
66+
for idx, (quarter, total) in enumerate(zip(quarters, totals, strict=True)):
67+
growth_rate = ""
68+
if idx > 0:
69+
growth = ((total - totals[idx - 1]) / totals[idx - 1]) * 100
70+
growth_rate = f"<br><span style='font-size:14px;'>+{growth:.1f}% QoQ</span>"
7371

74-
# Add total annotations above each bar
75-
for quarter, total in zip(quarters, totals, strict=True):
7672
fig.add_annotation(
7773
x=quarter,
78-
y=total + 10,
79-
text=f"${total}K",
74+
y=total + 12,
75+
text=f"<b>${total}K</b>{growth_rate}",
8076
showarrow=False,
81-
font={"size": 20, "color": "#333", "weight": "bold"},
77+
font={"size": 20, "color": INK},
78+
bgcolor=ELEVATED_BG,
79+
bordercolor=INK_SOFT,
80+
borderwidth=1,
8281
)
8382

84-
# Update layout
83+
# Update layout with sophisticated design refinements
8584
fig.update_layout(
86-
title={"text": "bar-stacked · plotly · pyplots.ai", "font": {"size": 32}, "x": 0.5, "xanchor": "center"},
87-
xaxis={"title": {"text": "Quarter", "font": {"size": 24}}, "tickfont": {"size": 20}},
88-
yaxis={
89-
"title": {"text": "Revenue (Thousands USD)", "font": {"size": 24}},
90-
"tickfont": {"size": 20},
91-
"gridcolor": "rgba(0,0,0,0.1)",
92-
"gridwidth": 1,
93-
},
94-
barmode="stack",
95-
bargap=0.3,
96-
template="plotly_white",
97-
legend={
98-
"orientation": "h",
99-
"yanchor": "bottom",
100-
"y": 1.02,
101-
"xanchor": "center",
85+
title={
86+
"text": "bar-stacked · plotly · anyplot.ai",
87+
"font": {"size": 28, "color": INK},
10288
"x": 0.5,
103-
"font": {"size": 20},
104-
"traceorder": "normal",
89+
"xanchor": "center",
90+
"y": 0.98,
91+
"yanchor": "top",
10592
},
106-
margin={"l": 100, "r": 60, "t": 120, "b": 80},
93+
xaxis=dict(
94+
title={"text": "Quarter", "font": {"size": 22, "color": INK}},
95+
tickfont={"size": 18, "color": INK_SOFT},
96+
showgrid=False,
97+
showline=True,
98+
linewidth=1.5,
99+
linecolor=INK_SOFT,
100+
mirror=False,
101+
),
102+
yaxis=dict(
103+
title={"text": "Revenue (Thousands USD)", "font": {"size": 22, "color": INK}},
104+
tickfont={"size": 18, "color": INK_SOFT},
105+
gridcolor=GRID_SUBTLE,
106+
gridwidth=0.5,
107+
showline=True,
108+
linewidth=1.5,
109+
linecolor=INK_SOFT,
110+
mirror=False,
111+
),
112+
barmode="stack",
113+
bargap=0.35,
114+
bargroupgap=0.1,
115+
paper_bgcolor=PAGE_BG,
116+
plot_bgcolor=PAGE_BG,
117+
font={"color": INK, "family": "system-ui, -apple-system, sans-serif"},
118+
legend=dict(
119+
orientation="v",
120+
yanchor="top",
121+
y=0.99,
122+
xanchor="right",
123+
x=0.99,
124+
font={"size": 18, "color": INK},
125+
bgcolor="rgba(255,253,246,0.95)" if THEME == "light" else "rgba(36,36,32,0.95)",
126+
bordercolor=INK_SOFT,
127+
borderwidth=1.5,
128+
),
129+
margin={"l": 110, "r": 70, "t": 120, "b": 90},
130+
width=1600,
131+
height=900,
107132
)
108133

109134
# Save as PNG (4800 x 2700 px)
110-
fig.write_image("plot.png", width=1600, height=900, scale=3)
135+
fig.write_image(f"plot-{THEME}.png", width=1600, height=900, scale=3)
111136

112137
# Save as HTML for interactivity
113-
fig.write_html("plot.html", include_plotlyjs="cdn")
138+
fig.write_html(f"plot-{THEME}.html", include_plotlyjs="cdn")

0 commit comments

Comments
 (0)