Tornado Diagram for Sensitivity Analysis — Makie.jl

A horizontal bar chart where bars are sorted by influence magnitude, extending left and right from a base case vertical reference line. Each bar represents one input parameter and shows how varying that parameter between its low and high values affects the output, creating a characteristic tornado shape (widest bars at top, narrowest at bottom). Dual colors distinguish low-input from high-input effects, making it immediately clear which parameters drive the most uncertainty.

Tornado Diagram for Sensitivity Analysis rendered with Makie.jl

Renders

Julia source (Makie.jl)

# anyplot.ai
# bar-tornado-sensitivity: Tornado Diagram for Sensitivity Analysis
# Library: makie 0.22.10 | Julia 1.11.9
# Quality: 90/100 | Created: 2026-06-02

using CairoMakie
using Colors
using Random

Random.seed!(42)

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"

# Imprint palette — semantic: green = high/upside, red = low/downside
const COLOR_HIGH = colorant"#009E73"  # Imprint position 1
const COLOR_LOW  = colorant"#AE3030"  # Imprint position 5 (semantic anchor: downside)

# NPV sensitivity analysis — capital investment project, values in millions USD
const BASE_NPV = 120.0

parameters = [
    "Discount Rate",
    "Revenue Growth",
    "Operating Costs",
    "Terminal Value Multiple",
    "Market Share",
    "Tax Rate",
    "Capex Intensity",
    "Working Capital",
    "Inflation Rate",
]
low_values  = [155.0,  95.0, 142.0, 102.0, 104.0, 135.0, 131.0, 126.0, 123.0]
high_values = [ 87.0, 148.0,  99.0, 141.0, 138.0, 109.0, 112.0, 114.0, 116.0]

# Sort ascending by sensitivity range — widest bar plots at top (highest y index)
ranges = abs.(high_values .- low_values)
order  = sortperm(ranges)
n      = length(parameters)
params = parameters[order]
lows   = low_values[order]
highs  = high_values[order]

low_deltas  = lows  .- BASE_NPV
high_deltas = highs .- BASE_NPV

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

title_str = "NPV Sensitivity Analysis · bar-tornado-sensitivity · julia · makie · anyplot.ai"
title_fs  = round(Int, 20 * 67 / length(title_str))

ax = Axis(
    fig[1, 1];
    title              = title_str,
    titlesize          = title_fs,
    titlefont          = :bold,
    titlecolor         = INK,
    xlabel             = "Net Present Value (millions USD)",
    xlabelcolor        = INK,
    xlabelsize         = 14,
    xticklabelsize     = 14,
    xticklabelcolor    = INK_SOFT,
    yticklabelsize     = 14,
    yticklabelcolor    = INK_SOFT,
    xtickcolor         = INK_SOFT,
    ytickcolor         = INK_SOFT,
    backgroundcolor    = PAGE_BG,
    topspinevisible    = false,
    rightspinevisible  = false,
    leftspinevisible   = false,
    bottomspinecolor   = INK_SOFT,
    xgridcolor         = RGBAf(INK.r, INK.g, INK.b, 0.12),
    ygridvisible       = false,
    xminorgridvisible  = false,
    yminorgridvisible  = false,
)

# Tornado bars — low and high input scenarios
barplot!(ax, 1:n, low_deltas;
    direction   = :x,
    offset      = BASE_NPV,
    color       = COLOR_LOW,
    strokewidth = 0,
)
barplot!(ax, 1:n, high_deltas;
    direction   = :x,
    offset      = BASE_NPV,
    color       = COLOR_HIGH,
    strokewidth = 0,
)

# Reference line at base case NPV
vlines!(ax, BASE_NPV; color = INK, linewidth = 2.0, linestyle = :dash)

# Y-axis parameter labels and limits
ax.yticks = (1:n, params)
ylims!(ax, 0.3, Float64(n) + 0.7)

# X-axis limits with padding for bar-end labels
x_pad = 10.0
xlims!(ax,
    minimum(min.(lows, highs)) - x_pad,
    maximum(max.(lows, highs)) + x_pad,
)

# Reference line annotation
text!(ax, [Point2f(BASE_NPV + 2.0, n + 0.4)];
    text     = ["Base: \$120M"],
    color    = INK,
    fontsize = 11,
    align    = (:left, :center),
)

# Bar-end value labels — one label per bar tip showing the resulting NPV
for i in 1:n
    xl = lows[i]
    xoff_l  = xl > BASE_NPV ? 2.0 : -2.0
    align_l = xl > BASE_NPV ? :left : :right
    text!(ax, [Point2f(xl + xoff_l, Float64(i))];
        text     = ["$(round(Int, xl))"],
        color    = COLOR_LOW,
        fontsize = 11,
        align    = (align_l, :center),
    )
    xh = highs[i]
    xoff_h  = xh > BASE_NPV ? 2.0 : -2.0
    align_h = xh > BASE_NPV ? :left : :right
    text!(ax, [Point2f(xh + xoff_h, Float64(i))];
        text     = ["$(round(Int, xh))"],
        color    = COLOR_HIGH,
        fontsize = 11,
        align    = (align_h, :center),
    )
end

# Legend — framevisible=false per style guide (no legend box borders)
elem_high = PolyElement(color = COLOR_HIGH, strokecolor = :transparent)
elem_low  = PolyElement(color = COLOR_LOW,  strokecolor = :transparent)
Legend(
    fig[1, 2],
    [elem_high, elem_low],
    ["High Input Scenario", "Low Input Scenario"];
    framevisible    = false,
    backgroundcolor = ELEVATED_BG,
    labelcolor      = INK_SOFT,
    labelsize       = 13,
    rowgap          = 8,
)

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/bar-tornado-sensitivity/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": "bar-tornado-sensitivity",
  "language": "julia",
  "library": "makie",
  "page": "https://anyplot.ai/bar-tornado-sensitivity/julia/makie",
  "hub": "https://anyplot.ai/bar-tornado-sensitivity",
  "code_json": "https://api.anyplot.ai/specs/bar-tornado-sensitivity/makie/code",
  "spec_json": "https://api.anyplot.ai/specs/bar-tornado-sensitivity",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/bar-tornado-sensitivity/julia/makie/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/bar-tornado-sensitivity/julia/makie/plot-dark.png",
  "quality_score": 90.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Tornado Diagram for Sensitivity Analysis on anyplot.ai.

Other implementations