XY Graphs: 2-D Cartesian Plots

Learn how to create plots with cartesian coordinates

Line, Area, and Vertical Bars are each specific plot types that are graphed in 2-dimensional Cartesian coordinates. In Koala Plot, all of these plot types are composed within the XYGraph() function. This Composable provides several capabilities including:

  • Holding the x-axis and y-axis models that transform coordinates between the plot space and screen space
  • Rendering the axes
  • Rendering background grid lines
  • Rendering axis titles and labels
  • Handling plot panning and zooming

This section will go step-by-step through how different XY Graph components can be customized, including specifying different axis types for linear, logarithmic, and category types.

Anatomy of an XY Graph

The below diagram illustrates the major components of an XYGraph which can be customized.

XY Graph Anatomy

Gridlines

The gridlines in the above diagram used default settings for all elements except for the minor gridlines, which were modified by changing the theme as shown in the below code between lines 2 and 5.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    KoalaPlotTheme(
        axis = KoalaPlotTheme.axis.copy(
            minorGridlineStyle = KoalaPlotTheme.axis.minorGridlineStyle!!.copy(
                pathEffect = PathEffect.dashPathEffect(floatArrayOf(3f, 3f))
            )
        )
    ) {
        XYGraph(
            xAxisModel = FloatLinearAxisModel(0f..10f),
            yAxisModel = FloatLinearAxisModel(0f..20f),
            xAxisTitle = "X Axis Title",
            yAxisTitle = "Y Axis Title",
        ) {
        }
    }

However, it is also possible to change the gridlines on an individual XYGraph, and separately for each gridline type.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
        XYGraph(
            xAxisModel = FloatLinearAxisModel(0f..10f),
            yAxisModel = FloatLinearAxisModel(0f..20f),
            horizontalMajorGridLineStyle = LineStyle(SolidColor(Color.Blue), strokeWidth = 3.dp),
            horizontalMinorGridLineStyle = LineStyle(
                SolidColor(Color.Blue),
                pathEffect = PathEffect.dashPathEffect(floatArrayOf(5f, 5f)),
            ),
            verticalMajorGridLineStyle = LineStyle(SolidColor(Color.DarkGray)),
            verticalMinorGridLineStyle = null,
        ) {
        }
/examples/src/jvmMain/kotlin/io/github/koalaplot/example/XYGraphGridlines.kt

XY Graph Gridlines

The sample above uses the LineStyle class to set the style of each gridline type with different characteristics. By setting the style for a gridline to null, the gridlines will not be drawn, as seen for verticalMinorGridLineStyle.

Axes

Since an XYGraph provides a graphing area for 2-d Cartesian plots, it requires a model, an implementation of the AxisModel interface, for the x-axis (horizontal) and y-axis (vertical) axes. The AxisModel provides an abstraction for converting from the units in the space used for the plots to the space used for the graph. It also defines where major and minor ticks should be placed along the axis, and handles transforming the axis for zooming and panning. KoalaPlot includes three of the most comment types of axes:

ILinearAxisModel

ILinearAxisModel is an interface for representing linear axes that can be of any Number type. There are concrete implementations provided for Int, Float, and Double types: IntLinearAxisModel, FloatLinearAxisModel, and DoubleLinearAxisModel.

The simplest way to get an instance of one of these is to call remember function corresponding to the data type needed, i.e., rememberIntLinearAxisModel, rememberFloatLinearAxisModel, or rememberDoubleLinearAxisModel. This will create axis model and remember it for you.

Setting the Range

The range parameter to LinearAxisModel defines the minimum and maxiumum values that will be displayed by XYGraph for the axis. If data points extend beyond the specified range, they will be clipped and not displayed.

A good starting point for setting the range is the function autoScaleRange and its variants. An example of using autoScaleRange can be seen in the basic example of a line plot.

It is also possible to manually specify the range, for example:

1
2
3
4
5
6
7
8
9
        rememberFloatLinearAxisModel(0f..12f, minorTickCount = 0),
        rememberFloatLinearAxisModel(-10f..110f, minorTickCount = 0),
    ) {
        LinePlot(
            data,
            lineStyle = LineStyle(SolidColor(Color.Blue))
        )
    }
}
/examples/src/jvmMain/kotlin/io/github/koalaplot/example/LinearAxisRange1.kt

LinearAxisModel Range

Since the x-axis data in this example ranges from 1 to 10 and the y-axis data ranges from 1 to 100, both axes are slightly over sized, which is sometimes desired.

The previous example also illustrated how to control the number of minor ticks that are displayed between the major ticks: by specifying the minorTickCount parameter to LinearAxisModel. There is always one minor grid line for each minor tick, but drawing the grid line itself is optional.

CategoryAxisModel

A CategoryAxisModel can use any data type for its values, and treats them as discrete values as opposed to LinearAxisModel which is continuous over its range. The below example uses Strings for the data type, with an XYGraph overload that renders the axis labels using the object’s toString() method.

1
2
3
4
5
6
7
8
    val burroughs = listOf("Bronx", "Brooklyn", "Manhattan", "Queens", "Staten Island")

    XYGraph(
        xAxisModel = CategoryAxisModel(burroughs),
        yAxisModel = rememberFloatLinearAxisModel(0f..4f, minorTickCount = 0),
        yAxisTitle = "Population (Millions)"
    ) {
    }
/examples/src/jvmMain/kotlin/io/github/koalaplot/example/CategoryAxis1.kt

Category Axis

CategoryAxisModel is simpler than LinearAxisModel and doesn’t provide minor ticks, since the axis is discrete over the category values, or allow panning and zooming.

LogAxisModel

LogAxisModel is continuous like LinearAxisModel but transforms its coordinates logarithmically from plot space to screen space. The below example illustrates using a logarithmic y-axis for plotting the function \( y=x^2 \). Note that the range for a LogAxisModel is specified as powers of 10, so this example uses a range from \(10^{-1} = 0.1 \) to \(10^2 = 100\)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    XYGraph(
        xAxisModel = rememberFloatLinearAxisModel(data.autoScaleXRange()),
        yAxisModel = LogAxisModel(-1..2)
    ) {
        LinePlot(
            data,
            lineStyle = LineStyle(SolidColor(Color.Blue))
        )
    }
}

/examples/src/jvmMain/kotlin/io/github/koalaplot/example/LogAxis1.kt

Log Axis

LogAxisModel can be used for either the x-axis and/or y-axis, enabling the creation of graphs where one or both axes use a logarithmic scale.

Axis Styling

Axis Titles & Labels

Axis titles and labels are provided by Composable functions as parameters to XYGraph. That enables a high degree of customizability, since, for example, axis labels do not need to all be the same color, and can even be non-text items like images or icons, as shown in the following example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
fun main() = singleWindowApplication {
    val yAxisTitle = buildAnnotatedString {
        pushStyle(ParagraphStyle(textAlign = TextAlign.Center))
        pushStyle(SpanStyle(fontWeight = FontWeight.Bold, fontSize = MaterialTheme.typography.titleLarge.fontSize))
        append("Votes\n")
        pop()
        pushStyle(SpanStyle(color = Color.Gray, fontSize = MaterialTheme.typography.labelSmall.fontSize))
        append("(Millions)")
    }

    XYGraph(
        xAxisModel = CategoryAxisModel(IconCategories.entries),
        yAxisModel = rememberFloatLinearAxisModel(0f..4f, minorTickCount = 0),
        xAxisLabels = { it.icon() },
        xAxisTitle = {},
        yAxisLabels = { Text(it.toString()) },
        yAxisTitle = {
            Box(modifier = Modifier.fillMaxHeight(), contentAlignment = Alignment.BottomCenter) {
                Text(
                    yAxisTitle,
                    modifier = Modifier.rotateVertically(VerticalRotation.COUNTER_CLOCKWISE)
                        .padding(bottom = KoalaPlotTheme.sizes.gap)
                )
            }
        },
        modifier = Modifier.padding(16.dp)
    ) {
    }
}

enum class IconCategories(private val icon: ImageVector, private val color: Color) {
    MONEY(Icons.Default.Money, Color(0, 150, 0)),
    HOME(Icons.Default.Home, Color.Black),
    DELETE(Icons.Default.Delete, Color.Red),
    SETTINGS(Icons.Default.Settings, Color.Gray),
    CHECK(Icons.Default.CheckCircle, Color.Blue)
    ;

    @Composable
    fun icon() {
        Icon(icon, contentDescription = null, tint = color)
    }
}
/examples/src/jvmMain/kotlin/io/github/koalaplot/example/CategoryAxis2.kt

Icon Categories

In addition to using icons with varying colors for the x-axis labels, the above example also uses a custom AnnotatedString for the y-axis title and demonstrates aligning it along the axis.

When providing a Composable for an axis title, the available space will be the graph height for y-axis titles and the graph width for x-axis titles. Typical layout methods are used to position the title content within this space. In this example, Box was used to align the content to the bottom of the y-axis.

A common requirement is to rotate the x-axis labels to accommodate long, closely spaced, labels. Axis label rotation can be specified through the AxisStyle, as shown in the below example:

1
2
3
4
5
6
7
    XYGraph(
        xAxisModel = CategoryAxisModel(burroughs),
        yAxisModel = rememberFloatLinearAxisModel(0f..4f, minorTickCount = 0),
        yAxisTitle = "Population (Millions)",
        xAxisStyle = rememberAxisStyle(labelRotation = 45)
    ) {
    }
/examples/src/jvmMain/kotlin/io/github/koalaplot/example/CategoryAxis3.kt

Rotated labels

Axis Ticks & Lines

The sizes, and position of the ticks, as well as the color of each axis, can be individually configured by providing an AxisStyle to the XYGraph Composable, or through KoalaPlotTheme. In this example we change the color, and set the length of the major and minor ticks to the same value:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    KoalaPlotTheme(
        axis = KoalaPlotTheme.axis.copy(
            minorGridlineStyle = KoalaPlotTheme.axis.minorGridlineStyle!!.copy(
                pathEffect = PathEffect.dashPathEffect(floatArrayOf(3f, 3f))
            )
        )
    ) {
        XYGraph(
            xAxisModel = FloatLinearAxisModel(0f..10f),
            yAxisModel = FloatLinearAxisModel(0f..20f),
            xAxisTitle = "X Axis Title",
            yAxisTitle = "Y Axis Title",
            xAxisStyle = rememberAxisStyle(
                Color.Blue,
                majorTickSize = 5.dp,
                minorTickSize = 5.dp,
            ),
            yAxisStyle = rememberAxisStyle(
                Color.Blue,
                majorTickSize = 5.dp,
                minorTickSize = 5.dp,
            )
        ) {
        }
    }
/examples/src/jvmMain/kotlin/io/github/koalaplot/example/XYGraph2.kt

Specifying axis ticks and color

Annotations

Annotations are a way to place additional content within the graph area and in the axis coordinate spaces. Koala Plot provides three types of annotations:

The below example illustrates using all of these annotations.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    XYGraph(
        xAxisModel = FloatLinearAxisModel(0f..10f),
        yAxisModel = FloatLinearAxisModel(0f..20f),
    ) {
        HorizontalLineAnnotation(10.4f, LineStyle(SolidColor(Color.Blue)))
        VerticalLineAnnotation(3.2f, LineStyle(SolidColor(Color.Red)))
        XYAnnotation(Point(3.2f, 10.4f), AnchorPoint.BottomLeft) {
            Text("Text annotation")
        }
    }
/examples/src/jvmMain/kotlin/io/github/koalaplot/example/Annotations1.kt

Annotations

The XYAnnotation, in addition to a coordinate, also requires an anchor point. The anchor point defines the positioning of the composable relative to the specified coordinate. In the above example, AnchorPoint.BottomLeft was used, which causes the bottom left corner of the Composable to be placed at the specified coordinate, which in this example is the intersection of the red and blue lines.


Line & Scatter Plots

Plotting lines and points in 2-d

Area Plots

Plotting lines with shaded areas

Bar Plots

Plotting vertical bars


Last modified April 23, 2024: Changes for 0.6.0 (19018a1)