A Marimekko chart (also called mekko or mosaic plot) is a stacked bar chart where both the width and height of segments represent data values. This visualization shows two categorical dimensions simultaneously with proportional areas, making it ideal for understanding how parts relate to wholes across categories of different sizes.

# anyplot.ai
# marimekko-basic: Basic Marimekko Chart
# Library: makie 0.21.9 | Julia 1.11.9
# Quality: 86/100 | Created: 2026-07-24
using CairoMakie
using Colors
# --- 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 WHITE_TEXT = colorant"#FFFDF6"
const DARK_TEXT = colorant"#1A1A17"
# --- Data: digital ad spend ($K) by channel (width) and objective (stack) --
x_categories = ["Social", "Search", "Video", "Display", "Audio"]
y_categories = ["Brand Awareness", "Performance", "Retention"]
# rows = y_categories (stack order, bottom to top), cols = x_categories
values = [
150 40 190 70 30
220 260 90 50 15
80 60 30 20 5
]
n_x = length(x_categories)
n_y = length(y_categories)
col_totals = vec(sum(values; dims = 1))
grand_total = sum(col_totals)
# Largest single channel/objective segment — called out with a bolder stroke
# below to give the eye one clear focal point among otherwise-equal columns.
max_j, max_i = Tuple(argmax(values))
# Bar widths proportional to column totals, with a thin gap between bars.
gap = 0.015
usable_width = 1.0 - gap * (n_x - 1)
widths = col_totals ./ grand_total .* usable_width
x_starts = zeros(n_x)
for i in 2:n_x
x_starts[i] = x_starts[i - 1] + widths[i - 1] + gap
end
# Segment fill colors: green first, then canonical order.
SEGMENT_COLORS = IMPRINT_PALETTE[1:n_y]
TEXT_ON_FILL = [WHITE_TEXT, DARK_TEXT, WHITE_TEXT] # contrast per SEGMENT_COLORS entry
# --- Figure -------------------------------------------------------------
fig = Figure(
resolution = (1600, 900),
fontsize = 14,
backgroundcolor = PAGE_BG,
)
ax = Axis(
fig[1, 1];
title = "marimekko-basic · julia · makie · anyplot.ai",
titlesize = 29,
titlecolor = INK,
xlabel = "Channel (bar width ∝ total ad spend)",
ylabel = "Share of Channel Spend",
xlabelsize = 14,
ylabelsize = 14,
xlabelcolor = INK,
ylabelcolor = INK,
xticklabelsize = 13,
yticklabelsize = 12,
xticklabelcolor = INK_SOFT,
yticklabelcolor = INK_SOFT,
xtickcolor = INK_SOFT,
ytickcolor = INK_SOFT,
backgroundcolor = PAGE_BG,
topspinevisible = false,
rightspinevisible = false,
leftspinecolor = INK_SOFT,
bottomspinecolor = INK_SOFT,
xticksvisible = false,
ygridvisible = true,
xgridvisible = false,
ygridcolor = RGBAf(INK.r, INK.g, INK.b, 0.15),
)
ax.xticks = (x_starts .+ widths ./ 2, x_categories)
ax.yticks = (0:0.25:1, ["0%", "25%", "50%", "75%", "100%"])
xlims!(ax, -0.02, 1.02)
ylims!(ax, 0, 1.06)
# Label threshold: only annotate segments that are large enough to hold text.
label_threshold = 0.04 * grand_total
for i in 1:n_x
y0 = 0.0
for j in 1:n_y
value = values[j, i]
frac = value / col_totals[i]
is_focal = i == max_i && j == max_j
poly!(
ax,
Rect2f(x_starts[i], y0, widths[i], frac);
color = SEGMENT_COLORS[j],
strokecolor = is_focal ? INK : PAGE_BG,
strokewidth = is_focal ? 5 : 3,
)
if value >= label_threshold
text!(
ax,
x_starts[i] + widths[i] / 2,
y0 + frac / 2;
text = is_focal ? "★ \$$(value)K" : "\$$(value)K",
color = TEXT_ON_FILL[j],
fontsize = 15,
font = is_focal ? :bold : :regular,
align = (:center, :center),
)
end
y0 += frac
end
# Column-total annotation above each bar, reinforcing "width = total".
text!(
ax,
x_starts[i] + widths[i] / 2,
1.03;
text = "\$$(col_totals[i])K",
color = INK_SOFT,
fontsize = 13,
align = (:center, :center),
)
end
Legend(
fig[1, 2],
[PolyElement(color = c) for c in SEGMENT_COLORS],
y_categories,
"Objective";
framevisible = false,
backgroundcolor = :transparent,
labelcolor = INK,
titlecolor = INK,
)
colsize!(fig.layout, 1, Relative(0.82))
# --- Save ---------------------------------------------------------------
save("plot-$(THEME).png", fig; px_per_unit = 2)
Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/marimekko-basic/makie/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.
{
"spec_id": "marimekko-basic",
"language": "julia",
"library": "makie",
"page": "https://anyplot.ai/marimekko-basic/julia/makie",
"hub": "https://anyplot.ai/marimekko-basic",
"code_json": "https://api.anyplot.ai/specs/marimekko-basic/makie/code",
"spec_json": "https://api.anyplot.ai/specs/marimekko-basic",
"render_light_png": "https://storage.googleapis.com/anyplot-images/plots/marimekko-basic/julia/makie/plot-light.png",
"render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/marimekko-basic/julia/makie/plot-dark.png",
"quality_score": 86.0,
"license": "MIT",
"guide": "https://anyplot.ai/llms.txt"
}Part of Basic Marimekko Chart on anyplot.ai.