Skip to content

Commit 6a1caf3

Browse files
feat(makie): implement area-stacked (#10286)
## Implementation: `area-stacked` - julia/makie Implements the **julia/makie** version of `area-stacked`. **File:** `plots/area-stacked/implementations/julia/makie.jl` **Parent Issue:** #2022 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/32033006827)* --------- 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 7421bf4 commit 6a1caf3

2 files changed

Lines changed: 371 additions & 0 deletions

File tree

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# anyplot.ai
2+
# area-stacked: Stacked Area Chart
3+
# Library: makie 0.21.9 | Julia 1.11.9
4+
# Quality: 87/100 | Created: 2026-08-17
5+
6+
using CairoMakie
7+
using Colors
8+
using Random
9+
10+
Random.seed!(42)
11+
12+
# --- Theme tokens -------------------------------------------------------------
13+
THEME = get(ENV, "ANYPLOT_THEME", "light")
14+
PAGE_BG = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17"
15+
INK = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8"
16+
INK_SOFT = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0"
17+
IMPRINT_PALETTE = [
18+
colorant"#009E73", colorant"#C475FD", colorant"#4467A3", colorant"#BD8233",
19+
]
20+
21+
# --- Data -----------------------------------------------------------------------
22+
# Monthly revenue ($ thousands) by product category, two years — largest series first
23+
months = 1:24
24+
month_labels = ["Jan 2024", "Apr 2024", "Jul 2024", "Oct 2024",
25+
"Jan 2025", "Apr 2025", "Jul 2025", "Oct 2025"]
26+
27+
electronics = 42 .+ 0.9 .* months .+ 4 .* sin.(months ./ 3) .+ randn(24) .* 3
28+
home_goods = 26 .+ 0.4 .* months .+ 3 .* sin.(months ./ 4 .+ 1) .+ randn(24) .* 2.5
29+
apparel = 18 .+ 0.15 .* months .+ 5 .* sin.(months ./ 6 .+ 2) .+ randn(24) .* 2
30+
sporting = 10 .+ 0.25 .* months .+ 2 .* sin.(months ./ 5) .+ randn(24) .* 1.5
31+
32+
electronics = max.(electronics, 5)
33+
home_goods = max.(home_goods, 4)
34+
apparel = max.(apparel, 3)
35+
sporting = max.(sporting, 2)
36+
37+
baseline = zeros(24)
38+
cum1 = electronics
39+
cum2 = cum1 .+ home_goods
40+
cum3 = cum2 .+ apparel
41+
cum4 = cum3 .+ sporting
42+
43+
# --- Plot -------------------------------------------------------------------------
44+
fig = Figure(
45+
resolution = (1600, 900),
46+
fontsize = 14,
47+
backgroundcolor = PAGE_BG,
48+
)
49+
50+
ax = Axis(
51+
fig[1, 1];
52+
title = "area-stacked · julia · makie · anyplot.ai",
53+
titlesize = 25,
54+
titlecolor = INK,
55+
xlabel = "Month",
56+
ylabel = "Revenue (\$ thousands)",
57+
xlabelsize = 14,
58+
ylabelsize = 14,
59+
xlabelcolor = INK,
60+
ylabelcolor = INK,
61+
xticklabelsize = 12,
62+
yticklabelsize = 12,
63+
xticklabelcolor = INK_SOFT,
64+
yticklabelcolor = INK_SOFT,
65+
backgroundcolor = PAGE_BG,
66+
topspinevisible = false,
67+
rightspinevisible = false,
68+
leftspinecolor = INK_SOFT,
69+
bottomspinecolor = INK_SOFT,
70+
xgridvisible = false,
71+
ygridcolor = RGBAf(INK.r, INK.g, INK.b, 0.15),
72+
xticks = (1:3:22, month_labels),
73+
xticklabelrotation = pi / 6,
74+
)
75+
76+
band!(ax, months, baseline, cum1; color = (IMPRINT_PALETTE[1], 0.85))
77+
band!(ax, months, cum1, cum2; color = (IMPRINT_PALETTE[2], 0.85))
78+
band!(ax, months, cum2, cum3; color = (IMPRINT_PALETTE[3], 0.85))
79+
band!(ax, months, cum3, cum4; color = (IMPRINT_PALETTE[4], 0.85))
80+
81+
lines!(ax, months, cum1; color = IMPRINT_PALETTE[1], linewidth = 2.5)
82+
lines!(ax, months, cum2; color = IMPRINT_PALETTE[2], linewidth = 2)
83+
lines!(ax, months, cum3; color = IMPRINT_PALETTE[3], linewidth = 2)
84+
lines!(ax, months, cum4; color = IMPRINT_PALETTE[4], linewidth = 2)
85+
86+
ylims!(ax, 0, nothing)
87+
xlims!(ax, 0.5, 29)
88+
89+
# Data-storytelling focal point: an end-of-line value label for Electronics
90+
# (the dominant series), placed in the margin whitespace beyond the last data
91+
# point so it reads against the page background — not against the colored
92+
# fill — and stays legible in both themes without fighting CairoMakie's
93+
# per-vertex triangulated color interpolation on `band!`.
94+
lines!(ax, [months[end], months[end] + 1], [cum1[end], cum1[end]];
95+
color = INK_SOFT, linewidth = 1)
96+
text!(ax, months[end] + 1.2, cum1[end];
97+
text = "Electronics\n\$$(round(Int, electronics[end]))k",
98+
color = INK, fontsize = 13, align = (:left, :center))
99+
100+
legend_labels = ["Electronics", "Home Goods", "Apparel", "Sporting Goods"]
101+
legend_elements = [PolyElement(color = (c, 0.85)) for c in IMPRINT_PALETTE]
102+
axislegend(ax, legend_elements, legend_labels; position = :lt, labelcolor = INK_SOFT, framevisible = false)
103+
104+
# --- Save -----------------------------------------------------------------------
105+
save("plot-$(THEME).png", fig; px_per_unit = 2)
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
library: makie
2+
language: julia
3+
specification_id: area-stacked
4+
created: '2026-08-17T13:07:46Z'
5+
updated: '2026-08-17T13:38:58Z'
6+
generated_by: claude-sonnet
7+
workflow_run: 32033006827
8+
issue: 2022
9+
language_version: 1.11.9
10+
library_version: 0.21.9
11+
preview_url_light: https://storage.googleapis.com/anyplot-images/plots/area-stacked/julia/makie/plot-light.png
12+
preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/area-stacked/julia/makie/plot-dark.png
13+
preview_html_light: null
14+
preview_html_dark: null
15+
quality_score: 87
16+
review:
17+
strengths:
18+
- 'Palette compliance is perfect: first series is exactly #009E73, and the remaining
19+
3 series follow Imprint canonical order (lavender #C475FD, blue #4467A3, ochre
20+
#BD8233), identical across both themes.'
21+
- 'Theme-adaptive chrome is fully correct in both renders — no dark-on-dark or light-on-light
22+
failures; backgrounds match #FAF8F1 (light) / #1A1A17 (dark) exactly.'
23+
- True stacked-area construction via band! + lines! with a proper zero baseline
24+
and largest-series-at-bottom ordering, plus an idiomatic manually-built PolyElement
25+
legend matching the band alpha.
26+
- Realistic, neutral retail-revenue dataset (Electronics/Home Goods/Apparel/Sporting
27+
Goods) with plausible per-category growth trajectories and month-to-month noise,
28+
not just parallel flat trends.
29+
weaknesses:
30+
- 'The ''Electronics $Xk'' end-of-line annotation and its connector line (added
31+
via lines!/text! after xlims!(ax, 0.5, 29)) render as nothing in BOTH plot-light.png
32+
and plot-dark.png — verified by pixel-level inspection of the right margin (x=3020-3200
33+
of the 3200x1800 canvas): zero ink-colored or ink-soft-colored pixels exist there
34+
outside the axis line/tick labels. This leaves the rightmost ~14% of canvas width
35+
as dead whitespace and forfeits the chart''s only intended data-storytelling element.
36+
Either make the annotation actually render (check whether it is being clipped
37+
by the Axis''s finalized limits/clip rect since it''s added after xlims!, and
38+
confirm visually with a local render) or delete the dead code and tighten xlims!(ax,
39+
0.5, 25) to reclaim that space for the data.'
40+
- The Electronics (bottom, green) band shows a visible diagonal hatching/seam pattern
41+
running across its entire width in both themes — thin diagonal lines recur roughly
42+
once per data interval. The other three bands (Home Goods, Apparel, Sporting Goods)
43+
render perfectly smooth with no such artifact. This looks like a CairoMakie band!
44+
triangulation seam exposed by the semi-transparent (alpha=0.85) fill against a
45+
flat zero baseline. Try alpha=1.0 on this band, render it with poly! instead,
46+
or otherwise confirm the seam disappears before shipping.
47+
- Title occupies only ~28% of canvas width (measured via pixel bounding box), noticeably
48+
narrower than the ~50-70% guideline for a title of this length — still fully legible,
49+
but there is headroom to grow titlesize a few points for better canvas utilization.
50+
image_description: |-
51+
Light render (plot-light.png):
52+
Background: Warm off-white, matches #FAF8F1, not pure white.
53+
Chrome: Title "area-stacked · julia · makie · anyplot.ai" in bold dark ink top-center (~28% of canvas width); legend top-left with 4 swatches (Electronics/Home Goods/Apparel/Sporting Goods, no box frame); X-axis "Month" with rotated (~30°) date ticks Jan 2024 - Oct 2025; Y-axis "Revenue ($ thousands)" 0-150 with subtle horizontal gridlines only (15% opacity); top/right spines removed. All chrome text clearly readable against the light background.
54+
Data: 4 stacked bands, bottom-to-top: green Electronics (#009E73), lavender Home Goods (#C475FD), blue Apparel (#4467A3), ochre Sporting Goods (#BD8233), each at ~0.85 alpha with a matching-color outline. Zero baseline confirmed. The bottom green band shows a visible diagonal hatching/seam artifact across its full width (confirmed via pixel inspection); the other 3 bands are smooth. No annotation/callout text is visible anywhere in the ~14%-wide empty right margin, despite the source code adding one — verified absent via pixel scan (no ink-colored pixels found outside axis/tick elements).
55+
Legibility verdict: PASS (all present chrome is readable); the coded annotation feature itself does not render at all.
56+
57+
Dark render (plot-dark.png):
58+
Background: Warm near-black, matches #1A1A17, not pure black.
59+
Chrome: Same layout and text as light render, recolored to light ink (#F0EFE8 primary text, #B8B7B0 secondary/tick/legend text) — fully readable, no dark-on-dark failures anywhere.
60+
Data: Identical 4 data colors to the light render (confirmed pixel-for-pixel same hues), only chrome flipped as expected. Same diagonal hatching artifact in the green Electronics band, isolated to that band exactly as in the light render. Same missing annotation in the right margin — confirmed absent via pixel scan.
61+
Legibility verdict: PASS (all present chrome is readable, no dark-on-dark text); the coded annotation feature does not render here either.
62+
criteria_checklist:
63+
visual_quality:
64+
score: 28
65+
max: 30
66+
items:
67+
- id: VQ-01
68+
name: Text Legibility
69+
score: 8
70+
max: 8
71+
passed: true
72+
comment: All font sizes explicitly set (titlesize, x/ylabelsize, x/yticklabelsize);
73+
balanced X/Y label and tick sizes; title fits without clipping in both themes.
74+
- id: VQ-02
75+
name: No Overlap
76+
score: 6
77+
max: 6
78+
passed: true
79+
comment: No overlapping text; legend sits in clear headroom above the data.
80+
- id: VQ-03
81+
name: Element Visibility
82+
score: 6
83+
max: 6
84+
passed: true
85+
comment: Band fills and boundary lines are clearly visible and appropriately
86+
sized for 24 monthly points.
87+
- id: VQ-04
88+
name: Color Accessibility
89+
score: 2
90+
max: 2
91+
passed: true
92+
comment: Good contrast between adjacent bands, no red-green-only reliance,
93+
boundary outlines aid separation.
94+
- id: VQ-05
95+
name: Layout & Canvas
96+
score: 2
97+
max: 4
98+
passed: false
99+
comment: ~14% of canvas width on the right is dead whitespace reserved for
100+
an annotation that never renders, creating unbalanced margins.
101+
- id: VQ-06
102+
name: Axis Labels & Title
103+
score: 2
104+
max: 2
105+
passed: true
106+
comment: '''Month'' and ''Revenue ($ thousands)'' — descriptive with units.'
107+
- id: VQ-07
108+
name: Palette Compliance
109+
score: 2
110+
max: 2
111+
passed: true
112+
comment: 'First series #009E73, canonical order for series 2-4, theme-correct
113+
chrome in both renders.'
114+
design_excellence:
115+
score: 12
116+
max: 20
117+
items:
118+
- id: DE-01
119+
name: Aesthetic Sophistication
120+
score: 6
121+
max: 8
122+
passed: true
123+
comment: Intentional Imprint palette use, layered typography hierarchy, matching-color
124+
band outlines — clearly above a configured default, short of publication-ready
125+
due to the two visual defects below.
126+
- id: DE-02
127+
name: Visual Refinement
128+
score: 4
129+
max: 6
130+
passed: true
131+
comment: Spines removed, subtle single-axis grid, generous whitespace — but
132+
a diagonal hatching artifact in the Electronics band and unused right-margin
133+
space keep this from 'every detail polished'.
134+
- id: DE-03
135+
name: Data Storytelling
136+
score: 2
137+
max: 6
138+
passed: false
139+
comment: The coded end-of-line 'Electronics' value callout — the chart's only
140+
intended storytelling/focal-point element — does not render in either theme,
141+
so the delivered chart shows data without interpretation.
142+
spec_compliance:
143+
score: 15
144+
max: 15
145+
items:
146+
- id: SC-01
147+
name: Plot Type
148+
score: 5
149+
max: 5
150+
passed: true
151+
comment: Correct true stacked-area chart via band!.
152+
- id: SC-02
153+
name: Required Features
154+
score: 4
155+
max: 4
156+
passed: true
157+
comment: Zero baseline, largest-series-at-bottom ordering, semi-transparent
158+
fills, legend all present.
159+
- id: SC-03
160+
name: Data Mapping
161+
score: 3
162+
max: 3
163+
passed: true
164+
comment: X=month (time), Y=stacked cumulative revenue, all data visible.
165+
- id: SC-04
166+
name: Title & Legend
167+
score: 3
168+
max: 3
169+
passed: true
170+
comment: Title format exactly matches 'area-stacked · julia · makie · anyplot.ai';
171+
legend labels match the 4 series.
172+
data_quality:
173+
score: 15
174+
max: 15
175+
items:
176+
- id: DQ-01
177+
name: Feature Coverage
178+
score: 6
179+
max: 6
180+
passed: true
181+
comment: 4 series with distinct growth rates, seasonal wobble, and realistic
182+
noise show varied composition dynamics over 24 months.
183+
- id: DQ-02
184+
name: Realistic Context
185+
score: 5
186+
max: 5
187+
passed: true
188+
comment: Retail revenue by product category — neutral, comprehensible, real-world
189+
scenario.
190+
- id: DQ-03
191+
name: Appropriate Scale
192+
score: 4
193+
max: 4
194+
passed: true
195+
comment: Monthly revenue values ($2k-$80k per category, ~$105k-$140k aggregate)
196+
are plausible for a small-to-mid retailer.
197+
code_quality:
198+
score: 9
199+
max: 10
200+
items:
201+
- id: CQ-01
202+
name: KISS Structure
203+
score: 3
204+
max: 3
205+
passed: true
206+
comment: Single top-down script, no functions/classes.
207+
- id: CQ-02
208+
name: Reproducibility
209+
score: 2
210+
max: 2
211+
passed: true
212+
comment: Random.seed!(42) set before data generation.
213+
- id: CQ-03
214+
name: Clean Imports
215+
score: 2
216+
max: 2
217+
passed: true
218+
comment: CairoMakie, Colors, Random all used.
219+
- id: CQ-04
220+
name: Code Elegance
221+
score: 1
222+
max: 2
223+
passed: true
224+
comment: The annotation lines!/text! block is dead code — it compiles and
225+
runs but produces no visible output, contradicting its own explanatory comment.
226+
- id: CQ-05
227+
name: Output & API
228+
score: 1
229+
max: 1
230+
passed: true
231+
comment: Saves plot-$(THEME).png via current save/px_per_unit API.
232+
library_mastery:
233+
score: 8
234+
max: 10
235+
items:
236+
- id: LM-01
237+
name: Idiomatic Usage
238+
score: 5
239+
max: 5
240+
passed: true
241+
comment: band! is the correct high-level Makie primitive for stacked areas;
242+
axislegend with explicit PolyElement is the documented pattern for custom
243+
band legends.
244+
- id: LM-02
245+
name: Distinctive Features
246+
score: 3
247+
max: 5
248+
passed: true
249+
comment: Manual PolyElement legend construction is a genuine Makie-specific
250+
technique, though the underlying band!/lines! stacking itself is replicable
251+
elsewhere.
252+
verdict: APPROVED
253+
impl_tags:
254+
dependencies: []
255+
techniques:
256+
- annotations
257+
- custom-legend
258+
- manual-ticks
259+
patterns:
260+
- data-generation
261+
dataprep:
262+
- cumulative-sum
263+
- time-series
264+
styling:
265+
- alpha-blending
266+
- grid-styling

0 commit comments

Comments
 (0)