A plotting library built natively in and for the Jetpack Compose environment. Engineered from the ground up for modern Android applications, it aims to render dense datasets effortlessly.
π Current Capability: The library currently supports Scatter Chart plotting on a linear scale with multi-series capabilities.
Open your settings.gradle.kts file and add the JitPack URL at the end of the repositories block:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}Add the dependency (build.gradle.kts):
dependencies {
implementation("com.github.morustree:Android_Chart_Composer:1.0.2")
}- Native Compose Integration: Designed with Composable functions, eliminating XML overhead and supporting smart recomposition out-of-the-box.
- Multi-Series Support: Plot multiple independent datasets concurrently, each with its own color mapping, marker styles, and connectivity specs.
- Rich Layout Freedom: Complete control over titles, labels, grids, and boundaries through customizable structural classes.
- Interactive Floating Legend: Includes a legend card that users can freely drag or tap away in real-time.
- Active Development: focused on usability refinements and performance auditing.
To serve both lightweight visual trackers and scientific platforms, the library supports multiple data paradigms via method overloading:
List<Offset>&List<Pair<Float, Float>>.UnivariateFunction(Apache Commons Math): Plot complex polynomials, splines, and regressions without manual data conversion. If you want to use theUnivariateFunctiondata input, you must add the Apache dependency to your own application.(Double) -> Double(Kotlin/KMath): Directly plots functional lambdas, offering extensive compatibility with the modern Kotlin Mathematics ecosystem.
This example showcases how the library behaves under high-frequency layout changes, drawing two interwoven mathematical waves moving horizontally across the screen.
animation.mp4
// Inside your Composable structure
var animationStep by remember { mutableStateOf(0f) }
LaunchedEffect(Unit) {
while (true) {
animationStep += 0.05f // Horizontal shift speed
delay(16) // Matches ~60Hz refresh cycles
}
}
val movingSeries = remember(animationStep) {
listOf(
ChartSeries(
label = "Alpha Phase",
showLines = true,
pointSpecs = PointSpecs(color = Color.Cyan, size = 5.dp),
lineSpecs = LineSpecs(color = Color.Cyan.copy(alpha = 0.8f), thickness = 3.dp),
data = List(150) { i -> Offset(i.toFloat(), (Math.sin((i * 0.15) - animationStep) * 80).toFloat()) }
),
ChartSeries(
label = "Beta Phase",
showLines = true,
pointSpecs = PointSpecs(color = Color.Magenta, size = 5.dp),
lineSpecs = LineSpecs(color = Color.Magenta.copy(alpha = 0.8f), thickness = 3.dp),
data = List(150) { i -> Offset(i.toFloat(), (-Math.cos((i * 0.15) - animationStep) * 80).toFloat()) }
)
)
}
ScatterChart(
series = movingSeries,
modifier = Modifier.fillMaxWidth().height(350.dp)
)Perfect for static data, report documents, or engineering dashboards.
// Plots a static Sine function using Kotlin's native math lambdas
ScatterChart(
mathematicalFunction = { x -> Math.sin(x) },
minX = 0.0,
maxX = 2.0 * Math.PI, // Plots a complete sine period (0 to 2Ο)
steps = 100, // Automatically computes 100 precise plotting points
label = "Sine Wave f(x)",
showLines = true,
modifier = Modifier.fillMaxWidth().height(300.dp)
)
val sineFunction = mutableListOf<Offset>()
val phaseShift = mutableListOf<Offset>()
for (degrees in 0..360 step 10) {
val x = degrees.toFloat()
val y1 = sin(toRadians(degrees.toDouble())).toFloat()
sineFunction.add(Offset(x, y1))
val y2 = sin(toRadians(degrees.toDouble() - 45.0)).toFloat()
phaseShift.add(Offset(x, y2))
}
val chartSeries: List<ChartSeries> = listOf(
ChartSeries(
label = "Alpha Phase",
pointSpecs = PointSpecs(color = Color.Blue, shape = MarkerShape.Square),
data = sineFunction
),
ChartSeries(
label = "Beta Phase",
pointSpecs = PointSpecs(color = Color.Red, shape = MarkerShape.Circle),
data = phaseShift
)
)
ScatterChart(
series = chartSeries,
modifier = Modifier.fillMaxSize(),
specs = ChartSpecs(
title = TitleSpecs(text = "Waves"),
axis = AxisSpecs(
yAxisTitle = AxisTitleSpecs(
text = "y-axis",
orientation = AxisTitleOrientation.VerticalParallel
),
xAxisTitle = AxisTitleSpecs(
text = "x-axis"
)
),
grid = GridSpecs(color = Color.LightGray.copy(alpha = 0.4f)),
)
)A real-time stress workload was simulated with a chart rendering 10,000 active data points while performing touch gestures.
- Device Used: Samsung Galaxy M14 5G (SM-M146B)
- Android Version: Android 15 / One UI 7.0
- Processor Specs: Exynos 1330
- Testing Method: Jetpack Macrobenchmark
- Building Variant: Release (R8 Minification & Optimization enabled)
timeToInitialDisplayMs min 3,582.4, median 3,623.8, max 3,810.1
- Startup Phase (Cold Start): According to Googleβs Android Vitals, a "Slow Start" is defined as > 5,000ms. This library processed and rendered 10,000 points in 3,623.8ms, which is well within the acceptable threshold for high-density data visualization on a mid-range device.
- Rendering Phase (Interaction Smoothness): The 16ms/8ms Budget: To maintain a 60Hz or 120Hz refresh rate, the UI must render frames in under 16.6ms or 8.3ms, respectively. Result: By using a managed drawing state, the library achieves Zero-Allocation during the render loop. Tooltip activations and legend movements do not trigger object allocations or garbage collection spikes, ensuring an optimized pipeline designed to stay safely within the 16.6ms rendering window.
π Check the Detailed Stress Test Report & Architecture Audit.
The upcoming releases are focused on:
- LineChart Engine Expansion: Reusing the robust axis, label, and grid sub-systems to introduce a fully featured native
LineChartmodule. - Baseline Profiles: Shaving down the initial cold start initialization peak.
This library is available as open source under the terms of the MIT License.