Funnel Chart
A funnel: the stages of something that fewer and fewer get through, one under another, each as wide as the number that reached it. It shows where the others were lost: the visitors who signed up, the sign-ups who tried, the trials who paid.
import com.pantrixui.components.chart.funnel.PantrixFunnelChart
- Shape. The largest stage is as wide as the chart; each stage narrows to the width of the one after it, and the stages make one funnel. The last keeps its width. A stage larger than the one before it does not narrow but widens: the chart draws what it is given.
shape = FunnelShape.Barsdraws each stage as a bar as wide as its value;horizontallays the stages side by side from where lines start, each as tall as its value. - Words. A stage's name and value are written in it; beside the value stands its share: of the first stage, of the one before, or both (
rate). A stage too narrow for its words has its name and value beside it, under it in a horizontal funnel; where there is room for neither, only the name is left.labels = nullwrites nothing. - The stage pointed at. The stage whose row the pointer or a finger is on is ringed, and the tooltip writes its name, its value and its share. On the keyboard the arrows go from stage to stage: up and down, right and left in a horizontal funnel.
- Colours. Each stage takes one of the theme's chart colours, by its place; the
colorfield ofFunnelStagegives a stage its own. - Export.
exportputs a button over the chart: the chart is made, as it is on the screen and without the part that is pointed at or its tooltip, into a PNG, an SVG or a PDF file and handed to the app. - Automation. The chart is tagged
Funnel_<tagKey>, and a stageFunnel_<tagKey>.Stage_<number>. A press on a stage is reported as aselectevent with the stage's key.
Examples
From a visit to a payment
At its plainest: the stages, given in their order. Each stage has a key, a name and a value.
Move the pointer over a stage's row, or touch the stage: the stage is ringed and the tooltip writes its value, with its share of the first stage.
PantrixFunnelChart(
stages = FUNNEL_STEPS,
description = "The stages a visit goes through until it comes to a payment",
tagKey = "plain"
)
private val FUNNEL_STEPS = listOf(
FunnelStage("visits", "Visits", 3000),
FunnelStage("signups", "Sign-ups", 1240),
FunnelStage("trials", "Trials", 520),
FunnelStage("paid", "Paid", 310)
)
Shape and direction
The stages' shape and direction. "Bars" draws each stage as a bar as wide as its value (shape = FunnelShape.Bars); turn the switch: the stages stand side by side (horizontal = true), each as tall as its value. The arrows on the keyboard then go right and left.
var shape by remember { mutableStateOf(FunnelShape.Funnel) }
var horizontal by remember { mutableStateOf(false) }
PantrixSegmentButton(
options = FUNNEL_SHAPES,
selected = shape,
onSelect = { shape = it },
fullWidth = false,
tagKey = "funnel_shape"
)
PantrixSwitchButton(
checked = horizontal,
onCheckedChange = { horizontal = it },
label = "Horizontal",
tagKey = "funnel_horizontal"
)
PantrixFunnelChart(
stages = FUNNEL_STEPS,
shape = shape,
horizontal = horizontal,
description = "The stages a visit goes through until it comes to a payment",
tagKey = "shape"
)
private val FUNNEL_STEPS = listOf(
FunnelStage("visits", "Visits", 3000),
FunnelStage("signups", "Sign-ups", 1240),
FunnelStage("trials", "Trials", 520),
FunnelStage("paid", "Paid", 310)
)
private val FUNNEL_SHAPES = listOf(
SegmentOption(value = FunnelShape.Funnel, label = "Funnel", tagKey = "funnel"),
SegmentOption(value = FunnelShape.Bars, label = "Bars", tagKey = "bars")
)
Conversion rates
Conversion rates (rate). "Of the first" writes each stage's share of the first stage, "Of the previous" how many of those who reached the stage before went on to this one, "Both" the two together. On the stage the shares stand bare, two of them apart by a dot; the tooltip and a screen reader say what each share is of.
var rate by remember { mutableStateOf(FunnelRate.Previous) }
PantrixSegmentButton(
options = FUNNEL_RATES,
selected = rate,
onSelect = { rate = it },
fullWidth = false,
tagKey = "funnel_rate"
)
PantrixFunnelChart(
stages = FUNNEL_STEPS,
rate = rate,
description = "The stages of a visit up to a payment, with conversion rates",
tagKey = "rates"
)
private val FUNNEL_STEPS = listOf(
FunnelStage("visits", "Visits", 3000),
FunnelStage("signups", "Sign-ups", 1240),
FunnelStage("trials", "Trials", 520),
FunnelStage("paid", "Paid", 310)
)
private val FUNNEL_RATES = listOf(
SegmentOption(value = FunnelRate.First, label = "Of the first", tagKey = "first"),
SegmentOption(value = FunnelRate.Previous, label = "Of the previous", tagKey = "previous"),
SegmentOption(value = FunnelRate.Both, label = "Both", tagKey = "both")
)
Values written short
Large numbers are written short (the format of ChartDataLabels): they leave room in the stages. Turn the switch: the words go away (labels = null) and the values stay in the tooltip and for the screen reader alone.
var written by remember { mutableStateOf(true) }
val strings = PantrixTheme.strings
PantrixSwitchButton(
checked = written,
onCheckedChange = { written = it },
label = "Words",
tagKey = "funnel_labels"
)
PantrixFunnelChart(
stages = CAMPAIGN_STEPS,
labels = if (written) ChartDataLabels(format = { strings.compact(it) }) else null,
description = "The stages of a campaign, from being shown to a sale",
tagKey = "labels"
)
private val CAMPAIGN_STEPS = listOf(
FunnelStage("shown", "Shown", 1_250_000),
FunnelStage("clicks", "Clicks", 410_000),
FunnelStage("carts", "Carts", 96_000),
FunnelStage("orders", "Orders", 38_500),
FunnelStage("repeat", "Bought again", 9_200)
)
Pressing a stage
A press is handed the stage: its key is in its series, its name in its name, its value in its value, and what the chart writes in its text. Press a stage: the line below writes what was pressed.
var pressed by remember { mutableStateOf<String?>(null) }
PantrixFunnelChart(
stages = FUNNEL_STEPS,
onSelect = { pressed = "${it.name}: ${it.text}" },
description = "The stages a visit goes through until it comes to a payment",
tagKey = "press"
)
PantrixText(text = pressed ?: "Press a stage")
private val FUNNEL_STEPS = listOf(
FunnelStage("visits", "Visits", 3000),
FunnelStage("signups", "Sign-ups", 1240),
FunnelStage("trials", "Trials", 520),
FunnelStage("paid", "Paid", 310)
)
A stage that widens
A stage can be larger than the one before it: more people called than leads that came in. The chart changes neither the order nor the number; that stage widens instead of narrowing, and its share goes over a hundred per cent.
The last stage is too narrow for its words, so its name and its value stand beside it.
PantrixFunnelChart(
stages = listOf(
FunnelStage("leads", "Leads", 800),
FunnelStage("calls", "Calls", 950),
FunnelStage("offers", "Offers", 300),
FunnelStage("won", "Sales", 40)
),
description = "From a lead to a sale: more calls than leads",
tagKey = "widening"
)
Your own colours
The stages' own colours: a role of the theme, lighter from stage to stage. Each FunnelStage carries its own colour; a stage given none takes the chart colour of its place.
val tone = PantrixTheme.colors.primary.color
PantrixFunnelChart(
stages = FUNNEL_STEPS.mapIndexed { place, stage ->
FunnelStage(stage.key, stage.name, stage.value, tone.copy(alpha = 1f - place * 0.2f))
},
description = "The stages a visit goes through until it comes to a payment, in one colour",
tagKey = "own_colors"
)
private val FUNNEL_STEPS = listOf(
FunnelStage("visits", "Visits", 3000),
FunnelStage("signups", "Sign-ups", 1240),
FunnelStage("trials", "Trials", 520),
FunnelStage("paid", "Paid", 310)
)
Export
export = ChartExport(...) puts an export button over the chart: press it and choose the kind (PNG, SVG or PDF). The chart makes the file and hands it to onExport; below, its name and size show, and a PNG's picture, and "Save the file" hands it over in the platform's way.
The file has the stages, their values and their rates.
var file by remember { mutableStateOf<ChartExportFile?>(null) }
PantrixFunnelChart(
stages = FUNNEL_STEPS,
description = "The stages a visit goes through until it comes to a payment",
tagKey = "exported",
export = ChartExport(fileName = "visit-to-payment") { file = it }
)
ExportedFile(file, tagKey = "exported")
private val FUNNEL_STEPS = listOf(
FunnelStage("visits", "Visits", 3000),
FunnelStage("signups", "Sign-ups", 1240),
FunnelStage("trials", "Trials", 520),
FunnelStage("paid", "Paid", 310)
)
API
stages
A funnel: the stages of something that fewer and fewer get through, one under another, each as wide as the number that reached it. It shows where the others were lost: the visitors who signed up, the sign-ups who tried, the trials who paid.
| Parameter | Type | Default | What it is |
|---|---|---|---|
stages | List<FunnelStage> | the stages, in the order they follow one another. Their keys tell them apart, and two with one key are refused. | |
modifier | Modifier | Modifier | the chart's modifier. A height here is the chart's. |
labels | ChartDataLabels? | ChartDataLabels() | what is written at the stages: each stage's name and its value, written as format says or in the theme's language where it says nothing, with its shares. null writes nothing at them. |
tooltip | ChartTooltip? | ChartTooltip() | what is shown for the stage that is pointed at: its name and its value with its shares. With both shares the value stands alone on the stage's row and each share on a row of its own under it, which a tooltip of the caller's own is handed as a point of the stage named by the share, with the share as its value and no text. A stage has no place to share and no line through it: shared and crosshair are not read. null shows none. |
empty | (@Composable () -> Unit)? | null | what the chart shows while it has no value to draw, in the place of the theme's words for it ("No data"), which a screen reader is then not told either. null writes the words. |
state | ChartState | rememberChartState() | what the reader points at: the chart's own where none is given. |
description | String? | null | what the chart shows, in a few words, for a screen reader: the first thing it says of the chart. Left out, the chart is announced by the names of its stages. |
onSelect | ((ChartPoint) -> Unit)? | null | called with the stage that is pressed: its key is its series and its x, its name its name and its label, and its text its value with its shares after it. |
style | ChartStyle? | null | this chart's own geometry: the fields set here, with the theme's for the rest. Two stages are a sliceGap apart. |
colors | ChartColors? | null | this chart's own colours, where they are not the theme's. |
tagKey | String? | null | the chart's automation key: the chart is Funnel_<tagKey>, its stages together Funnel_<tagKey>.Plot, a stage Funnel_<tagKey>.Stage_<number>, counted from one, and the tooltip Funnel_<tagKey>.Tooltip. A press on a stage is reported as a Funnel select event with the stage's key. Nothing of the values is reported. |
shape | FunnelShape | FunnelShape.Funnel | how the stages are drawn: as one funnel, or as bars. |
horizontal | Boolean | false | whether the stages stand side by side, from where lines start, and not one under another. |
rate | FunnelRate | FunnelRate.First | which shares a stage's value is written with: of the first stage, of the stage before it, or both. At a stage they stand in brackets after the value, two of them apart by a dot; to a tooltip and a screen reader one of the stage before it, and each of two, says which it is (PantrixStrings.shareOfFirst and shareOfPrevious). |
export | ChartExport? | null | what lets the reader take the chart out of the app as a PNG, an SVG or a PDF: a button over the chart makes the file of it as it is and hands it over (see ChartExport). null, the chart has no such button. |
ChartStyle
A chart's geometry: how tall it is where nothing says, how thick its lines and how large its marks are, and the room between its parts; and whether its fills have textures besides their colours.
| Parameter | Type | Default | What it is |
|---|---|---|---|
height | Dp | Dp.Unspecified | How tall a chart's plot is, with its axes, where the chart's modifier gives it no height. A legend above or under the plot adds its own. |
lineWidth | Dp | Dp.Unspecified | The width of a series' line. |
markerSize | Dp | Dp.Unspecified | The width and height of the mark on a point. |
selectedMarkerSize | Dp | Dp.Unspecified | The same of the mark on the point that is pointed at. |
gridWidth | Dp | Dp.Unspecified | The width of the grid's lines, of the lines along the axes and of the line that marks the place pointed at. Zero draws none. |
labelGap | Dp | Dp.Unspecified | Between the plot and what is written beside it and under it, and between that and an axis' title. |
tickSpacing | Dp | Dp.Unspecified | The least room between two values marked along the axis of values: what decides how many are marked where the axis does not say. |
legendGap | Dp | Dp.Unspecified | Between the legend and the chart it stands beside. |
legendItemGap | Dp | Dp.Unspecified | Between one entry of the legend and the next. |
swatchSize | Dp | Dp.Unspecified | How wide the sample of a series is in the legend and in the tooltip: a short run of its line, or a patch of its fill. |
swatchGap | Dp | Dp.Unspecified | Between that sample and the name after it. |
tooltipShape | ShapeSpec | ShapeSpec.Unspecified | The tooltip's corners: a step of the brand's shape scale, or a fixed shape. |
tooltipHorizontalPadding | Dp | Dp.Unspecified | Between the tooltip's sides and what it holds. |
tooltipVerticalPadding | Dp | Dp.Unspecified | Between its top and bottom and what it holds, and between its heading and rows. |
tooltipGap | Dp | Dp.Unspecified | Between the place pointed at and the tooltip beside it. |
tooltipElevation | Dp | Dp.Unspecified | The tooltip's shadow. |
barGap | Dp | Dp.Unspecified | Between two bars that stand side by side at a place. Bars too thin for it stand closer. |
barMaxWidth | Dp | Dp.Unspecified | The widest a bar is, however much room its place has: a chart of three places is not three slabs. |
barRadius | Dp | Dp.Unspecified | How round the corners of a bar's end are, the end away from nought. |
sliceGap | Dp | Dp.Unspecified | Between two slices of a pie that lie side by side, the same from the pie's middle to its rim. |
selectedSliceOffset | Dp | Dp.Unspecified | How far the slice of a pie that is pointed at stands out past the pie's rim. The pie keeps this much room about itself, so that the slice has where to stand. |
gaugeWidth | Dp | Dp.Unspecified | How thick the arc of a gauge is. An arc is no thicker than half its radius. |
meterHeight | Dp | Dp.Unspecified | How tall the bar of a meter group is. |
trackerHeight | Dp | Dp.Unspecified | How tall the blocks of a status tracker are. |
bubbleMinSize | Dp | Dp.Unspecified | How far across the smallest bubble of a scatter chart is: a point whose size is nothing, or too little to be seen as an area, is still a point. |
bubbleMaxSize | Dp | Dp.Unspecified | How far across its largest bubble is: the one whose size is the most of the chart's. |
navigatorHeight | Dp | Dp.Unspecified | How tall the strip under a chart's plot is, on which the part of the chart that is in view is marked and moved, in a chart whose zoom has one: how wide, beside a plot whose places go down its side. |
barBandShare | Float | Float.NaN | How much of a place's band its bars take, more than nought and one at the most: the rest is the room between one place's bars and the next's. Bars are no wider than barMaxWidth all the same. |
markLineWidth | Dp | Dp.Unspecified | The width of a mark's line: a target, a limit, a day. Zero draws none, and the mark's words are written all the same. |
markBandAlpha | Float | Float.NaN | How much of its colour a mark's band is drawn in, from nought to one: a band lies behind the series, and is a tint. |
patterns | Boolean? | null | Whether the fills of a chart's series, slices and tiles are drawn with textures besides their colours, and its lines broken in turn (ChartPattern): what tells them apart for a reader who cannot tell the colours apart. Left out, as the system is set: on where it is set to tell things apart without colour, iOS's "Differentiate Without Color" and Android's colour correction, and off otherwise. |
nodeWidth | Dp | Dp.Unspecified | How wide a node of a sankey is. |
nodeGap | Dp | Dp.Unspecified | Between two nodes of a column of a sankey. |
ChartColors
What a chart is painted with, for a chart that does not take the theme's colours: a colour that is set is used, and one that is left out stays the theme's.
| Parameter | Type | Default | What it is |
|---|---|---|---|
series | List<Color> | emptyList() | The colours the series are drawn in, in the order they are handed out, for the series that name none of their own. Empty, they are the theme's chart colours. |
grid | Color | Color.Unspecified | The lines drawn across the chart at the marks of an axis. The theme's divider. |
axis | Color | Color.Unspecified | The line along an axis. The theme's border. |
label | Color | Color.Unspecified | What is written beside the axes: the values, the places and the axes' titles, and the names in the legend. The theme's textSecondary. |
crosshair | Color | Color.Unspecified | The line that marks the place pointed at. The theme's borderHover: stronger than the grid it crosses, and quieter than the series it stands behind. |
tooltipContainer | Color | Color.Unspecified | The tooltip's fill. The theme's surface. |
tooltipBorder | Color | Color.Unspecified | The tooltip's edge. The theme's border. |
tooltipText | Color | Color.Unspecified | The tooltip's heading and its values. The theme's textPrimary. |
tooltipLabel | Color | Color.Unspecified | The names of the series in the tooltip. The theme's textSecondary. |
rising | Color | Color.Unspecified | What a value that goes up is drawn in: a step of a waterfall that adds. The theme's success. |
falling | Color | Color.Unspecified | What a value that goes down is drawn in: a step of a waterfall that takes away. The theme's danger. |
Accessibility
- Screen reader. First the chart's description, then each stage in its order: its name, its value and its shares. A share of the first stage written alone is read bare; otherwise each share is read with what it is a share of.