Funnel
Overview
A funnel chart shows the stages of a process, one bar per stage, each as long as the number of items that reached it: website visits, sign-ups, trials, orders. Bars are centered on the value axis, so the shape narrows like a funnel, and shaded connector regions join each stage to the next. Labels can show each stage's share of the first stage, of the previous stage or of the total. Use it for sales pipelines, conversion and recruiting funnels, or any process where items drop out step by step.
Holochart lays funnels out like bars and draws them with bar's renderer: the bars of a trace are one instanced GPU rect set, the connector regions one batched polygon fill, their edges one line primitive and the labels one batched SDF text set, so zooming or panning only updates a transform.
Pick a different chart when:
- you want each stage's area, not its length, to carry the value, in a pie-like shape without axes: use a funnel area;
- the stages are not a sequence that items drop out of, just categories to compare: use a sorted horizontal bar chart;
- you show how a total is built from gains and losses: use a waterfall chart.
Minimal example
import { createChart } from '@mk7s/holochart';
createChart(document.getElementById('chart')!, {
data: [
{
type: 'funnel',
y: ['Visits', 'Sign-ups', 'Trials', 'Orders'],
x: [4000, 1600, 700, 240],
},
],
});A funnel is horizontal by default: one bar per y stage, first stage on top, each labeled with its value. As in Plotly, the value axis is hidden and the stage axis reversed. The live example labels six sales stages with their value and their share of the first stage (textinfo: 'value+percent initial'):
funnel/basicData format
- Stages and values. A horizontal funnel (the default) takes the stages in
y(usually categories) and one value per stage inx(numbers; typed arrays work). Withorientation: 'v'(the default when onlyyis given) the funnel is vertical: stages inx, values iny. Without stages, bars sit aty0,y0 + dy, … (x0,x0 + dx, … when vertical; defaults 0 and 1). The trace has as many stages as its shorter array. - Missing values. Negative, missing or non-numeric values (
null,NaN) are treated as missing, as in Plotly: the stage has no bar and no connectors, and the others keep their places. - Percentages are computed per trace: percent initial is a stage's value relative to the first stage, percent previous relative to the previous stage with a value (100% for the first), and percent total relative to the sum of the trace's values.
- Axes. The value axis defaults to
visible: false, and a horizontal funnel's stage axis toautorange: 'reversed'(first stage on top), unless a trace of another type uses that axis or you set the axis'rangeorvisibleyourself. Periods (xperiod,yperiodand their0/alignmentcompanions) work as for bars.
Variations
Stacked funnels
Funnel traces that share their stages stack per stage (layout.funnelmode: 'stack', the default), and the whole stack is centered on the value axis. Each trace keeps its own connector regions, labels and percentages; the legend toggles each trace and the stacks re-center:
funnel/stackedLabels, percentages and vertical funnels
textinfo combines label, text, value and the three percentages; when a label shows several percentages, each says which it is (of initial, of previous, of total). On the left, per-stage colors and outlined, tinted connectors; on the right a vertical funnel (orientation: 'v') with labels outside the bars:
funnel/textinfoGrouped and overlaid funnels
funnelmode: 'group' puts the traces of a stage side by side, each bar centered on zero; 'overlay' draws them over each other (lower opacity or put the smaller trace last to see both). funnelgap sets the gap between stages (default 0.2) and funnelgroupgap the gap between the bars of one stage (default 0):
import { createChart } from '@mk7s/holochart';
const stages = ['Applied', 'Screened', 'Interviewed', 'Hired'];
createChart(document.getElementById('chart')!, {
data: [
{ type: 'funnel', name: '2024', y: stages, x: [480, 300, 120, 40] },
{ type: 'funnel', name: '2025', y: stages, x: [620, 350, 160, 55] },
],
layout: { funnelmode: 'group', funnelgap: 0.3, funnelgroupgap: 0.1 },
});funnel/groupedColors from a colorscale
marker.color takes one color per stage, or numbers mapped through marker.colorscale, with a colorbar when marker.showscale is on. With per-stage colors the connector regions default to translucent black, so set connector.fillcolor to match your palette:
import { createChart } from '@mk7s/holochart';
createChart(document.getElementById('chart')!, {
data: [
{
type: 'funnel',
y: ['Seen', 'Clicked', 'Signed up', 'Paid'],
x: [9000, 2600, 800, 190],
textinfo: 'value+percent previous',
marker: { color: [9000, 2600, 800, 190], colorscale: 'Viridis' },
connector: { fillcolor: 'rgba(128, 131, 143, 0.25)' },
},
],
});funnel/colorscaleStyling
- Bars.
marker.color(one color, one per stage, or numbers withmarker.colorscale,cmin/cmaxand a colorbar viashowscale),marker.opacityandmarker.line.color/marker.line.width. Each trace takes the next colorway color by default. - Connectors.
connector.visible(defaulttrue);connector.fillcolor, whose default is the marker color at half its opacity (black at half opacity whenmarker.coloris an array); andconnector.line.color,.widthand.dashfor lines along the slanted edges of each region (default width 0: no lines). Connectors draw under the bars. - Default look. In the default
holocharttemplate, bars are borderless and connector edges, when given a width, use the tick-label gray (#80838f).layout.template: 'plotly-classic'gives Plotly's look (themes and templates). - Labels.
textinfopicks what each bar says, in a fixed order, one per line:label(the stage),text,value,percent initial,percent previousandpercent total. The default is'value', or'text+value'whentextis an array. Percentages in labels are rounded to whole percents.texttemplateoverrides it with%{value},%{percentInitial},%{percentPrevious},%{percentTotal}(shown as percentages without a format, or with one such as%{percentInitial:.1%}),%{label},%{x},%{y},%{text},%{customdata}and%{meta}. - Label placement. Labels are centered by default (
insidetextanchor: 'middle',textangle: 0,textposition: 'auto');'inside','outside'and'none', the label fonts andconstraintextwork as for bars. - Extent and order.
widthandoffset(one number each, in position-axis units),offsetgroupandalignmentgroup, traceopacityandzorder. - Legend. Each trace shows a bar glyph in its marker color.
Interactivity
Hover. Hovering a bar shows the stage's value and the stage, then Plotly's percentage lines with one decimal:
50% of initial,50% of previous,30.8% of total.hoverinfopicks the parts:name,x,y,text,percent initial,percent previousandpercent total.Templates.
hovertemplatetakes%{value},%{percentInitial},%{percentPrevious}and%{percentTotal}(formatted as percentages without a format) besides%{x},%{y},%{text}and%{customdata}:tsimport { createChart } from '@mk7s/holochart'; createChart(document.getElementById('chart')!, { data: [ { type: 'funnel', y: ['Visits', 'Sign-ups', 'Orders'], x: [4000, 1600, 240], hovertemplate: '%{y}: %{x:,}<br>%{percentPrevious} of the stage before<extra></extra>', }, ], });Events.
hoverandclickpoints carryx,y,percentInitial,percentPreviousandpercentTotal(ratios,0.5),curveNumberandpointNumber.Selection. Box and lasso select bars as for bar charts; unselected bars dim.
Legend. A click hides a trace; stacked funnels re-center without it.
3D-native options
Holochart extensions (full bundle), see Extrusion & 2.5D:
depth: extrusion of the stages toward the viewer in px, a percentage of the stage thickness ('70%'), or one number per stage.0(default) draws a flat funnel.bevel.sizeandbevel.segments: rounded front and side edges.material: Plotly's lighting model (default),flat, or a three.js material type.layout.view3d: the plot area in perspective, with hover and click still exact on the stages.
The connector regions and their outlines lie on the plane of the stages' front faces, so the stages read as one continuous funnel; labels sit on the front faces.
funnel/depthPerformance notes
- A funnel draws in a constant number of draw calls whatever its stage count: one instanced rect set for the bars, one batched polygon fill for the connector regions (the fill primitive is loaded on first use), one line primitive for their edges and one batched SDF text set for the labels.
- Geometry is uploaded once per data change; zoom and pan only update transforms.
- Restyling colors and selections re-upload colors only.
- Funnels have few stages: readability, not rendering, is the limit.
Accessibility notes
- Screen readers: the hidden description (see the accessibility guide) reads
Funnel "name": N bars.; its table lists each stage with its value and its percentage of the first stage. - Keyboard: Tab moves into the plot area; the arrow keys along the stage axis then step through the stages (↑ / ↓ in the usual horizontal funnel) and the other two move between traces, each stop showing its hover label. See the keys.
- Color: stages are told apart by position and label, not color, so a single color per trace works well. Label the percentages (
textinfo: 'value+percent initial') so readers don't have to compare bar lengths across the gaps.
Attribute reference
See the funnel attribute reference for every attribute, its type, and its default. funnelmode, funnelgap and funnelgroupgap are in the layout reference.
Related charts
- Funnel area: the same stages as stacked trapezoids whose areas carry the values
- Horizontal bar: ranked categories that are not a process
- Waterfall: how a total is built from gains and losses
- Pie: how a whole splits into parts
Plotly migration notes
- Attribute names and defaults match Plotly's
funnel:x,y,orientation,marker(bar's marker withoutcornerradiusandpattern, as in Plotly),connector(visible,fillcolor,line),text,textinfo,texttemplate,textposition, the label fonts,insidetextanchor,textangle,constraintext,width,offset,offsetgroup,alignmentgroup,hoverinfo,hovertemplate,zorder, and the layout'sfunnelmode,funnelgapandfunnelgroupgap. Funnels never share slots with bars or waterfalls, as in Plotly. - The hidden value axis, the reversed stage axis, the centered stacks, the percentages and the label and hover text follow Plotly.
marker.opacityis applied (Plotly's funnel ignores it).%{percentTotal}intexttemplateis formatted as a percentage like the other two (Plotly's shows the raw ratio).- Not supported yet:
xhoverformat/yhoverformat(bar has none either),texttemplatefallback/hovertemplatefallback.