Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# These are supported funding model platforms

github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
github: sproctor
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
liberapay: sproctor
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: [https://plisio.net/donate/0gHfO7oL]
custom: # [https://plisio.net/donate/0gHfO7oL]
35 changes: 10 additions & 25 deletions .github/workflows/publish-library.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,42 +7,27 @@ jobs:
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Setup Java
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: 'zulu'
distribution: 'temurin'
java-version: '17'

- name: Install GPG
env:
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
run: |
brew install gpg
echo "$SIGNING_KEY" | gpg --dearmor > ${HOME}/secring.gpg

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3

- name: Add Gradle Properties
env:
MAVEN_CENTRAL_USER_NAME: ${{ secrets.MAVEN_CENTRAL_USER_NAME }}
MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }}
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
run: |
echo "mavenCentralUsername=${MAVEN_CENTRAL_USER_NAME}" >> gradle.properties
echo "mavenCentralPassword=${MAVEN_CENTRAL_PASSWORD}" >> gradle.properties
echo "signing.keyId=${SIGNING_KEY_ID}" >> gradle.properties
echo "signing.password=${SIGNING_KEY_PASSWORD}" >> gradle.properties
echo "signing.secretKeyRingFile=${HOME}/secring.gpg" >> gradle.properties
uses: gradle/actions/setup-gradle@v6

- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable

- name: Publish To Maven Central
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }}
run: |
./gradlew publishAndReleaseToMavenCentral --no-configuration-cache
./gradlew publishAndReleaseToMavenCentral --no-configuration-cache
54 changes: 54 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Overview

Compose Charts is a Kotlin Multiplatform charting library for Compose Multiplatform, published to Maven Central as `io.github.ehsannarmani:compose-charts`. It targets Android, Desktop (JVM), iOS, JS (browser), and Wasm/JS. The library exposes four chart composables — `LineChart`, `ColumnChart`, `RowChart`, `PieChart` — all drawn manually on a Compose `Canvas`/`DrawScope`.

Note: the Kotlin package/Android namespace is `ir.ehsannarmani.compose_charts`, but the published Maven coordinates use the `io.github.ehsannarmani` group.

## Modules

- `compose-charts/` — the published library. Only `commonMain` contains chart logic; the platform source sets (`androidMain`, `desktopMain`, `iosMain`/`nativeMain`, `jsMain`, `wasmJsMain`) exist almost entirely to provide the `expect`/`actual` for `extensions/PointerIntropFilter.kt` (pointer interop differs per platform).
- `app/` — a Compose Multiplatform sample/demo app that consumes `:compose-charts`. Entry points: `desktopMain/kotlin/main.kt`, `androidMain/.../MainActivity.kt`, `wasmJsMain`/`jsMain` `main`, `iosMain/MainViewController.kt`. Shared UI starts at `app/src/commonMain/.../ui/App.kt` with per-chart samples (`LineSample`, `ColumnSample`, `RowSample`, `PieSample`).

## Build & run

Build everything: `./gradlew build`
Build only the library: `./gradlew :compose-charts:build`

Run the demo app:
- Desktop: `./gradlew :app:run`
- Android: `./gradlew :app:installDebug` (or run from Android Studio)
- Wasm/JS in browser (dev server): `./gradlew :app:wasmJsBrowserDevelopmentRun`
- JS in browser: `./gradlew :app:jsBrowserDevelopmentRun`

Publishing (maintainer): `./gradlew :compose-charts:publishToMavenCentral`. Bump the `version` in `compose-charts/build.gradle.kts` (the `mavenPublishing { coordinates(...) }` block) before publishing.

There is no test suite in this repo, and no lint configuration beyond Kotlin's `official` code style (`kotlin.code.style=official` in `gradle.properties`).

## Library architecture

Each chart is a single large `@Composable` in `compose-charts/src/commonMain/.../<Chart>.kt`. The shared pattern across all four:

1. **Inputs are data + "*Properties*" config objects.** Charts take a `data` list (`List<Line>`, `List<Bars>`, `List<Pie>`) plus many optional config objects from the `models/` package (`GridProperties`, `LabelProperties`, `IndicatorProperties`, `PopupProperties`, `DotProperties`, `DividerProperties`, `LabelHelperProperties`, `BarProperties`, `LineProperties`, etc.). These are the public API — changing their constructors is a source-breaking change for consumers.
2. **Animation.** Each datum carries its own Compose `Animatable` for height/value, and visibility is driven via `LaunchedEffect`. `AnimationMode` (`models/AnimationMode.kt`) selects `Together` vs `OneByOne` (sequential, staggered) reveal. Row/Column animation helpers live in `utils/RCAnimation.kt`.
3. **Manual layout + draw.** Axis labels, indicators, and the chart body are laid out with `Column`/`Row`, and the plot itself is a `Canvas` whose `DrawScope` lambda draws bars/lines/slices, grid lines, dividers, dots, and the zero line. Value→pixel mapping uses helpers in `utils/` (`calculateOffset`, `rememberComputedChartMaxValue`, `Height.kt`, `IndicatorValues.kt`).
4. **Popups & selection.** Touch handling uses `pointerInput` + the platform `pointerInteropFilter` (`extensions/PointerIntropFilter.kt`, `expect`/`actual` per platform). Hit-testing produces `SelectedBar`/`BarPopupData` and popups are rendered via `extensions/line_chart/PopupHelper.kt` and `components/LabelHelper.kt`.

### Where things live
- `models/` — all public config/data classes (`Line`, `Bars`, `Pie`, and every `*Properties`). Start here when adding or changing a chart option.
- `extensions/` — `DrawScope`/geometry/number helpers (`Size.kt`, `Degree.kt`, `Circle.kt`, `CornerRadius.kt`, `Format.kt`, `GridLines.kt`). `extensions/line_chart/` holds line-specific path building (`getLinePath`, `drawLineGradient`, `PathData`).
- `utils/` — value computation: max-value, offsets, labels, animation, and `DataCheck.kt` (`require`-based validation of caller-supplied min/max vs actual data; "RC" = Row & Column).
- `components/` — shared composables like `LabelHelper` (the legend).

## Conventions

- New chart options should be added as a field on the relevant `*Properties` model (usually with a default) rather than as a new chart-function parameter, to keep call sites stable.
- Keep chart logic in `commonMain`; only touch platform source sets when something genuinely cannot be expressed commonly (the pointer interop `actual`s are the existing example).
- The `app/` samples double as the manual test harness — when changing a chart, update or use the corresponding `*Sample.kt` to verify visually via `./gradlew :app:run`.

## Documentation

User-facing docs are MkDocs-style markdown under `document/docs/` (per-chart guides under `charts/`, per-option guides under `chart-properties/`). The published site is https://ehsannarmani.github.io/ComposeCharts/. Update these when changing public API.
3 changes: 1 addition & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.jetbrainsCompose)
alias(libs.plugins.compose.compiler)

}

kotlin {
Expand Down Expand Up @@ -80,7 +79,7 @@ android {
compileSdk = 36

defaultConfig {
applicationId = "ir.ehsannarmani.compose_charts"
applicationId = "ir.ehsannarmani.compose_charts.app"
minSdk = 23
targetSdk = 36
versionCode = 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,15 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import ir.ehsannarmani.compose_charts.ui.App
import ir.ehsannarmani.compose_charts.ui.theme.ComposeChartsTheme


class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)


enableEdgeToEdge()
setContent {
ComposeChartsTheme(false) {
App()
}
App()
}
}
}
Expand Down

This file was deleted.

10 changes: 10 additions & 0 deletions app/src/commonMain/composeResources/drawable/dark_mode.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="#FFFFFF"
android:pathData="M480,840Q330,840 225,735Q120,630 120,480Q120,330 225,225Q330,120 480,120Q494,120 507.5,121Q521,122 534,124Q493,153 468.5,199.5Q444,246 444,300Q444,390 507,453Q570,516 660,516Q715,516 761,491.5Q807,467 836,426Q838,439 839,452.5Q840,466 840,480Q840,630 735,735Q630,840 480,840ZM480,760Q568,760 638,711.5Q708,663 740,585Q720,590 700,593Q680,596 660,596Q537,596 450.5,509.5Q364,423 364,300Q364,280 367,260Q370,240 375,220Q297,252 248.5,322Q200,392 200,480Q200,596 282,678Q364,760 480,760ZM470,490Q470,490 470,490Q470,490 470,490Q470,490 470,490Q470,490 470,490Q470,490 470,490Q470,490 470,490Q470,490 470,490Q470,490 470,490Q470,490 470,490Q470,490 470,490Q470,490 470,490Q470,490 470,490Z"/>
</vector>
10 changes: 10 additions & 0 deletions app/src/commonMain/composeResources/drawable/light_mode.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="#FFFFFF"
android:pathData="M565,565Q600,530 600,480Q600,430 565,395Q530,360 480,360Q430,360 395,395Q360,430 360,480Q360,530 395,565Q430,600 480,600Q530,600 565,565ZM338.5,621.5Q280,563 280,480Q280,397 338.5,338.5Q397,280 480,280Q563,280 621.5,338.5Q680,397 680,480Q680,563 621.5,621.5Q563,680 480,680Q397,680 338.5,621.5ZM200,520L40,520L40,440L200,440L200,520ZM920,520L760,520L760,440L920,440L920,520ZM440,200L440,40L520,40L520,200L440,200ZM440,920L440,760L520,760L520,920L440,920ZM256,310L155,213L212,154L308,254L256,310ZM748,806L651,705L704,650L805,747L748,806ZM650,256L747,155L806,212L706,308L650,256ZM154,748L255,651L310,704L213,805L154,748ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
</vector>
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ package ir.ehsannarmani.compose_charts.ui
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.drawscope.DrawStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
Expand All @@ -19,14 +19,14 @@ import ir.ehsannarmani.compose_charts.models.AnimationMode
import ir.ehsannarmani.compose_charts.models.DotProperties
import ir.ehsannarmani.compose_charts.models.Line
import ir.ehsannarmani.compose_charts.models.PopupProperties
import ir.ehsannarmani.compose_charts.models.StrokeStyle

@Composable
fun PhoneSample() {
LazyColumn(
modifier = Modifier
.statusBarsPadding(), verticalArrangement = Arrangement.spacedBy(28.dp),
contentPadding = PaddingValues(22.dp)
fun TabletSample() {
LazyVerticalGrid(
columns = GridCells.Adaptive(250.dp),
verticalArrangement = Arrangement.spacedBy(28.dp),
horizontalArrangement = Arrangement.spacedBy(28.dp),
contentPadding = PaddingValues(28.dp)
) {
item {
ChartParent(Modifier) {
Expand All @@ -52,23 +52,26 @@ fun PhoneSample() {
},
dotsProperties = DotProperties(
enabled = true,
color = SolidColor(Color.White)
color = SolidColor(MaterialTheme.colorScheme.onSurface)
),
modifier = Modifier.padding(22.dp),
animationMode = AnimationMode.None,
minValue = 0.0,
maxValue = 7.0,
popupProperties = PopupProperties(
textStyle = TextStyle.Default.copy(
color = Color.White,
fontSize = 12.sp
labelProperties = labelProperties,
labelHelperProperties = labelHelperProperties,
indicatorProperties = indicatorProperties,
popupProperties =
PopupProperties(
textStyle = TextStyle.Default.copy(
color = Color.White,
fontSize = 12.sp
),
confirmDraw = {
false
}
),
confirmDraw = {
false
}
),

)
)
}
}
// Pie
Expand All @@ -93,7 +96,7 @@ fun PhoneSample() {
ColumnSample3()
}

// Row
//Row
item {
RowSample()
}
Expand Down Expand Up @@ -132,5 +135,11 @@ fun PhoneSample() {
item {
LineSample9()
}

}
}
}





Loading