Scatter Chart
A scatter chart: points set out by two values, one along the chart and one up it, to show how the two go together. With a third value for the size of each point it is a bubble chart.
import com.pantrixui.components.chart.scatter.PantrixScatterChart
- Bubbles. The points of a series that is given
sizeare bubbles. A bubble's area is as much as its size is of the largest size in the chart: twice the value, twice the area. How large the smallest and the largest bubble are is in the chart's style (bubbleMinSize,bubbleMaxSize). A chart with bubbles keeps room for the largest at the ends of its axes. - A point's name.
labelsays what a point is called: a country, a product. It is the tooltip's heading and what a screen reader says. - Axes.
xAxisandyAxisare theChartAxisof a chart with places: title, format, ends, grid lines. The titles are what the values are called in the tooltip.sizeAxissays what the size is called (title), how it is written (format) and the value the largest bubble stands for (max); nothing of it is drawn. - Marks.
marksdraws a line at a value or a band between two; here a mark'sxis a value of the axis along the chart. - The point pointed at. The point under the pointer, or near it, or touched by a finger, is pointed at: a ring about it, two lines through it and the tooltip that writes its values. Of points that lie on one another, the smallest is pointed at.
- State and pressing. The point that is pointed at is kept in
ChartState, as a selection whosexis the row's place in its series and whoseseriesis the series' key.onSelectgives the pressed point with the same place. - Direction. In a language read from right to left the values along the chart start from the right, and those up it 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. - Zoom. With
zoomthe reader looks closer at both values at once: a wheel (with Ctrl or Command), two fingers, the plus and minus keys and the buttons look closer; a drag moves what is in view every way. - Automation.
ScatterChart_<tagKey>; the plot is.Plot, the points.Point_<series>_<number>, the legend's entries.Legend_<series>, the tooltip.Tooltip.
Examples
Two values, two groups
Fifty days of a café: the day's temperature and the cold drinks sold that day. Weekdays and weekends are separate series; the points go up with the temperature, and the weekends higher still.
Move the pointer near a point, or touch one: the tooltip writes the day's two values under the titles of the axes. Pressing an entry of the legend takes that series out of the chart.
PantrixScatterChart(
series = listOf(
ScatterSeries("weekdays", "Weekdays", cafeDays(weekend = false), x = { it.heat }, y = { it.sold }),
ScatterSeries("weekend", "Weekend", cafeDays(weekend = true), x = { it.heat }, y = { it.sold })
),
xAxis = ChartAxis(title = "Temperature (°C)"),
yAxis = ChartAxis(title = "Cold drinks"),
description = "Cold drinks sold by temperature",
tagKey = "plain"
)
internal fun cafeDays(weekend: Boolean): List<CafeDay> = List(50) { it }
.filter { (it % 7 >= 5) == weekend }
.map { day ->
val heat = 12.0 + (day * 7 % 19) + (day % 4) * 0.5
val wobble = (day * 37 % 29) - 14
val busy = if (weekend) 45 else 0
CafeDay(
heat = heat,
sold = (heat * 6.5 - 40 + wobble + busy).toInt().coerceAtLeast(4),
hot = (190 - heat * 4.5 + wobble).toInt().coerceAtLeast(4),
food = 85 + wobble + busy / 3
)
}
internal class CafeDay(
val heat: Double,
val sold: Int,
val hot: Int,
val food: Int
)
Bubbles
The products of a shop: price, rating and, as the bubble's size, how many were sold in the month. Each point goes by the product's name (label); sizeAxis says what the size is.
The area grows with the value: the bubble of a product that sold twice as many takes twice the area, not twice the width. Bubbles are drawn half transparent, so that one under another shows through.
val strings = PantrixTheme.strings
PantrixScatterChart(
series = listOf(
ScatterSeries(
key = "products",
name = "Products",
data = shopProducts(),
x = { it.price },
y = { it.rating },
size = { it.sold },
label = { it.name }
)
),
xAxis = ChartAxis(title = "Price", format = { "${strings.decimal(it)} ₺" }),
yAxis = ChartAxis(title = "Rating", min = 3.0, max = 5.0),
sizeAxis = ChartAxis(title = "Sales", format = { "${strings.decimal(it)} sold" }),
description = "Price, rating and sales of the products",
tagKey = "bubbles"
)
internal fun shopProducts(): List<ShopProduct> = listOf(
ShopProduct("Headset", 1450.0, 4.6, 820),
ShopProduct("Keyboard", 980.0, 4.3, 540),
ShopProduct("Mouse", 420.0, 4.4, 1260),
ShopProduct("Monitor", 5200.0, 4.7, 310),
ShopProduct("Webcam", 760.0, 3.9, 280),
ShopProduct("Speaker", 1890.0, 4.1, 190),
ShopProduct("Microphone", 2350.0, 4.5, 150),
ShopProduct("Charger", 310.0, 4.0, 1640),
ShopProduct("Cable", 120.0, 3.6, 2100),
ShopProduct("Tablet pen", 1150.0, 4.2, 95),
ShopProduct("Laptop stand", 640.0, 4.8, 470),
ShopProduct("External disk", 2780.0, 4.4, 360)
)
internal class ShopProduct(
val name: String,
val price: Double,
val rating: Double,
val sold: Int
)
Lines and bands
Marks that help read the points: a line at the average temperature, a line at the day's target, and a band over the hottest days. A mark's x is here not the name of a place but a value of the axis along the chart.
PantrixScatterChart(
series = listOf(
ScatterSeries("days", "Days", cafeDays(weekend = false), x = { it.heat }, y = { it.sold })
),
xAxis = ChartAxis(title = "Temperature (°C)"),
yAxis = ChartAxis(title = "Cold drinks"),
marks = listOf(
ChartReferenceBand(x1 = 28, label = "Hot days"),
ChartReferenceLine(y = 150, label = "Target"),
ChartReferenceLine(x = 21, label = "Average")
),
description = "Cold drinks sold by temperature, with the target",
tagKey = "marks"
)
internal fun cafeDays(weekend: Boolean): List<CafeDay> = List(50) { it }
.filter { (it % 7 >= 5) == weekend }
.map { day ->
val heat = 12.0 + (day * 7 % 19) + (day % 4) * 0.5
val wobble = (day * 37 % 29) - 14
val busy = if (weekend) 45 else 0
CafeDay(
heat = heat,
sold = (heat * 6.5 - 40 + wobble + busy).toInt().coerceAtLeast(4),
hot = (190 - heat * 4.5 + wobble).toInt().coerceAtLeast(4),
food = 85 + wobble + busy / 3
)
}
internal class CafeDay(
val heat: Double,
val sold: Int,
val hot: Int,
val food: Int
)
Telling series apart by shape
Series are told apart by shape as well as by colour: each is drawn with its own mark (marker). It reads for someone who cannot tell the colours apart, and on a black and white print.
val days = cafeDays(weekend = false)
PantrixScatterChart(
series = listOf(
ScatterSeries("cold", "Cold drinks", days, x = { it.heat }, y = { it.sold }),
ScatterSeries("hot", "Hot drinks", days, x = { it.heat }, y = { it.hot }, marker = ChartMarker.Square),
ScatterSeries("food", "Food", days, x = { it.heat }, y = { it.food }, marker = ChartMarker.Triangle)
),
xAxis = ChartAxis(title = "Temperature (°C)"),
yAxis = ChartAxis(title = "Sales"),
description = "Sales of three groups of products by temperature",
tagKey = "markers"
)
internal fun cafeDays(weekend: Boolean): List<CafeDay> = List(50) { it }
.filter { (it % 7 >= 5) == weekend }
.map { day ->
val heat = 12.0 + (day * 7 % 19) + (day % 4) * 0.5
val wobble = (day * 37 % 29) - 14
val busy = if (weekend) 45 else 0
CafeDay(
heat = heat,
sold = (heat * 6.5 - 40 + wobble + busy).toInt().coerceAtLeast(4),
hot = (190 - heat * 4.5 + wobble).toInt().coerceAtLeast(4),
food = 85 + wobble + busy / 3
)
}
internal class CafeDay(
val heat: Double,
val sold: Int,
val hot: Int,
val food: Int
)
Pressing a point
onSelect gives the point that is pressed: its x is the row's place in its series, its label the point's name. Press a bubble, or go to one with the keyboard and press Enter, and the line under the card writes the product that was pressed.
val report = LocalExampleEvents.current
val products = shopProducts()
PantrixScatterChart(
series = listOf(
ScatterSeries(
key = "products",
name = "Products",
data = products,
x = { it.price },
y = { it.rating },
size = { it.sold },
label = { it.name }
)
),
xAxis = ChartAxis(title = "Price"),
yAxis = ChartAxis(title = "Rating", min = 3.0, max = 5.0),
sizeAxis = ChartAxis(title = "Sales"),
onSelect = { point -> report("${point.label}: ${products[point.x as Int].sold} sold") },
description = "Price, rating and sales of the products",
tagKey = "pressed"
)
internal fun shopProducts(): List<ShopProduct> = listOf(
ShopProduct("Headset", 1450.0, 4.6, 820),
ShopProduct("Keyboard", 980.0, 4.3, 540),
ShopProduct("Mouse", 420.0, 4.4, 1260),
ShopProduct("Monitor", 5200.0, 4.7, 310),
ShopProduct("Webcam", 760.0, 3.9, 280),
ShopProduct("Speaker", 1890.0, 4.1, 190),
ShopProduct("Microphone", 2350.0, 4.5, 150),
ShopProduct("Charger", 310.0, 4.0, 1640),
ShopProduct("Cable", 120.0, 3.6, 2100),
ShopProduct("Tablet pen", 1150.0, 4.2, 95),
ShopProduct("Laptop stand", 640.0, 4.8, 470),
ShopProduct("External disk", 2780.0, 4.4, 360)
)
internal class ShopProduct(
val name: String,
val price: Double,
val rating: Double,
val sold: 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 axes, the points and the legend under them; the point that is pointed at and its tooltip are not in it.
var file by remember { mutableStateOf<ChartExportFile?>(null) }
PantrixScatterChart(
series = listOf(
ScatterSeries("weekdays", "Weekdays", cafeDays(weekend = false), x = { it.heat }, y = { it.sold }),
ScatterSeries("weekend", "Weekend", cafeDays(weekend = true), x = { it.heat }, y = { it.sold })
),
xAxis = ChartAxis(title = "Temperature (°C)"),
yAxis = ChartAxis(title = "Cold drinks"),
description = "Cold drinks sold by temperature",
tagKey = "exported",
export = ChartExport(fileName = "cold-drinks") { file = it }
)
ExportedFile(file, tagKey = "exported")
internal fun cafeDays(weekend: Boolean): List<CafeDay> = List(50) { it }
.filter { (it % 7 >= 5) == weekend }
.map { day ->
val heat = 12.0 + (day * 7 % 19) + (day % 4) * 0.5
val wobble = (day * 37 % 29) - 14
val busy = if (weekend) 45 else 0
CafeDay(
heat = heat,
sold = (heat * 6.5 - 40 + wobble + busy).toInt().coerceAtLeast(4),
hot = (190 - heat * 4.5 + wobble).toInt().coerceAtLeast(4),
food = 85 + wobble + busy / 3
)
}
internal class CafeDay(
val heat: Double,
val sold: Int,
val hot: Int,
val food: Int
)
Looking closer
zoom = ChartZoom() lets the reader look closer at both values at once: a wheel with Ctrl or Command and two fingers look closer about the point between them, plus and minus on the chart about the point that is pointed at, and the buttons about the middle. A drag of the mouse moves what is in view every way; the point the arrows go to comes into view.
Both axes are marked anew for the values in view, and what lies past the plot's edges is cut off. The part across is state.window, the part up state.valueWindow.
PantrixScatterChart(
series = listOf(
ScatterSeries("weekdays", "Weekdays", cafeDays(weekend = false), x = { it.heat }, y = { it.sold }),
ScatterSeries("weekend", "Weekend", cafeDays(weekend = true), x = { it.heat }, y = { it.sold })
),
xAxis = ChartAxis(title = "Temperature (°C)"),
yAxis = ChartAxis(title = "Cold drinks"),
description = "Cold drinks sold by temperature",
tagKey = "zoomed",
zoom = ChartZoom()
)
internal fun cafeDays(weekend: Boolean): List<CafeDay> = List(50) { it }
.filter { (it % 7 >= 5) == weekend }
.map { day ->
val heat = 12.0 + (day * 7 % 19) + (day % 4) * 0.5
val wobble = (day * 37 % 29) - 14
val busy = if (weekend) 45 else 0
CafeDay(
heat = heat,
sold = (heat * 6.5 - 40 + wobble + busy).toInt().coerceAtLeast(4),
hot = (190 - heat * 4.5 + wobble).toInt().coerceAtLeast(4),
food = 85 + wobble + busy / 3
)
}
internal class CafeDay(
val heat: Double,
val sold: Int,
val hot: Int,
val food: Int
)
API
series
A scatter chart: points set out by two values, one along the chart and one up it, to show how the two go together. With a third value for the size of each point it is a bubble chart.
| Parameter | Type | Default | What it is |
|---|---|---|---|
series | List<ScatterSeries<*>> | the series, in the order they are listed. 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, the legend's with it. |
xAxis | ChartAxis? | ChartAxis() | the axis of the values along the chart, under it. Its title is what those values are called in the tooltip and to a screen reader. null draws nothing of it. |
yAxis | ChartAxis? | ChartAxis() | the axis of the values up the chart, beside it. null draws nothing of it. |
sizeAxis | ChartAxis? | null | what the bubbles' sizes measure: its title is what a size is called in the tooltip and to a screen reader, its format how one is written, and its max the size the largest bubble stands for, where that is not the largest in the chart. Nothing of it is drawn, and nothing else of it is read. |
marks | List<ChartMark> | emptyList() | the lines and the bands drawn in the plot besides the points. |
legend | ChartLegend? | ChartLegend() | where the names of the series stand. null shows none. |
tooltip | ChartTooltip? | ChartTooltip() | what is shown for the point that is pointed at: its heading is what the point is called, and under it a row for each of its values. Its crosshair is a line across the plot and one up it through the point, and its format writes the value up the chart; a point has no place to share, and shared is not read. A tooltip's own content is given the point's values, the one along the chart first. 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 has hidden and what they point 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 series. |
onSelect | ((ChartPoint) -> Unit)? | null | called with the point that is pressed: its x is its place among its series' rows, counted from nought, its label what it is called, and its value its value up the chart. |
style | ChartStyle? | null | this chart's own geometry: the fields set here, with the theme's for the rest. |
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 ScatterChart_<tagKey>, its plot ScatterChart_<tagKey>.Plot, a point ScatterChart_<tagKey>.Point_<series' key>_<number>, the number being its row's counted from one, the legend's entry of a series ScatterChart_<tagKey>.Legend_<series' key> and the tooltip ScatterChart_<tagKey>.Tooltip. A press on a point is reported as a ScatterChart select event with the series' key, and a press on a legend's entry as a toggle event with the key and whether the series is shown. 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. |
zoom | ChartZoom? | null | what lets the reader look closer at a part of the chart's values, across it and up it alike (see ChartZoom): a wheel with Ctrl or Command and two fingers look closer about the point between them, plus and minus on the chart about the point that is pointed at, and the buttons about the middle; a drag of the mouse moves what is in view every way, the buttons along it, and the arrows bring the point they go to into view. Both axes are marked anew for the values in view, and what lies past the plot's edges is cut off. The parts are kept in state, as its window and its valueWindow. A scatter chart has no navigator: its navigator is not read, nor is its fitValues. null, the chart shows all its values. |
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 right and left arrows go along the chart between the points of a series, the up and down arrows to the series before and after, Home and End to the series' first and last point, Escape lets go, Enter and Space press the point that is pointed at.
- Screen reader. The chart says its
description, then each point, series by series, with its name and its values. A chart of more than two hundred points is told by its description alone.