Range Chart
A chart of what lies between two values at each place: the coldest and the warmest of a day, the least and the most a price went to, the hours a shift starts and ends at.
import com.pantrixui.components.chart.range.PantrixRangeChart
- A band or bars.
RangeKind.Areadraws the range as a band that goes from place to place: two lines, and a tint of the series' colour between them.RangeKind.Barputs a bar at each place, from the lower value to the higher; both ends of the bar are round, because it stands on nothing. - The band's look.
lines = falsedoes not draw the two lines, and the band stays as its tint alone;dashdraws the lines dashed or dotted,markerputs a mark on each point, andfillAlphasays how much of the series' colour the tint between them takes (about a sixth where it is not given). A bar reads none of these. - Zero. Nothing of a range stands on zero: the value axis does not start at zero unless the axis says so, and is set out around the range's values. What is read is how far apart the two values are and where they lie.
- A missing value. A row with no lower or no higher value has neither: the band stops before it and starts again after it. A place left by itself is a line from its lower value to its higher.
- Value labels.
labelswrites the two values at the two ends of the range: the higher over it, the lower under it. - Horizontal.
horizontal = truesets the places down the side of the chart, and the ranges go along it: like the shifts of a timetable. The values are written under the chart. - With other series. A chart that wants a line over the band (a forecast within its bounds), or two ranges in one chart, is a
PantrixChart: it takes aRangeSeriesfor the range and aLineSeriesfor the line. - Automation. The chart is tagged as the
PantrixChartit is:Chart_<tagKey>. Its one series has the keyrange.
Examples
The lowest and the highest of the day
At its plainest: the rows, the x that gives a row's place, the lower value (low), the higher value (high) and the range's name. The band's two lines go smoothly (curve).
Move the pointer over a day, or touch the day: the band's two ends are marked and the tooltip writes the two values together. The axis does not start at zero: nothing of a range stands on zero.
PantrixRangeChart(
data = WEATHER,
x = { it.day },
low = { it.coldest },
high = { it.warmest },
name = "Temperature",
curve = ChartCurve.Smooth,
yAxis = ChartAxis(format = { "${it.toInt()}°" }),
description = "The lowest and the highest temperature by day of the week",
tagKey = "plain"
)
private data class Weather(
val day: String,
val coldest: Int,
val warmest: Int
)
private val WEATHER = listOf(
Weather("Mon", coldest = 9, warmest = 17),
Weather("Tue", coldest = 11, warmest = 19),
Weather("Wed", coldest = 8, warmest = 15),
Weather("Thu", coldest = 6, warmest = 14),
Weather("Fri", coldest = 10, warmest = 21),
Weather("Sat", coldest = 13, warmest = 24),
Weather("Sun", coldest = 12, warmest = 22)
)
A band or bars
The same range is drawn two ways. Turn the switch: the band becomes a bar for each day, from the lower value to the higher (RangeKind.Bar). Both ends of a bar are round, and each day has a band of the chart's width.
A band suits what goes on from place to place: a temperature that changes day by day. Bars suit ranges that are read one by one.
var bars by remember { mutableStateOf(false) }
PantrixSwitchButton(
checked = bars,
onCheckedChange = { bars = it },
label = "As bars",
tagKey = "range_bars"
)
PantrixRangeChart(
data = WEATHER,
x = { it.day },
low = { it.coldest },
high = { it.warmest },
name = "Temperature",
kind = if (bars) RangeKind.Bar else RangeKind.Area,
yAxis = ChartAxis(format = { "${it.toInt()}°" }),
description = "The lowest and the highest temperature by day of the week",
tagKey = "kinds"
)
private data class Weather(
val day: String,
val coldest: Int,
val warmest: Int
)
private val WEATHER = listOf(
Weather("Mon", coldest = 9, warmest = 17),
Weather("Tue", coldest = 11, warmest = 19),
Weather("Wed", coldest = 8, warmest = 15),
Weather("Thu", coldest = 6, warmest = 14),
Weather("Fri", coldest = 10, warmest = 21),
Weather("Sat", coldest = 13, warmest = 24),
Weather("Sun", coldest = 12, warmest = 22)
)
The values at its two ends
The hours each team's shift starts and ends, a bar for each team. The two values are written at the bar's two ends (labels): the end over it, the start under it. The hours are written the same way on the axis and in the labels (format). The axis is marked every six hours of the day (values): round steps would have marked it every five.
The line shows the lunch break (marks): which shift takes it in is read.
val hour: (Double) -> String = { "${it.toInt()}:00" }
PantrixRangeChart(
data = SHIFT_HOURS,
x = { it.team },
low = { it.start },
high = { it.end },
name = "Shift",
kind = RangeKind.Bar,
labels = ChartDataLabels(format = hour),
yAxis = ChartAxis(values = listOf(0.0, 6.0, 12.0, 18.0, 24.0), format = hour),
marks = listOf(ChartReferenceLine(y = 12, label = "Lunch break")),
description = "The hour each team's shift starts and ends at",
tagKey = "labels"
)
private data class ShiftHours(
val team: String,
val start: Int,
val end: Int
)
private val SHIFT_HOURS = listOf(
ShiftHours("Warehouse", start = 6, end = 14),
ShiftHours("Store", start = 9, end = 21),
ShiftHours("Support", start = 8, end = 18),
ShiftHours("Evening", start = 14, end = 22)
)
Shifts across
The same shifts, horizontal (horizontal = true): the teams are set down the side of the chart, and each shift goes from the left, from the hour it starts at to the hour it ends at, as on a timetable. The hours are written under the chart; the lunch break's line cuts the chart from top to bottom.
val hour: (Double) -> String = { "${it.toInt()}:00" }
PantrixRangeChart(
data = SHIFT_HOURS,
x = { it.team },
low = { it.start },
high = { it.end },
name = "Shift",
kind = RangeKind.Bar,
horizontal = true,
labels = ChartDataLabels(format = hour),
yAxis = ChartAxis(values = listOf(0.0, 6.0, 12.0, 18.0, 24.0), format = hour),
marks = listOf(ChartReferenceLine(y = 12, label = "Lunch break")),
description = "The hours each team's shift starts and ends at, across",
tagKey = "horizontal_shifts"
)
private data class ShiftHours(
val team: String,
val start: Int,
val end: Int
)
private val SHIFT_HOURS = listOf(
ShiftHours("Warehouse", start = 6, end = 14),
ShiftHours("Store", start = 9, end = 21),
ShiftHours("Support", start = 8, end = 18),
ShiftHours("Evening", start = 14, end = 22)
)
Dashed lines and marks
The band's look comes from the series itself: its two lines dashed (dash), a mark on each of its points (marker), the tint between them darker than usual (fillAlpha). Turn the switch off (lines = false): the lines go, and the band stays with its tint and its marks.
var lines by remember { mutableStateOf(true) }
PantrixSwitchButton(
checked = lines,
onCheckedChange = { lines = it },
label = "The band's lines",
tagKey = "range_lines"
)
PantrixRangeChart(
data = OUTLOOK,
x = { it.month },
low = { it.least },
high = { it.most },
name = "Expected range",
lines = lines,
dash = ChartDash.Dashed,
marker = ChartMarker.Circle,
fillAlpha = 0.28f,
description = "The expected range of orders by month, with dashed lines and marks",
tagKey = "look"
)
private data class Outlook(
val month: String,
val expected: Int,
val least: Int,
val most: Int
)
private val OUTLOOK = listOf(
Outlook("Jul", expected = 420, least = 400, most = 440),
Outlook("Aug", expected = 450, least = 415, most = 485),
Outlook("Sep", expected = 470, least = 420, most = 520),
Outlook("Oct", expected = 510, least = 440, most = 580),
Outlook("Nov", expected = 540, least = 450, most = 630),
Outlook("Dec", expected = 600, least = 480, most = 720)
)
A forecast and its bounds
A chart that wants a line over the band is a PantrixChart: the bounds the forecast is trusted within are a RangeSeries, and the forecast itself a LineSeries given after it. A series given later is drawn over the one before it.
The tooltip writes the two series together: the bounds as a range, the forecast as a single value. In the legend the range is a patch and the forecast a dashed line.
PantrixChart(
series = listOf(
RangeSeries(
key = "bounds",
name = "Expected range",
data = OUTLOOK,
x = { it.month },
low = { it.least },
high = { it.most },
curve = ChartCurve.Smooth
),
LineSeries(
key = "expected",
name = "Forecast",
data = OUTLOOK,
x = { it.month },
y = { it.expected },
curve = ChartCurve.Smooth,
dash = ChartDash.Dashed
)
),
description = "Order forecast and its expected range by month",
tagKey = "with_line"
)
private data class Outlook(
val month: String,
val expected: Int,
val least: Int,
val most: Int
)
private val OUTLOOK = listOf(
Outlook("Jul", expected = 420, least = 400, most = 440),
Outlook("Aug", expected = 450, least = 415, most = 485),
Outlook("Sep", expected = 470, least = 420, most = 520),
Outlook("Oct", expected = 510, least = 440, most = 580),
Outlook("Nov", expected = 540, least = 450, most = 630),
Outlook("Dec", expected = 600, least = 480, most = 720)
)
Pressing a range
A press is handed both values of the range: the point's value is the higher and its from the lower; text carries the two as the chart writes them. Press a day: the line below writes that day's range and how wide it is.
var pressed by remember { mutableStateOf<String?>(null) }
PantrixRangeChart(
data = WEATHER,
x = { it.day },
low = { it.coldest },
high = { it.warmest },
name = "Temperature",
kind = RangeKind.Bar,
onSelect = { point ->
val wide = point.value - (point.from ?: point.value)
pressed = "${point.label}: ${point.text}, ${wide.toInt()} degrees apart"
},
description = "The lowest and the highest temperature by day of the week",
tagKey = "press"
)
PantrixText(text = pressed ?: "Press a day")
private data class Weather(
val day: String,
val coldest: Int,
val warmest: Int
)
private val WEATHER = listOf(
Weather("Mon", coldest = 9, warmest = 17),
Weather("Tue", coldest = 11, warmest = 19),
Weather("Wed", coldest = 8, warmest = 15),
Weather("Thu", coldest = 6, warmest = 14),
Weather("Fri", coldest = 10, warmest = 21),
Weather("Sat", coldest = 13, warmest = 24),
Weather("Sun", coldest = 12, warmest = 22)
)
Missing values
Days with no values: Wednesday and Saturday were not measured. The band stops before a day with no values and starts again after it; a day with no neighbour on either side is a line from its lower value to its higher.
PantrixRangeChart(
data = WEATHER,
x = { it.day },
low = { row -> row.coldest.takeIf { row.day !in UNMEASURED } },
high = { row -> row.warmest.takeIf { row.day !in UNMEASURED } },
name = "Temperature",
description = "Temperature by day of the week, with two days not measured",
tagKey = "missing"
)
private data class Weather(
val day: String,
val coldest: Int,
val warmest: Int
)
private val WEATHER = listOf(
Weather("Mon", coldest = 9, warmest = 17),
Weather("Tue", coldest = 11, warmest = 19),
Weather("Wed", coldest = 8, warmest = 15),
Weather("Thu", coldest = 6, warmest = 14),
Weather("Fri", coldest = 10, warmest = 21),
Weather("Sat", coldest = 13, warmest = 24),
Weather("Sun", coldest = 12, warmest = 22)
)
private val UNMEASURED = setOf("Wed", "Sat")
API
data
A chart of what lies between two values at each place: the coldest and the warmest of a day, the least and the most a price went to, the hours a shift starts and ends at.
| Parameter | Type | Default | What it is |
|---|---|---|---|
data | List<T> | the rows. | |
x | (T) -> Any | where a row stands along the chart: a name, a day, anything that is equal to itself. | |
low | (T) -> Number? | a row's lower value, or null where it has none. | |
high | (T) -> Number? | a row's higher value, or null where it has none. | |
name | String | what the two values are of: what the tooltip calls them, and a screen reader the chart where it has no description. | |
modifier | Modifier | Modifier | the chart's modifier. A height here is the chart's. |
kind | RangeKind | RangeKind.Area | whether the range is a band from place to place or a bar at each place. |
curve | ChartCurve | ChartCurve.Linear | how the two lines of a band go from a point to the next. A bar has no line. |
lines | Boolean | true | whether the two lines of a band are drawn, or the tint between them alone. |
dash | ChartDash? | null | whether the two lines are drawn whole, in dashes or in dots. null draws them whole, and dashed where the chart's patterns are on (ChartStyle.patterns). |
marker | ChartMarker | ChartMarker.None | the mark drawn on each point of the two lines. None by default. |
fillAlpha | Float | 0.16f | how much of the range's colour the tint between the lines has, from none to all of it. Left out, about a sixth. |
pattern | ChartPattern? | null | the texture the band or the bars are drawn with besides their colour. null draws none, and where the chart's patterns are on, none too: a chart of one range has nothing to tell it from. |
color | Color | Color.Unspecified | the colour the range is drawn in. Left out, it is the first of the chart's colours. |
horizontal | Boolean | false | whether the places go down the chart's side and the range along it, and not along its foot and up. |
labels | ChartDataLabels? | null | the two values written at the range's ends, the higher over it and the lower under it, where they have room. null writes none. |
xAxis | ChartAxis? | ChartAxis() | the axis of the places, under the chart, or beside it where it is horizontal. null draws nothing of it. |
yAxis | ChartAxis? | ChartAxis() | the axis of the values, beside the chart, or under it where it is horizontal. null draws nothing of it. |
marks | List<ChartMark> | emptyList() | the lines and the bands drawn in the plot besides the range: a limit, a stretch of places. |
tooltip | ChartTooltip? | ChartTooltip() | what is shown for the place that is pointed at. null shows none. |
zoom | ChartZoom? | null | what lets the reader look closer at a part of the places and move along the rest. null, which it is unless the chart is given one, shows all the places and no more. |
export | ChartExport? | null | what lets the reader take the chart out of the app as a file: a picture, or a drawing that is sharp at any size. null, which it is unless the chart is given one, and the chart has no such button. |
empty | (@Composable () -> Unit)? | null | what the chart shows in its plot 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. A file the chart is exported as says them. |
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. |
onSelect | ((ChartPoint) -> Unit)? | null | called with the range that is pressed. |
style | ChartStyle? | null | this chart's own geometry. |
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 tagged as the PantrixChart it is, Chart_<tagKey>, and its parts and events are that chart's; its one series has the key range. |
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
- Tooltip and screen reader. The two values are written with a dash between them: "9 – 17". A press is handed the higher value as the point's
valueand the lower as itsfrom.