Heatmap
A heatmap: a grid of cells, each in a shade that says how much its value is. It suits what has two names and a number: the hours of the days of a week, the months of several years, one list set against another. What is much and what is little is seen at a glance, where a table of the same numbers would have to be read.
import com.pantrixui.components.chart.heatmap.PantrixHeatmap
- Scale.
HeatmapScalesays how a value becomes a shade: its two ends (min,max; left out, the least and the most of the values), how many shades it has (steps; at nought each value has its own) and its colour (color). With one colour, a shade is a share of the colour on what the chart stands on: it reads in a light theme and a dark one. Givenlow, the scale goes from the least's colourlowtocolor; the shades between are the two colours mixed in Oklab, each step changing as much as the one before. - The value in a cell. With
labelseach cell has its value written in it, in the one of black and white that reads better on its shade; it is not written in a cell it has no room in. - Scale bar.
legendsays where the scale of shades stands: the least and the most of the values are written at its ends. - Names.
xAxisis the names of the columns andyAxisof the rows: whether they are written (labels) and how (label). A name that has no room is left out by a rule. - The cell pointed at. The cell under the pointer, or the one that is tapped, is ringed, and the tooltip writes its column, its row and its value.
- State and pressing. The cell that is pointed at is kept in
ChartState, as a selection whosexis the datum's place in the list.onSelectgives the cell that is pressed:labelis its column,nameits row,valueits value. - Direction. In a language read from right to left the columns start from the right, and the rows' names are written at the right.
- 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.
Heatmap_<tagKey>; the cells are.Plot, a cell.Cell_<number>, the scale.Legend, the tooltip.Tooltip.
Examples
A week by the hour
A shop's visitors over a week, by the day and by the part of the day. A dark cell is a busy hour.
Move the pointer over a cell or tap it: the tooltip writes the visitors of that hour of that day.
PantrixHeatmap(
data = weekVisits(),
x = { it.hours },
y = { it.day },
value = { it.visits },
description = "Visits by day and hour",
tagKey = "plain"
)
internal fun weekVisits(): List<SlotVisits> {
val days = listOf("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")
val hours = listOf("09", "11", "13", "15", "17", "19")
val visits = listOf(
listOf(12, 18, 34, 22, 41, 30),
listOf(10, 16, 31, 20, 38, 28),
listOf(14, 21, 36, 25, 44, 33),
listOf(11, 19, 35, 27, 47, 36),
listOf(16, 24, 42, 31, 58, 52),
listOf(28, 45, 61, 66, 72, 57),
listOf(22, 39, 54, 60, 49, 26)
)
return days.flatMapIndexed { day, name -> hours.mapIndexed { slot, at -> SlotVisits(name, at, visits[day][slot]) } }
}
internal class SlotVisits(
val day: String,
val hours: String,
val visits: Int
)
Five shades, with the values written
Exam scores, out of a hundred. The scale has five shades (steps): each twenty points are a shade. The values are written in the cells (labels), and the scale stands beside the chart. The cell of one who sat no exam is blank.
PantrixHeatmap(
data = examScores(),
x = { it.subject },
y = { it.student },
value = { it.score },
scale = HeatmapScale(min = 0.0, max = 100.0, steps = 5),
labels = ChartDataLabels(),
legend = ChartLegend(position = ChartLegendPosition.End),
description = "Students' scores by subject",
tagKey = "steps"
)
internal fun examScores(): List<ExamScore> {
val subjects = listOf("Maths", "Physics", "Chemistry", "History", "Language")
val scores = mapOf(
"Ada" to listOf(92, 85, 78, 64, 88),
"Lin" to listOf(71, 58, null, 90, 95),
"Can" to listOf(45, 62, 70, 81, 39),
"Ece" to listOf(84, 97, 91, 55, 73)
)
return scores.flatMap { (student, row) ->
subjects.mapIndexed { at, subject -> ExamScore(student, subject, row[at]) }
}
}
internal class ExamScore(
val student: String,
val subject: String,
val score: Int?
)
A scale of two colours
A scale of two colours, like a temperature: the fewest visits in the low colour (the warning colour's container), the most in color (the danger colour); the shades between are the two colours mixed in Oklab. The colours come from the theme, so the scale reads in a light theme and a dark one.
val colors = PantrixTheme.colors
PantrixHeatmap(
data = weekVisits(),
x = { it.hours },
y = { it.day },
value = { it.visits },
scale = HeatmapScale(low = colors.warning.container, color = colors.danger.color),
labels = ChartDataLabels(),
description = "Visits by day and hour",
tagKey = "two_colours"
)
internal fun weekVisits(): List<SlotVisits> {
val days = listOf("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")
val hours = listOf("09", "11", "13", "15", "17", "19")
val visits = listOf(
listOf(12, 18, 34, 22, 41, 30),
listOf(10, 16, 31, 20, 38, 28),
listOf(14, 21, 36, 25, 44, 33),
listOf(11, 19, 35, 27, 47, 36),
listOf(16, 24, 42, 31, 58, 52),
listOf(28, 45, 61, 66, 72, 57),
listOf(22, 39, 54, 60, 49, 26)
)
return days.flatMapIndexed { day, name -> hours.mapIndexed { slot, at -> SlotVisits(name, at, visits[day][slot]) } }
}
internal class SlotVisits(
val day: String,
val hours: String,
val visits: Int
)
Pressing a cell
onSelect gives the cell that is pressed: press a cell, or go to one with the keyboard and press Enter, and the line under the card writes what was pressed. The scale has a colour of its own (HeatmapScale.color), and there is no scale bar (legend = null).
val report = LocalExampleEvents.current
PantrixHeatmap(
data = weekVisits(),
x = { it.hours },
y = { it.day },
value = { it.visits },
scale = HeatmapScale(color = PantrixTheme.colors.success.color),
legend = null,
onSelect = { cell -> report("${cell.name}, ${cell.label}: ${cell.text}") },
description = "Visits by day and hour",
tagKey = "pressed"
)
internal fun weekVisits(): List<SlotVisits> {
val days = listOf("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")
val hours = listOf("09", "11", "13", "15", "17", "19")
val visits = listOf(
listOf(12, 18, 34, 22, 41, 30),
listOf(10, 16, 31, 20, 38, 28),
listOf(14, 21, 36, 25, 44, 33),
listOf(11, 19, 35, 27, 47, 36),
listOf(16, 24, 42, 31, 58, 52),
listOf(28, 45, 61, 66, 72, 57),
listOf(22, 39, 54, 60, 49, 26)
)
return days.flatMapIndexed { day, name -> hours.mapIndexed { slot, at -> SlotVisits(name, at, visits[day][slot]) } }
}
internal class SlotVisits(
val day: String,
val hours: String,
val visits: Int
)
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 cells and the names at their sides; the scale of colours stands under the cells in the file, wherever it stands on the screen.
var file by remember { mutableStateOf<ChartExportFile?>(null) }
PantrixHeatmap(
data = weekVisits(),
x = { it.hours },
y = { it.day },
value = { it.visits },
description = "Visits by day and hour",
tagKey = "exported",
export = ChartExport(fileName = "hours-of-the-week") { file = it }
)
ExportedFile(file, tagKey = "exported")
internal fun weekVisits(): List<SlotVisits> {
val days = listOf("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")
val hours = listOf("09", "11", "13", "15", "17", "19")
val visits = listOf(
listOf(12, 18, 34, 22, 41, 30),
listOf(10, 16, 31, 20, 38, 28),
listOf(14, 21, 36, 25, 44, 33),
listOf(11, 19, 35, 27, 47, 36),
listOf(16, 24, 42, 31, 58, 52),
listOf(28, 45, 61, 66, 72, 57),
listOf(22, 39, 54, 60, 49, 26)
)
return days.flatMapIndexed { day, name -> hours.mapIndexed { slot, at -> SlotVisits(name, at, visits[day][slot]) } }
}
internal class SlotVisits(
val day: String,
val hours: String,
val visits: Int
)
API
data
A heatmap: a grid of cells, each in a shade that says how much its value is. It suits what has two names and a number: the hours of the days of a week, the months of several years, one list set against another. What is much and what is little is seen at a glance, where a table of the same numbers would have to be read.
| Parameter | Type | Default | What it is |
|---|---|---|---|
data | List<T> | the data, a datum for each cell that has a value. | |
x | (T) -> Any | the column a datum stands in: a name, an hour, anything that is equal to itself. | |
y | (T) -> Any | its row, in the same way. | |
value | (T) -> Number? | its value, or null where it has none. | |
modifier | Modifier | Modifier | the chart's modifier. A height here is the chart's, the scale's with it. |
xAxis | ChartAxis? | ChartAxis() | the columns: whether their names are written under the cells (labels) and how a name is written (label). Nothing else of it is read. null writes none. |
yAxis | ChartAxis? | ChartAxis() | the rows: whether their names are written beside the cells, and how. null writes none. |
scale | HeatmapScale | HeatmapScale() | how a value becomes a shade. |
labels | ChartDataLabels? | null | the values written in the cells, each in the one of black and white that reads better on its shade, where the cell has room for it. null writes none. |
legend | ChartLegend? | ChartLegend() | where the scale of shades stands. null shows none. A scale has nothing to take out of the chart: only where it stands is read. |
tooltip | ChartTooltip? | ChartTooltip() | what is shown for the cell that is pointed at: its column as the heading, and under it its row and its value, written as format says. A cell 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 rows. |
onSelect | ((ChartPoint) -> Unit)? | null | called with the cell that is pressed: its x is the datum's place among data, counted from nought, its label its column, its name its row, and its value its value. |
style | ChartStyle? | null | this chart's own geometry: the fields set here, with the theme's for the rest. Two cells are a sliceGap apart, and their corners as round as barRadius says. |
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 Heatmap_<tagKey>, its cells together Heatmap_<tagKey>.Plot, a cell Heatmap_<tagKey>.Cell_<number>, counted from one row after row among the cells that are filled, the scale Heatmap_<tagKey>.Legend and the tooltip Heatmap_<tagKey>.Tooltip. A press on a cell is reported as a Heatmap select event with the datum's number. Nothing of the values is reported. |
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. The scale of shades stands under the cells in the file. |
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
- Keyboard. The chart is one stop for Tab: the arrows go from cell to cell, past the blank cells; Home and End go to the first and the last cell, Escape lets go, Enter and Space press the cell that is pointed at.
- Screen reader. The chart says its
description, then each cell, row after row, with its column, its row and its value. A chart of more than two hundred cells is told by its description alone.