Analytics
FunnelChart
A vertical column funnel with a summary panel.
Guidelines#
Each stage shows its count above a bar, a soft area joins the bar tops, numbered markers sit on a connector, and each label carries its conversion to the next stage. The panel shows the overall and per-step conversion.
Use it for acquisition and campaign funnels. In Max the funnel runs Leads enrolled → Engaged (opens + connections) → Replied (email + LinkedIn + WhatsApp) → Meetings booked. The grid adapts to any number of stages.
What you provide: title, optional subtitle, isLoading, and stages: AnalyticsFunnelStageDto[]. Each stage is { key, label, count, pct_of_first, pct_of_previous }, with percentages from 0 to 100. The chart prints them as ${value}%, so pass them already rounded; the server rounds to one decimal.
Anatomy: the panel is glass-panel border-glass rounded-3xl px-6 py-6 and the whole chart uses one hue, var(--chart-2). The bar zone is 176px tall. Bars are max-w-14 (sm:max-w-16) and rounded-t-md. A non-zero stage keeps at least 4% height; a true zero draws nothing. The joining area is filled at 10% opacity. Markers are 28px circles in border-glass bg-background. The summary aside is rounded-2xl bg-muted/40, lg:w-60, and sits beside the chart from lg.
States: while loading, a 4-column skeleton. When every count is 0: "No funnel activity in the selected period".
Don't count activity rows or pass non-monotonic stages. Count distinct leads and fold each stage into the one before it, so no conversion exceeds 100%.
Facts#
| Group | Analytics |
|---|---|
| Product | Max |
| Global | window.DigitalCrew.FunnelChart |
| Source | max-agent: src/features/analytics/ui/funnel-chart.tsx |
| Import in the app | import { FunnelChart } from "@/features/analytics/ui/funnel-chart"; |
API#
TypeScript declarations emitted from src/features/analytics/ui/funnel-chart.tsx.
/**
* Vertical column funnel: one column per stage with the count above a
* baseline-anchored bar, a soft area joining stage tops, numbered step
* markers on a connector line, and stage-over-stage conversion beneath.
* A summary panel carries the overall + per-step conversion rates.
*/
export declare function FunnelChart({ title, subtitle, stages, isLoading, }: {
title: string;
subtitle?: string;
stages: AnalyticsFunnelStageDto[];
isLoading: boolean;
}): JSX.Element;Example#
The code of the preview above: plain React.createElement against window.DigitalCrew, with realistic data.
<div id="root" class="p-6"></div>
<script>
(function () {
var h = React.createElement, DC = window.DigitalCrew, I = DC.Icons;
// AnalyticsFunnelStageDto[]: distinct leads per stage, monotonic, with the
// same rounding as core/acquisition-funnel.ts (one decimal place).
function pct(n, d) { return d === 0 ? 0 : Math.round((n / d) * 1000) / 10; }
var RAW = [
{ key: "contacted", label: "Leads enrolled", count: 1240 },
{ key: "engaged", label: "Engaged (opens + connections)", count: 684 },
{ key: "replied", label: "Replied (email + LinkedIn + WhatsApp)", count: 173 },
{ key: "meetings", label: "Meetings booked", count: 41 },
{ key: "deals", label: "Deals created", count: 15 }
];
var STAGES = RAW.map(function (s, i) {
return {
key: s.key, label: s.label, count: s.count,
pct_of_first: pct(s.count, RAW[0].count),
pct_of_previous: pct(s.count, i === 0 ? RAW[0].count : RAW[i - 1].count)
};
});
function App() {
return h(DC.FunnelChart, {
title: "Acquisition funnel",
subtitle: "Q3 outbound · from first touch to deal created",
stages: STAGES,
isLoading: false
});
}
ReactDOM.createRoot(document.getElementById("root")).render(h(DC.TooltipProvider, { delayDuration: 80 }, h(App)));
})();
</script>