Funnel Chart
Examples
(6)Properties
(47)Playground
Loading demo…
import
{
FunnelChart
}
from
'@platform-blocks/charts'
;
import
{
SALES_FUNNEL
}
from
'./data'
;
const
compact
=
(
value
:
number
)
=>
{
const
abs
=
Math
.
abs
(
value
)
;
if
(
abs
>=
1_000_000
)
return
`${(value / 1_000_000).toFixed(1).replace(/\.0$/, '')}M`
;
if
(
abs
>=
1_000
)
return
`${(value / 1_000).toFixed(1).replace(/\.0$/, '')}K`
;
return
`${value}`
;
}
;
export
function
Demo
(
)
{
return
(
<FunnelChart
title
=
"Product acquisition funnel"
maxWidth
=
{
420
}
height
=
{
420
}
series
=
{
SALES_FUNNEL
}
layout
=
{
{
shape
:
'trapezoid'
,
gap
:
8
,
showConversion
:
false
,
align
:
'center'
,
connectors
:
{
show
:
false
}
,
}
}
valueFormatter
=
{
(
value
)
=>
compact
(
value
)
}
legend
=
{
{
show
:
false
}
}
tooltip
=
{
{
show
:
true
,
formatter
:
(
step
)
=>
`${step.label}: ${step.value.toLocaleString()}`
,
}
}
/>
)
;
}
Loading demo…
import
{
FunnelChart
}
from
'@platform-blocks/charts'
;
import
{
PIPELINE_QUALITY
,
PipelineMeta
}
from
'./data'
;
const
compact
=
(
value
:
number
)
=>
{
const
abs
=
Math
.
abs
(
value
)
;
if
(
abs
>=
1_000_000
)
return
`${(value / 1_000_000).toFixed(1).replace(/\.0$/, '')}M`
;
if
(
abs
>=
1_000
)
return
`${(value / 1_000).toFixed(1).replace(/\.0$/, '')}K`
;
return
`${value}`
;
}
;
export
function
Demo
(
)
{
return
(
<FunnelChart
title
=
"Data pipeline quality checks"
subtitle
=
"From ingestion to certified datasets"
maxWidth
=
{
520
}
height
=
{
440
}
series
=
{
PIPELINE_QUALITY
}
layout
=
{
{
shape
:
'trapezoid'
,
gap
:
8
,
align
:
'center'
,
showConversion
:
false
,
connectors
:
{
show
:
false
}
,
}
}
valueFormatter
=
{
(
value
)
=>
compact
(
value
)
}
legend
=
{
{
show
:
false
}
}
tooltip
=
{
{
show
:
true
,
formatter
:
(
step
)
=>
{
const
idx
=
PIPELINE_QUALITY
.
steps
.
findIndex
(
(
candidate
)
=>
candidate
.
label
===
step
.
label
)
;
const
previous
=
idx
>
0
?
PIPELINE_QUALITY
.
steps
[
idx
-
1
]
:
undefined
;
const
dropValue
=
previous
?
previous
.
value
-
step
.
value
:
0
;
const
dropRate
=
previous
&&
previous
.
value
>
0
?
(
dropValue
/
previous
.
value
)
*
100
:
0
;
const
meta
=
step
.
meta as
PipelineMeta
|
undefined
;
return
[
step
.
label
,
`${step.value.toLocaleString()} rows`
,
previous
?
`Filtered: ${dropValue.toLocaleString()} (${dropRate.toFixed(1)}%)`
:
'Ingestion baseline'
,
meta
?
.
note
,
]
.
filter
(
Boolean
)
.
join
(
'\n'
)
;
}
,
}
}
/>
)
;
}
Loading demo…
import
{
FunnelChart
}
from
'@platform-blocks/charts'
;
import
{
CHECKOUT_FUNNEL
,
CheckoutMeta
}
from
'./data'
;
const
compact
=
(
value
:
number
)
=>
{
const
abs
=
Math
.
abs
(
value
)
;
if
(
abs
>=
1_000_000
)
return
`${(value / 1_000_000).toFixed(1).replace(/\.0$/, '')}M`
;
if
(
abs
>=
1_000
)
return
`${(value / 1_000).toFixed(1).replace(/\.0$/, '')}K`
;
return
`${value}`
;
}
;
const
formatPaymentSplit
=
(
split
:
CheckoutMeta
[
'paymentSplit'
]
)
=>
{
if
(
!
split
)
return
undefined
;
return
`Payment mix: ${Math.round(split.stripe * 100)}% Stripe • ${Math.round(split.paypal * 100)}% PayPal • ${Math.round(split.bnpl * 100)}% BNPL`
;
}
;
export
function
Demo
(
)
{
return
(
<FunnelChart
title
=
"Ecommerce checkout conversion"
subtitle
=
"Drop-off by stage"
maxWidth
=
{
520
}
height
=
{
440
}
series
=
{
CHECKOUT_FUNNEL
}
layout
=
{
{
shape
:
'trapezoid'
,
gap
:
8
,
align
:
'center'
,
showConversion
:
false
,
connectors
:
{
show
:
false
}
,
}
}
valueFormatter
=
{
(
value
)
=>
compact
(
value
)
}
legend
=
{
{
show
:
false
}
}
tooltip
=
{
{
show
:
true
,
formatter
:
(
step
)
=>
{
const
idx
=
CHECKOUT_FUNNEL
.
steps
.
findIndex
(
(
candidate
)
=>
candidate
.
label
===
step
.
label
)
;
const
previous
=
idx
>
0
?
CHECKOUT_FUNNEL
.
steps
[
idx
-
1
]
:
undefined
;
const
dropValue
=
previous
?
previous
.
value
-
step
.
value
:
0
;
const
dropRate
=
previous
&&
previous
.
value
>
0
?
(
dropValue
/
previous
.
value
)
*
100
:
0
;
const
meta
=
step
.
meta as
CheckoutMeta
|
undefined
;
const
paymentSplit
=
formatPaymentSplit
(
meta
?
.
paymentSplit
)
;
return
[
step
.
label
,
`${step.value.toLocaleString()} sessions`
,
previous
?
`Drop: ${dropValue.toLocaleString()} (${dropRate.toFixed(1)}%)`
:
'Entry point'
,
meta
?
.
insight
,
paymentSplit
,
]
.
filter
(
Boolean
)
.
join
(
'\n'
)
;
}
,
}
}
/>
)
;
}
Loading demo…
import
{
FunnelChart
}
from
'@platform-blocks/charts'
;
import
{
HIRING_SERIES
,
HiringMeta
,
STEP_LOOKUP
}
from
'./data'
;
const
compact
=
(
value
:
number
)
=>
{
const
abs
=
Math
.
abs
(
value
)
;
if
(
abs
>=
1_000_000
)
return
`${(value / 1_000_000).toFixed(1).replace(/\.0$/, '')}M`
;
if
(
abs
>=
1_000
)
return
`${(value / 1_000).toFixed(1).replace(/\.0$/, '')}K`
;
return
`${value}`
;
}
;
const
findSeriesContext
=
(
step
:
unknown
)
=>
STEP_LOOKUP
.
get
(
step as
any
)
??
null
;
export
function
Demo
(
)
{
return
(
<FunnelChart
title
=
"Hiring funnel — Staff engineer"
subtitle
=
"External candidates vs. internal transfers"
maxWidth
=
{
620
}
height
=
{
480
}
series
=
{
HIRING_SERIES
}
layout
=
{
{
shape
:
'bar'
,
gap
:
10
,
align
:
'center'
,
showConversion
:
false
,
seriesMode
:
'grouped'
,
connectors
:
{
show
:
false
}
,
}
}
valueFormatter
=
{
(
value
)
=>
compact
(
value
)
}
tooltip
=
{
{
show
:
true
,
formatter
:
(
step
)
=>
{
const
lookup
=
findSeriesContext
(
step as
any
)
??
undefined
;
if
(
!
lookup
)
{
return
`${step.label}: ${step.value.toLocaleString()} candidates`
;
}
const
series
=
lookup
.
series
;
const
stepIndex
=
lookup
.
stepIndex
;
const
previous
=
stepIndex
>
0
?
series
.
steps
[
stepIndex
-
1
]
:
undefined
;
const
dropValue
=
previous
?
previous
.
value
-
step
.
value
:
0
;
const
dropRate
=
previous
&&
previous
.
value
>
0
?
(
dropValue
/
previous
.
value
)
*
100
:
0
;
const
meta
=
step
.
meta as
HiringMeta
|
undefined
;
return
[
`${step.label} • ${series.name}`
,
`${step.value.toLocaleString()} candidates`
,
previous
?
`Drop: ${dropValue.toLocaleString()} (${dropRate.toFixed(1)}%)`
:
'Pipeline intake'
,
meta
?
.
medianDays
!=
null
?
`Median time in stage: ${meta.medianDays} days`
:
undefined
,
meta
?
.
topDeclineReason
?
`Top decline reason: ${meta.topDeclineReason}`
:
undefined
,
]
.
filter
(
Boolean
)
.
join
(
'\n'
)
;
}
,
}
}
/>
)
;
}
Loading demo…
import
{
FunnelChart
}
from
'@platform-blocks/charts'
;
import
{
INCIDENT_RESPONSE
,
IncidentMeta
}
from
'./data'
;
const
compact
=
(
value
:
number
)
=>
{
const
abs
=
Math
.
abs
(
value
)
;
if
(
abs
>=
1_000_000
)
return
`${(value / 1_000_000).toFixed(1).replace(/\.0$/, '')}M`
;
if
(
abs
>=
1_000
)
return
`${(value / 1_000).toFixed(1).replace(/\.0$/, '')}K`
;
return
`${value}`
;
}
;
export
function
Demo
(
)
{
return
(
<FunnelChart
title
=
"Incident response workflow"
subtitle
=
"Volume flowing through each stage"
maxWidth
=
{
520
}
height
=
{
460
}
series
=
{
INCIDENT_RESPONSE
}
layout
=
{
{
shape
:
'trapezoid'
,
gap
:
8
,
align
:
'center'
,
showConversion
:
false
,
connectors
:
{
show
:
false
}
,
}
}
valueFormatter
=
{
(
value
)
=>
compact
(
value
)
}
legend
=
{
{
show
:
false
}
}
tooltip
=
{
{
show
:
true
,
formatter
:
(
step
)
=>
{
const
idx
=
INCIDENT_RESPONSE
.
steps
.
findIndex
(
(
candidate
)
=>
candidate
.
label
===
step
.
label
)
;
const
previous
=
idx
>
0
?
INCIDENT_RESPONSE
.
steps
[
idx
-
1
]
:
undefined
;
const
dropValue
=
previous
?
previous
.
value
-
step
.
value
:
0
;
const
dropRate
=
previous
&&
previous
.
value
>
0
?
(
dropValue
/
previous
.
value
)
*
100
:
0
;
const
meta
=
step
.
meta as
IncidentMeta
|
undefined
;
return
[
`${step.label}`
,
`${step.value.toLocaleString()} incidents`
,
meta
?
.
medianDuration
,
previous
?
`Drop since prior: ${dropValue.toLocaleString()} (${dropRate.toFixed(1)}%)`
:
'Start of workflow'
,
meta
?
.
automationWin
?
`Automation impact: ${meta.automationWin}`
:
undefined
,
]
.
filter
(
Boolean
)
.
join
(
'\n'
)
;
}
,
}
}
/>
)
;
}
Loading demo…
import
{
FunnelChart
}
from
'@platform-blocks/charts'
;
import
{
TRIAL_CONVERSION
,
TrialMeta
}
from
'./data'
;
const
compact
=
(
value
:
number
)
=>
{
const
abs
=
Math
.
abs
(
value
)
;
if
(
abs
>=
1_000_000
)
return
`${(value / 1_000_000).toFixed(1).replace(/\.0$/, '')}M`
;
if
(
abs
>=
1_000
)
return
`${(value / 1_000).toFixed(1).replace(/\.0$/, '')}K`
;
return
`${value}`
;
}
;
export
function
Demo
(
)
{
return
(
<FunnelChart
title
=
"SaaS trial-to-paid conversion"
subtitle
=
"Retention from sign-up to paid"
maxWidth
=
{
520
}
height
=
{
440
}
series
=
{
TRIAL_CONVERSION
}
layout
=
{
{
shape
:
'trapezoid'
,
gap
:
8
,
align
:
'center'
,
showConversion
:
false
,
connectors
:
{
show
:
false
}
,
}
}
valueFormatter
=
{
(
value
)
=>
compact
(
value
)
}
legend
=
{
{
show
:
false
}
}
tooltip
=
{
{
show
:
true
,
formatter
:
(
step
)
=>
{
const
index
=
TRIAL_CONVERSION
.
steps
.
findIndex
(
(
candidate
)
=>
candidate
.
label
===
step
.
label
)
;
const
previous
=
index
>
0
?
TRIAL_CONVERSION
.
steps
[
index
-
1
]
:
undefined
;
const
dropValue
=
previous
?
previous
.
value
-
step
.
value
:
0
;
const
dropRate
=
previous
&&
previous
.
value
>
0
?
(
dropValue
/
previous
.
value
)
*
100
:
0
;
const
reason
=
(
step
.
meta as
TrialMeta
|
undefined
)
?
.
dropReason
;
return
[
`${step.label}`
,
`${step.value.toLocaleString()} accounts`
,
previous
?
`Drop: ${dropValue.toLocaleString()} (${dropRate.toFixed(1)}%)`
:
'Starting cohort'
,
reason
?
`Top reason: ${reason}`
:
undefined
,
]
.
filter
(
Boolean
)
.
join
(
'\n'
)
;
}
,
}
}
/>
)
;
}
</>
react-ui-library
100+ accessible, themeable components that work seamlessly across iOS, Android, and Web.
A Platform Blocks product.
Quick Links
Resources