Basic Bullet Chart — Makie.jl

A bullet chart displays a single measure against qualitative ranges and a target marker, designed by Stephen Few as a space-efficient alternative to gauge charts. The linear format shows actual performance as a bar, a target as a vertical marker, and background bands representing qualitative ranges (poor/satisfactory/good). Its compact design allows multiple bullet charts to fit on a single dashboard for easy comparison across metrics.

Basic Bullet Chart rendered with Makie.jl

Renders

Julia source (Makie.jl)

# anyplot.ai
# bullet-basic: Basic Bullet Chart
# Library: makie 0.22.10 | Julia 1.11.9
# Quality: 88/100 | Created: 2026-05-29

using CairoMakie
using Colors

# --- Theme tokens -----------------------------------------------------------
const THEME       = get(ENV, "ANYPLOT_THEME", "light")
const PAGE_BG     = THEME == "light" ? colorant"#FAF8F1" : colorant"#1A1A17"
const ELEVATED_BG = THEME == "light" ? colorant"#FFFDF6" : colorant"#242420"
const INK         = THEME == "light" ? colorant"#1A1A17" : colorant"#F0EFE8"
const INK_SOFT    = THEME == "light" ? colorant"#4A4A44" : colorant"#B8B7B0"
const INK_MUTED   = THEME == "light" ? colorant"#6B6A63" : colorant"#A8A79F"
const BRAND       = colorant"#009E73"  # Imprint palette position 1

# Grayscale background bands (theme-adaptive; spec requires grayscale shading)
const BAND_POOR = THEME == "light" ? RGBAf(0.67, 0.66, 0.64, 1.0) : RGBAf(0.22, 0.22, 0.21, 1.0)
const BAND_SAT  = THEME == "light" ? RGBAf(0.78, 0.77, 0.75, 1.0) : RGBAf(0.30, 0.30, 0.29, 1.0)
const BAND_GOOD = THEME == "light" ? RGBAf(0.88, 0.87, 0.85, 1.0) : RGBAf(0.38, 0.38, 0.37, 1.0)
const GRID_CLR  = THEME == "light" ?
    RGBAf(26 / 255, 26 / 255, 23 / 255, 0.12) :
    RGBAf(240 / 255, 239 / 255, 232 / 255, 0.12)

# --- Data -------------------------------------------------------------------
labels  = ["Q1 Revenue", "Customer Satisfaction", "Project Completion", "Code Quality", "Cost Efficiency"]
actuals = [82, 78, 68, 91, 38]  # Cost Efficiency at 38 exercises the poor zone (<50)
targets = [90, 85, 75, 88, 55]
n = length(labels)

# --- Plot -------------------------------------------------------------------
title_str  = "Sales KPI Dashboard · bullet-basic · julia · makie · anyplot.ai"
title_size = length(title_str) > 67 ? round(Int, 20 * 67 / length(title_str)) : 20

fig = Figure(size = (1600, 900), fontsize = 14, backgroundcolor = PAGE_BG)

ax = Axis(
    fig[1, 1];
    title             = title_str,
    titlesize         = title_size,
    titlecolor        = INK,
    xlabel            = "Score (%)",
    xlabelsize        = 14,
    xlabelcolor       = INK,
    xticklabelsize    = 12,
    xticklabelcolor   = INK_SOFT,
    xtickcolor        = INK_SOFT,
    yticklabelsize    = 12,
    yticklabelcolor   = INK_SOFT,
    yticksvisible     = false,
    backgroundcolor   = PAGE_BG,
    topspinevisible   = false,
    rightspinevisible = false,
    leftspinevisible  = false,
    bottomspinecolor  = INK_SOFT,
    xgridcolor        = GRID_CLR,
    ygridvisible      = false,
    xminorgridvisible = false,
    yminorgridvisible = false,
)

band_h = 0.60
bar_h  = 0.28

for i in 1:n
    y      = Float64(i)
    actual = Float64(actuals[i])
    target = Float64(targets[i])

    poly!(ax, Rect2f(0.0,  y - band_h / 2, 50.0, band_h); color = BAND_POOR, strokewidth = 0)
    poly!(ax, Rect2f(50.0, y - band_h / 2, 25.0, band_h); color = BAND_SAT,  strokewidth = 0)
    poly!(ax, Rect2f(75.0, y - band_h / 2, 25.0, band_h); color = BAND_GOOD, strokewidth = 0)

    poly!(ax, Rect2f(0.0, y - bar_h / 2, actual, bar_h); color = BRAND, strokewidth = 0)

    lines!(ax, [target, target], [y - band_h / 2 - 0.05, y + band_h / 2 + 0.05];
           color = INK, linewidth = 4.0)

    exceeds = actuals[i] > targets[i]
    text!(ax, actual + 1.5, y;
          text    = "$(actuals[i])%",
          color   = exceeds ? BRAND : INK_SOFT,
          fontsize = exceeds ? 13 : 11,
          align   = (:left, :center))
end

ax.yticks    = (Float64.(1:n), labels)
ax.xticks    = [0, 25, 50, 75, 100]
ax.yreversed = true
xlims!(ax, -2.0, 112.0)
ylims!(ax, 0.3, Float64(n) + 0.7)

Legend(
    fig[1, 2],
    [PolyElement(color = BAND_GOOD, strokewidth = 0),
     PolyElement(color = BAND_SAT,  strokewidth = 0),
     PolyElement(color = BAND_POOR, strokewidth = 0),
     PolyElement(color = BRAND,     strokewidth = 0),
     LineElement(color = INK, linewidth = 4.0)],
    ["Good (≥75%)", "Satisfactory (50–75%)", "Poor (<50%)", "Actual", "Target"];
    backgroundcolor = ELEVATED_BG,
    framevisible    = false,
    labelcolor      = INK_SOFT,
    labelsize       = 11,
    padding         = (8, 8, 8, 8),
)

colsize!(fig.layout, 2, Relative(0.20))

# --- Save -------------------------------------------------------------------
save("plot-$(THEME).png", fig; px_per_unit = 2)

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/bullet-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": "bullet-basic",
  "language": "julia",
  "library": "makie",
  "page": "https://anyplot.ai/bullet-basic/julia/makie",
  "hub": "https://anyplot.ai/bullet-basic",
  "code_json": "https://api.anyplot.ai/specs/bullet-basic/makie/code",
  "spec_json": "https://api.anyplot.ai/specs/bullet-basic",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/bullet-basic/julia/makie/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/bullet-basic/julia/makie/plot-dark.png",
  "quality_score": 88.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Basic Bullet Chart on anyplot.ai.

Other implementations