Driver-Based Revenue
Projects revenue from a multiplicative driver tree, splits actual from projected, and decomposes growth into per-driver contributions.
Revenue forecast · Q1–Q2 2026
$4.35M
+145% over 6 periods · 19.6%/period
- Actual
- Projected
Revenue rose from $1.78M to $4.35M in Q1 2025→Q2 2026, led by Signups (+$1.44M).
| Period | Revenue | Signups | Activation | ARPA |
|---|---|---|---|---|
| Q1 2025 | $1.78M | 1,850 | 52.0% | $1.9k |
| Q2 2025 | $2.22M | 2,140 | 54.0% | $1.9k |
| Q3 2025 | $2.68M | 2,380 | 56.0% | $2.0k |
| Q4 2025 | $3.3M | 2,610 | 58.0% | $2.2k |
| Q1 2026 | $3.39M | 2,750 | 56.0% | $2.2k |
| Q2 2026 | $4.35M | 3,050 | 59.0% | $2.4k |
Acme analytics · signups, activation, and pricing drivers · updated daily
Demo data is illustrative. Replace with your own typed data prop.
How it computes
Multiplicative driver tree (revenue = Πᵢ driverᵢ) with log-additive growth attribution and CAGR.
revₜ = Π driverᵢ,ₜ; Δrevᵢ = (ln(dᵢ,₁/dᵢ,₀) / Σⱼ ln(dⱼ,₁/dⱼ,₀))·Δrev; CAGR = (rev_end/rev_start)^(1/n) − 1Assumptions
- Drivers combine multiplicatively, not additively.
- The log-decomposition splits the joint change across drivers.
- Periods and drivers are position-aligned (the shortest array sets the length).
Honest about
- A driver that crosses zero contributes 0 to the log attribution and its share is silently absorbed by the others — the decomposition is incomplete there.
- CAGR falls back to 0 when either endpoint ≤ 0; zero and undefined aren't distinguished.
- The projection just recomposes revenue from the driver values you supply — there's no internal re-forecast.
- Under two usable periods it shows a "not enough periods" state.
Use it with your data
Data contract
Passed via the data prop — an object: validated at runtime, so a bad shape degrades to the error state rather than crashing.
| Field | Type | Required |
|---|---|---|
| periods | array | yes |
| actualThrough | integer | yes |
| drivers | array | yes |
Static props
import { FpaDriverRevenue } from "@/components/blocks/fpa-driver-revenue/fpa-driver-revenue";
<FpaDriverRevenue data={myData} />Client fetch (SWR)
const { data, isLoading, error } = useSWR("/api/metric", fetcher);
// route isLoading/error through <ChartStates>, then:
<FpaDriverRevenue data={data} />Server component (RSC)
const data = await fetch(url, {
next: { revalidate: 3600 },
}).then((r) => r.json());
<FpaDriverRevenue data={data} />Map arbitrary rows
import { mapRows } from "@/lib/sextant/column-map";
const out = mapRows(rows, { /* contractKey: "yourColumn" */ }, dataSchema);
if (out.ok) <FpaDriverRevenue data={out.data} />;Make it yours
Every customization below is an optional prop — omit them all and you get the defaults shown above. Recolor, reformat, restyle, or drop the card chrome without forking the component.
Recolor or rename a series (actual, projected, driver)
<FpaDriverRevenue
series={{ actual: { color: "var(--chart-3)", label: "…" } }}
/>Format numbers (currency / locale)
const eur = new Intl.NumberFormat("de-DE", {
style: "currency",
currency: "EUR",
notation: "compact",
});
<FpaDriverRevenue valueFormatter={(n) => eur.format(n)} />Replace or hide the narrative
<FpaDriverRevenue narrative="Your own one-liner." />
// …or hide it entirely:
<FpaDriverRevenue narrative={false} />Restyle any region
<FpaDriverRevenue
className="max-w-xl"
classNames={{ body: "bg-muted/20" }}
/>Drop the card chrome (headless plot)
import { FpaDriverRevenuePlot } from "@/components/blocks/fpa-driver-revenue/fpa-driver-revenue";
// Just the chart body — bring your own card/layout:
<FpaDriverRevenuePlot data={data} config={config} />