Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 148 additions & 0 deletions plots/waterfall-basic/implementations/julia/makie.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# anyplot.ai
# waterfall-basic: Basic Waterfall Chart
# Library: makie 0.21.9 | Julia 1.11.9
# Quality: 93/100 | Created: 2026-08-04

using CairoMakie
using Colors
using Printf

# --- Theme tokens -------------------------------------------------------------
const THEME = get(ENV, "ANYPLOT_THEME", "light")
const PAGE_BG = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17"
const INK = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8"
const INK_SOFT = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0"

const IMPRINT_PALETTE = [
colorant"#009E73", colorant"#C475FD", colorant"#4467A3", colorant"#BD8233",
colorant"#AE3030", colorant"#2ABCCD", colorant"#954477", colorant"#99B314",
]
const BRAND = IMPRINT_PALETTE[1] # Imprint palette position 1 — increases
const LOSS = IMPRINT_PALETTE[5] # Imprint semantic anchor for loss — decreases
const NEUTRAL = INK # theme-adaptive neutral — start/end totals

# --- Data: quarterly profit bridge, $ millions --------------------------------
categories = ["Starting\nBalance", "Product\nSales", "Service\nRevenue",
"Operating\nCosts", "Marketing\nSpend", "Taxes", "Net\nProfit"]
deltas = [12.4, 8.5, 4.3, -5.4, -3.1, -1.85, 0.0]
is_total = [true, false, false, false, false, false, true]
n = length(categories)

running = zeros(Float64, n)
running[1] = deltas[1]
for i in 2:(n - 1)
running[i] = running[i - 1] + deltas[i]
end
running[n] = running[n - 1]

bottoms = zeros(Float64, n)
tops = zeros(Float64, n)
bar_colors = Vector{typeof(BRAND)}(undef, n)
for i in 1:n
if is_total[i]
bottoms[i] = 0.0
tops[i] = running[i]
bar_colors[i] = NEUTRAL
else
lo, hi = extrema((running[i - 1], running[i]))
bottoms[i] = lo
tops[i] = hi
bar_colors[i] = deltas[i] >= 0 ? BRAND : LOSS
end
end

# --- Title (fontsize scaled to length — see plot-generator.md) ----------------
title_str = "Quarterly Profit Bridge · waterfall-basic · julia · makie · anyplot.ai"
title_fontsize = round(Int, 20 * min(1.0, 67 / length(title_str)))

# --- Plot -----------------------------------------------------------------------
fig = Figure(
resolution = (1600, 900),
fontsize = 14,
backgroundcolor = PAGE_BG,
)

ax = Axis(
fig[1, 1];
title = title_str,
titlesize = title_fontsize,
titlecolor = INK,
ylabel = "Amount (\$M)",
ylabelsize = 14,
ylabelcolor = INK,
xticklabelcolor = INK_SOFT,
yticklabelcolor = INK_SOFT,
xticklabelsize = 12,
yticklabelsize = 12,
backgroundcolor = PAGE_BG,
topspinevisible = false,
rightspinevisible = false,
leftspinecolor = INK_SOFT,
bottomspinecolor = INK_SOFT,
xgridvisible = false,
ygridcolor = RGBAf(INK.r, INK.g, INK.b, 0.15),
xticks = (1:n, categories),
xticksvisible = false,
)

bar_width = 0.62
barplot!(ax, 1:n, tops; fillto = bottoms, color = bar_colors, width = bar_width, strokewidth = 0)

# Connecting lines linking each bar's cumulative level to the next bar's start
half = bar_width / 2
for i in 1:(n - 1)
level = running[i]
lines!(ax, [i + half, i + 1 - half], [level, level];
color = INK_SOFT, linewidth = 1.8, linestyle = :dot)
end

# The largest single decrease gets a bolder, slightly larger label to call
# out the focal point of the bridge.
decrease_idxs = findall(i -> !is_total[i] && deltas[i] < 0, 1:n)
biggest_idx = decrease_idxs[argmax(abs.(deltas[decrease_idxs]))]
biggest_label = replace(categories[biggest_idx], "\n" => " ")

# Running total labels — above the bar for increases/totals, below for decreases
label_offset = maximum(tops) * 0.035
for i in 1:n
label = @sprintf("\$%.2fM", running[i])
emphasize = i == biggest_idx
lbl_fontsize = emphasize ? 17 : 15
lbl_font = emphasize ? :bold : :regular
if is_total[i] || deltas[i] >= 0
text!(ax, i, tops[i] + label_offset; text = label,
align = (:center, :bottom), color = INK, fontsize = lbl_fontsize, font = lbl_font)
else
text!(ax, i, bottoms[i] - label_offset; text = label,
align = (:center, :top), color = INK, fontsize = lbl_fontsize, font = lbl_font)
end
end

# Subtitle annotation summarizing the bridge's focal point, set in the axis's
# relative-space headroom above the tallest bar — a Makie `rich()` touch that
# mixes weight/color within a single text object to call out the two key figures.
subtitle_rich = rich(
"Net profit reaches ",
rich(@sprintf("\$%.2fM", running[end]); color = BRAND, font = :bold),
" despite a ",
rich(@sprintf("\$%.2fM", abs(deltas[biggest_idx])); color = LOSS, font = :bold),
" $biggest_label pullback — the bridge's largest single decrease",
)
text!(ax, 0.5, 0.97; space = :relative, text = subtitle_rich,
align = (:center, :top), fontsize = 13, color = INK_SOFT)

ylims!(ax, -maximum(tops) * 0.08, maximum(tops) * 1.18)

elements = [PolyElement(color = BRAND), PolyElement(color = LOSS), PolyElement(color = NEUTRAL)]
Legend(fig[1, 1], elements, ["Increase", "Decrease", "Total"];
tellwidth = false, tellheight = false,
halign = :left, valign = :top,
orientation = :horizontal,
framevisible = false,
labelcolor = INK_SOFT,
labelsize = 12,
backgroundcolor = :transparent,
)

# --- Save -----------------------------------------------------------------------
save("plot-$(THEME).png", fig; px_per_unit = 2)
232 changes: 232 additions & 0 deletions plots/waterfall-basic/metadata/julia/makie.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
library: makie
language: julia
specification_id: waterfall-basic
created: '2026-08-04T11:29:00Z'
updated: '2026-08-04T11:51:05Z'
generated_by: claude-sonnet
workflow_run: 30904486628
issue: 777
language_version: 1.11.9
library_version: 0.21.9
preview_url_light: https://storage.googleapis.com/anyplot-images/plots/waterfall-basic/julia/makie/plot-light.png
preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/waterfall-basic/julia/makie/plot-dark.png
preview_html_light: null
preview_html_dark: null
quality_score: 93
review:
strengths:
- Correct, fully-verified cumulative math (running totals match all displayed labels
exactly)
- Dotted connector lines and the bold/larger label on the largest single decrease
create real visual hierarchy and a clear narrative focal point
- rich() mixed-format subtitle is a Makie-idiomatic storytelling touch that ties
the headline numbers back into the chart
- Theme-adaptive total-bar color (INK) keeps start/end bars distinct from data bars
in both themes without introducing an off-palette color
- Canvas lands exactly on the 3200x1800 target with no clipping in either render
weaknesses:
- Total bars (Starting Balance, Net Profit) are colored via the ink/chrome token
rather than a dedicated categorical tone; visually distinct and legible, but leans
on a chrome color for a data-encoding role
- Design Excellence is solid but still close to the documented style-guide skeleton;
a bit more customization (e.g. a distinct total-bar hue, subtle bar corner rounding)
would push DE-01 further
image_description: |-
Light render (plot-light.png):
Background: Warm off-white, consistent with #FAF8F1
Chrome: Title "Quarterly Profit Bridge · waterfall-basic · julia · makie · anyplot.ai", rich-text subtitle, axis ticks/labels, legend (Increase/Decrease/Total) all in dark ink — all clearly readable
Data: First series (Increase) is Imprint #009E73 green; Decrease bars use semantic anchor #AE3030 red; Total bars (Starting Balance, Net Profit) are ink/near-black; dotted connector lines link cumulative levels between bars
Legibility verdict: PASS

Dark render (plot-dark.png):
Background: Warm near-black, consistent with #1A1A17
Chrome: Same title/subtitle/axis/legend elements now rendered in light ink against the dark surface — no dark-on-dark issues; Total bars flip to warm off-white/cream
Data: Green (#009E73) and red (#AE3030) data bars are pixel-identical to the light render; only chrome (background, ink, total-bar color) adapts
Legibility verdict: PASS
criteria_checklist:
visual_quality:
score: 29
max: 30
items:
- id: VQ-01
name: Text Legibility
score: 7
max: 8
passed: true
comment: All text explicitly sized and readable in both themes
- id: VQ-02
name: No Overlap
score: 6
max: 6
passed: true
comment: No collisions between labels, connectors, subtitle, or legend
- id: VQ-03
name: Element Visibility
score: 6
max: 6
passed: true
comment: 7 bars well-sized and separated for sparse dataset
- id: VQ-04
name: Color Accessibility
score: 2
max: 2
passed: true
comment: Green/red reinforced by legend and value labels, not sole signal
- id: VQ-05
name: Layout & Canvas
score: 4
max: 4
passed: true
comment: Confirmed exact 3200x1800 canvas, no overflow
- id: VQ-06
name: Axis Labels & Title
score: 2
max: 2
passed: true
comment: Y-axis carries units, title matches mandated format
- id: VQ-07
name: Palette Compliance
score: 2
max: 2
passed: true
comment: 'First series #009E73, semantic exception for loss, correct theme
backgrounds'
design_excellence:
score: 15
max: 20
items:
- id: DE-01
name: Aesthetic Sophistication
score: 6
max: 8
passed: true
comment: Custom palette, theme-adaptive total color, rich() subtitle
- id: DE-02
name: Visual Refinement
score: 4
max: 6
passed: true
comment: Spines removed, subtle grid, generous whitespace
- id: DE-03
name: Data Storytelling
score: 5
max: 6
passed: true
comment: Connector lines and emphasized biggest-decrease label create a clear
focal point
spec_compliance:
score: 15
max: 15
items:
- id: SC-01
name: Plot Type
score: 5
max: 5
passed: true
comment: Correct waterfall/bridge chart
- id: SC-02
name: Required Features
score: 4
max: 4
passed: true
comment: Color coding, connector lines, total bars, running-total labels all
present
- id: SC-03
name: Data Mapping
score: 3
max: 3
passed: true
comment: X=category, Y=cumulative amount, all 7 steps shown
- id: SC-04
name: Title & Legend
score: 3
max: 3
passed: true
comment: Title format matches, legend labels match encoding
data_quality:
score: 15
max: 15
items:
- id: DQ-01
name: Feature Coverage
score: 6
max: 6
passed: true
comment: Increases, decreases, totals, connectors, labels all shown
- id: DQ-02
name: Realistic Context
score: 5
max: 5
passed: true
comment: Plausible neutral quarterly profit-bridge scenario
- id: DQ-03
name: Appropriate Scale
score: 4
max: 4
passed: true
comment: Sensible $M-range financial values
code_quality:
score: 10
max: 10
items:
- id: CQ-01
name: KISS Structure
score: 3
max: 3
passed: true
comment: Flat top-level script
- id: CQ-02
name: Reproducibility
score: 2
max: 2
passed: true
comment: Deterministic hardcoded data
- id: CQ-03
name: Clean Imports
score: 2
max: 2
passed: true
comment: All imports used
- id: CQ-04
name: Code Elegance
score: 2
max: 2
passed: true
comment: Appropriate complexity, no fake UI
- id: CQ-05
name: Output & API
score: 1
max: 1
passed: true
comment: Saves plot-$(THEME).png with current API
library_mastery:
score: 9
max: 10
items:
- id: LM-01
name: Idiomatic Usage
score: 5
max: 5
passed: true
comment: Idiomatic Axis/barplot!/lines!/text!/Legend composition
- id: LM-02
name: Distinctive Features
score: 4
max: 5
passed: true
comment: Uses rich() to mix weight/color inline for subtitle
verdict: APPROVED
impl_tags:
dependencies: []
techniques:
- waterfall-connectors
- value-labels
- rich-text-annotation
- conditional-emphasis
patterns:
- cumulative-calculation
- conditional-coloring
dataprep: []
styling:
- publication-ready
- theme-adaptive-chrome
Loading